Kurz gesagt
Eine klassische japanische Erzählung mit kurzen Erklärungen zu ihren alten Ortsnamen. Die kleinen Nummern stehen neben dem Text, und die Erklärungen für beide offenen Seiten sind am Außenrand der linken Seite gesammelt.
Was Sie setzen
Vier Seiten einer Ausgabe im Format 四六判 von Higuchi Ichiyōs Child's Play (たけくらべ, 1895–96), ihr erstes Kapitel, mit sieben Noten zum alten Viertel Yoshiwara, das sie beschreibt. Die Noten sind 傍注 (bōchū), die Marginalien japanischer Klassiker im Taschenbuch: Ein kleines (1) steht rechts der Zeile nach dem Wort, das es erklärt, und die auf beiden Seiten einer Doppelseite zitierten Noten stehen am Außensteg der linken Seite, unter einer Linie von einem Drittel der Zeilenlänge, in einer um ein Fünftel kleineren Schrift. Leser finden jede Note, ohne umzublättern. Der Text, 42 Zeichen hinab in 17 Zeilen aus Shippori Mincho in 9,25 pt, behält Ichiyōs einen langen Absatz und ihre Sesampunkte über ぎつちよんちよん. Noten am Fuß der Spalte in einem europäischen Roman zeigt Nº 071.
Dieses Rezept beantwortet
- Wie setze ich die Anmerkungen eines japanischen Buchs: (1) am Kapitelende, am Seitenfuß oder neben dem Text über die Doppelseite?
- Wie mache ich Fußnoten?
- Wie kennzeichne ich japanischen Text mit Sesam-Betonungspunkten oder einer Seitenlinie statt mit Kursive?
Die kurze Antwort
// placement 'spread' gathers the notes cited on the two pages of a spread at the end of the
// left-hand page's text, the fore-edge in a book bound on the right, under a rule a third of
// the line long, and numbers them per spread. The marker (1) stands small at the right of
// the line, its digits upright, and the note opens with its number and a full note-em.
const footnotes = {
placement: 'spread', // 傍注; 'chapterEnd' sets 後注 after the chapter instead
numbering: 'spread',
markerPosition: 'right', markerTemplate: '({n})', // written out: 'ja' sets them so
numberGap: 'em', hangingIndent: em(2), // turnovers clear the number
fontSize: em(0.8), lineHeight: em(1.5),
separator: { width: 1 / 3, lineWidth: pt(0.5), color: col('plum') },
};
// Ichiyō's sesame marks (傍点) over ぎつちよんちよん: the mark Japan uses, right of the line.
const cjk = {
grid: { enabled: true, charsPerLine: CHARS, linesPerPage: LINES },
emphasisMark: { style: 'sesame', position: 'over' },
};
Zutaten
- Funktionen
- Anmerkungen in japanischen BüchernFußnotenJapanische Betonungszeichen (Bōten)Senkrechter TextRechts gebundene BücherFurigana: Kana-Lesungen über KanjiAufrechte Zahlen in senkrechtem TextJapanische Zahlzeichen und Kana-ZählerÜberschriften über Textzeilen (Gyōdori)ZeichenrasterJapanischer Zeilenumbruch (Kinsoku)Import aus Aozora BunkoChinesische, japanische und koreanische SchriftenKolumnentitel und SeitenzahlenKopfzeilen nach SeitenrolleÜberschriftenformateGestaltete AuftakteGespiegelte RänderSemantische FarbpalettePDF-Export
- Nutzt außerdem
- Chinesischer ZeilenumbruchBetonungspunkte, Namens- und TitellinienÜberschriftenattributePapierfarbeIm PDF eingebettete SchriftenRömisch paginierte TiteleiRuby: Pinyin- und Zhuyin-Lesungen
- Schrift
- Shippori Mincho, Noto Sans JP (SIL OFL 1.1)
- Dateien
- Keine: Jedes Bild wird im Code gezeichnet
Zubereitung
#1 · Die Noten der Doppelseite am Außensteg der linken Seite sammeln
Der Code steht in der kurzen Antwort oben. footnotes.placement: 'spread' setzt die auf den Seiten 2 und 3 zitierten Noten ans Ende des Textes von Seite 3, in einem rechts gebundenen Buch also an ihren Außensteg, und numbering: 'spread' beginnt die Noten jeder Doppelseite bei (1) (Fußnoten). Eine auf der rechten Seite zitierte Note wartet auf die linke; lassen die Noten der linken Seite weniger als eine Textzeile, bleiben die übrigen am Fuß der rechten Seite, wie JLReq es erlaubt. Ein Dokument mit dem Tag ja setzt die Zeichen eines vertikalen Buchs bereits als ({n}) rechts der Zeile, in 0,7 em, mit aufrechten Ziffern: Die beiden ausgeschriebenen Zeilen zeigen es. numberGap: 'em' setzt ein volles Notengeviert hinter die Nummer, und hangingIndent: em(2) richtet die Folgezeilen daran vorbei aus.
#2 · Die Zeichen dorthin schreiben, wo das Japanische sie setzt
Das Markdown schreibt 見返り柳[^1] nach dem Wort, das die Note erklärt, und die Noten als Absätze [^1]: … am Ende des Kapitels. Das Zeichen folgt dem erklärten Wort; eines, das einen Satz beendet, steht vor dessen 。 (…先生[^1]。), und die Engine lässt nie eine Zeile mit 。 oder einem Zeichen beginnen. Die sieben Noten wurden für dieses Rezept geschrieben: was 見返り柳 und お齒ぐろ溝 waren, wo 大音寺前 lag, was auf dem 酉の市 verkauft wurde, in schlichtem modernem Japanisch.
#3 · Die Sesampunkte der Vorlage behalten
cjk.emphasisMark bestimmt, was *…* und :dots[…] in diesem Buch drucken: Sesampunkte rechts einer vertikalen Zeile. Ein Dokument ja verwendet sie bereits; ausgeschrieben schalten sie die Kenten in der Liste der Funktionen ein. Der Aozora-Text hebt ぎつちよんちよん mit 傍点 hervor, und aozora.py schrieb das als :dots[ぎつちよんちよん]{style=sesame}; chinesische Bücher setzen die Punkte stattdessen unter die Zeile (Zeichen, Ruby und Warichu).
#4 · Auf einer Doppelseite beginnen
// Page-anchored design on a vertical page: x runs down the page from its top edge, y across
// it leftwards from its right edge. The title stands on the page's axis, the author lower.
const PT = 25.4 / 72; // mm in a point
const page = (down, across) => ({ anchor: { to: 'page', edge: 'top-left' },
offset: { x: mm(down), y: mm(across) } });
const TITLE = 26; // pt
// parity 'any': the story opens on the next page, either side (the default waits for a recto).
const story = { level: 1, breakBefore: { enabled: true, parity: 'any' },
advancedDesign: { enabled: true, minHeight: lines(LINES), slot: { elements: [
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: MINCHO, fontWeight: 700,
fontSize: pt(TITLE), lineHeight: 1, letterSpacing: pt(TITLE / 2), color: col('ink'),
placement: page(42, 127 / 2 - (TITLE * PT) / 2) },
{ kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.6), color: col('plum'),
placement: { ...page(42, 127 / 2 + (TITLE * PT) / 2 + 5), size: { width: mm(24) } } },
{ kind: 'text', id: 'author', content: '{author}', fontFamily: MINCHO, fontSize: pt(12),
letterSpacing: pt(4), color: col('ink'),
placement: page(118, 127 / 2 + (TITLE * PT) / 2 + 3) },
] } } };
const section = { level: 2, fontFamily: MINCHO, fontWeight: 700, fontSize: pt(11),
lineSpan: 3, indent: em(7) }; // 3行取り, seven characters down, as the Aozora text marks it
Der erste Satz des Kapitels zitiert vier Noten in seinen ersten drei Zeilen. Unter einem Titel auf Seite 1 gesetzt, ließen sie Platz für drei Textzeilen: Die linke Seite trägt ihre eigenen Noten und keine anderen. Eine Titelseite allein auf Seite 1 schiebt das Kapitel auf Seite 2, eine rechte Seite, sodass die erste Doppelseite den Text und alle seine Noten enthält. Die Titelseite ist eine Überschrift der Ebene 1, deren Gestaltung die Seite füllt, am Endformat verankert, damit der Titel auf der Achse der Seite steht.
#5 · Hashira und Paginas den Außensteg hinab setzen
const foreEdge = (id, content, parity, edge, y, pages) => ({
kind: 'text', id, content, parity, pages, writingMode: 'vertical-rl',
fontFamily: GOTHIC, fontSize: pt(7), letterSpacing: pt(1), color: col('muted'),
overflow: 'clip', placement: { anchor: { to: 'outer', edge }, offset: { y: em(y) } },
});
const header = { elements: [
foreEdge('hashira', '{title}', 'odd', 'top', 2, 'body'),
foreEdge('folio', '{pageNumber}', 'all', 'bottom', -2, 'body'),
] };
// The title page carries the colophon across the foot, in the edition's language.
const footer = { elements: [{ kind: 'text', id: 'colophon', content: '{attr.colophon}',
pages: 'opener', fontFamily: GOTHIC, fontSize: pt(5.5), lineHeight: 1.45,
color: col('muted'), overflow: 'wrap', align: 'center',
placement: { anchor: { to: 'page', edge: 'bottom' }, offset: { y: mm(-6) },
size: { width: mm(100) } } }] };
Der Titel läuft den Außenrand der linken Seiten hinab, außerhalb der Noten, und die Kanji-Paginas stehen am Fuß jeder Seite außer der Titelseite.
Das ganze Rezept
// ═══ Postext Cookbook · Nº 121 · Notes beside a vertical Japanese text, across the spread ══ // https://postext.dev/en/cookbook/vertical-notes-and-sidenotes // Code: MIT · Text: 樋口一葉『たけくらべ』, Aozora Bunko 389 (PD); notes CC BY 4.0 · Pictures: none // Fonts: Shippori Mincho, Noto Sans JP (SIL OFL 1.1) · Needs postext ≥ 1.25.0 import { buildDocument, withLoadedFonts, renderPageToCanvas, loadVerticalAlternates, } from 'https://esm.sh/postext'; import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf'; const LANG = 'en'; // @lang: the language of the colophon; the story is Japanese in both const RECIPE = 'vertical-notes-and-sidenotes'; // ─── 1 · Design ───────────────────────────────────────────────────────────── // #region palette: ink, a muted plum for the note numbers' rule, cream paper const palette = { ink: '#211d1b', // the text and the notes plum: '#6b3a55', // 紫: the rule over the notes, the engine's defaults muted: '#6b645d', // the hashira and the folios paper: '#fbf7ef', }; 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: 'defaults', value: { hex: palette.plum, model: 'hex' } }, ]; // #endregion const [MINCHO, GOTHIC] = ['Shippori Mincho', 'Noto Sans JP']; const [BODY, LEAD, CHARS, LINES] = [9.25, 16.25, 42, 17]; // pt, pt: 四六判 42字 × 17行 const lines = (n) => pt(n * LEAD); // #region answer: 傍注, the notes of both pages of a spread at the fore-edge of the left one // placement 'spread' gathers the notes cited on the two pages of a spread at the end of the // left-hand page's text, the fore-edge in a book bound on the right, under a rule a third of // the line long, and numbers them per spread. The marker (1) stands small at the right of // the line, its digits upright, and the note opens with its number and a full note-em. const footnotes = { placement: 'spread', // 傍注; 'chapterEnd' sets 後注 after the chapter instead numbering: 'spread', markerPosition: 'right', markerTemplate: '({n})', // written out: 'ja' sets them so numberGap: 'em', hangingIndent: em(2), // turnovers clear the number fontSize: em(0.8), lineHeight: em(1.5), separator: { width: 1 / 3, lineWidth: pt(0.5), color: col('plum') }, }; // Ichiyō's sesame marks (傍点) over ぎつちよんちよん: the mark Japan uses, right of the line. const cjk = { grid: { enabled: true, charsPerLine: CHARS, linesPerPage: LINES }, emphasisMark: { style: 'sesame', position: 'over' }, }; // #endregion // #region opener: a title page (扉) on its own, so the story opens on a spread // Page-anchored design on a vertical page: x runs down the page from its top edge, y across // it leftwards from its right edge. The title stands on the page's axis, the author lower. const PT = 25.4 / 72; // mm in a point const page = (down, across) => ({ anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(down), y: mm(across) } }); const TITLE = 26; // pt // parity 'any': the story opens on the next page, either side (the default waits for a recto). const story = { level: 1, breakBefore: { enabled: true, parity: 'any' }, advancedDesign: { enabled: true, minHeight: lines(LINES), slot: { elements: [ { kind: 'text', id: 'title', content: '{titleText}', fontFamily: MINCHO, fontWeight: 700, fontSize: pt(TITLE), lineHeight: 1, letterSpacing: pt(TITLE / 2), color: col('ink'), placement: page(42, 127 / 2 - (TITLE * PT) / 2) }, { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.6), color: col('plum'), placement: { ...page(42, 127 / 2 + (TITLE * PT) / 2 + 5), size: { width: mm(24) } } }, { kind: 'text', id: 'author', content: '{author}', fontFamily: MINCHO, fontSize: pt(12), letterSpacing: pt(4), color: col('ink'), placement: page(118, 127 / 2 + (TITLE * PT) / 2 + 3) }, ] } } }; const section = { level: 2, fontFamily: MINCHO, fontWeight: 700, fontSize: pt(11), lineSpan: 3, indent: em(7) }; // 3行取り, seven characters down, as the Aozora text marks it // #endregion // #region furniture: the hashira and kanji folios down the fore-edge const foreEdge = (id, content, parity, edge, y, pages) => ({ kind: 'text', id, content, parity, pages, writingMode: 'vertical-rl', fontFamily: GOTHIC, fontSize: pt(7), letterSpacing: pt(1), color: col('muted'), overflow: 'clip', placement: { anchor: { to: 'outer', edge }, offset: { y: em(y) } }, }); const header = { elements: [ foreEdge('hashira', '{title}', 'odd', 'top', 2, 'body'), foreEdge('folio', '{pageNumber}', 'all', 'bottom', -2, 'body'), ] }; // The title page carries the colophon across the foot, in the edition's language. const footer = { elements: [{ kind: 'text', id: 'colophon', content: '{attr.colophon}', pages: 'opener', fontFamily: GOTHIC, fontSize: pt(5.5), lineHeight: 1.45, color: col('muted'), overflow: 'wrap', align: 'center', placement: { anchor: { to: 'page', edge: 'bottom' }, offset: { y: mm(-6) }, size: { width: mm(100) } } }] }; // #endregion const config = () => ({ locale: 'ja', // written out, never LANG (gotcha: ja-locale-tag) colorPalette, page: { sizePreset: 'custom', width: mm(127), height: mm(188), dpi: 150, // 四六判 backgroundColor: col('paper'), margins: { top: mm(27), bottom: mm(21), left: mm(13), right: mm(16), mirror: true }, pageNumbering: { format: 'japanese-informal' }, }, layout: { layoutType: 'single', writingMode: 'vertical-rl' }, cjk, footnotes, bodyText: { fontFamily: MINCHO, fontSize: pt(BODY), lineHeight: pt(LEAD), color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'), textAlign: 'justify', firstLineIndent: em(1), indentAfterHeading: true, }, headings: { fontFamily: MINCHO, color: col('ink'), levels: [story, section] }, header, footer, }); // ─── 2 · Content ──────────────────────────────────────────────────────────── const markdown = String.raw`---Markdown-Beispiel · 23 Zeilen · content.en.md
title: "たけくらべ" author: "樋口一葉" --- # たけくらべ {colophon="Higuchi Ichiyō, たけくらべ (Child’s Play, 1895–96), chapter one; the notes are this recipe’s. Text: Aozora Bunko 389, public domain. Set in Shippori Mincho and Noto Sans JP (SIL OFL)."} ## 一 廻れば大門の見返り柳[^1]いと長けれど、お齒ぐろ溝[^2]に{燈火|ともしび}うつる三階[^3]の騷ぎも手に取る如く、明けくれなしの車の{行來|ゆきゝ}にはかり知られぬ全盛をうらなひて、{大音寺前|だいおんじまへ}[^4]と名は佛くさけれど、さりとは陽氣の町と住みたる人の申き、{三嶋神社|みしまさま}の角をまがりてより是れぞと見ゆる{大厦|いへ}もなく、かたぶく軒端の十軒長屋二十軒長や、商ひはかつふつ利かぬ處とて半さしたる雨戸の外に、あやしき{形|なり}に紙を切りなして、胡粉ぬりくり彩色のある{田樂|でんがく}みるやう、裏にはりたる串のさまもをかし、一軒ならず二軒ならず、朝日に干して夕日に仕舞ふ手當こと〴〵しく、一家内これにかゝりて夫れは何ぞと問ふに、知らずや霜月{酉|とり}の日[^5]例の神社に欲深樣のかつぎ給ふ是れぞ熊手の下ごしらへといふ、正月門松とりすつるよりかゝりて、一年うち通しの夫れは誠の商賣人、片手わざにも夏より手足を色どりて、{新年着|はるぎ}の支度もこれをば當てぞかし、南無や大鳥大明神、買ふ人にさへ大福をあたへ給へば製造もとの我等萬倍の利益をと人ごとに言ふめれど、さりとは思ひのほかなるもの、此あたりに大長者のうわさも聞かざりき、住む人の多くは{廓者|くるわもの}にて良人は小格子[^6]の何とやら、下足札そろへてがらんがらんの音もいそがしや夕暮より羽織引かけて立出れば、うしろに切火打かくる女房の顏もこれが見納めか十人ぎりの側杖無理{情死|しんぢう}のしそこね、恨みはかゝる身のはて危ふく、すはと言はゞ命がけの勤めに{遊山|ゆさん}らしく見ゆるもをかし、娘は{大籬|おほまがき}の{下新造|したしんぞ}とやら、七軒の何屋が客廻しとやら、{提燈|かんばん}さげてちよこちよこ走りの修業、卒業して何にかなる、とかくは檜舞臺と見たつるもをかしからずや、垢ぬけのせし三十あまりの年増、小ざつぱりとせし唐棧ぞろひに紺足袋はきて、雪駄ちやら〳〵忙がしげに横抱きの小包はとはでもしるし、茶屋が棧橋とんと沙汰して、廻り遠や此處からあげまする、誂へ物の仕事やさんと此あたりには言ふぞかし、一體の風俗よそと變りて、{女子|おなご}の後帶きちんとせし人少なく、がらを好みて巾廣の卷帶、年増はまだよし、十五六の小癪なるが{酸漿|ほゝづき}ふくんで此{姿|なり}はと目をふさぐ人もあるべし、所がら是非もなや、昨日河岸店に何紫の源氏名耳に殘れど、けふは地廻りの吉と手馴れぬ燒鳥の夜店を出して、身代たゝき骨になれば再び古巣への{内儀|かみさま}姿、どこやら素人よりは見よげに覺えて、これに染まらぬ子供もなし、秋は九月{仁和賀|にわか}[^7]の頃の大路を見給へ、さりとは宜くも學びし{露八|ろはち}が物眞似、{榮喜|えいき}が{處|しよ}作、孟子の母やおどろかん上達の速やかさ、うまいと褒められて今宵も一廻りと生意氣は七つ八つよりつのりて、やがては肩に置手ぬぐひ、鼻歌のそゝり節、十五の少年がませかた恐ろし、學校の唱歌にも:dots[ぎつちよんちよん]{style=sesame}と拍子を取りて、運動會に木やり音頭もなしかねまじき風情、さらでも教育はむづかしきに教師の苦心さこそと思はるゝ{入谷|いりや}ぢかくに育英舍とて、私立なれども生徒の數は千人近く、狹き校舍に目白押の窮屈さも教師が人望いよ〳〵あらはれて、唯學校と一ト口にて此あたりには呑込みのつくほど成るがあり、通ふ子供の數々に或は火消鳶人足、おとつさんは{刎橋|はねばし}の番屋に居るよと習はずして知る其道のかしこさ、梯子のりのまねびにアレ忍びがへしを折りましたと訴へのつべこべ、三百といふ代言の子もあるべし、お前の父さんは馬だねへと言はれて、名のりや{愁|つ}らき子心にも顏あからめるしほらしさ、出入りの{貸座敷|いへ}の祕藏息子寮住居に華族さまを氣取りて、ふさ付き帽子面もちゆたかに洋服かる〴〵と花々敷を、坊ちやん坊ちやんとて此子の追從するもをかし、多くの中に{龍華|りうげ}寺の{信如|しんによ}とて、{千筋|ちすぢ}となづる黒髮も今いく{歳|とせ}のさかりにか、やがては墨染にかへぬべき袖の色、{發心|ほつしん}は腹からか、坊は親ゆづりの勉強ものあり、性來をとなしきを友達いぶせく思ひて、さま〴〵の惡戲をしかけ、猫の死骸を繩にくゝりてお役目なれば{引導|いんだう}をたのみますと投げつけし事も有りしが、それは昔、今は校内一の人とて假にも{侮|あなど}りての處業はなかりき、歳は十五、{並背|なみぜい}にていが栗の{頭髮|つむり}も思ひなしか俗とは變りて、{藤本信如|ふぢもとのぶゆき}と{訓|よみ}にてすませど、何處やら{釋|しやく}といひたげの素振なり。 [^1]: 吉原遊廓の入口、大門へ下る衣紋坂の上の日本堤に立っていた柳。帰りの客がここで名残を惜しんで振り返ったという。 [^2]: 遊廓のまわりをめぐる堀。遊女が逃げ出さないように掘られたもので、水が黒く濁っていたのでこう呼ばれた。 [^3]: 廓の中に建ち並ぶ三階建ての大きな妓楼。 [^4]: 吉原の裏手、下谷龍泉寺町のあたりの通称。大音寺の門前にあたる。 [^5]: 十一月の酉の日に鷲(おおとり)神社で開かれる酉の市。縁起物の熊手が売られる。 [^6]: 格の低い、小さな遊女屋。 [^7]: 吉原で秋に催された俄(にわか)。芸者や幇間が茶番や踊りを見せて廓の中を練り歩いた。`; // content.<lang>.md: the same Japanese text in both // ─── 3 · Fonts ────────────────────────────────────────────────────────────── const FONTS = { 'Shippori Mincho': ['400', '700'], // the text and the notes; the title and 一 'Noto Sans JP': ['400'], // the hashira, the folios and the colophon }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── const title = markdown.match(/^title: "(.*)"$/m)[1]; const author = markdown.match(/^author: "(.*)"$/m)[1]; await loadFonts(FONTS, markdown); // the Latin files: the colophon // The notes' (1) and their upright digits come from the text face too. await loadCjkFonts({ [MINCHO]: ['400'] }, `${markdown}()0123456789`, { vertical: true }); await loadCjkFonts({ [MINCHO]: ['700'] }, `${title}${author}一`); await loadCjkFonts({ [GOTHIC]: ['400'] }, `${title}${markdown.match(/colophon="([^"]*)"/)[1]}一二三四五六七八九十`, { vertical: true }); const doc = await withLoadedFonts(() => buildDocument({ markdown }, config()), { ...kitFonts(FONTS), text: markdown }); showBook(doc, { title: t({ en: 'Notes beside a vertical Japanese text, across the spread', es: 'Notas junto a un texto japonés vertical, en la doble página' }) }); offerPdf(() => renderToPdf(doc, { fontProvider: cjkPdfProvider }), `${RECIPE}.pdf`);Kit · core, fonts, viewer, pdf, cjk: in jedem Rezept gleich · 458 Zeilen
// ─── 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 v3 ── the same in every recipe · postext.dev/cookbook // Postext loads the faces a config asks for before layout (prepareFonts, // buildDocumentWithFonts) and asks the kit for the files: Fontsource's, the // files the PDF embeds too. /** The options of buildDocumentWithFonts / prepareFonts / withLoadedFonts: * the Fontsource resolver, and the faces of FONTS loaded with the ones the * config names. FONTS = { 'Family Name': ['400', '400i', '700'] }. */ function kitFonts(FONTS = {}) { const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) => ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' }))); return { resolve: fontsourceResolver, faces }; } /** A face's Fontsource files, in a fixed order: latin, then latin-ext and greek * when `text` needs them and the family ships them (kitSubsetsFor), each with * its unicode-range. Null for a weight or slant Fontsource does not ship. */ async function fontsourceResolver(family, weight, style, { text = '' } = {}) { const id = fontsourceId(family); const meta = await fontsourceMeta(family); if (meta && !(meta.weights.includes(weight) && meta.styles.includes(style))) return null; kitStatus('Loading fonts…'); const at = await fontsourceAt(id); return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({ source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`, unicodeRange: kitRanges()[subset], })); } /** The unicode-range of each Fontsource file the kit loads (a function: the * kit is inlined after the pen's code, which runs before a const would be set). */ function kitRanges() { return { 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', greek: 'U+0370-03FF', }; } /** For the cjk, arabic and comics blocks, which add their own files: the * faces of `faces` = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample: * č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With * `optional`, a face Fontsource does not ship is skipped instead of failing. * The files are added once all have loaded, in the order asked (latin, then * latin-ext, then greek), whatever order they arrive in: the last one added * wins a character two files hold. Resolves to the number of faces added. */ async function loadFonts(faces, text = '', { optional = false } = {}) { kitStatus('Loading fonts…'); const ranges = kitRanges(); const jobs = []; for (const [family, specs] of Object.entries(faces)) { const id = fontsourceId(family); const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal']) .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await const at = fontsourceAt(id); const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null; const subsets = ['latin', ...kitSubsetsFor(text, meta)]; for (const [weight, style] of todo) { if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue; for (const subset of subsets) { const url = `${await at}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().catch(() => { if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`); })); } } } const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; }); return ready.filter((face) => face && document.fonts.add(face)).length; } /** A loaded FontFace covers this family, weight and style (fonts.check() would * also say yes 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; } /** The files beyond latin `text` needs that `meta`'s family ships. */ function kitSubsetsFor(text, meta) { return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']] .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x); } /** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */ function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); } /** A package's folder at the one version @5 names now (#766). */ function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) { return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json()) .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`); } /** The family's Fontsource metadata (weights, styles, subsets), 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 v2 ── the same in every recipe · postext.dev/cookbook /** The pages as spreads on a dark desk, page 1 alone, then verso | recto, * each painted when it scrolls near. Other options go to renderPageToCanvas. */ function showPages(docs, { title, width = 460, ...render } = {}) { 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, { ...render, 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 v3 ── the same in every recipe that exports a PDF /** The Fontsource files the screen used, as TrueType, with their range and * place: the nearest weight the family ships, upright with no italic; * latin, then what its letters need (kitSubsetsFor). */ async function fontsourceProvider(family, weight, style, request) { 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 text = String.fromCodePoint(...(request?.codePoints ?? [])); const files = await Promise.all(['latin', ...kitSubsetsFor(text, meta)].map(async (subset, order) => { const res = await fetch(`${await fontsourceAt(id)}files/${id}-${subset}-${w}-${s}.woff2`); if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} ${subset}`); return Object.assign(await decompressWoff2(new Uint8Array(await res.arrayBuffer())), { unicodeRange: kitRanges()[subset], order }); })); return files.length === 1 ? files[0] : files; } /** A "Build the PDF" button; then "Open the PDF" (a new tab: CodePen's frame * shows no 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 · cjk v2 ── Chinese, Japanese and Korean books · postext.dev/cookbook // Fontsource ships a CJK family as about a hundred files per weight, each // declared in its stylesheet with the unicode-range it covers. The screen // loads the files the sample touches; the PDF gets the same files for the // characters its pages set in each face, and embeds each as a subset. // A book bound on the right (vertical text) is shown with its spreads // mirrored: page 1 alone on the left of the spine, then [3 | 2]. /** The files of a Fontsource face, read from its stylesheet: { url, range, * ranges }, the last declared first (the order the browser tries them in). */ function cjkSlices(family, weight, style) { cjkSlices.cache ??= new Map(); const id = fontsourceId(family); const css = `${id}/${weight}${style === 'italic' ? '-italic' : ''}.css`; if (!cjkSlices.cache.has(css)) { let at; cjkSlices.cache.set(css, fontsourceAt(id).then((a) => fetch(at = a + css.slice(id.length + 1))) .then((res) => { if (!res.ok) throw new Error(`Fontsource has no ${family} ${weight} ${style} (${res.status})`); return res.text(); }) .then((text) => [...text.matchAll(/@font-face\s*{([^}]*)}/g)].map(([, rule]) => { const range = /unicode-range:\s*([^;]+);/.exec(rule)?.[1].trim() ?? 'U+0-10FFFF'; const ranges = range.split(',').map((part) => { const [lo, hi = lo] = part.trim().slice(2).split('-'); return [parseInt(lo, 16), parseInt(hi, 16)]; }); return { url: new URL(/url\(([^)]+?\.woff2)\)/.exec(rule)[1], at).href, range, ranges }; }).reverse())); } return cjkSlices.cache.get(css); } /** The file of `slices` the screen sets code point `cp` in, if any: the * last one loadCjkFonts adds whose range holds it. A numbered file that * repeats a letter of latin is added after latin, and wins it. */ function cjkSliceFor(slices, cp) { const holds = (slice) => slice.ranges.some(([lo, hi]) => cp >= lo && cp <= hi); return slices.reduce((last, slice) => (holds(slice) ? slice : last), undefined); } /** Whether Fontsource serves `family` as a Chinese, Japanese or Korean * family (its subsets name the script). Fails when the API does not * answer: a CJK face taken for a Latin one would paint in a system face. */ async function isCjkFamily(family) { const meta = await fontsourceMeta(family); if (!meta) throw new Error(`api.fontsource.org did not describe ${family}: reload to try again`); return !!meta.subsets?.some((subset) => /^(chinese|japanese|korean)/.test(subset)); } /** faces = { 'Noto Serif TC': ['400', '700'] }, as for loadFonts: the * whole FONTS object may be passed, its other families are left to * loadFonts. Adds one FontFace per file of each CJK face with its * unicodeRange, then loads the files `text` touches. `text` is what the * faces set: the sample for the text face; a book in several voices calls * it once per voice (loadCjkFonts({ 'LXGW WenKai TC': ['400'] }, quotes)), * so the heading and quotation faces fetch and check only their own * characters. Fails when a character of `text` is in no file of a face. * List every weight the pages use: a weight left to the engine's later round * gets the latin file only. With { vertical: true } it also loads each * family's vertical forms (brackets, quotes, pause marks) for the canvas, * which needs loadVerticalAlternates imported from postext. Resolves to * the number of files loaded. */ async function loadCjkFonts(faces, text, { vertical = false } = {}) { kitStatus('Loading fonts…'); let loaded = 0; try { if (vertical && typeof loadVerticalAlternates !== 'function') { throw new Error('loadCjkFonts(…, { vertical: true }) needs loadVerticalAlternates imported from postext'); } for (const [family, specs] of Object.entries(faces)) { if (!(await isCjkFamily(family))) continue; const twin = []; for (const spec of new Set(specs)) { const weight = parseInt(spec, 10); const style = spec.endsWith('i') ? 'italic' : 'normal'; const slices = await cjkSlices(family, weight, style); const missing = [...new Set(text)].filter((ch) => /\S/.test(ch) && !cjkSliceFor(slices, ch.codePointAt(0))); if (missing.length) { throw new Error(`${family} ${spec} has no file for ${missing.slice(0, 12).join(' ')}: ` + `give each face the text it sets (loadCjkFonts({ '${family}': ['${spec}'] }, text))`); } for (const slice of slices) { document.fonts.add(new FontFace(family, `url(${slice.url}) format('woff2')`, { weight: String(weight), style, unicodeRange: slice.range })); twin.push({ source: slice.url, weight: String(weight), style, unicodeRange: slice.range }); } const font = `${style === 'italic' ? 'italic ' : ''}${weight} 16px "${family}"`; loaded += (await document.fonts.load(font, text)).length; if (!document.fonts.check(font, text)) throw new Error(`${family} ${spec} did not load for the sample`); } // The same files under a twin name with the `vert` feature on: the // canvas paints the punctuation of vertical lines with it. if (vertical && twin.length) await loadVerticalAlternates(family, twin); } } catch (error) { kitFail(error); throw error; } return loaded; } /** The PDF font provider for recipes with CJK faces: a family whose * Fontsource subsets are Chinese, Japanese or Korean gets the files the * screen set its pages' characters in (`request.codePoints`), each marked * with its unicode-range and its place in the order loadCjkFonts added * them: postext-pdf sets a character two files hold in the one added * last, as the browser does. Any other family gets the latin file * fontsourceProvider fetches (the "pdf" block) and, when the face sets * letters only latin-ext has, that file too. */ async function cjkPdfProvider(family, weight, style, request) { if (!(await isCjkFamily(family))) return cjkLatinPdfFiles(family, weight, style, request); 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.styles.includes('italic') ? 'normal' : style; const slices = await cjkSlices(family, w, s); const picked = new Set(); for (const cp of request?.codePoints ?? []) { const slice = cjkSliceFor(slices, cp); if (slice) picked.add(slice); } if (!picked.size) picked.add(slices[0]); return Promise.all(slices.map(async (slice, order) => { if (!picked.has(slice)) return null; const res = await fetch(slice.url); if (!res.ok) throw new Error(`Fontsource file ${slice.url} (${res.status})`); const bytes = await decompressWoff2(new Uint8Array(await res.arrayBuffer())); return Object.assign(bytes, { unicodeRange: slice.range, order }); })).then((files) => files.filter(Boolean)); } /** A Latin family set next to the CJK faces: its latin file, then its * latin-ext file when the face sets letters only latin-ext has (ō ū in * Hepburn rōmaji, ǎ in pinyin), the file loadFonts adds on screen for * them, each marked with its unicode-range and its place in that order: * the PDF sets a character in the file the browser took it from. A face * Fontsource ships without latin-ext, or whose file does not come, gets * latin alone, and the PDF names the letters it lacks. */ async function cjkLatinPdfFiles(family, weight, style, request) { const meta = await fontsourceMeta(family); const beyond = [...(request?.codePoints ?? [])].some(cjkLatinExtOnly); if (!beyond || !meta?.subsets?.includes('latin-ext')) return fontsourceProvider(family, weight, style); 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.styles.includes('italic') ? 'normal' : style; const id = fontsourceId(family); const url = `${await fontsourceAt(id)}files/${id}-latin-ext-${w}-${s}.woff2`; const [latin, ext] = await Promise.all([fontsourceProvider(family, weight, style), fetch(url) .then(async (res) => (res.ok ? decompressWoff2(new Uint8Array(await res.arrayBuffer())) : null), () => null)]); const ranges = kitRanges(); return ext ? [Object.assign(latin, { unicodeRange: ranges.latin, order: 0 }), Object.assign(ext, { unicodeRange: ranges['latin-ext'], order: 1 })] : latin; } /** Whether code point `cp` is in Fontsource's latin-ext file and not in * its latin file: Latin Extended-A and -B, IPA, the spacing modifiers and * Latin Extended Additional (loadFonts's test for latin-ext), less the * few latin holds too (ı Œ œ ʻ ʼ ˆ ˚ ˜). */ function cjkLatinExtOnly(cp) { if (!((cp >= 0x100 && cp <= 0x2ff) || (cp >= 0x1e00 && cp <= 0x1eff))) return false; return ![0x131, 0x152, 0x153, 0x2bb, 0x2bc, 0x2c6, 0x2da, 0x2dc].includes(cp); } /** showPages for a book bound on either edge. A right-bound book (the * document says so: doc.binding is 'right' for page.binding 'right' and * for vertical text) lies on the desk as it opens: page 1 alone on the * left of the spine, then [3 | 2], the spine shade on each page's inner * edge. `binding` ('left' | 'right') overrides the document's. */ function showBook(docs, { binding, ...options } = {}) { const count = showPages(docs, options); const right = (binding ?? [docs].flat()[0]?.binding) === 'right'; if (!document.getElementById('pt-kit-cjk')) { // The pages keep direction ltr: a canvas draws text in the direction its // element inherits, and under rtl each run would end where the engine // starts it, its brackets mirrored. document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit-cjk"> .pt-spread[dir="rtl"] canvas { direction: ltr; } .pt-spread[dir="rtl"] 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); } </style>`); } // Each pair stays [verso, recto] in the page; right to left, the verso // sits on the right. Phones stack the pages in reading order either way. for (const spread of document.querySelectorAll('#pages > .pt-spread')) spread.dir = right ? 'rtl' : 'ltr'; document.getElementById('pages').dataset.binding = right ? 'right' : 'left'; return count; } // ─── /Kit ───────────────────────────────────────────────────────────────────────
Das zusammengesetzte script.js läuft unverändert: Fügen Sie es in das Modulskript einer beliebigen Seite ein, oder öffnen Sie das Rezept auf CodePen. Ordner des Rezepts auf GitHub ↗ (öffnet in neuem Tab)
Varianten
#Die Noten am Kapitelende sammeln
Ohne placement setzt ein vertikales ja-Buch seine Noten als 後注 (kōchū): nach dem Text des Kapitels, unter derselben Linie, durch das Kapitel nummeriert. Hier füllen sie die linke Hälfte von Seite 4.
- placement: 'spread', // 傍注; 'chapterEnd' sets 後注 after the chapter instead
- numbering: 'spread',
+ placement: 'chapterEnd', // 後注, the default for a vertical ja book
+ numbering: 'chapter',#Das Zeichen neben das Wort setzen
markerPosition: 'side' setzt ein kleineres Zeichen in den Zwischenraum neben der Zeile, so wie Ruby zwischen den Zeilen steht, sodass es in der Zeile gar keinen Platz einnimmt.
- markerPosition: 'right', markerTemplate: '({n})', // written out: 'ja' sets them so
+ markerPosition: 'side', markerTemplate: '{n}',Häufige Fehler
Häufiger Fehler
Zeichnen Sie japanischen Text mit 'ja' aus, nie mit zh-Hans oder LANG
Die Ausgaben eines Rezepts sind en und es, ein japanisches Beispiel ist aber in beiden Japanisch: `locale: LANG` würde es als Englisch oder Spanisch auszeichnen, und eine chinesische Auszeichnung würde es nach chinesischen Regeln setzen (Kaiming-Interpunktion, kleine Kana dürfen eine Zeile beginnen, 图 statt 図, chinesische Glyphenformen im PDF). Seit postext 1.30 ist die Auszeichnung auch die Sprache, in der der Text geformt wird (gemessen, gezeichnet und eingebettet). Jede Auszeichnung außer 'ja' liefert daher andere Glyphenformen als die japanischen (unter einer chinesischen Auszeichnung die chinesischen) und andere Zeilenumbrüche. Schreiben Sie 'ja': Das wählt die Region Japan (JLReq-Zeilenumbruch und -Interpunktion, Sesam-Betonungszeichen, Furigana-Abstände, die Beschriftungen 図 und 表) und schaltet die Silbentrennung ab. Der Lint schlägt bei einem Text mit Kana unter einer zh- oder ko-Auszeichnung fehl. Japanischer Zeilenumbruch (Kinsoku) →
Häufiger Fehler
Chinesische Schnitte werden in Teilstücken geladen, über den Block cjk
Fontsource liefert eine chinesische, japanische oder koreanische Familie als etwa hundert Dateien pro Schriftstärke aus, jede für einen Zeichenbereich. loadFonts holt nur die Datei latin, am Bildschirm stammen die Han-Zeichen also aus einer Systemschrift und werden falsch gemessen, und fontsourceProvider gibt dem PDF diese latin-Datei, die sie als leere Kästchen druckt. Führen Sie den Kit-Block cjk auf, rufen Sie loadCjkFonts(FONTS, markdown) nach loadFonts auf (einmal pro Stimme, mit dem Text, den sie setzt, wenn das Buch mehrere CJK-Schriften verwendet), und geben Sie renderToPdf fontProvider: cjkPdfProvider: Beide nehmen die Dateien, die die Zeichen des Textes enthalten. Chinesische, japanische und koreanische Schriften →
Häufiger Fehler
Ein rechts gebundenes Buch zeigt seine Doppelseiten mit showBook
In einem rechts gebundenen Buch (arabischer, hebräischer oder persischer Text, vertikales Chinesisch oder page.binding 'right') ist Seite 1 weiterhin die rechte Seite (recto), liegt aber links vom Rücken, und die Paare lesen sich [3 | 2]. showPages legt jedes Buch links gebunden aus; showBook aus dem Block book (der Block cjk enthält dieselbe Funktion) liest doc.binding und spiegelt die Paare. capture.hero benennt eine Doppelseite weiterhin in Lesereihenfolge, [verso, recto]: [2, 3]. Rechts gebundene Bücher →
Nachweise
- Rezept
- Ignacio Ferro
- Text
- 樋口一葉「たけくらべ」 (Child’s Play, 1895–96), chapter one; Aozora Bunko card 389, from 「日本現代文學全集10 樋口一葉集」 (Kōdansha, 1962), input 青空文庫, proofreading 米田進 and 小林繁雄, converted with aozora.py; the seven notes were written for this recipe (CC BY 4.0) · Higuchi Ichiyō; Aozora Bunko volunteers · gemeinfrei
- Schriften
- Shippori Mincho (SIL OFL 1.1) · Noto Sans JP (SIL OFL 1.1)
- Code
- MIT, wie Postext
Diesen Text bearbeiten ↗ (öffnet in neuem Tab)Ordner des Rezepts auf GitHub ↗ (öffnet in neuem Tab)


