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

Issue:
Deprecate/remove pom_parser in favor of nom_parser
@J-F-Liu I would like to get your opinion on removing pom_parser, or at least deprecating it.
A concrete problem that prompted this issue is described in https://github.com/J-F-Liu/lopdf/issues/125#issuecomment-2275055483.
#248 probably has the same cause, but the PDF link expired, so I can't check that.

The main advantages we would get when removing one of the parsers:
- reduced code complexity
- increased maintainability
  - no bugs that are only reproducible with one of the parsers
  - no doubled work when implementing new parser features, like #125 

It seems that the nom_parser is the better implementation of the two parser, at least in terms of performance (according to #60 and #208).




Discussion / hints:
It is ok to removing pom_parser to avoid doubled work when implementing new parser features.
Parser code using pom_parser has better readability.
Ok, thanks for your feedback.
As long as no major additional work or bugs come up, we can probably keep it in and make the final decision only when necessary.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 3fd2226..a0754d2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,7 +30,6 @@ 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 }
 time = { version = "0.3", features = ["formatting", "parsing"] }
@@ -53,13 +52,8 @@ chrono_time = ["chrono"]
 default = ["chrono_time", "nom_parser", "rayon"]
 embed_image = ["image"]
 nom_parser = ["nom", "nom_locate"]
-pom_parser = ["pom"]
 serde = ["dep:serde"]
 
-[[example]]
-name = "add_barcode"
-required-features = ["pom_parser"]
-
 [[example]]
 name = "extract_toc"
 required-features = ["serde"]
@@ -76,9 +70,5 @@ required-features = ["default"]
 name = "rotate"
 required-features = ["nom_parser"]
 
-[[test]]
-name = "modify"
-required-features = ["pom_parser"]
-
 [badges]
 travis-ci = { repository = "J-F-Liu/lopdf" }
diff --git a/README.md b/README.md
index 87a3e06..3952fc5 100644
--- a/README.md
+++ b/README.md
@@ -402,7 +402,7 @@ use lopdf::Document;
 
 // For this example to work a parser feature needs to be enabled
 #[cfg(not(feature = "async"))]
-#[cfg(any(feature = "pom_parser", feature = "nom_parser"))]
+#[cfg(feature = "nom_parser")]
 {
     let mut doc = Document::load("assets/example.pdf").unwrap();
 
@@ -416,7 +416,7 @@ use lopdf::Document;
 }
 
 #[cfg(feature = "async")]
-#[cfg(any(feature = "pom_parser", feature = "nom_parser"))]
+#[cfg(feature = "nom_parser")]
 {
     tokio::runtime::Builder::new_current_thread()
         .build()
diff --git a/src/cmap_parser.rs b/src/cmap_parser.rs
deleted file mode 100644
index 07338df..0000000
--- a/src/cmap_parser.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use log::warn;
-
-use crate::cmap_section::{CMapParseError, CMapSection};
-
-pub(crate) fn parse(_stream_content: &[u8]) -> Result<Vec<CMapSection>, CMapParseError> {
-    warn!(
-        "Unicode cmap parsing is not supported in pom version, \
-text extraction might contain missing characters"
-    );
-    Ok(Vec::new())
-}
diff --git a/src/encodings/cmap.rs b/src/encodings/cmap.rs
index 8518422..a7787c9 100644
--- a/src/encodings/cmap.rs
+++ b/src/encodings/cmap.rs
@@ -1,5 +1,5 @@
-use crate::cmap_parser::parse;
 use crate::cmap_section::{CMapParseError, CMapSection, CodeLen, SourceCode};
+use crate::parser::cmap_parser::parse;
 use crate::parser::ParserInput;
 
 use log::error;
diff --git a/src/lib.rs b/src/lib.rs
index 50ba714..5a2bd21 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,60 +2,47 @@
 #![forbid(unsafe_code)]
 #![deny(clippy::all)]
 
+pub mod content;
+pub mod encryption;
+pub mod filters;
+pub mod xobject;
+pub mod xref;
+
 #[macro_use]
 mod object;
-mod datetime;
-pub use crate::object::{Dictionary, Object, ObjectId, Stream, StringFormat};
-
-mod common_data_structures;
 mod document;
 mod incremental_document;
-mod object_stream;
-#[cfg(any(feature = "pom_parser", feature = "nom_parser"))]
-pub use object_stream::ObjectStream;
-pub mod xref;
-pub use crate::common_data_structures::{decode_text_string, text_string};
-pub use crate::document::Document;
-pub use crate::encodings::{encode_utf16_be, encode_utf8};
-pub use crate::incremental_document::IncrementalDocument;
 
 mod bookmarks;
-pub use crate::bookmarks::Bookmark;
-mod outlines;
-pub use crate::outlines::Outline;
-mod destinations;
-pub use crate::destinations::Destination;
-mod toc;
-pub use crate::toc::Toc;
-#[cfg(not(feature = "nom_parser"))]
-#[cfg(feature = "pom_parser")]
-mod cmap_parser;
-#[cfg(feature = "nom_parser")]
-#[path = "nom_cmap_parser.rs"]
-mod cmap_parser;
 mod cmap_section;
-pub mod content;
+mod common_data_structures;
 mod creator;
+mod datetime;
+mod destinations;
 mod encodings;
-pub use encodings::Encoding;
-pub mod encryption;
 mod error;
-pub use error::XrefError;
-pub mod filters;
-#[cfg(not(feature = "nom_parser"))]
-#[cfg(feature = "pom_parser")]
-mod parser;
-#[cfg(feature = "nom_parser")]
-#[path = "nom_parser.rs"]
-mod parser;
-mod parser_aux;
+mod outlines;
 mod processor;
-#[cfg(any(feature = "pom_parser", feature = "nom_parser"))]
-mod reader;
-#[cfg(any(feature = "pom_parser", feature = "nom_parser"))]
-pub use reader::Reader;
 mod rc4;
+mod toc;
 mod writer;
-pub mod xobject;
 
-pub use error::{Error, Result};
+mod object_stream;
+mod parser;
+mod parser_aux;
+mod reader;
+
+pub use document::Document;
+pub use object::{Dictionary, Object, ObjectId, Stream, StringFormat};
+
+pub use bookmarks::Bookmark;
+pub use common_data_structures::{decode_text_string, text_string};
+pub use destinations::Destination;
+pub use encodings::{encode_utf16_be, encode_utf8, Encoding};
+pub use error::{Error, Result, XrefError};
+pub use incremental_document::IncrementalDocument;
+pub use object_stream::ObjectStream;
+pub use outlines::Outline;
+#[cfg(feature = "nom_parser")]
+pub use reader::Reader;
+pub use toc::Toc;
diff --git a/src/object.rs b/src/object.rs
index 3a685bf..cda8ee8 100644
--- a/src/object.rs
+++ b/src/object.rs
@@ -365,8 +365,10 @@ impl Dictionary {
             Ok("MacRomanEncoding") => Ok(Encoding::OneByteEncoding(&encodings::MAC_ROMAN_ENCODING)),
             Ok("MacExpertEncoding") => Ok(Encoding::OneByteEncoding(&encodings::MAC_EXPERT_ENCODING)),
             Ok("WinAnsiEncoding") => Ok(Encoding::OneByteEncoding(&encodings::WIN_ANSI_ENCODING)),
-            // it is not customary to use PDFDocEncoding in fonts but it is not forbidden by the standard
-            Ok("PDFDocEncoding") => Ok(Encoding::OneByteEncoding(&encodings::PDF_DOC_ENCODING)),
+            Ok("PDFDocEncoding") => {
+                log::warn!("PDFDocEncoding is not a valid character encoding for a font");
+                Ok(Encoding::OneByteEncoding(&encodings::PDF_DOC_ENCODING))
+            }
             Ok("Identity-H") | Ok("Identity-V") => {
                 let stream = self.get_deref(b"ToUnicode", doc)?.as_stream()?;
                 self.get_encoding_from_to_unicode_cmap(stream)
diff --git a/src/object_stream.rs b/src/object_stream.rs
index 85ff14c..35036f0 100644
--- a/src/object_stream.rs
+++ b/src/object_stream.rs
@@ -1,5 +1,4 @@
-#![cfg(any(feature = "pom_parser", feature = "nom_parser"))]
-
+#[cfg(feature = "nom_parser")]
 use crate::parser::{self, ParserInput};
 use crate::{Error, Object, ObjectId, Result, Stream};
 use std::collections::BTreeMap;
diff --git a/src/parser.rs b/src/parser.rs
deleted file mode 100644
index 742dad9..0000000
--- a/src/parser.rs
+++ /dev/null
@@ -1,407 +0,0 @@
-use super::{Dictionary, Object, ObjectId, Stream, StringFormat};
-use crate::content::*;
-use crate::error::XrefError;
-use crate::reader::Reader;
-use crate::xref::*;
-use crate::{Error, Result};
-use pom::char_class::{alpha, hex_digit, multispace, oct_digit};
-use pom::parser::*;
-use std::cmp::max;
-use std::str::{self, FromStr};
-
-pub(crate) fn eol<'a>() -> Parser<'a, u8, u8> {
-    (sym(b'\r') * sym(b'\n')) | sym(b'\n') | sym(b'\r')
-}
-
-fn comment<'a>() -> Parser<'a, u8, ()> {
-    sym(b'%') * none_of(b"\r\n").repeat(0..) * eol().discard()
-}
-
-fn white_space<'a>() -> Parser<'a, u8, ()> {
-    one_of(b" \t\n\r\0\x0C").repeat(0..).discard()
-}
-
-pub(crate) fn space<'a>() -> Parser<'a, u8, ()> {
-    (one_of(b" \t\n\r\0\x0C").repeat(1..).discard() | comment())
-        .repeat(0..)
-        .discard()
-}
-
-fn integer<'a>() -> Parser<'a, u8, i64> {
-    let number = one_of(b"+-").opt() + one_of(b"0123456789").repeat(1..);
-    number.collect().convert(str::from_utf8).convert(i64::from_str)
-}
-
-fn real<'a>() -> Parser<'a, u8, f32> {
-    let number = one_of(b"+-").opt()
-        + ((one_of(b"0123456789").repeat(1..) * sym(b'.') - one_of(b"0123456789").repeat(0..))
-            | (sym(b'.') - one_of(b"0123456789").repeat(1..)));
-    number.collect().convert(str::from_utf8).convert(f32::from_str)
-}
-
-pub(crate) fn hex_char<'a>() -> Parser<'a, u8, u8> {
-    let number = is_a(hex_digit).repeat(2);
-    number
-        .collect()
-        .convert(|v| u8::from_str_radix(str::from_utf8(v).unwrap(), 16))
-}
-
-fn oct_char<'a>() -> Parser<'a, u8, u8> {
-    let number = is_a(oct_digit).repeat(1..4);
-    number
-        .collect()
-        .convert(|v| u8::from_str_radix(str::from_utf8(v).unwrap(), 8))
-}
-
-pub(crate) fn name<'a>() -> Parser<'a, u8, Vec<u8>> {
-    sym(b'/') * (none_of(b" \t\n\r\x0C()<>[]{}/%#") | (sym(b'#') * hex_char())).repeat(0..)
-}
-
-fn escape_sequence<'a>() -> Parser<'a, u8, Vec<u8>> {
-    sym(b'\\')
-        * (sym(b'\\').map(|_| vec![b'\\'])
-            | sym(b'(').map(|_| vec![b'('])
-            | sym(b')').map(|_| vec![b')'])
-            | sym(b'n').map(|_| vec![b'\n'])
-            | sym(b'r').map(|_| vec![b'\r'])
-            | sym(b't').map(|_| vec![b'\t'])
-            | sym(b'b').map(|_| vec![b'\x08'])
-            | sym(b'f').map(|_| vec![b'\x0C'])
-            | oct_char().map(|c| vec![c])
-            | eol().map(|_| vec![])
-            | empty().map(|_| vec![]))
-}
-
-fn nested_literal_string<'a>(depth: usize) -> Parser<'a, u8, Vec<u8>> {
-    if depth == 0 {
-        return Parser::new(move |_: &'a [u8], pos: usize| {
-            Err(pom::Error::Custom {
-                message: "Brackets embedded to deep.".to_string(),
-                position: pos,
-                inner: None,
-            })
-        });
-    }
-
-    sym(b'(')
-        * (none_of(b"\\()").repeat(1..) | escape_sequence() | call(move || nested_literal_string(depth - 1)))
-            .repeat(0..)
-            .map(|segments| {
-                let mut bytes = segments.into_iter().fold(vec![b'('], |mut bytes, mut segment| {
-                    bytes.append(&mut segment);
-                    bytes
-                });
-                bytes.push(b')');
-                bytes
-            })
-        - sym(b')')
-}
-
-fn literal_string<'a>() -> Parser<'a, u8, Vec<u8>> {
-    sym(b'(')
-        * (none_of(b"\\()").repeat(1..) | escape_sequence() | nested_literal_string(crate::reader::MAX_BRACKET))
-            .repeat(0..)
-            .map(|segments| segments.concat())
-        - sym(b')')
-}
-
-fn hexadecimal_string<'a>() -> Parser<'a, u8, Vec<u8>> {
-    sym(b'<') * (white_space() * hex_char()).repeat(0..) - (white_space() * sym(b'>'))
-}
-
-fn array<'a>() -> Parser<'a, u8, Vec<Object>> {
-    sym(b'[') * space() * call(_direct_object).repeat(0..) - sym(b']')
-}
-
-pub(crate) fn dictionary<'a>() -> Parser<'a, u8, Dictionary> {
-    let entry = name() - space() + call(_direct_object);
-    let entries = seq(b"<<") * space() * entry.repeat(0..) - seq(b">>");
-    entries.map(|entries| {
-        entries
-            .into_iter()
-            .fold(Dictionary::new(), |mut dict: Dictionary, (key, value)| {
-                dict.set(key, value);
-                dict
-            })
-    })
-}
-
-fn stream<'a>(reader: &'a Reader) -> Parser<'a, u8, Stream> {
-    (dictionary() - space() - seq(b"stream") - eol())
-        >> move |dict: Dictionary| {
-            if let Ok(length) = dict.get(b"Length").and_then(|value| {
-                if let Ok(id) = value.as_reference() {
-                    return reader.get_object(id).and_then(|value| value.as_i64());
-                }
-                value.as_i64()
-            }) {
-                if length < 0 {
-                    println!("Warning: invalid stream length {}", length);
-                    return empty().map(move |_| Stream::new(dict.clone(), vec![]));
-                }
-                let stream = take(length as usize) - eol().opt() - seq(b"endstream").expect("endstream");
-                stream.map(move |data| Stream::new(dict.clone(), data.to_vec()))
-            } else {
-                empty().pos().map(move |pos| Stream::with_position(dict.clone(), pos))
-            }
-        }
-}
-
-fn object_id<'a>() -> Parser<'a, u8, ObjectId> {
-    let id: Parser<u8, u32> = one_of(b"0123456789")
-        .repeat(1..)
-        .convert(|v| u32::from_str(str::from_utf8(&v).unwrap()));
-    let gen = one_of(b"0123456789")
-        .repeat(1..)
-        .convert(|v| u16::from_str(str::from_utf8(&v).unwrap()));
-    id - space() + gen - space()
-}
-
-pub fn direct_object(input: &[u8]) -> Option<Object> {
-    _direct_object().parse(input).ok()
-}
-
-fn _direct_object<'a>() -> Parser<'a, u8, Object> {
-    (seq(b"null").map(|_| Object::Null)
-        | seq(b"true").map(|_| Object::Boolean(true))
-        | seq(b"false").map(|_| Object::Boolean(false))
-        | (object_id().map(Object::Reference) - sym(b'R'))
-        | real().map(Object::Real)
-        | integer().map(Object::Integer)
-        | name().map(Object::Name)
-        | literal_string().map(Object::string_literal)
-        | hexadecimal_string().map(|bytes| Object::String(bytes, StringFormat::Hexadecimal))
-        | array().map(Object::Array)
-        | dictionary().map(Object::Dictionary))
-        - space()
-}
-
-fn object<'a>(reader: &'a Reader) -> Parser<'a, u8, Object> {
-    (seq(b"null").map(|_| Object::Null)
-        | seq(b"true").map(|_| Object::Boolean(true))
-        | seq(b"false").map(|_| Object::Boolean(false))
-        | (object_id().map(Object::Reference) - sym(b'R'))
-        | real().map(Object::Real)
-        | integer().map(Object::Integer)
-        | name().map(Object::Name)
-        | literal_string().map(Object::string_literal)
-        | hexadecimal_string().map(|bytes| Object::String(bytes, StringFormat::Hexadecimal))
-        | array().map(Object::Array)
-        | stream(reader).map(Object::Stream)
-        | dictionary().map(Object::Dictionary))
-        - space()
-}
-
-pub fn indirect_object(
-    input: &[u8], offset: usize, expected_id: Option<ObjectId>, reader: &Reader,
-) -> Result<(ObjectId, Object)> {
-    _indirect_object(expected_id, reader)
-        .parse_at(input, offset)
-        .map(|(out, _)| out)
-        .map_err(|_| Error::Parse { offset })
-}
-
-fn _indirect_object<'a>(expected_id: Option<ObjectId>, reader: &'a Reader) -> Parser<'a, u8, (ObjectId, Object)> {
-    object_id().convert(move |id| match expected_id {
-        Some(expected_id) if expected_id == id => Ok(id),
-        Some(_) => Err(()),
-        None => Ok(id),
-    }) - seq(b"obj")
-        - space()
-        + object(reader)
-        - space()
-        - seq(b"endobj").opt()
-        - space()
-}
-
-pub fn header(input: &[u8]) -> Option<String> {
-    (seq(b"%PDF-") * none_of(b"\r\n").repeat(0..).convert(String::from_utf8) - eol() - comment().repeat(0..))
-        .parse(input)
-        .ok()
-}
-
-/// Decode CrossReferenceTable
-fn xref<'a>() -> Parser<'a, u8, Xref> {
-    let xref_entry = integer().map(|i| max(0, i) as u32) - sym(b' ') + integer().map(|i| i as u16) - sym(b' ')
-        + one_of(b"nf").map(|k| k == b'n')
-        - take(2);
-    let xref_section = integer().map(|i| max(0, i) as usize) - sym(b' ') + integer() - sym(b' ').opt() - eol()
-        + xref_entry.repeat(0..);
-    let xref = seq(b"xref") * sym(b' ').opt() * eol() * xref_section.repeat(1..) - space();
-    xref.map(|sections| {
-        sections.into_iter().fold(
-            Xref::new(0, XrefType::CrossReferenceTable),
-            |mut xref: Xref, ((start, _count), entries): _| {
-                for (index, ((offset, generation), is_normal)) in entries.into_iter().enumerate() {
-                    if is_normal {
-                        xref.insert((start + index) as u32, XrefEntry::Normal { offset, generation });
-                    }
-                }
-                xref
-            },
-        )
-    })
-}
-
-fn trailer<'a>() -> Parser<'a, u8, Dictionary> {
-    seq(b"trailer") * space() * dictionary() - space()
-}
-
-pub fn xref_and_trailer<'a>(input: &'a [u8], reader: &'a Reader) -> Result<(Xref, Dictionary)> {
-    _xref_and_trailer(reader)
-        .parse(input)
-        .map_err(|_| Error::Xref(XrefError::Parse))
-}
-
-fn _xref_and_trailer<'a>(reader: &'a Reader) -> Parser<'a, u8, (Xref, Dictionary)> {
-    (xref() + trailer()).convert(|(mut xref, trailer)| -> Result<_> {
-        xref.size = trailer
-            .get(b"Size")
-            .and_then(Object::as_i64)
-            .map_err(|_| Error::Trailer)? as u32;
-        Ok((xref, trailer))
-    }) | _indirect_object(None, reader).convert(|(_, obj)| match obj {
-        Object::Stream(stream) => decode_xref_stream(stream),
-        _ => Err(Error::Xref(XrefError::Parse)),
-    })
-}
-
-pub fn xref_start(input: &[u8]) -> Option<i64> {
-    (seq(b"startxref") * white_space() * integer() - white_space() - seq(b"%%EOF") - space())
-        .parse(input)
-        .ok()
-}
-
-// The following code create parser to parse content stream.
-
-fn content_space<'a>() -> Parser<'a, u8, ()> {
-    is_a(multispace).repeat(0..).discard()
-}
-
-fn operator<'a>() -> Parser<'a, u8, String> {
-    (is_a(alpha) | one_of(b"*'\"")).repeat(1..).convert(String::from_utf8)
-}
-
-fn operand<'a>() -> Parser<'a, u8, Object> {
-    (seq(b"null").map(|_| Object::Null)
-        | seq(b"true").map(|_| Object::Boolean(true))
-        | seq(b"false").map(|_| Object::Boolean(false))
-        | real().map(Object::Real)
-        | integer().map(Object::Integer)
-        | name().map(Object::Name)
-        | literal_string().map(Object::string_literal)
-        | hexadecimal_string().map(|bytes| Object::String(bytes, StringFormat::Hexadecimal))
-        | array().map(Object::Array)
-        | dictionary().map(Object::Dictionary))
-        - content_space()
-}
-
-fn operation<'a>() -> Parser<'a, u8, Operation> {
-    let operation = operand().repeat(0..) + operator() - content_space();
-    operation.map(|(operands, operator)| Operation { operator, operands })
-}
-
-pub fn content(input: &[u8]) -> Option<Content<Vec<Operation>>> {
-    (content_space() * operation().repeat(0..).map(|operations| Content { operations }))
-        .parse(input)
-        .ok()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn parse_real_number() {
-        let r0 = real().parse(b"0.12");
-        assert_eq!(r0, Ok(0.12));
-        let r1 = real().parse(b"-.12");
-        assert_eq!(r1, Ok(-0.12));
-        let r2 = real().parse(b"10.");
-        assert_eq!(r2, Ok(10.0));
-    }
-
-    #[test]
-    fn parse_string() {
-        let data = vec![
-            ("()", ""),
-            ("(text())", "text()"),
-            ("(text\r\n\\\\(nested\\t\\b\\f))", "text\r\n\\(nested\t\x08\x0C)"),
-            ("(text\\0\\53\\053\\0053)", "text\0++\x053"),
-            ("(text line\\\n())", "text line()"),
-        ];
-
-        for (input, expected) in data {
-            assert_eq!(
-                literal_string().parse(input.as_bytes()),
-                Ok(expected.as_bytes().to_vec()),
-                "input: {:?} output: {:?}",
-                input,
-                expected,
-            );
-        }
-    }
-
-    #[test]
-    fn parse_name() {
-        let (text, expected) = (b"/ABC#5f", b"ABC\x5F");
-        let result = name().parse(text).ok();
-        assert_eq!(result, Some(expected.to_vec()));
-
-        let (text, expected) = (b"/#cb#ce#cc#e5", b"\xcb\xce\xcc\xe5");
-        let result = name().parse(text).ok();
-        assert_eq!(result, Some(expected.to_vec()));
-    }
-
-    #[test]
-    /// Run `cargo test -- --nocapture` to see output
-    fn parse_content() {
-        let stream = b"
-2 J
-BT
-/F1 12 Tf
-0 Tc
-0 Tw
-72.5 712 TD
-[(Unencoded streams can be read easily) 65 (,) ] TJ
-0 -14 TD
-[(b) 20 (ut generally tak) 10 (e more space than \\311)] TJ
-T* (encoded streams.) Tj
-		";
-        let content = content(stream);
-        println!("{:?}", content);
-        assert!(content.is_some());
-    }
-
-    #[test]
-    fn parse_cidsysteminfo_dictionary() {
-        let stream = b"<< /Registry (Adobe) /Ordering (UCS) /Supplement 0 >>";
-        let expected = dictionary!(
-            "Registry" => Object::string_literal("Adobe"),
-            "Ordering" => Object::string_literal("UCS"),
-            "Supplement" => Object::Integer(0));
-
-        let parsed_dict = dictionary().parse(stream);
-        assert_eq!(parsed_dict, Ok(expected));
-    }
-
-    #[test]
-    fn parse_font_dictionary() {
-        // from PDF 1.7 standard example font
-        let stream = b"<< /Type /Font
-    /Subtype /Type0
-    /BaseFont /HeiseiMin-W5-90ms-RKSJ-H
-    /Encoding /90ms-RKSJ-H
-    /DescendantFonts [15 0 R]
->>";
-        let expected = dictionary!(
-            "Type" => "Font",
-            "Subtype"=> "Type0",
-            "BaseFont" => "HeiseiMin-W5-90ms-RKSJ-H",
-            "Encoding" => "90ms-RKSJ-H",
-            "DescendantFonts" => Object::Array(vec![Object::Reference((15, 0))]));
-        let parsed_dict = dictionary().parse(stream);
-        println!("{:#?}", parsed_dict.clone().unwrap().get(b"DescendantFonts"));
-        assert_eq!(parsed_dict, Ok(expected));
-    }
-}
diff --git a/src/nom_cmap_parser.rs b/src/parser/cmap_parser.rs
similarity index 100%
rename from src/nom_cmap_parser.rs
rename to src/parser/cmap_parser.rs
diff --git a/src/nom_parser.rs b/src/parser/mod.rs
similarity index 99%
rename from src/nom_parser.rs
rename to src/parser/mod.rs
index 3538c5f..b5468f8 100644
--- a/src/nom_parser.rs
+++ b/src/parser/mod.rs
@@ -1,3 +1,4 @@
+#[cfg(feature = "nom_parser")]
 use super::{Dictionary, Object, ObjectId, Reader, Stream, StringFormat};
 use crate::content::*;
 use crate::error::XrefError;
@@ -21,6 +22,8 @@ use nom::IResult;
 use nom::Slice;
 use nom_locate::LocatedSpan;
 
+pub(crate) mod cmap_parser;
+
 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.
diff --git a/src/parser_aux.rs b/src/parser_aux.rs
index 0bb56cc..6f5ca2d 100644
--- a/src/parser_aux.rs
+++ b/src/parser_aux.rs
@@ -1,5 +1,4 @@
-#![cfg(any(feature = "pom_parser", feature = "nom_parser"))]
-
+#![cfg(feature = "nom_parser")]
 use log::warn;
 
 use crate::{
diff --git a/src/reader.rs b/src/reader.rs
index 2b2a156..cf13451 100644
--- a/src/reader.rs
+++ b/src/reader.rs
@@ -1,5 +1,4 @@
-#![cfg(any(feature = "pom_parser", feature = "nom_parser"))]
-
+#[cfg(feature = "nom_parser")]
 use log::{error, warn};
 use std::cmp;
 use std::collections::{BTreeMap, HashSet};
diff --git a/src/xref.rs b/src/xref.rs
index 3d235e4..0165486 100644
--- a/src/xref.rs
+++ b/src/xref.rs
@@ -137,5 +137,5 @@ impl XrefSection {
     }
 }
 
-#[cfg(any(feature = "pom_parser", feature = "nom_parser"))]
+#[cfg(feature = "nom_parser")]
 pub use crate::parser_aux::decode_xref_stream;


Test patch:
diff --git a/tests/annotation.rs b/tests/annotation.rs
index a5019aa..b7c2038 100644
--- a/tests/annotation.rs
+++ b/tests/annotation.rs
@@ -1,5 +1,5 @@
 // Only run test when parser is enabled
-#![cfg(any(feature = "pom_parser", feature = "nom_parser"))]
+#![cfg(feature = "nom_parser")]
 
 use lopdf::Result;
 
diff --git a/tests/incremental_document.rs b/tests/incremental_document.rs
index 34c7e28..76b8ca1 100644
--- a/tests/incremental_document.rs
+++ b/tests/incremental_document.rs
@@ -1,5 +1,5 @@
 // Only run test when parser is enabled
-#![cfg(any(feature = "pom_parser", feature = "nom_parser"))]
+#![cfg(feature = "nom_parser")]
 
 use lopdf::Result;
 use tempfile::tempdir;
diff --git a/tests/modify.rs b/tests/modify.rs
index 8f01aaf..acbd488 100644
--- a/tests/modify.rs
+++ b/tests/modify.rs
@@ -25,7 +25,7 @@ fn test_get_object() {
     assert!(obj2_exists);
 }
 
-#[cfg(any(feature = "pom_parser", feature = "nom_parser"))]
+#[cfg(feature = "nom_parser")]
 #[cfg(all(test, not(feature = "async")))]
 mod tests_with_parsing {
     use super::*;