~jaro/balkon

e29432083612d3bba827cd0e9a3594be8c5b95ed — Jaro 10 months ago 6e011c8
Allow explicit control of box collapsing.
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