balkon 1.0.0.1 → 1.1.0.0
raw patch · 42 files changed
+1424/−185 lines, 42 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- .golden/paginatedParagraphLayout/spannedArabicFiller20em.golden +4/−4
- .golden/paragraphLayout/arabicFiller20em.golden +2/−2
- .golden/paragraphLayout/devanagariAccent.golden +3/−3
- .golden/paragraphLayout/devanagariPrefixedAccent.golden +3/−3
- .golden/paragraphLayout/hardBreaksRTL.golden +2/−2
- .golden/paragraphLayout/spannedArabicFiller20em.golden +4/−4
- .golden/richParagraphLayout/mixedDirectionComplexLTR.golden +147/−0
- .golden/richParagraphLayout/mixedDirectionComplexRTL.golden +147/−0
- .golden/richParagraphLayout/mixedDirectionSimpleLTR.golden +50/−0
- .golden/richParagraphLayout/mixedDirectionSimpleRTL.golden +50/−0
- .golden/richParagraphLayout/neutralDirectionLTR.golden +18/−0
- .golden/richParagraphLayout/neutralDirectionRTL.golden +18/−0
- .golden/shapedRuns/mixedDirectionComplexRTL.fontInfo +1/−0
- .golden/shapedRuns/mixedDirectionComplexRTL.golden +84/−0
- CHANGELOG.md +19/−0
- balkon.cabal +6/−2
- lib/Data/Text/ParagraphLayout/Plain.hs +1/−1
- lib/Data/Text/ParagraphLayout/Rich.hs +1/−1
- src/Data/Text/ParagraphLayout/Internal/BiDiLevels.hs +177/−0
- src/Data/Text/ParagraphLayout/Internal/BiDiReorder.hs +2/−15
- src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs +1/−1
- src/Data/Text/ParagraphLayout/Internal/Layout.hs +8/−5
- src/Data/Text/ParagraphLayout/Internal/ParagraphLine.hs +3/−3
- src/Data/Text/ParagraphLayout/Internal/Plain.hs +15/−2
- src/Data/Text/ParagraphLayout/Internal/ProtoFragment.hs +10/−13
- src/Data/Text/ParagraphLayout/Internal/ProtoRun.hs +15/−0
- src/Data/Text/ParagraphLayout/Internal/ResolvedSpan.hs +2/−0
- src/Data/Text/ParagraphLayout/Internal/Rich.hs +12/−1
- src/Data/Text/ParagraphLayout/Internal/Rich/Paragraph.hs +11/−1
- src/Data/Text/ParagraphLayout/Internal/Run.hs +38/−42
- src/Data/Text/ParagraphLayout/Internal/Script.hs +5/−3
- src/Data/Text/ParagraphLayout/Internal/TextOptions.hs +3/−1
- src/Data/Text/ParagraphLayout/Internal/WithSpan.hs +1/−1
- test/Data/Text/ParagraphLayout/Internal/ApplyBoxesSpec.hs +2/−0
- test/Data/Text/ParagraphLayout/Internal/BiDiLevelsSpec.hs +81/−0
- test/Data/Text/ParagraphLayout/Internal/BiDiReorderSpec.hs +1/−0
- test/Data/Text/ParagraphLayout/Internal/RunSpec.hs +237/−8
- test/Data/Text/ParagraphLayout/Rich/ParagraphData.hs +57/−1
- test/Data/Text/ParagraphLayout/RichSpec.hs +65/−0
- test/Data/Text/ParagraphLayout/RunLengthEncoding.hs +8/−0
- test/Data/Text/ParagraphLayout/SpanData.hs +0/−66
- test/Data/Text/ParagraphLayout/TextData.hs +110/−0
.golden/paginatedParagraphLayout/spannedArabicFiller20em.golden view
@@ -180,7 +180,7 @@ { fragmentUserData = () , fragmentLine = 2 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge} ] , fragmentRect = Rect {x_origin = 0, y_origin = -1500, x_size = 3213, y_size = -1500} , fragmentPen = (0, -1085)@@ -240,7 +240,7 @@ { fragmentUserData = () , fragmentLine = 3 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0} ] , fragmentRect = Rect {x_origin = 14300, y_origin = 0, x_size = 4862, y_size = -1500} , fragmentPen = (0, -1085)@@ -320,7 +320,7 @@ { fragmentUserData = () , fragmentLine = 3 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge} ] , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 3898, y_size = -1500} , fragmentPen = (0, -1085)@@ -339,7 +339,7 @@ { fragmentUserData = () , fragmentLine = 4 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0} ] , fragmentRect = Rect {x_origin = 12238, y_origin = -1500, x_size = 2357, y_size = -1500} , fragmentPen = (0, -1085)
.golden/paragraphLayout/arabicFiller20em.golden view
@@ -6,7 +6,7 @@ { fragmentUserData = () , fragmentLine = 1 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge} ] , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 19850, y_size = -1500} , fragmentPen = (0, -1085)@@ -184,7 +184,7 @@ { fragmentUserData = () , fragmentLine = 4 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0} ] , fragmentRect = Rect {x_origin = 0, y_origin = -4500, x_size = 14595, y_size = -1500} , fragmentPen = (0, -1085)
.golden/paragraphLayout/devanagariAccent.golden view
@@ -1,5 +1,5 @@ ParagraphLayout- { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 645, y_size = -1000}+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 645, y_size = -1630} , spanLayouts = [ SpanLayout [ Fragment@@ -8,8 +8,8 @@ , fragmentAncestorBoxes = [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = SpacedEdge 0} ]- , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 645, y_size = -1000}- , fragmentPen = (0, -500)+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 645, y_size = -1630}+ , fragmentPen = (0, -1171) , fragmentGlyphs = [ (GlyphInfo {codepoint = 529, cluster = 0, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 645, y_advance = 0, x_offset = 0, y_offset = 0}) , (GlyphInfo {codepoint = 424, cluster = 0, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 0, y_offset = 0})
.golden/paragraphLayout/devanagariPrefixedAccent.golden view
@@ -1,5 +1,5 @@ ParagraphLayout- { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 0, y_size = -1000}+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 0, y_size = -1630} , spanLayouts = [ SpanLayout [ Fragment@@ -8,8 +8,8 @@ , fragmentAncestorBoxes = [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = SpacedEdge 0} ]- , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 0, y_size = -1000}- , fragmentPen = (0, -500)+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 0, y_size = -1630}+ , fragmentPen = (0, -1171) , fragmentGlyphs = [ (GlyphInfo {codepoint = 424, cluster = 1, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 0, y_offset = 0}) ]
.golden/paragraphLayout/hardBreaksRTL.golden view
@@ -6,7 +6,7 @@ { fragmentUserData = () , fragmentLine = 1 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge} ] , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 2808, y_size = -1500} , fragmentPen = (0, -1085)@@ -137,7 +137,7 @@ { fragmentUserData = () , fragmentLine = 9 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0} ] , fragmentRect = Rect {x_origin = 0, y_origin = -12000, x_size = 1960, y_size = -1500} , fragmentPen = (0, -1085)
.golden/paragraphLayout/spannedArabicFiller20em.golden view
@@ -180,7 +180,7 @@ { fragmentUserData = () , fragmentLine = 2 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge} ] , fragmentRect = Rect {x_origin = 0, y_origin = -1500, x_size = 3213, y_size = -1500} , fragmentPen = (0, -1085)@@ -200,7 +200,7 @@ { fragmentUserData = () , fragmentLine = 3 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0} ] , fragmentRect = Rect {x_origin = 14300, y_origin = -3000, x_size = 4862, y_size = -1500} , fragmentPen = (0, -1085)@@ -280,7 +280,7 @@ { fragmentUserData = () , fragmentLine = 3 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge} ] , fragmentRect = Rect {x_origin = 0, y_origin = -3000, x_size = 3898, y_size = -1500} , fragmentPen = (0, -1085)@@ -299,7 +299,7 @@ { fragmentUserData = () , fragmentLine = 4 , fragmentAncestorBoxes =- [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0} ] , fragmentRect = Rect {x_origin = 12238, y_origin = -4500, x_size = 2357, y_size = -1500} , fragmentPen = (0, -1085)
+ .golden/richParagraphLayout/mixedDirectionComplexLTR.golden view
@@ -0,0 +1,147 @@+ParagraphLayout+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 24557, y_size = -1500}+ , paragraphFragments =+ [ Fragment+ { fragmentUserData = "text1"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 6732, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 90, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 8, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 10, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 11, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 6, cluster = 12, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 580, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 13, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 16, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 17, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 6, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 580, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 19, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 20, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 21, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 22, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 23, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 24, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text2"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ [ AncestorBox {boxUserData = "box1", boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ ]+ , fragmentRect = Rect {x_origin = 6732, y_origin = 0, x_size = 1580, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 25, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 26, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 27, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 28, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 29, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text2"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ [ AncestorBox {boxUserData = "box1", boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}+ ]+ , fragmentRect = Rect {x_origin = 8312, y_origin = 0, x_size = 3166, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 293, cluster = 41, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 270, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 389, cluster = 39, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 38, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 37, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 36, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 35, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 34, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 512, cluster = 32, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 839, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 286, cluster = 30, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 224, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text2"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ [ AncestorBox {boxUserData = "box1", boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ ]+ , fragmentRect = Rect {x_origin = 11478, y_origin = 0, x_size = 6578, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 43, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 44, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 45, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 46, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 47, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 26, cluster = 48, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 351, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 49, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 50, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 51, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 52, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 53, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 54, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 55, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 26, cluster = 56, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 351, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 57, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 58, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 59, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 60, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 61, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text3"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 18056, y_origin = 0, x_size = 1580, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 62, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 63, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 64, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 65, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 66, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text3"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 19636, y_origin = 0, x_size = 3577, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 517, cluster = 78, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 839, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 955, cluster = 76, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 455, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 75, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 74, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 73, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 72, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 71, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 971, cluster = 69, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 450, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 394, cluster = 67, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text3"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 23213, y_origin = 0, x_size = 1344, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 80, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 81, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 82, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 83, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ ]+ }
+ .golden/richParagraphLayout/mixedDirectionComplexRTL.golden view
@@ -0,0 +1,147 @@+ParagraphLayout+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 24557, y_size = -1500}+ , paragraphFragments =+ [ Fragment+ { fragmentUserData = "text3"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 6501, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 147, cluster = 83, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 82, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 81, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 80, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 517, cluster = 78, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 839, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 955, cluster = 76, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 455, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 75, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 74, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 73, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 72, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 71, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 971, cluster = 69, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 450, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 394, cluster = 67, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 66, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 65, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 64, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 63, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 62, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text2"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ [ AncestorBox {boxUserData = "box1", boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}+ ]+ , fragmentRect = Rect {x_origin = 6501, y_origin = 0, x_size = 1580, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 61, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 60, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 59, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 58, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 57, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text2"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ [ AncestorBox {boxUserData = "box1", boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}+ ]+ , fragmentRect = Rect {x_origin = 8081, y_origin = 0, x_size = 3418, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 26, cluster = 48, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 351, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 49, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 50, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 51, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 52, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 53, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 54, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 55, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 26, cluster = 56, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 351, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text2"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ [ AncestorBox {boxUserData = "box1", boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}+ ]+ , fragmentRect = Rect {x_origin = 11499, y_origin = 0, x_size = 6326, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 47, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 46, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 45, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 44, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 43, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 293, cluster = 41, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 270, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 389, cluster = 39, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 253, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 38, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 37, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 36, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 35, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 34, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 512, cluster = 32, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 839, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 286, cluster = 30, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 224, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 29, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 28, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 27, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 26, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 25, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text1"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 17825, y_origin = 0, x_size = 1580, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 24, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 23, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 22, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 21, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 20, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text1"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 19405, y_origin = 0, x_size = 3808, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 4, cluster = 11, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 6, cluster = 12, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 580, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 13, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 16, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 17, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 6, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 580, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 19, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text1"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 23213, y_origin = 0, x_size = 1344, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 3, cluster = 10, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 8, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 236, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 272, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ ]+ }
+ .golden/richParagraphLayout/mixedDirectionSimpleLTR.golden view
@@ -0,0 +1,50 @@+ParagraphLayout+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 7954, y_size = -1500}+ , paragraphFragments =+ [ Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 3397, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 6, cluster = 0, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 580, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 1, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 14, cluster = 2, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 24, cluster = 3, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 363, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 4, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 15, cluster = 5, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 250, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 20, cluster = 6, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 3397, y_origin = 0, x_size = 1578, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 1143, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 1095, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 920, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 483, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 4975, y_origin = 0, x_size = 2979, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 17, cluster = 13, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 517, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 29, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 759, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 16, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 15, cluster = 17, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 250, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 26, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 351, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ ]+ }
+ .golden/richParagraphLayout/mixedDirectionSimpleRTL.golden view
@@ -0,0 +1,50 @@+ParagraphLayout+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 7954, y_size = -1500}+ , paragraphFragments =+ [ Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 2979, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 17, cluster = 13, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 517, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 29, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 759, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 16, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 15, cluster = 17, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 250, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 26, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 351, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 2979, y_origin = 0, x_size = 1578, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 1143, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 1095, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 920, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 483, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ , Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 4557, y_origin = 0, x_size = 3397, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 6, cluster = 0, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 580, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 1, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 14, cluster = 2, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 24, cluster = 3, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 363, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 4, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 534, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 15, cluster = 5, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 250, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 20, cluster = 6, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 568, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ ]+ }
+ .golden/richParagraphLayout/neutralDirectionLTR.golden view
@@ -0,0 +1,18 @@+ParagraphLayout+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 2003, y_size = -1500}+ , paragraphFragments =+ [ Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 2003, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 91, cluster = 3, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 803, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 1392, cluster = 6, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ ]+ }
+ .golden/richParagraphLayout/neutralDirectionRTL.golden view
@@ -0,0 +1,18 @@+ParagraphLayout+ { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 2003, y_size = -1500}+ , paragraphFragments =+ [ Fragment+ { fragmentUserData = "text"+ , fragmentLine = 1+ , fragmentAncestorBoxes =+ []+ , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 2003, y_size = -1500}+ , fragmentPen = (0, -1085)+ , fragmentGlyphs =+ [ (GlyphInfo {codepoint = 147, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 1392, cluster = 6, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 600, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 91, cluster = 3, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 803, y_advance = 0, x_offset = 0, y_offset = 0})+ ]+ }+ ]+ }
+ .golden/shapedRuns/mixedDirectionComplexRTL.fontInfo view
@@ -0,0 +1,1 @@+assets/fonts/plex/IBMPlexSansArabic-Regular.ttf 0 32,32 32,32
+ .golden/shapedRuns/mixedDirectionComplexRTL.golden view
@@ -0,0 +1,84 @@+[ (0, -35, + [ (GlyphInfo {codepoint = 147, cluster = 83, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 82, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 81, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 80, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 517, cluster = 78, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 27, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 955, cluster = 76, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 15, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 75, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 74, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 73, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 72, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 71, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 971, cluster = 69, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 14, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 394, cluster = 67, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 66, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 65, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 64, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 63, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 62, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ ])+, (212, -35, + [ (GlyphInfo {codepoint = 3, cluster = 61, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 60, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 59, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 58, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 57, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ ])+, (264, -35, + [ (GlyphInfo {codepoint = 26, cluster = 48, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 11, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 49, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 18, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 50, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 51, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 52, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 53, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 54, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 27, cluster = 55, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 18, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 26, cluster = 56, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 11, y_advance = 0, x_offset = 0, y_offset = 0})+ ])+, (374, -35, + [ (GlyphInfo {codepoint = 3, cluster = 47, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 46, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 45, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 44, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 43, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 293, cluster = 41, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 389, cluster = 39, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 38, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 37, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 36, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 35, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 34, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 512, cluster = 32, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 27, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 286, cluster = 30, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 7, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 29, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 28, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 27, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 26, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 25, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ ])+, (581, -35, + [ (GlyphInfo {codepoint = 3, cluster = 24, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 23, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 22, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 21, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 20, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ ])+, (633, -35, + [ (GlyphInfo {codepoint = 4, cluster = 11, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 17, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 6, cluster = 12, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 13, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 14, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 148, cluster = 16, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 17, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 6, cluster = 18, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 4, cluster = 19, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 17, y_advance = 0, x_offset = 0, y_offset = 0})+ ])+, (757, -35, + [ (GlyphInfo {codepoint = 3, cluster = 10, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 147, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 19, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 3, cluster = 8, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 8, y_advance = 0, x_offset = 0, y_offset = 0})+ , (GlyphInfo {codepoint = 90, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 9, y_advance = 0, x_offset = 0, y_offset = 0})+ ])+]
CHANGELOG.md view
@@ -1,5 +1,24 @@ # Revision history for Balkón +## 1.1.0.0 -- 2023-05-31++* Updated support for bidirectional text:++ * Implemented correct order of directional runs for paragraphs whose+ base direction is RTL.++ * Numbers now create an implicit LTR embedding level.++ * The direction of weak directional characters and neutral characters is+ now determined by the embedding level of surrounding characters, or if+ that does not match, by the base paragraph direction.++ * For the legacy plain text interface, base paragraph direction is now set+ according to the first strong character.++ * Shaping uses the base paragraph direction when no strongly directional+ characters are present.+ ## 1.0.0.1 -- 2023-05-18 * Added another test with "shaped runs" format to allow manual testing of
balkon.cabal view
@@ -14,7 +14,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 1.0.0.1+version: 1.1.0.0 stability: experimental @@ -106,6 +106,7 @@ exposed-modules: Data.Text.ParagraphLayout.Internal.AncestorBox, Data.Text.ParagraphLayout.Internal.ApplyBoxes,+ Data.Text.ParagraphLayout.Internal.BiDiLevels, Data.Text.ParagraphLayout.Internal.BiDiReorder, Data.Text.ParagraphLayout.Internal.BoxOptions, Data.Text.ParagraphLayout.Internal.Break,@@ -139,6 +140,7 @@ Data.Text.ParagraphLayout.Internal.ParagraphLine, Data.Text.ParagraphLayout.Internal.ProtoFragment, Data.Text.ParagraphLayout.Internal.ProtoLine,+ Data.Text.ParagraphLayout.Internal.ProtoRun, Data.Text.ParagraphLayout.Internal.Script build-depends:@@ -182,6 +184,7 @@ other-modules: Data.Text.ParagraphLayout.FontLoader, Data.Text.ParagraphLayout.Internal.ApplyBoxesSpec,+ Data.Text.ParagraphLayout.Internal.BiDiLevelsSpec, Data.Text.ParagraphLayout.Internal.BiDiReorderSpec, Data.Text.ParagraphLayout.Internal.BreakSpec, Data.Text.ParagraphLayout.Internal.LinePaginationSpec,@@ -196,7 +199,8 @@ Data.Text.ParagraphLayout.RectSpec, Data.Text.ParagraphLayout.Rich.ParagraphData, Data.Text.ParagraphLayout.RichSpec,- Data.Text.ParagraphLayout.SpanData+ Data.Text.ParagraphLayout.RunLengthEncoding,+ Data.Text.ParagraphLayout.TextData -- Test dependencies. build-depends:
lib/Data/Text/ParagraphLayout/Plain.hs view
@@ -4,7 +4,7 @@ -- the entire paragraph. As a consequence, all lines will have the same height. module Data.Text.ParagraphLayout.Plain {-# DEPRECATED "Use \"Data.Text.ParagraphLayout.Rich\" instead." #-}- -- Input paragraph+ -- * Input paragraph ( Paragraph (Paragraph) , LineHeight (Absolute, Normal) , ParagraphOptions
lib/Data/Text/ParagraphLayout/Rich.hs view
@@ -2,7 +2,7 @@ -- -- The main entry point is the function `layoutRich`. module Data.Text.ParagraphLayout.Rich- -- Input paragraph+ -- * Input paragraph ( Paragraph (Paragraph) , constructParagraph , BoxSpacing (BoxSpacingLeftRight)
+ src/Data/Text/ParagraphLayout/Internal/BiDiLevels.hs view
@@ -0,0 +1,177 @@+module Data.Text.ParagraphLayout.Internal.BiDiLevels+ ( Level+ , TextLevels (TextLevels)+ , WithLevel+ , dropLevels+ , firstStrongDirection+ , headLevel+ , level+ , levelDirectionH+ , tailLevels+ , textLevels+ )+where++import Data.Maybe (catMaybes, listToMaybe)+import Data.Text (Text, uncons, unpack)+import Data.Text.Glyphize (Direction (..))+import qualified Data.Text.ICU.Char as ICUChar+import Data.Word (Word8)++-- | BiDi level, between 0 and 125 inclusive.+-- Even values mean left-to-right text.+-- Odd values mean right-to-left text.+type Level = Word8++-- | Typeclass for any data structure with an associated BiDi level.+class WithLevel a where+ level :: a -> Level++-- | BiDi levels for each character from a given input text.+--+-- This wrapper is meant to ease the transition to a different internal+-- representation, if required when integrating with the ICU.+newtype TextLevels = TextLevels [Level]+ deriving (Eq, Show)++headLevel :: TextLevels -> Level+headLevel (TextLevels xs) = head xs++tailLevels :: TextLevels -> TextLevels+tailLevels (TextLevels xs) = TextLevels (tail xs)++dropLevels :: Int -> TextLevels -> TextLevels+dropLevels n (TextLevels xs) = TextLevels (drop n xs)++-- | Determine the BiDi level of each character in the input text+-- using a simplified algorithm with the following main limitations:+--+-- - Explicit directional formatting characters are ignored.+-- This, by extension, means that levels cannot be controlled manually.+--+-- - Arabic and European numbers are treated equally and do not absorb+-- separators, terminators, or nonspacing marks.+--+-- - Paired brackets have no effect on text direction.+--+-- TODO: Use Haskell bindings to the ICU BiDi implementation once available.+textLevels :: Direction -> Text -> TextLevels+textLevels baseDir txt = TextLevels $ textLevels' baseLevel baseLevel 0 txt+ where+ baseLevel = directionLevel 0 baseDir++-- | Determine the BiDi level of each character in the input text+-- using a simplified algorithm.+textLevels' :: Level -> Level -> Int -> Text -> [Level]+textLevels' baseLevel previousLevel neutrals txt = case uncons txt of+ Just (char, rest) -> case charLevel baseLevel char of+ Just currentLevel ->+ replicate neutrals (mergeLevel baseLevel previousLevel currentLevel)+ +++ currentLevel+ :+ textLevels' baseLevel currentLevel 0 rest+ Nothing ->+ textLevels' baseLevel previousLevel (neutrals + 1) rest+ Nothing ->+ replicate neutrals baseLevel++-- | Determine the level of a neutral character based on its surrounding levels.+mergeLevel :: Level -> Level -> Level -> Level+mergeLevel base prev cur+ | prev == cur+ = prev+ | low > base && even high -- Avoid breaking a RTL run by numbers.+ = low+ | otherwise+ = base+ where+ low = min prev cur+ high = max prev cur++data SimpleType = StrongLTR | StrongRTL | Number | Neutral | Reset++-- | Determine the BiDi level of one character+-- using a simplified algorithm.+charLevel :: Level -> Char -> Maybe Level+charLevel baseLevel c = case simpleType c of+ StrongLTR -> Just $ directionLevel baseLevel DirLTR+ StrongRTL -> Just $ directionLevel baseLevel DirRTL+ Number -> Just $ directionLevel (baseLevel + 1) DirLTR+ Neutral -> Nothing+ Reset -> Just baseLevel++-- | Reduce the character's BiDi class into a simpler category+-- for the purposes of the simplified algorithm.+simpleType :: Char -> SimpleType+simpleType c = case ICUChar.direction c of+ -- Strong characters have a definitive type.+ ICUChar.LeftToRight -> StrongLTR+ ICUChar.RightToLeft -> StrongRTL+ ICUChar.RightToLeftArabic -> StrongRTL+ -- Ignoring separators and terminators, all numbers behave the same.+ ICUChar.EuropeanNumber -> Number+ ICUChar.ArabicNumber -> Number+ -- Treating the following weak characters as neutral for simplicity.+ ICUChar.EuropeanNumberSeparator -> Neutral+ ICUChar.EuropeanNumberTerminator -> Neutral+ ICUChar.CommonNumberSeparator -> Neutral+ ICUChar.DirNonSpacingMark -> Neutral+ ICUChar.BoundaryNeutral -> Neutral+ -- The following characters are always neutral.+ ICUChar.WhiteSpaceNeutral -> Neutral+ ICUChar.OtherNeutral -> Neutral+ -- The following characters get their level reset.+ ICUChar.BlockSeparator -> Reset+ ICUChar.SegmentSeparator -> Reset+ -- Explicit formatting is not handled by this algorithm.+ ICUChar.LeftToRightEmbedding -> Neutral+ ICUChar.LeftToRightOverride -> Neutral+ ICUChar.RightToLeftEmbedding -> Neutral+ ICUChar.RightToLeftOverride -> Neutral+ ICUChar.PopDirectionalFormat -> Neutral+ ICUChar.FirstStrongIsolate -> Neutral+ ICUChar.LeftToRightIsolate -> Neutral+ ICUChar.RightToLeftIsolate -> Neutral+ ICUChar.PopDirectionalIsolate -> Neutral++-- | `Just` the direction of the first strongly directional character,+-- or `Nothing` if there is no strongly directional character.+firstStrongDirection :: Text -> Maybe Direction+firstStrongDirection = firstJust . map strongDirection . unpack++-- | `Just` the direction of a strongly directional character,+-- or `Nothing` if the character is not strongly directional.+strongDirection :: Char -> Maybe Direction+strongDirection c = case ICUChar.direction c of+ ICUChar.LeftToRight -> Just DirLTR+ ICUChar.RightToLeft -> Just DirRTL+ ICUChar.RightToLeftArabic -> Just DirRTL+ _ -> Nothing++-- | The first `Just` value found in the input list, if there is one,+-- otherwise `Nothing`.+firstJust :: [Maybe a] -> Maybe a+firstJust = listToMaybe . catMaybes++-- | Convert embedding level to horizontal text direction.+levelDirectionH :: Level -> Direction+levelDirectionH lvl+ | even lvl = DirLTR+ | otherwise = DirRTL++-- | Convert text direction to the smallest corresponding embedding level,+-- but no smaller than the given minimum.+directionLevel :: Level -> Direction -> Level+directionLevel low DirLTR = smallestEvenAtLeast low+directionLevel low DirRTL = smallestOddAtLeast low+directionLevel low DirTTB = smallestEvenAtLeast low+directionLevel low DirBTT = smallestOddAtLeast low++-- | Smallest even integer greater than or equal to @x@.+smallestEvenAtLeast :: Integral a => a -> a+smallestEvenAtLeast x = if even x then x else x + 1++-- | Smallest odd integer greater than or equal to @x@.+smallestOddAtLeast :: Integral a => a -> a+smallestOddAtLeast x = if odd x then x else x + 1
src/Data/Text/ParagraphLayout/Internal/BiDiReorder.hs view
@@ -1,24 +1,11 @@-module Data.Text.ParagraphLayout.Internal.BiDiReorder- ( Level- , WithLevel- , level- , reorder- )+module Data.Text.ParagraphLayout.Internal.BiDiReorder (reorder) where import Data.List.NonEmpty (NonEmpty ((:|))) import qualified Data.List.NonEmpty as NonEmpty import Data.Semigroup (sconcat)-import Data.Word (Word8) --- | BiDi level, between 0 and 125 inclusive.--- Even values mean left-to-right text.--- Odd values mean right-to-left text.-type Level = Word8---- | Typeclass for any data structure with an associated BiDi level.-class WithLevel a where- level :: a -> Level+import Data.Text.ParagraphLayout.Internal.BiDiLevels -- | Generic reordering of bidirectional text according to rule L2 of UAX #9 -- <https://www.unicode.org/reports/tr9/#Reordering_Resolved_Levels>.
src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs view
@@ -15,7 +15,7 @@ data BoxOptions = BoxOptions { boxSpacing :: BoxSpacing- -- Determines amount of empty space to add before the first fragment+ -- ^ Determines amount of empty space to add before the first fragment -- and/or after the last fragment of this box. -- -- This can be used to reserve space for a left margin, border,
src/Data/Text/ParagraphLayout/Internal/Layout.hs view
@@ -137,7 +137,7 @@ normalLineHeight = ascent + descent ascent = ascender extents descent = - descender extents- extents = fontExtentsForDir (textFont opts) (PF.direction pf)+ extents = fontExtentsForDir (textFont opts) (Just $ PF.direction pf) opts = RS.spanTextOptions rs ancestorBoxes@@ -194,10 +194,12 @@ -- TODO: Consider optimising. -- We do not need to look for soft breaks further than the -- shortest hard break.+ -- TODO: Untrimmed whitespace should be reset to paragraph BiDi level+ -- per rule L1. splits = hardSplit runs :| softSplits runs --- | Construct a `ProtoLine`, peeking at the text run on the following line--- to determine `nextOpenBoxes`.+-- | Construct a `PL.ProtoLine`, peeking at the text run on the following line+-- to determine `PL.nextOpenBoxes`. protoLine :: [RB.ResolvedBox d] -> [ProtoFragmentWithSpan d]@@ -295,9 +297,10 @@ layoutRunH :: WithSpan d Run -> ProtoFragmentWithSpan d layoutRunH (WithSpan rs run) = WithSpan rs pf where- pf = PF.protoFragmentH dir glyphs+ pf = PF.protoFragmentH dir lvl glyphs glyphs = shapeRun (WithSpan rs run) dir = runDirection run+ lvl = runLevel run -- | Calculate layout for the given run independently of its position. shapeRun :: WithSpan d Run -> [(GlyphInfo, GlyphPos)]@@ -307,7 +310,7 @@ buffer = defaultBuffer { text = Lazy.fromStrict $ runText run , contentType = Just ContentTypeUnicode- , direction = runDirection run+ , direction = Just $ runDirection run , script = runScript run , language = Just $ textLanguage opts -- Perhaps counter-intuitively, the `beginsText` and `endsText`
src/Data/Text/ParagraphLayout/Internal/ParagraphLine.hs view
@@ -56,11 +56,11 @@ shiftFragments dy = R.mapFragments (shiftFragment dy) appendFragments = R.appendFragments --- | Split the given `ParagraphLayout` into single-line layouts.+-- | Split the given paragraph layout into single-line layouts. cutLines :: GenericLayout pl => pl -> [pl] cutLines pl = map (\ n -> cutLine n pl) (lineNumbers pl) --- | Reduce the given `ParagraphLayout` to fragments with the given line number.+-- | Reduce the given paragraph layout to fragments with the given line number. cutLine :: GenericLayout pl => Int -> pl -> pl cutLine n pl = trimTop $ limitFragments n pl @@ -78,7 +78,7 @@ dedupe :: Eq a => [a] -> [a] dedupe xs = map NonEmpty.head $ NonEmpty.group xs --- | Put the given `ParagraphLayout`s together as a vertically contiguous+-- | Put the given paragraph layouts together as a vertically contiguous -- sequence. mergeLines :: GenericLayout pl => [pl] -> pl mergeLines lls = foldl mergeLine empty lls
src/Data/Text/ParagraphLayout/Internal/Plain.hs view
@@ -5,6 +5,7 @@ import Data.Text.Glyphize (Direction (DirLTR)) import Data.Text.ParagraphLayout.Internal.AncestorBox+import Data.Text.ParagraphLayout.Internal.BiDiLevels import Data.Text.ParagraphLayout.Internal.BoxOptions import Data.Text.ParagraphLayout.Internal.Fragment import Data.Text.ParagraphLayout.Internal.ParagraphOptions@@ -29,7 +30,8 @@ -- Span indexes are added to the user data internally, then used to split the -- resulting fragments according to their corresponding spans. plainToRich :: P.Paragraph d -> R.Paragraph (Int, d)-plainToRich (P.Paragraph arr off spans opts) = R.Paragraph arr off rootNode opts+plainToRich p@(P.Paragraph arr off spans opts) =+ R.Paragraph arr off rootNode opts where rootNode = RootBox rootBox rootBox = Box spanNodes baseOpts@@ -39,10 +41,21 @@ (boxFromPlain baseOpts i s) defaultBoxOptions indexedSpans = zip [0 ..] spans- baseOpts = (defaultTextOptions DirLTR)+ baseOpts = (defaultTextOptions $ detectDirection p) { textFont = paragraphFont opts , textLineHeight = paragraphLineHeight opts }++-- | Simplified autodetection of text direction for plain text,+-- to provide temporary compatibility with the old interface which+-- did not allow setting text direction explicitly.+--+-- Note that the detected direction carries over hard line breaks,+-- which is not compliant with the Unicode Bidirectional Algorithm.+detectDirection :: P.Paragraph d -> Direction+detectDirection p = case firstStrongDirection (P.paragraphText p) of+ Just dir -> dir+ Nothing -> DirLTR -- | Convert a legacy `Span` to a rich text box with one text node inside. --
src/Data/Text/ParagraphLayout/Internal/ProtoFragment.hs view
@@ -7,12 +7,15 @@ import Data.Int (Int32) import Data.Text.Glyphize (Direction (..), GlyphInfo, GlyphPos (x_advance)) -import Data.Text.ParagraphLayout.Internal.BiDiReorder+import qualified Data.Text.ParagraphLayout.Internal.BiDiLevels as BiDi -- | A box fragment which has not been positioned yet. data ProtoFragment = ProtoFragment- { direction :: Maybe Direction+ { direction :: Direction -- ^ Text direction, which is constant within a fragment.+ , level :: BiDi.Level+ -- ^ BiDi embedding level.+ -- Should be even for LTR fragments and odd for RTL fragments. , advance :: Int32 -- ^ Total advance of glyphs in this fragment, -- depending on the text direction.@@ -21,17 +24,11 @@ -- | Construct a `ProtoFragment`, automatically calculating the total advance -- for a horizontal text direction.-protoFragmentH :: Maybe Direction -> [(GlyphInfo, GlyphPos)] -> ProtoFragment-protoFragmentH dir gs = ProtoFragment dir adv gs+protoFragmentH :: Direction -> BiDi.Level -> [(GlyphInfo, GlyphPos)] ->+ ProtoFragment+protoFragmentH dir lvl gs = ProtoFragment dir lvl adv gs where adv = sum $ map (x_advance . snd) gs -instance WithLevel ProtoFragment where- level pf = case direction pf of- -- TODO: Allow externally set paragraph embedding level.- -- TODO: Properly calculate BiDi levels.- Just DirLTR -> 0- Just DirRTL -> 1- Just DirTTB -> 0- Just DirBTT -> 1- Nothing -> 0+instance BiDi.WithLevel ProtoFragment where+ level = level -- BiDi.level = ProtoFragment.level
+ src/Data/Text/ParagraphLayout/Internal/ProtoRun.hs view
@@ -0,0 +1,15 @@+module Data.Text.ParagraphLayout.Internal.ProtoRun (ProtoRun (..))+where++import Data.Text.ParagraphLayout.Internal.BiDiLevels+import Data.Text.ParagraphLayout.Internal.Script+import Data.Text.ParagraphLayout.Internal.Zipper++-- | Intermediate structure for creating+-- `Data.Text.ParagraphLayout.Internal.Run.Run` values.+data ProtoRun = ProtoRun+ { zipper :: Zipper+ , followingLevels :: TextLevels+ , level :: Level+ , script :: ScriptCode+ }
src/Data/Text/ParagraphLayout/Internal/ResolvedSpan.hs view
@@ -4,6 +4,7 @@ import Data.Text (Text) import qualified Data.Text.ICU as BreakStatus (Line) +import Data.Text.ParagraphLayout.Internal.BiDiLevels import Data.Text.ParagraphLayout.Internal.ResolvedBox import Data.Text.ParagraphLayout.Internal.TextContainer import Data.Text.ParagraphLayout.Internal.TextOptions@@ -17,6 +18,7 @@ , spanText :: Text , spanTextOptions :: TextOptions , spanBoxes :: [ResolvedBox d]+ , spanBiDiLevels :: TextLevels , spanLineBreaks :: [(Int, BreakStatus.Line)] -- TODO: Can be optimised by starting with the shortest line break. , spanCharacterBreaks :: [(Int, ())]
src/Data/Text/ParagraphLayout/Internal/Rich.hs view
@@ -5,8 +5,10 @@ import Data.List.NonEmpty (nonEmpty) import qualified Data.List.NonEmpty as NonEmpty import Data.Text (Text)+import qualified Data.Text as Text import Data.Text.ICU (Breaker, LocaleName, breakCharacter, breakLine) +import Data.Text.ParagraphLayout.Internal.BiDiLevels import Data.Text.ParagraphLayout.Internal.Break import Data.Text.ParagraphLayout.Internal.Fragment import Data.Text.ParagraphLayout.Internal.Layout@@ -43,7 +45,9 @@ spanToRunsWrapped s = map (WithSpan s) (spanToRuns s) resolveSpans :: Paragraph d -> [RS.ResolvedSpan d]-resolveSpans p@(Paragraph _ pStart root _) = do+resolveSpans p = do+ let Paragraph _ pStart root _ = p+ let RootBox (Box _ rootTextOpts) = root let leaves = flatten root let sTexts = paragraphSpanTexts p let sBounds = paragraphSpanBounds p@@ -57,8 +61,14 @@ <*> ZipList sTexts let (TextLeaf userData _ textOpts boxes) = leaf let lang = textLanguage textOpts+ -- TODO: Allow BiDi embedding/isolation for inner nodes.+ let pLevels = textLevels (textDirection rootTextOpts) pText let lBreaks = paragraphBreaks breakLine pText lang let cBreaks = paragraphBreaks breakCharacter pText lang+ -- TODO: Optimise. This has time complexity O(n*s), where n is number of+ -- characters and s is number of resolved spans.+ -- Maybe include byte offsets in the TextLevels data structure?+ let pPrefixLen = Text.length $ paragraphPrefix p sStart return RS.ResolvedSpan { RS.spanUserData = userData , RS.spanIndex = i@@ -67,6 +77,7 @@ , RS.spanText = sText , RS.spanTextOptions = textOpts , RS.spanBoxes = boxes+ , RS.spanBiDiLevels = dropLevels pPrefixLen pLevels , RS.spanLineBreaks = subOffsetsDesc (sStart - pStart) lBreaks , RS.spanCharacterBreaks = subOffsetsDesc (sStart - pStart) cBreaks }
src/Data/Text/ParagraphLayout/Internal/Rich/Paragraph.hs view
@@ -1,6 +1,7 @@ module Data.Text.ParagraphLayout.Internal.Rich.Paragraph ( Paragraph (..) , constructParagraph+ , paragraphPrefix , paragraphSpanBounds , paragraphSpanTexts , paragraphText@@ -68,7 +69,8 @@ -> ParagraphOptions -- ^ Options to apply to the paragraph as a whole. -> Paragraph d- -- ^ Constructed paragraph that can be passed to `layoutRich`.+ -- ^ Constructed paragraph that can be passed to+ -- `Data.Text.ParagraphLayout.Rich.layoutRich`. constructParagraph prefix root suffix = Paragraph arr afterPrefix root' where (Text arr beforePrefix _) = txt@@ -109,4 +111,12 @@ where start = NonEmpty.head sBounds end = NonEmpty.last sBounds+ sBounds = paragraphSpanBounds p++-- | Turn all text nodes from the input `Paragraph` up to the given end offset+-- into one combined `Text`. No bounds checking is performed!+paragraphPrefix :: Paragraph d -> Int -> Text+paragraphPrefix p@(Paragraph arr _ _ _) end = Text arr start (end - start)+ where+ start = NonEmpty.head sBounds sBounds = paragraphSpanBounds p
src/Data/Text/ParagraphLayout/Internal/Run.hs view
@@ -1,4 +1,4 @@-module Data.Text.ParagraphLayout.Internal.Run (Run (..), ScriptCode, spanToRuns)+module Data.Text.ParagraphLayout.Internal.Run (Run (..), spanToRuns) where import Data.List (mapAccumL)@@ -7,22 +7,23 @@ import qualified Data.Text as Text import Data.Text.Foreign (dropWord8, lengthWord8, takeWord8) import Data.Text.Glyphize (Direction (..))-import qualified Data.Text.ICU.Char as ICUChar +import Data.Text.ParagraphLayout.Internal.BiDiLevels+import Data.Text.ParagraphLayout.Internal.ProtoRun (ProtoRun (ProtoRun))+import qualified Data.Text.ParagraphLayout.Internal.ProtoRun as PR import Data.Text.ParagraphLayout.Internal.ResolvedSpan import Data.Text.ParagraphLayout.Internal.Script import Data.Text.ParagraphLayout.Internal.TextContainer import Data.Text.ParagraphLayout.Internal.Zipper -type ScriptCode = String- -- | Each span can be broken into one or more runs by Balkón. -- -- Each run could have a different script, language, or direction. data Run = Run { runOffsetInSpan :: Int , runText :: Text- , runDirection :: Maybe Direction+ , runLevel :: Level+ , runDirection :: Direction , runScript :: Maybe ScriptCode } deriving (Eq, Show)@@ -50,8 +51,6 @@ o' = o + l - l' dropWhileEnd p r = r { runText = Text.dropWhileEnd p (runText r) } -type ProtoRun = (Zipper, Maybe Direction, ScriptCode)- -- | Represents a zipper that can advance by at least one character. data ZipperChoice = ZipperChoice { nextChar :: Char@@ -75,53 +74,58 @@ data Merged a = Incompatible | Merged a spanToRuns :: ResolvedSpan d -> [Run]-spanToRuns s = snd $ mapAccumL run 0 $ protoRuns zipper+spanToRuns s = snd $ mapAccumL run 0 $ protoRuns zipper levels where- zipper = start $ spanText s- run acc (z, d, sc) = let t = preceding z in+ wholeText = spanText s+ zipper = start wholeText+ levels = spanBiDiLevels s+ run acc pr = let t = preceding (PR.zipper pr) in ( acc + lengthWord8 t , Run { runOffsetInSpan = acc , runText = t- , runDirection = d- , runScript = Just sc+ , runLevel = PR.level pr+ , runDirection = levelDirectionH $ PR.level pr+ , runScript = Just $ PR.script pr } ) -protoRuns :: Zipper -> [ProtoRun]-protoRuns z = reverse $ protoRuns' z []+protoRuns :: Zipper -> TextLevels -> [ProtoRun]+protoRuns z bl = reverse $ protoRuns' z bl [] -protoRuns' :: Zipper -> [ProtoRun] -> [ProtoRun]-protoRuns' curZipper curRuns = case considerNext curZipper of+protoRuns' :: Zipper -> TextLevels -> [ProtoRun] -> [ProtoRun]+protoRuns' curZipper curLevels curRuns = case considerNext curZipper of Nothing -> curRuns Just choice ->- let headRun@(nextZipper, _, _) :| tailRuns = foldRun choice curRuns- in protoRuns' nextZipper (headRun : tailRuns)+ let headRun :| tailRuns = foldRun (choice, curLevels) curRuns+ in protoRuns' (PR.zipper headRun) curLevels (headRun : tailRuns) -foldRun :: ZipperChoice -> [ProtoRun] -> NonEmpty ProtoRun+foldRun :: (ZipperChoice, TextLevels) -> [ProtoRun] -> NonEmpty ProtoRun -- If there are no runs, create a new run with a single character.-foldRun x [] = (continuingRun x, d, s) :| []+foldRun (x, bl) [] = ProtoRun (continuingRun x) lvls lvl s :| [] where- d = charDirection (nextChar x)+ lvl = headLevel bl+ lvls = tailLevels bl s = charScript (nextChar x) -foldRun x (previousRun@(_, d1, s1) : tailRuns) =- case (mergeDirections d1 d2, mergeScripts s1 s2) of- (Merged d, Merged s) -> (continuingRun x, d, s) :| tailRuns- _ -> (startingNewRun x, d2, s2) :| previousRun : tailRuns+foldRun (x, _) (previousRun : tailRuns) =+ case (mergeLevels l1 l2, mergeScripts s1 s2) of+ (Merged l, Merged s) ->+ ProtoRun (continuingRun x) bl' l s :| tailRuns+ _ ->+ ProtoRun (startingNewRun x) bl' l2 s2 :| previousRun : tailRuns where- d2 = charDirection (nextChar x)+ bl = PR.followingLevels previousRun+ l1 = PR.level previousRun+ s1 = PR.script previousRun+ l2 = headLevel bl s2 = charScript (nextChar x)+ bl' = tailLevels bl --- | Simplified detection of text direction for unidirectional text.-mergeDirections :: Maybe Direction -> Maybe Direction ->- Merged (Maybe Direction)-mergeDirections Nothing Nothing = Merged Nothing-mergeDirections (Just d1) Nothing = Merged (Just d1)-mergeDirections Nothing (Just d2) = Merged (Just d2)-mergeDirections (Just d1) (Just d2)- | d1 == d2 = Merged (Just d1)+mergeLevels :: Level -> Level -> Merged Level+mergeLevels l1 l2+ | l1 == l2 = Merged l1 | otherwise = Incompatible -- TODO: Implement proper inheritance rules.@@ -132,11 +136,3 @@ mergeScripts s1 s2 | s1 == s2 = Merged s1 | otherwise = Incompatible---- TODO: Use the BiDi algorithm to support bidirectional text.-charDirection :: Char -> Maybe Direction-charDirection c = case ICUChar.direction c of- ICUChar.LeftToRight -> Just DirLTR- ICUChar.RightToLeft -> Just DirRTL- ICUChar.RightToLeftArabic -> Just DirRTL- _ -> Nothing
src/Data/Text/ParagraphLayout/Internal/Script.hs view
@@ -1,4 +1,4 @@-module Data.Text.ParagraphLayout.Internal.Script (charScript)+module Data.Text.ParagraphLayout.Internal.Script (ScriptCode, charScript) where -- TODO: Use a direct interface to the ICU library, if possible.@@ -6,12 +6,14 @@ -- hardcoded ranges, which may get out of sync with ICU. import Unicode.Char.General.Scripts +type ScriptCode = String+ -- | A short script code for the given character, as expected by HarfBuzz.-charScript :: Char -> String+charScript :: Char -> ScriptCode charScript = code . script -- Hardcoded shortenings of script codes as of Unicode 15.0.0.-code :: Script -> String+code :: Script -> ScriptCode code Adlam = "Adlm" code Ahom = "Ahom" code AnatolianHieroglyphs = "Hluw"
src/Data/Text/ParagraphLayout/Internal/TextOptions.hs view
@@ -33,10 +33,12 @@ , textDirection :: Direction -- ^ Base text direction.+ -- -- Used to determine which box fragment gets the left spacing and which -- gets the right spacing when broken over multiple lines. --- -- TODO: Also use for the visual order of boxes.+ -- When applied to the root box in a paragraph, this is also used to+ -- determine the base direction of the paragraph. -- TODO: textVerticalAlign -- TODO: textLetterSpacing
src/Data/Text/ParagraphLayout/Internal/WithSpan.hs view
@@ -4,7 +4,7 @@ ) where -import Data.Text.ParagraphLayout.Internal.BiDiReorder+import Data.Text.ParagraphLayout.Internal.BiDiLevels import Data.Text.ParagraphLayout.Internal.ResolvedBox import Data.Text.ParagraphLayout.Internal.ResolvedSpan import Data.Text.ParagraphLayout.Internal.TextContainer
test/Data/Text/ParagraphLayout/Internal/ApplyBoxesSpec.hs view
@@ -6,6 +6,7 @@ import Test.Hspec import Data.Text.ParagraphLayout.Internal.ApplyBoxes+import Data.Text.ParagraphLayout.Internal.BiDiLevels import Data.Text.ParagraphLayout.Internal.BoxOptions import Data.Text.ParagraphLayout.Internal.ResolvedBox import Data.Text.ParagraphLayout.Internal.ResolvedSpan@@ -23,6 +24,7 @@ , spanText = empty , spanTextOptions = defaultTextOptions dir , spanBoxes = bs+ , spanBiDiLevels = TextLevels [] , spanLineBreaks = [] , spanCharacterBreaks = [] }
+ test/Data/Text/ParagraphLayout/Internal/BiDiLevelsSpec.hs view
@@ -0,0 +1,81 @@+module Data.Text.ParagraphLayout.Internal.BiDiLevelsSpec where++import Data.Text (Text)+import Data.Text.Glyphize (Direction (DirLTR, DirRTL))++import Test.Hspec+import Data.Text.ParagraphLayout.Internal.BiDiLevels+import Data.Text.ParagraphLayout.RunLengthEncoding+import Data.Text.ParagraphLayout.TextData++-- | Test that `textLevels` produces the levels of the lengths given in+-- "Data.Text.ParagraphLayout.TextData" and values given here.+shouldHaveLevels :: (Direction, a, Text, [Int]) -> [Level] -> SpecWith ()+shouldHaveLevels (dir, _, text, lens) levels = it description $+ result `shouldBe` TextLevels (runLengthDecode rls)+ where+ result = textLevels dir text+ rls = zip lens levels+ description = case rls of+ [] -> "should be empty"+ [(_, lvl)] -> "should all have level " ++ show lvl+ _ -> "should have multiple levels"++-- | Override the paragraph direction of sample data.+setDirection :: Direction -> (Direction, a, b, c) -> (Direction, a, b, c)+setDirection dir (_, a, b, c) = (dir, a, b, c)++-- | Override the paragraph direction of sample data to LTR.+setLTR :: (Direction, a, b, c) -> (Direction, a, b, c)+setLTR = setDirection DirLTR++-- | Override the paragraph direction of sample data to RTL.+setRTL :: (Direction, a, b, c) -> (Direction, a, b, c)+setRTL = setDirection DirRTL++spec :: Spec+spec = do++ describe "textLevels" $ do++ -- Empty input should produce empty output.+ -- Infinite list of level 0 is also acceptable.+ describe "on English input" $+ englishEmpty `shouldHaveLevels` []++ -- Empty input should produce empty output.+ -- Infinite list of level 1 is also acceptable.+ describe "on Arabic empty" $+ arabicEmpty `shouldHaveLevels` []++ -- All LTR text without numbers should always stay at the base level.+ describe "on English word in LTR" $+ setLTR englishWord `shouldHaveLevels` [0]++ -- All characters in a RTL paragraph must be at least at level 1.+ describe "on English word in RTL" $+ setRTL englishWord `shouldHaveLevels` [2]++ describe "on Arabic word in LTR" $+ setLTR arabicHello `shouldHaveLevels` [1]++ describe "on Arabic word in RTL" $+ setRTL arabicHello `shouldHaveLevels` [1]++ describe "on Serbian mixed script" $+ serbianMixedScript `shouldHaveLevels` [0]++ describe "on mixed direction with base LTR" $+ mixedDirectionSimple DirLTR `shouldHaveLevels` [0, 1, 0]++ describe "on mixed direction with base RTL" $+ mixedDirectionSimple DirRTL `shouldHaveLevels` [2, 1, 2]++ describe "on Arabic around English" $+ arabicAroundEnglish `shouldHaveLevels` [1, 2, 1, 2, 1]++ describe "on English around Arabic" $+ englishAroundArabic `shouldHaveLevels` [0, 1, 0]++ describe "on numbers in RTL run in LTR" $+ mixedDirectionNumbers `shouldHaveLevels` [0, 1, 2, 1]
test/Data/Text/ParagraphLayout/Internal/BiDiReorderSpec.hs view
@@ -5,6 +5,7 @@ import Data.Word (Word8) import Test.Hspec+import Data.Text.ParagraphLayout.Internal.BiDiLevels import Data.Text.ParagraphLayout.Internal.BiDiReorder ne :: [a] -> NonEmpty a
test/Data/Text/ParagraphLayout/Internal/RunSpec.hs view
@@ -1,44 +1,273 @@ module Data.Text.ParagraphLayout.Internal.RunSpec (spec) where -import Data.Text (pack)+import Data.Text (Text, pack) import Data.Text.Glyphize (Direction (..), emptyFont) import Test.Hspec+import Data.Text.ParagraphLayout.Internal.BiDiLevels+import Data.Text.ParagraphLayout.Internal.BoxOptions+import Data.Text.ParagraphLayout.Internal.LineHeight+import Data.Text.ParagraphLayout.Internal.ResolvedBox import Data.Text.ParagraphLayout.Internal.ResolvedSpan import Data.Text.ParagraphLayout.Internal.Run-import Data.Text.ParagraphLayout.SpanData+import Data.Text.ParagraphLayout.Internal.TextOptions+import Data.Text.ParagraphLayout.RunLengthEncoding+import Data.Text.ParagraphLayout.TextData +defaultBox :: Direction -> ResolvedBox ()+defaultBox dir = ResolvedBox () 0 defaultBoxOptions dir++sampleSpan :: (Direction, String, Text, a) -> TextLevels -> ResolvedSpan ()+sampleSpan (dir, lang, text, _) levels = ResolvedSpan+ { spanUserData = ()+ , spanIndex = 0+ , spanOffsetInParagraph = 0+ , spanText = text+ , spanTextOptions = (defaultTextOptions dir)+ { textFont = emptyFont+ , textLineHeight = Normal+ , textLanguage = lang+ }+ , spanBoxes = [defaultBox dir]+ , spanBiDiLevels = levels+ , spanLineBreaks = []+ , spanCharacterBreaks = []+ }++allLTR :: TextLevels+allLTR = TextLevels $ repeat 0++allRTL :: TextLevels+allRTL = TextLevels $ repeat 1++levelsRLE :: [(Int, Level)] -> TextLevels+levelsRLE rls = TextLevels $ runLengthDecode rls+ spec :: Spec spec = do describe "spanToRuns" $ do it "handles span with no text" $ do- spanToRuns (emptySpan emptyFont) `shouldBe` []+ let inputSpan = sampleSpan englishEmpty allLTR+ let runs = spanToRuns inputSpan+ runs `shouldBe` [] it "handles Czech hello" $ do- let inputSpan = czechHello emptyFont+ let inputSpan = sampleSpan czechHello allLTR let runs = spanToRuns inputSpan runs `shouldBe` [ Run { runOffsetInSpan = 0 , runText = spanText inputSpan- , runDirection = Just DirLTR+ , runLevel = 0+ , runDirection = DirLTR , runScript = Just "Latn" } ]+ it "handles Arabic hello" $ do+ let inputSpan = sampleSpan arabicHello allRTL+ let runs = spanToRuns inputSpan+ runs `shouldBe`+ [ Run+ { runOffsetInSpan = 0+ , runText = spanText inputSpan+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Arab"+ }+ ] it "handles Serbian with mixed script" $ do- let inputSpan = serbianMixedScript emptyFont+ let inputSpan = sampleSpan serbianMixedScript allLTR let runs = spanToRuns inputSpan runs `shouldBe` [ Run -- TODO: We might want both parentheses in the same run. { runOffsetInSpan = 0 , runText = pack "Vikipedija ("- , runDirection = Just DirLTR+ , runLevel = 0+ , runDirection = DirLTR , runScript = Just "Latn" } , Run { runOffsetInSpan = 12 , runText = pack "Википедија)"- , runDirection = Just DirLTR+ , runLevel = 0+ , runDirection = DirLTR+ , runScript = Just "Cyrl"+ }+ ]+ it "handles mixed direction with base LTR" $ do+ let levels = levelsRLE [(7, 0), (3, 1), (6, 0)]+ let inputSpan = sampleSpan (mixedDirectionSimple DirLTR) levels+ let runs = spanToRuns inputSpan+ runs `shouldBe`+ [ Run+ { runOffsetInSpan = 0+ , runText = pack "bahrain"+ , runLevel = 0+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ , Run+ { runOffsetInSpan = 7+ , runText = pack "مصر"+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Arab"+ }+ , Run+ { runOffsetInSpan = 13+ , runText = pack "kuwait"+ , runLevel = 0+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ ]+ it "handles mixed direction with base RTL" $ do+ let levels = levelsRLE [(7, 2), (3, 1), (6, 2)]+ let inputSpan = sampleSpan (mixedDirectionSimple DirRTL) levels+ let runs = spanToRuns inputSpan+ runs `shouldBe`+ [ Run+ { runOffsetInSpan = 0+ , runText = pack "bahrain"+ , runLevel = 2+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ , Run+ { runOffsetInSpan = 7+ , runText = pack "مصر"+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Arab"+ }+ , Run+ { runOffsetInSpan = 13+ , runText = pack "kuwait"+ , runLevel = 2+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ ]+ it "handles Arabic text with English inside" $ do+ let levels = levelsRLE [(3, 1), (9, 2), (36, 1), (3, 2), (1, 1)]+ let inputSpan = sampleSpan arabicAroundEnglish levels+ let runs = spanToRuns inputSpan+ runs `shouldBe`+ [ Run+ { runOffsetInSpan = 0+ , runText = pack "في "+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Arab"+ }+ , Run+ { runOffsetInSpan = 5+ , runText = pack "XHTML 1.0"+ , runLevel = 2+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ , Run+ { runOffsetInSpan = 14+ , runText = pack " يتم تحقيق ذلك بإضافة العنصر المضمن "+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Arab"+ }+ , Run+ { runOffsetInSpan = 79+ , runText = pack "bdo"+ , runLevel = 2+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ , Run+ { runOffsetInSpan = 82+ , runText = pack "."+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Zyyy"+ }+ ]+ it "handles English text with Arabic inside" $ do+ let levels = levelsRLE [(13, 0), (18, 1), (11, 0)]+ let inputSpan = sampleSpan englishAroundArabic levels+ let runs = spanToRuns inputSpan+ runs `shouldBe`+ [ Run+ { runOffsetInSpan = 0+ , runText = pack "The title is "+ , runLevel = 0+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ , Run+ { runOffsetInSpan = 13+ , runText = pack "مفتاح معايير الويب"+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Arab"+ }+ , Run+ { runOffsetInSpan = 47+ , runText = pack " in Arabic."+ , runLevel = 0+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ ]+ -- Unrealistic example where text changes direction+ -- without changing script.+ it "handles script and direction changes" $ do+ let levels = levelsRLE [(4, 1), (4, 2), (8, 1), (4, 2), (3, 1)]+ let inputSpan = sampleSpan serbianMixedScript levels+ let runs = spanToRuns inputSpan+ runs `shouldBe`+ [ Run+ { runOffsetInSpan = 0+ , runText = pack "Viki"+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Latn"+ }+ -- direction change+ , Run+ { runOffsetInSpan = 4+ , runText = pack "pedi"+ , runLevel = 2+ , runDirection = DirLTR+ , runScript = Just "Latn"+ }+ -- direction change+ , Run+ { runOffsetInSpan = 8+ , runText = pack "ja ("+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Latn"+ }+ -- script change+ , Run+ { runOffsetInSpan = 12+ , runText = pack "Вики"+ , runLevel = 1+ , runDirection = DirRTL+ , runScript = Just "Cyrl"+ }+ -- direction change+ , Run+ { runOffsetInSpan = 20+ , runText = pack "педи"+ , runLevel = 2+ , runDirection = DirLTR+ , runScript = Just "Cyrl"+ }+ -- direction change+ , Run+ { runOffsetInSpan = 28+ , runText = pack "ја)"+ , runLevel = 1+ , runDirection = DirRTL , runScript = Just "Cyrl" } ]
test/Data/Text/ParagraphLayout/Rich/ParagraphData.hs view
@@ -2,15 +2,18 @@ ( hardBoxBreakLTRParagraph , hardBoxBreakRTLParagraph , loremIpsumParagraph+ , mixedDirectionComplexParagraph+ , mixedDirectionSimpleParagraph , mixedScriptParagraph , mixedSizesParagraph , nestedBoxesParagraph+ , neutralDirectionParagraph , softBreakParagraph ) where import Data.Int (Int32)-import Data.Text (Text, pack)+import Data.Text (Text, empty, pack) import Data.Text.Glyphize (Direction (DirLTR, DirRTL), Font) import Data.Text.ParagraphLayout.Internal.BoxOptions@@ -18,6 +21,7 @@ import Data.Text.ParagraphLayout.Internal.Rich.Paragraph import Data.Text.ParagraphLayout.Internal.TextOptions import Data.Text.ParagraphLayout.Internal.Tree+import Data.Text.ParagraphLayout.TextData tLTR :: TextOptions tLTR = defaultTextOptions DirLTR@@ -88,6 +92,47 @@ where t = tLTR { textFont = font, textLanguage = "zxx" } +-- | Edge case:+-- Directionally neutral characters between box boundary and direction boundary.+--+-- In the case of a LTR paragraph, neutral characters should only be considered+-- RTL when surrounded by RTL characters on both sides, otherwise they should+-- be LTR.+--+-- In the case of a RTL paragraph, neutral characters should only be considered+-- LTR when surrounded by LTR characters on both sides, otherwise they should+-- be RTL.+--+-- Box boundaries should not, by default, affect the direction of neutral+-- characters.+mixedDirectionComplexParagraph :: Direction -> Font -> ParagraphOptions ->+ Paragraph String+mixedDirectionComplexParagraph dir font = constructParagraph+ (pack "prefix")+ (rootBox t+ [ text "text1" " . > ab . > ba . > "+ , box "box1" b_ t+ [ text "text2" " . > اب . > با . > tu . > ut . > "+ ]+ , text "text3" " . > تو . > وت . > "+ ]+ )+ (pack "suffix")+ where+ t = (defaultTextOptions dir) { textFont = font, textLanguage = "zxx" }++-- | A paragraph that contains only strongly directional letters of mixed+-- directions, no spaces or other neutral characters, for basic BiDi tests.+mixedDirectionSimpleParagraph :: Direction -> Font -> ParagraphOptions ->+ Paragraph String+mixedDirectionSimpleParagraph dir font = constructParagraph+ empty+ (rootBox t [TextSequence "text" textContents])+ empty+ where+ t = (defaultTextOptions dir) { textFont = font, textLanguage = lang }+ (_, lang, textContents, _) = mixedDirectionSimple dir+ mixedScriptParagraph :: Font -> ParagraphOptions -> Paragraph String mixedScriptParagraph font = constructParagraph (pack "prefix")@@ -149,6 +194,17 @@ (pack "suffix") where t = tLTR { textFont = font, textLanguage = "en" }++-- | A paragraph that contains only characters in the BiDi class+-- /Other neutrals (ON)/. This includes characters that can be mirrored.+neutralDirectionParagraph :: Direction -> Font -> ParagraphOptions ->+ Paragraph String+neutralDirectionParagraph dir font = constructParagraph+ (pack "_»")+ (rootBox t [text "text" "…―>"])+ (pack "*†")+ where+ t = (defaultTextOptions dir) { textFont = font, textLanguage = "zxx" } softBreakParagraph :: Int32 -> Font -> ParagraphOptions -> Paragraph String softBreakParagraph spacing font = constructParagraph
test/Data/Text/ParagraphLayout/RichSpec.hs view
@@ -1,12 +1,14 @@ module Data.Text.ParagraphLayout.RichSpec (spec) where import Data.Int (Int32)+import Data.Text.Glyphize (Direction (DirLTR, DirRTL)) import Test.Hspec import System.FilePath ((</>)) import Data.Text.ParagraphLayout import Data.Text.ParagraphLayout.FontLoader import Data.Text.ParagraphLayout.Internal.Paginable (paginateAll)+import Data.Text.ParagraphLayout.Internal.Rich.ParagraphLayout (shapedRuns) import Data.Text.ParagraphLayout.PrettyShow import Data.Text.ParagraphLayout.PrettyShow.Golden import Data.Text.ParagraphLayout.Rect@@ -120,6 +122,42 @@ let result = layoutRich input result `shouldBeGolden` "hardBoxBreakRTL" + it "handles neutral characters in RTL paragraph" $ do+ let opts = defaultParagraphOptions+ let input = neutralDirectionParagraph DirRTL font opts+ let result = layoutRich input+ result `shouldBeGolden` "neutralDirectionRTL"++ it "handles neutral characters in LTR paragraph" $ do+ let opts = defaultParagraphOptions+ let input = neutralDirectionParagraph DirLTR font opts+ let result = layoutRich input+ result `shouldBeGolden` "neutralDirectionLTR"++ it "handles simple mixed direction in RTL paragraph" $ do+ let opts = defaultParagraphOptions+ let input = mixedDirectionSimpleParagraph DirRTL font opts+ let result = layoutRich input+ result `shouldBeGolden` "mixedDirectionSimpleRTL"++ it "handles simple mixed direction in LTR paragraph" $ do+ let opts = defaultParagraphOptions+ let input = mixedDirectionSimpleParagraph DirLTR font opts+ let result = layoutRich input+ result `shouldBeGolden` "mixedDirectionSimpleLTR"++ it "handles complex mixed direction in RTL paragraph" $ do+ let opts = defaultParagraphOptions+ let input = mixedDirectionComplexParagraph DirRTL font opts+ let result = layoutRich input+ result `shouldBeGolden` "mixedDirectionComplexRTL"++ it "handles complex mixed direction in LTR paragraph" $ do+ let opts = defaultParagraphOptions+ let input = mixedDirectionComplexParagraph DirLTR font opts+ let result = layoutRich input+ result `shouldBeGolden` "mixedDirectionComplexLTR"+ describe "paginate" $ do let goldenDir = ".golden" </> "paginatedRichParagraphLayout"@@ -140,3 +178,30 @@ } let pages = paginateAll popts pl pages `shouldBeGolden` "loremIpsum20em"++ describe "shaped runs for demo" $ do+ let+ goldenDir = ".golden" </> "shapedRuns"+ shouldBeGolden = goldenTest goldenDir getShapedRuns ShapedRuns+ -- | Test shaped runs against an expected value,+ -- and write metadata about the used font afterwards.+ shapedRunsSpecWithFont fontPath font subject name result = do+ let infoPath = fontInfoPath goldenDir name+ let writeInfo = writeFontInfo infoPath fontPath font+ after_ writeInfo $ it subject $+ shapedRuns result `shouldBeGolden` name++ describe "with Arabic font" $ do+ let fontPath = arabicFont+ font <- runIO $ loadFont fontPath 0 demoOptions++ let+ opts = defaultParagraphOptions+ input = mixedDirectionComplexParagraph DirRTL font opts+ result = layoutRich input+ shapedRunsSpec = shapedRunsSpecWithFont fontPath font++ shapedRunsSpec+ "handles complex mixed direction in RTL paragraph"+ "mixedDirectionComplexRTL"+ result
+ test/Data/Text/ParagraphLayout/RunLengthEncoding.hs view
@@ -0,0 +1,8 @@+module Data.Text.ParagraphLayout.RunLengthEncoding (runLengthDecode) where++-- | Decode a run-length encoded list.+--+-- Helpful for concisely representing lists with many repeated values.+runLengthDecode :: [(Int, a)] -> [a]+runLengthDecode [] = []+runLengthDecode ((len, val) : xs) = replicate len val ++ runLengthDecode xs
− test/Data/Text/ParagraphLayout/SpanData.hs
@@ -1,66 +0,0 @@-module Data.Text.ParagraphLayout.SpanData- ( emptySpan- , czechHello- , serbianMixedScript- )-where--import Data.Text (pack)-import Data.Text.Glyphize (Direction (DirLTR), Font)--import Data.Text.ParagraphLayout.Internal.BoxOptions-import Data.Text.ParagraphLayout.Internal.LineHeight-import Data.Text.ParagraphLayout.Internal.ResolvedBox-import Data.Text.ParagraphLayout.Internal.ResolvedSpan-import Data.Text.ParagraphLayout.Internal.TextOptions--defaultBox :: Direction -> ResolvedBox ()-defaultBox dir = ResolvedBox () 0 defaultBoxOptions dir--emptySpan :: Font -> ResolvedSpan ()-emptySpan font = ResolvedSpan- { spanUserData = ()- , spanIndex = 0- , spanOffsetInParagraph = 0- , spanText = pack ""- , spanTextOptions = (defaultTextOptions DirLTR)- { textFont = font- , textLineHeight = Normal- , textLanguage = "en"- }- , spanBoxes = [defaultBox DirLTR]- , spanLineBreaks = []- , spanCharacterBreaks = []- }--czechHello :: Font -> ResolvedSpan ()-czechHello font = ResolvedSpan- { spanUserData = ()- , spanIndex = 0- , spanOffsetInParagraph = 0- , spanText = pack "Ahoj, světe!"- , spanTextOptions = (defaultTextOptions DirLTR)- { textFont = font- , textLineHeight = Normal- , textLanguage = "cs"- }- , spanBoxes = [defaultBox DirLTR]- , spanLineBreaks = []- , spanCharacterBreaks = []- }--serbianMixedScript :: Font -> ResolvedSpan ()-serbianMixedScript font = ResolvedSpan- { spanUserData = ()- , spanIndex = 0- , spanOffsetInParagraph = 0- , spanText = pack "Vikipedija (Википедија)"- , spanTextOptions = (defaultTextOptions DirLTR)- { textFont = font- , textLineHeight = Normal- , textLanguage = "sr"- }- , spanBoxes = [defaultBox DirLTR]- , spanLineBreaks = []- , spanCharacterBreaks = []- }
+ test/Data/Text/ParagraphLayout/TextData.hs view
@@ -0,0 +1,110 @@+module Data.Text.ParagraphLayout.TextData+ ( arabicEmpty+ , englishEmpty+ , englishWord+ , arabicHello+ , czechHello+ , serbianMixedScript+ , mixedDirectionNumbers+ , mixedDirectionSimple+ , arabicAroundEnglish+ , englishAroundArabic+ )+where++import Data.Text (Text, empty, pack)+import Data.Text.Glyphize (Direction (DirLTR, DirRTL))++type Language = String+type DirectionRunLengths = [Int]+type Sample = (Direction, Language, Text, DirectionRunLengths)++arabicEmpty :: Sample+arabicEmpty =+ ( DirRTL+ , "ar"+ , empty+ , []+ )++englishEmpty :: Sample+englishEmpty =+ ( DirLTR+ , "en"+ , empty+ , []+ )++englishWord :: Sample+englishWord =+ ( DirRTL+ , "en"+ , pack "word"+ , [4]+ )++arabicHello :: Sample+arabicHello =+ ( DirRTL+ , "ar"+ , pack "سلام"+ , [4]+ )++czechHello :: Sample+czechHello =+ ( DirLTR+ , "cs"+ , pack "Ahoj, světe!"+ , [12]+ )++serbianMixedScript :: Sample+serbianMixedScript =+ ( DirLTR+ , "sr"+ , pack "Vikipedija (Википедија)"+ , [23]+ )++-- | Source:+-- <https://www.w3.org/International/articles/inline-bidi-markup/uba-basics>+mixedDirectionNumbers :: Sample+mixedDirectionNumbers =+ ( DirLTR+ , "mul"+ , pack "one two ثلاثة 1234 خمسة"+ , [8, 6, 4, 5]+ )++-- | Source:+-- <https://www.w3.org/International/articles/inline-bidi-markup/uba-basics>+--+-- Used to test the effect of base direction.+mixedDirectionSimple :: Direction -> Sample+mixedDirectionSimple dir =+ ( dir+ , "ar"+ , pack "bahrainمصرkuwait"+ , [7, 3, 6]+ )++-- | Source:+-- <https://www.w3.org/International/articles/inline-bidi-markup/uba-basics>+arabicAroundEnglish :: Sample+arabicAroundEnglish =+ ( DirRTL+ , "ar"+ , pack "في XHTML 1.0 يتم تحقيق ذلك بإضافة العنصر المضمن bdo."+ , [3, 9, 36, 3, 1]+ )++-- | Source:+-- <https://www.w3.org/International/articles/inline-bidi-markup/uba-basics>+englishAroundArabic :: Sample+englishAroundArabic =+ ( DirLTR+ , "en"+ , pack "The title is مفتاح معايير الويب in Arabic."+ , [13, 18, 11]+ )