Aller au contenu principal
Recette numéro 171

Recettes · Chapitre 12 · Manuels scolaires

Un cahier d’exercices chinois, de l’élève et du maître

Un 练习册 de chinois et de maths : sections 一、二、, parenthèses ( ), 田字格 et opérations posées, composé deux fois à partir d’un texte, pour l’élève et le maître.

Sur cette page
Sortie
Canvas · PDF
Postext
Testé avec Postext 1.59.0
Nécessite ≥ 1.59.0 · postext-pdf ≥ 1.59.0
Licence
Mis à jour le 24 oct. 2026
Code MIT · Texte CC BY 4.0
  • Exemple en langue étrangère : pas encore d’édition française
  • Format 184 × 260 mm
  • 1 colonne
  • Noto Serif SC 10,5/21
  • LXGW WenKai TC
  • Noto Sans SC
  • 8 pages
  • Niveau
  • Postext 1.59.0
  • Mis en page en 124 ms
  • 162 lignes de code

En bref

Un cahier d’exercices d’école primaire chinoise : deux unités de langue et de maths. Le même texte donne le cahier de l’élève, avec des blancs et un corrigé à la fin, et le livre du maître, avec chaque réponse écrite en rouge à sa place.

Ce que vous allez composer

Deux unités d’un 练习册 de CE1 au format 16开 : une unité de langue, 春天来了, et une unité de maths sur les additions et les soustractions jusqu’à 100. Chacune s’ouvre sur un bandeau d’aquarelle, un onglet de matière et une ligne pour la classe, le nom et la note, puis enchaîne les sections qu’imprime un cahier chinois : 看拼音写词语, des lectures à entourer, des 选择题 avec une ( ) après l’énoncé et A. B. C. D. en ligne, des 判断题 avec une parenthèse en fin de ligne, des phrases à trous, un 连线 entre classificateurs et objets, une phrase à écrire dans des 田字格, du 口算 sur quatre colonnes, des 竖式 avec une case par chiffre et un problème. Le barème figure dans chaque consigne, là où l’élève le lit. Le même Markdown est mis en page deux fois : le cahier de l’élève, avec le 参考答案 à la fin de chaque unité, et le livre du maître, avec chaque réponse écrite en Kai rouge à sa place.

Cette recette répond à

  • Comment composer des questions d'examen chinois : des parenthèses ( ) après l'énoncé et les options A. B. C. D. sur une ligne ?
  • Comment tirer le livre du professeur, réponses imprimées à leur place, du livre de l'élève ?
  • Comment donner à une question ouverte une réglure Seyès, un quadrillage ou des cases 田字格 ?
  • Comment composer la division posée comme l'écrivent les écoles de chaque pays ?

La réponse courte

script.js · lignes 36–65dans le code complet
// 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' } });

Ingrédients

Typographie
Noto Serif SC, Noto Sans SC, LXGW WenKai TC (SIL OFL 1.1)
Ressources
  • market-1600.jpg
  • spring-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, original)
  • Two children count oranges, apples and pears at a grandmother's fruit stall, a cat on a stool beside them. (Generated With Diffusion Models, original)

Préparation

#1 · Un seul style pour toutes les sections

Le code est dans la réponse courte plus haut. Les sections sont numérotées en chiffres chinois suivis d’un 、 pleine chasse, donc aucune espace ne suit le numéro ; la consigne est composée dans la Hei, sans gras, pour que les points entre parenthèses se lisent comme une partie de la consigne. options.bracket écrit la ( ) après chaque énoncé, et dans le livre du maître la bonne lettre vient s’y loger. Les deux petits styles prolongent le principal : pinyin dessine chaque blanc comme une case aussi large que son mot, kuohao ne dessine rien, puisque la parenthèse de 在( )里填 est dans le texte.

#2 · Le cahier de l’élève et le livre du maître à partir d’un seul texte

script.js · lignes 334–338dans le code complet
const build = () => withLoadedFonts(() => buildDocument({ markdown, resources }, config()),
  { ...kitFonts(FONTS), text: markdown });
const pupil = await build();
edition = 'teacher';
const docs = [pupil, await build()];

La configuration est une fonction : chaque construction reçoit son propre objet, et le paquet du Sandbox garde celui de l’élève. edition: 'teacher' écrit les réponses dans la couleur et la police de answers : les mots dans leurs cases, les lettres dans leurs parenthèses, √ et × dans les 判断题, les chiffres dans les cases des 竖式, la phrase modèle à raison d’un caractère par carré. Le cahier de l’élève garde le corrigé pour la ligne :::solutions en fin d’unité ; le livre du maître n’en a pas besoin.

#3 · Une ouverture d’unité avec sa peinture

script.js · lignes 69–87dans le code complet
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' } } },
] } };

La peinture est ancrée à la page : elle monte jusqu’au bord du haut sur un recto comme sur un verso. L’onglet de matière et le titre se placent depuis le haut du bloc de texte, sous le pied de la peinture. Les deux unités partagent la maquette et ne diffèrent que par les attributs de leur titre, kicker et pic.

#4 · Les folios en pied

script.js · lignes 91–101dans le code complet
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' }),
] };

Les cahiers chinois placent le folio en pied. Le titre de la collection vient de l’en-tête du Markdown ({title}), celui de l’unité vient de son titre ({chapterTitle}).

La recette complète

Sandbox
// ═══ 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') },

Le script.js composé s’exécute tel quel : collez-le dans le script de module de n’importe quelle page, ou ouvrez la recette sur CodePen. Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)

Variantes

#Imprimer le corrigé en fin de chapitre

Sans lignes :::solutions, chapterEnd rassemble le corrigé de chaque unité avant le titre de niveau 1 suivant.

-  solutions: { placement: edition === 'teacher' ? 'none' : 'directive' } });
+  solutions: { placement: edition === 'teacher' ? 'none' : 'chapterEnd' } });

#Mettre les lectures à côté des blancs

L’abécédaire en pinyin place le pinyin au-dessus de ses caractères en ruby, ce qu’un blanc ne peut pas encore porter.

Erreurs fréquentes

Erreur fréquente

Les polices chinoises se chargent par tranches, avec le bloc cjk

Fontsource sert une famille chinoise, japonaise ou coréenne sous la forme d’une centaine de fichiers par graisse, chacun couvrant une plage de caractères. loadFonts ne récupère que le fichier latin : à l’écran, les caractères han viennent d’une police système et sont mal mesurés, et fontsourceProvider remet ce fichier latin au PDF, qui les imprime en cases vides. Listez le bloc cjk du kit, appelez loadCjkFonts(FONTS, markdown) après loadFonts (une fois par voix, avec le texte qu’elle compose, quand le livre utilise plusieurs polices CJK) et donnez à renderToPdf fontProvider: cjkPdfProvider : les deux prennent les fichiers qui contiennent les caractères du texte. Polices chinoises, japonaises et coréennes →

  • Une lecture ne peut pas encore se placer au-dessus d’un blanc : un ruby dans [[…]] fait partie de la réponse cachée, et une zone de 田字格 prend toute la justification. La section 看拼音写词语 met chaque syllabe sur sa propre ligne avec la case dessous, quatre par rangée dans un groupe :::columns.
  • La police Kai ne compose que les réponses : elle ne charge donc que les caractères des blancs et des réponses modèles. Un caractère qu’elle compose ailleurs doit être ajouté à ce texte.

Crédits

Texte
Texte original, CC BY 4.0
Images
  • A Chinese village in early spring: willows, swallows, peach blossom and two children on a path between rice fields. · Generated With Diffusion Models · original
  • Two children count oranges, apples and pears at a grandmother's fruit stall, a cat on a stool beside them. · Generated With Diffusion Models · original
Polices
Noto Serif SC (SIL OFL 1.1) · Noto Sans SC (SIL OFL 1.1) · LXGW WenKai TC (SIL OFL 1.1)
Sandbox