跳到主要内容
食谱编号170

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

按各国写法排的竖式计算练习单

一张计算练习单:每道算式是 Markdown 里 - 864 ÷ 4 这样的一行,数字一位一格排进方格,除法竖式照各国学校的写法排。

本页内容
输出
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栏
  • Fira Sans 12/18
  • Lexend
  • 4页
  • 难度
  • Postext 1.59.0
  • 排版用时62 ms
  • 118行代码

简单来说

一页给九岁孩子做的计算题,有西班牙文、英文、法文和德文四个版本。各国写除法的方式不同,练习单照各自的写法排;一个开关能换成印好全部答案的教师版。

成品一览

一本四年级计算练习册的第 3 单元《四则运算》,共四个版本。先是一排分成三栏的口算,接着是加法、减法和乘法竖式,一位数字占一格,下面留一行方格写得数,最后是除法和一道印在方格纸上的应用题。各版本的差别在除法上:英文版把除数写在 ⟌ 的外面,商写在上方;西班牙文版和法文版把除数放在被除数右边的直角框里,商写在框下;德文版把 864 : 4 = 写成一行,下面留出写计算步骤的地方,乘号也写成圆点。口算里的小数用逗号还是小数点,同样跟着语言走。页面上方有一个开关,能把学生版换成教师版,每个答案都用橙色印在自己的格子里。

这道食谱解答

  • 怎样按各国学校的写法排长除法竖式?
  • 怎样由学生用书生成答案印在原位的教师用书?

简短回答

script.js · 第26–42行在完整代码中
// `- 864 : 4` in the Markdown is one operation; the style sets it digit by digit in squares
// of 7.2 mm, a cell for each figure of the answer. Long division follows the country: the
// divisor in an L right of the dividend in Spain and France (potence), `864 : 4 =` on one
// line in Germany, the bracket ⟌ in English-speaking schools.
const calculo = {
  id: 'calculo',
  number: { shape: 'disc', background: col('accent'), fontFamily: LABEL, fontWeight: 700 },
  instruction: { bold: true, color: col('ink') },
  grid: { cell: mm(9), fontFamily: TEXT, fontSize: pt(15), rule: col('rule'),
    color: col('ink') },
  arithmetic: { columns: 4,
    division: t({ es: 'potence', fr: 'potence', de: 'inline', en: 'bracket' }) },
  blank: { look: 'box', width: em(3.2), color: col('rule'), weight: pt(0.8) },
  area: { kind: 'grid', spacing: mm(5), color: col('rule'), weight: pt(0.4) },
  answers: { color: col('accent'), fontFamily: LABEL }, // the teacher's copy, in the accent
  marginTop: mm(7), marginBottom: mm(2), itemSpacing: em(0.5),
};

用料

类型
Fira Sans, Lexend(SIL OFL 1.1)
素材
无:所有图片都用代码绘制

做法

#1 · 把算式写成 Markdown 里的一行

script.js · 第26–42行在完整代码中
// `- 864 : 4` in the Markdown is one operation; the style sets it digit by digit in squares
// of 7.2 mm, a cell for each figure of the answer. Long division follows the country: the
// divisor in an L right of the dividend in Spain and France (potence), `864 : 4 =` on one
// line in Germany, the bracket ⟌ in English-speaking schools.
const calculo = {
  id: 'calculo',
  number: { shape: 'disc', background: col('accent'), fontFamily: LABEL, fontWeight: 700 },
  instruction: { bold: true, color: col('ink') },
  grid: { cell: mm(9), fontFamily: TEXT, fontSize: pt(15), rule: col('rule'),
    color: col('ink') },
  arithmetic: { columns: 4,
    division: t({ es: 'potence', fr: 'potence', de: 'inline', en: 'bracket' }) },
  blank: { look: 'box', width: em(3.2), color: col('rule'), weight: pt(0.8) },
  area: { kind: 'grid', spacing: mm(5), color: col('rule'), weight: pt(0.4) },
  answers: { color: col('accent'), fontFamily: LABEL }, // the teacher's copy, in the accent
  marginTop: mm(7), marginBottom: mm(2), itemSpacing: em(0.5),
};

:::exercise{type="arithmetic"} 里每个列表项是一道算式,比如 - 864 ÷ 4,运算符号用该国学校的写法(÷、:、×、·)。得数、商和余数由引擎算出,算式怎么画由样式决定:方格边长 7.2 mm,arithmetic.division 用 t() 按版本选择。口算那一排是普通的填空题,它的列表放在 :::columns{count=3} 组里,每个 [[70]] 都是一个和其他空格一样宽的方框。

#2 · 排练习单的页头

script.js · 第46–67行在完整代码中
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(9.5),
    fontWeight: 600, color: col('muted'), align: 'left',
    placement: at('container', 'top-left', x, 26) },
  { kind: 'rule', id: `${id}-line`, direction: 'horizontal', thickness: pt(0.75),
    color: col('rule'), placement: at('container', 'top-left', x + 17, 31, { width: mm(width) }) },
];
const head = { enabled: true, minHeight: mm(36), slot: { elements: [
  { kind: 'box', id: 'band', style: { backgroundColor: col('accent'), borderRadius: mm(3) },
    placement: at('container', 'top-left', 0, 0, { width: 'fill', height: mm(19) }) },
  { kind: 'text', id: 'unit', content: '{attr.unidad}', fontFamily: LABEL, fontSize: pt(9),
    fontWeight: 700, color: col('accent'), align: 'center', verticalAlign: 'middle',
    textTransform: 'uppercase', letterSpacing: pt(1.2), lineHeight: 1.1,
    box: { backgroundColor: col('paper'), borderRadius: mm(3.5) },
    placement: at('container', 'top-left', 5, 5, { width: mm(26), height: mm(9) }) },
  { kind: 'text', id: 'title', content: '{titleText}', fontFamily: LABEL, fontSize: pt(21),
    fontWeight: 700, color: col('paper'), align: 'left', lineHeight: 1.1,
    placement: at('container', 'top-left', 37, 5.2) },
  ...field('name', 'nombre', 0, 100), ...field('date', 'fecha', 124, 38),
] } };

单元的一级标题画出橙色色带、圆角块和标题,姓名和日期两栏取自标题的属性(nombre="Name" fecha="Date"),所以每个版本都用自己的语言来标注。

#3 · 在学生版和教师版之间切换

script.js · 第180–194行在完整代码中
const form = document.getElementById('controls');
const labels = t({ es: ['Alumno', 'Profesor'], en: ['Pupil', 'Teacher'],
  fr: ['Élève', 'Enseignant'], de: ['Schüler', 'Lehrkraft'] });
form.querySelectorAll('label span').forEach((span, k) => { span.textContent = labels[k]; });
function render() {
  // The same config, edited in place: the next build sees the edition the form asks for.
  config.exercises.edition = form.elements.edition.value;
  const doc = buildDocument({ markdown }, config); // the faces are loaded by now
  showPages(doc, { title });
  document.querySelectorAll('#pt-actions a, [data-postext-pdf]').forEach((old) => old.remove());
  offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }),
    `${RECIPE}-${LANG}-${config.exercises.edition}.pdf`);
}
form.addEventListener('change', render);
render();

exercises.edition: 'teacher' 把每个答案印在它的位置上,颜色取自 answers.color,这里用的是强调色:口算的方框、每道题得数的各位数字、商和余数,以及应用题的参考答案。页面上方的表单直接修改配置并重新排版;字体已经加载,所以用普通的 buildDocument 就够了。截图时会先把两份练习单都排好,所以发布的页面先是学生版,后是教师版。

完整食谱

沙盒
// ═══ Postext Cookbook · Nº 170 · Column arithmetic as each country writes it ═══════
// https://postext.dev/en/cookbook/column-arithmetic-by-country
// Code: MIT · Text: original (CC BY 4.0) · Pictures: none
// Fonts: Fira Sans, Lexend (SIL OFL 1.1) · Needs postext ≥ 1.59.0
import { buildDocumentWithFonts, buildDocument, renderPageToCanvas } from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'es'; // @lang: the language of the worksheet ('es' | 'en' | 'fr' | 'de')
const RECIPE = 'column-arithmetic-by-country';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: ink, one orange accent, a pale tint, the grey of squared paper
const palette = {
  ink: '#1f2328', accent: '#c4511c', tint: '#fbeee6', rule: '#b9c0c8', muted: '#5f6670',
  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
const [TEXT, LABEL] = ['Fira Sans', 'Lexend'];

// #region answer: operations in squares, long division as the edition's schools set it
// `- 864 : 4` in the Markdown is one operation; the style sets it digit by digit in squares
// of 7.2 mm, a cell for each figure of the answer. Long division follows the country: the
// divisor in an L right of the dividend in Spain and France (potence), `864 : 4 =` on one
// line in Germany, the bracket ⟌ in English-speaking schools.
const calculo = {
  id: 'calculo',
  number: { shape: 'disc', background: col('accent'), fontFamily: LABEL, fontWeight: 700 },
  instruction: { bold: true, color: col('ink') },
  grid: { cell: mm(9), fontFamily: TEXT, fontSize: pt(15), rule: col('rule'),
    color: col('ink') },
  arithmetic: { columns: 4,
    division: t({ es: 'potence', fr: 'potence', de: 'inline', en: 'bracket' }) },
  blank: { look: 'box', width: em(3.2), color: col('rule'), weight: pt(0.8) },
  area: { kind: 'grid', spacing: mm(5), color: col('rule'), weight: pt(0.4) },
  answers: { color: col('accent'), fontFamily: LABEL }, // the teacher's copy, in the accent
  marginTop: mm(7), marginBottom: mm(2), itemSpacing: em(0.5),
};
// #endregion

// #region head: the unit pill, the title and the lines for a name and a date
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) => [

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

变化

#把答案倒印在题目下方

这是谜题书的老做法:每道题的答案旋转 180°,印在题目下面。

-  exercises: { edition: 'student' }, // the switch above the pages sets 'teacher'
+  exercises: { edition: 'student', solutions: { placement: 'inverted', layout: 'key' } },

#所有版本都用 ⟌ 写除法

中国和日本的学校也用这种除法竖式;没有列出的语言使用它自己的默认写法。

-    division: t({ es: 'potence', fr: 'potence', de: 'inline', en: 'bracket' }) },
+    division: 'bracket' },

常见问题

  • 算式只接受整数:带小数的算式,比如 12,5 + 3,75,不会被识别,会按原样印出来。小数请留在口算的填空里,那里的答案是文字。

致谢

文本
原创文字, CC BY 4.0
字体
Fira Sans (SIL OFL 1.1) · Lexend (SIL OFL 1.1)
沙盒PDF