md2star.linebreak module

Semantic line breaking: md2star chooses where a slide’s lines end.

A renderer wraps greedily, so a line ends wherever the next word stops fitting: on « de », on « the », before a lone « : », between « 500 » and « 000 ». The usual cure, a no-break space, does not work on every template: Satoshi, the course’s face, has no glyph for U+00A0 or U+202F at all.

So when md2star writes a title or a paragraph into a box whose width and font size it knows, it breaks the lines itself, with the face’s real advance widths (read from the font file’s cmap and hmtx tables, 0.55 em per character when the file cannot be found), and writes explicit line breaks the renderer keeps. The break points are chosen by dynamic programming over a cost model:

  • a line does not end on a function word (article, preposition, conjunction, pronoun, auxiliary — French and English) whenever any other break fits;

  • a line never starts with : ; ? ! » % and a number is never separated from the number or the unit after it (500 000, 2 min, 30 %);

  • no single orphan word on the last line;

  • breaking after punctuation, or before a word that opens a clause (qui, que, pour, quand, mais, which, when, because), is preferred;

  • among the permitted breaks, balanced lines win, and each extra line costs.

The cost model is the one the course’s figures already use (figkit.wrap).

md2star.linebreak.BREAK_BEFORE = frozenset({'alors', 'après', 'avant', 'because', 'but', 'car', 'comme', 'donc', 'if', 'mais', 'où', 'parce', 'pour', 'puis', 'quand', 'que', 'qui', 'si', 'so', 'that', 'then', 'when', 'where', 'which', 'while'})

breaking just before one reads naturally.

Type:

Words that open a clause

md2star.linebreak.FALLBACK_EM = 0.55

Advance of one character when the face’s own metrics cannot be read.

md2star.linebreak.GLUE_AFTER = frozenset({'a', 'an', 'and', 'are', 'as', 'at', 'au', 'aux', 'avec', 'be', 'by', "c'", 'ce', 'ces', 'cet', 'cette', 'chez', 'c’', "d'", 'dans', 'de', 'des', 'du', 'd’', 'elle', 'elles', 'en', 'entre', 'est', 'et', 'for', 'from', 'her', 'his', 'il', 'ils', 'in', 'into', 'is', 'its', "j'", 'je', 'j’', "l'", 'la', 'le', 'les', 'leur', 'leurs', 'l’', 'ma', 'mes', 'mon', 'my', "n'", 'ne', 'ni', 'no', 'nor', 'nos', 'not', 'notre', 'nous', 'n’', 'of', 'on', 'or', 'ou', 'our', 'par', 'pas', 'per', 'plus', 'pour', "qu'", 'que', 'qui', 'qu’', "s'", 'sa', 'sans', 'se', 'ses', 'son', 'sont', 'sous', 'sur', 's’', 'ta', 'tes', 'than', 'that', 'the', 'their', 'these', 'this', 'those', 'to', 'ton', 'très', 'un', 'une', 'vers', 'via', 'vos', 'votre', 'vous', 'was', 'with', 'y', 'your', 'à', 'être'})

a line ending on one reads as cut.

Type:

Words that announce what follows

md2star.linebreak.NO_LINE_START = frozenset({'!', '%', ')', ':', ';', '?', '»', '…'})

Characters a line must never start with.

md2star.linebreak.UNITS = frozenset({'$', '%', 'an', 'ans', 'cm', 'fois', 'g', 'h', 'j', 'jours', 'kg', 'km', 'k€', 'm', 'md', 'mds', 'min', 'mm', 'mois', 'ms', 'm€', 'pt', 's', 'x', '€'})

Units that stay with the number before them.

md2star.linebreak.WIDTH_MARGIN = 1.03

a renderer’s kerning, hinting or a slightly different inset must never make it re-wrap a line we broke.

Type:

Safety factor on measured widths

md2star.linebreak.break_lines(text, size, box_width_pt, *, family=None, bold=False)[source]

text with its line breaks chosen (\v between lines), ready to write.

A no-break space the face has no glyph for is written as a plain space: the break it protected is now ours to choose, and the cost model already keeps a number with its unit and a « : » off the start of a line.

Parameters:
Return type:

str

md2star.linebreak.cost_break(left, right)[source]

Cost of ending a line on word left and starting the next on right.

Parameters:
Return type:

float

md2star.linebreak.face(family, bold=False)[source]

The metrics of family (None: the 0.55 em fallback is used).

Parameters:
Return type:

tuple[int, list[int], dict[int, int]] | None

md2star.linebreak.has_glyph(metrics, ch)[source]

Whether the face draws ch (always False without metrics).

Parameters:

ch (str)

Return type:

bool

md2star.linebreak.width(text, size, metrics=None)[source]

Printed width of text at size pt, in points.

Parameters:
Return type:

float

md2star.linebreak.wrap(text, size, max_w, metrics=None)[source]

text broken into lines no wider than max_w points at size pt.

A word wider than the box alone gets a line of its own (the renderer will break it; nothing better exists). Existing newlines are kept.

Parameters:
Return type:

list[str]