Pular para o conteúdo principal
Receita número 171

Receitas · Capítulo 12 · Livros didáticos

Um caderno de exercícios chinês, do aluno e do professor

Um 练习册 de língua e matemática: seções 一、二、, parênteses ( ), 田字格 e contas armadas, composto duas vezes a partir de um texto, para o aluno e o professor.

Nesta página
Saída
Canvas · PDF
Postext
Testada com o Postext 1.59.0
Requer ≥ 1.59.0 · postext-pdf ≥ 1.59.0
Licença
Atualizada em 24 de out. de 2026
Código MIT · Texto CC BY 4.0
  • Amostra em outro idioma: ainda sem edição em português
  • Refile 184 × 260 mm
  • 1 coluna
  • Noto Serif SC 10,5/21
  • LXGW WenKai TC
  • Noto Sans SC
  • 8 páginas
  • Nível
  • Postext 1.59.0
  • Diagramado em 124 ms
  • 162 linhas de código

Em poucas palavras

Um caderno de exercícios de escola primária chinesa: duas unidades de língua e de matemática. O mesmo texto gera a cópia do aluno, com lacunas e gabarito no fim, e a do professor, com cada resposta escrita em vermelho no lugar dela.

O que você vai compor

Duas unidades de um 练习册 do segundo ano no formato 16开: uma de língua, 春天来了, e uma de matemática sobre adição e subtração até 100. Cada uma abre com uma faixa em aquarela, uma aba com a matéria e uma linha para turma, nome e nota, e segue pelas seções que um caderno chinês imprime: 看拼音写词语, leituras para circular, 选择题 com um ( ) depois do enunciado e A. B. C. D. em linha, 判断题 com um parêntese no fim da linha, lacunas, 连线 entre classificadores e coisas, uma frase para escrever em 田字格, 口算 em quatro colunas, 竖式 com um quadrinho por algarismo e um problema. Os pontos de cada questão ficam no enunciado, onde o aluno os lê. O mesmo Markdown é composto duas vezes: o livro do aluno, com o 参考答案 no fim de cada unidade, e o do professor, com cada resposta escrita em Kai vermelha no seu lugar.

Esta receita responde a

  • Como componho questões de prova chinesas: parênteses ( ) após o enunciado e as alternativas A. B. C. D. em linha?
  • Como faço o livro do professor, com as respostas impressas no lugar, a partir do livro do aluno?
  • Como dou a uma pergunta aberta pauta Seyès, quadriculado ou quadros 田字格 para escrever?
  • Como componho a divisão longa do jeito que as escolas de cada país a escrevem?

A resposta curta

script.js · linhas 36–65no código completo
// 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' } });

Ingredientes

Tipografia
Noto Serif SC, Noto Sans SC, LXGW WenKai TC (SIL OFL 1.1)
Materiais
  • 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)

Preparo

#1 · Um estilo para todas as seções

O código é a resposta curta logo acima. As seções são contadas com numerais chineses seguidos de um 、 de largura inteira, então nenhum espaço vem depois do número; o enunciado é composto na Hei, sem negrito, para que os pontos entre parênteses sejam lidos como parte dele. options.bracket escreve o ( ) depois de cada enunciado, e no livro do professor a letra certa entra nele. Os dois estilos pequenos estendem o principal: pinyin desenha cada lacuna como um quadrinho da largura da palavra, kuohao não desenha nada, porque o parêntese de 在( )里填 está no texto.

#2 · O livro do aluno e o do professor a partir de um texto

script.js · linhas 334–338no código completo
const build = () => withLoadedFonts(() => buildDocument({ markdown, resources }, config()),
  { ...kitFonts(FONTS), text: markdown });
const pupil = await build();
edition = 'teacher';
const docs = [pupil, await build()];

A configuração é uma função, então cada composição recebe o próprio objeto e o pacote do Sandbox guarda o do aluno. edition: 'teacher' escreve as respostas na cor e na fonte de answers: as palavras nos quadrinhos, as letras nos parênteses, √ e × nos 判断题, os algarismos nas casas do 竖式, a frase modelo com um caractere por quadrado. O livro do aluno guarda o gabarito para a linha :::solutions no fim de cada unidade; o do professor não precisa dele.

#3 · Uma abertura de unidade com a sua pintura

script.js · linhas 69–87no código completo
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' } } },
] } };

A pintura fica ancorada à página, então chega à borda superior tanto numa página ímpar quanto numa par; a aba e o título são posicionados a partir do topo da mancha de texto, abaixo do pé da pintura. As duas unidades compartilham o design e só mudam nos atributos do título, kicker e pic.

#4 · Fólios no pé

script.js · linhas 91–101no código completo
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' }),
] };

Os cadernos chineses põem o fólio no pé. O título da série vem do front matter do Markdown ({title}), e o da unidade, do seu título ({chapterTitle}).

A receita completa

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

O script.js montado funciona como está: cole-o como script de módulo em qualquer página ou abra a receita no CodePen. Pasta da receita no GitHub ↗ (abre em uma nova aba)

Variações

#Imprima o gabarito no fim do capítulo

Sem linhas :::solutions, chapterEnd reúne o gabarito de cada unidade antes do próximo título de nível 1.

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

#Escreva as leituras ao lado das lacunas

A cartilha com pinyin põe o pinyin sobre os caracteres como ruby, algo que uma lacuna ainda não comporta.

Erros comuns

Erro comum

Fontes chinesas são carregadas em fatias, pelo bloco cjk

O Fontsource serve uma família chinesa, japonesa ou coreana em cerca de cem arquivos por peso, cada um com um intervalo de caracteres. loadFonts baixa só o arquivo latin, então na tela os caracteres chineses vêm de uma fonte do sistema e são medidos errado, e o fontsourceProvider entrega ao PDF esse arquivo latin, que os imprime como caixas vazias. Inclua o bloco cjk do kit, chame loadCjkFonts(FONTS, markdown) depois de loadFonts (uma vez por voz, com o texto que ela compõe, quando o livro usa várias fontes CJK) e passe a renderToPdf fontProvider: cjkPdfProvider: os dois pegam os arquivos que contêm os caracteres do texto. Fontes chinesas, japonesas e coreanas →

  • Uma leitura ainda não pode ficar sobre uma lacuna: um ruby dentro de [[…]] faz parte da resposta oculta, e uma área de 田字格 ocupa toda a medida. A seção 看拼音写词语 põe cada sílaba numa linha própria e o quadrinho embaixo, quatro por fileira num grupo :::columns.
  • A fonte Kai compõe só as respostas, então carrega apenas os caracteres das lacunas e das respostas modelo; um caractere que ela componha em outro lugar precisa ser acrescentado a esse texto.

Créditos

Texto
Texto original, CC BY 4.0
Imagens
  • 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
Fontes
Noto Serif SC (SIL OFL 1.1) · Noto Sans SC (SIL OFL 1.1) · LXGW WenKai TC (SIL OFL 1.1)
Sandbox