章 11 · 篇 II · 工艺
配置:字体、颜色与输出
单位、颜色与调色板,自定义字体,HTML查看器,PDF与印刷输出,书页视图和调试
简单来说
本页讲整本书共用的设置,以及每种输出各自的设置。它说明尺寸和颜色怎样写,调色板里的颜色怎样命名。它说明怎样加入你自己的字体。接着讲网页视图、PDF文件、印刷厂要的文件和3D书本。最后一节用来打开工作时帮得上忙的参考线和警告。
#单位与颜色
#尺寸
Postext中所有物理量都用Dimension类型表示,即一个数值加一个单位:
interface Dimension {
value: number;
unit: DimensionUnit; // 'cm' | 'mm' | 'in' | 'pt' | 'px' | 'em' | 'rem'
}绝对单位:cm、mm、in、pt、px,按配置的DPI换算成像素。在300 DPI下,1 cm约等于118 px。
相对单位:em、rem,随当前字号缩放。em相对于元素自身的字号,rem相对于正文字号。
#颜色
颜色同时保存一个十六进制表示和一个目标颜色模型:
interface ColorValue {
hex: string; // '#ff0000'、'transparent' 等
model: ColorModel; // 'hex' | 'rgb' | 'cmyk' | 'hsl'
cmyk?: CmykPercent; // 以CMYK定义的颜色的准确印刷色数值。
}model字段指明预期的色彩空间。网页渲染通常用'hex'或'rgb'。印刷流程中,'cmyk'表明这个颜色是以CMYK定义的,cmyk保存它的数值:CMYK印刷渲染原样使用这些数值,hex是它们在屏幕上的呈现(见以CMYK定义的颜色)。
Postext面向出版级输出,所以正文的默认颜色带有model: 'cmyk'(#000000)。标题、粗体、斜体和列表的颜色默认链接到调色板中的主色(#295AA3,model: 'hex')。页面背景和界面叠加层(基线网格、裁切标记、调试标记)默认为model: 'hex'。如果需要不同的导出语义,可以在任意字段上覆盖color.model。
#透明度
颜色可以是半透明的。hex接受带alpha通道的写法:#rgba或#rrggbbaa。它也接受rgb() / rgba()颜色,逗号语法和空格语法都可以,alpha可以写成数字或百分比。transparent表示完全透明:
const config: PostextConfig = {
header: {
elements: [{
kind: 'box',
id: 'veil',
placement: {
anchor: { to: 'bleed', edge: 'top-left' },
size: { width: 'fill', height: { value: 40, unit: 'mm' } },
},
style: { backgroundColor: { hex: '#ffffffb3', model: 'hex' } }, // 白色,不透明度70 %
}],
},
bodyText: { color: { hex: 'rgba(0, 0, 0, 0.85)', model: 'rgb' } },
};三个后端的绘制结果相同。Canvas和HTML查看器把这个值当作CSS颜色。PDF后端把颜色的不透明度设为常量alpha,即一个ExtGState,填充用ca,描边用CA。文字、线条、框、表格填充和边框、行内标签、色块和公式都适用。半透明颜色叠加在它之前绘制的内容之上。页眉或页脚中的框最后绘制,因此会遮在其下的文字上;章首页色带中的框最先绘制,因此会给文字下方的页面着色。当PDF被强制转换到另一色彩空间时(pdfGeneration.forceColorSpace配合colorSpace: 'cmyk'或'grayscale'),颜色会被转换,alpha保持不变。在沙盒中,取色器的不透明度滑块以#rrggbbaa写入这些值,取色器也能读取其他写法。
#自定义字体
Postext把每个fontFamily字符串同时与Google Fonts字体目录和文档的customFonts列表进行匹配。名称冲突时自定义字体优先:如果你声明了customFonts: [{ name: 'Roboto', … }],Postext会使用你上传的文件,而不是Google Fonts的“Roboto”。
以下情况适合使用自定义字体:
- 文档需要品牌字体或授权字体,而Google Fonts上没有。
- 运行环境无法访问Google Fonts CDN(离线、内网、对隐私敏感)。
- 字体文件必须保密,不能上传给第三方。
#配置结构
type CustomFontFormat = 'woff2' | 'woff' | 'ttf' | 'otf';
type CustomFontStyle = 'normal' | 'italic';
interface CustomFontVariant {
weight: number; // CSS font-weight,100..900
style: CustomFontStyle;
fileId: string; // 二进制文件在外部存储中的不透明id
format: CustomFontFormat;
fileName?: string; // 上传时的原始文件名(可选,显示在界面中)
}
interface CustomFontFamily {
name: string; // 可用在任何能填Google Fonts字体家族名的地方
variants: CustomFontVariant[];
}
interface PostextConfig {
// ...
customFonts?: CustomFontFamily[];
}各个变体的二进制文件不嵌入配置本身。配置只保存fileId指针,字节数据存放在配置之外。在沙盒里,这指的是IndexedDB(键值存储,仅限浏览器,归该文档私有)。把Postext嵌入其他宿主的集成方可以用任何方式解析fileId,例如服务器接口、service worker缓存等,只要字节数据在buildDocument运行前到达主线程即可。
#在沙盒中管理自定义字体
从左侧活动栏打开字体面板(位于资源和版面设计之间)。其中的本书使用的字体列表列出版面设计用到的每个字体家族,以及它的用途、来自Google Fonts还是你的文件。在你的字体文件下,对每个字体家族:
- 添加字体家族:新建一个空的字体家族,可直接在原处重命名。
- 上传变体:选择字重(100–900)和样式(normal / italic),然后选择一个或多个
.woff2、.woff、.ttf或.otf文件。每个文件成为一个独立变体,绑定到当前选中的(字重,样式);上传的文件名会被记住并显示在该行,便于区分各个变体。之后随时可以用下拉菜单重新调整变体的字重或样式。 - 允许变体重复。如果两个文件落在同一个(字重,样式)位置,两者都会保留,并出现字体变体重复警告,提醒你为多出来的文件调整设置加以区分。
- 删除变体或删除家族:从配置中移除该条目,同时删除IndexedDB中保存的字节数据。
字体家族一经声明,所有字体选择器都会把它归入自定义组,排在Google Fonts列表上方。选中它,就会把这个字体家族写入你应用它的每个字体家族字段。
#渲染行为
内部机制如下:
customFonts变化时,每个已声明的字体家族都会自动以FontFace条目注册到document.fonts上。这样HTML查看器、Canvas视口(它通过document.fonts测量)以及任何直接引用它的CSS都能用上自定义字体,用户无需先打开字体选择器。- 排版工作线程通过现有的字体数据传输通道接收同样的ArrayBuffer,因此测量(
buildFontString、pretext)得到的度量与Google Fonts完全一致。 - 修改或删除某个变体时,工作线程会丢弃该字体家族的缓存字体,并在下次构建时重新注册,使预览与当前的变体集合保持同步。
- PDF导出:上传的二进制文件走同一条
PdfFontProvider流程。.woff2文件会被解压;.ttf和.otf直接传入。.woff会被拒绝并给出明确的错误(pdf-lib无法嵌入原始WOFF,请改为上传.woff2/.ttf/.otf)。CFF风格的OpenType(带OTTO标识的.otf)不做子集化直接嵌入,因为pdf-lib的CFF子集化程序在save()时会遍历每个字形,对实际字体可能卡住好几分钟;跳过子集化,PDF会稍大一些,换来稳定的渲染时间。
#缺失字体警告
沙盒的检查面板在其字体组中列出三种自定义字体特有的问题(都由同一个debug.warnings.missingFont开关控制,这个开关原本就管着通用的“未加载”警告):
- 未知字体家族:某个
fontFamily引用的名称既不是已知的Google Fonts字体,也不是当前已声明的自定义字体家族。当你删除一个仍被某个fontFamily字段引用的自定义字体家族时,这条警告也会立刻出现,不必等DOM察觉。 - 缺少字体变体:字体家族存在,但标准的字重/样式位置(400 / 700,normal / italic)中至少有一个没有上传文件。警告会列出具体缺少哪些组合。
- 字体变体重复:同一字体家族中,两个或更多上传文件占用同一个(字重,样式)位置。渲染时只会用到其中一个文件;警告提醒你重新调整其余条目。
点击其中任何一条警告都会打开字体面板,方便你上传缺少的变体、重新添加字体家族或区分重复的变体。
有了排版结果之后,面板还会列出引擎报告的排版时使用了替代字体(fontFallback):页面测量时没有用上的字体,要么根本没有,要么是浏览器借同一字体家族的另一个字重或倾斜画出来的。已经作为未知字体家族或缺少变体列出的字体家族不会重复列出。在第一次排版之前,由针对document.fonts的检查代替它。
#调色板
PostextConfig上的colorPalette属性让你定义一组可复用的命名颜色,并在配置中任何ColorValue里引用它们。它相当于Postext中的CSS自定义属性,或InDesign的色板面板:改一次调色板条目,所有指向它的颜色都会在整个文档中随之更新。
interface ColorPaletteEntry {
id: string; // 稳定的标识符,由ColorValue.paletteId引用
name: string; // 在沙盒界面中显示的名称
value: ColorValue;
}#默认调色板
Postext自带一个只有一个条目的默认调色板,名为主色(id: 'main-color',十六进制值#295AA3)。多项默认值,包括标题颜色、正文粗体/斜体颜色、:ref颜色、项目符号和编号的颜色,都通过paletteId: 'main-color'引用这个条目,所以改动这一个色板,文档中所有用到它的地方都会重新着色。
可以通过三个导出项查看、复制默认调色板,或与之比较:
import {
DEFAULT_COLOR_PALETTE,
cloneDefaultColorPalette,
isDefaultColorPalette,
} from 'postext';
// 内置调色板的只读快照。
DEFAULT_COLOR_PALETTE;
// => [{ id: 'main-color', name: 'Main Color', value: { hex: '#295AA3', model: 'hex' } }]
// 独立副本:修改这个,不要修改DEFAULT_COLOR_PALETTE。
const palette = cloneDefaultColorPalette();
// 检测用户是否改动过调色板。
isDefaultColorPalette(palette); // true调色板位于配置的顶层:
const config: PostextConfig = {
colorPalette: [
{ id: 'ink', name: 'Ink', value: { hex: '#0a0a0a', model: 'cmyk' } },
{ id: 'accent', name: 'Accent', value: { hex: '#b8860b', model: 'hex' } },
],
bodyText: { color: { hex: '#000000', model: 'cmyk', paletteId: 'ink' } },
headings: { color: { hex: '#000000', model: 'hex', paletteId: 'accent' } },
};#引用调色板条目
配置中的任何ColorValue都可以带一个可选的paletteId字段,指向colorPalette中的某个条目:页面背景、正文颜色(包括:ref颜色)、标题颜色、栏间线、列表颜色,表、题注、行内标签和标注框的颜色(框、色条、图标、标记、标签、标题、正文),裁切标记和基线网格的颜色,调试标记,以及版面设计中的所有颜色:书眉、标题章首页和栏内设计、标题样式的版面设计和书眉、篇名页以及目录中的篇条目(文字、线条、框的填充和边框、轮廓、首字下沉)。存在paletteId时,调色板条目的hex / model优先于一起保存的备用hex / model。只有在调色板缺失、为空或不含该id时才会使用内联的备用值;如果配置要交给不理解调色板的工具读取,这一点很有用。
postext 1.5中的变更。在postext 1.4及以前,调色板只作用于一份固定的设置清单:版面设计的颜色(书眉、章首页、标题样式、篇、目录条目)、bodyText.referenceColor、标注框标签的颜色以及标注框正文的粗体/斜体颜色,都保留与其paletteId一起保存的hex。如果文档中保存的值与调色板条目不同(凡是在条目改动后又在沙盒中编辑过的文档,以及主色不是#295AA3时的所有:ref),现在会按链接的含义印出调色板中的颜色。要让某个颜色保持原样,删除它的paletteId。
#调色板如何生效
buildDocument在两处应用调色板,这样无论是你明确写出的覆盖值,还是之后才填入的默认值,引用的颜色都能生效:
applyPaletteToConfig(config):解析原始用户配置中每个带paletteId的ColorValue。想查看引擎实际看到的配置时可以用它。applyPaletteToResolvedConfig(resolved, palette):在默认值解析之后运行,把链接到调色板的默认值(标题颜色、正文粗体/斜体颜色、:ref颜色、列表颜色、默认版面设计的颜色)改写为当前调色板的值。
两者都会遍历整个配置,不会遗漏任何链接到调色板的颜色。文本流中的大多数颜色(正文、标题、列表、表、题注、行内标签,以及标注框的框、标题和正文)输出为普通值。其余颜色,即版面设计的颜色、:ref颜色和标注框标签,取调色板的hex / model,并保留各自的paletteId。篇的palette属性和标题样式的palette正是通过这个链接在各自的页面上覆盖颜色(见篇),所以必须保留它。htmlViewer.overrides保持原样:HTML查看器先合并它,它带的调色板随后会作用于所有内容,版面设计也包括在内。
你很少需要自己调用这两个函数,但它们都已导出,便于查看或复用:
import {
applyPaletteToConfig,
applyPaletteToResolvedConfig,
resolveColorValue,
} from 'postext';
const flat = applyPaletteToConfig(config);
// 原始配置中每个带paletteId的ColorValue现在都带有
// 调色板条目的hex/model(版面设计的颜色保留其paletteId)。
// `applyPaletteToResolvedConfig`通常由buildDocument处理;如果你自己构建
// ResolvedConfig并希望应用调色板,可以直接调用它。resolveColorValue(value, palette, fallback)是处理单个值的版本,适合以命令式方式组装配置、需要逐个解析颜色的场合。
#编辑调色板
如果某个颜色的paletteId找不到对应条目,就会印出它保存的hex / model,而这个值可能早于条目赋予它的颜色。因此,删除条目之前,应先把链接到它的每个ColorValue改写为普通颜色,填入该条目的当前值。沙盒的调色板部分(版面设计 → 颜色)在你删除条目时会自动这样做,无论该颜色位于何处(包括版面设计和标注框标签),并在确认对话框中列出所有用到该条目的设置:按名称,或按它在配置中的路径(header.elements[2].color)。
#HTML查看器
htmlViewer属性控制HTML后端如何在屏幕上排列页面。它只在使用renderToHtml / renderToHtmlIndexed渲染时生效;Canvas和PDF路径完全忽略它,直接使用配置的page.width、page.height和page.dpi。
interface HtmlViewerConfig {
maxCharsPerLine?: number; // 目标栏宽,以正文字体的字符数计。
columnGap?: number; // 多栏模式下的栏间距(px)。
optimalLineBreaking?: boolean; // 在HTML查看器中使用Knuth–Plass,而不是贪心算法。
overrides?: HtmlViewerOverrides; // 仅用于屏幕的部分配置,合并到文档配置之上。
}
type HtmlViewerOverrides = Omit<PostextConfig, 'htmlViewer'>;| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
maxCharsPerLine | number | 70 | 每个渲染栏的目标行长,以正文字体的字符数表示。视口按这个长度取样一段有代表性的正文字符串,由此算出实际的像素宽度,因此结果能适应任何比例字体与字号的组合。 |
columnGap | number | 50 | 查看器处于多栏模式时的栏间距,单位为CSS像素。单栏模式下忽略。 |
optimalLineBreaking | boolean | false | 在HTML查看器中启用Knuth–Plass断行。默认关闭,因为查看器每次改变窗口大小或字号都会重新排版,贪心的首次适配算法足够快,感觉不到延迟。如果想得到与Canvas后端相同的最优断行,就打开它。 |
overrides | HtmlViewerOverrides | — | 只在屏幕上生效的部分文档配置。HTML查看器在排版前把它合并到文档配置之上(applyHtmlViewerOverrides);Canvas和PDF忽略它。对象递归合并;levels数组(标题、列表、目录)按level逐条合并;其他所有数组,如版面设计位置的elements、calloutStyles、colorPalette……整体替换基础数组。典型用法:去掉印刷用色带的章首页,或篇名页的标题依数字换行,而不是按固定的成品框宽度换行。沙盒以JSON形式编辑它。 |
const config: PostextConfig = {
headings: { levels: [{ level: 1, span: 'page', breakBefore: { enabled: true } }] },
htmlViewer: {
// 在屏幕上,各章连续排下去,不用通栏章首页。
overrides: { headings: { levels: [{ level: 1, span: 'column', breakBefore: { enabled: false } }] } },
},
};解析函数和精简函数与其他部分的模式相同:
import {
DEFAULT_HTML_VIEWER_CONFIG,
resolveHtmlViewerConfig,
stripHtmlViewerDefaults,
} from 'postext';
const resolved = resolveHtmlViewerConfig(config.htmlViewer);
// => { maxCharsPerLine: 70, columnGap: 50, optimalLineBreaking: false }
const minimal = stripHtmlViewerDefaults(config.htmlViewer);
// => 所有值都与默认值相同时为undefined完整示例见下文的集成HTML查看器。
#PDF生成(配置)
pdfGeneration属性控制PDF后端如何输出最终文档。这些设置由postext-pdf包在导出时使用;Canvas和HTML查看器会忽略它们。
buildDocument把它们放进VDT,即doc.config.pdfGeneration;renderToPdf对每项设置,从下列来源中第一个给出该设置的地方取值:
- 它自己的选项(
outlines、accessible、colorSpace); - 它渲染的第一个文档的
pdfGeneration(对于一本书,第一章的设置作用于整个文件); - 默认值:开启书签和标签,RGB颜色。
所以renderToPdf(doc, { fontProvider })遵循配置,而传给renderToPdf的选项只在该项设置上优先。forceColorSpace和colorSpace合起来对应colorSpace选项:forceColorSpace开启时采用配置中的colorSpace,关闭时PDF为RGB。早期版本的postext-pdf只读取选项;现在,设置了pdfGeneration的配置会改变不传选项的调用方所生成的PDF。
type PdfColorSpace = 'rgb' | 'cmyk' | 'grayscale';
interface PdfGenerationConfig {
outlines?: boolean; // 根据标题树生成PDF书签。
forceColorSpace?: boolean; // 把所有颜色转换为`colorSpace`。
colorSpace?: PdfColorSpace; // `forceColorSpace`为true时使用的目标色彩空间。
accessible?: boolean; // 带标签、面向PDF/UA的输出(结构树、替代文本、语言)。
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
outlines | boolean | true | 根据标题层级生成PDF大纲(书签),读者可以从PDF阅读器的侧边栏直接跳到任一标题。标题树没有意义的文档(例如单页海报)可以关闭它。 |
forceColorSpace | boolean | false | 为true时,渲染出的PDF中所有颜色都在导出时转换为colorSpace。以屏幕为主、输入颜色已经处于目标色彩空间的PDF可以不开;要确保来源混杂的颜色统一到一个色彩空间,就打开它。 |
colorSpace | 'rgb' | 'cmyk' | 'grayscale' | 'cmyk' | forceColorSpace开启时使用的目标色彩空间。胶印用'cmyk',仅供屏幕阅读的PDF用'rgb',黑白打样用'grayscale'。forceColorSpace为false时不起作用。CMYK经print的输出特性文件(默认FOGRA39)分色,并按其黑色设置处理,RGB图片也会转换;print中设了PDF/X标准时,无论这里怎么设,都写出CMYK。 |
accessible | boolean | true | 生成面向PDF/UA-1的无障碍、带标签的PDF:按阅读顺序排列的逻辑结构树(不跳级的标题、段落、列表、块引用、标注框、带表头单元格的表、带替代文本和题注的图、公式、作为链接的可点击引用;:::toc的内容作为一个TOC,每行一个TOCI,其中行的编号为Lbl,标题和页码为包含链接的Reference),文档标题和语言(顶层的locale),XMP元数据中的PDF/UA标识;所有装饰性标记(页面背景、线条、基线网格、页眉页脚、裁切标记、重复的表头、拆分标注框中重复的标题和续接标记)都标为artifact,屏幕阅读器会跳过它们。没有altText的图依次退而使用题注、标签。浮动的图或表紧接在首次引用它的文字之后阅读,或紧接在其::resource行之前的文字之后;浮动的框紧接在其围栏之前的文字之后阅读,即使浮动体排在后面的页面上也是如此;越过浮动体继续排下去的列表或目录仍是一个元素。只有在不需要额外结构的印刷母版中才关闭它。 |
pdfGeneration: {
outlines: true,
accessible: true,
forceColorSpace: true,
colorSpace: 'cmyk',
}解析函数和精简函数与其他部分一致:
import {
DEFAULT_PDF_GENERATION_CONFIG,
resolvePdfGenerationConfig,
stripPdfGenerationDefaults,
} from 'postext';
const resolved = resolvePdfGenerationConfig(config.pdfGeneration);
// => { outlines: true, forceColorSpace: false, colorSpace: 'cmyk', accessible: true }
const minimal = stripPdfGenerationDefaults(config.pdfGeneration);
// => 所有值都与默认值相同时为undefined完整的导出流程见下文的生成PDF。
#印刷输出(配置)
print属性规定一本书怎样付印:文件的PDF/X标准、CMYK分色所用的输出特性文件、黑色怎样印刷,以及印前检查的阈值。排版不读取它,所以改动它不会让任何一行移动。读取它的有三处:写出文件时的postext-pdf,检查排好的文档时的preflightDocument,以及Canvas和书页视图的印刷预览。
type PdfXStandard = 'none' | 'pdfx1a' | 'pdfx4';
interface PrintConfig {
standard?: PdfXStandard; // 'none':普通PDF。
outputProfile?: string; // 特性文件目录中的id('fogra39'、'fogra51'…)或'custom'。
customProfile?: CustomOutputProfile; // 上传的.icc文件。
renderingIntent?: 'relative' | 'perceptual';
blackPointCompensation?: boolean;
convertImages?: boolean; // 对RGB图片分色(PDF/X-1a总是分色)。
inkLimit?: number; // 总墨量,百分比。
black?: PrintBlackConfig;
preflight?: PrintPreflightConfig;
}
interface CustomOutputProfile {
name: string; // 特性文件的描述,或文件名。
fileId: string; // 存储的.icc文件。
registryName?: string; // 印刷条件的ICC注册名称(FOGRA51…),否则为'Custom'。
inkLimit?: number;
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
standard | 'none' | 'pdfx1a' | 'pdfx4' | 'none' | 文件采用的PDF/X标准。'pdfx1a'写出PDF/X-1a:2003:只有CMYK和灰度,没有透明,所有印厂都接受。'pdfx4'写出PDF/X-4:保留透明和色彩管理,适合现行流程。两者都把所有颜色经输出特性文件分色,无论pdfGeneration.colorSpace怎么设。 |
outputProfile | string | 'fogra39' | CMYK分色所针对的印刷条件:特性文件目录中的一个id,或者'custom',表示采用customProfile。目录中没有的id,或者没有文件的'custom',会退回默认值,并给出一条配置警告。 |
customProfile | CustomOutputProfile | 无 | 你自己提供的CMYK输出特性文件,也就是印厂给你的那个(例如ECI的PSOcoated_v3.icc)。它的字节像字体一样另行存储;renderToPdf通过outputProfile选项接收这些字节。registryName写作输出意图的条件标识符。 |
renderingIntent | 'relative' | 'perceptual' | 'relative' | 相对比色让印刷机能印出的颜色保持准确,其余颜色裁切到最接近的可印刷颜色;可感知压缩整个色域,让色域外的颜色保持彼此之间的关系。 |
blackPointCompensation | boolean | true | 采用相对比色意图时,把屏幕上的黑映射到印刷机能印出的最深的黑,使最暗的色调保留细节,不会糊成一片。 |
convertImages | boolean | true | 用特性文件把RGB图片分色为CMYK。PDF/X-1a总是这样做。在PDF/X-4中,false保留RGB图片,通过页面的/DefaultRGB标为sRGB,交给印厂的RIP转换。CMYK和灰度JPEG总是原样嵌入。 |
inkLimit | number | 取特性文件的值 | 印前检查接受的C+M+Y+K最大总量,以百分比计。默认取特性文件分色时的上限(大多数胶印条件为300%,IFRA26新闻纸为230%)。 |
black | PrintBlackConfig | 见黑色 | 只用K的灰色、叠印和复合黑。 |
preflight | PrintPreflightConfig | 见印前检查 | 印前检查查哪些内容,以及它的阈值。 |
print: {
standard: 'pdfx4',
outputProfile: 'fogra51',
black: { richBlackColor: { c: 60, m: 40, y: 40, k: 100 } },
preflight: { minImageResolution: 300, safeZone: { value: 5, unit: 'mm' } },
}#输出特性文件
postext在它的icc/文件夹中附带以下CMYK输出特性文件(在任何npm CDN上是postext/icc/<id>.icc,在postext.dev上是/icc/<id>.icc)。它们都没有已知的版权限制(CC0):FOGRA、GRACoL、SWOP和新闻纸特性文件来自colord,由各印刷条件的特性化数据生成;FOGRA51和FOGRA52由postext用ArgyllCMS根据Fogra自己的数据制作。ECI的特性文件(ISO Coated v2、PSO Coated v3、PSO Uncoated v3)描述的是相同的印刷条件,但不允许再分发;如果印厂要求用它们,请作为自定义特性文件上传。
| Id | 印刷条件 | 注册名称 | 墨量上限 |
|---|---|---|---|
fogra39 | 胶印,涂布纸(ISO Coated v2条件) | FOGRA39 | 300% |
fogra51 | 胶印,高级涂布纸(PSO Coated v3条件) | FOGRA51 | 300% |
fogra52 | 胶印,无木浆非涂布纸(PSO Uncoated v3条件) | FOGRA52 | 300% |
fogra47 | 胶印,白色非涂布纸(PSO Uncoated ISO 12647) | FOGRA47 | 300% |
fogra29 | 胶印,白色非涂布纸 | FOGRA29 | 300% |
fogra30 | 胶印,偏黄非涂布纸 | FOGRA30 | 340% |
fogra27 | 胶印,涂布纸(ISO 12647-2:1996) | FOGRA27 | 300% |
fogra28 | 热固轮转胶印,光面LWC纸 | FOGRA28 | 300% |
fogra45 | 热固轮转胶印,改良LWC纸 | FOGRA45 | 300% |
fogra40 | 热固轮转胶印,SC纸 | FOGRA40 | 340% |
gracol2006 | GRACoL 2006,1级涂布纸 | CGATS TR 006 | 300% |
swop3 | SWOP 2006,3级涂布纸 | CGATS TR 003 | 300% |
swop5 | SWOP 2006,5级涂布纸 | CGATS TR 005 | 300% |
ifra26 | 冷固新闻纸(ISO 12647-3) | IFRA26 | 230% |
snap2007 | SNAP 2007新闻纸 | CGATS TR 002 | 320% |
renderToPdf从它的outputProfile选项读取特性文件的字节;没有给出时,从profileBaseUrl(默认https://cdn.jsdelivr.net/npm/postext/icc/)下载目录中的文件。PDF/X渲染无法加载特性文件时会失败;普通的CMYK渲染则退回教科书公式,并给出outputProfileUnavailable警告。
import { readFile } from 'node:fs/promises';
import { renderToPdf } from 'postext-pdf';
const pdf = await renderToPdf(doc, {
fontProvider,
print: { standard: 'pdfx1a', outputProfile: 'fogra39' },
outputProfile: await readFile('node_modules/postext/icc/fogra39.icc'),
});#PDF/X-1a与PDF/X-4
两种标准都会写出:
- 输出意图(
GTS_PDFX),写明印刷条件并嵌入目标特性文件; - Info字典中的标识(
GTS_PDFXVersion、/Trapped /False、标题和日期)和XMP元数据中的标识(pdfxid:GTSPDFXVersion、文档id和版本id);文件带标签时,与PDF/UA标识合并; - 每一页的TrimBox和BleedBox(没有裁切线时就是整个页面);
- 文件尾的
/ID; - 经特性文件转换为DeviceCMYK(或灰度)的所有颜色,以及用套准色绘制的裁切标记;
- 不含链接注释:交付印刷的文件在出血框内不带任何注释,所以屏幕PDF中的链接被略去(书签保留)。
PDF/X-1a:2003是不带对象流的PDF 1.4,不允许透明:半透明的颜色换算成它印在纸上的颜色,图片的alpha拼合到白色上,调试用的页面负片被略去(并给出pageNegativeIgnored警告)。PDF/X-4是PDF 1.6:保留透明,每页带一个在CMYK中混合的透明组,因convertImages: false而保留的RGB图片通过/DefaultRGB标为sRGB。
PDF印刷母版(svg.pdfFileId)原样嵌入,所以它的颜色、字体和透明都是它自己的;印前检查会报告它带进来的内容。
#黑色
interface PrintBlackConfig {
kOnlyNeutrals?: boolean; // 灰色和黑色只用黑墨。
overprint?: boolean; // 100% K叠印。
richBlack?: boolean; // 大面积黑色用复合黑。
richBlackColor?: CmykPercent; // { c, m, y, k },百分比。
richBlackMinSize?: Dimension; // 区域短边需达到的尺寸。
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
kOnlyNeutrals | boolean | true | 中性色(#000000、#808080…)只用黑墨印刷,K值按明度匹配选取,绝不印成随套准偏移的四色灰。图片保留特性文件自己的黑版生成方式。 |
overprint | boolean | true | 只用100% K绘制的内容(黑色文字、线条、描边、小的黑色图形)叠印(op/OP加OPM 1),印版在印刷机上偏移时,周围不会露出白边。其他内容都镂空;图片和渐变从不叠印。 |
richBlack | boolean | true | 短边达到richBlackMinSize的黑色填充(背景、色带、方框)用richBlackColor印刷并镂空,看起来是深黑而不是深灰。文字从不变成复合黑。 |
richBlackColor | CmykPercent | | 复合黑配方,以百分比计。总量要低于墨量上限;印前检查会检查这一点。 |
richBlackMinSize | Dimension | 6mm | 黑色区域的短边达到这个尺寸,才用复合黑印刷。 |
#以CMYK定义的颜色
用CMYK写的颜色保留准确的数值:印刷渲染原样使用ColorValue.cmyk(百分比),hex是它在屏幕上的呈现。以CMYK定义的调色板条目,作用于所有链接到它的颜色。
colorPalette: [
{ id: 'brand', name: 'Brand', value: { hex: '#00a0e3', model: 'cmyk', cmyk: { c: 100, m: 0, y: 0, k: 0 } } },
],#印前检查
interface PrintPreflightConfig {
enabled?: boolean;
minImageResolution?: number; // 印刷尺寸下的ppi。
criticalImageResolution?: number;
minRuleWidth?: Dimension;
smallTextSize?: Dimension;
safeZone?: Dimension;
bleedSnap?: Dimension;
checkFonts?: boolean;
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 运行检查。 |
minImageResolution | number | 300 | 放置的位图在印刷尺寸下(含裁剪)每英寸像素数低于此值时给出警告,包括图、表格单元格中的图片、版面设计图像和漫画分格。没有自身分辨率的位图按自然尺寸以page.dpi印刷,所以以150 dpi排版的页面上,每张这样的图片都以150 ppi印刷;设了分辨率(bitmap.resolution、layout.bitmapResolution)的位图按自然尺寸以该分辨率印刷。 |
criticalImageResolution | number | 150 | 低于此值时,警告为严重。不会高于minImageResolution。 |
minRuleWidth | Dimension | 0.25pt | 比此值更细的线条、边框、栏线、表格线和漫画分格边框。 |
smallTextSize | Dimension | 9pt | 小于此字号、用多于一种墨印刷的文字(印刷色、复合黑):印版偏移时会发虚。只用K的黑色文字从不计入。 |
safeZone | Dimension | 5mm | 离裁切线比此值更近的文字,裁切时可能被切掉。 |
bleedSnap | Dimension | 3mm | 停在离裁切线这么近处、却没有延伸到出血的方框或图片:要么延伸到出血,要么内移。 |
checkFonts | boolean | true | 报告置入的PDF中未嵌入的字体(沙盒用inspectPrintMaster检查每个印刷母版)。Postext自己排的字体都会嵌入。 |
preflightDocument(doc, options)对排好的文档运行这些检查,返回一个问题列表。每个问题带有kind、severity('critical'、'warning'或'info')、全书范围的页序pageIndex、出问题的rect(页面px),以及元素有源码范围时的源码范围。kind有lowImageResolution、declaredPixelsMismatch、rgbImage、thinRule、smallProcessText、inkLimit、safeZone和nearTrim。没有transform时,中性色算一种墨,其他颜色算三种,也不检查总墨量;给出transform时,墨数和总墨量都是准确的。图片的分辨率按资源声明的像素计算;imageSize(fileId)给出文件本身的像素时(对字节调用bitmapInfo,或取自解码后的图像),则按文件的像素计算:声明与文件相差超过一个像素时,报告一次declaredPixelsMismatch,声明的像素多于文件实有的像素时为警告。placedImageResolutions(doc, { resources, imageSize })列出每张放置的位图及其有效ppi,不论印前检查是否开启。
import { bitmapInfo, outputTransform, parseIccProfile, preflightDocument, resolvePrintConfig } from 'postext';
import { inspectPrintMaster } from 'postext-pdf';
const print = resolvePrintConfig(config.print);
const transform = outputTransform(parseIccProfile(fogra51Bytes), { intent: print.renderingIntent });
const issues = preflightDocument(doc, {
print,
transform,
resources, // 位图的像素尺寸
imageSize: (fileId) => bitmapInfo(bytesOf(fileId)), // 文件的实际像素
imageColor: (fileId) => colourOf(fileId), // 'rgb' | 'cmyk' | 'gray',取自文件
});
if (issues.some((i) => i.severity === 'critical')) process.exit(1);
const master = await inspectPrintMaster(masterBytes);
// => { nonEmbeddedFonts: ['Helvetica'], rgb: true, transparency: false }#印刷预览
Canvas页面可以按印出来的样子绘制。createPrintPreview(transform, print, { paper, dpi })为一套设置构建软打样:每个像素都经特性文件分色(中性色只用K,与PDF相同),再显示回屏幕上;paper为true时,显示在纸张自身的白色上;大到足以用复合黑的黑色区域显示为复合黑。把它作为printPreview交给renderPageToCanvas;guides加上成品线、出血线和安全区线,marksFor勾出页面上的区域(印前检查给出的rect)。postext-folio接受同一个对象作为printPreview(paper: false,因为书本身的纸色会给书页着色)。
import { createPrintPreview, renderPageToCanvas } from 'postext';
const preview = createPrintPreview(transform, print, { paper: true, dpi: doc.config.page.dpi });
renderPageToCanvas(page, doc, canvas, {
printPreview: { ...preview, guides: { safeZonePx: 59 }, marksFor: () => issues.map((i) => i.rect!).filter(Boolean) },
});#色彩引擎
postext使用的色彩管理也已导出,可供你自己的工具使用。它用纯TypeScript读取ICC v2和v4特性文件(矩阵/TRC,以及mft1、mft2、mAB、mBA查找表),不用WebAssembly。
parseIccProfile(bytes)读取特性文件;deviceChannels(profile)给出它的通道数。outputTransform(profile, { intent, blackPointCompensation, preserveNeutrals })返回fromRgb(r, g, b)(sRGB 0..1 → CMYK 0..1)、toLab(cmyk, paper?)和proof(cmyk, paper?)(CMYK → 屏幕sRGB)。cmykToLab、labToCmyk、srgbToLab、labToSrgb、deltaE和totalAreaCoverage是单项转换;buildRgbLut/sampleRgbLut生成和读取用于像素处理的密集查找表。OUTPUT_PROFILES、outputProfileInfo(id)和loadOutputProfile(id, baseUrl?)提供特性文件目录;srgbProfileBytes()写出PDF/X-4标记RGB时所用的sRGB特性文件;authoredCmykColors(config)列出配置中以CMYK定义的颜色。
解析函数和精简函数与其他部分一致:resolvePrintConfig、stripPrintDefaults和profileInkLimit(config)(配置所指特性文件的上限,不计inkLimit覆盖),以及DEFAULT_PRINT_CONFIG、DEFAULT_PRINT_BLACK_CONFIG、DEFAULT_PRINT_PREFLIGHT_CONFIG和DEFAULT_RICH_BLACK。
#书页视图(配置)
folio属性决定书页视图(postext-folio)怎样以3D呈现印好的书:视角、纸张、装订、书下面的台面和光照。排版不读取它,Canvas、HTML和PDF输出也不读取。配置设置了其中任何一项时,buildDocument把解析后的设置放进VDT,即doc.config.folio;不设置的文档,其排版哈希保持不变。
interface FolioConfig {
tilt?: number; // Degrees from straight above, 0–70.
yaw?: number; // Degrees round the book, −180–180.
paper?: {
type?: 'uncoated' | 'bookWove' | 'coatedMatte' | 'coatedSilk' | 'coatedGloss'
| 'bible' | 'newsprint' | 'cardStock' | 'board';
grammage?: number; // g/m²
bulk?: number; // cm³/g; caliper µm = grammage × bulk
finish?: 'auto' | 'uncoated' | 'matte' | 'silk' | 'gloss';
texture?: 'auto' | 'smooth' | 'vellum' | 'wove' | 'laid' | 'linen' | 'felt';
textureStrength?: number; // 0–2
shade?: ColorValue;
showThrough?: boolean;
};
binding?: {
type?: 'hardcover' | 'paperback' | 'sewn' | 'layflat' | 'saddleStitch' | 'folded';
cover?: 'case' | 'pages';
coverMaterial?: 'auto' | 'cloth' | 'paper' | 'leather';
coverColor?: ColorValue;
spineImage?: string; // resource id
};
surface?: {
type?: 'oak' | 'walnut' | 'linen' | 'felt' | 'leather' | 'marble' | 'plain' | 'none';
color?: ColorValue;
};
lighting?: {
environment?: 'studio' | 'daylight' | 'lamp' | 'overcast' | 'night';
intensity?: number; // 0.25–2
shadows?: boolean;
};
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tilt | number | 22 | 视线偏离正上方的角度(度),限制在0–70。0度时从正上方俯视打开的书;角度越大,页面下端越靠近,书芯的厚度越明显。 |
yaw | number | 0 | 视角绕书转动的角度(度),换算到−180–180。0度时从页面下端看书;正角度使视线转到书的右侧,负角度转到左侧。它与tilt一起决定查看器打开时的视角,也是resetView()恢复到的视角。 |
paper.type | FolioPaperType | 'uncoated';报纸开本为'newsprint' | 纸种。下面五个字段的默认值由它决定(见纸种表)。cardStock是封面卡纸;board是硬纸板,如纸板书,翻页时不弯曲。 |
paper.grammage | number | 随纸种 | 克重,单位为克每平方米,20–2500。克重越大,纸越厚、越挺、越不透明:翻页的弧度更大,背面透得更少。 |
paper.bulk | number | 随纸种 | 松厚度:单位重量的厚度,cm³/g,0.5–3。单张纸厚(微米)= 克重 × 松厚度,书芯厚度由它和页数决定。 |
paper.finish | FolioPaperFinish | 'auto' | 非涂布(可见纤维,无光泽),或涂布后压光为哑光、丝光(柔和光泽)或光面。在 Folio 中,光面纸页会映出从上方翻过的书页。'auto'取纸种的设置。 |
paper.texture | FolioPaperTexture | 'auto' | 纸面的凹凸:smooth(平滑,经压光)、vellum(细颗粒)、wove(大多数书纸的均匀纹理,在编织铜网上成形)、laid(罗纹:细密的帘纹与较疏的链线交叉,由水印辊压出)、linen(压出的麻布纹)、felt(毛毯留下的不规则痕迹)。'auto'取纸种的设置。 |
paper.textureStrength | number | 1 | 纹理在光线下的明显程度,0–2。 |
paper.shade | ColorValue | 随纸种 | 印刷前的纸色(白、本白、米黄)。页面印在这一底色上。 |
paper.showThrough | boolean | true | 薄纸上淡淡透出背面的内容。除字典纸外,新闻纸透得最明显:油墨会渗进纸里。 |
binding.type | FolioBindingType | 'hardcover';报纸开本为'folded' | hardcover:精装,硬纸板封面略大于书页。paperback:无线胶装(铣背上胶),打开不够平。sewn:锁线平装。layflat:平摊装,书脊处不下陷。saddleStitch:骑马钉,折好的纸张在折缝处装订,如杂志或小册子,没有平书脊。folded(postext 1.18起):报纸,纸张对折一次,一张套一张,没有任何东西固定;没有订书钉、没有书脊、没有封面板,第一页就是头版。带shade的:::paper段可以把某一版(例如财经版)印在鲑鱼色新闻纸上。 |
binding.cover | FolioCoverSource | 'case' | 封面来源。'case' 在书页外画出书壳;'pages' 把全书第一页当作前封面板、最后一页(左页时)当作后封面板:翻开封面之前书是合着的,封面板硬挺地翻动,不再另画书壳。 |
binding.coverMaterial | FolioCoverMaterial | 'auto' | 'auto':精装用布面,其他装订用卡纸('paper')。 |
binding.coverColor | ColorValue | 深蓝(#2c3e57) | 封面材料的颜色。 |
binding.spineImage | string | 无 | 印在书脊上的位图或SVG资源的id:书立起来时看到的书脊,书头朝上、封面在右。图片缩放到铺满书脊并居中。骑马钉和对折装订不使用。 |
surface.type | FolioSurfaceType | 'oak' | 书放在什么上面。'none'保留宿主页面的背景。 |
surface.color | ColorValue | 无 | 为台面着色;'plain'时即台面颜色。 |
lighting.environment | FolioEnvironment | 'studio' | 涂布纸和光面纸反射的周围环境,以及投下阴影的主光。 |
lighting.intensity | number | 1 | 曝光,0.25–2。 |
lighting.shadows | boolean | true | 主光投下的阴影。 |
各纸种及其提供的值(FOLIO_PAPER_STOCKS),取自造纸厂技术参数表的常见数值:
| 纸种 | 克重 | 松厚度 | 纸厚 | 表面处理 | 纹理 | 纸色 |
|---|---|---|---|---|---|---|
uncoated (胶版纸) | 90 g/m² | 1.25 | 113 µm | 非涂布 | 均匀 | #fcfbf8 |
bookWove (米黄书纸,高松厚) | 80 g/m² | 1.6 | 128 µm | 非涂布 | 均匀 | #f6efdc |
coatedMatte (哑粉纸) | 115 g/m² | 1.0 | 115 µm | 哑光 | 平滑 | #fdfdfc |
coatedSilk (丝光铜版纸) | 115 g/m² | 0.9 | 104 µm | 丝光 | 平滑 | #ffffff |
coatedGloss (光面铜版纸) | 115 g/m² | 0.8 | 92 µm | 光面 | 平滑 | #ffffff |
bible (字典纸) | 40 g/m² | 1.1 | 44 µm | 非涂布 | 细颗粒 | #f9f6ee |
newsprint (新闻纸) | 48 g/m² | 1.5 | 72 µm | 非涂布 | 均匀 | #ebe7dc |
cardStock (卡纸) | 250 g/m² | 1.2 | 300 µm | 非涂布 | 细颗粒 | #fbfaf6 |
board (纸板) | 1250 g/m² | 1.6 | 2000 µm | 丝光 | 平滑 | #ffffff |
一本印在米黄书纸上、无线胶装的小说,放在胡桃木桌上,台灯照明:
folio: {
paper: { type: 'bookWove' },
binding: { type: 'paperback', coverColor: { hex: '#8a2b1f', model: 'hex' } },
surface: { type: 'walnut' },
lighting: { environment: 'lamp' },
}页面采用报纸开本(page.sizePreset为'broadsheet'、'berliner'、'tabloid'或'compact')时,如果配置既没有指定纸种,也没有指定装订方式,就显示为一份报纸:纸张为newsprint,装订为folded(postext 1.18起)。配置指定的纸种或装订方式照样保留,所以paper: { type: 'uncoated' }会把小报印在胶版纸上。不指定纸种而设置的纸张字段(如grammage、shade)作用于新闻纸。解析函数和精简函数以开本为第二个参数,folioForTrim(folio, sizePreset)则把这两个默认值写进配置:
resolveFolioConfig({ tilt: 30 }, 'tabloid');
// => { tilt: 30, paper: { type: 'newsprint', grammage: 48, bulk: 1.5, … }, binding: { type: 'folded', coverMaterial: 'paper', … }, … }
stripFolioDefaults({ paper: { type: 'newsprint' }, binding: { type: 'folded' } }, 'tabloid');
// => undefined这些颜色和配置中的其他颜色一样跟随调色板链接(paletteId)。解析函数和精简函数与其他部分相同;精简函数会去掉与所选纸种相同的纸张值:
import { FOLIO_PAPER_STOCKS, DEFAULT_FOLIO_CONFIG, resolveFolioConfig, stripFolioDefaults } from 'postext';
resolveFolioConfig({ paper: { type: 'bible' } }).paper;
// => { type: 'bible', grammage: 40, bulk: 1.1, finish: 'uncoated', texture: 'vellum', textureStrength: 1, shade: { hex: '#f9f6ee', … }, showThrough: true }
stripFolioDefaults({ paper: { type: 'bible', grammage: 40 } });
// => { paper: { type: 'bible' } }在沙盒中,这些设置是版面设计面板中的Folio组(版面设计 → Folio → 书页视图(3D))。修改时书页标签页会随即显示效果,不必重新排版。查看器本身见3D书本;要让一段页面换用另一种纸,见文档格式 › :::paper。
#调试
debug属性包含两类写作辅助:一是让源文本与渲染版面保持同步的可视叠加层,二是一组警告,在沙盒的检查面板中指出排版或结构上的问题。两者都不影响导出结果。
| 属性 | 类型 | 说明 |
|---|---|---|
cursorSync | SyncIndicatorConfig | 映射到渲染版面中的光标,见可视叠加层。 |
selectionSync | SyncIndicatorConfig | 在页面上高亮源文本中的选区,见可视叠加层。 |
looseLineHighlight | LooseLineHighlightConfig | 覆盖在稀松的两端对齐行上的叠加层,见可视叠加层。 |
pageNegative | | 页面的高对比度负片,见可视叠加层。 |
warnings | WarningsToggleConfig | 编辑器中每类写作警告各对应一个布尔值,见警告。 |
#可视叠加层
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cursorSync.enabled | boolean | true | 在渲染版面中显示一个光标,对应源文本中光标的位置。 |
cursorSync.color | ColorValue | #2563eb | 该光标的颜色。 |
selectionSync.enabled | boolean | true | 高亮渲染结果中与源文本选区对应的范围。 |
selectionSync.color | ColorValue | #fde04780 | 高亮的颜色,默认为半透明黄色。 |
looseLineHighlight.enabled | boolean | false | 在词间距超过正常空格宽度threshold倍的两端对齐行上绘制叠加层。 |
looseLineHighlight.color | ColorValue | #ff000040 | 该叠加层的颜色。 |
looseLineHighlight.threshold | number | 3 | 正常空格宽度的倍数,两端对齐行的空格超过它就算作稀松行。looseLines警告使用同一阈值。空格需要拉伸到3倍以上的两端对齐行会改排为齐左,所以在默认值下,叠加层和警告在连续正文中几乎找不到什么;把它调低(1.5或2),就能看到稀松但仍两端对齐的行。 |
pageNegative.enabled | boolean | false | 在页面上渲染一层高对比度负片,用来一眼检查跨页的整体形态(文字密度、各栏是否齐底、留白),不受字形细节干扰。 |
每个SyncIndicatorConfig都是{ enabled: boolean; color?: ColorValue }。LooseLineHighlightConfig是{ enabled: boolean; color?: ColorValue; threshold?: number }。pageNegative是一个最简的{ enabled: boolean }开关。
debug: {
cursorSync: { enabled: true, color: { hex: '#ff0066', model: 'hex' } },
selectionSync: { enabled: false, color: { hex: '#fde04780', model: 'hex' } },
looseLineHighlight: { enabled: true, color: { hex: '#ff000040', model: 'hex' }, threshold: 3 },
pageNegative: { enabled: true },
}这些叠加层由沙盒绘制在它的Canvas预览之上,不属于页面:renderPage、HTML输出和PDF都不会绘制它们。
#在你自己的Canvas中标出稀松行
引擎把稀松行高亮导出为两个辅助函数,供你在自己绘制的页面上使用:
import { buildDocument, renderPageToCanvas, drawLooseLines, findLooseLines } from 'postext';
const doc = buildDocument(content, config);
const canvas = document.querySelector('canvas')!;
renderPageToCanvas(doc.pages[0], doc, canvas, { scale: 0.5 });
drawLooseLines(canvas.getContext('2d')!, doc.pages[0], doc, { threshold: 2.5 });
// 同样的行也可以作为数据使用:生成报告、SVG叠加层、按页计数。
for (const { ratio, line, block } of findLooseLines(doc, { threshold: 2.5 })) {
console.log(`page ${block.pageIndex + 1}: ${ratio.toFixed(2)}× — ${line.text}`);
}findLooseLines(doc, { threshold?, pageIndex? })按阅读顺序返回每一个justifiedSpaceRatio超过threshold的两端对齐行:{ block, line, ratio, x, y, width, height }。这个矩形以页面像素计,就是高亮覆盖的横条:在该行的高度上横跨整个块宽。沙盒高亮的、并在检查面板中报告为looseLine的正是这些行。drawLooseLines(ctx, page, doc, { threshold?, color? })在一页上填充这些横条,并返回它绘制的行。它在上下文当前的变换下以页面像素绘制,所以要在同一个canvas上紧接着renderPage或renderPageToCanvas调用它:这两个函数都会让上下文保持按页面缩放。color可以是任何canvas填充样式。- 默认值。两个辅助函数都使用默认阈值(3)和默认颜色(
#ff000040),而不是文档的debug.looseLineHighlight:那项设置属于沙盒。要遵循某个配置,传入resolveDebugConfig(config.debug).looseLineHighlight.threshold和.color.hex。
#警告
debug.warnings控制哪些写作问题出现在沙盒的检查面板中(在版面设计 → 高级 → 警告中编辑)。每个键都是独立的布尔开关;把某个键设为false,就只关闭这一条警告,其他警告不受影响。
这些开关只过滤沙盒的面板。引擎自己记录的警告,即doc.warnings中溢出所在栏的框,doc.contentWarnings中未知的资源id、指令、嵌入和样式id以及不规则的表格网格,无论开关如何设置都会存在;渲染器也会报告它们以占位图绘制的图像。见文档中的警告。
interface WarningsToggleConfig {
missingFont?: boolean;
looseLines?: boolean;
headingHierarchy?: boolean;
consecutiveHeadings?: boolean;
listAfterHeading?: boolean;
designIssues?: boolean;
}| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
missingFont | boolean | true | 配置引用的字体在浏览器中加载失败时报告。它能及早发现fontFamily中的拼写错误和缺失的@fontsource/...包,免得它们在渲染结果中悄悄变成后备字体替换。 |
looseLines | boolean | true | 报告词间距超过debug.looseLineHighlight.threshold的两端对齐行。它与叠加层配合使用:警告在面板中逐条列出,叠加层在原位显示。 |
headingHierarchy | boolean | true | 报告跳级的标题层级,例如H1后面直接跟H3。标题结构上的断层通常说明标题层级写错了,或者对文档大纲的理解有误。 |
consecutiveHeadings | boolean | false | 一个标题后面紧跟另一个标题、中间没有段落或列表时报告。默认关闭,因为在许多模板中连续的标题是正当的(标题 + 副标题,章 + 题词);如果文稿中每个标题都应引出正文,就打开它。 |
listAfterHeading | boolean | false | 列表紧跟在标题之后、没有引导段落时报告。默认关闭,因为参考资料经常这样写;在每个列表都应由正文引出的叙述性写作中打开它。 |
designIssues | boolean | true | 报告版面设计位置中的完整性问题,包括页眉、页脚、篇名页及其后的空白左页、目录中的篇条目、标题的高级设计位置,以及每个标题样式的版面设计和分节书眉。涵盖循环的锚点链、悬空的锚点引用(元素锚定到已不存在的#id)、breakBefore被禁用的通栏标题,以及已启用但其元素从不渲染的高级设计。 |
debug: {
warnings: {
missingFont: true,
looseLines: true,
headingHierarchy: true,
consecutiveHeadings: true,
listAfterHeading: false,
designIssues: true,
},
}除此之外,面板始终会列出版面本身产生的警告(VDTDocument.warnings),例如溢出所在栏的标注框(calloutOverflow),以及引擎替换掉的配置值(VDTDocument.configWarnings或collectConfigWarnings(config);见下文的配置警告)。
#配置警告
配置本身的八类错误从不静默,也没有开关能隐藏它们。引擎不会因此失败:它会替换一个值,或略去该设置,并给出说明:
- 未知编号格式:有序列表的
numberFormat、page.pageNumbering.format或资源类型的counterFormat不属于任何一种编号格式写法。此时按十进制编号。 - 字体家族中写了字体栈:
fontFamily(或任何…FontFamily)中写了CSS字体栈。文字用字体栈中的第一个字体家族排(见每个fontFamily只写一个字体家族)。 - 侧栏没有空间:
'oneAndHalf'版式的sideColumnPercent(文档的,或标题样式自己的layout中的)会使其中一栏不足内容宽度的1%,或者不是数字。各栏按两者都能接受的最接近的值截取,used给出该值(sideColumnPercentClamped;见'oneAndHalf'版式)。 - 栏数超出范围:
'multiple'版式的columnCount(文档的,或标题样式自己的layout中的)不是3到8之间的整数。页面按最接近的有效栏数分栏(不是数字时为3),used给出这个数(columnCountClamped;见'multiple'版式)。 - 字符网格过大:
cjk.grid的每行字数或每页行数超出了页边距内能容纳的数量。网格按能容纳的最大数量排,used给出这个数(cjkGridClamped;见字符网格)。 - 未知标题设置:
headings、headings.balancing、某个标题级别、标题样式或段落样式中没有的键:拼错的letterSpacng、从其他工具借来的tracking、标题样式上的level、段落样式上的fontStyle: 'italic'(段落样式要用italic: true)。制表位也同样检查(用leaders代替leader)。引擎会忽略它(postext 1.4及以前是悄无声息地忽略)。value是这个键,used为空,如果某个设置与它只差一两个字母或只有大小写不同,suggestion会给出最接近的那个设置(unknownConfigKey)。 - 未知设置值:只能取几个固定词的设置写了别的值,例如
direction: 'right'(可取auto、ltr或rtl)。引擎改用默认值,used给出实际结果:对direction而言,是文档语言的书写方向(unknownConfigValue)。制表位的align不是它的四个取值之一时,按'start'读取;position既不是长度、'end'也不是百分比时,略去该制表位(used为'none')。漫画设置中的词同样会检查(漫画 › 漫画警告):used是该设置最终解析出的值(对内置样式而言,是该对白框样式自己的默认值),值与某个词接近时,suggestion给出最接近的那个词。 - 竖排文字中的行号:竖排文档(
layout.writingMode: 'vertical-rl')中写了lineNumbers.enabled: true。竖排页面不标行号,used为false(lineNumbersUnsupported;见行号)。 - 竖排中的文字绕排 — 竖排文档中资源类型的
defaultPlacement.wrap。竖排页面不在图旁排正文,used为none(wrapUnsupported;见文档格式 › 文字绕排)。不指明一侧的wrap是未知的设置值。
沙盒在检查面板中列出这些警告,并附上设置的路径。在代码中,buildDocument把它们作为configWarnings放在文档上(配置没有问题时不存在该字段),collectConfigWarnings(config)则不做排版直接返回它们:
import { buildDocument, collectConfigWarnings } from 'postext';
// 纯JavaScript:在TypeScript中,'roman'根本通不过类型检查。
const config = { bodyText: { fontFamily: 'EB Garamond, serif' }, orderedLists: { numberFormat: 'roman' } };
const doc = buildDocument({ markdown }, config);
doc.configWarnings;
// [{ kind: 'fontFamilyStack', path: 'bodyText.fontFamily', value: 'EB Garamond, serif', used: 'EB Garamond' },
// { kind: 'unknownNumberFormat', path: 'orderedLists.numberFormat', value: 'roman', used: 'arabic' }]
collectConfigWarnings(config); // 同一个列表所有嵌套的部分配置也会被检查:标题样式、篇内的列表、htmlViewer.overrides、设计元素。
formatWarning(见文档中的警告)同样会描述这些警告,并把设置的路径放在最前面,例如bodyText.fontFamily: font stack "EB Garamond, serif" — set in "EB Garamond"、headingStyles[0].letterSpacng: unknown setting "letterSpacng" — ignored (did you mean "letterSpacing"?),这样宿主程序可以用一个循环记录一次构建返回的全部三个列表。