From 409c6d7eddc9c7f96438f7c9c2d51b1c644f1413 Mon Sep 17 00:00:00 2001 From: Jaro Date: Sat, 1 Jul 2023 18:04:10 +0200 Subject: [PATCH] Add paragraphLines to Rich ParagraphLayout. BREAKING: The paragraphLines field of ParagraphLayout must be preserved for pagination. BREAKING: The width of a rich text ParagraphLayout will now remain unchanged after pagination. BREAKING: Empty space at the top of the first line and at the bottom of the last line in a ParagraphLayout will now be preserved. --- .../loremIpsum20em.golden | 21 +++++- .../richParagraphLayout/emptyBoxMiddle.golden | 3 + .../hardBoxBreakLTR.golden | 4 + .../hardBoxBreakRTL.golden | 4 + .../richParagraphLayout/loremIpsum20em.golden | 13 ++++ .../loremIpsum20emCentre.golden | 13 ++++ .../loremIpsum20emRight.golden | 13 ++++ .../mixedDirectionComplexLTR.golden | 3 + .../mixedDirectionComplexRTL.golden | 3 + .../mixedDirectionSimpleLTR.golden | 3 + .../mixedDirectionSimpleRTL.golden | 3 + .../mixedLineHeightBaseline.golden | 8 ++ .../mixedLineHeightBaseline3.golden | 8 ++ .../mixedLineHeightBottom.golden | 8 ++ .../mixedLineHeightTop.golden | 8 ++ .../richParagraphLayout/mixedScript.golden | 3 + .../mixedScriptWrap.golden | 6 ++ .golden/richParagraphLayout/mixedSizes.golden | 3 + .../richParagraphLayout/nestedBoxes.golden | 3 + .../neutralDirectionLTR.golden | 3 + .../neutralDirectionRTL.golden | 3 + .../newline1Paragraph.golden | 3 + .../newline1TextParagraph.golden | 4 + .../newline2Paragraph.golden | 4 + .../newline2TextParagraph.golden | 5 ++ .../spaceBoxCollapsed.golden | 10 +++ .../richParagraphLayout/spaceBoxMiddle.golden | 3 + .../spaceBoxPreserved.golden | 11 +++ CHANGELOG.md | 9 +++ balkon.cabal | 1 + lib/Data/Text/ParagraphLayout/Rich.hs | 13 +++- .../Text/ParagraphLayout/Internal/Layout.hs | 33 ++++++--- .../Text/ParagraphLayout/Internal/Line.hs | 25 +++++++ .../ParagraphLayout/Internal/ParagraphLine.hs | 27 ++++--- .../Text/ParagraphLayout/Internal/Rich.hs | 18 ++++- .../Internal/Rich/ParagraphLayout.hs | 73 +++++++++++-------- test/Data/Text/ParagraphLayout/PrettyShow.hs | 7 +- test/Data/Text/ParagraphLayout/RichSpec.hs | 3 +- 38 files changed, 321 insertions(+), 64 deletions(-) create mode 100644 src/Data/Text/ParagraphLayout/Internal/Line.hs diff --git a/.golden/paginatedRichParagraphLayout/loremIpsum20em.golden b/.golden/paginatedRichParagraphLayout/loremIpsum20em.golden index 7621cca..4745986 100644 --- a/.golden/paginatedRichParagraphLayout/loremIpsum20em.golden +++ b/.golden/paginatedRichParagraphLayout/loremIpsum20em.golden @@ -1,5 +1,9 @@ [ (Continue, ParagraphLayout - { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 18310, y_size = -2242} + { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -2242} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 19791, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" @@ -105,6 +109,14 @@ ) , (Continue, ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -6726} + , paragraphLines = + [ Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -2242, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -3363, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 7, lineRect = Rect {x_origin = 0, y_origin = -4484, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 8, lineRect = Rect {x_origin = 0, y_origin = -5605, x_size = 19791, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" @@ -426,7 +438,12 @@ } ) , (Continue, ParagraphLayout - { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 19199, y_size = -3363} + { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -3363} + , paragraphLines = + [ Line {lineNumber = 9, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 10, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 11, lineRect = Rect {x_origin = 0, y_origin = -2242, x_size = 19791, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/emptyBoxMiddle.golden b/.golden/richParagraphLayout/emptyBoxMiddle.golden index f43efa0..2f1cef2 100644 --- a/.golden/richParagraphLayout/emptyBoxMiddle.golden +++ b/.golden/richParagraphLayout/emptyBoxMiddle.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 5193, y_size = -1121} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 5193, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/hardBoxBreakLTR.golden b/.golden/richParagraphLayout/hardBoxBreakLTR.golden index da20c1f..29a7206 100644 --- a/.golden/richParagraphLayout/hardBoxBreakLTR.golden +++ b/.golden/richParagraphLayout/hardBoxBreakLTR.golden @@ -1,5 +1,9 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 5855, y_size = -2242} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 5855, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 5855, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/hardBoxBreakRTL.golden b/.golden/richParagraphLayout/hardBoxBreakRTL.golden index ab906d0..126551c 100644 --- a/.golden/richParagraphLayout/hardBoxBreakRTL.golden +++ b/.golden/richParagraphLayout/hardBoxBreakRTL.golden @@ -1,5 +1,9 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 999996545, y_origin = 0, x_size = 3455, y_size = -3000} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 999996545, y_origin = 0, x_size = 3455, y_size = -1500}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 999996545, y_origin = -1500, x_size = 3455, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text2" diff --git a/.golden/richParagraphLayout/loremIpsum20em.golden b/.golden/richParagraphLayout/loremIpsum20em.golden index 62b1662..50586a0 100644 --- a/.golden/richParagraphLayout/loremIpsum20em.golden +++ b/.golden/richParagraphLayout/loremIpsum20em.golden @@ -1,5 +1,18 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -12331} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -2242, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -3363, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -4484, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -5605, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 7, lineRect = Rect {x_origin = 0, y_origin = -6726, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 8, lineRect = Rect {x_origin = 0, y_origin = -7847, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 9, lineRect = Rect {x_origin = 0, y_origin = -8968, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 10, lineRect = Rect {x_origin = 0, y_origin = -10089, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 11, lineRect = Rect {x_origin = 0, y_origin = -11210, x_size = 19791, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/loremIpsum20emCentre.golden b/.golden/richParagraphLayout/loremIpsum20emCentre.golden index fcd4f93..932c0d1 100644 --- a/.golden/richParagraphLayout/loremIpsum20emCentre.golden +++ b/.golden/richParagraphLayout/loremIpsum20emCentre.golden @@ -1,5 +1,18 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 104, y_origin = 0, x_size = 19791, y_size = -12331} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 104, y_origin = 0, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 104, y_origin = -1121, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 104, y_origin = -2242, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 104, y_origin = -3363, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 104, y_origin = -4484, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 104, y_origin = -5605, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 7, lineRect = Rect {x_origin = 104, y_origin = -6726, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 8, lineRect = Rect {x_origin = 104, y_origin = -7847, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 9, lineRect = Rect {x_origin = 104, y_origin = -8968, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 10, lineRect = Rect {x_origin = 104, y_origin = -10089, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 11, lineRect = Rect {x_origin = 104, y_origin = -11210, x_size = 19791, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/loremIpsum20emRight.golden b/.golden/richParagraphLayout/loremIpsum20emRight.golden index de94dca..35c702f 100644 --- a/.golden/richParagraphLayout/loremIpsum20emRight.golden +++ b/.golden/richParagraphLayout/loremIpsum20emRight.golden @@ -1,5 +1,18 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 209, y_origin = 0, x_size = 19791, y_size = -12331} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 209, y_origin = 0, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 209, y_origin = -1121, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 209, y_origin = -2242, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 209, y_origin = -3363, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 209, y_origin = -4484, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 209, y_origin = -5605, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 7, lineRect = Rect {x_origin = 209, y_origin = -6726, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 8, lineRect = Rect {x_origin = 209, y_origin = -7847, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 9, lineRect = Rect {x_origin = 209, y_origin = -8968, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 10, lineRect = Rect {x_origin = 209, y_origin = -10089, x_size = 19791, y_size = -1121}} + , Line {lineNumber = 11, lineRect = Rect {x_origin = 209, y_origin = -11210, x_size = 19791, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/mixedDirectionComplexLTR.golden b/.golden/richParagraphLayout/mixedDirectionComplexLTR.golden index e791763..02dc0c3 100644 --- a/.golden/richParagraphLayout/mixedDirectionComplexLTR.golden +++ b/.golden/richParagraphLayout/mixedDirectionComplexLTR.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 24557, y_size = -1500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 24557, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/mixedDirectionComplexRTL.golden b/.golden/richParagraphLayout/mixedDirectionComplexRTL.golden index a5a39e9..1bd8285 100644 --- a/.golden/richParagraphLayout/mixedDirectionComplexRTL.golden +++ b/.golden/richParagraphLayout/mixedDirectionComplexRTL.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 999975443, y_origin = 0, x_size = 24557, y_size = -1500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 999975443, y_origin = 0, x_size = 24557, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text3" diff --git a/.golden/richParagraphLayout/mixedDirectionSimpleLTR.golden b/.golden/richParagraphLayout/mixedDirectionSimpleLTR.golden index bee293b..c199eae 100644 --- a/.golden/richParagraphLayout/mixedDirectionSimpleLTR.golden +++ b/.golden/richParagraphLayout/mixedDirectionSimpleLTR.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 7954, y_size = -1500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 7954, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/mixedDirectionSimpleRTL.golden b/.golden/richParagraphLayout/mixedDirectionSimpleRTL.golden index 5a994ab..a60b191 100644 --- a/.golden/richParagraphLayout/mixedDirectionSimpleRTL.golden +++ b/.golden/richParagraphLayout/mixedDirectionSimpleRTL.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 999992046, y_origin = 0, x_size = 7954, y_size = -1500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 999992046, y_origin = 0, x_size = 7954, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/mixedLineHeightBaseline.golden b/.golden/richParagraphLayout/mixedLineHeightBaseline.golden index a3a5733..228d7fb 100644 --- a/.golden/richParagraphLayout/mixedLineHeightBaseline.golden +++ b/.golden/richParagraphLayout/mixedLineHeightBaseline.golden @@ -1,5 +1,13 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -9400} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -3400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -4700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -6400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -7700, x_size = 16708, y_size = -1700}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "mediumText" diff --git a/.golden/richParagraphLayout/mixedLineHeightBaseline3.golden b/.golden/richParagraphLayout/mixedLineHeightBaseline3.golden index c755a79..959d222 100644 --- a/.golden/richParagraphLayout/mixedLineHeightBaseline3.golden +++ b/.golden/richParagraphLayout/mixedLineHeightBaseline3.golden @@ -1,5 +1,13 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -9400} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -3400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -4700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -6400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -7700, x_size = 16708, y_size = -1700}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "mediumText" diff --git a/.golden/richParagraphLayout/mixedLineHeightBottom.golden b/.golden/richParagraphLayout/mixedLineHeightBottom.golden index 8960763..4f5db0a 100644 --- a/.golden/richParagraphLayout/mixedLineHeightBottom.golden +++ b/.golden/richParagraphLayout/mixedLineHeightBottom.golden @@ -1,5 +1,13 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -9400} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -3400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -4700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -6400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -7700, x_size = 16708, y_size = -1700}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "mediumText" diff --git a/.golden/richParagraphLayout/mixedLineHeightTop.golden b/.golden/richParagraphLayout/mixedLineHeightTop.golden index 84e1670..8748bd2 100644 --- a/.golden/richParagraphLayout/mixedLineHeightTop.golden +++ b/.golden/richParagraphLayout/mixedLineHeightTop.golden @@ -1,5 +1,13 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -9400} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -3400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -4700, x_size = 16708, y_size = -1700}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -6400, x_size = 16708, y_size = -1300}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -7700, x_size = 16708, y_size = -1700}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "mediumText" diff --git a/.golden/richParagraphLayout/mixedScript.golden b/.golden/richParagraphLayout/mixedScript.golden index 6d561bc..a2c4faf 100644 --- a/.golden/richParagraphLayout/mixedScript.golden +++ b/.golden/richParagraphLayout/mixedScript.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 15283, y_size = -1121} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 15283, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/mixedScriptWrap.golden b/.golden/richParagraphLayout/mixedScriptWrap.golden index 3a1d5b7..3a45849 100644 --- a/.golden/richParagraphLayout/mixedScriptWrap.golden +++ b/.golden/richParagraphLayout/mixedScriptWrap.golden @@ -1,5 +1,11 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 4897, y_size = -4484} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 4897, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 4897, y_size = -1121}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -2242, x_size = 4897, y_size = -1121}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -3363, x_size = 4897, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/mixedSizes.golden b/.golden/richParagraphLayout/mixedSizes.golden index 101200f..dc10655 100644 --- a/.golden/richParagraphLayout/mixedSizes.golden +++ b/.golden/richParagraphLayout/mixedSizes.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 8634, y_size = -1121} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 8634, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "bigText1" diff --git a/.golden/richParagraphLayout/nestedBoxes.golden b/.golden/richParagraphLayout/nestedBoxes.golden index ff49547..705a5d7 100644 --- a/.golden/richParagraphLayout/nestedBoxes.golden +++ b/.golden/richParagraphLayout/nestedBoxes.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 14576, y_size = -1121} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 14576, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/neutralDirectionLTR.golden b/.golden/richParagraphLayout/neutralDirectionLTR.golden index 1306cfd..12e0537 100644 --- a/.golden/richParagraphLayout/neutralDirectionLTR.golden +++ b/.golden/richParagraphLayout/neutralDirectionLTR.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 2003, y_size = -1500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 2003, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/neutralDirectionRTL.golden b/.golden/richParagraphLayout/neutralDirectionRTL.golden index 61ed36b..eddd3ba 100644 --- a/.golden/richParagraphLayout/neutralDirectionRTL.golden +++ b/.golden/richParagraphLayout/neutralDirectionRTL.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 999997997, y_origin = 0, x_size = 2003, y_size = -1500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 999997997, y_origin = 0, x_size = 2003, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/newline1Paragraph.golden b/.golden/richParagraphLayout/newline1Paragraph.golden index ae3ee15..cc200dc 100644 --- a/.golden/richParagraphLayout/newline1Paragraph.golden +++ b/.golden/richParagraphLayout/newline1Paragraph.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 1000000000, y_origin = 0, x_size = 0, y_size = -1500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 1000000000, y_origin = 0, x_size = 0, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/newline1TextParagraph.golden b/.golden/richParagraphLayout/newline1TextParagraph.golden index 4f3214c..52b5455 100644 --- a/.golden/richParagraphLayout/newline1TextParagraph.golden +++ b/.golden/richParagraphLayout/newline1TextParagraph.golden @@ -1,5 +1,9 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 999999649, y_origin = 0, x_size = 351, y_size = -3000} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 999999649, y_origin = 0, x_size = 351, y_size = -1500}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 999999649, y_origin = -1500, x_size = 351, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/newline2Paragraph.golden b/.golden/richParagraphLayout/newline2Paragraph.golden index ccfec85..d3add71 100644 --- a/.golden/richParagraphLayout/newline2Paragraph.golden +++ b/.golden/richParagraphLayout/newline2Paragraph.golden @@ -1,5 +1,9 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 1000000000, y_origin = 0, x_size = 0, y_size = -3000} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 1000000000, y_origin = 0, x_size = 0, y_size = -1500}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 1000000000, y_origin = -1500, x_size = 0, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/newline2TextParagraph.golden b/.golden/richParagraphLayout/newline2TextParagraph.golden index 2269cff..d489bb0 100644 --- a/.golden/richParagraphLayout/newline2TextParagraph.golden +++ b/.golden/richParagraphLayout/newline2TextParagraph.golden @@ -1,5 +1,10 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 999999649, y_origin = 0, x_size = 351, y_size = -4500} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 999999649, y_origin = 0, x_size = 351, y_size = -1500}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 999999649, y_origin = -1500, x_size = 351, y_size = -1500}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 999999649, y_origin = -3000, x_size = 351, y_size = -1500}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text" diff --git a/.golden/richParagraphLayout/spaceBoxCollapsed.golden b/.golden/richParagraphLayout/spaceBoxCollapsed.golden index b3f30b9..776f31a 100644 --- a/.golden/richParagraphLayout/spaceBoxCollapsed.golden +++ b/.golden/richParagraphLayout/spaceBoxCollapsed.golden @@ -1,5 +1,15 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 590, y_size = -8968} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 590, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 590, y_size = -1121}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -2242, x_size = 590, y_size = -1121}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -3363, x_size = 590, y_size = -1121}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -4484, x_size = 590, y_size = -1121}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -5605, x_size = 590, y_size = -1121}} + , Line {lineNumber = 7, lineRect = Rect {x_origin = 0, y_origin = -6726, x_size = 590, y_size = -1121}} + , Line {lineNumber = 8, lineRect = Rect {x_origin = 0, y_origin = -7847, x_size = 590, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/spaceBoxMiddle.golden b/.golden/richParagraphLayout/spaceBoxMiddle.golden index 9231869..f594403 100644 --- a/.golden/richParagraphLayout/spaceBoxMiddle.golden +++ b/.golden/richParagraphLayout/spaceBoxMiddle.golden @@ -1,5 +1,8 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 4635, y_size = -1121} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 4635, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/.golden/richParagraphLayout/spaceBoxPreserved.golden b/.golden/richParagraphLayout/spaceBoxPreserved.golden index 2e2ed16..a7c3b83 100644 --- a/.golden/richParagraphLayout/spaceBoxPreserved.golden +++ b/.golden/richParagraphLayout/spaceBoxPreserved.golden @@ -1,5 +1,16 @@ ParagraphLayout { paragraphRect = Rect {x_origin = 0, y_origin = 0, x_size = 590, y_size = -10089} + , paragraphLines = + [ Line {lineNumber = 1, lineRect = Rect {x_origin = 0, y_origin = 0, x_size = 590, y_size = -1121}} + , Line {lineNumber = 2, lineRect = Rect {x_origin = 0, y_origin = -1121, x_size = 590, y_size = -1121}} + , Line {lineNumber = 3, lineRect = Rect {x_origin = 0, y_origin = -2242, x_size = 590, y_size = -1121}} + , Line {lineNumber = 4, lineRect = Rect {x_origin = 0, y_origin = -3363, x_size = 590, y_size = -1121}} + , Line {lineNumber = 5, lineRect = Rect {x_origin = 0, y_origin = -4484, x_size = 590, y_size = -1121}} + , Line {lineNumber = 6, lineRect = Rect {x_origin = 0, y_origin = -5605, x_size = 590, y_size = -1121}} + , Line {lineNumber = 7, lineRect = Rect {x_origin = 0, y_origin = -6726, x_size = 590, y_size = -1121}} + , Line {lineNumber = 8, lineRect = Rect {x_origin = 0, y_origin = -7847, x_size = 590, y_size = -1121}} + , Line {lineNumber = 9, lineRect = Rect {x_origin = 0, y_origin = -8968, x_size = 590, y_size = -1121}} + ] , paragraphFragments = [ Fragment { fragmentUserData = "text1" diff --git a/CHANGELOG.md b/CHANGELOG.md index c533a95..e600bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ * For backwards compatibility, line top alignment remains the default. Consider updating your code to use baseline alignment instead. +* Added `paragraphLines` to the rich text `ParagraphLayout`. + This describes the dimensions of each line of wrapped text, + including situations when the line contains more space than + what is necessary to contain its fragments. + This new field must be passed to pagination. + +* The width of a rich text `ParagraphLayout` will now remain unchanged + after pagination. + * Lines will now be sized to fit all ancestor boxes of all fragments on the line, as well as the root box, even if some of those boxes do not directly contain any text. diff --git a/balkon.cabal b/balkon.cabal index 0bfb5d7..c1a17e7 100644 --- a/balkon.cabal +++ b/balkon.cabal @@ -111,6 +111,7 @@ library balkon-internal Data.Text.ParagraphLayout.Internal.BoxOptions, Data.Text.ParagraphLayout.Internal.Break, Data.Text.ParagraphLayout.Internal.Fragment, + Data.Text.ParagraphLayout.Internal.Line, Data.Text.ParagraphLayout.Internal.LineHeight, Data.Text.ParagraphLayout.Internal.LinePagination, Data.Text.ParagraphLayout.Internal.Paginable, diff --git a/lib/Data/Text/ParagraphLayout/Rich.hs b/lib/Data/Text/ParagraphLayout/Rich.hs index b7a13a2..7b94de3 100644 --- a/lib/Data/Text/ParagraphLayout/Rich.hs +++ b/lib/Data/Text/ParagraphLayout/Rich.hs @@ -61,8 +61,18 @@ module Data.Text.ParagraphLayout.Rich , paragraphText -- * Output layout , layoutRich - , ParagraphLayout (ParagraphLayout, paragraphRect, paragraphFragments) + , ParagraphLayout + ( ParagraphLayout + , paragraphRect + , paragraphLines + , paragraphFragments + ) , paragraphSafeWidth + , Line + ( Line + , lineNumber + , lineRect + ) , Fragment ( Fragment , fragmentUserData @@ -89,6 +99,7 @@ where import Data.Text.ParagraphLayout.Internal.AncestorBox import Data.Text.ParagraphLayout.Internal.BoxOptions import Data.Text.ParagraphLayout.Internal.Fragment +import Data.Text.ParagraphLayout.Internal.Line import Data.Text.ParagraphLayout.Internal.LineHeight import Data.Text.ParagraphLayout.Internal.ParagraphAlignment import Data.Text.ParagraphLayout.Internal.ParagraphOptions diff --git a/src/Data/Text/ParagraphLayout/Internal/Layout.hs b/src/Data/Text/ParagraphLayout/Internal/Layout.hs index ccd9b05..829d35a 100644 --- a/src/Data/Text/ParagraphLayout/Internal/Layout.hs +++ b/src/Data/Text/ParagraphLayout/Internal/Layout.hs @@ -33,6 +33,7 @@ import Data.Text.ParagraphLayout.Internal.BiDiReorder import Data.Text.ParagraphLayout.Internal.BoxOptions import Data.Text.ParagraphLayout.Internal.Break import Data.Text.ParagraphLayout.Internal.Fragment +import Data.Text.ParagraphLayout.Internal.Line import Data.Text.ParagraphLayout.Internal.LineHeight import Data.Text.ParagraphLayout.Internal.ParagraphAlignment import Data.Text.ParagraphLayout.Internal.ParagraphExtents @@ -58,17 +59,22 @@ type ProtoFragmentWithBoxes d = WithBoxes d (ProtoFragmentWithSpan d) -- | Create a multi-line layout from the given runs, splitting them as -- necessary to fit within the requested line width. -- --- The output is a flat list of fragments positioned in both dimensions. +-- The first output component is a flat list of fragments positioned +-- in both dimensions. +-- +-- The second output component is a list of lines containing the +-- positioned content. layoutAndAlignLines :: Direction -> ParagraphAlignment -> Int32 -> NonEmpty (WithSpan d Run) - -> [FragmentWithSpan d] -layoutAndAlignLines dir align maxWidth runs = frags + -> ([FragmentWithSpan d], [Line]) +layoutAndAlignLines dir align maxWidth runs = (frags, ls) where frags = concatMap toList fragsInLines - (_, fragsInLines) = mapAccumL positionLine originY numberedLines + (fragsInLines, ls) = unzip fragsAndLines + (_, fragsAndLines) = mapAccumL positionLine originY numberedLines positionLine = positionLineH dir align maxWidth numberedLines = zip [1 ..] canonicalLines canonicalLines = fmap reorderProtoFragments visibleLines @@ -100,27 +106,32 @@ layoutLines maxWidth openBoxes runs = case nonEmpty rest of -- | Position all the given horizontal fragments on the same line, -- using @originY@ as its top edge, and return the bottom edge for continuation. +-- +-- Also return a `Line` structure with a defined vertical dimensions +-- but undefined horizontal dimensions. positionLineH :: Direction -> ParagraphAlignment -> Int32 -> Int32 -> (Int, PL.ProtoLine NonEmpty d) - -> (Int32, NonEmpty (FragmentWithSpan d)) -positionLineH dir align maxWidth originY (num, pl) = (nextY, frags) + -> (Int32, (NonEmpty (FragmentWithSpan d), Line)) +positionLineH dir align maxWidth originY (num, pl) = + (nextY, (frags, line)) where - nextY = minimum $ fmap y_min rects - rects = fmap (\ (WithSpan _ r) -> fragmentRect r) frags + line = Line { lineNumber = num, lineRect = Rect 0 originY 0 sizeY } + sizeY = nextY - originY (_, frags) = mapAccumL (positionFragmentH num) originX wpfs - wpfs = PL.applyBoxes $ verticalAlignment originY pl + wpfs = PL.applyBoxes alignedLine + (nextY, alignedLine) = verticalAlignment originY pl originX = paragraphOriginX + if lineWidth > maxWidth then overflowingLineOffset dir (lineWidth - maxWidth) else fittingLineOffset align dir (maxWidth - lineWidth) lineWidth = PL.width pl verticalAlignment :: Int32 -> PL.ProtoLine NonEmpty d -> - PL.ProtoLine NonEmpty d -verticalAlignment originY pl = PL.mapFragments setOrigin pl + (Int32, PL.ProtoLine NonEmpty d) +verticalAlignment originY pl = (bottomY, PL.mapFragments setOrigin pl) where bottomY = originY - finalLineHeight finalLineHeight = fittingTop - fittingBottom diff --git a/src/Data/Text/ParagraphLayout/Internal/Line.hs b/src/Data/Text/ParagraphLayout/Internal/Line.hs new file mode 100644 index 0000000..5a93cc2 --- /dev/null +++ b/src/Data/Text/ParagraphLayout/Internal/Line.hs @@ -0,0 +1,25 @@ +module Data.Text.ParagraphLayout.Internal.Line (Line (..), shiftLine) +where + +import Data.Int (Int32) + +import Data.Text.ParagraphLayout.Internal.Rect + +-- | Information about a line in a laid out paragraph. +data Line = Line + { lineNumber :: Int + -- ^ Logical number of the line box, starting at 1 for a given paragraph. + , lineRect :: Rect Int32 + -- ^ Dimensions of the line box (CSS), containing all fragments on a given + -- line, and matching the width of the whole paragraph. + } + deriving (Eq, Read, Show) + +-- | Add @dx@ and @dy@ to the line's `x_origin` and `y_origin`, +-- respectively. +shiftLine :: Int32 -> Int32 -> Line -> Line +shiftLine dx dy l = l' + where + l' = l { lineRect = r' } + r' = r { x_origin = x_origin r + dx, y_origin = y_origin r + dy } + r = lineRect l diff --git a/src/Data/Text/ParagraphLayout/Internal/ParagraphLine.hs b/src/Data/Text/ParagraphLayout/Internal/ParagraphLine.hs index 09fced8..b4b3496 100644 --- a/src/Data/Text/ParagraphLayout/Internal/ParagraphLine.hs +++ b/src/Data/Text/ParagraphLayout/Internal/ParagraphLine.hs @@ -34,13 +34,12 @@ class GenericLayout pl where -- | Keep only fragments with the given line number. limitFragments :: Int -> pl -> pl - -- | Add @dx@ and @dy@ to each fragment's `x_origin` and `y_origin`, - -- respectively. - shiftFragments :: Int32 -> Int32 -> pl -> pl + -- | Shift all contents of the paragraph by @dx, dy@. + shiftContents :: Int32 -> Int32 -> pl -> pl - -- | Combine fragments from two layouts into one, + -- | Combine contents from two layouts into one, -- without any adjustment of coordinates. - appendFragments :: pl -> pl -> pl + appendContents :: pl -> pl -> pl instance GenericLayout (P.ParagraphLayout d) where empty = P.emptyParagraphLayout @@ -48,17 +47,17 @@ instance GenericLayout (P.ParagraphLayout d) where topDistance pl = topFragmentOrigin $ P.paragraphFragments pl leftDistance pl = leftmostFragmentOrigin $ P.paragraphFragments pl limitFragments n = P.filterFragments (fragmentIsOnLine n) - shiftFragments dx dy = P.mapFragments (shiftFragment dx dy) - appendFragments = P.appendFragments + shiftContents dx dy = P.mapFragments (shiftFragment dx dy) + appendContents = P.appendFragments instance GenericLayout (R.ParagraphLayout d) where empty = R.emptyParagraphLayout rect = R.paragraphRect - topDistance pl = topFragmentOrigin $ R.paragraphFragments pl + topDistance = R.topDistance leftDistance pl = leftmostFragmentOrigin $ R.paragraphFragments pl - limitFragments n = R.filterFragments (fragmentIsOnLine n) - shiftFragments dx dy = R.mapFragments (shiftFragment dx dy) - appendFragments = R.appendFragments + limitFragments n = R.filterLine n + shiftContents dx dy = R.shiftContents dx dy + appendContents = R.appendContents -- | Split the given paragraph layout into single-line layouts. cutLines :: (GenericLayout pl, LineNumbers pl) => pl -> [pl] @@ -70,11 +69,11 @@ cutLine n pl = trimTop $ limitFragments n pl -- | Add a constant to each fragment's `y_origin` so that their maximum is zero. trimTop :: GenericLayout pl => pl -> pl -trimTop pl = shiftFragments 0 (-topDistance pl) pl +trimTop pl = shiftContents 0 (-topDistance pl) pl -- | Add a constant to each fragment's `x_origin` so that their minimum is zero. trimLeft :: GenericLayout pl => pl -> pl -trimLeft pl = shiftFragments (-leftDistance pl) 0 pl +trimLeft pl = shiftContents (-leftDistance pl) 0 pl topFragmentOrigin :: [Fragment d] -> Int32 topFragmentOrigin frags = maximum $ map (y_origin . fragmentRect) frags @@ -91,7 +90,7 @@ mergeLine :: GenericLayout pl => pl -> pl -> pl mergeLine pl nextLine = pl' where -- Quadratic time complexity. TODO: Consider optimising. - pl' = appendFragments pl $ shiftFragments 0 y nextLine + pl' = appendContents pl $ shiftContents 0 y nextLine y = y_terminus $ rect pl fragmentIsOnLine :: Int -> Fragment d -> Bool diff --git a/src/Data/Text/ParagraphLayout/Internal/Rich.hs b/src/Data/Text/ParagraphLayout/Internal/Rich.hs index a65d543..0dd4520 100644 --- a/src/Data/Text/ParagraphLayout/Internal/Rich.hs +++ b/src/Data/Text/ParagraphLayout/Internal/Rich.hs @@ -12,7 +12,10 @@ import Data.Text.ParagraphLayout.Internal.BiDiLevels import Data.Text.ParagraphLayout.Internal.Break import Data.Text.ParagraphLayout.Internal.Fragment import Data.Text.ParagraphLayout.Internal.Layout +import Data.Text.ParagraphLayout.Internal.Line +import Data.Text.ParagraphLayout.Internal.ParagraphExtents import Data.Text.ParagraphLayout.Internal.ParagraphOptions +import Data.Text.ParagraphLayout.Internal.Rect import qualified Data.Text.ParagraphLayout.Internal.ResolvedSpan as RS import Data.Text.ParagraphLayout.Internal.Rich.Paragraph import Data.Text.ParagraphLayout.Internal.Rich.ParagraphLayout @@ -23,15 +26,24 @@ import Data.Text.ParagraphLayout.Internal.WithSpan -- | Lay out a rich text paragraph. layoutRich :: Paragraph d -> ParagraphLayout d -layoutRich p = paragraphLayout $ map unwrap frags +layoutRich p = ParagraphLayout pRect stretchedLines unwrappedFrags where Paragraph _ _ root opts = p RootBox (Box _ rootTextOpts) = root + pRect = containRects $ map lineRect stretchedLines + stretchedLines = map stretchLine ls + stretchLine l = l { lineRect = stretchRect (lineRect l) } + stretchRect r = r + { x_origin = x_origin containingRect + , x_size = x_size containingRect + } + containingRect = containRects $ map fragmentSpacedRect unwrappedFrags + unwrappedFrags = map unwrap frags unwrap (WithSpan rs frag) = frag { fragmentUserData = RS.spanUserData rs } - frags = case nonEmpty wrappedRuns of + (frags, ls) = case nonEmpty wrappedRuns of Just xs -> layoutAndAlignLines dir align maxWidth xs - Nothing -> [] + Nothing -> ([], []) wrappedRuns = spansToRunsWrapped spans -- TODO: To support @unicode-bidi: plaintext@ as in CSS, allow ignoring -- the text direction of the root box, and instead use the BiDi diff --git a/src/Data/Text/ParagraphLayout/Internal/Rich/ParagraphLayout.hs b/src/Data/Text/ParagraphLayout/Internal/Rich/ParagraphLayout.hs index 9d6da9f..d7fe171 100644 --- a/src/Data/Text/ParagraphLayout/Internal/Rich/ParagraphLayout.hs +++ b/src/Data/Text/ParagraphLayout/Internal/Rich/ParagraphLayout.hs @@ -1,20 +1,21 @@ module Data.Text.ParagraphLayout.Internal.Rich.ParagraphLayout ( ParagraphLayout (..) - , appendFragments + , appendContents , emptyParagraphLayout - , filterFragments - , mapFragments - , paragraphLayout + , filterLine , paragraphOriginX , paragraphOriginY , paragraphSafeWidth , shapedRuns + , shiftContents + , topDistance ) where import Data.Int (Int32) import Data.Text.ParagraphLayout.Internal.Fragment +import Data.Text.ParagraphLayout.Internal.Line import Data.Text.ParagraphLayout.Internal.LineNumbers import Data.Text.ParagraphLayout.Internal.LinePagination import Data.Text.ParagraphLayout.Internal.ParagraphExtents @@ -24,6 +25,9 @@ import Data.Text.ParagraphLayout.Internal.Rect data ParagraphLayout d = ParagraphLayout { paragraphRect :: Rect Int32 -- ^ The containing block (CSS3). + , paragraphLines :: [Line] + -- ^ Information about line boxes (CSS). + -- May describe additional empty space around text fragments. , paragraphFragments :: [Fragment d] -- ^ The resulting layout of all input text, divided into fragments as -- required by the input structure, line breaking, text writing direction, @@ -37,38 +41,45 @@ instance LineNumbers (ParagraphLayout d) where instance LineHeight (ParagraphLayout d) where lineHeight pl = height $ paragraphRect pl --- | Wrap the given `Fragment`s and compute their containing rectangle. -paragraphLayout :: [Fragment d] -> ParagraphLayout d -paragraphLayout frags = ParagraphLayout pRect frags - where pRect = containRects $ map fragmentSpacedRect frags - --- | A `ParagraphLayout` with no fragments. --- Useful as an identity element for `appendFragments`. +-- | A `ParagraphLayout` with no fragments and no lines. +-- Useful as an identity element for `appendContents`. emptyParagraphLayout :: ParagraphLayout a -emptyParagraphLayout = ParagraphLayout emptyRect [] +emptyParagraphLayout = ParagraphLayout emptyRect [] [] --- | Remove fragments that do not match the given predicate. --- --- The containing rectangle will be recalculated. -filterFragments :: (Fragment d -> Bool) -> ParagraphLayout d -> - ParagraphLayout d -filterFragments predicate (ParagraphLayout _ frags) = - paragraphLayout $ filter predicate frags +-- | Distance from the paragraph origin to its topmost line. +topDistance :: ParagraphLayout d -> Int32 +topDistance pl = case paragraphLines pl of + [] -> 0 + ls -> maximum $ map (y_origin . lineRect) ls --- | Run a mapping function over each fragment inside a `ParagraphLayout`. --- --- The containing rectangle will be recalculated. -mapFragments :: (Fragment d -> Fragment d) -> ParagraphLayout d -> - ParagraphLayout d -mapFragments mapFunc (ParagraphLayout _ frags) = - paragraphLayout $ map mapFunc frags +-- | Keep the line with the given number and its fragments, +-- and remove everything else. +filterLine :: Int -> ParagraphLayout d -> ParagraphLayout d +filterLine num (ParagraphLayout _ ls frags) = + ParagraphLayout pRect' ls' frags' + where + pRect' = containRects $ map lineRect ls + ls' = filter ((== num) . lineNumber) ls + frags' = filter ((== num) . fragmentLine) frags + +-- | Add @dx@ and @dy@ to the origins of each line and fragment, +-- effectively shifting the whole paragraph by the given amount. +shiftContents :: Int32 -> Int32 -> ParagraphLayout d -> ParagraphLayout d +shiftContents dx dy (ParagraphLayout _ ls frags) = + ParagraphLayout pRect' ls' frags' + where + pRect' = containRects $ map lineRect ls + ls' = map (shiftLine dx dy) ls + frags' = map (shiftFragment dx dy) frags -- | Combine fragments from two `ParagraphLayout`s. --- --- The containing rectangle will be recalculated. -appendFragments :: ParagraphLayout d -> ParagraphLayout d -> ParagraphLayout d -appendFragments (ParagraphLayout _ a) (ParagraphLayout _ b) = - paragraphLayout $ a ++ b +appendContents :: ParagraphLayout d -> ParagraphLayout d -> ParagraphLayout d +appendContents (ParagraphLayout _ ls1 frags1) (ParagraphLayout _ ls2 frags2) = + ParagraphLayout pRect ls frags + where + pRect = containRects $ map lineRect ls + ls = ls1 ++ ls2 + frags = frags1 ++ frags2 -- | Return all shaped runs in the paragraph. shapedRuns :: ParagraphLayout d -> [ShapedRun] diff --git a/test/Data/Text/ParagraphLayout/PrettyShow.hs b/test/Data/Text/ParagraphLayout/PrettyShow.hs index 4b11bb0..3972230 100644 --- a/test/Data/Text/ParagraphLayout/PrettyShow.hs +++ b/test/Data/Text/ParagraphLayout/PrettyShow.hs @@ -147,7 +147,7 @@ instance Show d => PrettyShow (Fragment d) where ] instance Show d => PrettyShow (Rich.ParagraphLayout d) where - prettyShow (Rich.ParagraphLayout pr frags) = concat + prettyShow (Rich.ParagraphLayout pr ls frags) = concat [ "ParagraphLayout" , newline , indent1 @@ -155,6 +155,11 @@ instance Show d => PrettyShow (Rich.ParagraphLayout d) where , show pr , newline , indent1 + , ", paragraphLines =" + , newline + , concat $ commaFirstList indent2 $ map show ls + , newline + , indent1 , ", paragraphFragments =" , newline , concat $ commaFirstList indent2 $ map prettyShow frags diff --git a/test/Data/Text/ParagraphLayout/RichSpec.hs b/test/Data/Text/ParagraphLayout/RichSpec.hs index f63af44..4806ac4 100644 --- a/test/Data/Text/ParagraphLayout/RichSpec.hs +++ b/test/Data/Text/ParagraphLayout/RichSpec.hs @@ -109,7 +109,8 @@ spec = do let input w = loremIpsumParagraph font $ opts { paragraphMaxWidth = w } let result20em = layoutRich $ input commonWrapWidth - let resultSafe = layoutRich $ input expectedSafeWidth + -- Adding + 1 to work around rounding errors. + let resultSafe = layoutRich $ input (expectedSafeWidth + 1) let resultUnsafe = layoutRich $ input (expectedSafeWidth - 1) let resultUnsafe2 = layoutRich $ input (expectedSafeWidth - 2) -- 2.30.2