packages feed

hpdft 0.4.6.0 → 0.4.6.1

raw patch · 4 files changed

+113/−64 lines, 4 filesdep ~basedep ~bytestringdep ~containers

Dependency ranges changed: base, bytestring, containers, directory, filepath, text

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog +## 0.4.6.1 (2026-07-06)++### Fixed++- Object streams whose entries mix dictionaries and arrays (e.g. page-tree nodes stored in a single ObjStm) no longer leave intermediate `/Pages` nodes unresolved; page 1 and document order match the PDF catalog again.+- `hpdft.cabal`: `cabal-version` lowered to 3.8 and dependency bounds widened for GHC 9.8 (Hackage docbuilder compatibility).+ ## 0.4.6.0 (2026-07-05)  ### Changed
hpdft.cabal view
@@ -1,6 +1,6 @@-cabal-version:       3.12+cabal-version:       3.8 name:                hpdft-version:             0.4.6.0+version:             0.4.6.1 synopsis:            PDF parsing library and CLI for text, layout, diff, images, and forms description:     hpdft is a Haskell library and command-line tool for parsing PDF files.@@ -67,22 +67,22 @@   other-modules:       Paths_hpdft   other-extensions:    OverloadedStrings   build-depends:       attoparsec >= 0.14.4 && < 0.15-                     , base >= 4.20.0 && < 4.23+                     , base >= 4.18.0 && < 4.23                      , binary >= 0.8.9 && < 0.9-                     , bytestring >= 0.11.5 && < 0.13-                     , containers >= 0.7 && < 0.9+                     , bytestring >= 0.11.4 && < 0.13+                     , containers >= 0.6.7 && < 0.9                      , crypton >= 0.34 && < 0.36-                     , directory >= 1.3.9 && < 1.4+                     , directory >= 1.3.8 && < 1.4                      , dlist >= 1.0 && < 1.1                      , file-embed >= 0.0.16 && < 0.1-                     , filepath >= 1.5.2 && < 1.6+                     , filepath >= 1.4.2 && < 1.6                      , memory >= 0.18.0 && < 0.19                      , optparse-applicative >= 0.18.1 && < 0.19                      , parallel >= 3.2.2 && < 3.4                      , deepseq >= 1.4.8 && < 1.6                      , parsec >= 3.1.18 && < 3.2                      , semigroups >= 0.20 && < 0.21-                     , text >= 2.1.2 && < 2.2.2+                     , text >= 2.0.2 && < 2.2.2                      , utf8-string >= 1.0.2 && < 1.1                      , zlib >= 0.7.1 && < 0.8   autogen-modules:   Paths_hpdft@@ -98,8 +98,8 @@                      , TuiScroll   other-extensions:    OverloadedStrings   build-depends:       array >= 0.5 && < 0.6-                     , base >= 4.20.0 && < 4.23-                     , bytestring >= 0.11.5 && < 0.13+                     , base >= 4.18.0 && < 4.23+                     , bytestring >= 0.11.4 && < 0.13                      , hpdft                      , memory >= 0.18.0 && < 0.19                      , optparse-applicative >= 0.18.1 && < 0.19@@ -107,7 +107,7 @@                      , regex-tdfa >= 1.3.2 && < 1.4                      , semigroups >= 0.20 && < 0.21                      , terminal-size >= 0.3 && < 0.4-                     , text >= 2.1.2 && < 2.2.2+                     , text >= 2.0.2 && < 2.2.2                      , utf8-string >= 1.0.2 && < 1.1   autogen-modules:   Paths_hpdft   default-language:  Haskell2010@@ -118,10 +118,10 @@   main-is:             Golden.hs   hs-source-dirs:      test   ghc-options:         -threaded -rtsopts "-with-rtsopts=-N -A64m"-  build-depends:       base >= 4.20.0 && < 4.23-                     , bytestring >= 0.11.5 && < 0.13-                     , directory >= 1.3.9 && < 1.4-                     , filepath >= 1.5.2 && < 1.6+  build-depends:       base >= 4.18.0 && < 4.23+                     , bytestring >= 0.11.4 && < 0.13+                     , directory >= 1.3.8 && < 1.4+                     , filepath >= 1.4.2 && < 1.6                      , hpdft   default-language:    Haskell2010 @@ -131,32 +131,32 @@   hs-source-dirs:      test                      , .   ghc-options:         -threaded -rtsopts "-with-rtsopts=-N -A64m"-  build-depends:       base >= 4.20.0 && < 4.23-                     , bytestring >= 0.11.5 && < 0.13-                     , containers >= 0.7 && < 0.9-                     , directory >= 1.3.9 && < 1.4-                     , filepath >= 1.5.2 && < 1.6+  build-depends:       base >= 4.18.0 && < 4.23+                     , bytestring >= 0.11.4 && < 0.13+                     , containers >= 0.6.7 && < 0.9+                     , directory >= 1.3.8 && < 1.4+                     , filepath >= 1.4.2 && < 1.6                      , hpdft-                     , text >= 2.1.2 && < 2.2.2+                     , text >= 2.0.2 && < 2.2.2   other-modules:       TuiScroll   default-language:    Haskell2010  executable interpret-page   main-is:             scripts/interpret_page.hs   hs-source-dirs:      .-  build-depends:       base >= 4.20.0 && < 4.23+  build-depends:       base >= 4.18.0 && < 4.23                      , hpdft-                     , text >= 2.1.2 && < 2.2.2+                     , text >= 2.0.2 && < 2.2.2   default-language:    Haskell2010   buildable:           True  executable extract-text   main-is:             Main.hs   hs-source-dirs:      examples/extract-text-  build-depends:       base >= 4.20.0 && < 4.23-                     , bytestring >= 0.11.5 && < 0.13-                     , directory >= 1.3.9 && < 1.4-                     , filepath >= 1.5.2 && < 1.6+  build-depends:       base >= 4.18.0 && < 4.23+                     , bytestring >= 0.11.4 && < 0.13+                     , directory >= 1.3.8 && < 1.4+                     , filepath >= 1.4.2 && < 1.6                      , hpdft   default-language:    Haskell2010   buildable:           True@@ -164,10 +164,10 @@ executable page-api   main-is:             Main.hs   hs-source-dirs:      examples/page-api-  build-depends:       base >= 4.20.0 && < 4.23-                     , directory >= 1.3.9 && < 1.4-                     , filepath >= 1.5.2 && < 1.6+  build-depends:       base >= 4.18.0 && < 4.23+                     , directory >= 1.3.8 && < 1.4+                     , filepath >= 1.4.2 && < 1.6                      , hpdft-                     , text >= 2.1.2 && < 2.2.2+                     , text >= 2.0.2 && < 2.2.2   default-language:    Haskell2010   buildable:           True
src/PDF/ContentStream.hs view
@@ -336,11 +336,44 @@  hexletters :: PSParser T.Text hexletters = do+  st <- getState   char '<'-  lets <- manyTill hexletter (try $ char '>')+  hexChars <- many (oneOf "0123456789ABCDEFabcdef")+  char '>'   spaces-  return $ T.concat lets+  let cmap = Map.findWithDefault Map.empty (curfont st) (cmaps st)+      codes = hexStringToCodes (fontBytesPerCode st) hexChars+  parts <- mapM (lookupUcs cmap) codes+  return $ T.concat parts +-- | Split a hex string into character codes (matches 'PDF.Interpret.hexPairs'+-- and 'bytesToCodes' for 1- vs 2-byte fonts).+fontBytesPerCode :: PSR -> Int+fontBytesPerCode st =+  case Map.lookup (curfont st) (fontmaps st) of+    Just (CIDmap _) -> 2+    _ -> 1++hexStringToCodes :: Int -> String -> [Int]+hexStringToCodes bpc hex =+  let bytes = hexPairs hex+  in if bpc == 2 then pairBytes bytes else bytes+  where+    pairBytes [] = []+    pairBytes [_] = []+    pairBytes (a:b:rest) = (a * 256 + b) : pairBytes rest++hexPairs :: String -> [Int]+hexPairs [] = []+hexPairs [x] =+  case readHex [x, '0'] of+    [(n, "")] -> [n]+    _         -> []+hexPairs (a:b:rest) =+  case readHex [a, b] of+    [(n, "")] -> n : hexPairs rest+    _         -> hexPairs rest+ octletters :: PSParser T.Text octletters = do   char '('@@ -374,14 +407,13 @@ hexletter :: PSParser T.Text hexletter = do   st <- getState-  let font = curfont st-      cmap = Map.findWithDefault Map.empty font (cmaps st)-  hexDigits <- choice [ try $ count 4 $ oneOf "0123456789ABCDEFabcdef"-                      , try $ count 2 $ oneOf "0123456789ABCDEFabcdef"+  let cmap = Map.findWithDefault Map.empty (curfont st) (cmaps st)+  -- Single code inside a hex string fragment (legacy path; prefer hexletters).+  hexDigits <- choice [ try $ count 2 $ oneOf "0123456789ABCDEFabcdef"                       , try $ (:"0") <$> (oneOf "0123456789ABCDEFabcdef")                       ]   case readHex hexDigits of-    [(h,"")] -> lookupUcs cmap h+    [(h, "")] -> lookupUcs cmap h     _ -> return "????"  octletter :: PSParser T.Text
src/PDF/DocumentStructure.hs view
@@ -47,7 +47,7 @@ import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.ByteString.Builder as B import qualified Data.Text as T-import Data.Maybe (fromMaybe)+import Data.Maybe (fromMaybe, listToMaybe) import Numeric (readDec)  import Data.Attoparsec.ByteString.Char8 hiding (take, isDigit)@@ -114,7 +114,7 @@     objs = M.mapWithKey resolve xref     containerCache =       M.fromList-        [ (cnum, objStmEntries cnum)+        [ (cnum, objStmBody cnum)         | cnum <- nub [c | InObjStm c _ <- M.elems xref]         ]     resolve objNum (InFile off) =@@ -122,21 +122,19 @@       in snd (parsePDFObj msec (objNum, body))     resolve objNum (InObjStm cnum idx) =       case M.lookup cnum containerCache of-        Just entries ->-          case lookup objNum entries of-            Just o -> o-            Nothing ->-              case drop idx entries of-                ((_, o) : _) -> o-                _ -> [PdfNull]+        Just (locations, body) ->+          let off = case listToMaybe (drop idx locations) of+                Just (_, o) -> Just o+                Nothing -> lookup objNum locations+          in maybe [PdfNull] (parseObjStmObject body) off         Nothing -> [PdfNull]-    objStmEntries cnum =+    objStmBody cnum =       case rawStream msec cnum (M.findWithDefault [PdfNull] cnum objs) of         Right streamBytes ->-          case pdfObjStm cnum (BSL.toStrict streamBytes) of-            Right entries -> entries-            Left _ -> []-        Left _ -> []+          case parseObjStmHeader (BSL.toStrict streamBytes) of+            Right cache -> cache+            Left _ -> ([], BS.empty)+        Left _ -> ([], BS.empty)  buildIndexEager :: BS.ByteString -> Maybe Security -> PdfResult PDFObjIndex buildIndexEager bytes msec = do@@ -672,20 +670,32 @@         _ -> fail "invalid object stream reference"  pdfObjStm n s =-  case parseOnly refOffset s of-    Right (location, objstr) ->-      mapM (\(r,o) -> (,) r <$> parseDict (BS.pack $ drop o objstr)) location+  case parseObjStmHeader s of+    Right (location, body) ->+      Right [ (r, parseObjStmObject body o) | (r, o) <- location ]     Left err ->       Left (ParseError ("Failed to parse Object Stream: " ++ show err) (BS.take 80 s))-  where parseDict s' = case parseOnly pdfdictionary s' of-          Right obj -> Right [obj]-          Left  _   -> case parseOnly pdfarray s' of-            Right obj -> Right [obj]-            Left _ -> case parseOnly pdfletters s' of-              Right obj -> Right [obj]-              Left err ->-                Left (ParseError ((show err) ++ ": Failed to parse obj around")-                      (BS.take 100 s'))++parseObjStmHeader :: BS.ByteString -> Either String ([(Int, Int)], BS.ByteString)+parseObjStmHeader s =+  case parseOnly refOffset s of+    Right (location, objstr) -> Right (location, BS.pack objstr)+    Left err -> Left (show err)++parseObjStmObject :: BS.ByteString -> Int -> [Obj]+parseObjStmObject body off =+  case parseObjStmValue (BS.drop off body) of+    Right obj -> obj+    Left _ -> [PdfNull]++parseObjStmValue :: BS.ByteString -> Either String [Obj]+parseObjStmValue s' = case parseOnly pdfdictionary s' of+  Right obj -> Right [obj]+  Left _ -> case parseOnly pdfarray s' of+    Right obj -> Right [obj]+    Left _ -> case parseOnly pdfletters s' of+      Right obj -> Right [obj]+      Left err -> Left (show err)   -- make fontmap from page's /Resources (see 3.7.2 of PDF Ref.)