~jaro/balkon

21f976aa18e99769db1e2b63313bd26ede47ca4f — Jaro 1 year, 25 days ago 6c97775
Support hard line breaks.
A .golden/hardBreaksLTRParagraph/golden => .golden/hardBreaksLTRParagraph/golden +55 -0
@@ 0,0 1,55 @@
ParagraphLayout {paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 4305, y_size = -8968}, spanLayouts = [
    SpanLayout [Fragment {fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 1563, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 77, cluster = 1, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 2, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 3, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 4, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 5, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 6, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -1121, x_size = 3357, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 77, cluster = 8, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 10, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 11, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 12, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 13, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 3, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 231, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 16, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 17, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 19, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 20, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -2242, x_size = 1563, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 77, cluster = 22, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 23, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 24, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 25, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 26, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 27, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -3363, x_size = 4305, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 80, cluster = 29, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 30, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 31, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 32, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 33, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -4484, x_size = 861, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 80, cluster = 34, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -5605, x_size = 1563, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 77, cluster = 37, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 38, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 39, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 40, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 41, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 262, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 77, cluster = 42, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -6726, x_size = 3675, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 80, cluster = 45, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 46, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 3, cluster = 47, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 231, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 48, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 49, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -7847, x_size = 1722, y_size = -1121}, fragmentPen = (0,-932), fragmentGlyphs =
        [(GlyphInfo {codepoint = 80, cluster = 51, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 80, cluster = 52, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 861, y_advance = 0, x_offset = 0, y_offset = 0})]
    }]
]}

A .golden/hardBreaksRTLParagraph/golden => .golden/hardBreaksRTLParagraph/golden +56 -0
@@ 0,0 1,56 @@
ParagraphLayout {paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 5852, y_size = -12000}, spanLayouts = [
    SpanLayout [Fragment {fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 2808, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 642, cluster = 11, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 5, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 3, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 1, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -1500, x_size = 5852, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 642, cluster = 37, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 35, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 33, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 31, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 29, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 27, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 3, cluster = 26, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 24, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 22, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 20, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 16, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -3000, x_size = 2808, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 642, cluster = 50, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 48, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 46, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 44, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 42, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 40, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -4500, x_size = 4884, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 359, cluster = 63, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 827, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 359, cluster = 61, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 827, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 359, cluster = 59, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 827, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 359, cluster = 57, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 827, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 359, cluster = 55, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 827, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 370, cluster = 53, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 749, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -6000, x_size = 1211, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 687, cluster = 65, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 1211, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -7500, x_size = 2808, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 642, cluster = 79, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 77, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 75, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 73, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 71, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 642, cluster = 69, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 468, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -9000, x_size = 4156, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 687, cluster = 90, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 1211, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 370, cluster = 88, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 749, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 3, cluster = 87, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 687, cluster = 85, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 1211, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 370, cluster = 83, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 749, y_advance = 0, x_offset = 0, y_offset = 0})]
    }, Fragment {fragmentRect = Rect {x_origin = 0, y_origin = -10500, x_size = 1960, y_size = -1500}, fragmentPen = (0,-1085), fragmentGlyphs =
        [(GlyphInfo {codepoint = 687, cluster = 95, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 1211, y_advance = 0, x_offset = 0, y_offset = 0}),
        (GlyphInfo {codepoint = 370, cluster = 93, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False},GlyphPos {x_advance = 749, y_advance = 0, x_offset = 0, y_offset = 0})]
    }]
]}

M CHANGELOG.md => CHANGELOG.md +2 -0
@@ 2,6 2,8 @@

## 0.2.0.0 -- TBD

* Added support for forced (hard) line breaks in the input text.

* Internally, language tags will be cut at the first invalid character before
  being passed to ICU.


M src/Data/Text/ParagraphLayout/Internal/Plain.hs => src/Data/Text/ParagraphLayout/Internal/Plain.hs +45 -3
@@ 25,7 25,7 @@ import Data.Text.Glyphize
    ,shape
    )
import Data.Text.ICU (Breaker, LocaleName, breakCharacter, breakLine)
import qualified Data.Text.ICU as BreakStatus (Line)
import qualified Data.Text.ICU as BreakStatus (Line(Hard))
import Data.Text.Internal (Text(Text))
import qualified Data.Text.Lazy as Lazy



@@ 160,10 160,47 @@ layoutAndWrapRunsH maxWidth runs = NonEmpty.head $ validLayouts
        layouts = NonEmpty.map layoutFst splits
        layoutFst (runs1, runs2) = (layout runs1, runs2)
        layout runs1 = layoutRunsH $ trimTextsEnd isEndSpace runs1
        splits = noSplit :| (lSplits ++ cSplits)
        -- TODO: Consider optimising.
        --       We do not need to look for soft breaks further than the
        --       shortest hard break.
        -- TODO: Add a "strut" for empty lines.
        splits = hardSplit runs :| softSplits runs

-- | Treat a list of runs as a contiguous sequence, and split them into two
-- lists so that the first list contains as much of the input text as possible
-- without crossing a hard line break (typically after a newline character).
--
-- If there is no hard line break in the input, the first output list will
-- contain the whole input, and the second output list will be empty.
hardSplit :: [WithSpan Run] -> ([WithSpan Run], [WithSpan Run])
hardSplit runs = NonEmpty.last $ splits
    where
        -- TODO: Consider optimising.
        --       We do not need to look for any line breaks further than the
        --       shortest hard break.
        splits = noSplit :| map trimFst hSplits
        trimFst (runs1, runs2) = (trimTextsEnd isNewline runs1, runs2)
        noSplit = (runs, [])
        -- TODO: Use Soft/Hard line break distinction.
        hSplits = -- from longest to shortest
            splitTextsBy (map fst . filter isHard . runLineBreaks) runs
        isHard (_, status) = status == BreakStatus.Hard

-- | Treat a list of runs as a contiguous sequence,
-- and find all possible ways to split them into two non-empty lists,
-- using soft line break opportunities (typically after words) and then
-- using character boundaries.
--
-- The results in the form (prefix, suffix) will be ordered so that items
-- closer to the start of the list are preferred for line breaking, but without
-- considering overflows.
softSplits :: [WithSpan Run] -> [([WithSpan Run], [WithSpan Run])]
softSplits runs = splits
    where
        splits = lSplits ++ cSplits
        lSplits = splitTextsBy (map fst . runLineBreaks) runs
        -- TODO: Consider optimising.
        --       We do not need to look for character breaks further than the
        --       shortest line break.
        cSplits = splitTextsBy (map fst . runCharacterBreaks) runs

-- | The suffix remaining after removing the longest prefix of the list for


@@ 272,3 309,8 @@ runBreaksFromSpan run spanBreaks =
-- a line according to the CSS Text Module.
isEndSpace :: Char -> Bool
isEndSpace c = c `elem` [' ', '\t', '\x1680']

-- | Predicate for characters that should be removed from the end of a line in
-- the case of a hard line break.
isNewline :: Char -> Bool
isNewline c = c == '\n'

M test/Data/Text/ParagraphLayout/ParagraphData.hs => test/Data/Text/ParagraphLayout/ParagraphData.hs +17 -0
@@ 6,6 6,8 @@ module Data.Text.ParagraphLayout.ParagraphData
    ,devanagariPrefixedAccentParagraph
    ,emptyParagraph
    ,emptySpanParagraph
    ,hardBreaksLTRParagraph
    ,hardBreaksRTLParagraph
    ,ligatureParagraph
    ,loremIpsumParagraph
    ,mixedLanguageLTRParagraph


@@ 91,3 93,18 @@ devanagariAccentParagraph = "" |< zxx "\x954" >| ""

devanagariPrefixedAccentParagraph :: ParagraphOptions -> Paragraph
devanagariPrefixedAccentParagraph = "#" |< zxx "\x954" >| ""

-- | Test hard line breaks with Latin characters:
--
-- * after a short line (no soft line breaks needed),
-- * after a longer line (soft line breaks needed),
-- * after a long word (line break in the middle of a word needed),
-- * after spaces,
-- * after other line breaks.
hardBreaksLTRParagraph :: ParagraphOptions -> Paragraph
hardBreaksLTRParagraph = "x" |< zxx "jjjjjj\njjjjjj jjjjjj jjjjjj\nmmmmmm \njjjjjj\n\nmm mm mm" >| ""

-- | Test hard line breaks like `hardBreaksLTRParagraph`,
-- but with Arabic characters.
hardBreaksRTLParagraph :: ParagraphOptions -> Paragraph
hardBreaksRTLParagraph = "x" |< zxx "دددددد\nدددددد دددددد دددددد\nسسسسسسس \nدددددد\n\nسس سس سس" >| ""

M test/Data/Text/ParagraphLayoutSpec.hs => test/Data/Text/ParagraphLayoutSpec.hs +10 -0
@@ 218,6 218,11 @@ spec = do
                let withSpans = layoutPlain $ spannedArabicFillerParagraph opts
                paragraphRect withoutSpans `shouldBe` paragraphRect withSpans

            it "applies hard breaks correctly" $ do
                let opts = ParagraphOptions font Normal 6000
                let result = layoutPlain $ hardBreaksRTLParagraph opts
                result `shouldBeGolden` "hardBreaksRTLParagraph"

        describe "with Devanagari font" $ do
            font <- runIO $ loadFont devanagariFont 0 testingOptions



@@ 340,6 345,11 @@ spec = do
                let result = layoutPlain $ mixedScriptWordsParagraph opts
                result `shouldBeGolden` "mixedScriptWordsParagraph"

            it "applies hard breaks correctly" $ do
                let opts = ParagraphOptions font Normal 5000
                let result = layoutPlain $ hardBreaksLTRParagraph opts
                result `shouldBeGolden` "hardBreaksLTRParagraph"

    describe "shaped runs for demo" $ do

        describe "with Latin font" $ do