執筆時点のVivliostyle、というよりChrome/Chromiumでは、特定の条件でType 3フォントが埋め込まれる仕様になっています。本稿ではこの条件について掘り下げます。Skiaのコード参照は99c18e9172481690307d092eb5a70520049e3728に固定しています。また執筆時点のVivliostyle(cli: 11.0.1 / core: 2.43.1)がデフォルトでダウンロードするChrome for Testingのバージョンは149.0.7827.22です。
サブセット埋め込み形式の分岐はL934-946に存在します。このfFontTypeについて、Type 3が関係する箇所をかいつまんで追跡します。
void SkPDFFont::emitSubset(SkPDFDocument* doc) const {
switch (fFontType) {
case SkAdvancedTypefaceMetrics::kType1CID_Font:
case SkAdvancedTypefaceMetrics::kTrueType_Font:
return emit_subset_type0(*this, doc);
#ifndef SK_PDF_DO_NOT_SUPPORT_TYPE_1_FONTS
case SkAdvancedTypefaceMetrics::kType1_Font:
return SkPDFEmitType1Font(*this, doc);
#endif
default:
return emit_subset_type3(*this, doc);
}
}
フォント単位でL331-352の判定が行われます。可変フォントや埋め込み禁止などのほかに、kCFF_FontもkOther_Font、つまりdefaultのType 3になることがわかります。ここでkCFF_Fontとは、アウトラインをCFF / CFF2、すなわちPostScript由来の3次ベジェ曲線で持つOpenTypeフォントを指します。コメントにある通り、このケースについて以前はType 0+CIDFontType0形式の埋め込みが実装されており、最終的に断念された経緯があります。詳細は冒頭のVivliostyle.jsのIssueを参照してください。
SkAdvancedTypefaceMetrics::FontType SkPDFFont::FontType(const SkPDFStrike& pdfStrike,
const SkAdvancedTypefaceMetrics& metrics) {
if (SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kVariable_FontFlag) ||
// PDF is actually interested in the encoding of the data, not just the logical format.
// If the TrueType is actually wOFF or wOF2 then it should not be directly embedded in PDF.
// Export these as Type3 if the subsetter cannot handle table based fonts.
(SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kAltDataFormat_FontFlag)
&& !SkPDFCanSubsetTableBasedFonts()) ||
SkToBool(metrics.fFlags & SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag) ||
// Something like 45eeeddb00741493 and 7c86e7641b348ca7b0 to output OpenType should work,
// but requires PDF 1.6 which is still not supported by all printers. One could fix this by
// using bare CFF like 31a170226c22244cbd00497b67f6ae181f0f3e76 which is only PDF 1.3,
// but this only works when the CFF CIDs == CFF index == GlyphID as PDF bare CFF prefers
// CFF CIDs instead of GlyphIDs and Skia doesn't know the CIDs.
metrics.fType == SkAdvancedTypefaceMetrics::kCFF_Font ||
pdfStrike.fHasMaskFilter)
{
// force Type3 fallback.
return SkAdvancedTypefaceMetrics::kOther_Font;
}
return metrics.fType;
}
グリフ単位でもL373-377の判定が行われ、輪郭パスが加工されている場合はType 3になります。
// Keep the type (and original data) if the glyph is empty or the glyph has an unmodified path.
// Otherwise, fall back to Type3.
if (!(glyph->isEmpty() || (glyph->path() && !glyph->pathIsModified()))) {
type = SkAdvancedTypefaceMetrics::kOther_Font;
}
実際にVivliostyleで再現してみます。Google Fontsでも配布されているNoto Serif CJKのアップストリームでは、様々な形式でフォントが配布されています。
まずは可変フォントを使用した場合です。Noto SerifではOTF(CFF2)とTTFの2種類の可変フォントが提供されており、どちらもType 3として埋め込まれることが確認できます。
<style>
@font-face {
font-family: "Noto Serif CJK JP";
src: url(noto-cjk/02_NotoSerifCJK-OTF-VF/Variable/OTF/NotoSerifCJKjp-VF.otf);
}
p {
font-family: "Noto Serif CJK JP";
}
</style>
あのイーハトーヴォのすきとおった風
$ npx --yes @vivliostyle/cli@11.0.1 build vf-otf.md --output vf-otf.pdf
$ pdffonts vf-otf.pdf
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 4 0
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 5 0
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 6 0
<style>
@font-face {
font-family: "Noto Serif CJK JP";
src: url(noto-cjk/03_NotoSerifCJK-TTF-VF/Variable/TTF/NotoSerifCJKjp-VF.ttf);
}
p {
font-family: "Noto Serif CJK JP";
}
</style>
あのイーハトーヴォのすきとおった風
$ npx --yes @vivliostyle/cli@11.0.1 build vf-ttf.md --output vf-ttf.pdf
$ pdffonts vf-ttf.pdf
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 4 0
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 5 0
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 6 0
静的なOpenType版でも、Type 3フォントが埋め込まれます。
<style>
@font-face {
font-family: "Noto Serif CJK JP";
src: url(noto-cjk/05_NotoSerifCJKOTF/OTF/Japanese/NotoSerifCJKjp-Regular.otf);
}
p {
font-family: "Noto Serif CJK JP";
}
</style>
あのイーハトーヴォのすきとおった風
$ npx --yes @vivliostyle/cli@11.0.1 build static-otf-cff.md --output static-otf-cff.pdf
$ pdffonts static-otf-cff.pdf
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+NotoSerifCJKjp-Regular Type 3 Custom yes yes yes 4 0
AAAAAA+NotoSerifCJKjp-Regular Type 3 Custom yes yes yes 5 0
AAAAAA+NotoSerifCJKjp-Regular Type 3 Custom yes yes yes 6 0
先に紹介した通り、このケースについては特定バージョンのChromeを使用することで以前の埋め込み実装を再現できます。ただしChrome 129は2024年9月にリリースされた版であり、執筆時点ですでに2年近く前のバージョンです。具体的に例を挙げられるわけではないものの、その後に修正されたバグや追加された機能があるはずです。129のまま塩漬けするのは前向きな対処とは言えなさそうです。
$ npx --yes @vivliostyle/cli@11.0.1 build static-otf-cff.md --output static-otf-cff-chrome129.pdf --browser chrome@129
$ pdffonts static-otf-cff-chrome129.pdf
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+NotoSerifCJKjp-Regular CID Type 0C (OT) Identity-H yes yes yes 4 0
Type 3を回避しType 0+CIDFontType2サブセットが埋め込まれる例も紹介します。執筆時点ではNoto CJKのリリースには静的なTrueType版はありませんが、可変フォントの軸を固定して生成できます。
$ uv run --with fonttools fonttools varLib.instancer noto-cjk/03_NotoSerifCJK-TTF-VF/Variable/TTF/NotoSerifCJKjp-VF.ttf wght=400 -o NotoSerifCJKjp-Regular-static.ttf
<style>
@font-face {
font-family: "Noto Serif CJK JP";
src: url(NotoSerifCJKjp-Regular-static.ttf);
}
p {
font-family: "Noto Serif CJK JP";
}
</style>
あのイーハトーヴォのすきとおった風
$ npx --yes @vivliostyle/cli@11.0.1 build static-ttf-instancer.md --output static-ttf-instancer.pdf
$ pdffonts static-ttf-instancer.pdf
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+NotoSerifCJKjp-ExtraLight CID TrueType Identity-H yes yes yes 4 0
可変TrueTypeがなくても、AFDKOのotf2ttf1でCFFベースのOpenTypeからTrueTypeに変換することは可能です。近似変換なので印刷用途では--max-errorをできるだけ小さく設定して精度を上げるとよさそうです。0.1では先述の軸固定版フォントよりファイルサイズが小さく、0.01で超えたため十分と判断しました2。ヒンティング情報は消失しますが、印刷用途では関係ありません。変換自体の不具合を考慮するなら、u1f992/pdfdiffのようなツールである程度影響を可視化できます。
$ uv run --with afdko==5.0.1 otf2ttf --help
usage: otf2ttf [-h] [-o OUTPUT] [-e MAX_ERROR]
[--post-format POST_FORMAT]
[--keep-direction] [--face-index FACE_INDEX]
[--overwrite]
INPUT [INPUT ...]
positional arguments:
INPUT
options:
-h, --help show this help message and exit
-o OUTPUT, --output OUTPUT
-e MAX_ERROR, --max-error MAX_ERROR
--post-format POST_FORMAT
--keep-direction
--face-index FACE_INDEX
--overwrite
$ uv run --with afdko==5.0.1 otf2ttf noto-cjk/05_NotoSerifCJKOTF/OTF/Japanese/NotoSerifCJKjp-Regular.otf --max-error 0.01 --output NotoSerifCJKjp-Regular-cu2qu.ttf
<style>
@font-face {
font-family: "Noto Serif CJK JP";
src: url(NotoSerifCJKjp-Regular-cu2qu.ttf);
}
p {
font-family: "Noto Serif CJK JP";
}
</style>
あのイーハトーヴォのすきとおった風
$ npx --yes @vivliostyle/cli@11.0.1 build static-ttf-cu2qu.md --output static-ttf-cu2qu.pdf
$ pdffonts static-ttf-cu2qu.pdf
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+NotoSerifCJKjp-Regular CID TrueType Identity-H yes yes yes 4 0
埋め込まれるはずのフォントを使っても輪郭の改変が行われるとType 3にフォールバックされます。ここでは太字の生成が該当します。
<style>
@font-face {
font-family: "Noto Serif CJK JP";
src: url(NotoSerifCJKjp-Regular-static.ttf);
}
p {
font-family: "Noto Serif CJK JP";
font-weight: bold;
}
</style>
あのイーハトーヴォのすきとおった風
$ npx --yes @vivliostyle/cli@11.0.1 build static-ttf-bold.md --output static-ttf-bold.pdf
$ pdffonts static-ttf-bold.pdf
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 4 0
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 5 0
AAAAAA+NotoSerifCJKjp-ExtraLight Type 3 Custom yes yes yes 6 0
-
fontToolsのリポジトリにも同名のスクリプトがあります。コードや履歴から、AFDKO版はfontToolsからフォークして機能を追加し、CLIで実行できるようにしたもののようです。fontTools版は個別に提供されていないので、次のように使用します。
uv run --with fonttools "https://raw.githubusercontent.com/fonttools/fonttools/refs/tags/4.63.0/Snippets/otf2ttf.py"↩ -
もっとも、Noto CJKはadobe-fonts/source-han-sansのPostScriptソースがマスタであり、TrueType版自体が(おそらく)
otf2ttfで生成されたものです。 ↩