packages feed

hpdft 0.1.0.3 → 0.1.0.4

raw patch · 8 files changed

+416/−150 lines, 8 filesdep +attoparsecPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: attoparsec

API changes (from Hackage documentation)

+ PDF.ContentStream: parseColorSpace :: PSR -> ByteString -> [Text]
+ PDF.Definition: [colorspace] :: PSR -> String
+ PDF.Definition: [xcolorspaces] :: PSR -> [String]
+ PDF.Object: contentsColorSpace :: Dict -> PSR -> [PDFObj] -> [Text]
+ PDF.Object: parsePdfLetters :: Parser String
- PDF.Definition: PSR :: Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> String -> [(String, CMap)] -> [(String, FontMap)] -> PSR
+ PDF.Definition: PSR :: Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> String -> [(String, CMap)] -> [(String, FontMap)] -> String -> [String] -> PSR
- PDF.Outlines: getOutlines :: String -> IO ()
+ PDF.Outlines: getOutlines :: FilePath -> IO PDFOutlines
- PDF.PDFIO: getPDFBSFromFile :: String -> IO [PDFBS]
+ PDF.PDFIO: getPDFBSFromFile :: FilePath -> IO [PDFBS]
- PDF.PDFIO: getPDFObjFromFile :: String -> IO [PDFObj]
+ PDF.PDFIO: getPDFObjFromFile :: FilePath -> IO [PDFObj]

Files

README.md view
@@ -6,6 +6,6 @@  Example --------`Sample.hs` has some functions showing how to use hpdft. +[`Sample.hs`](https://github.com/k16shikano/hpdft/blob/master/data/sample/Sample.hs) has some functions showing how to use hpdft.   If you want to customize the character sets and encodings other than the Appendix D of the PDF specification, you could modify `PdfCharDict.hs`. For example, you can use ASCII single quote (`U+0027`) instead of `U+2019` by modifying the entry for `/quoteright`.
data/sample/Sample.hs view
@@ -8,33 +8,38 @@ import Data.ByteString.UTF8 (ByteString) import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.List (nub)  import Debug.Trace --- initstate = (0,0,70,660) initstate = PSR { linex=0                 , liney=0                 , absolutex=0-                , absolutey=700+                , absolutey=0                 , leftmargin=0.0-                , top=700.0+                , top=0.0                 , bottom=0.0-                , fontfactor=1.0+                , fontfactor=1                 , curfont=""                 , fontmaps=[]-                , cmaps=[]}+                , cmaps=[]+                , colorspace=""+                , xcolorspaces=[]+                }  --------------------------------------------------- Show each PDF Object by its reference number------------------------------------------------+-- | Show bare data in 'ref'ed-object.  objectByRef filename ref = getObjectByRef ref (getPDFObjFromFile filename) -streamByRef filename ref = do-  obj <- getObjectByRef ref (getPDFObjFromFile filename) -  return $ rawStream obj+-- | Show contents of page 'page' in 'filename'. +showPage filename page = do +  pagetree <- refByPage filename+  contentByRef filename $ pagetree !! (page - 1)+  +-- | Show /Content referenced from the 'ref'ed-object in 'filename'.+ contentByRef filename ref = do   objs <- getPDFObjFromFile filename   obj <- objectByRef filename ref@@ -44,6 +49,20 @@             Just dict -> contentsStream dict initstate objs             Nothing -> "" +-- | Show raw bytestring stream (without deflated) of page 'page' in 'filename'.++showRawPage filename page = do+  pagetree <- refByPage filename+  rawContentByRef filename $ pagetree !! (page - 1)++-- | Show raw bytestring stream (without deflated) of 'ref'ed-object.++streamByRef filename ref = do+  obj <- getObjectByRef ref (getPDFObjFromFile filename) +  return $ rawStream obj++-- | Show raw bytestring stream (without deflated) in a /Content referenced from the 'ref'ed-object in 'filename'.+ rawContentByRef filename ref = do   objs <- getPDFObjFromFile filename   obj <- objectByRef filename ref@@ -52,27 +71,21 @@           case findDictOfType "/Page" obj of             Just dict -> rawContentsStream dict objs             Nothing -> ""-  -showPage filename page = do -  pagetree <- refByPage filename-  contentByRef filename $ pagetree !! (page - 1) -showRawPage filename page = do-  pagetree <- refByPage filename-  rawContentByRef filename $ pagetree !! (page - 1)+-- | Show CMap information in object 'ref' in 'filename'.  cmapStreamByRef filename ref = do   objs <- getPDFObjFromFile filename   return $ toUnicode ref objs  ------------------------------------------- Sort Object References in Page order---------------------------------------- + data  PageTree = Nop | Page Int | Pages [PageTree]                  deriving Show +-- | Sort object references in page order.+ refByPage filename = do   root <- getRootRef filename   objs <- getPDFObjFromFile filename@@ -100,12 +113,10 @@ pageTreeToList Nop = []  ------------------------------ Get Whole Text from PDF------------------------------ First: grub objects--- Second: parse within each object, deflating its stream--- Third: linearize stream+-- | Get a whole text from 'filename'. It works as:+--    (1) grub objects+--    (2) parse within each object, deflating its stream+--    (3) linearize stream  pdfToText filename = do   contents <- BS.readFile filename@@ -132,7 +143,48 @@     Nothing -> ""  ----------------------- Meta Information    --------------------+-- | Show /Title from meta information in 'filename' +showTitle filename = do+  d <- getInfo filename+  let title = +        case findObjThroughDict d "/Title" of+          Just (PdfText s) -> s+          Just x -> show x+          Nothing -> error "No title anyway"+  putStrLn title+  return ()++-- | Show /Info from meta information in 'filename'++showInfo filename = do+  d <- getInfo filename+  putStrLn $ toString 0 (PdfDict d)+  return ()++-- | Show /Outlines from meta information in 'filename'++showOutlines filename = do+  d <- getOutlines filename+  putStrLn $ show d+  return ()+++-- | Show device color spaces of each page.++contentColorSpaceByRef filename ref = do+  objs <- getPDFObjFromFile filename+  obj <- objectByRef filename ref+  putStrLn $ show $ filter (/="") $ nub $ csOfObject obj objs+  where csOfObject obj objs =+          case findDictOfType "/Page" obj of+            Just dict -> contentsColorSpace dict initstate objs+            Nothing -> error "Seems to be no color space in content stream"++-- | Show device color spaces of all pages.++showColorSpaces filename = do+  pages <- refByPage filename+  pagescs <- mapM (contentColorSpaceByRef filename) pages+  mapM (putStrLn . show) $ zip [1..] pagescs+  return ()
hpdft.cabal view
@@ -1,5 +1,5 @@ name:                hpdft-version:             0.1.0.3+version:             0.1.0.4 synopsis:            A tool for looking through PDF file using Haskell -- description:          homepage:            https://github.com/k16shikano/hpdft@@ -33,6 +33,7 @@                      , bytestring >=0.10 && <0.11                      , text >=0.11                      , parsec >=3.1 && <3.2+                     , attoparsec >=0.13.0 && <1.0                      , zlib >=0.5                      , utf8-string >=0.3                      , directory >=1.2 && <1.3
src/PDF/ContentStream.hs view
@@ -3,6 +3,7 @@ module PDF.ContentStream         ( deflate        , decompressStream+       , parseColorSpace        ) where  import Data.Char (chr)@@ -32,9 +33,10 @@ parseContentStream p st = runParser p st ""  parseDeflated :: PSR -> BSC.ByteString -> PDFStream-parseDeflated psr pdfstream = case parseContentStream (T.concat <$> many (elems <|> skipOther)) psr pdfstream of-  Left  err -> error "Nothing to be parsed"-  Right str -> BSC.pack $ BS.unpack $ encodeUtf8 str+parseDeflated psr pdfstream = +  case parseContentStream (T.concat <$> many (elems <|> skipOther)) psr pdfstream of+    Left  err -> error "Nothing to be parsed"+    Right str -> BSC.pack $ BS.unpack $ encodeUtf8 str  deflate :: PSR -> PDFStream -> PDFStream deflate = parseDeflated@@ -47,6 +49,18 @@     Left err -> "err"     Right bs -> decompress bs +parseColorSpace :: PSR -> BSC.ByteString -> [T.Text]+parseColorSpace psr pdfstream = +  case parseContentStream (many (choice [ try colorSpace+                                        , try $ T.concat <$> xObject+                                        , (T.empty <$ elems)+                                        ])) psr pdfstream of+    Left  err -> error "Nothing to be parsed"+    Right str -> str+++-- | Parsers for Content Stream+ elems :: PSParser T.Text elems = choice [ try pdfopBT                 , try pdfopTf@@ -57,32 +71,37 @@                , try pdfopTw                , try pdfopTJ                , try pdfopTj+               , try pdfQuote+               , try pdfDoubleQuote                , try pdfopTast                , try letters <* spaces                , try hexletters <* spaces                , try array <* spaces                , try pdfopGraphics-               , try xObject+               , try dashPattern+               , try pathConstructor+               , try $ T.empty <$ xObject                , try graphicState+               , try pdfopcm+               , try $ T.empty <$ colorSpace                , unknowns                ]  pdfopGraphics :: PSParser T.Text pdfopGraphics = do   spaces-  choice [ try $ T.empty <$ oneOf "qQ" <* spaces+  choice [ try $ T.empty <$ oneOf "qQ" <* space <* spaces          , try $ T.empty <$ oneOf "fFbBW" <* (many $ string "*") <* spaces-         , try $ T.empty <$ oneOf "nsS" <* spaces-         , try $ T.empty <$ (digitParam <* spaces) <* oneOf "gG" <* spaces-         , try $ T.empty <$ (digitParam <* spaces) <* oneOf "jJ" <* spaces-         , try $ T.empty <$ (digitParam <* spaces) <* oneOf "dwi" <* spaces-         , try $ T.empty <$ (many1 (digitParam <* spaces) <* oneOf "ml" <* spaces)-         , try $ T.empty <$ (many1 (digitParam <* spaces) <* oneOf "kK" <* spaces)-         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "re" <* spaces)-         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "rg" <* spaces)-         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "RG" <* spaces)-         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "cm" <* spaces)-         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "c" <* spaces)+         , try $ T.empty <$ oneOf "nsS" <* space <* spaces+         , try $ T.empty <$ (digitParam <* spaces) <* oneOf "jJM" <* space <* spaces+         , try $ T.empty <$ (digitParam <* spaces) <* oneOf "dwi" <* space <* spaces+         , try $ T.empty <$ (many1 (digitParam <* spaces) <* oneOf "ml" <* space <* spaces)+         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "re " <* spaces)+         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "SCN " <* spaces)+         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "scn " <* spaces)+         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "SC " <* spaces)+         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "sc " <* spaces)+         , try $ T.empty <$ (many1 (digitParam <* spaces) <* string "c" <* space <* spaces)          ]   return T.empty @@ -94,13 +113,41 @@   spaces   return T.empty -xObject :: PSParser T.Text          +colorSpace :: PSParser T.Text+colorSpace = do+  gs <- choice [ try $ string "/" *> manyTill anyChar (try space) <* string "CS" <|> string "cs" <* spaces+               , try $ "DeviceRGB" <$ (many1 (digitParam <* spaces) <* string "rg" <* spaces)+               , try $ "DeviceRGB" <$ (many1 (digitParam <* spaces) <* string "RG" <* spaces)+               , try $ "DeviceGray" <$ (digitParam <* spaces) <* oneOf "gG" <* spaces+               , try $ "DeviceCMYK" <$ (many1 (digitParam <* spaces) <* oneOf "kK" <* spaces)+               ] +  updateState (\s -> s {colorspace = gs})+  return $ T.pack gs++dashPattern :: PSParser T.Text+dashPattern = do+  char '[' >> many digit >> char ']' >> spaces >> many1 digit >> spaces >> string "d"+  return T.empty+  +pathConstructor :: PSParser T.Text+pathConstructor = do+  choice [ try $ T.empty <$ (digitParam <* spaces) <* oneOf "ml" <* spaces+         , try $ T.empty <$ (digitParam <* spaces) <* oneOf "cvy" <* spaces+         , try $ T.empty <$ (digitParam <* spaces) <* oneOf "re" <* spaces+         , try $ T.empty <$ oneOf "h" <* spaces+         ]+  return T.empty++xObject :: PSParser [T.Text] xObject = do-  file <- (++) <$> string "/" <*> manyTill anyChar (try space)+  n <- (++) <$> string "/" <*> manyTill anyChar (try space)   spaces   string "Do"   spaces-  return T.empty+  st <- getState+  let xobjcs = xcolorspaces st+--  updateState (\s -> s {colorspace = xobjcs})+  return $ map T.pack xobjcs  pdfopBT :: PSParser T.Text pdfopBT = do@@ -124,11 +171,26 @@   spaces   return $ T.concat t   +pdfDoubleQuote :: PSParser T.Text+pdfDoubleQuote = do+  spaces+  t <- manyTill (letters <|> hexletters <|> array) (try $ string "\"")+  spaces+  return $ T.concat t+  +pdfQuote :: PSParser T.Text+pdfQuote = do+  spaces+  t <- manyTill (letters <|> hexletters <|> array) (try $ string "\'")+  spaces+  return $ T.concat t+ unknowns :: PSParser T.Text unknowns = do    ps <- manyTill anyChar (try $ oneOf "\r\n")-  return ""-  return $ T.pack $ "[[[UNKNOWN STREAM:" ++ take 100 (show ps) ++ "]]]"+  return $ if ps=="" +           then "" +           else T.pack $ "[[[UNKNOWN STREAM:" ++ take 100 (show ps) ++ "]]]"  skipOther :: PSParser T.Text skipOther = do@@ -183,7 +245,7 @@         Nothing -> []   c <- try (char '\\' >> oneOf "\\()")        <|>-       try (octToString . readOct <$> (char '\\' >> (count 3 $ oneOf "01234567")))+       try (octToChar . readOct <$> (char '\\' >> (count 3 $ oneOf "01234567")))        <|>        noneOf "\\"   return $ replaceWithDiff fontmap c@@ -193,8 +255,8 @@           replaceWithCharDict s = case Map.lookup s pdfcharmap of             Just cs -> cs             Nothing -> T.pack s-          octToString [] = '?'-          octToString [(o,_)] = chr o+          octToChar [] = '?'+          octToChar [(o,_)] = chr o  kern :: PSParser T.Text kern = do@@ -212,7 +274,9 @@   st <- getState   let ff = fontfactor st   updateState (\s -> s{ curfont = font-                      , fontfactor = ff*t})+                      , fontfactor = t+                      , linex = t+                      , liney = t})   return ""  pdfopTD :: PSParser T.Text@@ -230,15 +294,15 @@       ly = liney st       lm = leftmargin st       ff = fontfactor st-      needBreak = abs t2 > 0 && abs (ly - t2) > 0-  updateState (\s -> s { absolutex = ax + t1-                       , absolutey = ay + (if needBreak then 2*t2 else 0)-                       , linex = if abs t1 > 0 then t1 else lx-                       , liney = if abs t2 > 0 then 2*t2 else ly+      needBreak = t2 < 0+  updateState (\s -> s { absolutex = ax - lx+                       , absolutey = ay - ly+                       , linex = lx+                       , liney = -t2*ff                        })   return $ if needBreak -           then T.concat ["\n", (desideParagraphBreak t1 t2 lx ly lm ff)] -           else ""+           then T.concat ["\n", (desideParagraphBreak (t1*ff) (t2*ff) lx (-t2*ff) lm ff)]+           else if t1 > ff then " " else ""  pdfopTd :: PSParser T.Text pdfopTd = do@@ -255,15 +319,15 @@       ly = liney st       lm = leftmargin st       ff = fontfactor st-      needBreak = abs t2 > 0 && abs (ly - t2) > 0-  updateState (\s -> s { absolutex = ax + t1-                       , absolutey = ay + (if needBreak then t2 else 0)-                       , linex = if abs t1 > 0 then t1 else lx-                       , liney = if abs t2 > 0 then t2 else ly+      needBreak = t2 < 0 && abs t2 > ly+  updateState (\s -> s { absolutex = ax - lx+                       , absolutey = ay - ly+                       , linex = lx+                       , liney = ly                        })   return $ if needBreak             then T.concat ["\n", (desideParagraphBreak t1 t2 lx ly lm ff)] -           else ""+           else if t1 > ff then " " else ""  pdfopTw :: PSParser T.Text pdfopTw = do@@ -292,7 +356,7 @@ desideParagraphBreak :: Double -> Double -> Double -> Double -> Double -> Double                       -> T.Text desideParagraphBreak t1 t2 lx ly lm ff = T.pack $-  (if abs ly > abs ff || abs t2 > abs ff || (t1 - lm) > 0.5+  (if abs t2 > 1.8*ly || (t1 - lm) > 0.5    then "\n"    else "") @@ -319,17 +383,48 @@       ly = liney st       lm = leftmargin st       ff = fontfactor st-      needBreak = abs d*f > 0 && ly >= 0-  updateState (\s -> s { linex     = lx-                       , liney     = ly-                       , absolutex = a*e-                       , absolutey = d*f-                       , fontfactor = a*e*d*f*ff+      newff = (a+d)/2+      needBreak = ay - f/newff > (ly/newff)+  updateState (\s -> s { linex     = lx/newff+                       , liney     = ly/newff+                       , absolutex = e/newff+                       , absolutey = f/newff                        })   return $ if needBreak -           then T.concat ["\n", desideParagraphBreak (d*f) (d*f) lx ly lm ff]-           else if a*e > lx then " " else ""+           then T.concat ["\n", desideParagraphBreak (e*newff) (f*newff) lx ly lm newff]+           else if e > newff then " " else "" +pdfopcm :: PSParser T.Text+pdfopcm = do+  a <- digitParam+  spaces+  b <- digitParam+  spaces+  c <- digitParam+  spaces+  d <- digitParam+  spaces+  e <- digitParam+  spaces+  f <- digitParam+  spaces+  string "cm"+  spaces+  st <- getState+  let ax = absolutex st+      ay = absolutey st+      lx = linex st+      ly = liney st+      lm = leftmargin st+      ff = fontfactor st+      newff = (a+d)/2+  updateState (\s -> s { linex     = lx/newff+                       , liney     = ly/newff+                       , absolutex = e/newff+                       , absolutey = f/newff+                       })+  return T.empty+ pdfopTast :: PSParser T.Text pdfopTast = do   string "T*"@@ -340,8 +435,8 @@       ly = liney st   updateState (\s -> s { linex     = lx                        , liney     = ly-                       , absolutex = ax-                       , absolutey = ay+                       , absolutex = ax - lx+                       , absolutey = ay - ly                        })   return "\n" 
src/PDF/Definition.hs view
@@ -61,6 +61,9 @@                , fontfactor :: Double                , curfont    :: String                , cmaps      :: [(String, CMap)]-               , fontmaps   :: [(String, FontMap)]}+               , fontmaps   :: [(String, FontMap)]+               , colorspace :: String+               , xcolorspaces :: [String]+               }          deriving (Show) 
src/PDF/Object.hs view
@@ -8,6 +8,7 @@        , rawContentsStream        , rawStreamByRef        , rawStream+       , contentsColorSpace        , toUnicode        , pagesKids        , pages@@ -19,6 +20,7 @@        , findObjsByRef        , parsePDFObj        , parseRefsArray+       , parsePdfLetters        , getObjs        , pdfObj        , getRefs@@ -31,10 +33,15 @@ import Data.ByteString.UTF8 (ByteString) import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as BSL+import qualified Data.Text as T+import Data.Text.Encoding (decodeUtf16BE)+import Numeric (readOct, readHex)+import Data.ByteString.Builder (toLazyByteString, word16BE) -import Text.Parsec hiding (many, (<|>))+import Data.Attoparsec.ByteString hiding (inClass, notInClass, satisfy)+import Data.Attoparsec.ByteString.Char8+import Data.Attoparsec.Combinator import Control.Applicative-import Text.Parsec.ByteString import Codec.Compression.Zlib (decompress)  import Debug.Trace@@ -43,16 +50,19 @@ import PDF.ContentStream import PDF.Cmap +spaces = skipSpace+oneOf = satisfy . inClass+noneOf = satisfy . notInClass  -- parse pdf objects  getObjs :: BS.ByteString -> [PDFBS]-getObjs contents = case parse (many1 pdfObj) "" contents of+getObjs contents = case parseOnly (many1 pdfObj) contents of   Left  err -> []   Right rlt -> rlt  getXref :: BS.ByteString -> String-getXref contents = case parse (xref) "" contents of+getXref contents = case parseOnly (xref) contents of   Left  err -> []   Right rlt -> rlt @@ -66,7 +76,7 @@   return $ (read objn, BS.pack object)  parsePDFObj :: PDFBS -> PDFObj-parsePDFObj (n,pdfobject) = case parse (spaces >> many1 (pdfobj <|> objother)) "" pdfobject of+parsePDFObj (n,pdfobject) = case parseOnly (spaces >> many1 (pdfobj <|> objother)) pdfobject of   Left  err -> (n,[PdfNull])   Right obj -> (n,obj) @@ -103,15 +113,44 @@ pdfarray = PdfArray <$> (string "[" >> spaces *> manyTill pdfobj (try $ spaces >> string "]"))  pdfname :: Parser Obj-pdfname = PdfName <$> ((++) <$> string "/" <*> manyTill anyChar (try $ lookAhead $ oneOf "><][)( \n\r/")) <* spaces+pdfname = PdfName . BS.unpack <$> (BS.append <$> string "/" <*> (BS.pack <$> (manyTill anyChar (try $ lookAhead $ oneOf "><][)( \n\r/")))) <* spaces  pdfletters :: Parser Obj-pdfletters = PdfText <$> (concat <$> (char '(' *> manyTill pdfletter (try $ char ')')))-  where pdfletter = choice [ return <$> try (char '\\' >> oneOf "\\()") -                           , (++) <$> ("(" <$ char '(') <*> ((++")") . concat <$> manyTill pdfletter (try $ char ')'))-                           , return <$> (noneOf "\\")-                           ]+pdfletters = PdfText <$> parsePdfLetters +parsePdfLetters :: Parser String+parsePdfLetters = (concat <$> (char '(' *> manyTill (choice [try pdfutf, try pdfoctutf, pdfletter]) (try $ char ')')))+  where pdfletter = do+          str <- choice [ return <$> try (char '\\' >> oneOf "\\()")+                        , "\n" <$ try (string "\n")+                        , "\r" <$ try (string "\r")+                        , "\t" <$ try (string "\t")+                        , "\b" <$ try (string "\b")+                        , "\f" <$ try (string "\f")+                        , (++) <$> ("(" <$ char '(') <*> ((++")") . concat <$> manyTill pdfletter (try $ char ')'))+                        , return <$> (noneOf "\\")+                        ]+          return $ str+        pdfutf :: Parser String+        pdfutf = do +          str <- string "\254\255" *> manyTill anyChar (lookAhead $ string ")")+          return $ utf16be str+        +        pdfoctutf :: Parser String+        pdfoctutf = do+          string "\\376\\377" +          octstr <- manyTill (choice [ try (return . chr . fst . head . readOct <$> (char '\\' *> count 3 (oneOf "01234567")))+                                     , try ("\92" <$ string "\\\\")+                                     , return <$> noneOf "\\"+                                     ])+                    (lookAhead $ string ")")+          return $ utf16be $ concat octstr++        octToString [] = "????"+        octToString [(o,_)] = [chr o]++utf16be = T.unpack . decodeUtf16BE . BS.pack+ pdfstream :: Parser Obj pdfstream = PdfStream <$> stream @@ -119,19 +158,31 @@ pdfnumber = PdfNumber <$> pdfdigit   where pdfdigit = do            sign <- many $ char '-'-          num <- ((++) <$> (("0"++) <$> string ".") <*> many1 digit)+          num <- ((++) <$> (("0"++) . BS.unpack <$> string ".") <*> (many1 digit))                  <|>                  ((++) <$> (many1 digit) <*> ((++) <$> (many $ char '.') <*> many digit))           spaces                   return $ read $ sign ++ num  pdfhex :: Parser Obj-pdfhex = PdfHex <$> hex  +pdfhex = PdfHex <$> hex   where hex = do           char '<'-          lets <- manyTill (oneOf "0123456789abcdefABCDEF") (try $ char '>')-          return $ lets+          lets <- BS.pack <$> manyTill (oneOf "0123456789abcdefABCDEF") (try $ char '>')+          case parseOnly ((try $ string "feff" <|> string "FEFF") *> (many1 (oneOf "0123456789abcdefABCDEF"))) lets of+            Right s -> return $ pdfhexletter $ BS.pack s+            Left e -> return . BS.unpack $ lets +pdfhexletter s = case parseOnly (concat <$> many1 pdfhexutf16be) s of+  Right t -> utf16be t+  Left e -> BS.unpack s++pdfhexutf16be :: Parser String+pdfhexutf16be = do+  c <- count 4 $ oneOf "0123456789ABCDEFabcdef"+  let b = BSL.unpack . toLazyByteString . word16BE $ fst . head . readHex $ c+  return $ b+ pdfbool :: Parser Obj pdfbool = PdfBool <$> (True <$ string "true"                        <|> @@ -142,9 +193,14 @@  pdfobj :: Parser Obj pdfobj = choice [ try rrefs <* spaces-                , try pdfname <* spaces, try pdfnumber <* spaces, try pdfhex <* spaces-                , try pdfbool <* spaces, try pdfnull <* spaces-                , try pdfarray <* spaces, try pdfdictionary <* spaces, try pdfstream <* spaces+                , try pdfname <* spaces+                , try pdfnumber <* spaces+                , try pdfhex <* spaces+                , try pdfbool <* spaces+                , try pdfnull <* spaces+                , try pdfarray <* spaces+                , try pdfdictionary <* spaces+                , {-# SCC pdfstream #-} try pdfstream <* spaces                 , pdfletters <* spaces                 ] @@ -239,7 +295,8 @@     contents _                        = False  parsedContentStreamByRef :: Dict -> PSR -> [PDFObj] -> Int -> PDFStream-parsedContentStreamByRef dict st objs ref = deflate (st {fontmaps=fontdict, cmaps=cmap}) $ rawStreamByRef objs ref+parsedContentStreamByRef dict st objs ref = +  deflate (st {fontmaps=fontdict, cmaps=cmap}) $ rawStreamByRef objs ref   where fontdict = findFontMap dict objs         cmap = findCMap dict objs @@ -261,7 +318,17 @@ parseRefsArray (x:y)  = (parseRefsArray y) parseRefsArray [] = [] +contentsColorSpace :: Dict -> PSR -> [PDFObj] -> [T.Text]+contentsColorSpace dict st objs = case find contents dict of+  Just (PdfName "/Contents", PdfArray arr) -> concat $ map (parseColorSpace (st {xcolorspaces=xobjcs}) . rawStreamByRef objs) (parseRefsArray arr)+  Just (PdfName "/Contents", ObjRef x)     -> parseColorSpace (st {xcolorspaces=xobjcs}) $ rawStreamByRef objs x+  Nothing                                  -> error "No content to be shown"+  where+    contents (PdfName "/Contents", _) = True+    contents _                        = False+    xobjcs = findXObjectColorSpace dict objs + -- make fontmap from page's /Resources (see 3.7.2 of PDF Ref.)  findFontMap d os = encoding (getFontObjs d os) os@@ -332,23 +399,55 @@   otherwise -> []  +-- find XObject++findXObjectColorSpace d os = xobjColorSpaceMap (getXObject d os) os++xobjColorSpaceMap dict objs = map pairwise dict+  where+    pairwise (PdfName n, ObjRef r) = xobjColorSpace r objs+    pairwise x = ""++getXObject dict objs = case findResourcesDict dict objs of+  Just d -> case findObjThroughDict d "/XObject" of+    Just (PdfDict d) -> d+    otherwise -> []+  Nothing -> []++xobjColorSpace :: Int -> [PDFObj] -> String+xobjColorSpace x objs = case findObjThroughDictByRef x "/ColorSpace" objs of+  Just (PdfName cs) -> cs+  otherwise -> ""++ -- find root ref from Trailer or Cross-Reference Dictionary  parseTrailer :: BS.ByteString -> Maybe Dict-parseTrailer bs = case parse trailer "" bs of-  Left  err -> Nothing+parseTrailer bs = case parseOnly (try trailer <|> xref) bs of+  Left  err -> (trace (show err) Nothing)   Right rlt -> Just (parseCRDict rlt)   where trailer :: Parser BS.ByteString         trailer = do           manyTill anyChar (try $ string "trailer")           t <- manyTill anyChar (try $ string "startxref")           return $ BS.pack t+        xref :: Parser BS.ByteString+        xref = do+          manyTill anyChar (try $ string "startxref")+          offset <- spaces *> many1 digit+          return $ BS.drop (read offset :: Int) bs  parseCRDict :: BS.ByteString -> Dict-parseCRDict rlt = case parse (spaces >> pdfdictionary <* spaces) "" rlt of-    Left  err  -> error $ show err-    Right (PdfDict dict) -> dict-    Right other -> error "Could not find Cross-Reference dictionary"+parseCRDict rlt = case parseOnly crdict rlt of+  Left  err  -> error $ show rlt+  Right (PdfDict dict) -> dict+  Right other -> error "Could not find Cross-Reference dictionary"+  where crdict :: Parser Obj+        crdict = do +          spaces+          many (many1 digit >> spaces >> digit >> string " obj" >> spaces)+          d <- pdfdictionary <* spaces+          return d  rootRef :: BS.ByteString -> Maybe Int rootRef bs = case parseTrailer bs of@@ -393,20 +492,20 @@ objStm :: PDFObj -> [PDFObj] objStm (n, obj) = case findDictOfType "/ObjStm" obj of   Nothing -> [(n,obj)]-  Just _  -> getPdfObjStm n $ BS.pack $ BSL.unpack $ rawStream obj+  Just _  -> getPdfObjStm n $ BSL.toStrict $ rawStream obj    refOffset :: Parser ([(Int, Int)], String) refOffset = spaces *> ((,) -                       <$> many1 ((\r o -> (read r :: Int, read o :: Int)) +                       <$> many1 ((\r o -> (read r :: Int, read o :: Int))                                   <$> (many1 digit <* spaces)                                    <*> (many1 digit <* spaces))-                       <*> (manyTill anyChar (lookAhead $ string "<<") *> many1 anyChar))+                       <*> many1 anyChar)  getPdfObjStm n s = -  let (location, objstr) = case parse refOffset "" s of+  let (location, objstr) = case parseOnly refOffset s of         Right val -> val         Left err  -> error $ "Failed to parse Object Stream: "   in map (\(r,o) -> (r, parseDict $ BS.pack $ drop o objstr)) location-    where parseDict s' = case parse pdfdictionary "" s' of+    where parseDict s' = case parseOnly pdfdictionary s' of             Right obj -> [obj]-            Left  err -> error "Failed to parse obj"+            Left  err -> error $ "Failed to parse obj " ++ (show s') ++ (show err)
src/PDF/Outlines.hs view
@@ -7,7 +7,12 @@ import Debug.Trace  import Data.List (find)-import PDF.Definition+import Data.Attoparsec.ByteString hiding (inClass, notInClass, satisfy)+import Data.Attoparsec.ByteString.Char8+import Data.Attoparsec.Combinator+import qualified Data.ByteString.Char8 as BS++import PDF.Definition hiding (toString) import PDF.Object import PDF.PDFIO @@ -17,8 +22,18 @@                                     , subs :: PDFOutlines                                     }                  | PDFOutlinesNE-                 deriving (Show) +instance Show PDFOutlines where+  show = toString 0++toString :: Int -> PDFOutlines -> String+toString depth PDFOutlinesEntry {dest=d, text=t, subs=s} = (replicate depth ' ' ++ t) ++ toString (depth+1) s+toString depth (PDFOutlinesTree os) = concatMap (toString depth) os+toString depth PDFOutlinesNE = ""++-- | Get information of \/Outlines from 'filename'++getOutlines :: FilePath -> IO PDFOutlines getOutlines filename = do   dict <- outlineObjFromFile filename   objs <- getPDFObjFromFile filename  @@ -26,9 +41,9 @@     Just r -> return r     Nothing -> error "No top level outline entry."   firstdict <- case findObjsByRef firstref objs of-    Just [PdfDict d] -> return $ d+    Just [PdfDict d] -> return d     Nothing -> error $ "No Object with Ref " ++ show firstref-  showOutlines 0 $ gatherOutlines firstdict objs+  return $ gatherOutlines firstdict objs  gatherChildren dict objs = case findFirst dict of   Just r -> case findObjsByRef r objs of@@ -41,19 +56,14 @@   in case findNext dict of      Just r -> case findObjsByRef r objs of       Just [PdfDict d] -> PDFOutlinesTree (PDFOutlinesEntry { dest = head $ findDest dict-                                                            , text = findTitle dict objs+                                                            , text = findTitle dict objs ++ "\n"                                                             , subs = c}                                            : [gatherOutlines d objs])       Nothing -> error $ "No Object with Ref " ++ show r     Nothing -> PDFOutlinesEntry { dest = head $ findDest dict-                                , text = findTitle dict objs+                                , text = findTitle dict objs ++ "\n"                                 , subs = PDFOutlinesNE} -showOutlines :: Int -> PDFOutlines -> IO ()-showOutlines depth (PDFOutlinesEntry {dest=d, text=t, subs=s}) = putStrLn (replicate depth ' ' ++ t) >> showOutlines (depth+1) s-showOutlines depth (PDFOutlinesTree os) = mapM_ (showOutlines (depth)) os-showOutlines depth PDFOutlinesNE = putStr ""- outlines :: Dict -> Int outlines dict = case find isOutlinesRef dict of   Just (_, ObjRef x) -> x@@ -78,10 +88,13 @@  findTitle dict objs =    case findObjThroughDict dict "/Title" of-    Just (PdfText s) -> s+    Just (PdfText s) -> case parseOnly parsePdfLetters (BS.pack s) of+      Right t -> t+      Left err -> s     Just (ObjRef r) -> case findObjsByRef r objs of       Just [PdfText s] -> s-      Nothing -> error $ "No title object in "++(show r)+      Nothing -> error $ "No title object in " ++ show r+    Just x -> show x     Nothing -> error "No title object."  findDest dict = 
src/PDF/PDFIO.hs view
@@ -1,3 +1,12 @@+{-|+Module      : PDF.PDFIO+Description : IO utilities for hpdft+Copyright   : (c) Keiichiro Shikano, 2016+License     : MIT+Maintainer  : k16.shikano@gmail.com++Functions for use within IO. +-} module PDF.PDFIO ( getObjectByRef                  , getPDFBSFromFile                  , getPDFObjFromFile@@ -12,41 +21,34 @@  import qualified Data.ByteString.Char8 as BS ---- IO utilities+-- | Get PDF objects as a whole bytestring. Use 'getPDFObjFromFile' instead if there's no reason to see a raw bytestring.  -getPDFBSFromFile :: String -> IO [PDFBS]+getPDFBSFromFile :: FilePath -> IO [PDFBS] getPDFBSFromFile f = do   c <- BS.readFile f   let bs = getObjs c   return bs -getPDFObjFromFile :: String -> IO [PDFObj]+-- | Get PDF objects each parsed as 'PDFObj' without being sorted. ++getPDFObjFromFile :: FilePath -> IO [PDFObj] getPDFObjFromFile f = do   c <- BS.readFile f   let obj = expandObjStm $ map parsePDFObj $ getObjs c   return obj -getRawObjFromFile :: String -> Int -> IO BS.ByteString-getRawObjFromFile f r = do-  c <- BS.readFile f-  let objs = getObjs c-  case lookup r objs of-    Just obj -> return obj-    Nothing -> error $ "No object with ref "++show r+-- | Get a PDF object from a whole 'PDFObj' by specifying 'ref :: Int'  getObjectByRef :: Int -> IO [PDFObj] -> IO [Obj] getObjectByRef ref pdfobjs = do   objs <- pdfobjs   case findObjsByRef ref objs of-    Just os -> return $ os+    Just os -> return os     Nothing -> error $ "No Object with Ref " ++ show ref -getRootRefFromFile :: String -> IO (Maybe Int)-getRootRefFromFile f = do-  c <- BS.readFile f-  return $ rootRef c+-- | The reference number of /Root in 'filename'. +getRootRef :: FilePath -> IO Int getRootRef filename = do   c <- BS.readFile filename   let n = rootRef c@@ -54,6 +56,9 @@     Just i -> return i     Nothing -> error "Could not find rood object"     +-- | The /Root object in 'filename'.++getRootObj :: FilePath -> IO [Obj] getRootObj filename = do   rootref <- getRootRef filename   objs <- getPDFObjFromFile filename@@ -61,10 +66,16 @@     Just os -> return os     Nothing -> error "Could not get root object" +-- | The trailer of 'filename'.++getTrailer :: FilePath -> IO Dict getTrailer filename = do   c <- BS.readFile filename   return $ findTrailer c +-- | /Info of 'filename'.++getInfo :: FilePath -> IO Dict getInfo filename = do   d <- getTrailer filename   objs <- getPDFObjFromFile filename@@ -75,11 +86,3 @@   case findDictByRef inforef objs of     Just os -> return os     Nothing -> error "Could not get info object"--getTitle filename = do-  d <- getInfo filename-  case findObjThroughDict d "/Title" of-    Just (PdfText s) -> return s-    Just x -> error $ show x-    Nothing -> error "No title anyway"-