Vivliostyle.js v2.40.0/Vivliostyle CLI v10.3.0では、CSSでのCMYK指定と、Chrome/Chromiumを出力ブラウザに利用する場合のCMYKカラーのPDF出力機能が追加されました(私が実装しました!) 使用例はexamples/cmykを見てください。
この機能はかなり大胆な方法で実現しています。誰でも使えることを目指してはいるのですが、現時点ではある程度Vivliostyleの事情とPDFの構造について理解していたほうが、できること・できないことを予測しやすいです。この記事では仕組みを補足します。
前提として、Vivliostyleの組版コア処理の実態はWebアプリケーションです。Vivliostyle CLIではWebブラウザを制御してこのWebアプリを開きPDFを出力するという分担になっています。これまでVivliostyleがCMYKに対応することが難しかったのは、WebブラウザがCMYKカラーのPDFの出力に対応していないためです。当然WebブラウザはWebページを画面に表示するためのアプリケーションですから、印刷物、それも相当高度な目的でしか使用しないCMYKカラーに対応する動機はないものと思います。この件について根本的に解決するのは現実的ではありませんし、今回の機能追加でも状況は変わりません。
ではVivliostyleにおけるCMYK対応がどうなっているかというと、WebブラウザからPDFに書き込まれるRGB値を予測できる前提で、Vivliostyle.js側ではRGB値が衝突しないように考慮した簡易的なCMYK→RGBを行ってその対応を記録し、Vivliostyle CLI側では対応表をもとにPDF内のRGB値をCMYKに置換しています。
まずRGB値の予測について、Chromiumではcolor(srgb <r> <g> <b>)で指定した値を透過的にPDFに反映させることができます。値は小数点以下4桁の固定少数点数で、5桁目は四捨五入されます。具体的な内部処理はChromium(Skia)のソースコードをクローンすれば比較的簡単に見つけることができます。
小数点以下4桁の値を透過的にPDFに反映させることができる
<html>
<head>
<style>
@page {
size: A5;
}
body {
background-color: color(srgb 0 0.0004 0.0008);
}
</style>
</head>
<body></body>
</html>
$ docker run --rm --mount type=bind,source=.,target=/data ghcr.io/vivliostyle/cli:10.3.0 build srgb.html -o srgb.pdf
$ docker run --rm --mount type=bind,source=.,target=/workdir --entrypoint /usr/bin/gs registry.gitlab.com/islandoftex/images/texlive:TL2024-historic -dPDFDEBUG -dBATCH -dNOPAUSE -sDEVICE=nullpage srgb.pdf 2>&1 | grep rg
0 0.000400 0.000800 rg
この事実に基づいて、Vivliostyle.jsではdevice-cmyk()関数によるCMYK指定をごく単純な計算でRGBに変換します1。device-cmyk()関数はCSS Color Module Level 5で定義された、未校正のCMYK値を指定するCSS関数です2。「未校正のCMYK値」とは、色そのものではなく単にインクの量を表現する値と捉えてください。
<html>
<head>
<style>
@page {
size: A5;
}
</style>
</head>
<body>
<p style="color: device-cmyk(1 0 0 0)"><!-- color(srgb 0 1 1) -->C100</p>
<p style="color: device-cmyk(0.5 0 0 0.5)"><!-- color(srgb 0.25 0.5 0.5) -->C50 K50</p>
<p style="color: device-cmyk(0 0 0 1)"><!-- color(srgb 0 0 0) -->K100</p>
</body>
</html>
ここで理解していただきたいのは、この時点ではWebブラウザから出力されるPDFはあくまでRGBカラーであるという点です。CMYKのPDFを出力するために、Vivliostyle CLIが追加で処理を行います。
Vivliostyle.jsは組版時にRGB→CMYKの逆変換テーブルを構築しており、Vivliostyle CLIにこのテーブル共有します。Vivliostyle CLIでは、コマンドラインオプション--cmykまたは設定ファイルのpdfPostprocess.cmykを使用すると、このテーブルを用いてrg(RGB塗り)RG(RGBストローク)をk(CMYK塗り)K(CMYKストローク)に書き換えます。これがVivliostyleでのCMYK対応の仕組みです。
$ docker run --rm --mount type=bind,source=.,target=/data ghcr.io/vivliostyle/cli:10.3.0 build cmyk.html -o cmyk.pdf --cmyk
$ docker run --rm --mount type=bind,source=.,target=/workdir --entrypoint /usr/bin/gs registry.gitlab.com/islandoftex/images/texlive:TL2024-historic -dPDFDEBUG -dBATCH -dNOPAUSE -sDEVICE=nullpage cmyk.pdf 2>&1 | grep -e k$
1 0 0 0 k
0.500000 0 0 0.500000 k
0 0 0 1 k
$ docker run --rm --mount type=bind,source=.,target=/workdir --entrypoint /usr/bin/gs registry.gitlab.com/islandoftex/images/texlive:TL2024-historic -dQUIET -dBATCH -dNOPAUSE -sDEVICE=ink_cov -sOutputFile=- cmyk.pdf
0.05305 0.00000 0.00000 0.05748 CMYK OK
いくつか問題があります。ここまで見てきた通り、色指定にrg・RGを用いる部分のみがCMYKに置換可能です。代表的なものはテキスト、ボーダー、背景色、SVGのベクター要素です。一方でラスター画像やグラデーションは別の仕組みで色を管理しているので、この方法では変換されません。TIFFやJPEGはCMYKカラーをネイティブでサポートしますが、TIFF画像を表示できるブラウザは現時点ではSafariだけであり、CMYKのJPEG画像はCMYKのままPDFに貼り込まれるわけではなく一度RGBに変換されます。またSVGのベクター要素もCMYKに置換できるとは言いましたが、device-cmyk()関数に対応しているSVG編集ソフトウェアは存在しません。どうにかして、特定のCMYK値に変換されるRGB値を知る必要があります。
必要な機能は順次実装していきたいと考えているのですが、Vivliostyle CLI v10.3.0の時点ではreplaceImage/mapOutput/overrideMapというオプションを用意しておきました。
// @ts-check
import { defineConfig } from '@vivliostyle/cli';
export default defineConfig({
entry: ['manuscript.html'],
pdfPostprocess: {
cmyk: {
mapOutput: 'cmyk-map.json',
overrideMap: [
[
{ r: 5000, g: 3000, b: 2000 },
{ c: 0, m: 0, y: 0, k: 10000 },
],
[
{ r: 0, g: 0, b: 0 },
{ c: 0, m: 0, y: 0, k: 10000 },
],
],
},
replaceImage: [{ source: /^(.*)_rgb\.png$/, replacement: '$1_cmyk.tiff' }],
},
});
replaceImageは名前通り、画像を置換するオプションです。Chromium(Skia)のPDF出力では紙面に載せた画像の全ピクセル生データが埋め込まれています。これで一致をとってCMYK画像に置き換えてしまう、これまた大胆で単純な機能です。examples/cmykにも書いた通り、拡大縮小程度の変形なら元画像が埋め込まれますが、filterを適用するなど複雑な操作を行うと、Chromium(Skia)は変換済みの画像を埋め込むためreplaceImageで置換できなくなります。画像効果はGIMPなどで適用済みの画像を用意しておくのが順当な方法です。
mapOutputはVivliostyle.jsとVivliostyle CLIで受け渡される内部のテーブルを保存する機能です。強引ですが、これを見れば「特定のCMYK値に変換されるRGB値」を知ることは可能です。
overrideMapは、どこで使われているか謎だがどこかで使われているらしいRGBを置換するための最終手段です。CMYK機能を有効にするとwarnUnmappedがデフォルトで有効になり、置換されなかったRGB値について警告を表示します3。すべてCMYKに置換されるように指定したはずなのに警告が出るなら、overrideMapで誤魔化します。
以上がVivliostyleのCMYK対応の仕組みと、Vivliostyle CLI v10.3.0で用意したオプションの意義です。ご覧の通り、この機能は曲芸じみた方法で実現されています。まだ考慮できていないケースや不足している機能はあるものと思います。報告していただければ私が対応します。
-
実際には衝突回避があるので、この式の通りの値を用いるわけではありません。またこの変換処理はカラープロファイルなどを考慮しません。ここでRGB値は単なるキーなので厳密に再現する必要はないのです。 ↩
-
なおChromiumが出力するPDFは出力プロファイルを持ちません。より正確には、SkiaにはPDF/X-4を出力する機能があり、ここではsRGBを出力プロファイルに指定しますが、執筆時点のChromiumではこの機能は使用されていないようです。 ↩
-
ここまで説明した仕組みの都合上CMYKとRGBを混在させたPDFはサポートできないため、変換されていないRGB値は変換漏れとみなしてこの警告を出しています。たとえばK100が
#000に変換された場合、元から#000指定だった箇所とK100指定でRGB#000に変換された箇所を区別する手段はありません。Vivliostyle CLIはすべての#000をK100に置換するしかありません。 ↩