chore: atualização geral
This commit is contained in:
+91
-16
@@ -122,6 +122,33 @@ REFERENCE_BLOCK_LINE_GAP = 8.0
|
||||
# emphasis line's edge; the reference leaves a little air.
|
||||
REFERENCE_STAGGER_RATIO = 0.8
|
||||
|
||||
# Extra gap, as a fraction of the emphasis line's font size, added only to
|
||||
# the boundary right below it. The display italic's slant leans its stems
|
||||
# past the vertical ink box the metrics measure, so a body line directly
|
||||
# under the emphasis line reads tighter than the same nominal gap anywhere
|
||||
# else in the stack — this cushion (~14pt at the 230pt reference size)
|
||||
# closes that optical gap without touching the user's `line_gap` elsewhere.
|
||||
_EMPHASIS_ITALIC_CUSHION_RATIO = 0.06
|
||||
|
||||
# The numbers above were read off a hand export that used the "Essencial -
|
||||
# Título" template. That template never rendered when we generated it (see
|
||||
# Engine/docs/05_EXPERIENCIAS.md, 2026-08-17), so the writer switched to FCP's
|
||||
# own "Basic Text > Text" (Text.moti) — whose coordinate space is the FRAME
|
||||
# ITSELF (2160x3840), not the half-scale point canvas the numbers above were
|
||||
# measured in. Everything the template reads is in that space: fontSize,
|
||||
# kerning AND Position alike.
|
||||
#
|
||||
# Getting this half-right is worse than getting it wrong. Scaling only the type
|
||||
# left the block at the old spread with twice the type in it, so the lines
|
||||
# collided; scaling only the positions would spread a block of half-size type
|
||||
# across the frame. The layout keeps measuring in canvas points — every
|
||||
# constant above depends on that — and this single factor converts the whole
|
||||
# result on the way out, which is the only way the two stay in step.
|
||||
#
|
||||
# Exposed as `text_scale` on DynamicSubtitleConfig for a template authored
|
||||
# against a different space.
|
||||
TEXT_TEMPLATE_FONT_SCALE = 2.0
|
||||
|
||||
|
||||
def metrics_for(font: Optional[str], face: Optional[str] = None) -> Optional[Dict]:
|
||||
"""The embedded advance table for *font*/*face*, or None if uncovered.
|
||||
@@ -296,9 +323,14 @@ class PlacedWord:
|
||||
and other.bottom < self.top
|
||||
)
|
||||
|
||||
def position_param(self) -> str:
|
||||
"""The value for the title's "Posição" param, as FCP writes it."""
|
||||
return f"{self.x:g} {self.y:g}"
|
||||
def position_param(self, scale: float = 1.0) -> str:
|
||||
"""The value for the title's "Posição" param, as FCP writes it.
|
||||
|
||||
*scale* converts from canvas points to the template's own space; see
|
||||
TEXT_TEMPLATE_FONT_SCALE. It must be the same factor the emitted
|
||||
fontSize uses, or the type and the spacing drift apart.
|
||||
"""
|
||||
return f"{self.x * scale:g} {self.y * scale:g}"
|
||||
|
||||
# The rest of this block is the interface a placed unit shares with
|
||||
# PlacedBlock, so the writer emits titles from either without caring
|
||||
@@ -649,9 +681,14 @@ class PlacedBlock:
|
||||
"""When this block finishes being spoken, in seconds."""
|
||||
return max(float(w.get('end', 0.0)) for w in self.words)
|
||||
|
||||
def position_param(self) -> str:
|
||||
"""The value for the title's "Posição" param, as FCP writes it."""
|
||||
return f"{self.x:g} {self.y:g}"
|
||||
def position_param(self, scale: float = 1.0) -> str:
|
||||
"""The value for the title's "Posição" param, as FCP writes it.
|
||||
|
||||
*scale* converts from canvas points to the template's own space; see
|
||||
TEXT_TEMPLATE_FONT_SCALE. It must be the same factor the emitted
|
||||
fontSize uses, or the type and the spacing drift apart.
|
||||
"""
|
||||
return f"{self.x * scale:g} {self.y * scale:g}"
|
||||
|
||||
def overlaps(self, other: 'PlacedBlock') -> bool:
|
||||
return (
|
||||
@@ -742,10 +779,33 @@ def compose_sentence(
|
||||
for run in body_lines(entries[emphasis_index + 1:]):
|
||||
lines.append((run, body_look, False))
|
||||
|
||||
# A body run wraps onto a new line when it doesn't fit — but the
|
||||
# emphasis line is always exactly one word, so it can't wrap, and
|
||||
# nothing capped its size against the box. A long or all-caps word (an
|
||||
# emphasis pass sometimes upper-cases its pick) could run past both
|
||||
# edges of the frame — found on real footage, wide enough to spill off
|
||||
# BOTH sides while centred. Shrinking it back to the box scales its
|
||||
# font_size and kerning by the same factor, so the ink height used for
|
||||
# stacking below shrinks with it too — restoring the vertical
|
||||
# non-overlap the rest of this function already guarantees by
|
||||
# construction. Never shrunk below the body size: emphasis smaller
|
||||
# than body text isn't emphasis anymore, it's just a different font.
|
||||
def fit_emphasis(text: str, look) -> tuple:
|
||||
size, kerning, width = measure(text, look)
|
||||
if width <= box.width:
|
||||
return size, kerning, width
|
||||
floor = float(body_look.font_size) * scale
|
||||
fit = max(box.width / width, floor / size) if size > 0 else 1.0
|
||||
fit = min(fit, 1.0)
|
||||
return size * fit, kerning * fit, width * fit
|
||||
|
||||
measured = []
|
||||
for run, look, is_emphasis in lines:
|
||||
text = ' '.join(t for _, t in run)
|
||||
size, kerning, width = measure(text, look)
|
||||
if is_emphasis:
|
||||
size, kerning, width = fit_emphasis(text, look)
|
||||
else:
|
||||
size, kerning, width = measure(text, look)
|
||||
# Stack on the real ink each line contains, not on a nominal
|
||||
# cap-height: the display italic's accents and descenders run well
|
||||
# past it, and a nominal box lets them collide with the neighbour.
|
||||
@@ -764,14 +824,29 @@ def compose_sentence(
|
||||
# loses the whole point of the look — so if it does not fit, everything
|
||||
# from the emphasis on overflows together.
|
||||
gap = line_gap * scale
|
||||
|
||||
# The emphasis line's italic slant carries visual weight below its own
|
||||
# ink box — Playfair's stems lean past what the vertical metrics measure
|
||||
# — so a body line sitting right under it reads tighter than the same
|
||||
# nominal gap elsewhere, even though the ink boxes themselves never
|
||||
# touch. Add a size-proportional cushion only to the boundary right
|
||||
# after the emphasis line; every other pair keeps exactly the caller's
|
||||
# ``line_gap``.
|
||||
def pair_gap(prev_line: dict) -> float:
|
||||
if prev_line['emphasis']:
|
||||
return gap + _EMPHASIS_ITALIC_CUSHION_RATIO * prev_line['font_size']
|
||||
return gap
|
||||
|
||||
kept = 0
|
||||
total = 0.0
|
||||
prev = None
|
||||
for line in measured:
|
||||
advance = line['height'] if not kept else line['height'] + gap
|
||||
if kept and total + advance > box.height:
|
||||
advance = line['height'] if prev is None else line['height'] + pair_gap(prev)
|
||||
if prev is not None and total + advance > box.height:
|
||||
break
|
||||
total += advance
|
||||
kept += 1
|
||||
prev = line
|
||||
kept = max(kept, 1)
|
||||
if not any(line['emphasis'] for line in measured[:kept]):
|
||||
kept = min(kept, next(
|
||||
@@ -783,12 +858,12 @@ def compose_sentence(
|
||||
result.overflow.extend(w for w, _ in line['run'])
|
||||
|
||||
visible = measured[:kept]
|
||||
# Stack the ink boxes edge to edge with exactly *gap* between them, then
|
||||
# centre the whole stack on the band. Because the boxes are the real ink,
|
||||
# "no overlap" is a property of the arithmetic, not of a safety factor.
|
||||
stack_height = (
|
||||
sum(line['height'] for line in visible) + gap * (len(visible) - 1)
|
||||
)
|
||||
# Stack the ink boxes edge to edge with exactly *gap* between them (plus
|
||||
# the emphasis cushion where it applies), then centre the whole stack on
|
||||
# the band. Because the boxes are the real ink, "no overlap" is a
|
||||
# property of the arithmetic, not of a safety factor.
|
||||
gaps = [pair_gap(visible[i - 1]) for i in range(1, len(visible))]
|
||||
stack_height = sum(line['height'] for line in visible) + sum(gaps)
|
||||
edge = box.center_y + stack_height / 2
|
||||
|
||||
# Body lines hang off the emphasis line's edges, alternating sides in
|
||||
@@ -797,7 +872,7 @@ def compose_sentence(
|
||||
side = -1
|
||||
for index, line in enumerate(visible):
|
||||
if index:
|
||||
edge -= gap
|
||||
edge -= gaps[index - 1]
|
||||
cursor_y = edge - line['ink_top']
|
||||
edge = cursor_y + line['ink_bottom']
|
||||
if line['emphasis']:
|
||||
|
||||
Reference in New Issue
Block a user