かんたんな説明
カタルーニャ語の短い小説1冊を小さなペーパーバックに組みます。行末での単語の分け方はカタルーニャ語の表記ガイドに従い、中点の付いた二重のlも正しく分けます。
できあがり
ナルシス・ウリェーの『El transplantat』を、『Obres completes』(バルセロナ、1928年)の本文で全編、12 × 18.5 cmの32ページの文庫本に組みます。4つの章の前に、前扉、窯の前に立つパン屋の口絵、赤いイタリックのCormorant Garamondで組んだ本扉、版についての注記が入ります。各章は12行分の深さの版画の下で始まり、ローマ数字は赤です。本文は10.4 ptのCrimson Proで、Institut d’Estudis Catalansが求めるとおりカタルーニャ語の音節で改行します。分割の両側に最低2文字を残し、母音分立の母音のあいだでは分けず、アポストロフィの直後でも分けません。titil·lavenはtitil- | lavenと分け、ハイフンが中点の代わりになります。会話はemダッシュで、引用は«cometes baixes»で組みます。
このレシピが答える質問
- スペイン語・フランス語・ドイツ語のテキストで、きれいな両端そろえとハイフネーションを得るには?
- 章ごとに異なる写真、色、章扉のバリエーションを与えるには?
- 番号のないアートワーク(オーナメント、カット、ロゴ)を組むには?
- 会話のダッシュ、段落頭の年号、価格、記号そのものを、Markdownに誤読させずに書くには?
手短な答え
const LOCALE = 'ca'; // the exact bundled code: 'ca-ES' resolves to it too (postext ≥ 1.14)
const bodyText = { // config().bodyText
fontFamily: TEXT, fontSize: pt(BODY), lineHeight: pt(LEAD), color: col('ink'),
boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
firstLineIndent: mm(4.5), indentAfterHeading: false,
// The defaults justify, hyphenate in config().locale and break by Knuth–Plass. With 'ca'
// the dictionary follows the IEC: two letters on each side (ter-ra, cai-xa), no split in
// a hiatus (cièn-cia), none after an apostrophe (s’ha-via), and l·l breaks with the hyphen
// in place of the dot. compounds: false keeps arrossegar-se whole but for its own hyphen,
// the break the IEC prefers for a verb and its pronoun (Llibre d’estil, VI 2.5).
hyphenation: { compounds: false },
// Spaces past 1.6× take up to 10‰ of tracking instead; a last line under 22 characters is
// a runt. A line that must keep d’inaugurar-se whole still opens to 1.94× on page 7.
maxWordSpacing: 1.6, maxJustifyTracking: 10, runtMinCharacters: 22,
};
材料
- 機能
- ハイフネーションと文書の言語最適な改行(Knuth–Plass)表紙、扉、奥付デザインした章扉ページデザインの画像見出しスタイル見出しの属性ページデザインのテキスト・罫・ボックス奇数ページから始まる章柱とノンブルページの役割ごとの柱見開きの余白用紙の色段落スタイル独自のリソースの種類ウィドウ、オーファン、ラント文書のメタデータセマンティックカラーパレットPDFの書き出しPDFに埋め込むフォント
- 併用する機能
- リソースとしての図と表節ごとの柱
- 種類
- Crimson Pro, Cormorant Garamond, Cormorant SC (SIL OFL 1.1)
- 素材
cap1-1200.jpgcap2-1200.jpgcap3-1200.jpgcap4-1200.jpgfrontis-1000.jpg- Engraving, frontispiece: the baker at his oven's door (Generated With Diffusion Models, オリジナル)
- Engraving, chapter I: the night train into Barcelona (Generated With Diffusion Models, オリジナル)
- Engraving, chapter II: the village street in autumn (Generated With Diffusion Models, オリジナル)
- Engraving, chapter III: the Rambla at dawn (Generated With Diffusion Models, オリジナル)
- Engraving, chapter IV: the old man by the railway (Generated With Diffusion Models, オリジナル)
作り方
#1 · カタルーニャ語をコードで指定する
コードは前出の手短な答えにあります。locale: 'ca'でカタルーニャ語のパターンが選ばれ、postext 1.14以降、それはIECの『Llibre d’estil』に従います(対応するロケール)。パターンはTeXと同じもので、npmパッケージが同梱する3文字ではなく、分割の両側に最低2文字を残すため、ter-ra、cai-xa、plu-jaも分割できます。母音分立の母音は離さず(cièn-cia、ca-mions)、母音にはさまれたiは次の音節の頭になります(fe-ia)。ela geminadaは2つのlのあいだで分けます。行はtitil-で終わり、次の行はlavenで始まり、どちらにも中点は残りません。Knuth–Plassも中点を除いて行を測ります。

compounds: falseは、動詞と代名詞を、そのあいだのハイフン以外では分けません(arrossegar- | se)。IECが好む分け方です(ハイフネーション)。代償はゆるい行1つです。7ページでは、maxJustifyTracking: 10がほかの行で余りを吸収しても、d’inaugurar-seの前の行は語間が1.94倍に広がります。
#2 · 行がきっちり収まる文庫のページ
const TRIM = { width: 120, height: 185 }; // mm
const [TOP, INNER, OUTER, LINES] = [18, 16, 15, 29];
const MEASURE = TRIM.width - INNER - OUTER; // 89 mm: about 64 characters of Crimson Pro
const page = {
width: mm(TRIM.width), height: mm(TRIM.height), dpi: 150, backgroundColor: col('paper'),
margins: { top: mm(TOP), bottom: mm(TRIM.height - TOP - LINES * LINE),
left: mm(INNER), right: mm(OUTER), mirror: true }, // left is the inner margin on a recto
};
地の余白は仕上がりサイズ、天の余白、LINESから決まるので、版面には14 ptの行が29行入り、すべての満ページが同じベースラインで終わります。89 mmの行長にはCrimson Proでおよそ64字が入ります。mirror: trueで、見開きのどちらのページでも16 mmの余白がのど側に来ます(見開きの余白)。
#3 · 4つの章に1つの章扉
// # II {style="capitol" art="cap2"}: the art attribute names the picture's resource.
const PICTURE = { width: MEASURE, height: 12 * LINE }; // 89 × 59.3 mm: twelve lines, 3:2
const chapter = { id: 'capitol',
breakBefore: { enabled: true, parity: 'any' }, // a chapter opens on the next page, either side
marginBottom: pt(0), // the opener's own height already ends on a line
advancedDesign: { enabled: true, minHeight: pt(16 * LEAD), slot: { elements: [
{ kind: 'image', id: 'art', resourceId: '{attr.art}',
placement: at('container', 'top', 0, 0,
{ width: mm(PICTURE.width), height: mm(PICTURE.height) }) },
// From the container, not the image: images reserve no height (opener-image-no-reserve).
text('numeral', '{titleText}', DISPLAY, 21, at('container', 'top', 0, PICTURE.height + 4),
{ fontWeight: 600, color: col('vermell'), lineHeight: 1 }),
{ kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'),
placement: { ...at('container', 'top', 0, PICTURE.height + 13), size: { width: mm(8) } } },
] } },
};
resourceId: '{attr.art}'は見出しが指定した絵を描くので、# II {style="capitol" art="cap2"}は第II章を、ほかの章と同じスタイルのまま専用の版画の下で始めます(見出しの属性)。章扉の画像は高さを確保しないため、minHeightで16行を確保します。絵に12行、続いて数字とその罫が入り、本文はどの章でもページの17行目から始まります。
#4 · 第I章の前の4ページ
const front = (id, elements, { minHeight, ...extra } = {}) => ({ id,
breakBefore: { enabled: true, parity: 'any' }, marginBottom: pt(0),
header: { elements: [] }, footer: { elements: [] }, ...extra,
advancedDesign: { enabled: true, ...(minHeight && { minHeight }), slot: { elements } } });
const FRONTIS = { width: MEASURE, height: MEASURE * 1.5 }; // mm: the engraving is 2:3
const headingStyles = [
front('avantportada', [text('title', '{titleText}', DISPLAY, 19,
at('container', 'top', 0, 9 * LINE), { italic: true, fontWeight: 500, lineHeight: 1 })]),
front('frontis', [
{ kind: 'image', id: 'art', resourceId: '{attr.art}',
placement: at('container', 'top', 0, 0,
{ width: mm(FRONTIS.width), height: mm(FRONTIS.height) }) },
text('legend', '{titleText}', LABEL, 8.5, at('container', 'top', 0, FRONTIS.height + 5),
{ ...caps(8.5, 1.4, 'muted') }),
]),
front('portada', [
text('author', '{author}', LABEL, 10, at('container', 'top', 0, 5 * LINE),
caps(10, 2.2, 'ink')),
text('title', '{titleText}', DISPLAY, 34, at('container', 'top', 0, 8 * LINE),
{ italic: true, fontWeight: 500, color: col('vermell'), lineHeight: 1 }),
text('subtitle', '{subtitle}', DISPLAY, 13, at('container', 'top', 0, 8 * LINE + 17),
{ italic: true, fontWeight: 500 }),
{ kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'),
placement: { ...at('container', 'top', 0, 8 * LINE + 29), size: { width: mm(14) } } },
]),
// The note sits low on the verso behind the title, its label a line above it.
front('nota', [text('label', '{titleText}', LABEL, 8.5, at('container', 'top', 0, 13 * LINE),
caps(8.5, 1.4))], { minHeight: pt(15 * LEAD) }),
chapter,
];
前付けの各部分は、それぞれ専用のスタイルを持つ1レベル見出しです。# El transplantat {style="portada"}はフロントマターの著者と副題を印刷し、見出しを表題として印刷します。スタイルはレベルの改ページを継承するため、どのスタイルでも改ページを書き直し、ヘッダーとフッターを空にします。そのためこれらのページには柱もノンブルもありません。口絵は偶数ページの2ページに来るので、見開きで本扉と向き合います。
#5 · 偶数ページに著者、奇数ページに表題
const SHIFT = (INNER - OUTER) / 2; // mm: the text block's centre is off the page's centre
const head = (id, content, parity, edge, x, y = 10, pages = 'body') => ({
kind: 'text', id, content, parity, pages, ...caps(8, 1.3, 'muted'),
placement: at('page', edge, x, y),
});
const folio = { fontFamily: TEXT, fontWeight: 400, letterSpacing: pt(0), color: col('ink') };
const header = { elements: [
{ ...head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER), ...folio },
head('verso-author', '{author}', 'even', 'top', -SHIFT),
head('recto-title', '{title}', 'odd', 'top', SHIFT),
{ ...head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER), ...folio },
] };
const drop = (parity, x) => ({ ...head(`drop-${parity}`, '{pageNumber}', parity, 'bottom', x,
-11, 'opener'), ...folio });
const footer = { elements: [drop('odd', SHIFT), drop('even', -SHIFT)] };
parityで偶数ページに著者を、奇数ページに表題を置きます。pages: 'body'で章扉にはどちらも出さず、章扉ではpages: 'opener'を指定した地のノンブルが代わりに入ります。SHIFTは各柱を版面の中央に移します。2つの余白の幅が違うので、版面の中央はページの中央から0.5 mmずれています。
#6 · ラベルのない5点の版画
const resourceTypes = [{ id: 'gravat', name: 'Gravat', namePlural: 'Gravats', shortLabel: 'Gravat',
captionPrefix: '', numberingTemplate: '{n}', resetOn: 'never', counterFormat: 'decimal' }];
const PICTURES = { // id: [file, width, height, alt text in the book's language]
frontis: ['frontis-1000.jpg', 1000, 1500, 'Un carrer de poble al capvespre. A la porta del forn, '
+ 'on cremen les flames, el forner, gras i somrient, amb davantal blanc i els braços creuats.'],
cap1: ['cap1-1200.jpg', 1200, 800, 'De nit, el tren baixa cap a Barcelona: rengleres de fanals, '
+ 'la muntanya de Montjuïc i la lluna sobre el mar. Un pagès treu el cap per la finestreta.'],
cap2: ['cap2-1200.jpg', 1200, 800, 'Un carrer de poble buit en una nit de tardor. Per la porta '
+ 'baixa de la ferreria, el manyà pica l’enclusa enmig d’una estrella d’espurnes.'],
cap3: ['cap3-1200.jpg', 1200, 800, 'La Rambla a trenc d’alba des d’un balcó. Un fanaler apaga '
+ 'el gas amb la perxa; al fons, els pals dels vaixells del port.'],
cap4: ['cap4-1200.jpg', 1200, 800, 'Capvespre als afores. Un vell de cabells blancs, assegut '
+ 'al caire del desmunt, mira la via del tren, que es perd recta fins a l’horitzó.'],
};
const resources = Object.entries(PICTURES).map(([id, [fileId, w, h, altText]]) => ({
id, typeId: 'gravat', kind: 'bitmap', createdAt: 0, updatedAt: 0, altText,
bitmap: { fileId, format: 'jpeg', width: w, height: h } }));
絵はcaptionPrefixが空のリソース種別に属するので、番号もラベルも付きません。それぞれピクセル寸法で宣言し、組版の前にファイルIDで読み込みます。保存する前に白をページのクリーム色に、黒をインクの色に置き換えてあるので、上に貼った写真ではなく、ページの一部として印刷されます。
レシピの全体
// ═══ Postext Cookbook · Nº 104 · A Catalan novella, hyphenated by the IEC rules ═══════ // https://postext.dev/en/cookbook/catalan-pocket-novella // Code: MIT · Text: N. Oller, El transplantat, 1928 (PD, Viquitexts) · Pictures: diffusion models // Fonts: Crimson Pro, Cormorant Garamond, Cormorant SC (SIL OFL 1.1) · Needs postext ≥ 1.14.0 import { buildDocument, renderPageToCanvas, clearMeasurementCache, registerResourceImage } from 'https://esm.sh/postext'; import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf'; const LANG = 'en'; // @lang: the language of the sample document (the sample is Catalan in both) const RECIPE = 'catalan-pocket-novella'; // ─── 1 · Design ───────────────────────────────────────────────────────────── const palette = { ink: '#2a2320', // text: a warm near-black, the pictures' ink too vermell: '#953222', // the one accent: chapter numerals, rules, the title page's title muted: '#6e6359', // running heads, the edition's note, the colophon paper: '#f5efe1', // a cream book paper, the pictures' white }; // Each colour names its palette entry and carries its hex (gotcha: palette-skips-designs). const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id }); const colorPalette = [ ...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })), { id: 'main-color', name: 'vermell (defaults)', value: { hex: palette.vermell, model: 'hex' } }, ]; const [TEXT, DISPLAY, LABEL] = ['Crimson Pro', 'Cormorant Garamond', 'Cormorant SC']; const [BODY, LEAD] = [10.4, 14]; // pt: body and leading, the pitch of every vertical measure const LINE = (LEAD * 25.4) / 72; // mm: one line, 4.94 const at = (to, edge, x = 0, y = 0, size) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) }, ...(size && { size }) }); const text = (id, content, fontFamily, size, placement, extra = {}) => ({ kind: 'text', id, content, fontFamily, fontSize: pt(size), color: col('ink'), align: 'center', overflow: 'wrap', placement, ...extra }); // wrap, never '…' (gotcha: overflow-ellipsis-default) const caps = (size, tracking, color = 'vermell') => ({ fontFamily: LABEL, fontSize: pt(size), fontWeight: 600, letterSpacing: pt(tracking), color: col(color) }); // #region page: a 12 × 18.5 cm pocket book; the text block holds 29 whole lines const TRIM = { width: 120, height: 185 }; // mm const [TOP, INNER, OUTER, LINES] = [18, 16, 15, 29]; const MEASURE = TRIM.width - INNER - OUTER; // 89 mm: about 64 characters of Crimson Pro const page = { width: mm(TRIM.width), height: mm(TRIM.height), dpi: 150, backgroundColor: col('paper'), margins: { top: mm(TOP), bottom: mm(TRIM.height - TOP - LINES * LINE), left: mm(INNER), right: mm(OUTER), mirror: true }, // left is the inner margin on a recto }; // #endregion // #region answer: Catalan by its own code: l·l breaks as il- | lusió, no word splits its hiatus const LOCALE = 'ca'; // the exact bundled code: 'ca-ES' resolves to it too (postext ≥ 1.14) const bodyText = { // config().bodyText fontFamily: TEXT, fontSize: pt(BODY), lineHeight: pt(LEAD), color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'), firstLineIndent: mm(4.5), indentAfterHeading: false, // The defaults justify, hyphenate in config().locale and break by Knuth–Plass. With 'ca' // the dictionary follows the IEC: two letters on each side (ter-ra, cai-xa), no split in // a hiatus (cièn-cia), none after an apostrophe (s’ha-via), and l·l breaks with the hyphen // in place of the dot. compounds: false keeps arrossegar-se whole but for its own hyphen, // the break the IEC prefers for a verb and its pronoun (Llibre d’estil, VI 2.5). hyphenation: { compounds: false }, // Spaces past 1.6× take up to 10‰ of tracking instead; a last line under 22 characters is // a runt. A line that must keep d’inaugurar-se whole still opens to 1.94× on page 7. maxWordSpacing: 1.6, maxJustifyTracking: 10, runtMinCharacters: 22, }; // #endregion // #region opener: the chapter's engraving, then its numeral, in one style for all four // # II {style="capitol" art="cap2"}: the art attribute names the picture's resource. const PICTURE = { width: MEASURE, height: 12 * LINE }; // 89 × 59.3 mm: twelve lines, 3:2 const chapter = { id: 'capitol', breakBefore: { enabled: true, parity: 'any' }, // a chapter opens on the next page, either side marginBottom: pt(0), // the opener's own height already ends on a line advancedDesign: { enabled: true, minHeight: pt(16 * LEAD), slot: { elements: [ { kind: 'image', id: 'art', resourceId: '{attr.art}', placement: at('container', 'top', 0, 0, { width: mm(PICTURE.width), height: mm(PICTURE.height) }) }, // From the container, not the image: images reserve no height (opener-image-no-reserve). text('numeral', '{titleText}', DISPLAY, 21, at('container', 'top', 0, PICTURE.height + 4), { fontWeight: 600, color: col('vermell'), lineHeight: 1 }), { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'), placement: { ...at('container', 'top', 0, PICTURE.height + 13), size: { width: mm(8) } } }, ] } }, }; // #endregion // #region front: half-title, frontispiece, title page and the edition's note, one page each const front = (id, elements, { minHeight, ...extra } = {}) => ({ id, breakBefore: { enabled: true, parity: 'any' }, marginBottom: pt(0), header: { elements: [] }, footer: { elements: [] }, ...extra, advancedDesign: { enabled: true, ...(minHeight && { minHeight }), slot: { elements } } }); const FRONTIS = { width: MEASURE, height: MEASURE * 1.5 }; // mm: the engraving is 2:3 const headingStyles = [ front('avantportada', [text('title', '{titleText}', DISPLAY, 19, at('container', 'top', 0, 9 * LINE), { italic: true, fontWeight: 500, lineHeight: 1 })]), front('frontis', [ { kind: 'image', id: 'art', resourceId: '{attr.art}', placement: at('container', 'top', 0, 0, { width: mm(FRONTIS.width), height: mm(FRONTIS.height) }) }, text('legend', '{titleText}', LABEL, 8.5, at('container', 'top', 0, FRONTIS.height + 5), { ...caps(8.5, 1.4, 'muted') }), ]), front('portada', [ text('author', '{author}', LABEL, 10, at('container', 'top', 0, 5 * LINE), caps(10, 2.2, 'ink')), text('title', '{titleText}', DISPLAY, 34, at('container', 'top', 0, 8 * LINE), { italic: true, fontWeight: 500, color: col('vermell'), lineHeight: 1 }), text('subtitle', '{subtitle}', DISPLAY, 13, at('container', 'top', 0, 8 * LINE + 17), { italic: true, fontWeight: 500 }), { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.5), color: col('vermell'), placement: { ...at('container', 'top', 0, 8 * LINE + 29), size: { width: mm(14) } } }, ]), // The note sits low on the verso behind the title, its label a line above it. front('nota', [text('label', '{titleText}', LABEL, 8.5, at('container', 'top', 0, 13 * LINE), caps(8.5, 1.4))], { minHeight: pt(15 * LEAD) }), chapter, ]; // #endregion // #region heads: the author on the verso, the title on the recto; a drop folio on openers const SHIFT = (INNER - OUTER) / 2; // mm: the text block's centre is off the page's centre const head = (id, content, parity, edge, x, y = 10, pages = 'body') => ({ kind: 'text', id, content, parity, pages, ...caps(8, 1.3, 'muted'), placement: at('page', edge, x, y), }); const folio = { fontFamily: TEXT, fontWeight: 400, letterSpacing: pt(0), color: col('ink') }; const header = { elements: [ { ...head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER), ...folio }, head('verso-author', '{author}', 'even', 'top', -SHIFT), head('recto-title', '{title}', 'odd', 'top', SHIFT), { ...head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER), ...folio }, ] }; const drop = (parity, x) => ({ ...head(`drop-${parity}`, '{pageNumber}', parity, 'bottom', x, -11, 'opener'), ...folio }); const footer = { elements: [drop('odd', SHIFT), drop('even', -SHIFT)] }; // #endregion // #region pictures: five engravings, unnumbered and uncaptioned, at their pixel size const resourceTypes = [{ id: 'gravat', name: 'Gravat', namePlural: 'Gravats', shortLabel: 'Gravat', captionPrefix: '', numberingTemplate: '{n}', resetOn: 'never', counterFormat: 'decimal' }]; const PICTURES = { // id: [file, width, height, alt text in the book's language] frontis: ['frontis-1000.jpg', 1000, 1500, 'Un carrer de poble al capvespre. A la porta del forn, ' + 'on cremen les flames, el forner, gras i somrient, amb davantal blanc i els braços creuats.'], cap1: ['cap1-1200.jpg', 1200, 800, 'De nit, el tren baixa cap a Barcelona: rengleres de fanals, ' + 'la muntanya de Montjuïc i la lluna sobre el mar. Un pagès treu el cap per la finestreta.'], cap2: ['cap2-1200.jpg', 1200, 800, 'Un carrer de poble buit en una nit de tardor. Per la porta ' + 'baixa de la ferreria, el manyà pica l’enclusa enmig d’una estrella d’espurnes.'], cap3: ['cap3-1200.jpg', 1200, 800, 'La Rambla a trenc d’alba des d’un balcó. Un fanaler apaga ' + 'el gas amb la perxa; al fons, els pals dels vaixells del port.'], cap4: ['cap4-1200.jpg', 1200, 800, 'Capvespre als afores. Un vell de cabells blancs, assegut ' + 'al caire del desmunt, mira la via del tren, que es perd recta fins a l’horitzó.'], }; const resources = Object.entries(PICTURES).map(([id, [fileId, w, h, altText]]) => ({ id, typeId: 'gravat', kind: 'bitmap', createdAt: 0, updatedAt: 0, altText, bitmap: { fileId, format: 'jpeg', width: w, height: h } })); // #endregion const paragraphStyles = [ { id: 'nota', fontSize: pt(9), lineHeight: pt(LEAD * 0.9), color: col('muted'), italicColor: col('muted'), firstLineIndent: mm(4.5) }, { id: 'fi', fontFamily: LABEL, fontSize: pt(9), color: col('vermell'), textAlign: 'center', firstLineIndent: pt(0), marginTop: pt(LEAD) }, { id: 'colofo', fontSize: pt(7.5), lineHeight: pt(10), color: col('muted'), italicColor: col('muted'), textAlign: 'center', firstLineIndent: pt(0), marginTop: pt(2 * LEAD) }, ]; const config = () => ({ // a factory: the engine caches resolved configs per object locale: LOCALE, resourceTypes, colorPalette, page, bodyText, paragraphStyles, headingStyles, header, footer, layout: { layoutType: 'single' }, headings: { // the designs paint every title; this keeps Open Sans unloaded fontFamily: DISPLAY, fontWeight: 600, // Any headings object drops the H1 break (gotcha: headings-drop-h1-break): restated. levels: [{ level: 1, breakBefore: { enabled: true, parity: 'any' } }], }, }); // ─── 2 · Content ──────────────────────────────────────────────────────────── // The front matter, then the four chapters; chapter I in two files (2,500 words each at most). const markdown = [String.raw`---Markdownの見本 · 18行 · content.en.md
title: "El transplantat" subtitle: "De «Croquis del natural»" author: "Narcís Oller" --- # El transplantat {style="avantportada"} # El Forner de les Monges {style="frontis" art="frontis"} # El transplantat {style="portada"} # Nota a aquesta edició {style="nota"} :::paragraphs{style="nota"} Aquest text reprodueix «El transplantat» tal com Narcís Oller (1846–1930) el va revisar per al volum VI de les seves *Obres completes* (Barcelona, 1928), ja en l’ortografia de Pompeu Fabra. El conte havia aparegut el 1879 a *Croquis del natural*, el seu primer recull. S’hi han esmenat unes quantes errades de la impressió i, com fan les edicions d’avui, s’hi han suprimit els signes d’interrogació i d’exclamació inicials i els diàlegs s’hi marquen amb el guió llarg. Les cursives són de l’autor. Les il·lustracions s’han fet per a aquesta edició. :::`, String.raw`# I {style="capitol" art="cap1"}Markdownの見本 · 54行 · content.c1a.en.md
Daniel (o el Forner de les Monges, si el voleu conèixer per son nom popular), lliurat amb la major fe al treball des de molt jove, veient com ses gotes de suor anaven a parar al calaix convertides en monedes d’argent i d’or que la muller guardava, no com a instruments de canvi, sinó com a medalles honorífiques, era feliç en son poble, i res del món no li pareixia tan bonic. Degut a sa felicitat impertorbable, o tal volta perquè la farina alimenta fins amb ses flaires, havia conservat sempre una gran robustesa, i s’havia tornat tan desmesuradament panxut i gros que quasi feia pena. I dic quasi perquè el color sa de son rostre, la calma de sa mirada i el somrís permanent de sos llavis revelaven tanta satisfacció i salut que era impossible conservar el sentiment de compassió que l’aspecte de la deformitat vos hagués inspirat al primer cop d’ull. Res de descontentament o disgust de la vida, res de malestar: la felicitat del Forner de les Monges era proverbial en el poble i en tota l’encontrada. És clar que, en els seixanta anys que ja tenia, no havia pogut lliurar-se d’aquelles desgràcies que persegueixen a tot mortal, com eren les pèrdues de família, entre les quals hi havia la molt dolorosa de sa muller; però totes les penes, fins les més fortes, eren poc duradores per a en Daniel, gràcies a sa pròpia pasta, molt millor que la dels pans que enfornava, i gràcies també a la *filosofia* i *religió* de què en casos consemblants sabia revestir-se. Tenia una paraula per a tothom, i no hi havia qui passés per davant seu sense donar-li el bon dia o preguntar-li si *prenia la fresca*, o *el sol*, o *si feia temps*. La popularitat, que capgira tanta gent, era la flaca del Forner de les Monges, i a satisfer-la posava tota la voluntat, procurant-se les simpaties de pobres i rics, amanyagant a tothom i no ofenent a ningú. Això, que no deixa d’ésser difícil en totes les esferes socials, ho és en grau superlatiu en un poblet; però, com que l’instint tot ho pot i en Daniel en tenia per a això i portava a més a més la patent d’inofensiu, assolí fàcilment el que volia. Així aconseguí *fer-se* amb el doctor Andreu, home sapientíssim del poble, que deixava bocabadats tots sos veïns amb les descripcions sobre història, geografia o astronomia que els endreçava poc després d’haver-les après ell de cor, i amb els proverbis i *sentències* que, com altre Sanxo Pança, deixava caure pel descosit. En una de ses converses, que, sens dubte pel que eren instructives, qualificava el doctor Andreu de conferències, digué un dia que «en el poble curt l’home savi es torna ruc». Ignoro si el doctor, dient això, pensava haver degenerat, o si considerava que sos oients havien pogut ésser savis algun dia. Tampoc no sé que en Daniel tingués motiu de creure’s mal aprofitat en el poble on havia nascut i era feliç tot fent pans i acabalant or. El que sé és que l’esperit humà presenta rareses absurdes i que, per una d’aquestes, tal volta, aquella sentència donà molt que pensar al nostre senzill forner, qui des de llavors afalagà el desig de sortir un jorn de son poble per, com diríem nosaltres, descobrir nous horitzons. Sé, per fi, que, gràcies a aquest fenomen, en Daniel veié amb gust marxar son fill gran a Barcelona per aprendre l’ofici de perruquer, malgrat la pena que la separació per una part, i per una altra el rompiment del rutinari costum de fer de l’hereu el mateix que fou son pare, podien causar-li. Qui sap si el bon home creia que a son fill podria entrar-li la ciència pels dits tot pentinant algun acadèmic o algun senyoràs d’aquells que són autoritats i llueixen creus i bandes! Fos el que fos, el fill del Forner de les Monges va fer son aprenentatge a Barcelona, i, a l’època a què ens referim, obrí *saló* pel seu compte, invertint en la instal·lació i ornament bona part de les medalles que tant havia guardat sa mare. El plantar *saló* tenia massa importància perquè es portés a efecte sense la presència del pare; i, com era natural, el fill fou a buscar-lo i se l’emmenà a Barcelona, on no havia estat mai. El *saló* era a la Rambla, i l’endemà de l’arribada havia d’inaugurar-se solemnement. Era no sols bonic, sinó verament luxós. En Miquelet, que en el món industrial portava com a nom de batalla el de *Rumbós*, tenia més lletra menuda que son mestre Fígaro. Havia comprès l’època: sabia com són avui reproductives certes despeses, i no volia perdonar-ne cap. Així, no satisfet d’haver posat un *saló* com cap altre no n’hi hagués a Espanya, cregué convenient d’armar rebombori en els diaris, i, seguint l’exemple d’altres empreses, resolgué dar un dinar als representants de la premsa, com ara es diu. Ell i son pare arribaren a Barcelona la nit abans. Ja hem dit que en Daniel no hi havia estat mai, i falta afegir que encara no havia vist cap ciutat d’igual importància. Judiqueu de ses impressions, cent voltes majors que les que experimenta en tals casos una criatura, a la qual manca sempre suficient judici per a fer-se càrrec del valor absolut de les novetats, i coneixement cabal d’allò que té vist per a comparar-ho amb el que veu per primera vegada. Pocs quilòmetres abans d’arribar ja no sabia en Daniel dar-se compte del que li passava contemplant les llargues rengleres de fanals que unes voltes veia senceres i altres cops trencades, segons les corbes o nivells que el tren travessava. Cap barceloní no ha vist tan gran la ciutat com apareixia als ulls del foraster. El fill d’aquest coadjuvava en gran manera a engrandir aquesta impressió. —Veieu, pare, aquella renglera de mà esquerra per on corren llumets de colors? És el passeig de Gràcia. Les altres transversals són carrers de l’*Ensanxe*… Mireu a la dreta: tot aquell rengle tan llarg de fanals és un camí: el camí de Sans… Allà dalt hi ha Montjuïc… El veieu, ara que la lluna ha sortit d’aquells nuvolots?… No distingiu, dessota, allà lluny, una gran resplendor? És el mar… Ara que hi toca la lluna, mireu que és bonic! Talment sembla un bassal de *plata* bullent! En Daniel guaitava a la dreta, a l’esquerra, amunt i avall, a terra i al cel, i quasi es marejava. Una paret, una casa, un desnivell de la via, la mateixa rapidesa del tren, li feien pampalluguejar amb tan estranya inquietud els llums, que a voltes en confonia amb estrelles i arribava a perdre l’esma de cel i terra. —Veniu a aquest altre cantó. Si se’n veuen, ara, de fanals! En Daniel feia uns ulls com unes taronges. —Si fos de dia us ensenyaria Pedralbes, Sarrià, Vallvidrera, Sant Gervasi, Gràcia; un caseriu que no s’acaba mai… Ja ens hi anem acostant… Hola! el tren de Sarrià!… Mireu aquest talús que encreua la via: ara ens passarà un altre tren per sobre. El forner, prou marejat, quasi no entenia com podria passar per sobre sense aixafar-los, i estigué a punt de senyar-se. Per fi la màquina xiulà, i, ja parat el tren, aparegueren els mossos de l’estació a recollir els bitllets, enlluernant amb llurs brillants i inquietes llanternes als viatgers. Mentrestant, en Daniel, seguint la indicació de son fill, contemplava la feixuga mole de la Universitat, que, embolcallada en l’ombra, li semblava un monstruós parapet flanquejat per dues grans torres. —Heu vist aquella punxa tan prima de més avall? És un convent de monges: les *Adoratrices*. Hem passat també les *Arrepentides*, Sant Joan de Déu i altres convents. Tot això n’és ple. Els frares i les monges ho entenen com sempre: aquest és el cantó de migjorn… Ja tornem a caminar: ara la màquina ens empeny per darrera. Alceu-vos. Veieu aquestes quatre rengleres de llums? Són la Gran Via: un carrer que tindrà quatre hores. Els frens començaren a grunyir, les rodes a arrossegar-se peresosament. —Ja hem arribat —digué el Rumbós tot ficant-se la gorra a la butxaca i posant-se un bolet molt elegantó—. Amaniu-vos i *tentu* amb el rellotge, que per aquí corren uns alets… Impossible de descriure minuciosament cada una de les impressions que el forner experimentà tot travessant la Rambla. Els fanalets de color dels cotxes que es movien en direccions diverses, la gent que ocupava l’imperial dels tramvies, els dolls de claror que sortien de les botigues, els rengles de persones deturades davant certs aparadors, la multitud que passejava per sota els arbres, els fanals d’anunci; tot oferia a sos ulls estranya novetat i acabava de marejar-lo. Barcelona li pareixia un cel per sa il·luminació, un infern per son moviment. No sabia si li agradava o no: desitjava arribar a casa, i al mateix temps hauria volgut no arribar-hi massa aviat. Aquella vacil·lació d’esperit el deixà fatigat. Per això quan, després d’haver sopat, es va veure al llit, l’home respirà amb satisfacció i tancà els ulls per adormir-se. Però era inútil: son agitat esperit feia de les seves, més entestat que mai a no descansar. Si obria els ulls, li pareixia distingir, entre la fosca, que els cantons superiors i inferiors de la cambra s’enfonsaven o s’enlairaven com els d’una cabina d’embarcació lliurada a la inestabilitat perpètua de les ones. Si els tancava, veia un eixam de llumenetes de tots colors voleiar com una immensa constel·lació deslligada de tota llei física. En Daniel no sabia què fer en aquell llit de turment, i més d’una vegada pensà a alçar-se. Per fi l’ensopiment de la son pogué més que els nervis, la cambra restà embolcada per la negror impenetrable i quieta de la fosca, aquelles llumenetes s’aprimaren fins a formar migrades ratlles d’esblanqueïda resplendor, i l’esperit d’en Daniel davallà pausadament per la costa suau de la son, on es perd tot sentit d’existència.`, String.raw`L’endemà no fou dia de menys emocions. Matí i tarda passejà per Barcelona, aturant-se a cada pas davant els aparadors i a respectuosa distància dels cotxes que veia córrer envers ell.Markdownの見本 · 48行 · content.c1b.en.md
La vista del port, amb son bosc de pals i cordam, per entre els quals a penes s’albirava la interminable bellugadissa de la Riba; els cotxes de luxe, amb llurs troncs arrogants i llurs galonejats lacais; la plaça Reial, amb sa agradable simetria i sa netedat; la Rambla, amb son alegre brogit; i el passeig de Gràcia, ple també de moviment i vida; tenien embadalit en Daniel. Però, sumades totes les impressions del dia, no arribaven a poder ésser comparades amb les que li proporcionà el convit de la vesprada. En Daniel no cabia a la pell en contemplar son fill rodejat dels representants de la premsa, que, passejant els ulls de les parets a la taula tot esperant el sopar, omplien d’elogis l’amfitrió per la riquesa i el gust desplegats en l’establiment, i li auguraven un gran esdevenidor. Els miralls, els llums i els tocadors, i, sobretot, els olis, pomades, essències i cosmètics de procedència anglesa (una novetat que el nou perruquer presentava per millor acreditar-se), eren minuciosament examinats i lloats molt calorosament. El Rumbós no ho podia deixar d’ésser en aquell moment: amb un gest expressiu, feia que un dels aprenents anés empaquetant les ampolletes i pots que eren més elogiats, mal que l’armari perdés tota aquella simetria i abundància que a primera hora oferia. El Forner de les Monges quasi plorava de goig. «Son fill era un savi. Bé deia el doctor Andreu que “en el poble curt l’home savi es torna ruc”. Quan, en Miquelet, hauria tractat tanta gent de lletres ni après a viure entre ells amb tanta confiança i intimitat?» La perspicàcia del vell, baldament poc conreada, començava a veure en allò cert corrent secret d’utilitat que son fill explotava amb tota la malícia refinada d’un cortesà o, si voleu, d’un industrial d’avui. El pare tot just naixia i, com diu l’endevinalla del foc, el fill ja saltava pels terrats». Durant el sopar (que, com podeu comprendre, era confiat a un *intel·ligent* restaurant per més que tingués lloc a la mateixa perruqueria) la conversa fou poca al principi, s’animà un xic quan les viandes i els vins començaren a fer bullir els cervells, i en aparèixer el xampany esclatà amb tota sa força. El perruquer, sense immutar-se i com si hi estigués ja avesat, alçà la copa i pronuncià un discurs, en el qual «la premsa, la veu de l’opinió pública, la gran palanca del progrés», quedava col·locada per damunt totes les potestats de la terra. —Perquè —deia el perruquer— ningú com vosaltres s’associa i protegeix totes les empreses, per humils que siguin. La premsa, de la mateixa manera assisteix als convits de *palacio* que als d’un modest industrial com jo: jamai desdenya ningú, i la mateixa confraternitat que guarda entre tots sos representants, sense distinció de partits, dispensa a totes les classes socials. Els comensals aplaudiren estrepitosament. El forner plorava i reia alhora. Llavors començaren els brindis dels periodistes. L’un donà, en nom de la premsa (no barcelonina ni espanyola, sinó del món), les gràcies al perruquer pels elogis que havia prodigat a «la més gran institució dels nostres temps». Un altre procurà demostrar que no hi ha acte humà, per insignificant que sigui, que pugui deixar d’ésser útil a l’obra del progrés; i afegí que per això la premsa, «com havia dit molt bé l’amo de l’establiment», no es desdenya d’associar-se a la més petita de les empreses, protestant, però, que pogués qualificar-se de petita la d’una perruqueria, on l’art de la decoració, que purifica el gust, i l’art de tallar els cabells, que rodoneja i perfecciona el bust de l’home civilitzat fins a impedir, ja al primer cop d’ull, que es confongui amb el salvatge, corren units. El cervell del Forner de les Monges estava a punt d’esclatar. Un altre, amb dades estadístiques que duia apuntades a la memòria, provà que en el món civilitzat no hi ha encara sinó un perruquer per cada cinquanta mil homes, i que, si bé a Barcelona no existia tal desproporció, podia, de totes maneres, assegurar-se que el servei no s’havia explotat prou i que, per tant, la nova perruqueria tindria aviat bona parròquia. Aquesta última paraula inspirà un altre brindis, en el qual es sostenia que l’art de tallar i pentinar cabells no era indiferent al mateix catolicisme, com ho provava la diferència de pentinats que usaven els frares, i els edictes d’excomunió que en temps antics havia hagut d’expedir l’Església contra certes modes exagerades que, a Anglaterra i altres nacions, daven a l’home i a la dona l’aspecte d’éssers monstruosos. De tot això deduïa l’orador que la inauguració d’una perruqueria no era, en realitat, cosa desposseïda d’importància ni d’interès per a tots aquells que, estudiant l’essència de la civilització, no creuen que sigui element de progrés tota cosa nova, sinó tota cosa bona. Ja no dic ara el que passava per l’enteniment del bon forner. «Mai, mai no ho hauria somiat! Estava rodejat de savis molt més grans que el doctor Andreu!… I qui els havia aplegat, qui els obsequiava i en rebia elogis, era en Miquelet! El seu fill, Senyor, el seu fill!» Una vaga esperança d’ésser conegut entre aquells senyors tan il·lustrats llampeguejà per l’esperit d’en Daniel. Aquell espectacle de barreja i confraternitat entre persones que ell veia amb els ulls d’una il·lusió daurada el transportaven a l’esfera dels entusiasmes més candorosos. Remembrà la *sentència* del doctor Andreu, i mai no la veié tan justa com aleshores. Tots els veïns del seu poble li semblaren dignes de la major compassió. «Viure a Barcelona era viure.» Allò que havia vist de dia, el que veia ara, era un somni que convidava a fer-ne una realitat perpètua. «Estava resolt: a ses velleses s’havia de fer barceloní.» Li entrà l’afany de viure entre *persones*, i, sentint-se com superior a si mateix, volgué brindar. El Rumbós, que era a l’altre cap de taula, dissimulà amb estudiat somrís el temor que son pare fes alguna atzagaiada. Totes les veus callaren; tots els ulls cercaren, per sobre les estovalles, un punt en què fixar-se. Aquest silenci desconcertà el nou orador ja abans que desclogués els llavis; però el compromís era contret, el cor volia parlar. —Senyors —digué, per fi, en Daniel—, estic molt agraït… però molt… i… brindo per la salut de vostès i de totes les seves famílies… i… no dic res més perquè no puc. I caigué aplomat a la cadira, plens de llàgrimes els ulls, mentre l’auditori en pes l’eixordava amb sos aplaudiments tot mossegant-se els llavis. Dos o tres discursos més, d’altres tants periodistes, coronaren aquell monument d’oratòria; i, abans d’aparèixer el cafè, vingué la gran sorpresa que el Rumbós havia preparat amb el major secret. Era, aquesta, una mena d’urna de metall blanc, tapada i d’elegant forma etrusca. Col·locada al mig de la taula, tothom l’havia presa per un objecte d’adorn destinat a augmentar la magnificència de l’agençament entre les flors, els canelobres, les copes, les ampolles i els coberts, on s’emmirallava la llum de gas, desfent-se en mil colors, si viu i inquiet l’un, més encara l’altre. Però no era aquesta la destinació de la tal urna. En Miquelet s’alçà, pitjà el botó amb què acabava la figurada tapa, i per cent brolladors (de tan petits, invisibles) eixiren, impel·lits amb força, altres tants raigs d’aromàtica essència, que refrescaren aquella atmosfera i ompliren el saló de delitosa fragància. No cal pas dir si el truc fou celebrat: tot el concurs picava de mans, rabejant-se de gust, amb la fruïció sibarítica d’un romà del baix imperi, dins la suau ruixada d’aquella boira enervadora. La flaire de les tasses de cafè s’enlairava vaporosa fins al sostre amb els cercles de fum dels cigars; les espelmes feien una llum més blanca i prima; les bombes de gas prenien un to blavenc; i un somni de tendresa i benestar embadalia els esperits. L’endemà, els diaris de la ciutat, sense exceptuar-ne ni un, pintaven la nova perruqueria com un palau de fades, elogiaven totes les innovacions importades pel Rumbós, i daven compte dels industrials que havien intervingut en el decorat, encara que en això alguns incorrien en errades o mala atribució de noms.`, String.raw`# II {style="capitol" art="cap2"}Markdownの見本 · 30行 · content.c2.en.md
Les impressions abans descrites, i la sort que en Miquelet estava fent, engendraren en en Daniel el desig de llogar el forn i traslladar-se a Barcelona a passar els darrers anys de sa vida. Prou havia treballat, ja, perquè ningú pogués criticar que cap a ses velleses es lliurés al descans. Els cabals li ho permetien, i, de més a més, son fill li oferia, de bon grat, plat a taula i un bon llit. Amb tals propòsits, se’n tornà en Daniel al poble on tants anys havia viscut feliç. Si no hi hagués arribat amb la ferma resolució de deixar-lo, potser l’hauria acabat de resoldre el trist desencant que sofrí en petjar novament aquells carrers. Era a la tardor, i el nostre home hi arribava ja de vespre. Els que sols han vist els pobles a l’estiu, no poden judicar de la tristor que desperten a entrada d’hivern, i de com aquesta tristor creix quan el sol s’és amagat darrera les muntanyes. La primera oreneta que s’allunya és, per als pobles, la precursora de la soledat i la quietud. Darrera d’ella segueixen les altres, deixant buits els nius, abans calentons i animats, que el vent i la pluja esgrunaran. Els masos queden barrats; els pocs cotxes i tartanes de luxe passen a ensopir-se en els magatzems, on ben aviat els cobrirà una capa de pols i de teranyines; les diligències no poden donar l’abast a endur-se’n els forasters que durant tres mesos han animat el poble i sos entorns. S’han acabat les fontades, les balles, les festes majors, el brogit i l’alegria: tota la vida es reconcentra dins les cases, i ben aviat carrers i camins seran del domini exclusiu del vent. Quan el Forner de les Monges entrà en son carrer, el crepuscle havia terminat. Ja no hi trobà els rotllets de gent que pocs jorns abans hi prenia la fresca, ni veié oberta la botiga del fuster amb les peces de treball endreçades, el sòl regat i el menjador tribuna que en el fons s’albirava a la tèrbola claror d’un llum de petroli a mitja fosca. Tot era tancat, no passava ànima vivent, i sobre les desiguals teulades s’estenia, com tenderol esqueixalat, una llenca de cel blau turquí sembrat d’estrelles. En mig d’aquella soledat sentí xisclar la garlopa, grunyir la serra i copejar el martell del fuster. Els sorolls arribaven esmorteïts al cor d’en Daniel: per una vaga associació d’idees, cregué que el fuster feia una caixa de mort. Deu passos més amunt dringava, com campaneta llunyana, un martell sobre l’enclusa, i, per la portella d’una escaleta, en Daniel veié son veí, el manyà, entre la resplendor rogenca d’una estrella d’espumes que, al compàs dels cops, estirava o encongia ses punxes com eriçó de foc en turment. En Daniel va empènyer la feixuga porta de sa casa, davant la qual se li mostrà l’encesa boca del forn, plena de brilladores flames que, saltant pels aires i llepant l’estreta porta de llur presó, semblaven celebrar la benvinguda de l’amo. La fleca s’omplí d’alegria. Tothom deixà la feina voltant en Daniel, que no sabia per on girar-se ni com respondre a les cent preguntes que li endreçaven alhora. Quan arribà al pis i guaità a la finestra com portat d’un tèrbol desig de comparació, el carrer oferia la més trista soledat. El vent s’arrossegava per terra empenyent polsoses i alades fantasmes, la fosca feia invisibles les fatxades, i sols el cant quasi perdut d’una mare que bressava son infant, barrejat amb el no menys frèvol d’unes cosidores i el llarg rebombori d’un garranyic de cancell, trencava aquella quietud de cementiri. En Daniel es sentí contristat. Tot Barcelona li bullia en el cervell, i debades cercava en aquell fosc carrer els atractius que hi havia trobat durant tota sa vida. —*Al menos* fes lluna! —deia interiorment. I era que enyorava tots aquells rius de claror que en arribar la nit inundaven la ciutat; la resplendent claredat de la perruqueria; la il·luminació *veneciana* dels cotxes i tramvies que des del balcó de casa son fill veia passar corrent i fent capritxoses giragonses. No cal estar-hi avesat de molt temps: la bona llum artificial s’enyora així que els ulls n’han fruït algun dia. Penseu, doncs, el que devia passar al nostre forner, que s’havia vist en un establiment on, des del negre barret de copa del parroquià fins al mateix mosaic del sòl, tot lluïa, tot reflectia la clara llum de cent becs de gas. Cregué ja impossible de viure alegre en aquell poble tan fosc, tan brut, tan solitari, i recordà el sopar de la premsa, el proverbi del doctor Andreu, els avenços d’en Miquelet, els oferiments que aquest li feia, la promesa d’acceptar-los, els motius que per a descansar tenia… i, tancant la finestra, es ficà al llit, més resolt que mai a abandonar el seu poble.`, String.raw`# III {style="capitol" art="cap3"}Markdownの見本 · 30行 · content.c3.en.md
Fàcil fou a en Daniel d’arrendar el forn: ja no tant de deixar aquelles parets que, després de sos entusiasmes de moment i en començar a esvair-se-li el record de Barcelona, li tornaven a guanyar el cor revestint-se de tota la història d’una existència feliç, menyspreada de sobte i potser sense motiu prou just. Però la resolució era ferma: el forn era arrendat; ja no li quedava, en el poble, sinó una filla casada, amb la qual no vivia. «S’ajuntaria amb son fill, tractaria *persones*, i mil divertiments l’ajudarien a passar el temps que aquí, sense treballar, li havia d’ésser insuportable. Endemés, els estius hi tornaria: que per ventura no la veuria mai més, la casa? Doncs bé podia deixar-la.» I, tot espurnejant-li els ulls i fent el cor fort, aquell home, que pocs dies abans havia sentit tanta tristesa en arribar al poble, el deixà llavors amb una recança infinita. Però dins la colossal figura d’en Daniel alenava un esperit d’infant que, com tots sos consemblants, es sentia més decantat als alegrois frívols que a la tristor. Per això, quan veié les fileres de fanals que li anunciaven la nova arribada, l’home oblidà ja la poesia de sa casa i del seu forn, i pensà altra volta que, entre allò que deixava i el que la ciutat li oferia, l’elecció no era dubtosa. Oblidava, el pobre Daniel, que Barcelona no podia oferir-li novetats diàries, i que la força del costum, a certa edat, és del tot aclaparadora. En sa primera estada ja havia recorregut quasi tota la ciutat; son fill, enfeinat en la perruqueria, no podria acompanyar-lo: hauria de passejar tot sol; i, si vint-i-quatre hores diàries de renda són insuportables als mateixos ganduls, quant més no ho havien d’ésser a un home acostumat, com ell, al treball? L’endemà de l’arribada començà a tocar-ne els resultats. Avesat a aixecar-se quan les amassadores havien deixat pastada una fornada de pa, es despertà a l’hora en què això acostumava a succeir, que per a la perruqueria era a deshora; i, com que ara no hi estava en qualitat de foraster ni volia desvesar-se de matinejar, l’home saltà del llit. L’alba tot just clarejava el que era indispensable perquè a sa llum incipient pogués el forner vestir-se, endevinant amb el palp quina era cada una de les peces del seu vestit que penjaven de les cadires com draps de color incert. Tots els altres de la casa dormirien encara tres hores, i la mateixa escalforeta i quietud que regnava en dormitoris i sales feia caminar de puntetes i contenir l’alè al que acabava de llevar-se. D’altra part no sabia on eren les claus del pis ni la de la porta forana, i, per tant, no podia eixir al carrer. Si algun jorn us veieu en tals condicions, tancats dins una cambra que, per tot esbarjo, té una finestra de celobert, sabreu què és patir i comprendreu l’angúnia que degué passar en Daniel aquella matinada. I com, al seu poble, una nit demanava lluna, aquest dia demanava sol a ciutat. «Per tot hi ha cent llegües de mal camí!», mormolaren els seus llavis. I, no podent ja suportar el lligament de sa presó, mal s’exposés a despertar els de la casa, travessà el pis i obrí un dels balcons que daven a la Rambla, per esperar més distretament que els altres s’aixequessin. Els fanalers, mig ensopegant de son, anaven apagant el gas, que, tot esgrogueït, semblava estar ja cansat de fer pampallugues; un que altre cotxe tancat, un que altre carro buit, i una que altra persona mig perduda per la deserta via, formaven tot el moviment d’aquella Rambla que la nit d’arribada, amb sa constant bellugadissa, havia marejat en Daniel. Però, a poc a poc, el vel cendrós que s’estenia del port a la muntanya anà replegant-se sobre aquesta, arrabassat per un fresc oreig capaç de despertar les potències més adormides. Els arbres redreçaren les poques fulles que encara conservaven verdes, i remogueren llur brancatge com per espolsar-se la son; sota un cel de blau puríssim lluïren les punxes dels parallamps, els ferros i vidres dels campanars i torratxes, simulant enlluernadors estels; anaren obrint-se portes i finestres; i un formigueig de gent i carruatges, cada instant més gros, començà a bellugar per tots cantons com eixam d’abelles a l’entorn d’un buc. Un xic més tard, els aprenents de la perruqueria es posaren a despertar el *saló* a cops d’espolsadors, a raspallar-lo amb les escombres, a rentar-li la cara, a endreçar-lo i vestir-lo; en una paraula, a deixar-lo a punt de rebre les persones que aviat començarien a visitar-lo. Quan el forner baixà l’escala, el Rumbós encara dormia, però el *saló* era ja net, lluent, estirat com tot un senyor que sap honrar sa dignitat pròpia i vol que jamai ningú, per de matí que es llevi, no pugui trobar-lo desprevingut. No diré que en Daniel passés el dia enujat o avorrit: encara el contrast de l’animació de Barcelona amb la soledat i quietud de son poble el tenia entretingut i distret; però no tant, per això, com en sa primera estada. Els edificis i les botigues ja no li oferien novetat: ja quasi sabia de cor què trobaria a la dreta i què a l’esquerra en emprendre tal carrer o plaça de les principals, i per experimentar noves impressions havia d’allunyar-se fins a barris que li eren desconeguts, o bé escaure’s davant algun espectacle d’aquells que sempre atreuen badocs per més que es repeteixin, com són els arrencaqueixals, els ocellets de la bonaventura, l’orquestra de cecs de Sant Gaietà, les mones que s’enfilen pels balcons, i tants i tants altres, que ell, al cap de dos dies d’haver arribat del poble, sabia més que tots els habitants de la ciutat. Un jorn visitant la Barceloneta, veient, un altre, el castell de Montjuïc, ara anant a Pedralbes, o a Can Tunis, o a Vallcarca, o a la Bonanova (en una paraula, recorrent els encontorns que no havia pogut veure en son primer viatge), passà encara una mesada distreta; però no per això en Daniel es sentia ben centrat. Havia perdut, sense saber com, l’alegria que per tants anys resplendí en son cor: per sol que fes i per boniques que fossin les coses, li apareixien rodejades de certa ombra misteriosa que li causava més enuig que joia. Recordava el poble, sa passada vida, i anava convencent-se que podria encara suportar-la bé. Altres voltes evocava el record del sopar de la premsa, pensava en la sort que estava fent son fill, i sentia entrar en l’esperit un nou raig d’esperança i felicitat. Però en arribar a casa després de quatre o cinc hores de passejar-se sol sense obrir la boca, i en veure’s obligat a romandre assegut en el rebedor com un porter, o bé en el menjador, sense poder conversar amb ningú perquè així ho exigia la feina, el nostre home tornava a caure en un pou de melangia. Ell mateix no sabia donar-se compte del que li passava, ni arribava a descobrir la causa principal d’aquell malestar. —No tens bona taula, no tens bon llit, no *disfrutes* de la major llibertat i independència? Per ventura ton fill no et tracta bé? No ha de servir-te de satisfacció veure com avança son establiment, i no et trobes tu on desitjaves de molts anys ençà? Totes ses respostes havien d’ésser afirmatives… i, això no obstant, no quedava satisfet. La privació que sentí la primera matinada s’havia corregit el mateix dia proveint a en Daniel de les claus corresponents perquè fos amo d’entrar i sortir quan li fes pler; i, com aquesta, son fill havia anat destruint, una per una, totes les altres privacions de què aquell s’hagués queixat. Cada dia procurava assenyalar-li nous passeigs, i les vetllades de poca feina l’acompanyava a veure algun teatre o cafè mentre l’home volgués anar-hi, que fou ben poques vegades, perquè preferia dormir en el llit a adormir-se en públic.`, String.raw`# IV {style="capitol" art="cap4"}Markdownの見本 · 62行 · content.c4.en.md
Una vetlla en Daniel entrà molt content: havia topat un conterrani seu i parlat llargament amb ell del poble. Era la primera vegada, fora de les poques que eixia amb son fill, que havia passejat per Barcelona parlant. Aquell mutisme en mig de la multitud era una de les coses que més el posaven de mal humor. Recordava que en el seu poble tothom el saludava, i no podia avenir-se a travessar per entre mils i mils persones com ombra indiferent. Per això, en mig del soroll confós de la multitud, en Daniel hi trobava un silenci glacial molt més trist que el de la quietud i la soledat. Per això, a voltes, les cames se l’emportaven instintivament a indrets solitaris; i, un dia que en Miquelet se n’estranyà, son pare li respongué que el moviment l’entristia, que el centre de Barcelona li produïa l’efecte d’un cementiri on els morts es passegessin. Son conterrani trobà en Daniel decaigut, molt més blanc de cabells i «com si s’hagués escurçat o s’hagués posat calces de creixença». Observà també que mentre li parlava de Barcelona quasi no escoltava, i que en canvi s’entretenia molt a remembrar son poble, a preguntar per tothom, a informar-se de tot el que hi havia passat d’ençà de sa marxa, deixant caure alguna llàgrima sobre les noves dels morts o naixements que aquell li comunicava. Aquestes observacions van ésser comentades en el poble, i atribuïdes a desavinença entre pare i fill per uns, a un principi de feridura per altres. A la nit en Daniel somià son poble. Es veié, de primer, a si mateix passejant amb la Roser, la seva dona, pel cementiri cobert d’herbam barrejat amb roselles i campanetes blaves que s’entortolligaven en les poques creus que hi havia; veié després un cel molt negrós, en el qual una munió d’estrelles titil·laven sense parar; i, de la pròpia manera que havia passat de la claror del sol a plena nit sense crepuscle, es trobà, també sense saber com, dins de casa, en cos de camisa i amb la pala a la mà, davant d’un forn encès com un segment del sol. Sa llum, escapant-se per la boca, queia sobre l’enrajolat en forma pareguda a la del cercle lluminós d’una llanterna màgica; i, al mig d’ella, en Daniel veia l’ombra de sa pròpia figura escurçant-se o allargant-se a mercè de l’inquiet reflex de les flames i escarnint monstruosament les contorsions que ell amb la pala feia. Mentrestant, el pa, dins el forn, s’inflava i s’estovava com llavor dins bona terra. Poc després esdevenia de color d’or, i calia veure maniobrar en Daniel per muntar-lo a la pala i atreure-se’l en mig del riu de flames que li disputava la presa. En arribar aquí, un xiscle mig ofegat trencà la solemne quietud del dormitori. En Daniel es tombà sobre l’altre costat, i lliscà per sos llavis com un suau somrís; l’últim adéu de l’alegria que el visità aquell jorn. Després… son esperit reposà profundament. En despertar, sos ulls van clavar-se un instant en el balcó del celobert, i una llàgrima vergonyant s’escolà per llurs pestanyes. Llavors en Daniel rumià quant mancava per a arribar a l’estiu. Tot just era pel gener: cinc mesos li van semblar un segle. Però, com que el mateix desig de consol li engendrava noves esperances, l’home en concebé ben aviat una, i fou que anant a l’estació del carril veuria arribar algú altre del seu poble. En Daniel no pensà que, essent aquest a la província de Tarragona i trobant-se a cinc hores del ferrocarril, els seus veïns venien a la ciutat comtal sols per causes extraordinàries, i que, component-se de curt nombre d’habitants, pocs eren els que durant l’any visitaven Barcelona. Heus aquí, doncs, el bon home fent, dies i dies, debades, el camí de l’estació, anant-hi amb una esperança, tornant-se’n amb un desengany. L’enyorança, mentrestant, anava minant-lo cada dia més. En Daniel no semblava ja el gros Forner de les Monges: la roba li venia cada cop més ampla i com més llarga, se li havien tornat blancs tots els cabells, i un cercle blavenc d’amarga tristor que voltava sos ulls li dava un aspecte desencaixat, quasi cadavèric. En Miquelet, amb tot i la poca atenció que podia dispensar-li, se n’adonà, i volgué dur-lo a casa un metge. Tot fou inútil. El malalt havia posat mal geni. No sols no tenia fe en els facultatius desconeguts, sinó que (cosa pitjor) ni volia curar-se. Avorria el seu estat, avorria la vida, i notava en si mateix un misteriós afany que no sabia definir-se sinó com un cert desig de córrer, de córrer molt i empènyer les hores, que trobava molt llargues. La gran excitació en què vivia el feia passar del matí a la nit per tota una escala d’exaltacions i abatiments. A la senzilla vista d’una fleca en què el comprador no pot albirar el forn d’on surten els pans, es posava furiós. Altres voltes l’hauríeu vist dolçament entretingut en la contemplació de les flors o dels ocells que es venen a la Rambla. Si li haguessin preguntat què hi veia, per què s’hi delitava, hauria de segur respost que jamai, fins llavors, no s’havia fixat en flors ni en ocells, i que, no obstant, ara els contemplava amb gust, sense saber per què! Potser, sense adonar-se’n, hi descobria una secreta relació amb la primavera, precursora de l’anhelat estiu! L’home esclau d’un desig no sent pertot arreu veus misterioses de l’objecte de son afany? A les mateixes veus obeïa, sens dubte, la tendència que el forner sentia per la soledat, i més encara pels voltants de l’estació del ferrocarril. Ja no s’acontentava d’anar-hi a l’arribada del tren: cada tarda sos passeigs eren per aquest cantó de ciutat. No semblava sinó que per la via li arribessin flaires de la seva terra sense les quals no podia viure. Una tarda que les cames el portaren més enllà del convent de les Adoratrius, arribà a un punt en què la via s’adreçava i seguia recta fins a l’extrem de la visual, on els rails convergien com els talls d’una llança que es perdia dins el vel morat de la distància. Allí clavà sos ulls, desitjós de traspassar el límit de l’horitzó; i, encisat, sens dubte, per una atracció secreta, va asseure’s al caire mateix del vall o desmunt que formava la via. El sol anava enfonsant-se per l’aiguavés de Vallvidrera oposat a la ciutat; el caseriu de Pedralbes, Sarrià i Sant Gervasi era engolit per una boira cendrosa; i l’ombra gegantina del serrat envaïa solemnement tot el pla, ensems que la nuvolada mudava i ennegria, cada volta més, sos virolats colors. El mateix vall de la via anava enfosquint-se per moments, mentre en son fons els rails prenien la lluïssor de l’aigua a la dubtosa llum de la celístia. L’esguard d’en Daniel lliscava per damunt d’ells com el d’un desterrat per sobre el riu que va a parar a sa llunyana pàtria. El pobre home plorava. No sabia explicar-se com, el camí que un dia li havia ofert tants plaers, podia avui entristir-lo tant. Justificava una volta més la veritat del poeta: l’espectacle el porta l’espectador dins d’ell mateix. Tot d’una en Daniel s’alçà i prengué resolt el camí de la perruqueria amb el semblant animat com de feia molt temps no havia tingut. —Gràcies a Déu que us veig content! —digué son fill—. Sembleu tot un altre! A què es deu aquesta sobtada alegria? —Crec que m’he endevinat el mal, i penso posar-hi cura. M’enyoro: demà me’n tornaré a casa. —Pare! No us torneu boig? Enyorar-se a Barcelona el veí d’un poblet? No recordeu l’efecte que us va causar l’arribar-hi, venint d’aquí? —Aleshores això em semblava un cel, i ara em sembla un cementiri. —Espereu *sisquera* l’estiu. Que per ventura us manca alguna cosa? —Em manca tot i no em manca res: vés-ho a entendre. Soc arbre vell que un malentès ha transplantat. Si em quedo aquí em moriré de tristesa. Deixa’m tornar a casa meva i no ho prenguis a mal. T’agraeixo tot el que has fet per mi, t’ho estimo molt… però me n’he de tornar a casa… Què hi faré, pobre de mi, si m’enyoro… m’enyoro i… En arribar aquí, en Daniel plorava, i en una estreta de mà a son fill va encloure cent protestes d’agraïment i totes les altres raons en què es basava sa resolució. En Miquelet comprengué l’estat de son pare, i el matí següent l’acompanyà fins al carril. Era ja tard. L’alegria és una saba que, si es perd a certa edat, ja no es recobra. En Daniel l’havia perduda per sempre; i, si en retornar a la seva terra pogué per un instant creure-la reconquerida, aviat en tocà el desengany. Ell ho havia dit: era un arbre vell transplantat. I l’arbre vell, un cop arrencat, és inútil replantar-lo en el lloc primitiu: ja mai més no farà brotada. Per què tractar de pintar-vos ara el pobre forner altra volta al seu poble, fora de la casa i del forn que tan incautament havia arrendat, i perdent de dia en dia les forces i els colors, els ulls fixos a terra, enterbolits pel plor, sense delit d’enraonar ni de veure ningú, sense cap goig, sense altra esperança que la d’acabar el sofriment en la mortalla? Ni aquell estiu (temps per ell tan desitjat algun jorn!) no pogué ja veure: pel maig, l’herbam, barrejat amb les roselles i les campanetes blaves, embolcallava ja la creu de ferro que s’aixecava de la tomba del malaurat Daniel i sa muller sota el mantell joliu d’un cel pur de primavera. :::paragraphs{style="fi"} Fi ::: :::paragraphs{style="colofo"} Compost en Crimson Pro, Cormorant Garamond i Cormorant SC (SIL OFL). Text: Narcís Oller, *Obres completes*, vol. VI, Barcelona, 1928, domini públic (Viquitexts). Il·lustracions generades amb models de difusió. :::`].join('\n\n'); // ─── 3 · Fonts ────────────────────────────────────────────────────────────── const FONTS = { // every face the pages use, loaded before the build (gotcha: fonts-first) 'Crimson Pro': ['400', '400i'], // the text, the note, the colophon, the folios 'Cormorant Garamond': ['500i', '600'], // half-title and title; the chapter numerals 'Cormorant SC': ['400', '600'], // the closing Fi; author, labels, running heads }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── await loadFonts(FONTS, markdown); await Promise.all(Object.values(PICTURES).map(([file]) => loadImage(file, asset(file)))); const doc = await buildWithFonts(() => buildDocument({ markdown, resources }, config()), markdown); showPages(doc, { title: 'El transplantat' }); offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider, resourceBytes: imageBytes }), `${RECIPE}.pdf`);キット · core, fonts, viewer, pdf, images:全レシピ共通 · 310行
// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ───── // ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook ───────── function mm(value) { return { value, unit: 'mm' }; } function pt(value) { return { value, unit: 'pt' }; } function em(value) { return { value, unit: 'em' }; } /** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */ function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; } /** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */ function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; } // ─── Kit · fonts v1 ── the same in every recipe · postext.dev/cookbook ──────── // Postext measures text with the faces the browser has loaded, and caches the // widths, so every face must be ready before the first build. Faces come from // Fontsource: the same static files the PDF embeds, so screen and PDF agree. /** faces = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample: * letters beyond Latin-1 (č, ł, ő…) also load the latin-ext files. With * `optional`, a face Fontsource does not ship is skipped instead of failing. * Resolves to the number of faces added. */ async function loadFonts(faces, text = '', { optional = false } = {}) { kitStatus('Loading fonts…'); const ranges = { latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,' + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD', 'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,' + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF', }; const subsets = /[Ā-˿Ḁ-ỿ]/.test(text) ? ['latin', 'latin-ext'] : ['latin']; const jobs = []; let added = 0; for (const [family, specs] of Object.entries(faces)) { const id = fontsourceId(family); const meta = optional ? await fontsourceMeta(family) : null; for (const spec of new Set(specs)) { const weight = parseInt(spec, 10); const style = spec.endsWith('i') ? 'italic' : 'normal'; if (hasFace(family, weight, style)) continue; if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue; for (const subset of subsets) { const url = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-${subset}-${weight}-${style}.woff2`; const face = new FontFace(family, `url(${url}) format('woff2')`, { weight: String(weight), style, unicodeRange: ranges[subset] }); jobs.push(face.load().then((ready) => { document.fonts.add(ready); added++; }, () => { if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`); })); } } } await Promise.all(jobs).catch((error) => { kitFail(error); throw error; }); return added; } /** Runs `build` (a buildDocument or buildBundle call) and checks the faces * the pages use. A regular face missing from FONTS is loaded with a warning; * bold and italic variants are loaded when the family ships them. Then the * measurement caches are cleared and the build runs again. */ async function buildWithFonts(build, text = '') { const tried = new Set(); for (let round = 0; round < 3; round++) { kitStatus('Laying out…'); await new Promise(requestAnimationFrame); // let the status paint first const result = await Promise.resolve().then(build).catch((error) => { kitFail(error); throw error; }); const wanted = { base: {}, variants: {} }; for (const { font, base } of [result].flat().flatMap(fontStringsOf)) { const { family, weight, style } = parseFont(font); const key = `${family}|${weight}|${style}`; if (tried.has(key) || hasFace(family, weight, style)) continue; tried.add(key); (wanted[base ? 'base' : 'variants'][family] ??= []).push(`${weight}${style === 'italic' ? 'i' : ''}`); } if (Object.keys(wanted.base).length) { console.warn(`[cookbook] FONTS does not list ${JSON.stringify(wanted.base)}: loading them.`); } const added = await loadFonts(wanted.base, text) + await loadFonts(wanted.variants, text, { optional: true }); if (added === 0) return result; clearMeasurementCache(); } throw new Error('The fonts did not settle after three builds.'); } /** Every font string of the layout. `base` marks a block's own face; its * bold, italic and bold-italic variants are listed whether or not used. */ function fontStringsOf(doc) { const found = new Map(); const walk = (node) => { if (!node || typeof node !== 'object') return; if (Array.isArray(node)) { node.forEach(walk); return; } for (const [key, value] of Object.entries(node)) { if (typeof value === 'string' && /fontString$/i.test(key)) { found.set(value, found.get(value) || key === 'fontString'); } else if (value && typeof value === 'object') walk(value); } }; walk(doc.pages); walk(doc.blocks); return [...found].map(([font, base]) => ({ font, base })); } /** '700 37.5px Open Sans' / 'italic 400 13px "Source Serif 4"' → { family, weight, style }. * A string with no weight ('95.8px Young Serif', from a design text) is 400. */ function parseFont(font) { const m = /^(?:(italic|oblique)\s+)?(?:small-caps\s+)?(?:(\d+|bold|normal)\s+)?[\d.]+px\s+(.+)$/.exec(font.trim()); if (!m) throw new Error(`Unexpected font string: ${font}`); const weight = m[2] === 'bold' ? 700 : !m[2] || m[2] === 'normal' ? 400 : Number(m[2]); return { family: m[3].replace(/^["']|["']$/g, ''), weight, style: m[1] ? 'italic' : 'normal' }; } /** True when a loaded FontFace covers exactly this family, weight and style * (document.fonts.check() is also true for families nobody declared). */ function hasFace(family, weight, style) { for (const face of document.fonts) { if (face.status !== 'loaded' || face.style !== style) continue; if (face.family.replace(/^["']|["']$/g, '') !== family) continue; const [low, high = low] = face.weight.split(' ').map(Number); if (weight >= low && weight <= high) return true; } return false; } /** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */ function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); } /** The weights and styles a family ships ({ weights: [400, 700], styles: ['normal', 'italic'] }), or null. */ function fontsourceMeta(family) { fontsourceMeta.cache ??= new Map(); const id = fontsourceId(family); if (!fontsourceMeta.cache.has(id)) { fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`) .then((res) => (res.ok ? res.json() : null), () => null)); } return fontsourceMeta.cache.get(id); } // ─── Kit · viewer v1 ── the same in every recipe · postext.dev/cookbook ─────── /** Shows the pages as facing spreads on a dark desk: the first page is a * recto on its own, then verso | recto pairs, as in a bound book. Pages * are painted when they scroll near the screen. */ function showPages(docs, { title, width = 460 } = {}) { const root = viewer(title); const pages = [docs].flat().flatMap((doc) => doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index }))); const spreads = []; let verso = null; for (const p of pages) { if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; } else { spreads.push([verso, p]); verso = null; } } if (verso) spreads.push([verso, null]); const density = Math.min(window.devicePixelRatio || 1, 2); showPages.painter?.disconnect(); const painter = new IntersectionObserver((entries) => { for (const { isIntersecting, target } of entries) { if (!isIntersecting) continue; painter.unobserve(target); const { doc, page } = target.postext; renderPageToCanvas(page, doc, target, { scale: (width * density) / page.width }); } }, { rootMargin: '800px' }); showPages.painter = painter; root.replaceChildren(...spreads.map((pair) => { const spread = document.createElement('div'); spread.className = 'pt-spread'; for (const p of pair) { const figure = document.createElement('figure'); if (p) { const label = p.page.pageLabel || String(p.n + 1); const canvas = document.createElement('canvas'); canvas.postext = p; canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`; canvas.setAttribute('role', 'img'); canvas.setAttribute('aria-label', `Page ${label}`); const folio = document.createElement('figcaption'); folio.textContent = label; figure.append(canvas, folio); painter.observe(canvas); } else figure.className = 'pt-blank'; spread.append(figure); } return spread; })); kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`); document.documentElement.dataset.postext = 'ready'; return pages.length; } /** The desk, the bar and the error reporting, created once. */ function viewer(title) { if (!document.getElementById('pt-kit')) { document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit"> :root { color-scheme: dark; } body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; } #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px); border-bottom: 1px solid #23262d; } #pt-bar strong { color: #f4f1ea; font-weight: 600; } #pt-actions { display: flex; gap: 12px; margin-left: auto; } #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; } #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; } .pt-spread { display: flex; } .pt-spread figure { margin: 0; width: min(460px, 44vw); } .pt-spread canvas { display: block; width: 100%; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif; letter-spacing: .18em; text-transform: uppercase; color: #6c7079; } .pt-blank { visibility: hidden; } @media (max-width: 760px) { .pt-spread { flex-direction: column; gap: 32px; } .pt-spread figure { width: min(460px, 92vw); } .pt-blank { display: none; } } </style>`); document.body.insertAdjacentHTML('afterbegin', '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>'); document.getElementById('pt-title').textContent = document.title || 'Postext'; addEventListener('error', (event) => kitFail(event.error ?? event.message)); addEventListener('unhandledrejection', (event) => kitFail(event.reason)); } if (title) document.getElementById('pt-title').textContent = title; return document.getElementById('pages') ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' })); } function kitStatus(text) { viewer(); document.getElementById('pt-status').textContent = text; } function kitFail(error) { document.documentElement.dataset.postext = 'error'; kitStatus(`Error: ${error?.message ?? error}`); } // ─── Kit · pdf v1 ── the same in every recipe that exports a PDF ────────────── /** postext-pdf embeds TrueType bytes. Fetch the Fontsource file the screen * used, snapping to a weight the family ships and falling back to upright * when it has no italic: the PDF asks for every face a block could use. */ async function fontsourceProvider(family, weight, style) { const id = fontsourceId(family); const meta = await fontsourceMeta(family); const weights = meta?.weights?.length ? meta.weights : [400, 700]; const w = weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a)); const s = style === 'italic' && meta && !meta.styles.includes('italic') ? 'normal' : style; const res = await fetch(`https://cdn.jsdelivr.net/npm/@fontsource/${id}@5/files/${id}-latin-${w}-${s}.woff2`); if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} (${res.status})`); return decompressWoff2(new Uint8Array(await res.arrayBuffer())); } /** A "Build the PDF" button in the bar. Once built: "Open the PDF" (a new * tab, since CodePen's preview frame cannot show PDFs) and a download link. */ function offerPdf(makePdf, filename) { viewer(); const button = Object.assign(document.createElement('button'), { type: 'button', textContent: 'Build the PDF' }); button.dataset.postextPdf = filename; button.addEventListener('click', async () => { button.disabled = true; button.textContent = 'Building the PDF…'; try { const bytes = await makePdf(); const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' })); const size = `${Math.max(1, Math.round(bytes.length / 1024))} KB`; button.replaceWith( Object.assign(document.createElement('a'), { href: url, target: '_blank', rel: 'noopener', textContent: 'Open the PDF ↗' }), Object.assign(document.createElement('a'), { href: url, download: filename, textContent: `Download ${filename} · ${size}` })); } catch (error) { button.disabled = false; button.textContent = 'Build the PDF'; kitFail(error); } }); document.getElementById('pt-actions').append(button); } // ─── Kit · images v1 ── recipes with pictures · postext.dev/cookbook ────────── /** Registers a photo or PNG for the canvas and keeps its bytes for the PDF. * fetch → ImageBitmap never taints the canvas (a plain cross-origin <img> would). */ async function loadImage(fileId, url) { const res = await fetch(url); if (!res.ok) throw new Error(`Image not found (${res.status}): ${url}`); const bytes = new Uint8Array(await res.arrayBuffer()); registerResourceImage(fileId, await createImageBitmap(new Blob([bytes]))); (loadImage.bytes ??= new Map()).set(fileId, bytes); } /** Registers SVG markup (drawn in code, or fetched) as a vector image. */ async function loadSvg(fileId, svg) { const img = new Image(); img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; await img.decode(); registerResourceImage(fileId, img); (loadImage.bytes ??= new Map()).set(fileId, new TextEncoder().encode(svg)); } /** renderToPdf({ resourceBytes: imageBytes }) */ function imageBytes(fileId) { return loadImage.bytes?.get(fileId); } /** renderToHtml({ resourceImageUrl: imageUrl }) */ function imageUrl(fileId) { const bytes = imageBytes(fileId); if (!bytes) return undefined; imageUrl.urls ??= new Map(); if (!imageUrl.urls.has(fileId)) { const type = /\.svg$/i.test(fileId) ? 'image/svg+xml' : /\.png$/i.test(fileId) ? 'image/png' : 'image/jpeg'; imageUrl.urls.set(fileId, URL.createObjectURL(new Blob([bytes], { type }))); } return imageUrl.urls.get(fileId); } // ─── /Kit ───────────────────────────────────────────────────────────────────────
組み立てたscript.jsはそのまま動きます。任意のページのモジュールスクリプトに貼り付けるか、レシピをCodePenで開いてください。 GitHub上のレシピのフォルダー ↗ (新しいタブで開きます)
アレンジ
#代名詞の部分でも分割させる
既定のcompounds: trueでは、d’inaugurar-seをd’inau-gurar-seと分けられ、7ページのゆるい行がなくなります。その代わり、このページ範囲ではtitil- | lavenの分割が行末から外れます。
- hyphenation: { compounds: false },
+ hyphenation: { compounds: true },#どの章も奇数ページで始める
parity: 'odd'は、偶数ページで始まるはずの章の前に白ページを入れます。余裕のある版の組み方です。26ページで始まる第IV章は、白ページのあとの27ページに移ります。
- breakBefore: { enabled: true, parity: 'any' }, // a chapter opens on the next page, either side
+ breakBefore: { enabled: true, parity: 'odd' },よくあるつまずき
つまずき
ハイフネーションできるのは8つのロケールだけで、コードは完全一致
ハイフネーションが用意されているのはen-us、es、fr、de、it、pt、ca、nlで、コードは完全一致で照合されます。'es-ES'やほかの言語は、何の知らせもなくアメリカ英語にフォールバックします。 ハイフネーションと文書の言語 →
つまずき
章扉の画像は確保する高さに数えられない
postext 1.4.1では、詳細デザインの見出しは、確保する高さを画像抜きで計測します。テキスト、罫、箱はページに固定していても数えられますが、ページの頭に裁ち落としで置いた写真のような画像は何も確保しないため、本文がその上から始まることがあります。minHeightを本文が始まるべき位置に設定してください。 デザインした章扉 →
つまずき
見出しスタイルはその階層の改ページを受け継ぐ
headingStylesの項目は、指定しなかったフィールドをすべて見出しの階層から受け継ぎ、breakBeforeも例外ではありません。:::pagebreakのあとのH1に付けた目次や奥付のスタイルはparity 'odd'を受け継ぎ、白ページの後ろに置かれます。こうしたスタイルにはbreakBefore: { enabled: false }を指定してください。 見出しスタイル →
つまずき
headingsオブジェクトを渡すとH1の改ページが消える
既定ではH1は奇数ページへ改ページします(always-odd)。ところがheadingsオブジェクトを渡すと中身にかかわらずこの既定がリセットされ、章は改ページせずに続けて組まれ、span: 'page'も効かなくなります。どの設定でもheadings.levels[0].breakBefore: { enabled: true, parity }を書き直してください。 奇数ページから始まる章 →
つまずき
パレットを差し替えても、デザイン要素と参照色は変わらない
postext 1.4.1はcolorPaletteをテキストのスタイル(本文、見出し、リスト、キャプション、表、囲み)には反映しますが、ヘッダー、フッター、章扉、部扉の要素と、bodyText.referenceColorには反映しません。これらはpaletteIdの横に書いた16進の色のままです。画面用のダーク版や色替えのためにパレットを差し替えるときは、ビルドの前に、リンクしたすべての色をcolorPaletteから書き直してください。 セマンティックカラーパレット →
つまずき
デザインのテキストのoverflowの既定値は'ellipsis-end'
幅に収まらないデザインのテキスト要素は、既定では省略記号で終わります。複数行に折り返したいタイトルにはoverflow: 'wrap'を設定してください。 ページデザインのテキスト・罫・ボックス →
つまずき
属性値には{も}も書けない。"を含む値は一重引用符で囲む
属性値は閉じ波かっこで終わるため、{や}を含められません。二重引用符を含む値は一重引用符で囲みます。ドル記号は問題ありません。 見出しの属性 →
つまずき
フロントマターの値はすべて引用符で囲む
YAMLはtitle: 1984を数値として、日付をDateオブジェクトとして読みます。文字列でない値はプレースホルダーに空で出力され、PDFにもタイトルが付きません。値はすべて引用符で囲んでください(title: "1984")。 文書のメタデータ →
つまずき
レイアウトの前にすべてのフォントを読み込む
レイアウトはブラウザーが読み込んだフォントで文字を計測し、その幅をキャッシュします。最初のビルドのあとに届いたフォントがあると改行位置が狂い、PDFも画面と一致しなくなります。すべてのウェイトとスタイルを先に読み込み、遅れて届いたときは再ビルドの前にclearMeasurementCache()を呼んでください。 レイアウト前のフォント読み込み →
- 本文はViquitextsからイタリックごとコピーしてください。ウリェーはtentu、Ensanxe、palacioを口語またはスペイン語としてイタリックで示していますが、プレーンテキストで書き出すとそれが消えます。疑わしい語は1928年版のスキャン画像と照合してください。動詞がtornarである箇所(tomà、tomaré、tomar)で、翻刻がtom-となっているところが6か所、jornがjomとなっているところが1か所あります。
- 6,800語あるため、5つの本文ファイルとキットを合わせると、組み上がったスクリプトは約69 KBになります。1冊まるごとがCodePenのサンプルに載せられる最大の見本で、それより長い作品は
.postextバンドルにします。
クレジット
- 本文
- El transplantat, complete, from the Obres completes, vol. VI (Barcelona, 1928), with misprints corrected, no opening ¿ ¡ and today’s dialogue dashes · Narcís Oller · パブリックドメイン
- The note to the edition · Postext Cookbook · オリジナル
- 画像
- Engraving, frontispiece: the baker at his oven's door · Generated With Diffusion Models · オリジナル
- Engraving, chapter I: the night train into Barcelona · Generated With Diffusion Models · オリジナル
- Engraving, chapter II: the village street in autumn · Generated With Diffusion Models · オリジナル
- Engraving, chapter III: the Rambla at dawn · Generated With Diffusion Models · オリジナル
- Engraving, chapter IV: the old man by the railway · Generated With Diffusion Models · オリジナル
- フォント
- Crimson Pro (SIL OFL 1.1) · Cormorant Garamond (SIL OFL 1.1) · Cormorant SC (SIL OFL 1.1)


