BDF (Browser-specific Document Format) 仕様 v1.0

状態: 安定版。この仕様 v1.0 は BDF wire format version 1 と命令セット opset 1 を定義する。wire format と opset の番号は独立して進める。Go の module/import 名 github.com/shibukawa/bdf はこの仕様名と独立しており、変更しない。

この仕様への適合と互換性

この文書で 必須、禁止、推奨、してもよい は、それぞれ規範要件を表す。適合する書き手は必須の出力規則を満たし、適合する読み手は必須の入力規則を満たす。

この仕様はワイヤ形式を定める。Go の converter API、コマンド、ビューア UI の互換性は別の API 契約である。

1. 目的と非目的

目的

非目的

2. 用語

用語 意味
Part コンテナに格納される1つのバイト列。内容のハッシュで識別される。
Object 描画命令列を含む Part。ページ本体・マスター・シートのタイルなど、描けるものはすべて Object。
View 閲覧単位。スライド集、シート、文書、図のページなど。1 BDF に複数の View を持てる(Excel の複数シート、draw.io の複数ページ等)。
Page 固定サイズの矩形。fixed / flow View を構成する。
Tile sheet View を格子状に分割した矩形。1 Tile = 1 Object。
Resource Object から参照される Font / Image / Path / Paint。

3. 全体構造

BDF
├── manifest (JSON)               … View 一覧、ページ寸法、Part 一覧
└── parts/<hash>                  … 内容アドレスの Part 群
    ├── Object(描画命令列)
    ├── Font(WOFF2 等、そのままのバイト列)
    ├── Image(PNG/JPEG/WebP/AVIF/SVG など、そのままのバイト列)
    ├── Path collection(パス群のバイナリ)
    ├── Index(大きなページ表・タイル表・テキスト索引・再生の cue)
    └── Seq(View が演奏する音楽。Standard MIDI File)

3.1 内容アドレス(content addressing)

3.2 圧縮

3.3 1ファイル形式(single)

先頭にディレクトリを置く。ストリーミング読み(fetch().body を先頭から読み進めながら描画開始)と Range 取得の両方に対応する。

offset 0   : magic          u8[4]   "bdf\0"(62 64 66 00)
offset 4   : version        u16     フォーマットバージョン(1)
offset 6   : flags          u16     bit 0: 暗号化(§3.5)。他のビットは予約(0)
offset 8   : manifestOff    u64     manifest の先頭オフセット(通常 32)
offset 16  : manifestLen    u64     manifest の圧縮後長
offset 24  : manifestEnc    u8      0=identity, 1=deflate-raw
offset 25  : reserved       u8[7]
offset 32  : manifest
offset ... : parts 領域(manifest 直後から始まり、manifest.parts に記載された順に連続配置)

3.4 分割形式(split)

<base>/manifest.json
<base>/parts/<hash>

3.5 暗号化

パスワードで保護された入力(Office の読み取りパスワード、PDF のユーザーパスワード)から作った文書は、同じパスワードで暗号化する。Part を 1 つずつ封印するので、Range 取得・split 形式・CDN への配置はそのまま使える。

外側の manifest(平文)  … 暗号方式、鍵スロット、封印された Part の一覧
封印された Part
├── manifest             … 文書の manifest(§4。views、meta、parts)
└── 各 Part              … Part の格納バイト列(圧縮済み)を暗号化したもの

外側の manifest

{
  "bdf": 1,
  "encryption": {
    "cipher": "A256GCM",
    "keys": [
      { "type": "password", "kdf": "PBKDF2-SHA256", "iter": 600000,
        "salt": "<base64, 16 バイト>",
        "key": "<base64: AES-KW でラップしたコンテンツ鍵, 40 バイト>" }
    ],
    "manifest": { "part": "<hash>", "enc": "deflate-raw" }   // 封印された manifest とその符号化
  },
  "parts": [
    { "h": "<hash>", "t": "sealed", "enc": "identity", "len": 1474, "size": 1474, "off": 0 },
    …
  ]
}

鍵

ecdh スロット: 1 人の読み手の鍵ペアだけが開けるスロット。§3.6 の区間の文書を、要求ごとに読み手が作った鍵で封印するのに使う。

{ "type": "ecdh", "kdf": "HKDF-SHA256", "crv": "P-256",
  "epk": "<base64: 書き手の公開鍵, 非圧縮の点 65 バイト>",
  "key": "<base64: AES-KW でラップしたコンテンツ鍵, 40 バイト>" }

封印

封印された manifest

§4 の manifest と同じ形で、parts[] の各エントリに sealed(その Part を封印した外側の Part の名前)を持つ。off は使わず、外側のエントリの off/len で取得する。

読み手の手順は、外側の manifest を読む → パスワード(または鍵ペア)からラップ鍵を導いてコンテンツ鍵をアンラップ → encryption.manifest.part を復号・展開 → 以降は Part 名で引き、sealed が指す外側の Part を取得して復号・展開、となる。必要なのは WebCrypto の PBKDF2・AES-KW・AES-GCM(ecdh スロットではさらに ECDH・HKDF)だけ。コンテンツ鍵は抽出できない CryptoKey として Worker の中に置く。

守るもの・守らないもの

3.6 保護モード(区間の文書、segment)

ログインした読み手に本を読ませるサイトのように、サーバーが文書を持ち、読み手には開いたページのあたりだけを数ページずつ渡す使い方のための形。区間の文書は、ある View の連続したページ(区間)だけを運ぶ、それ自体は普通の BDF である。

{
  "bdf": 1, "opset": 1, "unit": "pt", "meta": { … },
  "segment": { "view": "pages", "from": 10, "to": 20 },   // 運ぶページ [from, to)(0 始まり)
  "views": [ { "id": "pages", "kind": "fixed",
               "pages": [ { "w": 419.5, "h": 595.3, "layers": [] },   // 区間の外: 大きさだけ
                          …,
                          { "w": 419.5, "h": 595.3, "layers": [ { "role": "body", "obj": "…" } ] },   // 区間のページ
                          … ] } ],
  "parts": [ … ]   // 区間のページが使う Part のうち、読み手がまだ持っていないもの
}

4. Manifest

JSON。読みやすさとツールでの扱いやすさを優先する。巨大になる部分(ページ表・タイル表)は Index Part に逃がす。

{
  "bdf": 1,                     // wire format version
  "opset": 1,                   // Object 命令セット(§7)
  "unit": "pt",                 // 1 unit = 1/72 inch。座標はすべてこの単位
  "meta": {                     // §4.3
    "dc": { "title": "…", "creator": ["…", "…"], "language": "ja", "created": "2026-09-25T07:21:16+09:00" },
    "source": "pptx", "generator": "bdf-go/0.1" },

  "views": [
    { "id": "slides", "kind": "fixed", "title": "スライド",
      "pages": [ { "w": 960, "h": 540, "layers": [
          { "role": "master", "obj": "3f9a…" },
          { "role": "body",   "obj": "b1c2…" } ] },
        … ] },

    { "id": "doc", "kind": "flow", "title": "本文",
      "pages": [ { "w": 595.3, "h": 841.9,
                   "body": { "x": 72, "y": 72, "w": 451.3, "h": 697.9 },
                   "layers": [
          { "role": "header", "obj": "…" },
          { "role": "body",   "obj": "…" },
          { "role": "footer", "obj": "…" } ] }, … ],
      "continuous": { "gap": 24 },
      "textIndex": "<hash>" },                       // 任意: テキスト索引 Part(§7.9)

    { "id": "scroll", "kind": "scroll", "title": "本文",
      "pages": [ { "w": 523.3, "h": 1014.2, "layers": [ { "role": "body", "obj": "…" } ] },
                 { "w": 523.3, "h": 988.5,  "layers": [ { "role": "body", "obj": "…" } ] }, … ] },

    { "id": "sheet1", "kind": "sheet", "title": "Sheet1",
      "tile": 2048,
      "cols": [[26, 64], [1, 120], [16358, 64]],   // [count, size] のランレングス
      "rows": [[1048576, 20]],
      "freeze": { "cols": 1, "rows": 1 },
      "gridlines": true,
      "tiles": { "0,0": "…", "1,0": "…" }           // 小さければインライン
      // "tilesRef": "<hash>"                        // 大きければ Index Part
    }
  ],

  "parts": [
    { "h": "3f9a…", "t": "obj",  "enc": "deflate-raw", "len": 1234, "size": 5678, "off": 0 },
    { "h": "c0ff…", "t": "font", "enc": "identity",    "len": 40210, "size": 40210, "off": 1234 },
    …
  ]
}

平文の完全な manifest は bdf: 1 と opset: 1 を持つ。暗号化文書の外側の manifest は bdf: 1 と暗号化情報、封印された Part 一覧だけを持ち、opset と views は封印された内側の manifest に置く。manifest の opset は読み手の早期判定用の補助情報で、省略または 0 なら読み手は Object ごとの値を使う。読み手は未知の任意メンバーを無視してよいが、この仕様で必須と定めたメンバーの欠落や不正値を受け入れてはならない。

parts[].t の値: obj / font / img / path / idx / seq / audio(§4.4)/ tpl と src(§4.5)。暗号化した文書の外側の manifest では sealed(§3.5)。読み手は、どの View や Object からも参照されない Part を読み込まなくてよい。知らない t の Part も同じで、文書を開く妨げにしてはならない。

数値の範囲 — manifest の数値は、読み手のループの回数と確保する大きさ(範囲に入る Tile、シートのグリッド線、連続モードの帯)を決める。読み手は文書を開くときに一度だけ確かめ、範囲の外の値を持つ文書は不正として開かない。

4.1 View の種類

fixed — 固定サイズページの列。ページごとに w,h が異なってよい。ビューアはページを縦または横に並べる、あるいは 1 ページずつ表示する。

flow — fixed に加え、各ページが body(余白を除いた本文矩形)を持ち、レイヤーに役割(role)が付く。ビューアは 2 つの表示モードを提供できる。

連続モードは再レイアウトではなく本文矩形の積み上げである(BDF は表示リストであり、テキストの折り返し位置は変えられない)。「1 枚の長いページとして再レイアウトしたもの」も欲しい場合、変換側が別 View(kind: scroll)を追加で生成する。共有 Object により画像やフォントは二重に格納されない。

scroll — ページを持たない縦に長い 1 枚の面(Word の下書き・Web レイアウト表示のように、用紙の高さを無限にしてレイアウトした文書)。pages はこの面を上から順に水平に切った帯(strip)で、形は fixed のページと同じ(w、h、layers)。

sheet — 原点 (0,0) から右下に伸びる無限平面。

複数の View — views の順が表示の順。View が 2 つ以上あるとき、ビューアは表計算ソフトのシート見出しのように View をタブで並べて切り替える(Excel のシート、draw.io のページ)。View の id は文書内で一意な任意の文字列で、LINK の #view= で参照される(§7.7)。

4.2 レイヤーの役割(role)

background / master / header / footer / body / notes / annotation。ビューアは役割ごとの表示切替に使える。未知の role は body として扱う。

4.3 メタデータ(meta)

キー 意味
dc 文書そのものの記述。Dublin Core(下記)
source 変換元の形式(pdf / ai / psd / pptx / xlsx / csv / parquet / vsdx / drawio / dxf / jww / sfc / p21 / cgm / hpgl / gerber / kicad / emf / wmf / tiff / png / jpeg / gif / webp / avif / bmp / ico / svg / html / markdown / epub / mml / midi / musicxml / font / mp3 / m4a / aac / flac / ogg / wav / aiff / fixture …)。Dublin Core の source とは別物
generator 書き出したソフトウェア(例 bdf-go/0.1)

meta.dc は Dublin Core Metadata Element Set 1.1 の 15 要素に、DCMI Metadata Terms の created と modified を加えたもの。キーは要素名(名前空間接頭辞なし)。

"dc": {
  "title": "四半期報告",
  "creator": ["山田太郎", "佐藤花子"],
  "subject": ["売上", "予算"],
  "language": "ja",
  "created": "2026-09-25T07:21:16+09:00"
}
要素 内容 推奨する書き方
title 題名
creator 作成者 人・組織ごとに 1 値
subject 主題・キーワード キーワードごとに 1 値
description 概要
publisher 発行者
contributor 寄与者
date 何らかの日付(発行日など) W3CDTF(ISO 8601 の部分集合)。2026、2026-09、2026-09-25、2026-09-25T07:21:16+09:00
type 種類 DCMI Type Vocabulary(Text、Image など)
format 形式 MIME タイプ
identifier 識別子 URI、ISBN、DOI など
source 派生元の資源
language 言語 BCP 47(ja、en-US)。最初の値が文書のテキストの既定の言語(§7.8 の LANG で空の run の言語)
relation 関連する資源
coverage 範囲(場所・期間)
rights 権利
created 作成日時 date と同じ書き方
modified 更新日時 date と同じ書き方

変換器は入力文書のメタデータを次のように写す。

要素 PDF(文書情報辞書) PowerPoint、Excel、Word(コアプロパティ) draw.io TIFF(先頭のページのタグ) EPUB(パッケージ文書)
title Title dc:title – DocumentName dc:title(EPUB 3 の title-type が main のものが先)
creator Author dc:creator – Artist(; で分割) dc:creator
subject Keywords(, ; 、 などで分割) dc:subject、cp:keywords(同様に分割) – – dc:subject
description Subject dc:description – ImageDescription dc:description(HTML ならその文字列)
identifier – dc:identifier – – dc:identifier(unique-identifier が指すものが先)
language – dc:language(なければ PowerPoint は既定のテキストスタイルの、Word は既定の run の言語。Word は本文の多くが和文なら東アジアの言語) – – dc:language
rights – – – Copyright dc:rights
created CreationDate(W3CDTF に変換) dcterms:created – – EPUB 2 の opf:event="creation" の dc:date
modified ModDate(W3CDTF に変換) dcterms:modified mxfile の modified DateTime(W3CDTF に変換) dcterms:modified の meta(EPUB 2 は opf:event="modification" の dc:date)

EPUB はほかの Dublin Core の要素(publisher、contributor、date、type、format、source、relation、coverage)もそのまま写す。

PDF と TIFF の対応は、XMP が文書情報辞書と TIFF のタグを写す方法に合わせている(TIFF の DocumentName は XMP にないので題名にした)。

画像(ブラウザがそのまま表示できる画像をそのまま格納したもの)は、XMP、EXIF、IPTC、形式自身の情報の順に、要素ごとに先にあるものを使う。XMP(SVG では metadata 要素の RDF)の dc:* はその要素に写す(rdf:Alt は既定の言語の値)。Photoshop の文書の XMP も同じ読み方をする。

要素 XMP EXIF IPTC 形式自身
title dc:title XPTitle Object Name(なければ Headline) PNG の Title、SVG の title 要素
creator dc:creator(なければ tiff:Artist) Artist(なければ XPAuthor。; で分割) By-line PNG の Author
subject dc:subject XPKeywords(; で分割) Keywords –
description dc:description(なければ tiff:ImageDescription) ImageDescription、XPSubject、XPComment、UserComment の順(カメラが既定で書く文字列は除く) Caption/Abstract PNG の Description(なければ Comment)、GIF のコメント、SVG の desc 要素
rights dc:rights(なければ tiff:Copyright) Copyright(撮影者と編集者) Copyright Notice PNG の Copyright
language dc:language – – SVG のルートの xml:lang(なければ lang)
created dcterms:created、photoshop:DateCreated、exif:DateTimeOriginal(なければ xmp:CreateDate、exif:DateTimeDigitized) DateTimeOriginal(なければ DateTimeDigitized)と OffsetTime* Date Created と Time Created PNG の Creation Time
modified xmp:ModifyDate、dcterms:modified(なければ tiff:DateTime) DateTime と OffsetTime – PNG の tIME

音声ファイル(MP3、M4A、AAC、FLAC、Ogg、WAV、AIFF)はタグを次のように写す(MP4 の uuid ボックスの XMP があれば、画像と同じくそれが先)。type は常に Sound、format はメディアタイプ(audio/mpeg など)。1 つのフレームやフィールドに入った複数の値(ID3v2.4 の NUL 区切り、セミコロン区切り)は複数の値にする。

要素 ID3v2 MP4(iTunes の ilst) Vorbis コメント(FLAC、Ogg) RIFF INFO(WAV)、AIFF
title TIT2 ©nam TITLE INAM、NAME
creator TPE1 ©ART ARTIST IART、AUTH
contributor TPE2(アルバムアーティスト)、TCOM、TEXT、TPE3、TPE4 aART、©wrt ALBUMARTIST、COMPOSER、LYRICIST、CONDUCTOR、ARRANGER、REMIXER IMUS、IWRI
subject TCON(ID3v1 の番号は名前に) ©gen、gnre GENRE IGNR
description COMM(iTunes などプログラム自身のものは除く) ©cmt、desc COMMENT、DESCRIPTION ICMT、bext の説明、ANNO
publisher TPUB ----:…:LABEL PUBLISHER、LABEL、ORGANIZATION –
date TDRC、または TYER + TDAT + TIME、または TDRL ©day DATE ICRD、bext の作成日
identifier TSRC(ISRC) ----:…:ISRC ISRC –
relation TALB(アルバム) ©alb ALBUM IPRD
language TLAN(ISO 639-2 を BCP 47 に直す) トラックの mdhd LANGUAGE ILNG
rights TCOP cprt COPYRIGHT、LICENSE ICOP、(c)

bdf generate の -dc 要素名=値(繰り返し可)で要素を上書きでき、-dc 要素名= でその要素を消せる。

4.4 演奏(play)

View は鳴らすものを play に持てる。楽譜(MML、MIDI、MusicXML から組んだもの)の View はその音楽を、音声ファイルのカードの View はその録音を持つ。BDF に音楽の命令はなく、楽譜は線・パス・文字の描画になる(§7)。play は描画とは別に、ビューアが音を鳴らし、鳴っている位置をページの上に示すためのもの。

{ "id": "score", "kind": "fixed", "title": "楽譜",
  "pages": [ … ],
  "play": { "seq": "<hash>",      // t: seq の Part(Standard MIDI File)
            "cues": "<hash>" } }  // 任意: t: idx の cue 索引 Part

{ "id": "card", "kind": "fixed", "title": "曲名",
  "pages": [ … ],
  "play": { "audio": "<hash>",    // t: audio の Part(音声ファイルそのもの)
            "type": "audio/mpeg", // そのメディアタイプ
            "cues": "<hash>" } }  // 任意: t: idx の cue 索引 Part

play は seq か audio のどちらか一方を持つ。audio を知らない読み手はカードを描くだけで、音は鳴らさない。

seq Part — Standard MIDI File(SMF、フォーマット 0 か 1)のバイト列そのもの。MIDI の入力はそのファイルをそのまま格納し、MML と MusicXML は変換器が書き出す。ビューアは SMF を読み、テンポ(FF 51)に従って音を合成する(Web Audio API)。音色は General MIDI のプログラム番号(チャンネル 10 は打楽器)で、ビューアはそれに近い音で鳴らせばよい(音源は規定しない)。次のコントローラーも解釈してよい。

CC 意味
7 チャンネルの音量
10 パン
11 エクスプレッション
70 矩形波のデューティー比(プログラム 80 Square Lead のとき。0〜31 = 50%、32〜63 = 12.5%、64〜95 = 25%、96〜127 = 75%)。MML の矩形波の音色の区別

audio Part — 音声ファイル(MP3、M4A、FLAC、Ogg、WAV、AIFF など)のバイト列そのもの。タグもカバーアートも入力のまま残す。再エンコードも圧縮もしない(§3.2)。ビューアはデコードをブラウザに任せる(<audio> 要素)。type はそのファイルのメディアタイプで、audio/ で始まる。読み手は audio/ で始まらない type を使ってはならない(無視して、ブラウザに中身から判別させる)。どの符号化を鳴らせるかはブラウザによる(AIFF と Apple Lossless は Safari だけ)。鳴らせないファイルの View も、カードはそのまま描く。

cue 索引 Part — 演奏の時刻とページの上の位置を結ぶ表。

u8[4]   "BCUE"
u16     version (1)
varuint nSystems
system ×n:
  varuint page     View のページ番号(0 始まり)
  f32     x y w h  鳴っている位置が動く矩形(楽譜の段、歌詞の行。unit)
varuint nCues
cue ×n(時刻順):
  varuint dt       前の cue からの時刻の差(最初は 0 から)。単位は seq の SMF の tick(ヘッダーの division)、audio ではミリ秒
  varuint system   段の番号
  f32     x        その時刻に鳴っている位置の x(unit、ページの座標)

seq の cue:

audio の cue:

4.5 コンパイル済みテンプレート(tpl と src)

帳票のテンプレート(タグを書いた Excel のブック、入力欄のある PDF など)を検査して配るための、任意の Part。この 2 つを持つ文書をコンパイル済みテンプレートと呼び、拡張子は .bdft を使う。コンテナと manifest はふつうの文書と同じで、View はテンプレートをサンプルデータで穴埋めしたページ(サンプルがなければタグのままのページ)を見せる。したがってビューア・サムネイル・テキスト抽出は、テンプレートをふつうの文書として扱える。

{
  "version": 1,                 // この記述の版
  "format": "xlsx",             // src の形式("xlsx" / "docx" / "pptx" / "pdf" / "pdfme")
  "name": "invoice.xlsx",       // 元のファイル名(任意)
  "source": "cbd3…",            // src の Part のハッシュ
  "fields": [                   // タグが引く名前
    { "name": "customer", "kind": "value", "at": ["Invoice!A2"] },
    { "name": "items", "kind": "section", "mode": "rows", "at": ["Invoice!A4"] },
    { "name": "price", "kind": "value", "in": ["items"], "at": ["Invoice!D4"] } ],
  "sample": { … },              // ページが見せているサンプルデータ(任意)
  "schema": { … }               // データの JSON Schema(任意)
}

5. Object Part

描画命令列と、それが参照するリソース表を持つ自己完結した Part。ページ、マスター、Tile、部品はすべてこの形式。

u8[4]   "BOBJ"
u16     opset バージョン
u16     flags(予約)
f32×4   bbox (x, y, w, h)  … この Object が描く範囲。キャッシュ用。原点は Object 自身のローカル座標
varuint nStrings ; UTF-8 文字列 [ varuint len, bytes ]×n
varuint nPaths   ; パス [ u8 kind(0=inline,1=ext) , inline: パスデータ(§6.3) / ext: u8[16] hash, varuint index ]×n
varuint nPaints  ; グラデーション・パターン定義(§6.4)×n
varuint nFonts   ; フォント参照(§6.1)×n
varuint nImages  ; 画像参照 [ u8[16] hash ]×n
varuint nObjects ; 子 Object 参照 [ u8[16] hash ]×n
varuint opsLen   ; 命令列のバイト長
u8[opsLen]       ; 命令列(§7)

6. リソース

6.1 Font

u8      kind        0=embedded, 1=system
u8[16]  hash        embedded のときのみ(Font Part。WOFF2 推奨、TTF/OTF も可)
string  family      CSS フォントファミリー文字列(フォールバック列を含む)。embedded では空でよく、空でなければ埋め込みフォントの後ろにフォールバックとして付ける
u16     weight      100–900
u8      style       0=normal, 1=italic, 2=oblique

6.2 Image

Part の生バイト列。ブラウザが画像要素で表示できる形式(PNG / JPEG / GIF / WebP / AVIF / BMP / ICO / SVG)で、再エンコードしない。読み手はビットマップ画像を createImageBitmap(new Blob([bytes])) でデコードする。デコードした大きさが画像の画素の大きさで、EXIF の向き(JPEG、PNG)や AVIF の irot はブラウザが適用したものになる。

SVG(先頭が <。バイト順マークと空白は飛ばす。UTF-16 のバイト順マークで始まるものも)は createImageBitmap ではデコードできない(Blob からはビットマップ画像だけ)。読み手は画像要素(HTMLImageElement)で読み込み、Canvas に drawImage で描いてビットマップにする。拡大してもぼけないよう、描くたびに必要な画素数(現在の変換で画像の 1 画素が覆うデバイス画素)で描き直してよい。Worker には画像要素がないので、ページに描かせる。SVG は画像要素の中で描かれるので、スクリプトは動かず、外部の資源は読み込まれない。

画像のヘッダーは大きさを自分で言い、デコードするとその分のメモリを使う(30 KB の PNG が 1 GiB になりうる)。読み手は、デコードする前にヘッダーから大きさを読み、自分の上限より大きい画像をデコードしなくてよい。その画像は何も描かずに残りを描く(TypeScript のビューアの既定は 2^28 画素、Go のラスタライザは 100 × 2^20 画素)。

SVG の画素の大きさ(IMAGE_SUB の元の矩形と、パターンの 1 画素の単位)は、画像要素での大きさ(CSS px)とする: ルート要素の width と height(CSS の絶対長さ。単位がなければ px)。一方がないか相対値なら、もう一方と viewBox の縦横比から求める。両方なければ viewBox の幅と高さ、viewBox もなければ 300 × 150(置換要素の既定の大きさ)。

6.3 Path

Path2D に変換されるバイナリ。

varuint nVerbs
u8[nVerbs]  verbs   M=0 L=1 Q=2 C=3 Z=4 R=5(rect x y w h) E=6(ellipse x y rx ry rot a0 a1 ccw) A=7(arcTo x1 y1 x2 y2 r) O=8(roundRect x y w h r)
f32[...]    verb ごとの引数を verbs と同じ順で連結

6.4 Paint

u8  kind      0=linear 1=radial 2=conic 3=pattern
linear : f32 x0 y0 x1 y1
radial : f32 x0 y0 r0 x1 y1 r1
conic  : f32 angle x y
上記3種: varuint nStops ; [ f32 offset, u32 rgba ]×n
pattern: varuint imageRef ; u8 repeat(0=repeat 1=repeat-x 2=repeat-y 3=no-repeat) ; f32×6 matrix

7. 命令セット(opset 1)

7.1 状態

op 名前 オペランド Canvas 対応
0x01 SAVE – save()
0x02 RESTORE – restore()
0x03 TRANSFORM f32 a b c d e f transform(a,b,c,d,e,f)
0x04 TRANSLATE f32 x y translate
0x05 SCALE f32 sx sy scale
0x06 CLIP_PATH varuint pathRef, u8 rule clip(path2d, rule)
0x07 CLIP_RECT f32 x y w h beginPath(); rect(); clip()

rule: 0=nonzero, 1=evenodd(以下同じ)。

7.2 スタイル

op 名前 オペランド Canvas 対応
0x10 FILL_COLOR u32 rgba fillStyle = "#…"
0x11 FILL_PAINT varuint paintRef fillStyle = gradient/pattern
0x12 STROKE_COLOR u32 rgba strokeStyle
0x13 STROKE_PAINT varuint paintRef strokeStyle
0x14 LINE f32 width, u8 cap, u8 join, f32 miter lineWidth/lineCap/lineJoin/miterLimit
0x15 DASH varuint n, f32[n], f32 offset setLineDash / lineDashOffset
0x16 ALPHA f32 globalAlpha
0x17 BLEND u8 globalCompositeOperation(表は付録 A)
0x18 SHADOW u32 rgba, f32 blur dx dy shadowColor/Blur/OffsetX/OffsetY
0x19 FILTER str filter(任意。非対応環境では無視)。CSS のフィルタ関数(blur、drop-shadow など)と、その長さ・数値・色だけ。url() などそれ以外を含む文字列は、読み手が none として扱う
0x1A FONT varuint fontRef, f32 size font = "<style> <weight> <size>px <family>"
0x1B TEXT_STYLE u8 align, u8 baseline, u8 dir, f32 letterSpacing textAlign/textBaseline/direction/letterSpacing

SHADOW の blur・dx・dy は unit で表す。Canvas の影は変換行列の影響を受けないので、読み手は設定時の変換の拡大率(行列式の平方根)を掛けて渡す。オフセットの向きはページの向きで、回転には追従しない。

cap: 0=butt 1=round 2=square。join: 0=miter 1=round 2=bevel。align: 0=left 1=right 2=center 3=start 4=end。baseline: 0=alphabetic 1=top 2=middle 3=bottom 4=hanging 5=ideographic。

7.3 図形

op 名前 オペランド Canvas 対応
0x20 FILL_RECT f32 x y w h fillRect
0x21 STROKE_RECT f32 x y w h strokeRect
0x22 FILL_PATH varuint pathRef, u8 rule fill(path2d, rule)
0x23 STROKE_PATH varuint pathRef stroke(path2d)
0x24 FILL_PATH_AT varuint pathRef, u8 rule, f32 x y translate して fill。グリフや繰り返し部品用
0x25 FILL_PATH_RUN u8 rule, varuint n, [varuint pathRef, f32 x y]×n 連続するグリフ描画を 1 命令で
0x26 CLEAR_RECT f32 x y w h clearRect

7.4 テキスト

op 名前 オペランド Canvas 対応
0x30 FILL_TEXT str s, f32 x y, f32 advance fillText(s, x, y)
0x31 STROKE_TEXT str s, f32 x y, f32 advance strokeText

advance はエンコーダが計算した期待幅(unit)。読み手は measureText(s).width と比較し、差が閾値(推奨 0.5%)を超える場合は advance / measured で x 方向にスケールして描く(pdf.js と同じ手法)。これでフォント代替や文字送りの差異による行のはみ出し・ずれを抑える。advance が 0 のときは補正しない。

文字単位の正確な配置が必要な場合(カーニング・詰めのある見出し、埋め込み不可フォント等)は、エンコーダが 1 文字ずつ FILL_TEXT を出すか、アウトライン化して FILL_PATH_RUN を使う。

7.5 画像

op 名前 オペランド Canvas 対応
0x40 IMAGE varuint imgRef, f32 x y w h drawImage(img, x, y, w, h)
0x41 IMAGE_SUB varuint imgRef, f32 sx sy sw sh dx dy dw dh 9 引数版 drawImage
0x42 SMOOTHING u8 enabled, u8 quality imageSmoothingEnabled/Quality

7.6 合成

op 名前 オペランド 意味
0x50 USE varuint objRef 子 Object を現在の変換で描く。暗黙 save/restore
0x51 USE_AT varuint objRef, f32 x y translate(x,y) して USE
0x52 GROUP_BEGIN f32 alpha, u8 blend, f32 x y w h グループ透過。読み手は一時キャンバスに描いて合成する
0x53 GROUP_END –
0x54 MASK_BEGIN u8 kind, u32 backdrop, varuint n, u8[n] transfer ソフトマスクの描画を始める
0x55 MASK_END – グループの内容にソフトマスクを掛ける

7.7 メタ・拡張

op 名前 オペランド 意味
0x70 LINK f32 x y w h, str url リンク領域(現在の座標系の矩形)。描画には影響しない
0x71 MARK u8 kind, str payload 構造マーク(§7.8)。描画には影響しない
0xFF EXT u32 len, u8[len] 拡張。未知なら読み飛ばす

LINK の矩形は他の命令と同じく現在の変換行列の座標系で表す。読み手は 4 隅を変換した外接矩形をリンク領域とし、その領域に重なる run をリンクの文字列とする。url は次のいずれか。読み手はそれ以外の url をリンクにしない。

7.8 MARK の種類とテキストの構造

MARK は描画に影響しない境界情報で、検索・選択・コピーのために命令列に読み順を与え、読み上げのために最小限の構造(見出し・リスト・表・図・言語)を与える。

kind 名前 payload 意味
0 PARAGRAPH 任意 段落の開始。以降の run は前の run と結合されない
1 LINE 任意 同じ段落内の行の開始。前の run との間に空白 1 つがあるものとして扱う
2 CELL セル参照(例 B12) 表・シートのセルの開始。PARAGRAPH と同じ境界
3 BOX 任意 テキストボックス・図形内テキストの開始。PARAGRAPH と同じ境界
4 ALT_TEXT 文字列 直後の描画命令 1 つ(FILL_TEXT / STROKE_TEXT / FILL_PATH_AT / FILL_PATH_RUN / USE / USE_AT)が表す文字列。テキスト抽出ではその命令の文字列の代わりにこの文字列を、位置はその命令の位置を使う(USE / USE_AT では、子 Object の bbox の x 方向の範囲をこの文字列の範囲とし、位置を bbox の左端に、字送りを bbox の幅にする)。合字や私用領域の文字で描いた run の本来の文字列、アウトライン化した文字、1 文字ずつ描いた縦書きなどに使う。描画命令が続かない場合は位置なしの run になる
5 WRAP 任意 同じ段落内の行の折り返し。前の run との間に区切りを入れずに結合する。和文など語を空白で区切らない文字の間で折り返した行に使い、行をまたぐ検索を可能にする
6 HEADING レベル 1〜6 見出しの段落の開始。PARAGRAPH と同じ境界
7 LIST 任意 リストを開く(END で閉じる)
8 LIST_ITEM 任意 最も内側の LIST の項目の開始。次の LIST_ITEM か LIST の END までが項目の中身(入れ子の LIST や続きの段落を含められる)。最も内側が LIST でなければ PARAGRAPH と同じ
9 TABLE 任意 表を開く(END で閉じる)。中のセルは CELL で始める
10 FIGURE 代替テキスト 図を開く(END で閉じる)。範囲は中の描画命令の外接矩形。代替テキストが空なら装飾で、読み上げない
11 END 任意 開いている LIST / TABLE / FIGURE のうち最も内側を閉じる。開いていなければ何もしない
12 LANG 言語(BCP 47)。空なら文書の既定(meta.dc.language の最初の値) 以降の run の言語

構造

読み上げ用の構造は、MARK を走査順にたどって組み立てる。

7.9 テキスト索引 Part

View ごとに任意で持てる索引。View.textIndex にハッシュを置く(t: idx)。全 Object をデコードせずに全文検索するため、またサーバー側の検索エンジンに同じデータを流すためのもの。

u8[4]   "BTXT"
u16     version (1)
varuint nRuns
run ×n:
  varuint a        fixed/flow/scroll: ページ(帯)番号   sheet: タイル x
  varuint b        fixed/flow/scroll: レイヤー番号       sheet: タイル y
  varuint ordinal  その Object のテキスト抽出(§8 のテキストバックエンド、USE の子を含む走査順)における run の通し番号。シートでは §4.1 の規則でその Tile に属さない run を載せないので、番号は飛ぶことがある
  u8      sep      前の run との結合: 0=連結 1=空白 2=段落境界(一致は境界をまたがない)
  str     text     run の文字列(ALT_TEXT の場合はその文字列)

8. 読み手(ビューア)アーキテクチャ

main thread                          worker
──────────────                       ────────────────────────────────────
Viewer UI                            Loader      fetch / DecompressionStream / Part キャッシュ
  スクロール・ズーム         ──msg──▶  Decoder     Object → 命令配列(またはその場実行)
  <canvas> or 転送済み        ◀─bitmap─ Renderer    OffscreenCanvas に描画。Path2D / ImageBitmap / FontFace / USE ビットマップのキャッシュ
  テキストレイヤー(DOM)      ◀─runs──  TextExtractor 同じ命令列を走査して文字列と矩形を返す

必要なブラウザ機能(いずれも 2023 年時点の主要ブラウザで利用可能): DecompressionStream("deflate-raw")、Worker 内 OffscreenCanvas、Worker 内 FontFace / self.fonts、createImageBitmap、Path2D、roundRect。SVG の画像にはページの画像要素が要る(§6.2。Worker はページに描かせる)。暗号化した文書(§3.5)には crypto.subtle が要る(HTTPS か localhost の安全なコンテキストでだけ使える)。letterSpacing、filter は任意機能とし、非対応環境では無視または代替描画する。

9. 書き手(エンコーダ)の責務

付録 A. BLEND 値

0=source-over 1=multiply 2=screen 3=overlay 4=darken 5=lighten 6=color-dodge 7=color-burn 8=hard-light 9=soft-light 10=difference 11=exclusion 12=hue 13=saturation 14=color 15=luminosity 16=destination-over 17=destination-in 18=destination-out 19=source-in 20=source-out 21=source-atop 22=destination-atop 23=xor 24=copy 25=lighter

付録 B. 命令列の例

幅 960×540 のスライド。マスター(Object #0)を敷き、タイトルを描く。

50 00                         USE obj[0]
1A 00 00 00 C0 41             FONT font[0], 24.0
10 20 20 20 FF                FILL_COLOR #202020FF
30 00 00 00 A0 42 00 00 F0 42 00 00 34 43
                              FILL_TEXT str[0] x=80 y=120 advance=180

付録 C. 検討したが採用しなかったもの