Handle bytes-typed fontnames (fixes #461 + #842)

Came across this bit of code, which helps to solve some of the mystery
in issues #461 and #842:

https://git.ghostscript.com/?p=mupdf.git;a=blob;f=source/pdf/pdf-font.c;h=6322cedf2c26cfb312c0c0878d7aff97b4c7470e;hb=HEAD#l774

Now, for every char's fontname, we:

- Check whether its a `str` or `byte`
    - If the latter, we check whether it's one of the well-known codes from
      the link above
        - If so, we use that (preserving the part, if present, before
          the `+`)
        - If not, we just cast to str
This commit is contained in:
Jeremy Singer-Vine
2023-04-07 11:09:08 -04:00
parent 1d5d646c04
commit 9441ff7628
4 changed files with 50 additions and 0 deletions
+25
View File
@@ -71,6 +71,27 @@ if TYPE_CHECKING: # pragma: nocover
from .display import PageImage
from .pdf import PDF
# via https://git.ghostscript.com/?p=mupdf.git;a=blob;f=source/pdf/pdf-font.c;h=6322cedf2c26cfb312c0c0878d7aff97b4c7470e;hb=HEAD#l774 # noqa
CP936_FONTNAMES = {
b"\xcb\xce\xcc\xe5": "SimSun,Regular",
b"\xba\xda\xcc\xe5": "SimHei,Regular",
b"\xbf\xac\xcc\xe5_GB2312": "SimKai,Regular",
b"\xb7\xc2\xcb\xce_GB2312": "SimFang,Regular",
b"\xc1\xa5\xca\xe9": "SimLi,Regular",
}
def fix_fontname_bytes(fontname: bytes) -> str:
if b"+" in fontname:
split_at = fontname.index(b"+") + 1
prefix, suffix = fontname[:split_at], fontname[split_at:]
else:
prefix, suffix = b"", fontname
suffix_new = CP936_FONTNAMES.get(suffix, str(suffix)[2:-1])
return str(prefix)[2:-1] + suffix_new
class Page(Container):
cached_properties: List[str] = Container.cached_properties + ["_layout"]
@@ -221,6 +242,10 @@ class Page(Container):
attr["stroking_color"] = gs.scolor
attr["non_stroking_color"] = gs.ncolor
# Handle (rare) byte-encoded fontnames
if isinstance(attr["fontname"], bytes):
attr["fontname"] = fix_fontname_bytes(attr["fontname"])
if "pts" in attr:
attr["pts"] = list(map(self.point2coord, attr["pts"]))
Binary file not shown.
Binary file not shown.
+25
View File
@@ -190,6 +190,31 @@ class Test(unittest.TestCase):
chars = (char for char in pdf.chars)
pdfplumber.utils.extract_text(chars)
def test_issue_461_and_842(self):
"""
pdfplumber should gracefully handle characters with byte-encoded
font names.
"""
before = b"RGJSAP+\xcb\xce\xcc\xe5"
after = pdfplumber.page.fix_fontname_bytes(before)
assert after == "RGJSAP+SimSun,Regular"
before = b"\xcb\xce\xcc\xe5"
after = pdfplumber.page.fix_fontname_bytes(before)
assert after == "SimSun,Regular"
path = os.path.join(HERE, "pdfs/issue-461-example.pdf")
with pdfplumber.open(path) as pdf:
page = pdf.pages[0]
assert all(isinstance(c["fontname"], str) for c in page.chars)
page.dedupe_chars()
path = os.path.join(HERE, "pdfs/issue-842-example.pdf")
with pdfplumber.open(path) as pdf:
page = pdf.pages[0]
assert all(isinstance(c["fontname"], str) for c in page.chars)
page.dedupe_chars()
def test_issue_463(self):
"""
Extracting annotations should not raise UnicodeDecodeError on utf-16 text