diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/hpdft.cabal b/hpdft.cabal
--- a/hpdft.cabal
+++ b/hpdft.cabal
@@ -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
diff --git a/src/PDF/ContentStream.hs b/src/PDF/ContentStream.hs
--- a/src/PDF/ContentStream.hs
+++ b/src/PDF/ContentStream.hs
@@ -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
diff --git a/src/PDF/DocumentStructure.hs b/src/PDF/DocumentStructure.hs
--- a/src/PDF/DocumentStructure.hs
+++ b/src/PDF/DocumentStructure.hs
@@ -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.)
