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