# 模板页

> 模板页的Markdown：用四个冒号的页面围栏指定页面模板，用框和slot属性把内容送进模板中的各个框，在图片锚点上放标注的插图，以及对照模板检查内容的警告

- HTML版本: https://postext.dev/zh/docs/composed-pages
- 最后更新: 2026-10-16
- 阅读时间: 9分钟
- 其他语言: [en](https://postext.dev/en/docs/composed-pages.md), [es](https://postext.dev/es/docs/composed-pages.md), [ca](https://postext.dev/ca/docs/composed-pages.md), [de](https://postext.dev/de/docs/composed-pages.md), [fr](https://postext.dev/fr/docs/composed-pages.md), [it](https://postext.dev/it/docs/composed-pages.md), [pt](https://postext.dev/pt/docs/composed-pages.md), [ja](https://postext.dev/ja/docs/composed-pages.md), [ar](https://postext.dev/ar/docs/composed-pages.md)

## 简单来说

有些页面不是一栏连续的正文，而是由一组框拼成，比如课本里的一页：顶上一条标题色带，下面一段课文，再加三个配有照片的彩色方框。Postext把这样的页面叫作模板页。页面的样子由配置中的模板决定，Markdown只说明哪段文字放进哪个框。本页介绍这种Markdown的写法：页面围栏、框、槽位名，以及像地图上的地名那样带标注的图片。本页还列出文字与模板对不上时Postext给出的警告。

**模板决定页面的形式；Markdown提供内容，并说明每一部分进哪个框。**

模板页是按模板排出的页面。模板里有命名的框（标题色带、一段课文、一个配有照片的方框）、一起移动的组和装饰，全部定义在配置的`pageTemplates`下。页面的Markdown是一个围栏块，写明所用的模板，并按名称把内容倒进各个框。Markdown里从不写位置和尺寸，配置里也从不写正文文字，所以译本就是同一个模板配上另一种语言的文字。

本页讲的是Markdown，以及页面如何据此排出。模板本身（框、尺寸、样式和装饰）见[配置 › 组合页面](https://postext.dev/zh/docs/configuration-composed-pages.md)。没有模板页的文档，排版结果与以前完全相同。

## 页面围栏

```md
::::page{template="tema-cajas" fondo="banda-retos"}
# La actividad económica y el dinero

Las personas utilizan :chip[recursos]{style="clave"} que se obtienen de la naturaleza…
::::
```

模板页以一行**四个冒号**开头，后接`page`和它的属性，以单独一行**同样数目的冒号**结束。页面内部，三个冒号的容器（`:::frame`、`:::callout`、`:::figure`、`:::columns`、`:::paper`）照常开启和关闭，所以单独的`:::`关闭的是其中最内层的那个，永远不会关闭页面。用五个冒号写的页面（`:::::page`）用五个冒号结束，这时页面里的四冒号行只是普通文字。

| 属性 | 作用 |
| --- | --- |
| `template="…"` | 必填。`pageTemplates`中某一项的id。 |
| `spread` | 这一页是由两个对页组成的跨页；模板也必须是跨页模板。 |
| `id="…"`、`#id` | 锚点：指向它的`:ref`印出模板页所在的页码。 |
| 其他任何属性 | 模板以`{attr.name}`读取的值，例如逐页选择的背景图：页面上写`fondo="banda-retos"`，装饰中写`resourceId: "{attr.fondo}"`。 |

模板页总是从新的一页（或跨页）开始，结束围栏之后的文字从下一页开始。

三个冒号的`:::page{split=…}`是漫画页（见[漫画](https://postext.dev/zh/docs/comics.md)），意思不变。带有`template=`的三冒号`:::page`仍按漫画页读取，同时警告`pageFenceLength`提示模板页需要四个冒号。

缺了围栏时，解析器会自行恢复，不丢文字：

- 没有结束的页面一直延续到章末，并在开始行报告为未闭合的容器；
- 页面尚未结束时遇到一行`::::page`，先在那里结束前一页（以同样的方式报告）；
- 结束围栏会关闭页面内仍未关闭的框或标注框，并逐一报告；
- 冒号数目不同的结束围栏不属于这一页：它留在文字中，一眼就能看到。

## 框与槽位

```md
:::frame{slot="dinero" label="El dinero"}
Es el instrumento utilizado como medio de pago…

Hasta hace pocos años, lo más normal era pagar con dinero en **efectivo**…
:::
```

**槽位**是一段内容要去的地方的名称，与模板中某个框的名称对应。`:::frame{slot="…"}`把其中的全部内容送进那个框。框里可以写任何Markdown：段落、列表、标题、行内标签、`::resource`嵌入、`:ref`引用、脚注、`:::columns`和嵌套的标注框。`label`和`title`为框的标签或标题提供文字，与`:::callout`相同；框的外观来自模板给它指定的标注框样式。

单个块也可以自己写明槽位：

| 块 | 写法 |
| --- | --- |
| 标题 | `## Retos {slot="retos-titulo"}` |
| 资源嵌入 | `::resource{id="dinero-pago" slot="dinero-fotos"}`（两个属性的先后不限） |
| 代码清单 | `````js {slot="codigo"}`` |
| 标注框或其他任何容器 | `:::callout{type="nota" slot="aside"}`：整个框进入该槽位 |
| 插图 | `:::figure{resource="mapa" slot="mapa"}`（见下文） |

内容按以下规则分配：

- 槽位相同的几段内容按Markdown中的顺序合在一起。
- 没有槽位的内容进入模板的**流文框**（标为`flow`的框）。
- 没有槽位的标题，如果模板里有接收其级别的框（`takes: "h1"`），就先进入那个框；所以课本里一课的标题只需在页首写`# 标题`。
- 写了模板中没有的槽位的内容进入流文框，并给出报告（`slotUnknown`）。

模板页之外的`slot`会被读取，但不起任何作用。

## 带标注的插图

```md
:::figure{resource="mapa-europa" slot="mapa"}
::label[OCÉANO ATLÁNTICO]{at="atlantico" style="mar"}
::label[**Madrid**]{at="madrid"}
::label[Mar Negro]{at="62% 48%"}
:::
```

`:::figure{resource="…"}`像`::resource`一样放置一个资源，带有题注和编号，并列出要放在图上的标注：每个标注一行`::label[文字]{…}`，行与行之间可以有空行。

- `at`指定资源的一个锚点（在图上定义一次、各语言共用的点），或者为没有锚点的标注用图片的百分比给出一个点：`at="x% y%"`。
- `style`指定一种标注样式，优先于锚点自带的样式。
- 方括号中的文字是行内Markdown：粗体、斜体、行内标签。

插图在正文中和在框中都能用。标注会被读取，并与其源码位置一起保留；在图上画出标注要等到以后的版本，在那之前插图按其资源原样排出。插图内部出现既不是标注也不是空行的一行时，缺少结束`:::`的插图就在那里结束，并报告为未闭合的容器。

## 示例

课本中的一页，有标题色带、一段带列表的课文，以及三个彩色方框，每个方框旁边配一张照片：

```md
::::page{template="tema-cajas" fondo="banda-retos"}
# La actividad económica y el dinero

## La actividad económica

Las personas utilizan :chip[recursos]{style="clave"} que se obtienen de la naturaleza…

- Las :chip[empresas.]{style="clave"} Ofrecen bienes y servicios a cambio de un beneficio.
- Las :chip[personas y familias.]{style="clave"} Adquieren lo que ofrecen las empresas…

:::frame{slot="dinero" label="El dinero"}
Es el instrumento utilizado como medio de pago, ya que su valor es conocido…
:::

::resource{id="dinero-pago" slot="dinero-fotos"}

:::frame{slot="gasto" label="El gasto"}
- **Fijo:** es el que se produce todos los meses…
- **Variable:** puede cambiar según las necesidades…
:::

::resource{id="gasto-compras" slot="gasto-fotos"}

:::frame{slot="ahorro" label="Préstamos y ahorro"}
Si necesitas o quieres comprar algo muy caro…
:::

::resource{id="ahorro-balanza" slot="ahorro-fotos"}
::::
```

标题进入接收`h1`的框；小标题、段落和列表进入流文框；每个方框和每张照片进入各自槽位对应的框。加泰罗尼亚语版就是同样的这些行换上另一种语言的文字：模板、槽位和照片都不变。

## 页面如何排出

模板页单独占一页（对设计元素的`pages`筛选而言，页面角色是`composed`）。模板按页面落在哪一侧来取，所以会套用它的`variants.recto`或`variants.verso`。然后逐个放置并填充各个框，用的是全书其余部分的排版设置：段落样式和标题样式、列表、行内标签、脚注标记，以及框自己的标注框样式。

### 放置框

框放在它的网格单元格里，或者放在`placement`指定的位置（锚点、边和偏移，与设计元素相同）；两者都没有时，放在模板区域的左上角，宽度与区域相同。锚定到另一个框的框（`"to": "#dinero"`）在那个框之后放置，并随它移动：`below`把它挂在那个框下面，`right-of`放在旁边，`top-right`这样的角则把它钉在那个框外框的对应角上。

框的高度可以是固定值（长度或百分比）、`fill`（一直到区域边缘），或默认的`auto`：取它所接收内容的高度，介于`minHeight`和`maxHeight`之间，且不超出区域底边。框比内容高时，内容的位置由`verticalAlign`决定（`top`、`middle`、`bottom`，或用`justify`把空间分到各块之间）。框的标注框样式按框的完整尺寸画出底色、边框、圆角、色条和图标；`:::frame`围栏上的`label`印在样式的页签里。

### 流文框与标题

没有写槽位的内容进入标为`flow`的框；某一级的第一个标题进入用`takes`接收这一级的框。一页中的各个框按其内容在Markdown中首次出现的顺序读取，每个框整体读完：屏幕阅读器、带标签的PDF和流式版式的输出，都在开篇正文之后读一个方框，而不会从正文中间插进去。

### 串接框

`next`把一个框装不下的内容送到同一页的另一个框，那个框再送往下一个，就像排版软件里串接的文本框：

```json
{ "name": "columna-1", "flow": true, "next": "columna-2", "height": { "value": 120, "unit": "mm" } },
{ "name": "columna-2", "placement": { "anchor": { "to": "#columna-1", "edge": "right-of" } } }
```

文字在两行之间断开，在下一个框中接着排，按那个框的宽度重新断行，不丢也不重复。`overflow.continueIn: "#frame"`的意思相同。接续到下一页（`"next-page"`）已在计划中。

### 推开文字的框

设了`exclude: "push"`的框不让其他框的文字进入自己的矩形：其他框中碰到它的行，在矩形遮住的那一侧缩短，就像正文中文字绕排图片一样。锚定在方框一角并标为`push`的照片，会让方框的文字从它旁边排过。如果方框要为图片留出一条固定的空栏，可以改用`padding`。

### 装饰、书眉与层

模板的装饰（`decoration`：图片、线条、方框和短文字）在各框的外框确定之后才排放，所以元素可以锚定到某个框（`"to": "#gasto"`）并跟着它走。装饰中的文字读取本页自己的属性：页面围栏上的`fondo="banda-retos"`给出`{attr.fondo}`，例如用`"resourceId": "{attr.fondo}"`逐页选择背景图。书眉和页脚也读取这些属性。模板的`header`或`footer`取代所在章节的设置，`false`把它隐藏，两者都没设时印出所在章节的书眉和页脚。

各框按`layer`的顺序绘制：装饰在第0层，框默认在第1层，层号高的画在层号低的上面，负数层画在装饰下面。`rotate`让框连同其内容绕框的中心旋转任意角度，在画布、PDF、HTML查看器和固定版式EPUB中都是如此。

### 放不下的内容

框的内容比框高、又没有可以接续的下一个框时，全部内容仍保留在文档中，但只显示放得下的部分：其余部分在分页输出中于框底被裁掉，警告[`frameOverflow`](https://postext.dev/zh/docs/composed-pages.md#frameoverflow)会说明裁掉了多少。没有任何框接收的内容（没有流文框，或这一侧缺少某个框）排进正文，放在模板页之后的那一页。

## 警告

这些检查在任何排版之前读取Markdown和配置，所以在你输入时就会出现在检查面板中，也会出现在`postext check`和文档的`contentWarnings`中，每条都带有所涉内容的源码范围（见[配置 › 文档中的警告](https://postext.dev/zh/docs/configuration-programmatic-usage.md#文档中的警告)）。

| 类型 | 原因 | 解决办法 |
| --- | --- | --- |
| `templateUnknown` | 页面围栏指定的模板在`pageTemplates`中没有对应的项，或者根本没有指定模板。它的槽位不做检查。 | 改正id，或者把这个模板加入配置。 |
| `slotUnknown` | 某个`slot="…"`指定的名称不是该页模板中的任何框（通过`extends`继承的框和`variants`页面中的框都算在内）。这段内容进入流文框。 | 改正槽位名，或者在模板中加一个同名的框。 |
| `slotEmpty` | 模板中的某个框在这一页上什么也没收到：没有槽位指向它，它不接收标题；如果它是流文框，也没有剩下无槽位的内容。只接收另一个框延续过来的内容（`next`）的框不报告。`postext check`把它列为提示。 | 为它写上带其槽位的内容，或者在模板中把这个框标为`optional: true`。 |
| `pageFenceLength` | 三冒号的`:::page`带有`template=`。它仍像以前一样按漫画页读取。 | 用四个冒号开启页面，写成`::::page{template="…"}`，并用`::::`结束。 |
| `unclosedContainer` | 模板页、框或插图始终没有结束（见上文的恢复规则）。检查面板把模板页称为`::::page`。 | 补上结束围栏：模板页用与开始行同样数目的冒号，框或插图用`:::`。 |

### frameOverflow

这条警告来自排版，而不是上面那些读源码的检查：页面排好之后，它才出现在检查面板、`postext check`和文档的`contentWarnings`中。

**原因**：灌入某个框的内容（通过它的槽位，或进入流文框的内容）比框高，而这个框已没有可以接续的`next`框。最常见的情况是文字比框能容纳的多：译文比原文长，或者框被固定的`height`或`maxHeight`限住了。放不下的部分在分页输出中于框底被裁掉；它仍留在文档、源码映射和流式版式的EPUB中。警告会给出模板、框、页码、被裁掉的高度（`overflowMm`）以及`configPath`，即该框在配置中的JSON指针（`/pageTemplates/0/frames/2`）。

**解决**：给框更多空间（调高或去掉它的`height`或`maxHeight`），用`next`把它接到另一个框，缩短文字，或者换一个模板来排这一页。目前排版不会为了放下文字而自动缩小它。

## 在代码中使用

`composedPages(blocks)`读取一章解析后的块（`parseMarkdownWithIssues(markdown).blocks`），按顺序为每个页面围栏返回一个节点，包括：`template`、`spread`、带有每个值源码范围（`attrSources`）的`attrs`、`fenceIndex`（该围栏在章中的序号，各语言相同）、`slots`（每个槽位的块索引，按Markdown中的顺序）、`unassigned`（没有槽位的块），以及整页的源码范围。块在`ContentBlock.slot`中记录它指定的槽位及该值的源码范围；插图是一个`resourceBlock`，其`figure`含有插图的属性和标注，每个标注带有文字、行内片段和指回Markdown的源码映射。`checkComposedSlots(pages, config)`为这些页面返回三种槽位警告。

排版之后，模板页的`VDTPage.composed`记录它的模板、所套用的变体、页面围栏的属性，以及每个框的这些信息：id（`cp0:dinero`，即页面围栏的序号加框名，各语言相同）、外框、层、旋转、放不下的内容高度（`overflowPx`）、从哪个框接续而来或接续到哪个框、它在配置中的路径（`/pageTemplates/0/frames/2`）以及它的块的id。这些块是`page.floats`和`doc.blocks`中普通的标注框块，带有各自的源码范围，所以在Sandbox预览中点击一个未旋转的框，就会跳到它的Markdown。`composedPaintOrder(page)`给出渲染器绘制各框和装饰的顺序，`frameMatrix(frame)`给出框的旋转。
