tituli.compose¶
The composed pieces: title cards, captions with attribution, lower thirds.
Each function turns a few strings into a Layout placed
on a Frame, applying the ink-and-scrim decision the frame’s
knowledge allows. The pipeline order is fixed and matters: **shape/wrap →
measure → decide ink → cut the scrim to the measured block → compose**. A scrim
sized before the text is measured is how a three-line caption ends up hanging
off its own backdrop.
>>> from tituli.frame import Frame
>>> lay = title_card("The Apple", "a concrete poem", frame=Frame.blank((1920, 1080)))
>>> "".join(r.text for r in lay.runs) # tracked styles set one run per glyph
'The Applea concrete poem'
>>> lay.meta["anchor"], lay.meta["ink"]
('center', 'background unknown: white on dark scrim')
Functions
|
Choose ink and whether a scrim is needed under |
|
Wrap and hard-truncate with an ellipsis at |
|
An opening card: optional kicker, title, optional subtitle. |
|
A museum label over a picture: what is on screen, plus a tiny credit line. |
|
Who is speaking: a name and a role, left-anchored, scrimmed if needed. |
|
An editorial context card: an optional headline over equal-weight lines. |
|
A silent-film style card: serif italic prose, centred on the frame. |
Classes
|
What the frame told us to do about legibility. |
- class tituli.compose.InkDecision(ink, scrim, luminance, reason)[source]¶
Bases:
objectWhat the frame told us to do about legibility.
- tituli.compose.caption(text, attribution='', *, frame, anchor='auto', style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.04, weight=600, italic=False, condensed=False, color=(255, 255, 255), tracking=0.0, leading=1.25, case='as-is', align='left', opacity=1.0, features=()), attribution_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.02, weight=400, italic=False, condensed=False, color=(255, 255, 255), tracking=0.03, leading=1.2, case='as-is', align='left', opacity=0.85, features=()), max_lines=3, max_width=0.55, ink=None, scrim=None, accent=True, on_overflow='fit')[source]¶
A museum label over a picture: what is on screen, plus a tiny credit line.
textis wrapped tomax_widthof the safe box and shrunk to fit withinmax_linesrather than cut;attributionis set small and slightly transparent — present enough to credit, small enough not to compete. If the words cannot be set legibly this raisesTextDoesNotFit; passon_overflow="truncate"for text whose length you do not control (seeOVERFLOW_POLICIES). Placement avoids the frame’savoidboxes and its delivery target’s reserved zones; the scrim is a 2-D corner falloff cut to the measured block.- Return type:
- tituli.compose.decide_ink(frame, box, *, preferred=None)[source]¶
Choose ink and whether a scrim is needed under
box.Rung (a) (nothing known): white on a dark scrim, the universal default. Rung (b)/(c): the ink with more contrast; a scrim only when contrast is still short of WCAG 4.5:1 or the patch is busy.
preferredis honoured when it already has enough contrast.- Return type:
- tituli.compose.intertitle(text, *, frame, style=None)[source]¶
A silent-film style card: serif italic prose, centred on the frame.
- Return type:
- tituli.compose.lower_third(name, role='', *, frame, anchor='bottom-left', name_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.042, weight=700, italic=False, condensed=False, color=(255, 255, 255), tracking=0.0, leading=1.2, case='as-is', align='left', opacity=1.0, features=()), role_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.026, weight=400, italic=False, condensed=False, color=(255, 255, 255), tracking=0.04, leading=1.2, case='as-is', align='left', opacity=0.9, features=()), ink=None, scrim=None, on_overflow='fit')[source]¶
Who is speaking: a name and a role, left-anchored, scrimmed if needed.
Respects the frame’s delivery zones — with
delivery="youtube"the block moves above the subtitle band rather than into it.The name and role are shrunk to fit rather than cut, and
TextDoesNotFitis raised if they cannot be set legibly. A lower third reading1981 · Centr…once shipped in a finished film; that is not a shortened label but a false one.- Return type:
- tituli.compose.note(lines, *, frame, headline='', anchor='top-left', headline_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.055, weight=700, italic=False, condensed=False, color=(255, 255, 255), tracking=0.0, leading=1.15, case='as-is', align='left', opacity=1.0, features=()), line_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.03, weight=400, italic=False, condensed=False, color=(255, 255, 255), tracking=0.0, leading=1.4, case='as-is', align='left', opacity=0.92, features=()), max_width=0.62, ink=None, scrim=None, accent=True, on_overflow='fit')[source]¶
An editorial context card: an optional headline over equal-weight lines.
The block for what the audio assumes and a cold viewer does not have (“what Hamilton is”, “Philip died at 19”). Heavier than a caption — schedule it with a higher
weightso the two never share a corner. Each line wraps tomax_widthof the safe box, shrinking to fit within_NOTE_MAX_LINESrows rather than being cut; every line shares the smallest size any of them needed, so the block reads as one block. RaisesTextDoesNotFitif a line cannot be set legibly.- Return type:
- tituli.compose.title_card(title, subtitle='', *, frame, kicker='', anchor='center', title_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.075, weight=700, italic=False, condensed=False, color=(255, 255, 255), tracking=-0.01, leading=1.1, case='as-is', align='center', opacity=1.0, features=()), subtitle_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.032, weight=400, italic=False, condensed=False, color=(255, 255, 255), tracking=0.02, leading=1.3, case='as-is', align='center', opacity=1.0, features=()), kicker_style=TextStyle(family=('Helvetica Neue', 'Inter', 'Helvetica', 'Avenir Next', 'Roboto', 'Univers', 'Liberation Sans', 'DejaVu Sans', 'Arial'), size=0.022, weight=500, italic=False, condensed=False, color=(255, 255, 255), tracking=0.18, leading=1.2, case='upper', align='center', opacity=1.0, features=()), ink=None, scrim=None)[source]¶
An opening card: optional kicker, title, optional subtitle.
Fits the title to 80 % of the safe width (shrinking, never clipping), stacks the parts with a gap proportional to the type, places the block at
anchor(subject-avoiding when the frame knows the picture), and picks ink by the frame’s contrast.scrimforces a scrim on/off; None decides.- Return type: