章 3 · 部 II · 技法
Postextの設定
Postextのレイアウト設定オプションをすべて網羅したリファレンス
かんたんな説明
このページには、ページの見た目を決める設定がすべて載っています。ページの大きさ、段の数、フォント、文字の大きさ、見出し、囲み、色など、すべての選択は1つの設定のまとまりに入っています。どの設定にも最初から無理のない値が入っているので、書くのは変えたいものだけです。このページは調べるための資料なので、必要なところへ直接進んでください。最後のいくつかの節では、プログラムを書く人向けに、Webページ、PDFファイル、EPUBの電子書籍をコードから作る方法を説明します。
Postextのレイアウトに関する判断は、すべて1つの設定オブジェクトで決まります。
PostextConfigは、ページの寸法、段組み、本文の文字組み、見出しのスタイル、文書の言語(locale)などを制御します。プロパティはすべて省略できます。Postextには伝統的な書籍組版にならった無理のない既定値が用意されているので、指定するのは変えたい項目だけで済みます。
import { buildDocument } from 'postext';
const document = buildDocument(content, {
page: { sizePreset: '21x28', dpi: 300 },
layout: { layoutType: 'double', gutterWidth: { value: 0.5, unit: 'cm' } },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 9, unit: 'pt' } }, // 9 pt overrides the 8 pt default
headings: { fontFamily: 'Open Sans' },
});エンジンがこの設定をどう処理するかの概要は、アーキテクチャーのページを参照してください。
#内容一覧
このリファレンスは長いため、主なブロックを挙げておきます。
- ページ:サイズ、余白、ベースライングリッド、トンボ、綴じ。
- レイアウト:段数、段間、段間罫、縦組み。
- 柱とノンブル:ページごとに置くテキスト要素と罫線要素。プレースホルダー、奇偶、そろえ位置を指定できます。
- 本文:文字組み、ハイフネーション、オーファンとウィドウ。
- 文書の言語:最上位の
locale。ハイフネーションのフォールバック、組み込みのリソースタイプ、表と分割された囲みの続きを示す文字列、アクセシブルなPDFにタグ付けされる言語を決めます。そこから導かれる数字(numerals)とテキストの方向(direction)、Postextが組める言語と文字体系もここで説明します。右から左に組む本のガイドはアラビア語の組版です。 - 東アジアの組版:
cjk。中国語・日本語・韓国語のテキストの地域ごとの慣習と行分割、両端そろえの行の空け方、約物の幅とぶら下げ、漢字とラテン文字の間のスペース、文字グリッド、縦組みでの数字の正立、そして圏点、ルビ、割注、漢文の訓点。全体のガイドは中国語の組版と日本語の組版です。 - 見出し:共通の既定値と、H1〜H6ごとの上書き。
- 箇条書きリストと番号付きリスト:行頭記号、番号付け、入れ子。
- 数式:LaTeXの描画、倍率、色、余白。
- リソースの種類:図、表、独自の種類に付ける種類別の番号。
- 表スタイル(名前付きの表スタイルを含む)とキャプションスタイル:表リソースとそのキャプションの文字組みと装飾。
- ダイアグラムスタイル:特色印刷のために、埋め込んだSVGのダイアグラムを単色に塗り替えます。
- 段落スタイル:
:::paragraphsコンテナー用の名前付きスタイル。参考文献、用語集、注記などに使います。 - 囲みスタイル:
:::calloutコンテナー用の、枠で囲んだ注記、ヒント、学習目標。 - 部:
:::partコンテナー用の部扉ページ。奇偶、本文領域、扉のデザイン、本文の文字組み。 - 見出しスタイル:
{style="…"}を付けた見出し用の名前付きスタイル。番号のない章や、独自の柱、ジオメトリー、パレットを持つ前付けに使います。 - 目次:
:::tocが出力する内容。項目の文字組み、リーダー、ページ番号、著者の行、部の行。 - 索引:
:::indexが出力する内容。項目の文字組み、字下げ、ページ番号の区切りと範囲、頭文字ごとのグループ、並べ替えの言語。 - 単位と色、カラーパレット:
Dimension、ColorValue、名前付きの色。 - カスタムフォント:ユーザーがアップロードしたフォントファミリーを、Google Fontsと並べて宣言します。
- HTMLビューアー:HTMLバックエンドの目標段幅と行分割。
- PDF生成(設定):PDFバックエンドのしおり、アクセシブルな(タグ付きの)出力、色空間の強制。
- Folioビューアー(設定):3Dの本のビューアーの用紙、綴じ、表面、光。
- デバッグ:エディター用の視覚的なオーバーレイと、執筆中の警告。
- プログラムからの利用:
buildDocumentとそれが報告する警告、リゾルバー、キャッシュ。 - Web Workerでレイアウトを実行する:キャンセルできる、メインスレッド外でのビルド。
- HTMLビューアーの統合とPDFの生成:最初から最後まで通した実例。
- EPUBの本:同じレイアウトから作る固定レイアウトとリフロー型のEPUB 3ファイルと、EPUBCheckによる検査。
#ページ
pageプロパティは、ページの物理的な寸法と外観を制御します。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
sizePreset | PageSizePreset | '17x24' | 定義済みのページサイズ。幅と高さを明示するには'custom'に設定します。 |
width | Dimension | 17 cm | ページの幅。省略するとsizePresetから取られます。明示した値が常に優先されます(完全に独自のサイズにはsizePreset: 'custom'を使います)。 |
height | Dimension | 24 cm | ページの高さ。省略するとsizePresetから取られます。明示した値が常に優先されます。 |
margins | PageMargins | 四辺とも2 cm | ページの端と版面の間の空き。各辺(上、下、左、右)は個別に設定します。mirror: trueのとき、余白は見開きの余白になります。leftはのど(背の側)の余白、rightは小口側の余白です。奇数ページ(1ページ目は奇数)は書いたとおりに使い、偶数ページは左右を入れ替えます。そのため版面が、そしてそれとともに段、フロートの帯、ヘッダーとフッターのコンテナー、章扉の帯も、見開きの中で左右に移ります。既定値はfalse。後述します。 |
backgroundColor | ColorValue | transparent | ページの背景色。 |
dpi | number | 300 | 1インチあたりのドット数。物理単位(cm、mm、in)をピクセルに換算するときに使われます。 |
cutLines | CutLinesConfig | 無効 | 断裁用のトンボをページの四隅に表示します。有効にすると、キャンバスが裁ち落とし領域とトンボを含む大きさに広がります。後述します。 |
baselineGrid | BaselineGridConfig | 無効 | ページの上にベースライングリッドを描き、縦のリズムを確認できるようにします。描くかどうかにかかわらず、レイアウトはグリッドに合わせられます。後述します。 |
binding | 'auto' | 'left' | 'right' | 'auto' | 本を綴じる側の辺。'auto'は、layout.writingModeが'vertical-rl'のとき、または文書が右から左に流れるとき(direction)は'right'、それ以外は'left'になります。右綴じの本は左ページから始まり、余白の左右の入れ替えも逆になります。本全体に対する設定で、見出しスタイル自身のlayoutで変わることはありません。綴じを参照してください。 |
#見開きの余白
本は見開きで読まれ、のどの余白はふつう小口の余白と異なります。margins.mirrorを使うと、4つの余白が見開きの余白になります。
{
"page": {
"margins": {
"top": { "value": 2, "unit": "cm" },
"bottom": { "value": 2.5, "unit": "cm" },
"left": { "value": 2.2, "unit": "cm" },
"right": { "value": 1.4, "unit": "cm" },
"mirror": true
}
}
}この設定では、どの奇数ページも左(背の側)の余白が2.2 cm、右(小口)が1.4 cmになり、どの偶数ページも左(小口)が1.4 cm、右(背の側)が2.2 cmになります。組まれた各ページはVDTPageに自分のcontentAreaを持つため、そこから導かれるもの、つまり段、全幅のフロートの帯、ヘッダーとフッターのコンテナー、span: 'page'の章扉の帯は、左右を入れ替えたジオメトリーに自動的に従います。'page'または'bleed'に固定したデザイン要素が使うページ枠と裁ち落とし枠は影響を受けません。これらは余白ではなく、物理的な用紙を表すものです。
#綴じ
「縦組みの中国語文書は右側で綴じ、横組みの文書は左側で綴じる」(clreq §7.1.1.1)。page.binding: 'right'は、本を右綴じとして組みます。
- 1ページ目はこれまでどおり奇数ページ(recto)です。そのため
breakBefore.parity、:::pagebreak{parity}、parityを持つデザイン要素、あらゆるページ数の意味は変わりません。変わるのは奇数ページが置かれる側で、見開きの左ページになります。新しい奇数ページから始まる章は、左ページから始まります(clreq §7.1.3.3)。 margins.mirrorを使う場合もleftはのどの余白ですが、左右を入れ替えるのは奇数ページのほうになります。1ページ目はのどの余白が右に、2ページ目は左に来ます。'outer'/'inner'に置いたoneAndHalfのサイド段、背に接する回転したフロート、部扉の余白、'outer'/'inner'に置いた囲みの角のアイコンも同じ規則に従います。- 文書自体がそれを示すため(
VDTDocument.binding: 'right')、ホストが設定を読む必要はありません。Sandbox(ブラウザーで動く編集環境)は見開きを[3 | 2]のように表示し、1ページ目は背の左側に単独で置かれます。SandboxのHTMLビューアーはページを右から左へ並べ、右端から開き、左矢印で次のページに進みます。renderToHtmlのmultiモードは、ページの列を右から左に並べます。 - PDFには、タグ付きかどうかにかかわらず
/ViewerPreferences << /Direction /R2L >>と/PageLayout /TwoPageRight(1ページ目は単独、以降は2ページずつ)が入ります。AcrobatとFoxitはこれに従いますが、Chromeの組み込みビューアーはどちらも無視します。
ノンブルと柱はひとりでには移動しません。ページ番号を外側の角に置くテンプレートでは、奇数ページ用と偶数ページ用の要素を右綴じに合わせて設定する必要があります(要素はparityを取ります)。
右から左に書く本(アラビア語、ペルシア語、ヘブライ語など)も右綴じです。文書のdirectionが'rtl'に解決されると、'auto'は右綴じになります。こうした本は流れ全体も反転するため、最初の段は右側になり、字下げ、リストの行頭記号、フロート、注は右に置かれます。ヘッダーとフッターのスロットは物理的な位置のままです。アラビア語の組版を参照してください。
#ページサイズのプリセット
| プリセット | 幅 | 高さ | 主な用途 |
|---|---|---|---|
'11x17' | 11 cm | 17 cm | ポケット判の本 |
'12x19' | 12 cm | 19 cm | 標準的なペーパーバック |
'17x24' | 17 cm | 24 cm | 技術書、教科書 |
'21x28' | 21 cm | 28 cm | 雑誌、報告書(A4に近い) |
#ベースライングリッド
ベースライングリッドは本文のリズムで、版面の上端から数えて本文の行の高さ1つ分ずつ離れた線です。描くかどうかにかかわらず、レイアウトはこれを使います。見出し、リストの終わり、囲み、図、別行立ての数式のあとでは、テキストがグリッドに戻ります(それぞれのsnapToGridがオフでない限り)。そのため隣り合う段の行はそろったままです。enabledはCanvas、PDF、Sandboxのビューにこの線を描いてリズムを確かめるためだけのもので、オンにしてもオフにしても何も動きません。線はページ上の実際のテキスト、つまり最初のテキスト行から最後の行までの範囲にだけ引かれます。フロートの帯、奇偶合わせの白ページ、使われていない末尾の空きにはグリッドは表示されません。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | false | グリッド線を描くかどうか(CanvasとPDF)。描画だけの設定で、レイアウトはどちらでも同じです。 |
color | ColorValue | #cccccc | グリッド線の色。 |
lineWidth | Dimension | 0.5 pt | グリッド線の太さ。 |
page: {
baselineGrid: { enabled: true, color: { hex: '#e0e0e0', model: 'hex' } }
}#トンボ
有効にすると、キャンバスが裁ち落とし領域を含む大きさに広がり、エンジンは印刷工程用のトンボを四隅に描きます。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | false | 裁ち落としの分だけキャンバスを広げ、トンボを描くかどうか。 |
bleed | Dimension | 3 mm | 印刷の裁ち落とし(塗り足し)に使う、ページの周囲の追加領域。 |
markLength | Dimension | 5 mm | トンボ1本の長さ。 |
markOffset | Dimension | 3 mm | 仕上がり線からトンボの始点までの間隔。トンボが裁ち落とし領域の内側から始まることはありません。bleedのほうが広いときは、裁ち落としの端から始まります。 |
markWidth | Dimension | 0.25 pt | トンボの線の太さ。 |
color | ColorValue | #000000 | キャンバス(画面上のプレビュー)でのトンボの色。PDFでは常にレジストレーションカラーで描かれます(後述)。 |
用紙は四辺それぞれbleed + markOffset + markLengthだけ大きくなり、仕上がりのページはその中央に置かれます。仕上がりの各角には長さmarkLengthのトンボが2本付き、それぞれそこで交わる辺の一方の延長線上に置かれます。トンボは仕上がりからmarkOffsetだけ外側で始まり、bleedのほうが広い場合は裁ち落としの端から始まるため、裁ち落としまで伸びた絵柄にトンボが重なることはありません。既定値(裁ち落とし3 mm、間隔3 mm)では、トンボは仕上がりの外側3 mmから8 mmの範囲に引かれ、その外側を幅3 mmの空白の帯が用紙を一周します。cropMarkSegments(page, doc.config.page, doc.trimOffset)は、1ページ分の8本のトンボ、つまりCanvasとPDFのバックエンドが描くものを、ページのピクセル単位で返します。第3引数は用紙の中で仕上がりが置かれる位置で、PDFのTrimBoxを書き出すもとになる値です。省略すると、cutLinesから同じ方法で算出されます。
裁ち落としの外側には、トンボ以外何も印刷されません。ページが描くものはすべて、'page'や'bleed'に固定したデザイン要素も含めて、Canvas、PDF、HTML出力のいずれでも、DTPソフトの書き出しと同じように裁ち落としの枠で切り取られます。意図して裁ち落としの外まで置いた帯や画像は裁ち落としの端で切られますが、そこは断裁でいずれ失われる部分です。postext 1.4までは、そうした要素はトンボの上を越えて用紙の端まで描かれていました。
PDF(postext-pdf)では、各ページのMediaBoxは用紙全体です。ページにはさらに、仕上がりのページを表すTrimBoxと、仕上がりに裁ち落としを加えたBleedBoxも入り、面付けやプリフライトのツールがこれを読みます。トンボはレジストレーションカラー、つまり/Allの分版で描かれるため、すべての版に印刷されます。これはPDFを書き出す色空間(RGB、グレースケール、CMYK)にかかわらず同じです。ただの黒では、リッチブラックとして、あるいは墨版だけとして印刷所に届いてしまいます。colorはキャンバスにだけ適用されます。
#ノンブル
page.pageNumberingブロックは、ページラベルの書式とカウンターの開始値を制御します。定義するのは文書全体の既定値だけです。文書の途中で番号を振り直す(たとえば、ローマ数字の前付けから、1から始まる算用数字の章に切り替える)には、:::numberingディレクティブを使います(文書形式 → ディレクティブを参照)。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
format | 'decimal' | 'lower-roman' | 'upper-roman' | 'lower-alpha' | 'upper-alpha'、または東アジアの書式 | 'decimal' | ページラベルの表示に使う数字の書式。東アジアの書式('trad-chinese-informal'はページを一, 二, 三と番号付けします)は番号書式の表記に挙げています。 |
startAt | number | 1 | 書式にかかわらず最初のページに割り当てる数値。format: 'lower-roman', startAt: 1ならi, ii, iii, …、format: 'decimal', startAt: 17なら17, 18, 19, …になります。 |
計算されたラベルはすべてのVDTPageにpageLabelとして格納され、ヘッダー・フッターのプレースホルダー{pageNumber}はこの値に解決されます。PDFには/PageLabelsの数値ツリーが出力されるため、プレビューやAcrobatのページ表示と「ページへ移動」が、印刷されたラベルとぴったり一致します。PDFにコードのない書式(漢数字、丸数字、全角数字)はページごとに書き出されるため、ビューアーにも一, 二, 三と表示されます。
番号書式の表記
番号書式を選ぶ設定は3つあり、それぞれが独自の表記を持つようになりました。ページラベル(page.pageNumbering.formatと:::numbering{format=…})はlower-roman、番号付きリスト(orderedLists.numberFormat)は10進数をarabic、リソースタイプ(counterFormat)はroman-lowerと書きます。どの設定も下の表記をすべて受け付けるため、ある設定からコピーした書式はほかの設定でも使えます。名前の大文字と小文字は区別しませんが、1文字の形式は区別します(iとIは別のものです)。
| 書式 | 出力 | 使える表記 |
|---|---|---|
| 10進数 | 1, 2, 3 | decimal、arabic、1 |
| 小文字のローマ数字 | i, ii, iii | lower-roman、roman-lower、i |
| 大文字のローマ数字 | I, II, III | upper-roman、roman-upper、I |
| 小文字のアルファベット | a, b, c | lower-alpha、alpha-lower、lower-latin、a |
| 大文字のアルファベット | A, B, C | upper-alpha、alpha-upper、upper-latin、A |
| 中国語の漢数字(簡体字) | 一, 十二, 一百零一 | simp-chinese-informal、簡体字の文書では一 |
| 中国語の漢数字(繁体字) | 一, 十二, 一萬 | trad-chinese-informal、cjk-ideographic、繁体字の文書では一 |
| 中国語の大字(簡体字) | 壹, 壹拾贰, 壹佰贰拾 | simp-chinese-formal、簡体字の文書では壹 |
| 中国語の大字(繁体字) | 壹, 壹拾貳, 壹佰貳拾 | trad-chinese-formal、繁体字の文書では壹 |
| 位取りの漢数字 | 一二〇, 二〇二六 | cjk-decimal、〇 |
| 十干 | 甲, 乙, 丙 … 癸 | cjk-heavenly-stem、甲 |
| 十二支 | 子, 丑, 寅 … 亥 | cjk-earthly-branch、子 |
| 日本語の漢数字 | 一, 十二, 百一, 一万一 | japanese-informal、日本語の文書では一 |
| 日本語の大字 | 壱, 壱拾弐, 壱百 | japanese-formal、壱 |
| ひらがな(五十音順) | あ, い, う … ん, ああ | hiragana、あ |
| カタカナ(五十音順) | ア, イ, ウ … ン, アア | katakana、ア |
| ひらがな(いろは順) | い, ろ, は … す, いい | hiragana-iroha、い |
| カタカナ(いろは順) | イ, ロ, ハ … ス, イイ | katakana-iroha、イ |
| 丸数字 | ①, ②, ③ … ㊿ | circled-decimal、① |
| 全角数字 | 1, 2, 3 | fullwidth-decimal、1 |
| アラビア・インド数字 | ١, ٢, ٣ … ١٠ | arabic-indic、١ |
| ペルシア数字 | ۱, ۲, ۳ … ۱۰ | persian、urdu、۱ |
| アラビア文字(アブジャド順) | أ, ب, ج, د, هـ … غ, أأ | abjad、أبجد |
| アラビア文字(アルファベット順) | أ, ب, ت, ث … ي, أأ | hijai、arabic-alpha、arabic-alphabetic、أبتث |
| アブジャド数字 | ا, ب … يا (11), غتمو (1446) | arabic-abjad |
| アブジャド数字(マグリブ式の数価) | ص (60), ض (90), ش (1000) | arabic-abjad-maghrebi、maghrebi-abjad |
東アジアの書式は、3つの設定のどれでもCSS Counter Stylesの名前を使います。中国語の漢数字(informal)は、10から19では十の前に一を付けず(十二。ただし一百一十)、数の中で続くゼロには零を1つだけ書き(一百零一、一千零五十)、1万には万または萬、1億には亿または億を使います(一万零一十)。〇を1桁ずつ使うのはcjk-decimalだけで、年を書くときの形です(二〇二六、GB/T 15835—2011)。十干は10、十二支は12、丸数字は50までで、それを超えると算用数字で出力されます。一と壹は文書のlocaleの字体に従い、zh-Hant、zh-TW、zh-HKなら繁体字、それ以外は簡体字になります。古い版には101を零なしで一百一と書くものもありますが、Postextはこの形を出力しません。
日本語の書式(postext 1.16以降)もCSS Counter Stylesに従います。japanese-informalは十の前に一を付けず、空の位に零を書きません(百一、千十)。japanese-formalは大字の壱 弐 参 拾 百 阡を使い、各単位の前に壱を残します(壱拾、壱百)。CSSではどちらも9 999までですが、Postextはその先も4桁ごとに万 億 兆(大字では萬 億 兆)で続け、各グループで単位の前の一を残します。一万一は10 001、一億一万は100 010 000です。日本語の文書(ja、ja-JPなど)では一はjapanese-informalを意味するため、第{1:一}章は日本語の文書では第百一章、中国語の文書では第一百零一章と出力されます。壹は中国語の大字のままです。仮名の系列はCSSのリストと同じで、五十音順の48字(ゐとゑを含む)と、いろは歌の順の47字です。最後の字を過ぎると2字で続き(ああ、いい)、ゼロはないため、0番の項目は0と出力されます。位取りの漢数字(二〇二六)は年やノンブルに使う形で、cjk-decimalにあたります。
アラビア語の書式は、CSSに名前があるものはその名前を使います(arabic-indic、persian。W3Cの『Ready-made Counter Styles』にあるurduとmaghrebi-abjadは、それぞれpersianとarabic-abjad-maghrebiとして読みます)。arabicは従来どおりの意味、つまりヨーロッパ式の数字です。arabic-abjadは、古典アラビア語や写本の丁付けに使われる加算式のアブジャド数字を、大きい値から順に書きます。11はيا、1446はغتموで、千の位の数はغの前に置きます(2000 بغ, 1002 غب)。999 999を超えると算用数字で出力されます。W3Cのノートはこの名前を、11がكになる28文字の系列に使っています。Postextではその系列をabjadと呼び、アブジャド順でリスト項目に文字を振るときに使います(أ، ب، ج، د، هـ)。アルファベット順はhijaiです(أ، ب، ت، ث)。どちらも最初の文字をハムザ付きのأで書き、単独で立つことになるハーはタトウィールを付けてهـと書くため、数字の١や٥と読まれることはありません。28を超えると、lower-alphaと同じように2文字になります(أأ, أب)。マグリブ式の数価は覚え歌صعفض قرست ثخذ ظغشに従います(ص 60, ض 90)。W3Cのリストではصとضが入れ替わっています。أ1字では2つの文字の系列を区別できないため、それぞれのトークンは最初の4文字にしています({1:أبجد}、{1:أبتث})。文書自体の数字はnumeralsで設定します。
ほかの表記は、型チェックを受けない設定、つまりJSONのプリセットと素のJavaScriptのためのものです。TypeScriptの型には、各設定に固有の表記、つまりSandboxが書き込みresolveAllConfigが返す表記(東アジアの名前を含む)しか挙げていません。そのため型付きのPostextConfigではその表記を使い、別の表記を使うにはキャストが必要です。
// JavaScript or a JSON preset (in TypeScript, each setting's own spelling)
orderedLists: { numberFormat: 'decimal' }, // same as 'arabic'
page: { pageNumbering: { format: 'roman-lower' } }, // same as 'lower-roman'
resourceTypes: [{ id: 'plate', counterFormat: 'upper-roman', … }], // same as 'roman-upper'resolveAllConfigは、リストとページの書式をそれぞれの設定に固有の表記に変換します。orderedListsでは'decimal'が'arabic'に、page.pageNumberingでは'roman-lower'が'lower-roman'に解決されます。stripConfigDefaultsは、既定値を別の表記で書いたものも取り除きます。Sandboxのパネルは、設定がどの表記を使っていても、3つの設定をそれぞれ固有の表記で表示します。それ以外の値(roman、01、タイプミスなど)は、undefinedと出力される代わりに10進数で番号付けされ、設定の警告として報告されます。見出しの番号テンプレートもコロンのあとに同じ名前を取り({1:roman-upper}は{1:I}と同じ)、独自のゼロ埋め{1:01}も使えます。
#レイアウト
layoutプロパティは、版面の中での段の配置を制御します。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
layoutType | 'single' | 'double' | 'oneAndHalf' | 'double' | 段の配置。各タイプの詳細は後述します。 |
gutterWidth | Dimension | 0.75 cm | 段と段の間の水平方向の空き。複数段のレイアウトにだけ適用されます。 |
sideColumnPercent | number | 33 | サイド段の幅を、版面に対する割合(%)で指定します。両方の段に幅が残る値なら書いたとおりに使われ、残らない値は範囲内に制限されて、ビルドがそれを報告します(レイアウトの種類を参照)。'oneAndHalf'レイアウトにだけ適用されます。 |
sideColumnRole | 'text' | 'floats' | 'text' | サイド段に入れるもの。本文(メインの段のあとにそこへ流れ込みます)か、span: 'side'で配置したリソースと囲みだけ(フロート専用の欄外段)かを選びます。'oneAndHalf'のみ。'text'では、ある段から次の段へまたがる段落は、続きが入る段の幅で改めて行分割されます。postext 1.4までは始まった段の行がそのまま使われたため、メインの段用に組んだ行がサイド段からはみ出し、切り取られていました。 |
sideColumnSide | 'right' | 'left' | 'outer' | 'inner' | 'right' | サイド段を置く版面の辺。'outer'/'inner'は、見開きの余白を使うときにページの奇偶に従います(奇数ページの外側は右辺、偶数ページの外側は左辺)。'oneAndHalf'のみ。 |
columnRule | ColumnRuleConfig | 無効 | 段の間に引く罫線(任意)。後述します。 |
fitFiguresToPage | boolean | false | 画像、キャプション、注を合わせた高さが版面より高くなる図(ビットマップまたはSVG)を、収まるまで縮小します(セーフエリアResource.safeAreaを持つ画像は、まず全幅のままセーフエリアの範囲内でトリミングされ、それでも収まらないときだけ縮小されます)。また、段に残った空きに対して少しだけ高すぎるインラインの図を小さく組み(幅の半分まで。キャプションは段の幅のまま)、本文から離れないようにします。縮小された画像はplacement.alignに従って枠の中に置かれます。HTMLビューアーはページの高さが画面の高さまでしかないため、この設定を有効にします。印刷用のページは図に合わせた大きさになっています。 |
hugClosingFloats | boolean | true | 章の(そして文書の)最後のページでは、テキストの最後の帯の下に置かれたページ幅の図と表が上に移動し、フロートの間隔1つ分を空けてその下に順に積み重なります。そこではあとに何も続かないためです。falseにすると配置どおりの位置に残るため、position: 'bottom'のフロートは、ほかのページと同じく最後のページでもページの下端で終わります。たとえば、すべてのページで輪郭が同じ高さで終わるデータシートに使えます。サイド段のあるページでは移動しません。 |
inlineResourceGap | 'around' | 'above' | 'around' | インラインのリソース(placement.position: 'here'、::resourceで埋め込んだもの)が、フロートの間隔(1行分)をどこに取るか。'around'ではリソースの上と下に取り、リソースのあとのテキストはその間隔の下でベースライングリッドに戻ります。直後に見出し、リスト、囲み、別のインラインのリソースが来る場合は、下の間隔をそれ自身の上の空きと共有し、大きいほうが適用されます。'above'では上にだけ取り、リソースのあとのテキストは、どれほど近くても(0から1行分のどこか)次のグリッド線から再開します。postext 1.4までと同じ動作です。以前のバージョンで保存された設定のうち、章にリソースを埋め込んでいるものは'above'として読み込まれるため、ページは動きません(postext 1.4以前で書き出されたバンドルを参照)。1.4向けにコードで書いた設定で以前の間隔を保つには、自分で'above'を設定するか、postext/bundleのpinLegacyInlineGapを使います。 |
inlineResourceGapInBoxes | boolean | true | 囲み(:::callout)の中のインラインのリソースが、inlineResourceGapで決まる間隔(囲み自身のテキストの1行分)を取るかどうか。リソースの上に取り、'around'なら下にも取って、その間隔と次のブロック自身の空きの大きいほうが適用されます。囲みの上端や下端、あるいは分割された囲みの断片の上端や下端では、パディングがリソースを隔てるため間隔は加えません。falseにすると、postext 1.4までと同じく、リソースを前のテキストの直下に、あとのテキストをリソースの直下に置きます。以前のバージョンで保存された設定のうち、章で囲みの中にリソースを埋め込んでいるものはfalseとして読み込まれます(postext 1.4以前で書き出されたバンドルを参照)。コードでは、postext/bundleのpinLegacyBoxResourceGapで同じことができます。 |
boxChildSplitMinLines | number | 2 | 囲みが分割されるとき、段落やリスト項目の内側での分割がその両側に残す最小の行数(囲みのスタイルのsplitMinLinesは、これまでどおり分割位置の両側で囲みのすべての行を数えます)。1以上の整数。既定値では、段落や項目の1行だけが段の下端や次の段の頭に取り残されることはありません。囲みスタイルのsplitMinLinesがこれより小さい場合は、そちらが上限になります。1にすると、postext 1.4までと同じく、段落や項目の1行だけを片側に残す分割を許します。以前のバージョンで保存された設定のうち、章に:::calloutを含むものは1として読み込まれます(postext 1.4以前で書き出されたバンドルを参照)。コードでは、postext/bundleのpinLegacyBoxChildCutで同じことができます。 |
writingMode | 'horizontal-tb' | 'vertical-rl' | 'horizontal-tb' | 行の進む方向。'vertical-rl'は中国語と日本語のテキストを縦に組みます。文字は上から下へ進み、各行は前の行の左に並びます。見出しスタイルのlayoutは、自分で設定しない限りこの値を引き継ぐため、縦組みの本のあとに横組みの付録を続けられます。縦組みを参照してください。 |
#縦組み
writingMode: 'vertical-rl'では、ページは横組みのページを時計回りに90度回したものとして組まれます。流れは用紙の高さと同じ幅の枠の中で組まれ、その行が縦組みテキストの列になって、右から読まれます。行に関してエンジンが行う処理(行分割、両端そろえ、フロート、脚注、分割禁止の規則)はすべてこの枠の中で働きます。そのため用紙の上では次のようになります。
- レイアウトの段は、縦組みでは上下に重なる段(tier、栏)になります。
layoutType: 'double'では上下に積まれた2つの段ができ、右上から埋まります。gutterWidthは段と段の間の空き、段間罫は段の間に引く水平の罫線になります。章の終わりで段の長さはそろえません(clreq §7.1.3.4)。縦組みの文書では、headings.balancing.enabledを設定しない限り段末そろえはオフです。 - 流れで「上」と呼ぶのは、読み始める側である用紙の右端です。上のフロートはページの右に、下のフロートは左に置かれ、ページ幅の章扉は右端に沿った帯になり、脚注は各段の左端に入ります。ページの上に固定したデザイン要素(見出しのデザイン、固定の囲み)は右端に固定されます。
sideColumnSideの'left'は上の段、'right'は下の段で、'outer'と'inner'はそれぞれ'right'と'left'として読みます。 - 流れの余白は、用紙の余白を回したものです。右の余白が流れの上、上の余白が流れの左になります。
page.marginsは用紙上での名前のままです。 - 柱、ノンブル、トンボ、ページの背景は、clreqが縦組みの本について述べているとおり、用紙の上に横組みで置かれます。
- 図と表は正立します。図はキャプションが許す範囲で段の高さいっぱいに広がり、幅は最大でページの幅までです。図が取る幅が、流れの中で使う分量になります。キャプションは図の下に横組みで置かれ、表のセルも同様で、どちらも横組みのテキストとして計測されます。表はページの上に正立して置かれ、段より高い場合は行が段に合わせて分割されます。
placement.alignは図を段の上端('left')、中央、下端に置きます。図が縦組みのテキストで最初に参照される位置ではplacement.rotateは適用されず、ビルドはrotateIgnoredVerticalのコンテンツ警告を報告します。本の中の横組みの部分(layoutで'horizontal-tb'を設定した見出しスタイル)では、図は指定どおりに回転します。 - デザインの画像(章扉のイラストや部扉のイラスト)も正立します。流れの中でのその枠は画像の幅と高さを入れ替えた大きさになるため、
size.widthは画像が段に沿って下へ伸びる長さを表し、用紙上での幅は縦横比から決まります。 - 文字:漢字、仮名、全角文字は正立し、それぞれ1em分を占めます。ラテン文字の単語と数字は横倒しになり、横組みでの幅を使います。約物はフォントの縦組み用字形を使います。句読点は回転させません。中国大陸のフォントは、、。,.をセルの右上隅に、!?:;をセルの右半分に置き、台湾や香港のフォントはこれらを中央に置きます(
cjk.region)。括弧は縦組み用の字形を使い、大陸のテキストの“ ” ‘ ’は『』「」として読みます。ダッシュ、三点リーダー、波ダッシュは、フォントに縦組み用の字形があればそれを使い(Noto CJKでは—の縦組み字形がfwidとともにvertに割り当てられていて、セルの中央を縦に通る罫になります)、なければ横倒しにして、インクを段の軸の中央に合わせます。中国語のテキストの破折号(——)は、段を縦に通る1本の罫です。縦組み用の字形では各セルの両端に空きが残るため、各ダッシュを行とともに回転させ、横組みと同じように引き伸ばします(約物の幅を参照)。2桁までの数は1つの正立したセルに収めますが、ラテン文字の文の中にある場合はその単語に従います(縦組みの数字を参照)。中黒(·)は大陸のテキストでは半セル、台湾と香港のテキストでは1セルを占めます。Unicodeが正立とする記号(× © ± § ℃ ①など)は、数の中にあっても独立したセルに正立します。3×4は、横倒しの3と4の間に×が正立して入ります。ラテン文字の単語の2文字の間にあるアポストロフィーや中黒(don’t、l·l)は単語の中に残り、横倒しになります。縦組みの流れの中にあるラテン文字の段落も同じ規則に従います。インラインの数式、チップ、色見本は行とともに横倒しになります。 - すべての文字は描かれるとおりに計測されます。正立のセルは行に沿ってセル1つ分進み、横倒しの文字列は横組みでの幅だけ進みます。用紙上で横組みのままのテキスト(柱、ノンブル、キャプション、表のセル)は横方向に計測されます。
- 約物の幅、ぶら下げ、和欧間のアキは、横組みで行に沿って適用されるのと同じように、縦組みでも行に沿って適用されます。字形の前の空きはその上に、後の空きはその下に来ます。開明式(Kaiming)の、は半セルを取り、
」「は1.5セルに詰められ、行頭で詰めた始め括弧は半セル上から始まり、ぶら下げた。は行の下端の下に置かれ、漢字とラテン文字の間のスペースは横倒しの単語の上下にその段の4分の1 em分入ります。:;?!は縦組みではどの地域でも1セル全体を取ります。 - 文字グリッドは、行に沿って文字を数え、ページを横切って行を数えます。
charsPerLineは段の長さを、linesPerPageはページに入る行数を決め、layoutType: 'double'では文字数が整数の2つの段と、整数のemからなる段間ができます。
レイアウトを読むホスト向けの情報です。縦組みのページはVDTPage.flowを持ちます。そのcontentArea、段、ブロック、行、フロート、脚注領域、章扉の帯、ブロックのデザインのオーバーレイは流れの座標系で表され、width、height、header、footerは用紙上の値です。flowToPage、pageToFlow、flowRectToPage、pageRectToFlowは両者の間を変換し、verticalOrientation(char, region)は文字の向きを返します。flow.centralBaselinesは、フォントファミリーごとに、レイアウトが正立した文字を中央合わせした軸を返します。これは中のインクの中心です。中の長い画はemボックスの高さいっぱいに伸びており(Noto SerifとNoto Sansでは、SCでもTCでもベースラインの0.38 em上)、中国語・日本語・韓国語のどの書体にもこの字があります。各行はこの軸で中央合わせされます。縦組みの行のベースラインは、行ボックスの上端から、行の高さの半分にファミリーの中央ベースラインを足した分だけ下にあります(横組みの行では行の高さの0.8倍下)。そのため文字の列は行送りの中央に立ち、行送りの整数倍の位置で2つの列の間に引いた罫は、両者のちょうど中間に来ます。縦組みのデザインテキストも、それ自身の行の中で同じように組まれます。縦組みのページはキャンバスが自分で描きます。約物をフォント自身の縦組み字形で描くために、ブラウザーのホストはファミリーごとに1回、その機能を有効にした双子のフェースをloadVerticalAlternates(family, faces)で読み込みます。facesはファミリーのソース(URLまたはバイト列)とディスクリプターです。双子が残されるのは、ブラウザーがその機能をキャンバスのテキストに適用する場合(Chrome 140以降)だけです。与えたフェースのウェイトとスタイルで「(《を、双子と、機能なしで読み込んだ同じフェースのコピーとで描き、インクが異なれば双子を残します。双子を使用中のファミリーについて、別のフェース(標準のあとの太字など)で後から呼び出すと、そのフェースが双子に追加されます。Sandboxは縦組みの文書のすべてのファミリーでこれを行います。双子がない場合、括弧はemボックスを中心に回転させ、大陸の句読点はフォントの縦組み字形が置く位置にセルの中で移動させます。、。,.は右上隅へ(Noto Serif SCの縦組み字形との差は0.07 em以内)、!?:;は右へ半em、少し上へ移します(差は0.02 em以内)。PDFとHTMLも同じページを組みます。PDFの縦組みと、HTML出力とcanvas、PDFとの違いの表を参照してください。Noto Serif TCとSCで、HarfBuzz(vertを使った縦組みレイアウト)とセルごとに照合すると、「賈雨村」云云,宜乎?故曰!;:、。“引”‘單’……のどの文字も、CanvasとPDFでは0.02 em以内、HTMLでは0.05 em以内に収まります(Chromeは回転した字形をフォントのアセントとディセントの中央に合わせますが、Notoではそれがemボックスの中心より0.05 em上にあります)。flow.dashAdvancesは、ファミリーごとに、ページがセルを埋めるために引き伸ばす各ダッシュ(— – ― ⸺ ⸻ -)の横方向の送り幅をem単位で返します。これはフォントメトリクスを自前で持たないレンダラーのためのもので、HTMLはこの値でダッシュを引き伸ばし、CanvasとPDFは自前の値で同じことをします。柱とノンブルも縦に組めます。縦組みのテキスト要素を参照してください。
#段間罫
段間に細い縦の罫線を引いて、段を視覚的に区切ります。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | false | 段間罫を引くかどうか。 |
color | ColorValue | #cccccc | 罫線の色。 |
lineWidth | Dimension | 0.5 pt | 罫線の太さ。 |
ページ幅の見出し(span: 'page')の下では、罫は見出しの帯の下、段のテキストが始まる位置から始まります。帯を描くのが標準の章扉でも、独自のデザインでも同じです。postext 1.4までは、版面の上端から帯を貫いて引かれていました。
見出しスタイルはlayoutで独自の罫を設定でき(見出しスタイルを参照)、そのセクションのページはその罫を描きます。スタイルで設定していない項目は文書の値を取るため、段組みだけを変えたセクションでは文書の罫がそのまま使われます。postext 1.4までは、スタイルの罫は描かれず、どのページも文書の罫を描いていました。
#レイアウトの種類
-
'single':版面の幅いっぱいの1段。幅の狭いページや、長い段落の多い文章中心の内容に向いています。 -
'double':同じ幅の2段。古典的な編集レイアウトで、1行の長さを読みやすい40〜50文字の範囲に保ちます。 -
'oneAndHalf':メインの段と、それより狭いサイド段からなる非対称のレイアウト。サイド段(sideColumnPercentで制御)は、傍注、小さな図、補足的な内容に適しています。25〜40%の値が使いやすく、行番号や欄外の記号のための細い欄なら10〜15%程度です。サイド段は版面の幅のsideColumnPercent%で、メインの段は段間を引いた残りです。そのため50%では、サイド段のほうがメインの段より段間1つ分広くなります。両方の段が版面の幅の1%以上を保つ限り、どの値も書いたとおりにレイアウトされます。どちらかがそれより狭くなる値(0以下、あるいはメインの段が段間に隠れて消えるほど大きな値)は、両方を保てる最も近い値に制限され、数値でない値は既定値の33になります。そのとき文書のconfigWarningsには{ kind: 'sideColumnPercentClamped', path: 'layout.sideColumnPercent', value, used }が入ります。見出しスタイル自身のlayoutの場合、パスはheadingStyles[2].layout.sideColumnPercentのようにそのスタイルを指し、値はそのスタイルの余白で計測されます。Sandboxはこれを検査パネルに表示し、collectConfigWarnings(config)は何もレイアウトせずに同じリストを返します。'oneAndHalf'以外のレイアウトはこの値を読まず、報告もしません。sideColumnRole: 'floats'では本文がサイド段に入ることはなく、サイド段はspan: 'side'で配置した図、表、囲みのための欄になります。サイドの図や表は、最初に参照されたページで欄の先頭から積まれます。教科書の欄外の図は、本文での参照がページの下のほうであっても、ページの上端に置かれます。欄の残りに収まらないものは次のページの欄に回ります。サイドの囲みは、中断したテキストの横に積まれます。欄の残りに収まらない場合は、収まる範囲で最も低い位置(囲みの下端が欄の下端に接する位置)まで上にずれるか、次のページに回ります。囲みのフェンスのあとのテキストが次のページに続く場合(段がいっぱいのときや、分割の規則がそのテキストを送るとき)、囲みはフェンスの位置、つまりその前のテキストの横に残ります。sideAtColumnEnd: 'after'を指定したスタイルでは、代わりにフェンスのあとのテキストの最初の行と同じ高さ、つまり次のページの欄に置かれます。行の前に書く行番号や欄外見出しには、この動作が必要です。欄の中に立つ見出しデザインの要素(外側の余白に固定した章番号など)も、積み重ねと重ならないように避けられます(確保する高さを参照)。span: 'page'のフロートと囲みは引き続き両方の段にまたがり、placement.captionSideを指定した段のフロートは、キャプションを欄の中、図と同じ高さに置きます。見開きの余白とsideColumnSide: 'outer'を組み合わせると、欄はすべてのページの外側の辺に置かれます。教科書の欄外段の形です。
#柱とノンブル
headerとfooterプロパティは、ページごとのヘッダーとフッターのスロットを制御します。ヘッダーとフッターは既存のページ余白の内側に描画されます。追加のスペースを確保することはなく、本文領域を狭めることもありません。
コンテナーの枠。'container'にアンカーした要素は、本文と仕上がり線のあいだの余白の帯に置かれ、本文領域の幅に広がります。ヘッダーのコンテナーは仕上がりの天から本文の上端まで、フッターのコンテナーは本文の下端から仕上がりの地までです。そのため、ヘッダーのtop-*アンカーとフッターのbottom-*アンカーは仕上がり線から測り、ヘッダーのbottom-*アンカーとフッターのtop-*アンカーは本文の端から測ります。コンテナーに裁ち落としやトンボの帯が含まれることはないため、page.cutLinesのオン・オフにかかわらず、ヘッダーやフッターは断裁後のページの同じ位置に来ます。本文領域の幅を超えて、または裁ち落としまで要素を広げるには、'page'(トリムボックス)か'bleed'にアンカーします。
スロットは統一されたデザインスロットモデルを使います。どの要素もplacementを持ち、そこにanchor(コンテナー、または#idで指定する別の要素へのアンカー)、省略可能なoffset、省略可能なsizeを指定します。従来のフラットなフィールドalign、marginFromBody、marginFromEdge、width: 'full'も入力としては引き続き受け付け、新しい形へ自動的に移行します。新しい形での同等の記述は以下で説明します。
各スロットはテキスト、罫線、ボックスの要素のリストを持ちます。配列の順序が描画順です(最初の要素が最初に描かれ、最後の要素がいちばん上に描かれます)。これはアンカーの指定にかかわらず変わりません。要素はリストで後に来る要素にもアンカーできる(anchor.to: '#ttl')ため、背景のボックスを先頭に置いたまま、その前面に来るテキストを基準に位置を決められます。
組み込みの既定値。headerまたはfooterがundefinedのとき、postextは空のスロットではなく、適切な組み込みの既定値を適用します。
- 既定のヘッダー:奇数ページでは
{title}を右そろえ、偶数ページでは{chapterTitle}を左そろえにし、全幅の罫線を引きます。いずれもパレットのメインカラー、Open Sans 8pt/600で、marginFromBodyは16pt(テキスト)/13pt(罫線)です。 - 既定のフッター:すべてのページで
{pageNumber}を中央に置きます。パレットのメインカラー、Open Sans 8pt/600で、marginFromBodyは16ptです。
組み込みの既定値を使わないようにするには、header: { elements: [] }(またはfooter: { elements: [] })と指定します。明示的な空のelements配列は「要素なし」としてそのまま保持され、既定値が適用されるのはundefinedのときだけです。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
elements | HeaderFooterElement[] | undefinedのときは組み込みの既定値、[]で無効 | テキスト要素と罫線要素の順序付きリスト。 |
#テキスト要素
テキスト要素は、プレースホルダーを置き換えたテンプレート文字列を描画します。プレースホルダーは{name}の構文で書き、{{と}}は波かっこそのものを出力します。
以下の表の既定値は、自分で追加したテキスト要素のものです。上の組み込みの既定値で説明した組み込みのヘッダーとフッターは、独自の値(パレットのメインカラーのOpen Sans 8pt/600)を持つ既製の要素であり、要素の既定値ではありません。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
kind | 'text' | — | 判別子。 |
id | string | — | スロット内で一意の固定ID。ほかの要素はanchor.to: '#id'でこの要素にアンカーします。Sandboxは作成時にIDを割り当てます。 |
content | string | '' | テンプレート文字列。以下のプレースホルダーに加えてが使えます。これは現在の章のH1の行に書いた属性です(# Title )。存在しない属性は警告なしに空文字列になります。改行、またはテンプレートや属性値に書いた2文字の\nは、overflowの値にかかわらず常に新しい行を始めます。プレースホルダーが文書からコピーするテキスト(タイトル、フロントマターのフィールド)は書かれたとおりに出力されます。そこで新しい行を始めるのは、タイトル内の改行のような実際の改行だけです。 |
align | 'left' | 'center' | 'right' | 'justify' | 'start' | 'end' | 'center' | 要素のボックス内での行の水平方向のそろえ。'justify'は、各段落の最終行を除いて折り返したすべての行の語間を広げ、行がボックスを満たすようにします。ドロップキャップの横の行は、その横の領域を満たします。hyphenateを指定すると、両端そろえの行の残りに収まらない単語も音節で分割して行を満たします。段落の最終行、広げるスペースのない行、折り返さないテキストは左そろえになります。両端そろえのテキストは段落ごとに組まれるため、段落間の空行は1つとして数えます。canvasとPDFはレイアウトが決めた位置にすべての単語を置き、HTMLはword-spacingでスペースを広げます。'start'と'end'はテキストのdirectionに従い、右から左のテキストではそれぞれ右と左になります。'left'と'right'はボックス自身の辺です。右から左のページの流れの中に組まれるデザイン(章扉の帯、見出しのデザイン)では流れが左右反転するため、本文と同じく、これらは開始側と終了側を指します。 |
direction | 'ltr' | 'rtl' | 'auto' | 文書の値 | テキストの基本方向。中立的な文字が置かれる位置、行内でのランの順序、'start'と'end'がどちらの辺を指すかを決めます。'auto'は解決後のテキストで方向性の強い最初の文字を読み取り、見つからなければ文書のdirectionに従います。アラビア語とヘブライ語のランは、基本方向にかかわらず右から左に読みます。アラビア文字を含むテキストには字間を付けず(テキスト全体でletterSpacingを無視します)、折り返しや省略の際にも単語を分割しません。ボックスより幅の広い単語ははみ出し、unbreakableWordOverflowとして報告されます。 |
parity | 'all' | 'odd' | 'even' | 'all' | 要素を表示するページ(ページ番号の奇偶。1ページ目は奇数)。 |
pages | 'all' | 'body' | 'opener' | 'part' | 'blank' | 'all' | 要素を表示するページの役割。parityと組み合わせて使います。配置が終わると、各ページは'blank'(奇偶合わせや区切りのための埋め、または内容なし)、'part'(部扉のページ)、'opener'(最初のブロックが、ページ全幅にわたる、または直前で改ページを強制するレベルの見出しであるページ。章の最初のページ)、'body'(それ以外すべて)のいずれかに分類されます。pages: 'body'は章扉で柱を隠し、pages: 'opener'はそこにだけノンブルを表示します。 |
fontFamily | string | 'EB Garamond' | フォントファミリー。 |
fontSize | Dimension | 8 pt | 文字サイズ。 |
fontWeight | number | 400 | フォントのウェイト(100〜900)。 |
italic | boolean | false | イタリックで描画するかどうか。 |
color | ColorValue | #000000 | テキストの色。 |
overflow | 'wrap' | 'ellipsis-start' | 'ellipsis-middle' | 'ellipsis-end' | 'clip' | 'ellipsis-end' | 要素で使える幅を超えるテキストを、エンジンがどう扱うか。'wrap'は行を複数行に折り返します。省略記号の各モードは各行を1行に保ち、先頭、中央、末尾のいずれかを…で省略します。'clip'は文字を挿入せずに、要素のバウンディングボックスで切り取ります。内容中の改行はどのモードでも有効で、省略と切り取りのモードは各行を個別に省略または切り取ります。overflowを省略した要素は'ellipsis-end'になるため、複数行になりうるもの(住所、長いタイトル)には'wrap'を指定してください。dropCapを持つテキストは、この値にかかわらず折り返します。'clip'の場合も、高さ固定のボックスの端で行は切り取られます。'ellipsis-end'と'ellipsis-start'は単語の境界で切り(The history of th…ではなくThe history of…)、省略記号にスペースやつなぎの約物(コンマ、コロン、ダッシュ、スラッシュ、始め括弧)が接することはありません。単語の途中で切るのは、それが避けられない場合、つまりその約物を除いた境界で切ると収まる分の半分未満しか残らない場合だけです。長い1語やURLがこれに当たります(http…ではなくhttp://exampl…)。ノーブレークスペースとノーブレークハイフン(U+2011。MS‑DOSなど)は単語の境界になりません。'ellipsis-middle'はどこででも切りますが、その両側のスペースは取り除きます。postext 1.4までは、どのモードもスペースを含め、収まる最後の文字で切っていました。 |
verticalAlign | 'top' | 'middle' | 'bottom' | 'middle' | 行より高いボックス(固定のplacement.size.height、またはアンカーした隣の要素によって引き伸ばされたボックス)の中で、テキストを置く位置。行がボックスより高い場合(高さ固定のボックスに入れた大きな数字、詰めたlineHeight)は、CSSのflexのそろえと同じく、そろえで空く側へはみ出します。'bottom'は最終行の行ボックスの下端をボックスの下端に合わせて上へはみ出し、'middle'は上下に均等にはみ出し、'top'は下へはみ出します。postext 1.4までは、そろえにかかわらず、こうした行は常に上端から下がっていました。dropCapは行と一緒に動きます(postext 1.4までは、'middle'や'bottom'で行が下へ移動したボックスでも、上端にとどまっていました)。 |
lineHeight | number | Dimension | 1.2 | 要素の行の行送り。数値はfontSizeの倍数です。設定内のほかの行送りと同じ書き方で、Dimensionも受け付けます。em/remは同じ倍数、絶対長(pt、mm、px…)はベースライン間の距離です。は、サイズにかかわらず15.5 ptの行送りにします。それ以外(ゼロ、負の数、不正なDimension)は既定値になります。postext 1.4までは、ここにDimensionを指定するとデザインの高さが測れなくなり、章扉はminHeightさえ確保せず、本文がタイトルに重なって組まれていました。 |
letterSpacing | Dimension | 0 | トラッキング。CSSのletter-spacingとまったく同じく、スペースを含むすべての文字の後に追加の送りを加えます。測定される幅もそのぶん増えるため、自動幅のボックスはぴったりのままです。行の最後の文字の後のトラッキングは、行のそろえと自動幅のボックスの計算から除外されます。そのため、字間を付けた中央そろえのタイトルは文字そのものを基準に中央に置かれ、右そろえのタイトルは端で終わり、両端そろえの行の最後の文字は端に届きます(postext 1.4までは、トラッキング単位の半分または1つ分だけ左にずれ、字間を付けた要素の右にアンカーした要素はトラッキング単位1つ分だけ離れていました)。負の値は字間を詰め(36 ptのディスプレイ用タイトルでは{ value: -0.3, unit: 'pt' }がよく使われます)、幅も同じように縮みます。canvas、HTML、PDFは同じように描画します(postext 1.4までは、負の値は警告なしに0として扱われていました)。 |
textTransform | 'none' | 'uppercase' | 'none' | 解決後のテキストに、プレースホルダーも含めて適用する大文字・小文字の変換。目次で部のタイトルを大文字で組む場合などに使います。 |
box | ElementBoxStyle | — | テキストの背後に描く背景と枠線(省略可)。backgroundColor、borderColor、borderWidth、borderRadiusと、テキストの外側へボックスを広げる辺ごとのpaddingを指定します(各フィールドはボックス要素を参照)。 |
dropCap | { lines, fontFamily, fontWeight, fontSize, color, gap } | — | ドロップキャップ。最初の文字を、最初のlines行(既定は2)の横に大きく組みます。書体、ウェイト、色は独自に指定でき、テキストとの間隔はgapです。この文字は、またがる最後の行のベースラインに立ちます。fontSizeの既定値は、文字の上端が1行目の大文字の高さとそろうサイズで、テキストのサイズにlines − 1行分の行送りを足したものです。大文字の高さは文字サイズの0.72とみなします(大文字がこれよりかなり高い、または低い書体では、独自のfontSizeを指定してください)。ドロップキャップを持つテキストは、overflowにかかわらず折り返します。'clip'の場合も、高さ固定のボックスの端で行は切り取られます。パレットにリンクしたcolorは、デザインのほかの部分と同じく部と節のパレットに従います。見出しのデザインでは、この文字はテキストの下にスペースを確保しません。ベースラインより下にある行ボックスの部分は本文を押し下げません。そのため、ベースラインより下に伸びる文字(多くの書体のQやJ)は、デザインの下のスペースに入り込むことがあります。その場合は見出しにmarginBottomを指定してください。postext 1.4までは、既定のサイズでは文字がまたがる行ボックス全体と同じ高さになったため、文字の上端が1行目より上に出ていました。また、'wrap'以外のoverflowでは警告なしに文字が消え、節や部のパレットでも色は変わらず、横のテキストと同じ深さまで下がる文字が本文をグリッド1行分押し下げることがありました。それより前に保存された設定は、1.4のサイズをfontSizeとして書き出した形で保持します(postext 1.4以前で書き出したバンドルを参照)。 |
paragraphIndent | Dimension | 0 | 最初の段落以外の各段落の1行目の字下げ。内容中の改行、または属性値から来るテキストでは2文字の\nが段落を区切ります。連続する改行は1つとして数えます。 |
hyphenate | boolean | false | trueでテキストが折り返す場合(overflow: 'wrap'、または常に折り返すdropCapがある場合)、通常の改行の後もはみ出す長い単語を、文書で有効なハイフネーションのロケールを使って音節の境界で分割し、分割位置にソフトハイフンを入れます。両端そろえのテキスト(align: 'justify')では、行の残りに収まらない単語も、収まる最後の音節の切れ目で分割して行を満たします。 |
inlineMarks | boolean | false | 解決後のテキストを、プレースホルダーの値も含めてインラインのMarkdownとして読みます。bold、italic、^superscript^、~subscript~が使えます。オフのときは記号が書かれたとおりに出力されます。インラインの書式記号と輪郭線を参照してください。 |
stroke | { width, color?, hollow? } | — | 文字の周りに描く輪郭線。width(Dimension。字形の縁を中心に描きます)、color(既定はテキストの色。ドロップキャップは自身の色を使います)、hollow(trueで輪郭線だけを描きます)を指定します。インラインの書式記号と輪郭線を参照してください。 |
writingMode | 'horizontal-tb' | 'vertical-rl' | 'horizontal-tb' | 'vertical-rl'は、文字を立てたまま、テキストを上から下へ、行を右から左へ組みます。小口に沿って縦に置く柱や、横組みの章の脇に置く縦書きのタイトルに使います。縦組みのテキスト要素を参照してください。 |
reserve | boolean | true | 見出しのデザイン専用。見出しがテキストの流れの中に確保する高さに、要素を含めるかどうか。テキストの下に重なってもよい装飾(ページ下部の印章、枠、側面の帯)にはfalseを指定します。確保する高さを参照してください。ヘッダー、フッター、部のデザインはこの値を無視します。 |
marginFromBody | Dimension | 6 pt | 要素の本文側の辺と本文の端との絶対距離。ほかの要素からは独立しています。placement.offset.yに移行されます。 |
marginFromEdge | Dimension | 0 pt | そろえた側の本文領域の端からの水平方向のインセット。alignが'left'または'right'のときだけ有効です。placement.offset.xに移行されます。 |
placement | ElementPlacement | align + marginFromBody + marginFromEdgeから導出 | 詳細な配置(下記参照)。指定すると、従来のフラットなフィールドより優先されます。 |
使えるプレースホルダー:
{pageNumber}— 現在のページの、1から始まるページ番号。{totalPages}— 文書の総ページ数。章ごとにレイアウトする本(Sandbox、buildBundle)では各章が1つの文書になるため、これはその章自身のページ数です。{bookTotalPages}— 本全体の総ページ数。すべての章と白ページを含みます。単独でレイアウトする文書では{totalPages}と同じです。下の本全体のページ数を参照してください。{title}、{subtitle}、{author}、{publishDate}—content.metadataから読む値。不明または空のメタデータは空文字列になります(Sandboxでは警告も出ます)。{chapterTitle}— 現在のページまで(そのページを含む)で最後に現れたH1のテキスト。見出しスタイルでrunningChapter: falseを指定したH1(図版、地図)は飛ばします。{chapterTitleAtTop}、{chapterNumberAtTop}— ページ上端の時点で有効な章のタイトルと番号。ほかのテキストの下で新しい章が始まるページでは、{chapterTitle}や{chapterNumber}と異なります。下のページ上端の章を参照してください。{partTitle}、{partNumber}— 現在の部のタイトルと番号(現在のページまでで最後の:::partページ。部扉の直前にある奇偶合わせの白ページは、すでにその部に属します)。最初の部より前では空です。{firstMark.<key>}、{lastMark.<key>}— ページの最初と最後の柱の見出し語。あるレベル(h1〜h6)の見出し、または段落スタイルの項目です。下の柱の見出し語:最初と最後のマークを参照してください。
本全体のページ数
{bookTotalPages}は本全体のページ数、つまり「12ページ/全348ページ」のように読者が目にする数を出力します。{totalPages}と同じく白ページを含めて物理的なページを数えますが、すべての章にわたって数えます。
- 単独でレイアウトする文書(
continuationなしのbuildDocument)は、それ自体が本全体です。{bookTotalPages}は{totalPages}と等しくなります。 buildBundleの場合、本をレイアウトして全章のページ数を合計し、その合計を使ってもう一度レイアウトするため、どの章も同じ数を出力します。この数が改ページの位置を動かすことはないので、1回の追加ラウンドで確定します。{bookTotalPages}を出力しない設定には追加のコストはかかりません。- Sandboxは、すべての章のページ数がわかった時点で各章に合計を渡します。それまでは、各章はその章の終わりまでのページ数を出力します。PDFタブの本全体の書き出しは、自身がレイアウトしたページを数えます。ページ数がまだわかっていなかったために別の数を出力した章があれば、各章から得た数でもう一度本をレイアウトします。
- 章を自分でレイアウトするホストは、合計を
continuation.bookPageCountとして渡します(最初の章にも渡します)。渡さない場合、{bookTotalPages}は文書の終わりまでのページ(continuation.pageIndexOffsetと自身のページ数の和)を数えます。これが正しいのは最後の章だけです。
footer: {
elements: [{
kind: 'text', id: 'folio', content: '{pageNumber} / {bookTotalPages}',
fontSize: { value: 8, unit: 'pt' },
placement: { anchor: { to: 'container', edge: 'top' }, size: { width: 'auto', height: 'auto' } },
}],
}configUsesPlaceholder(config, 'bookTotalPages')を使うと、ホストはこの数を計算する価値があるかどうかを判断できます。
柱の見出し語:最初と最後のマーク
辞書は各ページの柱に、そのページの最初と最後の見出し語を掲げます(「Aback – Anchor」)。参考図書なら最初と最後の節を掲げます。{firstMark.<key>}と{lastMark.<key>}はこれを出力します。キーは、何がページのマークになるかを指定します。
h1〜h6:そのレベルの見出し。マークは番号を除いた見出しのテキストです。- 段落スタイルのID(
entry)::::paragraphs{style="entry"}コンテナーの段落。マークは段落冒頭の太字の部分、つまり見出し語で、末尾の約物は除きます。**Aback.** Said of…はAbackをマークにします。太字で始まらない段落はマークを設定しません。
{firstMark.<key>}はページ内で始まる最初のマーク、{lastMark.<key>}は最後のマークです。マークが始まらないページ(長い項目が続いているページ)では、どちらもその時点で有効なマーク、つまりそれより前の最後のマークを出力します。最初のマークより前のページには何も出力されません。章ごとにレイアウトする本では、マークは次の章へ引き継がれないため、これはその章の最初のマークより前のページを指します。ページをまたいで分割された見出しや段落は、始まるページだけをマークします。キーはドットの後に英字、数字、_、-で書き、英字か_で始めます。不明なキーは何も出力しません。
:::paragraphs{style="entry"}
**Aback.** Said of the sails when pressed back against the mast.
**Abaft.** Towards the stern, or behind a given point.
:::header: {
elements: [{
kind: 'text', id: 'guide', content: '{firstMark.entry} – {lastMark.entry}',
fontSize: { value: 8, unit: 'pt' },
placement: { anchor: { to: 'container', edge: 'bottom' }, size: { width: 'auto', height: 'auto' } },
}],
}柱の見出し語は柱として扱われます。ヘッダーとフッターのスロット(見出しスタイルのheaderとfooterを含む)で解決され、見出し、部、目次のデザインでは何も出力しません。見出しと部のデザインでは、検査パネルが不明なプレースホルダーとして警告します。偶数ページに最初の見出し語、奇数ページに最後の見出し語を出すには、2つの要素にparity: 'even'とparity: 'odd'を指定します。見出しスタイルでrunningChapter: falseを指定したH1は、h1のマークを設定しません。
ページ上端の章
{chapterTitle}と{chapterNumber}は、そのページまでに始まった最後の章を指します。章のあいだで改ページせずに続けて組む本では、ある章を終えてページの下のほうで次の章を始めるページに、まだ前の章に属するテキストの上に新しい章のタイトルが載ってしまいます。{chapterTitleAtTop}と{chapterNumberAtTop}は、代わりにページ上端の時点で有効な章を指します。章を続けて組む小説や、多くの参考図書がこの方式です。
- 最初のブロックが章のH1であるページは、その章を指します。
- それ以外のページは、新しい章がページの下のほうで始まる場合でも、前から続いている章を指します。
- 奇偶合わせのために追加された白ページは次のページに、
always-*モードの区切りページは前のページに従います(白ページの帰属を参照)。奇偶合わせの白ページの次のページが章のH1ではなく章の終わりで始まる場合、その白ページもその章のままです。 runningChapter: falseのH1は飛ばします。
header: {
elements: [{
kind: 'text', id: 'chapter', content: '{chapterTitleAtTop}', parity: 'odd', pages: 'body',
fontSize: { value: 8, unit: 'pt' },
placement: { anchor: { to: 'container', edge: 'bottom-right' }, size: { width: 'auto', height: 'auto' } },
}],
}柱の見出し語と同じく、どちらも柱として扱われます。ヘッダーとフッターのスロットで解決され、見出し、部、目次のデザインでは何も出力しません。{attr.<key>}は常に最後に始まった章を読みます。
基準辺から決まるそろえ
テキスト要素のplacement.anchor.toが#idで別の要素を参照するとき、アンカーの基準辺によって、折り返した行の既定のそろえが決まります。
right-ofとalign-leftはテキストのalign: 'left'を意味します。折り返した行はアンカーから右へ流れます。left-ofとalign-rightはalign: 'right'を意味します。折り返した行はアンカー先に最も近い側に寄ります。
Sandboxの見出しエディターは、アンカーの基準辺や対象を変更すると、これらのそろえを自動的に適用します。これにより、複数行に折り返したテキストが、関連する要素に視覚的に結び付いたままになります(たとえば、折り返した「Postext」の「P」が「Introduction」の「I」の真下にそろいます)。
インラインの書式記号と輪郭線
テキスト要素は既定では1つの書体でテキストを組み、**、^などのMarkdownの記号は書かれたとおりに出力されます。inlineMarks: trueを指定すると、解決後のテキストをインラインのMarkdownとして読み、本文と同じ書式記号が使えます(文書形式 → インライン書式を参照)。
**bold**はウェイトを700にします(要素自身のfontWeightのほうが太ければそれを使います)。*italic*は要素の傾きを反転するため、イタリックの要素の中の強調は立体になります。***both***は両方を適用します。アンダースコアの形(__bold__、_italic_)も使えます。^superscript^と~subscript~はサイズの58%で組み、上付きはサイズの3分の1だけ上げ、下付きは0.15だけ下げます。接する下付きと上付き(T~0~^2^)は、本文と同じく上下に重ねて組みます。- バックスラッシュを付けると記号の文字そのものを組みます(
\*、\_、\^、\~)。リンクはテキストを残し、コードのバッククォートは取り除きます。
書式記号はプレースホルダーを埋めた後で読むため、値に書式記号を含めることができます。たとえば見出しの属性に書いた著者の行で、所属の番号を上付きにできます。折り返し、両端そろえ、省略記号のモード、dropCap、paragraphIndentは、いずれも書式付きのテキストで動作します。どのランも要素の色を保ちます。
{ "kind": "text", "id": "authors", "content": "{attr.authors}", "inlineMarks": true, "overflow": "wrap",
"fontSize": { "value": 11, "unit": "pt" },
"placement": { "anchor": { "to": "#title", "edge": "below" }, "offset": { "y": { "value": 6, "unit": "pt" } } } }# Snow cover and river flow {authors="Ana Ruiz^1^, Luis Gil^2^ and Marta Sanz^1,3^"}strokeは文字の周りに輪郭線を描きます。widthは線幅で、字形の縁を中心に描きます(半分が文字の内側、半分が外側に入り、測定されるテキストの幅は変わりません)。colorの既定はテキストの色です。hollow: trueは文字を塗らずに輪郭線だけを見せます。袋文字のディスプレイ数字や、写真の上で浮き立たせたいタイトルに使います。輪郭線は塗りの上に描かれ、canvas、HTML(-webkit-text-stroke)、PDF(テキスト描画モード2、hollowのときは1)で同じように描画されます。
{ "kind": "text", "id": "year", "content": "1863", "fontFamily": "Bitter", "fontSize": { "value": 120, "unit": "pt" }, "fontWeight": 700,
"color": { "hex": "#1d3557", "model": "hex" },
"stroke": { "width": { "value": 1.5, "unit": "pt" }, "hollow": true },
"placement": { "anchor": { "to": "page", "edge": "bottom-right" }, "offset": { "x": { "value": -15, "unit": "mm" }, "y": { "value": -20, "unit": "mm" } } } }PDFでは、太字とイタリックのランに要素のファミリーの対応する書体を埋め込むため、フォントプロバイダーがそれらを提供する必要があります。
テキストの既定値とアンカーの落とし穴
自分で書いたテキスト要素は次の値から始まります。意外に思えるものもあります。
overflowは'ellipsis-end'。幅に収まらないテキストは…付きの1行に切り詰められます。住所、著者名の行、長くなりうるタイトルにはoverflow: 'wrap'を指定してください。内容中の改行(改行文字、またはテンプレートや属性値に書いた\n)はどのモードでも新しい行を始め、省略記号のモードは各行を個別に切り詰めます。alignは'center'、verticalAlignは'middle'。自動幅の要素は最も長い行に合わせて縮むため、各行は互いに中央そろえになります。左そろえのブロックにするにはalign: 'left'を指定します(別の要素にアンカーした自動幅の要素は、アンカー側に自動的に行をそろえます。前述)。- 書体はEB Garamond 8 pt、黒、
lineHeightは1.2。本文が何を使っていても変わりません。設定内のほかの行送りと異なり、デザインのテキストのlineHeightは通常は単純な倍数(1.2)で書きます。Dimensionも使えます(前述)。
placement.size.widthを指定しない(または'auto'の)要素はテキストに合わせて自身のサイズを決めますが、その範囲は、アンカー点から、要素が伸びていく方向のコンテナーの端までの領域に限られます。topまたはbottomのアンカーでは、近いほうの端までの距離の2倍です。offsetも計算に入ります。その端から離れる方向のオフセットは領域を減らしません。top-leftにアンカーし、負のxで左の余白にはみ出させた柱は右へ伸びるため、領域は減りません。その端に向かうオフセットは、同じだけ領域を減らします(topまたはbottomのアンカーでは2倍)。アンカー点を端の外へ押し出すオフセットでは、領域がなくなります。xがコンテナー幅のマイナス値より小さいtop-rightのアンカーや、幅の半分を超えて横にずらしたtopのアンカーがこれに当たります。領域がなくなると、省略記号のモードは何も出力せず、'wrap'は1行に1文字ずつ積み重ねます。回避策は3つあります。
- 要素に固定の
size.widthを指定する。固定幅は切り詰められません。 'page'または'bleed'にアンカーし、ページ(または裁ち落とし)の枠を領域にする。- コンテナーの反対側の端にアンカーする。
ヘッダーのコンテナーは仕上がりの天から本文まで、フッターのコンテナーは本文から仕上がりの地までです。ヘッダーではtop-*アンカーは仕上がり線から、bottom-*アンカーは本文から測り、フッターではその逆です(上のコンテナーの枠を参照)。ヘッダーとフッターは本文のテキストを動かさず、その上に描画されるため、本文領域に押し出された要素はテキストを覆い隠します。一方、章扉の帯は本文のテキストの下に描画されます。それが流れの中でどれだけの領域を占めるかは確保する高さで説明しています。
#縦組みのテキスト要素
writingMode: 'vertical-rl'を指定したテキスト要素は、テキストが横組みのスロットの中で縦に組まれます。対象は、どの本でも紙面上の向きが変わらない柱とノンブル、そして横組みのページのあらゆるデザインです。要素は、時計回りに90度回した独自の枠の中で横組みのテキストとしてレイアウトされ、ページの向きに戻されます。
- ボックスは配置で指定した位置にとどまります。高さは1行の長さで、
size.heightで指定します('auto'ならテキストの長さ、'fill'ならコンテナーの端まで)。size.widthは横方向に何行収まるかを決め、size.maxWidthは1行の長さの上限を決めます。 alignはボックスに沿った行の位置('left'は上端)、verticalAlignはボックスを横切る方向の位置('top'は最初の行が立つ右端)を決めます。ボックスのパディングは、指定した辺に残ります。- 文字は縦組みのページと同じく測定・描画されます。漢字は1字ずつ全角で正立し、約物は縦組み用の字形になり、欧文の単語は横倒しになり、短い数字は1字分に収まります(
cjk.uprightDigits)。canvas、PDF、HTMLは同じ矩形の位置に配置します。 inlineMarks: trueを指定すると、本文と同じく向きの記号でランを区別できます。第:tcy[3.0]回は3.0を1字分に組み、:upright[GDP]は文字を正立させて縦に並べ、:sideways[…]はランを横倒しにします。これらの内部で行が分割されることはありません。横組みの要素はこれらを無視します。- 縦組みの要素にはドロップキャップを組みません。
- 縦組みのページの流れの中(章扉、部扉、囲みのタイトル)では、テキストはもともと縦に組まれるため、
writingModeは何も変えません。
縦組みの中国語の本では、柱とノンブルを次の3か所のいずれかに置きます(clreq §7.2。日本語についてはJLREQ §2.6)。
| 方式 | 位置 | 設定方法 |
|---|---|---|
| 横組みの柱とノンブル | 横組みの本と同じく、版面の上と下。最も一般的です。 | ヘッダーとフッターをそのまま使います。 |
| 小口(中缝式、台湾では邊峰) | 外側の余白に沿って縦に置きます。章名または書名は版面の天から約4字下から始め、ノンブルは版面の地から約5字上で終わるように置き、漢数字で、本文の約80%のサイズで組みます。 | 下に示す、'outer'にアンカーした2つの縦組みの要素。 |
| 小口側の地の角 | ページの地の、小口側の角に置くノンブル(台湾の中式の本の規則)。 | 右綴じの本では、フッターに'bottom-left'でparity: 'odd'の横組みの要素と、'bottom-right'でparity: 'even'の要素を置きます(左綴じの本では逆)。 |
Sandboxが追加する小口の柱(ヘッダー › 小口の柱(縦組み))を、本文が10 ptの場合で示します(Sandboxは本文サイズの80%で組みます)。
{
"page": { "pageNumbering": { "format": "trad-chinese-informal" } },
"header": { "elements": [
{ "kind": "text", "id": "head", "content": "{chapterTitle}", "writingMode": "vertical-rl",
"fontSize": { "value": 8, "unit": "pt" }, "overflow": "clip", "align": "left",
"placement": { "anchor": { "to": "outer", "edge": "top" }, "offset": { "y": { "value": 4, "unit": "em" } } } },
{ "kind": "text", "id": "folio", "content": "{pageNumber}", "writingMode": "vertical-rl",
"fontSize": { "value": 8, "unit": "pt" }, "overflow": "clip", "align": "left",
"placement": { "anchor": { "to": "outer", "edge": "bottom" }, "offset": { "y": { "value": -5, "unit": "em" } } } }
] }
}anchor.to: 'outer'(要素の配置を参照)は各ページの外側の余白を指すため、右綴じの本(page.binding)では、2つの要素は奇数ページの左端と偶数ページの右端に沿って縦に並びます。{pageNumber}はノンブルの書式で出力されます。103ページは、trad-chinese-informalでは一百零三、cjk-decimalでは一〇三になります。スロットごとの規則はここでも有効で、pages: 'body'を指定すると章扉には柱を出しません。柱とノンブルのあいだに置く短い飾り(魚尾︻、罫線)は、同じ枠にアンカーした通常の要素です。
VDTでは、縦組みのブロックはverticalを持ちます(VDTDesignTextBlock.vertical:領域、正立させる数字、各ファミリーの中心軸)。行はブロック自身の回転した枠の中にあり、xOffsetはボックスの上端から下向き、baselineYは右端から左向きに測ります。書式記号付きの行のランは、本文のセグメントと同じくtcyまたはorientationを持ちます。
#罫線要素
罫線要素は線を描画します。スロットを横切る水平線、またはスロットを縦に走る垂直線です。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
kind | 'rule' | — | 判別子。 |
id | string | — | スロット内で一意の固定ID。anchor.to: '#id'での参照に使います。 |
direction | 'horizontal' | 'vertical' | 'horizontal' | 水平の罫線はplacement.size.widthに沿って伸び('fill'はコンテナーの端まで)、高さはthicknessです。垂直の罫線はplacement.size.heightに沿って下へ伸び('fill'または未指定はコンテナーの端まで)、幅はthicknessです。柱とノンブルのあいだの区切り線などに使います。 |
color | ColorValue | #000000 | 線の色。 |
thickness | Dimension | 0.5 pt | 線の太さ。省略した罫線は既定値で描かれます(postext 1.4までは何も描かれませんでした)。 |
width | Dimension | 'full' | 'full' | 'full'は本文領域の幅に広がります。Dimensionを指定すると、alignで位置を決める固定長の線になります。 |
align | 'left' | 'center' | 'right' | 'center' | widthが'full'でないときのそろえ。 |
marginFromBody | Dimension | 6 pt | 罫線の本文側の辺と本文の端との絶対距離。ほかの要素からは独立しています。 |
marginFromEdge | Dimension | 0 pt | そろえた側の本文領域の端からの水平方向のインセット。widthが固定のDimensionで、alignが'left'または'right'のときだけ有効です。 |
parity | 'all' | 'odd' | 'even' | 'all' | 罫線を表示するページ。 |
pages | 'all' | 'body' | 'opener' | 'part' | 'blank' | 'all' | 罫線を表示するページの役割(テキスト要素のpagesフィールドを参照)。 |
reserve | boolean | true | 見出しのデザイン専用。見出しがテキストの流れの中に確保する高さに、罫線を含めるかどうか(確保する高さを参照)。 |
placement | ElementPlacement | align + marginFromBody + marginFromEdgeから導出 | 詳細な配置(要素の配置を参照)。size.width/size.heightで罫線の長さを指定します。width: 'fill'は従来の'full'に当たります。 |
#ボックス要素
ボックス要素はスロットの中に角丸の矩形を描きます。章扉、サイドバー、フッターでテキストの背景にするのに便利です。ボックス要素の位置はplacementフィールドだけで決まり、従来のフラットな省略形はありません。塗り、線、角の半径は、「要素の配置」のJSONの例のように、入れ子のstyleオブジェクト(ElementBoxStyle)に指定します。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
kind | 'box' | — | 判別子。 |
id | string | — | スロット内で一意の固定ID。同じスロットの要素はanchor.to: '#id'でこの要素にアンカーします。Sandboxは作成時にIDを割り当てます。 |
style.backgroundColor | ColorValue | transparent | 塗りの色。輪郭だけのボックスにするにはtransparentを指定します。 |
style.borderColor | ColorValue | transparent | 線の色。 |
style.borderWidth | Dimension | 0 pt | 線の太さ。線はボックスのバウンディング矩形の内側に描かれるため、外形の寸法は変わりません。線の外縁はボックスの縁に沿い、角丸のボックスは外側の半径を保ちます。ボックスと同じ太さの線はボックスを塗りつぶします。canvas、HTML、PDFは同じように描きます(postext 1.4までは、canvasとPDFは線を縁の中心に描いたため、半分がボックスの外に出ていました)。 |
style.borderRadius | Dimension | 0 pt | 角の半径。描画時に、短いほうの辺の半分までに制限されます。 |
placement | ElementPlacement | — | 必須。下の「要素の配置」を参照してください。 |
parity | 'all' | 'odd' | 'even' | 'all' | ボックスを表示するページ。 |
pages | 'all' | 'body' | 'opener' | 'part' | 'blank' | 'all' | ボックスを表示するページの役割(テキスト要素のpagesフィールドを参照)。 |
reserve | boolean | true | 見出しのデザイン専用。見出しがテキストの流れの中に確保する高さに、ボックスを含めるかどうか(確保する高さを参照)。 |
#画像要素
image要素は、文書のビットマップまたはSVGのリソースを描きます。扉ページの出版社のロゴや、柱に添えるマークなどです。サイズはplacement.sizeで決まります。width/heightの一方を'auto'(既定)のままにすると、もう一方は画像の縦横比に従います。両方を指定すると、画像はボックスに収まるように合わせて中央に置かれます。存在しないリソースや画像でないリソースは何も描きません。
{
kind: 'image', id: 'logo', resourceId: 'logo-publisher',
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: { value: 64, unit: 'mm' }, y: { value: 233, unit: 'mm' } }, size: { width: { value: 83, unit: 'mm' }, height: 'auto' } },
}resourceIdには、テキスト要素のcontentと同じプレースホルダーが使えるため、1つのデザインで見出しごとに異なる画像を描けます。見出しスタイルにresourceId: '{attr.vignette}'を指定すると、# Chapter I {style="opener" vignette="log"}はリソースlogを、# Chapter II {style="opener" vignette="wig"}はwigを描きます。画像ごとにスタイルを複製しなくても、各章で同じスタイルを共有できます。ヘッダーとフッターは、柱の{attr.<key>}と同じくページの章の属性を読み、ほかのプレースホルダーも使えます('map-{chapterNumber}')。見出しに属性がない場合など、空になったIDは何も描きません。postext 1.8から使えます。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
id | string | — | 固定の識別子。ほかの要素は#idとしてこれにアンカーできます。 |
resourceId | string | — | 文書のビットマップまたはSVGのResourceのID。{attr.<key>}をはじめとするプレースホルダーを含めることができ、見出し、部、ページごとに埋められます。 |
decorative | boolean | false | 画像は装飾専用です(飾り、帯)。リソースに代替テキストがあっても、出力に代替テキストを渡しません(下記参照)。 |
placement | ElementPlacement | — | アンカー、オフセット、サイズ(要素の配置を参照)。'fill'の辺はコンテナーの端まで伸びます。 |
parity、pages | 上と同じ | 'all' | 画像を表示するページ。 |
reserve | boolean | true | 見出しのデザイン専用。見出しがテキストの流れの中に確保する高さに、画像を含めるかどうか(確保する高さを参照)。 |
PDFバックエンドはリソースを図と同じように埋め込み(印刷用マスターを持つSVGはそれを使います)、HTMLビューアーはresourceImageUrlで解決します。
リソースが画像を説明している場合、デザインが描く画像はコンテンツになります。リソースのaltText、なければプレーンテキストにしたキャプション(チップはラベルで、:refはtextがあればそれで読みます)がVDTに入り(VDTDesignImageBlock.altText)、HTMLでは<img>のalt、タグ付きPDFでは/Altを持つFigureになり、そのデザインのテキストの直後に読まれます(章の図版は章の見出しの後)。リソースにどちらもない画像と、decorativeを指定した画像は装飾になり、HTMLではrole="presentation"付きのalt=""、PDFではアーティファクトになります。柱やフッターの画像はすべてのページで繰り返されるため、リソースの内容にかかわらず紙面の付属物として扱われます。VDTにはaltTextがなく、HTMLではrole="presentation"付きのalt=""、PDFではページのアーティファクトになります。
#要素の配置
ElementPlacementは、あらゆるデザインスロット(ページのヘッダー、ページのフッター、見出しレベルの詳細デザインのスロット)の中で、すべての種類の要素(テキスト、罫線、ボックス)が使う統一された配置モデルです。配置は3つの状態で記述します。
interface ElementPlacement {
/** What this element anchors to and which edge of that target. */
anchor: {
to: 'container' | 'page' | 'bleed' | 'outer' | `#${string}`; // container = the slot; page = trim box; bleed = trim box + bleed; outer = the outer margin (header, footer); #id = another element
edge: AnchorEdge;
};
/** Distance from the anchor point. */
offset?: { x?: Dimension; y?: Dimension };
/** Optional fixed width / height. Width also accepts 'fill' (span the slot).
* `maxWidth` caps an 'auto' width (text): the element still shrink-wraps its
* content, so elements anchored to it stay attached, but a long text wraps or
* ellipsizes there — a running head can reserve room for the label hanging
* off it instead of squeezing that label out. */
size?: { width?: Dimension | 'fill' | 'auto'; height?: Dimension | 'fill' | 'auto'; maxWidth?: Dimension };
}AnchorEdgeには次の値を指定できます。
- コンテナーの辺(
anchor.toが'container'、'page'、'bleed'のとき):top、top-left、top-right、bottom、bottom-left、bottom-right、left、right。 - 要素基準の辺(
anchor.to === '#someId'のとき):right-of、left-of、below、above、align-top、align-bottom、align-left、align-right。
要素基準の各辺は、要素の角の1つをアンカー先の要素の角に合わせ、offsetでそこから移動します。
right-of:要素の左上の角を対象の右上の角に合わせます(横に並び、上端がそろいます)。left-of:要素の右上の角を対象の左上の角に合わせます。below:要素の左上の角を対象の左下の角に合わせます(下に置き、左端がそろいます)。above:要素の左下の角を対象の左上の角に合わせます。align-topとalign-left:要素の左上の角を対象の左上の角に合わせます。2つの名前は同じ配置になり、上端と左端の両方がそろいます。align-bottom:要素の左下の角を対象の左下の角に合わせます。align-right:要素の右上の角を対象の右上の角に合わせます。
'container'、'page'、'bleed'とともに使った要素基準の辺、および'#id'とともに使ったコンテナーの辺は、左上の角として扱われます。
anchor.to: 'page'は要素をトリムボックス(断裁後の物理的なページ)に、'bleed'は各辺をcutLines.bleedだけ広げたトリムボックスにアンカーします(トンボが無効な間はトリムボックスと同じです)。どちらの枠もsize: 'fill'と自動幅の制限の基準にもなるため、ページの余白にかかわらず、色の帯を端から端まで伸ばせます。
{ "kind": "box", "id": "band", "placement": { "anchor": { "to": "bleed", "edge": "top-left" }, "size": { "width": "fill", "height": { "value": 6, "unit": "cm" } } }, "style": { "backgroundColor": { "hex": "#1d3557", "model": "hex" } } }トンボを有効にすると、要素が裁ち落としの枠の外に描いた部分は切り取られます(トンボを参照)。
anchor.to: 'outer'(ヘッダーとフッターのスロット)は、要素をページの外側の余白にアンカーします。これは、背と反対側で版面の端から仕上がり線までの範囲、かつ版面の天から地までの範囲です。左綴じの本では奇数ページの右側と偶数ページの左側、右綴じの本ではその逆になる(page.binding)ため、1つの要素で見開きの両ページに対応できます。小口に沿って縦に置く柱がその例です(縦組みのテキスト要素を参照)。ほかのスロットでは'container'として扱われます。テキスト要素のoffsetは、要素自身のfontSizeを単位とするemで書けます。版面の天から4字下は{ "y": { "value": 4, "unit": "em" } }です。
見出しの詳細デザインのスロットの中では、ページや裁ち落としにアンカーした要素は、見出しの上端より下に伸びない限り、見出しのために確保する高さを増やしません(ページ上部を横切る帯は章扉の背後に置かれ、見出しより下まで伸びる帯は本文のテキストを押し下げます)。章扉の高さをどんな場合でも固定で確保するにはadvancedDesign.minHeightを使い、テキストをまったく押し下げてはいけない要素にはreserve: falseを指定します。詳しい規則は確保する高さにあります。
各要素は固定のidを持ちます(Sandboxが自動で割り当てますが、手で設定することもできます)。ほかの要素にアンカーした要素は小さな依存グラフを作り、エンジンは測定の前にそれを解決します。そのため、手作業で座標を指定しなくても、要素を別の要素に連ねて配置できます。
従来のalign + marginFromBody + marginFromEdgeの形は入力時に解析され、設定の解決時にplacementへ書き換えられるため、既存の設定は変更なしでそのまま動作します。
#本文
bodyTextプロパティは、すべての段落テキストの文字組みを制御します。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
fontFamily | string | 'EB Garamond' | 本文のフォントファミリー。Google Fontsのフォント、システムフォント、customFontsで宣言した独自のファミリーのいずれでも指定できます。指定するのは1つのファミリーで、CSSのフォントスタックではありません(後述)。 |
fontSize | Dimension | 8 pt | 本文の基本の文字サイズ。 |
lineHeight | Dimension | 1.5 em | 行と行の縦方向の間隔。相対単位(em、rem)は文字サイズに合わせて伸縮します。 |
paragraphSpacing | boolean | false | 有効にすると、続く段落の間に空行(lineHeightと同じ高さ)を挟み、出版物で見られるように段落を区切ります。 |
color | ColorValue | #000000 | 文字の色。 |
boldColor | ColorValue | メインカラー(#295AA3) | 太字(strong)の範囲に適用する色。既定のパレットのmain-colorエントリーから解決されるため、パレットの色を変えると文書中のすべての太字の色が変わります。 |
italicColor | ColorValue | メインカラー(#295AA3) | イタリック(強調)の範囲に適用する色。既定値はboldColorと同じくパレットに連動します。 |
referenceColor | ColorValue | メインカラー(#295AA3) | インラインの:refラベル(リソースへの参照)に適用する色。既定値はboldColorと同じくパレットに連動します。パレットに従うのはpostext 1.5からで、1.4まではメインカラーにかかわらず#295AA3のままでした。 |
referenceBold | boolean | true | インラインの:refラベルを太字のフォントで描画します。 |
referenceItalic | boolean | false | インラインの:refラベルをイタリックで描画します。 |
emphasis | 'auto' | 'italic' | 'bold' | 'color' | 'overline' | 'auto' | …の組み方。イタリック、太字の書体とboldColor、italicColorで色を付けた正体、語の上に線を引いた正体のいずれかです。'auto'は、アラビア文字で書かれた文書では'bold'、それ以外では'italic'になります。アラビア文字のテキストを参照してください。 |
tashkil | 'keep' | 'strip' | 'strip-vowels' | 'keep' | アラビア語の母音記号。書かれたとおりに組むか、すべて取り除くか、シャッダ以外を取り除くかを選びます。アラビア文字のテキストを参照してください。 |
textAlign | 'left' | 'justify' | 'start' | 'end' | 'justify' | 行そろえ。'left'(または'start')は行が始まる側を指し、右から左に組む段落では右になります(テキストの方向を参照)。両端そろえでは、各行の間隔を配分して両端をそろえます。両端そろえの段落の最終行は自然な幅のまま行末不ぞろいで描画します。ただし、Knuth-Plassがグルーの縮みを前提に詰まりすぎの最終行を採用した場合は例外で、語間を詰めて行長にちょうど収めます(TeXのグルー設定の意味論で、Canvas、HTML、PDFの各バックエンドで同じように適用します)。 |
fontWeight | number | 400 | 通常のテキストのウェイト(100–900)。 |
boldFontWeight | number | 700 | 太字(strong)のテキストのウェイト(100–900)。 |
hyphenation | HyphenationConfig | 有効、'en-us' | 自動ハイフネーションの設定。後述します。 |
firstLineIndent | Dimension | 1.5em | 各段落の1行目に適用する字下げ(ぶら下げインデントが有効なときは1行目以外のすべての行に適用)。 |
hangingIndent | boolean | false | 有効にすると、字下げを1行目以外のすべての行に適用します(ぶら下げインデント)。 |
indentAfterHeading | boolean | true | falseにすると、見出しの直後の段落を1行目の字下げなしで描画します。科学系の出版物や多くの書籍のスタイルでよく使われる組版の慣習です。:::spaceの行の直後の段落にも同じ扱いを適用します。その間にあっても本文の外に置かれる囲み(サイド段span: 'side'に置いたもの、ページの天や地にフロートとして配置したもの、固定したもの)とフロートの図は飛ばして判定します。その段の中では段落は見出しに続いているものとみなされ、字下げなしで組まれます。本文中に置いた囲み(placement: 'here')は数に入り、その後の段落は字下げされます。hangingIndentが有効なときは効果がありません。 |
maxWordSpacing | number | 2 | 両端そろえのテキストの語間の上限。通常のスペース幅に対する倍率で表します。Knuth-Plassは、段落が許す限りすべての行をこの範囲に収め、そのためにまず語をハイフネーションするか、余りを隣の行に振り分けます。どの改行位置の組み合わせでも収まらない行は上限を超えて伸び、通常のスペースの3倍を超える行は行末不ぞろいで組みます。この比率を超えた行は「ゆるい行」とみなします。改行処理で埋められない行を参照してください。代わりに少しトラッキングを加えたい場合はmaxJustifyTrackingを使います。 |
minWordSpacing | number | 0.6 | 両端そろえのテキストの語間の下限。通常のスペース幅に対する倍率で表します。 |
maxJustifyTracking | number | 0 | 語間だけではmaxWordSpacingを超えて伸びるかminWordSpacingを超えて縮む両端そろえの行が取れるトラッキングの最大量。1/1000em単位で、伸ばす方向にも詰める方向にも働きます(InDesignの単位で、10は1文字あたり0.01em)。上限を超えた分の調整を文字間に回すので、ゆるい行の語間はmaxWordSpacingまで戻り、詰まった行はminWordSpacingで収まります。Knuth-Plassは改行位置を選ぶときにこれを考慮しますが、対象は語間だけでは上限・下限を超えてしまう行に限られます。それ以外の行、段落の最終行(はみ出す場合を除く)、1語だけの行、チップを含む行にはトラッキングを加えません。行はこれをletterSpacingとして記録し、Canvas、HTML、PDFが描画します。optimalLineBreakingが必要です。0で無効になります。最後の手段としてのトラッキングを参照してください。 |
kashida | 'auto' | 'none' | アラビア文字の文書では'auto'、それ以外は'none' | カシーダ(kashida)による両端そろえ。アラビア文字のテキストの両端そろえの行は、余りをまず語間(その幅の4分の1まで)で吸収し、次にカシーダで吸収します。カシーダは、つながった2文字の間に挿入するタトウィール(U+0640)1文字単位の伸長で、字間を広げることはありません。Knuth-Plassは各語の伸長を伸びとして数えます。ラテン文字の語、数字、見出し、行末不ぞろいの行、段落の最終行には使いません。タトウィールは描画されますが、プレーンテキストやコピーしたテキストには含まれません。アラビア文字のテキストのカシーダを参照してください。 |
kashidaPatterns | 'auto' | 'naskh' | 'simple' | 'nastaliq' | 'auto' | どの連結部にどの順でカシーダを入れるか。古典的なナスフ体(Naskh)の規則、Microsoftの優先順位、ナスタアリーク体(Nastaʿlīq)向けに調整したナスフ体の規則(raqim-kashidaに倣ったもの)から選びます。'auto'は本文のフォントから判断します。ルクア体(Ruqʿa)やディーワーニー体(Dīwānī)の書体(Aref Ruqaa)ではなし、ナスタアリーク体の書体ではナスタアリーク体の規則、それ以外ではナスフ体の規則です。 |
kashidaPerWord | number | 1 | 1語あたりの伸長の最大数。 |
kashidaMaxLength | number | 0.6 | 1か所の連結部での伸長の最大長(em単位)。収まるだけのタトウィールを1文字単位で入れます。 |
optimalLineBreaking | boolean | true | 先頭から順に詰める貪欲法ではなく、Knuth-Plassの最適改行を使います。段落全体で語間がより均一になります。中国語・日本語・韓国語の段落(東アジアの文字組みを参照)や、段幅より広い語を含む段落は、引き続き1行ずつ組みます。いくつかのCJKの語を引用するラテン文字の段落では最適改行を使い続けます。optimalRaggedを使うと、行末不ぞろいのテキストにも適用されます。ハイフネーションと両端そろえを参照してください。 |
optimalRagged | boolean | true | 行末不ぞろいの本文テキストもKnuth-Plassで改行します。対象は、左そろえ・右そろえ・中央そろえの本文、引用ブロック、リスト項目と、行末不ぞろいの段落スタイル、囲みの本文、部や節のスタイルの本文です。語間の幅は変えません。改行処理は各行が行長にどれだけ足りないかを評価し(3em足りない行は、maxWordSpacingの語間の両端そろえの行と同じコスト)、各行を埋めてから次の行に進む代わりに行末の不ぞろいをならします。ラントの規則(avoidRunts、tightenRunts)とhyphenateAcrossColumnsも、両端そろえのテキストと同様に行末不ぞろいのテキストに働きます。hyphenation.raggedを使う場合も、どの音節で行を終えてよいかはゾーンが決めます(行末不ぞろいのテキストを参照)。行末不ぞろいの見出し、キャプション、注、表のセル、目次は引き続き1行ずつ組みます。optimalLineBreakingが必要です。falseにすると、postext 1.4までと同様に行末不ぞろいのテキストを1行ずつ組みます。それ以前に保存した設定のうち、本文テキストの一部を行末不ぞろいにしているものは、この値で読み込まれます(postext 1.4以前で書き出したバンドルを参照)。 |
breakAfterDashes | boolean | true | 語と語の間に前後を詰めて置いたemダッシュやenダッシュの後で改行できるようにします。say—that’s、riddles.—I、Hamburg–Berlinのほか、ダッシュの後の語が別のスタイルで組まれている場合(see—and)も対象です。Knuth-Plassはこれを語間と同じように扱い、行はダッシュで終わって何も追加されません。次の場合はダッシュの後で改行しません。挿入句や台詞の行を始めるダッシュ(—dijo、said "—Hola、sagte »—Ichのように、ダッシュの前にスペースか、スペースと引用符があるもの。ただし"no"—and、ドイツ語の„nein“—und、フランス語の« non »—etのように語を閉じる引用符の後なら改行できます)、約物の前(él—,)、引用符や括弧の前(thinking—" and、says—“no”、says—(no)。ダッシュの後の引用符は、ダッシュで中断した発話を閉じることが多いため)、ダッシュの連続の中、enダッシュで示した数の範囲の中(1914–1918)。falseにすると1.4の改行に戻ります。Knuth-Plassはダッシュの後では決して改行せず、書式付きのテキストやハイフネーションする行末不ぞろいのテキストを1行ずつ組む改行処理は2文字の間でだけ改行します。それ以前に保存した設定のうち、テキストにそうしたダッシュを含むものは、この値で読み込まれます(postext 1.4以前で書き出したバンドルを参照)。本文テキスト、見出し、リスト、引用ブロック、囲みに適用されます。キャプション、注、表のセル、目次は1.4の改行のままで、1行ずつ組む書式なしの行末不ぞろいの段落は、いずれの場合もpretext自身の規則に従います。 |
breakAfterHyphens | boolean | true | Knuth-Plassで改行するすべての段落で、複合語のハイフン、つまり2文字の間のハイフンの後で改行できるようにします(well- · known、vencer- · se)。行はハイフンで終わり、何も追加されません。この改行は音節での分割と同じコストで評価します。数字や記号に隣接するハイフンの後では改行しません(COVID-19、-5 °C)。インライン書式のない両端そろえの段落では、ハイフンの前後にそれぞれ2文字以上ある場合にだけ改行するので、e-mailのe-で終わる行はできません。falseにすると1.4の改行に戻ります。インライン書式のない両端そろえの段落ではそこで改行しませんが、同じ段落でもどこかに1語でもイタリックがある場合や、行末不ぞろいの段落、1行ずつ組む段落では改行します。それ以前に保存した設定のうち、テキストに複合語を含むものは、この値で読み込まれます(postext 1.4以前で書き出したバンドルを参照)。本文テキスト、見出し、リスト、引用ブロック、囲みに適用されます。複合語を参照してください。 |
repeatHyphen | boolean | false | 複合語のハイフンで改行したとき、次の行の頭にもハイフンを置きます。ポルトガル語の正書法が求めるvencer- · -seや、2010年以降のスペイン王立アカデミーの規則が求めるléxico- · -semánticoの形です。繰り返したハイフンはその行の一部として計測・描画され、行はそれをrepeatedHyphenとして記録します。行のplainStartとsourceStartはハイフンの後を指すので、リンク、柱、Sandboxは語を書かれたとおりに読みます。PDFはこのハイフンを、それを除いた/ActualTextの下で描画するので、PDFからコピーまたは抽出したテキストでは語は1回だけ現れます。Webアドレスには付けません。本文テキスト、見出し、リスト、引用ブロック、囲みに適用されます。複合語を含む書式なしの段落は、このとき書式付きのテキストを組む改行処理で改行されます。 |
blockquote | BlockquoteConfig | 後述 | Markdownの引用ブロック(> …)の組み方。色、イタリック、インデントを指定します。引用ブロックを参照してください。 |
#引用ブロック
引用ブロック(>で始まる行)は、本文のフォントファミリー、サイズ、行送り、ウェイト、行そろえ、ハイフネーションを引き継ぎます。それ以外はbodyText.blockquoteで設定します。設定しない場合、引用ブロックはpostext 1.4までと同じ見た目になります。灰色のイタリックで、本文の1行目の字下げを使い、左右のインデントはありません。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
color | ColorValue | #666666 | 文字の色。パレットのエントリーに連動させた色(paletteId)は、ほかの場所と同様にそのエントリーに従います。本文の太字・イタリック・参照の色は引用ブロックの中には適用されません。 |
italic | boolean | true | テキストをイタリックで組みます。中にある…の範囲は正体に戻ります。falseの場合は、段落と同じようにイタリックになります。 |
indent | Dimension | 0 | 段または囲みの左端からの、すべての行のインデント。行長はその分だけ狭くなるので、両端そろえの行は右端で終わります。emは本文のサイズです。 |
firstLineIndent | Dimension | 本文の値 | 引用した各段落の1行目の字下げで、indentから数えます(本文のhangingIndentが有効なら、1行目以外のすべての行のインデント)。未設定の場合はbodyText.firstLineIndentです。 |
bodyText: {
firstLineIndent: { value: 1.5, unit: 'em' },
// Upright verse in the body colour, set in by 2 em, no first-line indent.
blockquote: { color: { hex: '#241f26', model: 'hex' }, italic: false, indent: { value: 2, unit: 'em' }, firstLineIndent: { value: 0, unit: 'em' } },
}Sandboxでは、これらは本文セクションの引用ブロックグループにあります。
#fontFamilyには1つのファミリー
fontFamilyは、ここでもほかのフォントファミリーの項目(headings.fontFamily、tableStyle.bodyFontFamily、separatorFontFamily、チップスタイルやデザイン要素のfontFamilyなど)でも、1つのファミリーを指定します。Canvas、HTML出力、PDFは同じ書体で組む必要があり、PDFはフォールバックの連鎖なしにファミリーごとに1つのフォントを埋め込むため、CSSのフォントスタックが代替として使えるものはありません。スタックを指定すると最初のファミリーで組まれ、設定の警告として報告されます。
bodyText: { fontFamily: "'EB Garamond', Georgia, serif" } // set in EB Garamondレイアウトの前にそのファミリーを読み込んでください(カスタムフォントと、PDFの生成のフォントプロバイダーを参照)。読み込まれていないと、スタックのほかの指定にかかわらず、ブラウザーは既定のフォントで計測します。引用符の中のカンマは名前の一部です('"Foo, Bar"'は1つのファミリーです)。
#ハイフネーション
行そろえを'justify'にしたとき、ハイフネーションは長い語を音節の境界で分割して、語間が広がりすぎるのを防ぎます。エンジンはTeX/Liangのパターンを使って、音節の境界にある自然な分割位置を見つけます。詳しくはハイフネーションと両端そろえを参照してください。行末不ぞろいのテキストは、指定した場合にだけハイフネーションします。後述の行末不ぞろいのテキストを参照してください。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | true | ハイフネーションを許可するかどうか。 |
locale | LocaleTag | トップレベルのlocale、なければ'en-us' | 音節の境界を決める言語の規則。後述の対応ロケールのいずれか、または任意のBCP 47タグ('es-ES'、'pt-BR')を指定します。 |
ragged | boolean | false | 行末不ぞろいのテキスト(左そろえ・右そろえ・中央そろえ)も、zoneの範囲でハイフネーションします。行末不ぞろいのテキストを参照してください。 |
zone | Dimension | 3em | 行末不ぞろいのテキストのハイフネーションゾーン。収まらない語は、丸ごと次の行に送るとこれより広いアキが残る場合にだけ分割します。emはそのテキスト自身の文字サイズが基準です。両端そろえのテキストでは無視されます。 |
compounds | boolean | true | 複合語、つまり2文字の間にハイフンを含む語(af-ter-dinner)を辞書で分割できるようにします。falseにすると、TeXと同様に、そうした語は自身のハイフン以外では分割しません。ハイフンの位置では引き続き行を終えられます(after- · dinner)。語の中に入力したソフトハイフンでは引き続き改行し、行全体より広い複合語は引き続き分割します。本文テキスト、見出し、リスト、引用ブロック、囲みに適用されます。キャプション、注、表のセル、目次では複合語を引き続き分割します。複合語を参照してください。 |
対応ロケール:'en-us'(英語)、'es'(スペイン語)、'fr'(フランス語)、'de'(ドイツ語)、'it'(イタリア語)、'pt'(ポルトガル語)、'ca'(カタルーニャ語)、'nl'(オランダ語)。
パターンを選ぶとき、地域・用字・バリアントのサブタグは無視され、大文字・小文字の違いと_の区切りも無視されます。'es-ES'、'es-MX'、'es_419'は'es'で、'pt-BR'は'pt'で、英語のタグはすべて('en'、'en-GB')'en-us'でハイフネーションします。同梱している英語のパターンはこれだけなので、イギリス英語のテキストもアメリカ式で分割されます。パターンを同梱していない言語('sv'、'pl'、'fi'など)は'en-us'のパターンでハイフネーションするため、分割されないのではなく誤った位置で分割されます。エンジンはこれをタグごとに1回console.warnで報告し、Sandboxは検査パネルに表示します。そうした文書ではenabled: falseを設定してください。警告も出なくなります。中国語・日本語・韓国語(zh、ja、ko。地域や用字は問いません)はパターンを必要とせず、警告も出ません。これらの言語の文書はハイフネーションなしで組まれます。文中に引用したラテン文字の語を分割するには、enabled: trueを設定し、localeにその言語を指定します(英語なら'en-us')。localeなしでenabled: trueにした場合や、中国語・日本語・韓国語のロケールを指定した場合は、ハイフネーションはオフのままで、その旨をコンソールに1回表示します。matchHyphenationLocale(tag)はタグに対応する同梱ロケールを返し(ない場合はundefined)、HYPHENATION_LOCALESは同梱ロケールの一覧です。解決済みの設定(doc.config.bodyText.hyphenation)は、実際に使ったパターンをlocaleに、指定したタグがそれと異なる場合はそのタグをtagに保持します。PDFバックエンドは文書の言語をトップレベルのlocaleから宣言し、localeが未設定の場合にだけこのタグから宣言します(文書の言語を参照)。
import { matchHyphenationLocale } from 'postext';
matchHyphenationLocale('es-MX'); // 'es'
matchHyphenationLocale('en-GB'); // 'en-us'
matchHyphenationLocale('sv'); // undefined: hyphenated with 'en-us', with a console warning5文字未満の語はハイフネーションしません。分割位置の前に2文字以上、後に3文字以上が必要です。
行末不ぞろいのテキスト
既定では、ハイフネーションするのは両端そろえのテキストだけです。textAlign: 'left'の場合や、左そろえ・中央そろえ・右そろえの段落スタイルでは、行末がどれほど不ぞろいになっても語は丸ごと保たれます。例外はテキストに入力したソフトハイフン(U+00AD)です。行末不ぞろいのテキストもハイフネーションするには、hyphenation.ragged: trueを設定します。
行末不ぞろいの行は伸ばさないので、収まらない語をすべて分割すると行末がハイフンだらけになります。これを抑えるのがハイフネーションゾーンで、DTPソフトの単一行コンポーザーと同じ方法です。行末に語が収まらないとき、エンジンは、その語を丸ごと次の行に送った場合に残るアキを調べます。アキがzoneより広ければ、収まる最後の音節で語を分割し、そうでなければ語を丸ごと次の行に送ります。emで指定したゾーンは、そのテキスト自身の文字サイズが基準です。既定値の3emでは、目立って短い行を残す語だけを分割します。ゾーンを広くするとハイフンが減り、行末の不ぞろいが大きくなります。0にすると、収まらない語はすべて分割します。1行ずつ組む場合、音節で終わる行は3行以上続きません。ハードハイフン(enseñanza-aprendizaje)、URLの区切り、テキストに入力したソフトハイフン、行全体より広い語は、これまでどおりに改行します。ゾーンと2行の制限が制御するのは辞書による音節だけです。
この設定は文書全体に効きます。本文テキストと引用ブロックが行末不ぞろいのときに適用され、行末不ぞろいの段落スタイルや囲みの本文のうち、自身のhyphenationがオンのもの(既定値は本文のhyphenation.enabled)にも適用されます。したがって、hyphenation: falseにすると、そのスタイルの語は丸ごと保たれます。見出し、キャプション、注、表のセル、目次はハイフネーションしません。そこでは、ほかと同様に、行長全体より広い語だけを分割します。デザインのテキスト(柱、章扉、部扉)は、各テキスト要素のhyphenateフラグに従います。組み込みの全ページの章扉と部扉はこれを設定しており、文書の辞書も使います。両端そろえのテキストではraggedとzoneは無視されます。
const config: PostextConfig = {
locale: 'es',
bodyText: {
textAlign: 'left',
// A little more hyphenation than the 3 em default.
hyphenation: { ragged: true, zone: { value: 2, unit: 'em' } },
},
};optimalRagged(既定)を使うと、行末不ぞろいの本文テキストはKnuth-Plassで改行され、ゾーンはそこでも同じ意味を持ちます。語を分割するのは、行の残りに収まらず、丸ごと送るとゾーンを超えるアキが残る場合だけです。音節で終わる行が2行続くことは禁止されませんが、両端そろえのテキストでハイフンが2行続くのと同じコストがかかるため、3行続くことはまれです。optimalRagged: falseまたはoptimalLineBreaking: falseでは、postext 1.4までと同様に、各行を埋めてから次の行に進みます。
行末不ぞろいのハイフネーションでは、インライン書式(太字、イタリック、リンク、数式)を含む段落が常に使う改行処理で段落を組みます。そのため、オンにするとハイフン以外の改行位置もいくつか変わることがあります。行末不ぞろいの行では、この改行処理はスペースと辞書の音節のほかに、2語の間のハードハイフンやダッシュの後(largas—separadas。breakAfterDashesを使うと、語の間に前後を詰めて置いたあらゆるダッシュ、つまりriddles.—Iやriddles—*and*も)、URLの区切り、表意文字の間で改行し、複数の範囲にまたがって組まれた語は分割しません(**Nota**:、(*véase*)。行末不ぞろいのハイフネーションを使わない場合、書式のない行末不ぞろいの段落は、optimalRaggedがオン(既定)ならKnuth-Plassで改行されます。改行位置は、語間、2文字の間のハードハイフンの後(meta- · analyses。もう一方の改行処理と同じ)、breakAfterDashesを使う場合は前後を詰めたダッシュの後です。1行ずつ組む場合はpretextの改行処理を通り、3つの点で異なります。挿入句を閉じるダッシュの前や開くダッシュの後でも改行することがあり(él · — y)、ハイフネーションするときはスラッシュの後でも改行し(km/ · h)、行より広い語を任意の文字の位置でハイフンなしに切ります。もう一方の改行処理は、まず音節で分割します。
Sandboxでは、このスイッチは本文セクションの段落の行そろえの下にある行末不ぞろいのテキストもハイフネーションで、その下にゾーンがあります。本文が両端そろえの場合、同じスイッチは両端そろえの設定と並んでおり、行末不ぞろいの段落スタイルと囲みに適用されます。
#文書の言語
トップレベルのlocaleは文書全体の言語です。hyphenation.localeと同じ値を取り、そのフィールドが未設定のときの代わりになります。スペイン語の本なら、locale: 'es'だけでスペイン語のハイフネーションになります。表と分割された囲みの、組み込みの継続表示の文字列((cont.)/ContinuedかContinúaか。ページより高い表と分割された囲みの目印を参照)や、言葉で書く見出し番号(Chapter OneかCapítulo unoか。語で綴る番号を参照)の言語もこれで決まり、アクセシブルなPDFにタグ付けする言語にもなります。未設定の場合、エンジンは'en-us'とみなします。Sandboxはインターフェースの言語を使い、このフィールドをデザイン › 表記体系で設定します。
組み込みのリソースタイプもこれで決まります。resourceTypesが未設定の場合、buildDocumentはdefaultResourceTypes(locale)で番号とキャプションを付けるので、locale: 'de'だけでAbbildung 1.1やTabelle 1.1になります。localeが未設定の場合は、リソースタイプでも表の文字列でも、ハイフネーションのロケールが代わりに使われます。明示的に指定したresourceTypesのリストが常に優先されます。以前のバージョンでは、defaultResourceTypes(locale)を自分で渡さない限り、localeにかかわらず英語のタイプを使っていました。localeを設定しつつ英語のラベルを残したい文書は、resourceTypes: defaultResourceTypes('en')を渡します。Sandboxの本とバンドルは自身のリストを持っているので、影響を受けません。
ここでもhyphenation.localeと同様に任意のBCP 47タグが使えます。'de-AT'ならドイツ語の文字列になります。組み込みの文字列があるのは、ハイフネーションが対応する8言語、中国語(簡体字と繁体字)、日本語、アラビア語です。それ以外の言語には英語の文字列が使われます。
中国語では、zh、zh-Hans、zh-Hant、zh-CN、zh-SG、zh-TW、zh-HK、zh-MOと長い形(zh-Hant-TW)を、大文字・小文字を問わず、-でも_でも受け付けます。文字列はIntl.Locale(tag).maximize()で読み取った用字に従います。zh、zh-CN、zh-SGは簡体字、zh-TW、zh-HK、zh-MOは繁体字です。一方、言語に依存する組版の既定値は、clreq §1.2の推奨どおり地域に従います。CN、SG、MYは中国大陸、TWは台湾、HKとMOは香港とみなし、地域のないタグは用字で判断します(zh-Hantは台湾、zhとzh-Hansは中国大陸)。日本語では、ja、ja-JP、ja-Jpan、そのほかのjaタグを受け付けます(isJapaneseLanguage(tag))。postext 1.16からは日本語独自の文字列と独自の地域japanを持ち、その組版の既定値はJLReqに従います(日本語の組版を参照)。日本語の項目がない文字列の表では、中国語ではなく英語が使われます。localeScript(tag)、cjkRegionOf(tag)、stringsKeyOf(tag)、sameContentLocale(a, b)はこれらの判定結果を返します。DOCUMENT_LANGUAGESは組み込みの文字列がある言語の一覧で、各言語はその言語自身の名前で表記されています(日本語は中国語の項目とالعربيةの間)。Sandboxの文書の言語の選択肢にはこの一覧が表示されます。
| 言語 | 図:名前、複数形、短いラベル | 表:名前、複数形、短いラベル | 表の継続表示:continuedSuffix、continuesMarker |
|---|---|---|---|
英語(en) | Figure, Figures, Fig. | Table, Tables, Tab. | (cont.), Continued |
スペイン語(es) | Figura, Figuras, Fig. | Tabla, Tablas, Tabla | (cont.), Continúa |
フランス語(fr) | Figure, Figures, Fig. | Tableau, Tableaux, Tabl. | (suite), À suivre |
ドイツ語(de) | Abbildung, Abbildungen, Abb. | Tabelle, Tabellen, Tab. | (Forts.), Wird fortgesetzt |
イタリア語(it) | Figura, Figure, Fig. | Tabella, Tabelle, Tab. | (segue), Continua |
ポルトガル語(pt) | Figura, Figuras, Fig. | Tabela, Tabelas, Tab. | (cont.), Continua |
カタルーニャ語(ca) | Figura, Figures, Fig. | Taula, Taules, Taula | (cont.), Continua |
オランダ語(nl) | Figuur, Figuren, Fig. | Tabel, Tabellen, Tab. | (vervolg), Wordt vervolgd |
簡体字中国語(zh-Hans、zh、zh-CN) | 图, 图, 图 | 表, 表, 表 | (续), 接下页 |
繁体字中国語(zh-Hant、zh-TW、zh-HK) | 圖, 圖, 圖 | 表, 表, 表 | (續), 接下頁 |
日本語(ja、ja-JP) | 図, 図, 図 | 表, 表, 表 | (続き), 次ページへ続く |
アラビア語(ar、ar-EG、ar-MA…) | شكل, أشكال, شكل | جدول, جداول, جدول | (تابع), يتبع |
キャプションの接頭辞はタイプの名前です(Figure 1.1.)。中国語のタイプは章ごとにハイフンで番号を付け({h1}-{n}、图 1-1)、キャプションの設定でlabelNumberGap: ''とlabelSeparator: ' 'を指定すると、キャプションは图1-1 标题になります(キャプションスタイルを参照)。索引も言語に従います。簡体字では、相互参照の前に见と另见を置き、記号と数字の項目の上に符号と数字の見出しを置きます。繁体字では見、另見、符號、數字を使います。日本語も同じ方法でタイプに番号を付け、この2つの設定なしでキャプションを図1-1 題と組みます。相互参照は第3章、2.3節、12ページと書き、参考文献の見出しを「参考文献」とします。索引は記号と数字の項目の上にそれぞれ「記号」「数字」の見出しを置き、相互参照を矢印で示します。seeは→夏目漱石、see alsoはページ番号の後に→夏目漱石、森鷗外も見よと書き、ラベルは正体です。アラビア語も同じ方法でタイプに番号を付け({h1}-{n}、شكل 2-3)、相互参照はالفصل 3、القسم 2-1、ص 12と書き、参考文献の見出しをالمراجعとします。索引はانظر/انظر أيضًا、رموز、أرقامを使い、アラビア語のカンマとセミコロン(، ؛)を使います。
中国語・日本語・韓国語の文書は、HTML出力(.pt-docのルートのlang。zh-Hant-TWはそのまま保持)と、描画するCanvas(ctx.lang。Chrome 136以降)で言語を宣言します。これにより、ブラウザーは地域に合った字形で描画します。Unicodeは漢字を統合しているため、同じコードポイントでも台湾のフォントと日本のフォントでは見た目が異なるからです。ほかの文書には、これまでどおりlangを付けません。PDFは、タグ付きかどうかにかかわらず、すべての文書でlocaleから用字と地域を含めて/Langを宣言します。右から左に書く言語(アラビア語、ペルシア語、ウルドゥー語、ヘブライ語など)の文書も言語を宣言し、フォントの言語別字形(locl)とフォールバックフォントはそれに従います。
const config: PostextConfig = {
locale: 'es',
bodyText: { textAlign: 'justify', hyphenation: { enabled: true } }, // hyphenates in Spanish
};文書の数字
トップレベルのnumeralsは、エンジンが書くすべての数の数字を設定します。対象は、ノンブルと、目次・索引・ページ参照のページ表記、番号付きリストと脚注の番号、見出しと章のカウンター、{chapterNumber}、図番号とその参照の{h1}と{n}、{totalPages}、{bookTotalPages}、{numberDecimal}です。'latn'は0–9、'arab'はアラビア・インド数字٠–٩、'arabext'はペルシア数字۰–۹で書きます。変わるのは10進の形式(decimal、リストのarabic、または既定値のままの設定)だけなので、著者が名前で指定した形式はそのとおりに出力されます。lower-romanはi、ii、iiiのままで、ラテン文字の文書でarabic-indicを指定すれば١، ٢، ٣と書きます。文書のテキストが書き換えられることはありません。
既定値の'auto'は、localeの数字を使います(defaultNumeralsFor(tag))。地域なしのアラビア語とマグリブ以外の地域のアラビア語(ar、ar-EG、ar-SA、ar-AEなど)は'arab'、ar-MA、ar-DZ、ar-TN、ar-LY、ar-MR、ar-EHは'latn'、ペルシア語(fa)、パシュトー語(ps)、インドのウルドゥー語(ur-IN)は'arabext'、パキスタンのウルドゥー語を含むそれ以外はすべて'latn'です。CLDRは地域なしのarとar-AEにlatnを割り当てていますが、マシュリクと湾岸地域のアラビア語の本は٠–٩で印刷され、Postextはそれに従います。数字を指定したタグはその数字を保ちます(ar-MA-u-nu-arab)。文書の数字でノンブルを付けたページは、pageNumberFormatとしてarabic-indicまたはpersianを記録するので、PDFのページラベルにも同じ数字が表示されます。不明な値は言語に従い、unknownNumeralsとして報告されます。
著者が入力した数は、3つの方式のいずれでも読み取ります。リスト項目٣.は3から始まり、{startAt=٥}、:::numbering{startAt=٥}、:::space{lines=٢}、:::part{number="٣"}({numberDecimal}用)もその値を読み取ります。
const config: PostextConfig = { locale: 'ar' }; // ١، ٢، ٣
const maghreb: PostextConfig = { locale: 'ar-MA' }; // 1, 2, 3
const forced: PostextConfig = { locale: 'ar', numerals: 'latn' }; // 1, 2, 3テキストの方向
トップレベルのdirectionは文書の基本方向を設定します。'ltr'、'rtl'、'auto'(既定)のいずれかです。既定値は、localeの用字が右から左に書かれる場合(アラビア文字、ペルシア語、ウルドゥー語、ヘブライ文字、シリア文字、ターナ文字、ンコ文字、アドラム文字など。directionOf(tag)で判定)に'rtl'、それ以外では'ltr'になります。右から左の文書は鏡像の座標系でレイアウトされます。行は右から始まり、第1段は右の段になり、インデント、リストのマーカー、フロート、脚注、囲みは右側に置かれ、page.binding: 'auto'は右綴じになります。解決済みの設定がdirection: 'rtl'を持つのはそうした文書だけなので、左から右の文書はこれまでどおりに解決されます。不明な値は'auto'として読み取られ、unknownConfigValueとして報告されます。Unicodeの双方向アルゴリズム(UAX #9)は、どちらの方向でも各行のランを並べます。英語の本の中のアラビア語の引用は、その場で右から左に読めます。
文書の中では、見出しや:::コンテナーに{dir=ltr}や{dir=rtl}を付けられ、インラインの:ltr[…]と:rtl[…]はテキストの範囲を分離します(マークアップでの文字方向を参照)。表のリソースにはtable.directionを指定します。文書と逆の方向に組んだブロックは、自身の開始側を保ちます。そのインデント、リストのマーカー、最終行の寄せる側は、テキストが始まる側に移ります。
側を指定する設定は、用紙の側ではなく、テキストまたは本文の流れの側を意味します。そのため、英語の本のために作ったデザインは、本をアラビア語に切り替えてもそのまま機能します。'start'と'end'も明示的な名前として受け付けます。
| 設定 | 'left'/'right' | 'start'/'end' |
|---|---|---|
本文、見出し、段落スタイル、部、脚注、囲みの本文のtextAlign。キャプションとキャプションの注記のalign | テキストの側。'left'は行が始まる側で、アラビア語の段落では右になり、両端そろえの段落の最終行もそちらに寄ります。 | 'left'と'right'の同義語。解決済みの設定は'left'/'right'を持ち、保存した設定は書かれたとおりの値を保ちます。 |
表のセルのalign | セルのテキストの側。表の方向(table.direction)で読み取ります。 | 同義語。表の方向で読み取ります。 |
placement.align(フロート、幅の狭い図) | 本文の流れの側。右から左の本では、'left'は用紙の右です。 | 同義語。 |
囲みのstripe.side、icon.cornerSide、labelTab.position('top-start'、'top-end') | 本文の流れの側。ページ上のすべての囲みで同じです。 | 囲み自身の方向(アラビア語の本の中の:::callout{dir=ltr}は用紙の左から始まります)。 |
| ヘッダーとフッターのスロット。用紙に固定したデザイン要素 | 用紙の側。 | テキスト要素のみ。要素自身のdirectionの始まりと終わり。 |
placement.rotateは鏡像のページでも物理的な意味を保ちます。時計回りに回転した図は、用紙の上でも時計回りに回転します。レイアウトを読み取るホストは、各ページの鏡像の座標系(direction: 'rtl'を持つVDTPage.flow、pageIsMirrored(page))と、各行のセグメントの表示順(VDTLine.order)を参照できます。flowToPageとpageToFlowは、流れと用紙の間で座標を変換します。アラビア語の組版を参照してください。
const arabic: PostextConfig = { locale: 'ar' }; // right to left, bound on the right
const english: PostextConfig = { locale: 'en', direction: 'rtl' }; // forced; rarely what you want#言語と文字体系
Postextは、左から右に書くアルファベット系の文字体系と、横組み・縦組みの中国語と日本語を組めます。中国語の組版と日本語の組版では、それらの組み方と、それを制御する設定を説明しています。設定キーは東アジアの組版、縦組み、綴じにあります。文字体系ごとの扱いは次のとおりです。
- 中国語は、段落のCJK文字が語間より多いときにCJKコンポーザーで組まれます。行は
cjk.lineBreakの行頭・行末の規則のもとで文字の間で改行し(。、」やーで始まる行、「や(で終わる行は作りません)、——と……、記号付きの数、ラテン文字の語は分割しません。両端そろえの行は、文字の間を広げて行長にそろえます。約物の幅、ぶら下げ、漢字とラテン文字の間のアキ、文字グリッド、圏点、固有名詞と書名の符号、ルビ、割注は、横組みでも縦組み(layout.writingMode: 'vertical-rl')でも、localeの地域に従います。いくつかのCJKの語を引用するラテン文字の段落は最適改行を保ち、それらの語の隣で改行することがあります。ラテン文字のテキストに引用したCJKの括弧、中黒、全角の記号(〈h〉、%)は何も変えません。 - 日本語は同じコンポーザーを通りますが、postext 1.16からは独自の規則、つまりW3Cの『日本語組版処理の要件』(JLReq)とJIS X 4051の規則に従います。
jaロケールはjapan地域になり、その自動の値によって、JLReqの禁則のレベル(小書きの仮名とーは行頭に置かない)、約物の連続の詰めを伴う全角の約物、?!の後の全角アキ、段落冒頭の始め括弧を字下げの後半に置く処理、本文の上に付けるゴマ圏点、『』による書名、1:2:1の割付けによるルビ、日本語の番号表記、注、読みで並べる索引が設定されます。以前のバージョンでは、日本語を中国大陸の既定値で組んでいました。 - 韓国語も同じコンポーザーを通り、ハイフネーションなしで組まれますが、既定値は中国大陸のものです。韓国語独自の規則(KLREQ)は実装されていません。韓国語のテキストは、スペースのほかに音節の間でも改行します。
- アラビア語とそのほかの右から左の文字体系(ペルシア語、ウルドゥー語、ヘブライ語など)は右から左に組まれます。詳しくはアラビア語の組版で説明しています。文書の
directionはlocaleの用字から決まり、Unicodeの双方向アルゴリズムが各行の中のラテン文字の語と数を並べ、本は右綴じで第1段が右になり、エンジンが書くすべての数は地域の数字を使います。アラビア文字を含む語は、ハイフネーション、字間の調整、分割の対象にならず、アラビア語の両端そろえの行は語間とカシーダで伸ばします。母音記号、強調、脚注、アラビア語の文字列はアラビア文字のテキストにあります。ペルシア語、ウルドゥー語、ヘブライ語には方向、数字、語を分割しない規則が適用されますが、独自の組み込みの文字列はありません。
ハイフネーションのパターンは8言語分を同梱しています(en-us、es、fr、de、it、pt、ca、nl)。組み込みのリソースタイプと継続表示の文字列は、この8言語と、中国語、日本語、アラビア語にあります。中国語、日本語、韓国語と、右から左に書く言語は、ハイフネーションなしで組まれます。それ以外の言語は、コンソールに警告を出したうえでアメリカ英語のパターンでハイフネーションし、英語の文字列を使います。そうした文書では、hyphenation.enabled: falseを設定し、resourceTypesとtableStyleの継続表示の文字列をその言語で渡してください。
#アラビア文字のテキスト
ここの設定はアラビア文字のテキストのためのもので、ほかの文字体系で書かれた文書には何の影響もありません。アラビア語の組版では、方向、綴じ、数字、韻文、目次、索引といったアラビア語の本のほかの要素とあわせて説明しています。
- 母音記号と行送り。母音記号付きのテキストの母音記号(ファトハ、カスラ、シャッダ、タンウィーン、短剣アリフ、クルアーンの記号)は、文字の上下の行間に積み重なり、行送りはそのために広がることはありません。記号を含む各行は、記号のインクがどこまで届くか(
VDTLine.markInk)を記録し、レンダラーの段のクリップは段の最初と最後の行の記号を含めます。語の上の記号が、上の行の語の下にぶら下がる文字や記号とぶつかると、ビルドはその段落を報告します(arabicMarksExceedLeading。Sandboxの検査パネル)。比較するのは上下に重なる語だけです。部分的に母音記号を付けたテキストには約1.7–1.85 emのlineHeight、完全に母音記号を付けた韻文には1.9–2.1 emが適しています。 - 強調。アラビア文字の活字にはイタリックがないため、
localeがアラビア文字で書かれる文書では、*…*は既定で太字になります(bodyText.emphasis: 'auto')。'color'はitalicColorの正体で組み、'overline'は語の上に線を引きます。アラビア語の本で使われるkhaṭṭ fawqīです。この設定は、本文の書体で組むすべてのテキスト、つまり段落、リスト、引用ブロック、段落スタイル、囲みの本文、注、見出しに及びます。キャプション、表のセル、目次、索引は、自身のイタリックの設定を保ちます。どれを選んでも、エンジンがアラビア文字をイタリックにすることはありません。イタリックで組んだ範囲のうち、アラビア語の語は正体のままで、ラテン文字の語はイタリックを保ちます。そうした文書では、引用ブロックは既定で正体です。 - タシュキール(tashkīl)。
bodyText.tashkil: 'strip'は、レイアウトで組むテキストから母音記号とクルアーンの記号を取り除きます。母音記号付きの原稿から母音記号なしの版を作るためのものです。対象は、ファトハ、ダンマ、カスラとそのタンウィーン、スクーン、シャッダ、短剣アリフ(هٰذاはهذاになります)、U+0656–U+065FとU+06D6–U+06EDの記号です。'strip-vowels'は、現代の多くの本と同じようにシャッダを残します。ハムザとマッダは残ります(أ إ آは文字であり、結合記号で入力した場合も同じです)。原稿は記号を保ち、行、見出し、目次は記号なしで組まれますが、組まれた文字はすべて原稿の中の位置に対応づけられたままです。 - 脚注。
footnotes.markerTemplate: '({n})'は合印を文書の数字で«(١)»と書き、numbering: 'page'はページごとに番号を振り直し、noteNumberPosition: 'inline'は注自身の番号を行の中に組みます。区切り線と注の番号は段の始まり側に置かれ、右から左の本では右側になります。 - 語を分割しない。アラビア文字を含む語は、アラビア語の本の中でも、ほかの言語の本に引用した場合でも、ハイフネーション、分割、字間の調整の対象になりません。アラビア文字のテキストに
letterSpacingを設定したスタイルは報告され(joiningScriptLetterSpacing)、行より広い語は行からはみ出して報告されます(unbreakableWordOverflow)。アラビア語の組版を参照してください。 - カシーダと韻文。アラビア語の両端そろえの行は、語間に加えてカシーダでも伸ばします(
bodyText.kashida。アラビア文字のカシーダを参照)。古典詩は、:::verseを使うと、同じ幅の2つの半句からなるベイト(bayt)を1行に1つずつ組みます(:::verseを参照)。 - 索引。アラビア語の索引はアルファベット順に並べ、冠詞ال(
index.ignoreArticle)、母音記号、ハムザの座を無視します。索引を参照してください。
#オーファン、ウィドウ、ラント、分割禁止の規則
これらのデメリットの仕組みはハイフネーションと両端そろえを参照してください。この節は、それらを制御するbodyTextのキーのリファレンスです。
本文の設定には、ハイフネーションと語間の上限・下限のほかに、構造的に不格好な段落の分割を防ぐ緩やかな規則があります。これらはすべてデメリットとしてKnuth-Plassの改行アルゴリズムに渡されます。厳格な規則を強制することはなく、レイアウトをすっきりした分割の方向に寄せるだけです。どれかを実質的に無効にするには、その*Penaltyの値を0にします。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
avoidOrphans | boolean | true | 段落が、次の段の頭でorphanMinLines行未満で終わるのを避けます。 |
orphanMinLines | number | 2 | 段落を分割するとき、次の段の頭に必要な最小の行数。avoidOrphansがtrueのときだけ有効です。 |
orphanPenalty | number | 1000 | オーファンの制約に反したときに加えるデメリット。値を大きくするほど、アルゴリズムはオーファンを強く避けます。0でペナルティーを無効にします。 |
avoidOrphansInLists | boolean | true | trueにすると、段落だけでなくリスト項目もオーファンから保護します。avoidOrphansがtrueのときだけ効果があります。 |
avoidWidows | boolean | true | 段落が、現在の段の末尾でwidowMinLines行未満で始まるのを避けます。 |
widowMinLines | number | 2 | 段落を分割するとき、現在の段の末尾に必要な最小の行数。avoidWidowsがtrueのときだけ有効です。 |
widowPenalty | number | 1000 | ウィドウの制約に反したときに加えるデメリット。0でペナルティーを無効にします。 |
avoidWidowsInLists | boolean | true | trueにすると、リスト項目もウィドウから保護します。avoidWidowsがtrueのときだけ効果があります。 |
avoidRunts | boolean | true | 段落が非常に短い最終行、つまりラント(たとえば短い1語だけの行)で終わるのを避けます。optimalRaggedを使うと、行末不ぞろいのテキストにも適用されます。中国語・日本語・韓国語の段落は、1文字だけ(単独または閉じ記号を伴うもの)の行(孤字)で終わりません。上の行が字間の上限内で引き続き両端そろえにできる場合、その行から最後の1文字を送ります。 |
runtMinCharacters | number | 20 | 段落の最終行のしきい値。文字数ではなく語間の数で数えます。行の幅がruntMinCharacters × normalSpaceWidthピクセルより狭いと、その行はラントです。語間は多くの本文用書体で1/4–1/3 em、つまり小文字の約半分の幅なので、既定値の20は4–7 em未満、およそ8–12文字未満の最終行を捉えます。約N文字未満の最終行を捉えるには、2 × N程度に設定します。 |
runtPenalty | number | 1000 | Knuth–Plassの二乗デメリットの式に加える、バッドネス換算の値(行のバッドネスと同じ尺度で、バッドネスは10000で飽和します)。0でペナルティーを無効にします。 |
gradedRuntPenalty | boolean | false | ラントのペナルティーを、最終行がどれだけ短いかに応じて段階的にします。しきい値t未満の幅wの最終行は、ペナルティー全体ではなくruntPenalty × (1 − w / t)のコストになります。2語で終わる場合は1語で終わる場合よりコストが小さくなり、上の行に余裕があれば改行処理は語を1つ下の行に送ります('…sallies of our' / 'minds.'ではなく'…sallies of' / 'our minds.')。既定ではオフで、どのラントも同じコストになり、改行処理は上の行を詰めた状態に保ちます。 |
avoidRuntsInLists | boolean | true | trueにすると、リスト項目にもラントのペナルティーを適用します。avoidRuntsがtrueのときだけ効果があります。 |
tightenRunts | boolean | true | ペナルティーでラントを避けられなかったとき、代わりに段落を1行短く組みます。語間を詰め(minWordSpacingを超えない範囲)、それだけで行が収まらなければ、わずかなマイナスのトラッキングを加えます。短く組むことで、両端そろえの行がmaxWordSpacingを超えて伸びる場合、段落にすでにそれよりゆるい両端そろえの行があるときはその行を超えて伸びる場合、または行末不ぞろいで組む行(通常のスペースの3倍を超える行)が元の段落より増える場合は、短く組むのをやめ、ラントはそのまま残ります。行末不ぞろいのテキストの語間は幅を変えないので、そこではトラッキングだけが働きます。optimalLineBreakingとavoidRunts(行末不ぞろいのテキストではさらにoptimalRagged)が必要です。 |
maxRuntTracking | number | 10 | ラントの解消に使えるトラッキングの最大量。1/1000em単位で(InDesignの単位で、10は1文字あたり0.01em)、詰める方向に適用します。0にすると、解消を語間だけに任せます。 |
slackWeight | number | 10 | 「段の使われない空間」の二乗コストに掛ける重み。値を大きくするほど、レイアウトは段を隙間なく埋める方向を選びます。0にすると、余りに対する圧力を完全に無効にします。 |
keepColonWithList | boolean | true | 段落がコロンで終わり、そのまま直後のリストを導入するとき、コロンを含む最終行をリストから離しません。段落を置くと最初のリスト項目を同じ段・ページで始める余地がなくなる場合、最終行(段落が1行だけなら段落全体)をリストと一緒に次の段に送ります。どれだけの余地があれば十分かはcolonListRoomで決まります。この規則で段落全体を送ることになり、その段で段落の直前に見出しが続いている場合は、headings.keepWithNextが保たれるよう、その見出しも一緒に送ります。 |
colonListRoom | 'item' | 'line' | 'item' | keepColonWithListがコロンの行の下に求める余地。'item':リストのオーファンとウィドウの規則が段の末尾に残す最初の項目の分。そこで分割できるなら1行、規則が項目を分割しない場合(たとえば2行の項目)はその全体です。'line':postext 1.4までと同じく1行。この場合、規則が分割しない最初の項目は単独で次の段に送られ、コロンの行が段の末尾に残ります。configVersion 6より前に保存した設定で、本の中にコロンでリストを導入する箇所があるものは'line'で読み込まれます(postext 1.4以前で書き出したバンドルを参照)。ほかの値は'item'として読み込まれます。 |
hyphenateAcrossColumns | boolean | true | 段やページがハイフネーションした語で終わることを許します(InDesignのHyphenate Across Column)。falseにすると、段の切れ目をまたぐ段落を改行し直し、その段での最終行が語の途中で終わらないようにします。差分は上の行の語間がmaxWordSpacingとminWordSpacingの範囲で吸収します。これは優先度の指定で、その範囲内の改行位置で避けられない場合はハイフンが残ります。段落のすべての段の切れ目を試します。最初の切れ目と、段がいっぱいになる位置にあるそれ以降の切れ目は1回の改行し直しでまとめて扱い、それ以外の位置にある切れ目(ウィドウを避けたもの、帯の高さをそろえるために切ったもの)はその段から別に改行し直します。このとき、前の段ですでに組んだ行は改行位置を保ち、残りだけを改行します。囲みの本文には影響しません。optimalRaggedを使うと、行末不ぞろいの段落も同じように改行し直します。語間の幅は変わらないので、行末の位置だけが動きます。改行し直すたびに段落をもう一度計測するので、段末のハイフンが多い本ではレイアウトが少し遅くなります。optimalLineBreakingが必要で、行末不ぞろいのテキストではさらにoptimalRaggedが必要です。 |
paragraphContainerSpacing | 'collapse' | 'add' | 'collapse' | 段落で終わる:::paragraphsコンテナーの下、つまりその段落と次のブロックの間のアキ(:::paragraphsコンテナーを参照)。'collapse':スタイルのspaceBetweenとmarginBottom、およびコンテナーの周りのテキストの段落間隔(paragraphSpacingを使う場合は1行。囲みの中なら囲みの値)のうち大きいほうを取り、本文テキストの2つの段落の間と同様に、次のブロックが自身の上に取るアキと統合します。参考文献の下の見出しは、そのマージンに項目の間隔を足した位置ではなく、自身のmarginTopだけ下に置かれます。'add':postext 1.4までと同じく、グリッドへのスナップの前に最終行の下にスタイルのアキだけを置き、その下に次のブロック自身の上のアキを加え、段落間隔は含めません。そのため、コンテナーの後の段落がほかのどこよりもコンテナーに近づくことがありました。configVersion 8より前に保存した設定で、段落スタイルを宣言し、本の中にそうしたコンテナーがあるものは'add'で読み込まれます(postext 1.4以前で書き出したバンドルを参照)。マイナスのmarginBottomは、どちらの場合も次のブロックを引き上げます。 |
ラントについて。ラントとは、最終行が短すぎてまともな1行に見えない段落のことで、典型的には段落の末尾に短い語が1つか2つ取り残されたものです。判定は通常のスペース幅に対する行のピクセル長に基づくため、runtMinCharactersは現在の文字サイズに自動的に適応します。runtMinCharacters × spaceWidthより見た目が広い短い語は問題なく、それより狭い語(または本当に1語だけの行)にはラントのペナルティーがかかります。しきい値は語間で数え、語間の幅は文字の約半分なので、既定値の20はおよそ8–12文字の最終行にあたります。どのラントもペナルティー全体のコストになるので、しきい値未満の2つの終わり方のうち、改行処理は上の行を詰めたほうを選びます。gradedRuntPenaltyを使うと、それぞれを不足分に応じて評価し、長いほうの終わり方が選ばれます。数学的な補足として、既定のruntPenaltyである1000では、ラントの回避は、語間の伸びがおよそr≈2.15までの改行位置の組み合わせのどれよりも優先されます。
厳格ではなく緩やかな規則。これらの規則はどれも分割を禁止できません。エンジンは常にレイアウトを生成します。これらはデメリットであり、アルゴリズムはバッドネス、ハイフネーションのコスト、適合クラス(fitness class)の滑らかさ、これらの構造上のペナルティーを1つの大域的な最適化の中で比較し、総コストが最も小さい改行位置の組み合わせを選びます。より強い保証が必要ならペナルティーを上げ、ペナルティーを緩めたほうが読みやすい文書なら下げてください。
#見出し
headingsプロパティは、すべての見出しレベル(H1〜H6)の文字組みを制御します。全レベルに適用される共通の既定値を設定し、そのうえで特定のプロパティをレベルごとに上書きできます。
#共通の既定値
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
fontFamily | string | 'Open Sans' | すべての見出しのフォントファミリー。 |
lineHeight | Dimension | 1.2 em | 見出しの行の高さ。本文より詰めた値です。 |
color | ColorValue | Main Color (#295AA3) | 見出しの文字色。既定のパレットのmain-color項目に結び付いているため、パレットの色を差し替えるとすべての見出しの色が変わります。 |
textAlign | 'left' | 'justify' | 'center' | 'right' | 'start' | 'end' | 'left' | すべての見出しレベルに共通のそろえ方です(レベルごとの値はありません)。左そろえ(行末不ぞろい)、両端そろえ、中央そろえ、右そろえ(行頭不ぞろい)から選びます。両端そろえの見出しは段落と同じく最終行を左端にそろえるので、1行の見出しは'left'と同じに見えます。Canvas、HTML、PDFは、番号の接頭辞も含めて同じ位置に行を置きます。詳細デザインのないspan: 'page'見出しの既定の章扉もこの値に従います(両端そろえは左端にそろえます)。詳細デザインでは、各テキスト要素が自身のalignでそろえられます。 |
fontWeight | number | 700 | 見出しのフォントウェイト(100〜900)。 |
marginTop | Dimension | 1.5 em | 見出しの上の空き。 |
marginBottom | Dimension | 0.5 em | 見出しの下の空き。 |
keepWithNext | boolean | true | trueのとき、見出しが段やページの最後の要素として置かれることはありません。続くブロックが見出しの後に少なくともbodyText.widowMinLines行(bodyText.avoidWidowsがfalseのときは1行)の余地を得られない場合、見出しは本文から離れないよう先へ送られます。bodyText.keepColonWithListと連動し、この規則がコロンで終わる段落をまるごと送る場合、段の末尾にある見出しも取り残されずに一緒に移ります。 |
keepWithNextSpread | boolean | false | keepWithNextが有効でも、偶数ページの最後の本文の段を見出しで終えることは許します。その本文は同じ見開きの向かいの奇数ページで始まるからです(JLReq §4.1.7)。見開きは1ページ目が単独、続いて2〜3ページ、4〜5ページ…と、pageIndexOffsetを加味して数えます。左綴じ・右綴じのどちらの本でも同じです。postext 1.16以降。 |
keepWithNextSplit | 'rules' | 'fill' | 'rules' | 見出しが段の末尾に来て、段落をまるごと送ると見出しが取り残される場合に、見出しの下の段落をどう分割するか。'rules':収まるだけの行を置きます。ただし、見出しの下に少なくともbodyText.widowMinLines行、次の段に少なくともbodyText.orphanMinLines行が残ることが条件です。両方を満たす分割がなければ見出しは段落とともに先へ移り、空いた余地は段末そろえが埋めます。'fill':次へ送る行がどれほど少なくても、収まるだけの行を置きます。3行分の余地がある4行の段落は3 + 1に分かれます。postext 1.4まではすべての見出しがこの方法で分割していました。configVersion 8より前に保存された設定はこの動作を保ちます(postext 1.4以前で書き出したバンドルを参照)。avoidWidowsまたはavoidOrphansがオフのときは、規則のその側を外します。 |
snapToGrid | boolean | true | 見出しの下で流れをベースライングリッドに戻すかどうか。trueでは見出しのmarginBottomをグリッドの行単位に切り上げます。falseでは指定どおりの空きを保ち、見出しの下の本文は次の吸着点(リストの終わり、:::paragraphsの末尾、別行立て数式)までグリッドから外れることがあります。見出しの下に1行半を空ける、多くの本に見られる組み方です。レベル(levels[].snapToGrid)や見出しスタイルは独自の値を持てます。ここでの値は、それらが継承する値です。 |
inlineMarks | boolean | true | 見出しが段落と同じように文字書式の記号を読むかどうか。対象はitalic、bold、^superscript^、~subscript~、:smallcaps[…]、リンクです。イタリックの範囲は見出しの傾きを反転させるため、イタリックの見出しの中では立体(ローマン)になります。太字の範囲はbodyText.boldFontWeightを使い、見出し自身のウェイトのほうが重ければそちらを使います。目次にも太字とイタリックの範囲が反映されます。柱とPDFのしおりには文字だけが出ます。falseでは記号を取り除き、postext 1.4までと同様に、語を見出し自身のスタイルで組みます。デザインのないspan: 'page'見出しの既定の章扉も、太字・イタリック・上付き・下付きの範囲を組みます。見出しのデザイン(デザインした章扉の帯、または段内のadvancedDesign)は、どちらの場合もをプレーンテキストとして出力します。それ以前に保存された設定のうち見出しに記号を含むものは、falseとして読み込みます(postext 1.4以前で書き出したバンドルを参照)。 |
balancing | ColumnBalancingConfig | 有効 | 縦方向の段末そろえ。段がページの下端にそろって終わるよう、見出しの上に空きを足します。後述します。 |
デザインスロットを使わずに、詩や戯曲の各幕の見出しを中央にそろえる例です:
headings: {
textAlign: 'center',
levels: [{ level: 2, textTransform: 'uppercase' }],
}headingsオブジェクトを渡しても、改めて指定しなかったレベルの既定値はすべて保たれます。H1の改ページも同じです(レベルごとの上書きを参照)。
#段末そろえ
出版社は、どの段もページの上端から始まり、下端にそろって終わることを求めます。改行・改段の規則(オーファンとウィドウの防止、見出しと本文の結び付き、分割できない図)は、どうしても短い段を生みます。段の下にベースライングリッドの空き行が1行以上残るのです。段末そろえを有効にすると、エンジンは組版者と同じことを行い、編集上の優先順位に従って調整手段を適用します。
- 段を閉じる囲み:短い段の末尾にある囲みを、その下の空きちょうどの分だけ下げ、下端をページの最後のグリッド位置、つまり隣の段の最終行と同じ高さにそろえます。その空きが1行に満たなくても、何も別の段へ移らない限りはすべて使います。既定ではほかのどの調整手段よりも先に働いて空きをすべて使うため、直前の段落に注釈を付ける囲みがその段落から数行離れることがあります。
closingBox: 'last'にすると、見出し、リストの終わり、そのほかの空きの調整手段が先に行単位で空きを取り、囲みはその残りだけを取ります。closingBox: 'off'では囲みを動かしません。 - セーフエリアのある画像:セーフエリア(
Resource.safeArea、文書形式 › セーフエリアを参照)を持つ画像が、短い段にインラインで置かれているか、その段だけにかかるフロートとして段の頭または末尾にある場合、段が埋まるか切り抜きがセーフエリアに達するまで、セーフエリアの範囲内で切り抜きながらグリッドの行単位で大きくなります。画像が高くなってもページに穴は開かないので、空きを足すどの手段よりも先に働きます。段の頭がそろったままになるページや帯(後述)で段の頭に置かれたフロートは大きくなりません。flexFigureとして記録されます。専用の設定はなく、リソースにセーフエリアが指定された画像だけが大きくなれます。 - 見出し:短い段の中にある見出しの上の空きに、グリッドの行単位で行を足します。複数行が必要で、段に見出しが複数ある場合は行を分配し、常に最も重要な見出しに最も多く割り当てます(
h2はh3より多く受け取ります)。段の一番上にある見出しは空きを受け取らないので、段はページの上端から始まったままです。例外は、続きのあるページで段の頭に置かれた図や表の直下にある見出しで、その見出しの上、つまり図の下に空きが入ります。 - リストの終わり:見出しだけで空きを吸収しきれないとき、リストや番号付きリストの終わりにグリッドの行を足します(リストの後の空きは自然に読めます)。リストの終わりごとに上限があります。
- ゆるい段落:最後の手段として、段の中の段落1つを1行長く組み直します(TeXの
\looseness=+1)。語間の広がりが目立たないよう、最も長い段落を選びます。ゆるくした組み方は、すべての行が上限のbodyText.maxWordSpacing未満にとどまる場合にだけ採用します。すでに設定した上限を文字の濃度(タイプカラー)が超えることはありません。bodyText.optimalLineBreakingが必要です。
ページの最後の段をそろえるのは、そのページが自然に次のページへ続く場合だけです。章の最後のページは短く終わってよいからです。そのようなページと、trailingで高さをそろえて切った最後の帯では、段の頭もそろったままです。段の頭に置かれた図や表の下に行を足すこと(フロート後の調整手段)はせず、そのような図の下で段を始める見出しや囲みも、stretchAfterFloatsの値にかかわらず段の頭にとどまります。末尾をそろえるためだけに、隣の段より低い位置から段を始めることはありません。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | true | 段の下端をそろえるかどうか。 |
maxLinesPerHeading | number | 4 | 1つの見出しの上に足せるグリッド行数の上限。 |
stretchAfterLists | boolean | true | 見出しだけで空きを吸収しきれないとき、リストの終わりにグリッド行を足すことを許します。 |
maxLinesAfterList | number | 1 | リストの終わり1か所あたりに足せるグリッド行数の上限。 |
stretchAfterFloats | boolean | true | 短い段の頭にある図や表(上端のフロート)の下に、リストの終わりの調整手段の次の段階でグリッド行を足すことを許します。段を短く終える代わりに、その下の本文を下げます。続きのないページ(章の最後のページ)や、trailingで高さをそろえて切った最後の帯では行いません。そこでは段の頭がそろったままで、最後の段は1行短く終わることがあります。図の下の最初のブロックが前のページで始まった段落の続きでも、同じように下がります。その量は、改行・改段の規則が段の末尾に空けた行(ウィドウの規則で空のまま残った行や、後に本文を置く余地のない段落間の空き)の分です。図のすぐ下に本文を置きたい場合はfalseにします。 |
maxLinesAfterFloat | number | 1 | 上端のフロート1つの下に足せるグリッド行数の上限。 |
looseParagraphs | boolean | true | 最後の手段として、短い段の段落をbodyText.maxWordSpacingの範囲内で1行ゆるく(それぞれ1行多く)組み直します。 |
maxLooseParagraphs | number | 2 | 1つの短い段で1行長くしてよい段落の数。長いものから選びます。 |
trackParagraphs | boolean | true | 語間だけでは1行を稼げないとき、ゆるくする段落に、それを稼げる最小の正のトラッキング(字間)を加えることも許します。 |
maxTracking | number | 10 | そのトラッキングの上限。1文字あたり、emの1000分の1を単位とします(10 = 0.01 em)。 |
trailing | boolean | true | 章と文書の最後の帯をそろえます。ページが埋まる前に流れが終わり(章扉、:::part、章を閉じるplacement: 'fixed'の囲み、または文書の終わりで)、段の高さがそろっていない場合、段を同じ高さで切ります。帯の上限はceil(Σ used / N / grid)行で、上記の調整手段が前のページを確定させた後に求めます。これにより、短い参考文献が最初の段を埋めて最後の段が半分空くことはなく、どの段でも同じ高さで終わります。この切断は、切らない帯が守る規則を守ります。切断位置をまたいで分割できないブロック(オーファンとウィドウの最小行数でひとまとまりに保たれる段落の末尾、分割しない囲み)がそこを越えてしまう場合(レンダラーは段をその枠で切り抜くので、最後の行が失われます)や、見出しが段を閉じてその本文が次の段で始まってしまう場合(既定のheadings.keepWithNextのとき)は、切断位置を1行下げます。これを3回まで試し、それでもだめなら切断をやめます。段の頭にある図の下で、切断が残す行の中で始められないブロック(段落にはそこにオーファンの最小行数が必要です)は、切らない段の場合と同じように帯の次の段へ進み、図はその段に単独で立ちます。末尾の差が1グリッド行以下の段はそのままにします。最後の段が1行短く終わるのは最後の帯のよくある終わり方で、切っても1行が移るだけだからです。切断は測った帯に適用されます。ページ幅の囲みがページの途中で求める切断も同じです。postext 1.4までは、最後のページの最初の本文が先に余地のない帯(フロートがまるごと占めるページや、前のページの末尾にページ幅の囲みが残す細い帯)に回された場合、切断がその帯で使われてしまい、最後のページではすべての行が最初の段に入っていました。幅の異なる段(両方に本文がある1段半レイアウト)は面積で切ります。狭い段の1行に入る本文は少ないので、各段の高さをその幅で重み付けします。enabledがtrueのときだけ働きます。 |
beforeSpan | boolean | true | ページ幅のブロックが後に残す帯をそろえます。span: 'page'の囲みが、高さをそろえて切った後でも現在の段の下に収まらず、次のページへ移るか分割(calloutStyles[].keepTogether: false)しなければならない場合、それが中断する段を同じ高さで切ります。最後の帯と同じ末尾の上限を使うので、最初の段がページを埋めて最後の段が短く終わることはありません。そのページは明示的な改ページとして扱い、上記の調整手段が最後の段をページ下端まで引き伸ばし直さないようにします。囲み、またはそのうち収まる部分は、そろえた段の下に置かれます。enabledがtrueのときだけ働きます。 |
closingBox | 'first' | 'last' | 'off' | 'first' | 短い段を閉じる囲みが、いつその下の空きを取るか(調整手段1、trailingCalloutとして記録)。'first':postext 1.4までと同様、ほかのどの調整手段よりも先に取ります。囲みは数行あっても空きをすべて取り、その上の見出しには何も残りません。'last':見出し、リストの終わり、別行立て、フロートの各調整手段が先に行単位で空きを取った後に取ります。囲みはその上の本文とともに下がり、残った分(たいていは1行未満)だけを取るので、下端は最後のグリッド位置にそろったまま、注釈を付ける本文の近くにとどまります。'off':取りません。囲みはその下の空きを残します。ただし、ほかの調整手段が囲みの上に空きを足せば行単位で下がることはあり、その下に残る1行未満の空きはそのままです。最後のページでは、隣の段と下端をそろえる調整手段が囲みしかないため、'off'では流れが置いた位置のままです。それ以外の値は'first'として扱います。 |
どの調整手段が働いたか
レイアウトは、適用した調整手段をすべて、適用先のブロックに記録します。buildDocumentが返すVDTのblock.balancingです。これを使えば、校正刷り、テスト、レポートで、段がなぜ下端にそろっているか、またどの段はどの調整手段でも閉じられなかったかを示せます。段末そろえが手を付けなかったブロックにはbalancingがなく、enabled: falseで組んだ文書には一切ありません。
interface VDTBalancing {
levers: BalanceLever[]; // usually one: a paragraph after a list can take the list-end line and run a line long too
spaceAbove: number; // px the spacing levers added above the block (0 when only looseParagraph or flexFigure fired)
bodyGrowth?: number; // flexFigure: px the picture grew, cropped within its safe area
extraLines?: number; // looseParagraph: lines the paragraph gained
tracking?: number; // looseParagraph: the tracking that gained them, in thousandths of an em (0 = word spacing alone)
}
type BalanceLever = 'trailingCallout' | 'flexFigure' | 'heading' | 'listEnd' | 'afterDisplay' | 'afterFloat' | 'looseParagraph';| 調整手段 | 記録先 | 行ったこと |
|---|---|---|
trailingCallout | 囲みの枠のブロック | 段を閉じる囲みを、その下の空きちょうどの分だけ下げました(spaceAbove。1行未満でもかまいません)。 |
flexFigure | 画像のブロック(インライン)またはフロート | セーフエリアのある画像を、セーフエリア内で切り抜きながら、グリッドの行単位でbodyGrowth px高くしました。リソースブロックのbodySourceは表示される部分、bodyFlex.deltaは同じ増分です。 |
heading | 見出し | 見出しの上にグリッドの行を、maxLinesPerHeadingまで足しました。 |
listEnd | リストの後の最初のブロック | リストの終わりにグリッド行を、maxLinesAfterListまで足しました。 |
afterDisplay | 数式または囲みの後のブロック | 別行立て数式や囲みの下にグリッド行を1行足しました。 |
afterFloat | 段の最初のブロック | 段の頭にあるフロートの帯とその本文の間にグリッド行を、maxLinesAfterFloatまで足しました。 |
looseParagraph | 段落 | 段落をextraLines行長く組み直しました。そのために使った最小のトラッキングがtrackingです(block.letterSpacingは同じ値をpxで表したもの)。 |
高さをそろえる切断は、切った段に記録します。column.bandCappedは高さをそろえて切ったすべての段(ページ幅の囲みが残す帯、最後の帯)でtrueになり、column.trailingCapはさらに章や文書の最後の帯(trailing)であることを示します。
import { buildDocument } from 'postext';
const doc = buildDocument(content, config);
for (const page of doc.pages) {
page.columns.forEach((column, i) => {
const levers: string[] = column.blocks.flatMap((block) => block.balancing?.levers ?? []);
if (column.trailingCap) levers.push('closing band cut level');
else if (column.bandCapped) levers.push('band cut level');
if (levers.length > 0) console.log(`page ${page.index + 1}, column ${i + 1}: ${levers.join(', ')}`);
});
}
// page 3, column 1: heading, heading
// page 3, column 2: listEnd, looseParagraph#レベルごとの上書き
各見出しレベルは、levels配列で共通の既定値を上書きできます。既定で異なるのはfontSize(と、後述するH1のbreakBefore)だけで、ほかのプロパティはすべて見出しの共通設定を継承します。
| レベル | 既定のフォントサイズ | 既定のbreakBefore |
|---|---|---|
| H1 | 18 pt | { enabled: true, parity: 'always-odd' } |
| H2 | 15 pt | { enabled: false, parity: 'any' } |
| H3 | 12 pt | { enabled: false, parity: 'any' } |
| H4 | 10 pt | { enabled: false, parity: 'any' } |
| H5 | 9 pt | { enabled: false, parity: 'any' } |
| H6 | 8 pt | { enabled: false, parity: 'any' } |
H1の既定値は書籍の章立てを模したものです。最上位の見出しはすべて新しい右ページ(奇数ページ)から始まり、前の章との間に必ず白ページを1ページはさみます。書籍ほど階層のない文書では、levels[0].breakBeforeで上書きしてください。
レベルのbreakBeforeはこの既定値にフィールド単位でマージされ、それに触れないheadingsオブジェクトは既定値を保ちます。H1に{ parity: 'odd' }を指定すると改ページを保ったまま奇偶だけが変わり、{ enabled: false }にすると章が続けて組まれます:
headings: {
fontFamily: 'Merriweather', // H1 still breaks to a fresh recto (always-odd)
levels: [{ level: 1, breakBefore: { parity: 'odd' } }], // …or: a recto, with no mandatory blank
}
headings: { levels: [{ level: 1, breakBefore: { enabled: false } }] } // chapters run onpostext 1.5での変更。postext 1.4までは、headingsオブジェクトを渡すとlevels[0].breakBeforeを改めて指定しない限りH1の改ページが無効になり、一部だけ指定したbreakBeforeは欠けたフィールドを改ページなしの既定値で補っていました。そのため、headingsオブジェクトを持ちH1の改ページを指定していない、1.4向けにコードで書いた設定では、いまはどの章も新しい奇数ページから始まり、必要に応じて白の偶数ページが入ります。章を続けて組むには、levelsのH1の項目にフィールドを1つ与えます:
headings: { fontFamily: 'Merriweather', levels: [{ level: 1, breakBefore: { enabled: false } }] } // as 1.4 laid it out設定が保存されたものであれば、エンジンがそれを見分けて代わりに処理します。Sandboxは当時保存した本と設定について(Sandbox → 作業内容の保存を参照)、openBundle / readBundleはpostext 1.4以前で書き出された.postextバンドルについて(postext 1.4以前で書き出したバンドルを参照)処理します。自分で保存した設定は、postext/bundleのmigrateConfig(config)に通せます。これは1.4が組んだ改ページを明示的に書き出します(pinLegacyHeadingBreaks)。改ページのなかったH1にはenabled: falseを、H1と見出しスタイルで奇偶を指定していないenabled: trueにはparity: 'any'を添えます。さらに次の項目も1.4の設定どおりに固定します:数式のサイズ、インライン図の周りの空き(囲みの中も)、見出しの文字書式、ドロップキャップのサイズ、リストを導くコロンの行の下の余地、囲みの切断が段落やリスト項目に残す行数、ダッシュでの改行、不ぞろい組みの本文の1行ずつの改行、見出しの下の段落の分割、複合語のハイフンでの改行、:::paragraphsコンテナーの下の空き。第3引数として本のMarkdownを{ content }で渡すと、不要な固定を省きます。本文に$がなければ数式の固定を、リソースを埋め込む行がなければ空きの固定を(囲みの中になければ囲み内の空きの固定を)、記号を含む見出しがなければ見出しの文字書式の固定を、コロンで終わる行の後にリストが続かなければコロン行の固定を、本文が:::calloutを開かなければ囲みの切断の固定を、語の間に詰めて置かれたダッシュがなければダッシュの固定を、本文に見出しがなければ見出し下の分割の固定を、本文が:::paragraphsコンテナーを開かなければコンテナーの固定を、2つの文字の間にハイフンがなければ複合語の固定を省きます。不ぞろい組みの改行の固定は本文の一部を不ぞろいに組む設定にだけ、コンテナーの固定は段落スタイルを宣言する設定にだけ加えます(postext 1.4以前で書き出したバンドルを参照)。改ページだけを固定するにはpinLegacyHeadingBreaks(config)を呼び出します。
レベルごとの上書きには、共通の既定値と同じプロパティ(fontSize、lineHeight、fontFamily、color、fontWeight、marginTop、marginBottom、snapToGrid)に加え、次のレベル専用のフィールドを使えます(見出しスタイルでも使えます):
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
italic | boolean | false | 見出しをイタリックで描画します。fontWeightに重ねて適用します。 |
textTransform | 'none' | 'uppercase' | 'none' | 見出しのタイトルを大文字にします(番号の接頭辞は書いたとおりに残り、タイトル中のチップや:refのラベルも同様です)。エディターのソースマップを1対1に保つため文字数を変えないので、大文字にすると長くなる文字(ß → SS)はそのままです。変換後のタイトルは、詳細デザインと章扉の{titleText}プレースホルダーにも渡ります。PDFのしおりはタイトルを書いたとおりに保ちます(AUTHOR CONTRIBUTIONSではなくAuthor contributions)。CSSのtext-transformが文字列そのものを変えないのと同じです(postext 1.4までは大文字になっていました)。 |
letterSpacing | Dimension | 0 | 見出しのすべてのグリフの後に入るトラッキングで、スペースと番号の接頭辞も含みます。CSSのletter-spacingと同じです。正の値は字間を広げ(textTransform: 'uppercase'で組んだ大文字には少し広げるのが普通です:{ value: 0.12, unit: 'em' })、負の値は大きな表示サイズを詰めます。emの値はそのレベルのfontSizeに対する相対値です。見出しの行はこの値込みで測るので、字間を付けた文字列の終わりで折り返し、Canvas、HTML、PDFは同じように描画します。中央そろえや右そろえの行は文字の範囲で配置します。デザインのテキストと同様に最後のグリフの後のトラッキングは除くので、中央そろえの見出しはspan: 'page'見出しの既定の章扉とそろいます。advancedDesignから描画するレベルは、ここにあるほかの文字組みのフィールドと同様にこの値を無視します。デザインの各テキスト要素がそれぞれletterSpacingを持っているからです。見出しスタイルでも、そのスタイルを使う見出しに対して設定できます。postext 1.4までは見出しにトラッキングがなく、このキーは何の通知もなく無視されていました。 |
lineSpan | number | 未設定 | 行取り(JLReq §4.1.6)。見出しを本文のこの行数分の帯に組み、文字をその中央に置きます。marginTopとmarginBottomの代わりに使います。3なら3行取りの見出しです。見出しがグリッドに吸着するとき帯は本文の行の位置から始まり、その後の本文もグリッドに乗ったままです。縦組み・横組みのどちらでも、cjk.gridを使っていても同じです。行数が足りない見出しは、次の整数の行数を取ります。中央とは文字の中央(ベースラインから書体の仮想ボディの中心までを引いた位置)で、JLReqの測り方に従います。行のボックスの中央ではありません。章扉(span: 'page')、advancedDesignから描画する見出し、非表示の見出し、囲みの中の見出しには適用しません。見出しスタイルで0を指定すると、そのレベルの行取りを解除できます。postext 1.16以降。 |
indent | Dimension | 0 | 見出しの行頭からの字下げ。emは本文のサイズで、日本の書籍が見出しの字下げを本文の字数で数えるのに合わせています(JLReq §4.1.3)。{ value: 6, unit: 'em' }は、見出しのサイズにかかわらず6字下げです。行長はその分狭くなり、中央そろえの見出しは残りの幅の中央に置かれます。postext 1.16以降。 |
jidori | number | 未設定 | 字取り。見出し自身のemでこの数より狭い1行の見出しを、ちょうどその幅になるよう均等に割り付けます。3なら序章を序 章と組みます。それより広いタイトルや複数行の見出しはそのままです。見出しのタイトルの後に{jidori=N}と書くとその見出しだけに指定でき、{jidori=0}でそのレベルの字取りを解除します。postext 1.16以降。 |
numberingTemplate | string | '' | そのレベルの自動番号のテンプレート。〜のトークンはその見出しレベルの通し番号を出力し、接尾辞で書式を指定できます。は大文字のローマ数字、は小文字のローマ数字、 / はアルファベット、はゼロ埋め、は語で綴る数(twenty-one)、は序数(twenty-first)、は漢数字(日本語の文書では日本式:第百一章)、は1桁ずつの漢数字、は大字、は丸数字です。番号書式の表記にある名前も使えます。それ以外の文字はそのまま出力します('Chapter . '、'.'、第一百二十回なら'第回'。バックスラッシュで波かっこそのものを書けます)。カウンターがまだ空のトークンは、隣接する区切りとともに消えます。空(既定)なら自動番号は付きません。生成された番号は流れの中でタイトルの前に付き、詳細デザインのスロットではプレースホルダーに渡り(そこでは接頭辞自体は付きません)、目次にも出力されます。語での綴り方は語で綴る番号を、レベルの一部の見出しに独自のテンプレートを与えるには見出しスタイルを参照してください。 |
numberSeparator | string | ' ' | 番号とタイトルの間に入るもの。段の中、span: 'page'レベルの既定の章扉、見出しの行を出力する柱、PDFのしおりで使われます。レベル1の区切りは、既定の部扉と、目次の既定の部の行で、部の番号とタイトルをつなぐのにも使います。中国語の章見出しには全角スペースを入れるか、何も入れません(' ':第一回 甄士隱夢幻識通靈)。目次は独自の番号の列を持ちます(toc.levels[].numberGap)。見出しスタイルは独自の値を設定できます。中国の小説の対句の回目のようにタイトルをで2つに分けた場合、1行で示す形(段の中、目次、柱)では、両側が中国語または日本語の文字なら全角スペースで、そうでなければ半角スペースで、2つの半分をつなぎます。 |
numberPosition | 'before' | 'replace' | 'before' | 生成された番号を置く位置。'before':タイトルの前に置き、numberSeparatorでつなぎます。'replace':番号がタイトル全体になり、ソースに書いたタイトルは出力しません。たとえばnumberingTemplate: 'الليلة {1:ordinal-feminine}'のもとで# Nightはالليلة الثانيةと出力されます。目次は番号を項目のタイトルとして示し(番号の列はなし)、柱(、)とPDFのしおりもそれを読みます。このような見出しではは空です。テンプレート(レベルのもの、またはスタイルのもの)を持つ番号付きの見出しにだけ適用し、それ以外はタイトルを保ちます。見出しスタイルは独自の値を設定でき、'before'にすると、レベルが置き換えるはずのタイトルを残せます。 |
breakBefore | HeadingBreakBeforeConfig | H1: { enabled: true, parity: 'always-odd' }H2–H6: { enabled: false, parity: 'any' } | このレベルのすべての見出しの前で改ページします。parity: 'odd' / 'even'は、見出しが見開きのどちら側から始まるかをさらに制約します。必要に応じて埋め合わせの白ページを挿入します(ページ番号には数えます)。'always-odd' / 'always-even'は、さらに前の内容と新しい見出しの間に、少なくとも1ページの必須の区切りの白ページを保証します(区切りのページは前の章に属し、奇偶合わせのための追加のページは新しい章に属します)。見出しが文書の最初のブロックで、1ページ目がまだ空のときは奇偶の強制を行わず、見出しは書いたとおり1ページ目に置かれます。指定しないフィールドはレベルの既定値を保つので、H1に{ parity: 'odd' }だけを指定しても改ページは行われます。 |
hidden | boolean | false | 構造上の見出し。何も出力せず、段の中でも囲みの中でも場所を取りません(本文も空きも章扉の帯もありません)が、見出しとしてのほかの働きはすべて行います。breakBeforeはページを始め、スタイルのセクションを開き、番号に数えられ(スタイルがnumbered: falseの場合を除く)、:::tocの一覧に載り、の柱に名前が出て、PDFのしおりにも入ります。目次や読者のしおりには必要でも、ページには表示しない献辞、エピグラフのページ、奥付に使います。レベル全体ではなく見出しスタイルに設定してください。個々の見出しでは / で上書きできます。 |
headings: {
fontFamily: 'Merriweather',
levels: [
// Canonical book preset: chapters on a right-hand (odd) page.
{ level: 1, fontSize: { value: 24, unit: 'pt' }, breakBefore: { enabled: true, parity: 'odd' } },
{ level: 2, fontSize: { value: 18, unit: 'pt' }, italic: true },
]
}snapToGridはレベルごとにも働きます。指定しなければレベルはheadings.snapToGridに従い、指定すればそれを上書きします。そのため1つの文書の中で、H2では本文との間に1行半を空けて次の吸着点までグリッドから外し、H3では下の空きをグリッドの行単位に切り上げる、といった組み方ができます。見出しスタイルでも、そのスタイルを使う見出しに対して設定できます。
headings: {
marginBottom: { value: 1.5, unit: 'em' },
levels: [
{ level: 2, snapToGrid: false }, // exactly 1.5 em under every H2
{ level: 3 }, // inherits headings.snapToGrid: true
],
}#前で改ページ
breakBeforeは番号付けの制御とは独立しています。有効にすると改ページを強制しますが、番号のカウンターがリセットされるのは:::numberingディレクティブを明示的に挿入したときだけです。奇偶合わせの白ページも実際のページとして数え、通常の奇数・偶数の規則に従ってヘッダーとフッターが付きます。
このような見出しの直前に:::pagebreakを置いても、見出しの改ページの代わりにはなりません。見出しはディレクティブが始めたページの後でも奇偶を適用するため、白ページが加わることがあります。手動の改ページの直後から始めたい見出しについては、見出しスタイルを参照してください。
奇偶の値
| 値 | 動作 |
|---|---|
'any'(既定) | 奇偶の制約はありません。見出しは単に次のページから始まります。 |
'odd' | 見出しが奇数(右)ページから始まるようにします。白ページを1ページ挿入するのは、自然な次のページが偶数ページのときだけです。 |
'even' | 同様に、偶数(左)ページから始めます。 |
'always-odd' | 前の内容と新しい見出しの間に少なくとも1ページの必須の区切りの白ページを保証し、そのうえで奇数ページにそろえます。どの章も新しい見開きから始めたいときに使います。 |
'always-even' | 同様に、偶数ページにそろえます。 |
白ページの帰属
breakBeforeが挿入した白ページには、挿入された理由に応じて、章タイトルの柱が付きます:
- 奇偶の制約(
'odd'、'even'、または'always-*'の奇偶合わせの部分)を満たすために挿入したページは、これから始まる章に属します。柱の{chapterTitle}プレースホルダーは新しい章のタイトルになります。その白ページは、新しい章を正しい奇偶のページへ送るためだけにあるからです。 'always-odd'/'always-even'が先頭に挿入する必須の区切りは、前の章に属します。章の終わりに意図して置く間なので、柱の{chapterTitle}プレースホルダーには前の章のタイトルが出たままです。
スタイルを持つセクション(見出しスタイルを参照)の柱とパレットも、白ページでは同じ2つの規則に従います。
文書冒頭の例外
文書の最初のブロックがbreakBeforeを有効にした見出しである場合、またはソースが:::pagebreakで始まる場合、1ページ目がまだ空のあいだは奇偶の強制を行いません。設定した奇偶にかかわらず見出しは書いたとおり1ページ目に置かれるので、parity: 'odd'を設定した# Chapter 1で始まる文書の先頭に、余計な白ページが入ることはありません。何か内容が置かれた後は、奇偶の強制は通常どおり働きます。
#幅と詳細デザイン
各見出しレベルには、見出しをページ幅いっぱいの章扉として描画する方法を制御するフィールドが、さらに2つあります。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
span | 'column' | 'page' | 'column' | 'page'のとき、見出しは章扉として扱われ、その詳細デザイン(有効な場合)は本文の上の章扉の帯としてページに付きます。章扉が確実に新しいページから始まるよう、breakBefore.enabled: trueと組み合わせてください。独自のデザインがない場合、タイトルは既定の章扉が版面の全幅にわたって描きます。レベルの文字組みと行送りを使い、太字・イタリック・上付き下付きの範囲(headings.inlineMarks)も反映し、帯はその幅で測ります。帯は章扉が描くすべての行を収めます。章扉が見出し自身の行長より多くの行を使う場合(語間を詰めれば1行に収まってしまう両端そろえのタイトル、強制改行)も、帯はその行数を取ります。postext 1.4までは帯を段の幅で折り返したタイトルで測っていたため、版面の幅なら1行に収まるタイトルが2行分の高さの帯を取り、その中央に1行が置かれていました。ほかの段も、見出し自身の段と同じく帯の下から始まります。それらの段の冒頭の本文は、章扉自身の段で何が続くかにかかわらず、章扉の直下の本文が始まる位置から始まります。つまり、タイトルまたはデザインの下に章扉のmarginBottomを取り、レベルが吸着するときは次のグリッド行まで進めた位置です。それらの段を見出し、別行立て数式、目次の行で始める場合は、章扉の下にある最初の見出しや別行立て数式と同じ高さに、そこでの空きも含めて置きます。章扉の段がそれ以外の内容で続く場合は、その本文が始まる位置から始まります。章扉の下の非表示の見出しは数に入らず、段末そろえが章扉の下の見出しの上に足した空きは、ほかの段では繰り返しません。これらの段がグリッドに吸着させるものは、ページのグリッドに乗ります。postext 1.4までは、それらの段の最初のブロックは帯の下端に置かれていました。帯が2行の間で終わればグリッドから外れ、章扉の後に見出しが続くと空きなしで帯に接していました(帯がグリッド上で終わる場合は現在より1行上)。また、そこにある見出しは、最初の段の見出しが保っていた上の空きも失っていました。 |
advancedDesign | HeadingAdvancedDesignConfig | | このレベルの自由配置のデザインスロット。enabledのとき、スロットの要素が章扉を構成します。テキスト要素の中でを使うと見出しのタイトル文字列を描画し、、などで書式付きの見出し番号を挿入します。 |
advancedDesign.minHeight | Dimension | — | 段の流れの中で見出しに確保する最小の高さ。見出しはmax(design content bottom, minHeight)を取り、その下に見出しのmarginBottom(見出しスタイル、レベル、またはheadings.marginBottomから。既定では見出しのサイズの0.5 em)を加え、見出しが吸着する場合はその合計をベースライングリッドに切り上げます。そのため章扉は、要素が短い場合や、見出しより上でページ枠・裁ち落とし枠に固定されている場合でも、本文を押し下げる(あるいはページ全体を占める)ことができます。帯をちょうどminHeightの高さにするには、そのmarginBottomを0にし、minHeightをグリッドの行の整数倍にします。スロットが空でも、enabledがtrueなら常に適用します。デザインの内容の下端に何が含まれるかは、確保する高さを参照してください。 |
ページと同じ高さの章扉。確保する高さが段の末尾を越える場合(minHeightがページの高さに等しい表紙や、ページまたは裁ち落としに固定されて仕上がり線まで伸びる画像や囲み)、章扉はページの残りを占めます。その後の本文は、2段組みでも1段半レイアウトでも、どの段でも次のページから始まります。したがって表紙の後に:::pagebreakは不要です(置いても害はなく、白ページは加わりません)。段内の見出し(span: 'column')のデザインが段より高い場合はその段を占め、本文は次の段の頭から始まります。見出しのブロックは占めた段の末尾まで伸び、デザインはその帯に対してレイアウトされます。上端に固定した要素は固定位置にとどまり、帯に従う要素(帯の中央や下端に固定したもの、または高さが'fill'のもの)は、高さが収まるときに確保した高さに収まるのと同じように、見出しが占める範囲に収まります(postext 1.4までは、ブロックは本文の高さのままだったので、それらの要素はタイトル1つ分の高さの帯に対してレイアウトされ、その後の本文はデザインの下へ続いていました)。ページを占めるべきでないページの装飾(ページ全体にわたる小口側の帯、ページ下端の飾り)は、ヘッダーかフッターのデザインに入れてください。'page'または'bleed'に固定し、pages: 'opener'で表示すれば、章扉のページに描かれ、本文の場所は確保しません。
例:タイトルの上に「Chapter N」を表示する簡素な章扉です。
{
"headings": {
"levels": [
{
"level": 1,
"span": "page",
"breakBefore": { "enabled": true, "parity": "always-odd" },
"advancedDesign": {
"enabled": true,
"slot": {
"elements": [
{
"kind": "text",
"id": "chapterLabel",
"placement": {
"anchor": { "to": "container", "edge": "top" },
"offset": { "y": { "value": 48, "unit": "pt" } },
"size": { "width": "fill" }
},
"content": "Chapter {numberRoman}",
"fontSize": { "value": 10, "unit": "pt" },
"align": "center",
"overflow": "ellipsis-end"
},
{
"kind": "text",
"id": "chapterTitle",
"placement": {
"anchor": { "to": "#chapterLabel", "edge": "below" },
"offset": { "y": { "value": 12, "unit": "pt" } },
"size": { "width": "fill" }
},
"content": "{titleText}",
"fontSize": { "value": 24, "unit": "pt" },
"fontWeight": 700,
"align": "center",
"overflow": "wrap",
"hyphenate": true
}
]
}
}
}
]
}
}レベルのデザインスロットで使える見出しのプレースホルダー:
{titleText}:見出しのプレーンテキスト(番号の接頭辞を除く)。タイトル中の強制改行(\\)は、章扉の帯でも段内のデザインでも、ここでは改行になります(postext 1.4までは、段内のデザインではスペースを出力し、高さは改行込みで測っていました)。非表示の見出しが段の中で折り返した行は、書いたとおりのタイトルに戻してつなぎます。語自身のハイフンの後で折り返した語はハイフンを残して後にスペースを入れず、段が分割した語は改行で加わったハイフンを除いて元の1語に戻します。段より広く、ノーブレークスペースの位置で分けられたまとまりはノーブレークスペースを取り戻し(CapítuloXVIIIではなくCapítulo XVIII)、それ以外の改行はスペース1つに戻します。postext 1.4までは改行がすべてスペースになったので、ハイフンで折り返したタイトルはWord- Bookと出力され、そのようなタイトルの強制改行は失われていました。{number}:レベルのnumberingTemplateに従って書式化した番号。{numberDecimal}、{numberRoman}、{numberRomanLower}、{numberAlpha}、{numberAlphaLower}:見出しのカウンター(テンプレートが何を出力するかにかかわらず、そのレベルの通し番号)を、ほかの数字の書式で表したもの。3番目の章なら3、III、iii、C、cとなり、numberingTemplateの有無は問いません。番号を付けない見出し(numbered: falseのスタイル)では空になります。{numberWords}、{numberWordsLower}、{numberOrdinalWords}、{numberOrdinalWordsLower}:同じカウンターを語で綴ったもの。先頭を大文字にするか、すべて小文字にするかを選べます:Three / three、Third / third(語で綴る番号を参照)。{numberHan}:同じカウンターを、文書のlocaleの字体の漢数字で表したもの。第{numberHan}回と設定した章扉は12番目の章で第十二回と出力し、目次には12と載ります。{chapterNumber}、{chapterTitle}、{pageNumber}、{totalPages}、{bookTotalPages}、{title}、{subtitle}、{author}、{publishDate}:共通のメタデータのプレースホルダー。{attr.<key>}:見出しの行そのものに書いた属性(# Title {author="I. Zango Martín"})。なければ現在の章のH1の属性を使います。存在しない属性は、警告なしで空文字列になります。
{chapterNumber}は、柱がその章について出力するものと同じ値を出力します。H1のレベル(またはスタイル)にnumberingTemplateがあればH1の番号、なければ章の序数(1、2…で、この章より前に組んだ章から続きます)で、番号を付けない章や、テンプレートが''のスタイルでは何も出力しません。見出しのデザインは、その見出しが属する章を読みます。レベル1の見出しは自身の章を、下位の見出しはその前にある最後のレベル1見出しの章を読みます。2つの章が1ページで接する場合も同じで、そのページの柱は後の章を出力します。章扉が確保する高さも、同じ値で測ります。postext 1.4までは見出しの番号の接頭辞(テンプレートがなければ空)で測っていたため、高さが{chapterNumber}に依存するデザインは確保した場所より高く描かれることがありました。また、デザインはページの章を出力していたので、1ページを共有する2つの章のうち最初の章に、2番目の章の番号が出ていました。
カウンターのプレースホルダーは、1章ずつ組んだ章をまたいで本全体に従います(continuationAfter()が引き継ぐカウンター)。見出しのstartAt属性でリセットされます(文書形式 → 見出しの属性を参照)。流れと目次では3.と番号が付いたタイトルの上に、Chapter IIIと表示する章扉の例です:
{
level: 1,
span: 'page',
numberingTemplate: '{1}.',
advancedDesign: {
enabled: true,
slot: { elements: [
{ kind: 'text', id: 'label', content: 'Chapter {numberRoman}', fontSize: { value: 10, unit: 'pt' }, overflow: 'ellipsis-end',
placement: { anchor: { to: 'container', edge: 'top-left' }, size: { width: 'fill', height: 'auto' } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontSize: { value: 24, unit: 'pt' }, overflow: 'wrap',
placement: { anchor: { to: '#label', edge: 'below' }, size: { width: 'fill', height: 'auto' } } },
] },
},
}確保する高さ
詳細デザインを持つ見出しは、ほかのブロックと同じように段の中で場所を取り、その後の本文はその下から始まります。その場所は次の3つの高さのうち最も高いもので、いずれも見出しの上端(ページの最初に来る章扉では版面の上端)から下へ測ります:
- レベルの文字組みで組んだ見出し自身の本文(デザインの下に隠れますが、行は保ちます)。
- デザインの内容の下端:数に入る要素(後述)のうち、最も低い下辺。
advancedDesign.minHeight。
その後に見出しのmarginBottom(見出しスタイル、レベル、またはheadings.marginBottomから。既定は0.5 em)を加え、見出しが吸着する場合はベースライングリッドに切り上げます。章扉(span: 'page')では、同じ帯をページのすべての段で空けておきます。段に収まる段内の見出しでは、デザインはちょうどその高さの見出しのボックスの中にレイアウトされます。
数に入る要素。デザインの要素は、テキスト、罫線、ボックス、画像のいずれもすべて数に入ります(postext 1.4まではimage要素は数に入らなかったので、minHeightで押さえない限り、本文が帯の画像に重なって始まることがありました)。ただし、次のものは除きます:
reserve: falseの要素:本文の下に敷いてよい装飾。- 帯そのものに従う要素:コンテナーの中段(
left、center、right)または下段(bottom-left、bottom、bottom-right)に固定した要素、高さをコンテナーに対する'fill'とした要素(高さのないボックスや縦罫線は既定でコンテナーを満たします)、およびそれらに固定した要素。コンテナーは確保した帯なので、これらは帯の下端に置かれるか、帯全体にわたります。帯の下の罫線や、タイトルの背後の色付きのパネルなどです。高さに従うだけで、高さを決めることはありません。ただし、そのうち自身の高さを保つテキストには、それでも場所が必要です。帯の下端に固定したタイトルは帯を少なくともタイトルの高さにするので、タイトルが見出しの上端より上から始まることはありません。minHeight: 36mmでタイトルをbottom-leftに固定すると、見出しのボックスは36 mmにmarginBottomを加え、グリッドに切り上げた高さになり、タイトルはそのボックスの下端、つまり続く本文のすぐ上に置かれます。minHeightがなければ、見出し自身の本文より多くの行を使うタイトルは、帯をタイトルの深さまで広げます。postext 1.4までは、このようなタイトルは見出しの上にある本文に重ねて上方向に描かれていました。帯に従うボックス、罫線、画像にはこのような下限はないので、下端に固定したパネルが見出しより上まで届くことがあります。
ページと裁ち落としに固定した要素は、見出しの上端より下へどれだけ届くかで数えます。見出しより上で終わるページ上部の帯は何も数えず、見出しを越えて伸びる裁ち落としいっぱいの画像は、本文をその下辺まで押し下げます。ページの低い位置にあるものも同じです。ページの下端から25 mm上にある印章、ページ全高にわたる側面の帯、枠などはページをその下辺まで確保するので、本文はたいてい次のページから始まります。このような装飾にはreserve: falseを付け(描画はされ、ほかの要素はそれに固定できます)、見出しに必要な場所はテキスト要素かminHeightで与えてください:
{
"kind": "image", "id": "seal", "resourceId": "seal", "reserve": false,
"placement": {
"anchor": { "to": "page", "edge": "bottom-right" },
"offset": { "x": { "value": -25, "unit": "mm" }, "y": { "value": -25, "unit": "mm" } },
"size": { "width": { "value": 30, "unit": "mm" } }
}
}確保しない要素に固定した要素は、それ自身にも印を付けない限り数に入ります(印章に付けたキャプションには、それ自身のreserve: falseが必要です)。
描画される位置。章扉のデザイン(span: 'page')は本文より先に描画されるので、何も確保しない装飾は本文の下に敷かれます。段内の見出しのデザインは見出しのブロックとともに描画され、段の中で先行するブロックより手前、後続のブロックより奥に描かれます。段の末尾より上であれば、左右の余白、天の余白、裁ち落とし、隣の段のどこに置いてもかまいません。流れはそこで終わるので、CanvasでもPDFでも段の末尾で切れます(後述の段より高い場合を参照)。postext 1.4までは、CanvasとPDFは段の上端でも切っていたため、ページや裁ち落としの上端に固定した帯は左右の余白には伸びても、天の余白で止まっていました。ページの下端に固定する装飾は、章扉か、pages: 'opener'を指定したフッターのデザインに入れてください。
段より高い場合。確保する高さが段の末尾を越える場合(ページと同じ高さのminHeight、仕上がり線まで伸びる画像や枠)、見出しはページ(章扉ではすべての段)または段(段内の見出し)の残りを占め、その後の本文は次のページまたは段から始まります。見出しのブロックは段の末尾まで伸び、本文の高さに切り詰められることはないので、デザインがレイアウトされる帯は占めた範囲そのものです(minHeightも含め、末尾まで)。ページそのものより高いデザイン(小さな画面用ページの長いリードなど)は、それでもページの末尾で切れます(段内のデザインは段の末尾で切れます)。Sandboxはこれを検査パネルで見出しのデザインの欠けとして示します。レイアウト自体は警告を出しません。ページを占める表紙はよくある使い方で、失うものもないからです。ただし、どのホストでも組み上がったレイアウトに同じ検査を行えます。collectHeadingDesignCuts(doc)は、デザインのテキストがページの仕上がりの末尾(where: 'page'、章扉)または段の末尾(where: 'column')を越えている見出しごとに{ kind: 'headingDesignCut', pageIndex, level, where, overflowPx, sourceStart, sourceEnd }を1つ返し、formatWarningがそれぞれを説明します。幅と詳細デザインのページと同じ高さの章扉を参照してください。
サイド段。サイド段がフロートを受け持つ(sideColumnRole: 'floats')1段半レイアウトでは、段内の見出しのデザインのうちサイド段に立つ要素(教科書の章扉で、小口側の余白の段にページ基準で固定した章番号など)が、サイド段に積まれるフロートを遠ざけます。見出しの後にそのページが置くspan: 'side'の図、表、囲みは、そのような要素からフロートの間隔1つ分を空けます。積み重ねで決まる位置で要素より上に収まればそこにとどまり、収まらなければ要素の下へ回り、サイド段の残りにそこで収まらなければ次のページを待ちます。そのため、サイド段の頭にある章番号は積み重ね全体をその下に置き、ページの下のほうにある見出しの脇の余白に掛けた節番号は、サイド段の頭をそのページが引用する図に譲ります(余白の図は引き続きページの上端に置かれます)。見出しを置く時点でサイド段がすでに持っているものは動かしません。ページの前のほうで積まれ、見出しの要素まで届く図はそのまま要素の下に残るので、ページの途中でサイド段に要素が立つデザインでは、図を見出しの後で引用してください。reserve: falseの要素は、本文に対してと同じくサイド段も空けたままにします。章扉(span: 'page')ではこのような配慮は不要です。その帯はサイド段を含むすべての段で確保されるからです。postext 1.4までは、このような章扉で引用したサイドの図は、サイド段の頭、章番号の上に置かれていました。
表紙。ページを埋める表紙の見出し(ページと同じ高さのminHeight、またはデザイン内のページ全面の画像)は、こうして1段組みでも多段組みでも、それだけで後の本文を次のページへ送ります。直後の:::pagebreakは任意で、害もありません。まだ空のページでの改ページは何もしないので、白ページが加わることはありません。必要になるのは、表紙のデザインがページの末尾まで届かず、それでも本文を新しいページから始めたい場合だけです。
# Annual report 2026 {style="cover"}
:::pagebreak
# Letter from the chair#語で綴る番号
番号付けテンプレートの2つの接尾辞は、カウンターを文書の言語(最上位のlocale、なければハイフネーションのロケール。文書の言語を参照)の語で書きます。基数にはwords、序数にはordinalを使います。文字のA / aと同じく、接尾辞の大文字・小文字が語の大文字・小文字を決めます:
| トークン | 英語(21) | スペイン語(21) | 中国語(21) |
|---|---|---|---|
| twenty-one | veintiuno | 二十一 |
| Twenty-one | Veintiuno | 二十一 |
| TWENTY-ONE | VEINTIUNO | 二十一 |
| twenty-first | vigesimoprimero | 第二十一 |
| Twenty-first | Vigesimoprimero | 第二十一 |
| TWENTY-FIRST | VIGESIMOPRIMERO | 第二十一 |
英語、スペイン語、中国語、アラビア語は語で綴ります。ほかの言語は、組み込みの表の続き文字列と同じく英語の語を使います。中国語はlocaleの字体に従い、simp-chinese-informalまたはtrad-chinese-informalの通常の漢数字(一万 / 一萬)で書き、序数の前に第を付けます。漢字には大文字・小文字がないので、接尾辞の3つの書き方は同じ結果になります。英語はアメリカ式です(one hundred five、andなし)。スペイン語は、capítuloやlibroに番号を付けるときと同じく男性形を使い(capítulo primero、tercero、veintiuno)、13から29の序数はRAEが推奨するとおり1語で書きます(decimotercero、vigesimoprimero)。基数は999 999まで、スペイン語の序数は999まで語で綴り、それより大きい数は数字で出力します。
アラビア語の数は数える名詞と性が一致するので、接尾辞に修飾子を付けます。女性名詞には-feminine(または-f)、男性名詞には-masculine(-m、既定)を使います。-classicalは、ブーラーク版や多くのエジプトの刊本と同じく、百を現代のمئةではなくمائةと綴ります。{1:ordinal}は、見出しで使う限定・主格の序数を書きます。الفصل {1:ordinal}はالفصل الأول、الفصل الحادي عشر、الفصل الحادي والعشرونとなり、الليلة {1:ordinal-feminine}はالليلة الأولى、الليلة الحادية عشرة、الليلة الحادية والعشرون、الليلة المئتانとなります。100を超えると、古典的な「〜の後の」の定型になります:الليلة الخامسة والأربعون بعد الثلاثمئة、الليلة الحادية بعد الألف。{1:words}は基数を書きます(واحد وعشرون。女性形はإحدى عشرة、واحدة وعشرون)。序数は9 999まで、基数は99 999まで語で綴ります。修飾子は組み合わせられ({1:ordinal-f-classical})、ほかの言語では無視されます。アラビア語には大文字がないので、接尾辞の大文字・小文字は何も変えません。デザインのプレースホルダー{numberWords}と{numberOrdinalWords}は男性形を書きます。女性形の章扉では、レベルのテンプレートに序数を入れて{number}で出力してください。
見出しのデザインでは、{numberWords} / {numberWordsLower}と{numberOrdinalWords} / {numberOrdinalWordsLower}が見出しのカウンターを同じように綴るので、目次には1と載せたまま、章扉にはChapter Oneと表示できます。大文字にするには、テキスト要素のtextTransform: 'uppercase'を使います:
// Spanish novel: "CAPÍTULO PRIMERO" above the title, "1." in the contents.
{ level: 1, numberingTemplate: '{1}.', span: 'page',
advancedDesign: { enabled: true, slot: { elements: [
{ kind: 'text', id: 'n', content: 'Capítulo {numberOrdinalWordsLower}', textTransform: 'uppercase', /* … */ },
{ kind: 'text', id: 't', content: '{titleText}', /* … */ },
] } } }#箇条書きリスト
unorderedListsプロパティは、箇条書きリスト(-、*、+)とGFMのタスクリスト(- [ ]、- [x])の描画方法を制御します。入れ子は5階層までサポートしています。
#箇条書きリストの既定値
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
fontFamily | string | bodyText.fontFamilyを継承 | 項目の文字に使うフォントです。 |
color | ColorValue | メインカラー(#295AA3) | 項目の文字と行頭記号の色です。既定のパレットのmain-colorエントリーに結び付けられています。 |
fontWeight | number | 700 | 項目の文字のウェイトです(100–900)。レベルごとに上書きしない限り、行頭記号もこのウェイトを継承します。 |
italic | boolean | false | 項目の文字をイタリックで描画します。 |
bulletChar | string | '•' | 行頭記号として使うグリフです。 |
bulletFontSize | Dimension | 1 em | 行頭記号のグリフのサイズです。相対単位は本文のフォントサイズに応じて拡大縮小します。 |
gap | Dimension | 0.5 em | 行頭記号と項目の文字のあいだの水平方向のアキです。 |
indent | Dimension | 0 em | レベル1の基本インデントです。それより深いレベルは、上書きしない限り、親の文字の開始位置から連鎖して決まります(後述)。 |
bulletVerticalOffset | Dimension | 0 em | 行頭記号の垂直位置を微調整します。負の値で上へ、正の値で下へ移動します。 |
marginTop / marginBottom | Dimension | 1.5 em | リスト全体の前後のアキです。 |
itemSpacing | Dimension | 0 em | 行の高さに加えて項目間に挿入する、垂直方向の追加のアキです。別のリストの項目の中に入れ子にしたリストの周囲では、外側のリストのアキが両側、つまり入れ子のリストの最初の項目の前と最後の項目の後に適用されます(postext 1.4までは、入れ子のリストの後の項目には入れ子のリストのアキが使われていました)。 |
snapTopToGrid | boolean | false | 見出しの下の本文と同じように、リストの上のアキを切り上げて、最初の行頭記号がベースライングリッドに乗るようにします。このときmarginTopは最小値になります。リストの終わりではどちらの設定でも流れがグリッドに戻るため、itemSpacingが0なら、すべての項目が隣の段の本文と行がそろいます。postext 1.4までと同じく、既定ではオフです。この場合、行の整数倍でないmarginTopを指定すると、リストが終わるまで項目はグリッドから外れます。内部がグリッドから外れている囲みの中のリストは影響を受けません。 |
hangingIndent | boolean | true | 有効にすると、折り返した行を行頭記号の下ではなく、文字の最初の字にそろえます。 |
levels | UnorderedListLevelConfig[] | — | レベル1–5の深さごとの上書きです。後述します。 |
#タスクリストの拡張
GFMのタスク項目(- [ ] …、- [x] …)は、行頭記号の代わりにチェックボックスのグリフを置いた箇条書きの項目として描画されます。次のフィールドはタスク項目にだけ適用されます。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
taskCheckboxChar | string | '☐' | 未完了のタスクに使うグリフです。 |
taskCheckedChar | string | '☑' | 完了したタスクに使うグリフです。 |
taskCompletedStrikethrough | boolean | true | 完了したタスクの文字に取り消し線を引きます。 |
taskCompletedColor | ColorValue | 項目の色を継承 | 完了したタスクの文字に適用する色です(省略可)。省略すると通常の項目の色を使います。 |
#箇条書きリストのレベル別上書き
levelsの各エントリーは1つの深さ(1–5)を対象とし、次のどの項目でも上書きできます。
| プロパティ | 型 | 説明 |
|---|---|---|
bulletChar | string | この深さの行頭記号のグリフです。 |
fontFamily | string | この深さの項目のフォントファミリーです。 |
fontSize | Dimension | この深さの行頭記号のグリフのサイズです。 |
color | ColorValue | 項目の色です。 |
fontWeight | number | 項目のウェイトです。 |
italic | boolean | イタリックの切り替えです。 |
indent | Dimension | この深さの行頭記号の明示的なインデントです。下記の連鎖の規則を参照してください。 |
verticalOffset | Dimension | この深さの行頭記号の垂直方向の微調整です。 |
インデントの連鎖。レベル1は常に全体のindentの値から始まります(既定は0 emで、行頭記号は段の端に固定されます)。レベル2–5では、indentを未定義のままにすると、エンジンは行頭記号を1つ上のレベルの文字の開始位置(親のインデント+行頭記号の幅+gap)に置きます。あるレベルにindentを明示すると連鎖が切れ、その深さを好きな位置に固定できます。
unorderedLists: {
bulletChar: '—',
gap: { value: 0.4, unit: 'em' },
hangingIndent: true,
levels: [
{ level: 2, bulletChar: '·' },
{ level: 3, bulletChar: '◦', color: { hex: '#666666', model: 'hex' } },
],
}#番号付きリスト
orderedListsプロパティは番号付きリスト(1.、2)など)を制御します。入れ子は5階層までサポートしており、深さごとに異なる番号の書式を使えます。
#番号付きリストの既定値
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
fontFamily | string | bodyText.fontFamilyを継承 | 項目の文字と番号に使うフォントです。 |
color | ColorValue | メインカラー(#295AA3) | 項目の文字と番号の色です。既定のパレットのmain-colorエントリーに結び付けられています。 |
fontWeight | number | 700 | 項目の文字と番号のウェイトです(100–900)。 |
italic | boolean | false | 項目の文字をイタリックで描画します。 |
numberFormat | OrderedListNumberFormat | 'arabic' | 番号のスタイルで、'arabic'、'lower-alpha'、'upper-alpha'、'lower-roman'、'upper-roman'のいずれかです。ほかの設定での表記も使えます('decimal'、'roman-lower'、'i'…。番号書式の表記を参照)。未知の値はアラビア数字で番号を付け、報告されます。 |
prefix | string | '' | 番号の前に置く文字列で、区切りと同じスタイルで組みます。ここに'('、区切りに')'を指定すると、中国語のリストは(一)、(二)となります。区切りが独立したランとして描画される場合は、接頭辞も番号の直前で独立したランになります。 |
separator | string | '.' | 番号と文字のあいだに置く文字です。通常は'.'または')'です。 |
separatorFontFamily | string | fontFamilyを継承 | 区切りのフォントです。区切りのスタイルのどれかが番号と異なる場合、区切りは(右そろえの)番号の後に独立したランとして描画されます。たとえば、Optima Boldの黒の1の後に、DIN Pro Boldの青の•が続きます。 |
separatorFontWeight | number | fontWeightを継承 | 区切りのウェイトです(100–900)。 |
separatorItalic | boolean | italicを継承 | 区切りをイタリックで描画します。 |
separatorColor | ColorValue | colorを継承 | 区切りの色です。パレットの参照も反映されます。 |
separatorGap | Dimension | 0 em | 番号と区切りのあいだのアキです。項目の文字は、これまでどおり区切りからgap離れた位置から始まります。 |
numberFontSize | Dimension | 1 em | 番号のサイズです。 |
gap | Dimension | 0.5 em | 番号と項目の文字のあいだの水平方向のアキです。 |
indent | Dimension | 0 em | レベル1の基本インデントです。それより深いレベルは、上書きしない限り、親の文字の開始位置から連鎖して決まります。 |
numberVerticalOffset | Dimension | 0 em | 番号の垂直位置を微調整します。 |
marginTop / marginBottom | Dimension | 1.5 em | リスト全体の前後のアキです。 |
itemSpacing | Dimension | 0 em | 項目間の垂直方向の追加のアキです。別のリストの項目の中に入れ子にしたリストの周囲では、外側のリストのアキが両側、つまり入れ子のリストの最初の項目の前と最後の項目の後に適用されます(postext 1.4までは、入れ子のリストの後の項目には入れ子のリストのアキが使われていました)。 |
snapTopToGrid | boolean | false | 見出しの下の本文と同じように、リストの上のアキを切り上げて、最初の番号がベースライングリッドに乗るようにします。このときmarginTopは最小値になります。リストの終わりではどちらの設定でも流れがグリッドに戻るため、itemSpacingが0なら、すべての項目が隣の段の本文と行がそろいます。postext 1.4までと同じく、既定ではオフです。この場合、行の整数倍でないmarginTopを指定すると、リストが終わるまで項目はグリッドから外れます。内部がグリッドから外れている囲みの中のリストは影響を受けません。 |
numberWidth | 'run' | 'level' | 'run' | 項目の番号欄の幅で、これによって文字の開始位置が決まります。番号は欄の中で右そろえになります。'run':その項目が属する連続部分(run)の中でもっとも幅の広い番号に合わせます。連続部分とは、同じ深さの項目が、より深い項目だけを挟んで続くまとまりです。2つの項目のあいだに図、段落、囲みがあると新しい連続部分が始まるため、表の後のii)は、その前のi)より少し右から文字が始まることがあり、9項目のリストは12項目のリストより左から文字が始まります。'level':文書全体(本では章)で、その項目の深さにあるもっとも幅の広い番号に合わせます。そのため、すべてのリストと、途中で中断されたリストの各部分が同じ位置から文字を始めます。より深いレベルのインデントがすでにそうなっているのと同じです。 |
hangingIndent | boolean | true | 折り返した行を、番号の下ではなく文字の最初の字にそろえます。 |
levels | OrderedListLevelConfig[] | — | レベル1–5の深さごとの上書きです。 |
#番号付きリストのレベル別上書き
levelsの各エントリーでは、numberFormat、prefix、separator、fontFamily、fontSize、color、fontWeight、italic、indent、verticalOffsetと区切りのスタイル(separatorFontFamily、separatorFontWeight、separatorItalic、separatorColor、separatorGap)を上書きできます。箇条書きリストと同じインデントの連鎖が適用されます。あるレベルの区切りのスタイルは、リスト全体の区切りの設定が指定されていない限り、そのレベル自身の番号のスタイルを継承します。
右そろえ。パイプラインは連続部分の中でもっとも幅の広い書式付きの番号を測り、その連続部分のすべての項目をインデントして、番号の右端をそろえます。1.–10.と描画される10項目のリストでは、1桁の番号に余白を補い、区切りが同じ位置にそろうようにします。
orderedLists: {
numberFormat: 'arabic',
separator: '.',
levels: [
{ level: 2, numberFormat: 'lower-alpha' },
{ level: 3, numberFormat: 'lower-roman', separator: ')' },
],
}これで、定番の入れ子の組み合わせになります。
1. First item
a. Sub-item
i) Deep note
b. Sub-item
2. Second item
中国語の公文書の階層(GB/T 15834—2011、付録B.3)は5レベルで、一、→(一)→1.→(1)→①の順になります。
orderedLists: {
levels: [
{ level: 1, numberFormat: 'simp-chinese-informal', separator: '、' },
{ level: 2, numberFormat: 'simp-chinese-informal', prefix: '(', separator: ')' },
{ level: 3, numberFormat: 'arabic', separator: '.' },
{ level: 4, numberFormat: 'arabic', prefix: '(', separator: ')' },
{ level: 5, numberFormat: 'circled-decimal', separator: '' },
],
}#数式
mathプロパティは、$...$(インライン)と$$...$$(別行立て)で囲んだLaTeXの数式の解析と描画の方法を制御します。基盤のエンジンはSVG出力モードのMathJax(mathjax-fullパッケージ)で、キャンバスにはラスタライズして描画し、PDFには拡大縮小可能なグリフとして埋め込みます。
interface MathConfig {
enabled?: boolean; // Render LaTeX. When false, spans pass through as literal TeX.
fontSizeScale?: number; // × the surrounding text's size (the body size for display maths).
color?: ColorValue; // Formula colour; inherits body colour if omitted.
marginTop?: Dimension; // Space above display math blocks.
marginBottom?: Dimension; // Minimum space below; baseline grid snap may enlarge it.
indentAfterDisplay?: boolean; // Indent a paragraph that follows a display formula.
keepWithLeadIn?: boolean; // Keep a display formula with the line that leads into it.
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | true | falseの場合も$...$と$$...$$のスパンは解析されますが(そのため、閉じていない区切り記号の警告は出ます)、TeXのソースがそのまま描画されます。内容に意図的にドル記号が含まれる場合や、数式の描画を完全に無効にしたい場合に使います。 |
fontSizeScale | number | 1.0 | 描画の前に周囲のテキストのサイズに掛ける倍率です。数式のTeXフォントの1emは、そのサイズ×fontSizeScaleになります。そのサイズとは、別行立て数式と本文中のインライン数式ではbodyText.fontSize、見出し、段落スタイル、キャプション、囲みの本文の中のインライン数式では、それを含むブロックのサイズです。1.0で周囲のテキストと一致します。数式のフォントが本文のフォントよりわずかに大きく、または小さく見える場合は、0.9–1.1の範囲の値が一般的です。postext 1.5で変更:1.4までは、数式がこれより約13%大きく組まれていました(後述)。 |
color | ColorValue | 本文の色を継承 | 描画される数式の色です。省略するとbodyText.colorを継承します。数式を本文と異なる色にしたい場合、たとえば見出しのアクセントカラーに合わせたい場合に明示的に設定します。 |
marginTop | Dimension | 0.8em | 別行立て数式の上のアキです。インライン数式では無視されます。 |
marginBottom | Dimension | 0.8em | 別行立て数式の下のアキです。これは最小値で、次のベースラインがグリッド線に乗るように、グリッドへのスナップで広がることがあります(page.baselineGridでグリッドを表示するかどうかにかかわらず)。 |
indentAfterDisplay | boolean | true | 別行立て数式に続く段落の1行目を、ほかの段落と同じく字下げします。falseにすると、別行立て数式の直後の段落はすべて字下げせずに組まれ、数式が中断した文の続き(「ここでLは…」)として扱われます。段落の中に書いた数式(上にも下にも空行がないもの)の後は、常に字下げしません。閉じる$$の下の文はその段落の続きであり、字下げされることはありません(数式を参照)。 |
keepWithLeadIn | boolean | false | 別行立て数式を、それを導入する行と同じ段に置きます。TeXのpredisplay penaltyに相当します。数式がその前の段落の最終行の下に収まらない場合、その行は数式と一緒に次の段またはページへ移ります。後に残る行がbodyText.widowMinLines未満になる場合(bodyText.avoidWidowsがオフなら1行未満)は、その段で始まる段落が丸ごと次へ移ります(headings.keepWithNextにより、その上で段を閉じている見出しも一緒に移ります)。持ち越された行は、オーファンの規則が何を求めていても、次の段の先頭に単独で置かれます。falseの場合は数式だけが次へ移り、それを導入する行が上の段を閉じたり、次の段の先頭にある図の上に置かれたりすることがあります。 |
math: {
enabled: true,
fontSizeScale: 1.0,
color: { hex: '#295AA3', model: 'hex' },
marginTop: { value: 1, unit: 'em' },
marginBottom: { value: 1, unit: 'em' },
}数式のサイズ(postext 1.5で変更)。MathJaxは数式のボックスをexで返し、TeXフォントの1exは0.442emです。postext 1.4まではエンジンがこれを0.5emとして扱っていたため、どの数式もbodyText.fontSize × fontSizeScaleより約13%大きく組まれていました。現在は数式がドキュメントに記載したとおりのサイズで組まれ、数式を含む行とページは組み直されます。1.4向けにコードで書いた設定は、書いたままの状態でpostext/bundleのpinLegacyMathSizeに1回通すと、1.4の数式サイズを保てます。
import { pinLegacyMathSize } from 'postext/bundle';
config = pinLegacyMathSize(config); // formulas, and the space around display formulas, as 1.4 set them1.5でのほかの規則の変更でも、ページが動くことがあります。対象は、見出しの改行位置、インラインの図の周囲のアキ(本文中と囲みの中)、見出しのインラインのマーク、ドロップキャップのサイズ、コロンで終わる行の下に、それが導入するリストのために確保する余地、囲みの分割で段落やリスト項目に残る行、ダッシュの後の改行、行末不ぞろいのテキストの改行、見出しの下での段落の分割、複合語のハイフンの後の改行、:::paragraphsコンテナーの下のアキです。その設定で組むMarkdownを渡してmigrateConfigを1回実行すると、そのテキストに必要なものを、数式のサイズも含めて固定します(postext 1.4以前で書き出したバンドルを参照)。
import { migrateConfig } from 'postext/bundle';
config = migrateConfig(config, undefined, { content: markdown }); // heading breaks, formulas, inline gaps, heading marks, drop caps, colon lines, box cuts, dash breaks, compound breaks, ragged breaking, splits under a heading and container space as 1.4 set themこれで、こうした規則の変更によって動くものは抑えられますが、1.4のページがすべて保たれるわけではありません。バージョン1.5ではレイアウトのバグも修正しており、修正は固定できません。古い設定にも新しい設定と同じく修正が適用されるため、修正の影響を受けるページは動くことがあります。主なものは次のとおりです。独自のデザインを持たないページ幅の見出しは、ページ全体の幅で測られ、そのレベルの行送りで組まれます。見出しデザインの中のドロップキャップのベースラインの下には、何も確保されません。ドロップキャップはセクションのパレットの色を取り、overflowが'wrap'でないデザインテキストにも組まれ、そのテキストは回り込むようになります。囲みの中の段落は、測ったときのトラッキングで描画されます。分割された囲みは、すべての断片でアイコンの列を保ちます。語間を3倍を超えて広げることになる囲みの行は、本文と同じく行末不ぞろいで組まれます。ゆるい段落を調整する仕組みは、両端そろえの行をmaxWordSpacingが許す以上に広げません。フロートとして配置した囲みは、フロートのアキより広いmarginBottom(上の帯の場合)またはmarginTop(下の帯の場合)を保ちます。トラッキングを指定した中央そろえまたは右そろえのデザインテキスト(柱、章扉のタイトル)は、最後の文字の後のトラッキングを含めず、文字そのもので位置が決まります。ページ幅の章扉の下では、2段目を始めるテキストが、章扉の直下のテキストと同じ位置から始まります。章扉の後に見出しが続く場合も同様です。連続するドットをカーニングで離す書体でも、目次のリーダーはページ番号の手前で止まります。柱は見出しを書かれたとおりに読み、ハイフンやダッシュの後、または幅のために分割された語の途中で行が終わる箇所に空白を入れません(MEDIOAMBIENTALE SではなくMEDIOAMBIENTALES)。見出しデザインの{titleText}も同じように読みます(thousand- colourではなくthousand-colour)。見出しデザインの帯の下端または中央に固定したテキストは、それが収まる高さに帯を保つため、見出しの上のテキストに重なることがなくなりました。行より幅の広い語が、その語に含まれるハイフンの隣で分割される場合は、そのハイフンの後で分割され、2つ目のハイフンは付きません。別のリストに入れ子にしたリストの後の項目は、入れ子のリストではなく自分のリストのitemSpacingを使います。最後に、行より幅が広いために分割された語の残りは、その語自身の分割位置を保ちます。そのため、Webアドレスは区切りの位置で、複合語はハイフンの位置で改行され続け、辞書の音節で改行されることはありません。
pinLegacyMathSizeは倍率に1.1312(0.5 ÷ 0.442)を掛け、emで指定した別行立て数式のアキを同じ係数で割ります。別行立て数式を含む本では、倍率だけでは足りません。そのアキは数式自身のサイズに対する長さなので、同じく13%大きくなり、下のテキストを押し下げてしまいます。既定のアキについて書き出すと、3つの値は次のとおりです。
math: {
fontSizeScale: 1.131,
marginTop: { value: 0.7072, unit: 'em' },
marginBottom: { value: 0.7072, unit: 'em' },
} // formulas as large as 1.4 set them, with the space 1.4 left around them設定が保存されていた場合は、エンジンがそれを判別して自動的に処理します。1.5より前に書かれた.postextバンドルではopenBundle / readBundleが(postext 1.4以前で書き出したバンドルを参照)、その時期に保存された本、作業コピー、postext-config.jsonファイルではSandboxが処理します(Sandbox → 作業内容の保存を参照)。これらはmigrateConfigを通して読み込まれ、サイズが固定されます(pinLegacyMathSize)。fontSizeScaleは保存されていた倍率(未設定なら1)×1.1312になり、emまたはremで指定した別行立て数式のアキ(数式自身のサイズに対する長さ)は同じ係数で割られるため、別行立て数式の周囲のアキは1.4のときのまま保たれます(既定の0.8emは0.7072emになります)。ページの単位(pt、mm…)で指定したアキはそのままで、enabled: falseの設定や、本に$がない設定も変更されません。こうして古い本は1.4と同じように組まれ、そのmathセクションには実際に組まれるサイズが表示されます。そのような本を現在のサイズで組みたい場合は、これらの値をリセットします。Sandboxでは数式 → サイズの倍率、別行立て数式の上のアキ、別行立て数式の下のアキで、コードでは次のようにします。
math: { ...config.math, fontSizeScale: 1, marginTop: undefined, marginBottom: undefined } // today's size and margins番号付きの数式。\tag{…}を含む別行立て数式は、その組幅(段、または囲みの内側の幅)いっぱいに広がります。数式は中央に置かれ、その番号は数式の行の右端にそろえて組まれます。alignでは、タグの付いたすべての行でそうなります。\tag*{…}はラベルを括弧なしで、書かれたとおりに印字します。番号が付くのは明示的なタグだけで、equationなどの環境はそれだけでは番号が付きません。組幅より広い番号付きの数式は、ほかの別行立て数式と同じく右へはみ出します。(postext 1.4までは、\tagを含む数式はまったく描画されませんでした。)
解決関数(resolver)と既定値の除去関数(stripper)は、ほかのセクションと同じ形です。
import {
DEFAULT_MATH_CONFIG,
resolveMathConfig,
stripMathDefaults,
} from 'postext';
const resolved = resolveMathConfig(config.math);
const minimal = stripMathDefaults(config.math);#数式エンジンの起動
MathJaxはエンジンのほかの部分と一緒には読み込まれず、必要になったときに読み込まれます。メインスレッドでレイアウトする場合は、数式を含む文書を構築する前に起動してください。
import { buildDocument, initMathEngine, renderPage } from 'postext';
await initMathEngine(); // loads MathJax once; later calls resolve at once
const doc = buildDocument({ markdown: 'Euler: $e^{i\\pi}+1=0$.' }, config);
document.body.append(renderPage(doc.pages[0], doc));- エンジンが動くまで、数式はプレースホルダーです。各数式は推定サイズの灰色のボックスとして配置されます。
initMathEngine()を一度も呼ばずに数式を含む文書をレイアウトすると、コンソールにその旨の警告が1回表示されます。 - レイアウトワーカーは自動で起動します。
postext/workerでのビルドは、Markdownに$が含まれていれば自らinitMathEngine()を呼びます。 - すぐにレイアウトし、準備ができたらもう一度。
isMathReady()はエンジンが動いているかどうかを返します。onMathReady(fn)はエンジンが動き出したときに(すでに動いていればすぐに)fnを1回呼び、その呼び出しを取り消す関数を返します。エディターはすぐにプレースホルダーを表示し、MathJaxが届いたらもう一度レイアウトできます。initMathEngine()の実行中は警告は出ません。 - 失敗。MathJaxを読み込めない場合、
initMathEngine()はrejectされます。次の呼び出しで再び読み込みを試みます。 - どのバンドラーでも、Nodeでも、CDNでも。MathJaxは、事前にバンドルした1つのモジュールとしてパッケージに同梱されています(圧縮前で約1.8MB、
initMathEngineが呼ばれたときにだけ取得されます)。単純なimport { initMathEngine } from 'https://esm.sh/postext'で動作し、?bundleは不要です。mathjax-fullパッケージはpostextのビルドにだけ必要なため、postextをインストールしてもインストールされません。MathJaxと、それに同梱されたmhchemパーサーはApache-2.0ライセンスです。それらの告知とライセンス文は、モジュールの隣にdist/math/THIRD_PARTY_LICENSES.txtとして同梱されています。 - 1ページにエンジンは1つ。エンジンと、描画済みの数式のキャッシュは、同じJavaScriptレルムで
postextをインポートするすべてのコードで共有されます(1つのページで共有されるグローバルな状態を参照)。
文書側の文法($...$、$$...$$、ドル記号そのもののエスケープ)については、文書形式を参照してください。
#脚注
footnotesプロパティは、[^id]で引用した注をどこに置き、どのように番号を付け、どのような見た目にするかを設定します。記法は文書形式で説明しています。
interface FootnotesConfig {
placement?: 'column' | 'chapterEnd' | 'spread'; // Foot of the citing column, after the chapter, or beside the text of a spread.
numbering?: 'chapter' | 'document' | 'page' | 'column' | 'spread'; // Start again at each chapter, run on, or start again on each page / column / spread.
numberFormat?: string; // decimal, lower-roman, circled-decimal (①)…
markerPosition?: 'auto' | 'superscript' | 'inline' | 'side' | 'right'; // Raised, on the baseline, beside the word, or right of a vertical line.
markerSize?: Dimension; // Inline, side or right marker size; em is the text around it.
numberGap?: 'en' | 'em'; // Space after the note's own number.
chapterEndAlign?: 'foot' | 'text'; // chapterEnd: notes at the column foot, or under the text.
fontSize?: Dimension; // Note size; em is the body size.
lineHeight?: Dimension; // Note leading; em is the note size.
color?: ColorValue; // Note colour; the body colour when unset.
textAlign?: TextAlign; // The body alignment when unset.
hangingIndent?: Dimension; // Indent of a note's turnover lines.
spaceBetween?: Dimension; // Space between two notes.
spaceAbove?: Dimension; // Space between the text and the rule; em is the body size.
spaceBelowRule?: Dimension; // Space between the rule and the first note.
separator?: {
enabled?: boolean; // Draw the rule.
width?: number; // Rule length, a fraction of the column width.
lineWidth?: Dimension; // Rule thickness.
color?: ColorValue; // The note colour when unset.
};
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
placement | 'column' | 'chapterEnd' | 'spread' | 'column'(日本語の縦組みの本:'chapterEnd') | 'column'は、各注を、それを引用する行がある段の下端に、短い罫線の下に組みます。1段組みのレイアウトではページの下端になります。'chapterEnd'は、章のすべての注を、その章の最後のブロックの後に引用順に組みます。'spread'(傍注、縦組みのみ)は、見開きの両ページで引用された注を、奇数ページ(右綴じの本では左ページ)の末尾に罫線の下に組みます。偶数ページで引用された注は奇数ページまで待ちます。奇数ページに残る本文が1行未満になってしまう注は、それ以降の注とともに偶数ページの下端にとどまり、偶数ページで終わる章は、待っている注をそこに組みます。横組みではunknownConfigValueの警告を出して'column'にフォールバックします。postext 1.16以降。 |
numbering | 'chapter' | 'document' | 'page' | 'column' | 'spread' | 'chapter'(日本語の横組みの本:'page'、placement: 'spread'の場合:'spread') | 'chapter'は、レベル1の見出しのたびと文書の始まりで、1から番号を振り直します。'document'は文書全体を通して番号を続け、章ごとにレイアウトする本では章から章へと続けます(continuationAfterが最後の番号をcontinuation.footnoteNumberとして引き継ぎます)。'page'はページごとに、'column'は段ごとに1から振り直し、レイアウトが注を組んだ位置で(ページ内の段は読む順に)注を数えます。中国語の本で一般的な页下注です。文書はいったんレイアウトされ、注が置かれた位置で番号が付けられ、番号が安定するまで再びレイアウトされます(追加のビルドは最大3回)。どちらも段の下端の注に適用され、placement: 'chapterEnd'では注は章ごとに番号が付きます。'spread'は、placement: 'spread'用に、見開き(2–3ページ、4–5ページ…)ごとに引用順で振り直します。 |
numberFormat | string | 'decimal' | 番号の書き方です。番号付けの設定が受け付けるどの表記でも指定できます('decimal'、'lower-roman'、'lower-alpha'、'circled-decimal'(または'①')、'cjk-decimal'、'一'…)。本文中の合印と、注の冒頭の番号の両方に使われます。circled-decimalは50を超える番号を10進数で書きます。未知の名前は、unknownNumberFormatの警告とともに10進数で番号を付けます。 |
markerPosition | 'auto' | 'superscript' | 'inline' | 'side' | 'right' | 'auto' | 'superscript'は、本文中の合印と注の冒頭の番号を、縮小したサイズで上付きにします。'inline'はそれらをベースライン上に組みます。合印はmarkerSize、注の番号は注のサイズです。縦組みでは、行内の丸数字の合印は専用のセルの中で正立します。'right'は、日本語の縦組みの本が(1)を組むように、縮小した合印を縦の行の右側にそろえて組みます。横組みでは上付きになります。'side'(合印)は、印を付けた語の脇、ルビの側に小さな合印を組み、語の最後の文字が終わる位置で終わらせます。行の中で場所を取らないため、行はそれがないものとして改行・両端そろえされます。それを収めるには狭すぎる行間はrubyExceedsLeadingとして報告されます。'auto'は、circled-decimalではinline、日本語の縦組みの本では'right'、それ以外では上付きになります。どの場合も、合印は直前の文字から離れず、行頭に来ることはありません。 |
markerSize | Dimension | 1em。'side'では0.6em、'right'では0.7em | インライン、脇、右の合印のサイズです。emは周囲のテキストのサイズです(0.75emがよく使われる縮小率です)。上付きの合印には影響しません。 |
numberGap | 'en' | 'em' | 'en'(日本語の後注:'em') | 注の冒頭の番号の後のアキです。二分アキ、または注の全角アキ(番号か注にCJKのテキストが含まれる場合は和文の全角スペース)で、伸縮も改行もしません。postext 1.16以降。 |
markerTemplate | string | '{n}'(日本語の縦組みの本:'({n})') | 注の番号の書き方です。{n}が、番号の書式と文書の数字で書いた番号を表します。'({n})'とすると、アラビア語の本の括弧付きの合印«(١)»になります。本文中の合印と注の冒頭の番号の両方を、この形で書きます。{n}を含まないテンプレートは既定値として扱われます。 |
noteNumberPosition | 'auto' | 'superscript' | 'inline' | 'auto' | 注の冒頭の番号の位置で、上付きにするか、注のサイズでベースライン上に置くかを決めます。'auto'はmarkerPositionに従います。アラビア語の本では、本文中の合印を上付きにし、注自身の番号はベースライン上に組みます。 |
chapterEndAlign | 'foot' | 'text' | 'foot' | placement: 'chapterEnd'の場合の配置です。'foot'は、段を閉じる注を段の下端に組み、余った行は本文と注のあいだに残ります。段の下端の注と同じ配置です。'text'は本文の直下に組みます。 |
fontSize | Dimension | 0.8em | 注の文字のサイズです。emとremは本文のサイズです。注には本文のファミリーとウェイトが使われます。 |
lineHeight | Dimension | 1.25em | 注の文字の行送りです。emは注のサイズです。注はベースライングリッドから外れます。段の下端から積み上がり、その上の本文はグリッドに乗ったままです。 |
color | ColorValue | 本文の色 | 注の文字の色です。 |
textAlign | TextAlign | 本文の行そろえ | 注の文字の行そろえです。 |
hangingIndent | Dimension | 0 | 注の2行目以降のインデントで、番号より後ろにそろえるためのものです。 |
spaceBetween | Dimension | 0 | 2つの注のあいだのアキです。 |
spaceAbove | Dimension | 0.5em | 本文の最終行と罫線のあいだのアキです。emは本文のサイズです。'chapterEnd'とchapterEndAlign: 'text'の場合は、spaceAbove+spaceBelowRuleが本文と最初の注のあいだのアキになります。 |
spaceBelowRule | Dimension | 0.4em | 罫線と最初の注のあいだのアキです。 |
separator.enabled | boolean | true | 各段の注の上に罫線を引きます。falseの場合も、上のアキは残ります。 |
separator.width | number | 0.3 | 段の幅に対する比率(0–1)で表した罫線の長さで、段の左端から引きます。 |
separator.lineWidth | Dimension | 0.5pt | 罫線の太さです。 |
separator.color | ColorValue | 注の色 | 罫線の色です。 |
footnotes: {
fontSize: { value: 7.5, unit: 'pt' },
lineHeight: { value: 9.5, unit: 'pt' },
hangingIndent: { value: 0.8, unit: 'em' },
separator: { width: 0.25, lineWidth: { value: 0.4, unit: 'pt' } },
}日本語の本。日本語の文書(locale: 'ja'、postext 1.16以降)では、未設定のフィールドにJLReq §4.2の値が使われます。縦組みの本は注を章の後に組み(placement: 'chapterEnd'、後注)、章ごとに番号を付け、合印({n})を行の右に置きます(markerPosition: 'right'、数字はcjk.uprightDigitsにより正立)。横組みの本は注をページの下端に組み、ページごとに番号を付け、合印を上付きにします。どちらも罫線を組幅の3分の1の長さで引き(separator.width: 1/3)、章の後に置く注にはnumberGap: 'em'と2字分のぶら下げインデントを使います。明示した値が優先され、保存時にも保持されます(stripFootnotesDefaultsは文書自身の既定値と比較します)。中国語、アラビア語、ラテン文字の文書の解決結果は従来どおりです。footnoteDocumentDefaults(locale, writingMode, placement?)はこれらの値を返します。合印は文末の。の前に書いてください(先生[^1]。)。合印は直前の文字から離れず、。が行頭に来ることはありません。日本語の組版 › 注を参照してください。
段の下端での注の組み方は次のとおりです。
- 注と引用箇所は同じ段に置かれます。行を置く前に、レイアウトはその行が初めて引用する注の高さ(その段の最初の注の場合は罫線も)を加えます。その下に注が収まらない行は、オーファンとウィドウの規則に従い、段落の残りとともに次の段へ移ります。段の本文領域は注の高さの分だけ縮むため、段末そろえと章の最後の帯では本文だけが数えられます。
- 複数の注が1つの段にある場合は、1本の罫線の下に引用順に積み重なります。あとで再び引用された注は番号を保ち、二度は組まれません。
- 下側のフロート。注を組んだ後に段の下端を占める図は、注の上に入ります。図の後に組まれた注は図の上に入ります。
- 囲み。囲み(インライン、フロート、固定)の中で引用された注は、囲みの後の本文が続く段の下端に置かれます。通常は同じ段です。文書を閉じる囲みは、本文が終わった段の下端に注を残します。
- 制限。注は分割されません。段より高い注は段からはみ出します。キャプション、表のセル、見出しの中の合印は読み取られません(書いたとおりに印字されます)。
- 出力。Canvas、HTML、PDFは注、合印(上付きの番号)、罫線を描画します。PDFでは各合印がその注にリンクし、タグ付きPDFでは各注を一意の
/IDを持つNote要素とし、構造ツリーの/IDTreeに列挙します(PDF/UA-1)。注はfootnoteNoteが設定されたVDTBlockとしてpage.floatsに、罫線はpage.footnoteAreasに入ります。 - 警告。
undefinedFootnote(定義のない合印。番号は空の注の上に印字されます)とunusedFootnote(どの合印からも引用されない定義。組まれません)があります。
解決関数と除去関数は、ほかのセクションと同じ形です。
import { DEFAULT_FOOTNOTES_CONFIG, resolveFootnotesConfig, stripFootnotesDefaults, footnoteDocumentDefaults } from 'postext';
resolveFootnotesConfig(config.footnotes, 'ja', 'vertical-rl'); // unset fields take the Japanese vertical defaults#相互参照
crossRefsプロパティは、相互参照が番号やページの前後に印字する語と、スタイルを指定しない:refに使うスタイルを設定します。各テンプレートには、番号が入る位置に{n}を書きます。これを含まないテンプレートでは、ノーブレークスペースの後に番号が入ります("§"は「§ 3.2」と印字されます)。未設定のテンプレートは文書の言語に従い、英語ではchapter / section / p.、スペイン語ではcapítulo / sección / pág.、中国語では第{n}章 / 第{n}节 / 第{n}页となります。フランス語、ドイツ語、イタリア語、ポルトガル語、カタルーニャ語、オランダ語も同様です。
interface CrossRefsConfig {
chapter?: string; // Words around a level-1 heading's number: "chapter {n}".
section?: string; // Around any other heading's number: "section {n}".
page?: string; // Around a page number: "p. {n}".
defaultStyle?: 'default' | 'number' | 'title' | 'page'; // A :ref without style=.
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
chapter | string | 言語による | レベル1の見出しへの参照に使います("chapter {n}")。テンプレートがすでにその語を含む見出し番号(Chapter {1}、第{1:一}章)は、そのまま印字されます。 |
section | string | 言語による | レベル2–6の見出しへの参照に使います("section {n}"、"§ {n}")。 |
page | string | 言語による | ページ参照(style=page)に使います("p. {n}"、"page {n}")。 |
defaultStyle | 'default' | 'number' | 'title' | 'page' | 'default' | style=を指定しない、見出しまたはアンカーへの:refが印字する内容です。'default':番号付きの見出しはその語と番号、番号のない見出しはタイトル、アンカーはそのテキストを印字します。styleを指定した参照はその指定を保ち、図と表への参照には影響しません。 |
参照には、すべての参照に共通する色、ウェイト、傾き(bodyText.referenceColor、referenceBold、referenceItalic)が使われます。
#引用
citationsプロパティでは、引用スタイルと、引用と参考文献の見た目を選びます。記法は引用と参考文献で説明しています。スタイルはpostext-citeprocパッケージが適用します。
interface CitationsConfig {
style?: string; // 'apa', 'ieee', 'chicago-notes-bibliography'… or 'custom'
customStyle?: string; // a whole CSL style (.csl XML), used with style: 'custom'
locale?: string; // CSL locale; the document language when unset
link?: boolean; // citations link to their entries
marker?: 'style' | 'brackets' | 'parentheses' | 'superscript' | 'corner';
collapseRanges?: boolean;
notes?: 'footnote' | 'warichu';
bibliography?: {
title?: string; // unset: the document language's word; '' or ' ': none
scope?: 'book' | 'chapter';
auto?: boolean;
fontSize?: Dimension;
lineHeight?: Dimension;
hangingIndent?: Dimension;
entrySpacing?: Dimension;
labelWidth?: Dimension;
labelAlign?: 'left' | 'right';
doi?: 'link' | 'text' | 'hide';
includeUncited?: boolean;
groupByLanguage?: boolean;
};
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
style | string | 'apa' | 同梱のスタイルのID(スタイルを参照)または'custom'です。スタイルは、引用と文献項目の内容(名前、日付、順序、句読点、引用を注にするかどうか)を決めます。 |
customStyle | string | — | styleが'custom'のときに使う、CSLスタイル全体(.cslファイルのXML)です。Sandboxではファイルから読み込みます。 |
locale | string | 文書の言語 | スタイルが語句を書くときのCSLロケールです(en-US、es-ES、zh-CN、zh-TW、ja-JP…)。日本語の文書はja-JPとして扱われ、叙述的な引用では2人の著者を「と」でつなぎ、それより多い場合は「ほか」で省略します。 |
link | boolean | true | 引用を参考文献の項目にリンクします(PDFのリンク、HTMLのアンカー、Sandboxでのクリック)。 |
marker | 'style' | 'brackets' | 'parentheses' | 'superscript' | 'corner' | 'style' | 番号方式のスタイルで、引用をどう示すかです。スタイルの表記どおり、[1]、(1)、上付き、〔1〕(縦組みでは正立)から選びます。番号の後にロケーターが続きます。 |
collapseRanges | boolean | true | 連続する番号を範囲にまとめます。独立した1つの合印の場合は1–3、IEEEの場合は[2]–[4]です。falseでは別々のままにします。 |
notes | 'footnote' | 'warichu' | 'footnote' | 注方式のスタイルで引用を組む場所です。脚注(footnotesの設定どおりに配置・番号付け)か、行の中に2行で組む割注(夹注)かを選びます。 |
bibliography.title | string | 言語による | 一覧の上に置くタイトルで、太字の段落になります。空白にするとタイトルは付きません。独自の見出しは:::bibliographyの上に置きます。 |
bibliography.scope | 'book' | 'chapter' | 'book' | 本が引用するすべての文献を1つの一覧にするか、章ごとにその章が引用する文献の一覧を作るかです。複数の章からなる文書では、各H1が新しい章の一覧を始めます。autoでは、:::bibliographyを置かない章の末尾に一覧が入ります。 |
bibliography.auto | boolean | true | :::bibliographyで位置を指定しない場合、一覧を本文の後(本全体の一覧なら最終章の後)に組みます。 |
bibliography.fontSize | Dimension | 0.9em | 項目のサイズです。emは本文のサイズです。 |
bibliography.lineHeight | Dimension | 本文の行送り | 項目の行送りです。 |
bibliography.hangingIndent | Dimension | 2em | 番号のない項目の2行目以降のインデントです。 |
bibliography.entrySpacing | Dimension | 0.3em | 2つの項目のあいだのアキです。 |
bibliography.labelWidth | Dimension | もっとも長いラベル | 番号付きの一覧で番号を置く欄の幅です。どの項目の文字も、1行目も2行目以降も、この幅だけ内側から始まるため、9.と10.が同じ欄に収まります。ラベルは引き続き項目のテキストの一部です。 |
bibliography.labelAlign | 'left' | 'right' | 'left' | ラベルを欄のどこに置くかです。欄の左端に寄せるか、文字の側に寄せるか(9.と10.の末尾がそろいます)を選びます。 |
bibliography.doi | 'link' | 'text' | 'hide' | 'link' | DOIとURLを、リンクにするか、プレーンテキストにするか、省略するかです。 |
bibliography.includeUncited | boolean | false | 引用の有無にかかわらず、すべての文献を一覧にします(nocite: "@*"と同様)。 |
bibliography.groupByLanguage | boolean | false | 中国語、日本語、韓国語の文献を先に、その他を後に並べます。著者・年方式と著者・ページ方式のスタイルのみが対象で、番号付きの一覧は番号の順序を保ちます。 |
#東アジアの組版
cjkプロパティは、中国語・日本語・韓国語のテキストの組み方を設定します。どの地域の慣習に従うか、行をどこで分割できるか、約物をどの幅で組み、ぶら下げるかどうか、漢字と欧文の間のアキ、版面の文字グリッド、そして圏点、傍線、ルビ、割注、漢文の訓点をどう印字するかを決めます。どのフィールドも省略でき、'auto'は文書の言語(locale)の地域に従います。そのためlocale: 'zh-Hant'の本は台湾式に行を分割して約物を組み、locale: 'ja'の本は日本式に組みます。ほかに設定するものはありません。中国語の組版と日本語の組版では、これらの設定の背後にある規則を、それぞれ2つの完全な設定例とともに説明しています。日本語向けのフィールドと値はpostext 1.16から使えます。
interface CjkConfig {
region?: 'auto' | 'mainland' | 'taiwan' | 'hongkong' | 'japan'; // Regional conventions.
lineBreak?: 'auto' | 'none' | 'basic' | 'gb' | 'strict'
| 'ja-very-strict' | 'ja-strict' | 'ja-loose'; // Which marks may not open or close a line.
punctuationWidth?: 'auto' | 'fullwidth' | 'kaiming' | 'lineEndHalf' | 'halfwidth';
compressAdjacent?: 'auto' | boolean; // Two marks that meet take 1.5 em.
trimLineStart?: 'auto' | boolean; // Brackets at a line edge lose their outer half.
hangingPunctuation?: 'auto' | 'none' | 'allow' | 'force';
spaceAfterQuestion?: 'auto' | boolean; // One em after ?! inside a paragraph (Japan).
paragraphStartBracket?: 'auto' | 'indent' | 'half' | 'flush'; // A bracket opening an indented paragraph.
wordBreak?: 'normal' | 'keep-all'; // keep-all: break only at spaces (分かち書き, Korean).
latinSpacing?: Dimension; // Between Han and Latin; default 0.25 em.
uprightDigits?: 0 | 2 | 3 | 4; // Vertical text: numbers in one upright cell; default 2.
grid?: { enabled?: boolean; charsPerLine?: number; linesPerPage?: number; show?: boolean };
emphasis?: 'auto' | 'italic' | 'dots'; // What *…* does to CJK characters.
emphasisMark?: { // The mark *…* and :dots[…] draw when they do not say.
style?: 'auto' | 'dot' | 'circle' | 'sesame';
fill?: 'auto' | 'filled' | 'open';
position?: 'auto' | 'over' | 'under';
};
bookTitleMark?: 'auto' | 'brackets' | 'wavy' | 'none'; // What :book[…] prints.
bookTitleBrackets?: 'auto' | { open: string; close: string }[]; // The brackets, outermost first.
annotationColor?: ColorValue; // Dots and name/title lines; default the text colour.
ruby?: {
fontFamily?: string; fontSize?: Dimension; color?: ColorValue;
position?: 'auto' | 'over' | 'under' | 'right';
overhang?: 'auto' | 'none' | 'kana' | 'any'; // How far a reading may run onto its neighbours.
align?: 'auto' | 'center' | 'jis' | 'start'; // How a reading shorter than its base is spaced.
smallKana?: 'keep' | 'full'; // Small kana in readings as written, or full size.
};
warichu?: { fontSize?: Dimension; color?: ColorValue; open?: string; close?: string };
kunten?: { fontSize?: Dimension; color?: ColorValue; placement?: 'inline' | 'interlinear' }; // Kanbun marks.
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
region | 'auto' | 'mainland' | 'taiwan' | 'hongkong' | 'japan' | 'auto' | テキストが従う慣習です。地域の区分はW3Cの中国語組版の要件(Requirements for Chinese Text Layout)(clreq)に準じます。'auto'は地域をlocaleから読み取り、localeが未設定のときはbodyText.hyphenation.localeから読み取ります。zh、zh-Hans、zh-CN、zh-SGは'mainland'、zh-Hantとzh-TWは'taiwan'、zh-HKとzh-MOは'hongkong'、jaとすべてのja-*タグは'japan'になります。この地域の慣習はW3Cの日本語組版処理の要件(JLReq)に従います。その他の言語は'mainland'になります。地域は、以下のすべての'auto'フィールドの既定値を決めます。'japan'を手動で設定すると、どの文書も日本式に組まれます。ただし組み込みの文字列と、注および索引の既定値は引き続き文書の言語に従います。 |
lineBreak | 'auto' | 'none' | 'basic' | 'gb' | 'strict' | 'ja-very-strict' | 'ja-strict' | 'ja-loose' | 'auto' | 行頭・行末に置けない記号を決めます(clreq §6.1.1とJLReq付録C.3。下の表を参照)。'auto'は中国大陸では'gb'、台湾と香港では'basic'、日本では'ja-very-strict'です。どのレベルもあらゆる文書で選べます。 |
punctuationWidth | 'auto' | 'fullwidth' | 'kaiming' | 'lineEndHalf' | 'halfwidth' | 'auto' | 全角の約物をどの幅で組むかを決めます(約物の幅を参照)。'auto'は中国大陸では'kaiming'、台湾・香港・日本では'fullwidth'です(日本では、約物の連続、行末、中点についてJLReqの規則を適用します)。 |
compressAdjacent | 'auto' | boolean | 'auto' | 連続する2つの約物(。」、》(、:“)の間の二分のアキを詰め、2つで全角2つ分ではなく1.5em分にします。'auto'は中国大陸・香港・日本ではオン、台湾ではオフです。 |
trimLineStart | 'auto' | boolean | 'auto' | 行頭に来た始め括弧・始めの引用符は前側の二分のアキを、行末に来た終わり括弧・終わりの引用符は後ろ側の二分のアキを取り除きます。'auto'は中国大陸・香港・日本ではオン、台湾ではオフです。日本では、行末の終わりの約物は行がそのアキを必要とするまで二分のアキを保ちます(約物の幅を参照)。 |
hangingPunctuation | 'auto' | 'none' | 'allow' | 'force' | 'auto' | 読点類・句点類を行末より外にぶら下げてよいかを決めます(ぶら下げを参照)。'auto'は日本では'allow'(、。,.のみ)、それ以外では'none'です。postext 1.15までは既定値が'none'で、日本以外での'auto'は現在もその値です。 |
spaceAfterQuestion | 'auto' | boolean | 'auto' | 段落の中で、全角の?や!(‼⁇⁈⁉も含み、それらに続く注の合印の後も含む)の後に全角のアキを入れます。日本語の組み方です(JLReq §3.1.6)。終わり括弧や同種の記号の前、段落の末尾では入れず、行末に来たときは取り除きます。著者がそこに入力したU+3000やスペースは、このアキに置き換わります。このアキは伸び縮みしません。'auto'は日本ではオン、それ以外ではオフです。 |
paragraphStartBracket | 'auto' | 'indent' | 'half' | 'flush' | 'auto' | 1行目を字下げした段落が始め括弧で始まるときの組み方です(JLReq §3.1.5)。'indent'は字下げを保ち、括弧の後ろに全角のアキを与えます(パターン①)。'half'は括弧のグリフの前のアキを取り除き、グリフは全角の字下げの後半に収まって、本文はほかの段落の本文と同じ位置から始まります(パターン③)。'flush'は字下げせずに括弧を行頭にそろえます(天付き)。字下げのない段落やぶら下げインデントの段落はそのままです。'auto'は日本では'half'です。それ以外では、従来どおりほかの行頭と同じように括弧を組みます。 |
wordBreak | 'normal' | 'keep-all' | 'normal' | 2つの文字の間のどこで改行するかを決めます。'keep-all'はスペース(U+0020、U+3000)と、lineBreakが許す約物の隣(、。」の後、「の前)でだけ改行し、2つの文字(かな、漢字、ハングル、ラテン文字)の間では改行しません。絵本や初等教科書のように文節の間にスペースを入れて書くかな(分かち書き)や、韓国語に向いています。行より長い文節は、'normal'と同じ位置で文節の中で分割します。段落スタイルでも個別に設定できます(wordBreak)。postext 1.16以降。 |
latinSpacing | Dimension | { value: 0.25, unit: 'em' } | 漢字とその隣のラテン文字または数字の間のアキです。CJKテキストの文字サイズを基準とするemか、任意の長さで指定します。0でオフになります(和欧間のアキを参照)。 |
uprightDigits | 0 | 2 | 3 | 4 | 2 | 縦組みで、この桁数以下の数字を1つの正立したマスに収めます(縦中横)。ただし欧文の文中にある数字は、その文の語に合わせて横倒しになります。0でオフになります(縦組みの数字を参照)。 |
grid | { enabled?, charsPerLine?, linesPerPage?, show? } | オフ | 版面を1行の字数と1ページの行数で指定します(文字グリッドを参照)。 |
emphasis | 'auto' | 'italic' | 'dots' | 'auto' | Markdownの強調(…)が中国語・日本語の文字をどう扱うかを決めます。'dots'は:dots[…]と同じように各文字の脇に圏点を付け(形と位置はemphasisMarkに従います)、同じ強調の中のラテン文字はイタリックのままです。'italic'は文字をイタリックにしますが、CJKの書体では疑似的な斜体にしかなりません。'auto'は文書の言語が中国語か日本語なら'dots'、それ以外では'italic'です(圏点と傍線、ルビ、割注を参照)。 |
emphasisMark | { style?, fill?, position? } | いずれも'auto' | …と属性のない:dots[…]が付ける圏点です。styleは'dot'、'circle'、'sesame'(﹅)、fillは'filled'または'open'(autoでは丸は白抜き)、positionは'over'(縦組みでは右)または'under'(縦組みでは左)です。autoの場合、日本では黒ゴマ点を文字の上(縦組みでは右)に付けます(JLReq §3.3.9)。それ以外では従来どおり点を文字の下(縦組みでは右)に付けます。圏点自身の属性が優先されます。 |
bookTitleMark | 'auto' | 'brackets' | 'wavy' | 'none' | 'auto' | :book[…]が印字するものです。書名を囲む括弧(bookTitleBrackets)、書名の下の波線の書名号、または書名だけのいずれかです。'auto'は中国大陸と日本では括弧、台湾と香港では波線です。 |
bookTitleBrackets | 'auto' | { open, close }[] | 'auto' | 'brackets'のときに:book[…]が使う括弧で、外側から順に並べます。リストより深く入れ子になった書名には最後の組を使います。'auto'(または空のリスト)は、日本では『』、次に「」、それ以外では《》、次に〈〉です。 |
annotationColor | ColorValue | 本文の色 | 圏点、専名号、書名号の色です。ルビと割注の色の既定値にもなります。 |
ruby | { fontFamily?, fontSize?, color?, position?, overhang?, align?, smallKana? } | 半分のサイズ、'auto' | :ruby[…]と{紅樓|hóng|lóu}のルビです。書体(既定値は本文の書体)、サイズ(既定値は本文の{ value: 0.5, unit: 'em' }、注音符号はその60%)、色、そしてルビ自身が指定しないときの位置('auto':注音符号は各文字の右、ピンインとかなは横組みでは文字の上、縦組みでは右)を決めます。overhangは、親文字より長いルビが隣の文字にどこまで掛かってよいかです。'kana'はかな、ー、約物のアキ側にルビ1字分、始め括弧にルビ半字分掛かり、漢字には掛かりません(JLReq §3.3.8)。'any'はルビのない隣の文字ならどれにでもルビ半字分掛かります。'none'は掛かりません。autoは日本では'kana'、それ以外では従来どおりルビのない隣の文字にルビサイズの4分の1まで掛かります。alignは、親文字より短いルビの配置です。'jis'は1:2:1(両端に二分、間に全角)、'center'はベタ組みで中央、'start'は親文字の先頭から配置します。autoは日本では'jis'、それ以外では'center'です。smallKana: 'full'はルビの小書きのかなを並字にします(既定値の'keep'は書かれたとおり)。ルビ自身のmodeとalignが優先されます。 |
warichu | { fontSize?, color?, open?, close? } | 半分のサイズ。括弧は日本では()、それ以外ではなし | :warichu[…]の2行の注です。注のサイズ(既定値は二分で、2行で行の全角を埋めます)、色、そして1行目の前と最終行の後に本文サイズで組む括弧を決めます(注自身のopen/closeが優先されます)。日本では、空のopenやclose('')を指定すると、その括弧なしで注を組みます。 |
kunten | { fontSize?, color?, placement? } | 半分のサイズ、'inline' | :kunten[…]の漢文の訓点(返り点、送り仮名、竪点)です。サイズ(既定値は{ value: 0.5, unit: 'em' }、JIS X 4051 §5.5)、色(既定値はannotationColor、未設定なら本文の色)、そしてplacementを決めます。'inline'はJISと同じく返り点を親字の後に二分の幅で組みます。'interlinear'はすべての訓点を行間に置き、送り幅を取りません。どの地域でも同じです(圏点と傍線、ルビ、割注を参照)。 |
| レベル | 行頭に置かない | 行末に置かない |
|---|---|---|
none | なし。台湾や香港の新聞のように、どの2文字の間でも改行できます。 | なし。 |
basic | 読点類・句点類 、,;:。!?.‼⁇⁈⁉、終わりの引用符 ” ’ 」 』と終わり括弧 )〕]}】〗》〉、連接記号 – ~ ~ と2語の間の単独の —、中点 · ‧ ・、繰返し記号 々〻ゝゞヽヾとー、数字の単位 % ‰ ° ℃ %と単位記号 ㎡ ㎏ ㏄。 | 始めの引用符 “ ‘ 「 『と始め括弧 (〔[{【〖《〈、通貨記号 ¥ $ € £。 |
gb | basicに加え、斜線 / /(GB/T 15834—2011 §5.1.9)。 | basicに加え、斜線。 |
strict | gbに加え、2倍ダッシュ —— ⸺と省略記号 …… ⋯⋯。 | gbと同じ。 |
日本語のレベルは、JLReq付録C.3の3つの慣習に従います。どのレベルでも、始め括弧・始めの引用符で終わる行はなく、通貨記号は数字から離れず(ja-looseを除く)、斜線には規則がなく、——、――、……は行頭に来てもよいものの分割されません(〳〵も同じです)。
| レベル | 行頭に置かない |
|---|---|
ja-very-strict | 終わり括弧類と終わりの引用符、、,。.、ハイフン類 ‐ – ゠ 〜 ~、?!‼⁇⁈⁉、・:;、繰返し記号 ゝゞヽヾ〻と々、ー、小書きのかな ぁぃぅぇぉっゃゅょゎゕゖ ァィゥェォッャュョヮヵヶ ㇰ–ㇿ(とその半角形)、単位 % ℃。JIS X 4051の既定です。 |
ja-strict | ja-very-strictから小書きのかな、ー、々を除いたもの(JLReqの一般書籍)。 |
ja-loose | 終わり括弧類と終わりの引用符、、,と。.のみ(新聞)。 |
すべてのレベルに共通する規則です。
- ——と……は全角2つ分の幅の1単位として扱い、分割しません。この単位が2つ続くときは、その間で分かれてもかまいません。
- 数字は符号や単位と離れず(¥5,999、50%、50%、120㎡)、間にスペースがあっても同じです(−3 ℃、50 %)。ラテン文字の語も分割されません。CJK文字に挟まれた欧文は、行の途中で改行しないひと続きとして組みます。ただし、その欧文だけで行より長い場合は、音節でハイフンを付けて分割するか、収まる最後の文字で分割します(ウェブアドレスは区切りの位置で分割します)。単位記号 ㎡ ㎏ ㎞ ㏄(U+3371〜337A、U+3380〜33DF、U+33FF)は数字のひと続きに含まれ、欧文の段落をCJKの段落にすることはありません。
- 全角の数字やラテン文字で書いた数や語(123456、50%、¥599、3.14、12:30、ABC)も分割しません。両端そろえの行では、漢字と同じようにそれらの字間も広げます。
- 単語間のスペースは、規則が許す場所でだけ改行位置になります。次の文字が行頭に置けない場合(
参见图表 ( 第三章 )は)で始まる行を作りません)や、スペース直前の文字が行末に置けない場合は、そこで改行しません。 - 脚注の合印、
:ref、上付き文字・下付き文字は、直前の文字から離れません。 - 和字間隔U+3000は全角幅の1文字です。その後では改行できますが、その前では改行しません。伸ばされることはなく、行頭でも取り除かれません。中国語の段落の2字下げには、U+3000を2つ入力するのではなく
bodyText.firstLineIndent: { value: 2, unit: 'em' }を設定してください(段落の先頭にあるU+3000はパーサーが取り除きます)。
行に収まらない文字が次の行の行頭に置ける場合は、その文字を次の行へ送り、両端そろえの行は残りの字間を広げます。次の行の行頭に置けない場合(読点、終わり括弧、始め括弧の次の文字)は、まず行を詰めてその文字を収めようとします(追い込み、clreq §6.2.2.3)。詰められるアキ(約物の幅を参照)であふれた分をまかなえれば、その文字は(句点の後の終わりの引用符のように、一緒にとどまるべき記号とともに)その行に入り、行は行長ちょうどに組まれます。まかなえなければ、行はその文字のために文字を手放します(追い出し)。改行位置は規則が許す直前の位置まで戻り、両端そろえの行は残りの字間を広げます。数字と句点を合わせた幅より狭い行では行末に置けるものがないため、結局は句点の前で改行します。
対象となる段落は、単語間のスペースよりCJK文字(漢字、かな、注音符号、ハングル)を多く含む段落です。CJK文字が2つ続いていなくてもかまいません(第1条、第2条、价¥5,999。好)。CJK文字や約物の隣のスペースは単語間のスペースとは数えません。2026 年 9 月 28 日は2026年9月28日と同じように組まれます。これらの段落は、装飾のない経路でも書式付きの経路でも1行ずつ組まれるため、太字の語を含む段落も、太字を除いた同じテキストとまったく同じ位置で改行します。中国語の書名や人名を引用する欧文の段落は文字よりスペースが多いので、最適改行を保ちます。その段落でも、CJK文字の隣では同じ規則で改行できます。キャプション、表のセル、注、囲みも文書のレベルで改行します。辞書によるハイフネーションは、CJKの段落内の欧文の語には及びません。
両端そろえのCJKの行のうち、段落の最終行でないものは、次の順で行長まで広げます(clreq §6.2.2.4)。欧文の語間のスペースを1つあたり二分まで、次に漢字と欧文の間のアキを1つあたり二分まで、最後にすべての字間とそれらのスペースを均等に広げます。ラテン文字の語、数字、全角2つ分の記号の内側にはアキを入れず、連接記号や斜線の隣にも入れません。アキは行のセグメントごとに設定され(VDTLineSegment.tracking。各文字の後のpx数で、セグメントのwidthに含まれます)、Canvas、HTML、PDFの出力は字間として描画します。後ろにアキのあるラテン文字の語は、最後の文字が独立したセグメントになります。セグメントには欧文のひと続き1つか、同じスタイル・リンク・字間で同じように送られる文字が入ります。そのためSandboxはカーソルを置くときにセグメントの幅を文字に均等に割り振り、リンクは自分の文字だけを覆います。字間を二分より(bodyText.maxJustifyTrackingを設定している場合はその値より)広げる必要のある行は、その上限まで広げて行長に満たないまま組みます。その行にはcjkLooseとraggedのフラグが付き、ビルドは行のテキストを含むcjkLooseLineコンテンツ警告を報告します。前の行に上げられない長いラテン文字の語やウェブアドレスがよくある原因です。CJK文字を含まない行(長いウェブアドレスの先頭部分など)は、欧文の1語だけの行と同じく、警告なしで行末不ぞろいに組みます。ハイフネーションと両端そろえを参照してください。
#約物の幅
中国語の全角の約物は、二分のグリフと二分のアキでできています。設定で調整するのはアキで、グリフは変わりません(clreq §6.3.2)。アキは、始め括弧・始めの引用符ではグリフの前、終わり括弧・終わりの引用符ではグリフの後ろ、そして中国大陸の読点類・句点類 、,。.;:?!ではグリフの後ろにあります(これらはボックスの隅に置かれます)。台湾と香港で中央に置く約物(、,。.;:)と中点は、両側に四分ずつのアキを持ちます。?!は台湾と香港の横組みでは全角のままで、縦組みではどの地域でも:;?!が全角のままです。中国大陸の中点は、どのスタイルでも、どちらの書字方向でも二分幅で中央に置きます(GB/T 15834、clreq §5.1)。全角のグリフは両側のアキを取り除き、縦組みでは最初からマスが二分です。
| スタイル | 行中 | 行末 |
|---|---|---|
fullwidth(全角式) | すべての約物を全角。 | 全角。trimLineStartがあれば終わり括弧は二分。 |
kaiming(开明式) | 。.?!は全角、,、;:、括弧、引用符、中点は二分。中国大陸の本の大半。 | すべての約物を二分。 |
lineEndHalf(行末半角) | すべての約物を全角。 | すべての約物を二分(GB/T 15834—2011 §5.1.10を文字どおりに読んだもの)。 |
halfwidth(半角式) | 辞書のように、すべての約物を二分。 | 二分。 |
日本(punctuationWidth: 'fullwidth'、autoの値)では、約物は代わりにJLReq §3.1に従います。、,。.はグリフの後ろにアキを持ち、:;と・は両側に四分ずつ、?!は全角を埋めます。行末の終わりの約物は二分のアキを保ち、行にもう1文字収める必要があるときは、まず最初にそのアキを丸ごと取り除きます。。の後ろのアキは行中では詰めません。行が詰めるアキの順序はJLReq §3.8.3に従います。単語間のスペース、行末の約物のアキ、中点、括弧と、,、そして和欧間のアキの順です。行を広げるときは、始め括弧の後、終わり括弧の前、、。・:;?!とU+3000の隣にはアキを加えません(§3.1.11)。2つの漢数字に挟まれた、と・はベタ組みにし、そこでは改行しません(二、三日、三・一四)。別のpunctuationWidth(kaiming、halfwidthなど)を指定した日本語の文書は、この節のclreqの規則に従います。
compressAdjacentを設定すると、連続する2つの約物の間のアキを詰めます(clreqの以前の草案にある8つの規則)。対象は、終わり括弧に続く終わり括弧、中国大陸の読点類・句点類に続く終わり括弧(。」。台湾と香港の中央に置く約物の後は対象外)、終わり括弧に続く読点類・句点類(」,)、これらのいずれかまたは別の始め括弧に続く始め括弧(,「、》(、「『)です。また、中点とその前の終わり括弧、またはその後の始め括弧の間は四分詰めます。詰めるのは、2つで1.5em分になるまでです。開明式では。”はもともと1.5em分なのでそのまま、》(は全角1つ分になります。開明式ではcompressAdjacentの有無にかかわらず、句点の後に終わりの約物が続くと、句点のアキをその約物の後ろに置きます。2つのグリフは隣り合い、二分のアキは引用符の後ろに来ます(。␣”母ではなく。”␣母)。行末では句点のアキと同じ扱いになります。trimLineStartを設定すると、行頭の始め括弧は前側のアキを取り除き、インクが本文の端にそろいます(段落の1行目では字下げの中に二分入ります)。行末の終わり括弧は後ろ側のアキを取り除きます。中央に置く約物は、片側で二分ではなく両側で四分ずつ取り除きます。2つの約物の間で改行するときは、どちらも詰めを保たず、それぞれ行の端の約物として組みます(全角の,の後の「が次の行頭に来る場合、読点の行は全角で終わります)。
行頭に置けない文字を行に収める(追い込む)のは、まだ詰められるアキであふれた分をまかなえるときです。詰める順序はclreqに従います。単語間のスペースを四分まで、次に中点、括弧、読点類、漢字と欧文の間のアキを八分まで、最後に句点類で、各段階で均等に詰めます。kaimingが句点類を二分まで詰めるのはこの場合だけです。もう1文字分足りないだけの行は広げて組むので、。?!は行中で全角を保ちます。lineEndHalfはすべての約物を二分まで詰められます。fullwidthの約物は何も詰めず(全角式の本は字詰めの格子を保ちます)、halfwidthの約物にはもう詰めるアキがありません。
欧文と中国語で共通の約物(“ ” ‘ ’ … — ·)は、中国語のテキストの中では、フォント自体の送り幅にかかわらず中国語の約物のボックスを取ります。LXGW WenKaiは“ ”を0.35em幅で描き、Noto Serif SCはemダッシュを0.89em、·を3分の1emで描きます。これらの記号は、両側のどちらかで最も近い文字(同種の記号は飛ばします)が中国語であるか、隣に欧文が何もないときに中国語として扱います。始めの引用符のグリフは全角のボックスの末尾に、終わりの引用符のグリフは先頭に置きます。中点、省略記号、単独のダッシュは中央に置きます。2つ続く省略記号(……)は、フォントが2つを並べて組んだ形で、全角2つ分の中央に置きます。破折号(——)は途切れない1本の線です。各ダッシュを書体自体のサイドベアリングから全角いっぱいに伸ばし、2本の線を継ぎ目で重ね、文字の中央の高さまで上げます(VDTLineSegment.inkScale。描画時だけの拡大率です)。そのうえで、ほかの約物と同じようにスタイルがボックスを調整します。両側が欧文の場合(他说:He said “yes” and left.)はフォントの送り幅を保ち、もともと全角幅のグリフは何も変わりません。
レンダラーは、ブラウザー独自の約物のアキをテキストに持ち込みません。Chromeは、連続する2つの約物を1つのランとして計測・描画すると、最初の約物を半角にします(Noto Serif SCの本)》录はそのように組むと3.5em)。そのため連続する2つの約物は別々に計測・描画し、CJKテキストのHTMLの行にはtext-spacing-trim: space-allとtext-autospace: no-autospaceを付け、chws、halt、vchwの機能をオフにします。
VDTでは、アキを詰めた約物はそれだけで1つのセグメントになり、widthは残った送り幅で、inkOffset(px)が設定されます。レンダラーはそのグリフをx + inkOffsetに描画するので、グリフの前のアキを詰めた約物(行頭の始め括弧)はボックスよりその分だけ前に描かれます(負のオフセット)。中国語のボックスで組んだ共通の約物は、ボックス内のグリフの位置から前側で詰めたアキを差し引いた値を持ち、正の値になることもあります。PDFでは、そのようなグリフに文字間隔を付けて送り幅がボックスの終わりで終わるようにするため、読者には次の文字に重なって見えません。またその行を/ActualTextのスパンに入れます(和欧間のアキを参照)。約物のアキがどちら側にあるかを決めるのはフォントではなく地域です。本はその地域の書体で組んでください(中国大陸にはNoto Serif SC、台湾にはTC、香港にはHK)。中国大陸のタグで繁体字のフォントを使うと、中央に置く約物の誤った側を詰めてしまいます。
#ぶら下げ
日本でのautoの値であるhangingPunctuation: 'allow'は、、,。.のいずれか(約物がボックスの先頭側に置かれる中国大陸では;:?!も)が次の行の行頭に来てしまい、行を詰めても収められないときに、行末より外へぶら下げます。台湾と香港の横組みでは、中央に置いた約物が切れて見えるためぶら下げません(縦組みでは可能です)。縦組みでは、ぶら下げた約物は行の下端の外に置かれます。'force'は、そうした約物が行末に来たときは(段落の最終行を除き)常にぶら下げ、収まらないときはただちにぶら下げます。ほかの約物と接している約物(。」、,「)はぶら下げません。ぶら下げたセグメントにはhangsのフラグが付きます。行のbbox.width、両端そろえ、行のそろえ位置はこのセグメントを除いて計算し、CanvasとPDFは段のクリップ領域を最も広いぶら下げ約物の幅(hangingPunctuationOverhang)だけ広げるので、切れることはありません。中国語の本の多くは約物をぶら下げず、clreqがぶら下げを勧めるのは文字グリッドを使う場合だけです。日本語の本は、どちらの書字方向でも、、。,.をぶら下げ(ぶら下げ組み)、;:?!や括弧はぶら下げません。ぶら下げるのは、行を詰めてもその約物を収められないときだけです。
#和欧間のアキ
latinSpacing(既定値は四分)は、漢字(またはかな)とその隣のラテン文字・アラビア数字の間にアキを入れます。用iPhone拍照と1999年は用 iPhone 拍照と1999 年のように組まれます。行頭と行末、ラテン文字と中国語の約物の間(用iPhone,ではコンマの前に何も入れません)、中国語の括弧の内側((iPhone))、文字でも数字でもない記号の隣(为¥5,999)にはアキを入れません。著者がこうした境界に入力したスペース(多くのウェブのテキストにある用 iPhone 拍照のような書き方)は、和欧間のアキに置き換え、加算はしません。そのため2通りの書き方は同じように組まれます。ノーブレークスペースと和字間隔はそのまま残します。両端そろえの行では、文字の字間を広げる前にこのアキを二分まで広げます。行頭に置けない文字を追い込む行では八分まで詰めます。em以外の単位で指定した長さは、ページのdpiで換算します。0でオフになり、そこに入力したスペースは単語間のスペースのままです。
このアキは、autospaceのフラグが付いたkind: 'space'のセグメントで、textは空(または著者が入力したスペース)です。widthは確定値で、レンダラー独自のスペースによる両端そろえはこれを変えません。プレーンテキストには含まれないため、検索、コピー&ペースト、ソース範囲は書かれたとおりのテキストを読み取ります。PDFでは、分割して組んだ行(字間を広げた文字、和欧間のアキ、アキを詰めた約物、ぶら下げた約物を含む行)をすべて/Spanに入れ、その/ActualTextを行のテキストにします。そのためテキスト抽出では用 iPhone 拍 照ではなく用iPhone拍照と読み取られ、半角の約物を含む行も1行として読み取られます。
#縦組みの数字
縦組みでは、ラテン文字の語や長い数字は横倒しにし、短い数字は正立させて、その桁を全角1マスに横に並べます。これが縦中横です(縱中橫、clreq §2.1.3、CSS text-combine-upright)。uprightDigitsは、そうした数字の最大桁数を2(既定値)、3、4、またはなしを意味する0で設定します。2のとき、2026年9月28日の2026は横倒し、9は正立、28は1マスに正立します。
- 数字全体を縦中横にするか、まったくしないかのどちらかです。
2のとき、3桁の数字は分割されず横倒しのままです。 - ラテン文字と接する数字(
A4、mp3、3D)は語の中にとどまり、横倒しになります。小数点や桁区切りを含む数字(3.14、10,000)も同じです。 - 欧文の文中にある数字はその文に従います。両側にラテン文字の語があれば、語と一緒に横倒しになります(
printed in 49 and 32 copies、chapters 49, 32 and (7) of)。左右それぞれ、スペース、ほかの数字、注の合印、横倒しのランの約物(, . : ; ( ) ' " - /)、enダッシュとemダッシュ、カーリークォート(pages 3–5 of、the “49” copies)を飛ばし、最初のラテン文字か中国語の文字まで見ます。約物の先が中国語のテキストに続く数字は正立します(上午12:30:45开会、比分为3:2:1、见图(3)所示、他住在"12"号楼)。中国語の文字や約物の隣にある数字(スペースの有無を問いません。第 3 回、用iPhone 15拍攝、第3 copies)、正立する記号の隣の数字(a 30×40 print)、片側にしか語のない段落の先頭や末尾の数字(49 copies were printed、on page 7.。横倒しにするには:sideways[…]と書きます)も正立します。段落は改行、強調、リンクをまたいで全体として読み取ります。 - Unicodeが正立とする記号の隣では、数字はそれぞれ独自のマスを取ります。
30×40は30、×、40がすべて正立します。 - 全角1つ分に収まらない文字数のマスは、全角幅に押し縮めます。トラッキングや両端そろえはマスの内側には入らず、後ろにだけ入ります。
- 改行と両端そろえの際、マスは中国語の1文字として数え、その前後に和欧間のアキは入れません。
- 日本では、半角または全角の
!!、!?、?!、??の組も1つの正立したマスに収めます(すごい!?)。ただし、ラテン文字、数字、3つ目の約物と接している場合を除きます。全角の組は半角の約物で描画します。小書きのかな、:;、“ ”(〝〟で描画)、・は日本語の縦組み用字形を使います(日本語の組版 › 縦組み用の字形を参照)。 - 計測器とすべてのレンダラーは同じマスを見つけます。Canvasは数字を正立に戻して押し縮めて描画し、PDFも横組み用のフォントで同じように描画し、HTMLは
text-combine-upright: allで囲みます。
手動で指定する場合は、3つのインラインマークが縦組みでこの設定を上書きします。横組みでは何も変わりません(文書形式 › 縦組みでの文字の向きを参照)。
第:tcy[120]回、:upright[GDP]與:sideways[12]:tcy[…]はテキストを1つの正立したマスに収め、:upright[…]は各文字をそれぞれのマスに正立させ(ラテン文字はマスの中央)、:sideways[…]は中国語の文字も含めてラン全体を横倒しにします。VDTでは、:tcyのランはtcy: trueを持つ全角幅のセグメントになり、:uprightや:sidewaysのランはorientationを持ちます。uprightDigitsで1マスにした数字にはフラグがありません。verticalRuns(graphemes, region, uprightDigits)で見つけられます。段落内でラテン文字の語と一緒に横倒しになる短い数字は、:sideways[…]と書いた場合と同じくorientation: 'sideways'を持ちます。そのセグメントに、一緒に並ぶ語が含まれるとは限りません。
#文字グリッド
中国語の版面は文字数で指定します(clreq §7.1.1)。本文の文字サイズ×1行の字数×1ページの行数に、行間と、2段組みなら段間を加えたものです。gridは版面をこの方法で設定します。
cjk: { grid: { enabled: true, charsPerLine: 28, linesPerPage: 28, show: true } }enabledのとき、設定は何かが読み取る前に書き換えられます。各段の幅はbodyText.fontSizeのcharsPerLine字分、版面の高さはbodyText.lineHeightのlinesPerPage行分になります。layoutType: 'double'では、段間はlayout.gutterWidthを全角の整数倍(最低1字)に丸めた値です。page.marginsの余白は最小値として扱われます。版面はそれらの余白が残す領域の中央に置かれ、各余白は、その軸方向に残った余裕の半分ずつ広がります(見開きを対称にした本では、のどと小口の余白は別々の値のまま、同じ量だけ広がります)。charsPerLineとlinesPerPageを設定しない場合は、収まるだけの数になります。収まる数より大きい値は収まる数まで減らし、cjkGridClamped設定警告として報告します。フォントサイズと行の高さは書かれたとおりのままです。oneAndHalfレイアウトでは、charsPerLineは主段の字数です。サイド段は、設定した余白の内側でsideColumnPercentが与える幅に最も近い全角の整数倍(最低1字)を取り、段間は2段組みと同じように丸められ、両方の段が全角の整数倍で区切られるようにsideColumnPercentが書き換えられます。縦組み(layout.writingMode: 'vertical-rl')では、行の文字はページを上から下へ、行はページを横切って並ぶので、charsPerLineはページの高さ方向で測ります。doubleレイアウトは上下に重なった2段になり、オーバーレイのマスは文字の中心が並ぶ軸の上に置かれます。
中国の慣行から2つのレイアウトを挙げます。どちらも五号(10.5 pt)、行間6 pt(lineHeight: 16.5pt)です。
- 大32开、140×203 mm、28字×28行:行長294 pt(103.7 mm)、版面の高さ462 pt(163 mm)。
page.marginsをのど・小口16/20 mm、天・地18/20 mmにすれば収まります。 - 16开、184×260 mm、小五(9 pt、行送り13.5 pt)で23字の2段組み、段間2字:2×207 pt+18 pt。
showは版面にグリッド(稿纸、原稿用紙)を重ねて描きます。各行の文字位置ごとに薄い灰色の正方形を1つ描き(ベースラインを基準にした文字の全角ボックス)、ページの各段に描きます(oneAndHalfのサイド段は、ページの奇数・偶数で決まる側に描きます)。CanvasとHTMLに描画されます。PDFに描くのはrenderToPdfにcharacterGrid: trueを渡したときだけです。画面上の補助として使うものです。cjkGridGeometry(config)は設定が決めるグリッド(使われる数値、余白、版面)を返し、applyCjkGrid(config)は書き換えた設定を返します。
#圏点と傍線、ルビ、割注
中国語の記号、ルビ、割注のマークアップは、テキストを段落の中に残します。検索、目次、索引のアンカー、コピーしたテキストは書かれたとおりの文字を読み取り、これらの設定で変わるのは文字の周りに描くものだけです。
- 圏点(着重号、傍点。
:dots[…]と、emphasis: 'dots'のときのCJK文字に付けた*…*)は、文字1つにつき1つ、文字の中央に付きます(両端そろえで広げたアキは含みません)。約物やスペースには付きません。横組みでは文字の下、縦組みでは右に付きます(clreq §5.3.1)。styleで黒点、白丸、ゴマ点を選び、fill="open"で白抜きにし、pos="over|under"で側を決めます。圏点自身が指定しない項目はemphasisMarkが決めます(日本では、ゴマ点を文字の上、縦組みでは右に付けます)。ゴマ点はどちらの書字方向でも紙面上で同じ向きに立ち、ルビと同じ側の圏点はルビの外側に付きます。 - 傍線(
:sideline[…]{style pos})は約物やスペースも含めてテキストの脇に引きます。solid、double、wavy、dottedがあり、既定では横組みで文字の下、縦組みで文字の右に引きます。2本の線が接するときは、それぞれ八分ずつ縮めます。どの文字体系でも使えます。 - 専名号と書名号(专名号
:name[…]、bookTitleMark: 'wavy'のときの书名号:book[…])は、文字の全角ボックスの下(縦組みでは左)に、ラン内のスペースもまたいで引きます。2つのランが接するところでは、それぞれの端を八分ずつ縮めるので、:name[賈寶玉]:name[林黛玉]は2つの名前として読めます。同じテキストの同じ側に圏点と線がある場合は、線のほうがテキストに近くなります。'brackets'のときの《》は、行を分割するときにも扱われるテキストで、ほかの文字と同じように描画・コピーされます。ただしプレーンテキストやソースマップの文字は占めません(セグメントにはinsertedのフラグが付きます)。 - ルビは、親文字の全角ボックスに接して行間に置かれ、親文字に対して中央にそろいます。ラテン文字を含むルビ(ピンイン)が親文字の上に立つ場合は、書体のディセンダー(g、j、p、q、y)の深さに本文の0.04emを加えた分だけ持ち上げて親文字に重ならないようにし、行のすべてのルビは1本のベースラインを共有します。親文字より広いルビは、ルビのない隣の文字に掛けてよいルビ全角の4分の1を差し引いた分だけ親文字のボックスを広げます。2つのルビの間はルビ全角の4分の1空けます(注音符号2つの間は記号の全角の4分の1で、2つの文字の脇にそれぞれ3つの記号を並べてもマスに収まります)。モノルビ(1文字に1つのルビ)は文字の間で改行でき、グループルビは改行しません。日本では、ルビは代わりに
ruby.alignとruby.overhangに従います。親文字より短いときは1:2:1で配置し、長いときはかなには掛かっても漢字には掛からず、残りは親文字の字間を広げてまかないます。2文字以上のモノルビは熟語ルビで、各文字はそれぞれのルビを保ちます。ルビは語の次の文字に掛かってもよいものの、別のルビとは重ならず、行はその間で改行できます(JLReq §3.3)。行頭・行末では、親文字とルビを行の端にそろえます(clreq §5.5.4)。横組みの注音符号は各文字の右に縦1列で並び、文字のボックスはその列の分だけ広がります。縦組みでは同じ列が文字の右側を上から下へ並びます。声調記号は列の右に、インクの半分が最後の記号の上端より上に出るように置きます(clreq §5.5.3.3)。フォントはこうした記号を全角ボックスの高い位置に置くので、位置はインクを基準に決めます。縦組みでは、最初の記号の上に付く軽声の点と同じく、正立します。 - 割注(双行夹注)は注のサイズで2行に折り返し、行の中央に行間なしで置きます。上の行は、少なくともその部分の半分を収めるまで文字を取るので、下の行のほうが長くなることはありません。下の行が行頭に置けない約物で始まってしまう間は、さらに1文字取ります。行の残りの領域より長い注は、その行を埋めて次の行・段・ページに続きます。括弧は最初の行の前と最後の行の後にだけ付きます。縦組みでは、右の行が上の行で、先に読みます。
- 訓点(
:kunten[字]{kaeri okuri tate})は、最後の文字の返り点をその次の文字の行の下端側(縦組みでは左下)に、送り仮名を文字の中ほどから行の上端側に置きます。長い送り仮名は次の文字を押し下げます。竪点は次の文字までの短い線として引きます(JIS X 4051 §5)。コピーしたテキストとPDFの/ActualTextでは、送り仮名は文字の後に続けて読み取られ、返り点は除かれます。訓点を置くには狭すぎる行間はkuntenExceedsLeadingとして報告します。
行送りは変わりません。圏点やルビは行間に収まります。行間(行の高さから文字サイズを引いたもの)が、片側にだけ記号が付く場合は二分、両側に付く場合は8分の5em(clreq §5.6.1)を下回る段落は、cjkMarksExceedLeadingコンテンツ警告として報告します。ルビが行間より高い段落(ピンインのルビは持ち上げた分を含めて数えます)はrubyExceedsLeadingとして報告します。そうした段落には、行送りを広げた段落スタイルを指定してください。振り仮名を付ける日本語の本は、本文の行の高さを1.75em程度にするのが一般的です(JLReq §2.4.2)。
VDTでは、記号付きのセグメントはcjkMarksを持ち、レイアウトは点や線をVDTLine.marks(点、丸、ゴマ点、直線、二重線、点線、波線。行の流れの座標系で表します)としてその行に置きます。Canvas、HTML、PDFはこれをそのまま描画します(PDFではArtifact /Layout、HTMLではaria-hiddenのボックスと、圏点付きテキストの<em>)。ルビの親文字のセグメントはruby(ルビとそのラン。日本語のルビでは、熟語のほかの文字と共有する注釈のidとjukugoも)を持ち、ルビによって字間が広がった場合はtracking付きで親文字をinkOffsetの位置に描画します。訓点の付いた文字はkunten(そのランと竪点)を持ちます。行上にある割注の部分は1つのセグメントで、そのtextは上の行、次に下の行で、warichuにはレンダラーが代わりに描画する2行が入ります。タグ付きPDFは、ルビを親文字のRubyのRTに、注をWarichuに入れ、行の/ActualTextは親文字と注を1回ずつ読み取ります。圏点、ルビ、注は、本文、見出し、リスト、囲み、そしてリソースのキャプション、表のセル、注に描画され、それらの行は本文の行と同じくmarksとrubyを持ちます。デザインのテキストは、これらを付けずにテキストだけを保ちます。
設定の解決と既定値の除去は、ほかの節と同じ仕組みです。spaceAfterQuestion、paragraphStartBracket、wordBreakと、ルビのoverhang、align、smallKanaは、何かを変える場合(日本の場合、または設定した場合)にだけ解決済みの設定に含まれます。setCjkLineBreakとsetCjkCompositionは、buildDocumentの外で行う計測のために、レベルと組み方(約物の幅、ぶら下げ、和欧間のアキ。cjkCompositionOf(resolved.cjk, dpi))を設定します。ビルドの中では、設定からこれらが自動で設定されます。ビルドの外では、約物は送り幅をすべて保ち、和欧間のアキは入りません。
import { DEFAULT_CJK_CONFIG, resolveCjkConfig, stripCjkDefaults, cjkRegionOf, setCjkLineBreak, setCjkComposition, cjkCompositionOf } from 'postext';
resolveCjkConfig(undefined, 'zh-HK');
// { region: 'hongkong', lineBreak: 'basic', punctuationWidth: 'fullwidth', compressAdjacent: true,
// trimLineStart: true, hangingPunctuation: 'none', latinSpacing: { value: 0.25, unit: 'em' }, uprightDigits: 2,
// grid: { enabled: false, charsPerLine: 0, linesPerPage: 0, show: false },
// emphasis: 'dots', emphasisMark: { style: 'dot', fill: 'auto', position: 'auto' },
// bookTitleMark: 'wavy', bookTitleBrackets: [{ open: '《', close: '》' }, { open: '〈', close: '〉' }],
// ruby: { fontSize: { value: 0.5, unit: 'em' }, position: 'auto' },
// warichu: { fontSize: { value: 0.5, unit: 'em' }, open: '', close: '' }, kunten: { … } }
resolveCjkConfig(undefined, 'ja');
// { region: 'japan', lineBreak: 'ja-very-strict', punctuationWidth: 'fullwidth', compressAdjacent: true,
// trimLineStart: true, hangingPunctuation: 'allow', spaceAfterQuestion: true, paragraphStartBracket: 'half', …,
// emphasisMark: { style: 'sesame', fill: 'auto', position: 'over' }, bookTitleMark: 'brackets',
// bookTitleBrackets: [{ open: '『', close: '』' }, { open: '「', close: '」' }],
// ruby: { fontSize: { value: 0.5, unit: 'em' }, position: 'auto', overhang: 'kana', align: 'jis' },
// warichu: { fontSize: { value: 0.5, unit: 'em' }, open: '(', close: ')' }, kunten: { … } }Sandboxでは、これらの設定はデザイン › 表記体系 › 東アジアの組版にあります。
#リソースの種類
リソースの種類は、ユーザーが定義できる分類です。たとえば「図」「表」「ダイアグラム」「コードリスト」などで、その種類のリソースにどう番号を付け、キャプションを付け、参照するかを決めます。リストはconfig.resourceTypesにあり、Sandboxではデザイン → 図と表 → 番号付けと配置で編集します。
config.resourceTypesが未設定の場合、Postextは3つの組み込みの既定の種類、図(Figure)、表(Table)、動画(Video)を用意します。それぞれ独自の{h1}.{n}で番号を付け(レベル1の見出しのたびにリセット)、カウンターは10進数です。videoの種類を含まないリスト(動画が導入される前に保存された本)でも、種類がvideoの動画リソースには番号が付きます。そのために組み込みの動画の種類が追加されます(effectiveResourceTypes(config, resources))。defaultVideoResourceType(locale)はこの種類だけを返します。名前は文書の言語で付きます。config.locale、なければbodyText.hyphenation.locale、それもなければ英語です(文書の言語を参照)。
組み込みの既定値はロケールに対応しています。エクスポートされたdefaultResourceTypes(locale = 'en')は、種類の名前、略称、キャプションの接頭辞を文書のロケールに合わせてローカライズします。英語では「Figure/Fig.」と「Table/Tab.」、スペイン語では「Figura/Fig.」と「Tabla/Tabla」になり、フランス語、ドイツ語、イタリア語、ポルトガル語、カタルーニャ語、オランダ語にもそれぞれの名前があります(一覧は文書の言語の表にあります)。es-ESのような地域付きのタグは言語で解決され、翻訳のないロケールは英語にフォールバックします。番号付けの動作(numberingTemplate: '{h1}.{n}'、resetOn: 'h1'、10進数のカウンター)は言語に依存しません。呼び出すたびに新しいオブジェクトを返すので、結果は自由に変更できます。
import { defaultResourceTypes } from 'postext';
const types = defaultResourceTypes('es');
// => [{ id: 'figure', name: 'Figura', shortLabel: 'Fig.', captionPrefix: 'Figura',
// numberingTemplate: '{h1}.{n}', resetOn: 'h1', counterFormat: 'decimal', … },
// { id: 'table', name: 'Tabla', shortLabel: 'Tabla', captionPrefix: 'Tabla', … },
// { id: 'video', name: 'Vídeo', shortLabel: 'Vídeo', captionPrefix: 'Vídeo', … }]type ResourceCounterFormat =
| 'decimal'
| 'roman-lower'
| 'roman-upper'
| 'alpha-lower'
| 'alpha-upper';
type ResourceCounterReset = 'never' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
interface ResourcePlacement {
position?: 'auto' | 'top' | 'bottom' | 'here'; // which free slot a float may take; 'here' = inline embed at the ::resource directive
span?: 'column' | 'page' | 'side'; // one column, the full content width, or the float-only side column
rotate?: 'ccw' | 'cw'; // a quarter turn: a landscape table on a page of its own
width?: number; // fraction (0 < width < 1) of the column or page width; default: the whole width
align?: 'left' | 'center' | 'right'; // where a float narrower than its column sits; default 'left'
captionSide?: boolean; // caption beside the figure, in the side column of a oneAndHalf layout (column floats only)
}
interface ResourceType {
id: string; // stable id, referenced by Resource.typeId
name: string; // singular display name, e.g. "Figure"
namePlural?: string; // optional plural, e.g. "Figures"
shortLabel: string; // compact label for inline refs, e.g. "Fig."
numberingTemplate: string; // "{h1}.{n}" or "{n}"
resetOn: ResourceCounterReset; // when the {n} counter resets
counterFormat: ResourceCounterFormat;// how {n} is formatted
captionPrefix: string; // prepended to the caption, e.g. "Figure"
defaultPlacement?: ResourcePlacement;// fallback placement for this type's resources
}ResourcePlacementは、リソースが自身のplacementに設定するものと同じ形です。positionはフロートが入れる空き位置の種類を選びます。auto(既定値)は最初の参照の後にある最初の空き位置を取り、top/bottomはその種類の帯に限定し、hereはリソースをインラインで埋め込みます。spanはフロートの範囲を、1段、本文領域の全幅、または1段半レイアウトのフロート専用サイド段から決めます。rotateはリソースを90度回転させ、独立したページに置くページ幅のフロートにします。widthはフロートを段の幅(ページ幅のフロートではページの幅)の一部に狭めます。たとえば広い段に置く小さな表です。alignは、そのように狭めたフロートを置く位置(既定は左、ほかに中央と右)と、空き位置より狭い画像をその中のどこに置くかを決めます。段より小さいビットマップや、layout.fitFiguresToPageが縮小した画像が対象です。キャプションと注は空き位置の行長を保ちます(postext 1.4までは、そうした画像は常に左そろえでした)。captionSideは、1段半レイアウト(layout.sideColumnRole: 'floats')のフロート専用サイド段で、キャプションを図の横、図の上端(下側のフロートでは下端)の高さに置きます。段幅のフロートにだけ適用され、そうした段のないページではキャプションは図の下のままです。リソースもその種類も配置を設定しない場合、組み込みの既定値はauto/columnです。
| プロパティ | 型 | 説明 |
|---|---|---|
id | string | 各リソースのtypeIdが参照する不変の識別子です。種類を作るときに1度だけ設定します。リソースがまだ参照している種類を削除すると、存在しない種類の警告が出ます。 |
name | string | 単数形の表示名です。style="full"のインライン参照で使われます(例:Figure 1.7)。 |
namePlural | string(省略可) | 複数形の表示名で、UIのラベルやリソースの一覧に使います。 |
shortLabel | string | 既定のインライン参照スタイルで使う短い略称です(例:Fig. 1.7)。 |
numberingTemplate | string | 計算される番号のテンプレートです。下のテンプレートのトークンを参照してください。よく使う形は(章単位。例:2.3)と(1つの通し番号)です。 |
resetOn | ResourceCounterReset | 'never'は文書全体の通し番号になります。'h1'〜'h6'は、そのレベル(またはその上位のいずれか)の見出しが現れるたびにカウンターをリセットします。テンプレートに現れる見出しレベルに合わせて設定してください。たとえばにはresetOn: 'h1'を組み合わせます。 |
counterFormat | ResourceCounterFormat | カウンターの表示形式です。10進数(1, 2, 3)、小文字・大文字のローマ数字(i, ii/I, II)、小文字・大文字のアルファベット(a, b/A, B)から選びます。ノンブルやリストの表記も使えます('lower-roman'、'arabic'など。番号書式の表記を参照)。不明な値は10進数で数え、報告されます。見出しのトークン(など)は常に10進数で表示されます。 |
captionPrefix | string | 図や表のキャプションの前に付けるテキストです。計算された番号は接頭辞の後に続き、キャプションは . のように表示されます(例:Figure 1.7. The original plan.)。numberingTemplateが空の種類には番号がなく、キャプションは. になります。接頭辞の末尾のスペースは取り除かれ、すでに.、:、!、?、…(またはそれらの全角形)で終わる接頭辞には、2つ目のピリオドを付けません(Pl. Lines at 0°)。 |
defaultPlacement | ResourcePlacement(省略可) | 自身のplacementを設定しない、この種類のリソースが使う配置です。position、span、rotate、width、align、captionSideはそれぞれ個別に解決されます。リソースも種類もあるフィールドを設定しない場合は、組み込みの既定値が適用されます。auto/column、回転なし、全幅、左そろえ、キャプションは図の下です。解決の順序は下の番号付けと参照を、回転したリソースを含む各値の動作は文書形式 › リソースを参照してください。 |
captionStyle | CaptionStyleConfig(省略可) | この種類のリソースに対する、キャプションのスタイルの部分的な上書きです。設定したキーだけがグローバルのcaptionStyleを置き換え、それ以外はすべて継承されます(colorを上書きすると、ラベルと注の色も明示的に設定されていない限りその色になります)。典型的な使い方は、表のキャプションを色付きの帯にして上に置き、図のキャプションは下のままにすることです。パレットの参照は、ほかの色と同じように解決されます。 |
#テンプレートのトークン
numberingTemplateは、見出しの番号付けと同じエンジンで表示されます(見出しを参照)。認識するトークンは2種類です。
{n}:種類ごとのカウンターで、counterFormatに従って書式化されます。リソースごとに増え、resetOnに従ってリセットされる値です。{h1}〜{h6}:最初の参照の位置で有効な見出し番号で、常に10進数で表示されます。{h1}は現在のレベル1の見出しの番号、{h2}はレベル2の番号、以下同様です。
そのほかのテキストは文字どおりに扱われます。バックスラッシュで、文字としての{、}、\をエスケープします。見出しのトークンにその範囲で値がない場合(たとえば最初のレベル1の見出しより前の{h1})は、隣接する区切り文字とともに消えます。そのため{h1}.{n}は、カウンターだけの形に自然に縮退します。
空のテンプレート('')は番号を表示しませんが、その種類はリソースを数え続けます。キャプションは「Do. Lines at 0°」となり、:refはラベルだけ(Do)を表示します。postext 1.4までは、そうしたキャプションは「Do .Lines at 0°」となり、参照の末尾にはノーブレークスペースが付いていました。
| テンプレート | h1 = 2、カウンターが3のとき | 備考 |
|---|---|---|
| 3 | 1つの通し番号。resetOn: 'never'と組み合わせます。 |
| 2.3 | 章単位。resetOn: 'h1'と組み合わせます。 |
| 2.0.3 | 節単位。resetOn: 'h2'と組み合わせます。 |
#番号付けと参照
リソースの種類が生成する番号は、:refが表示し、キャプションの接頭辞の後に続くものです。基本の形は:ref{id}です。読む順で最初の参照がリソースを取り込み、リソースはその参照の後にある最初の空き位置へフロートとして配置されます。参照している段の下端、次の空いた段の上端または下端、あるいは次のページの帯です(解決された配置に従います。position: 'auto' | 'top' | 'bottom' | 'here'とspan: 'column' | 'page' | 'side'、さらにrotate、width、align、captionSideは、リソースごと、次に種類のdefaultPlacement、最後に組み込みのauto/columnの順に解決されます。'top'/'bottom'はその種類の空き位置に探索を限定します)。ブロックとして埋め込む::resource{id}は省略可能で、placement.position: 'here'のとき、つまり流れの中の正確な位置にフロートさせずにインラインで埋め込むときにだけ必要です。文書側の完全な文法(両方の形と、:refのstyleおよびtextオプション)は、最初の参照の順序が番号にどう影響するかも含めて文書形式 › リソースで説明しています。
これは見出しの番号付けと対応しています。見出しのレベルがnumberingTemplateを持つのと同じように、リソースの種類もテンプレートを持ちます。ただしリソースのカウンター({n})は見出しごとではなく最初の参照ごとに進み、resetOnによって見出しの階層と結び付きます。
#番号が付くもの
リソースは、本文が:refまたは::resourceの埋め込みで参照したときに、最初の参照の順で番号が付きます。配置は問いません。フロート、インライン(here)、サイド段、回転のいずれでも同じです。デザインだけが描くリソース(章扉、柱、部扉のimage要素)や、何からも参照されないリソースには番号が付かず、その種類のカウンターも進みません。そのため、裁ち落としの図版を章扉の画像にし、小さな図版1点だけを本文で引用したフロートにした写真集では、章扉がそれまでに何点の図版を見せていても、そのフロートが図版Iになります。章扉の図版にはデザインの中で番号を付け({attr.plate}のような属性を使います)、カウンターは本文が引用する図版のために残しておいてください。
章ごとにレイアウトする本(Sandbox、buildBundle、またはcontinuationAfter()が引き継ぐカウンターを使うbuildDocument)では、本全体で最初の参照が有効です。リソースは最初に参照した章で得た番号を保ち、その章だけがリソースを配置します。後の章の:refはその番号を表示しますが何も配置せず、そこでのフロートするリソースの::resource埋め込みもただの参照になります(インラインのhereの埋め込みは、書かれた位置に組まれます)。そうした参照は、図が同じ出力にあればその図へリンクします。本全体のPDFでは、前の章のページへリンクします。章を単独でHTMLやPDFに描画した場合は、図がその文書にないため、リンクの色のプレーンテキストとして組みます。各章のHTMLを1つのページにつなげるホストは、各章が配置するリソースをrefTargetsとしてrenderToHtmlに渡します。そうすると、そうした参照は再び前の章の図へリンクします。
import { anchoredResourceIds, buildBundle, renderToHtml } from 'postext';
const docs = buildBundle(bundle);
const refTargets = new Set(docs.flatMap((d) => [...anchoredResourceIds(d)]));
const html = docs.map((d) => renderToHtml(d, { refTargets })).join('');postext 1.5での変更:1.4までは、図を参照するすべての章がその図を再びフロートとして配置し、すべての:refのHTMLは、図がそのページにあるかどうかにかかわらずリンクでした。
{h1}はレベル1の見出しの通し番号です。見出しスタイルがnumbered: falseを設定しない限り、すべてのH1がこの番号を進めます。空のnumberingTemplateは見出しの番号を隠すだけで、数えるのは止めません。そのため、H1がタイトルだけの記事では、組み込みの{h1}.{n}の種類で図の番号が1.1、1.2…になります。図1、2…と表示する方法は2つあります。
{n}で番号を付け、resetOn: 'never'にした種類を使う(resetOn: 'h1'ではH1のたびに番号が最初からになります)。- タイトルと、数えたくないほかのH1に、
numbered: falseの見出しスタイルを指定する。そうした見出しは{h1}を進めず、そのままにします。最初の数える対象のH1より前では空のままで、{h1}.{n}はカウンターだけになります。またresetOn: 'h1'のリセットも起こさないので、番号はその見出しをまたいで続きます。# Introductionとその図1.1の後、番号なしの# Appendixの下にある最初の図は2.1ではなく1.2になります。
// Figure 1, 2, 3… in a single-article document
resourceTypes: defaultResourceTypes('en').map((t) => ({ ...t, numberingTemplate: '{n}', resetOn: 'never' })),#表スタイル
tableStyleプロパティは、表リソースの文字組みと装飾を設定します。名前付きの表スタイルを選んだ表を除き、すべての表に適用されます。本体のセルと見出しのセルは別々に設定します。フォントファミリー、サイズ、色は、未設定のときは解決済みの本文の値を継承するため、tableStyleのない文書では表が本文の文字組みで描画されます。
const config: PostextConfig = {
tableStyle: {
headerBold: true,
headerBackground: { hex: '#f0f0f0', model: 'hex' },
borders: true,
borderWidth: { value: 0.75, unit: 'pt' },
},
};| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
bodyFontFamily | string | 本文のフォント | 本体のセルのフォントファミリー。 |
bodyFontSize | Dimension | 本文のサイズ | 本体のセルのフォントサイズ。 |
bodyColor | ColorValue | 本文の色 | 本体のセルの文字色。 |
headerFontFamily | string | 本文のフォント | 見出しのセルのフォントファミリー。 |
headerFontSize | Dimension | 本文のサイズ | 見出しのセルのフォントサイズ。 |
headerColor | ColorValue | 本文の色 | 見出しのセルの文字色。 |
headerBold | boolean | true | 見出しのセルを太字で描画します。 |
headerItalic | boolean | false | 見出しのセルをイタリックで描画します。 |
headerLetterSpacing | Dimension | 0pt | 見出しのセルの各文字の後に加えるトラッキングで、スペースも対象になります。CSSのletter-spacingと同じです。正の値は文字の間隔を広げ(大文字の見出しには通常0.05emから0.1emが適します)、負の値は詰めます。emは見出しのサイズです。見出しの行はこの値を含めて計測されるため、折り返し、中央ぞろえ、そろえにトラッキングが反映され、キャンバス、HTML、PDFで同じように描かれます。見出し行と、isHeaderを指定したセルを含め、すべての見出しのセルに適用されます。 |
headerTextTransform | 'none' | 'uppercase' | 'none' | 見出しのセルを大文字で組みます。テキストの長さは変わらないため、Sandboxは引き続き各文字をソースに対応付けられます。大文字にすると長くなる文字(ß)はそのまま残ります。リソースへの参照はラベルを保ちます。 |
headerBackgroundEnabled | boolean | true | 見出し行の背後を塗ります。 |
headerBackground | ColorValue | #f0f0f0 | 見出し行の塗りの色。 |
bodyBackgroundEnabled | boolean | false | 本体の行の背後を塗ります。 |
bodyBackground | ColorValue | #ffffff | 本体の行の塗りの色(有効なときだけ塗ります)。 |
bodyAlternateBackgroundEnabled | boolean | false | 縞模様の行。本体の行を1行おきにbodyAlternateBackgroundで塗ります。縞模様の行を参照してください。 |
bodyAlternateBackground | ColorValue | #f2f2f2 | 交互の本体の行の塗り(有効なときだけ塗ります)。 |
borders | boolean | true | セルの罫線を引きます。 |
borderColor | ColorValue | 本文の色 | 罫線の色。 |
borderWidth | Dimension | 0.75pt | 罫線の太さ(96 DPIで約1px。ページのDPIに応じて変わります)。 |
cellPadding | Dimension | 0.375em | すべてのセルの内側余白。 |
rules | 'grid' | 'horizontal' | 'outer' | 'none' | 'grid' | bordersがオンのときに引く罫線です。セルの格子全体、水平の罫線だけ(各行の上端と下端。縦の罫線はなし)、外枠だけ、なしのいずれかです。 |
borderRadius | Dimension | 0 | 表の外枠の角の半径です。外枠は角を丸めて引かれ(gridまたはouterの罫線の場合)、セルの塗りと見出しの背景はこの形で切り抜かれます。rules: 'none'のときや罫線がオフのときも同じです。水平の罫線は外側の輪郭に合わせて切り詰められ、内側の罫線はまっすぐなままです。ページをまたいで分割された表は、最初の部分の上の角と最後の部分の下の角を丸めます。値は表の幅と高さの半分までに制限されます。 |
overflow | 'split' | 'clip' | 'hide' | 'split' | ページより高い表の扱いです。次のページ以降に続ける、収まる行だけを残す、表を省く、のいずれかです。後述します。 |
continuedSuffix | string | '(cont.)' | 分割された表の続きの各部分で、キャプションの後にスペースを挟み、イタリックで付け加えます。中国語の文字または全角文字で始まる接尾辞('(续)')は、キャプションに詰めて組みます。 |
continuesMarkerEnabled | boolean | true | 次のページに続く各部分の下にマーカーを置きます。 |
continuesMarker | string | 'Continued' / 'Continúa' | そのマーカーの文字列で、部分の下に注記の書体で右そろえに組みます(キャプションスタイルを参照)。既定値は文書の言語に従います(8つの言語については文書の言語を参照)。 |
罫線の太さは端数のまま保たれます。0.5ptの罫線は1ピクセルに切り上げられず、PDFでも画面でもヘアラインとして引かれます(下限は0.25pxです)。
#縞模様の行
長いデータ表は、1行おきに色を付けると行を横にたどりやすくなります。bodyAlternateBackgroundEnabledで縞をオンにし、bodyAlternateBackgroundでその色を設定します。
const config: PostextConfig = {
tableStyle: {
bodyBackgroundEnabled: true,
bodyBackground: { hex: '#ffffff', model: 'hex' },
bodyAlternateBackgroundEnabled: true,
bodyAlternateBackground: { hex: '#eef3fa', model: 'hex' },
},
};行は見出し行の次の行から数えます(見出し行はTableModel.headerRowCount、または見出しのセルだけからなる先頭の行)。その行はbodyBackgroundのまま(bodyBackgroundEnabledがオフなら塗りなし)で、次の行が交互の塗りになり、以下同様に続きます。数え方はページではなく表モデルに従います。そのため、ページをまたいで分割された表でも各行の縞はどのページでも変わらず、複数の行にわたって結合したセルは最初の行の縞になります。見出しのセルは見出しの塗りを保ち、セル自身のbackgroundはその両方に優先し、パレットに連動した色はパレットに従います。名前付きの表スタイルはこの2つのフィールドをほかのフィールドと同じように設定するため、1つのスタイルだけを縞模様にし、文書のほかの表はそのままにできます。Sandboxでは、本体のセルにある縞模様の行スイッチとその色がこれに当たります。
VDTでは、交互の行のセルがalternate: trueを持ち、表のレイアウトがbodyAlternateBackgroundを持ちます。tableCellFill(table, cell)は、セルを塗る色(セル自身の色、見出しの色、交互の色、本体の色のいずれか)を返し、キャンバス、HTML、PDFのバックエンドはどれもこの色で塗ります。隣り合う塗りは継ぎ目なく接します。端数のピクセル比で表示するブラウザーやPDFビューアーは塗りを1つずつアンチエイリアス処理するため、そのままでは2つのセルの間にヘアライン状にページが透けて見えます。そこでHTMLとPDFのバックエンドはtableCellFillRects(table)を塗ります。これは各セルの塗りに、後から塗るセルと共有するすべての辺をまたぐ細い帯を加えたもので、その帯は後のセルが覆います。キャンバスは塗りをデバイスピクセルに合わせます。
#名前付きの表スタイル
文書の表がすべて同じ体裁であることはまれです。角を丸めた外枠付きの紺の格子のチェックリスト、外枠だけで囲んだ選択肢の行、水平の罫線だけのデータ表、といった具合です。tableStylesで名前付きの変種を宣言し、表リソースはtable.styleIdでその1つを選びます。スタイルが設定していないフィールドは、まずtableStyleから、次に本文から読み取られるため、スタイルにはその表を区別する点だけを書けば済みます。styleIdのない表や、どのスタイルも宣言していないidを持つ表はtableStyleのままです。tableStylesのない文書は、これまでとまったく同じに描画されます。
const config: PostextConfig = {
tableStyle: {
borderColor: { hex: '#163a76', model: 'hex' },
borderWidth: { value: 1.3, unit: 'pt' },
borderRadius: { value: 10, unit: 'pt' },
},
tableStyles: [
{
id: 'option',
name: 'Option row',
rules: 'outer',
borderColor: { hex: '#7a9cc6', model: 'hex' },
borderWidth: { value: 1, unit: 'pt' },
borderRadius: { value: 8, unit: 'pt' },
headerBackgroundEnabled: false,
},
],
};
// In the resources: this table is set in the "option" style.
const resource: Resource = {
id: 'choices', typeId: 'table', kind: 'table', createdAt: 0, updatedAt: 0,
table: { model: { rows: [/* … */] }, styleId: 'option' },
};各エントリーはtableStyleのすべてのフィールドに加えて、id(table.styleIdが参照するもの)と、エディター向けの省略可能なname(既定値はid)を取ります。スタイルで設定できるものはすべて表ごとに適用されます。文字組み、塗り、罫線、罫線の引き方、角の半径、内側余白、そして続きの文字列を含む、ページに収まらないときの扱いです。resolveTableStylesConfig(styles, tableStyle, resolvedBodyText, locale?)は解決済みのリストを、pickTableStyle(resolved, styleId)は表を組むスタイルを返し、stripTableStylesDefaultsは未設定のフィールドを取り除きます(組み込みの既定値と等しいフィールドは残します。そのフィールドは、異なるtableStyleの値を引き続き上書きするからです)。
#ページより高い表
フロートの表が、渡された新しいページに収まらない場合でも、縮められたりはみ出したりはしません。overflow: 'split'(既定値)では、エンジンがページに収まる最後の行境界で表を分割し、必要なだけ次のページに続けます。続きの各部分は表の見出し行(TableModel.headerRowCount、未設定なら見出しのセルだけからなる先頭の行)を繰り返し、説明の後にcontinuedSuffixを付けたキャプションを再び掲げます(「Table 6-4. Title (cont.)」)。次に続く各部分の下には、注記の書体でcontinuesMarkerを右そろえに置きます。表の注記は最後の部分まで持ち越されます。分割は結合したセルを横切りません(行方向に結合したセルは丸ごと次の部分に移ります)。また、下に続く行の見出しとなる行、つまり表の全幅にわたる1つのセルからなる行は、ページの末尾に取り残されず、次の部分へ送られます。
最初の部分の終わる位置。参照の後で空の段の頭を渡された表は、そこに収まる行を取り、次の枠に続きます。段を単独で占めるときは、段の下端まで埋めます。部分の下に本文の行が3行未満しか残らない場合は、テキストの切れ端を残さず、その行も取ります。段にすでに別のフロートの帯がある場合(たとえばページの頭を横切る全幅の図)は、部分は段の下端から少なくとも本文3行分手前で止まります。これは、フロートがほかのフロートと段を共有するときに残すテキストの余地で、段がフロートだけで終わらず、表の下にいくらかテキストが来るようにするためです。長い表を段の下端まで続けたい場合は、表が始まるページにほかのフロートがない位置で参照するか(たとえば全幅の図のあるページの次)、行を段に収まるようにしてください。
'clip'はページに収まる先頭の行を残し、残りを知らせずに捨てます(注記はその部分の最後に置かれます)。'hide'は表全体を省きます。どちらも表がページより高いときだけ働き、収まる表はどのモードでも丸ごと置かれます。インライン(placement.position: 'here')の表は分割されません。
続きの文字列の既定値は文書の言語(locale、なければハイフネーションのロケール)によって決まります。英語は(cont.) / Continued、スペイン語は(cont.) / Continúaで、フランス語、ドイツ語、イタリア語、ポルトガル語、カタルーニャ語、オランダ語にも同様の既定値があります(文書の言語に一覧があります)。
セルの内容はインラインのMarkdownで、セル内の改行(改行文字、またはキャプションや注記と同じく\\)は新しい段落を始めます。行頭記号かダッシュ(•、-、*、–)または番号(1.、1))の後にスペースが続く段落は、リストの項目として組まれます。記号は書かれたとおりに描かれ、テキストは文書のunorderedLists.gapだけ離れてぶら下がり、折り返した行はテキストにそろい、先頭のスペース2つで1段階入れ子になります。そのため、• Ofrece elección\n• Acomoda a personas diestras y zurdasと書いたセルは2項目のリストになります。通常のスペースだけの行は何も加えません。ノーブレークスペース(U+00A0)を含む行は、CommonMarkと同じくセルの1行になるため、1\nの後にノーブレークスペースを続けると、その行は2行分の高さになります。セルのテキスト末尾のノーブレークスペースは幅を保ちます。760とノーブレークスペースを(231)の上に右ぞろえで置くと、端からスペース1つ分手前で終わり、0が1に近づきます。数字が正確にそろうのは、スペースが括弧と同じ幅の場合だけで、たいていの書体ではスペースのほうが狭くなります(postext 1.4までは、どちらも取り除かれていました)。
列の幅はスタイルではなく表モデルに属します。TableModel.columnWidthsは列ごとの相対的な重みを並べた省略可能な配列で、レイアウト時に正規化されます。[2, 1, 1]は最初の列に幅の半分を与えます。配列がない場合、長さが合わない場合、正でない重みがある場合は、均等に分割されます。表エディターは、列を追加または削除したときに配列を列とそろえたまま保ちます。
#表モデルの構築
TableModelは行優先の格子で、各セルは格子内の位置によって配置されます。rows[r][c]は列cに置かれます。そのため、結合したセルは自分が覆うセルを格子に残し、覆われた各セルには主セルを指すhiddenByが付きます。覆われたセルを省くHTMLの表とは異なります。postextがエクスポートするモデルのヘルパーはこの形を保ちます。どれも新しいモデルを返す純粋関数で、mergeCells(model, { start, end })とunmergeCell(model, at)、addRow、addColumn、removeRow、removeColumn、setCellContent、setCellImage、setCellBackground、setAlignmentがあります。行と列の4つのヘルパーは結合を崩しません。結合したブロックの内側に追加した行や列はブロックを広げ、ブロックの前に追加したものはブロックを移動させ、ブロックから削除したものはブロックを縮めます。最初の行や列を失ったブロックは、新しい左上のセルに内容を保ちます。そして、すべてのhiddenByは引き続き主セルを指します。
parseTSV(text, options?)は、タブ区切りのテキスト、つまりスプレッドシートから貼り付けた範囲からモデルを作ります。行は改行で、セルはタブで分割し、短い行は格子が長方形になるように補います。headerRowsは先頭の行を見出し行にします。そのセルにはisHeaderが、モデルにはheaderRowCountが付くため、ページをまたいで分割された表ではこれらの行が繰り返されます。
import { parseTSV, mergeCells } from 'postext';
let model = parseTSV('Part\tQty\tNote\nBolt\t4\tM6\nNut\t8\t', { headerRows: 1 });
// model.headerRowCount === 1; model.rows[0][0] is { content: 'Part', isHeader: true }
model = mergeCells(model, { start: { row: 2, col: 1 }, end: { row: 2, col: 2 } });
// rows[2][1] gets colSpan: 2; rows[2][2] stays in the grid with hiddenBy: { row: 2, col: 1 }tableGridIssues(model)は格子を検査します。健全なモデルには空のリストを返し、そうでなければ格子が壊れている箇所をすべて行の順に返します。spanOverlapは、別のセルのcolSpan / rowSpanの下にある可視のセルです(coveredByがそのセルを示します)。覆われたセルをHTMLのように省くとこうなります。その後のすべてのセルが結合の上にずれるためです。missingCellsは、最後の列より前で終わり、残りを覆う結合もない行で、格子に穴が開きます。
import { tableGridIssues } from 'postext';
tableGridIssues({
rows: [
[{ content: 'A', colSpan: 2 }, { content: 'C' }],
[{ content: '1' }, { content: '2' }, { content: '3' }],
],
});
// => [{ kind: 'spanOverlap', row: 0, col: 1, coveredBy: { row: 0, col: 0 } },
// { kind: 'missingCells', row: 0, col: 2 }]文書で使う表の格子にこのような問題があると、doc.contentWarningsにraggedTableGridとして報告されます(文書の中の警告を参照)。
#キャプションスタイル
captionStyleプロパティは、リソースのキャプション(画像、SVG、表の下、または上に置くFigure 1 — …の行)を設定します。番号付きのラベルと説明は同じ書体とサイズを共有しますが(エンジンの制約です)、ラベルには独自のウェイト、イタリック、色を付けられます。フォントファミリー、サイズ、色は、未設定のときは本文の値を継承します。キャプションはリソースの上に置くことができ(表では通常この形です)、ブロックの幅いっぱいに広がる色付きの帯の上に組むこともできます。省略可能な小さめの注記(出典、クレジット。Resource.note)は、サブオブジェクトnoteで設定します。リソースの種類は、ResourceType.captionStyleで自分のリソースについてこれらのフィールドのどれでも上書きできます(リソースの種類を参照)。
const config: PostextConfig = {
captionStyle: {
align: 'center',
labelBold: true,
labelColor: { hex: '#295AA3', model: 'hex' },
descriptionItalic: true,
position: 'above',
backgroundEnabled: true,
padding: { value: 0.35, unit: 'em' },
note: { italic: true, align: 'left' },
},
};| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
fontFamily | string | 本文のフォント | キャプションのフォントファミリー(ラベルと説明)。 |
fontSize | Dimension | 本文のサイズ | キャプションのフォントサイズ(ラベルと説明)。 |
color | ColorValue | 本文の色 | 説明の文字色。 |
align | 'left' | 'center' | 'right' | 'justify' | 'start' | 'end' | 'left' | キャプションの行の水平方向のそろえです。'justify'は最後の行以外を全幅に広げます。キャプションの帯の上では、行は帯の内側余白の内側でそろい、横に置くキャプションは自身の幅の中でそろいます。 |
gap | Dimension | 0.75em | リソースとキャプションの間の垂直方向のアキ。 |
labelBold | boolean | true | 番号付きのラベル(たとえばFigure 1)を太字で描画します。 |
labelItalic | boolean | false | 番号付きのラベルをイタリックで描画します。 |
labelColor | ColorValue | キャプションのcolor | 番号付きのラベルの色。 |
descriptionItalic | boolean | false | 説明のテキストをイタリックで描画します。 |
position | 'above' | 'below' | 'below' | キャプションの位置です。'above'ではキャプション(とその帯)が先に来て、リソースの本体はキャプションの高さとgapの分だけ下がります。この場合、注記は本体の下に置かれます。 |
backgroundEnabled | boolean | false | キャプションの背後に帯を塗ります。帯はブロックの幅いっぱいに広がり、キャプションの行と四方のpaddingを囲みます。 |
background | ColorValue | パレットのメインカラー | 帯の塗りの色(有効なときだけ塗ります)。 |
padding | Dimension | 0.35em | 帯の縁とキャプションのテキストの間の内側余白。帯がオフのときは無視されます。 |
note | object | — | リソースの注記のスタイル。下のサブテーブルを参照してください。 |
labelNumberGap | string | ノーブレークスペース。日本語の文書では'' | キャプションとインラインの:refで、ラベルと番号の間に置くものです(Figure 1.7、Fig. 1.7)。中国語と日本語では詰めて組みます。''で图1-1になり、日本語の文書ではこれが既定値です(図1-1)。 |
labelSeparator | string | '. '。日本語の文書では' ' | 番号の後、説明の前に置くものです(Figure 1.7. A caption)。中国語のキャプションは全角スペース' 'を使い(图1-1 标题)、日本語のキャプションも既定で同じです(図1-1 東京の地図、JLReq §4.3)。番号のないラベルは独自の規則に従い、接頭辞がピリオドで終わっていなければピリオドを付けます。 |
サブオブジェクトnoteはResource.noteのスタイルを設定します。これはリソースの下に小さめのサイズで組む短いテキスト(出典、クレジット、補足)です。キャプションと同じインライン書式と:refを使え、キャプションの書体を継承します。キャプションが下にあるときはキャプションの下に、キャプションが上にあるときはリソースの本体の下に置かれます。注記の高さはブロックに含まれるため、注記付きのリソースは1つの単位としてフロートします。
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
note.fontSize | Dimension | キャプションのサイズの0.85倍 | 注記のフォントサイズ。 |
note.color | ColorValue | キャプションのcolor | 注記の文字色。 |
note.italic | boolean | false | 注記をイタリックで描画します。 |
note.gap | Dimension | 0.35em | 注記とその前にあるもの(キャプションまたは本体)の間のアキ。 |
note.align | 'left' | 'center' | 'right' | 'justify' | 'start' | 'end' | 'left' | 注記の行の水平方向のそろえ。キャプションのalignと同じです。 |
種類ごとの上書きはmergeCaptionStyle(resolvedCaptionStyle, override, palette?)で統合されます。パイプラインの外で同じ解決を行う必要のあるホストのためにエクスポートされています。
#ダイアグラムスタイル
diagramStyleプロパティは、埋め込んだSVGのダイアグラム(kind: 'svg'のリソース)の色の付け方を設定します。現在の機能は単色刷りモードの1つだけです。ダイアグラム内のすべての色を1つのインキの濃淡に置き換える色変換の処理で、1色の特色で印刷する文書でも図が忠実に再現されます。
const config: PostextConfig = {
diagramStyle: {
singleInk: true,
inkColor: { hex: '#295AA3', model: 'hex' },
},
};| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
singleInk | boolean | false | 埋め込んだすべてのSVGダイアグラムを、1つのインキの濃淡に色変換します。 |
inkColor | ColorValue | メインカラー(#295AA3) | インキです。既定値は文書のパレットのメインカラーで(paletteId: 'main-color'でパレットに連動)、パレットの色見本を差し替えると、見出しや太字の範囲と一緒にダイアグラムの色も変わります。 |
#単色刷りの仕組み
singleInkを有効にすると、SVGマークアップ内のすべての色がinkColorの濃淡に書き換えられます。その濃さは1 − 相対輝度です(ガンマ補正済みのチャンネルにRec. 709の係数を適用します。知覚的な近似ですが、濃淡の対応付けには十分です)。この対応付けは知覚上の明るさを保ちます。白は紙の白に、黒はインキのベタになり、明るい塗りは元の色相にかかわらず明るいままです。淡い黄色の背景はインキの淡い濃淡になり、暗い線はインキのベタに近づきます。
色変換はエクスポートされたapplySingleInkToSvg(svgText, inkHex)が行います。この関数はDOMを使わず、SVGマークアップをテキストとして処理します。
#rgb/#rgba/#rrggbb/#rrggbbaaの16進リテラル、rgb()/rgba()関数、hsl()/hsla()関数は、表示属性、インラインのstyle、グラデーション、<defs>など、どこにあっても書き換えられます。関数のチャンネルは整数、小数、パーセントのいずれでもよく、カンマ区切りとスペース区切りのどちらの構文も使えます。そのためrgb(11.37%, 20%, 50.59%)(Cairoが書き出す形)やrgb(51 102 153 / 50%)も色変換されます。濃淡にした関数はrgb(…)またはrgba(…)として書き戻されます。whiteとblackのキーワードは、ペイントの値として現れる場所(fill、stroke、stop-color、flood-color、color。属性またはインラインスタイルのプロパティとして)でだけ置き換えられ、テキストの内容やラベルの中では置き換えられません。none、transparent、currentColorはそのまま残り、ほかの名前付きの色(red、steelblueなど)や、塗りを指定していない図形やテキストの既定の黒もそのままです。こうした要素を色変換したい場合は、明示的に色を指定してください。- アルファチャンネルは保たれます(
#rgba/#rrggbbaaの桁とrgba(…)のアルファ成分はそのまま引き継がれます。パーセントのアルファは数値として書き出されます)。 inkHexを解析できない場合は、入力がそのまま返されます。- 結果のルートの
<svg>にはdata-postext-single-ink="#…"(インキ)が付き、すでにこの印を持つマークアップは、どのインキを示していてもそのまま返されます。この対応付けはべき等ではありません。2回目の処理ではすべての色が明るくなり、黒はインキの約3分の2になります。そのため画像の色変換は、利用者のコードとバックエンドのどちらが先に処理しても1回だけ行われます(この印はpostext 1.5で追加されたもので、1.4で色変換したマークアップには付いていません)。
import { applySingleInkToSvg } from 'postext';
const recoloured = applySingleInkToSvg(svgText, '#295AA3');
applySingleInkToSvg(recoloured, '#295AA3') === recoloured; // true: never twice単色刷りは3つのバックエンドすべてに適用されます。PDFバックエンドは、resourceBytesから渡されたSVGのバイト列を色変換してからベクターとして描きます。キャンバスとHTMLのバックエンドは、求めに応じて描画するSVGの画像に色を付けます(キャンバスとHTMLでの単色刷りを参照)。そのため、書き出したPDFは画面上のプレビューと一致します。
解決関数(resolver)と既定値の除去関数(stripper)は、ほかのセクションと同じ形で、型DiagramStyleConfig / ResolvedDiagramStyleConfigとともに提供されます。
import {
DEFAULT_DIAGRAM_STYLE_CONFIG,
resolveDiagramStyleConfig,
stripDiagramStyleDefaults,
applySingleInkToSvg,
} from 'postext';
import type { DiagramStyleConfig, ResolvedDiagramStyleConfig } from 'postext';
const resolved = resolveDiagramStyleConfig(config.diagramStyle);
// => { singleInk: false, inkColor: { hex: '#295AA3', model: 'hex', paletteId: 'main-color' } }
const minimal = stripDiagramStyleDefaults(config.diagramStyle);
// => undefined when everything matches the defaults#キャンバスとHTMLでの単色刷り
キャンバスとHTMLのバックエンドは、画像をSVGマークアップではなく、デコード済みの画像(registerResourceImage)またはURL(resourceImageUrl)として受け取ります。求めに応じて、描くものに同じ対応付けを適用します。
- キャンバス(
renderPage、renderPageToCanvas、renderToCanvas)。色付けの対象となるすべてのSVG画像(図、表のセルの画像、デザインの画像、ボックスのアイコンやマーカー)は、配置されたサイズでラスタライズされ、そのピクセルがインキの濃淡に色付けされます。<img>として登録したかImageBitmapとして登録したかは問いません。色付けしたビットマップは、ほかのベクターのラスターと同じくキャッシュされます。ビットマップの画像は色付けされません。 - HTML(
renderToHtml、renderToHtmlIndexed)。各SVGの<img>にはfilter: url(#pt-ink-…)が付き、ページが持つfeColorMatrixを指します。これはサイズ0の<svg>に入った<filter>で、ページの最初に置かれ、インデックス付きの出力ではページのdecorationHtmlに含まれます。単色刷りが適用されている間は、画像の有無にかかわらずすべてのページがこれを持つため、ブロックを1つずつ差し替えるホストが、フィルターのない画像を持ち込むことはありません。
二度は色付けしない。postext 1.4までは、キャンバスとHTMLのバックエンドは画像を渡されたとおりに描いていたため、ホストは渡す前にapplySingleInkToSvgで自らマークアップを色変換していました。バンドルのアダプターとSandboxは今もそうしています。マークアップの処理はPDFとまったく同じ色になるからです(下の最後の段落を参照)。そのため画像の色付けは1回だけで、それを保証する次の3つの規則が3つのバックエンドすべてで成り立ちます。
- 印の付いたマークアップには手を加えない。PDFバックエンドは
resourceBytesをapplySingleInkToSvgで色変換するため、すでに色変換されたSVGのバイト列は渡されたとおりに描かれます。キャンバスとHTMLでも、マークアップに印のあるSVGのデータURIから読み込んだ画像は色付けされません。 - postext 1.xでは、求められない限りオフ。キャンバスは、独自のフラグなしで登録されたSVG画像を、描画に
singleInk: true(RenderPageOptions)が渡されたときだけ色付けし、registerResourceImage(id, img, { singleInk: true })で登録した画像はどの描画でも色付けします。HTMLバックエンドは、renderToHtmlがsingleInk: trueを受け取ったとき、またはresourceImageUrlのリゾルバーがsingleInk: trueを持つときに色付けします。1.4向けに書かれたホスト、つまりマークアップを色変換し、デコードした画像をフラグなしで登録するホストは、出力が変わりません。次のメジャーリリースでは既定で色付けするようになります。 singleInk: falseは色付けしない。blobやネットワークのURLの先にあるマークアップは読み戻せないため、自分で色変換してそのような方法でデコードする画像は、registerBundleImagesやSandboxと同じくsingleInk: falseで登録します。bundleImageUrl(bundle)はsingleInk: falseを持つリゾルバーを返し、bundleResourceBytesはバンドル自身のバイト列をPDFに渡し、PDFバックエンドがそれを1回だけ色変換します。
どちらのバックエンドも、各画像の種別をVDTから知ります。図とセルの画像はリソースの種別を持ち、デザインの画像ブロックはレイアウト時にリソースから取ったimageKind('svg'または'bitmap')を持ちます。imageKindができる前に作られたVDTでは、キャンバスはベクターのソースとして登録されたデザインの画像をSVGとして扱い、HTMLバックエンドはURLがSVGのデータURIであるか.svgで終わる画像をSVGとして扱います。
マークアップを自分で色変換するか、バックエンドに元の画像を色付けさせるか、どちらか一方にしてください。両方を行ってはいけません。
import { applySingleInkToSvg, registerResourceImage, renderPage, renderToHtml } from 'postext';
// Raw SVG: tinted while diagramStyle.singleInk is on…
registerResourceImage('diagram.svg', rawImg, { singleInk: true });
// …or register it plainly and ask on each render.
registerResourceImage('diagram.svg', rawImg);
const canvas = renderPage(doc.pages[0], doc, { singleInk: true });
// Recoloured before decoding (as postext 1.4 hosts do): painted as given.
const inked = applySingleInkToSvg(svgText, ink);
registerResourceImage('diagram.svg', await decode(inked), { singleInk: false });
// The HTML backend with URLs to the raw markup.
const html = renderToHtml(doc, { resourceImageUrl: urlFor, singleInk: true });renderToHtmlはsingleInkの既定値をリゾルバー自身のフラグから取るため、bundleImageUrl(bundle)にはオプションが要りません。
マークアップの処理が書き換えるすべての色(16進、rgb()、hsl()の値、whiteとblack。単色刷りの仕組みを参照)について、ピクセルの対応付けは、アンチエイリアスのかかった縁やグラデーションも含めて同じ結果になります。両者が異なるのは、マークアップの処理が色をそのまま残す場合です。whiteとblack以外の名前付きの色、currentColor、塗りのない図形やテキスト(既定の黒で描かれるもの)、SVGに埋め込まれたビットマップは、画面では色付けされますが、PDFでは元の色のままです。まったく同じ出力を得るには、ダイアグラムのすべての要素に16進、rgb()、hsl()のいずれかで明示的に色を指定してください。キャンバスがピクセルを読み戻せない場合(別のオリジンからCORSなしで読み込んだ<img>)、画像は色付けされずに描かれます。
#動画スタイル
videoStyleプロパティは、動画リソースの印刷のされ方(ポスター画像に重ねる再生マークとQRコード、ポスター画像を動画へのリンクにするかどうか)と、HTMLビューアーとEPUBでプレーヤーが提供する機能を設定します。
const config: PostextConfig = {
videoStyle: {
playMark: { shape: 'rounded', position: 'top-left', size: { value: 10, unit: 'mm' } },
qr: { position: 'bottom-right', size: { value: 20, unit: 'mm' }, errorCorrection: 'Q' },
player: { download: false, privacy: true },
},
};| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
playMark | VideoPlayMarkConfig | 後述 | ポスター画像に印刷し、再生できることを示すマーク。 |
qr | VideoQrConfig | 後述 | ポスター画像に印刷するQRコード。動画のYouTubeまたはVimeoのページ、あるいはファイルの公開先アドレスを開きます。 |
linkPoster | boolean | true | ポスター画像を動画へのリンクにします。PDFではポスター画像の上にリンク注釈を置き、HTMLとEPUBではポスター画像を表示するすべての場所でそれを<a>で囲みます。 |
html | 'player' · 'poster' | 'player' | HTML出力で動画の位置に何を置くかです。プレーヤーか、オーバーレイ付きの印刷用のポスター画像かを選びます。 |
player | VideoPlayerOptions | 後述 | すべての動画のプレーヤーのオプションです。動画ごとのvideo.playerがその上に重ねられます。 |
#再生マーク
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | true | マークを印刷します。 |
shape | 'circle' · 'rounded' · 'triangle' | 'circle' | 三角形入りの円、三角形入りの角丸長方形(幅は高さの1.45倍)、または背景色で縁取った三角形だけ。 |
position | VideoOverlayPosition | 'center' | 'center'、角('top-left'、'top-right'、'bottom-left'、'bottom-right')、または辺の中央('top'、'bottom'、'left'、'right')。位置は物理的なもので、右から左の本でも右上の角は右上の角です。 |
size | Dimension | 12mm | マークの高さ。ポスター画像の短い辺の40%を超えることはありません。 |
inset | Dimension | 4mm | 角や辺に置くときの、ポスター画像の縁からの距離。 |
color | ColorValue | 白 | 三角形。 |
background | ColorValue | メインカラー | 背後の円または長方形。三角形だけのときはその縁取り。既定でパレットに連動します。 |
backgroundOpacity | number | 0.9 | 背景の不透明度(0–1)。 |
#QRコード
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
enabled | boolean | true | コードを印刷します。公開先アドレスのないファイルには付きません。 |
position | VideoOverlayPosition | 'bottom-right' | 再生マークと同じです。2つには別々の位置を指定してください。 |
size | Dimension | 18mm | クワイエットゾーンを含むコードの1辺。ポスター画像の短い辺の45%を超えることはありません。スマートフォンのカメラが読み取れるのは3分の1ミリメートル以上のモジュールです。30文字のアドレスは29モジュールのコードになるため、18 mmでクワイエットゾーンを2にすると、モジュールは0.55 mmになります。 |
inset | Dimension | 3mm | ポスター画像の縁からの距離。 |
errorCorrection | 'L' · 'M' · 'Q' · 'H' | 'M' | コードがどれだけ損傷したり覆われたりしても読み取れるかで、約7%、15%、25%、30%です。コードのモジュール数が変わらない範囲で、自動的に引き上げられます。 |
quietZone | number | 2 | 下地の上でコードの周りに置く明るいモジュールの数(0–8)。下地がポスター画像から浮き出るため、何もない紙の上で規格が求める4モジュールは必要ありません。 |
color | ColorValue | 黒 | 暗いモジュール。明るい下地の上で暗い色にしてください。ほとんどのリーダーは反転したコードを読み取れません。 |
background | ColorValue | 白 | 下地。 |
radius | Dimension | 1mm | 下地の角の半径。 |
コードはエンジン自身がエンコードし(encodeQr(text, level):バイトモード、UTF-8、バージョン1から40、ペナルティが最も低いマスク)、ベクターとして描きます。キャンバスはモジュールの連なりからなる1つのパスを塗り、PDFは1回のdrawSvgPath、HTMLはshape-rendering="crispEdges"を指定した1つの<path>で描くため、どの印刷サイズでも鮮明なままです。
#プレーヤーのオプション
VideoPlayerOptionsは、videoStyle.playerと各動画のvideo.playerで使います。ファイルのHTML5プレーヤーはすべてに従い、YouTubeとVimeoのプレーヤーは埋め込みパラメーターで指定できる範囲で従います。
| プロパティ | 既定値 | 対応 | 説明 |
|---|---|---|---|
controls | true | YouTube、Vimeo、ファイル | プレーヤーのコントロールを表示します。 |
download | true | ファイル | ブラウザーのダウンロードボタンを表示します(オフのときはcontrolslist="nodownload")。ボタンを隠すだけで、ファイルを保護するものではありません。YouTubeとVimeoはダウンロードを提供しません。 |
fullscreen | true | YouTube、Vimeo、ファイル | 全画面表示を提供します(fs=0、iframeのallowfullscreen、nofullscreen)。 |
playbackRate | true | Vimeo、ファイル | 再生速度のメニューを提供します(speed=0、noplaybackrate)。 |
pictureInPicture | true | Vimeo、ファイル | ピクチャー・イン・ピクチャーを提供します(pip=0、disablepictureinpicture)。 |
remotePlayback | true | ファイル | 別の画面へのキャストを提供します(disableremoteplayback)。 |
autoplay | false | YouTube、Vimeo、ファイル | 自動で再生を始めます。ブラウザーの要件どおり、常にミュートされます。 |
muted | false | YouTube、Vimeo、ファイル | 音を消した状態で始めます。 |
loop | false | YouTube、Vimeo、ファイル | 最後まで再生したら、最初から再び再生します。 |
preload | 'metadata' | ファイル | 再生前にブラウザーが読み込む量です。'none'、'metadata'、'auto'のいずれかです。 |
privacy | true | YouTube、Vimeo | プライバシー強化モードの埋め込みです。YouTubeはyoutube-nocookie.comから、Vimeoはdnt=1付きで埋め込みます。 |
EPUBはスキーマが認める属性だけを残します。ファイルはcontrols、autoplay、muted、loop、playsinline、preload付きで再生され、それ以外はリーディングシステムが決めます。
解決関数と既定値の除去関数は、ほかのセクションと同じ形で、型VideoStyleConfig / ResolvedVideoStyleConfigとともに提供されます。
import {
DEFAULT_VIDEO_STYLE_CONFIG,
DEFAULT_VIDEO_PLAYER_OPTIONS,
resolveVideoStyleConfig,
resolveVideoPlayerOptions,
stripVideoStyleDefaults,
} from 'postext';
const resolved = resolveVideoStyleConfig(config.videoStyle);
const player = resolveVideoPlayerOptions(resource.video?.player, resolved.player);
const minimal = stripVideoStyleDefaults(config.videoStyle); // undefined when all defaults#段落スタイル
paragraphStylesプロパティは名前付きのスタイルを宣言し、文書は:::paragraphs{style="…"}コンテナーで一連の段落にそれを適用します。参考文献、用語集、注記など、独自の書体、ウェイト、傾き、サイズ、行送り、大文字、スモールキャピタル、ぶら下げインデントを必要とする項目のまとまりに使います。文字組みのフィールドはどれも省略でき、未設定のときは本文の値を継承するため、スタイルには通常の本文と異なる点だけを書けば済みます。
const config: PostextConfig = {
paragraphStyles: [
{
id: 'bibliography',
name: 'Bibliography',
fontSize: { value: 7, unit: 'pt' },
lineHeight: { value: 1.2, unit: 'em' },
hangingIndent: { value: 2, unit: 'em' },
spaceBetween: { value: 0.25, unit: 'em' },
marginTop: { value: 1, unit: 'em' },
marginBottom: { value: 1, unit: 'em' },
},
],
};## References
:::paragraphs{style="bibliography"}
Knuth, D. E. (1984). *The TeXbook*. Addison-Wesley.
Bringhurst, R. (2004). *The Elements of Typographic Style*. Hartley & Marks.
:::| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
id | string | 必須 | :::paragraphs{style="…"}から参照する識別子。 |
name | string | id | 人が読むための名前です(エディターのUIでのみ使います)。 |
fontFamily | string | 本文のフォント | フォントファミリー。ウェイトは下のfontWeight / boldFontWeightで指定します(未設定のときは本文の値)。 |
fontSize | Dimension | 本文のサイズ | フォントサイズ。 |
lineHeight | Dimension | 本文の行の高さ | 行送り。em/remはスタイル自身のフォントサイズに対する値なので、継承した1.5emはサイズが小さくなるとそれに合わせて詰まります。 |
color | ColorValue | 本文の色 | 文字色。太字とイタリックの範囲は、boldColor / italicColorでスタイル独自の色を設定しない限り、本文の強調の色を保ちます。 |
textAlign | 'left' | 'justify' | 'center' | 'right' | 'start' | 'end' | 本文のそろえ | 水平方向のそろえ。'center'と'right'は、すべての行を反対側から不ぞろいに組みます(献辞、署名欄)。右から左の段落では、'left'はその開始側、つまり右になります。 |
boldColor | ColorValue | bodyText.boldColor | 太字の範囲の色(著者の一覧で名前をハウスカラーにするなど)。 |
italicColor | ColorValue | bodyText.italicColor | イタリック(…)の範囲の色です。italicのスタイルでは、正体に戻る範囲の色になります。colorには従わないため、イタリックもスタイルの色のままにしたい色付きのスタイルでは両方を設定します。 |
fontWeight | number | bodyText.fontWeight | 通常のテキストのウェイト(100–900)。ワークシートのセミボールドの設問、ライトの題辞などに使います。 |
boldFontWeight | number | bodyText.boldFontWeight | 太字(…)の範囲のウェイト。 |
italic | boolean | false | 段落をイタリックで組みます(ト書き、題辞)。中にあるイタリックの…の範囲は、引用ブロックと同じく正体に戻ります。 |
smallCaps | boolean | false | 段落をスモールキャピタルで組みます。小文字はサイズの70%の大文字に、大文字はそのままのサイズになり、どのバックエンドでも同じように描かれます(スモールキャピタルを参照)。配役表や用語集の見出し語に使います。 |
hyphenation | boolean | 本文のハイフネーション | 両端そろえのときにハイフネーションを行います(文書のロケールを使います)。 |
indent | Dimension | 0 | 段の左端(または段落を収めるボックスの左端)からのすべての行のインデントです。emはスタイル自身のサイズです。1行目のインデントとぶら下げインデントはこの位置から測るため、字下げした詩の行は、折り返し部分を行の開始位置より深くぶら下げられます。indent: 1.5emとhangingIndent: 2.5emでは、行は1.5 em、折り返しは4 emの位置になります。負の値は0として扱います。 |
endIndent | Dimension | 0 | 行末側(横組みの行の右、縦組みの行の下)からのすべての行のインデントです。emはスタイル自身のサイズです。textAlign: 'end'と組み合わせると、日本語の手紙の日付や署名の地からN字上げのように、行を末尾から何字か上げて組めます。postext 1.16から使えます。 |
firstLineIndent | Dimension | 本文の1行目のインデント | indentから測った1行目のインデント。hangingIndentが0でないときは無視されます。 |
hangingIndent | Dimension | 0 | 1行目を除くすべての行に適用するインデントで、indentから測ります。参考文献や用語集の典型的な形です。 |
spaceBetween | Dimension | 0 | コンテナー内で続く段落の間の垂直方向のアキ。0では項目が接します。 |
marginTop | Dimension | 0 | コンテナーの最初の段落の上のアキ。ほかのマージンと同じく、保留中のアキと相殺され、段の頭では消えます。 |
marginBottom | Dimension | 0 | コンテナーの最後の段落の下の最小のアキ。コンテナーの次のブロックのアキとの関係はbodyText.paragraphContainerSpacingで決まります。 |
snapToGrid | boolean | true | コンテナーの下で流れをベースライングリッドに戻します。下のアキは最小値になります。falseでは正確なアキを保ち、コンテナーの後のテキストは、次にグリッドに合わせるブロック(見出し、リストの終わり、別行立ての数式)までグリッドから外れたままになります。グリッドに沿わない文書や、独自の行送りを持つまとまりに使います。グリッドのない囲みの中では何も変わりません。 |
textTransform | 'none' | 'uppercase' | 'none' | 段落の大文字・小文字です。'uppercase'は段落を大文字で組み(配役表、ト書きの1行)、チップの語や:refのラベルも対象になります。文字数は変わらないため、エディターのソースマップは1対1のままです。大文字にすると長くなる文字(ß)は書かれたとおりに残ります。数式はそのままで、段落をマークとして読む柱({firstMark.style})は書かれたとおりのテキストを取ります。柱を大文字にするには、デザインのテキスト自身のtextTransformを使います。 |
wordBreak | 'normal' | 'keep-all' | cjk.wordBreak | 段落のCJKの行が、文字の間のどこで改行できるかです(cjk.wordBreakを参照)。通常の文章の本の中で引用する、分かち書きのかなの入門書には'keep-all'を、その逆には'normal'を使います。postext 1.16から使えます。 |
戯曲では、ト書きをイタリックで、配役表をスモールキャピタルで組みます。
paragraphStyles: [
{ id: 'direction', italic: true, fontSize: { value: 9, unit: 'pt' } },
{ id: 'cast', smallCaps: true, textAlign: 'center', fontWeight: 600 },
],:::paragraphs{style="direction"}
Elsinore. A platform before the castle. *Francisco* at his post.
:::ト書きはイタリックで、その中の名前は正体で印字されます。スタイルのウェイト、italic、smallCapsは囲みの中でも適用されます。
詩集では、一部の行を字下げし、行長に収まらない長い行の折り返しを、行そのものより深くぶら下げます。indentは段落のすべての行を内側に寄せ、ぶら下げインデントはそこから数えます。
paragraphStyles: [
{ id: 'verse', textAlign: 'left', firstLineIndent: { value: 0, unit: 'em' }, hangingIndent: { value: 4, unit: 'em' } },
{ id: 'verse-indented', textAlign: 'left', indent: { value: 1.5, unit: 'em' }, hangingIndent: { value: 2.5, unit: 'em' } },
],verse-indentedの行は1.5 emから始まり、折り返しは4 emから始まるため、verseの行の折り返しとそろいます。indentがなければ、スタイルは1行目を字下げするか、ほかの行をぶら下げるかのどちらかしかできません。hangingIndentを設定するとfirstLineIndentは無視されます。
#:::paragraphsコンテナー
:::paragraphs{style="<id>"}の行でコンテナーを開き、:::だけの行で閉じます。間にあるすべての段落は名前付きのスタイルを取り、中にある見出し、リスト、その他のブロックは通常のスタイルを保ちます。コンテナーはほかのフェンス付きコンテナーの中に入れ子にできます。不明なstyleのidはエラーにならず、段落は通常の本文として描画されます。
フェンスは、スタイルの有無にかかわらずalign(start、end、left、right、center、justify)、indent、endIndent(単位のない数値はem)も取ります。スタイルがあればそれを上書きし、なければ外側のコンテナーのスタイル、またはフェンスのある場所のテキストのスタイル(本文、部、スタイル付きの節、ボックス)の上に適用されます。:::paragraphs{align=end}はブロックを行末にそろえて組み(地付き)、:::paragraphs{align=end endIndent=1}はそこから1字上げて組みます。postext 1.16から使えます。
コンテナーの中では流れがベースライングリッドから外れます(7ptで行送り1.2emの項目は、8pt/1.5emのグリッドには乗りません)。最後の段落で流れはグリッドに戻ります(グリッドが優先され、下のアキは最小値になります。見出しと同じ規則です)。項目は本文の段落と同じく段やページをまたいで分割され、オーファンとウィドウの保護も同じです。コンテナーの直前の見出しは、その最初の段落と離れないように保たれます。
コンテナーの下のアキは、スタイルのspaceBetweenとmarginBottom、そして周囲のテキストの段落間のアキ(bodyText.paragraphSpacingがオンなら1行)のうち最も大きいものになり、2つの本文の段落の間のアキと同じく、次のブロックが自身の上に取るアキと統合されます。参考文献の後の見出しは、最後の項目から自身のmarginTopだけ下に置かれ(スタイルのアキのほうが大きければそちら)、詰めた項目のまとまりの後の段落は、テキストの段落間のアキを保ちます。流れはまずテキストの下でグリッドに戻り、グリッドに戻すことで埋まらなかった分はグリッドの行単位で持ち越されるため、コンテナーの後のテキストはグリッドに乗ります。postext 1.4までは、スタイルのアキはグリッドに戻る前に最後の行の下に置かれ、その下に次のブロックの上のアキが加えられ、段落間のアキは含まれませんでした。bodyText.paragraphContainerSpacing: 'add'はこの規則を保ち、この設定より前に保存された設定はこの規則で読み込まれます。snapToGrid: falseのスタイルはグリッドに戻りません。コンテナーの後のテキストはその正確なアキだけ下に置かれ、次にグリッドに合わせるブロックまでグリッドから外れます。リストで終わるコンテナーは、どちらの規則でも1.4と同じに組まれます。リストは自身の下のアキを保ち、marginBottomはそのアキの後に続いて、次のブロックのアキと統合されます。
:::calloutの中でも、コンテナーはスタイルのマージンを同じように取ります。marginTopとmarginBottomは前後のブロックのアキと相殺され(負の値は前後を近づけます)、ボックスの先頭にあるコンテナーは、段の頭と同じく上のマージンを取りません。ボックスには戻る先のベースライングリッドがないため、最後の段落の下のアキは、marginBottom、spaceBetween、ボックス自身の段落間のアキ(そのbody.paragraphSpacing、つまりボックスのテキスト1行分。paragraphContainerSpacing: 'add'では含まれません)のうち最も大きいもの、または次のブロック自身の上のマージンのほうがさらに大きければそれになります。負のmarginBottomは、代わりに次のブロックを引き上げます(postext 1.4までは、囲みの中のコンテナーはどちらのマージンも無視していました)。
const resolved = resolveParagraphStylesConfig(config.paragraphStyles, resolvedBodyText);
// => every unset field filled from the resolved body text
const minimal = stripParagraphStylesDefaults(config.paragraphStyles);
// => undefined when the list is empty; zero margins and `name === id` dropped#チップスタイル
chipStylesプロパティは、インラインの:chip[text]{style="…"}の名前付きスタイルを宣言します。単語リストや、キーボードのキー、タグに使う、角を丸めた色付きのボックスです(構文と改行の規則は文書形式のリファレンスにあります)。既定ではchipというスタイルが1つ用意されています(淡い青の塗りに、メインカラーのヘアラインの枠、わずかな角丸、テキストは周囲の語と同じ)。そのため:chip[…]は設定なしで使えます。chipStylesを宣言すると、この既定のリストが置き換えられます。styleのないチップや、どのスタイルも宣言していないidを持つチップは、最初のスタイルになります。
const config: PostextConfig = {
chipStyles: [
{ id: 'chip', name: 'Word bank' },
{
id: 'key',
name: 'Keyboard key',
background: { hex: '#fff4d6', model: 'hex' },
borderColor: { hex: '#8a6d1f', model: 'hex' },
borderRadius: { value: 2, unit: 'pt' },
bold: true,
},
],
};Classify: :chip[battery] :chip[cable] :chip[switch]
Press :chip[Ctrl]{style="key"} + :chip[C]{style="key"}.| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
id | string | 必須 | :chip[…]{style="…"}から参照する識別子。 |
name | string | id | 人が読むための名前です(エディターのUIでのみ使います)。 |
backgroundEnabled | boolean | true | ボックスを塗ります。 |
background | ColorValue | #e8eef7 | ボックスの塗り(パレットに連動できます)。 |
borderColor | ColorValue | パレットのメインカラー | 枠線の色。 |
borderWidth | Dimension | 0.5pt | 枠線の太さ。0では枠線を描きません。枠線はボックスの縁の内側に引かれます。 |
borderRadius | Dimension | 0.3em | 角の半径。ボックスの高さの半分までに制限されます(大きな値にすると錠剤形になります)。 |
paddingX | Dimension | 0.3em | 枠線とテキストの間の左右の余白。チップの送り幅に含まれます。 |
paddingY | Dimension | 0.1em | テキストの帯の上下の余白。行のボックスの外側に描かれ、行の高さは変わりません。 |
paddingTop、paddingBottom | Dimension | paddingY | テキストの帯の上または下の余白で、それぞれpaddingYの代わりに使われます。帯はベースラインの0.8 em上から0.25 em下までなので、その中央はベースラインの0.275 em上にあり、大文字の中央(たいていの書体で約0.35 em)より低くなります。そのため、丸いチップ(borderRadius: 1em)の中の大文字や数字は高く見えます。上の余白を下の余白より、その差の2倍だけ大きくすると中央にそろいます。大文字の高さが0.7 emの書体なら、paddingTop: 0.2emとpaddingBottom: 0.05emです。 |
fontFamily | string | 周囲のテキスト | チップのテキストのフォントファミリー。ウェイトは周囲のテキストに従います。 |
fontSize | Dimension | 周囲のテキスト | チップのテキストのサイズ。emは周囲のテキストに対する値です。 |
color | ColorValue | 周囲のテキスト | チップのテキストの色。未設定のときは、太字とイタリックの範囲は強調の色を保ちます。 |
bold | boolean | false | チップのテキストを、それ自身の書式に加えて太字にします。 |
italic | boolean | false | チップのテキストを、それ自身の書式に加えてイタリックにします。 |
gap | Dimension | 0.25em | 語間のスペースを挟んで隣り合う語やチップとボックスとの間に確保する最小の間隔です。それより狭いスペースはチップの送り幅の内側で補われるため、両端そろえで削られることはありません。行の端や、詰めて組む約物に接する側には何も加えません。 |
ボックスのemの長さ(paddingX、paddingY、borderRadius、borderWidth、gap)は、チップ自身のフォントサイズに対する値です。ボックスはベースラインの0.8 em上から0.25 em下までの帯で、paddingYと枠線の分だけ大きくなります。行のボックスの外側に描かれ、行送りを変えることはないため、ベースライングリッドは保たれます。ボックスが行送りより高くなると、チップが上下の行のチップに重なることがあります。異なる行の2つのチップが重なると、Sandboxは重なりをポイントで示したチップが隣の行に接触の警告を表示するため、paddingY、枠線、fontSizeを小さくして対処できます。上下にチップのない背の高いチップは報告されません。
VDTでは、チップはkind: 'chip'の行のセグメントで、そのchipフィールドがテキストのラン(それぞれフォント文字列と幅を持つ)、ボックスのジオメトリー(boxWidth、ascent、descent、paddingX、borderWidth、borderRadius、間隔のマージン)、色を持ちます。セグメントのtextは1文字のプレースホルダーなので、プレーンテキストのオフセットとソースマップではチップは1文字として数えられます。
const resolved = resolveChipStylesConfig(config.chipStyles);
// => the built-in `chip` style when unset; every field filled
const minimal = stripChipStylesDefaults(config.chipStyles);
// => undefined for the built-in default; static defaults dropped
const style = pickChipStyle(resolved, 'key');
// => the `key` style, else the first one#囲みスタイル
calloutStylesプロパティは、文書が:::callout{type="…"}コンテナーで適用する名前付きの囲みスタイルを宣言します。注記、ヒント、警告、学習目標など、色付きの背景や枠線で本文から切り離して組む内容に使います。既定では中立的なスタイルnoteが1つ用意されています(薄いグレーの背景、枠線・帯・アイコン・タイトルなし)。そのため:::calloutは何も設定しなくても使えます。calloutStylesを宣言すると、この既定のリストは置き換えられます。
const config: PostextConfig = {
calloutStyles: [
{ id: 'note', name: 'Note' },
{
id: 'objectives',
name: 'Learning objectives',
title: 'Objectives',
stripe: { enabled: true, side: 'left' },
icon: { kind: 'glyph', glyph: '✓' },
titleStyle: { textTransform: 'uppercase' },
lists: { bulletChar: '–' },
},
{
id: 'warning',
title: 'Warning',
backgroundEnabled: false,
border: { enabled: true, color: { hex: '#AA0000', model: 'hex' }, width: { value: 1, unit: 'pt' } },
borderRadius: { value: 1, unit: 'mm' },
titleStyle: { color: { hex: '#AA0000', model: 'hex' } },
},
],
};:::callout{type="objectives"}
- Describe the parts of the lantern.
- Trim the wick at dusk.
:::
:::callout{type="warning" title="Do not touch the lens"}
The glass stays hot for an hour after the flame is out.
:::| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
id | string | — | :::callout{type="…"}で選ぶ識別子です。typeが未知または欠けているフェンスには、設定した最初のスタイルが適用されます(未知のtypeはSandboxが警告します)。 |
name | string | id | 人が読むための名前です(エディターのUIでのみ使います)。 |
title | string | '' | 既定のタイトルの文字列です。空ならタイトルはありません。フェンスのtitle属性で個別に上書きできます。 |
span | 'column' | 'page' | 'side' | 'column' | 横方向の広がりです。段、版面の全幅、または1段半レイアウトのフロート専用のサイド段(layout.sideColumnRole: 'floats')のいずれかです。サイド段のとき、囲みは流れから外れ、割り込んだ位置のテキストの横にあるその段に積まれます。span属性で個別に上書きできます。段組みのレイアウトでは'page'の囲みはページ幅のブロック(span block)になります。ページを段の帯に分け、全幅の独自の段に収まります(後述のコンテナーの節を参照)。 |
placement | 'here' | 'auto' | 'top' | 'bottom' | 'fixed' | 'here' | 囲みを置く場所です。'here'は流れの中にそのまま組みます。'top'/'bottom'はリソースと同じようにフロートとして配置します('auto'はページの上と下のうち先に空いた帯を使います)。囲みは現れた位置で流れから外れ、その位置以降で最初に空いている帯(現在のページの下、または流れが次に開くページの上か下)に入り、後に続くテキストがその周りのページを埋めます。'fixed'は後述のfixedでページ座標に固定し、段の流れから外します。詳しくはコンテナーの節を参照してください。placement属性で個別に上書きできます。 |
sideAtColumnEnd | 'before' | 'after' | 'before' | フェンスの後のテキストが同じ段で続かないときに、サイドの囲み(span: 'side')を置く位置です。続かないのは、段にそのテキストの入る余地がないときや、改段・改ページの規則がテキストを先へ送るとき(ウィドウとオーファンの規則で段落ごと送られる場合、見出しが本文と一緒に送られる場合)です。'before'は囲みをフェンスの位置、つまりそのページで前のテキストの横に置きます。フェンスの下に収まらないときは段の下端からせり上げます。説明する箇所の後に書いた注釈に向いています。'after'はフェンスの後のテキストの最初の行と高さをそろえ、そのテキストが続くページのサイド段に置きます。行の前に書いた行番号や欄外見出しに向いています。テキストが同じ段で続くときは、どちらもフェンスの位置に置きます。章の中で後に何も続かない囲みは、どちらの場合も前のテキストとともに残り、続けて書いたサイドの囲みは順序を保ちます。postext 1.4まではすべてのサイドの囲みが'before'として動作しており、これが既定のままです。注釈用のスタイルでは、囲みは説明する箇所のページにとどまります。 |
fixed | | | 'fixed'の囲みの位置です。ElementAnchor(to:'container'=版面、偶数ページでは左右反転、'page'=仕上がり枠、'bleed'=裁ち落とし枠。edge:コンテナーの9つの基準位置のいずれか)と、省略できるoffset(x/yの寸法)で指定します。 |
floatBarrier | boolean | false | 囲みをフロートの区切りにします。囲みより前で参照された図や表は、すべて囲みより前に配置されます(ページの空き位置、なければ囲みより先に開くページ)。こうして、章を締めくくる囲み(典型的には「要点」のまとめ)を越えてフロートが流れ出ることはありません。章扉、:::part、文書の末尾は常に区切りになります。 |
段組みのページにあるspan: 'page'の囲みは、直前のテキストの下で帯を切ります。囲みより前で参照された全幅の図は、先にその切れ目を使います。テキストの段の高さをそろえ、テキストが終わった位置に図を置き、囲みはその下に続きます(収まらなくなったときは次のページへ送ります)。高さをそろえたテキストの後に収まらないほど高い図は次のページの先頭に置き、囲みはその後に続きます。図が抜けた帯も高さをそろえて終わります。分割できる囲み(keepTogether: false)は、テキストと図の下で、入るだけの項目を収めて始まり、残りは次のページに続きます。 | |||
width | 'fill' | 'auto' | 'fill' | 'fill'は使える幅いっぱいに広がります。'auto'はタイトルに合わせて縮み(バッジ用)、子要素は無視します。 |
backgroundEnabled / background | boolean / ColorValue | true / #f4f4f4 | 囲みの塗りです。 |
border | { enabled, color, width } | false, #cccccc, 0.5pt | 囲みの枠線です。ボックス要素の枠線と同じく、囲みの縁の内側に引きます(ボックス要素を参照)。 |
borderRadius | Dimension | 0 | 背景と枠線の角の半径です(囲みの幅と高さの半分までに制限されます)。帯もこれに従います。角の丸い囲みでは、CSSがborder-leftをborder-radiusで切り抜くのと同じように、帯を丸い枠で切り抜きます。labelのタブは角を四角いまま保ちます。 |
padding | { top, right, bottom, left } | 各0.75em | 囲みの縁と内容の間の内側余白です。emの値は囲みの本文のサイズが基準です。 |
stripe | { enabled, side, width, color } | false、'left'、1.5em、メインカラー | 1辺に沿った塗りの帯です。'left'/'right'の帯は内容の幅を狭め、'top'の帯は内容を下へ押し下げます。'left'と'right'は本文の流れから見た側です(右から左の本では'left'は紙面の右になります)。'start'と'end'は囲み自身の方向に従うので、アラビア語の本の中の:::callout{dir=ltr}では'start'の帯は左に付きます。borderRadiusのある囲みでは、帯の外側の角は枠と同じく丸くなります(postext 1.4までは四角いままで、丸みからはみ出していました)。 |
icon | { kind, glyph, resourceId, fontFamily, fontWeight, size, width, color, align, position, cornerSide } | 'none'、見出しのフォント、400、1.5em、メインカラー、'top'、'inline'、'right' | 内容の横に置く、文字のグリフ(kind: 'glyph')またはビットマップやSVGのリソース(kind: 'resource'+resourceId)です。側辺に帯があるときアイコンは帯の上の中央に置き、帯がなければ専用の列(size+titleStyle.gap)を確保します。align: 'center'は内容に対して縦方向の中央に置きます。リソースの画像は縦横比を保って正方形の中に収めます。widthを設定するとwidth×sizeの枠の中に収めます(横長のアイコンの列など)。内容より高いアイコンは、囲みをその高さまで広げます(align: 'center'のときは内容をアイコンの中央にそろえます)。position: 'corner'はアイコンをバッジとして上の角に掛け、半分を枠の外に出し、内容の場所は取りません。横長のアイコン(width)は描画される幅を基準に角の中央に置き、左の角ではタイトルをアイコンの内側の半分より先から始めます(postext 1.4までは高さを基準に置いていたため、横長の列が囲みからはみ出してタイトルに重なっていました)。cornerSideは角を選びます。'right'/'left'、または左右対称の余白のページの奇偶に従う'outer'/'inner'です(outerは奇数ページでは右、偶数ページでは左)。 |
marker | { kind, glyph, resourceId, fontFamily, fontWeight, size, color, align, gap, rule } | 'none'、見出しのフォント、400、1.5em、メインカラー、'center'、0.5em、罫線なし(0.5pt、メインカラー、長さ0) | 囲みの外、左側の列に描く2つ目のアイコンです。囲みとの間に縦のruleを引くこともできます。自己評価のバッジの横に置く「ここをタップ」の手のマークなどに使います。全体の枠は[marker][rule][gap][box]となり、高さは3つのうち最も高いものに合わせます。alignはそれらを互いの中央にそろえるか、上端にそろえます。rule.lengthは最小値で、罫線は常に少なくとも囲みの高さ分の長さになります。 |
titleStyle | { fontFamily, fontSize, fontWeight, italic, color, textTransform, gap, letterSpacing, indent, lineHeight } | 見出しのフォント、本文のサイズ、700、false、メインカラー、'none'、0.5em、0、0、1.2em | タイトルの文字組みです。gapはタイトルと最初の子要素の間のアキです(アイコンの列とのアキにもなります)。lineHeightはタイトルの行送りで、emはタイトル自身のサイズが基準です。本文と同じく、ベースラインは各行の上から行送りの0.8の位置に来ます。そのため本文の行送りに合わせたタイトル(12 ptのグリッドでlineHeight: 12pt)なら、囲みの高さは行の整数倍のままで、タイトルもグリッドに乗ります。既定の1.2 emでは、囲みごとに1行の端数が加わります。textTransform: 'uppercase'は文字数を変えません。letterSpacingはタイトルにトラッキングをかけます(canvasのletterSpacing、PDFのTc)。indentはタイトルを囲みの内側の縁から右へ押し出します。タイトル側(左の角)に掛かる角のバッジは、先に自分の場所(バッジの内側の半分+gap)を確保するので、どのページに来てもタイトルはバッジにかかりません。indentはそれに上乗せされるだけです。 |
body | { fontFamily, fontSize, lineHeight, color, boldColor, italicColor, fontWeight, boldFontWeight, italic, smallCaps, textAlign, hyphenation, paragraphSpacing, firstLineIndent } | bodyTextを継承。italic/smallCapsはfalse | 囲みの中の段落とリスト項目の文字組みです。未設定のフィールドはすべて本文テキストを継承します。italicColorはイタリックの部分の色を設定します(囲みの色でイタリックにした引用文など)。fontWeight/boldFontWeightは通常と太字の部分のウェイトを設定します。italicは囲み全体をイタリックにし、…の部分は立体に戻ります。smallCapsは囲み全体をスモールキャピタルにします。囲みのテキストがほかに何を引き継ぐかは囲みの中の文字組みを参照してください。 |
lists | { bulletChar, color, indent, gap, itemSpacing, bulletFontSize, bulletFontWeight } | unorderedListsを継承 | 囲みの中のリストの文字組みです(color、indent、gap、itemSpacingは番号付きリストにも適用されます)。bulletFontSize/bulletFontWeightは、行頭記号のグリフを囲みの本文の書体でそのサイズとウェイトにします(太い色付きの行頭記号など)。 |
label | { fontFamily, fontSize, fontWeight, color, background, position, height, paddingX, offset, inset, icon, rule } | 未設定(タブなし) | 囲みの上辺に付け、フェンスのlabel属性を印字するタブです。番号付きの囲みの番号(「BOX 1-1」)に使います。positionの角('top-right'/'top-left')に寄せてinsetだけ内側に置き、囲みの上端からoffsetだけ突き出します(この分はブロックの一部としてmarginTopに上乗せされるので、段の先頭でもタブの場所が確保されます)。高さはheightで、テキストの左右にpaddingXを取ります。横にiconリソース({ resourceId, width, gap }、角と反対側)を付けることも、上辺に沿って反対側の角からタブまでrule({ enabled, color, width })を引くこともできます。既定値は見出しのフォント、本文のサイズ、700、メインカラーの地に白、高さ1.4em、内側余白0.6emです。タブは枠の上に立つ独立した形なので、囲みのborderRadiusにかかわらず角は四角いままです。 |
columnGap | Dimension | 1.5em | 囲みの中の:::columnsグループの段間です(後述のコンテナーの節を参照)。 |
marginTop / marginBottom | Dimension | 0.75em / 0.75em | 囲みの上のアキ(前のブロックのマージンと相殺されます)と、下の最小のアキ(snapToGrid: falseのときは正確なアキ)です。フロートとして配置した囲み(placement: 'top'、'bottom'、'auto')は、帯とテキストの間にフロートのアキ(本文1行分)を保ちます。それより広いmarginBottomは上の帯にある囲みの下のアキを、それより広いmarginTopは下の帯にある囲みの上のアキを決め、帯とともにグリッドに切り上げられます。postext 1.4までは、フロートとして配置した囲みはマージンを無視していました。 |
snapToGrid | boolean | true | trueのとき、囲みの後の流れはベースライングリッドに戻るので、下のアキはmarginBottomをグリッドの行単位に切り上げた値になります。falseのとき、囲みは正確なmarginBottomを保ち、それが次のブロックの上マージンと相殺されます。このスタイルの囲みが2つ続けば、間隔はちょうどmax(marginBottom, marginTop)です。囲みの後のテキストは、次にグリッドに戻る位置(見出し、リストの終わり)までグリッドから外れることがあり、headings.snapToGrid: falseの見出しの後と同じ動作になります。囲みを積み重ねて作る文書(ワークシート、書式)向けです。これが効くのは流れの中の囲みです。段組みのレイアウトのページ全幅の囲み、フロートとして配置した囲み、固定の囲み、サイドの囲みはグリッドを保ちます。段の帯とフロートの領域はグリッドに沿って組まれるためです。段末そろえの手段は変わりません。段を締めくくる囲みは、引き続き段の最後のグリッド位置まで押し下げられます。 |
keepTogether | boolean | true | trueのとき、囲みは分割しません。残りの場所に収まらない囲みは、まるごと次の段かページに送ります。分割せずに保てないのは、何も入っていない1段分(span: 'page'の囲みでは1ページ分)より高い囲みだけです。そうした囲みは、はみ出す代わりに後述のfalseの規則で分割し、現れた位置から始めます。1段に収まる続きは、そこからまるごと送ります。それほど高いフロートの囲み(placement: 'top' | 'bottom' | 'auto')はフロートにならず、現れた位置で流れの中にとどまります。falseのとき、どの囲みも子ブロックの間、または段落やリスト項目の行の間で分割できます。収まる最も深い切れ目で現在の段(span: 'page'の囲みではページ。段の下端にそろえます)を閉じ、残りは次の段の先頭で独立した囲みとして続きます。枠と帯は同じで、アイコンはなく、repeatTitleで繰り返さない限りタイトルもありません。それでも高すぎれば、さらに分割します。インラインのアイコンが先頭の部分で占める列は、どの断片でも空けたまま残すので、囲みはどのページでも同じ行長になります。リスト項目の中で切るときは、行頭記号を先頭の部分に残します。どの断片の枠もフェンスのcontentIndex/containerIdを共有し、callout.part/callout.continuedを記録します。章を締めくくる長い「要点」の囲みにはheadings.balancing.beforeSpanと組み合わせて、また囲みが図をページから押し出してはならない注記のスタイルに使います。入れ子の囲み(別の囲みの中の:::callout)は親の子要素の1つとして扱います。切れ目はその前後に入り、入れ子の囲みの中で切るのは、そのスタイルが分割を許すとき(keepTogether: false、または1段より高いとき)だけで、そのスタイル自身のsplitMinLinesに従います。 |
splitMinLines | number | 2 | 分割された囲み(keepTogether: false、または1段より高い分割しない囲み)の断片が、切れ目の前後それぞれに残すテキストの最小行数です。対象はテキストだけです。図、表、別行立ての数式、入れ子の囲みを1つ以上含む側は、行数にかかわらず認められるので、図版の囲みは1点だけをページに残すことがあります。段落やリスト項目の中で切るときも、前後それぞれのすべての行を数えます(そこにある図や数式は1行と数えます)。加えて、その段落や項目の行を前後それぞれに少なくともlayout.boxChildSplitMinLines行残します(既定は2行。この最小値のほうが小さければその値なので、1にすれば1行を許します)。既定値では、2行や3行の項目は分割されず、4行の項目は2行ずつにだけ分割されます。既定値では、段の下端や次の段の先頭にテキストが1行だけ残るような分割はしません。最小値を満たす切れ目がないときは、囲みをまるごと送ります。(postext 1.5より前は、段落の中で切るときに側全体の行数しか確かめなかったため、囲みのほかの行で最小値に達すれば、2行の項目が1行ずつに分割されることがありました。) |
repeatTitle | boolean | false | 分割された囲みの続きの先頭ごとに、タイトルの後にcontinuedSuffixを付けて繰り返します(「Key points (cont.)」)。繰り返したタイトルにはタイトルのスタイルが適用されます。タイトルのない囲みは何も繰り返しません。分割された囲みの目印を参照してください。 |
continuedSuffix | string | '(cont.)' | 繰り返したタイトルの後に付ける文字列です。分割された表と同じく文書の言語(locale、なければハイフネーションのロケール)に従い、表と同じ方法でタイトルにつなげます。 |
continuesMarkerEnabled | boolean | false | 分割された囲みのうち、続きのある各部分の最終行の下、囲みの中にcontinuesMarkerを置きます。 |
continuesMarker | string | 'Continued' / 'Continúa' | そのマーカーの文字列です(脚本の「(MORE)」など)。囲みの本文の書体とサイズで組み、文書の言語ごとに既定値があります。 |
continuesMarkerAlign | 'left' | 'center' | 'right' | 'right' | 囲みの内側の幅の中でマーカーを置く位置です。 |
continuesMarkerItalic | boolean | true | マーカーをイタリックで組みます。 |
#:::calloutコンテナー
:::callout{type="<id>"}の行で囲みを開き、:::だけの行で閉じます。フェンスは4つの属性を受け付けます。type(スタイルのid)、title(スタイルのタイトルを上書き)、spanとplacement(スタイルの値を上書き)です。間の内容は囲みの中に組まれます。省略できるタイトルの後に段落、リスト、引用、数式、リソースの埋め込みが続き、それぞれスタイルのbody/listsの文字組みで組まれます(見出しは通常のスタイルのままです)。子要素の間のマージンは本文と同じく相殺されます。囲みの内部はベースライングリッドから外れ、囲みの後で流れはグリッドに戻り、下に少なくともmarginBottomのアキを取ります(グリッドが優先され、マージンは最小値です。リソースと同じ約束です)。snapToGrid: falseのスタイルでは代わりに正確なmarginBottomを保ち、囲みの後のテキストは次の見出しかリストの終わりまでグリッドから外れます。囲みの直前の見出しは囲みと一緒に送られます。
このバージョンでの制約は次のとおりです。
- 囲みは、スタイルで
keepTogether: falseを設定しない限り分割しません。段の残りの場所に収まらないときは、まるごと次の段かページに送ります。フロートの帯や帯の上限で短くなった空の段からも、1段分あれば収まる限り送ります。1段より高い囲みは、分割できる囲みと同じように分割します。どの切れ目でも分割できない囲み(段より高い図、表、:::columnsグループを含むもの、またはどの切れ目でもsplitMinLinesを満たせないもの)だけは、そのまま配置してはみ出させます。このときレイアウトはcalloutOverflow警告(VDTDocument.warnings)を記録し、Sandboxはそれを一覧に表示します。分割できる囲みは、収まる部分(子要素まるごと、または前後それぞれにsplitMinLines行まで残した段落の行。図、表、別行立ての数式は1つで1つの側として足ります)を残し、次の段かページでアイコンのない囲みとして続きます。スタイルで繰り返さない限りタイトルはなく、残した部分の下にマーカーを付けることもできます(分割された囲みの目印を参照)。 - フロートは分割できない囲みに譲ります。図の参照の直後のブロックが
keepTogetherの囲みのとき、その囲みが現在の帯の中で入る段(フロートの前に囲みを収めていた参照元の段、またはその後の空の段)をなくしてしまう位置は使いません。図は次の候補の位置(たいていは次のページ)に移り、囲みは流れの中にとどまります。囲みをページから押し出して段に図だけを残すのではなく、組版者が組むとおりに組みます。 - 段組みのレイアウトで
span: 'page'を指定すると、囲みはページ幅のブロックになります。版面の全幅で組まれ、ページを段の帯に分けます。囲みの上の段は切れ目の線で閉じ、囲みは全幅の独自の段に入り、その下で新しい段の帯が始まるので、流れはすべての段で囲みの下に続きます。段の高さがそろっている位置(ページの先頭、span: 'page'の章扉の見出しの直下、別のページ幅のブロックの直下、上のフロートの帯の直下)では、囲みはそこでそのまま切ります。段の高さがそろわないページの途中に来たときは、組版者が組むとおりに組みます。囲みの上のテキストをすべての段で同じ高さに切り(エンジンは帯の段を同じグリッド行数に縮めて配置をやり直すので、テキストは段から段へ自然にあふれ、オーファン、ウィドウ、次と分離しない規則もすべて適用されます)、囲みはページ全幅に広がり、その下で段が再開します。切るには配置を何回か余分にやり直します。切れ目の線で、囲みと、その下にウィドウの最小行数分の本文行を入れる余地がなくなるとき、または数回試しても収まる配置がないときは、囲みを次のページの先頭へ送ります。囲みの上のテキストは切れ目に従います。図の下で段が上に残す数行に入りきらない段落は次の段に送り(その図は段の中で単独になります)、それでもあるブロックが切れ目を越えるときは、そのブロックの終わる位置ではなく、切れ目を1行下げます。帯を開く分割された囲みもそこで切るので、残りが切れ目を越えることはありません。headings.balancing.beforeSpan(既定)のときは、囲みが抜けた帯は章の最後の帯と同じように後ろで高さをそろえて切ります。スタイルが分割を許す(keepTogether: false)ときは、高さをそろえた段の下に収まる囲みの部分がページを締めくくり、残りが次のページの先頭に来ます。beforeSpan: falseのときは、抜けたページは通常どおり段末をそろえるだけで、改ページは強制しません。ページ幅のブロックの直前の見出しは、囲みと一緒には送られません。1段のレイアウトでは、span: 'page'はただのインラインです。 placement: 'fixed'は囲みを流れから外します。囲みは組まれた後(width: 'auto'はタイトルに合わせて縮み、'fill'はアンカーの点の下にあるテキストの段の幅を取ります)、流れの中で現れたページの、fixed.anchor/fixed.offsetが示す位置に固定されます。既定は版面の左下の角です。囲みが覆うテキストの段は、フロートの帯とまったく同じようにその領域を明け渡します(下から削り、段がまだ空なら上から削ります)。その領域にすでにテキスト、フロート、ページ幅のブロックがあるときは、囲みを次のページへ送ります。章を締めくくる固定の囲み(次のブロックが章扉、:::part、フロートの区切りの囲み、または文書の末尾のもの)は、まず上の段の高さをそろえる(headings.balancing.trailing)ので、短い最終ページは下のバッジとそろって終わります。枠と子要素はpage.floatsに入り、どのバックエンドでも段のクリップの外に描画されます。placement: 'top' | 'bottom'は囲みをリソースと同じようにフロートとして配置します。囲みは現れた位置で流れから外れ、その位置以降で最初に空いている帯(現在のページの下('bottom')、または流れが次に開くページの上か下)に、段の幅(span: 'column')または版面の全幅(span: 'page')で入ります。後に続くテキストが、囲みの抜けたページを埋めます。枠と子要素は、固定の囲みと同じくpage.floatsに入ります。新しいページの先頭に来るフロートの囲みは、そのページを待っている図より先に組まれます。前のページで引用され、その下に収まる図は、テキストが3行未満しか残らないときでも、そのページの残りを使います(図版のページ:囲みと図だけで、間にテキストはありません)。span: 'side'の囲みはフロートになりません。placementにかかわらず、テキストの横に積まれます。width: 'auto'はタイトルにだけ合わせて縮み、子要素は無視します。- ほかの囲みの中に入れ子にした
:::calloutは、独立した囲みです。自分のスタイル(背景、枠線、角の半径、内側余白、帯、タイトル、アイコン、マーカー、ラベル、文字組み)で、親の内側の全幅に組まれ、親の子要素の1つとして積まれます。marginTop/marginBottomは隣の要素と相殺されます。spanとplacement(フェンスまたはスタイル)は無視され、入れ子の囲みは常に親の中を流れます。floatBarrierとsnapToGridも無視されます。囲みは何重にでも入れ子にでき、:::columnsグループの中にも置けます(それぞれ1つの段にまるごと入ります)。親が分割されるとき、切れ目は入れ子の囲みの前後に入り、入れ子のスタイルが分割を許すときはその中にも入ります。どの断片も、切れ目が通る枠を描き直します。前の断片から続く入れ子の囲みは、トップレベルの続きと同じく、タイトルとアイコンを省きます。 - 子要素の中の
:::columns{count=N}…:::グループは、囲みの中で、その子要素を等幅のN段にcolumnGapの間隔で組みます。段の高さが最もそろうブロックや行の境界で切り(途中で切れた段落やリスト項目は、次の段の先頭で行頭記号なしに続きます)、どの段もグループの上端から始まり、グループの高さは最も高い段に合わせます。グループの後の子要素は全幅に戻ります。分割された囲み(keepTogether: false)は、グループの中では切りません。要点を2段にまとめたり、幅の広い囲みで表を横に並べたりするのに使います。 - フェンスの5つ目の属性
labelは、スタイルのラベルタブに印字されます(前述のlabelを参照)。例::::callout{type="box" label="BOX 1-1" title="The octet rule"}。labelスタイルがなければこの属性は無視されます。
VDTでは、囲みはtype: 'callout'の枠ブロックになり、その装飾(背景、帯、アイコン、タイトル)はdesignOverlayに入ります。その後に、同じ段の中で子ブロックが続きます。枠とすべての子要素はフェンスのcontainerIdを持ちます。入れ子の囲みは、子要素の中の独立したtype: 'callout'の枠で、その後に自分のブロックが続きます。それらはトップレベルのフェンスのcontainerIdを保ち(配置と段末そろえからは1つの単位に見えます)、calloutPathを加えます。これは周りの入れ子のフェンスのコンテナーidを外側から順に並べたものです(入れ子の枠自身のidが最後の要素です)。タグ付きPDFは、入れ子の囲みごとに、親の囲みの中にDivを作ります。アイコンの画像はリソースの画像と同じ方法で解決されます。canvasの画像レジストリー、HTMLのresourceImageUrlオプション、PDFのresourceBytesプロバイダーです。
const resolved = resolveCalloutStylesConfig(config.calloutStyles, resolvedBodyText, resolvedHeadings, resolvedUnorderedLists, config.locale);
// => every inherited field filled from the resolved sections; the optional
// locale picks the language of the continuation strings
const minimal = stripCalloutStylesDefaults(config.calloutStyles);
// => undefined for the built-in `note` default; static defaults dropped#囲みの中の文字組み
囲みは内容を自分のbodyとlistsの文字組みで組みます。それ以外はすべて文書のスタイルのままです。
- 段落は囲みの
bodyに従います。書体、サイズ、行送り、色、強調の色、ウェイト、italic、smallCaps、行そろえ、ハイフネーション、字下げ、段落間隔です。未設定のフィールドはbodyTextを継承します。継承した強調の色はパレットとのリンクを保つので、囲みの中の太字、イタリック、:refのラベルは、囲みの外と同じくcolorPaletteに合わせて変わります。(postext 1.4までは、囲みの中の太字はメインカラーにかかわらず#295AA3のままでした。) - 箇条書きリストは
unorderedListsより囲みのlists(行頭記号の文字、色、グリフのサイズとウェイト、字下げ、アキ、項目の間隔)を優先し、テキストは囲みの本文テキストになります。lists.bulletCharやlists.colorが文書の値(unorderedLists)と異なるときは、すべてのレベルの行頭記号や色を置き換えます。同じ値を繰り返すとき、または未設定のときは、各レベルの値(unorderedLists.levels)をそのまま残すので、入れ子のダッシュも囲みの中で保たれます。 - 番号付きリストは
lists.indent、gap、itemSpacingに従い、スタイルがlists.colorを設定していれば、文書の行頭記号の色と同じ値でもそれに従います。設定していないスタイルでは、番号はorderedLists.colorのままです。(postext 1.4までは、色がunorderedLists.colorと異なるときにしか番号に反映されなかったため、まさにその色に設定しても何も変わりませんでした。)番号そのもの(書体、サイズ、区切り)は全体のorderedListsから取ります。listsには行頭記号のフィールドしかないので、囲みの番号はそちらで設定してください。 - 囲みの中の
:::paragraphsコンテナーは、入れ子の囲みの中でも、その段落スタイルに従います。スタイルが設定しないフィールドは、囲みのbodyではなく文書のbodyTextを継承します(囲みのイタリックやスモールキャピタルも継承しません)。 :::columnsグループには独自のスタイルはありません。どの段も囲みの本文とリストの文字組みを共有し、columnGapが段間を決めます。- 引用は、囲みの本文の書体、サイズ、ウェイト(本文と同じくイタリックでグレー)と、スモールキャピタルに従います。見出しは見出しのスタイルのまま、別行立ての数式は数式の設定のままです。
- 図と表は文書のキャプションと表のスタイルのまま、囲みの内側の幅で組まれます。通常と太字のウェイトは囲みの
bodyのウェイトに従います。 - チップはチップのスタイルのままです。
emのサイズは囲みの本文のサイズを基準に読みます。 :::spaceの空きは囲みの本文の行で測ります(どこで省かれるかは:::spaceを参照)。- 入れ子の囲みは自分のスタイルにすべて従います。
span、placement、floatBarrier、snapToGridは無視されます。
#分割された囲みの目印
囲みが段やページをまたいで分割されるとき(keepTogether: false、または段より高い囲み)、最初の部分の後の各部分はタイトルとアイコンなしで始まり、既定では囲みが続いていることを読者に示すものは何もありません。2つのオプションで、本や脚本で使う目印を加えられます。
repeatTitle: trueにすると、続きの先頭ごとにタイトルを繰り返し、その後にcontinuedSuffixを付けます。既定は「Key points (cont.)」の形です。繰り返したタイトルにはタイトルのスタイルが適用されるので、textTransform: 'uppercase'と組み合わせれば脚本の「HAMLET (CONT'D)」になります。アイコンとラベルタブは最初の部分にだけ付きます。continuesMarkerEnabled: trueにすると、続きのある各部分の最終行の下、囲みの中にcontinuesMarker(「Continued」、スペイン語の文書では「Continúa」)を、囲みの本文の書体とサイズで置きます。continuesMarkerItalicがfalseでない限りイタリックで、continuesMarkerAlignが'left'か'center'でない限り右そろえです。マーカーは自分が締めくくる部分の中に場所を取り、切れ目はマーカーが収まるように選ばれます。
calloutStyles: [{
id: 'speech',
keepTogether: false,
titleStyle: { textTransform: 'uppercase' },
repeatTitle: true,
continuedSuffix: "(CONT'D)",
continuesMarkerEnabled: true,
continuesMarker: '(MORE)',
continuesMarkerAlign: 'center',
continuesMarkerItalic: false,
}],:::callout{type="speech" title="Hamlet"}
A speech long enough to run over the foot of the page…
:::ページを締めくくる部分は「(MORE)」で終わり、次のページは「HAMLET (CONT'D)」で始まります。どちらもページ割りのための補助的な表示です。アクセシブルなPDFではアーティファクトになり、HTMLでは支援技術から隠されるので、タイトルは1度だけ読み上げられます。VDTでは、artifact: trueの付いたdesignOverlayのテキストブロックです。
#部
partsプロパティは、文書が:::part{number="…" title="…"}コンテナーで開く部扉のページを設定します。章のまとまりを束ねる「Part I — Foundations」のページです。部は常に独立したページを占めます。コンテナーは設定した奇偶の新しいページへ改ページし、そのページをparts.marginsで本文領域を決める1段のページに変え、部扉のデザインをページ全体に重ね、閉じるフェンスの後でもう一度改ページします。こうして次の章(自身のbreakBefore.parityを持ちます)はきれいに始まります。H1の既定の設定では、奇数ページの部扉、白の偶数ページ、次の奇数ページに章、という古典的な構成になります。
const config: PostextConfig = {
parts: {
breakBefore: { parity: 'odd' },
breakAfter: { enabled: true, parity: 'any' },
margins: { top: { value: 9, unit: 'cm' }, left: { value: 3, unit: 'cm' }, right: { value: 3, unit: 'cm' } },
design: {
elements: [
{
kind: 'text', id: 'number', content: 'Part {numberRoman}',
fontSize: { value: 12, unit: 'pt' }, fontWeight: 600, align: 'left',
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: { value: 3, unit: 'cm' }, y: { value: 5, unit: 'cm' } }, size: { width: 'auto', height: 'auto' } },
},
{
kind: 'text', id: 'title', content: '{titleText}',
fontSize: { value: 28, unit: 'pt' }, fontWeight: 700, align: 'left', overflow: 'wrap',
placement: { anchor: { to: '#number', edge: 'below' }, size: { width: { value: 15, unit: 'cm' }, height: 'auto' } },
},
],
},
bodyStyle: { fontSize: { value: 11, unit: 'pt' }, numberColor: { hex: '#AA0000', model: 'hex' } },
},
};:::part{number="I" title="Foundations"}
1. The lantern and its parts
2. Trimming the wick
3. Reading the weather
:::
# The lantern and its parts| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
page | boolean | true | :::partが部扉のページを開くかどうかです。falseではページを開かず、フェンスの本体も組みません。部の番号、タイトル、パレットは、それ自体の改ページなしに、次の内容から効力を持ちます。典型的な使い方はhtmlViewer.overrides.parts.page: falseで、部扉のない画面用の版にします。 |
breakBefore.parity | HeadingBreakParity | 'odd' | 部が始まるページの奇偶です。値と白ページの帰属の規則は見出しのbreakBeforeと同じです。奇偶を合わせるために挿入した白ページは部に属し(そのはすでに新しい部を指します)、'always-*'の必須の区切りページは前の内容に属します。 |
breakAfter.enabled | boolean | true | 閉じるフェンスの後の内容を新しいページに送ります。falseのときは、部扉のページの1段の中で続きます。 |
breakAfter.parity | HeadingBreakParity | 'any' | その新しいページの奇偶です。'any'のままにして、白の偶数ページを入れるかどうかは次の章自身のbreakBefore.parityに決めさせます。改ページは次のブロックを配置するときに適用されるので、文書の最後にある部の後に空のページは残りません。 |
margins | PageMargins | ページの余白 | 部扉のページの本文領域で、フェンスの中のブロックが流れる1段です。各辺は、未設定ならページの余白を継承します。mirrorは、ページの余白とまったく同じように偶数ページでのどと小口を入れ替えます。 |
design | DesignSlot | 空 | 部扉のデザインです。コンテナーはページの仕上がり枠なので、コンテナーのアンカーと'page'のアンカーは一致し、トンボを表示しているときは'bleed'が裁ち落としまで届きます。装飾専用で、本文の場所を確保することはありません。本文と重ならないようにするにはmargins.topを大きくしてください。空のときは、本文領域の左上に既定ののテキストをH1の文字組みで生成し、番号とタイトルの間にH1のnumberSeparatorを入れます。 |
versoDesign | DesignSlot | 空 | 部扉のページの後に続く白の偶数ページ(部扉の丁の裏)のデザインです。コンテナーとプレースホルダーはdesignと同じです。何もない裏ページにするには空のままにします。部扉の次のページが白のまま残るときにだけ描画され、それには奇偶を指定した改ページが必要です。後述の部扉裏のデザインを参照してください。 |
bodyStyle.fontFamily, fontSize, lineHeight, color, textAlign | bodyTextと同じ | bodyTextを継承 | フェンスの中の段落、引用、リスト項目の文字組みです。ウェイト、強調の色、ハイフネーションは本文テキストから取ります。 |
bodyStyle.bulletColor | ColorValue | unorderedLists.color | 部の中の箇条書きリストの行頭記号の色です。 |
bodyStyle.numberColor | ColorValue | orderedLists.color | 部の中の番号付きリストの番号の色です。番号は常に本文の太字のウェイトで組むので、章のリストは目次のように読めます。 |
bodyStyle.unorderedLists | UnorderedListsConfig | — | 部の中で、bulletColorの後に文書のunorderedListsに重ねて適用する部分的な上書きです。リスト全体の値は、それを継承していたレベルに伝わります。levelsの要素はそのレベルにだけ適用されます。 |
bodyStyle.orderedLists | OrderedListsConfig | — | 部の中で、numberColorと太字のウェイトの後に文書のorderedListsに重ねて適用する部分的な上書きです。たとえば部扉の章のリストに、独自のseparatorFontFamilyとseparatorColorを持つ'•'のseparatorを使えます。 |
#部扉デザインのプレースホルダー
デザインスロットは、見出しのプレースホルダーを部自身の値で解決します。{titleText}はフェンスのtitle、{number}は書いたとおりのnumberです。{numberDecimal}、{numberRoman}、{numberRomanLower}、{numberAlpha}、{numberAlphaLower}はそれを書式化し直します。番号は10進数、ローマ数字、漢数字として、それを囲む語の有無にかかわらず解析され("IV"、"iv"、"4"、"4"、"四"、"卷四"、"第四卷"はどれも{numberDecimal}=4になります)、それ以外は''になります。{partTitle}/{partNumber}、{chapterTitle}/{chapterNumber}(部の前の章)、{pageNumber}、{totalPages}、{bookTotalPages}、メタデータのプレースホルダーも使えます。{attr.<key>}は現在の章のH1の属性を読みます。
#部扉裏のデザイン
versoDesignは、部扉の直後のページに内容がないとき、そのページ、つまり部扉の丁の裏を飾ります。部そのものがそのページを白のまま残すことはありません。breakAfter.parityの既定は'any'なので、何かが奇偶を求めない限り、フェンスの後の内容はすぐ次のページから始まります。
- 次の章の見出し。H1の既定の
breakBeforeは'always-odd'で、奇数ページの部扉の後では'odd'も同じ結果になります。章は次の奇数ページに移り、偶数ページは白のまま残ります。部扉裏のデザインが描画されます。 parts.breakAfter: { enabled: true, parity: 'odd' }。次の見出しの設定にかかわらず、部自身が次の奇数ページを求めます。章が左右どちらのページからでも始まるとき(breakBefore.parity: 'any'、またはbreakBefore.enabled: false)に使います。
どちらもなければ、章は偶数ページから始まり、部扉裏のデザインは描画されません。breakAfter.enabled: falseのときは、内容は部扉のページそのものに続きます。部扉裏は部のパレットに従うので、フェンスのpalette="band=#…"は部扉裏の色も変えます。
parts: {
breakBefore: { parity: 'odd' },
breakAfter: { enabled: true, parity: 'odd' }, // always a blank verso to paint
versoDesign: {
elements: [{
kind: 'box', id: 'field',
style: { backgroundColor: { hex: '#b07d2b', model: 'hex', paletteId: 'band' } },
placement: { anchor: { to: 'page', edge: 'top-left' }, size: { width: 'fill', height: 'fill' } },
}],
},
}章を締めくくる部。章ごとにレイアウトする本(Sandbox、buildBundle)では、:::partのフェンスはそれだけで1つの章にも、章の終わりにもなります。このとき部扉のページは章の最後のページになり、部がまだ果たしていないことを次の章が引き継ぎます。最初のブロックの前にbreakAfterを適用し、最初のページが白のまま残るときはそこにversoDesignを描画します。ページは、本全体を1つの文書で組んだときと同じになります。フェンスの後に置けるのは、何も配置しないディレクティブ(:::numbering、:::space)だけです。それ以外は章の内容となり、その場合は章自身が改ページします。部の直後の空の章は独立した1ページになり、そのページが部扉裏になります。章を自分でレイアウトするホストは、これをcontinuationAfter()から得られます。部で終わる章に対してafterPartPage: trueを返すので、それを次の章のcontinuationに渡してください。
#:::partコンテナー
:::part{number="…" title="…"}の行で部を開き、:::だけの行で閉じます。どちらの属性も省略できます(既定は'')。間のブロック(典型的には章のリスト)は、部扉のページの1段の中をbodyStyleで、margins.topから流れます。ページに収まらない本体は通常のページに続きます。このページはrole: 'part'に分類され(VDTPage.partInfoが番号とタイトルを持ちます)、ヘッダーとフッターの要素はpages: 'part'/pages: 'body'でこのページを対象にしたり除外したりできます。PDFバックエンドは、しおりの中で部をその章の上に加えます。本体が空のもの(:::part{…}の直後に:::)がよくある形で、これでもページは作られます。続けて書いた部が1ページを共有することはありません。部の中に入れ子にした:::partは外側の部にまとめられます。
3つ目の属性palette="<id>=<hex>[, <id>=<hex>…]"は、部に独自の色を与えます。部扉のページと、その後の次の部までのすべてのページで、これらのパレットidのいずれかにリンクしたデザインの色(ヘッダー、フッター、章扉の帯、部扉と部扉裏のデザイン)は、文書のパレットの値の代わりに部の値を取ります。こうして、本の各部が2つ目のデザインなしに、角のタブ、柱の点、章扉の帯の色を変えられます。例::::part{number="II" title="…" palette="band=#f6c297"}。テキストの流れも従います。同じページで、上書きしたパレットの要素の基本値と等しい流れの色はすべて部の値を取ります。対象は、見出し、太字、イタリック、参照の色、行頭記号とリストの番号、キャプションのラベルとキャプションのバー、表のテキスト、罫線、塗り(ヘッダー、本体、縞模様の行、セル自身の塗り)、囲み(背景、枠線、帯、タイトル)、チップ(塗り、輪郭、テキスト)です。そのためbandにリンクしたheadings.levels[1].colorは、各部の見出しをその部の色で組みます。インラインのスウォッチは書かれた色のままです。組は、カンマ、セミコロン、スペースで区切り、=または:でidと色をつなぎます。#は省略できます。部はフェンスが閉じた後も効力を持ち続けます。{partTitle}、{partNumber}、パレットは流れに沿ってその後の章へ引き継がれ、さらにcontinuationAfter()が返すcontinuation.partを通じて、個別にレイアウトする章にも引き継がれます。そのため、ある部の2つ目の章も、1つ目の章とまったく同じように柱にその部を表示します。
2つのパレットの要素が同じ基本値を持っていても、部が一方だけを上書きしたり、異なる色を与えたりすれば、部の中では異なる値を取ることがあります。値だけでは流れの色がどの要素から来たのかわからないので、流れの各色は、それが由来しうる設定と照合され、それらの設定がリンクする値を取ります。次のものは区別されます。
- ブロックのテキストの色:テキストの色、太字・イタリック・参照の色、リストの記号(行頭記号または番号)、番号の後の区切り。
bodyText.colorをinkに、bodyText.boldColorをaccentにリンクし、どちらも#1a1a1aのとき、palette="accent=#b8413d"の部は太字の部分の色を変え、テキストはそのままにします。代わりにunorderedLists.colorをaccentにリンクすると、行頭記号の色を変え、項目のテキストはそのままにします。 - 見出しの各レベルと、色を設定する各見出しスタイル
- 目次の行のテキスト、番号、ページ番号と副題
- 各囲みスタイルの塗り(背景、帯、ラベルタブ)、枠線、罫線(マーカーとラベル)、テキスト(タイトル、アイコン、マーカーのグリフ、ラベル)
- 各表スタイル、チップスタイル、キャプションスタイルのそれぞれの色。名前付きの表スタイルは
tableStyleとは区別され、リソースの種類のキャプションスタイルはcaptionStyleとは区別されます。セル自身の塗りは、そのセル自身のリンクに従います。
1つのケースだけは、まだ値で決まります。異なる場所にある設定が、ブロックの同じ色を設定する場合です。たとえばbodyText.color、bodyText.blockquote.color、段落スタイルのcolor、囲みスタイルのbody.colorは、どれもブロックのテキストの色を設定します。そのうち2つが同じ基本値を持つ要素にリンクし、部がそれらを別の値にするとき、色は上書きを取ります(両方とも上書きされていれば、最後に書いたもの)。そうした要素には、それぞれ独自の基本値を与えてください。
const resolved = resolvePartsConfig(config.parts, resolvedPage, resolvedBodyText, resolvedUnorderedLists, resolvedOrderedLists);
// => margins filled from the page, bodyStyle from the body / list configs
const minimal = stripPartsDefaults(config.parts);
// => undefined when only static defaults remain#見出しスタイル
headingStylesプロパティは、文書が# Title {style="<id>"}で見出しに適用する名前付きスタイルを宣言します。スタイルは2つのことをします。1つは、見出しのレベルの文字組み、デザイン、番号付けを上書きすることです。レベルの要素のlevel以外のすべてのフィールド(フォント、サイズ、色、breakBefore、span、advancedDesign、textTransform、hidden、numberingTemplate…)が対象です。もう1つは、見出しが開く節を制御することです。節のページ(同じかそれより上のレベルの次の見出しまで)は、スタイルの柱、ページのジオメトリー、本文の文字組み、パレットを取ります。こうして、2つ目の設定なしに、10進数で番号を振った2段組みのマニュアルの中に、本の前付け(ローマ数字のノンブルと青い帯を持つ、1段の広い段で組んだ序文)を収められます。
const config: PostextConfig = {
headingStyles: [
{
id: 'front-matter',
numbered: false,
breakBefore: { enabled: true, parity: 'odd' },
span: 'page',
advancedDesign: { enabled: true, minHeight: { value: 52, unit: 'mm' }, slot: { elements: [/* bands, `{titleText}` */] } },
header: { elements: [/* folio | rule | `{title}. {subtitle}` */] },
margins: { left: { value: 50, unit: 'mm' }, right: { value: 17, unit: 'mm' } },
layout: { layoutType: 'single' },
bodyStyle: { fontSize: { value: 10.5, unit: 'pt' }, textAlign: 'justify' },
palette: { band: '#547396' },
},
],
};# Preface {style="front-matter"}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
id | string | — | 見出しの行のから参照する識別子です。未知のidの見出しはそのままです。 |
name | string | id | 人が読むための名前です(エディターのUIでのみ使います)。 |
numbered | boolean | true | 見出しを数えるかどうかです。数える見出しは、そのレベルのカウンター(numberingTemplateの番号、リソース番号の)、の元になる章の序数、目次に印字する番号を進めます。序文、執筆者一覧、索引にはfalseを使います。その後の最初の番号付きの章は章1のままで、それらのページではは空になります。 |
toc | boolean | true | :::tocが見出しを載せるかどうかです。見出しは/で上書きできます。 |
runningChapter | boolean | true | このスタイルのレベル1の見出しを、柱の章にするかどうかです。柱の章とは、そのページとそれ以降のページで、、とそれらの…AtTop形が指す章です。章の中にH1として組んだ図版ページ、地図、表紙にはfalseを使います。柱はその見出しを飛ばし(その見出し自身のページでも)、割り込まれた章を指し続けます。h1のガイドワード(guide word)も設定しません。numberedなら見出しはなお数えられ(自身のデザインは自身のを読みます)、tocなら:::tocになお載ります。さらにtoc: falseにするとPDFのしおりも作られないので、章扉の前のページに置いた章の図版ページは、しおりを章に任せます。ほかのレベルの見出しはこの設定を無視します。既定のままでは、図版ページの後のページは図版ページのタイトルを印字します。numbered: falseの序文や序章は、なお独立した章なので既定のままにします。このオプションが変えるのは、プレースホルダーがどの章を指すかだけです。スタイルはすべての見出しスタイルと同じく独自の節を開くので、次のレベル1の見出しまで、ページは図版ページのスタイルの柱のスロット、余白、段組み、本文スタイル、パレット(スタイルが設定しないものは文書の値)を取ります。割り込まれた章が開いたスタイル付きの節の値ではありません。章ごとにレイアウトする本では、柱は章のファイルから次のファイルへ引き継がれないので、ファイルの先頭にある図版ページでは、そのファイルの最初の章見出しまで章のプレースホルダーは空になります。 |
| レベルのフィールド | headings.levels[]と同じ | そのレベルの値 | fontFamily、fontSize、lineHeight、fontWeight、italic、color、marginTop、marginBottom、snapToGrid、breakBefore、span、advancedDesign、textTransform、letterSpacing、lineSpan、indent、jidori、hidden:設定したものはそれぞれ、このスタイルの見出しについて見出しのレベルの値を置き換えます。breakBeforeはレベルの値にフィールドごとにマージされます。parityだけを設定したスタイルはレベルのenabledを保ち、enabled: trueだけを設定したスタイルはレベルの奇偶を保ちます(postext 1.4までは、欠けたフィールドは改ページなしの既定値から取られていました)。 |
numberingTemplate | string | そのレベルの値 | このスタイルの見出しを、レベルのテンプレートの代わりに番号付けするテンプレートです(トークンはlevels[].numberingTemplateと同じです)。カウンターはレベルのものなので、5つの章の後で'Appendix 'を使う付録のスタイルはAppendix Fと印字します。最初の付録でを付けて数え直してください。''は番号を印字しませんが、見出しはなお数えられます。テンプレートのないレベル1の見出しで目次とが表示する章の序数も印字しません。番号は、流れの中、スタイルのデザインの、目次、に表示されます。 |
header, footer | DesignSlot | 文書の値 | 節のページの柱で、そこではheader/footerを置き換えます(要素のparityとpagesのフィルターは引き続き適用されます)。空のスロットにすると柱をなくします。 |
margins | PageMargins | ページの余白 | 節のページの本文領域です。各辺は、未設定ならページの余白を継承します(mirrorも含みます)。効力を持つのは節が開くページなので、breakBeforeと組み合わせて使います。 |
layout | LayoutConfig | layout | 節のページの段組み(layoutType、gutterWidth…)です。2段組みの本で序文を1段の広い段で組む場合などに使います。そのcolumnRuleは節のページに描かれ、未設定のフィールドは文書のlayout.columnRuleの値を取るので、段組みだけを変える節は文書の罫線を保ちます(段間罫を参照)。 |
bodyStyle | PartsBodyStyleConfig | bodyTextを継承 | 節の中の段落、引用、リストの文字組みです。フィールドはparts.bodyStyleと同じです。 |
palette | Record<string, string> | | 節のページのパレットの上書き(id → 16進数)で、現在の部の上書きに重ねて適用します。部のpalette属性と同じしくみで、及ぶ範囲も同じです。それらのページに組まれるデザインスロット(柱、章扉の帯。上書きしたidにリンクしたすべての色)だけでなく、テキストの流れにも値で及びます。上書きした要素の基本値と等しい流れの色(見出し、太字、イタリック、参照の色、行頭記号とリストの番号、キャプションのラベルとキャプションのバー、表のテキスト、罫線、塗り、囲み(背景、枠線、帯、タイトル)、チップ(塗り、輪郭、テキスト))はすべて、部の下と同じように節の値を取ります。同じ基本値を持つ2つの要素についての規則も同じです(:::partコンテナーを参照)。インラインのスウォッチは書かれた色のままです。 |
節は、同じかそれより上のレベルの次の見出しで閉じます。スタイル付きの見出しの後にあるスタイルなしの#は、文書の柱とジオメトリーに戻ります。スタイル付きの見出しは独自の節を開きます。節が奇偶を合わせるために白のまま残したページは、章のタイトルと同じく、その節に属します。
改ページは見出し自身の改ページの代わりにはなりません。スタイルはレベルのbreakBeforeを継承し(レベル1の既定値は{ enabled: true, parity: 'always-odd' })、見出しはどこにあってもそれを適用します。:::pagebreakの直後でも同じです。改ページで新しいページが開き、見出しはそれでもなお見開きの自分の側を求めます。parity: 'odd'では、偶数ページで終わる改ページの後に白ページが入り、見出しは次の奇数ページから始まります。'always-odd'では区切りの白ページも入り、改ページは何も変えません。見出しはいずれにしてもそのページを開いていたからです。タイトルページの後の目次のページのように、手動の改ページが開いたページから始めたいスタイルは、自分の改ページをオフにします。
headingStyles: [
// Starts where the text puts it: on the page the `:::pagebreak` before it opened.
{ id: 'contents', numbered: false, toc: false, breakBefore: { enabled: false } },
],左右を選ばずに独立したページを保つには、代わりにbreakBefore: { parity: 'any' }を設定し、:::pagebreakは省きます。
ページがどの節に属するか。柱とパレットは、見出しごとではなくページごとに選ばれます。ページは、そのページ上の最後の節の切り替わりの後に有効な節を取ります。同じページで1つの節が終わって別の節が始まるとき(たとえば辞書の短い2つの文字の項)、ページは2つ目の節の柱とパレットを持ちます。スタイル付きの節がページの途中でスタイルなしの見出しによって終わるときは、ページは文書の値に戻ります。{chapterTitle}も同じ規則に従い、2つの章が出会うページは後の章のタイトルを表示します。白ページは章のタイトルの規則に従います。奇偶を合わせる白ページ(blankForParity)はその後に開く節に属し、'always-odd'/'always-even'の改ページが加える区切り(blankForForce)はその前の節に属します。部扉は開いている節を閉じます。
# A {style="letter"}
Aardvark, abacus.
# B {style="letter"}
Babble, badger… (runs on to the next page)どちらの文字もページ1から始まるので、ページ1はBの節の柱を取ります。スタイルのヘッダーに置いた爪見出しはそこで「B」になり、「A」の爪見出しを持つページはありません。どの節にも爪見出しが必要なら、各節に独立したページ(breakBefore)を与えてください。番号付きの章の後のアルファベット付きの付録と、目次とPDFのしおりには載るがページ上にはタイトルを出さない献辞のページの例です。
headingStyles: [
{ id: 'appendix', numberingTemplate: 'Appendix {1:A}' },
{ id: 'silent', hidden: true, numbered: false },
],# Dedication {style="silent"}
For M., who read every draft.
# Method
…
# Survey instrument {style="appendix" startAt=1}
# Raw data {style="appendix"}レベル1にnumberingTemplate: '{1}.'を設定すると、章は1.、2.…、付録はAppendix AとAppendix Bと印字されます。献辞は自分のページを開き(そのレベルのbreakBefore)、段落だけを印字しますが、:::toc、{chapterTitle}の柱、PDFのしおりにはDedicationとして表示されます。目次から外すには、スタイルにtoc: falseを加えます。章の途中にH1として組んだ図版ページや地図は、献辞とは逆のことを求めます。runningChapter: falseのスタイル(通常はnumbered: falseとtoc: falseも付けます)は、柱を割り込まれた章のまま保ちます。
const resolved = resolveHeadingStylesConfig(config.headingStyles, resolvedPage, resolvedBodyText, resolvedUnorderedLists, resolvedOrderedLists);
// => level overrides normalised, margins filled from the page, bodyStyle from the body
const minimal = stripHeadingStylesDefaults(config.headingStyles);
// => undefined when no style remains#目次
tocプロパティは、:::tocディレクティブが印刷する内容を設定します(文書形式を参照)。目次は文書のアウトライン、つまり番号とページラベルの付いたすべての見出しと、すべての:::partから組み立てられるため、章に追従します。章の名前を変える、別の部へ移す、著者を変えると、項目もそれに合わせて変わります。項目は、独立した欄に置く見出しの番号、タイトル、リーダー、右端のページラベル、そして任意のサブタイトル行からなります。部はparts.designでデザインする行になります。
const config: PostextConfig = {
toc: {
levels: [{ level: 1, fontWeight: 700, color: { hex: '#00507b', model: 'hex' }, numberWidth: { value: 7.4, unit: 'mm' } }],
unnumbered: { color: { hex: '#000000', model: 'hex' } },
pageNumber: { fontWeight: 400, width: { value: 8, unit: 'mm' } },
leader: { char: '.', gap: { value: 1, unit: 'mm' } },
subtitle: { enabled: true, attr: 'author', italic: true, fontSize: { value: 8.5, unit: 'pt' } },
parts: {
height: { value: 23, unit: 'pt' },
marginTop: { value: 11.5, unit: 'pt' },
design: { elements: [/* a band box, 'SECTION {number}', '{titleText}', '{pageNumber}' */] },
},
},
};| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
levels | TocLevelConfig[] | レベル1 | 目次に載せる見出しレベルと、レベルごとの項目の文字組みです。fontFamily、fontSize、lineHeight(既定値は本文の行送りで、目次がグリッドに乗ります)、fontWeight、italic、color、indent(項目全体のインデント)、numberWidth / numberGap(タイトルの前に置く番号欄。番号は欄の中で右そろえになり、الفصل الحادي عشرやChapter 12のようにnumberWidthより幅の広い番号があると、そのレベルの欄は最も広い番号に合わせて広がります)、numberFontFamily、numberFontSize、numberFontWeight、numberColor、marginTop、marginBottomを指定できます。未設定のフィールドは本文の値を引き継ぎます。番号は書体やサイズにかかわらずタイトルの1行目のベースラインに乗り、canvas、HTML、PDFのいずれでも同じです(postext 1.4までは箇条書きの記号と同じくxハイトの中央にそろえていたため、ディスプレイ書体や大きなサイズの番号はタイトルより上に浮いていました)。独自のレンダラーは、このベースラインを項目ブロックのbulletBaselineYから得られます。bulletYは1.4と同じく番号のem枠の中点のままなので、この新しいフィールドより前に書かれたレンダラーは、これまでどおりの位置に番号を描きます。 |
unnumbered | TocEntryStyleConfig | — | スタイルがnumbered: falseの見出し(序文など)に対する上書きです。番号を印刷せず、レベルのindentの位置から字下げなしで始まります。 |
pageNumber | object | レベル1の書体、本文のウェイト | ページラベルのfontFamily、fontSize、fontWeight、italic、colorと、width(既定値2em)です。後者は右端にページラベル用に確保する欄の幅で、ラベルはその中で右そろえになります。 |
leader | object | | charをタイトルとページ番号のあいだに繰り返して並べます。続く項目の点が縦にそろうよう右そろえで並べます('. 'とすると点の間隔が空きます)。gapはタイトルとリーダーのあいだに最低限残す間隔です。リーダーの文字数は、その書体でひと続きの文字列として測り、入るだけの数になります。そのため、連続するピリオドをカーニングで離す書体では、点がページ番号まで届くことはなく、点の数が少なくなります(postext 1.4までは点1つの幅から数を求めていたため、そうした書体ではリーダーがタイトルから番号に食い込んでいました)。ラベルの入る余地がなくなるタイトルは、少し手前で折り返します。 |
subtitle | object | | 見出しの属性(attr)から取る、項目の下の2行目です(章の著者など)。独自のfontFamily、fontSize、fontWeight、italic(既定値true)、colorと、追加のindentを持ちます。この行は項目と同じ行送りで組まれ、タイトルから離れることはありません。 |
parts.enabled | boolean | true | 部の区切りに行を設けるかどうかです。 |
parts.breakBefore | boolean | false | 最初の部を除くすべての部の行の前で新しいページを始めます。各部の章がそれぞれ独立したページに並びます。 |
parts.design | DesignSlot | 空 | 行のデザインです。コンテナーは行そのもの(段の幅 × height)です。プレースホルダーは、、…、、です(最後のものは部扉のページラベルです。部扉を作らないparts.page: falseのときは、部の内容が始まるページ、つまり柱がその部に切り替わるページのラベルになります。章ごとにレイアウトする本では、章を閉じるフェンスは次の章の最初の内容ページを指します)。パレットに連動した色は部自身のpaletteを使うため、各部の行はその部の色で表示されます。空のときは、(あいだにH1のnumberSeparator)とページ番号を、レベル1の項目の文字組みで組みます。 |
parts.height, marginTop, marginBottom | Dimension | 2em, 0, 0 | 行の高さと、その上下のアキです。emは本文の文字サイズなので、既定の行の高さは本文2行分ではなく、本文サイズの2倍です。本文が9.5/13.5 ptなら19 ptになります。本文2行分の行にするには、高さをptで指定します(この例では27pt)。 |
ページラベルは文書が印刷するものと同じです。buildDocument()は、:::tocを含む文書を前のパスのラベルで組み直し、ラベルが確定するまで繰り返します(追加のパスは最大3回)。本を章ごとにレイアウトするホストは、代わりに本全体のアウトラインをPostextContent.outlineとして渡します。このアウトラインはcontentOutline()(テキストだけから得る見出しと部)とoutlineFromDoc()(同じ項目に、あるレイアウトのページラベルを付けたもの)から組み立て、そのアウトラインのoutlineKey()が変わるたびに目次の章を組み直します。各見出しの項目は目次が印刷するnumberを持ち、番号付きの見出しであればcounterも持ちます。これは、テンプレートが何を印刷するかにかかわらず、startAtを適用したあとのそのレベルの通し番号です。ホストが自分の一覧で章の横に表示するのに使えます。
#索引
indexプロパティは、:::indexディレクティブが印刷する内容を設定します(文書形式を参照)。索引は、本文中で:index[…]と:index{term="…"}で印を付けた語を並べ替え、先頭の文字でグループにまとめ(中国語ではピンインの頭文字または画数、日本語では五十音の行。groupByを参照)、それぞれに出現するページを添えたものです。項目は見出し語、区切り、ページ番号からなります。副項目はそのあとに続き、レベルごとに1段階ずつ字下げされます。折り返した行はturnoverIndentの分だけ下げるため、副項目と位置がそろうことはありません。
const config: PostextConfig = {
headingStyles: [
// The index in two columns, under its own heading.
{ id: 'index', numbered: false, layout: { layoutType: 'double', gutterWidth: { value: 6, unit: 'mm' } } },
],
index: {
fontSize: { value: 8.5, unit: 'pt' },
lineHeight: { value: 11, unit: 'pt' },
rangeFormat: 'chicago',
groups: { fontFamily: 'Source Sans 3', fontWeight: 700, color: { hex: '#8a1c1c', model: 'hex' } },
},
};| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
fontFamily, fontSize, lineHeight, fontWeight, color | — | 本文 | 項目の文字組みです。グループ見出しを含め、索引のすべての行はlineHeightで組まれます。索引は独自のリズムを保ち、ベースライングリッドには吸着しません。 |
indent | Dimension | 1em | 副項目のレベルごとのインデントです。 |
turnoverIndent | Dimension | 2em | 項目の折り返し行に、そのレベルのインデントに加えて付ける追加のインデントです。 |
entrySpacing | Dimension | 0 | 各主項目の上のアキです。 |
separator, locatorSeparator, rangeSeparator | string | ', '、', '、'–'。アラビア文字では最初の2つが'، ' | 見出し語と最初のページのあいだ、2つのページのあいだ、範囲の両端のあいだに印刷する文字列です。 |
mergeRanges | boolean | true | 同じ番号書式の連続するページを範囲にまとめます。12, 13, 14は12–14と印刷されます。主要ページはまとめません。 |
rangeFormat | 'full' | 'chicago' | 'full' | 範囲の2つ目の数の書き方です。省略せずに書く(234–237)か、The Chicago Manual of Style(9.64)の求めるとおり1つ目と共通する桁を省いて書きます(71–72、100–104、101–8、321–28、1496–500)。ローマ数字のラベルは常に省略せずに書きます。 |
main | | 太字 | 主要ページ(印にmainを付けたページ)の組み方です。 |
see | | 言語による。イタリック(アラビア文字では正体) | 相互参照の前に置く語です。未設定のときは文書の言語に従います。See / See also、Véase / Véase también、Voir / Voir aussi、见 / 另见(繁体字中国語では見 / 另見)…。中国語の索引では、参照を句点のあとに空白なしで置きます:贾琏 12。见贾政。 |
locale | string | 文書の言語 | 項目の並べ替えに使うアルファベット順の言語です(BCP 47タグで、Intl.Collatorが読み取ります)。スペイン語ではñはnのあとに並び、独立したグループの見出しになります。アクセント記号で順序が変わることはありません。 |
groupBy | 'auto' | 'letter' | 'pinyin' | 'stroke' | 'gojuon' | 'kana' | 'none' | 'auto' | グループ見出しの種類です。'letter':並べ替えキーの最初の文字。'pinyin':漢字で始まる項目はピンイン読みのラテン文字の頭文字の下に入り(贾宝玉はJの下)、ラテン文字の並べ替えキーはその文字の下で、その文字の中国語の項目のあとに入ります(照合器はラテン文字を漢字のあとに並べます)。sort="jia mu"はJの末尾に来ます。'stroke':最初の文字の画数の下に入ります(一畫、二畫…。簡体字中国語では一画…)。'gojuon':かなの項目は五十音の行(あ行、か行 … わ行)の下に入ります。'kana'では最初のかなの下に入ります(カタカナとひらがなは見出しを共有します)。並びは日本語の索引のJIS X 4061の順です。読み(印のyomi、なければルビのかな読み、なければsort、なければテキスト)で並べ、カタカナはひらがなとして、小書きのかなは大きいかなとして、ーは直前の母音として扱い、清音、濁音、半濁音の順に並べます。全体は記号、値の順の数字、文字ごとのラテン文字の語、かなの順になります。漢字の見出しのままの項目はindexReadingMissingとして報告され、かなのあとに見出しなしで置かれます。'none':見出しを付けません。記号、数字、語はgroups.marginTopのアキだけで区切ります。'auto'は、日本語の索引(ja、ja-*)を五十音の行で、簡体字中国語の索引(zh、zh-Hans、zh-CN)をピンインで、繁体字中国語の索引(zh-Hant、zh-TW、zh-HK)を画数で、それ以外の言語を文字でグループにまとめます。項目は見出しの元になる照合で並べます。ピンインでグループにまとめたzh-Hantの索引は、ピンインで並びます。読みと画数は照合器(CLDR)のものです。照合器が文字を誤って読む場合(重阳の重をzhòng、行业の行をxíngと読むなど)は、望む読みしか持たない文字で書いたsortキーを印に与えると、正しい位置に並びます。重阳にはsort="崇阳"、行业にはsort="航业"とします。中国語の照合データを持たないブラウザーでは、ピンインと画数の索引は見出しなしで印刷されます。 |
ignoreArticle | boolean | アラビア語ではtrue | アラビア語の項目を、先頭の冠詞ال(ٱل)がないものとして並べ替え、グループにまとめます。البصرةはبの下、بدرとبغدادのあいだに入り、表記は書かれたままです。اللهは冠詞を保ち、独自のsortキーを持つ項目はそのキーのとおりに並びます。この設定にかかわらず、アラビア語の索引は母音記号とタトウィールを無視し、أ إ آ ٱをاの下に入れ、ؤをو、ئとىをي、ةをهとして並べます。 |
groups.enabled | boolean | true | 各グループの上に見出しを印刷します(A、B…または画数。数字には0–9、それ以外にはSymbols。中国語では数字 / 數字と符号 / 符號)。 |
groups.fontFamily, fontSize, fontWeight, italic, color | — | 項目と同じ、ウェイト700 | グループ見出しの書体です。項目と同じ行送りで組まれます。 |
groups.marginTop | Dimension | 索引の1行分 | 各グループの上のアキです。見出しの有無にかかわらず適用します。最初のグループの上には付かず(見出しからの距離は見出し側のアキで決まります)、段の先頭にも付きません。 |
groups.symbolsLabel, numbersLabel | string | 言語による。'0–9'、中国語では'数字' / '數字' | 記号で始まる項目と、数字で始まる項目の見出しです。 |
ページ番号は、目次と同じく本のアウトラインから得ます。computeOutline()とcontentOutline()は、テキストの索引の印を種類'indexMark'の項目として一覧にし(indexMark.path、sort、see、seeAlso、main、range、indexを持ちます)、outlineFromDoc()がそれぞれに配置されたページを与えます。このページは、ビルドがdoc.indexMarks(印ごとに{ sourceStart, pageIndex })に記録します。buildDocument()は、自身の索引を印刷する文書を、番号が確定するまで組み直します。本を章ごとにレイアウトするホストは、:::indexを含む章に、本全体のアウトラインをPostextContent.outlineとして渡します。tocOutline()とindexOutline()はアウトラインを目次が読む部分と索引が読む部分に分けるため、ホストは各章を、その章が印刷する部分だけに結び付けられます。Sandboxは、索引の章を印が動いたときだけ、目次の章を見出しが動いたときだけ組み直します。contentOutline()は、テキストが索引を印刷するかどうか(hasIndex)も示します。
#単位と色
#寸法
Postextの物理的な寸法はすべてDimension型を使います。値と単位の組です:
interface Dimension {
value: number;
unit: DimensionUnit; // 'cm' | 'mm' | 'in' | 'pt' | 'px' | 'em' | 'rem'
}絶対単位(cm、mm、in、pt、px)は、設定したDPIでピクセルに換算されます。300 DPIでは1 cmは約118 pxです。
相対単位(em、rem)は現在の文字サイズに応じて変わります。emは要素自身の文字サイズ、remは本文の文字サイズが基準です。
#色
色は、16進表記と対象のカラーモデルの両方で保存します:
interface ColorValue {
hex: string; // '#ff0000', 'transparent', etc.
model: ColorModel; // 'hex' | 'rgb' | 'cmyk' | 'hsl'
}modelフィールドは意図する色空間を示します。Web向けの描画では'hex'か'rgb'が一般的です。印刷のワークフローでは、'cmyk'にすると、PDFへの書き出し時にCMYKで指定するという意図が保たれます。
Postextは出版品質の出力を目指しているため、本文の色の既定値はmodel: 'cmyk'(#000000)です。見出し、太字、イタリック、リストの色の既定値は、パレットに連動したメインカラー(#295AA3、model: 'hex')です。ページの背景とUIのオーバーレイ(ベースライングリッド、トンボ、デバッグ表示)の既定値はmodel: 'hex'です。別の書き出しの扱いが必要なら、任意のフィールドでcolor.modelを上書きしてください。
#透明度
色は半透明にできます。hexはアルファチャンネルを#rgbaまたは#rrggbbaaの形で受け付けます。rgb() / rgba()の色も、カンマ区切りまたはスペース区切りの構文で、アルファを数値またはパーセンテージで書いて受け付けます。transparentは完全な透明です:
const config: PostextConfig = {
header: {
elements: [{
kind: 'box',
id: 'veil',
placement: {
anchor: { to: 'bleed', edge: 'top-left' },
size: { width: 'fill', height: { value: 40, unit: 'mm' } },
},
style: { backgroundColor: { hex: '#ffffffb3', model: 'hex' } }, // white at 70 %
}],
},
bodyText: { color: { hex: 'rgba(0, 0, 0, 0.85)', model: 'rgb' } },
};3つのバックエンドはこれを同じように描きます。canvasとHTMLビューアーは値をCSSの色として扱います。PDFバックエンドは色の不透明度を定数アルファとして設定します(塗りにはca、線にはCAを持つExtGState)。対象はテキスト、罫線、ボックス、表の塗りと罫線、チップ、スウォッチ、数式です。半透明の色は、それより前に描かれたものの上に合成されます。ヘッダーやフッターのボックスは最後に描かれるため、その下のテキストを薄く覆います。扉の帯のボックスは最初に描かれるため、テキストの下のページに色を付けます。PDFを別の色空間に強制した場合(pdfGeneration.forceColorSpaceとcolorSpace: 'cmyk'または'grayscale')、色は変換され、アルファは保たれます。Sandboxでは、カラーピッカーの不透明度スライダーがこれらの値を#rrggbbaaとして書き込みます。ピッカーはほかの形式も読み込めます。
#カスタムフォント
Postextは、すべてのfontFamily文字列を、Google Fontsのカタログと文書のcustomFontsリストの両方に照らして解決します。名前が衝突した場合はカスタムフォントが優先されます。customFonts: [{ name: 'Roboto', … }]を宣言すると、PostextはGoogle Fontsの「Roboto」ではなく、アップロードしたファイルを使います。
カスタムフォントは次の場合に使います:
- 文書に、Google Fontsにないブランド書体やライセンス書体が必要な場合。
- 実行環境からGoogle FontsのCDNに到達できない場合(オフライン、イントラネット、プライバシーを重視する環境)。
- フォントのバイナリを非公開に保ち、第三者にアップロードしてはならない場合。
#設定のスキーマ
type CustomFontFormat = 'woff2' | 'woff' | 'ttf' | 'otf';
type CustomFontStyle = 'normal' | 'italic';
interface CustomFontVariant {
weight: number; // CSS font-weight, 100..900
style: CustomFontStyle;
fileId: string; // opaque id of the binary in out-of-band storage
format: CustomFontFormat;
fileName?: string; // original upload filename (optional, shown in UI)
}
interface CustomFontFamily {
name: string; // used anywhere a Google Font family name fits
variants: CustomFontVariant[];
}
interface PostextConfig {
// ...
customFonts?: CustomFontFamily[];
}各バリアントのバイナリは、設定そのものには埋め込まれません。設定が保持するのはfileIdのポインターだけで、バイト列は設定の外に保存されます。Sandboxでは、その保存先はIndexedDB(キーバリューストア、ブラウザー内のみ、文書ごとに非公開)です。Postextを別のホストに組み込む場合は、buildDocumentの実行前にバイト列がメインスレッドに届きさえすれば、fileIdをどのように解決してもかまいません(サーバーのエンドポイント、Service Workerのキャッシュなど、方法は問いません)。
#Sandboxでのカスタムフォントの管理
左のアクティビティバー(リソースとデザインのあいだ)からフォントパネルを開きます。この本の書体の一覧には、デザインが使うすべてのファミリーが、その役割と、Google Fontsのものか自分のファイルかとともに表示されます。自分のフォントファイルでは、ファミリーごとに次の操作ができます:
- フォントファミリーを追加:空のファミリーを作ります。名前はその場で変更できます。
- ファイルをアップロード:ウェイト(100–900)とスタイル(normal / italic)を選び、
.woff2、.woff、.ttf、.otfのファイルを1つ、または複数選びます。各ファイルは、そのとき選んでいる(ウェイト、スタイル)に結び付いた独立したバリアントになります。アップロードしたファイル名は記録されて行に表示されるので、バリアントを見分けられます。バリアントのウェイトとスタイルは、ドロップダウンからいつでも変更できます。 - バリアントの重複は許されます。2つのファイルが同じ(ウェイト、スタイル)の枠に入った場合は両方とも保持され、フォントのバリアントの重複警告が表示されるので、余分なファイルの設定を区別し直す必要があるとわかります。
- バリアントを削除またはファミリーを削除:設定の項目と、IndexedDBに保存したバイト列の両方を削除します。
ファミリーを宣言すると、すべてのフォントピッカーで、Google Fontsの一覧の上にあるカスタムグループに表示されます。それを選ぶと、適用したすべてのフォントファミリー欄にそのファミリーが設定されます。
#描画の仕組み
内部では次のように動きます:
customFontsが変わると、宣言されたすべてのファミリーがdocument.fontsにFontFaceとして自動で登録されます。そのため、HTMLビューアー、Canvasビューポート(document.fontsを通して測定します)、CSSからの直接の参照はいずれも、ユーザーが先にフォントピッカーを開かなくてもカスタムの書体を使えます。- レイアウトワーカーは、既存のフォントペイロードの転送経路で同じArrayBufferを受け取ります。そのため測定(
buildFontString、pretext)は、Google Fontsのフォントと同じ方法でメトリクスを求めます。 - バリアントを変更または削除すると、そのファミリーについてワーカーがキャッシュしていた書体が破棄され、次のビルドで再登録されます。これにより、プレビューは常に現在のバリアントの組に追従します。
- PDF書き出し:アップロードしたバイナリは同じ
PdfFontProviderのパイプラインを通ります。.woff2は展開され、.ttfと.otfはそのまま渡されます。.woffははっきりしたエラーで拒否されます(pdf-libは生のWOFFを埋め込めないため、.woff2/.ttf/.otfでアップロードし直してください)。CFFベースのOpenType(OTTOマジックを持つ.otf)はサブセット化せずに埋め込みます。pdf-libのCFFサブセッターはsave()の時点ですべてのグリフを走査し、実際のフォントでは数分間止まることがあるためです。サブセット化を省くとPDFはやや大きくなりますが、描画時間が安定します。
#フォント欠落の警告
Sandboxの検査パネルは、カスタムフォントに固有の3種類の問題をフォントグループに表示します(いずれも、汎用の「読み込まれていない」警告を制御しているdebug.warnings.missingFontの切り替えで有効になります):
- 不明なフォントファミリー:
fontFamilyが、既知のGoogle Fontでも現在宣言されているカスタムファミリーでもない名前を参照しています。いずれかのfontFamily欄がまだ参照しているカスタムファミリーを削除した場合も、DOMが気付くのを待たずにすぐに表示されます。 - フォントのバリアントがありません:ファミリーはあるものの、標準のウェイト/スタイルの枠(400 / 700、normal / italic)の少なくとも1つにアップロード済みのファイルがありません。警告には、欠けている組み合わせが具体的に表示されます。
- フォントのバリアントの重複:1つのファミリーの中で、2つ以上のアップロード済みファイルが同じ(ウェイト、スタイル)の枠を共有しています。描画時に実際に使われるのは1つのファイルだけです。警告は残りの項目を調整し直すよう促します。
これらの警告をクリックするとフォントパネルが開き、欠けているバリアントのアップロード、ファミリーの再追加、重複の解消ができます。
#カラーパレット
PostextConfigのcolorPaletteプロパティは、名前付きの色の再利用可能なセットを定義し、設定内の任意のColorValueから参照できるようにします。CSSのカスタムプロパティやInDesignのスウォッチパネルに相当するもので、パレットの項目を1回変えれば、それを指すすべての色が文書全体で更新されます。
interface ColorPaletteEntry {
id: string; // stable identifier — referenced by ColorValue.paletteId
name: string; // human label shown in sandbox UIs
value: ColorValue;
}#既定のパレット
Postextには、メインカラー(id: 'main-color'、16進値#295AA3)という項目1つだけの既定のパレットが付属します。いくつかの既定値(見出しの色、本文の太字/イタリックの色、:refの色、箇条書きと番号のマーカーの色)はpaletteId: 'main-color'でこの項目を参照しているため、このスウォッチ1つを変えると、それを使う文書のすべての部分の色が変わります。
既定のパレットは、3つのエクスポートで確認、複製、比較できます:
import {
DEFAULT_COLOR_PALETTE,
cloneDefaultColorPalette,
isDefaultColorPalette,
} from 'postext';
// Read-only snapshot of the shipped palette.
DEFAULT_COLOR_PALETTE;
// => [{ id: 'main-color', name: 'Main Color', value: { hex: '#295AA3', model: 'hex' } }]
// Independent copy — mutate this, not DEFAULT_COLOR_PALETTE.
const palette = cloneDefaultColorPalette();
// Detect whether a user has customised the palette at all.
isDefaultColorPalette(palette); // trueパレットは設定の最上位に置きます:
const config: PostextConfig = {
colorPalette: [
{ id: 'ink', name: 'Ink', value: { hex: '#0a0a0a', model: 'cmyk' } },
{ id: 'accent', name: 'Accent', value: { hex: '#b8860b', model: 'hex' } },
],
bodyText: { color: { hex: '#000000', model: 'cmyk', paletteId: 'ink' } },
headings: { color: { hex: '#000000', model: 'hex', paletteId: 'accent' } },
};#パレット項目の参照
設定内の任意のColorValueは、colorPaletteの項目を指す省略可能なpaletteIdフィールドを持てます。対象は、ページの背景、本文の色(:refの色を含む)、見出しの色、段間罫、リストの色、表・キャプション・チップ・囲みの色(ボックス、帯、アイコン、マーカー、ラベル、タイトル、本文)、トンボとベースライングリッドの色、デバッグ表示、そしてデザインのすべての色です。デザインの色には、柱、見出しの扉デザインと段内のデザイン、見出しスタイルのデザインと柱、部扉、目次の部の行(テキスト、罫線、ボックスの塗りと枠線、輪郭線、ドロップキャップ)が含まれます。このフィールドがあるときは、パレット項目のhex / modelが、横に保存された予備のhex / modelより優先されます。インラインの予備の値が使われるのは、パレットがない、空である、またはそのidを含まない場合だけです。パレットを理解しないツールが読む設定を配布するときに役立ちます。
postext 1.5での変更。postext 1.4までは、パレットが届くのは決まった設定の一覧だけでした。デザインの色(柱、扉、見出しスタイル、部、目次の行)、bodyText.referenceColor、囲みのラベルの色、囲みの本文の太字/イタリックの色は、paletteIdの横に保存されたhexのままでした。保存された値がパレット項目と異なる文書(項目を変えたあとにSandboxで編集した文書すべてと、メインカラーが#295AA3でないときのすべての:ref)は、リンクのとおりパレットの色で印刷されるようになりました。色を元のままにするには、そのpaletteIdを削除してください。
#パレットの適用方法
buildDocumentはパレットを2か所で適用します。これにより、明示的に書いた上書きにも、あとから補われる既定値にも、参照した色が効きます:
applyPaletteToConfig(config):ユーザーの生の設定のうち、paletteIdを持つすべてのColorValueを解決します。エンジンが実際に受け取る値を確認したいときに便利です。applyPaletteToResolvedConfig(resolved, palette):既定値を解決したあとに実行し、パレットに連動した既定値(見出しの色、本文の太字/イタリックの色、:refの色、リストの色、既定のデザインの色)を現在のパレットに合わせて書き換えます。
どちらも設定全体を走査するため、パレットに連動した色が取り残されることはありません。テキストの流れのほとんどの色(本文、見出し、リスト、表、キャプション、チップ、囲みのボックス・タイトル・本文)は単純な値になります。それ以外の色(デザインの色、:refの色、囲みのラベル)は、パレットのhex / modelを受け取りつつ、paletteIdを保持します。このリンクは、部のpalette属性と見出しスタイルのpaletteがそれぞれのページで上書きするものなので(部を参照)、残しておく必要があります。htmlViewer.overridesは書かれたままです。HTMLビューアーはこれを最初にマージするため、そこに含まれるパレットは、デザインを含むすべてに適用されます。
これらを自分で呼び出す必要はほとんどありませんが、確認や再利用ができるよう、両方ともエクスポートされています:
import {
applyPaletteToConfig,
applyPaletteToResolvedConfig,
resolveColorValue,
} from 'postext';
const flat = applyPaletteToConfig(config);
// Every ColorValue with a paletteId in the raw config now carries the
// palette entry's hex/model (a design colour keeps its paletteId).
// `applyPaletteToResolvedConfig` is typically handled by buildDocument; use it
// directly if you build a ResolvedConfig yourself and want the palette applied.resolveColorValue(value, palette, fallback)は値を1つだけ解決する版です。設定をコードで組み立てていて、色を1つずつ解決する必要があるときに便利です。
#パレットの編集
paletteIdが存在しない項目を指す色は、保存されたhex / modelで印刷されます。これは、項目が与えていた色より古い場合があります。そのため、項目を削除する前に、その項目に連動するすべてのColorValueを、項目の現在の値を持つ単純な色に書き換えてください。Sandboxのパレットセクション(デザイン → 色)は、項目を削除するときに、色がどこにあっても(デザインや囲みのラベルも含めて)これを行います。確認画面には、その項目を使うすべての設定が、名前または設定内のパス(header.elements[2].color)で表示されます。
#HTMLビューアー
htmlViewerプロパティは、HTMLバックエンドが画面上でページをどうレイアウトするかを制御します。適用されるのはrenderToHtml / renderToHtmlIndexedで描画するときだけです。canvasとPDFの経路はこれをまったく無視し、設定されたpage.width、page.height、page.dpiを直接使います。
interface HtmlViewerConfig {
maxCharsPerLine?: number; // Target column width, in characters of the body font.
columnGap?: number; // Horizontal gap between columns in multi-column mode (px).
optimalLineBreaking?: boolean; // Use Knuth–Plass inside the HTML viewer instead of greedy.
overrides?: HtmlViewerOverrides; // Screen-only partial config merged over the document config.
}
type HtmlViewerOverrides = Omit<PostextConfig, 'htmlViewer'>;| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
maxCharsPerLine | number | 70 | 描画する各段の目標の行長で、本文フォントの文字数で表します。ビューポートは、その長さの代表的な文章の文字列を測って実際のピクセル幅を求めます。そのため、どのプロポーショナルフォントと文字サイズの組み合わせにも適応します。 |
columnGap | number | 50 | ビューアーが複数段モードのときの段間の水平方向の間隔で、単位はCSSピクセルです。1段モードでは無視されます。 |
optimalLineBreaking | boolean | false | HTMLビューアーでKnuth–Plassの行分割を有効にします。ビューアーはリサイズや文字サイズの変更のたびにレイアウトをやり直すため、既定ではオフです。最初に収まる位置で改行する貪欲法なら、即座に反応すると感じられるほど高速です。canvasバックエンドと同じ最適な改行が必要なときにオンにします。 |
overrides | HtmlViewerOverrides | — | 画面上でのみ適用される、部分的な文書設定です。HTMLビューアーはレイアウトの前に、これを文書設定の上にマージします(applyHtmlViewerOverrides)。canvasとPDFはこれを無視します。オブジェクトは再帰的にマージされます。levels配列(見出し、リスト、目次)はlevelをキーに項目ごとにマージされ、それ以外の配列(デザインスロットのelements、calloutStyles、colorPalette…)は元の配列を丸ごと置き換えます。典型的な使い方は、印刷用の帯のない章扉や、仕上がり枠の固定幅ではなく番号の横でタイトルを折り返す部扉です。SandboxではJSONとして編集します。 |
const config: PostextConfig = {
headings: { levels: [{ level: 1, span: 'page', breakBefore: { enabled: true } }] },
htmlViewer: {
// On screen, chapters flow on without the page-span opener.
overrides: { headings: { levels: [{ level: 1, span: 'column', breakBefore: { enabled: false } }] } },
},
};リゾルバーとストリッパーは、ほかのセクションと同じパターンに従います:
import {
DEFAULT_HTML_VIEWER_CONFIG,
resolveHtmlViewerConfig,
stripHtmlViewerDefaults,
} from 'postext';
const resolved = resolveHtmlViewerConfig(config.htmlViewer);
// => { maxCharsPerLine: 70, columnGap: 50, optimalLineBreaking: false }
const minimal = stripHtmlViewerDefaults(config.htmlViewer);
// => undefined when everything matches the defaults一連の使用例は、後述のHTMLビューアーの統合を参照してください。
#PDF生成(設定)
pdfGenerationプロパティは、PDFバックエンドが最終的な文書をどう出力するかを制御します。これらの設定は書き出し時にpostext-pdfパッケージが使い、canvasとHTMLのビューアーは無視します。
buildDocumentはこれをVDTの中にdoc.config.pdfGenerationとして持ち運び、renderToPdfは各設定を、最初に値を与える場所から取ります:
- 自身のオプション(
outlines、accessible、colorSpace) - 最初に描画する文書の
pdfGeneration(本では、最初の章の設定がファイル全体に適用されます) - 既定値:しおりとタグ付けはオン、色はRGB
そのため、renderToPdf(doc, { fontProvider })は設定に従い、renderToPdfに渡したオプションはその設定についてだけ優先されます。forceColorSpaceとcolorSpaceは2つでcolorSpaceオプションに相当します。forceColorSpaceがオンのあいだは設定のcolorSpaceが適用され、オフのあいだはPDFはRGBになります。以前のリリースのpostext-pdfはオプションしか読みませんでした。現在は、pdfGenerationを指定した設定によって、オプションを渡さない呼び出し側のPDFも変わります。
type PdfColorSpace = 'rgb' | 'cmyk' | 'grayscale';
interface PdfGenerationConfig {
outlines?: boolean; // Emit PDF bookmarks from the heading tree.
forceColorSpace?: boolean; // Convert every colour to `colorSpace`.
colorSpace?: PdfColorSpace; // Target space used when `forceColorSpace` is true.
accessible?: boolean; // Tagged, PDF/UA-oriented output (structure tree, alt text, language).
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
outlines | boolean | true | 見出しの階層からPDFのアウトライン(しおり)を出力し、読者がPDFビューアーのサイドバーから任意の見出しへ直接移動できるようにします。見出しの木構造に意味のない文書(1ページのポスターなど)ではオフにします。 |
forceColorSpace | boolean | false | trueのとき、書き出し時に、描画したPDFのすべての色をcolorSpaceに変換します。入力の色がすでに目的の色空間にある画面向けのPDFではオフのままにし、さまざまな素材が混在しても単一の色空間を保証したいときにオンにします。 |
colorSpace | 'rgb' | 'cmyk' | 'grayscale' | 'cmyk' | forceColorSpaceがオンのときに使う変換先の色空間です。オフセット印刷には'cmyk'、画面専用のPDFには'rgb'、モノクロの印刷校正には'grayscale'を使います。forceColorSpaceがfalseのときは効果がありません。 |
accessible | boolean | true | PDF/UA-1に沿った、アクセシブルなタグ付きPDFを出力します。読む順序に並んだ論理構造ツリー(レベルを飛ばさない見出し、段落、リスト、引用ブロック、囲み、見出しセル付きの表、代替テキストとキャプション付きの図、数式、リンクとしてのクリック可能な参照、:::tocの内容は1つのTOCにまとめて行ごとにTOCIを置き、行の番号をLbl、タイトルとページをリンクを含むReferenceとします)、文書のタイトルと言語(最上位のlocale)、XMPメタデータ内のPDF/UAの識別情報を含めます。また、装飾的な要素(ページの背景、罫線、ベースライングリッド、ヘッダーとフッター、トンボ、繰り返される表の見出し行、分割された囲みで繰り返されるタイトルと続きの印)をアーティファクトとして指定し、スクリーンリーダーが読み飛ばせるようにします。altTextのない図は、代わりにキャプションを、それもなければラベルを使います。フロートした図や表は、それを最初に引用するテキスト、または::resource行の前のテキストの直後に読まれ、フロートしたボックスはフェンスの前のテキストのあとに読まれます。フロートがあとのページに置かれても同じです。フロートをまたいで続くリストや目次は1つの要素のままです。追加の構造が不要な印刷用マスターの場合だけオフにします。 |
pdfGeneration: {
outlines: true,
accessible: true,
forceColorSpace: true,
colorSpace: 'cmyk',
}リゾルバーとストリッパーはほかのセクションと同じです:
import {
DEFAULT_PDF_GENERATION_CONFIG,
resolvePdfGenerationConfig,
stripPdfGenerationDefaults,
} from 'postext';
const resolved = resolvePdfGenerationConfig(config.pdfGeneration);
// => { outlines: true, forceColorSpace: false, colorSpace: 'cmyk', accessible: true }
const minimal = stripPdfGenerationDefaults(config.pdfGeneration);
// => undefined when everything matches the defaults書き出しの一連の手順は、後述のPDFの生成を参照してください。
#Folioビューアー(設定)
folioプロパティは、Folioビューアー(postext-folio)が印刷された本を3Dでどう見せるかを設定します。対象は視点の角度、紙、製本、本を置く台、照明です。レイアウトはこれを無視し、canvas、HTML、PDFの出力も同様です。buildDocumentは、設定がいずれかの値を指定しているときに限り、解決した設定をVDTのdoc.config.folioとして持ち運びます。そのため、これらを指定しない文書のレイアウトハッシュは変わりません。
interface FolioConfig {
tilt?: number; // Degrees from straight above, 0–70.
yaw?: number; // Degrees round the book, −180–180.
paper?: {
type?: 'uncoated' | 'bookWove' | 'coatedMatte' | 'coatedSilk' | 'coatedGloss'
| 'bible' | 'newsprint' | 'cardStock' | 'board';
grammage?: number; // g/m²
bulk?: number; // cm³/g; caliper µm = grammage × bulk
finish?: 'auto' | 'uncoated' | 'matte' | 'silk' | 'gloss';
texture?: 'auto' | 'smooth' | 'vellum' | 'wove' | 'laid' | 'linen' | 'felt';
textureStrength?: number; // 0–2
shade?: ColorValue;
showThrough?: boolean;
};
binding?: {
type?: 'hardcover' | 'paperback' | 'sewn' | 'layflat' | 'saddleStitch';
cover?: 'case' | 'pages';
coverMaterial?: 'auto' | 'cloth' | 'paper' | 'leather';
coverColor?: ColorValue;
spineImage?: string; // resource id
};
surface?: {
type?: 'oak' | 'walnut' | 'linen' | 'felt' | 'leather' | 'marble' | 'plain' | 'none';
color?: ColorValue;
};
lighting?: {
environment?: 'studio' | 'daylight' | 'lamp' | 'overcast' | 'night';
intensity?: number; // 0.25–2
shadows?: boolean;
};
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
tilt | number | 22 | 真上からの視点の傾きで、単位は度です。0–70の範囲に収めます。0では開いた本を真上から平らに見ます。角度を大きくするとページの地が手前に来て、本の束の厚みが見えるようになります。 |
yaw | number | 0 | 本のまわりで視点を回す角度で、単位は度です。−180–180の範囲に収めます。0では本をページの地の側から見ます。正の角度では視点が本の右側へ、負の角度では左側へ回ります。tiltとあわせて、ビューアーが開いたときの視点となり、resetView()で戻る視点にもなります。 |
paper.type | FolioPaperType | 'uncoated' | 用紙です。下の5つのフィールドの既定値を与えます(用紙の表を参照)。cardStockは表紙用の厚紙、boardはボードブックに使うような硬い板紙で、その丁は曲がらずにめくれます。 |
paper.grammage | number | 用紙の値 | 1平方メートルあたりのグラム数で表す重さ(坪量)で、20–2500です。重い紙ほど厚く、硬く、不透明になります。丁は大きな曲線を描いて曲がり、裏面の透けが少なくなります。 |
paper.bulk | number | 用紙の値 | 重さあたりの厚さ(嵩)で、単位はcm³/g、範囲は0.5–3です。1枚の紙厚(マイクロメートル)は坪量 × 嵩で、本の束の厚さはこれとページ数から決まります。 |
paper.finish | FolioPaperFinish | 'auto' | 非塗工(繊維のまま、光沢なし)か、塗工してカレンダー加工でマット、シルク(柔らかな光沢)、グロスに仕上げたものです。'auto'は用紙の値を使います。 |
paper.texture | FolioPaperTexture | 'auto' | 表面の凹凸です。smooth(カレンダー加工の平滑な面)、vellum(細かなざらつき)、wove(多くの書籍用紙に見られる均一な地合いで、織った金網の上で抄かれます)、laid(ダンディロールが残す、細かく並んだ簀の目と、それと交わる間隔の広いチェーンライン)、linen(エンボス加工の布目)、felt(不規則なフェルト目)があります。'auto'は用紙の値を使います。 |
paper.textureStrength | number | 1 | 光の中でテクスチャーがどれだけ強く見えるかで、0–2です。 |
paper.shade | ColorValue | 用紙の値 | 印刷前の紙の色(白、ナチュラル、クリーム)です。ページはこの上に印刷されます。 |
paper.showThrough | boolean | true | 薄い紙では、ページの裏面がかすかに透けて見えます。 |
binding.type | FolioBindingType | 'hardcover' | hardcover:上製本で、表紙ボードはページよりわずかに大きくなります。paperback:無線綴じ(背を削って糊付け)で、平らに開きにくくなります。sewn:折丁を糸でかがったソフトカバーです。layflat:のどにくぼみができず、平らに開きます。saddleStitch:雑誌や小冊子のように、折った紙を折り目でホチキス留めします。平らな背はありません。 |
binding.cover | FolioCoverSource | 'case' | 表紙です。'case'はページをくるむ表紙を描きます。'pages'は本の最初のページを表表紙に、最後のページが偶数ページであればそれを裏表紙にします。本は表紙をめくるまで閉じた状態で置かれ、表紙は曲がらずにめくれます(中綴じでは、表紙はページより少し厚い紙で、ページと同じようにめくれます)。くるむ表紙は描かれません。 |
binding.coverMaterial | FolioCoverMaterial | 'auto' | 'auto'は、上製本ではクロス、ほかの製本では厚紙('paper')です。 |
binding.coverColor | ColorValue | 濃紺(#2c3e57) | 表紙の素材の色です。 |
binding.spineImage | string | なし | 背に印刷するビットマップまたはSVGリソースのidです。画像は、本を天を上にして立て、表表紙を右に向けたときに見える背の向きで用意します。背を覆うように、中央にそろえて収めます。中綴じでは無視されます。 |
surface.type | FolioSurfaceType | 'oak' | 本を置く台です。'none'ではホストの背景がそのまま残ります。 |
surface.color | ColorValue | なし | 置き台に色合いを付けます。'plain'ではこれが置き台の色になります。 |
lighting.environment | FolioEnvironment | 'studio' | 塗工紙や光沢紙に映り込む周囲の環境で、影を落とすキーライトと組になっています。 |
lighting.intensity | number | 1 | 露出で、0.25–2です。 |
lighting.shadows | boolean | true | キーライトが落とす影です。 |
用紙と、それぞれが与える値(FOLIO_PAPER_STOCKS)です。製紙会社のデータシートにある代表的な値を使っています:
| 用紙 | 坪量 | 嵩 | 紙厚 | 仕上げ | テクスチャー | 紙色 |
|---|---|---|---|---|---|---|
uncoated(上質紙、オフセット) | 90 g/m² | 1.25 | 113 µm | 非塗工 | ウーブ | #fcfbf8 |
bookWove(クリーム、嵩高) | 80 g/m² | 1.6 | 128 µm | 非塗工 | ウーブ | #f6efdc |
coatedMatte | 115 g/m² | 1.0 | 115 µm | マット | 平滑 | #fdfdfc |
coatedSilk | 115 g/m² | 0.9 | 104 µm | シルク | 平滑 | #ffffff |
coatedGloss | 115 g/m² | 0.8 | 92 µm | グロス | 平滑 | #ffffff |
bible | 40 g/m² | 1.1 | 44 µm | 非塗工 | ベラム | #f9f6ee |
newsprint | 48 g/m² | 1.5 | 72 µm | 非塗工 | ウーブ | #ebe7dc |
cardStock | 250 g/m² | 1.2 | 300 µm | 非塗工 | ベラム | #fbfaf6 |
board | 1250 g/m² | 1.6 | 2000 µm | シルク | 平滑 | #ffffff |
クリーム色の書籍用紙に刷った並製本の小説を、読書灯の下のウォールナットの机に置いた例です:
folio: {
paper: { type: 'bookWove' },
binding: { type: 'paperback', coverColor: { hex: '#8a2b1f', model: 'hex' } },
surface: { type: 'walnut' },
lighting: { environment: 'lamp' },
}色は、設定のほかの色と同じくパレットのリンク(paletteId)に従います。リゾルバーとストリッパーはほかのセクションと同じです。ストリッパーは、選んだ用紙の値と等しい紙の値を取り除きます:
import { FOLIO_PAPER_STOCKS, DEFAULT_FOLIO_CONFIG, resolveFolioConfig, stripFolioDefaults } from 'postext';
resolveFolioConfig({ paper: { type: 'bible' } }).paper;
// => { type: 'bible', grammage: 40, bulk: 1.1, finish: 'uncoated', texture: 'vellum', textureStrength: 1, shade: { hex: '#f9f6ee', … }, showThrough: true }
stripFolioDefaults({ paper: { type: 'bible', grammage: 40 } });
// => { paper: { type: 'bible' } }Sandboxでは、これらの設定はデザインパネルのFolioグループ(デザイン → Folio → Folioビューアー(3D))にあります。Folioタブは、本を組み直さずに変更をその場で反映します。ビューアーそのものについては3Dの本を、別の用紙に刷る一連のページについては文書形式 › :::paperを参照してください。
#デバッグ
debugプロパティは、2種類の執筆支援をまとめます。ソースのテキストと描画されたレイアウトを同期させる視覚的なオーバーレイと、組版上・構造上の問題をSandboxの検査パネルに表示する警告です。どちらも書き出す出力には影響しません。
| プロパティ | 型 | 説明 |
|---|---|---|
cursorSync | SyncIndicatorConfig | 描画したレイアウトに映すキャレットです。表示オーバーレイを参照してください。 |
selectionSync | SyncIndicatorConfig | ソースの選択範囲をページ上で強調します。表示オーバーレイを参照してください。 |
looseLineHighlight | LooseLineHighlightConfig | 両端そろえのゆるい行に重ねるオーバーレイです。表示オーバーレイを参照してください。 |
pageNegative | | ページのハイコントラストの反転表示です。表示オーバーレイを参照してください。 |
warnings | WarningsToggleConfig | エディターに表示する執筆時の警告の種類ごとのブール値です。警告を参照してください。 |
#表示オーバーレイ
| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
cursorSync.enabled | boolean | true | ソースのカーソル位置を映すキャレットを、描画したレイアウトに表示します。 |
cursorSync.color | ColorValue | #2563eb | そのキャレットの色です。 |
selectionSync.enabled | boolean | true | ソースの選択範囲に対応する、描画された範囲を強調します。 |
selectionSync.color | ColorValue | #fde04780 | 強調の色です。既定では半透明の黄色です。 |
looseLineHighlight.enabled | boolean | false | 語間が通常のスペース幅のthreshold倍を超える両端そろえの行に、オーバーレイを描きます。 |
looseLineHighlight.color | ColorValue | #ff000040 | そのオーバーレイの色です。 |
looseLineHighlight.threshold | number | 3 | 両端そろえの行をゆるいとみなす、通常のスペース幅に対する倍率です。looseLines警告も同じしきい値を使います。スペースが3倍を超えて伸びる両端そろえの行は行末をそろえずに組まれるため、既定値のままでは、オーバーレイも警告も通常の本文ではほとんど何も見つけません。ゆるいがまだ両端そろえになっている行を見るには、値を下げてください(1.5や2)。 |
pageNegative.enabled | boolean | false | ページにハイコントラストの反転オーバーレイを重ねます。字形の細部に気を取られずに、見開きの全体の形(文字の密度、段末のそろい、余白)をひと目で確かめるのに役立ちます。 |
各SyncIndicatorConfigは{ enabled: boolean; color?: ColorValue }です。LooseLineHighlightConfigは{ enabled: boolean; color?: ColorValue; threshold?: number }です。pageNegativeは最小限の{ enabled: boolean }の切り替えです。
debug: {
cursorSync: { enabled: true, color: { hex: '#ff0066', model: 'hex' } },
selectionSync: { enabled: false, color: { hex: '#fde04780', model: 'hex' } },
looseLineHighlight: { enabled: true, color: { hex: '#ff000040', model: 'hex' }, threshold: 3 },
pageNegative: { enabled: true },
}これらのオーバーレイは、SandboxがCanvasのプレビューの上に描くものです。ページの一部ではないため、renderPage、HTML出力、PDFがこれを描くことはありません。
#自前のcanvasでのゆるい行
エンジンは、自分で描くページのために、ゆるい行の強調を2つのヘルパーとしてエクスポートしています:
import { buildDocument, renderPageToCanvas, drawLooseLines, findLooseLines } from 'postext';
const doc = buildDocument(content, config);
const canvas = document.querySelector('canvas')!;
renderPageToCanvas(doc.pages[0], doc, canvas, { scale: 0.5 });
drawLooseLines(canvas.getContext('2d')!, doc.pages[0], doc, { threshold: 2.5 });
// The same lines as data: a report, an SVG overlay, a count per page.
for (const { ratio, line, block } of findLooseLines(doc, { threshold: 2.5 })) {
console.log(`page ${block.pageIndex + 1}: ${ratio.toFixed(2)}× — ${line.text}`);
}findLooseLines(doc, { threshold?, pageIndex? }):justifiedSpaceRatioがthresholdを超えるすべての両端そろえの行を、読む順に返します({ block, line, ratio, x, y, width, height })。矩形はページのピクセル単位で、強調が覆う帯、つまりその行の位置でブロックの全幅にわたる帯です。これらは、Sandboxが強調し、検査パネルでlooseLineとして報告する行と同じです。drawLooseLines(ctx, page, doc, { threshold?, color? }):1ページについてその帯を塗り、塗った行を返します。コンテキストの現在の変換のもとでページのピクセル単位で描くため、同じcanvasでrenderPageまたはrenderPageToCanvasを呼んだ直後に呼び出してください。どちらもコンテキストをページに合わせてスケールした状態のままにします。colorには任意のcanvasの塗りスタイルを指定できます。- 既定値:どちらのヘルパーも、文書の
debug.looseLineHighlightではなく、既定のしきい値(3)と色(#ff000040)を使います。この設定はSandboxのためのものです。設定に従わせるには、resolveDebugConfig(config.debug).looseLineHighlight.thresholdと.color.hexを渡します。
#警告
debug.warningsは、Sandboxの検査パネル(デザイン → 詳細設定 → 警告で編集)にどの執筆時の問題を表示するかを制御します。各キーは独立したブール値の切り替えで、1つをfalseにすると、ほかを無効にせずにその警告だけを止められます。
これらの切り替えが絞り込むのはSandboxのパネルだけです。エンジン自身が記録する警告(doc.warningsに入る、段からあふれるボックス。doc.contentWarningsに入る、不明なリソースid・ディレクティブ・埋め込み・スタイルidと、不規則な表のグリッド)は切り替えにかかわらず記録され、レンダラーはプレースホルダーとして描いた画像を報告します。文書の中の警告を参照してください。
interface WarningsToggleConfig {
missingFont?: boolean;
looseLines?: boolean;
headingHierarchy?: boolean;
consecutiveHeadings?: boolean;
listAfterHeading?: boolean;
designIssues?: boolean;
}| プロパティ | 型 | 既定値 | 説明 |
|---|---|---|---|
missingFont | boolean | true | 設定が参照するフォントをブラウザーで読み込めなかったときに報告します。fontFamilyの打ち間違いや@fontsource/...パッケージの不足を、描画結果で気付かれないままフォールバックフォントに置き換わる前に、早い段階で見つけられます。 |
looseLines | boolean | true | 語間がdebug.looseLineHighlight.thresholdを超える両端そろえの行を報告します。オーバーレイと対になる機能で、警告はパネルに一覧を出し、オーバーレイはその場で示します。 |
headingHierarchy | boolean | true | 段階を飛ばす見出しレベルを報告します(H1の直後にH3が来るなど)。見出しの構造の飛びは、たいてい見出しの深さの打ち間違いか、文書のアウトラインの取り違えを示しています。 |
consecutiveHeadings | boolean | false | 見出しのすぐあとに、段落やリストを挟まずに別の見出しが続くときに報告します。見出しの連続は多くのテンプレートで正当なもの(タイトルとサブタイトル、章とエピグラフ)なので、既定ではオフです。すべての見出しが本文を導入するはずの原稿ではオンにします。 |
listAfterHeading | boolean | false | 導入の段落なしで、見出しの直後にリストが始まるときに報告します。参考資料ではよくあることなので、既定ではオフです。すべてのリストを文章で導入すべき物語的な文章ではオンにします。 |
designIssues | boolean | true | デザインスロットの整合性の問題を報告します。対象は、ページのヘッダー、フッター、部扉とそのあとの白の偶数ページ、目次の部の行、見出しの詳細デザインのスロット、各見出しスタイルのデザインとセクションの柱です。循環するアンカーの連鎖、宙に浮いたアンカー参照(存在しなくなった#idにアンカーした要素)、breakBeforeが無効なページ全幅の見出し、要素がをまったく描かない有効な詳細デザインを検出します。 |
debug: {
warnings: {
missingFont: true,
looseLines: true,
headingHierarchy: true,
consecutiveHeadings: true,
listAfterHeading: false,
designIssues: true,
},
}これらのほかに、パネルには、レイアウト自体が出す警告(VDTDocument.warnings)、たとえば段からあふれる囲み(calloutOverflow)と、エンジンが置き換えた設定値(VDTDocument.configWarnings、またはcollectConfigWarnings(config)。後述の設定の警告を参照)が常に表示されます。
#設定の警告
設定そのものにある次の6種類の誤りは、決して黙って処理されず、どの切り替えでも隠せません。エンジンはどれについても失敗せず、値を置き換えるか設定を除外し、そのことを報告します:
- 不明な番号形式:番号付きリストの
numberFormat、page.pageNumbering.format、リソースタイプのcounterFormatのいずれかが、番号書式の表記のどれでもない場合です。10進数で番号を振ります。 - フォントファミリーにフォントスタックを指定:
fontFamily(または任意の…FontFamily)がCSSのフォントスタックを持つ場合です。テキストはスタックの最初のファミリーで組まれます(fontFamilyには1つのファミリーを参照)。 - 範囲外のサイド段:
'oneAndHalf'レイアウトのsideColumnPercent(文書のもの、または見出しスタイル自身のlayoutのもの)が、いずれかの段を内容幅の1%未満にしてしまう場合、または数値でない場合です。段は両方が取れる最も近い値で分けられ、usedがその値を示します(sideColumnPercentClamped。'oneAndHalf'レイアウトを参照)。 - 大きすぎる文字グリッド:
cjk.gridの1行の字数または1ページの行数が、余白の内側に収まる数より多い場合です。グリッドは収まる最大の数で組まれ、usedがその数を示します(cjkGridClamped。文字グリッドを参照)。 - 不明な設定:
headings、headings.balancing、見出しレベル、見出しスタイル、段落スタイルにないキーです。たとえば綴りを誤ったletterSpacng、別のツールから持ち込んだtracking、見出しスタイルに付けたlevel、段落スタイルに付けたfontStyle: 'italic'(段落スタイルはitalic: trueを取ります)などです。エンジンはこれを無視します(postext 1.4までは何も知らせずに無視していました)。valueはそのキー、usedは空で、suggestionは、1、2文字の違いか大文字小文字の違いだけで済む設定があれば、その最も近い設定の名前を示します(unknownConfigKey)。 - 不明な設定値:いくつかの語のうち1つを取る設定に、別の値が入っている場合です。たとえば
direction: 'right'です(auto、ltr、rtlのいずれかを取ります)。エンジンは代わりに既定値を読み、usedがその結果を示します。directionなら文書の言語の方向になります(unknownConfigValue)。
Sandboxはこれらを検査パネルに、設定のパスとともに表示します。コードでは、buildDocumentが文書にconfigWarningsとして付け(設定に問題がなければ存在しません)、collectConfigWarnings(config)はレイアウトを行わずにこれらを返します:
import { buildDocument, collectConfigWarnings } from 'postext';
// Plain JavaScript: in TypeScript, 'roman' does not type-check to begin with.
const config = { bodyText: { fontFamily: 'EB Garamond, serif' }, orderedLists: { numberFormat: 'roman' } };
const doc = buildDocument({ markdown }, config);
doc.configWarnings;
// [{ kind: 'fontFamilyStack', path: 'bodyText.fontFamily', value: 'EB Garamond, serif', used: 'EB Garamond' },
// { kind: 'unknownNumberFormat', path: 'orderedLists.numberFormat', value: 'roman', used: 'arabic' }]
collectConfigWarnings(config); // the same list入れ子になった部分的な設定もすべて検査されます。見出しスタイル、部の中のリスト、htmlViewer.overrides、デザインの要素が対象です。
formatWarning(文書の中の警告を参照)もこれらを説明し、設定のパスを先頭に置きます(bodyText.fontFamily: font stack "EB Garamond, serif" — set in "EB Garamond"、headingStyles[0].letterSpacng: unknown setting "letterSpacng" — ignored (did you mean "letterSpacing"?))。そのため、ホストはビルドが返す3つの一覧すべてを1つのループで記録できます。
#プログラムからの利用
推奨する方法:Web Workerを使う。ブラウザーでの統合は、ほぼすべての場合、メインスレッドで
buildDocumentを直接呼ぶのではなく、postext/workerのcreateLayoutWorker()を通して組版パイプラインを動かすべきです。ワーカーを使えば、ビルド中もUIが応答し続け、差分の再ビルドをまたいでテキストの計測結果がキャッシュされ、「最後の要求が勝つ」キャンセル(last-wins)が組み込まれるので、新しいキー入力が実行中の古いビルドを中止します。標準の手順はWeb Workerでレイアウトを実行するにまとめてあります。この節の残りの内容(buildDocumentの直接呼び出し、リゾルバー、既定値の除去関数、キャッシュ)も役に立ちます。ワーカーはまったく同じ入力と出力を公開しているからです。ただし、UIのコードではワーカーのラッパーから始めるのが正解です。メインスレッドでbuildDocumentを呼ぶのは、1回きりの書き出し、サーバーサイドでの描画(Node)、テストに限ってください。
#文書のビルド
buildDocument関数は組版パイプライン全体を実行し、すべての要素の正確な座標を持つ仮想文書ツリー(Virtual Document Tree、VDT)を返します。これが最も低レベルの入口です。UIのコードではWeb Workerのラッパーを使ってください。ラッパーは専用のワーカースレッドの中で、同じ引数でbuildDocumentを呼びます。
import { buildDocument } from 'postext';
const content = {
markdown: '# Chapter One\n\nThe story begins here...',
};
const config = {
page: { sizePreset: '17x24' },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 9, unit: 'pt' } }, // 9 pt overrides the 8 pt default
};
// Build the layout — produces a VDT with one entry per page in `vdt.pages`
const vdt = buildDocument(content, config);
console.log(`Document has ${vdt.pages.length} pages`);#文書の中の警告
buildDocumentは、誤った参照や未知のスタイルがあっても止まりません。代わりの値を当て、何をしたかをdoc.contentWarningsに記録します。レイアウトがやむを得ず配置したボックスはdoc.warningsに入ります。こちらはpostext 1.4のときの形を保っており、各項目はpageIndex、columnIndex、overflowPxを持つcalloutOverflowです。報告することがなければ、それぞれのフィールドは存在しません。どの項目にもkindがあります。内容に関する種類は、その構文のソース範囲(渡したMarkdown内のオフセットであるsourceStart / sourceEnd。フロントマターも含めて数えます)と、構文がページに配置された場合はそのpageIndexを持ちます。
| 種類 | 発生する条件 | 出力の扱い |
|---|---|---|
calloutOverflow | :::calloutのボックスがどの段にも収まらず、どこで切っても分割できない。 | それでも配置され、段からoverflowPxだけはみ出します(pageIndex / columnIndexの位置)。doc.warningsに入る唯一の種類で、以下の種類はdoc.contentWarningsに入ります。 |
unknownResourceId | ::resourceの埋め込み(usage: 'embed')、インラインの:ref('ref')、表のセルの画像('cellImage')が、どのリソースにもないidを指している。 | 埋め込みは省かれ、参照は番号もリンクもなしに?(またはtext=のラベル)を印字し、セルはテキストだけになります。inResourceは、その参照をキャプション、注、セルに含むリソースを示します。 |
unknownDirective | 名前がディレクティブでもコンテナーでもない:::nameの行。 | その行はテキストとして組まれます。 |
malformedEmbed | 単独で立つ正しい形の埋め込みになっていない::nameの行。idが引用符なしか一重引用符で書かれている、または別の属性を持つ::resourceや、空行を挟まずに段落の下に続けて書かれた行です。 | その行はテキストとして組まれます。 |
fullwidthMarkup | 中国語や日本語の入力方式で入力されたマークアップを含む行。:::のフェンス、#の見出し、[^…]の脚注記号、フェンスや見出しの後の{…}属性、**…**の太字が対象です。typedは書かれたままのマークアップ、asciiは入力すべき形です。1行につき1件。 | その行はテキストとして組まれ、何も変換されません。 |
attributeKeyInvalid | 属性のキーにASCII以外の文字が含まれている(作者=曹雪芹)。キーの位置を指します。 | その属性は無視されます。 |
unknownParagraphStyle | :::paragraphsstyleの名前に該当する段落スタイルがない。 | 段落は本文として組まれます。 |
unknownCalloutType | :::callouttypeの名前がcalloutStylesのどれにも該当しない。囲みスタイルが1つ以上設定されているときにだけ発生します。 | ボックスには最初の囲みスタイルが使われます。 |
unknownChipStyle | :chip[…]styleの名前に該当するチップスタイルがない。 | チップには最初のチップスタイルが使われます。 |
undefinedFootnote | どの[^id]:段落でも定義されていない脚注記号[^id](idはその注のid)。 | 番号は印字され、注は空になります。 |
unusedFootnote | どの記号からも参照されていない脚注定義[^id]:。 | 注は組まれません。 |
indexMarkInvalid | 語のない索引マーク。:index、または角かっこのテキストを持たないマークにtermのない属性を付けたものです。 | そのマークは何も索引に載せません。 |
indexSeeUnknown | seeまたはseealsoの参照先(target)が、その索引(index。主索引は'')の項目にない。:::indexの行を指します。 | 相互参照はそのまま印字されます。 |
indexRangeUnclosed | 対応するrange="end"のないrange="start"のマーク、またはその逆(missingは欠けている側、termは項目を示します)。:::indexの行を指します。 | 範囲はその1ページだけを印字します。 |
unknownHeadingStyle | 見出しのstyle="…"の名前に該当する見出しスタイルがない(levelはその見出しのレベル)。 | 見出しとその節は、そのレベル自体の設定のままになります。 |
unknownTableStyle | 表リソースのtable.styleIdがtableStylesのどの項目にも該当しない。 | 表はtableStyleで組まれます。 |
raggedTableGrid | 結合を数に入れると、表のグリッドが長方形にならない(表モデルの構築を参照)。 | セルが結合部分にずれ込むか、穴が残ります。reason('spanOverlap' / 'missingCells')、row、colが最初の問題の位置を示し、countが問題の数を示します。 |
リソースに関する警告(表スタイル、グリッド、キャプションや注、セルの中の参照)は、本文の中でそのリソースが最初に埋め込まれた位置か参照された位置を指し、リソースごとに1回だけ挙がります。検査するのは文書が使うリソースだけです。本の1章は、本が持つすべての表ではなく、その章が参照する表について報告します。
import { buildDocument, formatWarning } from 'postext';
const doc = buildDocument({ markdown: 'See :ref{id="fig-map"}.\n\n:::sidebar\nNotes.' }, config);
for (const w of [...(doc.warnings ?? []), ...(doc.contentWarnings ?? []), ...(doc.configWarnings ?? [])]) console.warn(formatWarning(w));
// Unknown resource id "fig-map" in :ref — it prints "?" (or its text= label), with no number or link (page 1, offset 4)
// Unknown directive ":::sidebar" — the line is set as text (page 1, offset 25)
// Narrow on `kind` to read the fields of a kind.
const missing = (doc.contentWarnings ?? []).flatMap((w) => (w.kind === 'unknownResourceId' ? [w.resourceId] : []));formatWarning(w)は英語の説明を1行で返します。メッセージをローカライズするホストは、代わりにkindで分岐してください。マイナーリリースで種類が増えることがあるので、既定の分岐も残しておきます。collectContentWarnings(markdown, config, resources)は、何もレイアウトせずに内容の警告を返します(ビルドが加えるのと同じリストで、pageIndexはありません)。入力しながらテキストを検査するエディター向けです。collectHeadingDesignCuts(doc)は、組み上がったレイアウトを調べ、見出しデザインのテキストがページや段の下端を越えているものを探します(kind: 'headingDesignCut'。確保する高さを参照)。これはレイアウト自体は報告しないもので、その結果もformatWarningで説明できます。Sandboxの検査パネルは、これらをすべて一覧にします。
レンダラーは、指定どおりに描けなかったものをonWarningオプションで報告します。対象はrenderPageToCanvas、renderPage、renderToCanvas(RenderPageOptions)、renderToHtmlとrenderToHtmlIndexed(RenderHtmlOptions)、renderToPdf(RenderToPdfOptions。PDFワーカー経由でも可)です。描画時の種類は現在1つだけで、missingImageです。描くものがない画像(図、表のセルの画像、囲みのアイコン、デザイン画像)は中立的なプレースホルダーとして描かれ、fileIdと描画呼び出しごとに1回報告されます。報告にはpageIndex、描画側がわかる場合はresourceId(図とセルの画像)、PDFでは複数文書の描画におけるdocumentIndexが含まれます。描くものがないとは、canvasではそのfileIdのregisterResourceImageがないこと、HTMLではresourceImageUrlからURLが得られないこと、PDFではresourceBytesからバイト列が得られないか、得られたバイト列をデコードできないことを指します。fileIdをまったく持たないビットマップやSVGのリソースは、要求するものがないので、報告なしにプレースホルダーとして描かれます。描画時の警告はVDTには保存されません。ホストが用意できるものはレイアウトの後で変わるからです。
import { buildDocument, renderPage, type RenderWarning, type Resource } from 'postext';
const map: Resource = {
id: 'fig-map', typeId: 'figure', kind: 'bitmap', caption: 'The route.', createdAt: 0, updatedAt: 0,
bitmap: { fileId: 'map-file', format: 'png', width: 1200, height: 800 },
};
const doc = buildDocument({ markdown: 'See :ref{id="fig-map"}.', resources: [map] }, config);
const warnings: RenderWarning[] = [];
const canvas = renderPage(doc.pages[0], doc, { onWarning: (w) => warnings.push(w) });
// Until 'map-file' is registered with registerResourceImage:
// [{ kind: 'missingImage', fileId: 'map-file', resourceId: 'fig-map', pageIndex: 0 }]#ページをビットマップに描画する
各ページは個別にラスタライズできます。renderPage(page, doc)を使うと、指定したページのHTMLCanvasElementが得られます。このcanvasはページの寸法(設定したDPIでのピクセル数)ちょうどの大きさのビットマップなので、表示や書き出しに使ったり、任意の画像処理に渡したりできます。
import { buildDocument, renderPage } from 'postext';
const vdt = buildDocument(content, config);
// Render page 3 (zero-indexed) to a bitmap canvas
const pageNumber = 2;
const page = vdt.pages[pageNumber];
if (!page) throw new Error(`Page ${pageNumber} does not exist`);
const canvas = renderPage(page, vdt);
// canvas.width / canvas.height are the page bitmap size in pixels
// Show it in the DOM
document.body.appendChild(canvas);
// …or export it as a PNG data URL
const pngDataUrl = canvas.toDataURL('image/png');
// …or get a Blob for download / upload
canvas.toBlob((blob) => {
if (blob) saveAs(blob, `page-${pageNumber + 1}.png`);
}, 'image/png');
// …or grab raw RGBA pixels
const ctx = canvas.getContext('2d')!;
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);すでに持っているcanvas(たとえば特定のレイアウトでDOMに配置したもの)に描きたい場合は、renderPageToCanvas(page, doc, canvas)を使います。新しいcanvasを作る代わりに、渡したcanvasの大きさを変えて描画します。
全ページを描画するにはvdt.pagesを順に処理します。
const bitmaps = vdt.pages.map((page) => renderPage(page, vdt));ライブサンプル:ページを画像にする
ここまでの内容をブラウザーで動かしたものです。このCodePenのサンプルは、最新のpostextリリースをCDNからインポートし、Webフォントを待ってから短い2段組みの文書をレイアウトし、最初のページをcanvasに描いて、そのビットマップをPNGとして提供します。CodePenで実行を押すとエディターが読み込まれ、Markdownや設定を変更できます。編集するたびにページが描き直されます。
import { buildDocument, renderPage } from 'https://esm.sh/postext';
const markdown = `# The Lantern
The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
## Two columns
Postext lays this text out in **two columns**, breaking each paragraph with the *Knuth–Plass* algorithm and hyphenating with TeX patterns. Widows and orphans are avoided, and the columns are balanced on the last page.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.`;
const config = {
// 150 dpi: crisp enough for a preview, light enough to paint instantly.
page: { sizePreset: '17x24', dpi: 150 },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// Postext measures text with the fonts the browser has loaded,
// so wait for every face the document uses before laying it out.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
// The whole layout: one entry per page in doc.pages, with exact coordinates.
const doc = buildDocument({ markdown }, config);
// Rasterise the first page. The canvas is sized to the page at the configured dpi.
const canvas = renderPage(doc.pages[0], doc);
document.getElementById('page').replaceChildren(canvas);
document.getElementById('status').textContent =
`${doc.pages.length} page(s) · page 1 is ${canvas.width} × ${canvas.height} px`;
// The same bitmap as a PNG file.
canvas.toBlob((blob) => {
const link = document.getElementById('download');
link.href = URL.createObjectURL(blob);
link.hidden = false;
}, 'image/png');index.html
<p id="status">Laying out…</p>
<a id="download" download="page-1.png" hidden>Download page 1 as PNG</a>
<div id="page"></div>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
#page canvas {
display: block;
max-width: 100%;
height: auto;
margin-top: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}codepen.ioからインタラクティブなエディターを読み込みます。サンプルは最新リリースのpostextをCDNから読み込みます。
#React
postext/reactはcreateLayout(content, config?)を公開しています。これはマウント時に文書を1回だけレイアウトし、各ページを<div>の中の<canvas>として表示するコンポーネントです。
import { createLayout } from 'postext/react';
const Article = createLayout(
{ markdown: '# Hello\n\nThe first paragraph of the article.' },
{ page: { sizePreset: '17x24' } },
);
export function ArticlePage() {
return <Article className="pages" style={{ maxWidth: 480 }} />;
}- メインスレッドで1回だけ。ページは文書の解像度で描かれ、コンテナーの幅に合わせて拡大縮小されます。
contentとconfigはcreateLayoutを呼んだ時点で固定されるので、別の内容を表示するには別のコンポーネントを作ります。ライブプレビューには、Web Workerでビルドし、そこにあるReactの例のようにrenderPageToCanvasで描画してください。 - フォントと画像を先に。コンポーネントをマウントする前に文書のWebフォントを読み込み、画像を
registerResourceImageで登録しておきます。Markdownに$が含まれていれば、コンポーネントが自分で数式エンジンを起動します。 - Reactはメインのエントリーに含まれない。
postextはReactをインポートせず、インポートするのはpostext/reactだけです。既存のコードが動き続けるよう、createLayoutは今もpostextから公開されていますが、非推奨です。呼び出したときにpostext/reactを読み込み、それが届くまでコンポーネントはサスペンドします(届けばReactが自動で再描画します)。postext/reactからインポートしてください。 - 非推奨のコンポーネントはサスペンドする。
postext/reactが届くまで、postextのcreateLayoutには、コンカレントルート(createRoot)か、上位の<Suspense>境界が必要です。レガシーのReactDOM.renderルートやrenderToStringで境界がなければ、Reactはエラーを報告します。バンドラーがこの遅延インポートを解決できるよう、reactは引き続き必須のピア依存関係です。
#既定値の解決
リゾルバー関数は、部分的な設定オブジェクトに既定値を補います。調べたり比べたりするために完全な設定が必要なときに便利です。
import { resolvePageConfig, resolveBodyTextConfig } from 'postext';
const fullPage = resolvePageConfig({ sizePreset: '21x28' });
// => { sizePreset: '21x28', width: { value: 21, unit: 'cm' }, height: { value: 28, unit: 'cm' },
// margins: { top: { value: 2, unit: 'cm' }, ... }, dpi: 300, cutLines: { enabled: false, ... }, ... }
const fullBody = resolveBodyTextConfig({ fontFamily: 'Inter' });
// => { fontFamily: 'Inter', fontSize: { value: 8, unit: 'pt' }, lineHeight: { value: 1.5, unit: 'em' }, ... }リゾルバーはトップレベルの節ごとに1つあります。resolvePageConfig、resolveLayoutConfig、resolveBodyTextConfig、resolveHeadingsConfig、resolveHeadingStylesConfig、resolveTocConfig、resolvePartsConfig、resolveUnorderedListsConfig、resolveOrderedListsConfig、resolveMathConfig、resolveTableStyleConfig、resolveCaptionStyleConfig、resolveDiagramStyleConfig、resolveParagraphStylesConfig、resolveCalloutStylesConfig、resolveHeaderFooterConfig、resolveDebugConfig、resolveHtmlViewerConfig、resolvePdfGenerationConfig。これに加えて、デザインスロット1つを解決するresolveDesignSlotがあります。カラーパレットは、applyPaletteToConfig(config)、applyPaletteToResolvedConfig(resolved, palette)、resolveColorValue(value, palette, fallback)で別に適用します。カラーパレットを参照してください。
既定値が別の節から引き継がれるリゾルバーは、解決済みのその節を追加の引数に取ります。リストのfontFamilyとcolorの既定値は本文から引き継がれるので、resolveUnorderedListsConfigとresolveOrderedListsConfigは解決済みの本文を取ります。resolveCalloutStylesConfigは解決済みの本文、見出し、箇条書きリストを取り(囲みスタイルの例を参照)、resolveHeadingStylesConfigは解決済みのページ、本文、2つのリストの節を取ります。それぞれの正確なシグネチャーは、パッケージの型宣言で確認してください。
import { resolveBodyTextConfig, resolveUnorderedListsConfig } from 'postext';
const body = resolveBodyTextConfig({ fontFamily: 'Inter' });
const lists = resolveUnorderedListsConfig({ bulletChar: '—' }, body);
// => lists.fontFamily === 'Inter' (inherited)静的な既定値(引き継ぎが関わらないときに使われる値)も公開されています。DEFAULT_PAGE_CONFIG、DEFAULT_CUT_LINES、DEFAULT_PAGE_NUMBERING、PAGE_SIZE_PRESETS、DEFAULT_LAYOUT_CONFIG、DEFAULT_COLUMN_RULE、DEFAULT_COLUMN_BALANCING、DEFAULT_BODY_TEXT_CONFIG、DEFAULT_HYPHENATION_CONFIG、DEFAULT_HEADINGS_CONFIG、DEFAULT_UNORDERED_LISTS_STATIC、DEFAULT_ORDERED_LISTS_STATIC、DEFAULT_PARAGRAPH_STYLES、DEFAULT_CALLOUT_STYLES、DEFAULT_CALLOUT_STYLE_STATIC、DEFAULT_PARTS_CONFIG、DEFAULT_HEADING_STYLES、DEFAULT_TOC_CONFIG、DEFAULT_MATH_CONFIG、DEFAULT_DIAGRAM_STYLE_CONFIG、DEFAULT_DEBUG_CONFIG、DEFAULT_HTML_VIEWER_CONFIG、DEFAULT_PDF_GENERATION_CONFIG、DEFAULT_COLOR_PALETTE、DEFAULT_MAIN_COLOR、DEFAULT_MAIN_COLOR_ID、DEFAULT_MAIN_COLOR_NAME、DEFAULT_MAIN_COLOR_HEX、柱とノンブルの要素の既定値(DEFAULT_HEADER_FOOTER_SLOT、DEFAULT_HEADER_SLOT、DEFAULT_FOOTER_SLOT、DEFAULT_TEXT_ELEMENT、DEFAULT_RULE_ELEMENT、DEFAULT_BOX_ELEMENT)、そしてロケールに応じたdefaultResourceTypes(locale)です(リソースの種類を参照)。
#既定値の除去
設定を保存するとき(localStorageやファイルなど)は、stripConfigDefaultsで既定値と一致する値を取り除きます。保存する設定が最小限になり、意図して変更した値だけが残ります。
import { stripConfigDefaults } from 'postext';
const minimal = stripConfigDefaults(fullConfig);
// Only properties that differ from defaults remainリゾルバーごとに個別の除去関数もあります。stripPageDefaults、stripLayoutDefaults、stripBodyTextDefaults、stripHeadingsDefaults、stripHeadingStylesDefaults、stripTocDefaults、stripPartsDefaults、stripUnorderedListsDefaults、stripOrderedListsDefaults、stripMathDefaults、stripTableStyleDefaults、stripCaptionStyleDefaults、stripDiagramStyleDefaults、stripParagraphStylesDefaults、stripCalloutStylesDefaults、stripHeaderFooterDefaults、stripDesignSlotDefaults、stripDebugDefaults、stripHtmlViewerDefaults、stripPdfGenerationDefaults。
#解析
エンジンは、Markdownのトークナイザーとフロントマターの読み取り関数を公開しています。ビルドの前に文書を調べたり、Postextが見るのと同じブロック構造をほかのツールに渡したりするのに使えます。
import { parseMarkdown, extractFrontmatter } from 'postext';
const source = '---\ntitle: Chapter One\n---\n\n# Opening\n\nThe story begins here.';
const { metadata, content } = extractFrontmatter(source);
// metadata.title === 'Chapter One'
const blocks = parseMarkdown(content);
// => [ { type: 'heading', level: 1, text: 'Opening', … },
// { type: 'paragraph', text: 'The story begins here.', … } ]Postextが認識するMarkdownの構文の全一覧は、文書形式のページを参照してください。
#計測キャッシュ
テキストの計測は、レイアウトの中で負荷の高い処理です。2種類のキャッシュがこれを軽くしており、クリアの方法はそれぞれ異なります。
- 自分で持つブロックキャッシュ。
createMeasurementCache()はMeasurementCacheを返します。これは計測したすべての段落を、テキスト、フォント、幅、改行のオプション、有効なハイフネーション辞書をキーとして記憶します。buildDocument(またはbuildDocumentAsync)の3番目の引数に渡すと、収束パスの間やビルドの間で計測結果を再利用できます。キー入力のたびに文書をレイアウトするエディターなら、変更された段落だけを計測すれば済みます。キャッシュがなければ、パスのたびにすべてのブロックを計測し直します。キャッシュから読んだ段落は新たに計測した段落と同じなので、キャッシュを使ったビルドは、使わないビルドとすべての行を同じように組みます。postext 1.4.1では、キャッシュした段落から最終行がラントであるという印が失われ、ラントの詰めや段末そろえで違う改行になることがありました。レイアウトのワーカーはビルドをまたいで1つのキャッシュを持ち、フォントが変わると新しいものに置き換えます。 - グローバルな幅のキャッシュ。語の幅は、ページ内のすべてのビルドが共有するモジュールの状態に、フォント文字列ごとにキャッシュされます。pretextも独自のキャッシュを持っています。テキストを代替フォントで計測した後にWebフォントが届くと、これらは古くなります。
import { buildDocument, createMeasurementCache, clearMeasurementCache } from 'postext';
import type { MeasurementCache } from 'postext';
let cache: MeasurementCache = createMeasurementCache();
let doc = buildDocument(content, config, cache);
// A web font finished loading: widths measured with the fallback are stale.
await document.fonts.ready;
clearMeasurementCache(); // no argument: clears the global width caches
cache = createMeasurementCache(); // a block cache has no clear(); start a new one
doc = buildDocument(content, config, cache);clearMeasurementCache()は引数を取らず、MeasurementCacheには触れません。そのブロックも古い幅で計測されているので、破棄して新しいものを作ってください。フォントの読み込み後にクリアせずに再ビルドしても、代替フォントで計測したのと同じ改行になります。
テキストを少しずつ計測するアプリケーション向けに、cachedMeasureBlock(text, font, maxWidthPx, lineHeightPx, options, cache)とcachedMeasureRichBlock(spans, normalFont, boldFont, italicFont, boldItalicFont, maxWidthPx, lineHeightPx, options, cache)があります。measureBlockとmeasureRichBlockの引数に加えて、最後にキャッシュを取ります。
#1つのページで共有されるグローバルな状態
Postextの状態の一部は、モジュールレベルの変数にあります。同じJavaScriptのレルム(realm)でpostextをインポートするものは、すべてこれを共有します。ページとそのスクリプトは1つのコピーを共有し、iframeやワーカーはそれぞれ自分のコピーを持ちます。1ページに1文書なら問題は起きません。1ページに複数の文書がある場合(2つのライブプレビュー、サンプルのギャラリーなど)は影響が出ます。
- リソースの画像。
registerResourceImage(fileId, image)は、fileIdをキーとする1つの登録簿に書き込み、renderPageとrenderPageToCanvasがそこから読みます。2つの文書がどちらもfigure.svgを登録すると、その項目を共有し、最後の登録が両方に適用されます。ファイルidには文書ごとの接頭辞を付け、文書が不要になったらunregisterResourceImage(fileId)かclearResourceImages()を呼んでください。canvasバックエンドがキャッシュするラスターも同じキーで管理され、画像と一緒に破棄されます。 - テキストの計測。計測した幅は、フォント文字列とテキストごとにレルム全体でキャッシュされます。Webフォントの読み込み完了前に計測したテキストは、
clearMeasurementCache()(引数なし)がキャッシュを空にするまで、どの文書でも代替フォントの幅のままです。フォントが読み込まれたらこれを1回呼び、ビルドし直してください。 - 解決済みの設定。設定オブジェクトはそれぞれ1回だけ解決され、その結果がオブジェクトに結び付けてキャッシュされます。設定をその場で書き換えてビルドし直すと、古い設定でレイアウトされます。変更のたびに新しいオブジェクトを渡してください(
{ ...config, … }またはstructuredClone(config))。 - ハイフネーションの言語。ビルドのたびに、プロセス全体のハイフネーション言語が、その文書の
bodyText.hyphenation.localeに設定されます。公開されているhyphenateText(text)とlayoutDesignSlotは、言語を渡さなければ最後のビルドの言語を使います。hyphenateText(text, 'es')のように呼んでください。 - 数式エンジン。MathJaxのエンジンと描画済み数式のキャッシュは、レルムに1つずつです。
initMathEngine()は全員のためにそれを起動します。
最も簡単な分離は、文書ごとにレルムを分けることです。ライブサンプルごとにiframeを使う(CodePenの埋め込みがこれにあたります)か、計測とハイフネーションのために文書ごとにレイアウトのワーカーを使います(画像は引き続きページに登録されます)。
#Web Workerでレイアウトを実行する
ブラウザーでPostextを使うときは、この方法を推奨します。ライブプレビュー、エディター、サイズ変更に追従するビューアー、Sandboxのような試用環境など、対話的なものを作るなら、postext/workerのcreateLayoutWorker()を通してパイプラインを動かしてください。UIのコードでは、メインスレッドでbuildDocumentを直接呼ばないでください。
メインスレッドでbuildDocumentを呼ぶと、パイプライン全体(解析、計測、7つのパス、最大5回の収束の反復)が、呼び出したスレッドで実行されます。1回きりの書き出しならそれで問題ありません。対話的なUIにとっては不適切なスレッドです。150ミリ秒のレイアウトが入力イベントをふさぎ、キー入力が溜まり、スクロールが引っかかります。ワーカーは、その時間をすべてバックグラウンドのスレッドに移します。
Postextには専用のWeb Workerのエントリーポイントpostext/workerがあり、パイプラインをメインスレッドから外します。大半の統合で使われることを想定している方法です。SandboxのCanvas、HTML、PDFのビューポートは、1つのuseLayoutWorkerフック(packages/postext-sandbox/src/worker/useLayoutWorker.ts)を通して同じcreateLayoutWorker()のハンドルを共有し、「最後の要求が勝つ」キャンセルで動かしています。新しいキー入力は、実行中のビルドを終わる前に中止します。
標準的な統合の概要は次のとおりです。
- 作成:ビューポートごとに1回、
createLayoutWorker()でワーカーを作ります。 - フォントの登録:ファミリーごとに1回、
registerFonts(payloads)で転送可能なArrayBufferを送ります。 - ビルド:
build(content, config, { signal })でビルドします。古いビルドをキャンセルできるよう、呼ぶたびに新しいAbortSignalを渡します。 - 置き換え:次のビルドを始める前に、前のビルドのシグナルを中止します。これが「最後の要求が勝つ」パターンです。
- 破棄:ワーカーを所有するコンポーネントがアンマウントされたら、ワーカーを破棄します。
build(...)から返る同じVDTDocumentが、後段のすべてのレンダラーに渡ります。canvasにはrenderPage/renderPageToCanvas、HTMLにはrenderToHtmlIndexed、PDFには(postext-pdfの)renderToPdfです。ビルドはワーカーで1回行い、ラスタライズはメインスレッドでUIが必要とするだけ何度でも行えます。
#ワーカーで得られるもの
- メインスレッドが空いたまま。解析、計測、7パスの収束ループはすべてワーカーの中で動きます。メインスレッドに処理が戻るのは、完成した
VDTDocumentが送り返されるときだけです。 - 「最後の要求が勝つ」キャンセル。
build(content, config, { signal })はAbortSignalをワーカーまで通します。完了前に中止すると、メイン側ではAbortErrorが発生します。ワーカーの中では、パイプラインが次のブロックごとのキャンセル確認点でBuildCancelledErrorを投げ、すぐに止まります。 - ワーカーごとの計測キャッシュ。ワーカーは、存続している間1つの
MeasurementCacheを持ちます。フォント、テキスト、幅が同じ後続のビルドは、キャッシュした行の計測を再利用します。長い文書に1文字入力しても、計測し直すのは入力が実際に変わったブロックだけです。 - メインスレッドと同一の計量値。フォントは転送可能な
ArrayBufferとしてワーカーに送られ、ワーカー自身のFontFaceSetにnew FontFace(...)で登録されます。ワーカーはメインスレッドと同じcanvasのフォント計量値で計測するので、改行と段の高さはバイト単位で一致します。 - 数式のラスターキャッシュがワーカーのビルドをまたいで残る。数式レンダラーは、同一性をキーとするキャッシュに加えて、内容をキーとするラスターキャッシュを持っています。そうしなければ、
MathRenderをワーカーの境界を越えて構造化複製するたびに、再ビルドのたびに同一性のキャッシュが外れてしまいます。
#公開API
ワーカーのクライアントはpostext/workerサブパスにあり、名前はわずかです。
createLayoutWorker(opts?): LayoutWorkerHandle:専用のワーカーを起動し(opts.workerで渡したワーカーを包むか、opts.urlのワーカーエントリーを起動することもできます)、型付きのハンドルを返します。CDNからワーカーを読み込むを参照してください。LayoutWorkerHandle.registerFonts(faces: FontPayload[]): Promise<void>:フォントのバイト列をワーカーに送ります。バッファーは転送されるので、後で送り直す必要があるなら、メインスレッドに新しいコピーを残しておいてください。LayoutWorkerHandle.build(content, config?, { signal? }): Promise<VDTDocument>:パイプラインを実行します。シグナルを中止すると、実行中のビルドがキャンセルされます。LayoutWorkerHandle.dispose(): void:ワーカーを終了し、保留中のビルドをAbortErrorで拒否します。FontPayload:{ family, weight, style, unicodeRange?, buffer: ArrayBuffer }。weightはCSSのウェイトの文字列('700'、'bold')です。registerFontsは数値(700)も受け付けます。bufferは、registerFontsを呼んだときにワーカーへ転送されます。BuildCancelledError(postextから再エクスポート):options.shouldCancelがtrueを返したときにbuildDocumentが内部で投げるものです。メインスレッドでは通常目にしません。ワーカーのプロトコルが、コードに届く前にAbortErrorに変換するからです。
パッケージは、コンパイル済みのワーカースクリプトを指すpostext/worker/entryパスも公開しています。createLayoutWorker()はこのURLを自動で解決します。明示的に参照する必要があるのは、バンドラーが手で組み立てたnew Worker(new URL(...), { type: 'module' })呼び出しを要求する場合か、エントリーを自分で配信する場合(opts.url)だけです。
#最小限の統合
import { createLayoutWorker } from 'postext/worker';
import type { FontPayload, LayoutWorkerHandle } from 'postext/worker';
import type { PostextConfig, VDTDocument } from 'postext';
// 1. Create the worker once and keep the handle for the lifetime of your viewport.
const layout: LayoutWorkerHandle = createLayoutWorker();
// 2. Register fonts once per family (transferable ArrayBuffers).
// getConfigFontFamilies(config) is a helper that lists the families your config will render.
const payloads: FontPayload[] = await collectFontPayloadsForFamilies([
'EB Garamond',
'Open Sans',
]);
await layout.registerFonts(payloads);
// 3. Drive builds with last-wins cancellation: abort the previous signal
// before starting a new build. A stale build is thrown away inside the worker.
let pending: AbortController | null = null;
async function rebuild(
markdown: string,
config: PostextConfig,
): Promise<VDTDocument | null> {
pending?.abort();
pending = new AbortController();
try {
return await layout.build({ markdown }, config, { signal: pending.signal });
} catch (err) {
if ((err as { name?: string } | null)?.name === 'AbortError') return null;
throw err;
}
}
// 4. Dispose when the component that owns the worker unmounts.
// Pending builds reject with AbortError.
layout.dispose();Reactのコンポーネントに包むと、次の形になります。
import { useEffect, useRef } from 'react';
import { createLayoutWorker } from 'postext/worker';
import type { LayoutWorkerHandle } from 'postext/worker';
import { renderPageToCanvas } from 'postext';
import type { PostextConfig } from 'postext';
export function CanvasPreview({
markdown,
config,
}: {
markdown: string;
config: PostextConfig;
}) {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const workerRef = useRef<LayoutWorkerHandle | null>(null);
const pendingRef = useRef<AbortController | null>(null);
// Mount: spin up the worker and ship the fonts once.
useEffect(() => {
const handle = createLayoutWorker();
workerRef.current = handle;
(async () => {
const payloads = await collectFontPayloadsForFamilies(
getConfigFontFamilies(config),
);
await handle.registerFonts(payloads);
})();
return () => {
pendingRef.current?.abort();
handle.dispose();
};
}, []); // fonts registered once; re-register only when the family set changes
// Every keystroke or config change: supersede the in-flight build and kick a new one.
useEffect(() => {
const handle = workerRef.current;
if (!handle) return;
pendingRef.current?.abort();
const ac = new AbortController();
pendingRef.current = ac;
(async () => {
try {
const vdt = await handle.build({ markdown }, config, { signal: ac.signal });
const canvas = canvasRef.current;
if (!canvas || !vdt.pages[0]) return;
renderPageToCanvas(vdt.pages[0], vdt, canvas); // rasterise on the main thread
} catch (err) {
if ((err as { name?: string } | null)?.name !== 'AbortError') throw err;
}
})();
}, [markdown, config]);
return <canvas ref={canvasRef} />;
}パターンは常に同じです。作成は1回、フォントの登録も1回、AbortSignal付きのビルドは何度でも、アンマウントで破棄。
#CDNからワーカーを読み込む
ワーカーのスクリプトはページと同じオリジンから来なければなりません。そのため、CDNが配信するpostext/workerのコピーは、隣にあるlayout.worker.jsファイルを起動できません。createLayoutWorker()がこれに対処します。
- esm.shならオプションなし。
postext/worker自体がesm.shから読み込まれている場合(モジュールのURLがhttps://esm.sh/postext@1.5.0/es2022/worker.mjsのような形)、クライアントは、対応するhttps://esm.sh/postext@1.5.0/worker/entryを、それをインポートするだけの1行の同一オリジンのblobモジュールを通して起動します。?deps=、?external=、?alias=を付けたインポートや、https://esm.sh/*postext@1.5.0/workerの形でも同じです。ワーカーには外部の依存関係を解決するインポートマップがないので、常にそのバージョンの素のビルドが使われます。 - ほかのサーバーなら
urlを指定。jsDelivr(/+esm)やunpkgなど、ほかのCDNは検出されません。createLayoutWorker({ url })はurlにあるワーカーエントリーのモジュールを起動します。同一オリジンのURLは直接、別オリジンのURLは同じblobのラッパーを通して起動します。そのサーバーはクロスオリジンのリクエスト(CORS)を許可している必要があります。 - バンドラーでは何も変わらない。Vite、webpack、Next.jsでは、引き続きオプションなしで
createLayoutWorker()を呼んでください。バンドラーがワーカーをアプリのチャンクとして出力します。
import { createLayoutWorker } from 'https://esm.sh/postext/worker';
const layout = createLayoutWorker();
const face = async (weight, style) => ({
family: 'EB Garamond',
weight,
style,
buffer: await (await fetch(`https://cdn.jsdelivr.net/npm/@fontsource/eb-garamond@5/files/eb-garamond-latin-${weight}-${style}.woff2`)).arrayBuffer(),
});
await layout.registerFonts(await Promise.all([face(400, 'normal'), face(700, 'normal'), face(400, 'italic')]));
const doc = await layout.build({ markdown }, { bodyText: { fontFamily: 'EB Garamond' } });ワーカーにはページのフォントが見えません。ワーカーは独自のフォントセットを持ち、そこにあるのはregisterFontsで送ったフェイスと、システムにインストールされたフォントだけです。ワーカーが見つけられないファミリーでテキストを組むビルドでは、そのテキストは代替フォントで計測されるため、改行がページと一致しません。このときクライアントは、ファミリーごとに1回コンソールに警告を出し("EB Garamond" is not available inside the layout worker…)、そのファミリーをBuildStats.missingFontsに挙げます。これはbuildのonStatsコールバックが受け取ります。
#フォントペイロードの収集(Fontsource / Google Fonts)
registerFontsはフォントの生のバイト列を受け取ります。取得するのはメインスレッドが適しています。Google Fontsはブラウザーらしいユーザーエージェント文字列にしかWOFF2を返さないこと、また中央のキャッシュがあれば複数のワーカーインスタンスで同じバイト列を共有できることが理由です。
SandboxのcollectFontPayloadsForFamilies(packages/postext-sandbox/src/controls/fontLoader.ts)は、そのまま使える参考実装です。次の処理を行います。
https://api.fontsource.org/v1/fonts/{family-id}に問い合わせ、利用できるウェイトと、ファミリーが可変軸を持つかどうかを調べます。- ファミリーが提供するすべてのウェイトとスタイルを含むGoogle FontsのCSS2のURLを組み立てます。
- 生成された
@font-faceのスタイルシートを取得し、各src: url(...) format('woff2')宣言を抜き出して、生のバイト列をダウンロードします。 - 呼び出しごとに
bufferが新しいArrayBufferになっているFontPayload[]を返します。registerFontsはバッファーを転送し、送信側のコピーを切り離された(detached)状態にするので、これが重要です。
getConfigFontFamilies(config)と組み合わせると、あるPostextConfigが実際に描画するファミリー(本文、見出し、リストの記号、番号付きリストの番号)の一覧が得られます。
#エンジン内部の協調的キャンセル
buildDocumentを自分で動かす場合(たとえば独自のワーカーの中で)、パイプラインが公開しているshouldCancelフックを直接使えます。
import { buildDocument, BuildCancelledError } from 'postext';
let superseded = false;
try {
const vdt = buildDocument(content, config, cache, {
shouldCancel: () => superseded,
});
} catch (err) {
if (err instanceof BuildCancelledError) return; // a newer build took over
throw err;
}shouldCancelは、配置の間、トップレベルのブロックごとに1回呼ばれます。このフックは意図的に協調的な仕組みになっています。pretext自体のレイアウト呼び出しを行の途中で止めることはできませんが、キャンセルの粒度を十分小さく(ミリ秒単位に)保つので、速く入力するユーザーが古いビルドを待たされることはありません。
#ワーカーからのPDF書き出し
PDFバックエンドは、できあがったVDTDocumentを受け取ってPDFのバイト列にします。レイアウトを再実行することはありません。そのため、ブラウザーでの標準的なPDFの流れはワーカーときれいに組み合わさります。VDTをワーカーで(メインスレッドの外で、キャンセル可能に、キャッシュを再利用して)ビルドし、同じVDTに対してメインスレッドでrenderToPdfを呼びます。
import type { LayoutWorkerHandle } from 'postext/worker';
import { renderToPdf } from 'postext-pdf';
import type { PostextConfig } from 'postext';
import { createPdfFontProvider } from './pdfFontProvider';
const fontProvider = createPdfFontProvider();
export async function exportPdf(
layout: LayoutWorkerHandle,
markdown: string,
config: PostextConfig,
): Promise<Uint8Array> {
// 1. Build the VDT in the worker — UI stays responsive during the layout passes.
const vdt = await layout.build({ markdown }, config);
// 2. Rasterise to PDF on the main thread. renderToPdf is fast once the VDT exists
// because it is walking precomputed coordinates, not remeasuring text.
return renderToPdf(vdt, {
fontProvider,
// pdfGeneration config on `vdt.config` is honoured automatically.
});
}ライブプレビュー用のワーカーのハンドルをすでに持っているなら、2つ目のワーカーを起動せずに、書き出しにもそれを再利用してください。ワーカーの中の計測キャッシュのおかげで、画面のプレビューに続くPDFの書き出しには、ほとんど費用がかかりません。
長い本では、PDFそのものの書き出しにも数秒かかります。postext-pdf/workerは、その段階を専用のワーカーで実行します(ワーカーでPDFを描画するを参照)。
#ワーカーを使う場面、使わない場面
ワーカーを使うのは次の場合です。
- ライブプレビュー、エディター、試用環境。ユーザーの入力に応じて文書をビルドし直すものすべて。
- サイズ変更に追従するHTMLビューアー。
ResizeObserverが通知するたびにレイアウトを再実行するもの。 - ブラウザー内でのPDF書き出し。ライブプレビューをすでに持つUIから実行する場合。既存のワーカーのハンドルを再利用すれば、書き出しが計測キャッシュの恩恵を受けられます。
- 複数の出力タブ。すべてが同じVDTを必要とする場合(SandboxのCanvas / HTML / PDFのビューポートは、ビューポートのマウントごとに1つのワーカーのハンドルを共有します)。
ワーカーを使わないのは次の場合です。
- サーバーサイドでの生成。Nodeにはブラウザーの
FontFaceSetがなく、スレッドはいずれにしても自分で制御できます。 - 単発の書き出し(CLI、ヘッドレスの書き出しスクリプト、Cloud Function)で、ふさいでしまう対話的なUIがない場合。
buildDocumentを直接呼ぶほうが簡単で、最初のフォント転送の費用もかかりません。
#HTMLビューアーの統合
HTMLビューアーは、Postextの画面向けのレンダラーです。ページをビットマップにラスタライズする代わりに、絶対配置のDOMノードを出力し、その位置と大きさは印刷用の出力を生むのと同じパイプラインで決まります。そのため、PDFビューアーを持ち込まずに、ブラウザーで読みやすく、選択でき、サイズ変更に追従する文字組みがほしい場合(読書アプリ、製品内のプレビュー、埋め込みのドキュメント画面など)に適しています。
公開APIの主な要素は次のとおりです。
buildDocument(content, config, cache?):組版パイプライン全体を実行し、VDTDocumentを返します。renderToHtmlIndexed(doc, options):VDTを1つのHTML文字列と、ページごと・ブロックごとの内訳に変換します。内訳があれば、描画の間で少数のブロックだけが変わったときに、DOMを低コストで部分的に更新できます。resolveHtmlViewerConfig(partial):HTMLビューアーの既定値(maxCharsPerLine、columnGap、optimalLineBreaking)を補います。buildFontString+measureGlyphWidth+dimensionToPx:目標の文字数から実際の段幅をピクセルで求めるための計測の基本関数です。createMeasurementCache/clearMeasurementCache:差し替え可能なキャッシュで、再レイアウトの間で計測結果を再利用できます。
#ライブサンプル:HTML文字列
下のReactでの統合に入る前に、素のJavaScriptで一連の流れを示します。文書をビルドし、VDTDocumentをrenderToHtmlに渡し、得られた文字列をコンテナーに入れます。mode: 'single'はページを縦に積み重ねます。ページは既定では透明なので、backgroundで色を付けます。このCodePenのサンプルは生成したマークアップも表示するので、レンダラーが出力する絶対配置の行を確認できます。ブラウザーはそれを描画しますが、リフローはしません。
import { buildDocument, renderToHtml } from 'https://esm.sh/postext';
const markdown = `# The Lantern
The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
## Two columns
Postext lays this text out in **two columns**, breaking each paragraph with the *Knuth–Plass* algorithm and hyphenating with TeX patterns. Widows and orphans are avoided, and the columns are balanced on the last page.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.`;
const config = {
// 96 dpi: page pixels are CSS pixels, so the HTML shows at its real size.
page: { sizePreset: '17x24', dpi: 96 },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// Postext measures text with the fonts the browser has loaded,
// so wait for every face the document uses before laying it out.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const doc = buildDocument({ markdown }, config);
// One HTML string for the whole document. Every line is an absolutely
// positioned element, so the browser never reflows the text.
const html = renderToHtml(doc, { mode: 'single', background: '#ffffff' });
document.getElementById('viewer').innerHTML = html;
document.getElementById('source').textContent = html;
document.getElementById('status').textContent =
`${doc.pages.length} page(s) · ${(html.length / 1024).toFixed(1)} KB of HTML`;index.html
<p id="status">Laying out…</p>
<div id="viewer"></div>
<details>
<summary>Generated HTML</summary>
<pre id="source"></pre>
</details>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
/* The page is wider than this pane: let it scroll instead of clipping it.
The renderer centres pages with an inline style, hence the !important. */
#viewer {
overflow: auto;
}
#viewer .pt-doc {
align-items: flex-start !important;
}
/* Each page is a .pt-page block; the renderer positions every line inside it. */
#viewer .pt-page {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
details {
margin-top: 16px;
}
#source {
max-height: 240px;
overflow: auto;
padding: 8px;
background: #fff;
font-size: 11px;
white-space: pre-wrap;
word-break: break-all;
}codepen.ioからインタラクティブなエディターを読み込みます。サンプルは最新リリースのpostextをCDNから読み込みます。
#最小限の統合
次のコードは、役に立つ統合としては最も短いものです。現在のビューポートの大きさで文書をビルドし、コンテナーに描画し、サイズが変わったら再実行します。
import { useEffect, useRef } from 'react';
import {
buildDocument,
renderToHtmlIndexed,
resolveHtmlViewerConfig,
buildFontString,
measureGlyphWidth,
dimensionToPx,
createMeasurementCache,
} from 'postext';
import type { PostextConfig, MeasurementCache } from 'postext';
// Screen-friendly DPI: at 144 DPI, an 8pt body size resolves to 16 px.
const HTML_DPI = 144;
const PADDING_PX = 24;
// Prose sample used to measure the target column width. Proportional fonts
// make "N × average width" unreliable, so we measure a representative string.
const SAMPLE =
'The quick brown fox jumps over the lazy dog. Sphinx of black quartz, judge my vow.';
function sampleForChars(n: number): string {
let s = SAMPLE;
while (s.length < n) s += ' ' + SAMPLE;
return s.slice(0, n);
}
export function PostextHtmlViewer({
markdown,
config,
mode = 'multi',
}: {
markdown: string;
config: PostextConfig;
mode?: 'single' | 'multi';
}) {
const hostRef = useRef<HTMLDivElement | null>(null);
const cacheRef = useRef<MeasurementCache>(createMeasurementCache());
useEffect(() => {
const host = hostRef.current;
if (!host) return;
const relayout = () => {
const rect = host.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const viewer = resolveHtmlViewerConfig(config.htmlViewer);
const fontFamily = config.bodyText?.fontFamily ?? 'EB Garamond';
const fontWeight = config.bodyText?.fontWeight ?? 400;
const fontSize = config.bodyText?.fontSize ?? { value: 8, unit: 'pt' as const };
const fontSizePx = dimensionToPx(fontSize, HTML_DPI);
// Measure the *actual* column width for N characters of body prose.
const targetColumnPx = measureGlyphWidth(
sampleForChars(viewer.maxCharsPerLine),
buildFontString(fontFamily, fontSizePx, String(fontWeight), 'normal'),
);
const inner = Math.max(rect.width - PADDING_PX * 2, 100);
let columnWidthPx: number;
if (mode === 'single') {
columnWidthPx = Math.min(targetColumnPx, inner);
} else {
// Fit as many columns as we can at the target width.
const count = Math.max(
1,
Math.floor((inner + viewer.columnGap) / (targetColumnPx + viewer.columnGap)),
);
columnWidthPx = (inner - viewer.columnGap * (count - 1)) / count;
}
columnWidthPx = Math.max(Math.floor(columnWidthPx), 80);
// Single mode uses one very tall page; multi mode uses the viewport
// height so each VDT "page" becomes one column.
const pageHeightPx =
mode === 'single' ? Math.max(rect.height * 20, 200_000) : Math.max(rect.height - PADDING_PX * 2, 400);
const override: PostextConfig = {
...config,
page: {
...config.page,
dpi: HTML_DPI,
width: { value: columnWidthPx, unit: 'px' },
height: { value: pageHeightPx, unit: 'px' },
margins: {
top: { value: 0, unit: 'px' },
bottom: { value: 0, unit: 'px' },
left: { value: 0, unit: 'px' },
right: { value: 0, unit: 'px' },
},
},
layout: { ...config.layout, layoutType: 'single' },
bodyText: {
...config.bodyText,
optimalLineBreaking: viewer.optimalLineBreaking,
},
};
const doc = buildDocument({ markdown }, override, cacheRef.current);
const { html } = renderToHtmlIndexed(doc, {
mode,
columnGap: viewer.columnGap,
padding: PADDING_PX,
background: 'transparent',
});
host.innerHTML = html;
};
relayout();
const ro = new ResizeObserver(() => relayout());
ro.observe(host);
// Re-measure when web fonts land so glyph widths aren't taken from fallbacks.
const onFontsDone = () => relayout();
document.fonts?.addEventListener?.('loadingdone', onFontsDone);
return () => {
ro.disconnect();
document.fonts?.removeEventListener?.('loadingdone', onFontsDone);
};
}, [markdown, config, mode]);
return <div ref={hostRef} style={{ width: '100%', height: '100%', overflow: 'auto' }} />;
}この例で行っていることを補足します。
- 段幅は概算せずに計測する。
maxCharsPerLineは文字数で表した目標なので、実際のピクセル幅は本文のフォントによって変わります。measureGlyphWidthは選んだフォントで実際に計測するので、フォントを差し替えても行の長さが一定に保たれます。 - ページの書き換え。HTMLビューアーは、VDTの各「ページ」を画面上の1つの段として扱います。この例では
page.widthを計測した段幅で上書きし、余白をゼロにして(パディングはページの外側、包んでいる.pt-docのdivにあります)、HTML_DPI = 144を使うことで8ptの本文が16pxになるようにしています。 - フォントの読み込みへの対応。
document.fonts.loadingdoneは、新たに要求したWebフォントが届いたときに発火します。再レイアウトしなければ、最初の描画は代替フォントの計量値を使い、本来のフォントが届いたときに表示が跳ねます。 - 計測キャッシュの再利用。コンポーネントごとに1回だけキャッシュを作ることで、サイズ変更やフォントの拡大縮小のときに、すべての段落を計測し直さず、前回の描画の計測結果を再利用できます。
#さらに進んだ統合
上の例は意図的に単純にしてあります。実運用の統合では、たいてい次のものを加えます。
- Shadow DOMによる分離:
host.attachShadow({ mode: 'open' })に描画すれば、外側のページのCSSがビューアーに漏れ込みません。 - 差分の部分更新:
renderToHtmlIndexedはpages[i].blocksを返し、各ブロックは安定したidとブロックの外側のHTMLを持ちます。2回の描画の間で変わったブロックが少なければ、innerHTMLを作り直さずに、そのブロックのラッパーだけをその場で置き換えられます。 - オーバーレイ:各
.pt-pageの上に絶対配置のSVGを重ね、カーソル、選択範囲、ベースライングリッドを表示します。 - リンク:Markdownのリンクの語は
<a href="…" rel="noopener noreferrer">で包まれ、テキストの色を受け継ぎ、下線は付きません。文書形式 › リンクを参照してください。エディターのようなビューアーでは、#で始まらないa[href]のクリックを横取りし、新しいタブで開いてください(:refのアンカーは文書内にリンクします)。 - 単色の図:
diagramStyle.singleInkを有効にすると、SVGの<img>にCSSフィルターが掛かります。すでに色を変えてあるURLにはsingleInk: falseを渡してください。キャンバスとHTMLでの単色刷りを参照してください。
SandboxのHtmlPreviewコンポーネント(packages/postext-sandbox/src/viewport/HtmlPreview/index.tsx)は、ここで示したのと同じAPIの上にこれらすべてを実装しており、参考にできます。また、すべてのビルドを共有のレイアウトワーカーに通しているので(Web Workerでレイアウトを実行するを参照)、ライブ編集やサイズ変更がメインスレッドをふさぐことはありません。レイアウトをメインスレッドから外す準備ができたら、上のコードのbuildDocument(...)の直接呼び出しをlayoutWorker.build(...)に置き換えてください。
#HTML出力とcanvas、PDFとの違い
renderToHtmlは、すべての行、図、デザイン要素を、canvasやPDFとまったく同じ位置に置きますが、その周りに描くものは少なくなります。
| 機能 | Canvas (renderPage) | HTML (renderToHtml) | PDF (renderToPdf) |
|---|---|---|---|
| ページの背景 | 白。仕上がりと裁ち落としの範囲にpage.backgroundColorを重ねます。 | 透明。backgroundを渡すかpage.backgroundColorを設定した場合は色が付きます(このときはスラッグを含むページボックス全体を塗ります)。 | 白。仕上がりと裁ち落としの範囲にpage.backgroundColorを重ねます。 |
ベースライングリッド(page.baselineGrid) | 描画する | 描画しない | 描画する |
段間罫(layout.columnRule) | 描画する | 描画しない | 描画する |
トンボ(page.cutLines) | 描画する | 描画しない。ページボックスには、仕上がりの周りのスラッグも含まれたままです。 | 描画する |
| ページの反転(ネガ) | pageNegativeオプション | 利用できない | pageNegativeオプション |
| テキスト | ピクセル | 絶対配置の要素に入った選択可能なテキスト。CSSのフォントファミリーで組まれるので、ページで同じフェイスを読み込む必要があります。 | fontProviderから得たフォントを埋め込みます。選択と検索ができ、タグ付きです。 |
縦組みのテキスト(layout.writingMode: 'vertical-rl') | 文字を1字分の枠ずつ描き、正立に戻します。縦組み用の字形は双子のフェイス(loadVerticalAlternates)から取ります。 | 1つのボックスの中の流れを90度回し、各行を正立に戻してwriting-mode: vertical-rlで組みます。そのため、ブラウザーが縦組み用の字形を使い、文字を立てます。短い数字はtext-combine-upright: allで組みます。ダッシュ、三点リーダー、中黒、波ダッシュは字枠のボックスに入れ(ブラウザーは横組みの幅で送ってしまうため)、ダッシュはflow.dashAdvancesによって字枠いっぱいに伸ばします。 | 各フォントのIdentity-Vの双子で、正立の文字を組みます。PDFの縦組みを参照してください。 |
| 画像 | registerResourceImage | resourceImageUrl(fileId)オプション。ない場合は灰色のプレースホルダーのボックス。 | resourceBytes(fileId)オプション。 |
| 数式 | ベクターパス | インラインの<svg> | ベクターパス |
| リンク | なし | :refの参照はそのリソースにリンクします。目次の行はリンクしません。 | :refの参照と目次の行、さらにアウトライン(しおり)。 |
透明なページは、ダークな背景のサイトで問題になります。backgroundのないプレビューは、サイトの暗い背景の上に黒いテキストを表示してしまいます。renderToHtml(doc, { background: '#ffffff' })を渡すか、文書にpage.backgroundColorを設定してください。
ホストページのテキストスタイルは持ち込まれません。各行はエンジンが計測した幅で組まれるので、周囲のページから出力が受け継いだletter-spacing、word-spacing、text-transform、font-variantがあると、グリフの並びが広がって行が重なって印字されてしまいます。そのため.pt-docのルートは、自身のレイアウトの宣言より前に、受け継がれるテキストのプロパティをリセットします。対象は、字間と語間、大文字・小文字の変換、インデント、空白の扱い、フォントのスタイル・バリアント・ウェイト・幅・機能・カーニング、行の高さ、そろえ、テキストの影と強調、ハイフン、方向、書字方向、テキストの輪郭と塗り、モバイルでの文字の自動拡大です。これにより、シャドウルートの中でも、スタイルの付いた要素の下でも、出力は同じに見えます。この一覧は、CSSの宣言を並べた文字列HTML_TEXT_RESETとして公開されています。ページのinnerHtml(renderToHtmlIndexedから得たもの)を自前のコンテナーに入れるホストは、そのコンテナーのルートにこれを設定してください。postext 1.4までは、ルートは何もリセットしていませんでした。回避策はall: initialを指定したラッパーでした。
#PDFの生成
PDFの出力は別パッケージ(postext-pdf)が受け持ちます。Webだけで使う組み込みがpdf-libと@pdf-lib/fontkitのコストを負わずに済むようにするためです。PDFバックエンドはテキストを計測し直しません。renderToCanvasやrenderToHtmlに渡すのとまったく同じVDTDocumentを受け取り、そのピクセル単位の座標をPDFのポイントに変換します。そのため3つの出力は、改行位置、段の高さ、リソースの配置が必ず一致します。
ブラウザーでは、VDTをWeb Workerで構築してください。
renderToPdf自体はVDTができていれば高速で、時間がかかるのはVDTを生成したレイアウトのパイプラインのほうです。このパイプラインをワーカーで動かせばUIの応答性が保たれ、PDFの書き出しで、ライブプレビューがすでに温めた計測キャッシュを再利用できます。推奨する流れはワーカーからのPDF書き出しを参照してください。以下のメインスレッドの例は、各引数が何を意味するかを示すリファレンスです。UIのコードでは、まずワーカーでVDTを構築し、renderToPdfだけを直接呼び出してください。
#インストール
npm install postext postext-pdfpostextはpostext-pdfのピア依存関係(peer dependency)です。postext-pdfの各リリースには、同時にリリースされたpostextか、同じメジャーバージョンのそれより新しいものが必要です(ピア依存の範囲はそのバージョンに^を付けたもので、1.5.0なら^1.5.0)。そのリリースでpostextに追加されたヘルパーをインポートしているためです。2つは一緒にアップグレードし、CDNでは同じバージョンに固定してください。
#公開API
このパッケージは、エントリーポイントを1つと、いくつかの型を公開しています。
renderToPdf(doc, options): Promise<Uint8Array>—VDTDocument(または本の章を並べたその配列)を受け取り、PDFの生のバイト列を返します。PdfFontProvider—renderToPdfが新しいファミリー・ウェイト・スタイルの組み合わせを埋め込む必要があるときに、フォントのバイト列を要求するために使うコールバックのシグネチャ(family, weight, style, request?) => Promise<Uint8Array | Uint8Array[]>です。request.codePointsには、ページがそのフェイスで組む文字が入ります。応答は1つのファイルか、合わせて1つのフェイスを構成する複数のファイルです(中国語・日本語・韓国語のフォントを参照)。RenderToPdfOptions—{ fontProvider, resourceBytes?, outlines?, accessible?, colorSpace?, pageNegative?, characterGrid?, onProgress?, onWarning?, rasterizeSvg?, harfbuzzWasm? }。outlines、accessible、colorSpaceは、省略するとドキュメントのpdfGenerationの値になります(PDF生成(設定)を参照)。resourceBytesはリソースのバイト列と印刷用マスターで、onWarningはプロバイダーに要求されるフェイスと文書の中の警告で説明します。characterGrid: trueは、cjk.grid.showが画面に描くグリッドを印刷します。指定しなければPDFには含まれません(文字グリッドを参照)。harfbuzzWasmは、右から左に書く文字や連結する文字を含むドキュメントのために、HarfBuzzのharfbuzz.wasmをどこから読み込むかを指定します(ページからの相対URL、またはファイルのバイト列)。省略すると、postext-pdfのモジュールの隣にあるコピー、次にjsDelivrとesm.shにある同じharfbuzzjsのリリースを使います。PdfWarning—onWarningで報告される致命的でない問題です。kindで絞り込みます。'fontFallback'(PdfFontFallbackWarning)はフェイスがファミリーの別のカットで組まれたこと、'missingGlyph'(PdfMissingGlyphWarning)はフェイスのどのファイルにもグリフがない文字、'variableFontDefaultInstance'(PdfVariableFontWarning)は可変フォントが既定のインスタンス以外のウェイトで要求されたこと、'cffEmbeddedWhole'(PdfCffEmbeddedWholeWarning)は2 MBを超えるCFFフェイスが丸ごと埋め込まれたこと、'complexShapingUnavailable'(PdfComplexShapingWarning)はHarfBuzzを読み込めず(reasonに探した場所が並びます)、右から左に書く文字や連結する文字がHarfBuzzなしで描かれたため、アラビア文字の符号の位置がずれることを示します。'missingImage'は、バイト列のない画像がプレースホルダーとして描かれたことを示します(これは渡したonWarningにだけ報告されます。それがないとき、フォントの警告はconsole.warnに出ます)。decompressWoff2(bytes): Uint8Array— WOFF2ファイルを、pdf-libがそのまま埋め込める形式であるTTFのバイト列に変換するヘルパーです。createPdfWorker(options?)(postext-pdf/workerから) — 同じ描画をWeb Workerで行います。ワーカーでPDFを描画するを参照してください。
#最小限の例
import { buildDocument } from 'postext';
import { renderToPdf } from 'postext-pdf';
const vdt = buildDocument(
{ markdown: '# Chapter One\n\nThe story begins here…' },
{
page: { sizePreset: '17x24' },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 9, unit: 'pt' } }, // 9 pt overrides the 8 pt default
},
);
const pdfBytes = await renderToPdf(vdt, {
fontProvider: async (family, weight, style) => {
// Return TTF bytes for this family/weight/style.
// See the "Font provider" section below for a real implementation.
const res = await fetch(`/fonts/${family}-${weight}${style === 'italic' ? 'i' : ''}.ttf`);
return new Uint8Array(await res.arrayBuffer());
},
});
// `pdfBytes` is a Uint8Array — save, download, or stream it.
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url);#なぜフォントプロバイダーが必要か
pdf-libはPDFに実際のフォントファイルを埋め込みます。描画時にはブラウザーにインストールされたフォントを使えず、画面上の計測のためだけに読み込んだフォントも、それだけでは自己完結したPDFを作るのに足りません。renderToPdfはページを走査して、ページが描くすべてのフェイス(family|weight|styleの組み合わせごとに1つ。プロバイダーに要求されるフェイスを参照)を集め、固有の組み合わせごとに1回プロバイダーを呼び出します。プロバイダーはTTFまたはOTFのバイト列をUint8Arrayで返します。複数のファイルで提供されるフェイスなら、そのリストを返します(中国語・日本語・韓国語のフォントを参照)。pdf-libはTrueTypeのアウトラインをサブセット化し、CFF(.otf)ファイルは丸ごと埋め込みます。ファミリーにない太字の代わりにレギュラーのカットを返す場合のように、プロバイダーが同じファイルで応えたフェイスどうしは、埋め込みフォントを1つ共有します。SVGの図を画像として描くときのその図のテキストのフェイスのように、最終的にどのページの描画にも使われないフェイスはファイルに含まれません。
可変フォント1つではなく、ウェイトごとの静的フォントを使ってください。Google Fontsは多くの場合、ファミリーごとにウェイト軸全体を覆う可変WOFF2を1つ配信しています。pdf-libは可変フォントのファイルから既定のインスタンスしか埋め込めないため、太字の段落がレギュラーのウェイトで描かれてしまいます。Fontsourceはウェイトごとの静的WOFF2ファイルを公開しており、この問題をきれいに解決できます。Sandboxが使っているのもこの方法です。既定のインスタンス以外のウェイトで要求された可変フォントのファイルは、variableFontDefaultInstance警告として報告されます。
テキストのどの語も、レイアウトが置いた位置に描かれます。段落、リスト項目、引用、囲みなど流し込まれるテキストでは、各語がVDTの計測した位置から始まるので、ブラウザーの幅と埋め込んだフェイスの幅の差が行に沿って積み重なることはありません。インライン書式のない行は、語と語の間でペンを動かす1つのテキストオブジェクトとして描かれます。両端そろえの行、中央そろえの行、書式を含む行は語ごとに描かれます。フェイスにグリフのない文字は、ブラウザーが別のフォントで計測し、PDFではフェイスの欠落グリフの箱として描かれますが、後に続く語を動かすことはなく、フェイスごとに1回missingGlyph警告として報告されます。狭いノーブレークスペースや数字幅スペースのようにフェイスにないスペースは、ブラウザーが与えた幅をとり、単語結合子(word joiner)やゼロ幅スペースのような不可視の文字は描かれません。フェイスにないノーブレークハイフン(U+2011)は、ブラウザーの表示と同じく、フェイスのハイフン(U+2010)で描かれ、それもなければハイフンマイナスで描かれます。Open SansやOutfitなどは、その両方を持っていません。どちらの場合も欠落グリフには数えません。例外が2つあります。右から左に書く文字を含む行は、従来どおり1つのランとして描かれます(言語と文字体系を参照)。デザインが配置するテキスト(柱、フッター、章扉、囲みのタイトルなどのデザイン要素)は、埋め込んだフェイス自身の幅で組まれるため、そこでフェイスにないグリフがあると、行の残りが動きます。
#プロバイダーに要求されるフェイス
renderToPdfはページを描くときと同じ順にたどり、実際に描かれるフェイスだけをプロバイダーに要求します。
- 1行でも組むブロックのレギュラーのフェイスと、実際に太字、イタリック、太字イタリックで組まれる各ランのフェイス
- チップのラン、リストのマーカー、デザインのスロット(柱、ノンブル、章扉と部扉の帯)のテキスト
- すべてのリソースのキャプション、注記、表のセルのテキスト
- SVGの図を埋め込むときに、その
<text>が指定するフェイス。プロバイダーがまったく提供できないファミリーは、SVGのfont-familyリストの次のファミリーに引き継がれます
そのため、どこでもイタリックにしない見出しのファミリーのイタリックが要求されることはなく、注記のない図に注記のフェイスが必要になることもありません。
プロバイダーがフェイスを拒否しても、描画は続きます。同じファミリーの別のフェイスが代わりに埋め込まれ、PdfWarningが報告されます。代わりになるのは最初に読み込めたフェイスで、9つの標準ウェイト(100〜900)をCSSのフォントマッチングと同じ順に試します。これはプレビューでブラウザーが表示するフェイスでもあります。
- まず同じスタイル。400〜500のウェイトでは、500までのウェイトを先に試し、次に軽いウェイトを近いものから順に、その後600以上の重いウェイトを試します。400未満のウェイトでは、軽いウェイトを近いものから先に試し、次に重いウェイトを試します。500を超えるウェイトでは、重いウェイトを先に試し、次に軽いウェイトを試します。
- 次にもう一方のスタイル(立体ならイタリック、イタリックなら立体)を、要求されたウェイトとほかのウェイトについて、同じ順で試します。
したがって、イタリックのないファミリーはイタリックのランを立体で組み、400と700だけのファミリーは600を700で組み、カットが1つしかないファミリーはすべてをそのカットで組みます。プロバイダーへの要求はこの順に1フェイスずつ行い、同じフェイスを2度要求することはないので、何にも使われないフェイスが埋め込まれることはありません。プロバイダーがまったく提供できないファミリーについては、描画が失敗するまでにこの18のフェイスすべてが要求されます。
const bytes = await renderToPdf(doc, {
fontProvider,
onWarning: (w) => {
// { kind: 'fontFallback', family: 'Oswald', weight: 700, style: 'italic',
// fallback: { weight: 700, style: 'normal' }, reason: '…', message: '…' }
console.info(w.message);
},
});onWarningがないと、メッセージはconsole.warnに出ます。テキストの位置はVDTから来ており、ブラウザーが持っていたフェイスで計測されたものなので、幅の異なる代替フェイスでは詰まって見えたり、ゆるく見えたりすることがあります。直すには本来のフェイスを提供してください。描画が失敗する(postext-pdf: failed to load font(s): …)のは、プロバイダーがあるファミリーのどのフェイスも、標準ウェイトで立体・イタリックのいずれでも提供できない場合だけです。
#ブラウザー向けフォントプロバイダー(Fontsource + WOFF2)
SandboxにはcreatePdfFontProvider()(packages/postext-sandbox/src/viewport/pdfFontProvider.ts)があり、どのブラウザーアプリにもコピーして使えます。要点は次のとおりです。
import type { PdfFontProvider } from 'postext-pdf';
import { decompressWoff2 } from 'postext-pdf';
const bytesCache = new Map<string, Promise<Uint8Array>>();
function fontsourceId(family: string): string {
return family.toLowerCase().replace(/\s+/g, '-');
}
function fontsourceWoff2Url(
family: string,
weight: number,
style: 'normal' | 'italic',
): string {
const id = fontsourceId(family);
return `https://cdn.jsdelivr.net/npm/@fontsource/${id}@latest/files/${id}-latin-${weight}-${style}.woff2`;
}
export function createPdfFontProvider(): PdfFontProvider {
return async (family, weight, style) => {
const key = `${family}|${weight}|${style}`;
const cached = bytesCache.get(key);
if (cached) return cached;
const promise = (async (): Promise<Uint8Array> => {
const url = fontsourceWoff2Url(family, weight, style);
const res = await fetch(url, { mode: 'cors' });
if (!res.ok) throw new Error(`font fetch failed: ${res.status} ${url}`);
// pdf-lib needs TTF bytes, so decompress the WOFF2 wrapper client-side.
return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
})();
bytesCache.set(key, promise);
return promise;
};
}本番用の実装では、さらに次のことも行ってください。
- 利用できるウェイトを問い合わせ(
https://api.fontsource.org/v1/fonts/{id}を使用)、要求されたウェイトを、ファミリーが実際に提供する最も近いウェイトに合わせます。こうすれば、{400, 700}しかないファミリーにweight: 600を要求しても成功します。 - イタリックから立体にフォールバックします。要求されたウェイトにイタリックのカットがないファミリーでも、描画全体を失敗させずに済みます。
- 描画をまたいでキャッシュを再利用します(
bytesCacheは呼び出しごとではなく、モジュールスコープに置きます)。こうすれば、設定を変えた後のPDFの再生成には実質的にコストがかかりません。
#中国語・日本語・韓国語のフォント
CJKのフェイスは小さな1つのファイルでは提供されません。FontsourceはNoto Serif SCを1ウェイトあたり約100のファイルで配信しており、各ファイルは文字の一部を収め、ファミリーのスタイルシート(@fontsource/noto-serif-sc/400.css)でそのunicode-rangeとともに宣言されています。ブラウザーは、ページのテキストが使うファイルをダウンロードします。上のプロバイダーが取得するlatinファイルには漢字がまったく含まれず、名前付きのサブセットも不完全です。Noto Serif SCのchinese-simplifiedには釵がなく、Noto Serif TCのchinese-traditionalには全角の約物(),!?:;が1つもありません。
そこで、プロバイダーは1つのフェイスに複数のファイルで応えることができます。renderToPdfは、ページがそのフェイスで組む文字(request.codePoints)をプロバイダーに渡します。これは描画を始める前にすべての章から集めたものです。プロバイダーは、それらの文字を収めるファイルを、ブラウザーが参照するのと同じ順で返します。各ファイルはそれぞれ独立したサブセットとして埋め込まれ、各文字は、その文字のグリフを持つ最初のファイルから描かれます。60のスライスを使う章には、60の小さなサブセットが埋め込まれます。SVGの図のテキストなどのために同じフェイスが再び要求されるときは、それまでのファイルにない文字だけが要求されます。Uint8Arrayを1つ返すプロバイダーは従来どおり動作します。Sandboxのプロバイダーは、そのウェイトとスタイルのFontsourceのスタイルシートを読み、範囲がテキストの文字を含むファイルを取得します。その中核部分は次のとおりです。
import type { PdfFontProvider } from 'postext-pdf';
import { decompressWoff2 } from 'postext-pdf';
type Slice = { url: string; ranges: Array<[number, number]> };
async function fontsourceSlices(family: string, weight: number, style: 'normal' | 'italic'): Promise<Slice[]> {
const id = family.toLowerCase().replace(/\s+/g, '-');
const cssUrl = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/${weight}${style === 'italic' ? '-italic' : ''}.css`;
const css = await (await fetch(cssUrl)).text();
return [...css.matchAll(/@font-face\s*{([^}]*)}/g)].map(([, rule]) => ({
url: new URL(/url\(\.?\/?([^)]+\.woff2)\)/.exec(rule)![1], cssUrl).href,
ranges: /unicode-range:\s*([^;]+);/.exec(rule)![1].split(',').map((part) => {
const [lo, hi = lo] = part.trim().slice(2).split('-');
return [parseInt(lo, 16), parseInt(hi, 16)] as [number, number];
}),
}));
}
export const sliceFontProvider: PdfFontProvider = async (family, weight, style, request) => {
// Where ranges overlap, the browser tries the last rule first.
const slices = (await fontsourceSlices(family, weight, style)).reverse();
const picked = new Set<Slice>();
for (const cp of request?.codePoints ?? []) {
const slice = slices.find((s) => s.ranges.some(([lo, hi]) => cp >= lo && cp <= hi));
if (slice) picked.add(slice);
}
if (picked.size === 0) picked.add(slices[0]!);
return Promise.all(slices.filter((s) => picked.has(s)).map(async (s) =>
decompressWoff2(new Uint8Array(await (await fetch(s.url)).arrayBuffer()))));
};ラテン文字のファミリーも同じコードで扱えます。英語のテキストにはlatinファイルだけが、チェコ語にはlatinとlatin-extが使われます。
フェイスのどのファイルにもグリフのない文字は、フォントの.notdefグリフ(多くのフォントでは空の四角)で描かれ、renderToPdfはページを描き終えた後に、フェイスごとに1回それを報告します。
// { kind: 'missingGlyph', family: 'Noto Serif TC', weight: 400, style: 'normal',
// characters: [',', '!', '?'], message: '…' }Sandboxは、これと以下の2つの警告を、PDFを生成するたびに検査パネルに一覧表示します。本、その設定、リソースが変わると、次のPDFで置き換わるまで、それらは以前のPDFによるものとして示されます。別の本を開くと消えます。
- 太字にはウェイトごとの静的ファイルが必要です。FontsourceはNoto Serif SCとTCの各ウェイトを別々の静的ファイルとして配信しているので、Sandboxでは太字が使えます。Google Fontsのファイル(
NotoSerifSC[wght].ttf、25 MB)は可変フォントです。pdf-libはその既定のインスタンスを埋め込むため、700のフェイスは400で印刷され、renderToPdfはそれをvariableFontDefaultInstanceとして報告します。バンドルでは、fontToolsでウェイトごとに静的インスタンスを1つ切り出し(fonttools varLib.instancer NotoSerifSC[wght].ttf wght=700)、pyftsubsetで本の文字だけにサブセット化してください。 - TrueType版を使ってください。Source Han SerifとNoto Serif CJKの
.otfファイルはCFFアウトラインで、postext-pdfはこれを丸ごと、1ウェイトあたり8〜25 MB埋め込みます。2 MBを超えるCFFフェイスはcffEmbeddedWholeとして報告されます。TrueType版(Google Fonts、Fontsource)は、使われたグリフだけにサブセット化されます。日本語では、Noto Serif JPとNoto Sans JP(Google Fonts、またはFontsourceの番号付きスライス)、Shippori Mincho、Zen Old Mincho、BIZ UDMinchoはTrueTypeで提供されています。Source Han Serif JPと、Noto Serif CJKのJPの.otfファイルはCFFです。 - 汎CJKフェイスの日本語字形。漢字、約物、引用符の同じコードポイントでも、日本と中国では字形が異なることがあり、汎CJKフェイス(Source Han、Noto CJK)は両方を持っています。PDFは、日本語のドキュメント(
locale: 'ja')と、どのドキュメントでも日本語のアイソレート(:ltr[…]{lang=ja})を、OpenTypeの言語システムJANでシェーピングします。そのため、フェイスのlocl機能によって、CanvasとHTMLがlangを通じて表示するのと同じ日本語の字形が印刷されます。日本語の本の中にある別の言語のアイソレートは、その言語の字形(中国語ではフォントの既定の字形)でシェーピングされ、その/Langを持つSpanとしてタグ付けされます。中国語などのドキュメントは、従来どおりフォントの既定の字形でシェーピングされます。Noto Serif JPのように日本語用に作られたフェイスは既定の字形が日本語のものですが、それでも日本語のテキストの“ ”はJANの字形で組まれます。
あるファミリーに欠けている文字を、別のファミリーから借りることはありません。Noto Serif TCはNoto Serif SCから借りません。収録範囲はフォントファイルを作る段階で決着させてください。红楼梦のショーケースは、TCのサブセットに欠けているグリフをSCのフェイスからコピーしています。
#サーバー側のフォントプロバイダー(Node、ローカルファイル)
Nodeでは、WOFF2の手順を丸ごと省き、ディスクからTTF/OTFファイルを読み込めます。
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import type { PdfFontProvider } from 'postext-pdf';
const FONT_DIR = '/path/to/fonts';
function filename(family: string, weight: number, style: 'normal' | 'italic'): string {
const slug = family.replace(/\s+/g, '');
const styleSuffix = style === 'italic' ? 'Italic' : '';
const weightName =
weight >= 700 ? 'Bold'
: weight >= 600 ? 'SemiBold'
: weight >= 500 ? 'Medium'
: weight >= 300 ? 'Light'
: 'Regular';
return `${slug}-${weightName}${styleSuffix}.ttf`;
}
export const localFontProvider: PdfFontProvider = async (family, weight, style) => {
const buf = await readFile(join(FONT_DIR, filename(family, weight, style)));
return new Uint8Array(buf);
};#リソースのバイト列と印刷用マスター
resourceBytes(fileId)は画像の生のバイト列を返し、バックエンドはその形式を判別します。
- PNG、JPEG、GIF、WebPは画像として埋め込まれます
- SVGのマークアップはベクターのパスとして描かれます。ベクターのサブセットにない機能を使っている場合は、ブラウザーで600 dpiでラスタライズされます
- PDFは最初のページがそのまま、フォームXObjectとして埋め込まれます
各画像は、何回描かれてもファイルには1回だけ格納されます。ベクターのパスとして描かれるSVGは、すべてのページが描画するフォームXObjectになるので、30ページのドキュメントのページデザインにある枠やロゴは、30回ではなく1回だけ書き込まれます。ページが1つ増えるごとに増えるのは数百バイトです。postext-pdf 1.4までは、各ページがパスのコピーをそれぞれ持っていました。
SVGの図は、svg.pdfFileIdで印刷用マスターを指定できます。同じ図の1ページのPDFで、通常はSVGの書き出し元になった原本です。renderToPdfはまずresourceBytesにマスターのidを問い合わせます。そして、図として、表のセルの画像(TableCell.image)として、デザインの画像として、囲みのアイコン(そのmarkerも)として、SVGが描かれるすべての場所で、フォント、グラデーション、色空間を保ったまま、そのページをSVGの代わりに埋め込みます。VDTはそれぞれの使用箇所にマスターのidを持っているので(図のリソースにはsvg.pdfFileId、セルの画像とデザインの画像ブロックにはpdfFileId)、本ではどの章にもマスターが使われます。CanvasとHTMLのバックエンドは引き続きSVGを描きます。SVG自身のバイト列が代わりに使われるのは、マスターがない場合、マスターがPDFでない場合、単色インクが有効な場合(diagramStyle.singleInkはSVGのマークアップだけを色替えします)の3つです。
const resources: Resource[] = [{
id: 'map', typeId: 'figure', kind: 'svg', createdAt: 0, updatedAt: 0,
svg: { fileId: 'map.svg', width: 800, height: 600, pdfFileId: 'map.pdf' },
}];
const files = new Map([['map.svg', svgBytes], ['map.pdf', masterPdfBytes]]);
const pdf = await renderToPdf(buildDocument({ markdown, resources }, config), {
fontProvider,
resourceBytes: (fileId) => files.get(fileId),
});ホストは、bundleResourceBytesのように、SVG自身のidに対してマスターのバイト列を返すこともできます。どちらの方法でも動作します。
#PDFの縦組み
縦組みのページ(layout.writingMode: 'vertical-rl')は、Canvasが描くのと同じく90度回転した座標系を通して描かれ、テキストは縦方向に組まれます。
- 正立する文字は、同じ埋め込みファイルから作る2つ目のType0フォントで表示されます。CIDFont、幅、ToUnicodeマップは同じで、
Encoding /Identity-V(縦書きモード)を使います。ひと続きの文字は1つのテキストオブジェクトになり、そのグリフは自ら1 emずつ下へ進むので(DW2 [880 −1000])、PDFビューアーでは縦の1行を1行として選択・抽出できます。グリフはOpenTypeのvertとfwidでシェーピングされ、括弧、引用符、中国大陸式の読点、三点リーダー、ダッシュが縦組み用の字形になります。そのままで正立する文字は横組み用のグリフのままです。フォントのどの部分も2回埋め込まれることはありません。 - ラテン文字の単語と長い数字は横組み用のフォントで横倒しに組まれ、1マスに収める数字は正立し、1字幅より広いときは横方向に詰めて1字幅に収めます。フォントに縦組み用の字形がない記号は、Canvasと同じく回転または移動されます。
- トラッキングは文字間に
TJの数値として書き込まれ、縦書きモードではペンを下へ移動させます。 - 縦組みの各行には、そのテキストの
/ActualTextが付くので、コピーやテキスト抽出では書かれたとおりに読み取られます。pdftotextとpdf.jsは、行を上から下へ、右から左へと読みます。pdf.jsは1マスに収めた数字の位置で新しい行を始めます。 - リンク、しおり、移動先は用紙上の座標に対応付けられます。縦組みの行の上にあるリンクは縦長の細い矩形になり、しおりは見出しの行の上端でページを開きます。
- タグ付きPDFは、
Document要素で組方向を宣言します(Layout属性のWritingMode /TbRl。すべての要素がこれを継承します)。縦組みの章はPDF/UA-1の検証(veraPDF)に合格します。 - ビューアー:Acrobat、Preview、Chrome(PDFium)、pdf.js、Popplerは縦組み用のフォントを表示できます。右綴じの本(
page.binding)は、見開きを右から左に並べるようビューアーにも求めます(/Direction /R2L、/PageLayout /TwoPageRight)。AcrobatとFoxitはこれに従いますが、Chromeは従いません。
Noto Serif TC(本で使う文字だけ、TrueType)で組んだ43ページの章は約820 KBで、その大半を2つのフォントサブセットが占めます。
#PDFのリンク
Markdownのリンク(文書形式 › リンクを参照)の語は、URIリンク注釈になります。行ごとに、リンクされた語のひと続きにつき1つです。各注釈は行のボックスを覆い、枠線はありません。アクセシブルな描画では、ひと続きごとにLink要素になり、その/Contentsはそのテキストです。リンクになるのは、絶対URLのhttp:、https:、mailto:、tel:、ftp:のターゲットだけです。相対URLはPDFの中では基準となるURLを持たないためです。印字可能なASCII以外の文字はパーセントエンコードされます。:refによる参照と目次の行は、ドキュメント内へのリンクのままです。
#完全なブラウザーの例:構築、描画、ダウンロード
すべてを組み合わせて、VDTを構築し、PDFに描画し、ブラウザーからダウンロードを開始します。
import { buildDocument, createMeasurementCache } from 'postext';
import { renderToPdf } from 'postext-pdf';
import { createPdfFontProvider } from './pdfFontProvider';
const fontProvider = createPdfFontProvider();
export async function downloadPdf(markdown: string, config: PostextConfig) {
const cache = createMeasurementCache();
const vdt = buildDocument({ markdown }, config, cache);
const bytes = await renderToPdf(vdt, { fontProvider });
const blob = new Blob([bytes.slice().buffer], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.pdf';
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}重要:Webフォントを参照する設定では、buildDocumentの前にensureConfigFontsLoaded(config)(または同等の処理)を呼び出してください。レイアウトは、そのファミリーについてブラウザーがその時点で持っているフォントメトリクスで計測されます。本来のフォントがまだ届いていなければ、VDTは代替フォントで計測され、PDFはCanvasやHTMLの出力と一致しません。Sandboxは描画のたびに、その前にこれを明示的に行っています(packages/postext-sandbox/src/viewport/PdfViewport.tsxを参照)。
#ライブサンプル:ブラウザーでPDFを作る
上の一連の流れをブラウザーで実行します。このCodePenのサンプルは、postextとpostext-pdfをCDNからインポートし、Webフォントを読み込み、ドキュメントを構築し、フォントプロバイダーを通じてFontsourceのカットを埋め込み、そのバイト列を、ファイルを新しいタブで開くリンクとダウンロードリンクに渡します。得られるPDFは、CanvasやHTMLの出力と同じ改行位置を持ち、実際のフォントが埋め込まれ、アウトラインのしおりも付いています。
import { buildDocument } from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
const markdown = `# The Lantern
The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.
## Two columns
Postext lays this text out in **two columns**, breaking each paragraph with the *Knuth–Plass* algorithm and hyphenating with TeX patterns. Widows and orphans are avoided, and the columns are balanced on the last page.
The light it gave was small, but it was enough to find the step. The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.`;
const config = {
page: { sizePreset: '17x24' },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// The PDF embeds real font files. Fontsource publishes one static WOFF2 per
// weight and style; decompress it to the TTF bytes pdf-lib can embed.
const fontProvider = async (family, weight, style) => {
const id = family.toLowerCase().replace(/\s+/g, '-');
const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@latest/files/${id}-latin-${weight}-${style}.woff2`;
const res = await fetch(url);
if (!res.ok) throw new Error(`font fetch failed: ${res.status} ${url}`);
return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
};
// Layout is measured with the browser's fonts, so load them before building:
// otherwise the PDF would not match the canvas or HTML output.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const doc = buildDocument({ markdown }, config);
// Same VDT, now translated to PDF points: identical line breaks and placement.
const bytes = await renderToPdf(doc, { fontProvider });
// A PDF viewer cannot run inside this sandboxed result frame,
// so hand the file to a new tab and to a download link.
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }));
document.getElementById('open').href = url;
document.getElementById('download').href = url;
document.getElementById('links').hidden = false;
document.getElementById('status').textContent =
`${doc.pages.length} page(s) · ${(bytes.length / 1024).toFixed(0)} KB PDF`;index.html
<p id="status">Rendering…</p>
<p id="links" hidden>
<a id="open" target="_blank" rel="noopener">Open lantern.pdf in a new tab</a> ·
<a id="download" download="lantern.pdf">Download it</a>
</p>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
}codepen.ioからインタラクティブなエディターを読み込みます。サンプルは最新リリースのpostextをCDNから読み込みます。
#ワーカーでPDFを描画する
postext-pdf/workerは、renderToPdfをメインスレッドの外に移します。ワーカーはテキスト、ベクターの図、構造ツリー、ファイルそのものを書き出します。Webページ側でしかできない処理が2つあり、ワーカーはそれらをメインスレッドに依頼します。フォントの取得と、<img>を通じたSVGのラスタライズです。数百ページの本では、描画にかかる数秒のあいだページが固まってしまうのを防げます。数ページなら、renderToPdfを直接呼び出すほうが簡単です。
import { createPdfWorker } from 'postext-pdf/worker';
const pdfWorker = createPdfWorker();
const bytes = await pdfWorker.render(docs, {
fontProvider, // runs on this thread
resourceBytes: new Map([['map.svg', svgBytes]]), // a Map; its buffers move to the worker
onProgress: ({ phase, pages, totalPages }) => showProgress(phase, pages, totalPages),
onWarning: (w) => console.info(w.message),
});
pdfWorker.dispose();render(docs, options)は、1つのドキュメント、または本の章のドキュメントの配列を受け取ります。オプションはrenderToPdfのものですが、違いが2つあります。resourceBytesはMap<string, Uint8Array>で、そのバッファーは転送されるため、手元に残すバイト列はコピーを渡してください。rasterizeSvgは、指定した場合はメインスレッドで実行されます。指定しなければ、ページ自身のImageとcanvasが処理します。- 1つのハンドルが描画するドキュメントは一度に1つです。
dispose()はワーカーを終了し、保留中の描画をすべて拒否(reject)します。 createPdfWorker({ worker })は、自分で作成したWorkerを受け取ります。ワーカーのURLを制御するビルドツール向けです。そのワーカーではpostext-pdf/worker/entryを実行する必要があります。
CDNから使う場合。既定では、ワーカーのスクリプトはパッケージ自身のURL(new URL('./pdf.worker.js', import.meta.url))から読み込まれます。別のオリジンのページでは、これを起動できないことがあります。esm.shからインポートすると、createPdfWorker()はFailed to construct 'Worker': Script at 'https://esm.sh/postext-pdf@…/pdf.worker.js' cannot be accessed from origin …を投げます。代わりに、エントリーをインポートする同一オリジンのモジュールワーカーを起動してください(バージョンを固定する場合は、両方のURLで同じバージョンに固定します)。
import { createPdfWorker } from 'https://esm.sh/postext-pdf/worker';
const entry = URL.createObjectURL(new Blob(
["import 'https://esm.sh/postext-pdf/worker/entry';"],
{ type: 'text/javascript' },
));
const pdfWorker = createPdfWorker({ worker: new Worker(entry, { type: 'module' }) });postext/workerのレイアウトワーカーにも、postext/worker/entryを包む同じラッパーが必要です(Web Workerでレイアウトを実行するを参照)。
#印刷入稿用のPDF
本番の印刷ワークフローでは、描画の前に次の設定を調整してください。
page.cutLines.enabled: true— 仕上がり線の周りに裁ち落とし領域とトンボを加え、各ページにTrimBoxとBleedBoxを設定します。トンボを参照してください。colorSpace: 'cmyk'(またはpdfGeneration: { forceColorSpace: true, colorSpace: 'cmyk' }) — postextが描く色(テキスト、罫線、塗り、ベクターの図)をDeviceCMYKで書き出します(トンボは常にレジストレーションカラーです)。ビットマップとPDFの印刷用マスターはそのまま埋め込まれるため、RGBの写真はRGBのままです。追加する前に変換してください。page.dpi: 300(またはそれ以上) — PDFのポイントは1インチあたり72に固定されていますが、Postextのレイアウト計算はピクセルで行われます。DPIを高くすると、mmやcmで指定した要素をより細かく分割できます。colors.model: 'cmyk'— 色がCMYK色空間で指定されたという意図を保持します。現在、PDFへの実際の描画にはhexのフォールバックが使われます。ここでmodelを説明するのは、下流のツールのためにVDTにも引き継がれるからです。{ pageNegative: true }(RenderToPdfOptionsのオプション) — Differenceブレンドモードで仕上がり領域を反転します(トンボは反転しません)。明るい地に暗い文字の組版を、プリフライトで確認するのに便利です。
#リファレンス実装
SandboxのPdfViewportコンポーネント(packages/postext-sandbox/src/viewport/PdfViewport.tsx)は、上の要素を組み合わせて、再計算、ダウンロード、印刷のボタンを備えたライブプレビューにしたもので、ブラウザー内でPDFを扱う組み込みの出発点に適しています。VDTは共有のレイアウトワーカーで構築するので(Web Workerでレイアウトを実行するを参照)、再計算をクリックしても、パイプラインの実行中にUIが固まりません。メインスレッドが受け持つのはrenderToPdfだけです(VDTができていれば、これは高速です)。
#3Dの本(postext-folio)
postext-folioは、レイアウト済みのドキュメントを、机の上に開いて置かれた印刷された本として画面に表示します。見開きは奇数ページの規則に従って組まれ、読者は‹ ›ボタン、矢印キー、スワイプ、ページのクリック、またはページの端をつかんで向こう側へドラッグすることで丁をめくります。各丁は紙に応じてthree.jsで湾曲し、下にあるページに本物の影を落とします。WebGLのキャンバスは静止した本もめくられる本も同じように描くので、ページが着地したときに見た目が変わることはありません。レシピ集のレシピと、SandboxのFolioタブで使われているビューアーです。
npm install postext postext-folio threeimport { buildDocument } from 'postext';
import { createFolioFromDocument } from 'postext-folio';
const doc = buildDocument({ markdown }, config);
const book = createFolioFromDocument(document.getElementById('book')!, doc, {
onChange: ({ pages }) => console.log('showing pages', pages),
});
// After an edit: the same viewer, on the same page.
book.setDocument(buildDocument({ markdown: edited }, config));- ページは必要になったときに描かれます。
createFolioFromDocumentは各ページを、ページ枠のデバイスピクセルちょうどの大きさでrenderPageToCanvasにより描きます(WebGLはそれをテクセル対ピクセルで表示するので、Canvasのプレビューと同じくらい鮮明です)。描くのは、開いている見開きの周りの見開きだけです(window、既定では前後に3つずつ)。その範囲から外れたページは解放されるので、1000ページの本でも数ページ分のメモリーしか使いません。遠いページに移動するときは、その見開きを最初に描きます。10ページ先まではページが1枚ずつめくられ、それより遠いと、間にあるページのかたまりが、それらのページを合わせた厚さ(紙厚の合計)を持つ1枚の板として持ち上がり、反対側に着地します。setDocumentは、新しいレイアウトでも内容が同じページの描画を保持します({ repaint: true }を指定すると、画像が届いた後などに、すべてを描き直します)。 - ドキュメントが本を決めます。最初のページが奇数ページ(
pageIndexOffsetが偶数)なら、右側に単独で開きます。右綴じの本(page.binding: 'right'、または縦組みのドキュメント)は左右反転して置かれ、左向きにめくられます。空白ページはページの背景色になり、ページの仕上がり幅(pageWidthMm)に応じて紙厚と表紙の板の縮尺が決まります。continuation付きでレイアウトした章は、本のほかのページ(前はpageIndexOffset、後はbookPageCountまで)を、描かずに2つのページブロックの厚さに数えます(extraPages)。 - ドキュメントが見た目を決めます。紙、綴じ、机、光は、ドキュメントの
folio設定(doc.config.folio)で決まります。:::paperの範囲内で組まれたページは独自の用紙(VDTPage.paper)を持ち、その丁は、その紙の色、表面、厚さ、こしで描かれます。binding.cover: 'pages'では、最初のページが表表紙の板になり、最後のページが偶数ページなら裏表紙の板になります(covers)。 - コンテナーが大きさを決めます。本はコンテナーいっぱいに広がり、ボタンとページ数は余白に置かれるので、コンテナーには高さを指定してください。リサイズすると、新しい大きさでページを描き直します。幅が560 px未満では1ページずつ表示します(
mode: 'auto'。'single'と'double'はどちらかに固定します)。背はページの内側の端に沿い、丁はその上でめくられます。背の方向へのドラッグで先へめくり、背から離れる方向へのスワイプで前に戻り、タップでめくります。 - ポインターの動作。
interaction(後からはsetInteraction)は、左ボタン、1本指、ペンが本に対して何をするかを決めます。'hand'(既定)はページをつかんでめくり、'orbit'は右ドラッグと同じように視点を回し(トラックパッドやタブレット向け)、'select'はポインターをホストに任せます(テキストの選択などのため)。pageAt(event)は、傾けたり回したりした見た目のままの本の上で、ポインターの下にあるページとその位置({ page, x, y }。ページの左上隅からの割合)を返します。pointOnScreen(point)はその逆で、ページ上にキャレットや選択範囲を描くためのものです。refreshPage(src)は、ホストがその場で描き直したページのキャンバスを再表示します。Sandboxはこれらをすべて使って、3Dのページ上でテキストを選択し、エディターのキャレットを追います。 - 読者は本の周りを見回せます。右ドラッグで本の周りを視点が回り(真上から70°まで)、丁がめくられている間も回せます。
resetView()は設定のtiltとyawへ滑らかに戻し、getView()は現在見えている視点({ tilt, yaw }、度単位)を返すので、それを設定として保存できます。Sandboxはこれらを2つのボタン、視点をリセットと既定の視点として保存に割り当てています。 - フォントと画像を先に。
renderPageと同じく、ページを描く前に、ドキュメントが使うフェイスをdocument.fontsに読み込み、リソースの画像をregisterResourceImageで登録しておく必要があります。 - アクセシブル。ビューアーはフォーカス可能なグループで、←/→(右綴じの本では反転)、Page Up/Down、Home、Endを受け付けます。ボタンとページ数にはラベルが付き(
labelsで翻訳できます)、各ページのキャンバスにはaltテキストが付きます(alt: (index) => …)。 - WebGL2がない場合、または読者が動きを減らす設定にしている場合は、見開きが単に切り替わります。WebGLの本はスマートフォンには重いため(ページの面ごとにテクスチャーを1枚使います)、SandboxはWebGL2が使え、画面の短辺が600 px以上の場合にだけFolioタブを表示します。
canFlip()は、その環境で丁が3Dでめくられるかどうか(WebGL2があり、動きを減らす設定でないこと)を返します。
#外観
appearanceオプション(後からはsetAppearance)は、ドキュメントの指定を上書きします。省略したものは、ドキュメントの指定のままです。
const book = createFolioFromDocument(container, doc, {
appearance: {
folio: {
tilt: 22,
paper: { type: 'bookWove', texture: 'laid' },
binding: { type: 'hardcover', coverColor: { hex: '#5a1f1f', model: 'hex' } },
surface: { type: 'walnut' },
lighting: { environment: 'lamp' },
},
// Photographed desks: a folder laid out as postext.dev's /folio/textures/
// (manifest.json and one folder per desk). Without it, procedural maps.
textureBaseUrl: '/folio/textures',
// The picture for `folio.binding.spineImage`: the resource's URL,
// or a drawn canvas or image.
spineImage: spineUrl,
},
});
// A settings panel: the book redrawn in place, nothing painted again.
book.setAppearance({ folio: { ...folio, lighting: { environment: 'daylight' } } });
book.resetView();| フィールド | 内容 |
|---|---|
folio | folio設定:傾き、紙、綴じ、表面、照明。指定すると、ドキュメントの設定を置き換えます。 |
pageWidthMm | ページの仕上がり幅(mm)。紙厚と表紙の板は、これを基準に縮尺されます。ドキュメントからは、そのdpiでの仕上がりページの幅。createFolioでは既定値150。 |
extraPages | :渡したページ以外の本のページ。ページブロックの厚さに数えますが、描画はしません。 |
covers | :渡した最初のページが表表紙、最後のページが(偶数ページに当たる場合)裏表紙になります。どちらも板としてめくられ、別の表紙(ケース)は描かれません。ドキュメントからは、最初のページで始まり最後のページで終わる本でbinding.cover: 'pages'を指定した場合。 |
spineImage | 背に印刷される画像。URL、キャンバス、画像のいずれか。createFolioFromDocumentはリソースを参照しないので、folio.binding.spineImageが指定するリソースの画像を渡してください。中綴じでは無視されます。 |
textureBaseUrl | 写真から作った机のテクスチャーの配信場所。読み込まれるまでの間、または指定しない場合、机はプロシージャルなマップで描かれます。 |
createFolio(container, { pages })は、任意のページを対象にした同じビューアーです。ページには画像のURL、<img>または<canvas>要素、空白ページを表す""を使えます。ページは{ src, alt, paper }にすることもでき、paperはその丁の:::paper形式の用紙です。PageFlipperはthree.jsのエンジン単体で、見開きのDOMを自分で組むホスト向けです。FlatPageFlipperは3Dの本より前からある平面のページめくりで、レシピ集のライトテーブルのために残されています。オプションの一覧はパッケージのREADMEにあります。
#ライブサンプル:3Dの本
このCodePenのサンプルは、postextとpostext-folioをCDNからインポートし、短いドキュメントをレイアウトして本として開きます。右ページの端をつかんで、向こう側へドラッグしてみてください。
import { buildDocument } from 'https://esm.sh/postext';
import { createFolioFromDocument } from 'https://esm.sh/postext-folio';
const paragraph = `The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved. The light it gave was small, but it was enough to find the step.`;
// Thirty-six short sections: about ten pages to turn.
const markdown = ['# The Lantern']
.concat(Array.from({ length: 36 }, (_, i) => `## Evening ${i + 1}\n\n${paragraph} ${paragraph}\n\n${paragraph}`))
.join('\n\n');
const config = {
page: { sizePreset: '17x24', dpi: 150 },
layout: { layoutType: 'double' },
bodyText: { fontFamily: 'EB Garamond', fontSize: { value: 10, unit: 'pt' } },
};
// Postext measures text with the fonts the browser has loaded,
// so wait for every face the document uses before laying it out.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('italic 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const doc = buildDocument({ markdown }, config);
const status = document.getElementById('status');
// The book: drag a page by its edge, click it, or use ← → and the buttons.
// Pages are painted at the size they are shown, around the open spread only.
createFolioFromDocument(document.getElementById('book'), doc, {
onChange: ({ pages }) => {
status.textContent = `${doc.pages.length} pages · open at ${pages.map((i) => i + 1).join('–')}`;
},
});
status.textContent = `${doc.pages.length} pages · drag a page by its edge to turn it`;index.html
<p id="status">Laying out…</p>
<div id="book"></div>style.css
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #eee;
background: radial-gradient(ellipse 70% 75% at 50% 42%, #272b34 0%, #1a1d23 58%, #121418 100%);
min-height: 100vh;
}
#status {
margin: 12px 16px 0;
font-size: 14px;
opacity: 0.8;
}
/* The viewer fits the book into its container: give it a height. */
#book {
height: calc(100vh - 48px);
--postext-folio-accent: #f0b35a;
}codepen.ioからインタラクティブなエディターを読み込みます。サンプルは最新リリースのpostextをCDNから読み込みます。
#ライブサンプル:ページの画像
キャンバスに描いたページ、空白の最終ページ、紙の色を使ったcreateFolioの例です。
import { createFolio } from 'https://esm.sh/postext-folio';
// Any pages will do: image URLs, <img> or <canvas> elements, and "" for a
// blank page. Here, eight pages drawn on canvases.
function drawPage(n) {
const canvas = document.createElement('canvas');
canvas.width = 600;
canvas.height = 840;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fbf8f1';
ctx.fillRect(0, 0, 600, 840);
ctx.fillStyle = `hsl(${n * 45} 45% 45%)`;
ctx.fillRect(60, 80, 480, 320);
ctx.fillStyle = '#222';
ctx.font = 'bold 56px Georgia, serif';
ctx.fillText(`Plate ${n}`, 60, 480);
ctx.font = '22px Georgia, serif';
for (let line = 0; line < 8; line++) ctx.fillRect(60, 530 + line * 30, line === 7 ? 260 : 480, 3);
ctx.textAlign = 'center';
ctx.fillText(String(n), 300, 800);
return { src: canvas, alt: `Plate ${n}` };
}
const pages = Array.from({ length: 8 }, (_, i) => drawPage(i + 1));
// A blank page at the end, drawn as paper.
pages.push('');
const status = document.getElementById('status');
createFolio(document.getElementById('book'), {
pages,
firstPageRecto: true, // page 1 opens alone, on the right
binding: 'left', // 'right' lays a right-to-left book mirrored
paper: '#fbf8f1',
onChange: (state) => {
status.textContent = `Showing ${state.pages.map((i) => i + 1).join('–')} of ${pages.length}`;
},
});
status.textContent = 'Drag a page by its edge, click it, or use ← →';index.html
<p id="status">Drawing pages…</p>
<div id="book"></div>style.css
body {
margin: 0;
font-family: system-ui, sans-serif;
color: #eee;
background: radial-gradient(ellipse 70% 75% at 50% 42%, #272b34 0%, #1a1d23 58%, #121418 100%);
min-height: 100vh;
}
#status {
margin: 12px 16px 0;
font-size: 14px;
opacity: 0.8;
}
/* The viewer fits the book into its container: give it a height. */
#book {
height: calc(100vh - 48px);
--postext-folio-accent: #f0b35a;
}codepen.ioからインタラクティブなエディターを読み込みます。サンプルは最新リリースのpostextをCDNから読み込みます。
#EPUBの本(postext-epub)
postext-epubは、レイアウトした本をEPUB 3.3ファイルとして、ブラウザーでもNodeでもサーバーなしで書き出します。本の場合にrenderToPdfが受け取るのと同じ章のドキュメントを読むので、ページ番号、注、引用、相互参照、目次、索引が解決済みの状態で届きます。ファイルはバイト列として返されます。SandboxのEPUB 3タブで使われている書き出し機能です。
npm install postext postext-epubpostextは、postext-pdfと同じくピア依存関係です。2つは一緒にアップグレードし、CDNでは同じバージョンに固定してください。
#固定レイアウトとリフロー型
EPUB 3は2つのレンディションを定義しており、パッケージのrendition:layoutプロパティで設定します。layoutでどちらかを選びます。
layout: 'fixed' | layout: 'reflowable' | |
|---|---|---|
| EPUBでの名前 | pre-paginated(固定レイアウト、FXL) | reflowable(EPUBの既定) |
| コンテンツ文書 | 印刷ページごとに1つのXHTML文書。仕上がりのページサイズ(CSS px) | 章ごとに1つのXHTML文書(部はそれ自身の文書を開始します) |
| 保持するもの | ページ:段、フロート、柱、章扉、改行位置と配置を、埋め込んだフォントで。テキストは本物のテキストのままで、選択、検索、読み上げができます | テキストとその構造:見出し、行から組み立て直した段落、リスト、asideとしての囲み、引用する本文の後に置く図と表、注、リンク、印刷ページの目印。設定から導いたスタイルシート |
| 失うもの | 読者による書体、サイズ、余白の選択。小さな画面ではページが縮小されます | 段、柱、ページデザイン、正確な改行位置 |
| 見開きと方向 | 奇数・偶数と綴じから決まるpage-spread-left / page-spread-right。右綴じの本は右から左に読みます | 読む方向は綴じから決まります。縦組みの中国語はvertical-rlを保ち、アラビア語はdir="rtl"になります |
| 向いているもの | デザインされたページ:図版の多い本、教科書、カタログ、雑誌。大きな画面 | 流れる本文:小説、エッセイ、レポート。スマートフォンや電子ペーパー端末 |
どちらのレンディションも同じナビゲーションを持ちます。見出しと部扉から作る目次、印刷ページのラベルによるページリスト、ランドマーク(表紙、印刷された目次、本文の開始)、古いリーディングシステム向けのNCXです。
#本を書き出す
import { openBundle, buildBundle } from 'postext';
import { renderToEpub } from 'postext-epub';
const bundle = await openBundle(fileBytes);
const docs = buildBundle(bundle); // one VDTDocument per chapter, in book order
const bytes = await renderToEpub(docs, {
layout: 'reflowable',
metadata: { title: 'Lantern', creators: ['Ada Lovelace'], language: 'en' },
fonts: bundle.fonts.map((f) => ({ family: f.family, weight: f.weight, style: f.style, bytes: new Uint8Array(f.bytes), format: f.format })),
resourceBytes: (fileId) => {
const data = bundle.files.get(fileId);
return data ? { bytes: data, mediaType: '' } : undefined;
},
onWarning: (w) => console.warn(w),
});単独のドキュメントは、1章だけの本として渡します(renderToEpub([doc], options))。
renderToEpub(docs, options): Promise<Uint8Array>:ファイルを書き出します。optionsは{ layout, metadata, fonts?, resourceBytes?, cover?, onProgress?, onWarning?, signal? }です。metadata:titleとlanguage(BCP 47タグ)は必須で、subtitle、creators、identifier、date、publisher、rights、description、modifiedは省略できます。ISBNだけを書くとurn:isbn:…になります。identifierがなければ、本にはタイトル、著者、言語から導いたurn:uuid:が付くので、同じ本の新しい版も読者のライブラリーで同じ位置を保ちます。バイト単位で同一の出力を得るには、modifiedも渡してください。fonts:埋め込むフェイス。{ family, weight, style, bytes, format, unicodeRange? }の形で、formatはwoff2、woff、ttf、otfのいずれかです。各フェイスは1つのファイルと1つの@font-face規則になり、unicodeRangeを持つ複数のファイルで1つのフェイスを構成することもできます(Google Fontsのスライス)。ページが使うファミリー、ウェイト、スタイルに埋め込みフェイスがない場合はmissingFontとして報告され、リーディングシステムが独自のフォントで代替します。ライセンスが許すフォントだけを埋め込んでください。resourceBytes(fileId):ページが配置する画像を、同期または非同期で{ bytes, mediaType }として返します。mediaTypeが空ならバイト列から判別します。ビットマップは格納されたまま、SVGはソースのまま渡し、PDFの印刷用マスター(svg.pdfFileId)は渡さないでください。各画像は1回だけ格納されます。単色インクの本(diagramStyle.singleInk)では、ファイル内のSVGが色替えされます。バイト列のない画像はmissingImageとして報告され、空の枠として残ります。cover:{ bytes, mediaType, alt? }。JPEG、PNG、WebP、SVGの画像です。指定すると、本はそれを収めた表紙の文書から始まり、その画像がパッケージのcover-image(ライブラリーのサムネイル)になります。指定しなければ、固定レイアウトでは最初のページが表紙として指定され、リフロー型の本には表紙画像がありません。onProgress({ phase, done, total }):resources(フォントと画像)、documents(固定レイアウトではページ、リフロー型では章)、packageの順に進みます。signal:ステップの合間で中止します。readEpub(bytes):ビューアーのためにファイルを読み戻します。DOMParserは使いません。レイアウト、メタデータ、読む方向、パスごとのすべてのファイル、マニフェスト、スパイン、目次、ページリスト、固定レイアウトのビューポート、表紙を返します。Sandboxのリーダーはこれをもとに作られています。
どちらのレンディションも、EPUB Accessibility 1.1のメタデータ(アクセスモード、目次や印刷ページ番号などの機能、ハザード、概要)を持ち、既定ではWCAGへの準拠を主張しません。オプションの一覧と制限事項はパッケージのREADMEにあります。
#EPUBCheckでファイルを検査する
W3C EPUBCheckはEPUBのリファレンスとなる検証ツールで、電子書籍ストアは受け取ったファイルをこれで検査します。インストールすると(brew install epubcheck、またはJava版のリリース)、epubcheck book.epubでエラー、警告、使用上の注意が一覧表示されます。Postextの出力に残る使用上の注意(CSS-028、OBS-001、HTM_062)は情報提供のためのものです。Postextのリポジトリーでは、pnpm --filter postext-epub epubcheckがテストスイートのサンプルの本を検査し、node packages/postext-epub/scripts/epubcheck.mjs book.postext --layout bothは1つの.postextファイルまたはプリセットのフォルダーをレイアウトして両方のレンディションを検査し、pnpm --filter postext-epub validateは本の組み合わせ全体(Postextガイド、ショーケースのプリセット、中国語、アラビア語、レシピ集の本)を検査します。どの本もエラーも警告もなく合格します。
#バンドル(.postextファイル)
.postextファイルは、1冊の本をまるごと収めた1つのファイルです。zipアーカイブで、preset.jsonマニフェスト、章ごとに1つのMarkdownファイル、リソースのデータ本体(ビットマップ、SVG、PDFの印刷用マスター)、設定が指定するフォントファイルを収めます。Sandboxがこれを書き出し・読み込みし、エージェントスキルがこれを出力します。postextパッケージでも作成し、開くことができるので、本はこれらのツールと自分のプログラムの間を何も失わずに行き来できます。
my-book.postext
├── preset.json manifest: name, locale, chapters, config, resources, fonts
├── chapters/01-dusk.md
├── chapters/02-night.md
├── resources/lantern.svg
└── fonts/ebgaramond-400-normal.woff2
マニフェストの各フィールドは、Sandboxの付録プリセットバンドルの形式で説明しています。ファイルにはlayouts.jsonを含めることもできます。これはSandboxのページ数の記録で、本がSandboxで最初からページ分けされた状態で開きます。多言語の本では版ごとに1つずつ持つこともできます(layouts.zh-Hant.json。こちらが先に読まれます)。openBundleはこれらを無視します。
APIはpostext本体と、低レベルのヘルパーを加えたpostext/bundleサブパスから書き出されています。描画もする場合はpostextからインポートしてください。そうすればバンドルのアダプターとレンダラーが1つのモジュールインスタンスを共有します。これは、エントリーポイントごとに別のビルドになるesm.shのようなCDNで重要です。
#バンドルを開く
openBundleはファイルのバイト列(Uint8Array、ArrayBuffer、または<input type="file">から得たBlob / File)を受け取り、エンジンとそのバックエンドが必要とするものをすべて返します。
import { openBundle } from 'postext';
const bundle = await openBundle(await file.arrayBuffer(), { locale: 'es' });
bundle.chapters; // [{ title, file, markdown }, …] in book order
bundle.config; // PostextConfig, ready for buildDocument
bundle.resources; // Resource[]
bundle.files; // Map<path, Uint8Array>: every file of the bundle| フィールド | 内容 |
|---|---|
manifest | 検証済みのpreset.json。 |
id, name, description | マニフェストから取得した値。 |
locale, locales | 内容を読んだロケールと、多言語のバンドルが持つすべてのロケール。options.localeでどれを読むかを選びます。まず完全に一致するタグ、次に基本言語、最後にバンドル自身のロケールの順に探します。 |
chapters | 章ごとの{ title, file, markdown }。マニフェストにタイトルがない章は、最初の#見出しのテキストをタイトルにします。 |
config | 既定のカラーパレットとバンドルの言語でのリソースタイプの上に、マニフェストのconfig、さらにロケールの上書きを重ねたもの。バンドルの言語は上のlocaleなので、1言語のバンドルはoptions.localeが何を求めても、その言語のラベルになります。言語を指定していないマニフェストでは、そのconfigが設定する言語(locale、次にハイフネーションのロケール)を使い、それもなければoptions.localeを使います。customFontsにはバンドルのフォントファミリーが並びます。Sandboxがバンドルを開くときと同じ設定です。 |
resources | 選んだロケールのキャプションを付けたリソース。マニフェストにないサイズはファイルから読み取ります。 |
fonts | フェイスごとに1項目:{ family, weight, style, format, file, bytes }。 |
files | アーカイブ内のすべてのファイル。キーはそのパスです。 |
thumbnail, canvasScope | 表紙画像のパスと、バンドルが求める表示方法。マニフェストのviewの上に、読み込んだ言語のlocalized[…].viewを重ねたものです。 |
warnings | 致命的でない問題。対応していないフォントファイル、印刷用マスターの欠落など。 |
データ本体のfileIdは、バンドル内でのそのパスです。resource.svg.fileId、resource.bitmap.fileId、customFontsの各バリアントのfileIdは、そのままbundle.filesで引けます。openBundleは、バイト列がzipでない場合、有効なpreset.jsonが(ルートにも、トップレベルの1つのフォルダーの下にも)ない場合、マニフェストが指定するファイルが欠けている場合に例外を投げます。
postext 1.4以前で書き出したバンドル
createBundleとSandboxが書き出すマニフェストには、必ずconfigVersion: 8が入っています。これは、そのconfigがどの設定規則を前提に書かれたかを示します。これを持たないマニフェストはpostext 1.4以前で書き出されたもので、次の13点の扱いが現在と異なります。
- 見出しの改ページ(規則3):1.4までは、H1の改ページを持たない
headingsオブジェクトでは改ページが入りませんでした(レベルごとの上書きを参照)。 - 数式のサイズ(規則4):1.4までは、数式が
fontSizeScaleの指定より1.131倍大きく組まれていました(数式のサイズを参照)。 - インラインのリソースの下のアキ(規則5):1.4までは、
placement.position: 'here'の図や表の後のテキストが、その下にフロートのアキを取らずに次のグリッド線から再開していました(レイアウトのlayout.inlineResourceGapを参照)。 - ボックスの中のインラインのリソースの周りのアキ(規則6):1.4までは、そうしたリソースが周りのボックスのテキストに接して置かれていました(レイアウトの
layout.inlineResourceGapInBoxesを参照)。 - 見出し内の文字書式(規則6):1.4までは、見出しの中の
*italic*、**bold**などのマークが付いた語を、見出し自身の素のスタイルで印字していました(見出しのheadings.inlineMarksを参照)。 - ドロップキャップのサイズ(規則6):1.4までは、
fontSizeのないデザインテキストのdropCapは、またがる行ボックス全体と同じ高さになり、上端が1行目より上に出ていました(テキスト要素のdropCapを参照)。 - コロンで終わる行の下の余地(規則6):1.4までは、
keepColonWithListはコロンで終わる行の下に1行分の余地があればリストには十分とみなしていました。そのため、オーファンとウィドウの規則が分割しない2行の最初の項目は、コロンの行を残して次の段に送られていました(bodyText.colonListRoomを参照)。 - ボックスの分割で残る行(規則6):1.4までは、段落やリスト項目の途中で分割したボックスは、ボックスの各側に合計で
splitMinLines行あれば、その段落や項目の1行だけを片側に残すことがありました(レイアウトのlayout.boxChildSplitMinLinesを参照)。 - ダッシュでの改行(規則7):1.4までは、Knuth-Plassは語と語の間に前後を詰めて置いたemダッシュやenダッシュ(
say—that’s)の後で行を終えることがなく、書式付きのテキストを1行ずつ組む改行処理は2文字の間でだけ改行していました(本文のbodyText.breakAfterDashesを参照)。 - 行末不ぞろいのテキスト(規則7):1.4までは、
optimalLineBreakingの値にかかわらず、行末不ぞろいの本文テキストを1行ずつ、各行を埋めてから次の行に進んで組んでいました(本文のbodyText.optimalRaggedを参照)。 - 見出しの下での分割(規則8):1.4までは、段の末尾にある見出しの下の段落は、次の段に送られる行がどれほど少なくても、そこに入るだけの行を残していました(見出しの
headings.keepWithNextSplitを参照)。 :::paragraphsコンテナーの下のアキ(規則8):1.4までは、スタイルのアキをグリッドへのスナップの前に最後の段落の下に置き、その下に次のブロックの上のアキ(見出しのmarginTop)を加え、テキストの段落間隔は含めていませんでした(本文のbodyText.paragraphContainerSpacingを参照)。- 複合語のハイフンでの改行(規則8):1.4までは、インライン書式のない段落では、Knuth-Plassは2文字の間のハイフン(
well-known)の後で両端そろえの行を終えることがなく、書式のある段落では終えていました(本文のbodyText.breakAfterHyphensを参照)。
openBundleとreadBundleは、そのようなマニフェストのconfigと、各ロケールのlocalizedの設定をmigrateConfigを通して読み込みます。この関数は1.4が組んだ改ページを明示的に書き出し、数式の倍率に1.131を掛けます(emで指定した別行立て数式のアキはこの値で割ります)。1.5のプレリリースが書いた3から7の値を持つマニフェストには、その値より後の規則の固定だけが入ります。3なら数式のサイズ、インラインのアキ、規則6の5つの固定、規則7の2つ、規則8の3つ。4ならインラインのアキと、規則6、7、8の固定。5なら規則6、7、8の固定。6なら規則7と8の固定。7なら規則8の固定だけです。見出しの下での分割(pinLegacyHeadingSplit)は、各層を重ねた結果有効になるheadingsにheadings.keepWithNextSplit: 'fill'として書き込まれます。条件は、読み込んだ章に見出しがあり、設定が独自の値を指定しておらず、headings.keepWithNextを有効のままにし、bodyText.avoidOrphansを無効にしていないことです。複合語の改行(pinLegacyHyphenBreaks)は、有効なbodyTextにbodyText.breakAfterHyphens: falseとして書き込まれます。条件は、読み込んだ章に2文字の間のハイフンがあり、設定がこの値をすでに設定しておらず、optimalLineBreakingも無効にしていないことです。コンテナーの下のアキ(pinLegacyParagraphContainerSpacing)は、有効なbodyTextにbodyText.paragraphContainerSpacing: 'add'として書き込まれます。条件は、設定が段落スタイルを(paragraphStylesまたはHTMLビューアーの上書きで)宣言し、読み込んだ章が単独の行で:::paragraphsコンテナーを開き、設定がこの値をすでに設定していないことです。ダッシュでの改行(pinLegacyDashBreaks)は、有効なbodyTextにbodyText.breakAfterDashes: falseとして書き込まれます。条件は、読み込んだ章に語と語の間に前後を詰めて置いたemダッシュやenダッシュがあり(前に文字、数字、閉じる約物があり、後ろに文字、数字、開き括弧や開き引用符があるもの。ダッシュの前の引用符は、その引用符の前に文字、数字、閉じる約物、ノーブレークスペースがあれば数に入ります。"no"—andは該当し、said "—Holaは該当しません。ダッシュや引用符に接するインラインのマーク、たとえば**riddles.**—Iの**は、どちら側にあっても数に入ります)、設定がこの値をすでに設定していないことです。行末不ぞろいの改行(pinLegacyRaggedBreaking)は、有効なbodyTextにbodyText.optimalRagged: falseとして書き込まれます。条件は、設定が本文テキストのどこかを行末不ぞろいにし(本文テキスト、段落スタイル、囲みの本文、部の本文、節のスタイルの本文(headingStyles[].bodyStyle)、またはHTMLビューアーの上書きで、'justify'以外のtextAlignを指定する)、この値をすでに設定しておらず、optimalLineBreakingも無効にしていないことです。ボックスの中のアキ(pinLegacyBoxResourceGap)は、有効なlayoutにlayout.inlineResourceGapInBoxes: falseとして書き込まれます。条件は、読み込んだ章の:::calloutの中にリソースが単独の行で埋め込まれ、設定がこの値をすでに設定していないことです。ボックスの分割(pinLegacyBoxChildCut)は、有効なlayoutにlayout.boxChildSplitMinLines: 1として書き込まれます。条件は、読み込んだ章が単独の行で:::calloutを開き、設定がこの値をすでに設定していないことです。見出しのマーク(pinLegacyHeadingMarks)は、各層を重ねた結果有効になるheadingsにheadings.inlineMarks: falseとして書き込まれます。条件は、読み込んだ章の見出しがマーク(タイトル内の*、_、^、~、:smallcaps[、またはリンク)を含み、設定が独自の値を指定していないことです。ドロップキャップ(pinLegacyDropCapSize)は、どこにあっても1.4のサイズがdropCap.fontSizeとして書き出されます。単位は、要素の行送りが長さで指定されていればその単位、そうでなければフォントサイズの単位です。コロンで終わる行の下の余地(pinLegacyColonListRoom)は、有効なbodyTextにbodyText.colonListRoom: 'line'として書き込まれます。条件は、読み込んだ章のリストがコロンで終わる行に続き(間に空行があってもかまいません)、設定が余地を指定しておらず、keepColonWithListも無効にしていないことです。インラインのアキ(pinLegacyInlineGap)は、各層を重ねた結果有効になるlayoutにlayout.inlineResourceGap: 'above'として書き込まれます。条件は、読み込んだ章のある行がリソースを埋め込み(パーサーの解釈どおり、::resource{id="…"}が単独で行にあるもの。本文中やコードスパンでの言及は数に入りません)、設定が独自のアキを指定していないことです。サイズは、各層を重ねた結果有効になるmathに固定されます(ロケール独自のmathは共有のものを置き換えます)。これは読み込んだ章に$がある場合に限られ、数式のないバンドルのconfigは書かれたまま残ります。マニフェストもロケールもmathを指定しない場合、有効なものはreadBundleのbaseConfig(読み込む側自身の設定)のもので、これも固定されます。1.4はバンドルの数式をそのサイズで組んでいたからです。基本設定のfontSizeScale: 1.5は1.5 × 1.1312として読まれます。基本設定の見出しの改ページはそのまま使われます。このように古いバンドルはこれらの規則で組まれた結果を保ち、bundle.configには、実際に組むときの改ページ、数式のサイズ、アキ、見出しのマーク、ドロップキャップのサイズ、コロンで終わる行の下の余地、ボックスの分割、ダッシュでの改行、複合語の改行、行末不ぞろいの改行、見出しの下での分割、コンテナーの下のアキが表示されます。1.5のレイアウトの修正には固定がなく、ほかの本と同じく古いバンドルにも適用されるため、それらが関わるページは動くことがあります(一覧は数式のサイズを参照)。現在の規則に合わせて手で書くpreset.jsonには"configVersion": 8を指定します。古いバンドルのマニフェストにこの値を書き込むのも、現在の規則で読み込むための1行で済む方法です(その場合、バージョンのないバンドルは見出しの改ページの固定も失います)。
import { CONFIG_VERSION, migrateConfig } from 'postext/bundle';
migrateConfig({ headings: { fontFamily: 'Georgia' } }, undefined, { content: 'A book with no maths.' });
// => { headings: { fontFamily: 'Georgia', levels: [{ level: 1, breakBefore: { enabled: false } }] } }
migrateConfig({ math: { fontSizeScale: 1.2 } }, 3);
// => { math: { fontSizeScale: 1.35746…, marginTop: { value: 0.7072, unit: 'em' }, marginBottom: { value: 0.7072, unit: 'em' } },
// layout: { inlineResourceGap: 'above', inlineResourceGapInBoxes: false, boxChildSplitMinLines: 1 },
// headings: { inlineMarks: false, keepWithNextSplit: 'fill' }, bodyText: { colonListRoom: 'line', breakAfterDashes: false, breakAfterHyphens: false } }
migrateConfig({ layout: { layoutType: 'single' } }, 4, { content: 'Text.\n\n::resource{id="fig"}' });
// => { layout: { layoutType: 'single', inlineResourceGap: 'above' } }
migrateConfig({ layout: { layoutType: 'single' } }, 5, { content: ':::callout\nText.\n\n::resource{id="fig"}\n:::' });
// => { layout: { layoutType: 'single', inlineResourceGapInBoxes: false, boxChildSplitMinLines: 1 } }
migrateConfig({ bodyText: { textAlign: 'left' } }, 6, { content: 'I say—that is all.' });
// => { bodyText: { textAlign: 'left', breakAfterDashes: false, optimalRagged: false } }
migrateConfig({ paragraphStyles: [{ id: 'verse' }] }, 7, { content: ':::paragraphs{style="verse"}\nA line.\n:::' });
// => { paragraphStyles: [{ id: 'verse' }], bodyText: { paragraphContainerSpacing: 'add' } }
migrateConfig({ bodyText: { fontFamily: 'Georgia' } }, 7, { content: 'A well-known tale.' });
// => { bodyText: { fontFamily: 'Georgia', breakAfterHyphens: false } }
migrateConfig(config, CONFIG_VERSION); // today's rules: `config` itselfcontentは、その設定で組むMarkdownです(文字列または章のリスト)。これがない場合、数式のサイズは数式が有効なら常に、コンテナーの下のアキは設定が段落スタイルを宣言していれば常に固定されます。2つのアキ、見出しのマーク、コロンで終わる行の下の余地、ボックスの分割、ダッシュでの改行、見出しの下での分割、複合語の改行は無条件に固定されます。本に数式、:::paragraphsコンテナー、インラインの図、マークを含む見出し、コロンで導入するリスト、ボックス、詰めて置いたダッシュ、見出し、複合語があるかどうかを、エンジンが判断できないためです。行末不ぞろいの改行は、内容の有無にかかわらず設定だけで固定されます。保存した設定は一度だけ移行し、CONFIG_VERSIONで保存し直してください。数式の固定は倍率に掛け算をするので、2回移行した設定は2回大きくなります。
#バンドルを組んで描画する
開いたバンドルをエンジンとバックエンドにつなぐヘルパーは4つあります。
loadBundleFonts(bundle):バンドルのフェイスをdocument.fontsに登録します。レイアウトはブラウザーが持つフォントでテキストを計測するので、組む前にその完了を待ってください。バンドルが指定していても収めていないファミリー(Google Fonts)は、ほかの文書と同じく自分で読み込む必要があります。registerBundleImages(bundle):canvasバックエンド(renderPage、renderToCanvas)のために画像をデコードします。動画のポスターも含みます。renderToHtmlのリゾルバーは2つあり、bundleImageUrl(bundle)(resourceImageUrl)は画像を、bundleVideoUrl(bundle)(resourceVideoUrl)はバンドルが収める動画ファイルを解決します。どちらもdiagramStyle.singleInkが有効な場合はSVGの図を一度だけ色替えします。マークアップを色替えし、画像に印を付けて、どのバックエンドも重ねて色を付けないようにします(キャンバスとHTMLでの単色刷りを参照)。buildBundle(bundle):章を順に組み、章ごとに1つのVDTDocumentを返します。各章は前の章を引き継ぎます。見出しとリソースのカウンター、開いている部、ページの奇偶、ノンブルです。目次(:::toc)や索引(:::index)を印字する章は、本全体のアウトラインを受け取ります。buildDocumentと同じオプションに加えて、バンドルの設定を上書きするconfig、計測キャッシュを共有するcache、metadata(後述)を受け付けます。bundleResourceBytes(bundle)、bundleFontProvider(bundle, { decodeWoff2, fallback }):postext-pdfのrenderToPdfのresourceBytesオプションとfontProviderオプションです。フォントプロバイダーは、求められたスタイルのうち最も近いウェイトをバンドルから選びます。.woff2のフェイスにはdecompressWoff2が必要です。バンドルが収めていないファミリーについては、requestを含むレンダラーの引数でfallbackを呼び、その戻り値をそのまま渡します。ファミリーのlatinファイルだけを取得するフォールバックでは、バンドルが埋め込んでいない中国語のファミリーが空の四角として印字されます。中国語・日本語・韓国語のフォントのsliceFontProviderのようにスライスで応えるフォールバックなら、すべて印字されます。
import { openBundle, loadBundleFonts, registerBundleImages, buildBundle, renderPage,
bundleResourceBytes, bundleFontProvider } from 'postext';
import { renderToPdf, decompressWoff2 } from 'postext-pdf';
const bundle = await openBundle(bytes);
await loadBundleFonts(bundle);
await registerBundleImages(bundle);
const docs = buildBundle(bundle); // one VDTDocument per chapter
const firstPage = renderPage(docs[0].pages[0], docs[0]); // a <canvas>
const pdf = await renderToPdf(docs, { // the whole book
fontProvider: bundleFontProvider(bundle, { decodeWoff2: decompressWoff2, fallback: fontsource }),
resourceBytes: bundleResourceBytes(bundle),
});1つの章だけを自分で組むには、ほかの文書と同じく、bundle.chapters[i].markdown、bundle.resources、bundle.configをbuildDocumentに渡します。
本のメタデータ。Sandboxと同じく、最初の章のフロントマターが本のメタデータになります。buildBundleはそのtitle、authorなどをすべての章に渡すので、{title}や{author}の柱はどのページにも入り、どの章のdoc.metadataにもそれらが入ります。2章目以降の先頭にあるフロントマターのブロックは無視されます。---の行で見つけられ、解析されずに空白に置き換えられるので、パーサーが受け付けないYAMLでも害はありません。options.metadataは、フロントマターが設定しない値を補います(フロントマターが優先されます)。本のページ数もすべての章に届きます。{bookTotalPages}はそれを印字し、{totalPages}は章のページを数えます(本全体のページ数を参照)。
const docs = buildBundle(bundle, { metadata: { author: 'A. Author' } });
docs[3].metadata.title; // the first chapter's `title:`#ライブサンプル:バンドルを開く
このCodePenのサンプルは、リポジトリーから2章の見本の本(独自の書体、SVGの図、表を持つlantern.postext)を読み込みます。バンドルのフォントと画像を登録し、buildBundleで本を組んで、全ページを描画します。「Make the PDF」ボタンは、同じ文書をpostext-pdfで描画し、バンドルのフォントを埋め込みます。Sandboxから書き出したものなど、自分の.postextファイルを選べば、同じように表示されます。
import {
openBundle,
loadBundleFonts,
registerBundleImages,
buildBundle,
bundleResourceBytes,
bundleFontProvider,
renderPage,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
// A two-chapter book with its own typeface, an SVG figure and a table.
const SAMPLE = 'https://cdn.jsdelivr.net/gh/drnachio/postext@main/docs/examples/open-bundle/lantern.postext';
const status = document.getElementById('status');
const pdfButton = document.getElementById('pdf');
let current = null;
async function show(data) {
// Chapters, config (fonts wired to the bundle's own files), resources and
// every file, keyed by its path inside the bundle.
const bundle = await openBundle(data);
// Layout measures text with the fonts the browser has: register the
// bundle's faces, and load the Google Fonts it names but does not carry
// (the default running heads use Open Sans; see the pen's CSS).
await loadBundleFonts(bundle);
await document.fonts.load('600 16px "Open Sans"');
await registerBundleImages(bundle);
// One VDTDocument per chapter, each continuing the one before it.
const docs = buildBundle(bundle);
const pages = docs.flatMap((doc) => doc.pages.map((page) => renderPage(page, doc)));
document.getElementById('pages').replaceChildren(...pages);
status.textContent = `${bundle.name} · ${bundle.chapters.length} chapter(s) · ${pages.length} page(s)`
+ (bundle.warnings.length ? ` · ${bundle.warnings.length} warning(s)` : '');
current = { bundle, docs };
pdfButton.disabled = false;
document.getElementById('links').hidden = true;
}
// Fonts the bundle does not carry come from Fontsource.
async function fontsource(family, weight, style) {
const id = family.toLowerCase().replace(/\s+/g, '-');
const res = await fetch(`https://cdn.jsdelivr.net/npm/@fontsource/${id}@latest/files/${id}-latin-${weight}-${style}.woff2`);
if (!res.ok) throw new Error(`font fetch failed: ${res.status} ${family}`);
return decompressWoff2(new Uint8Array(await res.arrayBuffer()));
}
pdfButton.addEventListener('click', async () => {
pdfButton.disabled = true;
status.textContent = 'Rendering the PDF…';
const { bundle, docs } = current;
const bytes = await renderToPdf(docs, {
fontProvider: bundleFontProvider(bundle, { decodeWoff2: decompressWoff2, fallback: fontsource }),
resourceBytes: bundleResourceBytes(bundle),
});
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }));
document.getElementById('open').href = url;
document.getElementById('download').href = url;
document.getElementById('links').hidden = false;
status.textContent = `${bundle.name} · ${(bytes.length / 1024).toFixed(0)} KB PDF`;
pdfButton.disabled = false;
});
document.getElementById('file').addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
status.textContent = `Opening ${file.name}…`;
await show(file).catch((err) => { status.textContent = `Could not open ${file.name}: ${err.message}`; });
});
const res = await fetch(SAMPLE);
await show(await res.arrayBuffer());index.html
<p>
<label>Open a .postext file: <input id="file" type="file" accept=".postext,application/zip"></label>
<button id="pdf" disabled>Make the PDF</button>
<span id="links" hidden>
<a id="open" target="_blank" rel="noopener">open it</a> ·
<a id="download" download="book.pdf">download it</a>
</span>
</p>
<p id="status">Loading the sample book…</p>
<div id="pages"></div>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
#pages {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: flex-start;
}
#pages canvas {
display: block;
width: 240px;
height: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}codepen.ioからインタラクティブなエディターを読み込みます。サンプルは最新リリースのpostextをCDNから読み込みます。
#バンドルを作成する
createBundleは、文書(章、設定、リソース、それらが参照するデータ本体)から.postextファイルを書き出します。
import { createBundle } from 'postext';
const { bytes, manifest, warnings } = await createBundle({
name: 'The Lantern',
locale: 'en',
chapters: [
{ markdown: '# Dusk\n\nIt is drawn in :ref{id="lantern"}.' },
{ title: 'Night', markdown: '# Night\n\n…' },
],
config,
resources: [{
id: 'lantern', typeId: 'figure', kind: 'svg', caption: 'The lantern.',
svg: { fileId: 'lantern.svg', width: 240, height: 150 },
createdAt: 0, updatedAt: 0,
}],
files: { 'lantern.svg': svgMarkup, 'garamond-regular': fontBytes },
});| 入力 | 意味 |
|---|---|
name, id, description, locale | マニフェストのメタデータ。idの既定値はnameのスラッグです。 |
chapters or markdown | 本の内容。章ごとに1つの{ title?, markdown }、または1つの文書です。 |
config | PostextConfig。既定値と同じ値はマニフェストに書き出しません。 |
resources | リソース。画像はbitmap.fileId / svg.fileId(印刷用マスターはsvg.pdfFileId)でデータ本体を指定します。 |
files | fileIdごとのデータ本体(オブジェクトまたはMap)。リソースが参照する画像と、config.customFontsのバリアントが参照するフォントファイルです。値はUint8Array、ArrayBuffer、Blob、文字列(SVGのマークアップ)のいずれかです。 |
thumbnail | { data, mime }:表紙画像(PNG、JPEG、WebP、GIF、SVG)。 |
canvasScope | 'book'にすると、本全体を1つのキャンバスとして組むようビューアーに求めます。 |
mtime | アーカイブのすべてのファイルに書き込む更新日時(Date、タイムスタンプ、日付文字列のいずれか)。省略すると呼び出した時刻になるので、同じ入力でも2回の呼び出しで異なるバイト列になります。固定の日付を渡せば同じ入力から同じバイト列が得られ、ハッシュを取ったり比較したりできます。zipが保持する日時はタイムゾーンを持たず、2秒刻みで、1980年から2099年までです。日付はマシンのローカル時刻で書き込まれます。どのマシンでも一致するバイト列を得るには、new Date(1980, 0, 1)のようにローカル時刻の各フィールドから日付を作ります。タイムスタンプやZで終わる文字列は特定の瞬間を指すので、タイムゾーンごとに異なるローカル時刻になります('1980-01-01T00:00:00Z'はUTCより西では1979年のままです)。ローカル時刻でこの範囲の外にある日付は例外になります。 |
localized | 同じ本の別の言語。ロケールタグごとに{ es: { chapters?, config?, resources? } }の形で指定します。この場合、上の入力は、必須のlocaleの内容になります。多言語のバンドルを参照してください。 |
戻り値は、アーカイブのbytes、preset.jsonとして書き出したmanifest、すべてのファイルを収めたfiles(パス → バイト列)、warningsのリストです。ファイル名はリソースのid(resources/lantern.svg)またはフォントのファイル名(fonts/…)から、章のファイル名は順番とタイトル(chapters/01-dusk.md)から付けます。フォントはマニフェストのfontsで宣言し、config.customFontsの中には決して書きません。次のものは、それぞれ警告を出して除外します。
- データ本体が
filesにないリソースやフォントのフェイス .woffのフェイス(PDFバックエンドが埋め込めないため)redistributable: falseが付いたファミリー
ブラウザーでは、bytesをダウンロードリンクに渡します:URL.createObjectURL(new Blob([bytes], { type: 'application/zip' }))。Nodeではfs.writeFileで書き込みます。createBundleとopenBundleにDOMは不要です。配布物のモジュールパスには拡張子がないので、バンドラーを使わない素のNodeではresolveフックが必要です。リポジトリーのdocs/examples/open-bundle/build-sample.mjsに、数行で書いた例があります。
#多言語のバンドル
.postextファイルは本を複数の言語で収めることができ、openBundle(bytes, { locale })はそのどの言語でも読み込めます。createBundleはlocalizedからこれを書き出します。追加のロケールごとに1項目で、それぞれ主となる内容(localeの入力)と異なる部分を持ちます。
const { bytes, manifest } = await createBundle({
name: 'The Lantern',
locale: 'en',
chapters: [{ markdown: '# Dusk\n\n…' }, { markdown: '# Night\n\n…' }],
config,
resources: [lanternFigure, hoursTable],
files: { 'lantern.svg': svgEn, 'lantern-es.svg': svgEs },
localized: {
es: {
chapters: [{ markdown: '# Anochecer\n\n…' }, { markdown: '# Noche\n\n…' }],
config: { headings: { levels: [{ level: 1, numberingTemplate: 'Capítulo {1}' }] } },
resources: [
{ id: 'lantern', caption: 'El farol.', svg: { fileId: 'lantern-es.svg', width: 240, height: 150 } },
{ id: 'hours', caption: 'Horas de luz.' },
],
},
},
});
const es = await openBundle(bytes, { locale: 'es' }); // Spanish chapters, config and captionschapters:その言語での本。章のファイルはロケールごとに1つのフォルダーに入り(chapters/en/01-dusk.md、chapters/es/01-anochecer.md)、マニフェストのchaptersはロケール → 章のマップになります。chaptersのないロケールは主言語の章を読みます。独自の章を持つロケールが1つもなければ、章は1つのリストのままです。config:その言語の設定。そのロケールでバンドルを読むと、トップレベルの各キーが共有のキーを丸ごと置き換えます。上の例のheadingsはheadingsオブジェクト全体を置き換えます。省略したキーや共有のものと同じキーは共有され、書き出されません。そのため、ロケールの設定全体を渡しても、変わる数個のキーだけを渡しても同じように動きます。共有のキーが既定値でないのにロケール側を既定値にしたキー(layout: {})は指定どおり書き出されるので、共有の値をリセットします。フォントは共有です。ロケールのcustomFontsのファミリーはバンドルのfontsに加わります。resources:共有のリソースの文言で、idで対応づけます。caption、note、altText、表のtableです。文字を含む画像は独自のアートワークを持てます。bitmap.fileIdやsvg.fileId(とsvg.pdfFileId)でfiles内の別のデータ本体を指定すると、resources/es/lantern.svgとして書き出されます。タイプや配置などほかのフィールドは共有です。resourcesにないidは警告を出して除外し、ロケールの画像が欠けている場合も、警告を出してそのロケールは共有の画像を使います。
マニフェストはlocalesにすべての言語を並べ(['en', 'es'])、主言語をlocaleに残し、それ以外をlocalizedに格納します。ロケールを指定しないopenBundleは主言語を読みます。
読み手が得る言語。openBundle(bytes, { locale })は、完全に一致するロケール、なければその基本言語(es-MXならes)、それもなければ主言語を返し、どれを返したかをbundle.localeが示します。章と文言は常に同じ言語から取られます。localizedが主言語の地域変種を持っていても、主言語は共有の文言を保ちます。pt-BRの項目を持つpt-PTのバンドルは、pt-BRに対してだけブラジルのキャプションを読み、pt-PTとptには共有のキャプションを読みます。
#ライブサンプル:バンドルを作成する
このCodePenのサンプルは、SVGの図を持つ2章の本を作り、createBundleが書き出したファイルとマニフェストを一覧表示します。アーカイブをダウンロードできるようにしてから、openBundleで開き直して最初のページを描画します。数行で往復のすべてを行うサンプルです。ダウンロードしたファイルをSandboxで読み込めば、そこで作業を続けられます。
import { createBundle, openBundle, registerBundleImages, buildBundle, renderPage } from 'https://esm.sh/postext';
// A picture resource names its payload by fileId; the bytes (here, SVG
// markup) go in `files` under that same id.
const lanternSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 150">
<rect width="240" height="150" fill="#f3efe6"/>
<path d="M100 36 h40 l8 14 h-56 z" fill="#2f3e46"/>
<rect x="98" y="50" width="44" height="58" rx="4" fill="#f6c453" stroke="#2f3e46" stroke-width="4"/>
<circle cx="120" cy="79" r="11" fill="#fff4c2"/>
<path d="M94 108 h52 l-6 12 h-40 z" fill="#2f3e46"/>
</svg>`;
const resources = [{
id: 'lantern',
typeId: 'figure',
kind: 'svg',
caption: 'The lantern by the door.',
svg: { fileId: 'lantern.svg', width: 240, height: 150 },
createdAt: 0,
updatedAt: 0,
}];
const text = 'The lantern hung from a nail by the door, and every evening someone lit it. Nobody remembered who had put the nail there, or why the lantern was never moved.';
// One entry per chapter; a chapter without a title takes its first # heading.
const chapters = [
{ markdown: `# Dusk\n\n${text} It is drawn in :ref{id="lantern"}.\n\n${text}\n\n${text}` },
{ markdown: `# Night\n\n${text}\n\n${text}` },
];
const config = {
layout: { layoutType: 'double' },
// Two short chapters that run on, with no blank verso between them (an
// H1 otherwise opens on a fresh recto), as in the open-bundle sample.
headings: { levels: [{ level: 1, numberingTemplate: 'Chapter {1}', breakBefore: { enabled: false } }] },
};
// Everything a .postext file holds: manifest, chapters, resources, fonts.
const { bytes, manifest, files, warnings } = await createBundle({
name: 'The Lantern',
locale: 'en',
chapters,
config,
resources,
files: { 'lantern.svg': lanternSvg },
});
if (warnings.length) console.warn(warnings);
const url = URL.createObjectURL(new Blob([bytes], { type: 'application/zip' }));
document.getElementById('download').href = url;
document.getElementById('actions').hidden = false;
document.getElementById('files').replaceChildren(...Object.entries(files).map(([path, data]) => {
const li = document.createElement('li');
li.textContent = `${path} (${data.length} B)`;
return li;
}));
document.getElementById('manifest').textContent = JSON.stringify(manifest, null, 2);
// Round trip: open the file just written, the way any program would.
await Promise.all([
document.fonts.load('16px "EB Garamond"'),
document.fonts.load('bold 16px "EB Garamond"'),
document.fonts.load('bold 16px "Open Sans"'), // the default heading face
]);
const bundle = await openBundle(bytes);
await registerBundleImages(bundle);
const [firstChapter] = buildBundle(bundle);
document.getElementById('page').replaceChildren(renderPage(firstChapter.pages[0], firstChapter));
document.getElementById('status').textContent =
`${bundle.name}: ${bundle.chapters.length} chapters, ${(bytes.length / 1024).toFixed(1)} KB`;index.html
<p id="status">Building the bundle…</p>
<p id="actions" hidden>
<a id="download" download="lantern.postext">Download lantern.postext</a> ·
<a href="https://postext.dev/en/sandbox" target="_blank" rel="noopener">open the Sandbox</a> and import it (Projects → New → Import .postext…)
</p>
<div id="output">
<section>
<h3>Files in the bundle</h3>
<ul id="files"></ul>
<h3>preset.json</h3>
<pre id="manifest"></pre>
</section>
<section>
<h3>Opened again: page 1</h3>
<div id="page"></div>
</section>
</div>style.css
body {
margin: 16px;
font-family: system-ui, sans-serif;
background: #e8e8e8;
}
#output {
display: flex;
flex-wrap: wrap;
gap: 24px;
align-items: flex-start;
}
#output section {
flex: 1 1 280px;
min-width: 0;
}
h3 {
margin: 8px 0;
font-size: 14px;
}
ul {
margin: 0;
padding-left: 20px;
font-family: ui-monospace, monospace;
font-size: 13px;
}
pre {
max-height: 320px;
overflow: auto;
padding: 8px;
background: #fff;
font-size: 12px;
}
#page canvas {
display: block;
max-width: 100%;
height: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}codepen.ioからインタラクティブなエディターを読み込みます。サンプルは最新リリースのpostextをCDNから読み込みます。
#バンドルの活用
Sandbox、エージェントスキル、postextパッケージはどれも同じファイルを読み書きするので、.postextファイルは本をツールからツールへ渡す手軽な手段になります。
- バンドルから始める。既存の出版物をエージェントスキルで移植するか、Sandboxで本をデザインして書き出します(本パネルの、その本の行の⋯メニューにあるダウンロード(.postext))。そのファイルをプログラムから
openBundleで読み込み、canvas、HTML、PDFに描画します。ファイルを本のソースとして手元に置き、章、設定、リソースをコードで編集してcreateBundleで書き戻すか、変わるたびに読み込み直します。 - Sandboxでデバッグし、微調整する。プログラムの出力に手直しが必要なとき(図が違うページに入る、見出しのスタイル、段末そろえなど)は、プログラムが組んだものを
createBundleで書き出します。そのファイルをSandboxで読み込み(本 → 新規 → .postextファイルを開く…)、ライブプレビュー、検査パネル、PDFビューを見ながらテキスト、デザイン、図を直して、もう一度書き出します。プログラムは修正されたファイルをopenBundleで読み込みます。あるいは、変わった部分をコードに書き戻します。マニフェストのconfigには既定値と異なる値だけが入っているので、短い差分として読めます。
#低レベルAPI
postext/bundleは、openBundleとcreateBundleの土台になる部品も書き出しています。バンドルを独自の方法で保存したり配信したりするホスト(HTTPで配信する展開済みのディレクトリー、データベースのレコードなど)向けです。
openBundleZip(bytes)/zipBundle(files, { mtime }):アーカイブの層。開くときはトップレベルのフォルダーを許容し、__MACOSXの項目とドットファイルを無視します。バンドルの外に出るパスは拒否します。mtimeはcreateBundleの入力と同じようにファイルの日時を決めます。readBundle(manifest, readFile, options):マニフェストとreadFile(path)コールバックから、章、設定、リソース、画像、フォントを読み込みます。optionsでは、ロケール、ファイルidの命名方法(ids)、基本設定(baseConfig。マニフェストの設定の下に置かれます。既定ではバンドルの言語でのbundleBaseConfigのパレットとリソースタイプで、その言語はresolveBundleConfigLocale(manifest, locale)が返します。独自のbaseConfigを渡すホストは、それをこの言語にローカライズしてください。configVersion: 4より古いマニフェストではそのmathがバンドルのものと一緒に固定され、5より古ければlayoutのインラインのアキ、6より古ければlayoutのボックスの中のアキ、bodyTextのコロンで終わる行の下の余地、headingsのインラインのマーク、ドロップキャップのサイズ、7より古ければbodyTextのダッシュでの改行と行末不ぞろいの改行、8より古ければheadingsの見出しの下での分割と、bodyTextの複合語の改行および:::paragraphsコンテナーの下のアキが固定されます。postext 1.4以前で書き出したバンドルを参照)、固有のサイズの計測方法を指定します。planBundle(meta, content)/resolveBundleFiles(plan, sources):書き出し側。純粋な計画(ファイル名とマニフェスト)と、readBlob/readFontコールバックによるバイト列の解決に分かれています。isBundleManifest(value)、ロケールの選択関数(pickChapterSpecs、pickLocaleOverrides、pickBundleView、resolveBundleLocale、resolveBundleConfigLocale)、svgSize/bitmapSize、形式の型(BundleManifest、BundleResourceSpec、BundleFontFamilySpec、…)。CONFIG_VERSION、migrateConfig(config, configVersion, { content })、pinLegacyHeadingBreaks(config)、pinLegacyMathSize(config)、pinLegacyInlineGap(config)、pinLegacyBoxResourceGap(config)、pinLegacyHeadingMarks(config)、pinLegacyDropCapSize(config)、pinLegacyColonListRoom(config)、pinLegacyBoxChildCut(config)、pinLegacyDashBreaks(config)、pinLegacyRaggedBreaking(config)、pinLegacyHeadingSplit(config)、pinLegacyParagraphContainerSpacing(config)、pinLegacyHyphenBreaks(config)、LEGACY_MATH_SIZE(0.5 ÷ 0.442):保存した設定を現在の規則で表したものにします(postext 1.4以前で書き出したバンドルを参照)。readBundleはこれを適用します。設定を独自の方法で保存するホストも、保存したコピーごとに1回ずつ適用できます。
Sandboxはこれらの上に作られています。Sandboxは独自の保存用idとlayouts.jsonのページ記録を加えます。