Repository: J-F-Liu/lopdf (language: rust)

Issue:
CMap dstString with space between unicode values
A single character code can be expanded into multiple characters in `beginbfchar`

```text
<1d> <0066 0069> % fi
<1f> <0066 0066> % ff
```

I believe the parser cannot currently parse this case.


Here is the full CMap. 

```text
/CIDInit /ProcSet findresource begin

12 dict begin

begincmap

/CIDSystemInfo 3 dict dup begin
  /Registry (callas) def
  /Ordering (MyriadPro-Regular19-UCMap) def
  /Supplement 0 def
end def

/CMapName /MyriadPro-Regular19-UCMap def
/CMapType 2 def

1 begincodespacerange
<1d> <a9>
endcodespacerange
7 beginbfchar
<1d> <0066 0069>
<1e> <00A0>
<1f> <0066 0066>
<20> <0020>
<5f> <005F>
<96> <2013>
<a9> <00A9>
endbfchar
10 beginbfrange
<28> <29> <0028>
<2c> <3a> <002C>
<41> <45> <0041>
<47> <48> <0047>
<4a> <4c> <004A>
<4e> <55> <004E>
<61> <69> <0061>
<6c> <70> <006C>
<72> <77> <0072>
<79> <7a> <0079>
endbfrange
endcmap
CMapName currentdict /CMap defineresource pop
end
end
```


Discussion / hints:

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 619514d..1020a8b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,7 @@ itoa = "1.0"
 log = "0.4"
 md-5 = "0.10"
 nom = { version = "7.1", optional = true }
+nom_locate = { version = "4.2.0", optional = true }
 pom = { version = "3.2", optional = true }
 rayon = { version = "1.6", optional = true }
 serde = { version = "1.0", features = ["derive"], optional = true }
@@ -49,7 +50,7 @@ async = ["tokio/rt-multi-thread", "tokio/macros"]
 chrono_time = ["chrono"]
 default = ["chrono_time", "nom_parser", "rayon"]
 embed_image = ["image"]
-nom_parser = ["nom"]
+nom_parser = ["nom", "nom_locate"]
 pom_parser = ["pom"]
 serde = ["dep:serde"]
 
diff --git a/examples/extract_text.rs b/examples/extract_text.rs
index 3cf72ba..f30f0bf 100644
--- a/examples/extract_text.rs
+++ b/examples/extract_text.rs
@@ -123,7 +123,7 @@ fn get_pdf_text(doc: &Document) -> Result<PdfText, Error> {
                 let text = doc.extract_text(&[page_num]).map_err(|e| {
                     Error::new(
                         ErrorKind::Other,
-                        format!("Failed to extract text from page {page_num} id={page_id:?}: {e:?}"),
+                        format!("Failed to extract text from page {page_num} id={page_id:?}: {e:}"),
                     )
                 })?;
                 Ok((
diff --git a/src/common_data_structures/mod.rs b/src/common_data_structures/mod.rs
index 06feaa7..31d7727 100644
--- a/src/common_data_structures/mod.rs
+++ b/src/common_data_structures/mod.rs
@@ -46,7 +46,8 @@ pub fn decode_text_string(obj: &Object) -> Result<String> {
 #[cfg(test)]
 mod test {
     use crate::{
-        common_data_structures::decode_text_string, encodings, text_string, writer::Writer, Object, StringFormat,
+        common_data_structures::decode_text_string, encodings, parser::ParserInput, text_string, writer::Writer,
+        Object, StringFormat,
     };
 
     #[test]
@@ -68,7 +69,7 @@ mod test {
     #[test]
     fn spec_example1_decode() {
         let input = b"<</Key(text\\213)>>";
-        let dict = crate::parser::direct_object(input).unwrap();
+        let dict = crate::parser::direct_object(ParserInput::new_extra(input, "")).unwrap();
         let dict = dict.as_dict().unwrap();
         let actual = decode_text_string(dict.get(b"Key").unwrap()).unwrap();
         let expected = "text‰";
@@ -92,7 +93,7 @@ mod test {
     #[test]
     fn spec_example2_decode() {
         let input = b"<</Key<FEFF0442043504410442>>>";
-        let dict = crate::parser::direct_object(input).unwrap();
+        let dict = crate::parser::direct_object(ParserInput::new_extra(input, "")).unwrap();
         let dict = dict.as_dict().unwrap();
         let actual = decode_text_string(dict.get(b"Key").unwrap()).unwrap();
         // Russian for "test"
diff --git a/src/encodings/cmap.rs b/src/encodings/cmap.rs
index 177e6eb..8518422 100644
--- a/src/encodings/cmap.rs
+++ b/src/encodings/cmap.rs
@@ -1,5 +1,6 @@
 use crate::cmap_parser::parse;
 use crate::cmap_section::{CMapParseError, CMapSection, CodeLen, SourceCode};
+use crate::parser::ParserInput;
 
 use log::error;
 use rangemap::RangeInclusiveMap;
@@ -46,7 +47,7 @@ impl ToUnicodeCMap {
     }
 
     pub(crate) fn parse(stream_content: Vec<u8>) -> Result<ToUnicodeCMap, UnicodeCMapError> {
-        let cmap_sections = parse(&stream_content[..])?;
+        let cmap_sections = parse(ParserInput::new_extra(&stream_content[..], "cmap"))?;
         Self::from_sections(cmap_sections)
     }
 
diff --git a/src/nom_cmap_parser.rs b/src/nom_cmap_parser.rs
index 17f6aed..b583453 100644
--- a/src/nom_cmap_parser.rs
+++ b/src/nom_cmap_parser.rs
@@ -1,19 +1,17 @@
 use crate::cmap_section::{ArrayOfTargetStrings, CMapParseError, CMapSection, CodeLen, SourceCode, SourceRangeMapping};
-use crate::parser::{comment, dictionary, eol, hex_char, name, NomError, NomResult};
+use crate::parser::{comment, dict_dup, dictionary, eol, hex_char, name, NomError, NomResult, ParserInput};
 use nom::branch::alt;
 pub use nom::bytes::complete::tag;
 use nom::combinator::map;
 use nom::error::ParseError;
 use nom::multi::{fold_many0, fold_many1, fold_many_m_n, many1, many_m_n, separated_list1};
-use nom::sequence::{pair, preceded, separated_pair};
+use nom::sequence::{pair, preceded, separated_pair, terminated};
 use nom::Parser;
 use nom::{
     character::complete::digit1,
     sequence::{delimited, tuple},
 };
 
-type ParserInput<'a> = &'a [u8];
-
 impl<E> From<nom::Err<E>> for CMapParseError {
     fn from(err: nom::Err<E>) -> Self {
         match err {
@@ -141,9 +139,9 @@ fn cid_system_info(input: ParserInput) -> NomResult<()> {
         (
             tag(b"/CIDSystemInfo"),
             multispace0,
-            dictionary,
+            alt((dictionary, dict_dup)),
             multispace1,
-            tag("def"),
+            tag(b"def"),
             multispace1,
         ),
         input,
@@ -220,7 +218,7 @@ fn bf_char_section(input: ParserInput) -> NomResult<CMapSection> {
 fn target_string(input: ParserInput) -> NomResult<Vec<u16>> {
     // according to specification dstString can be up to 512 bytes
     // in ToUnicode cmap these should be 2-byte big endian Unicode values
-    delimited(tag(b"<"), many_m_n(1, 256, hex_u16), tag(b">"))(input)
+    delimited(tag(b"<"), many_m_n(1, 256, terminated(hex_u16, multispace0)), tag(b">"))(input)
 }
 
 fn bf_range_section(input: ParserInput) -> NomResult<CMapSection> {
@@ -249,158 +247,191 @@ fn range_target_array(input: ParserInput) -> NomResult<ArrayOfTargetStrings> {
 
 #[cfg(test)]
 mod tests {
-
     use super::*;
 
+    fn test_span(s: &[u8]) -> ParserInput {
+        ParserInput::new_extra(s, "")
+    }
     #[test]
     fn parse_1byte_source_code() {
         let data = b"<0A>";
-        assert_eq!(source_code(data), NomResult::Ok((b"", (0x0a, 1))))
+        let (rem, res) = source_code(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(res, (0x0a, 1));
     }
 
     #[test]
     fn parse_source_code() {
         let data = b"<080F>";
-        assert_eq!(source_code(data), NomResult::Ok((b"", (0x080f, 2))))
+        let (rem, res) = source_code(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(res, (0x080f, 2));
     }
 
     #[test]
     fn parse_invalid_source_code() {
-        let data = "<080g01>";
-        assert!(source_code(data.as_bytes()).is_err())
+        let data = b"<080g01>";
+        assert!(source_code(test_span(data)).is_err())
     }
 
     #[test]
     fn parse_too_long_source_code() {
-        let data = "<080g01030a>";
-        assert!(source_code(data.as_bytes()).is_err())
+        let data = b"<080g01030a>";
+        assert!(source_code(test_span(data)).is_err())
     }
 
     #[test]
     fn parse_code_range_pair() {
-        let data = "<080F> <08FF> ";
-        assert_eq!(
-            code_range_pair(data.as_bytes()),
-            NomResult::Ok((b" ", (0x080f, 0x08ff, 2)))
-        )
+        let data = b"<080F> <08FF> ";
+        let (rem, res) = code_range_pair(test_span(data)).unwrap();
+        assert_eq!(*rem, b" ");
+        assert_eq!(res, (0x080f, 0x08ff, 2));
     }
 
     #[test]
     fn parse_code_range_pair_without_spaces() {
-        let data = "<080F><08FF>";
-        assert_eq!(
-            code_range_pair(data.as_bytes()),
-            NomResult::Ok((b"", (0x080f, 0x08ff, 2)))
-        )
+        let data = b"<080F><08FF>";
+
+        let (rem, res) = code_range_pair(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(res, (0x080f, 0x08ff, 2));
     }
 
     #[test]
     fn parse_code_range_pair_with_not_matching_len() {
-        let data = "<080F> <08>";
-        assert!(code_range_pair(data.as_bytes()).is_err())
+        let data = b"<080F> <08>";
+        assert!(code_range_pair(test_span(data)).is_err())
     }
 
     #[test]
     fn parse_bfrange_line() {
-        let data = "<080f> <08ff> <09000110>\n";
-        assert_eq!(
-            bf_range_line(data.as_bytes()),
-            NomResult::Ok((b"", ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110]])))
-        )
+        let data = b"<080f> <08ff> <09000110>\n";
+        let (rem, res) = bf_range_line(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(res, ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110]]));
     }
     #[test]
     fn parse_bfrange_line_without_spaces() {
-        let data = "<080f><08ff><09000110>\n";
-        assert_eq!(
-            bf_range_line(data.as_bytes()),
-            NomResult::Ok((b"", ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110]])))
-        )
+        let data = b"<080f><08ff><09000110>\n";
+        let (rem, res) = bf_range_line(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(res, ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110]]));
     }
 
     #[test]
     fn parse_bfrange_line_array() {
-        let data = "<080f> <08ff> [ <09000110> <08fe> ] \n";
-        assert_eq!(
-            bf_range_line(data.as_bytes()),
-            NomResult::Ok((b"", ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110], vec![0x08fe]])))
-        )
+        let data = b"<080f> <08ff> [ <09000110> <08fe> ] \n";
+        let (rem, res) = bf_range_line(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(res, ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110], vec![0x08fe]]));
     }
     #[test]
     fn parse_invalid_bfrange_line() {
-        let data = "<080f> <08ff> [ <09000110> <08FF> <09fe80> ]\n";
-        assert!(bf_range_line(data.as_bytes()).is_err())
+        let data = b"<080f> <08ff> [ <09000110> <08FF> <09fe80> ]\n";
+        assert!(bf_range_line(test_span(data)).is_err())
     }
 
     #[test]
     fn parse_codespace_range_section() {
-        let data = "1 begincodespacerange\n\
+        let data = b"1 begincodespacerange\n\
             <0000> <FFFF> \n\
         endcodespacerange\n";
-        assert_eq!(
-            codespace_range_section(data.as_bytes()),
-            NomResult::Ok((b"", CMapSection::CsRange(vec![(0x0000, 0xffff, 2)])))
-        )
+        let (rem, res) = codespace_range_section(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(res, CMapSection::CsRange(vec![(0x0000, 0xffff, 2)]));
     }
 
     #[test]
     fn parse_bf_range_section() {
-        let data = "3 beginbfrange \n\
+        let data = b"3 beginbfrange \n\
             <0000> <000f> <0000>\n\
             <0010> <001f> <00000010> \n\
             <0020>  <002f> [<0000> <00000010> ]\n\
         endbfrange\n";
+
+        let (rem, res) = bf_range_section(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
+        assert_eq!(
+            res,
+            CMapSection::BfRange(vec![
+                ((0x0000, 0x000f, 2), vec![vec![0x0000]]),
+                ((0x0010, 0x001f, 2), vec![vec![0x0000, 0x0010]]),
+                ((0x0020, 0x002f, 2), vec![vec![0x0000], vec![0x0000, 0x0010]]),
+            ])
+        );
+    }
+
+    #[test]
+    fn parse_bf_char_section() {
+        let data = b"4 beginbfchar \n\
+            <1d> <0066 0069>\n\
+            <1e> <00A0>\n\
+            <1f> <0066 0066>
+            <20> <0020>\n\
+        endbfchar\n";
+        let (rem, res) = bf_char_section(test_span(data)).unwrap();
+        assert_eq!(*rem, b"");
         assert_eq!(
-            bf_range_section(data.as_bytes()),
-            NomResult::Ok((
-                b"",
-                CMapSection::BfRange(vec![
-                    ((0x0000, 0x000f, 2), vec![vec![0x0000]]),
-                    ((0x0010, 0x001f, 2), vec![vec![0x0000, 0x0010]]),
-                    ((0x0020, 0x002f, 2), vec![vec![0x0000], vec![0x0000, 0x0010]]),
-                ])
-            ))
-        )
+            res,
+            CMapSection::BfChar(vec![
+                ((0x1d, 1), vec![0x0066, 0x0069]),
+                ((0x1e, 1), vec![0x00a0]),
+                ((0x1f, 1), vec![0x0066, 0x0066]),
+                ((0x20, 1), vec![0x0020]),
+            ])
+        );
     }
 
     #[test]
     fn parse_cid_system_info() {
-        let data = "/CIDSystemInfo <<
+        let data = b"/CIDSystemInfo <<
 /Registry (Adobe)
 /Ordering (UCS)
 /Supplement 0
 >> def
 ";
-        assert!(cid_system_info(data.as_bytes()).is_ok())
+        assert!(cid_system_info(test_span(data)).is_ok())
+    }
+
+    #[test]
+    fn parse_cid_system_info_dict_dup() {
+        let data = b"/CIDSystemInfo 3 dict dup begin
+  /Registry (callas) def
+  /Ordering (MyriadPro-Regular14-UCMap) def
+  /Supplement 0 def
+end def
+";
+        assert!(cid_system_info(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cid_system_info_with_spaces() {
-        let data = "/CIDSystemInfo
+        let data = b"/CIDSystemInfo
 << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
-        assert!(cid_system_info(data.as_bytes()).is_ok())
+        assert!(cid_system_info(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cmap_name() {
-        let data = "/CMapName /Adobe-Identity-UCS def\n";
-        assert!(cmap_name(data.as_bytes()).is_ok())
+        let data = b"/CMapName /Adobe-Identity-UCS def\n";
+        assert!(cmap_name(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cmap_name2() {
-        let data = "/CMapName /Adobe-UCS-0 def\n";
-        assert!(cmap_name(data.as_bytes()).is_ok())
+        let data = b"/CMapName /Adobe-UCS-0 def\n";
+        assert!(cmap_name(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cmap_type() {
-        let data = "/CMapType 2 def\n";
-        assert!(cmap_type(data.as_bytes()).is_ok())
+        let data = b"/CMapType 2 def\n";
+        assert!(cmap_type(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cmap_section_1() {
-        let data = "/CIDInit /ProcSet findresource begin
+        let data = b"/CIDInit /ProcSet findresource begin
 12 dict begin
 begincmap
 /CIDSystemInfo <<
@@ -679,12 +710,12 @@ endcmap
 CMapName currentdict /CMap defineresource pop
 end
 end";
-        assert!(cmap_stream(data.as_bytes()).is_ok())
+        assert!(cmap_stream(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cmap_section_2() {
-        let data = "/CIDInit /ProcSet findresource begin
+        let data = b"/CIDInit /ProcSet findresource begin
 12 dict begin
 begincmap
 /CMapType 2 def
@@ -776,12 +807,12 @@ endcmap
 CMapName currentdict /CMap defineresource pop
 end end
 ";
-        assert!(cmap_stream(data.as_bytes()).is_ok())
+        assert!(cmap_stream(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cmap_section_bfchar() {
-        let data = "/CIDInit /ProcSet findresource begin
+        let data = b"/CIDInit /ProcSet findresource begin
 12 dict begin
 begincmap
 /CIDSystemInfo
@@ -833,12 +864,12 @@ endcmap
 CMapName currentdict /CMap defineresource pop
 end
 end";
-        assert!(cmap_stream(data.as_bytes()).is_ok())
+        assert!(cmap_stream(test_span(data)).is_ok())
     }
 
     #[test]
     fn parse_cmap_section_with_discontigous_range() {
-        let data = "/CIDInit /ProcSet findresource begin
+        let data = b"/CIDInit /ProcSet findresource begin
 12 dict begin
 begincmap
 /CIDSystemInfo
@@ -862,7 +893,7 @@ endcmap
 CMapName currentdict /CMap defineresource pop
 end
 end";
-        assert!(cmap_stream(data.as_bytes()).is_ok())
+        assert!(cmap_stream(test_span(data)).is_ok())
     }
 
     #[test]
@@ -915,7 +946,7 @@ endcmap
 CMapName currentdict /CMap defineresource pop
 end
 end\n";
-        assert!(cmap_stream(data).is_ok())
+        assert!(cmap_stream(test_span(data)).is_ok())
     }
 
     #[test]
@@ -940,6 +971,64 @@ endcmap
 CMapName currentdict /CMap defineresource pop
 end
 end\n";
-        assert!(cmap_stream(data).is_ok())
+        assert!(cmap_stream(test_span(data)).is_ok())
+    }
+    #[test]
+    fn parse_cmap_section_error() {
+        let data = b"%!PS-Adobe-3.0 Resource-CMap
+%%DocumentNeededResources: ProcSet (CIDInit)
+%%IncludeResource: ProcSet (CIDInit)
+%%BeginResource: CMap (MyriadPro-Regular14-UCMap)
+%%Title: (MyriadPro-Regular14-UCMap callas MyriadPro-Regular14-UCMap 0)
+%%EndComments
+
+/CIDInit /ProcSet findresource begin
+
+12 dict begin
+
+begincmap
+
+/CIDSystemInfo 3 dict dup begin
+  /Registry (callas) def
+  /Ordering (MyriadPro-Regular14-UCMap) def
+  /Supplement 0 def
+end def
+
+/CMapName /MyriadPro-Regular14-UCMap def
+/CMapType 2 def
+
+1 begincodespacerange
+<1e> <a9>
+endcodespacerange
+7 beginbfchar
+<1e> <00A0>
+<1f> <0066 0066>
+<20> <0020>
+<56> <0056>
+<5f> <005F>
+<96> <2013>
+<a9> <00A9>
+endbfchar
+8 beginbfrange
+<28> <29> <0028>
+<2c> <3a> <002C>
+<41> <4b> <0041>
+<4d> <54> <004D>
+<61> <69> <0061>
+<6c> <70> <006C>
+<72> <77> <0072>
+<79> <7a> <0079>
+endbfrange
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+
+%%EndResource
+%%EOF
+";
+        let res = cmap_stream(test_span(data));
+        println!("{:#?}", res);
+        assert!(res.is_ok())
     }
 }
diff --git a/src/nom_parser.rs b/src/nom_parser.rs
index ee1cbe4..1085563 100644
--- a/src/nom_parser.rs
+++ b/src/nom_parser.rs
@@ -8,18 +8,25 @@ use std::str::{self, FromStr};
 
 use nom::branch::alt;
 use nom::bytes::complete::{tag, take, take_while, take_while1, take_while_m_n};
+use nom::character::complete::multispace1;
+use nom::character::complete::space1;
 use nom::character::complete::{digit0, digit1, one_of};
 use nom::character::{is_hex_digit, is_oct_digit};
 use nom::combinator::{map, map_opt, map_res, opt, verify};
 use nom::error::{ErrorKind, ParseError};
 use nom::multi::{fold_many0, fold_many1, many0, many0_count};
 use nom::sequence::{delimited, pair, preceded, separated_pair, terminated, tuple};
+use nom::AsBytes;
 use nom::IResult;
+use nom::Slice;
+use nom_locate::LocatedSpan;
 
+pub(crate) type ParserInput<'a> = LocatedSpan<&'a [u8], &'a str>;
 // Change this to something else that implements ParseError to get a
 // different error type out of nom.
-pub(crate) type NomError<'a> = ();
-pub(crate) type NomResult<'a, O, E = NomError<'a>> = IResult<&'a [u8], O, E>;
+pub(crate) type NomError<'a> = nom::error::Error<ParserInput<'a>>;
+
+pub(crate) type NomResult<'a, O, E = NomError<'a>> = IResult<ParserInput<'a>, O, E>;
 
 #[inline]
 fn strip_nom<O>(r: NomResult<O>) -> Option<O> {
@@ -27,11 +34,11 @@ fn strip_nom<O>(r: NomResult<O>) -> Option<O> {
 }
 
 #[inline]
-fn convert_result<O, E>(result: Result<O, E>, input: &[u8], error_kind: ErrorKind) -> NomResult<O> {
+fn convert_result<O, E>(result: Result<O, E>, input: ParserInput, error_kind: ErrorKind) -> NomResult<O> {
     result.map(|o| (input, o)).map_err(|_| {
         // this is a unit bind if NomError = ()
         #[allow(clippy::let_unit_value)]
-        let err: NomError = NomError::from_error_kind(input, error_kind);
+        let err: NomError = nom::error::Error::from_error_kind(input, error_kind);
         nom::Err::Error(err)
     })
 }
@@ -43,11 +50,11 @@ fn offset_stream(object: &mut Object, offset: usize) {
     }
 }
 
-pub(crate) fn eol(input: &[u8]) -> NomResult<&[u8]> {
+pub(crate) fn eol(input: ParserInput) -> NomResult<ParserInput> {
     alt((tag(b"\r\n"), tag(b"\n"), tag(b"\r")))(input)
 }
 
-pub(crate) fn comment(input: &[u8]) -> NomResult<()> {
+pub(crate) fn comment(input: ParserInput) -> NomResult<()> {
     map(
         tuple((tag(b"%"), take_while(|c: u8| !b"\r\n".contains(&c)), eol)),
         |_| (),
@@ -74,11 +81,11 @@ fn is_direct_literal_string(c: u8) -> bool {
     !b"()\\\r\n".contains(&c)
 }
 
-fn white_space(input: &[u8]) -> NomResult<()> {
+fn white_space(input: ParserInput) -> NomResult<()> {
     map(take_while(is_whitespace), |_| ())(input)
 }
 
-fn space(input: &[u8]) -> NomResult<()> {
+fn space(input: ParserInput) -> NomResult<()> {
     fold_many0(
         alt((map(take_while1(is_whitespace), |_| ()), comment)),
         || {},
@@ -86,14 +93,14 @@ fn space(input: &[u8]) -> NomResult<()> {
     )(input)
 }
 
-fn integer(input: &[u8]) -> NomResult<i64> {
+fn integer(input: ParserInput) -> NomResult<i64> {
     let (i, _) = pair(opt(one_of("+-")), digit1)(input)?;
 
     let int_input = &input[..input.len() - i.len()];
     convert_result(i64::from_str(str::from_utf8(int_input).unwrap()), i, ErrorKind::Digit)
 }
 
-fn real(input: &[u8]) -> NomResult<f32> {
+fn real(input: ParserInput) -> NomResult<f32> {
     let (i, _) = pair(
         opt(one_of("+-")),
         alt((
@@ -106,27 +113,29 @@ fn real(input: &[u8]) -> NomResult<f32> {
     convert_result(f32::from_str(str::from_utf8(float_input).unwrap()), i, ErrorKind::Digit)
 }
 
-pub(crate) fn hex_char(input: &[u8]) -> NomResult<u8> {
+pub(crate) fn hex_char(input: ParserInput) -> NomResult<u8> {
     map_res(
-        verify(take(2usize), |h: &[u8]| h.iter().cloned().all(is_hex_digit)),
-        |x| u8::from_str_radix(str::from_utf8(x).unwrap(), 16),
+        verify(take(2usize), |h: &ParserInput| {
+            h.as_bytes().iter().copied().all(is_hex_digit)
+        }),
+        |x: ParserInput| u8::from_str_radix(str::from_utf8(&x).unwrap(), 16),
     )(input)
 }
 
-fn oct_char(input: &[u8]) -> NomResult<u8> {
+fn oct_char(input: ParserInput) -> NomResult<u8> {
     map_res(
         take_while_m_n(1, 3, is_oct_digit),
         // Spec requires us to ignore any overflow.
-        |x| u16::from_str_radix(str::from_utf8(x).unwrap(), 8).map(|o| o as u8),
+        |x: ParserInput| u16::from_str_radix(str::from_utf8(&x).unwrap(), 8).map(|o| o as u8),
     )(input)
 }
 
-pub(crate) fn name(input: &[u8]) -> NomResult<Vec<u8>> {
+pub(crate) fn name(input: ParserInput) -> NomResult<Vec<u8>> {
     preceded(
         tag(b"/"),
         many0(alt((
             preceded(tag(b"#"), hex_char),
-            map_opt(take(1usize), |c: &[u8]| {
+            map_opt(take(1usize), |c: ParserInput| {
                 if c[0] != b'#' && is_regular(c[0]) {
                     Some(c[0])
                 } else {
@@ -137,7 +146,7 @@ pub(crate) fn name(input: &[u8]) -> NomResult<Vec<u8>> {
     )(input)
 }
 
-fn escape_sequence(input: &[u8]) -> NomResult<Option<u8>> {
+fn escape_sequence(input: ParserInput) -> NomResult<Option<u8>> {
     preceded(
         tag(b"\\"),
         alt((
@@ -148,15 +157,15 @@ fn escape_sequence(input: &[u8]) -> NomResult<Option<u8>> {
             map(tag(b"t"), |_| Some(b'\t')),
             map(tag(b"b"), |_| Some(b'\x08')),
             map(tag(b"f"), |_| Some(b'\x0C')),
-            map(take(1usize), |c: &[u8]| Some(c[0])),
+            map(take(1usize), |c: ParserInput| Some(c[0])),
         )),
     )(input)
 }
 
 enum InnerLiteralString<'a> {
-    Direct(&'a [u8]),
+    Direct(ParserInput<'a>),
     Escape(Option<u8>),
-    Eol(&'a [u8]),
+    Eol(ParserInput<'a>),
     Nested(Vec<u8>),
 }
 
@@ -170,7 +179,7 @@ impl<'a> InnerLiteralString<'a> {
     }
 }
 
-fn inner_literal_string(depth: usize) -> impl Fn(&[u8]) -> NomResult<Vec<u8>> {
+fn inner_literal_string(depth: usize) -> impl Fn(ParserInput) -> NomResult<Vec<u8>> {
     move |input| {
         fold_many0(
             alt((
@@ -188,10 +197,10 @@ fn inner_literal_string(depth: usize) -> impl Fn(&[u8]) -> NomResult<Vec<u8>> {
     }
 }
 
-fn nested_literal_string(depth: usize) -> impl Fn(&[u8]) -> NomResult<Vec<u8>> {
+fn nested_literal_string(depth: usize) -> impl Fn(ParserInput) -> NomResult<Vec<u8>> {
     move |input| {
         if depth == 0 {
-            map(verify(tag(b"too deep" as &[u8]), |_: &[u8]| false), |_| vec![])(input)
+            map(verify(tag(b"too deep"), |_| false), |_| vec![])(input)
         } else {
             map(
                 delimited(tag(b"("), inner_literal_string(depth - 1), tag(b")")),
@@ -205,18 +214,18 @@ fn nested_literal_string(depth: usize) -> impl Fn(&[u8]) -> NomResult<Vec<u8>> {
     }
 }
 
-fn literal_string(input: &[u8]) -> NomResult<Vec<u8>> {
+fn literal_string(input: ParserInput) -> NomResult<Vec<u8>> {
     delimited(tag(b"("), inner_literal_string(crate::reader::MAX_BRACKET), tag(b")"))(input)
 }
 
 #[inline]
-fn hex_digit(input: &[u8]) -> NomResult<u8> {
-    map_opt(take(1usize), |c: &[u8]| {
-        str::from_utf8(c).ok().and_then(|c| u8::from_str_radix(c, 16).ok())
+fn hex_digit(input: ParserInput) -> NomResult<u8> {
+    map_opt(take(1usize), |c: ParserInput| {
+        str::from_utf8(&c).ok().and_then(|c| u8::from_str_radix(c, 16).ok())
     })(input)
 }
 
-fn hexadecimal_string(input: &[u8]) -> NomResult<Object> {
+fn hexadecimal_string(input: ParserInput) -> NomResult<Object> {
     map(
         delimited(
             tag(b"<"),
@@ -243,22 +252,22 @@ fn hexadecimal_string(input: &[u8]) -> NomResult<Object> {
     )(input)
 }
 
-fn boolean(input: &[u8]) -> NomResult<Object> {
+fn boolean(input: ParserInput) -> NomResult<Object> {
     alt((
         map(tag(b"true"), |_| Object::Boolean(true)),
         map(tag(b"false"), |_| Object::Boolean(false)),
     ))(input)
 }
 
-fn null(input: &[u8]) -> NomResult<Object> {
+fn null(input: ParserInput) -> NomResult<Object> {
     map(tag(b"null"), |_| Object::Null)(input)
 }
 
-fn array(input: &[u8]) -> NomResult<Vec<Object>> {
+fn array(input: ParserInput) -> NomResult<Vec<Object>> {
     delimited(pair(tag(b"["), space), many0(_direct_object), tag(b"]"))(input)
 }
 
-pub(crate) fn dictionary(input: &[u8]) -> NomResult<Dictionary> {
+pub(crate) fn dictionary(input: ParserInput) -> NomResult<Dictionary> {
     delimited(
         pair(tag(b"<<"), space),
         fold_many0(
@@ -273,7 +282,34 @@ pub(crate) fn dictionary(input: &[u8]) -> NomResult<Dictionary> {
     )(input)
 }
 
-fn stream<'a>(input: &'a [u8], reader: &Reader, already_seen: &mut HashSet<ObjectId>) -> NomResult<'a, Object> {
+pub(crate) fn dict_dup(input: ParserInput) -> NomResult<Dictionary> {
+    delimited(
+        tuple((
+            digit1,
+            space1,
+            tag(b"dict"),
+            space1,
+            tag(b"dup"),
+            space1,
+            tag(b"begin"),
+            multispace1,
+        )),
+        fold_many0(
+            terminated(
+                pair(terminated(name, space), _direct_object),
+                pair(tag(b"def"), multispace1),
+            ),
+            Dictionary::new,
+            |mut dict, (key, value)| {
+                dict.set(key, value);
+                dict
+            },
+        ),
+        tag(b"end"),
+    )(input)
+}
+
+fn stream<'a>(input: ParserInput<'a>, reader: &Reader, already_seen: &mut HashSet<ObjectId>) -> NomResult<'a, Object> {
     let (i, dict) = terminated(dictionary, tuple((space, tag(b"stream"), eol)))(input)?;
 
     if let Ok(length) = dict.get(b"Length").and_then(|value| {
@@ -296,19 +332,21 @@ fn stream<'a>(input: &'a [u8], reader: &Reader, already_seen: &mut HashSet<Objec
     }
 }
 
-fn unsigned_int<I: FromStr>(input: &[u8]) -> NomResult<I> {
-    map_res(digit1, |digits| I::from_str(str::from_utf8(digits).unwrap()))(input)
+fn unsigned_int<I: FromStr>(input: ParserInput) -> NomResult<I> {
+    map_res(digit1, |digits: ParserInput| {
+        I::from_str(str::from_utf8(&digits).unwrap())
+    })(input)
 }
 
-fn object_id(input: &[u8]) -> NomResult<ObjectId> {
+fn object_id(input: ParserInput) -> NomResult<ObjectId> {
     pair(terminated(unsigned_int, space), terminated(unsigned_int, space))(input)
 }
 
-fn reference(input: &[u8]) -> NomResult<Object> {
+fn reference(input: ParserInput) -> NomResult<Object> {
     map(terminated(object_id, tag(b"R")), Object::Reference)(input)
 }
 
-fn _direct_objects(input: &[u8]) -> NomResult<Object> {
+fn _direct_objects(input: ParserInput) -> NomResult<Object> {
     alt((
         null,
         boolean,
@@ -323,15 +361,15 @@ fn _direct_objects(input: &[u8]) -> NomResult<Object> {
     ))(input)
 }
 
-fn _direct_object(input: &[u8]) -> NomResult<Object> {
+fn _direct_object(input: ParserInput) -> NomResult<Object> {
     terminated(_direct_objects, space)(input)
 }
 
-pub fn direct_object(input: &[u8]) -> Option<Object> {
+pub fn direct_object(input: ParserInput) -> Option<Object> {
     strip_nom(_direct_object(input))
 }
 
-fn object<'a>(input: &'a [u8], reader: &Reader, already_seen: &mut HashSet<ObjectId>) -> NomResult<'a, Object> {
+fn object<'a>(input: ParserInput<'a>, reader: &Reader, already_seen: &mut HashSet<ObjectId>) -> NomResult<'a, Object> {
     terminated(
         alt((|input| stream(input, reader, already_seen), _direct_objects)),
         space,
@@ -339,17 +377,19 @@ fn object<'a>(input: &'a [u8], reader: &Reader, already_seen: &mut HashSet<Objec
 }
 
 pub fn indirect_object(
-    input: &[u8], offset: usize, expected_id: Option<ObjectId>, reader: &Reader, already_seen: &mut HashSet<ObjectId>,
+    input: ParserInput, offset: usize, expected_id: Option<ObjectId>, reader: &Reader,
+    already_seen: &mut HashSet<ObjectId>,
 ) -> crate::Result<(ObjectId, Object)> {
-    let (id, mut object) = _indirect_object(&input[offset..], offset, expected_id, reader, already_seen)?;
+    let (id, mut object) = _indirect_object(input.slice(offset..), offset, expected_id, reader, already_seen)?;
 
     offset_stream(&mut object, offset);
 
     Ok((id, object))
 }
 
-fn _indirect_object(
-    input: &[u8], offset: usize, expected_id: Option<ObjectId>, reader: &Reader, already_seen: &mut HashSet<ObjectId>,
+fn _indirect_object<'a>(
+    input: ParserInput<'a>, offset: usize, expected_id: Option<ObjectId>, reader: &Reader,
+    already_seen: &mut HashSet<ObjectId>,
 ) -> crate::Result<(ObjectId, Object)> {
     let (i, (_, object_id)) =
         terminated(tuple((space, object_id)), pair(tag(b"obj"), space))(input).map_err(|_| Error::Parse { offset })?;
@@ -361,7 +401,7 @@ fn _indirect_object(
 
     let object_offset = input.len() - i.len();
     let (_, mut object) = terminated(
-        |i| object(i, reader, already_seen),
+        |i: ParserInput<'a>| object(i, reader, already_seen),
         tuple((space, opt(tag(b"endobj")), space)),
     )(i)
     .map_err(|_| Error::Parse { offset })?;
@@ -371,19 +411,19 @@ fn _indirect_object(
     Ok((object_id, object))
 }
 
-pub fn header(input: &[u8]) -> Option<String> {
+pub fn header(input: ParserInput) -> Option<String> {
     strip_nom(map_res(
         delimited(
             tag(b"%PDF-"),
             take_while(|c: u8| !b"\r\n".contains(&c)),
             pair(eol, many0_count(comment)),
         ),
-        |v| str::from_utf8(v).map(Into::into),
+        |v: ParserInput| str::from_utf8(&v).map(Into::into),
     )(input))
 }
 
 /// Decode CrossReferenceTable
-fn xref(input: &[u8]) -> NomResult<Xref> {
+fn xref(input: ParserInput) -> NomResult<Xref> {
     let xref_eol = map(alt((tag(b" \r"), tag(b" \n"), tag(b"\r\n"))), |_| ());
     let xref_entry = pair(
         separated_pair(unsigned_int, tag(b" "), unsigned_int::<u32>),
@@ -415,11 +455,11 @@ fn xref(input: &[u8]) -> NomResult<Xref> {
     )(input)
 }
 
-fn trailer(input: &[u8]) -> NomResult<Dictionary> {
+fn trailer(input: ParserInput) -> NomResult<Dictionary> {
     delimited(pair(tag(b"trailer"), space), dictionary, space)(input)
 }
 
-pub fn xref_and_trailer(input: &[u8], reader: &Reader) -> crate::Result<(Xref, Dictionary)> {
+pub fn xref_and_trailer(input: ParserInput, reader: &Reader) -> crate::Result<(Xref, Dictionary)> {
     let xref_trailer = map(pair(xref, trailer), |(mut xref, trailer)| {
         xref.size = trailer
             .get(b"Size")
@@ -449,7 +489,7 @@ pub fn xref_and_trailer(input: &[u8], reader: &Reader) -> crate::Result<(Xref, D
     .unwrap_or(Err(Error::Trailer))
 }
 
-pub fn xref_start(input: &[u8]) -> Option<i64> {
+pub fn xref_start(input: ParserInput) -> Option<i64> {
     strip_nom(delimited(
         pair(tag(b"startxref"), eol),
         integer,
@@ -459,18 +499,18 @@ pub fn xref_start(input: &[u8]) -> Option<i64> {
 
 // The following code create parser to parse content stream.
 
-fn content_space(input: &[u8]) -> NomResult<()> {
+fn content_space(input: ParserInput) -> NomResult<()> {
     map(take_while(|c| b" \t\r\n".contains(&c)), |_| ())(input)
 }
 
-fn operator(input: &[u8]) -> NomResult<String> {
+fn operator(input: ParserInput) -> NomResult<String> {
     map_res(
         take_while1(|c: u8| c.is_ascii_alphabetic() || b"*'\"".contains(&c)),
-        |op| str::from_utf8(op).map(Into::into),
+        |op: ParserInput| str::from_utf8(&op).map(Into::into),
     )(input)
 }
 
-fn operand(input: &[u8]) -> NomResult<Object> {
+fn operand(input: ParserInput) -> NomResult<Object> {
     terminated(
         alt((
             null,
@@ -487,7 +527,7 @@ fn operand(input: &[u8]) -> NomResult<Object> {
     )(input)
 }
 
-fn operation(input: &[u8]) -> NomResult<Operation> {
+fn operation(input: ParserInput) -> NomResult<Operation> {
     map(
         preceded(
             many0(comment),
@@ -497,14 +537,14 @@ fn operation(input: &[u8]) -> NomResult<Operation> {
     )(input)
 }
 
-fn _content(input: &[u8]) -> NomResult<Content<Vec<Operation>>> {
+fn _content(input: ParserInput) -> NomResult<Content<Vec<Operation>>> {
     preceded(
         content_space,
         map(many0(operation), |operations| Content { operations }),
     )(input)
 }
 
-pub fn content(input: &[u8]) -> Option<Content<Vec<Operation>>> {
+pub fn content(input: ParserInput) -> Option<Content<Vec<Operation>>> {
     strip_nom(_content(input))
 }
 
@@ -512,6 +552,10 @@ pub fn content(input: &[u8]) -> Option<Content<Vec<Operation>>> {
 mod tests {
     use super::*;
 
+    fn test_span(s: &[u8]) -> ParserInput {
+        LocatedSpan::new_extra(s, "test")
+    }
+
     fn tstrip<O>(r: NomResult<O>) -> Option<O> {
         r.ok().and_then(|(i, o)| if !i.is_empty() { None } else { Some(o) })
     }
@@ -520,9 +564,9 @@ mod tests {
     fn parse_real_number() {
         let real = |i| tstrip(real(i));
 
-        assert_eq!(real(b"0.12"), Some(0.12));
-        assert_eq!(real(b"-.12"), Some(-0.12));
-        assert_eq!(real(b"10."), Some(10.0));
+        assert_eq!(real(test_span(b"0.12")), Some(0.12));
+        assert_eq!(real(test_span(b"-.12")), Some(-0.12));
+        assert_eq!(real(test_span(b"10.")), Some(10.0));
     }
 
     #[test]
@@ -539,7 +583,7 @@ mod tests {
 
         for (input, expected) in data {
             assert_eq!(
-                literal_string(input.as_bytes()),
+                literal_string(test_span(input.as_bytes())),
                 Some(expected.as_bytes().to_vec()),
                 "input: {:?} output: {:?}",
                 input,
@@ -551,11 +595,11 @@ mod tests {
     #[test]
     fn parse_name() {
         let (text, expected) = (b"/ABC#5f", b"ABC\x5F");
-        let result = tstrip(name(text));
+        let result = tstrip(name(test_span(text)));
         assert_eq!(result, Some(expected.to_vec()));
 
         let (text, expected) = (b"/#cb#ce#cc#e5", b"\xcb\xce\xcc\xe5");
-        let result = tstrip(name(text));
+        let result = tstrip(name(test_span(text)));
         assert_eq!(result, Some(expected.to_vec()));
     }
 
@@ -574,7 +618,7 @@ BT
 [(b) 20 (ut generally tak) 10 (e more space than \\311)] TJ
 T* (encoded streams.) Tj
 		";
-        let content = tstrip(_content(stream));
+        let content = tstrip(_content(test_span(stream)));
         println!("{:?}", content);
         assert!(content.is_some());
     }
@@ -582,7 +626,7 @@ T* (encoded streams.) Tj
     #[test]
     fn hex_partial() {
         // Example from PDF specification.
-        let out = tstrip(hexadecimal_string(b"<901FA>"));
+        let out = tstrip(hexadecimal_string(test_span(b"<901FA>")));
 
         match out {
             Some(Object::String(s, _)) => assert_eq!(s, b"\x90\x1F\xA0".to_vec()),
@@ -592,7 +636,7 @@ T* (encoded streams.) Tj
 
     #[test]
     fn hex_separated() {
-        let out = tstrip(hexadecimal_string(b"<9 01F A>"));
+        let out = tstrip(hexadecimal_string(test_span(b"<9 01F A>")));
 
         match out {
             Some(Object::String(s, _)) => assert_eq!(s, b"\x90\x1F\xA0".to_vec()),
@@ -633,7 +677,7 @@ startxref
 153804
 %%EOF
 ";
-        match xref(input) {
+        match xref(test_span(input)) {
             Ok((_, re)) => assert_eq!(re.entries.len(), 15),
             Err(err) => panic!("unexpected {:?}", err),
         }
@@ -648,7 +692,7 @@ startxref
 (Hello, world!) show
 % Another comment
 ";
-        let out = content(input).unwrap();
+        let out = content(test_span(input)).unwrap();
         assert_eq!(out.operations.len(), 3);
     }
 }
diff --git a/src/object_stream.rs b/src/object_stream.rs
index 2cf17af..6b9e6bc 100644
--- a/src/object_stream.rs
+++ b/src/object_stream.rs
@@ -1,6 +1,6 @@
 #![cfg(any(feature = "pom_parser", feature = "nom_parser"))]
 
-use crate::parser;
+use crate::parser::{self, ParserInput};
 use crate::{Error, Object, ObjectId, Result, Stream};
 use std::collections::BTreeMap;
 use std::str::FromStr;
@@ -52,7 +52,7 @@ impl ObjectStream {
                 warn!("out-of-bounds offset in object stream");
                 return None;
             }
-            let object = parser::direct_object(&stream.content[offset..])?;
+            let object = parser::direct_object(ParserInput::new_extra(&stream.content[offset..], "direct object"))?;
 
             Some(((id, 0), object))
         };
diff --git a/src/parser_aux.rs b/src/parser_aux.rs
index cbdfee5..7ec1c56 100644
--- a/src/parser_aux.rs
+++ b/src/parser_aux.rs
@@ -8,6 +8,7 @@ use crate::{
     encodings::Encoding,
     error::XrefError,
     object::Object::Name,
+    parser::ParserInput,
     xref::{Xref, XrefEntry, XrefType},
     Error, Result,
 };
@@ -20,7 +21,7 @@ use std::{
 impl Content<Vec<Operation>> {
     /// Decode content operations.
     pub fn decode(data: &[u8]) -> Result<Self> {
-        parser::content(data).ok_or(Error::ContentDecode)
+        parser::content(ParserInput::new_extra(data, "content operations")).ok_or(Error::ContentDecode)
     }
 }
 
diff --git a/src/reader.rs b/src/reader.rs
index dc0dda7..8df05b9 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -22,7 +22,7 @@ use tokio::pin;
 
 use crate::error::XrefError;
 use crate::object_stream::ObjectStream;
-use crate::parser;
+use crate::parser::{self, ParserInput};
 use crate::xref::XrefEntry;
 use crate::{Document, Error, IncrementalDocument, Object, ObjectId, Result};
 
@@ -219,7 +219,7 @@ impl<'a> Reader<'a> {
     pub fn read(mut self, filter_func: Option<FilterFunc>) -> Result<Document> {
         // The document structure can be expressed in PEG as:
         //   document <- header indirect_object* xref trailer xref_start
-        let version = parser::header(self.buffer).ok_or(Error::Header)?;
+        let version = parser::header(ParserInput::new_extra(self.buffer, "header")).ok_or(Error::Header)?;
 
         let xref_start = Self::get_xref_start(self.buffer)?;
         if xref_start > self.buffer.len() {
@@ -227,7 +227,8 @@ impl<'a> Reader<'a> {
         }
         self.document.xref_start = xref_start;
 
-        let (mut xref, mut trailer) = parser::xref_and_trailer(&self.buffer[xref_start..], &self)?;
+        let (mut xref, mut trailer) =
+            parser::xref_and_trailer(ParserInput::new_extra(&self.buffer[xref_start..], "xref"), &self)?;
 
         // Read previous Xrefs of linearized or incremental updated document.
         let mut already_seen = HashSet::new();
@@ -241,7 +242,8 @@ impl<'a> Reader<'a> {
                 return Err(Error::Xref(XrefError::PrevStart));
             }
 
-            let (prev_xref, prev_trailer) = parser::xref_and_trailer(&self.buffer[prev as usize..], &self)?;
+            let (prev_xref, prev_trailer) =
+                parser::xref_and_trailer(ParserInput::new_extra(&self.buffer[prev as usize..], ""), &self)?;
             xref.merge(prev_xref);
 
             // Read xref stream in hybrid-reference file
@@ -251,7 +253,8 @@ impl<'a> Reader<'a> {
                     return Err(Error::Xref(XrefError::StreamStart));
                 }
 
-                let (prev_xref, _) = parser::xref_and_trailer(&self.buffer[prev as usize..], &self)?;
+                let (prev_xref, _) =
+                    parser::xref_and_trailer(ParserInput::new_extra(&self.buffer[prev as usize..], ""), &self)?;
                 xref.merge(prev_xref);
             }
 
@@ -420,7 +423,13 @@ impl<'a> Reader<'a> {
             return Err(Error::Offset(offset));
         }
 
-        parser::indirect_object(self.buffer, offset, expected_id, self, already_seen)
+        parser::indirect_object(
+            ParserInput::new_extra(self.buffer, "indirect object"),
+            offset,
+            expected_id,
+            self,
+            already_seen,
+        )
     }
 
     fn get_xref_start(buffer: &[u8]) -> Result<usize> {
@@ -431,7 +440,7 @@ impl<'a> Reader<'a> {
             .ok_or(Error::Xref(XrefError::Start))
             .and_then(|xref_pos| {
                 if xref_pos <= buffer.len() {
-                    match parser::xref_start(&buffer[xref_pos..]) {
+                    match parser::xref_start(ParserInput::new_extra(&buffer[xref_pos..], "xref")) {
                         Some(startxref) => Ok(startxref as usize),
                         None => Err(Error::Xref(XrefError::Start)),
                     }


Test patch: