module Data.Text.ParagraphLayout.Internal.Paragraph ( Paragraph (..) , ParagraphOptions (..) , paragraphSpanBounds , paragraphSpanTexts , paragraphText ) 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.Internal (Text (Text)) import Data.Text.ParagraphLayout.Internal.LineHeight 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) -- | Calculate the offsets into the `Paragraph`'s underlying `Data.Text.Array` -- where each span starts and ends, in ascending order. The resulting list -- will be one larger than the list of input spans. -- -- You can use this function to verify that Balkón will slice the input text -- correctly. paragraphSpanBounds :: Paragraph -> NonEmpty Int paragraphSpanBounds (Paragraph _ initialOffset spans _) = -- TODO: Consider adding checks for array bounds. NonEmpty.scanl (+) initialOffset (map spanLength spans) -- | Turn each span of the input `Paragraph` into a `Text`. -- -- You can use this function to verify that Balkón will slice the input text -- correctly. paragraphSpanTexts :: Paragraph -> [Text] paragraphSpanTexts p@(Paragraph arr _ _ _) = zipWith toText sStarts sEnds where toText start end = Text arr start (end - start) sStarts = NonEmpty.init sBounds sEnds = NonEmpty.tail sBounds sBounds = paragraphSpanBounds p -- | Turn all spans of the input `Paragraph` into one combined `Text`. -- -- You can use this function to verify that Balkón will slice the input text -- correctly. paragraphText :: Paragraph -> Text paragraphText p@(Paragraph arr _ _ _) = Text arr start (end - start) where start = NonEmpty.head sBounds end = NonEmpty.last sBounds sBounds = paragraphSpanBounds p