From e29432083612d3bba827cd0e9a3594be8c5b95ed Mon Sep 17 00:00:00 2001 From: Jaro Date: Tue, 20 Jun 2023 12:41:50 +0200 Subject: [PATCH] Allow explicit control of box collapsing. --- CHANGELOG.md | 3 + lib/Data/Text/ParagraphLayout/Rich.hs | 3 + .../ParagraphLayout/Internal/BoxOptions.hs | 57 ++++++++++++++++++- .../ParagraphLayout/Internal/ProtoLine.hs | 13 ++++- 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2bfc6..6e10618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ * For backwards compatibility, left alignment remains the default. Consider updating your code to use start alignment instead. +* Added option to prevent making boxes invisible when they contain no glyphs + and no forced (hard) line breaks. + * 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. diff --git a/lib/Data/Text/ParagraphLayout/Rich.hs b/lib/Data/Text/ParagraphLayout/Rich.hs index b58b113..5d3b0d2 100644 --- a/lib/Data/Text/ParagraphLayout/Rich.hs +++ b/lib/Data/Text/ParagraphLayout/Rich.hs @@ -12,6 +12,7 @@ module Data.Text.ParagraphLayout.Rich , AlignRight , AlignCentreH ) + , BoxCollapse (AllowBoxCollapse, AvoidBoxCollapse) , BoxSpacing (BoxSpacingLeftRight) , LineHeight (Absolute, Normal) , ParagraphOptions @@ -35,6 +36,8 @@ module Data.Text.ParagraphLayout.Rich -- | These are record selectors that can be used for reading -- as well as updating specific option fields. , boxSpacing + , boxCollapse + , activateBoxSpacing -- ** Text options -- | These are record selectors that can be used for reading -- as well as updating specific option fields. diff --git a/src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs b/src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs index 8c921c9..ea009de 100644 --- a/src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs +++ b/src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs @@ -1,6 +1,8 @@ module Data.Text.ParagraphLayout.Internal.BoxOptions - ( BoxOptions (..) + ( BoxCollapse (..) + , BoxOptions (..) , BoxSpacing (..) + , activateBoxSpacing , defaultBoxOptions ) where @@ -20,6 +22,13 @@ data BoxOptions = BoxOptions -- -- This can be used to reserve space for a left margin, border, -- and/or padding. + -- + -- In order to get CSS-compliant behaviour, consider using + -- `activateBoxSpacing` instead. + + , boxCollapse :: BoxCollapse + -- ^ Determines how this box affects the height of a line that would + -- otherwise be empty. -- TODO: textVerticalAlign @@ -47,8 +56,54 @@ data BoxSpacing deriving (Eq, Show, Read) +-- | Determines how an inline box affects the height of a line that would +-- otherwise be empty. +-- +-- Note that for this setting to have any effect, the box must be the ancestor +-- of at least one (possibly empty) text sequence, so that it can generate +-- a `Data.Text.ParagraphLayout.Rich.Fragment`. +-- +-- For CSS-compliant behaviour: +-- +-- - Ensure that empty boxes contain empty text sequences as mentioned above. +-- +-- - Set `AllowBoxCollapse` for boxes with zero margins, padding, +-- and borders. +-- +-- - Set `AvoidBoxCollapse` for boxes with non-zero margins, padding, +-- or borders. +-- +-- Note that `BoxSpacing` may contain zero values even when calculated from +-- non-zero CSS values, because of rounding to integer values and/or because +-- of adding together positive and negative values that cancel out. You should +-- therefore compare the individual computed values for margins, padding, and +-- borders with zero, and set `boxCollapse` to `AllowBoxCollapse` if and only +-- if all these values are equal to zero. +data BoxCollapse + + = AllowBoxCollapse + -- ^ The box should disappear when possible to make empty lines invisible. + + | AvoidBoxCollapse + -- ^ The box should be preserved when possible, making lines visible + -- even when they would otherwise be empty. + + deriving (Eq, Read, Show) + -- | `BoxOptions` with default values. defaultBoxOptions :: BoxOptions defaultBoxOptions = BoxOptions { boxSpacing = BoxSpacingLeftRight 0 0 + , boxCollapse = AllowBoxCollapse + } + +-- | Shorthand for updating `boxSpacing` and setting `boxCollapse` +-- to `AvoidBoxCollapse`. +-- +-- Can be used to activate CSS-compliant rendering of empty boxes whose +-- spacing is calculated from non-zero values. +activateBoxSpacing :: BoxSpacing -> BoxOptions -> BoxOptions +activateBoxSpacing spacing boxOptions = boxOptions + { boxSpacing = spacing + , boxCollapse = AvoidBoxCollapse } diff --git a/src/Data/Text/ParagraphLayout/Internal/ProtoLine.hs b/src/Data/Text/ParagraphLayout/Internal/ProtoLine.hs index 3e4f58a..d5abd24 100644 --- a/src/Data/Text/ParagraphLayout/Internal/ProtoLine.hs +++ b/src/Data/Text/ParagraphLayout/Internal/ProtoLine.hs @@ -11,8 +11,10 @@ import Data.Int (Int32) import Data.List.NonEmpty (NonEmpty ((:|))) import qualified Data.Text.ParagraphLayout.Internal.ApplyBoxes as Algo +import Data.Text.ParagraphLayout.Internal.BoxOptions import Data.Text.ParagraphLayout.Internal.ProtoFragment import Data.Text.ParagraphLayout.Internal.ResolvedBox +import Data.Text.ParagraphLayout.Internal.ResolvedSpan import Data.Text.ParagraphLayout.Internal.WithSpan -- | Contents of a line that have not been visually ordered or positioned yet. @@ -39,16 +41,21 @@ nonEmpty (ProtoLine [] _ _) = Nothing nonEmpty pl@(ProtoLine { protoFragments = (f : fs) }) = Just pl { protoFragments = f :| fs } --- | `True` if this line should generate a visible line box. +-- | `True` if this line should generate a visible line box, +-- or `False` if this line should generate an /invisible line box/ +-- as defined by . visible :: Foldable f => ProtoLine f d -> Bool visible pl = any visibleProtoFragment $ protoFragments pl visibleProtoFragment :: WithSpan d ProtoFragment -> Bool -visibleProtoFragment (WithSpan _ pf) = - hasGlyphs || hasHardBreak +visibleProtoFragment (WithSpan rs pf) = + hasGlyphs || hasHardBreak || hasNonCollapsibleBoxes where hasGlyphs = not $ null $ glyphs pf hasHardBreak = hardBreak pf + hasNonCollapsibleBoxes = + any (== AvoidBoxCollapse) $ map (boxCollapse . boxOptions) boxes + boxes = spanBoxes rs -- | Total width of the line (content and spacing). width :: (Foldable f, Functor f) => ProtoLine f d -> Int32 -- 2.30.2