In short
A page of sums for nine-year-olds in Spanish, English, French and German. Each country writes long division its own way, and the sheet follows it; a switch shows the teacher's copy with every answer filled in.
What you'll build
Unit 3 of a Year 4 calculation book, The four operations, in four editions. A row of mental sums in three columns, then additions, subtractions and multiplications written one digit to a square with a row of squares for the answer, then long division and a word problem on squared paper. The division is where the editions part: the English sheet sets the divisor outside the ⟌ bracket with the quotient on top; the Spanish and French ones put the divisor in an L to the right of the dividend and the quotient under it; the German one writes 864 : 4 = on one line with room for the working below, and its products with a dot. The decimal comma or point of the mental sums follows the language too. A switch above the pages turns the pupil's sheet into the teacher's copy, every answer printed in orange in its square.
This recipe answers
- How do I set long division the way each country's schools write it?
- How do I make a teacher's edition with the answers printed in their places from the pupil's book?
The short answer
// `- 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),
};
Operations in squares, long division as the edition's schools set it
Ingredients
- Features
- Operations in columnsTeacher's editionExercise stylesExercisesGaps and word banksOpen questions and writing areasDesigned openersHeading attributesSemantic colour paletteHyphenation and document languagePages on a canvasPDF exportParagraph styles
- Type
- Fira Sans, Lexend (SIL OFL 1.1)
- Assets
- None: every picture is drawn in code
Method
#1 · Write the sums as lines of the Markdown
// `- 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),
};
An :::exercise{type="arithmetic"} takes one operation per list item, - 864 ÷ 4, in the signs its country writes (÷, :, ×, ·). The engine works out the answer, the quotient and the remainder, and the style decides how the sum is drawn: squares of 7.2 mm, and arithmetic.division picked per edition with t(). A mental-arithmetic row is an ordinary gaps exercise whose list sits in a :::columns{count=3} group, each [[70]] a box as wide as the others.
#2 · Head the sheet
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),
] } };
The unit's level-1 heading draws the orange band, the pill and the title, and the name and date fields from its attributes (nombre="Name" fecha="Date"), so every edition labels them in its own language.
#3 · Switch between the pupil's sheet and the teacher's copy
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' prints every answer in its slot in the colour answers.color gives, here the accent: the boxes of the mental sums, the digits of each result, the quotient and remainder, the model answer of the word problem. The form above the pages edits the config in place and builds again; the fonts are already loaded, so the plain buildDocument is enough. The capture builds both sheets first, so the published pages show the pupil's sheet and then the teacher's.
The whole recipe
// ═══ 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 = 'en'; // @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) => [
<form id="controls" aria-label="Edition">
<label><input type="radio" name="edition" value="student" checked> <span></span></label>
<label><input type="radio" name="edition" value="teacher"> <span></span></label>
</form>
<main id="pages"></main>
/* The edition switch above the desk: two pills in the sheet's orange. */
#controls {
display: flex; justify-content: center; gap: 8px; padding: 14px 16px; background: #16181d;
font: 600 14px/1.2 Lexend, system-ui, sans-serif;
}
#controls label {
display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 18px;
border-radius: 22px; color: #f4f1ea; border: 1px solid #5f6670; cursor: pointer;
}
#controls label:has(input:checked) { background: #c4511c; border-color: #c4511c; }
#controls input { accent-color: #fff; }
The composed script.js runs as it is: paste it into any page’s module script, or open the recipe on CodePen. Recipe folder on GitHub ↗ (opens in a new tab)
Variations
#Put the answers upside down at the foot
A puzzle-book habit: each exercise's key, turned 180° under it.
- exercises: { edition: 'student' }, // the switch above the pages sets 'teacher'
+ exercises: { edition: 'student', solutions: { placement: 'inverted', layout: 'key' } },#Divide under the bracket in every edition
Japanese and Chinese schools also use the bracket; the default for a language you do not list is its own.
- division: t({ es: 'potence', fr: 'potence', de: 'inline', en: 'bracket' }) },
+ division: 'bracket' },Pitfalls
- Operations take whole numbers: a sum with decimals such as
12,5 + 3,75is not read and prints as written. Keep decimals in the mental-arithmetic gaps, where the answer is text.
Credits
- Recipe
- Ignacio Ferro
- Text
- Original prose, CC BY 4.0
- Fonts
- Fira Sans (SIL OFL 1.1) · Lexend (SIL OFL 1.1)
Edit this write-up ↗ (opens in a new tab)Recipe folder on GitHub ↗ (opens in a new tab)


