跳到主要内容
食谱编号169

排版食谱 · 第12章 · 教科书

一张练习单,十种学校传统

同样七个 :::exercise 块,用十种语言各排一版。每一版用同一个练习样式来排,题号、选项、判断题、填空和除法竖式都照该国课本的习惯。

本页内容
输出
Canvas · PDF
难度
中级
Postext
已用Postext 1.59.0测试
需要≥ 1.59.0 · postext-pdf ≥ 1.59.0
许可证
更新于2026年10月24日
代码MIT · 文本CC BY 4.0
  • 成品尺寸210 × 297 mm
  • 1栏
  • Noto Serif SC 12/18
  • Noto Sans SC
  • 2页
  • 难度
  • Postext 1.59.0
  • 排版用时15 ms
  • 154行代码

简单来说

一张两页的科学和数学练习单,讲水的循环,做了十次。题目都一样;题号、方框、书写线和除法竖式,都照各国课本的样子印。

成品一览

四年级科学与数学课程的第 7 张练习单《水的旅行》,排成十个版本。各语言的题目都一样:选一个答案,判断对错,用词语库里的词填空,把词语和意思连起来,圈出一个词,把水箱里的水平均分,说说为什么要节约用水。每个版本都照本国学校的样子印。西班牙文版和加泰罗尼亚文版把题号放在圆圈里,判断题标 V 或 F,除法把除数放在被除数右边的 L 形里。法文版用方块题号、cases à cocher 方框和点线空格,作答处是 Seyès 格纸。德文版写 R 和 F,填空题叫 Lückentext,864 : 4 = 写成一行,乘号用点。巴西版要在括号里写 V 或 F。英文版在 ⟌ 下面做除法。中文版的大题用一、二、编号,每个题干后面有( ),A. B. C. 排成一行,判断题打 √ 或 ×,最后一题是田字格里的“看拼音,写词语”。日文版用ア イ ウ和○ ×,阿拉伯文版从右往左排,用 أ ب ج 和 صح 或 خطأ。

这道食谱解答

  • 怎样把同一张练习纸按各国教科书的习题版式来排?
  • 怎样按各国学校的写法排长除法竖式?
  • 怎样排中文选择题:题干后的( )括号和一行排开的 A. B. C. D. 选项?

简短回答

script.js · 第36–69行在完整代码中
// The Markdown of every edition has the same seven exercises. What changes is this table:
// how the number prints, how options are labelled and answered, how true/false reads, where
// a blank is written, the paper of the open answer and the layout of long division.
const disc = { shape: 'disc' };
const square = { shape: 'square' };
const bare = { shape: 'none', suffix: '.' };
const TRADITIONS = {
  es: { number: disc, options: { marker: 'none' }, trueFalse: { look: 'boxes' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'potence' } },
  fr: { number: square, options: { marker: 'box' }, trueFalse: { look: 'boxes' },
    blank: { look: 'dots' }, area: { kind: 'seyes', spacing: mm(8) },
    arithmetic: { division: 'potence' } },
  de: { number: square, options: { marker: 'box' }, trueFalse: { look: 'boxes' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'inline' } },
  it: { number: { shape: 'rounded' }, options: { marker: 'box' }, trueFalse: { look: 'boxes' },
    blank: { look: 'dots' }, area: { kind: 'lines' }, arithmetic: { division: 'potence' } },
  pt: { number: bare, options: { marker: 'none' }, trueFalse: { look: 'bracket' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'potence' } },
  en: { number: bare, options: { marker: 'none' }, trueFalse: { look: 'boxes' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'bracket' } },
  // 一、二、 sections; ( ) after the stem and A. B. C. in a row; √ × in a bracket; 田字格.
  zh: { number: { shape: 'none', format: 'simp-chinese-informal', suffix: '、' },
    options: { labels: 'alpha-upper', suffix: '.', layout: 'row', bracket: true },
    trueFalse: { look: 'bracket' }, blank: { look: 'line' },
    area: { kind: 'tianzige', spacing: mm(13), lines: 1 }, arithmetic: { division: 'bracket' } },
  // ア イ ウ in a row with ( ), ○ × in a bracket, the number in a square.
  ja: { number: square, options: { labels: 'katakana', suffix: '', layout: 'row', bracket: true },
    trueFalse: { look: 'bracket' }, blank: { look: 'line' }, area: { kind: 'lines' },
    arithmetic: { division: 'bracket' } },
  ar: { number: disc, options: { marker: 'none', prefix: '(', suffix: ')' },
    trueFalse: { look: 'boxes' },
    blank: { look: 'dots' }, area: { kind: 'lines' }, arithmetic: { division: 'bracket' } },
};
TRADITIONS.ca = TRADITIONS.es; // Catalan schools set exercises as Spanish ones do

用料

类型
Nunito, Baloo 2, Noto Serif SC, Noto Sans SC, Klee One, Noto Sans JP, Noto Naskh Arabic, Baloo Bhaijaan 2(SIL OFL 1.1)
素材
无:所有图片都用代码绘制

做法

#1 · 把每种传统写进一张表

script.js · 第36–69行在完整代码中
// The Markdown of every edition has the same seven exercises. What changes is this table:
// how the number prints, how options are labelled and answered, how true/false reads, where
// a blank is written, the paper of the open answer and the layout of long division.
const disc = { shape: 'disc' };
const square = { shape: 'square' };
const bare = { shape: 'none', suffix: '.' };
const TRADITIONS = {
  es: { number: disc, options: { marker: 'none' }, trueFalse: { look: 'boxes' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'potence' } },
  fr: { number: square, options: { marker: 'box' }, trueFalse: { look: 'boxes' },
    blank: { look: 'dots' }, area: { kind: 'seyes', spacing: mm(8) },
    arithmetic: { division: 'potence' } },
  de: { number: square, options: { marker: 'box' }, trueFalse: { look: 'boxes' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'inline' } },
  it: { number: { shape: 'rounded' }, options: { marker: 'box' }, trueFalse: { look: 'boxes' },
    blank: { look: 'dots' }, area: { kind: 'lines' }, arithmetic: { division: 'potence' } },
  pt: { number: bare, options: { marker: 'none' }, trueFalse: { look: 'bracket' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'potence' } },
  en: { number: bare, options: { marker: 'none' }, trueFalse: { look: 'boxes' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'bracket' } },
  // 一、二、 sections; ( ) after the stem and A. B. C. in a row; √ × in a bracket; 田字格.
  zh: { number: { shape: 'none', format: 'simp-chinese-informal', suffix: '、' },
    options: { labels: 'alpha-upper', suffix: '.', layout: 'row', bracket: true },
    trueFalse: { look: 'bracket' }, blank: { look: 'line' },
    area: { kind: 'tianzige', spacing: mm(13), lines: 1 }, arithmetic: { division: 'bracket' } },
  // ア イ ウ in a row with ( ), ○ × in a bracket, the number in a square.
  ja: { number: square, options: { labels: 'katakana', suffix: '', layout: 'row', bracket: true },
    trueFalse: { look: 'bracket' }, blank: { look: 'line' }, area: { kind: 'lines' },
    arithmetic: { division: 'bracket' } },
  ar: { number: disc, options: { marker: 'none', prefix: '(', suffix: ')' },
    trueFalse: { look: 'boxes' },
    blank: { look: 'dots' }, area: { kind: 'lines' }, arithmetic: { division: 'bracket' } },
};
TRADITIONS.ca = TRADITIONS.es; // Catalan schools set exercises as Spanish ones do

所有版本的 Markdown 都一样:七个 :::exercise 块,答案就写在文字里(- [x]、[[水蒸气]]、- 蒸发 -> 水变成水蒸气、- 864 ÷ 4)。各国课本不同的地方在于形式,所以形式只放在这张表里:题号的形状和数字写法,选项的字母,学生是勾方框还是圈字母,判断题的样子,空格的样子,书写区,还有除法竖式的排法。加泰罗尼亚文版原样沿用西班牙文版那一行。

#2 · 把传统铺在本社样式上

script.js · 第73–87行在完整代码中
const T = TRADITIONS[LANG];
const ficha = {
  id: 'ficha', ...T,
  number: { background: col('accent'), color: T.number.shape === 'none' ? col('accent')
    : col('paper'), fontFamily: LABEL, fontWeight: 700, ...T.number },
  // Chinese and Japanese sheets set the instructions in the gothic (黑体, ゴシック).
  instruction: { bold: true, color: col('ink'), ...(LANG === 'zh' || LANG === 'ja'
    ? { fontFamily: LABEL } : {}) },
  blank: { color: col('ink'), weight: pt(0.7), ...T.blank },
  match: { look: 'dots', gap: em(5), color: col('accent') },
  bank: { order: 'alpha' },
  area: { color: col('rule'), spacing: mm(9), weight: pt(0.6), ...T.area },
  grid: { cell: mm(8.5), fontFamily: TEXT, rule: col('rule'), color: col('ink') },
  marginTop: mm(7), marginBottom: mm(1), itemSpacing: em(0.4),
};

练习样式只有一个,叫 ficha,它带着十张练习单共有的东西(题号的蓝色、灰色的线、练习之间的空白),各版本的那一行再铺在它上面。表里没写的,由语言决定:T F、V F、R F、√ ×、○ × 和 صح خطأ 都是引擎自带的词,a) b) c)、A. B. C.、ア イ ウ 和 أ ب ج 也是,所以哪个版本都不用自己写。

#3 · 给每种文字配字体

script.js · 第27–32行在完整代码中
const FACES = {
  zh: { text: 'Noto Serif SC', label: 'Noto Sans SC' },
  ja: { text: 'Klee One', label: 'Noto Sans JP' },
  ar: { text: 'Noto Naskh Arabic', label: 'Baloo Bhaijaan 2' },
}[LANG] ?? { text: 'Nunito', label: 'Baloo 2' };
const [TEXT, LABEL] = [FACES.text, FACES.label];

拉丁字母的各版共用 Nunito 和 Baloo 2。中文版正文用宋体,题目要求用黑体;日文版正文用教科书体 Klee One,题目要求用一款黑体(ゴシック);阿拉伯文版用 Noto Naskh Arabic。Klee One 最粗的字重是 600,所以日文版把粗体设成 600(boldFontWeight):如果用 700,浏览器会自己画出加粗效果,页面捕获就会失败。

#4 · 在练习单顶部留出写姓名的横线

script.js · 第91–110行在完整代码中
const at = (to, edge, x = 0, y = 0, size) => ({ anchor: { to, edge },
  offset: { x: mm(x), y: mm(y) }, ...(size && { size }) });
const field = (id, attr, x, width) => [
  { kind: 'text', id, content: `{attr.${attr}}`, fontFamily: LABEL, fontSize: pt(10),
    fontWeight: 700, color: col('muted'), align: 'left', placement: at('container', 'top-left',
      x, 28) },
  { kind: 'rule', id: `${id}-line`, direction: 'horizontal', thickness: pt(0.75),
    color: col('rule'), placement: at('container', 'top-left', x + 16, 33, { width: mm(width) }) },
];
const head = { enabled: true, minHeight: mm(38), slot: { elements: [
  { kind: 'box', id: 'band', style: { backgroundColor: col('accent'), borderRadius: mm(3) },
    placement: at('container', 'top-left', 0, 0, { width: 'fill', height: mm(21) }) },
  { kind: 'text', id: 'kicker', content: '{title} · {subtitle}', fontFamily: LABEL,
    fontSize: pt(8.5), fontWeight: 700, color: col('tint'), align: 'left',
    placement: at('container', 'top-left', 6, 3.5) },
  { kind: 'text', id: 'title', content: '{titleText}', fontFamily: LABEL, fontSize: pt(22),
    fontWeight: 700, color: col('paper'), align: 'left', lineHeight: 1.1,
    placement: at('container', 'top-left', 6, 9.5) },
  ...field('name', 'nombre', 0, 96), ...field('date', 'fecha', 120, 40),
] } };

一级标题画出色带、取自 frontmatter 的课程名和标题,还有两个带横线的栏目,栏目里的字取自标题的属性(nombre="姓名" fecha="日期")。所以德文版写着 Datum,日文版写着“名前”,代码里却没有一个字符串。

完整食谱

沙盒
// ═══ Postext Cookbook · Nº 169 · One worksheet in ten school traditions ═════════
// https://postext.dev/en/cookbook/school-traditions-worksheet
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Nunito, Baloo 2, Klee One, Noto Naskh Arabic and others (SIL OFL) · Needs postext ≥ 1.59.0
import {
  buildDocument, withLoadedFonts, renderPageToCanvas,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'zh'; // @lang: the language of the worksheet (en es ca fr de it pt zh ja ar)
const RECIPE = 'school-traditions-worksheet';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a blue-black ink, one blue accent, a pale tint, grey rules
const palette = {
  ink: '#1d2633', accent: '#2a5d9f', tint: '#e8f0fa', rule: '#a9b6c6', muted: '#5d6878',
  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.accent, model: 'hex' } },
];
// #endregion

// #region faces: Latin editions share two faces; Chinese, Japanese and Arabic their own
const FACES = {
  zh: { text: 'Noto Serif SC', label: 'Noto Sans SC' },
  ja: { text: 'Klee One', label: 'Noto Sans JP' },
  ar: { text: 'Noto Naskh Arabic', label: 'Baloo Bhaijaan 2' },
}[LANG] ?? { text: 'Nunito', label: 'Baloo 2' };
const [TEXT, LABEL] = [FACES.text, FACES.label];
// #endregion

// #region answer: the form of the exercises, as each country's school books set them
// The Markdown of every edition has the same seven exercises. What changes is this table:
// how the number prints, how options are labelled and answered, how true/false reads, where
// a blank is written, the paper of the open answer and the layout of long division.
const disc = { shape: 'disc' };
const square = { shape: 'square' };
const bare = { shape: 'none', suffix: '.' };
const TRADITIONS = {
  es: { number: disc, options: { marker: 'none' }, trueFalse: { look: 'boxes' },
    blank: { look: 'line' }, area: { kind: 'lines' }, arithmetic: { division: 'potence' } },
  fr: { number: square, options: { marker: 'box' }, trueFalse: { look: 'boxes' },
    blank: { look: 'dots' }, area: { kind: 'seyes', spacing: mm(8) },
    arithmetic: { division: 'potence' } },
  de: { number: square, options: { marker: 'box' }, trueFalse: { look: 'boxes' },

组合好的script.js可以直接运行:把它粘贴到任何页面的模块脚本中,或在CodePen上打开这道食谱。 GitHub上的食谱文件夹 ↗ (在新标签页中打开)

变化

#印教师版

每个答案都用品红色印在自己的位置上:括号、方框、空格和商。

   exerciseStyles: [ficha],
+  exercises: { edition: 'teacher' },

#把答案集中到练习单底部

placement: 'after' 把每道题的答案排在题目下面;'inverted' 把答案倒过来印,就像谜题页那样。

+  exercises: { solutions: { placement: 'inverted', layout: 'key' } },

常见问题

  • 字体如果没有 700 的字重,题干编号的粗体就是合成出来的。给这种正文字体设一个 boldFontWeight,用它实际带有的字重。
  • 日文题目要求里的○和中文题目要求里的√只有 CJK 字体里才有:把它们放在 CJK 正文的句子里,不要放进拉丁字母的标签。

致谢

文本
原创文字, CC BY 4.0
字体
Nunito (SIL OFL 1.1) · Baloo 2 (SIL OFL 1.1) · Noto Serif SC (SIL OFL 1.1) · Noto Sans SC (SIL OFL 1.1) · Klee One (SIL OFL 1.1) · Noto Sans JP (SIL OFL 1.1) · Noto Naskh Arabic (SIL OFL 1.1) · Baloo Bhaijaan 2 (SIL OFL 1.1)
沙盒PDF