FAILURE MAP
← Case archive

FA-80156 / Typography line breaking / Open access

Kerning and ligature line measure: kerning across spaces · case 01

Lines measure narrower when a word ends and the next begins with a kern pair.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Line width is measured on the words concatenated without spaces, so pairs kern across word boundaries.

THE FAILURE

Line width is measured on the words concatenated without spaces, so pairs kern across word boundaries.

Unsuccessful approach: Measuring the space-joined text treats the space as a default-width glyph.

Case contract

Input [words, width]. Glyph widths: i,l,j,.,, 4; t,f 6; m 15; w 14; M 16; W 18; other lowercase 10; other uppercase 13; space 5. Within a word, fi/fl/ff form ligatures (11/11/12) with no kerning into or out of them; case-sensitive kern pairs apply between adjacent glyphs; "|" is a non-joiner (invisible, blocks ligatures and kerning). No kerning across spaces. Greedy fill. Return [[text, width]].

Why this case matters

Line breaking decides where paragraphs wrap on screen and in print; a wrong decision point shifts every following line.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    words, width = x
    W = {'i': 4, 'l': 4, 'j': 4, '.': 4, ',': 4, 't': 6, 'f': 6, 'm': 15, 'w': 14, 'M': 16, 'W': 18}
    LIG = {'fi': 11, 'fl': 11, 'ff': 12}
    KERN = {'AV': -3, 'VA': -3, 'To': -2, 'Ta': -2, 'Yo': -2, 'LT': -3, 'Wa': -2, 'av': -1, 'va': -1, 'y.': -2, 'r.': -2, 'l.': -1, 'f.': -1, 'Va': -2}
    SPACE = 5
    def glyph(c):
        return W.get(c, 13 if c.isupper() else 10)
    def measure(w):
        total, prev, i = 0, None, 0
        while i < len(w):
            if w[i] == '|':
                prev = None
                i += 1
                continue
            pair = w[i:i + 2]
            if pair in LIG:
                total += LIG[pair]
                prev = None
                i += 2
                continue
            g = w[i]
            total += glyph(g)
            if prev is not None:
                total += KERN.get(prev + g, 0)
            prev = g
            i += 1
        return total
    def line_width(ws):
        return measure(''.join(ws)) + SPACE * (len(ws) - 1)
    lines, cur = [], []
    for w in words:
        cand = cur + [w]
        if not cur or line_width(cand) <= width:
            cur = cand
        else:
            lines.append(cur)
            cur = [w]
    if cur:
        lines.append(cur)
    return [[' '.join(t.replace('|', '') for t in ln), line_width(ln)] for ln in lines]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('regression: kerning across spaces', [['Wavy', 'Ta', 'office', 'AT', 'office', 'shelf|life', 'flow'], 140], [['Wavy Ta office', 122], ['AT office', 77], ['shelflife flow', 104]]), ('partial-repair probe', [['LA|VALLE', 'av', 'Ta', 'LA|VALLE', 'dry.', 'wolf|like', 'wolf|like'], 51], [['LAVALLE', 88], ['av Ta', 45], ['LAVALLE', 88], ['dry.', 32], ['wolflike', 62], ['wolflike', 62]]), ('partial-repair probe', [['raffle', 'LA', 'fi', 'fl.', 'Wavy', 'raffle'], 128], [['raffle LA fi fl.', 113], ['Wavy raffle', 96]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('non-joiner blocks ligature', [['shelf|life'], 200], [['shelflife', 64]]), ('control layout', [['fly.', 'LA|VALLE'], 93], [['fly.', 23], ['LAVALLE', 88]]), ('control layout', [['our', 'Av'], 54], [['our', 30], ['Av', 23]])], [('regression: kerning across spaces', [['LA', 'VIA'], 102], [['LA VIA', 70]]), ('regression: kerning across spaces', [['AT', 'AT', 'office', 'Ta', 'staff.', 'Tofu'], 114], [['AT AT office', 108], ['Ta staff. Tofu', 110]]), ('partial-repair probe', [['shelf|life', 'LA|VALLE', 'raffle', 'y.', 'fl.'], 132], [['shelflife', 64], ['LAVALLE', 88], ['raffle y. fl.', 83]]), ('partial-repair probe', [['Tofu', 'LA'], 126], [['Tofu LA', 68]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('control layout', [['fi', 'dry.', 'wolf|like', 'VIA', 'Tofu'], 45], [['fi', 11], ['dry.', 32], ['wolflike', 62], ['VIA', 39], ['Tofu', 37]]), ('control layout', [['raffle', 'raffle'], 87], [['raffle', 46], ['raffle', 46]])], [('regression: kerning across spaces', [['fi', 'LT', 'office', 'Ta'], 104], [['fi LT office', 90], ['Ta', 21]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('regression: kerning across spaces', [['office', 'AT', 'our', 'fly.', 'flow', 'shelf|life'], 129], [['office AT our', 112], ['fly. flow', 63], ['shelflife', 64]]), ('partial-repair probe', [['Tofu', 'our', 'wolf|like', 'y.', 'flow', 'LT'], 91], [['Tofu our', 72], ['wolflike y.', 79], ['flow LT', 63]]), ('ligature then period', [['fl.', 'fi,'], 200], [['fl. fi,', 35]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('control layout', [['fly.', 'LA|VALLE'], 93], [['fly.', 23], ['LAVALLE', 88]]), ('control layout', [['Vat', 'VIA', 'Tofu', 'office', 'y.'], 62], [['Vat', 27], ['VIA', 39], ['Tofu', 37], ['office', 46], ['y.', 12]])], [('regression: kerning across spaces', [['shelf|life', 'AVAIL', 'Ta'], 103], [['shelflife', 64], ['AVAIL Ta', 85]]), ('regression: kerning across spaces', [['LA|VALLE', 'Wavy', 'fl.', 'AT', 'av', 'our'], 80], [['LAVALLE', 88], ['Wavy fl.', 65], ['AT av', 50], ['our', 30]]), ('partial-repair probe', [['VIA', 'AVAIL', 'VIA', 'raffle', 'raffle', 'wolf|like'], 91], [['VIA', 39], ['AVAIL', 59], ['VIA raffle', 90], ['raffle', 46], ['wolflike', 62]]), ('partial-repair probe', [['flow', 'VIA', 'Tofu', 'Av', 'fly.', 'Vat', 'Yo'], 74], [['flow', 35], ['VIA', 39], ['Tofu Av', 65], ['fly. Vat', 55], ['Yo', 21]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('control layout', [['fi', 'LA|VALLE'], 55], [['fi', 11], ['LAVALLE', 88]]), ('control layout', [['Av', 'Tofu'], 41], [['Av', 23], ['Tofu', 37]])], [('regression: kerning across spaces', [['flow', 'dry.', 'LA', 'LA', 'LT', 'av'], 77], [['flow dry.', 72], ['LA LA', 57], ['LT av', 47]]), ('regression: kerning across spaces', [['AT', 'LT', 'Tofu', 'LT', 'office', 'Tofu'], 111], [['AT LT Tofu', 96], ['LT office', 74], ['Tofu', 37]]), ('partial-repair probe', [['av', 'Av', 'Av'], 104], [['av Av Av', 75]]), ('partial-repair probe', [['LA|VALLE', 'shelf|life', 'office', 'AT', 'wolf|like'], 116], [['LAVALLE', 88], ['shelflife office', 115], ['AT wolflike', 93]]), ('ligature then period', [['fl.', 'fi,'], 200], [['fl. fi,', 35]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('control layout', [['AVAIL', 'LA|VALLE', 'staff.'], 78], [['AVAIL', 59], ['LAVALLE', 88], ['staff.', 42]]), ('control layout', [['VIA', 'LA|VALLE', 'Wavy', 'wolf|like'], 72], [['VIA', 39], ['LAVALLE', 88], ['Wavy', 45], ['wolflike', 62]])]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
cross-word pair must not kern[['LA Vat', 55]][['LA Vat', 58]]Failed
regression: kerning across spaces[['Wavy Ta office', 122], ['AT office', 75], ['shelflife flow', 104]][['Wavy Ta office', 122], ['AT office', 77], ['shelflife flow', 104]]Failed
partial-repair probe[['LAVALLE', 88], ['av Ta', 45], ['LAVALLE', 88], ['dry.', 32], ['wolflike', 62], ['wolflike', 62]][['LAVALLE', 88], ['av Ta', 45], ['LAVALLE', 88], ['dry.', 32], ['wolflike', 62], ['wolflike', 62]]Passed
partial-repair probe[['raffle LA fi fl.', 113], ['Wavy raffle', 96]][['raffle LA fi fl.', 113], ['Wavy raffle', 96]]Passed
case-sensitive pair[['Av av', 46]][['Av av', 47]]Failed
non-joiner blocks ligature[['shelflife', 64]][['shelflife', 64]]Passed
control layout[['fly.', 23], ['LAVALLE', 88]][['fly.', 23], ['LAVALLE', 88]]Passed
control layout[['our', 30], ['Av', 23]][['our', 30], ['Av', 23]]Passed

SHA-256 / 6f026cf2887bead2c152d506af6c5fe0b08bbd08134e646066698fb067140bd0

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(x):
    words, width = x
    W = {'i': 4, 'l': 4, 'j': 4, '.': 4, ',': 4, 't': 6, 'f': 6, 'm': 15, 'w': 14, 'M': 16, 'W': 18}
    LIG = {'fi': 11, 'fl': 11, 'ff': 12}
    KERN = {'AV': -3, 'VA': -3, 'To': -2, 'Ta': -2, 'Yo': -2, 'LT': -3, 'Wa': -2, 'av': -1, 'va': -1, 'y.': -2, 'r.': -2, 'l.': -1, 'f.': -1, 'Va': -2}
    SPACE = 5
    def glyph(c):
        return W.get(c, 13 if c.isupper() else 10)
    def measure(w):
        total, prev, i = 0, None, 0
        while i < len(w):
            if w[i] == '|':
                prev = None
                i += 1
                continue
            pair = w[i:i + 2]
            if pair in LIG:
                total += LIG[pair]
                prev = None
                i += 2
                continue
            g = w[i]
            total += glyph(g)
            if prev is not None:
                total += KERN.get(prev + g, 0)
            prev = g
            i += 1
        return total
    def line_width(ws):
        return measure(' '.join(ws))
    lines, cur = [], []
    for w in words:
        cand = cur + [w]
        if not cur or line_width(cand) <= width:
            cur = cand
        else:
            lines.append(cur)
            cur = [w]
    if cur:
        lines.append(cur)
    return [[' '.join(t.replace('|', '') for t in ln), line_width(ln)] for ln in lines]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('regression: kerning across spaces', [['Wavy', 'Ta', 'office', 'AT', 'office', 'shelf|life', 'flow'], 140], [['Wavy Ta office', 122], ['AT office', 77], ['shelflife flow', 104]]), ('partial-repair probe', [['LA|VALLE', 'av', 'Ta', 'LA|VALLE', 'dry.', 'wolf|like', 'wolf|like'], 51], [['LAVALLE', 88], ['av Ta', 45], ['LAVALLE', 88], ['dry.', 32], ['wolflike', 62], ['wolflike', 62]]), ('partial-repair probe', [['raffle', 'LA', 'fi', 'fl.', 'Wavy', 'raffle'], 128], [['raffle LA fi fl.', 113], ['Wavy raffle', 96]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('non-joiner blocks ligature', [['shelf|life'], 200], [['shelflife', 64]]), ('control layout', [['fly.', 'LA|VALLE'], 93], [['fly.', 23], ['LAVALLE', 88]]), ('control layout', [['our', 'Av'], 54], [['our', 30], ['Av', 23]])], [('regression: kerning across spaces', [['LA', 'VIA'], 102], [['LA VIA', 70]]), ('regression: kerning across spaces', [['AT', 'AT', 'office', 'Ta', 'staff.', 'Tofu'], 114], [['AT AT office', 108], ['Ta staff. Tofu', 110]]), ('partial-repair probe', [['shelf|life', 'LA|VALLE', 'raffle', 'y.', 'fl.'], 132], [['shelflife', 64], ['LAVALLE', 88], ['raffle y. fl.', 83]]), ('partial-repair probe', [['Tofu', 'LA'], 126], [['Tofu LA', 68]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('control layout', [['fi', 'dry.', 'wolf|like', 'VIA', 'Tofu'], 45], [['fi', 11], ['dry.', 32], ['wolflike', 62], ['VIA', 39], ['Tofu', 37]]), ('control layout', [['raffle', 'raffle'], 87], [['raffle', 46], ['raffle', 46]])], [('regression: kerning across spaces', [['fi', 'LT', 'office', 'Ta'], 104], [['fi LT office', 90], ['Ta', 21]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('regression: kerning across spaces', [['office', 'AT', 'our', 'fly.', 'flow', 'shelf|life'], 129], [['office AT our', 112], ['fly. flow', 63], ['shelflife', 64]]), ('partial-repair probe', [['Tofu', 'our', 'wolf|like', 'y.', 'flow', 'LT'], 91], [['Tofu our', 72], ['wolflike y.', 79], ['flow LT', 63]]), ('ligature then period', [['fl.', 'fi,'], 200], [['fl. fi,', 35]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('control layout', [['fly.', 'LA|VALLE'], 93], [['fly.', 23], ['LAVALLE', 88]]), ('control layout', [['Vat', 'VIA', 'Tofu', 'office', 'y.'], 62], [['Vat', 27], ['VIA', 39], ['Tofu', 37], ['office', 46], ['y.', 12]])], [('regression: kerning across spaces', [['shelf|life', 'AVAIL', 'Ta'], 103], [['shelflife', 64], ['AVAIL Ta', 85]]), ('regression: kerning across spaces', [['LA|VALLE', 'Wavy', 'fl.', 'AT', 'av', 'our'], 80], [['LAVALLE', 88], ['Wavy fl.', 65], ['AT av', 50], ['our', 30]]), ('partial-repair probe', [['VIA', 'AVAIL', 'VIA', 'raffle', 'raffle', 'wolf|like'], 91], [['VIA', 39], ['AVAIL', 59], ['VIA raffle', 90], ['raffle', 46], ['wolflike', 62]]), ('partial-repair probe', [['flow', 'VIA', 'Tofu', 'Av', 'fly.', 'Vat', 'Yo'], 74], [['flow', 35], ['VIA', 39], ['Tofu Av', 65], ['fly. Vat', 55], ['Yo', 21]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('case-sensitive pair', [['Av', 'av'], 200], [['Av av', 47]]), ('control layout', [['fi', 'LA|VALLE'], 55], [['fi', 11], ['LAVALLE', 88]]), ('control layout', [['Av', 'Tofu'], 41], [['Av', 23], ['Tofu', 37]])], [('regression: kerning across spaces', [['flow', 'dry.', 'LA', 'LA', 'LT', 'av'], 77], [['flow dry.', 72], ['LA LA', 57], ['LT av', 47]]), ('regression: kerning across spaces', [['AT', 'LT', 'Tofu', 'LT', 'office', 'Tofu'], 111], [['AT LT Tofu', 96], ['LT office', 74], ['Tofu', 37]]), ('partial-repair probe', [['av', 'Av', 'Av'], 104], [['av Av Av', 75]]), ('partial-repair probe', [['LA|VALLE', 'shelf|life', 'office', 'AT', 'wolf|like'], 116], [['LAVALLE', 88], ['shelflife office', 115], ['AT wolflike', 93]]), ('ligature then period', [['fl.', 'fi,'], 200], [['fl. fi,', 35]]), ('cross-word pair must not kern', [['LA', 'Vat'], 200], [['LA Vat', 58]]), ('control layout', [['AVAIL', 'LA|VALLE', 'staff.'], 78], [['AVAIL', 59], ['LAVALLE', 88], ['staff.', 42]]), ('control layout', [['VIA', 'LA|VALLE', 'Wavy', 'wolf|like'], 72], [['VIA', 39], ['LAVALLE', 88], ['Wavy', 45], ['wolflike', 62]])]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
cross-word pair must not kern[['LA Vat', 63]][['LA Vat', 58]]Failed
regression: kerning across spaces[['Wavy Ta office', 132], ['AT office', 82], ['shelflife flow', 109]][['Wavy Ta office', 122], ['AT office', 77], ['shelflife flow', 104]]Failed
partial-repair probe[['LAVALLE', 88], ['av Ta', 50], ['LAVALLE', 88], ['dry.', 32], ['wolflike', 62], ['wolflike', 62]][['LAVALLE', 88], ['av Ta', 45], ['LAVALLE', 88], ['dry.', 32], ['wolflike', 62], ['wolflike', 62]]Failed
partial-repair probe[['raffle LA fi fl.', 128], ['Wavy raffle', 101]][['raffle LA fi fl.', 113], ['Wavy raffle', 96]]Failed
case-sensitive pair[['Av av', 52]][['Av av', 47]]Failed
non-joiner blocks ligature[['shelflife', 64]][['shelflife', 64]]Passed
control layout[['fly.', 23], ['LAVALLE', 88]][['fly.', 23], ['LAVALLE', 88]]Passed
control layout[['our', 30], ['Av', 23]][['our', 30], ['Av', 23]]Passed

SHA-256 / 550661cf4dfbd2aba1444f5a458c5d104b0e490daa0ae4dcb78db099b0ba8df3

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 8 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

Verification & scope

A deterministic toy typesetting model with integer widths and a stipulated rule set; it does not claim conformance to any engine. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:49:51.197522+00:00.

Case digest / 690d7c9a3613c3ab63ac9bda07ff372c4048409e4fff5c782d9217a8b33408fb