--------------------------------------------------------- -- | -- Copyright : (c) 2006-2016, alpheccar.org -- License : BSD-style -- -- Maintainer : misc@NOSPAMalpheccar.org -- Stability : experimental -- Portability : portable -- -- Horizontal mode --------------------------------------------------------- -- #hide {-# LANGUAGE CPP #-} module Graphics.PDF.Typesetting.Horizontal ( HBox(..) , mkHboxWithRatio , horizontalPostProcess ) where #if !MIN_VERSION_base(4,8,0) import Data.Monoid #endif import Graphics.PDF.LowLevel.Types import Graphics.PDF.Typesetting.Breaking import Graphics.PDF.Shapes import Graphics.PDF.Draw import Graphics.PDF.Coordinates import qualified Data.ByteString as S(reverse,cons,singleton) import Data.Maybe(isJust,fromJust) import Data.List(foldl') import Graphics.PDF.Colors import Graphics.PDF.Text import Graphics.PDF.Typesetting.Box import Control.Monad.Writer(tell) import Control.Monad(when) import Graphics.PDF.LowLevel.Serializer -- | Current word (created from letter) is converted to a PDFString saveCurrentword :: PDFGlyph -> PDFGlyph saveCurrentword (PDFGlyph g) = PDFGlyph . S.reverse $ g -- WARNING -- According to splitText, PDFText to concatenate ARE letters so we can optimize the code -- Sentences are created when no word style is present, otherwise we just create words createWords :: ComparableStyle s => PDFFloat -- ^ Adjustement ratio -> Maybe (s,PDFGlyph, PDFFloat) -- ^ Current word -> [Letter s] -- ^ List of letters -> [HBox s] -- ^ List of words or sentences createWords _ Nothing [] = [] -- Empty list, current word or sentence is added createWords _ (Just (s,t,w)) [] = [createText s (saveCurrentword t) w] -- Start of a new word createWords r Nothing ((AGlyph s t w):l) = createWords r (Just (s,PDFGlyph (S.singleton (fromIntegral t)),w)) l -- New letter. Same style added to the word. Otherwise we start a new word createWords r (Just (s,PDFGlyph t,w)) ((AGlyph s' t' w'):l) | s `isSameStyleAs` s' = createWords r (Just (s,PDFGlyph (S.cons (fromIntegral t') t),w+w')) l | otherwise = (createText s (saveCurrentword $ (PDFGlyph t)) w):createWords r (Just (s',PDFGlyph (S.singleton (fromIntegral t')),w')) l -- Glue close the word and start a new one because we want glues of different widths in the PDF createWords r (Just (s,t,w)) ((Glue w' y z (Just s')):l) = (createText s (saveCurrentword $ t) w):(HGlue w' (Just(y,z)) (Just s')):createWords r Nothing l -- Penalties are invisible. The are needed just to compute breaks createWords r c (Penalty _:l) = createWords r c l createWords r c (FlaggedPenalty _ _ _:l) = createWords r c l -- We just add the box createWords r Nothing ((Glue w' y z s):l) = (HGlue w' (Just(y,z)) s):createWords r Nothing l createWords r (Just (s,t,w)) ((Glue w' y z Nothing):l) = (createText s (saveCurrentword $ t) w):(HGlue w' (Just(y,z)) Nothing):createWords r Nothing l createWords r Nothing ((Kern w' s):l) = (HGlue w' Nothing s):createWords r Nothing l createWords r (Just (s,t,w)) ((Kern w' s'):l) = (createText s (saveCurrentword $ t) w):(HGlue w' Nothing s'):createWords r Nothing l createWords r Nothing ((Letter d a s):l) = (SomeHBox d a s):createWords r Nothing l createWords r (Just (s,t,w)) ((Letter d a st):l) = (createText s (saveCurrentword $ t) w):(SomeHBox d a st):createWords r Nothing l -- | horizontalPostProcess horizontalPostProcess :: (Style s) => [(PDFFloat,[Letter s],[Letter s])] -- ^ adjust ratio, hyphen style, list of letters or boxes -> [(HBox s,[Letter s])] -- ^ List of lines horizontalPostProcess [] = [] horizontalPostProcess ((r,l',r'):l) = let l'' = createWords r Nothing . simplify $ l' in if null l'' then horizontalPostProcess l else ((mkHboxWithRatio r l''),r'):horizontalPostProcess l -- | An horizontal Hbox (sentence or word) -- The width of the glue was computed with the adjustement ratio of the HLine containing the glue -- The width of the text is already taking into account the adjustement ratio of the HLine containing the Text -- Otherwise, HBox cannot dilate or compress. data HBox s = HBox !PDFFloat !PDFFloat !PDFFloat ![HBox s] | HGlue !PDFFloat !(Maybe (PDFFloat,PDFFloat)) !(Maybe s) | Text !s !PDFGlyph !PDFFloat | SomeHBox !BoxDimension !AnyBox !(Maybe s) -- | Change the style of the box withNewStyle :: s -> HBox s -> HBox s withNewStyle _ a@(HBox _ _ _ _) = a withNewStyle s (HGlue a b _) = HGlue a b (Just s) withNewStyle s (Text _ a b) = Text s a b withNewStyle s (SomeHBox d a _) = SomeHBox d a (Just s) -- | A line of hboxes with an adjustement ratio required to display the text (generate the PDF command to increase space size) --data HLine = HLine !PDFFloat ![HBox] deriving(Show) mkHboxWithRatio :: Style s => PDFFloat -- ^ Adjustement ratio -> [HBox s] -> HBox s mkHboxWithRatio _ [] = error "Cannot create an empty horizontal box" mkHboxWithRatio r l = let w = foldl' (\x y -> x + glueSizeWithRatio y r) 0.0 l --h = maximum . map boxHeight $ l ascent = maximum . map boxAscent $ l d = maximum . map boxDescent $ l h = ascent + d addBox (HGlue gw (Just(y,z)) s) (HBox w' h' d' l') = HBox w' h' d' (HGlue (glueSize gw y z r) Nothing s:l') addBox a (HBox w' h' d' l') = HBox w' h' d' (a:l') addBox _ _ = error "We can add boxes only to an horizontal list" in -- Add boxes and dilate glues when needing fixing their dimensions after dilatation foldr addBox (HBox w h d []) l instance Style s => MaybeGlue (HBox s) where glueSizeWithRatio (HGlue w (Just(y,z)) _) r = glueSize w y z r glueSizeWithRatio a _ = boxWidth a glueY (HGlue _ (Just(y,_)) _) = y glueY _ = 0 glueZ (HGlue _ (Just(_,z)) _) = z glueZ _ = 0 -- | Create an HBox createText :: s -- ^ Style -> PDFGlyph -- ^ List of glyphs -> PDFFloat -- ^ Width -> HBox s createText s t w = Text s t w instance Show (HBox s) where show (HBox _ _ _ a) = "(HBox " ++ show a ++ ")" show (HGlue a _ _) = "(HGlue " ++ show a ++ ")" show (Text _ t _) = "(Text " ++ show t ++ ")" show (SomeHBox _ t _) = "(SomeHBox " ++ show t ++ ")" -- | Draw a line of words and glue using the word style drawTextLine :: (Style s) => s -> [HBox s] -> PDFFloat -> PDFFloat -> Draw () drawTextLine _ [] _ _ = return () drawTextLine style l@(a:l') x y | (isJust . wordStyle $ style) = do let h = boxHeight a d = boxDescent a y' = y + h - d strokeBox (withNewStyle style a) x y' drawTextLine (updateStyle style) l' (x + boxWidth a) y | otherwise = drawWords style l x y -- | Draw a line of words, glue, or any box without word style drawWords :: (Style s) => s -> [HBox s] -> PDFFloat -> PDFFloat -> Draw () drawWords _ [] _ _ = return () drawWords s ((Text _ t w):l) x y = do (l',x') <- drawText $ do drawTheTextBox StartText s x y (Just t) drawPureWords s l (x + w) y drawWords s l' x' y drawWords s l@((HGlue _ _ _ ):_) x y = do (l',x') <- drawText $ do drawTheTextBox StartText s x y Nothing drawPureWords s l x y drawWords s l' x' y drawWords s (a@(SomeHBox _ _ _):l) x y = do let h = boxHeight a d = boxDescent a w = boxWidth a y' = y - d + h strokeBox a x y' drawWords s l (x + w) y drawWords _ _ _ _ = return () -- | Draw only words and glues using PDF text commands drawPureWords :: Style s => s -> [HBox s] -> PDFFloat -> PDFFloat -> PDFText ([HBox s],PDFFloat) drawPureWords s [] x y = do drawTheTextBox StopText s x y Nothing return ([],x) drawPureWords s ((Text _ t w):l) x y = do drawTheTextBox ContinueText s x y (Just t) drawPureWords s l (x + w) y drawPureWords s ((HGlue w _ _):l) x y = do drawTextGlue s w drawPureWords s l (x + w) y drawPureWords s l@((SomeHBox _ _ _):_) x y = do drawTheTextBox StopText s x y Nothing return (l,x) drawPureWords s (_:l) x y = drawPureWords s l x y -- When a start of line is detected by drawLineOfHBoxes, we start the drawing startDrawingNewLineOfText :: (Style s) => PDFFloat -> PDFFloat -> [HBox s] -> PDFFloat -> PDFFloat -> s -> Draw () startDrawingNewLineOfText hl dl l x y style = do -- Position of draw line based upon the whole line and not just this word let y' = y - hl + dl (l',l'') = span (isSameStyle style) l w' = foldl' (\x' ny -> x' + boxWidth ny) 0.0 l' if (isJust . sentenceStyle $ style) then do (fromJust . sentenceStyle $ style) (Rectangle (x :+ (y - hl)) ((x+w') :+ y)) (drawTextLine style l' x y') else do drawTextLine style l' x y' drawLineOfHboxes hl dl l'' (x + w') y drawLineOfHboxes :: (Style s) => PDFFloat -- ^ Height of the total line first time this function is called -> PDFFloat -- ^ Descent of the total line first time this function is called -> [HBox s] -- ^ Remaining box to display -> PDFFloat -- ^ x for the remaining boxes -> PDFFloat -- ^ y for the whole line -> Draw () drawLineOfHboxes _ _ [] _ _ = return () -- | Start a new text drawLineOfHboxes hl dl l@((Text style _ _):_) x y = startDrawingNewLineOfText hl dl l x y style drawLineOfHboxes hl dl l@((HGlue _ _ (Just style)):_) x y = startDrawingNewLineOfText hl dl l x y style drawLineOfHboxes hl dl (a:l) x y = do let h = boxHeight a d = boxDescent a -- Compute top of box a y' = y - hl + dl - d + h strokeBox a x y' drawLineOfHboxes hl dl l (x + boxWidth a) y instance Style s => Box (HBox s) where boxWidth (Text _ _ w) = w boxWidth (HBox w _ _ _) = w boxWidth (SomeHBox d _ _) = boxWidth d boxWidth (HGlue w _ _) = w boxHeight (Text style _ _) = styleHeight style boxHeight (HBox _ h _ _) = h boxHeight (SomeHBox d _ _) = boxHeight d boxHeight (HGlue _ _ (Just s)) = styleHeight s boxHeight (HGlue _ _ _) = 0 boxDescent (Text style _ _) = styleDescent style boxDescent (HBox _ _ d _) = d boxDescent (SomeHBox d _ _) = boxDescent d boxDescent (HGlue _ _ (Just s)) = styleDescent s boxDescent (HGlue _ _ _) = 0 -- Draw a text box drawTheTextBox :: Style style => TextDrawingState -> style -> PDFFloat -> PDFFloat -> Maybe PDFGlyph -> PDFText () drawTheTextBox state style x y t = do when (state == StartText || state == OneBlock) $ (do setFont (textFont . textStyle $ style) strokeColor (textStrokeColor . textStyle $ style) fillColor (textFillColor . textStyle $ style) renderMode (textMode . textStyle $ style) setWidth (penWidth . textStyle $ style) textStart x y tell $ mconcat [newline,lbracket]) -- Here we need to dilate the space to take into account r and the font setting when (state == StartText || state == OneBlock || state == ContinueText) $ (do case t of Nothing -> return () Just myText -> tell $ toPDF myText ) when (state == StopText || state == OneBlock) $ (do tell rbracket tell $ serialize " TJ") -- | Draw the additional displacement required for a space in a text due to the dilaton of the glue drawTextGlue :: Style style => style -> PDFFloat -> PDFText () drawTextGlue style w = do let ws = spaceWidth style PDFFont _ size = textFont . textStyle $ style delta = w - ws return () tell . mconcat $ [ lparen, bspace,rparen,bspace,toPDF ((-delta) * 1000.0 / (fromIntegral size) ), bspace] data TextDrawingState = StartText -- ^ Send PDF commands needed to start a text | ContinueText -- ^ Continue adding text | StopText -- ^ Stop the text | OneBlock -- ^ One block of text deriving(Eq) instance (Style s) => DisplayableBox (HBox s) where strokeBox a@(HBox _ _ _ l) x y = do let he = boxHeight a de = boxDescent a drawLineOfHboxes he de l x y strokeBox a@(HGlue w _ (Just style)) x y = do let de = boxDescent a he = boxHeight a y' = y - he + de -- In word mode we have to apply a special function to the word -- otherwise we apply a different function to the sentence if (isJust . wordStyle $ style) then (fromJust . wordStyle $ style) (Rectangle (x :+ (y' - de)) ((x+w) :+ (y' - de + he))) DrawGlue (return ()) else return () strokeBox a@(Text style t w) x y = do let de = boxDescent a he = boxHeight a y' = y - he + de -- In word mode we have to apply a special function to the word -- otherwise we apply a different function to the sentence if (isJust . wordStyle $ style) then (fromJust . wordStyle $ style) (Rectangle (x :+ (y' - de)) ((x+w) :+ (y' - de + he))) DrawWord (drawText $ drawTheTextBox OneBlock style x y' (Just t)) else drawText $ drawTheTextBox OneBlock style x y' (Just t) strokeBox (SomeHBox _ a _) x y = strokeBox a x y strokeBox (HGlue _ _ _) _ _ = return () -- Test is a box has same style isSameStyle :: (Style s) => s -> HBox s -> Bool isSameStyle s (Text style _ _) = s `isSameStyleAs` style isSameStyle s (HGlue _ _ (Just style)) = s `isSameStyleAs` style isSameStyle s (SomeHBox _ _ (Just style)) = s `isSameStyleAs` style isSameStyle _ _ = False