跳到主要内容
食谱编号172

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

答案倒印在题下的日本练习册

一套日本练习册中的两页,算术横排,汉字竖排,每道题后面跟着倒印的答案。

本页内容
输出
Canvas · PDF
难度
中级
Postext
已用Postext 1.59.0测试
需要≥ 1.59.0 · postext-pdf ≥ 1.59.0
许可证
更新于2026年10月24日
代码MIT · 文本CC BY 4.0
  • 外文样例:尚无中文版本
  • 成品尺寸182 × 257 mm
  • 1栏
  • Klee One 12/22
  • Zen Maru Gothic
  • 4页
  • 难度
  • Postext 1.59.0
  • 排版用时105 ms
  • 137行代码

简单来说

一本日本小学练习册:一页是竖式、填空、选择和判断题,另一页是从上往下排的汉字和读音。每道题下面的答案都倒着印,孩子把本子转过来就能对答案。

成品一览

一套三年级练习册中的两页,B5 开本。算术页横排:一条浅蓝色带,上面有科目标签、标题和吉祥物狸猫,下面是写名字的横线和分数框。接着是ひっ算(竖式),结果的每一位都有一个方框;六道算式后面各有一个方框;ア イ ウ三个选项排成一行;○×判断题在行末留一个括号;两道应用题留着写算式的横线。汉字页按国語练习册的印法排,从上到下,从右到左:读音旁边有写汉字的方框,划了线的词在括号里写读音,还有要圈出的选项和要挑的词义。每道题下面,答案用灰色小字倒着印。孩子看得到答案在哪里,一眼却读不出来,要把本子转过来才能对答案。

这道食谱解答

  • 怎样把答案倒印在题目下方?
  • 怎样用与横排页相同的设计排一页竖排的日语练习?
  • 怎样按各国学校的写法排长除法竖式?

简短回答

script.js · 第35–55行在完整代码中
// `placement: 'inverted'` sets each exercise's key right after it in a box turned 180°: the
// pupil sees where the answers are without reading them, and turns the book to check. The
// key's words come from the exercise (the options' katakana, ○ and ×, the operations), so
// the Markdown holds nothing twice.
const drill = {
  id: 'drill',
  number: { shape: 'square', fontFamily: TEXT, fontWeight: 600, background: col('blue') },
  instruction: { fontFamily: ROUND, bold: false },
  options: { layout: 'row' }, // ア イ ウ side by side (the default labels in Japanese)
  trueFalse: { look: 'bracket' }, // ( ) for ○ or ×
  blank: { look: 'box', width: 'answer', color: col('rule') }, // □ to write in
  area: { kind: 'lines', color: col('rule') },
  grid: { rule: col('rule') },
  solutions: { placement: 'inverted', box: 'kotae' },
  marginTop: em(1.1), marginBottom: em(0.4),
};
const kotae = { id: 'kotae', title: '', backgroundEnabled: false, border: { enabled: false },
  padding: { top: pt(0), right: pt(0), bottom: pt(0), left: pt(0) },
  body: { fontFamily: ROUND, fontSize: pt(8), lineHeight: pt(12), color: col('muted'),
    firstLineIndent: pt(0) },
  marginTop: mm(1), marginBottom: mm(2), keepTogether: true };

用料

类型
Klee One, Zen Maru Gothic(SIL OFL 1.1)
素材
  • kitsune-600.jpg
  • tanuki-600.jpg
  • A young tanuki in a yellow school hat holding a big pencil. (Generated With Diffusion Models, 原创)
  • A fox cub in a red scarf reading a book. (Generated With Diffusion Models, 原创)

做法

#1 · 把每道题的答案倒过来

代码就在上面的简短回答里。placement: 'inverted' 把答案放在每道题后面一个旋转 180° 的框里,这个框又放在题目自己的框里,所以分页永远不会把两者分开。答案从题目里来:算式连同结果、正确选项的片假名、○ 和 ×、填空的词。答案的框样式 kotae 只把字设成小号灰色。

#2 · 一条色带,横排页和竖排页都用

script.js · 第59–76行在完整代码中
// A heading design on a vertical page is laid along its column: x runs down the page and y
// to the left, so the same band stands at the right edge, from head to foot.
const BAND = 30; // mm: the band's depth (its height across, its width down)
const header = { enabled: true, minHeight: mm(BAND + 3), slot: { elements: [
  { kind: 'box', id: 'band', reserve: false, style: { backgroundColor: col('tint'),
    borderRadius: mm(4) }, placement: { anchor: { to: 'container', edge: 'top-left' },
    size: { width: 'fill', height: mm(BAND) } } },
  { kind: 'image', id: 'mascot', resourceId: '{attr.pic}', decorative: true, reserve: false,
    placement: { anchor: { to: 'container', edge: 'top-right' }, offset: { x: mm(-4), y: mm(1) },
      size: { width: mm(28), height: mm(28) } } },
  { kind: 'text', id: 'subject', content: '{attr.kicker}', fontFamily: ROUND, fontSize: pt(10),
    fontWeight: 700, color: col('ink'), box: { backgroundColor: col('sun'), borderRadius: mm(3),
      padding: { top: mm(0.6), bottom: mm(0.6), left: mm(2.4), right: mm(2.4) } },
    placement: { anchor: { to: 'container', edge: 'top-left' }, offset: { x: mm(5), y: mm(5) } } },
  { kind: 'text', id: 'title', content: '{titleText}', fontFamily: ROUND, fontSize: pt(20),
    fontWeight: 700, color: col('ink'),
    placement: { anchor: { to: '#subject', edge: 'below' }, offset: { x: mm(0), y: mm(2.5) } } },
] } };

在竖排页上,标题的设计沿着所在的栏铺开:x 沿页面往下,y 向左。以 top-left 锚定、占满整个宽度的色带,在算术页上横在顶部,在汉字页上沿右边缘竖下来;以 top-right 锚定的吉祥物在两页上都落在色带的末端。设计里没有一处写明方向。

#3 · 一份文字,两本练习册

script.js · 第271–279行在完整代码中
// Two drill books of one series: arithmetic across, bound on the left; kanji in columns,
// bound on the right (vertical text binds there by itself).
const [sansu, kokugo] = markdown.split(/(?=^# 春のことば)/m);
const build = (text) => withLoadedFonts(() => buildDocument({ markdown: text, resources },
  config()), { ...kitFonts(FONTS), text: markdown });
const across = await build(sansu);
// 国語 is set in columns, top to bottom, right to left: the same design, turned.
writingMode = 'vertical-rl';
const docs = [across, await build(kokugo)];

配置是一个函数,所以每次排版都得到自己的对象,两次之间只改 writingMode。竖排时,国語练习册自己就会在右边装订;showBook 把两本练习册分别放在书脊两侧。正文用教科书体 Klee One,标签用 Zen Maru Gothic;两种字体都加载了竖排标点,给汉字页的括号用。

完整食谱

沙盒
// ═══ Postext Cookbook · Nº 172 · A Japanese drill with the answers upside down ═══
// https://postext.dev/en/cookbook/japanese-drill-upside-down-answers
// Code: MIT · Text: original (CC BY 4.0) · Pictures: generated with diffusion models
// Fonts: Klee One, Zen Maru Gothic (SIL OFL 1.1) · Needs postext ≥ 1.59.0
import {
  buildDocument, withLoadedFonts, renderPageToCanvas, registerResourceImage,
  loadVerticalAlternates,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';

const LANG = 'ja'; // @lang: the language of the frame; the drill is Japanese in all
const RECIPE = 'japanese-drill-upside-down-answers';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a drill book's blue, a warm yellow and a pale sky for the band
const palette = {
  ink: '#23272c', // text
  blue: '#2a6cb0', // the accent: exercise numbers, the title band's rule, the score box
  sun: '#f2c14e', // the subject badge
  tint: '#eaf2fb', // the title band
  rule: '#a8bfd8', // writing lines and boxes
  muted: '#69717b', // folios, the name line, the upside-down answers
  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.blue, model: 'hex' } },
];
// #endregion
// 教科書体 for the text a child copies from, a round gothic for the labels.
const [TEXT, ROUND] = ['Klee One', 'Zen Maru Gothic'];

// #region answer: the answers printed upside down under each exercise
// `placement: 'inverted'` sets each exercise's key right after it in a box turned 180°: the
// pupil sees where the answers are without reading them, and turns the book to check. The
// key's words come from the exercise (the options' katakana, ○ and ×, the operations), so
// the Markdown holds nothing twice.
const drill = {
  id: 'drill',
  number: { shape: 'square', fontFamily: TEXT, fontWeight: 600, background: col('blue') },
  instruction: { fontFamily: ROUND, bold: false },
  options: { layout: 'row' }, // ア イ ウ side by side (the default labels in Japanese)
  trueFalse: { look: 'bracket' }, // ( ) for ○ or ×
  blank: { look: 'box', width: 'answer', color: col('rule') }, // □ to write in
  area: { kind: 'lines', color: col('rule') },
  grid: { rule: col('rule') },
  solutions: { placement: 'inverted', box: 'kotae' },

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

变化

#把答案集中到最后

另附答案册的练习册,不把答案印在题目页上:chapterEnd 把答案列在最后一道题之后。

-  solutions: { placement: 'inverted', box: 'kotae' },
+  solutions: { placement: 'chapterEnd', box: 'kotae' },

#排一页谜题

谜题页也用同样的方法把答案倒过来,放在两栏的卡片上。

常见问题

易错点

中文字体通过cjk块分片加载

Fontsource把一个中文、日文或韩文字体家族按每个字重约一百个文件提供,每个文件覆盖一段字符范围。loadFonts只获取latin文件,所以屏幕上的汉字来自系统字体,测量不准;fontsourceProvider交给PDF的也是这个latin文件,汉字印出来是空框。列出kit中的cjk块,在loadFonts之后调用loadCjkFonts(FONTS, markdown)(书中用到几种CJK字体时,每种字体调用一次,并传入它所排的文字),并给renderToPdf传fontProvider: cjkPdfProvider:两者都会取用包含文中字符的那些文件。 中日韩字体 →

易错点

日文文本标记为'ja',不要用zh-Hans或LANG

食谱的版本是en和es,但日文示例在两个版本里都是日文:`locale: LANG`会把它标记为英文或西班牙文,中文标记则会按中文规则排它(开明式标点、小假名可以出现在行首、图代替図、PDF中用中文字形)。postext 1.30起,这个标记也是文字成形所用的语言(测量、绘制和嵌入都按它进行),所以'ja'以外的标记给出的不是日文字形(中文标记给出中文字形),断行的位置也不同。写'ja':它选用日本地区(JLReq断行和标点、芝麻着重号、振假名的分布、図和表标签),并关闭断词。lint会让带假名却标记为zh或ko的文本失败。 日文禁则处理 →

  • 读音暂时还不能放在空格上方:[[…]] 里的注音(ruby)属于被隐藏的答案。漢字の書き取り这道题把每个读音单独放一行,方框跟在后面,用 :::columns 组每行排三个;在竖排页上,读音像振假名那样立在方框旁边。
  • Klee One 只有 400 和 600 两种字重:boldFontWeight: 600 让所有粗体都落在实际存在的字重上,小题号和大题号也一样。

致谢

文本
原创文字, CC BY 4.0
图片
  • A young tanuki in a yellow school hat holding a big pencil. · Generated With Diffusion Models · 原创
  • A fox cub in a red scarf reading a book. · Generated With Diffusion Models · 原创
字体
Klee One (SIL OFL 1.1) · Zen Maru Gothic (SIL OFL 1.1)
沙盒