packages feed

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 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]+    )