{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FunctionalDependencies #-} --------------------------------------------------------- -- | -- Copyright : (c) 2006-2016, alpheccar.org -- License : BSD-style -- -- Maintainer : misc@NOSPAMalpheccar.org -- Stability : experimental -- Portability : portable -- -- Box --------------------------------------------------------- -- #hide 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) -- ^ Default value (12,0.17,0.0) , lineskip :: !(PDFFloat,PDFFloat,PDFFloat) -- ^ Default value (3.0,0.33,0.0) , lineskiplimit :: !PDFFloat -- ^ Default value 2 , 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 -- ^ Glue height -> PDFFloat -- ^ Glue dilatation factor -> PDFFloat -- ^ Glue compression factor -> PDFFloat -- ^ Glue width -> PDFFloat -- ^ Glue delta -> 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 -- | Container for vboxes (x,y,width,maxheight,height,currenty,current z, tolerance para) -- tolerance para means a paragraph is not started if too close from the bottom edge of the box data Container ps s = Container PDFFloat PDFFloat Width PDFFloat PDFFloat PDFFloat PDFFloat PDFFloat [VBox ps s] -- | Create a empty container to constraint the amount of line that can be displayed mkContainer :: PDFFloat -- ^ x -> PDFFloat -- ^ y -> PDFFloat -- ^ width -> PDFFloat -- ^ height -> PDFFloat -- ^ Pargraph tolerance -> Container ps s -- ^ New container mkContainer x y width height tol = Container x y width height 0 0 0 tol [] -- | Get the width of the container containerWidth :: Container ps s -> PDFFloat containerWidth (Container _ _ w _ _ _ _ _ _) = w -- | Get the width of the container containerParaTolerance :: Container ps s -> PDFFloat containerParaTolerance (Container _ _ _ _ _ _ _ t _) = t -- | Get the height of the container containerHeight :: Container ps s -> PDFFloat containerHeight (Container _ _ _ h _ _ _ _ _) = h -- | Get the current height of the container without glue dilatation containerCurrentHeight :: Container ps s -> PDFFloat containerCurrentHeight (Container _ _ _ _ ch _ _ _ _) = ch -- | Get the content height of the container with glue dilatation 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 -- | Get the minimum left border of the container content containerContentLeftBorder :: Container ps s -> PDFFloat containerContentLeftBorder (Container _ _ _ _ _ _ _ _ []) = 0.0 containerContentLeftBorder (Container _ _ _ _ _ _ _ _ l) = minimum . map getBoxDelta $ l -- | Get the maximum right border of the container content (maybe bigger than container width due to overfull lines) 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 -- | Container horizontal position containerX :: Container ps s -> PDFFloat containerX (Container x _ _ _ _ _ _ _ _) = x -- | Container vertical position containerY :: Container ps s -> PDFFloat containerY (Container _ y _ _ _ _ _ _ _) = y -- | Return the rectangle containing the text after formatting and glue dilatation 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 -- | Get the required style for the interline glue 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 -- | Interline glue required 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 -- | Paragraph style class (ComparableStyle a, Style s) => ParagraphStyle a s | a -> s where -- | Width of the line of the paragraph lineWidth :: a -- ^ The style -> PDFFloat -- ^ Width of the text area used by the typesetting algorithm -> Int -- ^ Line number -> PDFFloat -- ^ Line width -- | Horizontal shift of the line position relatively to the left egde of the paragraph bounding box linePosition :: a -- ^ The style -> PDFFloat -- ^ Width of the text area used by the typesetting algorithm -> Int -- ^ Line number -> PDFFloat -- ^ Horizontal offset from the left edge of the text area -- | How to style the interline glues added in a paragraph by the line breaking algorithm interline :: a -- ^ The style -> Maybe (Rectangle -> Draw ()) -- ^ Function used to style interline glues interline _ = Nothing lineWidth _ w _ = w linePosition _ _ = const 0.0 -- | Change the content of a paragraph before the line breaking algorithm is run. It may also change the style paragraphChange :: a -- ^ The style -> Int -- ^ Line offset different from 0 when a paragraph has been broken -> [Letter s] -- ^ List of letters in the paragraph -> (a,[Letter s]) -- ^ Update style and list of letters paragraphChange a _ l = (a,l) -- | Get the paragraph bounding box and the paragraph draw command to apply additional effects paragraphStyle :: a -- ^ The style -> Maybe (Rectangle -> Draw b -> Draw ()) -- ^ Function used to style a paragraph paragraphStyle _ = Nothing -- | Get the delta used to position a box with non rectangular shapes 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') -- | Stroke the VBoxes strokeVBoxes :: (ParagraphStyle ps s) => [VBox ps s] -- ^ List of boxes -> PDFFloat -- ^ X pos -> PDFFloat -- ^ Y pos -> 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)