WOFF2 の glyf 変換の復元

背景(実測で特定)

Font Awesome のアイコンが箱(☐)として描かれていた。

静的試験(tests/src/test_sugi_lab_cascade.rs)で以下を確定済み:

カスケードは正しく、☐ はフォントから描かれた字形そのもの。

原因

WOFF2 のテーブル変換の判定が仕様と逆だった。

テーブル version 0 version 3
glyf / loca 変換あり 変換なし
その他 変換なし 変換あり

実装は「0 = 変換なし」と決め打ちしていたため:

  1. 変換された glyf を「変換なし」と誤判定し、 transformLength を読まずに元長で切り出す → ストリーム上の位置がそこから全部ずれる
  2. 変換の復元処理そのものが無い → 変換済みバイト列をそのまま SFNT の glyf として書く

head / cmap は無事なのでフォントはパースに成功し glyph_index() も引けるが、輪郭が壊れているので箱になる。

仕様(WOFF2 §5.1 glyf transform)

変換された glyf は 7 本のストリームの連結として格納される。 先頭に固定長ヘッダがある。

version           u32   (予約。0)
numGlyphs         u16
indexFormat       u16   (復元後の loca の形式。0=short, 1=long)
nContourStreamSize    u32
nPointsStreamSize     u32
flagStreamSize        u32
glyphStreamSize       u32
compositeStreamSize   u32
bboxStreamSize        u32
instructionStreamSize u32

続いて各ストリームがこの順で並ぶ。

各グリフの復元

nContourStream から輪郭数 nContours を読む(i16)。

単純グリフ:

  1. nPointsStream から輪郭ごとの点数を 255UInt16 で読む
  2. 総点数ぶん flagStream から 1 バイトずつフラグを読む (最上位ビットが「x/y の符号と幅」の三角符号化に使う)
  3. glyphStream から座標を三角符号化で読む
  4. instructionStream から命令列を読む(長さは glyphStream の 255UInt16)
  5. 標準の glyf グリフ(endPtsOfContours / instructions / flags / xCoordinates / yCoordinates)を組み立てる

bbox

bboxStream は先頭に ceil(numGlyphs/8) バイトのビットマップを持ち、 立っているグリフだけ 4 個の i16(xMin/yMin/xMax/yMax)が続く。 立っていない単純グリフは座標から計算する。

不変条件

モジュール

kernel/woff2_glyf.rs(純粋・no_std)

/// 変換された glyf ストリームから (glyf, loca) を復元する。
pub fn reconstruct_glyf(data: &[u8]) -> Option<(Vec<u8>, Vec<u8>, u16)>;

/// 255UInt16(WOFF2 の可変長整数)
pub fn read_255_u16(data: &[u8], pos: &mut usize) -> Option<u16>;