FAILURE MAP
← Case archive

FA-80086 / Typography line breaking / Open access

Justified interword spacing: ragged fallback spacing · case 01

Ragged lines are set with the wrong space width.

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

ROOT CAUSE

The ragged setter uses the minimum space instead of the natural space.

VERIFIED REPAIR

Set ragged lines with the natural space.

Unsuccessful approach: Using the maximum space is just as wrong in the other direction.

Case contract

Input [word widths, target width, is_last, natural space, min space, max space]. Last lines and single-word lines are set ragged with natural spaces. Otherwise distribute target-sum(words) over the gaps: equal share with the remainder units added to the rightmost gaps; a share below min space is ["overfull"]; if any gap would exceed max space fall back to ragged. Return word x positions.

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, target, is_last, sp, lo, hi = x
    n = len(words)
    gaps = n - 1
    def natural():
        pos, xx = [], 0
        for w in words:
            pos.append(xx)
            xx += w + lo
        return pos
    if is_last or gaps == 0:
        return natural()
    space = target - sum(words)
    per, rem = divmod(space, gaps)
    if per < lo:
        return ['overfull']
    if per + (1 if rem else 0) > hi:
        return natural()
    pos, xx = [], 0
    for k, w in enumerate(words):
        pos.append(xx)
        if k < gaps:
            xx += w + per + (1 if k >= gaps - rem else 0)
    return pos
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('regression: ragged fallback spacing', [[4, 7, 7, 7], 32, True, 2, 1, 3], [0, 6, 15, 24]), ('regression: ragged fallback spacing', [[3, 5], 14, False, 3, 2, 4], [0, 6]), ('partial-repair probe', [[3, 9], 17, False, 3, 3, 4], [0, 6]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[9, 9, 9, 5, 4, 4], 48, False, 3, 3, 4], ['overfull']), ('control layout', [[6], 7, False, 2, 2, 4], [0])], [('regression: ragged fallback spacing', [[7, 9, 6], 27, True, 3, 1, 4], [0, 10, 22]), ('regression: ragged fallback spacing', [[8, 5, 5, 8, 5], 57, True, 3, 2, 5], [0, 11, 19, 27, 38]), ('regression: ragged fallback spacing', [[6, 6], 22, False, 3, 1, 7], [0, 9]), ('regression: ragged fallback spacing', [[3, 3, 4, 4, 5], 38, False, 2, 1, 3], [0, 5, 10, 16, 22]), ('single word line', [[7], 20, False, 2, 1, 4], [0]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[7, 2, 5, 3], 21, False, 2, 1, 5], [0, 8, 11, 18]), ('control layout', [[3, 3], 8, False, 3, 1, 5], [0, 5])], [('regression: ragged fallback spacing', [[7, 6], 18, False, 2, 1, 3], [0, 9]), ('regression: ragged fallback spacing', [[6, 3, 4, 3], 30, False, 2, 1, 4], [0, 8, 13, 19]), ('partial-repair probe', [[7, 4, 2, 7, 4], 25, True, 3, 3, 6], [0, 10, 17, 22, 32]), ('regression: ragged fallback spacing', [[7, 4], 16, False, 2, 1, 3], [0, 9]), ('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('control layout', [[2, 8, 3, 8], 27, False, 3, 3, 6], ['overfull']), ('control layout', [[8], 9, False, 2, 2, 4], [0])], [('regression: ragged fallback spacing', [[6, 6], 22, False, 3, 1, 7], [0, 9]), ('regression: ragged fallback spacing', [[4, 7], 18, False, 3, 2, 6], [0, 7]), ('partial-repair probe', [[7, 8, 3, 7, 9], 37, True, 2, 2, 5], [0, 9, 19, 24, 33]), ('regression: ragged fallback spacing', [[8, 4], 18, False, 3, 2, 5], [0, 11]), ('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('control layout', [[3, 7, 7, 8, 9], 41, False, 2, 1, 3], [0, 4, 13, 22, 32]), ('control layout', [[6, 3, 2, 7, 8, 2], 47, False, 3, 3, 6], [0, 9, 16, 22, 33, 45])], [('regression: ragged fallback spacing', [[5, 5, 6, 4, 2], 50, False, 2, 1, 5], [0, 7, 14, 22, 28]), ('regression: ragged fallback spacing', [[9, 8, 3, 8], 40, False, 2, 1, 3], [0, 11, 21, 26]), ('regression: ragged fallback spacing', [[7, 2, 9, 2, 4, 6], 59, True, 2, 1, 4], [0, 9, 13, 24, 28, 34]), ('regression: ragged fallback spacing', [[6, 3, 8], 28, False, 3, 2, 5], [0, 9, 15]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[6, 4, 8, 8, 2], 31, False, 3, 1, 4], ['overfull']), ('control layout', [[5, 8, 2, 5, 5], 30, False, 3, 1, 5], [0, 6, 15, 18, 25])]]
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
last line ragged[0, 4, 9][0, 5, 11]Failed
regression: ragged fallback spacing[0, 5, 13, 21][0, 6, 15, 24]Failed
regression: ragged fallback spacing[0, 5][0, 6]Failed
partial-repair probe[0, 6][0, 6]Passed
exactly minimum share[0, 4, 8][0, 4, 8]Passed
remainder goes to right gaps[0, 5, 11, 18][0, 5, 11, 18]Passed
control layout['overfull']['overfull']Passed
control layout[0][0]Passed

SHA-256 / bc164e8cc4bc0624cea78f9c953e545a3fe3960a0893e16480a85276b10eb385

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(x):
    words, target, is_last, sp, lo, hi = x
    n = len(words)
    gaps = n - 1
    def natural():
        pos, xx = [], 0
        for w in words:
            pos.append(xx)
            xx += w + hi
        return pos
    if is_last or gaps == 0:
        return natural()
    space = target - sum(words)
    per, rem = divmod(space, gaps)
    if per < lo:
        return ['overfull']
    if per + (1 if rem else 0) > hi:
        return natural()
    pos, xx = [], 0
    for k, w in enumerate(words):
        pos.append(xx)
        if k < gaps:
            xx += w + per + (1 if k >= gaps - rem else 0)
    return pos
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('regression: ragged fallback spacing', [[4, 7, 7, 7], 32, True, 2, 1, 3], [0, 6, 15, 24]), ('regression: ragged fallback spacing', [[3, 5], 14, False, 3, 2, 4], [0, 6]), ('partial-repair probe', [[3, 9], 17, False, 3, 3, 4], [0, 6]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[9, 9, 9, 5, 4, 4], 48, False, 3, 3, 4], ['overfull']), ('control layout', [[6], 7, False, 2, 2, 4], [0])], [('regression: ragged fallback spacing', [[7, 9, 6], 27, True, 3, 1, 4], [0, 10, 22]), ('regression: ragged fallback spacing', [[8, 5, 5, 8, 5], 57, True, 3, 2, 5], [0, 11, 19, 27, 38]), ('regression: ragged fallback spacing', [[6, 6], 22, False, 3, 1, 7], [0, 9]), ('regression: ragged fallback spacing', [[3, 3, 4, 4, 5], 38, False, 2, 1, 3], [0, 5, 10, 16, 22]), ('single word line', [[7], 20, False, 2, 1, 4], [0]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[7, 2, 5, 3], 21, False, 2, 1, 5], [0, 8, 11, 18]), ('control layout', [[3, 3], 8, False, 3, 1, 5], [0, 5])], [('regression: ragged fallback spacing', [[7, 6], 18, False, 2, 1, 3], [0, 9]), ('regression: ragged fallback spacing', [[6, 3, 4, 3], 30, False, 2, 1, 4], [0, 8, 13, 19]), ('partial-repair probe', [[7, 4, 2, 7, 4], 25, True, 3, 3, 6], [0, 10, 17, 22, 32]), ('regression: ragged fallback spacing', [[7, 4], 16, False, 2, 1, 3], [0, 9]), ('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('control layout', [[2, 8, 3, 8], 27, False, 3, 3, 6], ['overfull']), ('control layout', [[8], 9, False, 2, 2, 4], [0])], [('regression: ragged fallback spacing', [[6, 6], 22, False, 3, 1, 7], [0, 9]), ('regression: ragged fallback spacing', [[4, 7], 18, False, 3, 2, 6], [0, 7]), ('partial-repair probe', [[7, 8, 3, 7, 9], 37, True, 2, 2, 5], [0, 9, 19, 24, 33]), ('regression: ragged fallback spacing', [[8, 4], 18, False, 3, 2, 5], [0, 11]), ('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('control layout', [[3, 7, 7, 8, 9], 41, False, 2, 1, 3], [0, 4, 13, 22, 32]), ('control layout', [[6, 3, 2, 7, 8, 2], 47, False, 3, 3, 6], [0, 9, 16, 22, 33, 45])], [('regression: ragged fallback spacing', [[5, 5, 6, 4, 2], 50, False, 2, 1, 5], [0, 7, 14, 22, 28]), ('regression: ragged fallback spacing', [[9, 8, 3, 8], 40, False, 2, 1, 3], [0, 11, 21, 26]), ('regression: ragged fallback spacing', [[7, 2, 9, 2, 4, 6], 59, True, 2, 1, 4], [0, 9, 13, 24, 28, 34]), ('regression: ragged fallback spacing', [[6, 3, 8], 28, False, 3, 2, 5], [0, 9, 15]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[6, 4, 8, 8, 2], 31, False, 3, 1, 4], ['overfull']), ('control layout', [[5, 8, 2, 5, 5], 30, False, 3, 1, 5], [0, 6, 15, 18, 25])]]
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
last line ragged[0, 9, 19][0, 5, 11]Failed
regression: ragged fallback spacing[0, 7, 17, 27][0, 6, 15, 24]Failed
regression: ragged fallback spacing[0, 7][0, 6]Failed
partial-repair probe[0, 7][0, 6]Failed
exactly minimum share[0, 4, 8][0, 4, 8]Passed
remainder goes to right gaps[0, 5, 11, 18][0, 5, 11, 18]Passed
control layout['overfull']['overfull']Passed
control layout[0][0]Passed

SHA-256 / 526f47141320855f57d770065be399a7776f138a1efa2f59376328e64c21aa88

3 / The verified repair

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

N = 1
observations = []
def solve(x):
    words, target, is_last, sp, lo, hi = x
    n = len(words)
    gaps = n - 1
    def natural():
        pos, xx = [], 0
        for w in words:
            pos.append(xx)
            xx += w + sp
        return pos
    if is_last or gaps == 0:
        return natural()
    space = target - sum(words)
    per, rem = divmod(space, gaps)
    if per < lo:
        return ['overfull']
    if per + (1 if rem else 0) > hi:
        return natural()
    pos, xx = [], 0
    for k, w in enumerate(words):
        pos.append(xx)
        if k < gaps:
            xx += w + per + (1 if k >= gaps - rem else 0)
    return pos
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('regression: ragged fallback spacing', [[4, 7, 7, 7], 32, True, 2, 1, 3], [0, 6, 15, 24]), ('regression: ragged fallback spacing', [[3, 5], 14, False, 3, 2, 4], [0, 6]), ('partial-repair probe', [[3, 9], 17, False, 3, 3, 4], [0, 6]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[9, 9, 9, 5, 4, 4], 48, False, 3, 3, 4], ['overfull']), ('control layout', [[6], 7, False, 2, 2, 4], [0])], [('regression: ragged fallback spacing', [[7, 9, 6], 27, True, 3, 1, 4], [0, 10, 22]), ('regression: ragged fallback spacing', [[8, 5, 5, 8, 5], 57, True, 3, 2, 5], [0, 11, 19, 27, 38]), ('regression: ragged fallback spacing', [[6, 6], 22, False, 3, 1, 7], [0, 9]), ('regression: ragged fallback spacing', [[3, 3, 4, 4, 5], 38, False, 2, 1, 3], [0, 5, 10, 16, 22]), ('single word line', [[7], 20, False, 2, 1, 4], [0]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[7, 2, 5, 3], 21, False, 2, 1, 5], [0, 8, 11, 18]), ('control layout', [[3, 3], 8, False, 3, 1, 5], [0, 5])], [('regression: ragged fallback spacing', [[7, 6], 18, False, 2, 1, 3], [0, 9]), ('regression: ragged fallback spacing', [[6, 3, 4, 3], 30, False, 2, 1, 4], [0, 8, 13, 19]), ('partial-repair probe', [[7, 4, 2, 7, 4], 25, True, 3, 3, 6], [0, 10, 17, 22, 32]), ('regression: ragged fallback spacing', [[7, 4], 16, False, 2, 1, 3], [0, 9]), ('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('control layout', [[2, 8, 3, 8], 27, False, 3, 3, 6], ['overfull']), ('control layout', [[8], 9, False, 2, 2, 4], [0])], [('regression: ragged fallback spacing', [[6, 6], 22, False, 3, 1, 7], [0, 9]), ('regression: ragged fallback spacing', [[4, 7], 18, False, 3, 2, 6], [0, 7]), ('partial-repair probe', [[7, 8, 3, 7, 9], 37, True, 2, 2, 5], [0, 9, 19, 24, 33]), ('regression: ragged fallback spacing', [[8, 4], 18, False, 3, 2, 5], [0, 11]), ('last line ragged', [[3, 4, 5], 20, True, 2, 1, 6], [0, 5, 11]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('control layout', [[3, 7, 7, 8, 9], 41, False, 2, 1, 3], [0, 4, 13, 22, 32]), ('control layout', [[6, 3, 2, 7, 8, 2], 47, False, 3, 3, 6], [0, 9, 16, 22, 33, 45])], [('regression: ragged fallback spacing', [[5, 5, 6, 4, 2], 50, False, 2, 1, 5], [0, 7, 14, 22, 28]), ('regression: ragged fallback spacing', [[9, 8, 3, 8], 40, False, 2, 1, 3], [0, 11, 21, 26]), ('regression: ragged fallback spacing', [[7, 2, 9, 2, 4, 6], 59, True, 2, 1, 4], [0, 9, 13, 24, 28, 34]), ('regression: ragged fallback spacing', [[6, 3, 8], 28, False, 3, 2, 5], [0, 9, 15]), ('exactly minimum share', [[3, 3, 3], 11, False, 2, 1, 4], [0, 4, 8]), ('remainder goes to right gaps', [[3, 4, 5, 2], 20, False, 2, 1, 4], [0, 5, 11, 18]), ('control layout', [[6, 4, 8, 8, 2], 31, False, 3, 1, 4], ['overfull']), ('control layout', [[5, 8, 2, 5, 5], 30, False, 3, 1, 5], [0, 6, 15, 18, 25])]]
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
last line ragged[0, 5, 11][0, 5, 11]Passed
regression: ragged fallback spacing[0, 6, 15, 24][0, 6, 15, 24]Passed
regression: ragged fallback spacing[0, 6][0, 6]Passed
partial-repair probe[0, 6][0, 6]Passed
exactly minimum share[0, 4, 8][0, 4, 8]Passed
remainder goes to right gaps[0, 5, 11, 18][0, 5, 11, 18]Passed
control layout['overfull']['overfull']Passed
control layout[0][0]Passed

SHA-256 / 936a40fb60c3d12e42eed698758c5c419d44503a5565fcbb65e6fc22b53affc4

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:50.556904+00:00.

Case digest / f443d18744f035036e9215c8e77f287cfb7614a6da29e17b0fd1faf62c863e8e