module Data.Text.ParagraphLayout.Internal.Paragraph
(Paragraph(..)
,ParagraphLayout(..)
,ParagraphOptions(..)
,paragraphLayout
,paragraphOriginX
,paragraphOriginY
,paragraphSpanBounds
)
where
import Data.Int (Int32)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Text.Array (Array)
import Data.Text.Glyphize (Font)
import Data.Text.ParagraphLayout.Internal.LineHeight
import Data.Text.ParagraphLayout.Internal.Rect
import Data.Text.ParagraphLayout.Internal.Span
-- | Text to be laid out as a single paragraph.
--
-- May be divided into any number of neighbouring spans, each of which will
-- be represented as a separate `SpanLayout` in the resulting layout.
--
-- The input text must be encoded as UTF-8 in a contiguous byte array.
--
-- You may need to use "Data.Text.Internal" in order to determine the byte
-- array and the necessary offsets to construct the paragraph without copying
-- data.
--
-- For simple use cases, it may be sufficient to construct paragraphs using
-- [ParagraphConstruction]("Data.Text.ParagraphLayout.ParagraphConstruction").
data Paragraph = Paragraph
Array
-- ^ A byte array containing the whole text to be laid out, in UTF-8.
--
-- This array will be passed to "Data.Text.Glyphize", which passes it to
-- [@hb_buffer_add_utf8()@]
-- (https://harfbuzz.github.io/harfbuzz-hb-buffer.html#hb-buffer-add-utf8).
--
-- In the output, `Data.Text.Glyphize.cluster` will be a byte offset of
-- the corresponding input character from this array.
Int
-- ^ Byte offset of the first span from the start of the byte array.
-- Any characters preceding this offset will not be shaped, but may still
-- be used to influence the shape of neighbouring characters.
[Span]
-- ^ Parts of the text to be laid out, in logical order.
-- The initial offset plus total length of all spans must not exceed
-- the bounds of the byte array.
-- Any characters following the last span will not be shaped, but may still
-- be used to influence the shape of neighbouring characters.
ParagraphOptions
-- ^ Options applying to the paragraph as a whole.
data ParagraphOptions = ParagraphOptions
{ paragraphFont :: Font
-- ^ Font to be used for shaping and measurement.
-- Make sure to set its scale (see `Data.Text.Glyphize.optionScale`) using
-- the same units that you want in the output.
, paragraphLineHeight :: LineHeight
-- ^ Preferred line height of the resulting box fragments.
, paragraphMaxWidth :: Int32
-- ^ Line width at which line breaking should occur.
-- Lines will be broken at language-appropriate boundaries.
-- If a line still exceeds this limit then, it will be broken at character
-- boundaries, and if it already consists of a single cluster that cannot
-- be further broken down, it will overflow.
}
deriving (Eq, Show)
-- | The resulting layout of the whole paragraph.
data ParagraphLayout = ParagraphLayout
{ paragraphRect :: Rect Int32
-- ^ The containing block (CSS3).
, spanLayouts :: [SpanLayout]
}
deriving (Eq, Read, Show)
-- | Calculate the offsets into the `Paragraph`'s underlying `Array` where each
-- span starts and ends, in ascending order. The resulting list will be one
-- larger than the list of input spans.
paragraphSpanBounds :: Paragraph -> NonEmpty Int
paragraphSpanBounds (Paragraph _ initialOffset spans _) =
NonEmpty.scanl (+) initialOffset (map spanLength spans)
paragraphOriginX :: (Num a) => a
paragraphOriginX = 0
paragraphOriginY :: (Num a) => a
paragraphOriginY = 0
empty :: (Num a) => Rect a
empty = Rect
{ x_origin = paragraphOriginX
, y_origin = paragraphOriginY
, x_size = 0
, y_size = 0
}
containRects :: (Ord a, Num a) => [Rect a] -> Rect a
containRects = foldr union empty
-- | Wrap the given `SpanLayout`s and compute their containing rectangle.
paragraphLayout :: [SpanLayout] -> ParagraphLayout
paragraphLayout sls = ParagraphLayout pRect sls
where pRect = containRects $ concat $ map spanRects sls