简单来说
一篇讲印刷术历史的学校读物。关键术语标成绿色,年代标成琥珀色,人物和地点标成蓝色,第一页的图例说明每种颜色的含义。
成品一览
学校历史读本《Readings in Early Modern History》中某单元的第二篇读物:四页,讲1450年到1550年间印刷术如何从美因茨传遍欧洲,页面 170 × 240 mm。编者像一个认真的学生那样给正文划了重点,而且整本书用同一套颜色:关键术语铺绿色,年代铺琥珀色,人物和地点铺浅蓝色。标题下的图例直接用这三种颜色说明代码,末页的术语表每个词条都以绿色术语开头。高亮只是铺在普通文字下面的色带,所以行的断行、断词和两端对齐都和没有色带时完全一样。
这道食谱解答
- 怎样用各有含义的颜色(术语、日期、人名)给学习材料的关键词划重点?
简短回答
// :mark[Mainz]{style="name"} in the text picks a style by id. The band is painted under
// words that stay ordinary text, so the lines break, hyphenate and justify as if the
// mark were not there, and a mark that runs over a line end gets a band on each line.
const band = (id, name) => ({
id, name,
background: col(id), // pale enough for ink at full contrast on top
color: col('ink'), // the words keep the colour of the text
// borderRadius: 0 is the default (square ends, as a highlighter pen leaves them)
paddingX: em(0.12), // the band reaches a little past the first and the last letter
paddingTop: em(0.04), paddingBottom: em(0.1), // and down past the descenders
});
const markStyles = [
band('term', t({ en: 'Key term', es: 'Término clave' })),
band('date', t({ en: 'Date', es: 'Fecha' })),
band('name', t({ en: 'Name', es: 'Nombre' })),
];
用料
- 类型
- Source Serif 4, Source Sans 3(SIL OFL 1.1)
- 素材
press-1400.jpg- The printing workshop on the opening page, a painting (Generated With Diffusion Models, 原创)
做法
#1 · 每类词一个样式
代码就是上面的简短回答。markStyles 声明三个有名字的样式,正文里每处高亮选一个::mark[Johann Fust]{style="name"}、:mark[1455]{style="date"}、:mark[colophon]{style="term"}(高亮标记)。每个样式表示一种含义(name 是编辑时看到的名字),于是 Markdown 记下的是一个词为什么被标出,而不是它用什么颜色。改一个样式的颜色,全书所有日期都跟着变。两端是方的,postext 1.52 起默认如此(高亮样式):荧光笔画出来的两端是方的,圆角色带看起来像标签。
#2 · 调色板里的三种浅色
const palette = {
ink: '#1f1c19', // text: a warm near-black
accent: '#8a3220', // kickers, rules, numbers: an iron-gall red
tint: '#f5f0e7', // the key's background
rule: '#d3cabc', // hairlines
muted: '#6b645b', // running heads, colophon
paper: '#ffffff',
term: '#cce8c8', // key terms: a pale green
date: '#fbe0a1', // dates: amber
name: '#cfe0f4', // people and places: a pale blue
};
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' } },
];
三条色带和正文墨色、强调色一样是调色板条目,在 Sandbox 里打开这本书的人改一个色样就能换掉整套颜色。它们很浅,亮度接近方框的底色,上面的文字对比度保持在 12.6:1 以上;三种颜色在同一行里相邻时也分得清,比如第3页的“In 1517 Martin Luther”。高亮不占地方:paddingX 向短语旁边的空格伸出 0.12 em,paddingBottom 把色带延到下伸部以下,两者都画在原本就有的空白里。
#3 · 开篇:标题上方的画
const opener = {
enabled: true,
minHeight: mm(122), // the floor of the opener: the key starts on the same line
marginBottom: pt(0),
slot: {
elements: [
{ kind: 'image', id: 'press', resourceId: 'press', fit: 'cover',
placement: { anchor: { to: 'page', edge: 'top-left' },
size: { width: mm(170), height: mm(96) } } },
{ kind: 'text', id: 'kicker', content: '{attr.kicker}', fontFamily: SANS,
fontSize: pt(8.5), fontWeight: 700, letterSpacing: pt(1.6),
textTransform: 'uppercase', color: col('accent'), align: 'left',
placement: { anchor: { to: 'container', edge: 'top-left' },
offset: { x: mm(0), y: mm(82) } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: SERIF,
fontSize: pt(26), lineHeight: 1.08, fontWeight: 700, color: col('ink'), align: 'left',
placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { x: mm(0), y: mm(2) },
size: { width: mm(126), height: 'auto' } } },
{ kind: 'text', id: 'standfirst', content: '{attr.standfirst}', fontFamily: SERIF,
italic: true, fontSize: pt(10.5), lineHeight: LEAD / 10.5, color: col('ink'),
align: 'left',
placement: { anchor: { to: '#title', edge: 'below' }, offset: { x: mm(0), y: mm(3) },
size: { width: mm(118), height: 'auto' } } },
],
},
};
一级标题印的是一个设计。画锚定在页面上,所以从上边和左右两边出血,170 × 96 mm,用 fit: 'cover'。栏目标签和导语来自标题行上的属性(标题属性),图例则是一个普通的 :::callout,列表项分别以三种高亮开头。minHeight 决定开篇的深度:不管标题多长,图例都从同一行开始。
#4 · 按奇偶页排的书眉
const head = (id, content, parity, edge, x, extra = {}) => ({
kind: 'text', id, content, parity, pages: 'body',
fontFamily: SANS, fontSize: pt(7.5), fontWeight: 600, letterSpacing: pt(1.2),
textTransform: 'uppercase', color: col('muted'),
placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(13) } },
...extra,
});
const folio = { fontWeight: 700, color: col('ink') };
const header = {
elements: [
head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
head('verso-title', '{title}', 'even', 'top-left', OUTER + 9),
head('recto-title', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 9)),
head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
],
};
const footer = {
elements: [head('drop-folio', '{pageNumber}', 'all', 'top', 0, {
...folio, pages: 'opener',
placement: { anchor: { to: 'container', edge: 'top' }, offset: { x: mm(0), y: mm(9) } },
})],
};
左页印丛书名,右页印本篇标题,两者都用 parity 筛选,并用 pages: 'body' 避开开篇页(文本元素)。开篇页的页码放在页脚。
完整食谱
// ═══ Postext Cookbook · Nº 180 · Colour-coded highlights in a study reader ════════
// https://postext.dev/en/cookbook/colour-coded-study-highlights
// Code: MIT · Text: original (CC BY 4.0) · Picture: painting made for the recipe
// Fonts: Source Serif 4, Source Sans 3 (SIL OFL 1.1) · Needs postext ≥ 1.65.0
import {
buildDocumentWithFonts, renderPageToCanvas, registerResourceImage, defaultResourceTypes,
} from 'https://esm.sh/postext';
const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'colour-coded-study-highlights';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: semantic colours, three of them for the highlighter
const palette = {
ink: '#1f1c19', // text: a warm near-black
accent: '#8a3220', // kickers, rules, numbers: an iron-gall red
tint: '#f5f0e7', // the key's background
rule: '#d3cabc', // hairlines
muted: '#6b645b', // running heads, colophon
paper: '#ffffff',
term: '#cce8c8', // key terms: a pale green
date: '#fbe0a1', // dates: amber
name: '#cfe0f4', // people and places: a pale blue
};
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 OUTER = 20; // outer margin in mm: the running heads align to it
const LEAD = 14.5; // body leading in pt
const SANS = 'Source Sans 3';
const SERIF = 'Source Serif 4';
// #region answer: three highlight styles, one per kind of word, linked to the palette
// :mark[Mainz]{style="name"} in the text picks a style by id. The band is painted under
// words that stay ordinary text, so the lines break, hyphenate and justify as if the
// mark were not there, and a mark that runs over a line end gets a band on each line.
const band = (id, name) => ({
id, name,
background: col(id), // pale enough for ink at full contrast on top
color: col('ink'), // the words keep the colour of the text
// borderRadius: 0 is the default (square ends, as a highlighter pen leaves them)
paddingX: em(0.12), // the band reaches a little past the first and the last letter
paddingTop: em(0.04), paddingBottom: em(0.1), // and down past the descenders
});
const markStyles = [
组合好的script.js可以直接运行:把它粘贴到任何页面的模块脚本中,或在CodePen上打开这道食谱。 GitHub上的食谱文件夹 ↗ (在新标签页中打开)
变化
#让色带两端变圆
圆角取字号的五分之一,就像记号笔画出的柔和两端。
- // borderRadius: 0 is the default (square ends, as a highlighter pen leaves them)
+ borderRadius: em(0.2), // soft ends, as a felt-tip pen leaves them#每行一条完整色带
设置 decorationBreak: 'clone' 后,跨行的高亮在每一段的两端都有留白,比如第3页的“pamphlets”在“pam-”后断开的地方。
paddingX: em(0.12), // the band reaches a little past the first and the last letter
+ decorationBreak: 'clone', // each line piece gets a whole band常见问题
- 只靠颜色,黑白复印后就看不出来了,而很多读物正是这样复印后发到课堂上的。术语表另外把术语设为粗体;如果读者会复印这些页面,就给三类词中的一类再加一个自己的标志。
markStyles里没有声明的样式 id 会退回第一个样式,页面上不会报错(Sandbox 会提示)。id 要短,并检查正文里的拼写。
致谢
- 文本
- The reading, its key, the review questions and the colophon · Ignacio Ferro · CC BY 4.0
- 图片
- The printing workshop on the opening page, a painting · Generated With Diffusion Models · 原创
- 字体
- Source Serif 4 (SIL OFL 1.1) · Source Sans 3 (SIL OFL 1.1)


