module Data.Text.ParagraphLayout.Internal.ProtoLine
( ProtoLine (..)
, nonEmpty
, visible
, width
, applyBoxes
)
where
import Data.Int (Int32)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.Text.ParagraphLayout.Internal.ApplyBoxes as Algo
import Data.Text.ParagraphLayout.Internal.ProtoFragment
import Data.Text.ParagraphLayout.Internal.ResolvedBox
import Data.Text.ParagraphLayout.Internal.WithSpan
-- | Contents of a line that have not been visually ordered or positioned yet.
data ProtoLine f d = ProtoLine
{ protoFragments :: f (WithSpan d ProtoFragment)
-- ^ Fragments on the line, in logical order.
-- Intended to be either a regular list or a non-empty list.
, prevOpenBoxes :: [ResolvedBox d]
-- ^ Boxes whose starts are located on preceding lines.
-- The spacing for their start edge will not count towards this line.
, nextOpenBoxes :: [ResolvedBox d]
-- ^ Boxes whose ends are located on following lines.
-- The spacing for their end edge will not count towards this line.
}
-- | Covert a line with a regular list of fragments into `Just` a line with
-- a non-empty list of fragments, or `Nothing` if there are no fragments.
nonEmpty :: ProtoLine [] d -> Maybe (ProtoLine NonEmpty d)
nonEmpty (ProtoLine [] _ _) = Nothing
nonEmpty pl@(ProtoLine { protoFragments = (f : fs) }) =
Just pl { protoFragments = f :| fs }
-- | `True` if this line should generate a visible line box.
visible :: Foldable f => ProtoLine f d -> Bool
visible pl = any visibleProtoFragment $ protoFragments pl
visibleProtoFragment :: WithSpan d ProtoFragment -> Bool
visibleProtoFragment (WithSpan _ pf) =
hasGlyphs || hasHardBreak
where
hasGlyphs = not $ null $ glyphs pf
hasHardBreak = hardBreak pf
-- | Total width of the line (content and spacing).
width :: (Foldable f, Functor f) => ProtoLine f d -> Int32
width pl = totalAdvances pl + totalBoxesStart pl + totalBoxesEnd pl
-- | Determine which horizontal fragments are the leftmost and which are the
-- rightmost within their ancestor inline boxes.
--
-- The input must be ordered from left to right.
applyBoxes :: ProtoLine NonEmpty d ->
NonEmpty (Algo.WithBoxes d (WithSpan d ProtoFragment))
applyBoxes pl = Algo.applyBoxes prevOpen nextOpen pfs
where
prevOpen = prevOpenBoxes pl
nextOpen = nextOpenBoxes pl
pfs = protoFragments pl
-- | Total glyph advances on the line.
totalAdvances :: (Foldable f, Functor f) => ProtoLine f d -> Int32
totalAdvances pl = sum $ fmap getAdvance $ protoFragments pl
where getAdvance (WithSpan _ pf) = advance pf
-- | Total spacing for boxes starting on the line.
totalBoxesStart :: (Foldable f, Functor f) => ProtoLine f d -> Int32
totalBoxesStart pl = sum $ fmap boxStartSpacing $ boxesStart pl
-- | Total spacing for boxes ending on the line.
totalBoxesEnd :: (Foldable f, Functor f) => ProtoLine f d -> Int32
totalBoxesEnd pl = sum $ fmap boxEndSpacing $ boxesEnd pl
-- | Boxes that start on the given line.
boxesStart :: (Foldable f, Functor f) => ProtoLine f d -> [ResolvedBox d]
boxesStart pl = allBoxes (protoFragments pl) `diff` prevOpenBoxes pl
-- | Boxes that end on the given line.
boxesEnd :: (Foldable f, Functor f) => ProtoLine f d -> [ResolvedBox d]
boxesEnd pl = allBoxes (protoFragments pl) `diff` nextOpenBoxes pl