~jaro/balkon

ff8e9d3d0ef7236b5dbbc566203645483c56a9b9 — Jaro 1 year, 5 months ago ca5a620
Make RTL text overflow the left paragraph edge.

This behaviour is consistent with CSS Text Module Level 3.

BREAKING: Previously, all text would overflow the right paragraph edge,
regardless of text direction.
M .golden/paragraphLayout/arabicHelloParagraphUltraNarrow.golden => .golden/paragraphLayout/arabicHelloParagraphUltraNarrow.golden +14 -14
@@ 1,5 1,5 @@
ParagraphLayout
    { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 827, y_size = -19500}
    { paragraphRect = Rect {x_origin = -727, y_origin = 0, x_size = 827, y_size = -19500}
    , spanLayouts = [
        SpanLayout
        [ Fragment


@@ 8,7 8,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = SpacedEdge 0, boxStartEdge = SpacedEdge 0, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = 0, x_size = 224, y_size = -1500}
            , fragmentRect = Rect {x_origin = -124, y_origin = 0, x_size = 224, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 302, cluster = 5, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 224, y_advance = 0, x_offset = 0, y_offset = 0})


@@ 20,7 20,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -1500, x_size = 269, y_size = -1500}
            , fragmentRect = Rect {x_origin = -169, y_origin = -1500, x_size = 269, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 477, cluster = 7, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 269, y_advance = 0, x_offset = 0, y_offset = 0})


@@ 32,7 32,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -3000, x_size = 827, y_size = -1500}
            , fragmentRect = Rect {x_origin = -727, y_origin = -3000, x_size = 827, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1665, cluster = 9, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 303, y_offset = -40})


@@ 45,7 45,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -4500, x_size = 314, y_size = -1500}
            , fragmentRect = Rect {x_origin = -214, y_origin = -4500, x_size = 314, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1588, cluster = 15, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 26, y_offset = 451})


@@ 58,7 58,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -6000, x_size = 270, y_size = -1500}
            , fragmentRect = Rect {x_origin = -170, y_origin = -6000, x_size = 270, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 293, cluster = 19, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 270, y_advance = 0, x_offset = 0, y_offset = 0})


@@ 70,7 70,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -7500, x_size = 555, y_size = -1500}
            , fragmentRect = Rect {x_origin = -455, y_origin = -7500, x_size = 555, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1641, cluster = 21, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 185, y_offset = 120})


@@ 83,7 83,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -9000, x_size = 479, y_size = -1500}
            , fragmentRect = Rect {x_origin = -379, y_origin = -9000, x_size = 479, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1588, cluster = 26, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 99, y_offset = 170})


@@ 96,7 96,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -10500, x_size = 314, y_size = -1500}
            , fragmentRect = Rect {x_origin = -214, y_origin = -10500, x_size = 314, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1588, cluster = 30, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 26, y_offset = 451})


@@ 109,7 109,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -12000, x_size = 310, y_size = -1500}
            , fragmentRect = Rect {x_origin = -210, y_origin = -12000, x_size = 310, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1563, cluster = 34, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 42, y_offset = 40})


@@ 122,7 122,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -13500, x_size = 452, y_size = -1500}
            , fragmentRect = Rect {x_origin = -352, y_origin = -13500, x_size = 452, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1641, cluster = 38, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 4, y_offset = 379})


@@ 135,7 135,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -15000, x_size = 524, y_size = -1500}
            , fragmentRect = Rect {x_origin = -424, y_origin = -15000, x_size = 524, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 1588, cluster = 42, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 0, y_advance = 0, x_offset = 131, y_offset = 80})


@@ 148,7 148,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = NoEdge, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = NoEdge}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -16500, x_size = 270, y_size = -1500}
            , fragmentRect = Rect {x_origin = -170, y_origin = -16500, x_size = 270, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 293, cluster = 46, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 270, y_advance = 0, x_offset = 0, y_offset = 0})


@@ 160,7 160,7 @@ ParagraphLayout
            , fragmentAncestorBoxes =
                [ AncestorBox {boxUserData = (), boxLeftEdge = SpacedEdge 0, boxRightEdge = NoEdge, boxStartEdge = NoEdge, boxEndEdge = SpacedEdge 0}
                ]
            , fragmentRect = Rect {x_origin = 0, y_origin = -18000, x_size = 284, y_size = -1500}
            , fragmentRect = Rect {x_origin = -184, y_origin = -18000, x_size = 284, y_size = -1500}
            , fragmentPen = (0, -1085)
            , fragmentGlyphs =
                [ (GlyphInfo {codepoint = 108, cluster = 48, unsafeToBreak = False, unsafeToConcat = False, safeToInsertTatweel = False}, GlyphPos {x_advance = 284, y_advance = 0, x_offset = 0, y_offset = 0})

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

## 1.2.0.0 -- TBD

* Text will now overflow the end edge of the paragraph according to the
  text direction of the root box, instead of always overflowing the right edge.

* Fixed a bug where only the height of the shortest fragment on a line
  would be considered when positioning the following line.


M src/Data/Text/ParagraphLayout/Internal/Layout.hs => src/Data/Text/ParagraphLayout/Internal/Layout.hs +28 -9
@@ 15,7 15,7 @@ import Data.Text.Foreign (lengthWord8)
import Data.Text.Glyphize
    ( Buffer (..)
    , ContentType (ContentTypeUnicode)
    , Direction (DirLTR, DirRTL)
    , Direction (DirLTR, DirRTL, DirTTB, DirBTT)
    , FontExtents (..)
    , GlyphInfo
    , GlyphPos


@@ 54,12 54,16 @@ type ProtoFragmentWithBoxes d = WithBoxes d (ProtoFragmentWithSpan d)
-- necessary to fit within the requested line width.
--
-- The output is a flat list of fragments positioned in both dimensions.
layoutAndAlignLines :: Int32 -> NonEmpty (WithSpan d Run) ->
    [FragmentWithSpan d]
layoutAndAlignLines maxWidth runs = frags
layoutAndAlignLines
    :: Direction
    -> Int32
    -> NonEmpty (WithSpan d Run)
    -> [FragmentWithSpan d]
layoutAndAlignLines dir maxWidth runs = frags
    where
        frags = concatMap NonEmpty.toList fragsInLines
        (_, fragsInLines) = mapAccumL positionLineH originY numberedLines
        (_, fragsInLines) = mapAccumL positionLine originY numberedLines
        positionLine = positionLineH dir maxWidth
        numberedLines = zip [1 ..] canonicalLines
        canonicalLines = fmap reorderProtoFragments logicalLines
        logicalLines = nonEmptyItems $ layoutLines maxWidth [] runs


@@ 99,15 103,30 @@ layoutLines maxWidth openBoxes runs = case nonEmpty rest of
-- @vertical-align: top@ in CSS.
--
-- TODO: For rich text, allow other types of vertical alignment.
positionLineH :: Int32 -> (Int, PL.ProtoLine NonEmpty d) ->
    (Int32, NonEmpty (FragmentWithSpan d))
positionLineH originY (num, pl) = (nextY, frags)
positionLineH
    :: Direction
    -> Int32
    -> Int32
    -> (Int, PL.ProtoLine NonEmpty d)
    -> (Int32, NonEmpty (FragmentWithSpan d))
positionLineH dir maxWidth originY (num, pl) = (nextY, frags)
    where
        nextY = minimum $ fmap y_min rects
        rects = fmap (\ (WithSpan _ r) -> fragmentRect r) frags
        (_, frags) = mapAccumL (positionFragmentH num originY) originX wpfs
        wpfs = PL.applyBoxes pl
        originX = paragraphOriginX
        originX = paragraphOriginX + if lineWidth > maxWidth
            then overflowingLineOffset dir (lineWidth - maxWidth)
            else 0
        lineWidth = PL.width pl

-- | Inline offset of the first fragment on a line that overflows.
overflowingLineOffset :: Direction -> Int32 -> Int32
overflowingLineOffset DirLTR _ = 0
overflowingLineOffset DirTTB _ = 0
overflowingLineOffset DirRTL excess = -excess
-- TODO: Check if the sign needs to be flipped for vertical text.
overflowingLineOffset DirBTT excess = -excess

-- | Position the given horizontal fragment on a line,
-- using @originY@ as its top edge and @originX@ as its left edge,

M src/Data/Text/ParagraphLayout/Internal/Rich.hs => src/Data/Text/ParagraphLayout/Internal/Rich.hs +5 -2
@@ 23,14 23,17 @@ import Data.Text.ParagraphLayout.Internal.WithSpan

-- | Lay out a rich text paragraph.
layoutRich :: Paragraph d -> ParagraphLayout d
layoutRich p@(Paragraph _ _ _ opts) = paragraphLayout $ map unwrap frags
layoutRich p = paragraphLayout $ map unwrap frags
    where
        Paragraph _ _ root opts = p
        RootBox (Box _ rootTextOpts) = root
        unwrap (WithSpan rs frag) =
            frag { fragmentUserData = RS.spanUserData rs }
        frags = case nonEmpty wrappedRuns of
            Just xs -> layoutAndAlignLines maxWidth xs
            Just xs -> layoutAndAlignLines dir maxWidth xs
            Nothing -> []
        wrappedRuns = spansToRunsWrapped spans
        dir = textDirection rootTextOpts
        maxWidth = paragraphMaxWidth opts
        spans = resolveSpans p