{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
module Graphics.PDF.Typesetting.Layout (
Container(..)
, Width
, Height
, VBox(..)
, ParagraphStyle(..)
, VerState(..)
, vglue
, addTo
, isOverfull
, mkContainer
, strokeVBoxes
, containerX
, containerY
, containerWidth
, containerHeight
, containerContentHeight
, containerContentRightBorder
, containerContentLeftBorder
, containerCurrentHeight
, containerContentRectangle
, containerParaTolerance
) where
import Graphics.PDF.LowLevel.Types
import Graphics.PDF.Typesetting.Breaking
import Graphics.PDF.Draw
import Graphics.PDF.Coordinates
import Graphics.PDF.Shapes(Rectangle(..))
import Graphics.PDF.Typesetting.Box
import Data.List(foldl')
import Data.Maybe(isJust,fromJust)
data VerState s = VerState { baselineskip :: !(PDFFloat,PDFFloat,PDFFloat)
, lineskip :: !(PDFFloat,PDFFloat,PDFFloat)
, lineskiplimit :: !PDFFloat
, currentParagraphStyle :: !s
}
data VBox ps s = Paragraph Int [Letter s] !(Maybe ps) !BRState
| VBox !PDFFloat !PDFFloat !PDFFloat ![VBox ps s] !(Maybe ps)
| VGlue !PDFFloat !PDFFloat !PDFFloat !(Maybe (PDFFloat,PDFFloat)) !(Maybe ps)
| SomeVBox !PDFFloat !BoxDimension !AnyBox !(Maybe ps)
notGlue :: VBox ps s -> Bool
notGlue (VGlue _ _ _ _ _) = False
notGlue (Paragraph _ _ _ _) = False
notGlue _ = True
vglue :: Maybe ps
-> PDFFloat
-> PDFFloat
-> PDFFloat
-> PDFFloat
-> PDFFloat
-> VBox ps s
vglue s h y z width delta = VGlue h width delta (Just(y,z)) s
instance Show (VBox ps s) where
show (VBox _ a _ l _) = "(VBox " ++ show a ++ " " ++ show l ++ ")"
show (VGlue a _ _ _ _) = "(VGlue " ++ show a ++ ")"
show (Paragraph _ _ _ _) = "(Paragraph)"
show (SomeVBox _ d t _) = "(SomeVBox " ++ show (boxHeight d) ++ " " ++ show t ++ ")"
instance MaybeGlue (VBox ps s) where
glueSizeWithRatio (VGlue w _ _ (Just(y,z)) _) r = glueSize w y z r
glueSizeWithRatio a _ = boxHeight a
glueY (VGlue _ _ _ (Just(y,_)) _) = y
glueY _ = 0
glueZ (VGlue _ _ _ (Just(_,z)) _) = z
glueZ _ = 0
instance Box (VBox ps s) where
boxWidth (Paragraph _ _ _ _) = 0
boxWidth (VBox w _ _ _ _) = w
boxWidth (SomeVBox _ d _ _) = boxWidth d
boxWidth (VGlue _ w _ _ _) = w
boxHeight (Paragraph _ _ _ _) = 0
boxHeight (VBox _ h _ _ _) = h
boxHeight (SomeVBox _ d _ _) = boxHeight d
boxHeight (VGlue h _ _ _ _) = h
boxDescent (Paragraph _ _ _ _) = 0
boxDescent (VBox _ _ d _ _) = d
boxDescent (SomeVBox _ d _ _) = boxDescent d
boxDescent (VGlue _ _ _ _ _) = 0
instance (ParagraphStyle ps s) => DisplayableBox (VBox ps s) where
strokeBox (Paragraph _ _ _ _) _ _ = return ()
strokeBox b@(VBox _ _ _ l _) x y'' = strokeVBoxes l x y'
where
y' = y'' - boxHeight b
strokeBox (VGlue h w delta _ (Just style)) x y =
if (isJust . interline $ style)
then
(fromJust . interline $ style) $ Rectangle ((x+delta) :+ (y-h)) ((x+w+delta) :+ y)
else
return()
strokeBox (VGlue _ _ _ _ _) _ _ = return ()
strokeBox (SomeVBox delta _ a _) x y = strokeBox a (x+delta) y
type Width = PDFFloat
type Height = PDFFloat
data Container ps s = Container PDFFloat PDFFloat Width PDFFloat PDFFloat PDFFloat PDFFloat PDFFloat [VBox ps s]
mkContainer :: PDFFloat
-> PDFFloat
-> PDFFloat
-> PDFFloat
-> PDFFloat
-> Container ps s
mkContainer x y width height tol = Container x y width height 0 0 0 tol []
containerWidth :: Container ps s -> PDFFloat
containerWidth (Container _ _ w _ _ _ _ _ _) = w
containerParaTolerance :: Container ps s -> PDFFloat
containerParaTolerance (Container _ _ _ _ _ _ _ t _) = t
containerHeight :: Container ps s -> PDFFloat
containerHeight (Container _ _ _ h _ _ _ _ _) = h
containerCurrentHeight :: Container ps s -> PDFFloat
containerCurrentHeight (Container _ _ _ _ ch _ _ _ _) = ch
containerContentHeight :: Container ps s -> PDFFloat
containerContentHeight (Container _ _ _ maxh h y z _ _) = let r = min (dilatationRatio maxh h y z) 2.0 in
glueSize h y z r
containerContentLeftBorder :: Container ps s -> PDFFloat
containerContentLeftBorder (Container _ _ _ _ _ _ _ _ []) = 0.0
containerContentLeftBorder (Container _ _ _ _ _ _ _ _ l) = minimum . map getBoxDelta $ l
containerContentRightBorder :: Container ps s -> PDFFloat
containerContentRightBorder (Container _ _ _ _ _ _ _ _ []) = 0.0
containerContentRightBorder (Container _ _ _ _ _ _ _ _ l) =
let xmax = maximum . map rightBorder $ l
rightBorder x = getBoxDelta x + boxWidth x
in
xmax
containerX :: Container ps s -> PDFFloat
containerX (Container x _ _ _ _ _ _ _ _) = x
containerY :: Container ps s -> PDFFloat
containerY (Container _ y _ _ _ _ _ _ _) = y
containerContentRectangle :: Container ps s -> Rectangle
containerContentRectangle c = Rectangle ((x+l) :+ (y-th)) ((x+r) :+ y)
where
x = containerX c
y = containerY c
th = containerContentHeight c
l = containerContentLeftBorder c
r = containerContentRightBorder c
getInterlineStyle :: ComparableStyle ps => VBox ps s -> VBox ps s -> Maybe ps
getInterlineStyle (VBox _ _ _ _ (Just s)) (SomeVBox _ _ _ (Just s')) | s `isSameStyleAs` s' = Just s
| otherwise = Nothing
getInterlineStyle (VBox _ _ _ _ (Just s)) (VBox _ _ _ _ (Just s')) | s `isSameStyleAs` s' = Just s
| otherwise = Nothing
getInterlineStyle (SomeVBox _ _ _ (Just s)) (SomeVBox _ _ _ (Just s')) | s `isSameStyleAs` s' = Just s
| otherwise = Nothing
getInterlineStyle (SomeVBox _ _ _ (Just s)) (VBox _ _ _ _ (Just s')) | s `isSameStyleAs` s' = Just s
| otherwise = Nothing
getInterlineStyle _ _ = Nothing
interlineGlue :: ComparableStyle ps => VerState ps -> VBox ps s -> VBox ps s -> Maybe (VBox ps s, PDFFloat, PDFFloat)
interlineGlue settings a b | notGlue a && notGlue b =
let p = boxDescent a
h = boxHeight b - boxDescent b
(ba,by,bz) = baselineskip settings
(lw,ly,lz) = lineskip settings
li = lineskiplimit settings
istyle = getInterlineStyle a b
theWidth = boxWidth a
theDelta = getBoxDelta a
in
if p <= -1000
then
Nothing
else
if ba - p - h >= li
then
Just $ (vglue istyle (ba-p-h) by bz theWidth theDelta,by,bz)
else
Just $ (vglue istyle lw ly lz theWidth theDelta,ly,lz)
| otherwise = Nothing
addTo :: ComparableStyle ps => VerState ps -> VBox ps s -> Container ps s -> Container ps s
addTo _ line (Container px py w maxh h y z t []) = Container px py w maxh ((boxHeight line)+h) y z t [line]
addTo settings line (Container px py w maxh h y z t l@(a:_)) =
case interlineGlue settings a line of
Nothing ->
let h' = boxHeight line + h
y' = y + glueY line
z' = z + glueZ line
in
Container px py w maxh h' y' z' t (line:l)
Just (v,ny,nz) ->
let h' = boxHeight line + h + boxHeight v
y' = y + ny + glueY line
z' = z + nz + glueZ line
in
Container px py w maxh h' y' z' t (line:v:l)
isOverfull :: Container ps s -> Bool
isOverfull (Container _ _ _ maxh h y z _ _) = let r = dilatationRatio maxh h y z
in
if r >= bigAdjustRatio then h > maxh else r <= -1
class (ComparableStyle a, Style s) => ParagraphStyle a s | a -> s where
lineWidth :: a
-> PDFFloat
-> Int
-> PDFFloat
linePosition :: a
-> PDFFloat
-> Int
-> PDFFloat
interline :: a
-> Maybe (Rectangle -> Draw ())
interline _ = Nothing
lineWidth _ w _ = w
linePosition _ _ = const 0.0
paragraphChange :: a
-> Int
-> [Letter s]
-> (a,[Letter s])
paragraphChange a _ l = (a,l)
paragraphStyle :: a
-> Maybe (Rectangle -> Draw b -> Draw ())
paragraphStyle _ = Nothing
getBoxDelta :: VBox ps s -> PDFFloat
getBoxDelta (Paragraph _ _ _ _) = 0.0
getBoxDelta (VBox _ _ _ _ _) = 0.0
getBoxDelta (VGlue _ _ delta _ _) = delta
getBoxDelta (SomeVBox delta _ _ _) = delta
isSameParaStyle :: ComparableStyle ps => ps -> VBox ps s -> Bool
isSameParaStyle s (Paragraph _ _ (Just s') _) = s `isSameStyleAs` s'
isSameParaStyle s (VBox _ _ _ _ (Just s')) = s `isSameStyleAs` s'
isSameParaStyle s (VGlue _ _ _ _ (Just s')) = s `isSameStyleAs` s'
isSameParaStyle s (SomeVBox _ _ _ (Just s')) = s `isSameStyleAs` s'
isSameParaStyle _ _ = False
recurseStrokeVBoxes :: (ParagraphStyle ps s) => Int -> [VBox ps s] -> PDFFloat -> PDFFloat -> Draw ()
recurseStrokeVBoxes _ [] _ _ = return ()
recurseStrokeVBoxes _ (Paragraph _ _ _ _:_) _ _ = return ()
recurseStrokeVBoxes nb (a@(VGlue _ _ _ _ _):l) xa y = do
let h = boxHeight a
strokeBox a xa y
recurseStrokeVBoxes nb l xa (y-h)
recurseStrokeVBoxes nb (a:l) xa y = do
let h = boxHeight a
strokeBox a xa y
recurseStrokeVBoxes (nb+1) l xa (y-h)
drawWithParaStyle :: (ParagraphStyle ps s) => ps -> [VBox ps s] -> PDFFloat -> PDFFloat -> Draw ()
drawWithParaStyle style b xa y' = do
let (l',l'') = span (isSameParaStyle style) b
h' = foldl' (\x' ny -> x' + boxHeight ny) 0.0 l'
if (isJust . paragraphStyle $ style)
then do
let xleft = (minimum $ 100000:map getBoxDelta l' ) + xa
xright = (maximum $ 0:(map (\x -> boxWidth x + getBoxDelta x) l')) + xa
(fromJust . paragraphStyle $ style) (Rectangle (xleft :+ (y'- h')) (xright :+ y')) (recurseStrokeVBoxes 1 l' xa y')
else
recurseStrokeVBoxes 1 l' xa y'
strokeVBoxes l'' xa (y' - h')
strokeVBoxes :: (ParagraphStyle ps s) => [VBox ps s]
-> PDFFloat
-> PDFFloat
-> Draw ()
strokeVBoxes [] _ _ = return ()
strokeVBoxes b@((Paragraph _ _ (Just s') _):_) xa y = drawWithParaStyle s' b xa y
strokeVBoxes b@((VBox _ _ _ _ (Just s')):_) xa y = drawWithParaStyle s' b xa y
strokeVBoxes b@((VGlue _ _ _ _ (Just s')):_) xa y = drawWithParaStyle s' b xa y
strokeVBoxes b@((SomeVBox _ _ _ (Just s')):_) xa y = drawWithParaStyle s' b xa y
strokeVBoxes (a:l) xa y =
do
let h = boxHeight a
strokeBox a xa y
strokeVBoxes l xa (y-h)