From 5d957e7be94dcae1e047975b9eed6e79936cc50f Mon Sep 17 00:00:00 2001 From: chesirecatt Date: Mon, 5 Oct 2026 12:01:05 +0300 Subject: [PATCH] =?UTF-8?q?pdf2html:=20CMYK-=D0=B1=D0=BB=D0=BE=D0=BA=D0=B8?= =?UTF-8?q?=20=D1=81=20ext=3Dpng=20=D0=BD=D0=B0=20=D0=B4=D0=B5=D0=BB=D0=B5?= =?UTF-8?q?=20JPEG=20=E2=80=94=20=D0=B4=D0=B5=D0=BA=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=87=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=20Pixmap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit На «Запускаем Ansible» 3-го изд. 10 из 593 картинок — блоки с b["ext"]=="png" и colorspace=4, но реальные байты — CMYK JPEG (FFD8, подтверждено file). Запись b["image"] как есть под именем .png давала файл, который pngquant и часть читалок отказывались декодировать. _image_png() декодирует сырые байты через pymupdf.Pixmap (MuPDF определяет формат по содержимому, не по чужому ext), при colorspace не Gray/RGB конвертирует в RGB, и всегда отдаёт настоящий PNG. На отказе декодирования — откат к исходным байтам, не падать. Co-Authored-By: Claude Sonnet 5 --- scripts/pdf2html.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/scripts/pdf2html.py b/scripts/pdf2html.py index d6871dc..08c9c66 100644 --- a/scripts/pdf2html.py +++ b/scripts/pdf2html.py @@ -45,6 +45,26 @@ SOFT_HYPHEN = "\u00ad" OCR = False +def _image_png(raw): + """Байты картинки блока -> настоящий PNG. + + b["ext"] врёт: на «Запускаем Ansible» 3-го изд. блоки с ext="png" и + colorspace=4 на деле — CMYK JPEG (magic FFD8, подтверждено `file`), и + PNG-инструменты (pngquant) отказываются их декодировать с именем .png. + Вместо доверия заявленному формату декодируем через Pixmap (MuPDF сам + распознаёт реальный формат по данным) и всегда отдаём RGB PNG — тогда + имя файла (.png) не расходится с содержимым и CMYK не ловит инверсию + цвета в читалках, которые не ждут Adobe-JPEG с четырьмя каналами. + """ + try: + pix = pymupdf.Pixmap(raw) + if pix.colorspace is not None and pix.colorspace.n not in (1, 3): + pix = pymupdf.Pixmap(pymupdf.csRGB, pix) + return pix.tobytes("png") + except Exception: + return raw # не смогли декодировать — пишем как есть, лучше так, чем ничего + + def style(font, flags=0): """Полужирный и курсив: сначала флаги, затем имя — имена бывают пустыми.""" f = font.lower() @@ -330,7 +350,7 @@ for pno, page in enumerate(doc): continue # подложка отсканированной полосы: текст берём из OCR-слоя img_n += 1 name = "img%02d.png" % img_n - (IMGDIR / name).write_bytes(b["image"]) + (IMGDIR / name).write_bytes(_image_png(b["image"])) if open_para: parts.append(open_para) open_para = None