1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VivliostyleでType 3フォントが埋め込まれる条件

1
Posted at

執筆時点の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_FontkOther_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として埋め込まれることが確認できます。

vf-otf.md
<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
vf-ttf.md
<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フォントが埋め込まれます。

static-otf-cff.md
<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
static-ttf-instancer.md
<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がなくても、AFDKOotf2ttf1で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
static-ttf-cu2qu.md
<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にフォールバックされます。ここでは太字の生成が該当します。

static-ttf-bold.md
<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
  1. fontToolsのリポジトリにも同名のスクリプトがあります。コードや履歴から、AFDKO版はfontToolsからフォークして機能を追加し、CLIで実行できるようにしたもののようです。fontTools版は個別に提供されていないので、次のように使用します。uv run --with fonttools "https://raw.githubusercontent.com/fonttools/fonttools/refs/tags/4.63.0/Snippets/otf2ttf.py"

  2. もっとも、Noto CJKはadobe-fonts/source-han-sansのPostScriptソースがマスタであり、TrueType版自体が(おそらく)otf2ttfで生成されたものです。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?