@@ 5,6 5,7 @@ module Graphics.Layout.Box(Border(..), mapX, mapY,
         Size(..), mapSizeX, mapSizeY,
         PaddedBox(..), zeroBox, lengthBox, mapX', mapY',
         width, height, minWidth, minHeight, maxWidth, maxHeight,
+        leftSpace, rightSpace, topSpace, bottomSpace, hSpace, vSpace,
         Length(..), mapAuto, lowerLength, Zero(..), CastDouble(..)) where
 
 -- | Amount of space surrounding the box.
@@ 110,6 111,13 @@ maxWidth PaddedBox {..} = left margin + left border + left padding +
 maxHeight PaddedBox {..} = top margin + top border + top padding +
     block max + bottom padding + bottom border + bottom margin
 
+leftSpace PaddedBox {..} = left margin + left border + left padding
+rightSpace PaddedBox {..} = right margin + right border + right padding
+topSpace PaddedBox {..} = top margin + top border + top padding
+bottomSpace PaddedBox {..} = bottom margin + bottom border + bottom padding
+hSpace self = leftSpace self + rightSpace self
+vSpace self = topSpace self + bottomSpace self
+
 -- | A partially-computed length value.
 data Length = Pixels Double -- ^ Absolute number of device pixels.
         | Percent Double -- ^ Multiplier by container width.
 
@@ 4,11 4,11 @@
 module Graphics.Layout.Inline(inlineMinWidth, inlineMin, inlineNatWidth, inlineHeight,
     inlineSize, inlineChildren, layoutSize, layoutChildren, positionChildren,
     fragmentSize, fragmentSize', fragmentPos, FragmentTree(..),
-    positionSubtree, subtreeInner) where
+    positionSubtree, subtreeInner, paragraphMap, layoutMap, treeMap) where
 
 import Data.Text.ParagraphLayout.Rich (Paragraph(..), ParagraphOptions(..),
                                 Fragment(..), ParagraphLayout(..), AncestorBox(..),
-                                layoutRich)
+                                layoutRich, InnerNode(..), Box(..), RootNode(..))
 import Data.Text.ParagraphLayout.Rect (Rect(..),
                                        width, height, x_max, x_min, y_min, y_max)
 import Data.Text.Internal (Text(..))
@@ 16,10 16,11 @@ import qualified Data.Text as Txt
 import Data.Char (isSpace)
 import Data.Int (Int32)
 
-import Graphics.Layout.Box (PaddedBox, Length, Size(..), CastDouble(..), fromDouble)
+import Graphics.Layout.Box hiding (min, max, width, height)
 import Graphics.Layout.CSS.Font (Font', hbUnit)
 
 -- | Convert from Harfbuzz units to device pixels as a Double
+hbScale :: Int32 -> Double
 hbScale = (/hbUnit) . fromIntegral
 -- | Convert from Harfbuzz units to device pixels as a Double or Length.
 c :: CastDouble a => Int32 -> a
@@ 61,23 62,63 @@ layoutRich' :: Paragraph a -> Int32 -> Rect Int32
 layoutRich' (Paragraph a b c d) width =
     paragraphRect $ layoutRich $ Paragraph a b c d { paragraphMaxWidth = width }
 
+-- | Apply an operation to the 2nd field of the paragraph's userdata,
+-- for it's entire subtree.
+paragraphMap :: (b -> b') -> Paragraph (a, b, c) -> Paragraph (a, b', c)
+paragraphMap cb (Paragraph a b (RootBox c) d) =
+    Paragraph a b (RootBox $ inner c) d
+  where
+    inner (Box childs opts) = flip Box opts $ map inner' childs
+    inner' (InlineBox (e, f, g) child opts) =
+        InlineBox (e, cb f, g) (inner child) opts
+    inner' (TextSequence (e, f, g) leaf) = TextSequence (e, cb f, g) leaf
+
+-- | Apply an operation to the 2nd field of a laid-out paragraph's userdata,
+-- for it's entire subtree.
+layoutMap :: (b -> b') -> ParagraphLayout (a, b, c) -> ParagraphLayout (a, b', c)
+layoutMap cb (ParagraphLayout a b) = ParagraphLayout a $ map inner b
+  where
+    inner self@Fragment { fragmentUserData = (a, b, c) } = self {
+        fragmentUserData = (a, cb b, c),
+        fragmentAncestorBoxes = map inner' $ fragmentAncestorBoxes self
+      }
+    inner' self@AncestorBox { boxUserData = (a, b, c) } = self {
+        boxUserData = (a, cb b, c)
+      }
+
+-- | Apply an operation to the 2nd field of the tree extracted from a laid-out
+-- paragraph, for all nodes.
+treeMap :: (b -> b') -> FragmentTree (a, b, c) -> FragmentTree (a, b', c)
+treeMap cb (Branch self@AncestorBox { boxUserData = (a, b, c) } childs) =
+    Branch self { boxUserData = (a, cb b, c) } $ map (treeMap cb) childs
+treeMap cb (Leaf self@Fragment { fragmentUserData = (a, b, c) }) =
+    Leaf self { fragmentUserData = (a, cb b, c), fragmentAncestorBoxes = [] }
+
 -- | Retrieve the rect for a fragment & convert to CatTrap types.
 fragmentSize :: (CastDouble x, CastDouble y) =>
-        FragmentTree (a, PaddedBox Length Length, c) -> Size x y
+        FragmentTree (a, PaddedBox x y, c) -> Size x y
 fragmentSize self = Size (c $ width r) (c $ height r)
     where r = treeRect self
 -- | Compute the unioned rect for a subtree.
 treeRect :: (CastDouble m, CastDouble n) =>
         FragmentTree (a, PaddedBox m n, c) -> Rect Int32
-treeRect (Branch _ childs) = foldr unionRect (Rect 0 0 0 0) $ map treeRect childs
-    where unionRect a b = Rect (x_min a `min` x_min b) (y_min a `min` y_min b)
-            ((x_max a `max` x_max b) - (x_min a `min` x_min b))
-            ((y_max a `max` y_max b) - (y_min a `min` x_min b))
+treeRect (Branch AncestorBox { boxUserData = (_, box', _)} childs) =
+        foldr unionRect (Rect 0 0 0 0) $ map treeRect childs
+    where
+        unionRect a b = Rect
+            (x_min a `min` x_min b - leftSpace box)
+            (y_min a `min` y_min b - topSpace box)
+            (x_max a `max` x_max b - x_min a `min` x_min b + hSpace box)
+            (y_max a `max` y_max b - y_min a `min` x_min b + vSpace box)
+        box :: PaddedBox Int32 Int32
+        box = mapX' unscale $ mapY' unscale box'
+        unscale :: CastDouble x => x -> Int32
+        unscale = floor . (*hbUnit) . toDouble
 treeRect (Leaf self) = fragmentRect self
 
 -- | Variant of `fragmentSize` asserting to the typesystem that both fields
 -- of the resulting `Size` are of the same type.
-fragmentSize' :: CastDouble x => FragmentTree (a, PaddedBox Length Length, c) -> Size x x
+fragmentSize' :: CastDouble x => FragmentTree (a, PaddedBox x x, c) -> Size x x
 fragmentSize' = fragmentSize -- Work around for typesystem.
 -- | Retrieve the position of a fragment.
 fragmentPos :: (Double, Double) -> Fragment a -> (Double, Double)
@@ 126,9 167,9 @@ reconstructTree' frags@(Fragment {
     sameBranch Fragment { fragmentAncestorBoxes = [] } = False
 reconstructTree' [] = []
 
-positionSubtree :: (Double, Double) ->
-        FragmentTree (a, PaddedBox Length Length, c) ->
-        FragmentTree (a, PaddedBox Length Length, ((Double, Double), c))
+positionSubtree :: (CastDouble m, CastDouble n) => (Double, Double) ->
+        FragmentTree (a, PaddedBox m n, c) ->
+        FragmentTree (a, PaddedBox m n, ((Double, Double), c))
 positionSubtree (x, y) self@(Branch (AncestorBox (a, b, c) d e f g) childs) =
     Branch (AncestorBox (a, b, (pos, c)) d e f g) $
         map (positionSubtree pos) childs