chore: atualização geral

This commit is contained in:
João Henrique
2026-08-19 16:35:29 -04:00
parent 8fca456ceb
commit e7748c2c58
66 changed files with 13037 additions and 4237 deletions
+91 -16
View File
@@ -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']: