M CHANGELOG.md => CHANGELOG.md +3 -0
@@ 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.
M lib/Data/Text/ParagraphLayout/Rich.hs => lib/Data/Text/ParagraphLayout/Rich.hs +3 -0
@@ 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.
M src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs => src/Data/Text/ParagraphLayout/Internal/BoxOptions.hs +56 -1
@@ 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
}
M src/Data/Text/ParagraphLayout/Internal/ProtoLine.hs => src/Data/Text/ParagraphLayout/Internal/ProtoLine.hs +10 -3
@@ 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 <https://www.w3.org/TR/css-inline-3/#invisible-line-boxes>.
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