M src/Data/Text/ParagraphLayout/Internal/Layout.hs => src/Data/Text/ParagraphLayout/Internal/Layout.hs +29 -28
@@ 189,33 189,7 @@ positionFragmentH line originY originX (WithBoxes lbs (WithSpan rs pf) rbs) =
             , VO.baseline = baseline
             , VO.fontBottom = fontBottom
             , VO.layoutBottom = layoutBottom
-            } = VO.alignLayoutTop originY $ verticalOffsets (WithSpan rs pf)
-
--- | Vertical offsets for the given fragment, with baseline set to 0.
-verticalOffsets :: ProtoFragmentWithSpan d -> VO.VerticalOffsets
-verticalOffsets (WithSpan rs pf) = VO.VerticalOffsets
-    { VO.layoutTop = ascent + topHalfLeading
-    , VO.fontTop = ascent
-    , VO.baseline = 0
-    , VO.fontBottom = - descent
-    , VO.layoutBottom = - descent - bottomHalfLeading
-    }
-    where
-        -- non-negative leading values iff `lineHeight` > `normalLineHeight`
-        leading = lineHeight - normalLineHeight
-        topHalfLeading = -((-leading) `div` 2)
-        bottomHalfLeading = leading `div` 2
-        -- `normalLineHeight` > 0 for horizontal fonts
-        normalLineHeight = ascent + descent
-        -- `ascent` >= 0 for horizontal fonts
-        ascent = ascender extents
-        -- `descent` >= 0 for horizontal fonts
-        descent = - descender extents
-        extents = fontExtentsForDir (textFont opts) (Just $ PF.direction pf)
-        lineHeight = case textLineHeight opts of
-            Normal -> normalLineHeight
-            Absolute h -> h
-        opts = RS.spanTextOptions rs
+            } = VO.alignLayoutTop originY $ PF.verticalOffsets pf
 
 ancestorBoxes
     :: [RB.ResolvedBox d]
@@ 385,12 359,39 @@ layoutRunsH runs = fmap layoutRunH runs
 layoutRunH :: WithSpan d Run -> ProtoFragmentWithSpan d
 layoutRunH (WithSpan rs run) = WithSpan rs pf
     where
-        pf = PF.protoFragmentH dir lvl glyphs hard
+        pf = PF.protoFragmentH dir lvl vo glyphs hard
         glyphs = shapeRun (WithSpan rs run)
         dir = runDirection run
         lvl = runLevel run
+        vo = verticalOffsets rs dir
         hard = runHardBreak run
 
+-- | Vertical offsets for the given fragment, with baseline set to 0.
+verticalOffsets :: RS.ResolvedSpan d -> Direction -> VO.VerticalOffsets
+verticalOffsets rs dir = VO.VerticalOffsets
+    { VO.layoutTop = ascent + topHalfLeading
+    , VO.fontTop = ascent
+    , VO.baseline = 0
+    , VO.fontBottom = - descent
+    , VO.layoutBottom = - descent - bottomHalfLeading
+    }
+    where
+        -- non-negative leading values iff `lineHeight` > `normalLineHeight`
+        leading = lineHeight - normalLineHeight
+        topHalfLeading = -((-leading) `div` 2)
+        bottomHalfLeading = leading `div` 2
+        -- `normalLineHeight` > 0 for horizontal fonts
+        normalLineHeight = ascent + descent
+        -- `ascent` >= 0 for horizontal fonts
+        ascent = ascender extents
+        -- `descent` >= 0 for horizontal fonts
+        descent = - descender extents
+        extents = fontExtentsForDir (textFont opts) (Just dir)
+        lineHeight = case textLineHeight opts of
+            Normal -> normalLineHeight
+            Absolute h -> h
+        opts = RS.spanTextOptions rs
+
 -- | Calculate layout for the given run independently of its position.
 shapeRun :: WithSpan d Run -> [(GlyphInfo, GlyphPos)]
 shapeRun (WithSpan rs run) = shape font buffer features
 
M src/Data/Text/ParagraphLayout/Internal/ProtoFragment.hs => src/Data/Text/ParagraphLayout/Internal/ProtoFragment.hs +12 -4
@@ 1,5 1,5 @@
 module Data.Text.ParagraphLayout.Internal.ProtoFragment
-    ( ProtoFragment (direction, advance, glyphs, hardBreak)
+    ( ProtoFragment (direction, verticalOffsets, advance, glyphs, hardBreak)
     , protoFragmentH
     )
 where
@@ 8,6 8,7 @@ import Data.Int (Int32)
 import Data.Text.Glyphize (Direction (..), GlyphInfo, GlyphPos (x_advance))
 
 import qualified Data.Text.ParagraphLayout.Internal.BiDiLevels as BiDi
+import qualified Data.Text.ParagraphLayout.Internal.VerticalOffsets as VO
 
 -- | A box fragment which has not been positioned yet.
 data ProtoFragment = ProtoFragment
@@ 16,6 17,8 @@ data ProtoFragment = ProtoFragment
     , level :: BiDi.Level
     -- ^ BiDi embedding level.
     -- Should be even for LTR fragments and odd for RTL fragments.
+    , verticalOffsets :: VO.VerticalOffsets
+    -- ^ Vertical offsets used for aligning the fragment within its line.
     , advance :: Int32
     -- ^ Total advance of glyphs in this fragment,
     -- depending on the text direction.
@@ 26,9 29,14 @@ data ProtoFragment = ProtoFragment
 
 -- | Construct a `ProtoFragment`, automatically calculating the total advance
 -- for a horizontal text direction.
-protoFragmentH :: Direction -> BiDi.Level -> [(GlyphInfo, GlyphPos)] -> Bool ->
-    ProtoFragment
-protoFragmentH dir lvl gs hard = ProtoFragment dir lvl adv gs hard
+protoFragmentH
+    :: Direction
+    -> BiDi.Level
+    -> VO.VerticalOffsets
+    -> [(GlyphInfo, GlyphPos)]
+    -> Bool
+    -> ProtoFragment
+protoFragmentH dir lvl vo gs hard = ProtoFragment dir lvl vo adv gs hard
     where
         adv = sum $ map (x_advance . snd) gs