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

Issue:
ToUnicode CMap error with 0.34.0
Not exactly sure what has changed since 0.32.0 (last version I used and worked) but some of my pdfs are now giving me error.
pdf in question - https://arxiv.org/pdf/2312.16171 - there are others but this one is publicly available.

```
thread 'tokio-runtime-worker' panicked at src/reporting/document.rs:39:40:
called `Result::unwrap()` on an `Err` value: Generic("ToUnicode CMap error: Could not parse ToUnicodeCMap: Error!"
```
If you need any other information, I can provide more.
Unable to extract text from PDF generated by Word.
I saved a Word document as a PDF, and when I try to extract the text I get the following errors:
```
[2023-01-24T13:57:48Z ERROR lopdf::reader] Object load error: Parse { offset: 0 }
[2023-01-24T13:57:48Z ERROR lopdf::reader] Object load error: Parse { offset: 0 }
[2023-01-24T13:57:48Z ERROR lopdf::reader] Object load error: Parse { offset: 0 }
[2023-01-24T13:57:48Z ERROR lopdf::reader] Object load error: Parse { offset: 0 }
[2023-01-24T13:57:48Z ERROR lopdf::reader] Object load error: Parse { offset: 0 }
[2023-01-24T13:57:48Z ERROR lopdf::reader] Object load error: Parse { offset: 0 }
```

And the output content looks like this:
```
"R\n\"\n\"\n.0$((\" A*\" &1\" $++&’&51\" ’5\" $((\" 5’0*,\" ,*-*+&*.\" $2$&($A(*\" $’\" ($>\" 5,\" &1\" *K/&’=F\" ./A]*:’\" ’5\" $1=\" *Q%,*..\" \n*Q:(/.&51.\"5,\"(&-&’$’&51. \n\"\n&1\"’0&.\"B3,**-*1’\"’5\"’0*\":51’,$,=C \n\"\n\"\n\"\n!L\n\"\n!\n!’1$3’&%&><(&)’ \n+\n\"\n9*,2&:*\" ;,52&+*,\" .0$((\" +*4*1+F\" &1+*-1&4=\" $1+\" 05(+\" 0$,-(*..\" \n’0*\" #5-%$1=\" \n$1+\"\n&’. \n\"\n./A.&+&$,&*.F\" \n$44&(&$’*.F\" $1+\" ,*.%*:’&2*\" 544&:*,.F\" +&,*:’5,.F\" *-%(5=**.F\" $3*1’.F\" . \n/::*..5,.\" $1+\" %*,-&’’*+\" $..&31. \n\"\nG*$:0F\" $ \n\"\n7\n#5-%$1= \n\"\n@1+*-1&’**8H\" 4,5-\" $1+\" $3$&1.’\" $((\" (5..*.F\" +$-$3*.F\" (&$A&(&’&*.F\" +*4&:&*1:&*.F\" \n$:’&51.F\"]/+3-*1’.F\"&1’*,*.’F\"$>$,+.F\"%*1$(’&*.F\"4&1*.F\":5.’.\"5,\"*Q%*1.*.\"54\ (...)
```

I tried using `pdfutil` with the `extract_text` subcommand` and I get the same errors. Any recommendations on the steps I can do to debug the code to understand why parsing fails?


Discussion / hints:
#314 is probably what causes this. Before this PR the UnicodeCmap was simply ignored.
cc @dkaluza 
Indeed it looks like majority of text in this pdf was properly extracted in previous version.

Current problem is probably caused by the fact that some fonts in this document have 1 byte ranges in the ToUnicode cmap specified and current implementation is limited to 2 bytes ranges.

I will try to look into this next week.

At the first sight I see that there are also 'differences' encoding in this pdf that as far as I know are not treated correctly in neither version. 
This issue seems to be widespread, with users having problems dealing with PDF files using TrueType fonts while others who are working with Type1 fonts seem to have no issues. It's likely related to the specific behavior of the software or library you're using when dealing with these font formats. Please provide more context if possible so that a more accurate solution can be provided.
Can you share the document?
Ádding myself here. It looks like Word generates *different* PDFs.

```
; curl https://www.africau.edu/images/default/sample.pdf

%PDF-1.3
%����

1 0 obj
<<
...
```

Now, one generated with Word ([original source URL](https://dkp.de/wp-content/uploads/parteitage/Sozialismusvorstellungen-der-DKP.pdf)):

```
; head ./Sozialismusvorstellungen-der-DKP.pdf
%����1.2
treamr /LZWDecode
 ��P�[������7�8����d6�+шҸ6�ׅ�1���m���T�#���̆�(��;:Pgf3Ft�l������=M�Y��i:`kA�s��,Ƞú����HO+�
                   WRgy�����-��<lAZ��
�̰�p�2�pb�.��Z#��2���
streamr /LZWDecode    ��v5�Ø�7�Ø�9B`¥%n@���
...
```

I *imagine* that there needs to be additional decoding?
Similarly, docs generated with LibreOffice seems to also not work.
For example, running [this PDF from Richard Stallman website](https://stallman.org/ebooks.pdf) through extract_text will output this:
```json
{"text":{"1":["","","","","","","","","","","","","!\"#$%&","\"#","’(\"#","\"#",")","*\"#","\"#+,","","!-./","\"#","0112\"#345267","\"#","","","*","8","","9",":","","","#","&",";","$(","&<<<<))","%","","8$=:3%","","","","’.>&","&<<<<","!015550575?.(!!\"@1",""]},"errors":[]}
```
Both of those pdfs now work with https://github.com/jrmuizel/pdf-extract
I have also noticed that if you create the PDF from Word using the Print option, Microsoft Print to PDF versus exporting or saving the file as a PDF, you get two different types of PDF, the latter works fine.

Although both of these types of PDFs work fine with Python based PDF libraries.
Here’s another that won’t work, taken from <https://old.cbic.gov.in/htdocs-cbec/customs/cs-act/notifications/notfns-2023/cs-nt2023/csnt44-2023.pdf>: [csnt44-2023.pdf](https://github.com/J-F-Liu/lopdf/files/12210136/csnt44-2023.pdf). I can open it in Acrobat with no issues.

Anyone looking into fixing this?
@katzeprior the problem is probably the same as in #125, and it looks like that may be solved in the near future.

Fix patch:
diff --git a/src/cmap_parser.rs b/src/cmap_parser.rs
index 986862f..07338df 100644
--- a/src/cmap_parser.rs
+++ b/src/cmap_parser.rs
@@ -1,720 +1,11 @@
-use pom::parser::*;
+use log::warn;
 
-use crate::cmap_section::{ArrayOfTargetStrings, CMapParseError, CMapSection, SourceCharMapping, SourceRangeMapping};
-use crate::parser::{dictionary, eol, hex_char, name, space};
+use crate::cmap_section::{CMapParseError, CMapSection};
 
-fn hex_u16<'a>() -> Parser<'a, u8, u16> {
-    (hex_char() + hex_char()).map(|(u1, u2)| u1 as u16 * 256 + u2 as u16)
-}
-
-fn space_no_crlf<'a>() -> Parser<'a, u8, ()> {
-    one_of(b" \t").repeat(0..).discard()
-}
-
-fn ws_newline<'a>() -> Parser<'a, u8, ()> {
-    space_no_crlf() * eol().discard()
-}
-
-fn whitespace<'a>() -> Parser<'a, u8, ()> {
-    one_of(b" \t\n\r\0\x0C").repeat(1..).discard()
-}
-
-fn source_code<'a>() -> Parser<'a, u8, u16> {
-    sym(b'<') * hex_u16() - sym(b'>')
-}
-
-fn code_range_pair<'a>() -> Parser<'a, u8, (u16, u16)> {
-    space_no_crlf() * source_code() + space_no_crlf() * source_code()
-}
-
-fn codespace_range_section<'a>() -> Parser<'a, u8, Vec<(u16, u16)>> {
-    one_of(b"0123456789").repeat(1..)
-        * space_no_crlf()
-        * seq(b"begincodespacerange")
-        * ws_newline()
-        * (code_range_pair() - ws_newline()).repeat(1..)
-        - seq(b"endcodespacerange")
-        - ws_newline()
-}
-
-fn target_string<'a>() -> Parser<'a, u8, Vec<u16>> {
-    // no more than 512 bytes in a target string
-    sym(b'<') * hex_u16().repeat(1..256) - sym(b'>')
-}
-
-fn range_target_array<'a>() -> Parser<'a, u8, ArrayOfTargetStrings> {
-    sym(b'[') * space_no_crlf() * (target_string() - space_no_crlf()).repeat(1..) - sym(b']')
-}
-
-fn bf_range_line<'a>() -> Parser<'a, u8, SourceRangeMapping> {
-    code_range_pair() + space_no_crlf() * (target_string().map(|it| vec![it]) | range_target_array()) - ws_newline()
-}
-
-fn bf_range_section<'a>() -> Parser<'a, u8, Vec<SourceRangeMapping>> {
-    one_of(b"0123456789").repeat(1..)
-        * space_no_crlf()
-        * seq(b"beginbfrange")
-        * ws_newline()
-        * bf_range_line().repeat(1..)
-        - seq(b"endbfrange")
-        - ws_newline()
-}
-
-fn bf_char_line<'a>() -> Parser<'a, u8, SourceCharMapping> {
-    space_no_crlf() * source_code() + space_no_crlf() * target_string() - space_no_crlf() - eol().discard()
-}
-
-fn bf_char_section<'a>() -> Parser<'a, u8, Vec<SourceCharMapping>> {
-    one_of(b"0123456789").repeat(1..)
-        * space_no_crlf()
-        * seq(b"beginbfchar")
-        * ws_newline()
-        * bf_char_line().repeat(1..)
-        - seq(b"endbfchar")
-        - ws_newline()
-}
-
-fn cmap_type<'a>() -> Parser<'a, u8, ()> {
-    space_no_crlf() * seq(b"/CMapType") * space_no_crlf() * sym(b'2') * space_no_crlf() * seq(b"def") * ws_newline()
-}
-
-fn cmap_name<'a>() -> Parser<'a, u8, ()> {
-    space_no_crlf() * seq(b"/CMapName") * space_no_crlf() * name() * space_no_crlf() * seq(b"def") * ws_newline()
-}
-
-fn cid_system_info<'a>() -> Parser<'a, u8, ()> {
-    space_no_crlf() * seq(b"/CIDSystemInfo") * space() * dictionary() * space() * seq(b"def") * ws_newline()
-}
-
-fn cmap_stream<'a>() -> Parser<'a, u8, Vec<CMapSection>> {
-    use self::space_no_crlf as ws;
-    use self::ws_newline as nl;
-    ws() * seq(b"/CIDInit")
-        * ws()
-        * seq(b"/ProcSet")
-        * ws()
-        * seq(b"findresource")
-        * ws()
-        * seq(b"begin")
-        * nl()
-        * ws()
-        * one_of(b"0123456789").repeat(1..)
-        * ws()
-        * seq(b"dict")
-        * ws()
-        * seq(b"begin")
-        * nl()
-        * ws()
-        * seq(b"begincmap")
-        * nl()
-        * (cmap_type() | cmap_name() | cid_system_info()).repeat(1..4)
-        * (codespace_range_section().map(CMapSection::CsRange)
-            | bf_char_section().map(CMapSection::BfChar)
-            | bf_range_section().map(CMapSection::BfRange))
-        .repeat(1..)
-        - ws() * seq(b"endcmap") * nl()
-        - ws()
-            * seq(b"CMapName")
-            * ws()
-            * seq(b"currentdict")
-            * ws()
-            * seq(b"/CMap")
-            * ws()
-            * seq(b"defineresource")
-            * ws()
-            * seq(b"pop")
-            * nl()
-            * ws()
-            * seq(b"end")
-            * whitespace()
-            * seq(b"end")
-}
-
-pub(crate) fn parse(stream_content: &[u8]) -> Result<Vec<CMapSection>, CMapParseError> {
-    cmap_stream().parse(stream_content).map_err(CMapParseError::from)
-}
-
-impl From<pom::Error> for CMapParseError {
-    fn from(err: pom::Error) -> Self {
-        match err {
-            pom::Error::Incomplete => CMapParseError::Incomplete,
-            _ => CMapParseError::Error,
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn parse_source_code() {
-        let data = "<080F>";
-        assert_eq!(source_code().parse(data.as_bytes()), Ok(0x080f))
-    }
-
-    #[test]
-    fn parse_invalid_source_code() {
-        let data = "<080f01>";
-        assert!(source_code().parse(data.as_bytes()).is_err())
-    }
-
-    #[test]
-    fn parse_code_range_pair() {
-        let data = "<080F> <08FF> ";
-        assert_eq!(code_range_pair().parse(data.as_bytes()), Ok((0x080f, 0x08ff)),)
-    }
-
-    #[test]
-    fn parse_bfrange_line() {
-        let data = "<080f> <08ff> <09000110>\n";
-        assert_eq!(
-            bf_range_line().parse(data.as_bytes()),
-            Ok(((0x080f, 0x08ff), vec![vec![0x0900, 0x0110]]))
-        )
-    }
-    #[test]
-    fn parse_bfrange_line_array() {
-        let data = "<080f> <08ff> [ <09000110> <08fe> ] \n";
-        assert_eq!(
-            bf_range_line().parse(data.as_bytes()),
-            Ok(((0x080f, 0x08ff), vec![vec![0x0900, 0x0110], vec![0x08fe]]))
-        )
-    }
-    #[test]
-    fn parse_invalid_bfrange_line() {
-        let data = "<080f> <08ff> [ <09000110> <08FF> <09fe80> ]\n";
-        assert!(bf_range_line().parse(data.as_bytes()).is_err())
-    }
-
-    #[test]
-    fn parse_codespace_range_section() {
-        let data = "1 begincodespacerange\n\
-            <0000> <FFFF> \n\
-        endcodespacerange\n";
-        assert_eq!(
-            codespace_range_section().parse(data.as_bytes()),
-            Ok(vec![(0x0000, 0xffff)])
-        )
-    }
-
-    #[test]
-    fn parse_bf_range_section() {
-        let data = "3 beginbfrange \n\
-            <0000> <000f> <0000>\n\
-            <0010> <001f> <00000010> \n\
-            <0020>  <002f> [<0000> <00000010> ]\n\
-        endbfrange\n";
-        assert_eq!(
-            bf_range_section().parse(data.as_bytes()),
-            Ok(vec![
-                ((0x0000, 0x000f), vec![vec![0x0000]]),
-                ((0x0010, 0x001f), vec![vec![0x0000, 0x0010]]),
-                ((0x0020, 0x002f), vec![vec![0x0000], vec![0x0000, 0x0010]]),
-            ])
-        )
-    }
-
-    #[test]
-    fn parse_cid_system_info() {
-        let data = "/CIDSystemInfo <<
-/Registry (Adobe)
-/Ordering (UCS)
-/Supplement 0
->> def
-";
-        assert!(cid_system_info().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cid_system_info_with_spaces() {
-        let data = "/CIDSystemInfo
-<< /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
-        assert!(cid_system_info().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cmap_name() {
-        let data = "/CMapName /Adobe-Identity-UCS def\n";
-        assert!(cmap_name().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cmap_name2() {
-        let data = "/CMapName /Adobe-UCS-0 def\n";
-        assert!(cmap_name().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cmap_type() {
-        let data = "/CMapType 2 def\n";
-        assert!(cmap_type().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cmap_section_1() {
-        let data = "/CIDInit /ProcSet findresource begin
-12 dict begin
-begincmap
-/CIDSystemInfo <<
-/Registry (Adobe)
-/Ordering (UCS)
-/Supplement 0
->> def
-/CMapName /Adobe-Identity-UCS def
-/CMapType 2 def
-1 begincodespacerange
-<0000> <FFFF>
-endcodespacerange
-96 beginbfrange
-<0000> <00FF> <0000>
-<0100> <01FF> <0100>
-<0200> <02FF> <0200>
-<0300> <03FF> <0300>
-<0400> <04FF> <0400>
-<0500> <05FF> <0500>
-<0600> <06FF> <0600>
-<0700> <07FF> <0700>
-<0800> <08FF> <0800>
-<0900> <09FF> <0900>
-<0A00> <0AFF> <0A00>
-<0B00> <0BFF> <0B00>
-<0C00> <0CFF> <0C00>
-<0D00> <0DFF> <0D00>
-<0E00> <0EFF> <0E00>
-<0F00> <0FFF> <0F00>
-<1000> <10FF> <1000>
-<1100> <11FF> <1100>
-<1200> <12FF> <1200>
-<1300> <13FF> <1300>
-<1400> <14FF> <1400>
-<1500> <15FF> <1500>
-<1600> <16FF> <1600>
-<1700> <17FF> <1700>
-<1800> <18FF> <1800>
-<1900> <19FF> <1900>
-<1A00> <1AFF> <1A00>
-<1B00> <1BFF> <1B00>
-<1C00> <1CFF> <1C00>
-<1D00> <1DFF> <1D00>
-<1E00> <1EFF> <1E00>
-<1F00> <1FFF> <1F00>
-<2000> <20FF> <2000>
-<2100> <21FF> <2100>
-<2200> <22FF> <2200>
-<2300> <23FF> <2300>
-<2400> <24FF> <2400>
-<2500> <25FF> <2500>
-<2600> <26FF> <2600>
-<2700> <27FF> <2700>
-<2800> <28FF> <2800>
-<2900> <29FF> <2900>
-<2A00> <2AFF> <2A00>
-<2B00> <2BFF> <2B00>
-<2C00> <2CFF> <2C00>
-<2D00> <2DFF> <2D00>
-<2E00> <2EFF> <2E00>
-<2F00> <2FFF> <2F00>
-<3000> <30FF> <3000>
-<3100> <31FF> <3100>
-<3200> <32FF> <3200>
-<3300> <33FF> <3300>
-<3400> <34FF> <3400>
-<3500> <35FF> <3500>
-<3600> <36FF> <3600>
-<3700> <37FF> <3700>
-<3800> <38FF> <3800>
-<3900> <39FF> <3900>
-<3A00> <3AFF> <3A00>
-<3B00> <3BFF> <3B00>
-<3C00> <3CFF> <3C00>
-<3D00> <3DFF> <3D00>
-<3E00> <3EFF> <3E00>
-<3F00> <3FFF> <3F00>
-<4000> <40FF> <4000>
-<4100> <41FF> <4100>
-<4200> <42FF> <4200>
-<4300> <43FF> <4300>
-<4400> <44FF> <4400>
-<4500> <45FF> <4500>
-<4600> <46FF> <4600>
-<4700> <47FF> <4700>
-<4800> <48FF> <4800>
-<4900> <49FF> <4900>
-<4A00> <4AFF> <4A00>
-<4B00> <4BFF> <4B00>
-<4C00> <4CFF> <4C00>
-<4D00> <4DFF> <4D00>
-<4E00> <4EFF> <4E00>
-<4F00> <4FFF> <4F00>
-<5000> <50FF> <5000>
-<5100> <51FF> <5100>
-<5200> <52FF> <5200>
-<5300> <53FF> <5300>
-<5400> <54FF> <5400>
-<5500> <55FF> <5500>
-<5600> <56FF> <5600>
-<5700> <57FF> <5700>
-<5800> <58FF> <5800>
-<5900> <59FF> <5900>
-<5A00> <5AFF> <5A00>
-<5B00> <5BFF> <5B00>
-<5C00> <5CFF> <5C00>
-<5D00> <5DFF> <5D00>
-<5E00> <5EFF> <5E00>
-<5F00> <5FFF> <5F00>
-endbfrange
-96 beginbfrange
-<6000> <60FF> <6000>
-<6100> <61FF> <6100>
-<6200> <62FF> <6200>
-<6300> <63FF> <6300>
-<6400> <64FF> <6400>
-<6500> <65FF> <6500>
-<6600> <66FF> <6600>
-<6700> <67FF> <6700>
-<6800> <68FF> <6800>
-<6900> <69FF> <6900>
-<6A00> <6AFF> <6A00>
-<6B00> <6BFF> <6B00>
-<6C00> <6CFF> <6C00>
-<6D00> <6DFF> <6D00>
-<6E00> <6EFF> <6E00>
-<6F00> <6FFF> <6F00>
-<7000> <70FF> <7000>
-<7100> <71FF> <7100>
-<7200> <72FF> <7200>
-<7300> <73FF> <7300>
-<7400> <74FF> <7400>
-<7500> <75FF> <7500>
-<7600> <76FF> <7600>
-<7700> <77FF> <7700>
-<7800> <78FF> <7800>
-<7900> <79FF> <7900>
-<7A00> <7AFF> <7A00>
-<7B00> <7BFF> <7B00>
-<7C00> <7CFF> <7C00>
-<7D00> <7DFF> <7D00>
-<7E00> <7EFF> <7E00>
-<7F00> <7FFF> <7F00>
-<8000> <80FF> <8000>
-<8100> <81FF> <8100>
-<8200> <82FF> <8200>
-<8300> <83FF> <8300>
-<8400> <84FF> <8400>
-<8500> <85FF> <8500>
-<8600> <86FF> <8600>
-<8700> <87FF> <8700>
-<8800> <88FF> <8800>
-<8900> <89FF> <8900>
-<8A00> <8AFF> <8A00>
-<8B00> <8BFF> <8B00>
-<8C00> <8CFF> <8C00>
-<8D00> <8DFF> <8D00>
-<8E00> <8EFF> <8E00>
-<8F00> <8FFF> <8F00>
-<9000> <90FF> <9000>
-<9100> <91FF> <9100>
-<9200> <92FF> <9200>
-<9300> <93FF> <9300>
-<9400> <94FF> <9400>
-<9500> <95FF> <9500>
-<9600> <96FF> <9600>
-<9700> <97FF> <9700>
-<9800> <98FF> <9800>
-<9900> <99FF> <9900>
-<9A00> <9AFF> <9A00>
-<9B00> <9BFF> <9B00>
-<9C00> <9CFF> <9C00>
-<9D00> <9DFF> <9D00>
-<9E00> <9EFF> <9E00>
-<9F00> <9FFF> <9F00>
-<A000> <A0FF> <A000>
-<A100> <A1FF> <A100>
-<A200> <A2FF> <A200>
-<A300> <A3FF> <A300>
-<A400> <A4FF> <A400>
-<A500> <A5FF> <A500>
-<A600> <A6FF> <A600>
-<A700> <A7FF> <A700>
-<A800> <A8FF> <A800>
-<A900> <A9FF> <A900>
-<AA00> <AAFF> <AA00>
-<AB00> <ABFF> <AB00>
-<AC00> <ACFF> <AC00>
-<AD00> <ADFF> <AD00>
-<AE00> <AEFF> <AE00>
-<AF00> <AFFF> <AF00>
-<B000> <B0FF> <B000>
-<B100> <B1FF> <B100>
-<B200> <B2FF> <B200>
-<B300> <B3FF> <B300>
-<B400> <B4FF> <B400>
-<B500> <B5FF> <B500>
-<B600> <B6FF> <B600>
-<B700> <B7FF> <B700>
-<B800> <B8FF> <B800>
-<B900> <B9FF> <B900>
-<BA00> <BAFF> <BA00>
-<BB00> <BBFF> <BB00>
-<BC00> <BCFF> <BC00>
-<BD00> <BDFF> <BD00>
-<BE00> <BEFF> <BE00>
-<BF00> <BFFF> <BF00>
-endbfrange
-64 beginbfrange
-<C000> <C0FF> <C000>
-<C100> <C1FF> <C100>
-<C200> <C2FF> <C200>
-<C300> <C3FF> <C300>
-<C400> <C4FF> <C400>
-<C500> <C5FF> <C500>
-<C600> <C6FF> <C600>
-<C700> <C7FF> <C700>
-<C800> <C8FF> <C800>
-<C900> <C9FF> <C900>
-<CA00> <CAFF> <CA00>
-<CB00> <CBFF> <CB00>
-<CC00> <CCFF> <CC00>
-<CD00> <CDFF> <CD00>
-<CE00> <CEFF> <CE00>
-<CF00> <CFFF> <CF00>
-<D000> <D0FF> <D000>
-<D100> <D1FF> <D100>
-<D200> <D2FF> <D200>
-<D300> <D3FF> <D300>
-<D400> <D4FF> <D400>
-<D500> <D5FF> <D500>
-<D600> <D6FF> <D600>
-<D700> <D7FF> <D700>
-<D800> <D8FF> <D800>
-<D900> <D9FF> <D900>
-<DA00> <DAFF> <DA00>
-<DB00> <DBFF> <DB00>
-<DC00> <DCFF> <DC00>
-<DD00> <DDFF> <DD00>
-<DE00> <DEFF> <DE00>
-<DF00> <DFFF> <DF00>
-<E000> <E0FF> <E000>
-<E100> <E1FF> <E100>
-<E200> <E2FF> <E200>
-<E300> <E3FF> <E300>
-<E400> <E4FF> <E400>
-<E500> <E5FF> <E500>
-<E600> <E6FF> <E600>
-<E700> <E7FF> <E700>
-<E800> <E8FF> <E800>
-<E900> <E9FF> <E900>
-<EA00> <EAFF> <EA00>
-<EB00> <EBFF> <EB00>
-<EC00> <ECFF> <EC00>
-<ED00> <EDFF> <ED00>
-<EE00> <EEFF> <EE00>
-<EF00> <EFFF> <EF00>
-<F000> <F0FF> <F000>
-<F100> <F1FF> <F100>
-<F200> <F2FF> <F200>
-<F300> <F3FF> <F300>
-<F400> <F4FF> <F400>
-<F500> <F5FF> <F500>
-<F600> <F6FF> <F600>
-<F700> <F7FF> <F700>
-<F800> <F8FF> <F800>
-<F900> <F9FF> <F900>
-<FA00> <FAFF> <FA00>
-<FB00> <FBFF> <FB00>\r
-<FC00> <FCFF> <FC00>
-<FD00> <FDFF> <FD00>
-<FE00> <FEFF> <FE00>
-<FF00> <FFFF> <FF00>
-endbfrange
-endcmap
-CMapName currentdict /CMap defineresource pop
-end
-end";
-        assert!(cmap_stream().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cmap_section_2() {
-        let data = "/CIDInit /ProcSet findresource begin
-12 dict begin
-begincmap
-/CMapType 2 def
-/CMapName/R27 def
-1 begincodespacerange
-<0000><ffff>
-endcodespacerange
-78 beginbfrange
-<0020><0020><0020>
-<0028><0028><0028>
-<0029><0029><0029>
-<002b><002b><002b>
-<002c><002c><002c>
-<002d><002d><002d>
-<002e><002e><002e>
-<002f><002f><002f>
-<0030><0030><0030>
-<0031><0031><0031>
-<0032><0032><0032>
-<0033><0033><0033>
-<0034><0034><0034>
-<0035><0035><0035>
-<0036><0036><0036>
-<0037><0037><0037>
-<0038><0038><0038>
-<0039><0039><0039>
-<003a><003a><003a>
-<003d><003d><003d>
-<0041><0041><0041>
-<0042><0042><0042>
-<0043><0043><0043>
-<0044><0044><0044>
-<0045><0045><0045>
-<0046><0046><0046>
-<0047><0047><0047>
-<0048><0048><0048>
-<0049><0049><0049>
-<004a><004a><004a>
-<004b><004b><004b>
-<004c><004c><004c>
-<004d><004d><004d>
-<004e><004e><004e>
-<004f><004f><004f>
-<0050><0050><0050>
-<0052><0052><0052>
-<0053><0053><0053>
-<0054><0054><0054>
-<0055><0055><0055>
-<0056><0056><0056>
-<0057><0057><0057>
-<0058><0058><0058>
-<005a><005a><005a>
-<005c><005c><005c>
-<0061><0061><0061>
-<0062><0062><0062>
-<0063><0063><0063>
-<0064><0064><0064>
-<0065><0065><0065>
-<0066><0066><0066>
-<0067><0067><0067>
-<0068><0068><0068>
-<0069><0069><0069>
-<006a><006a><006a>
-<006b><006b><006b>
-<006c><006c><006c>
-<006d><006d><006d>
-<006e><006e><006e>
-<006f><006f><006f>
-<0070><0070><0070>
-<0072><0072><0072>
-<0073><0073><0073>
-<0074><0074><0074>
-<0075><0075><0075>
-<0076><0076><0076>
-<0077><0077><0077>
-<0078><0078><0078>
-<0079><0079><0079>
-<007a><007a><007a>
-<007c><007c><007c>
-<00a3><00a3><00a3>
-<00d6><00d6><00d6>
-<00df><00df><00df>
-<00e4><00e4><00e4>
-<00f6><00f6><00f6>
-<00fc><00fc><00fc>
-<2019><2019><2019>
-endbfrange
-endcmap
-CMapName currentdict /CMap defineresource pop
-end end
-";
-        assert!(cmap_stream().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cmap_section_bfchar() {
-        let data = "/CIDInit /ProcSet findresource begin
-12 dict begin
-begincmap
-/CIDSystemInfo
-<< /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def
-/CMapName /Adobe-UCS-0 def
-/CMapType 2 def
-1 begincodespacerange
-<0000> <FFFF>
-endcodespacerange
-36 beginbfchar
-<0000> <0000>
-<0001> <004C>
-<0002> <0069>
-<0003> <0073>
-<0004> <0074>
-<0005> <0061>
-<0006> <006F>
-<0007> <0070>
-<0008> <0065>
-<0009> <0072>
-<000A> <0063>
-<000B> <006A>
-<000C> <007A>
-<000D> <006B>
-<000E> <0064>
-<000F> <0032>
-<0010> <0030>
-<0011> <0033>
-<0012> <002D>
-<0013> <0035>
-<0014> <0031>
-<0015> <0039>
-<0016> <0034>
-<0017> <0057>
-<0018> <006C>
-<0019> <0075>
-<001A> <0142>
-<001B> <0079>
-<001C> <0077>
-<001D> <004F>
-<001E> <0044>
-<001F> <0052>
-<0020> <0068>
-<0021> <006E>
-<0022> <004B>
-<0023> <0067>
-endbfchar
-endcmap
-CMapName currentdict /CMap defineresource pop
-end
-end";
-        assert!(cmap_stream().parse(data.as_bytes()).is_ok())
-    }
-
-    #[test]
-    fn parse_cmap_section_with_discontigous_range() {
-        let data = "/CIDInit /ProcSet findresource begin
-12 dict begin
-begincmap
-/CIDSystemInfo
-<< /Registry (Adobe)
-/Ordering (UCS)
-/Supplement 0
->> def
-/CMapName /Adobe-Identity-UCS def
-/CMapType 2 def
-1 begincodespacerange
-<0000> <FFFF>
-endcodespacerange
-2 beginbfrange
-<0000> <005E> <0020>
-<005F> <0061> [<D83DDE00> <D83DDD27> <D83DDD28>]
-endbfrange
-1 beginbfchar
-<3A51> <D840DC3E>
-endbfchar
-endcmap
-CMapName currentdict /CMap defineresource pop
-end
-end";
-        assert!(cmap_stream().parse(data.as_bytes()).is_ok())
-    }
+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/cmap_section.rs b/src/cmap_section.rs
index b74acbf..1bd3cf0 100644
--- a/src/cmap_section.rs
+++ b/src/cmap_section.rs
@@ -1,19 +1,22 @@
 /*
 ToUnicode CMaps are special CMaps and thus can be parsed simpler. Assumptions:
 - /CMapType is always 2
-- codespace range always from <0000> to <ffff>
 - only bfchar and bfrange sections allowed
 - no glyph names as target allowed, only hex strings
-- source character codes always 2 bytes
 - target encoded in UTF16-BE
  */
 
 pub(crate) type ArrayOfTargetStrings = Vec<Vec<u16>>;
-pub(crate) type SourceRangeMapping = ((u16, u16), ArrayOfTargetStrings);
-pub(crate) type SourceCharMapping = (u16, Vec<u16>);
+
+// According to pdf documentation source codes can be of various byte length but they
+// should be smaller than integer
+pub(crate) type SourceCode = u32;
+pub(crate) type CodeLen = u8;
+pub(crate) type SourceRangeMapping = ((SourceCode, SourceCode, CodeLen), ArrayOfTargetStrings);
+pub(crate) type SourceCharMapping = ((SourceCode, CodeLen), Vec<u16>);
 #[derive(Debug, PartialEq)]
 pub enum CMapSection {
-    CsRange(Vec<(u16, u16)>),
+    CsRange(Vec<(SourceCode, SourceCode, CodeLen)>),
     BfChar(Vec<SourceCharMapping>),
     BfRange(Vec<SourceRangeMapping>),
 }
diff --git a/src/encodings/cmap.rs b/src/encodings/cmap.rs
index 8900e08..177e6eb 100644
--- a/src/encodings/cmap.rs
+++ b/src/encodings/cmap.rs
@@ -1,18 +1,23 @@
 use crate::cmap_parser::parse;
-use crate::cmap_section::{CMapParseError, CMapSection};
+use crate::cmap_section::{CMapParseError, CMapSection, CodeLen, SourceCode};
 
+use log::error;
 use rangemap::RangeInclusiveMap;
 use std::fmt;
 
+/// Unicode Cmap is implemented by 4 maps.
+/// Each map contains a mappings from source codes to unicode values for a different length of codes.
+/// Codes vary from 1 byte to 4 bytes so they are always in limits of u32.
+/// However to map a code to a unicode value an additional knowledge about the number of bytes is needed,
+/// as 2 byte code <0000> shouldn't be matched with a single byte <00> even though they have the same integer value.
 #[derive(Debug, Default)]
 pub struct ToUnicodeCMap {
-    bf_ranges: RangeInclusiveMap<u16, BfRangeTarget>,
+    bf_ranges: [RangeInclusiveMap<SourceCode, BfRangeTarget>; 4],
 }
 
 #[derive(Debug)]
 pub enum UnicodeCMapError {
     Parse(CMapParseError),
-    UnsupportedCodeSpaceRange,
     InvalidCodeRange,
 }
 
@@ -21,7 +26,6 @@ impl fmt::Display for UnicodeCMapError {
         use UnicodeCMapError::*;
         match self {
             Parse(cmap_parse_error) => write!(f, "Could not parse ToUnicodeCMap: {:#?}!", cmap_parse_error),
-            UnsupportedCodeSpaceRange => write!(f, "Unsupported codespace range given!"),
             InvalidCodeRange => write!(f, "Invalid code range given!"),
         }
     }
@@ -33,9 +37,11 @@ impl From<CMapParseError> for UnicodeCMapError {
 }
 
 impl ToUnicodeCMap {
+    const REPLACEMENT_CHAR: u16 = 0xfffd;
+
     pub fn new() -> ToUnicodeCMap {
         ToUnicodeCMap {
-            bf_ranges: RangeInclusiveMap::new(),
+            bf_ranges: [(); 4].map(|_| RangeInclusiveMap::new()),
         }
     }
 
@@ -48,17 +54,14 @@ impl ToUnicodeCMap {
         let mut cmap = Self::new();
         for section in cmap_sections {
             match section {
-                CMapSection::CsRange(ranges) => match ranges.len() {
-                    1 if ranges[0] == (0x0000, 0xffff) => {}
-                    _ => return Err(UnicodeCMapError::UnsupportedCodeSpaceRange),
-                },
+                CMapSection::CsRange(_) => (), // currently no additional validation is implemented for code ranges
                 CMapSection::BfChar(char_mappings) => {
-                    for (code, dst) in char_mappings {
-                        cmap.put_char(code, dst);
+                    for ((code, code_len), dst) in char_mappings {
+                        cmap.put_char(code, code_len, dst);
                     }
                 }
                 CMapSection::BfRange(range_mappings) => {
-                    for ((start, end), dst_vec) in range_mappings {
+                    for ((start, end, code_len), dst_vec) in range_mappings {
                         if end < start {
                             return Err(UnicodeCMapError::InvalidCodeRange);
                         }
@@ -66,13 +69,14 @@ impl ToUnicodeCMap {
                             1 if dst_vec[0].len() == 1 => cmap.put(
                                 start,
                                 end,
+                                code_len,
                                 BfRangeTarget::UTF16CodePoint {
-                                    offset: u16::wrapping_sub(dst_vec[0][0], start),
+                                    offset: u32::wrapping_sub(dst_vec[0][0] as u32, start),
                                 },
                             ),
-                            1 => cmap.put(start, end, BfRangeTarget::HexString(dst_vec[0].clone())),
+                            1 => cmap.put(start, end, code_len, BfRangeTarget::HexString(dst_vec[0].clone())),
                             0 => return Err(UnicodeCMapError::InvalidCodeRange),
-                            _ => cmap.put(start, end, BfRangeTarget::ArrayOfHexStrings(dst_vec.clone())),
+                            _ => cmap.put(start, end, code_len, BfRangeTarget::ArrayOfHexStrings(dst_vec.clone())),
                         }
                     }
                 }
@@ -81,36 +85,48 @@ impl ToUnicodeCMap {
         Ok(cmap)
     }
 
-    pub fn get(&self, code: u16) -> Option<Vec<u16>> {
+    pub fn get(&self, code: SourceCode, code_len: CodeLen) -> Option<Vec<u16>> {
+        if code_len > 4 || code_len == 0 {
+            error!("Code lenght should be between l and 4 bytes, got {code_len}");
+            return None;
+        }
         use BfRangeTarget::*;
-        self.bf_ranges.get_key_value(&code).map(|(range, value)| match value {
+
+        let bf_ranges_map = &self.bf_ranges[(code_len - 1) as usize];
+
+        bf_ranges_map.get_key_value(&code).map(|(range, value)| match value {
             HexString(ref vec) => {
                 let mut ret_vec = vec.clone();
-                *(ret_vec.last_mut().unwrap()) += code - range.start();
+                *(ret_vec.last_mut().unwrap()) += (code - range.start()) as u16;
                 ret_vec
             }
-            UTF16CodePoint { offset } => vec![u16::wrapping_add(code, *offset)],
+            UTF16CodePoint { offset } => vec![u32::wrapping_add(code, *offset) as u16],
             ArrayOfHexStrings(ref vec_of_strings) => vec_of_strings[(code - range.start()) as usize].clone(),
         })
     }
 
-    pub fn get_or_replacement_char(&self, code: u16) -> Vec<u16> {
-        self.get(code).unwrap_or(vec![0xfffd])
+    pub fn get_or_replacement_char(&self, code: SourceCode, code_len: CodeLen) -> Vec<u16> {
+        self.get(code, code_len)
+            .unwrap_or(vec![ToUnicodeCMap::REPLACEMENT_CHAR])
     }
 
-    pub fn put(&mut self, src_code_lo: u16, src_code_hi: u16, target: BfRangeTarget) {
-        self.bf_ranges.insert(src_code_lo..=src_code_hi, target)
+    pub fn put(&mut self, src_code_lo: SourceCode, src_code_hi: SourceCode, code_len: CodeLen, target: BfRangeTarget) {
+        if code_len > 4 || code_len == 0 {
+            error!("Code lenght should be between l and 4 bytes, got {code_len}, ignoring");
+            return;
+        }
+        self.bf_ranges[(code_len - 1) as usize].insert(src_code_lo..=src_code_hi, target)
     }
 
-    pub fn put_char(&mut self, code: u16, dst: Vec<u16>) {
+    pub fn put_char(&mut self, code: SourceCode, code_len: CodeLen, dst: Vec<u16>) {
         let target = if dst.len() == 1 {
             BfRangeTarget::UTF16CodePoint {
-                offset: u16::wrapping_sub(dst[0], code),
+                offset: u32::wrapping_sub(dst[0] as u32, code),
             }
         } else {
             BfRangeTarget::HexString(dst)
         };
-        self.put(code, code, target)
+        self.put(code, code, code_len, target)
     }
 }
 
@@ -120,6 +136,46 @@ pub enum BfRangeTarget {
     HexString(Vec<u16>),
     // don't store the actual codepoint but rather an offset to the src_code_lo
     // so that consecutive ranges can be mapped to the same value in the range map
-    UTF16CodePoint { offset: u16 },
+    UTF16CodePoint { offset: u32 },
     ArrayOfHexStrings(Vec<Vec<u16>>),
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn put_char_can_be_retrieved() {
+        let mut cmap = ToUnicodeCMap::new();
+        let char_code = 0x01;
+        let char_value = vec![0x1234];
+        cmap.put_char(char_code, 2, char_value.clone());
+
+        assert_eq!(cmap.get(char_code, 2), Some(char_value))
+    }
+
+    #[test]
+    fn char_can_be_retrieved_only_by_appropriate_len() {
+        let mut cmap = ToUnicodeCMap::new();
+        let char_code = 0x1;
+        let code_len = 4;
+        let char_value = vec![0x1234];
+        cmap.put_char(char_code, code_len, char_value.clone());
+
+        for i in 1..=3 {
+            assert_eq!(cmap.get(char_code, i), None);
+        }
+
+        assert_eq!(cmap.get(char_code, code_len), Some(char_value));
+    }
+
+    #[test]
+    fn wrong_code_len_does_not_panic() {
+        let mut cmap = ToUnicodeCMap::new();
+        let char_code = 0x1;
+        let char_value = vec![0x1234];
+
+        cmap.put_char(char_code, 5, char_value.clone());
+        cmap.put_char(char_code, 0, char_value.clone());
+    }
+}
diff --git a/src/encodings/mod.rs b/src/encodings/mod.rs
index 4a2f79e..270abe4 100644
--- a/src/encodings/mod.rs
+++ b/src/encodings/mod.rs
@@ -50,10 +50,33 @@ impl<'a> Encoding<'a> {
                 Ok(UTF_16BE.decode(bytes).0.to_string())
             }
             Self::UnicodeMapEncoding(unicode_map) => {
-                let utf16_str: Vec<u8> = bytes
-                    .chunks_exact(2)
-                    .map(|chunk| chunk[0] as u16 * 256 + chunk[1] as u16)
-                    .flat_map(|cp| unicode_map.get_or_replacement_char(cp))
+                let mut output_bytes = Vec::new();
+
+                // source codes can have a variadic length from 1 to 4 bytes
+                let mut bytes_in_considered_code = 0u8;
+                let mut considered_source_code = 0u32;
+                for byte in bytes {
+                    if bytes_in_considered_code == 4 {
+                        let mut value = unicode_map.get_or_replacement_char(considered_source_code, 4);
+                        considered_source_code = 0;
+                        bytes_in_considered_code = 0;
+                        output_bytes.append(&mut value);
+                    }
+                    bytes_in_considered_code += 1;
+                    considered_source_code = considered_source_code * 256 + *byte as u32;
+                    if let Some(mut value) = unicode_map.get(considered_source_code, bytes_in_considered_code) {
+                        considered_source_code = 0;
+                        bytes_in_considered_code = 0;
+                        output_bytes.append(&mut value);
+                    }
+                }
+                if bytes_in_considered_code > 0 {
+                    let mut value =
+                        unicode_map.get_or_replacement_char(considered_source_code, bytes_in_considered_code);
+                    output_bytes.append(&mut value);
+                }
+                let utf16_str: Vec<u8> = output_bytes
+                    .iter()
                     .flat_map(|it| [(it / 256) as u8, (it % 256) as u8])
                     .collect();
                 Ok(UTF_16BE.decode(&utf16_str).0.to_string())
@@ -61,6 +84,7 @@ impl<'a> Encoding<'a> {
             Self::SimpleEncoding(_) => Err(Error::ContentDecode),
         }
     }
+
     pub fn string_to_bytes(&self, text: &str) -> Vec<u8> {
         match self {
             Self::OneByteEncoding(map) => string_to_bytes(map, text),
@@ -99,3 +123,23 @@ pub fn encode_utf8(text: &str) -> Vec<u8> {
     bytes.extend(text.bytes());
     bytes
 }
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn unicode_with_2byte_code_does_not_convert_single_bytes() {
+        let mut cmap = ToUnicodeCMap::new();
+
+        cmap.put(0x0000, 0x0002, 2, cmap::BfRangeTarget::UTF16CodePoint { offset: 0 });
+        cmap.put(0x0024, 0x0025, 2, cmap::BfRangeTarget::UTF16CodePoint { offset: 0 });
+
+        let bytes: [u8; 2] = [0x00, 0x24];
+
+        let result = Encoding::UnicodeMapEncoding(cmap).bytes_to_string(&bytes);
+
+        assert_eq!(result.unwrap(), "\u{0024}");
+    }
+}
diff --git a/src/nom_cmap_parser.rs b/src/nom_cmap_parser.rs
index 6261a01..d5670cb 100644
--- a/src/nom_cmap_parser.rs
+++ b/src/nom_cmap_parser.rs
@@ -1,8 +1,9 @@
-use crate::cmap_section::{ArrayOfTargetStrings, CMapParseError, CMapSection, SourceRangeMapping};
-use crate::parser::{dictionary, eol, hex_char, name, NomError, NomResult};
+use crate::cmap_section::{ArrayOfTargetStrings, CMapParseError, CMapSection, CodeLen, SourceCode, SourceRangeMapping};
+use crate::parser::{comment, dictionary, eol, hex_char, name, NomError, NomResult};
 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::Parser;
@@ -57,17 +58,36 @@ fn space1(input: ParserInput) -> NomResult<()> {
     fold_many1(alt((tag(b" "), tag("\t"))), || {}, |_, _| ())(input)
 }
 
+fn parser_discard_result<'a, O>(
+    parser: impl Fn(ParserInput<'a>) -> NomResult<'a, O>,
+) -> impl Fn(ParserInput<'a>) -> NomResult<'a, ()> {
+    move |input| {
+        let result = parser(input);
+        match result {
+            Ok((rest_of_input, _)) => Ok((rest_of_input, ())),
+            Err(e) => Err(e),
+        }
+    }
+}
+
 fn multispace0(input: ParserInput) -> NomResult<()> {
-    fold_many0(alt((tag(b" "), tag("\t"), eol)), || {}, |_, _| ())(input)
+    let space = parser_discard_result(tag(b" "));
+    let tab = parser_discard_result(tag("\t"));
+    let eol = parser_discard_result(eol);
+    fold_many0(alt((space, tab, eol, comment)), || {}, |_, _| ())(input)
 }
 
 fn multispace1(input: ParserInput) -> NomResult<()> {
-    fold_many1(alt((tag(b" "), tag("\t"), eol)), || {}, |_, _| ())(input)
+    let space = parser_discard_result(tag(b" "));
+    let tab = parser_discard_result(tag("\t"));
+    let eol = parser_discard_result(eol);
+    fold_many1(alt((space, tab, eol, comment)), || {}, |_, _| ())(input)
 }
 
 fn cidinit_procset(input: ParserInput) -> NomResult<()> {
     tuple_discard_result(
         (
+            multispace0,
             tag(b"/CIDInit"),
             space0,
             tag(b"/ProcSet"),
@@ -156,12 +176,33 @@ fn codespace_range_section(input: ParserInput) -> NomResult<CMapSection> {
     Ok((rest_of_input, CMapSection::CsRange(ranges_result)))
 }
 
-fn code_range_pair(input: ParserInput) -> NomResult<(u16, u16)> {
-    separated_pair(source_code, space0, source_code)(input)
+fn code_range_pair(input: ParserInput) -> NomResult<(SourceCode, SourceCode, CodeLen)> {
+    let (rest_of_input, ((code_begin, code_len_beg), (code_end, code_len_end))) =
+        separated_pair(source_code, space0, source_code)(input)?;
+    if code_len_beg != code_len_end {
+        create_code_len_err(rest_of_input)
+    } else {
+        Ok((rest_of_input, (code_begin, code_end, code_len_beg)))
+    }
 }
 
-fn source_code(input: ParserInput) -> NomResult<u16> {
-    delimited(tag(b"<"), hex_u16, tag(b">"))(input)
+fn create_code_len_err<'a, T, E: ParseError<ParserInput<'a>>>(input: ParserInput<'a>) -> Result<T, nom::Err<E>> {
+    Err(nom::Err::Failure(nom::error::make_error(
+        input,
+        nom::error::ErrorKind::LengthValue,
+    )))
+}
+
+fn source_code(input: ParserInput) -> NomResult<(SourceCode, CodeLen)> {
+    let (rest_of_input, bytes) = delimited(tag(b"<"), many_m_n(1, 4, hex_char), tag(b">"))(input)?;
+    let code_len = bytes.len();
+    let source_code = bytes
+        .into_iter()
+        .rev()
+        .zip(0..4)
+        .map(|(byte, i)| 256u32.pow(i) * byte as u32)
+        .sum();
+    Ok((rest_of_input, (source_code, code_len as u8)))
 }
 
 fn hex_u16(input: ParserInput) -> NomResult<u16> {
@@ -211,15 +252,27 @@ mod tests {
 
     use super::*;
 
+    #[test]
+    fn parse_1byte_source_code() {
+        let data = b"<0A>";
+        assert_eq!(source_code(data), NomResult::Ok((b"", (0x0a, 1))))
+    }
+
     #[test]
     fn parse_source_code() {
         let data = b"<080F>";
-        assert_eq!(source_code(data), NomResult::Ok((b"", 0x080f)))
+        assert_eq!(source_code(data), NomResult::Ok((b"", (0x080f, 2))))
     }
 
     #[test]
     fn parse_invalid_source_code() {
-        let data = "<080f01>";
+        let data = "<080g01>";
+        assert!(source_code(data.as_bytes()).is_err())
+    }
+
+    #[test]
+    fn parse_too_long_source_code() {
+        let data = "<080g01030a>";
         assert!(source_code(data.as_bytes()).is_err())
     }
 
@@ -228,14 +281,23 @@ mod tests {
         let data = "<080F> <08FF> ";
         assert_eq!(
             code_range_pair(data.as_bytes()),
-            NomResult::Ok((b" ", (0x080f, 0x08ff)))
+            NomResult::Ok((b" ", (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))))
+        assert_eq!(
+            code_range_pair(data.as_bytes()),
+            NomResult::Ok((b"", (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())
     }
 
     #[test]
@@ -243,7 +305,7 @@ mod tests {
         let data = "<080f> <08ff> <09000110>\n";
         assert_eq!(
             bf_range_line(data.as_bytes()),
-            NomResult::Ok((b"", ((0x080f, 0x08ff), vec![vec![0x0900, 0x0110]])))
+            NomResult::Ok((b"", ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110]])))
         )
     }
     #[test]
@@ -251,7 +313,7 @@ mod tests {
         let data = "<080f><08ff><09000110>\n";
         assert_eq!(
             bf_range_line(data.as_bytes()),
-            NomResult::Ok((b"", ((0x080f, 0x08ff), vec![vec![0x0900, 0x0110]])))
+            NomResult::Ok((b"", ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110]])))
         )
     }
 
@@ -260,7 +322,7 @@ mod tests {
         let data = "<080f> <08ff> [ <09000110> <08fe> ] \n";
         assert_eq!(
             bf_range_line(data.as_bytes()),
-            NomResult::Ok((b"", ((0x080f, 0x08ff), vec![vec![0x0900, 0x0110], vec![0x08fe]])))
+            NomResult::Ok((b"", ((0x080f, 0x08ff, 2), vec![vec![0x0900, 0x0110], vec![0x08fe]])))
         )
     }
     #[test]
@@ -276,7 +338,7 @@ mod tests {
         endcodespacerange\n";
         assert_eq!(
             codespace_range_section(data.as_bytes()),
-            NomResult::Ok((b"", CMapSection::CsRange(vec![(0x0000, 0xffff)])))
+            NomResult::Ok((b"", CMapSection::CsRange(vec![(0x0000, 0xffff, 2)])))
         )
     }
 
@@ -292,9 +354,9 @@ mod tests {
             NomResult::Ok((
                 b"",
                 CMapSection::BfRange(vec![
-                    ((0x0000, 0x000f), vec![vec![0x0000]]),
-                    ((0x0010, 0x001f), vec![vec![0x0000, 0x0010]]),
-                    ((0x0020, 0x002f), vec![vec![0x0000], vec![0x0000, 0x0010]]),
+                    ((0x0000, 0x000f, 2), vec![vec![0x0000]]),
+                    ((0x0010, 0x001f, 2), vec![vec![0x0000, 0x0010]]),
+                    ((0x0020, 0x002f, 2), vec![vec![0x0000], vec![0x0000, 0x0010]]),
                 ])
             ))
         )
@@ -803,9 +865,8 @@ end";
         assert!(cmap_stream(data.as_bytes()).is_ok())
     }
 
-    #[ignore = "2char ToUnicode cmaps are currently not implemented"]
     #[test]
-    fn parse_truetype_cmap_section_with_2char_bfchars() {
+    fn parse_truetype_cmap_section_with_1byte_bfchars() {
         let data = b"/CIDInit/ProcSet findresource begin
 12 dict begin
 begincmap
diff --git a/src/nom_parser.rs b/src/nom_parser.rs
index 6e8bf9a..ee1cbe4 100644
--- a/src/nom_parser.rs
+++ b/src/nom_parser.rs
@@ -47,7 +47,7 @@ pub(crate) fn eol(input: &[u8]) -> NomResult<&[u8]> {
     alt((tag(b"\r\n"), tag(b"\n"), tag(b"\r")))(input)
 }
 
-fn comment(input: &[u8]) -> NomResult<()> {
+pub(crate) fn comment(input: &[u8]) -> NomResult<()> {
     map(
         tuple((tag(b"%"), take_while(|c: u8| !b"\r\n".contains(&c)), eol)),
         |_| (),


Test patch:
diff --git a/tests/unicode.rs b/tests/unicode.rs
index 03ee79b..099fb49 100644
--- a/tests/unicode.rs
+++ b/tests/unicode.rs
@@ -1,3 +1,5 @@
+#![cfg(feature = "nom_parser")]
+
 use lopdf::content::{Content, Operation};
 use lopdf::{dictionary, Document, Object, Stream, StringFormat};
 
@@ -121,7 +123,7 @@ fn get_text_from_first_page(doc: &Document) -> String {
     let mut pages = doc.get_pages();
     let first_page = pages.first_entry().expect("Expected pages to be non empty");
     let extracted_text = doc
-        .extract_text(&[first_page.key().clone()])
+        .extract_text(&[*first_page.key()])
         .expect("Expected to find text on the first page");
     extracted_text
 }