2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WebブラウザベースCSS組版はCMYK出力の夢を見るか?——PostCSSプラグインによるdevice-cmyk色指定の変換

Last updated at Posted at 2024-12-16

CSS組版 Advent Calendar 2024 17日目の記事です。昨日はhidarumaさんでした。明日はhidarumaさんです。この記事の内容はCSS組版というよりJavaScriptの領分です。

CSS組版で本格的な版下データを出力するには、CMYKへの対応が不可欠です。しかし、Vivliostyleをはじめとするレンダリングを既成のWebブラウザで行うCSS組版エンジンは、Webブラウザが対応しない限りCMYKカラーの出力ができません1。印刷というWebとは異なるドメインに属するカラースペースであるCMYKへの対応を、Webブラウザ側が積極的に進める可能性は高くないと考えられます。

過去には、CSS側で疑似的にCMYKを指定した後、RGBに変換して組版を行い、さらにRGBからCMYKを復元する情報を出力することで、実質的にCMYKを指定する方法を検討しました。そして、先日公開したu1f992/postcss-device-cmykにより、当面の目標を達成できたと感じています。この記事では、u1f992/postcss-device-cmykを使用したCMYK指定とPDF出力について紹介します。

プロジェクト例

.
├── css
│   ├── JapanColor2001Coated.icc
│   ├── package.json
│   ├── postcss.config.js
│   └── src
│       └── style.css
├── manuscript.md
├── package.json
└── vivliostyle.config.js
> mkdir vivliostyle-cmyk-example && cd vivliostyle-cmyk-example
> npm init --yes
> npm pkg set type=module
> npm i --save-dev @vivliostyle/cli
> npx vivliostyle init
package.json
{
  "name": "vivliostyle-cmyk-example",
  "type": "module",
  "scripts": {
    "build": "cd css && npm run build && cd .. && vivliostyle build",
    "preview": "node -e \"[['npm run watch','css'],['npx vivliostyle preview','']].map(([c,cwd])=>require('node:child_process').exec(c,{cwd})).forEach(p=>p.stdout.on('data',d=>process.stdout.write(d))||p.stderr.on('data',d=>process.stderr.write(d)))\""
  },
  "devDependencies": {
    "@vivliostyle/cli": "^8.17.1"
  }
}
vivliostyle.config.js
// @ts-check
/** @type {import('@vivliostyle/cli').VivliostyleConfigSchema} */
const vivliostyleConfig = {
  title: "...",
  author: "...",
  language: "ja",
  theme: "./css",
  entry: ["manuscript.md"],
  output: ["./output.pdf"],
  workspaceDir: ".vivliostyle",
};

export default vivliostyleConfig;
> mkdir css && cd css
> echo '{"name":"css","type":"module","main":"dest/style.css","scripts":{"build":"postcss src --verbose --dir dest","watch":"postcss src --verbose --dir dest --watch"}}' > package.json
> npm install --save-dev postcss-cli @u1f992/postcss-device-cmyk
> cd ..
css/postcss.config.js
// @ts-check

import fs from "node:fs";
import path from "node:path";

import deviceCMYK from "@u1f992/postcss-device-cmyk";

const outputDir = "dest";

/**
 * @param {string} dir
 * @returns {string[]}
 */
function collectFiles(dir) {
  return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
    const filePath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      return collectFiles(filePath);
    }
    return entry.isFile() && filePath.endsWith(".css") ? [filePath] : [];
  });
}

export default (ctx) => ({
  plugins: [
    deviceCMYK({
      // https://www.adobe.com/support/downloads/iccprofiles/iccprofiles_win.html
      cmykProfilePath: "JapanColor2001Coated.icc",
      restoreJSONPath: path.join(outputDir, "device-cmyk.restore.json"),
      otherFiles: collectFiles(ctx.file.dirname),
    }),
  ],
});

package.json"scripts"に記載されている通り、$ npm run preview$ npm run buildを実行することで、PostCSSを用いたCSSのビルドとVivliostyleによるビルドが一連の処理として実行されます。postcss-cliにはWatch機能があるため、device-cmykを含むCSSファイルを変更するだけで、変換を意識せずにVivliostyleのプレビューを更新することが可能です。

device-cmykを使用した色指定

u1f992/postcss-device-cmykは、CSS Color Module Level 5で定義されているdevice-cmykによる色指定をサポートするPostCSSプラグインです。関数を単独で使用する場合に限らず、border: 1mm solid device-cmyk(0 1 0 0);のように他のプロパティ値の一部として使用するケースにも対応しています。

css/src/style.css
@page {
    size: A5;
}

h1 {
    color: device-cmyk(0 0 0 1);
    border: 1mm solid device-cmyk(0 1 0 0);
    border-radius: 5mm;
    padding: 10px;
    font-size: 7mm;
}

p {
    color: device-cmyk(0 0 0 1);
}

a {
    color: device-cmyk(1 0 0 0);
}

code {
    background-color: device-cmyk(0 0 0 25%);
}

strong {
    text-decoration: underline;
    text-decoration-color: device-cmyk(0, 0, 1, 0);
    text-decoration-thickness: 3px;
    text-underline-offset: 3px;
}
変換結果
css/dest/style.css
@page {
    size: A5;
}

h1 {
    color: rgb(35 25 22);
    border: 1mm solid rgb(228 0 127);
    border-radius: 5mm;
    padding: 10px;
    font-size: 7mm;
}

p {
    color: rgb(35 25 22);
}

a {
    color: rgb(0 161 233);
}

code {
    background-color: rgb(211 211 212);
}

strong {
    text-decoration: underline;
    text-decoration-color: rgb(255 241 0);
    text-decoration-thickness: 3px;
    text-underline-offset: 3px;
}

image.png

同時に、postcss.config.jsで指定したパスに復元情報を出力します。この復元情報は、RGBをキーとしてCMYKを関連付けた単純なJSONファイルです。キー自体がJSON形式である点に注意してください。JSONを処理できる環境であれば、キーのJSON文字列からオブジェクトを復元することもまた可能なはずであり、これは妥当な手段と考えています。

css/dest/device-cmyk.restore.json
{
  "[35,25,22]": [0, 0, 0, 1],
  "[228,0,127]": [0, 1, 0, 0],
  "[0,161,233]": [1, 0, 0, 0],
  "[211,211,212]": [0, 0, 0, 0.25],
  "[255,241,0]": [0, 0, 1, 0]
}

Scribusを使用したCMYKへの復元

さらなる調査と加筆が必要です。InDesignでも検証したい。

$ npm run buildで生成されるPDFは、特に変換を施していないRGB形式のPDFです。このPDFをCMYK形式に復元するには、生成されたJSONをもとにInDesignやScribusを使用する必要があります。比較的信頼性の高い方法はInDesignですが、私が所持していないので、ここではScribusを使用する方法を紹介します。

  1. ファイル > 設定 > カラーマネジメント > Document Options > Activate Color Managementを有効化。よしなに設定image.png
  2. ファイル > 開くからPDFを開く。Import Text As Vectorsで問題なしimage.png
  3. スクリプト > スクリプトを実行で以下のPythonスクリプトを実行する
  4. 適切な設定でエクスポートする
restore_cmyk.py
import importlib
import json
import typing


class ScribusModule(typing.Protocol):
    def fileDialog(
        self,
        caption: str,
        filter: str = ...,
        defaultname: str = ...,
        haspreview: bool = ...,
        issave: bool = ...,
        isdir: bool = ...,
    ) -> str: ...
    def getColorNames(self) -> "list[str]": ...
    def getColorAsRGB(self, name: str) -> "tuple[int, int, int]": ...
    def changeColorCMYKFloat(
        self, name: str, c: float, m: float, y: float, k: float
    ) -> None: ...


scribus = typing.cast(ScribusModule, importlib.import_module("scribus"))


def main():
    json_path = scribus.fileDialog("Open", "JSON (*.json)")
    if json_path == "":
        # Cancel
        return
    with open(json_path, encoding="utf-8") as f:
        restoration_table = typing.cast(
            "dict[tuple[int, int, int], tuple[float, float, float, float]]",
            {tuple(json.loads(k)): tuple(v) for k, v in json.loads(f.read()).items()},
        )

    colors_from_pdf = {
        name: scribus.getColorAsRGB(name)
        for name in scribus.getColorNames()
        if name.startswith("FromPDF")
    }
    for name, color in colors_from_pdf.items():
        # FIXME:
        # try:
        c, m, y, k = restoration_table[color]
        # except KeyError:
        #     # It seems that Vivliostyle (or Chromium) internally uses rgb(0, 0, 0)
        #     if color == (0, 0, 0):
        #         c, m, y, k = 1, 1, 1, 1
        #     else:
        #         raise
        scribus.changeColorCMYKFloat(name, c * 100, m * 100, y * 100, k * 100)


if __name__ == "__main__":
    main()

image.png

補遺

言うまでもありませんが、このアイデアですべての場合に対応するのは困難です。例えばこんな問題があるかもしれません。

色数の制約

必ず元のCMYK値に復元できるよう、このPostCSSプラグインでは、どんなに近いCMYK値であっても異なるRGB値にマッピングします。たとえば、device-cmyk(1 0 0 0)device-cmyk(0.999 0 0 0)device-cmyk(0.998 0 0 0) ……のように使用すると、ビルド時間が延び、プレビュー時点での再現度も低下します。当然ながら、256^3色を超える指定はできません。ただし、手作業で指定する程度の個数であれば、まったく問題ありません。

図版

SVGで使用した色はScribus上で同様に列挙されているので、変換先のRGBのみを使用して図版を作成すればよいでしょう。

/* SVGで使う色をCSSに含めておく */
:root {
    --cmyk-1-0-0-0: device-cmyk(1 0 0 0);
    --cmyk-0_8-0-0-0: device-cmyk(0.8 0 0 0);
    --cmyk-0_6-0-0-0: device-cmyk(0.6 0 0 0);
    --cmyk-0_4-0-0-0: device-cmyk(0.4 0 0 0);
    --cmyk-0_2-0-0-0: device-cmyk(0.2 0 0 0);

    --cmyk-0-0-0-1: device-cmyk(0 0 0 1);
    --cmyk-0-0-0-0_8: device-cmyk(0 0 0 0.8);
    --cmyk-0-0-0-0_6: device-cmyk(0 0 0 0.6);
    --cmyk-0-0-0-0_4: device-cmyk(0 0 0 0.4);
    --cmyk-0-0-0-0_2: device-cmyk(0 0 0 0.2);
}

/* 変換後のRGBカラーのみを使用してSVG図版を作成する */
:root {
    --cmyk-1-0-0-0: rgb(0 161 233);
    --cmyk-0_8-0-0-0: rgb(0 175 236);
    --cmyk-0_6-0-0-0: rgb(85 195 241);
    --cmyk-0_4-0-0-0: rgb(159 217 246);
    --cmyk-0_2-0-0-0: rgb(211 237 251);

    --cmyk-0-0-0-1: rgb(35 25 22);
    --cmyk-0-0-0-0_8: rgb(89 87 87);
    --cmyk-0-0-0-0_6: rgb(137 137 137);
    --cmyk-0-0-0-0_4: rgb(181 181 182);
    --cmyk-0-0-0-0_2: rgb(220 221 221);
}

ラスター画像は手動で貼り替える以外に良い方法が思いつきません。何らかのメタデータを含められたら自動で貼り替えられるかもしれません。

画面キャプチャや写真は、元画像がCMYKである可能性は低いため、4Cであれば自動変換で妥当な結果を得られるのではないでしょうか。1~3Cの場合は体感としてモノクロで掲載することが多いことから、こちらも自動変換で十分対応できるかもしれません。シンプルなイラストの類については、ベクター画像への変換を許容する方が実用的かもしれません。

グラデーションなど

linear-gradientなどでdevice-cmykを使用すれば変換は行われますが、InDesignやScribus上でどのように取り込まれるのか、また操作可能なのかは不明です。仮に自動でCMYKに変換できたとしても、たとえば2色刷りでdevice-cmyk(1 0 0 0)device-cmyk(0 0 0 0)を意図したグラデーションの中間色にシアン以外の色が含まれないとは限りません。結局、手動で貼り替える必要があるのではないでしょうか。

トンボ

Vivliostyleのトンボは、すべての版に何らかが表示されるよう、あえてblackではなくなっているということです2

高度なトンボを付けたいなら、Vivliostyleからは「塗り足しあり・トンボなし」で出力して、InDesignやScribusでトンボを付けたほうがよさそうです。

  1. 参考:https://print-css.rocks/lesson/lesson-cmyk

  2. https://github.com/vivliostyle/vivliostyle.js/issues/910

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?