Skip to content

Instantly share code, notes, and snippets.

@aminophen
Last active November 19, 2015 04:32
Show Gist options
  • Save aminophen/a810e6369f6b1f482545 to your computer and use it in GitHub Desktop.
Save aminophen/a810e6369f6b1f482545 to your computer and use it in GitHub Desktop.
dvipdfmx 経由での PDF の /Rotate サポート案

dvipdfmx の /Rotate サポート案

現在の ZR 案の状況

  1. extractbb は従来どおり「原座標系」に則った Box 値を返し、これに加えて %%Rotate: 行も出力する。dvipdfmx.def は bbox 値と rotate 値を取得し、TeX が確保すべき箱のサイズを計算する。
  2. dvipdfmx.def は bbox 値について従来どおり「bb= オプションがあればそれを採用 → オプションがなければ .xbb ファイルを探して読む → ファイルがなければ extractbb を実行してパイプ入力する」。rotate 値については bbox 値取得と同じ段階で行われ、「bb= オプションがあれば pdfrotate= オプションの値を読む → bb= オプションがなければ .xbb ファイルを探して読む → ファイルがなければ extractbb を実行してパイプ入力する」(bbox 値取得と同じ段階での読み込みに失敗すれば0とみなす)。
  3. epdf special の bbox 値には、viewport を考慮した画像表示範囲を「原座標系」で与える。あとは dvipdfmx がその範囲の画像を /Rotate にあわせて回転して取り込む。

上記 ZR 案の問題点

  • rotate 値が取得できなかった場合に0にフォールバックするという状況が起こりうるので、これは dvipdfmx.def が /Rotate 無視を意図したといえる。それにもかかわらず dvipdfmx が実際の取り込みで /Rotate を解釈することを許しているので、一貫性がない。
    • 一貫性を持たせる案1【警告を新設 + \special を拡張】:rotate 値の取得法は上記のままとするが、取得に失敗した場合は0にフォールバックする前に警告を出す。これは /Rotate 無視にほかならず、その場合は箱確保が回転を考慮せずになされることから、dvipdfmx による取り込み時にも /Rotate を無視させる。これを実現するため、回転すべき新仕様版 DVI にだけ \special で userotate パラメタを埋め込んでマーキングし、これを検知した場合のみ PDF を回転する。案1では、一律に /Rotate を無視する従来のマチガッタ挙動と互換性も保証される(これはあくまで新 dvipdfmx と dvipdfmx.def を整合させるための要請からくる副次的な恩恵)。
    • 一貫性を持たせる案2【エラーを新設】:rotate 値の取得法は上記のままとするが、取得に失敗した場合はエラーで落とす。ユーザには pdfrotate= オプションの明示または xbb ファイルの作り直しを強制する。この場合、従来のマチガッタ dvipdfmx に対しては正義だった “静的な bb 取得法” をバッドノウハウ認定することを意味する。悪いのは従来の dvipdfmx であるにもかかわらず、非のないユーザにしわ寄せすることとなり納得できないだろう。
    • 一貫性を持たせる案3【警告を新設】:rotate 値の取得法は上記のままとするが、取得に失敗した場合は0にフォールバックする前に警告を出す。これは /Rotate 無視にほかならないが、rotate 値を明示しなかったユーザが悪いといえるので結果的に PDF がヘンになっても文句は言えないことにする。この場合も、従来のマチガッタ dvipdfmx に対しては正義だった “静的な bb 取得法” をバッドノウハウ認定することを意味する。また、古い DVI ファイルはもう新しい dvipdfmx で処理できないことを意味する。
    • 一貫性を持たせる案4【rotate 取得試行を強化したうえでエラーを新設】:rotate 値の取得試行を bbox 取得試行と同じ段階に限定するのをやめ、たとえ bbox 値がとれても rotate 値がまだなら試行継続する。すなわち「pdfrotate= オプションがあればそれを採用 → オプションがなければ .xbb ファイルを探して読む → %%Rotate 行がなければ extractbb を実行してパイプ入力する」(ここまでの試行で失敗すればエラーで落とす)。案2のように即エラーとなるのは優しくないが、もうワンクッションあることで rotate 値の取得可能性が高まる利点がある(なお、-no-shell-escape 下では結局のところ案2と同じになる)。その一方で、bb= オプションや xbb 事前準備という “静的な取得” を望んだ人にとっては “動的な取得” が行われる余地を生み、再び完全な “静的な取得” を実現するために pdfrotate= オプションの明示または xbb ファイルの作り直しを強制することになる。案4は正しい結果に導く可能性が最も高い方法である。
    • 一貫性を持たせる案5【案4 + \special を拡張】案4に加え、案1の一部である \special でマーキングする処理も行う。案5では、正しい結果に導く可能性が最も高いことに加えて、一律に /Rotate を無視する従来のマチガッタ挙動と互換性も保証される(案5は案1と異なり、新仕様の要請からではなく意図的に後方互換性を確保するもの)。

XeTeX + xdvipdfmx に向けて

XeTeX は libpoppler の機能で Box 値のみならず rotate 値も取得できるので、これを xetex.def に渡して同様の処理を行えばよいだろう。

互換性を考慮するための参考

  1. 古い dvipdfmx で処理される前提で出力された DVI ファイルは、これまでどおり処理されると好ましい。これには、DVI 作成段階で /Rotate を考慮した箱が確保された DVI はそれとわかるようにマーキングし、マーキングがなければ /Rotate を無視した箱しか確保されていないとみなすという方法が考えられる(上記第1案参照)。マーキングがなければ、取り込まれる PDF に書かれている /Rotate は dvipdfmx によって無視されなければならない。
  2. extractbb が返す値は、従来と同じ (HiRes)BoundingBox 値を返さなければならない。そうでなければ、現在正しいはずの xbb ファイルや bb= オプション指定が突如として不正な値であることになってしまう。
  3. pdfLaTeX の viewport オプションは、/Rotate によって回転されたあとの「新座標系」における、“見せたい領域” の “採用される Box” に対する相対座標で指定する。この仕様を dvipdfmx でも踏襲しなければならない。
  4. dvipdfmx ドライバのための改変であるので、ドライバ非依存のファイル(graphics.sty など)を書き換えてはいけない。ただし、dvipdfmx.def によるマクロ定義の上書きは許される。
@aminophen
Copy link
Author

Twitter での議論(まとめ (1), (2))を受けて、最近の検討状況をメモしたもの。ZR さんによる「pdf:epdf の仕様案」のうち (1), (3), (4) は、本ページの第1案と第5案に出現する「epdf special の拡張による互換性確保」の実装例とみることができる。ちなみに、僕のいまの意見としては ZR さんが提示している7つの案のうち (3) を推している。すなわち

  • pdf:epdf 命令に「回転を適用するか」を示す userotate パラメタを新設する。この値が 1 の時は PDF の /Rotate の値に従って回転する。0 の時は回転しない。

つまり「/Rotate の設定を無視する」という動作を“仕様”であると認定する(一つの理由として「『公式の PDF とりこみかたの仕様書』は存在せず、『/Rotate の設定を無視することはマチガッテいる』と否定するに足る根拠が存在しないため」、もう一つの理由として「古い DVI を入力する場合のみならず、たとえば従来の xbb が存在する場合には『dvipdfmx.def によって rotate 値が無視される』ということが新仕様版でも起こりうるため、その場合にも一貫性を保証すべきであるため」)。

本ページに書いた案の中では第2案と第3案については僕は否定的である。第1案と第4, 5案を比べると、第4, 5案の方が「正しい結果を生む可能性が高い」気はするのだが、「パフォーマンス重視・no-shell-escape でもいける」という点を重視して xbb をいままで作ってきた人の立場からすれば、第4, 5案は嫌われるのだろうと予想している(しかも、dvipdfmx.def のロジックも変更を余儀なくされるはず)。僕は第1案を推す。

@aminophen
Copy link
Author

「pdf:epdf の書式を拡張して userotate パラメタを導入する」方向で、さらに「なんらかの理由で rotate 値を取得失敗した場合に警告を出す」ように dvipdfmx.def を改修するところに落ち着きそうである。その改修が完了したと仮定すると、これを dvipdfmx の側でどう解釈するかも問題である。結果だけ考えると

古い書式:

pdf:epdf (snowman.pdf) → 回転なし

新しい書式:

pdf:epdf userotate 1 (snowman.pdf) → 270度回転
pdf:epdf userotate 0 (snowman.pdf) → 回転なし

であるが、その変換途中にいつ警告を出すかも気になるところである。一つの案として

  • dvipdfmx は取り込み対象の PDF の /Rotate の値を確認する。
    • 「/Rotate が0である」 → そのまま貼る(警告なし)
    • 「/Rotate が0でない」かつ「userotate パラメタがない」 → 変換中に警告を出しつつ回転せず貼る
    • 「/Rotate が0でない」かつ「userotate 0 である」 → 回転せず貼る(警告なし)
    • 「/Rotate が0でない」かつ「userotate 1 である」 → 回転して貼る(警告なし)

とすることが考えられる(新書式の「userotate 0 を明記」と旧書式の「明記せず」を区別しているところがポイント)。この場合の新しい dvipdfmx.def の実装は

  • rotate 値の取得に失敗 → 旧書式で pdf:epdf を書く
  • rotate 値の取得に成功 → 新書式で pdf:epdf に userotate 1 を書き込む

とするのが良いだろう。これにより、dvipdfmx.def と dvipdfmx が連携する:

  • dvipdfmx.def が rotate 値の取得に成功した場合は、たとえ rotate 値が0であっても一律に userotate 1 を書き込む → dvipdfmx は実際の PDF から /Rotate を読み取り、userotate 1 なのでそれに合わせて回して貼る
  • dvipdfmx.def が rotate 値の取得に失敗した場合は、敢えて旧書式で pdf:epdf を書く → dvipdfmx は実際の PDF から /Rotate を読み取り、その値が0でない場合は(古い dvipdfmx がやっていたように)警告を出しつつ回さずに貼る

なお、dvipdfmx.def の助けなしにユーザが userotate 0 を使った場合は、明らかに意図的に回さないことを選択しているので

  • userotate 0 を書き込む → dvipdfmx は(とりあえず実際の PDF の /Rotate は読むが、値が0の場合はそのまま貼り、値が0でなければ userotate 0 に従ってそのまま貼るので)そのまま回さず貼る

とするのが自然である。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment