doraTeX/TeX2img

Ghostscript のバージョンが古すぎる場合への対処

Closed this issue · 18 comments

Ghostscript 7.07 を使っている場合に失敗する という報告を発見。

どのバージョンの gs から TeX2img が正しく動作するのかはテストしていませんが,9以降ならおそらく大丈夫でしょう。

そこで,

  • 公式サイトの動作要件に「Ghostscript 9.x 以上」と書き加える
  • 動作時のバージョンチェック(Mac版は9.15以上か未満かを毎回判定しています)して,gs のバージョンが9未満の場合はエラーダイアログを出して動作を停止させる

といった対処をとるのがよさそうです。

手元で gs7.07(自力ビルド;山田泰司さんのパッチ済み)を使って

GNU Ghostscript 7.07 (2003-05-17)
Copyright (C) 2003 artofcode LLC, Benicia, CA.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Error: /syntaxerror in readxref

が再現しました(すみません、この現象は既に知っていました…)。最近の dvipdfmx は -V5 すなわち PDF-1.5 を吐くのですが、gs7.07 当時は PDF-1.4 が最新だったためです。gs7.07 を使う場合は -V4 を dvipdfmx に付けておけば成功します。例のブログのほうにもコメントを書いておきます。

なるほど、dvipdfmx の新しさと ghostscript の古さが不釣り合いだったわけですね。
対処法があるなら、古い gs を使うという選択肢を不必要に封じることはなさそうです。
TeX Wiki のトラブルシューティングにでも対処法を追記しておくことでよしとしましょうか。

対処法があるなら、古い gs を使うという選択肢を不必要に封じることはなさそうです。

はい、できれば縦組対応の gs-cjk を封じないでいただきたいです;) Wiki の FAQ 等への追記はよろしくお願いします。

あれ、改めて TeX2img から gs7.07 を呼んで実行してみると BoundingBox は取得できるはずなのに落ちました。

ターミナルから gs7.07 を呼ぶと

$ gs -sDEVICE=bbox -dBATCH -dNOPAUSE docs.pdf
GNU Ghostscript 7.07 (2003-05-17)
Copyright (C) 2003 artofcode LLC, Benicia, CA.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 1.
Page 1
%%BoundingBox: 72 448 524 707
%%HiResBoundingBox: 72.215998 448.397986 523.205984 706.769978
Error: /rangecheck in --setpagedevice--
Operand stack:
   --nostringval--   --dict:36/36(ro)(G)--   --nostringval--   --nostringval--   --nostringval--   true   --nostringval--   GraphicsAlphaBits   1   UseCIEColor   false   BitsPerPixel   8   HWResolution   --nostringval--   .HWMargins   --nostringval--   MaxSeparations   1   Margins   --nostringval--   TextAlphaBits   1   NumCopies   --nostringval--   Colors   1   %MediaSource   0   Separations   false   HWSize   --nostringval--   ImagingBBox   --nostringval--   .LockSafetyParams   false   PageBoundingBox   --nostringval--   .IgnoreNumCopies   false   .MarginsHWResolution   --nostringval--   GrayValues   256   %MediaDestination   0   SeparationColorNames   --nostringval--   ProcessColorModel   DeviceGray   --dict:36/36(ro)(G)--
Execution stack:
   %interp_exit   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--   --nostringval--   --nostringval--   false   1   %stopped_push   1   3   %oparray_pop   1   3   %oparray_pop   1   3   %oparray_pop   1   3   %oparray_pop   --nostringval--   1   3   %oparray_pop   --nostringval--   --nostringval--   --nostringval--   --nostringval--   --nostringval--   --nostringval--
Dictionary stack:
   --dict:1059/1123(ro)(G)--   --dict:0/20(G)--   --dict:68/200(L)--
Current allocation mode is local
GNU Ghostscript 7.07: Unrecoverable error, exit code 1

となって「エラーは出るが値は取得できる」という状態です。これが TeX2img 経由だと

TeX2img: Getting the bounding box...

$ /usr/local/teTeX/bin/gs -dBATCH -dNOPAUSE -sDEVICE=bbox "temp36026-F5BC6AD0-B77F-4FC5-A501-5FD0408952D9-outline.pdf" > temp36026-F5BC6AD0-B77F-4FC5-A501-5FD0408952D9-bbox 2>&1
tex2img: [Error] Ghostscript cannot be executed.
Check errors in the source code.

TeX2img: PDF → PNG (Page 1)
2016-06-01 13:05:20.151 tex2img[36026:707] An uncaught exception was raised
2016-06-01 13:05:20.152 tex2img[36026:707] Cannot lock focus on image <NSImage 0x10d83a6d0 Size={0, 0} Reps=(
)>, because it is size zero.
2016-06-01 13:05:20.153 tex2img[36026:707] (
    0   CoreFoundation                      0x00007fff96952f56 __exceptionPreprocess + 198
    1   libobjc.A.dylib                     0x00007fff8efa2d5e objc_exception_throw + 43
    2   CoreFoundation                      0x00007fff96952d8a +[NSException raise:format:arguments:] + 106
    3   CoreFoundation                      0x00007fff96952d14 +[NSException raise:format:] + 116
    4   AppKit                              0x00007fff90f59f77 -[NSImage _lockFocusOnRepresentation:rect:context:hints:flipped:] + 398
    5   AppKit                              0x00007fff90f59de0 __-[NSImage lockFocusWithRect:context:hints:flipped:]_block_invoke_1 + 118
    6   AppKit                              0x00007fff90ed00e9 -[NSImage _usingBestRepresentationForRect:context:hints:body:] + 170
    7   AppKit                              0x00007fff90f59d61 -[NSImage lockFocusWithRect:context:hints:flipped:] + 185
    8   AppKit                              0x00007fff90f59c9f -[NSImage lockFocusFlipped:] + 124
    9   tex2img                             0x000000010d6c38a1 -[Converter pdf2image:outputFileName:page:crop:] + 1298
    10  tex2img                             0x000000010d6c5ffa -[Converter convertPDF:intermediateOutlinedFileName:outputFileName:page:useCache:skipEmptyPage:] + 825
    11  tex2img                             0x000000010d6c8fb7 -[Converter compileAndConvert] + 7755
    12  tex2img                             0x000000010d6ca1e8 -[Converter compileAndConvertWithCheck] + 565
    13  tex2img                             0x000000010d6cbd77 -[Converter compileAndConvertWithInputPath:] + 747
    14  tex2img                             0x000000010d6d0e4b main + 127
    15  tex2img                             0x000000010d6b9ffc start + 52
)
2016-06-01 13:05:20.153 tex2img[36026:707] *** Terminating app due to uncaught exception 'NSImageCacheException', reason: 'Cannot lock focus on image <NSImage 0x10d83a6d0 Size={0, 0} Reps=(
)>, because it is size zero.'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff96952f56 __exceptionPreprocess + 198
    1   libobjc.A.dylib                     0x00007fff8efa2d5e objc_exception_throw + 43
    2   CoreFoundation                      0x00007fff96952d8a +[NSException raise:format:arguments:] + 106
    3   CoreFoundation                      0x00007fff96952d14 +[NSException raise:format:] + 116
    4   AppKit                              0x00007fff90f59f77 -[NSImage _lockFocusOnRepresentation:rect:context:hints:flipped:] + 398
    5   AppKit                              0x00007fff90f59de0 __-[NSImage lockFocusWithRect:context:hints:flipped:]_block_invoke_1 + 118
    6   AppKit                              0x00007fff90ed00e9 -[NSImage _usingBestRepresentationForRect:context:hints:body:] + 170
    7   AppKit                              0x00007fff90f59d61 -[NSImage lockFocusWithRect:context:hints:flipped:] + 185
    8   AppKit                              0x00007fff90f59c9f -[NSImage lockFocusFlipped:] + 124
    9   tex2img                             0x000000010d6c38a1 -[Converter pdf2image:outputFileName:page:crop:] + 1298
    10  tex2img                             0x000000010d6c5ffa -[Converter convertPDF:intermediateOutlinedFileName:outputFileName:page:useCache:skipEmptyPage:] + 825
    11  tex2img                             0x000000010d6c8fb7 -[Converter compileAndConvert] + 7755
    12  tex2img                             0x000000010d6ca1e8 -[Converter compileAndConvertWithCheck] + 565
    13  tex2img                             0x000000010d6cbd77 -[Converter compileAndConvertWithInputPath:] + 747
    14  tex2img                             0x000000010d6d0e4b main + 127
    15  tex2img                             0x000000010d6b9ffc start + 52
)
terminate called throwing an exceptionAbort trap: 6

で落ちます (Abort trap: 6) 。環境は例によって OS X 10.7.5 (Lion) です。

まだ確かめていませんが、おそらく、gs の実行に 2 >&1 がついているので、エラーメッセージ部も出力に取り込まれ、TeX2img 側での bb の出力の解析に失敗しているのではないかと思います。

gs の実行に 2 &>1 がついているので、エラーメッセージ部も出力に取り込まれ

そういえば gs の bbox は標準エラー出力に出るのでした… gs 自体はエラー終了しているわけですが、bbox だけは取得できるのでなんとかなれば嬉しいです。

ちなみに pdfcrop.pl (by Heiko Oberdiek) のほうは

$ pdfcrop --debug docs.pdf
PDFCROP 1.38, 2012/11/02 - Copyright (c) 2002-2012 by Heiko Oberdiek.
(snip)
* Running ghostscript for BoundingBox calculation ...
* Ghostscript call: gs -sDEVICE=bbox -dBATCH -dNOPAUSE -c save pop -f docs.pdf
GNU Ghostscript 7.07 (2003-05-17)
Copyright (C) 2003 artofcode LLC, Benicia, CA.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Unrecoverable error: configurationerror in setpagedevice
Operand stack:
    true  --nostringval--
* Cleanup
* Temporary files: tmp-pdfcrop-36250.tex
!!! Error: Ghostscript exited with error code 255!

となるので、pdfcrop でも(PDF-1.4 であっても)gs7.07 は使えないようでした。

一応確認したところ、手元の gs7.07 でひとつひとつ test.pdf (PDF-1.4) に対して

  1. gs -sDEVICE=bbox -dBATCH -dNOPAUSE test.pdf で bbox をみる(値の表示後にエラー終了)
  2. gs -sDEVICE=epswrite -dBATCH -dNOPAUSE -sOutputFile=a.eps test.pdf で a.eps をつくる
  3. bbox の値をテキストエディタで 1. のものに置き換える
  4. epstopdf a.eps で a.pdf をつくる

とすると、「クロップされたアウトライン化 PDF」を作ることは出来ました。2. でできた eps は、左下がクロップできていない状態になるようで、1. の値を書き込むことで初めてクロップ可能でした。

ソースを確認してみました。

エラーメッセージ部も出力に取り込まれ、TeX2img 側での bb の出力の解析に失敗している

のではなくて,

  • Ghostscript の終了ステータスを見て,Ghostscirpt がエラー終了した場合は BoundingBox 取得エラーと見なして tex2img: [Error] Ghostscript cannot be executed. というエラーを表示している。
  • ただしこれまでのテストケースで「gsは実行可能だがbb取得に失敗する」という場合のテストが不足していたため,「bb取得に失敗した場合」のエラー処理が不十分で,bb取得に失敗してもその後の処理はそのまま実行され,それがその後のクラッシュにつながっている。

ということのようです。

  • bb取得時に gs がエラー終了した場合も,BoundingBox らしき文字列が含まれていれば成功と見なしその後の処理を続行する。
  • 真に gs がエラー終了した場合にその後の処理を停止するようにエラー処理を強化する。

という対処をとるのがよいでしょうか。

Ghostscript の終了ステータスを見て

なるほど、納得しました。おっしゃるとおり、可能ならば「bb取得時に gs がエラー終了した場合も,〜」の対処がよいと思います。

「gsは実行可能だがbb取得に失敗する」に該当するのはいまのところ

  • dvipdfmx で作成した PDF-1.5 の場合
  • gs に bbox device がそもそも組み込まれていなかった場合

が考えられますね。(gs の bbox device は gs7.xx 時代は流動的で、デフォルトでは make で activate されていなかったこともあったそうです → gs-devel のここここ

  • bb取得時に gs がエラー終了した場合も,BoundingBox らしき文字列が含まれていれば成功と見なしその後の処理を続行する。
  • 真に gs がエラー終了した場合にその後の処理を停止するようにエラー処理を強化する。

とした Ver. 2.1.6 beta 1 を作ってみました。
手元で古いgsの環境がすぐに用意できないものでして,お手数ですがテストをよろしくお願いいたします。

今度は epswrite で Abort trap: 6 が出ました。よく考えてみると、この直前の段階で「pdfcrop 類似処理によるロンダリング」が入っているので、ここでまた PDF-1.5 に上がっているようです。pdfcrop 類似処理のときに作る中間 .tex ソースで pdfminorversion を 1.4 に制限すればいけるような気がします。

中間 .tex ソースで pdfminorversion を 1.4 に制限すればいけるような気がします。

と書いたのですが、ロンダリングは Quartz だったかもしれないと思って Quartz で保存し直した PDF を gs7.07 に食わせてみると、PDF-1.3 なのに

gs -dBATCH -dNOPAUSE -dAutoRotatePages=/None -r20016 -sOutputFile=a.eps -dFirstPage=1 -dLastPage=1 -sDEVICE=epswrite -dNOCACHE docs-quartz.pdf
GNU Ghostscript 7.07 (2003-05-17)
Copyright (C) 2003 artofcode LLC, Benicia, CA.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
Processing pages 1 through 1.
Page 1
Error: /rangecheck in --get--

で epswrite が EPS を吐き損ねました…(EPS ファイルが存在するけれどページ描画コマンドが書き込まれていません)。これがその後の Abort trap: 6 につながるようです。

gs7.07 が成功:

  • dvipdfmx の PDF-1.4 → bbox 可(ただしエラー終了)、epswrite 可
  • pdftex の PDF-1.4 → bbox 可(ただしエラー終了)、epswrite 可

gs7.07 が失敗:

  • dvipdfmx の PDF-1.5 → bbox 不可、epswrite 不可
  • pdftex の PDF-1.5 → bbox 不可、epswrite 不可
  • quartz の PDF-1.3 → bbox 不可、epswrite 不可

Quartz を使うとなぜか gs7.07 には読めなくなってしまうようです。gs7.07 をサポートするために全体を Quartz 回避して変えるとなると難しいので、やっぱりサポート外とするほうがよいのでしょうか。

Quartz を使うとなぜか gs7.07 には読めなくなってしまうようです。

うーん,不可解ですね……。また gs の闇に触れてしまったようです。

Quartz 回避して変えるとなると難しいので、やっぱりサポート外とするほうがよいのでしょうか。

当初のように

  • 公式サイトの動作要件に「Ghostscript 9.x 以上」と書き加える
  • 動作時のバージョンチェック(Mac版は9.15以上か未満かを毎回判定しています)して,gs のバージョンが9未満の場合はエラーダイアログを出して動作を停止させる

としますかね?

gs8.xx は割と新しいので、まだ使っている人が多い印象を受けるので、厳しいバージョンチェックはあまり入ってほしくないですね…

  • gs7.07 などの古い gs はサポート外(詳細な番号は未確認)と公式サイトに明記
  • gs がエラー終了したらそこで処理を止める(ただし bbox だけは 2.1.6 beta 1 のようにどうにかなるかもしれないので例外的に許可)

がよいかもしれないです。

gs がエラーを起こした場合のエラー処理を多少強化した Ver. 2.1.6 beta 2 を作りました。
こちらの環境では同じタイミングでエラーを起こすことができず,エラー処理の妥当性をテストできておりませんので,テストをよろしくお願いいたします。

Ver. 2.1.6 beta 2 を試してみました。「bbox らしき文字列が取得できなかった場合に終了」がちゃんと効いているのかどうかよくわかりませんでした…が、出力の成否については期待した結果と一致していました。gs7.07 でも

tex2img --quick test.pdf test-a.png

tex2img --with-text test.pdf test-a.pdf

が通るようになりました。gs7.07 は epswrite / pdfwrite が失敗する一方で bbox だけは使いものになるので、この結果は妥当な気がします。gs7.07 は可能な限りサポートできたように思えますので、これで十分だと思います。

検証ありがとうございます。
公式サイトの動作要件に Ghostscript Ver. 9 以降と明記(ただし厳密なバージョンチェックはあえてしない)し,TeX Wiki の FAQ にも追記しておきました。