~jaro/balkon

85a32d42819fa08632855b2106af255f2e52e3fa — Jaro 1 year, 6 months ago 72076d1
Add centre/right paragraph alignment.
M balkon.cabal => balkon.cabal +1 -0
@@ 114,6 114,7 @@ library balkon-internal
        Data.Text.ParagraphLayout.Internal.LineHeight,
        Data.Text.ParagraphLayout.Internal.LinePagination,
        Data.Text.ParagraphLayout.Internal.Paginable,
        Data.Text.ParagraphLayout.Internal.ParagraphAlignment,
        Data.Text.ParagraphLayout.Internal.ParagraphConstruction,
        Data.Text.ParagraphLayout.Internal.ParagraphOptions,
        Data.Text.ParagraphLayout.Internal.Plain,

M lib/Data/Text/ParagraphLayout/Rich.hs => lib/Data/Text/ParagraphLayout/Rich.hs +3 -0
@@ 5,6 5,7 @@ module Data.Text.ParagraphLayout.Rich
    -- * Input paragraph
    ( Paragraph (Paragraph)
    , constructParagraph
    , ParagraphAlignment (AlignLeft, AlignRight, AlignCentreH)
    , BoxSpacing (BoxSpacingLeftRight)
    , LineHeight (Absolute, Normal)
    , ParagraphOptions


@@ 12,6 13,7 @@ module Data.Text.ParagraphLayout.Rich
    -- ** Paragraph options
    -- | These are record selectors that can be used for reading
    -- as well as updating specific option fields.
    , paragraphAlignment
    , paragraphMaxWidth
    -- NOTE: `paragraphFont` is only used by the legacy plain text interface,
    --       and is therefore not exported here.


@@ 67,6 69,7 @@ import Data.Text.ParagraphLayout.Internal.AncestorBox
import Data.Text.ParagraphLayout.Internal.BoxOptions
import Data.Text.ParagraphLayout.Internal.Fragment
import Data.Text.ParagraphLayout.Internal.LineHeight
import Data.Text.ParagraphLayout.Internal.ParagraphAlignment
import Data.Text.ParagraphLayout.Internal.ParagraphOptions
import Data.Text.ParagraphLayout.Internal.Rich
import Data.Text.ParagraphLayout.Internal.Rich.Paragraph

M src/Data/Text/ParagraphLayout/Internal/Layout.hs => src/Data/Text/ParagraphLayout/Internal/Layout.hs +13 -4
@@ 32,6 32,7 @@ import Data.Text.ParagraphLayout.Internal.BiDiReorder
import Data.Text.ParagraphLayout.Internal.Break
import Data.Text.ParagraphLayout.Internal.Fragment
import Data.Text.ParagraphLayout.Internal.LineHeight
import Data.Text.ParagraphLayout.Internal.ParagraphAlignment
import Data.Text.ParagraphLayout.Internal.ParagraphExtents
import qualified Data.Text.ParagraphLayout.Internal.ProtoFragment as PF
import qualified Data.Text.ParagraphLayout.Internal.ProtoLine as PL


@@ 57,14 58,15 @@ type ProtoFragmentWithBoxes d = WithBoxes d (ProtoFragmentWithSpan d)
-- The output is a flat list of fragments positioned in both dimensions.
layoutAndAlignLines
    :: Direction
    -> ParagraphAlignment
    -> Int32
    -> NonEmpty (WithSpan d Run)
    -> [FragmentWithSpan d]
layoutAndAlignLines dir maxWidth runs = frags
layoutAndAlignLines dir align maxWidth runs = frags
    where
        frags = concatMap NonEmpty.toList fragsInLines
        (_, fragsInLines) = mapAccumL positionLine originY numberedLines
        positionLine = positionLineH dir maxWidth
        positionLine = positionLineH dir align maxWidth
        numberedLines = zip [1 ..] canonicalLines
        canonicalLines = fmap reorderProtoFragments logicalLines
        logicalLines = nonEmptyItems $ layoutLines maxWidth [] runs


@@ 106,11 108,12 @@ layoutLines maxWidth openBoxes runs = case nonEmpty rest of
-- TODO: For rich text, allow other types of vertical alignment.
positionLineH
    :: Direction
    -> ParagraphAlignment
    -> Int32
    -> Int32
    -> (Int, PL.ProtoLine NonEmpty d)
    -> (Int32, NonEmpty (FragmentWithSpan d))
positionLineH dir maxWidth originY (num, pl) = (nextY, frags)
positionLineH dir align maxWidth originY (num, pl) = (nextY, frags)
    where
        nextY = minimum $ fmap y_min rects
        rects = fmap (\ (WithSpan _ r) -> fragmentRect r) frags


@@ 118,7 121,7 @@ positionLineH dir maxWidth originY (num, pl) = (nextY, frags)
        wpfs = PL.applyBoxes pl
        originX = paragraphOriginX + if lineWidth > maxWidth
            then overflowingLineOffset dir (lineWidth - maxWidth)
            else 0
            else fittingLineOffset align (maxWidth - lineWidth)
        lineWidth = PL.width pl

-- | Inline offset of the first fragment on a line that overflows.


@@ 129,6 132,12 @@ overflowingLineOffset DirRTL excess = -excess
-- TODO: Check if the sign needs to be flipped for vertical text.
overflowingLineOffset DirBTT excess = -excess

-- | Inline offset of the first fragment on a line with extra blank space.
fittingLineOffset :: ParagraphAlignment -> Int32 -> Int32
fittingLineOffset AlignLeft _ = 0
fittingLineOffset AlignRight slack = slack
fittingLineOffset AlignCentreH slack = slack `div` 2

-- | Position the given horizontal fragment on a line,
-- using @originY@ as its top edge and @originX@ as its left edge,
-- returning the X coordinate of its right edge for continuation.

A src/Data/Text/ParagraphLayout/Internal/ParagraphAlignment.hs => src/Data/Text/ParagraphLayout/Internal/ParagraphAlignment.hs +32 -0
@@ 0,0 1,32 @@
module Data.Text.ParagraphLayout.Internal.ParagraphAlignment
    ( ParagraphAlignment (..)
    )
where

-- | Determines how the contents of the paragraph should be aligned
-- horizontally, that is, how should fragments be stacked inside each line
-- when there is space available.
--
-- This has no effect on lines with overflowing content.
data ParagraphAlignment

    = AlignLeft
    -- ^ Stack fragments to the left edge of each line,
    -- leaving free space on the right side.
    --
    -- The leftmost fragment on each non-overflowing line will have its
    -- `Data.Text.ParagraphLayout.Rect.x_min` equal to @0@.

    | AlignRight
    -- ^ Stack fragments to the right edge of each line,
    -- leaving free space on the left side.
    --
    -- The rightmost fragment on each non-overflowing line will have its
    -- `Data.Text.ParagraphLayout.Rect.x_max` equal to
    -- `Data.Text.ParagraphLayout.Rich.paragraphMaxWidth`.

    | AlignCentreH
    -- ^ Stack fragments to the horizontal centre of each line,
    -- leaving free space split evenly between each side.

    deriving (Eq, Read, Show)

M src/Data/Text/ParagraphLayout/Internal/ParagraphOptions.hs => src/Data/Text/ParagraphLayout/Internal/ParagraphOptions.hs +5 -0
@@ 8,6 8,7 @@ import Data.Int (Int32)
import Data.Text.Glyphize (Font, emptyFont)

import Data.Text.ParagraphLayout.Internal.LineHeight
import Data.Text.ParagraphLayout.Internal.ParagraphAlignment

-- | Defines options relevant to the entire paragraph.
--


@@ 24,6 25,9 @@ data ParagraphOptions = ParagraphOptions
    , paragraphLineHeight :: LineHeight
    -- ^ Preferred line height of the resulting fragments.

    , paragraphAlignment :: ParagraphAlignment
    -- ^ Alignment of non-overflowing lines within the paragraph.

    , paragraphMaxWidth :: Int32
    -- ^ Line width at which line breaking should occur.
    -- Lines will be broken at language-appropriate boundaries.


@@ 39,5 43,6 @@ defaultParagraphOptions :: ParagraphOptions
defaultParagraphOptions = ParagraphOptions
    { paragraphFont = emptyFont
    , paragraphLineHeight = Normal
    , paragraphAlignment = AlignLeft
    , paragraphMaxWidth = maxBound
    }

M src/Data/Text/ParagraphLayout/Internal/Rich.hs => src/Data/Text/ParagraphLayout/Internal/Rich.hs +2 -1
@@ 30,10 30,11 @@ layoutRich p = paragraphLayout $ map unwrap frags
        unwrap (WithSpan rs frag) =
            frag { fragmentUserData = RS.spanUserData rs }
        frags = case nonEmpty wrappedRuns of
            Just xs -> layoutAndAlignLines dir maxWidth xs
            Just xs -> layoutAndAlignLines dir align maxWidth xs
            Nothing -> []
        wrappedRuns = spansToRunsWrapped spans
        dir = textDirection rootTextOpts
        align = paragraphAlignment opts
        maxWidth = paragraphMaxWidth opts
        spans = resolveSpans p