# تظليل مرمَّز بالألوان في كتاب قراءات دراسي

> كتاب قراءات في التاريخ تحمل كلماته المفتاحية ثلاثة ألوان تظليل: الأخضر للمصطلحات والكهرماني للتواريخ والأزرق للأسماء، من ثلاثة أنماط في markStyles.

- نسخة HTML: https://postext.dev/ar/cookbook/colour-coded-study-highlights
- وصفة رقم 180 · الحروف والنص · المستوى 2 (متوسط) · المخرجات: Canvas
- الأنواع: الكتب المدرسية
- تتطلب postext ≥ 1.65.0 · اختُبرت مع 1.65.0 بتاريخ 2026-10-31
- الصفحات: [1](https://postext.dev/cookbook/colour-coded-study-highlights/en/p01.webp?v=d1c18f3b), [2](https://postext.dev/cookbook/colour-coded-study-highlights/en/p02.webp?v=d1c18f3b), [3](https://postext.dev/cookbook/colour-coded-study-highlights/en/p03.webp?v=d1c18f3b), [4](https://postext.dev/cookbook/colour-coded-study-highlights/en/p04.webp?v=d1c18f3b)
- افتح في Sandbox: https://postext.dev/ar/sandbox#recipe=colour-coded-study-highlights&lang=en (.postext: https://postext.dev/cookbook/colour-coded-study-highlights/en/colour-coded-study-highlights.postext)
- آخر تحديث: 2026-10-31
- لغات أخرى: [en](https://postext.dev/en/cookbook/colour-coded-study-highlights.md), [es](https://postext.dev/es/cookbook/colour-coded-study-highlights.md), [ca](https://postext.dev/ca/cookbook/colour-coded-study-highlights.md), [de](https://postext.dev/de/cookbook/colour-coded-study-highlights.md), [fr](https://postext.dev/fr/cookbook/colour-coded-study-highlights.md), [it](https://postext.dev/it/cookbook/colour-coded-study-highlights.md), [pt](https://postext.dev/pt/cookbook/colour-coded-study-highlights.md), [zh](https://postext.dev/zh/cookbook/colour-coded-study-highlights.md), [ja](https://postext.dev/ja/cookbook/colour-coded-study-highlights.md)

## باختصار

قراءة مدرسية عن تاريخ الطباعة. تُظلَّل المصطلحات بالأخضر والتواريخ بالكهرماني والأشخاص والأماكن بالأزرق، ويشرح مفتاح في الصفحة الأولى معنى كل لون.

## ما الذي ستنضده

القراءة الثانية من وحدة في كتاب مدرسي للتاريخ، *Readings in Early Modern History*: أربع صفحات عن انتشار الطباعة من ماينتس في أنحاء أوروبا بين 1450 و1550، على صفحة قياسها 170 × 240 مم. علّم المحرّرون النص كما يفعل طالب مجتهد، برمز لا يتغيّر في الكتاب كله: المصطلحات المفتاحية على شريط أخضر، والتواريخ على الكهرماني، والأشخاص والأماكن على الأزرق الفاتح. ويشرح مفتاح تحت العنوان هذا الرمز بالألوان الثلاثة نفسها، ويبدأ المسرد في الصفحة الأخيرة كل مدخل بمصطلحه الأخضر. العلامات أشرطة تحت نص عادي، فتنكسر الأسطر وتُقسَّم الكلمات وتُضبط الأطراف كأن الأشرطة غير موجودة.

**تجيب هذه الوصفة عن:**

- كيف أظلّل نصًّا دراسيًّا بألوان لكل منها معنى: المصطلحات والتواريخ والأسماء؟

## الجواب المختصر

```js
// script.js, سطرًا 37–52
// :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' })),
];
```

## المكونات

**تعلّم**

- [علامات التظليل](https://postext.dev/ar/docs/document-format-inline.md#علامات-التظليل): شريط ملوّن خلف العبارة كما يظلّل الكتاب المدرسي كلماته المفتاحية: تبقى الكلمات نصًّا عاديًّا وتنتقل بين الأسطر، ويتبع الشريط كل قطعة منها.

**تستخدم أيضًا**

- [إطارات التنبيه](https://postext.dev/ar/docs/configuration-callouts.md#أنماط-الإطارات)
- [لوحة ألوان دلالية](https://postext.dev/ar/docs/configuration-fonts-colors-viewers.md#لوحة-الألوان)
- [صفحات افتتاح مصمَّمة](https://postext.dev/ar/docs/configuration-headings.md#الامتداد-والتصميم-المتقدم)
- [الصور في تصاميم الصفحة](https://postext.dev/ar/docs/configuration-headers-footers.md#عناصر-الصورة)
- [سمات العنوان](https://postext.dev/ar/docs/document-format.md#سمات-العناوين)
- [الترويسات وأرقام الصفحات](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات)
- [الترويسات بحسب دور الصفحة](https://postext.dev/ar/docs/configuration-headers-footers.md#عناصر-النص)
- [هوامش متناظرة](https://postext.dev/ar/docs/configuration-page-layout.md#الهوامش-المتناظرة)
- [أنماط الفقرات](https://postext.dev/ar/docs/configuration-styles.md#أنماط-الفقرات)
- [الأسطر الأرامل والأسطر اليتيمة والكلمات المعزولة](https://postext.dev/ar/docs/configuration-page-flow.md#الأسطر-اليتيمة-والأرامل-والكلمات-المعزولة-وقواعد-الإبقاء-معًا)
- [قوائم المراجع ومسارد المصطلحات](https://postext.dev/ar/docs/configuration-styles.md#أنماط-الفقرات)
- [«شكل» و«جدول» بلغتك](https://postext.dev/ar/docs/configuration-resources.md#أنواع-الموارد)
- [أنواع موارد مخصّصة](https://postext.dev/ar/docs/configuration-resources.md#أنواع-الموارد)
- [الأشكال والجداول بوصفها موارد](https://postext.dev/ar/docs/document-format-resources.md#الموارد)

**الإعدادات في لمحة**

- [`bodyText`](https://postext.dev/ar/docs/configuration-text.md#نص-المتن), [`calloutStyles`](https://postext.dev/ar/docs/configuration-callouts.md#أنماط-الإطارات), [`captionStyle`](https://postext.dev/ar/docs/configuration-resources.md#نمط-التعليقات), [`colorPalette`](https://postext.dev/ar/docs/configuration-fonts-colors-viewers.md#لوحة-الألوان), [`footer`](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات), [`header`](https://postext.dev/ar/docs/configuration-headers-footers.md#الترويسات-والتذييلات), [`headings`](https://postext.dev/ar/docs/configuration-headings.md#العناوين), [`layout`](https://postext.dev/ar/docs/configuration-page-layout.md#التخطيط), [`locale`](https://postext.dev/ar/docs/configuration-language.md#تقسيم-الكلمات-بالواصلة), [`markStyles`](https://postext.dev/ar/docs/configuration-styles.md#أنماط-التظليل), [`orderedLists`](https://postext.dev/ar/docs/configuration-lists-math.md#القوائم-المرقّمة), [`page`](https://postext.dev/ar/docs/configuration-page-layout.md#الصفحة), [`paragraphStyles`](https://postext.dev/ar/docs/configuration-styles.md#أنماط-الفقرات), [`resourceTypes`](https://postext.dev/ar/docs/configuration-resources.md#أنواع-الموارد), [`unorderedLists`](https://postext.dev/ar/docs/configuration-lists-math.md#القوائم-غير-المرقّمة)

**واجهات API**

- [`buildDocumentWithFonts`](https://postext.dev/ar/docs/configuration-programmatic-usage.md#تحميل-الخطوط-قبل-الإخراج), [`defaultResourceTypes`](https://postext.dev/ar/docs/configuration-resources.md#أنواع-الموارد), [`registerResourceImage`](https://postext.dev/ar/docs/architecture.md#واجهة-البرمجة-المتاحة-api), [`renderPageToCanvas`](https://postext.dev/ar/docs/configuration-programmatic-usage.md#رسم-صفحة-في-صورة-نقطية)

**الخطوط**

- Source Serif 4 (OFL-1.1), Source Sans 3 (OFL-1.1)

## طريقة التحضير

### 1 · نمط لكل نوع من الكلمات

الشيفرة في [الجواب المختصر](https://postext.dev/ar/cookbook/colour-coded-study-highlights.md#الجواب-المختصر) أعلاه. تعلن `markStyles` ثلاثة أنماط مسمّاة، ويختار النص واحدًا منها لكل علامة: `:mark[Johann Fust]{style="name"}` و`:mark[1455]{style="date"}` و`:mark[colophon]{style="term"}` ([علامات التظليل](https://postext.dev/ar/docs/document-format-inline.md#علامات-التظليل)). كل نمط يدلّ على معنى (`name` هو ما يراه المحرّر)، فيحفظ Markdown سبب تعليم الكلمة لا لونها. غيّر لون نمط تتغيّر معه كل تواريخ الكتاب. أطراف الأشرطة مربّعة، وهذا هو الافتراضي منذ postext 1.52 ([أنماط التظليل](https://postext.dev/ar/docs/configuration-styles.md#أنماط-التظليل)): قلم التظليل يترك أطرافًا مربّعة، والشريط المستدير الطرفين يُقرأ كأنه شارة.

### 2 · ثلاث درجات فاتحة من اللوحة

```js
// script.js, سطرًا 14–29
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 أو أكثر، وتبقى الألوان الثلاثة متمايزة حين تتجاور في سطر واحد، كما في «In 1517 Martin Luther» في الصفحة 3. العلامة لا تأخذ مكانًا: يمتد `paddingX` مسافة 0.12 em في الفراغ بجوار العبارة، وينزل `paddingBottom` بالشريط تحت الأذيال السفلية، وكلاهما يُرسم في فراغ كان موجودًا.

### 3 · الافتتاح: لوحة فوق العنوان

```js
// script.js, سطرًا 56–81
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 مم، مع `fit: 'cover'`. ويأتي العنوان الفرعي والمقدمة من سمات في سطر العنوان ([سمات العناوين](https://postext.dev/ar/docs/document-format.md#سمات-العناوين))، ثم يليهما المفتاح في `:::callout` عادي تبدأ بنود قائمته بالعلامات الثلاث. ويحدّد `minHeight` عمق الافتتاح: يبدأ المفتاح في السطر نفسه مهما طال العنوان.

### 4 · ترويسات بحسب زوجية الصفحة

```js
// script.js, سطرًا 85–106
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'` ([عناصر النص](https://postext.dev/ar/docs/configuration-headers-footers.md#عناصر-النص)). وتحمل صفحة الافتتاح رقمها في أسفلها.

## الوصفة كاملة

ملف واحد، مركّب من مجلد الوصفة ومعه نص المثال وأدوات دليل الوصفات المشتركة مضمّنة؛ وهو ينشئ صفحته بنفسه. لتشغيله، ضعه داخل `<script type="module">` في صفحة فارغة أو الصقه في لوحة JS في مثال CodePen جديد (وحدةً). يستورد postext من esm.sh، فلا حاجة إلى تثبيت أو بناء.

- مجلد الوصفة: https://github.com/drnachio/postext/tree/main/cookbook/colour-coded-study-highlights

### script.js

```js
// ═══ 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 = [
  band('term', t({ en: 'Key term', es: 'Término clave' })),
  band('date', t({ en: 'Date', es: 'Fecha' })),
  band('name', t({ en: 'Name', es: 'Nombre' })),
];
// #endregion

// #region opener: the painting bleeds off the top of the page, the title sits under it
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' } } },
    ],
  },
};
// #endregion

// #region running-heads: series title on the verso, reading title on the recto
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) } },
  })],
};
// #endregion

const config = {
  locale: t({ en: 'en-us', es: 'es' }),
  resourceTypes: defaultResourceTypes(LANG),
  colorPalette,
  markStyles,
  page: {
    sizePreset: 'custom', width: mm(170), height: mm(240), dpi: 150,
    margins: { top: mm(22), bottom: mm(22), left: mm(22), right: mm(OUTER), mirror: true },
  },
  layout: { layoutType: 'single' },
  bodyText: {
    fontFamily: SERIF, fontSize: pt(10.5), lineHeight: pt(LEAD), color: col('ink'),
    boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true,
  },
  headings: {
    fontFamily: SERIF, color: col('ink'), fontWeight: 700,
    levels: [
      { level: 1, fontSize: pt(26), breakBefore: { parity: 'odd' }, advancedDesign: opener },
      { level: 2, fontFamily: SANS, fontSize: pt(12), lineHeight: pt(LEAD), fontWeight: 700,
        color: col('accent'), marginTop: pt(LEAD), marginBottom: pt(0) },
    ],
  },
  unorderedLists: { bulletChar: '–', color: col('accent'), fontWeight: 400,
    marginTop: pt(0), marginBottom: pt(0) },
  orderedLists: { numberFormat: 'arabic', color: col('accent'), fontWeight: 700,
    marginTop: pt(0), marginBottom: pt(0) },
  paragraphStyles: [
    // The glossary: each green term opens its entry, the turnovers hang 1.5 em.
    { id: 'glossary', fontSize: pt(9.5), lineHeight: pt(13), textAlign: 'left',
      hangingIndent: em(1.5) },
    { id: 'colophon', fontFamily: SANS, fontSize: pt(7.5), lineHeight: pt(10),
      color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD) },
  ],
  // The key: a tint with a top stripe, its title in the label face.
  calloutStyles: [
    { id: 'key', title: t({ en: 'Key', es: 'Clave' }), background: col('tint'),
      stripe: { enabled: true, side: 'top', width: pt(2.5), color: col('accent') },
      padding: { top: mm(3), right: mm(4), bottom: mm(3), left: mm(4) },
      titleStyle: { fontFamily: SANS, fontSize: pt(8), fontWeight: 700,
        textTransform: 'uppercase', letterSpacing: pt(1.2), color: col('accent') },
      body: { fontFamily: SANS, fontSize: pt(9.5), lineHeight: pt(13), color: col('ink'),
        firstLineIndent: pt(0), textAlign: 'left', boldColor: col('ink'),
        italicColor: col('ink') } },
  ],
  captionStyle: { fontFamily: SANS, fontSize: pt(8), color: col('ink'), labelBold: true,
    labelColor: col('accent'), note: { color: col('muted') } },
  header,
  footer,
};

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = String.raw`---
title: "Readings in Early Modern History"
author: "Study edition"
---

# How Printing Spread Across Europe {kicker="Unit 4 · Reading 2" standfirst="In a hundred years a craft from one German town filled Europe with books. This reading follows the press from Mainz to more than two hundred towns and asks what changed when books became cheap."}

:::callout{type="key" title="Reading the highlights"}
The editors have marked three kinds of words in this reading. Use the same colours in your own notes.

- :mark[Key term]{style="term"}: a word the glossary at the end of the unit defines.
- :mark[Date]{style="date"}: a year or a period to place on your timeline.
- :mark[Name]{style="name"}: a person or a place in the story.
:::

## A workshop in Mainz

Around :mark[1450]{style="date"} a goldsmith named :mark[Johannes Gutenberg]{style="name"} was running a printing workshop in :mark[Mainz]{style="name"}, a city on the Rhine. Printing itself was not new. Printers in China had cut whole pages into wooden blocks for centuries, and Korean printers had cast metal type by the :mark[thirteenth century]{style="date"}. What Gutenberg put together was a system that suited the twenty-odd letters of the Latin alphabet.

The system had four parts. The first was :mark[movable type]{style="term"}: single letters cast in metal, set in lines, printed and then put back in the case for the next page. A hand mould cast thousands of identical letters from an alloy of lead, tin and antimony. An ink made with linseed oil clung to the metal where a watery ink would have run off. And a :mark[screw press]{style="term"}, of the kind used to press grapes and to flatten wet paper, pushed the paper evenly onto the inked type.

The first large book from the workshop was a Latin Bible in two volumes, set in two columns of 42 lines, finished by :mark[1455]{style="date"}. About 180 copies were printed, most on paper and some on vellum. A single scribe could spend years copying one Bible; the workshop produced its whole edition in about the same time.

Gutenberg had borrowed heavily to build all this. His lender, the Mainz merchant :mark[Johann Fust]{style="name"}, took him to court in :mark[1455]{style="date"} and kept much of the equipment. Fust carried on the business with :mark[Peter Schöffer]{style="name"}, who had trained in the workshop. Their Psalter of :mark[1457]{style="date"} is the first printed book whose :mark[colophon]{style="term"}, the closing note of a book, names its printers and the day it was finished.

## The craft travels

For about ten years printing stayed close to Mainz. In :mark[1462]{style="date"} the city was sacked in a war between two rival archbishops, and many of its craftsmen left. Printers trained on the Rhine set up wherever they found readers, paper and money: at :mark[Subiaco]{style="name"}, near Rome, in :mark[1465]{style="date"}; in :mark[Venice]{style="name"} in :mark[1469]{style="date"}; in :mark[Paris]{style="name"}, in rooms at the Sorbonne, in :mark[1470]{style="date"}. A press was at work in :mark[Segovia]{style="name"} around :mark[1472]{style="date"}, and :mark[Valencia]{style="name"} had one by :mark[1474]{style="date"}.

:mark[William Caxton]{style="name"}, an English merchant who had learned the craft in :mark[Cologne]{style="name"}, set up his press at :mark[Westminster]{style="name"} in :mark[1476]{style="date"}. Most printers of the time worked in Latin, the language of the Church and the universities, and sold across borders. Caxton printed mostly in the :mark[vernacular]{style="term"}, the language people spoke at home: romances, histories and Chaucer's *Canterbury Tales*, for readers who had no Latin.

Books printed before :mark[1501]{style="date"} are called :mark[incunabula]{style="term"}, from the Latin word for a cradle: the books of printing's infancy. Catalogues today list about 30,000 editions from those fifty years. By :mark[1500]{style="date"} presses had worked in more than 250 towns, and estimates of the copies they printed run from eight to twenty million.

## Venice and the pocket book

By the end of the century :mark[Venice]{style="name"} had more printing houses than any other city in Europe. It had paper mills on the mainland, ships to carry books to every port of the Mediterranean, and buyers with money. :mark[Aldus Manutius]{style="name"} opened his press there in the :mark[mid-1490s]{style="date"} and printed the Greek classics in editions prepared by scholars. His books carried a printer's mark, a dolphin wrapped round an anchor, so that buyers could tell them from the copies other printers soon made.

In :mark[1501]{style="date"} Aldus issued a Virgil in :mark[octavo]{style="term"}, a sheet folded three times to make sixteen pages, so that the book fitted in a hand. It was set in a slanting letter cut by :mark[Francesco Griffo]{style="name"}, which we now call :mark[italic]{style="term"}. The narrow letters fitted more words on each line, so the small books needed less paper and cost less. Readers could carry a poet in a pocket instead of reading him at a desk.

## Print and the Reformation

In :mark[1517]{style="date"} :mark[Martin Luther]{style="name"}, who taught theology at :mark[Wittenberg]{style="name"}, wrote ninety-five theses against the sale of indulgences. Printers in several German cities reprinted them within weeks. Luther went on to write short :mark[pamphlets]{style="term"} in German: a few pages each, quick to set, cheap to buy and easy to pass from hand to hand. Historians estimate that between :mark[1518]{style="date"} and :mark[1525]{style="date"} his writings made up about a third of all the books printed in German.

His opponents answered in print too, and the quarrel over the Church was argued in thousands of pamphlets read aloud in taverns and market squares. Printing did not cause the Reformation, but it carried each side's arguments further and faster than any preacher could.

## Check your reading

1. Name the four parts of Gutenberg's system, and say what each one did.
2. Why did the sack of Mainz in 1462 help printing to spread?
3. What is a colophon, and why would a historian be glad to find one?
4. Caxton chose the vernacular. Who were his readers, and why did that choice sell books?
5. Put the amber dates of this reading on a timeline from 1450 to 1525.

## Glossary

:::paragraphs{style="glossary"}
**:mark[colophon]{style="term"}** A note at the end of a book that gives the printer, the place and the date of printing.

**:mark[incunabula]{style="term"}** Books printed in Europe before 1501. One such book is an incunable.

**:mark[italic]{style="term"}** A slanting style of type, first cut by Francesco Griffo for Aldus Manutius.

**:mark[movable type]{style="term"}** Single letters cast one by one, which can be set in lines, printed and used again.

**:mark[octavo]{style="term"}** A book whose sheets are folded three times, giving sixteen pages to the sheet.

**:mark[pamphlet]{style="term"}** A short booklet of a few unbound pages, quick and cheap to print.

**:mark[screw press]{style="term"}** A press in which a turning screw drives a flat plate down onto the paper and the type.

**:mark[vernacular]{style="term"}** The language people speak at home, as opposed to Latin.
:::

:::paragraphs{style="colophon"}
Set in Source Serif 4 and Source Sans 3 (SIL Open Font License) · Text: original, CC BY 4.0 · Opening picture: a painting made for this edition.
:::
`; // content.<lang>.md: the reading, its key and questions

// #region art: a printing workshop around 1460, a painting in assets/ (no lettering)
const resources = [{ id: 'press', typeId: 'figure', kind: 'bitmap', createdAt: 0, updatedAt: 0,
  bitmap: { fileId: 'press-1400.jpg', format: 'jpeg', width: 1400, height: 790 },
  altText: t({
    en: 'A printing workshop: a compositor picks type from a case by the window while a '
      + 'pressman pulls the bar of a wooden screw press; printed sheets hang drying overhead.',
    es: 'Un taller de imprenta: un cajista toma tipos de la caja junto a la ventana mientras '
      + 'un prensista tira de la barra de una prensa de husillo; arriba se secan los pliegos.',
  }) }];
await loadImage(resources[0].bitmap.fileId, asset(resources[0].bitmap.fileId));
// #endregion

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
const FONTS = {
  'Source Serif 4': ['400', '400i', '700'],
  'Source Sans 3': ['400', '600', '700'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
const doc = await buildDocumentWithFonts({ markdown, resources }, config, kitFonts(FONTS));
showPages(doc, { title: t({ en: 'A study reader in three highlight colours',
  es: 'Un libro de lecturas resaltado en tres colores' }) });

// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ─────

// ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook
function mm(value) { return { value, unit: 'mm' }; }
function pt(value) { return { value, unit: 'pt' }; }
function em(value) { return { value, unit: 'em' }; }
/** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */
function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; }
/** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */
function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; }

// ─── Kit · fonts v3 ── the same in every recipe · postext.dev/cookbook
// Postext loads the faces a config asks for before layout and asks the kit
// for Fontsource's files, the ones the PDF embeds too.

/** buildDocumentWithFonts' options: the resolver, and FONTS' faces loaded
 *  with the config's. FONTS = { 'Family Name': ['400', '400i', '700'] }. */
function kitFonts(FONTS = {}) {
  const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) =>
    ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' })));
  return { resolve: fontsourceResolver, faces };
}

/** A face's files with their ranges: latin, then latin-ext and greek when
 *  `text` needs them (kitSubsetsFor). Null for a face Fontsource lacks. */
async function fontsourceResolver(family, weight, style, { text = '' } = {}) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  if (meta && !(await fontsourceWeights(family, style)).includes(weight)) return null;
  kitStatus('Loading fonts…');
  const at = await fontsourceAt(id);
  return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({
    source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`,
    unicodeRange: kitRanges()[subset],
  }));
}

/** Each file's unicode-range (a function: the kit runs after the pen's code). */
function kitRanges() {
  return {
    latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,'
      + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD',
    'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,'
      + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF',
    greek: 'U+0370-03FF',
  };
}

/** For the cjk, arabic and comics blocks: loads `faces` (as FONTS) for the
 *  sample `text`; `optional` skips a face Fontsource lacks. Files are added in
 *  the order asked, the last winning a shared character. Resolves to the count. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = kitRanges();
  const jobs = [];
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal'])
      .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await
    const at = fontsourceAt(id);
    const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null;
    const subsets = ['latin', ...kitSubsetsFor(text, meta)];
    for (const [weight, style] of todo) {
      if (optional && !(meta && (await fontsourceWeights(family, style)).includes(weight))) continue;
      for (const subset of subsets) {
        const url = `${await at}files/${id}-${subset}-${weight}-${style}.woff2`;
        const face = new FontFace(family, `url(${url}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: ranges[subset] });
        jobs.push(face.load().catch(() => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return ready.filter((face) => face && document.fonts.add(face)).length;
}

/** A loaded FontFace covers this face (fonts.check() says yes to any family). */
function hasFace(family, weight, style) {
  for (const face of document.fonts) {
    if (face.status !== 'loaded' || face.style !== style) continue;
    if (face.family.replace(/^["']|["']$/g, '') !== family) continue;
    const [low, high = low] = face.weight.split(' ').map(Number);
    if (weight >= low && weight <= high) return true;
  }
  return false;
}

/** The files beyond latin `text` needs that `meta`'s family ships. */
function kitSubsetsFor(text, meta) {
  return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']]
    .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x);
}

/** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */
function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); }

/** A package's folder at the one version @5 names now (#766). */
function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) {
  return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json())
    .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`);
}

/** The family's Fontsource metadata (weights, styles, subsets), or null. */
function fontsourceMeta(family, id = fontsourceId(family)) {
  return (fontsourceMeta.c ??= {})[id] ??= fetch(`https://api.fontsource.org/v1/fonts/${id}`)
    .then((res) => (res.ok ? res.json() : null), () => null);
}

/** The weights the package ships in `style`: the API pairs none (no Libre
 *  Caslon 700 italic file), so italics are read from latin-italic.css. */
function fontsourceWeights(family, style, id = fontsourceId(family)) {
  return (fontsourceWeights.c ??= {})[id + style] ??= fontsourceMeta(family).then(async (m) => {
    if (!m?.styles.includes(style)) return m ? [] : [400, 700];
    const css = style === 'italic' ? await fetch(`${await fontsourceAt(id)}latin-italic.css`).then((r) => (r.ok ? r.text() : ''), () => '') : '';
    const w = [...css.matchAll(/weight: (\d+)/g)].map((x) => +x[1]);
    return w.length ? w : m.weights;
  });
}

// ─── Kit · viewer v2 ── the same in every recipe · postext.dev/cookbook
/** The pages as spreads on a dark desk, page 1 alone, then verso | recto,
 *  each painted when it scrolls near. Other options go to renderPageToCanvas. */
function showPages(docs, { title, width = 460, ...render } = {}) {
  const root = viewer(title);
  const pages = [docs].flat().flatMap((doc) =>
    doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index })));
  const spreads = [];
  let verso = null;
  for (const p of pages) {
    if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; }
    else { spreads.push([verso, p]); verso = null; }
  }
  if (verso) spreads.push([verso, null]);
  const density = Math.min(window.devicePixelRatio || 1, 2);
  showPages.painter?.disconnect();
  const painter = new IntersectionObserver((entries) => {
    for (const { isIntersecting, target } of entries) {
      if (!isIntersecting) continue;
      painter.unobserve(target);
      const { doc, page } = target.postext;
      renderPageToCanvas(page, doc, target, { ...render, scale: (width * density) / page.width });
    }
  }, { rootMargin: '800px' });
  showPages.painter = painter;
  root.replaceChildren(...spreads.map((pair) => {
    const spread = document.createElement('div');
    spread.className = 'pt-spread';
    for (const p of pair) {
      const figure = document.createElement('figure');
      if (p) {
        const label = p.page.pageLabel || String(p.n + 1);
        const canvas = document.createElement('canvas');
        canvas.postext = p;
        canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`;
        canvas.setAttribute('role', 'img');
        canvas.setAttribute('aria-label', `Page ${label}`);
        const folio = document.createElement('figcaption');
        folio.textContent = label;
        figure.append(canvas, folio);
        painter.observe(canvas);
      } else figure.className = 'pt-blank';
      spread.append(figure);
    }
    return spread;
  }));
  kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`);
  document.documentElement.dataset.postext = 'ready';
  return pages.length;
}

/** The desk, the bar and the error reporting, created once. */
function viewer(title) {
  if (!document.getElementById('pt-kit')) {
    document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit">
      :root { color-scheme: dark; }
      body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; }
      #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center;
        gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px);
        border-bottom: 1px solid #23262d; }
      #pt-bar strong { color: #f4f1ea; font-weight: 600; }
      #pt-actions { display: flex; gap: 12px; margin-left: auto; }
      #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
      #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; }
      .pt-spread { display: flex; }
      .pt-spread figure { margin: 0; width: min(460px, 44vw); }
      .pt-spread canvas { display: block; width: 100%; background: #fff;
        box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif;
        letter-spacing: .18em; text-transform: uppercase; color: #6c7079; }
      .pt-blank { visibility: hidden; }
      @media (max-width: 760px) {
        .pt-spread { flex-direction: column; gap: 32px; }
        .pt-spread figure { width: min(460px, 92vw); }
        .pt-blank { display: none; }
      }
    </style>`);
    document.body.insertAdjacentHTML('afterbegin',
      '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>');
    document.getElementById('pt-title').textContent = document.title || 'Postext';
    addEventListener('error', (event) => kitFail(event.error ?? event.message));
    addEventListener('unhandledrejection', (event) => kitFail(event.reason));
  }
  if (title) document.getElementById('pt-title').textContent = title;
  return document.getElementById('pages')
    ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' }));
}

function kitStatus(text) {
  viewer();
  document.getElementById('pt-status').textContent = text;
}

function kitFail(error) {
  document.documentElement.dataset.postext = 'error';
  kitStatus(`Error: ${error?.message ?? error}`);
}

// ─── Kit · images v3 ── recipes with pictures · postext.dev/cookbook
/** Registers a photo or PNG for the canvas and keeps its bytes for the PDF.
 *  fetch → ImageBitmap never taints the canvas (a plain cross-origin <img> would). */
async function loadImage(fileId, url) {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Image not found (${res.status}): ${url}`);
  const bytes = new Uint8Array(await res.arrayBuffer());
  registerResourceImage(fileId, await createImageBitmap(new Blob([bytes])));
  (loadImage.bytes ??= new Map()).set(fileId, bytes);
}

/** Registers SVG markup as a vector image, its fonts inside (inlineSvgFonts, ≥ 1.25).
 *  `options` go to registerResourceImage: { singleInk: true } tints it on the canvas. */
async function loadSvg(fileId, svg, options) {
  if (typeof inlineSvgFonts === 'function') svg = await inlineSvgFonts(svg, svgFaces);
  const img = new Image();
  img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
  await img.decode();
  registerResourceImage(fileId, img, options);
  (loadImage.bytes ??= new Map()).set(fileId, new TextEncoder().encode(svg));
}

/** A face's Fontsource files: nearest weight; latin + what the SVG sets. */
async function svgFaces(family, weight, style, { codePoints = [] } = {}) {
  const [id, m] = [fontsourceId(family), await fontsourceMeta(family)];
  const w = m?.weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a)) ?? weight;
  const s = m?.styles.includes(style) === false ? 'normal' : style;
  return Promise.all(['latin', ...kitSubsetsFor(String.fromCodePoint(...codePoints), m)].map(async (x) => new Uint8Array(
    await (await fetch(`${await fontsourceAt(id)}files/${id}-${x}-${w}-${s}.woff2`)).arrayBuffer())));
}

/** renderToPdf({ resourceBytes: imageBytes }) */
function imageBytes(fileId) { return loadImage.bytes?.get(fileId); }

/** renderToHtml({ resourceImageUrl: imageUrl }) */
function imageUrl(fileId) {
  const bytes = imageBytes(fileId);
  if (!bytes) return undefined;
  imageUrl.urls ??= new Map();
  if (!imageUrl.urls.has(fileId)) {
    const type = /\.svg$/i.test(fileId) ? 'image/svg+xml' : /\.png$/i.test(fileId) ? 'image/png' : 'image/jpeg';
    imageUrl.urls.set(fileId, URL.createObjectURL(new Blob([bytes], { type })));
  }
  return imageUrl.urls.get(fileId);
}

// ─── /Kit ───────────────────────────────────────────────────────────────────────
```

## تنويعات

### دوّر أطراف الأشرطة

نصف قطر يساوي خُمس حجم الخط يعطي الأطراف الناعمة لقلم اللبّاد.

```diff
-  // 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-».

```diff
   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` يأخذ النمط الأول، بلا خطأ على الصفحة (ينبّه إليه Sandbox). اجعل المعرّفات قصيرة وتحقّق من كتابتها في النص.

## الحقوق

- الوصفة: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- النص: 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 (OFL-1.1), Source Sans 3 (OFL-1.1)
- الشيفرة: MIT · محتوى المثال: CC-BY-4.0

## وصفات ذات صلة

- [رقم 076 · كتاب قراءة بالبينيين: النطق فوق كل حرف](https://postext.dev/ar/cookbook/pinyin-primer.md): روبي أحادي في كتاب قراءة أول من هونغ كونغ: {人之初|rén zhī chū} يضع مقطع بينيين واحدًا في الوسط فوق كل حرف، بخط Andika، داخل فجوة أسطر من 28 نقطة. · المستوى 2 (متوسط) · الكتب المدرسية, كتب التمارين والتدريبات
- [رقم 163 · كتاب في الهارموني بأمثلة موسيقية مكتوبة بلغة ABC](https://postext.dev/ar/cookbook/harmony-textbook-abc-examples.md): فصل عن القفلات في كتاب للهارموني، أمثلته كتل ABC داخل Markdown تُنقش بعرض العمود، ويمتد أطولها على صفحتين. · المستوى 2 (متوسط) · الكتب المدرسية
- [رقم 122 · نص 漢文 بعلامات قراءته، وقراءته اليابانية](https://postext.dev/ar/cookbook/kanbun-kundoku.md): ثلاثة أقوال من «المختارات» ورباعية من عصر تانغ كما ينضّدها كتاب قراءة ياباني: علامات العودة وحروف okurigana بلون الزنجفر، والقراءة بالكانا تحت كل مقطع. · المستوى 2 (متوسط) · الكتب المدرسية
