From 21f976aa18e99769db1e2b63313bd26ede47ca4f Mon Sep 17 00:00:00 2001 From: Jaro Date: Tue, 21 Mar 2023 12:43:14 +0100 Subject: [PATCH] Support hard line breaks. --- .golden/hardBreaksLTRParagraph/golden | 55 ++++++++++++++++++ .golden/hardBreaksRTLParagraph/golden | 56 +++++++++++++++++++ CHANGELOG.md | 2 + .../Text/ParagraphLayout/Internal/Plain.hs | 48 +++++++++++++++- .../Text/ParagraphLayout/ParagraphData.hs | 17 ++++++ test/Data/Text/ParagraphLayoutSpec.hs | 10 ++++ 6 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 .golden/hardBreaksLTRParagraph/golden create mode 100644 .golden/hardBreaksRTLParagraph/golden diff --git a/.golden/hardBreaksLTRParagraph/golden b/.golden/hardBreaksLTRParagraph/golden new file mode 100644 index 0000000..ab877b9 --- /dev/null +++ b/.golden/hardBreaksLTRParagraph/golden @@ -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})] + }] +]} diff --git a/.golden/hardBreaksRTLParagraph/golden b/.golden/hardBreaksRTLParagraph/golden new file mode 100644 index 0000000..0f4d4b4 --- /dev/null +++ b/.golden/hardBreaksRTLParagraph/golden @@ -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})] + }] +]} diff --git a/CHANGELOG.md b/CHANGELOG.md index 334f6b6..6656515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/Data/Text/ParagraphLayout/Internal/Plain.hs b/src/Data/Text/ParagraphLayout/Internal/Plain.hs index 81c07ff..6005896 100644 --- a/src/Data/Text/ParagraphLayout/Internal/Plain.hs +++ b/src/Data/Text/ParagraphLayout/Internal/Plain.hs @@ -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' diff --git a/test/Data/Text/ParagraphLayout/ParagraphData.hs b/test/Data/Text/ParagraphLayout/ParagraphData.hs index 82cce58..90e32aa 100644 --- a/test/Data/Text/ParagraphLayout/ParagraphData.hs +++ b/test/Data/Text/ParagraphLayout/ParagraphData.hs @@ -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سس سس سس" >| "" diff --git a/test/Data/Text/ParagraphLayoutSpec.hs b/test/Data/Text/ParagraphLayoutSpec.hs index b6c3d8f..abd39c4 100644 --- a/test/Data/Text/ParagraphLayoutSpec.hs +++ b/test/Data/Text/ParagraphLayoutSpec.hs @@ -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 -- 2.30.2