Wednesday, April 29, 2009

Использование Ghostscript для перевода PDF в JPEG

В одном из последних проектов я занимался пакетной обработкой pdf-документов: слияние (merge) документов и распознание 1D штрих-кодов (barcode recognition).

Хочу поделиться интересными, на мой взгляд, особенностями и ссылками на Open Source-проекты.

С первой задачей (merge документов) отлично справился PDFsharp - Open Source-проект со свободной лицензией и с достаточно простым и удобным API (.Net).

Со штрих-кодами оказалось немного сложнее. Дело в том, что основной формат документов для "читалок" - файлы изображений (jpeg, png, tiff и т.д.), а перевести PDF в картинку оказалось не так просто...

Мы решили эту задачу с помощью Ghostscript - Open Source (GPL License) интерпретатор языка PostScript и пакет для работы с PDF.

Вообще говоря, перевести PDF в картинку, используя Ghostscript, можно одной командой, примерно так:


c:\Temp>c:\dev\bin\gs\gs8.64\bin\gswin32c.exe -q -dSAFER -dBATCH -dNOPAUSE -sDEVICE=jpeg -r150 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -dMaxStripSize=8192 -sOutputFile=page-%d.jpg mydocument.pdf


На выходе должно быть несколько файлов (по одному на страницу): в данном случае page-1.jpg, page-2.jpg и т.д.

Проблемы начинаются, если PDF-документ содержит экзотические шрифты (например, IDAutomationHC39M.ttf - шрифт штрих-кода Code39), или если документ был создан при помощи MS Reporting Services или другой программы, которая по тем или иным причинам не включает используемые шрифты в выходной PDF-файл (PDF fonts embedding).

Наш PDF был именно такой:



В результате во время рендеринга у меня возникла такая ошибка:


Error: /undefined in findresource
Operand stack:
--nostringval-- --dict:7/16(L)-- F14 10.0 --dict:5/5(L)-- --dict:5/5(L)-- TimesNewRoman,Bold --dict:11/12(ro)(G)-- --nostringval-- CIDFontObject --dict:6/6(L)-- --dict:6/6(L)-- Adobe-Identity
Execution stack:
%interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1862 1 3 %oparray_pop 1861 1 3 %oparray_pop 1845 1 3 %oparray_pop --nostringval-- --nostringval-- 3 1 2 --nostringval-- %for_pos_int_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- %array_continue --nostringval-- false 1 %stopped_push --nostringval-- %loop_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- %array_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- %loop_continue --nostringval-- --nostringval-- --nostringval-- %loop_continue
Dictionary stack:
--dict:1156/1684(ro)(G)-- --dict:1/20(G)-- --dict:74/200(L)-- --dict:74/200(L)-- --dict:106/127(ro)(G)-- --dict:278/300(ro)(G)-- --dict:22/25(L)-- --dict:4/6(L)-- --dict:21/40(L)-- --dict:10/13(L)--
Current allocation mode is local
Last OS error: No such file or directory
GPL Ghostscript 8.64: Unrecoverable error, exit code 1


Ошибка связана с тем, что у меня на компьютере нет шрифта TimesNewRoman,Bold.

В Интернете все упоминания о подобных ошибках связаны с использованием японского или корейского шрифтов, но у меня в документе находится обычный английский текст.

Обратите также внимание на кодировку шрифта: Identity-H. Из того, что мне удалось выяснить, эта кодировка говорит о том, что используемый шрифт, вообще говоря, не существует, и в документе используются только коды символов. Для отображения символов, соответствующих этим кодам Ghostscript'у нужно "подсунуть" шрифт с аналогичной таблицей символов. Как это сделать описано в документации Ghostscript (см. CID Font Substitution).

В кратце, в файл c:\dev\bin\gs\gs.8.64\lib\cidfmap (этот файл не будет создан, если при установке вы не отметили галочку "Use Windows TrueType fonts for Chinese, Japanese and Korean"; в этом случае создайте его сами) нужно добавить инструкцию, которая будет указывать, какой шрифт использовать на самом деле вместо того, который указан в PDF.

У меня заняло некоторое время, чтобы подобрать параметры для этой инструкции. Дело в том, что нельзя заменить шрифт на любой другой (например, я не могу заменить мой английский шрифт на японский или корейский). Кроме как в исходниках я нигде больше не смог найти, что нужно подставить на место /CSI Ordering. В итоге получилась такая строчка:


/TimesNewRoman,Bold << /FileType /TrueType /Path (timesbd.ttf) /SubfontID 0 /CSI [(Unicode) 0] >> ;


Файл timesbd.ttf я взял из папки C:\Windows\fonts. Есть некоторые особенности использования абсолютных и относительных путей файла с использованием опции -dSAFER. Если указана эта опция, то путь к имени файла шрифта должен быть относительным. Поэтому я скопировал его в папку c:\dev\bin\gs\fonts (в моем случае именно она используется для поиска шрифтов по умолчанию).

Чтобы посмотреть детальные логи работы Ghostscript, нужно убрать опцию -q, и вот что мы увидим:


GPL Ghostscript 8.64 (2009-02-03)
Copyright (C) 2009 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 2.
Page 1
Substituting font Helvetica for IDAutomationHC39M.
Loading NimbusSanL-Regu font from %rom%Resource/Font/NimbusSanL-Regu... 2875432 1123207 25112624 23807796 3 done.
Loading NimbusRomNo9L-Regu font from %rom%Resource/Font/NimbusRomNo9L-Regu... 2912216 1253685 25112624 23819376 3 done.
Loading NimbusRomNo9L-Medi font from %rom%Resource/Font/NimbusRomNo9L-Medi... 3069576 1402046 25132720 23832802 3 done.
Page 2
Substituting font Helvetica for IDAutomationHC39M.
Loading NimbusSanL-Bold font from %rom%Resource/Font/NimbusSanL-Bold... 3166648 1539642 25152816 23854836 3 done.
Loading a TT font from C:\dev\bin\gs\fonts/timesbd.ttf to emulate a CID font TimesNewRoman,Bold ... Done.


Ошибка пропала, но при рендеринге штрих-кода вместо "правильного" шрифта использовался Helvetica. Это связано с тем, что Ghostscript не может использовать системные шрифты по-умолчанию. Эту проблему я решил копированием файлов шрифтов (IDAutomationHC39M.ttf и IDAutomationHC39M_0.ttf) в папку C:\dev\bin\gs\fonts и указанием ключика -sFONTPATH="C:\dev\bin\gs\fonts".

В результате получаем:


c:\Temp>c:\dev\bin\gs\gs8.64\bin\gswin32c.exe -sFONTPATH="C:\dev\bin\gs\fonts" -dSAFER -dBATCH -dNOPAUSE -sDEVICE=jpeg -r150 -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -dMaxStripSize=8192 -sOutputFile=page-%d.jpg mydocument.pdf

GPL Ghostscript 8.64 (2009-02-03)
Copyright (C) 2009 Artifex Software, Inc. All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 2.
Page 1
Scanning c:\dev\bin\gs\fonts for fonts... 3 files, 3 scanned, 2 new fonts.
Loading IDAutomationHC39M font from c:\dev\bin\gs\fonts/IDAutomationHC39M.ttf... 2868992 1068452 25189416 23882410 3 done.

Loading NimbusRomNo9L-Regu font from %rom%Resource/Font/NimbusRomNo9L-Regu... 2885680 1201118 25209512 23896006 3 done.
Loading NimbusRomNo9L-Medi font from %rom%Resource/Font/NimbusRomNo9L-Medi... 3022944 1346357 25209512 23905928 3 done.
Loading NimbusSanL-Regu font from %rom%Resource/Font/NimbusSanL-Regu... 3120016 1438677 25531048 24170429 3 done.
Page 2
Loading IDAutomationHC39M font from c:\dev\bin\gs\fonts/IDAutomationHC39M.ttf... 3120016 1443415 25189416 23883485 3 done.
Loading NimbusSanL-Bold font from %rom%Resource/Font/NimbusSanL-Bold... 3217088 1539692 25229608 23926657 3 done.
Loading a TT font from C:\dev\bin\gs\fonts/timesbd.ttf to emulate a CID font TimesNewRoman,Bold ... Done.


Хочется отметить еще одну полезную опцию Ghostscript: -r150. Если этот параметр не указывать, то рендеринг будет происходить в мастшабе по умолчанию, в результате чего из-за маленьких размеров не распознавался штрих-код. Увеличение размера до 150 пикселей на дюйм (-r150) это исправило.

Аналогично можно также уменьшать размер картинки, например, чтобы сделать thumbnails (см. заметки по качеству перевода PDF в JPEG).

5 comments:

  1. Добрый день,

    у меня имеется следующий вопрос:

    - как можно узнать какие шрифты GS поддерживает.

    ReplyDelete
  2. Если я правильно помню, он должен поддерживать все True Type шрифты.

    Посмотрите еще в посте есть про CID Font Substitution, на случай если шрифт не установлен в системе, но вы хотите, чтобы GS его увидел.

    ReplyDelete
  3. hello,

    I've similar problem with TimesNewRoman, but after executing gs, I get this:
    gs -dBATCH -dNOPAUSE -sFONTPATH="/usr/share/fonts/TTF" -I/usr/local/share/ghostscript/8.71/Resource/Init -sDEVICE=pdfwrite -sOutputFile=1.pdf 20100630144713_136.pdf
    GPL Ghostscript 8.71 (2010-02-10)
    Copyright (C) 2010 Artifex Software, Inc. All rights reserved.
    This software comes with NO WARRANTY: see the file PUBLIC for details.
    Processing pages 1 through 1.
    Page 1
    Loading a TT font from /usr/share/fonts/TTF/timesnew.ttf to emulate a CID font TimesNewRoman ... Done.
    Error: /typecheck in --run--
    Operand stack:
    --dict:8/17(L)-- F0 12 --dict:7/7(L)-- --dict:7/7(L)-- TimesNewRoman --dict:9/10(L)-- --dict:7/7(L)-- --dict:9/10(L)-- --nostringval-- --nostringval-- 0 0 1 2 (\003\003\004\004\005\005\006\006\007\007\b\b\t\t\n\n\013\013\f\f\r\r\016\016\017\017\020\020\021\021\022\022\023\023\024\024\025\025\026\026\027\027\030\030\031\031\032\032\033\033\034\034\035\035\036\036\037\037 !!""##$$%%&&''\(\(\)\)**++,,--..//00112233445566778899::;;<<==>>??@@AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ[[\\\\]]^^__``aa\003\003\243\243\204\204\205\205) (\000!\000!\000"\000#\000$\000%\000&\000'\000\(\000\)\000*\000+\000,\000-\000.\000/\0000\0001\0002\0003\0004\0005\0006\0007\0008\0009\000:\000;\000<\000=\000>\000?\000@\000A\000B\000C\000D\000E\000F\000G\000H\000I\000J\000K\000L\000M\000N\000O\000P\000Q\000R\000S\000T\000U\000V\000W\000X\000Y\000Z\000[\000\\\000]\000^\000_\000`\000a\000b\000c\000d\000e\000f\000g\000h\000i\000j\000k\000l\000m\000n\000o\000p\000q\000r\000s\000t\000u\000v\000w\000x\000y\000z\000{\000|\000}\000~\000\240\000\241\000\242\000\243) (\000!) 33 34 1 (\003\003\004\004\005\005\006\006\007\007\b\b\t\t\n\n\013\013\f\f\r\r\016\016\017\017\020\020\021\021\022\022\023\023\024\024\025\025\026\026\027\027\030\030\031\031\032\032\033\033\034\034\035\035\036\036\037\037 !!""##$$%%&&''\(\(\)\)**++,,--..//00112233445566778899::;;<<==>>??@@AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ[[\\\\]]^^__``aa\003\003\243\243\204\204\205\205)
    Execution stack:
    %interp_exit .runexec2 --nostringval-- --nostringval-- --nostringval-- 2 %stopped_push --nostringval-- --nostringval-- --nostringval-- false 1 %stopped_push 1878 1 3 %oparray_pop 1877 1 3 %oparray_pop 1861 1 3 %oparray_pop --nostringval-- --nostringval-- 2 1 1 --nostringval-- %for_pos_int_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- %array_continue --nostringval-- false 1 %stopped_push --nostringval-- %loop_continue --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- --nostringval-- %array_continue --nostringval-- 5 5 729 --nostringval-- %for_pos_int_continue --nostringval-- 4 2 197 --nostringval-- %for_pos_int_continue --nostringval--
    Dictionary stack:
    --dict:1160/1684(ro)(G)-- --dict:1/20(G)-- --dict:76/200(L)-- --dict:76/200(L)-- --dict:108/127(ro)(G)-- --dict:288/300(ro)(G)-- --dict:22/25(L)-- --dict:6/8(L)-- --dict:21/40(L)-- --dict:1/1(ro)(G)-- --dict:9/15(L)-- --dict:1/100(L)--
    Current allocation mode is local
    GPL Ghostscript 8.71: Unrecoverable error, exit code 1

    Maybe you have met this issue?

    ReplyDelete
  4. Hi,

    I don't remember such issue. But seems you're doing something different than me, I noticed you use pdfwrite device, I don't have any experience with it.

    ReplyDelete
  5. Как пешить проблему с парсером журналов? Получаются вот "такие" странички https://docs.google.com/file/d/0B2YaHY0dqLv0RHlMSEFIVGhjeEU/edit?usp=sharing

    Как от этого избавиться? Пустота слева, а на в следующей странице справа.

    ReplyDelete