module Data.Text.ParagraphLayout.Internal.Fragment ( Fragment (..) , ShapedRun , fragmentSpacedRect , shapedRun , shiftFragment ) where import Data.Int (Int32) import Data.Text.Glyphize (GlyphInfo, GlyphPos) import Data.Text.ParagraphLayout.Internal.AncestorBox import Data.Text.ParagraphLayout.Internal.Rect -- | A unit of text laid out in a rectangular area. -- -- Roughly equivalent to the term /text fragment/ as used in -- [CSS Display Module Level 3](https://www.w3.org/TR/css-display-3/). -- -- An input span (or /text sequence/ in CSS terms) can be broken into multiple -- fragments because of line breaking, because of bidirectional ordering, -- or because it contains glyphs from multiple scripts. data Fragment d = Fragment { fragmentUserData :: d -- ^ User-defined data associated with the input text span that produced -- this fragment. , fragmentLine :: Int -- ^ Logical number of the line box holding the fragment, starting at 1. -- Fragments with the same line number are on the same line and will not be -- separated by page breaks. , fragmentAncestorBoxes :: [AncestorBox d] -- ^ Information about inline boxes which contain this fragment -- (starting from the nearest ancestor and continuing upwards through the -- tree, up to but excluding the root) and the spacing required by them. , fragmentContentRect :: Rect Int32 -- ^ Physical position of the fragment within the paragraph, calculated -- using all glyph advances in this fragment and the used font's ascent -- and descent metrics. -- -- This can be used to determine the /content box/ of an inline element -- as understood in CSS. , fragmentRect :: Rect Int32 -- ^ Physical position of the fragment within the paragraph, calculated -- using all glyph advances in this fragment and the calculated line height. -- -- This is the space that the glyphs "take up" and is probably what you -- want to use for detecting position-based events such as mouse clicks. -- -- Beware that actual glyphs will not be drawn exactly to the borders of -- this rectangle -- they may be offset inwards and they can also extend -- outwards! -- -- These are not the typographic bounding boxes that you use for determining -- the area to draw on -- you need FreeType or a similar library for that. -- -- The origin coordinates are relative to the paragraph. -- -- The vertical extent of this rectangle is the equivalent of -- /layout bounds/ defined by CSS. -- -- Box spacing is not included. , fragmentPen :: (Int32, Int32) -- ^ Coordinates of the initial pen position, from which the first glyph -- should be drawn, relative to the origin of the `fragmentRect`. Each -- glyph's `Data.Text.Glyphize.x_advance` or `Data.Text.Glyphize.y_advance` -- are then used to move the pen position for the next glyph. , fragmentGlyphs :: [(GlyphInfo, GlyphPos)] -- ^ Glyphs contained in the fragment, as returned from HarfBuzz. } deriving (Eq, Read, Show) -- | Physical position of the fragment within the paragraph, with spacing -- added to the glyph advances. This is the space that the fragment takes up -- in the paragraph. fragmentSpacedRect :: Fragment d -> Rect Int32 fragmentSpacedRect (Fragment { fragmentRect = r, fragmentAncestorBoxes = bs }) | x_size r >= 0 = r { x_origin = x_origin r - leftSpacing , x_size = x_size r + leftSpacing + rightSpacing } | otherwise = r { x_origin = x_origin r + rightSpacing , x_size = x_size r - leftSpacing - rightSpacing } where leftSpacing = totalLeftSpacing bs rightSpacing = totalRightSpacing bs -- | A simplified representation of a box fragment, suitable for passing to a -- text drawing library but lacking detailed size information. type ShapedRun = (Int32, Int32, [(GlyphInfo, GlyphPos)]) -- | Convert a `Fragment` to a `ShapedRun`. shapedRun :: Fragment d -> ShapedRun shapedRun f = (x, y, g) where x = x_origin r + px y = y_origin r + py g = fragmentGlyphs f (px, py) = fragmentPen f r = fragmentRect f -- | Add @dx@ and @dy@ to the fragment's `x_origin` and `y_origin`, -- respectively. shiftFragment :: Int32 -> Int32 -> Fragment d -> Fragment d shiftFragment dx dy f = f' where f' = f { fragmentContentRect = cr', fragmentRect = r' } cr' = cr { x_origin = x_origin r + dx, y_origin = y_origin r + dy } cr = fragmentContentRect f r' = r { x_origin = x_origin r + dx, y_origin = y_origin r + dy } r = fragmentRect f