Kurz gesagt
Ein Übungsheft aus einer chinesischen Grundschule: zwei Einheiten zu Sprache und Mathe. Derselbe Text ergibt das Heft der Kinder mit Lücken und Lösungen am Ende und die Lehrerausgabe, in der jede Antwort rot an ihrer Stelle steht.
Was Sie setzen
Zwei Einheiten eines 练习册 für die zweite Klasse im Format 16开: eine Spracheinheit, 春天来了, und eine Mathe-Einheit zum Plus- und Minusrechnen bis 100. Jede beginnt mit einem Aquarellband, einem Fachreiter und einer Zeile für Klasse, Name und Punktzahl. Danach folgen die Abschnitte, die ein chinesisches Übungsheft druckt: 看拼音写词语, Lesungen zum Einkreisen, 选择题 mit einer ( ) nach dem Aufgabentext und A. B. C. D. in einer Reihe, 判断题 mit einer Klammer am Zeilenende, Lückensätze, 连线 zwischen Zählwörtern und Dingen, ein Satz zum Schreiben in 田字格, 口算 in vier Spalten, 竖式 mit einem Kästchen je Ziffer und eine Textaufgabe. Die Punkte stehen in jeder Arbeitsanweisung, dort, wo die Kinder sie lesen. Dasselbe Markdown wird zweimal gesetzt: als Schülerheft mit dem 参考答案 am Ende jeder Einheit und als Lehrerausgabe, in der jede Antwort in roter Kai an ihrer Stelle steht.
Dieses Rezept beantwortet
- Wie setze ich chinesische Prüfungsfragen: eine Klammer ( ) nach der Frage und die Antworten A. B. C. D. in einer Reihe?
- Wie erzeuge ich aus dem Schülerbuch die Lehrerausgabe mit den Lösungen an ihrer Stelle?
- Wie gebe ich einer offenen Frage Seyès-Lineatur, Kästchen oder 田字格-Felder zum Schreiben?
- Wie setze ich die schriftliche Division so, wie die Schulen jedes Landes sie schreiben?
Die kurze Antwort
// The exercise style is the form; the Markdown holds every word, the points included. The
// same text builds both editions: `edition: 'teacher'` writes each answer into its blank,
// bracket and square in red Kai, rings the right options and draws the matching lines.
const lianxi = {
id: 'lianxi',
number: { format: 'simp-chinese-informal', suffix: '、', fontFamily: HEI, color: col('green') },
instruction: { fontFamily: HEI, bold: false }, // 黑体, not bold: the points stay quiet
// 选择题: A. B. C. D. in a row, a ( ) after the stem for the letter.
options: { labels: 'alpha-upper', suffix: '.', layout: 'row', bracket: true },
trueFalse: { look: 'bracket' }, // 判断题: ( ) at the end of the line, √ or ×
blank: { look: 'line', color: col('ink') },
area: { kind: 'tianzige', color: col('rule') }, // 写话 on 田字格
match: { color: col('green') },
grid: { fontFamily: SONG, rule: col('rule') },
answers: { color: col('red'), fontFamily: KAI },
solutions: { box: 'daan' },
marginTop: em(1.2), marginBottom: em(0.6),
};
const exerciseStyles = [
lianxi,
// 看拼音写词语: a box under each syllable, as wide as its word.
{ id: 'pinyin', extends: 'lianxi', blank: { look: 'box', width: 'answer', color: col('rule') } },
// 在( )里填: the bracket is in the text, the blank inside it draws nothing.
{ id: 'kuohao', extends: 'lianxi', blank: { look: 'none', width: 'answer' } },
];
let edition = 'student'; // the second build below prints the teacher's book
// The pupil's book: 参考答案 where a :::solutions line stands, at the end of each unit.
// The teacher's book: every answer on the page, so no key at the end.
const exercises = () => ({ edition,
solutions: { placement: edition === 'teacher' ? 'none' : 'directive' } });
Zutaten
- Funktionen
- ÜbungsstileLehrerausgabeAuswahl und richtig oder falschÜbungenLücken und WortspeicherZuordnen und sortierenOffene Fragen und SchreibflächenSchriftliches RechnenLösungenChinesische ZahlzeichenChinesische, japanische und koreanische SchriftenZeichenrasterBetonungspunkte, Namens- und TitellinienInline-ChipsDrei oder mehr SpaltenGestaltete AuftakteÜberschriftenattributeBilder in SeitengestaltungenSemantische FarbpalettePDF-Export
- Nutzt außerdem
- Spalten in einem Kasten oder im TextKästen, die sich teilen oder ganz bleibenChinesischer ZeilenumbruchSpaltenausgleichAbsatzformateIm PDF eingebettete SchriftenAbbildungen und Tabellen als Ressourcen
- Schrift
- Noto Serif SC, Noto Sans SC, LXGW WenKai TC (SIL OFL 1.1)
- Dateien
market-1600.jpgspring-1600.jpg- A Chinese village in early spring: willows, swallows, peach blossom and two children on a path between rice fields. (Generated With Diffusion Models, eigener Text)
- Two children count oranges, apples and pears at a grandmother's fruit stall, a cat on a stool beside them. (Generated With Diffusion Models, eigener Text)
Zubereitung
#1 · Ein Stil für alle Abschnitte
Der Code steht oben in der kurzen Antwort. Die Abschnitte werden mit chinesischen Zahlzeichen gezählt, gefolgt von einem 、 in voller Breite, also steht hinter der Zahl kein Wortzwischenraum. Die Arbeitsanweisung steht in der Hei, nicht fett, damit man die Punkte in Klammern als Teil von ihr liest. options.bracket schreibt die ( ) hinter jeden Aufgabentext, und in der Lehrerausgabe kommt der richtige Buchstabe hinein. Die beiden kleinen Stile erweitern den Hauptstil: pinyin zeichnet jede Lücke als Kästchen, so breit wie ihr Wort, kuohao zeichnet nichts, weil die Klammer von 在( )里填 im Text steht.
#2 · Schülerheft und Lehrerausgabe aus einem Text
const build = () => withLoadedFonts(() => buildDocument({ markdown, resources }, config()),
{ ...kitFonts(FONTS), text: markdown });
const pupil = await build();
edition = 'teacher';
const docs = [pupil, await build()];
Die Konfiguration ist eine Funktion, also bekommt jeder Build sein eigenes Objekt, und das Sandbox-Bundle behält das des Schülerhefts. edition: 'teacher' schreibt die Antworten in Farbe und Schrift von answers: die Wörter in ihre Kästchen, die Buchstaben in ihre Klammern, √ und × in die 判断题, die Ziffern in die Kästchen der 竖式, den Mustersatz mit einem Zeichen pro Feld. Das Schülerheft druckt die Lösungen dort, wo am Ende jeder Einheit die Zeile :::solutions steht; die Lehrerausgabe braucht sie nicht.
#3 · Ein Auftakt für jede Einheit, mit seinem Bild
const BAND = 184 / 2.4; // mm: the painting's height at the page's width
const opener = { enabled: true, minHeight: mm(BAND - 22 + 23), slot: { elements: [
{ kind: 'image', id: 'picture', resourceId: '{attr.pic}', decorative: true, reserve: false,
placement: { anchor: { to: 'page', edge: 'top-left' },
size: { width: mm(184), height: mm(BAND) } } },
{ kind: 'text', id: 'subject', content: '{attr.kicker}', fontFamily: HEI, fontSize: pt(11),
fontWeight: 700, color: col('paper'), letterSpacing: pt(2), align: 'center',
box: { backgroundColor: col('green'), borderRadius: mm(1.5),
padding: { top: mm(0.8), bottom: mm(0.8), left: mm(2.4), right: mm(2) } },
placement: { anchor: { to: 'container', edge: 'top-left' },
offset: { x: mm(0), y: mm(BAND - 22 + 6) } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: HEI, fontSize: pt(24),
fontWeight: 700, color: col('ink'), align: 'left',
placement: { anchor: { to: '#subject', edge: 'right-of' },
offset: { x: mm(4), y: mm(-2.6) } } },
{ kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1.2), color: col('green'),
placement: { anchor: { to: 'container', edge: 'top-left' },
offset: { x: mm(0), y: mm(BAND - 22 + 19) }, size: { width: 'fill' } } },
] } };
Das Bild ist an der Seite verankert und reicht deshalb auf einer rechten wie auf einer linken Seite bis an den oberen Rand. Fachreiter und Titel werden von der Oberkante des Satzspiegels aus platziert, unter dem Fuß des Bilds. Beide Einheiten teilen sich die Gestaltung und unterscheiden sich nur in den Attributen ihrer Überschrift, kicker und pic.
#4 · Seitenzahlen im Fuß
const foot = (id, content, parity, edge, x, extra = {}) => ({ kind: 'text', id, content,
parity, fontFamily: HEI, fontSize: pt(8), color: col('muted'),
placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(-10) } }, ...extra });
const footer = { elements: [
foot('folio-v', '{pageNumber}', 'even', 'bottom-left', 16,
{ fontWeight: 700, color: col('green') }),
foot('series', '{title}', 'even', 'bottom-left', 24),
foot('unit', '{chapterTitle}', 'odd', 'bottom-right', -24, { align: 'right' }),
foot('folio-r', '{pageNumber}', 'odd', 'bottom-right', -16,
{ fontWeight: 700, color: col('green'), align: 'right' }),
] };
Chinesische Übungshefte setzen die Seitenzahl in den Fuß. Der Reihentitel kommt aus dem Front Matter des Markdowns ({title}), der Titel der Einheit aus ihrer Überschrift ({chapterTitle}).
Das ganze Rezept
// ═══ Postext Cookbook · Nº 171 · A Chinese exercise book, pupil's and teacher's ═══
// https://postext.dev/en/cookbook/chinese-exercise-book
// Code: MIT · Text: original (CC BY 4.0) · Pictures: generated with diffusion models
// Fonts: Noto Serif SC, Noto Sans SC, LXGW WenKai TC (SIL OFL 1.1) · Needs postext ≥ 1.59.0
import {
buildDocument, withLoadedFonts, renderPageToCanvas, registerResourceImage,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
const LANG = 'zh'; // @lang: the language of the frame; the exercise book is Chinese in all
const RECIPE = 'chinese-exercise-book';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a green exercise book, and the red of the teacher's pen
const palette = {
ink: '#1f2523', // text: a green-tinted near-black
green: '#2c7350', // the one accent: section numbers, the subject tab, the squares
red: '#d0352c', // the teacher's answers
tint: '#e6f2ea', // the subject tab's ground, the answers box
rule: '#8fb9a2', // 田字格, the matching dots' lines
muted: '#68736d', // folios, the series line
paper: '#ffffff',
};
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: 'accent (defaults)', value: { hex: palette.green, model: 'hex' } },
];
// #endregion
// 宋 for the text, 黑 for the instructions and labels, 楷 for what the teacher writes.
const [SONG, HEI, KAI] = ['Noto Serif SC', 'Noto Sans SC', 'LXGW WenKai TC'];
const CHARS = 40; // characters per line: 40 × 10.5 pt = 148 mm
const SIZE = 10.5; // pt: 五号, the size of a school book's text
// #region answer: sections 一、二、 that print the pupil's book or the teacher's
// The exercise style is the form; the Markdown holds every word, the points included. The
// same text builds both editions: `edition: 'teacher'` writes each answer into its blank,
// bracket and square in red Kai, rings the right options and draws the matching lines.
const lianxi = {
id: 'lianxi',
number: { format: 'simp-chinese-informal', suffix: '、', fontFamily: HEI, color: col('green') },
instruction: { fontFamily: HEI, bold: false }, // 黑体, not bold: the points stay quiet
// 选择题: A. B. C. D. in a row, a ( ) after the stem for the letter.
options: { labels: 'alpha-upper', suffix: '.', layout: 'row', bracket: true },
trueFalse: { look: 'bracket' }, // 判断题: ( ) at the end of the line, √ or ×
blank: { look: 'line', color: col('ink') },
area: { kind: 'tianzige', color: col('rule') }, // 写话 on 田字格
match: { color: col('green') },
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 sich in einem neuen Tab)
Varianten
#Die Lösungen am Kapitelende drucken
Ohne :::solutions-Zeilen sammelt chapterEnd die Lösungen jeder Einheit vor der nächsten Überschrift der Ebene 1.
- solutions: { placement: edition === 'teacher' ? 'none' : 'directive' } });
+ solutions: { placement: edition === 'teacher' ? 'none' : 'chapterEnd' } });#Die Lesungen neben die Lücken schreiben
Die Pinyin-Fibel setzt das Pinyin als Ruby über ihre Zeichen; eine Lücke kann das noch nicht tragen.
Häufige Fehler
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 →
- Eine Lesung kann noch nicht über einer Lücke stehen: Ein Ruby in
[[…]]gehört zur verborgenen Antwort, und eine 田字格-Fläche nimmt die ganze Satzbreite ein. Der Abschnitt 看拼音写词语 setzt jede Silbe in eine eigene Zeile und das Kästchen darunter, vier pro Reihe in einer:::columns-Gruppe. - Die Kai setzt nur die Antworten und lädt deshalb nur die Zeichen der Lücken und der Musterantworten. Setzt sie anderswo ein weiteres Zeichen, muss es in diesen Text aufgenommen werden.
Nachweise
- Rezept
- Ignacio Ferro
- Text
- Eigener Text, CC BY 4.0
- Bilder
- A Chinese village in early spring: willows, swallows, peach blossom and two children on a path between rice fields. · Generated With Diffusion Models · eigener Text
- Two children count oranges, apples and pears at a grandmother's fruit stall, a cat on a stool beside them. · Generated With Diffusion Models · eigener Text
- Schriften
- Noto Serif SC (SIL OFL 1.1) · Noto Sans SC (SIL OFL 1.1) · LXGW WenKai TC (SIL OFL 1.1)
- Code
- MIT, wie Postext
Diesen Text bearbeiten ↗ (öffnet sich in einem neuen Tab)Ordner des Rezepts auf GitHub ↗ (öffnet sich in einem neuen Tab)


