章 9 · 篇 II · 工艺
漫画
Postext如何用Markdown排漫画和日本漫画:按切分树分格的漫画页,围绕安全区裁切的分格画面,由脚本生成的嵌字(对白、心声、耳语、喊叫、广播、旁白框、拟声词),对白框样式,画面上的说话人锚点,正文中的漫画条,双页跨页,从右到左阅读与竖排嵌字,翻译,以及Canvas、PDF、HTML和EPUB输出
简单来说
本页说明怎样用Postext做漫画。画就是分格,人物说的、想的和听到的一切都作为文字写在Markdown文件里。Postext把分格排到页面上,并自动围着文字画出对白框、旁白框和拟声词。翻译漫画时只需改写文字,Postext会用新的语言重新嵌字:日文竖排成列,阿拉伯文从右往左。本页还介绍书中的漫画条、横跨两个对页的跨页、各种输出以及尚存的限制。
画就是分格;说出的、想到的、旁白的和听到的一切,都是Markdown里的文字。
在Postext中,一部漫画由一组画和一份脚本组成。画是普通的位图或SVG资源,画面上不带任何文字。脚本写在章的Markdown里:一个:::page块把一页切成若干分格,给每一格指定画面,并逐行列出这一格里的对白框、旁白框和拟声词。Postext排出分格,把每幅画裁进它的格子而不切掉要紧的部分,用全书统一的嵌字字体和字号排出文字,围着文字画出对白框,让尾巴指向说话人,再按阅读顺序把对白框放在不挡脸的地方。译本是另一个Markdown文件,分格相同,文字不同;Postext从头为它重新嵌字,日文竖排成列,阿拉伯文从右往左。
漫画是可选功能:没有:::page或:::strip的文档,排版结果与以前完全相同。相关设置在配置中的comics一节,标记的摘要见文档格式 › 漫画。
#漫画页
一个:::page块就是一页漫画。它以:::page{…}一行开头,每个分格一行::panel,后面各跟这一格的脚本,最后以单独的:::结束:
:::page{split="30 [30 | 20 | *] / *" gutter=4mm}
::panel{art=lh-arrive}
caption: 每年夏天,玛雅都要到灯塔住上一个星期。
maya: 爷爷!我来啦!
::panel{art=lh-radio}
tomas: 来得正好。收音机哑了,
tomas: 暴风雨又要来了。
::panel{art=lh-maya}
maya{thought}: 那个阀门好像松了……
::panel{art=lh-beam bleed}
sfx{rotate=-8}: 咔啦
tomas{shout}: 抓紧栏杆!
:::这个块总是从新的一页开始,并独占这一页。结束围栏之后的文字从下一页开始,所以连续两个:::page块就是两页漫画,两者之间的:::pagebreak不会多出一页。引擎像读:::verse一样自己逐行读取块的内容:块内不识别Markdown块、容器和其他指令,<!-- 注释 -->会被忽略,跨多行的注释也一样。
页面的角色是'comic'(VDTPage.role)。漫画页全是分格,所以不印书眉和页码,但它计入页码编号;comics.runningHeads: true会把它们印出来。分格从画框中切出:画框就是页面边距以内的版心,除非comics.frame.margins为漫画页另设边距。
起始行的属性:
| 属性 | 作用 |
|---|---|
split="…" | 画框怎样切成格子(见分割页面)。未设置时,各分格按等高的行(一排分格为一行)自上而下堆叠。 |
gutter=4mm、gutter="4mm 2mm" | 行与行之间的距离;给出第二个值时,它是同一行内左右相邻分格之间的距离。不带单位的数字以毫米计。未设置时用comics.gutter(4 mm和2 mm)。 |
style=… | 本页所有分格使用的命名分格样式(comics.panelStyles)。 |
bleed | 贴着画框的每个分格都延伸到裁切线,并伸进出血区。 |
direction=rtl、dir=ltr | 本页的阅读方向,不管配置怎样设置(见阅读方向)。 |
spread | 把这一页铺在两个对页上(见跨页)。 |
#分割页面
split属性把页面描述成一棵切分树。用/分隔的一列尺寸把格子自上而下堆叠成行;用|分隔的一列让格子左右并排,从页面开始阅读的那一侧排起。尺寸后面跟一个方括号列表,表示沿另一个方向把这个格子再切一次。上例的页面30 [30 | 20 | *] / *是这样的:第一行占画框高度的30%,切成宽度为30%和20%的两格,以及占去其余宽度的第三格;下面的第二行占去页面的其余部分:
完整的语法:
split := list
list := item ( "/" item )* | item ( "|" item )*
item := size [ "[" list "]" ]
size := number ["%"] [ "~" number ["%"] ] | "*" | (nothing)
- 尺寸是父格子沿该列表方向的百分比;
30和30%相同。*或空着的尺寸平分写明的尺寸剩下的部分。列表的最后一项总是延伸到尽头,不管它写的尺寸是多少。 - 每个列表只用一种分隔符。 在同一个列表里混用
/和|、方括号没有闭合,或者方括号列表与父列表同向,都是语法错误(comicSplitSyntax):只采用能读懂的部分。 - 尺寸过大。 尺寸合计超过100%时按比例缩小,让每个
*保留5%,页面报告comicSplitOverflow。 - 阅读顺序就是树的顺序:各行自上而下,行内的分格从起始侧排起。
::panel行按这个顺序填入格子。分格比格子多时,多出的分格不排;比格子少时,最后几个格子留空(comicPanelCount,只在写了split时报告)。
几种切分:
| 切分 | 页面 |
|---|---|
* | 整页一格的大画面。 |
33 / 33 / * | 三行。 |
* / * / * / * | 四个等高的行,即四格漫画。 |
50 [* | *] / 50 [* | * | *] | 上面两格,下面三格。 |
30 / 35 [* | * | *] / * | 一个宽格,一行三格,再一个宽格。 |
60 | * [50 / *] | 起始侧一个高格,旁边上下叠着两格。 |
* [40~55 | *] / 35 | 被一条斜线切开的动作行,下面是高35%的一行。 |
#斜切
写成a~b的尺寸给出一条斜线:格子的远边从交叉轴起点处的a%延伸到另一端的b%。对于左右并排的分格,起点是顶端,所以40~55 | *画出一条在顶端位于宽度40%、在底端位于55%的线;对于行,起点是页面开始阅读的那一侧。尺寸在两端分别累加,格子始终是凸多边形。圆角只用于矩形分格;斜切出的分格保持尖角。
#格间距
格间距跨在每条切分线上,两侧各占一半,所以写在30%的线正好位于格子的30%处。斜线保持它所在方向的格间距,沿垂直于线的方向量取。默认值与常见的漫画页一致:行与行之间4 mm,同一行的分格之间2 mm(comics.gutter.horizontal和vertical,或页面的gutter属性)。
#出血
出血的分格越过画框:它贴着画框的每条边都向外移到裁切线,页面设有出血时(page.cutLines.bleed)再移到裁切线以外的出血位。页面的bleed属性让所有分格出血;分格自己的bleed只让这一格出血,bleed="top start"只让这几条边出血(top、bottom、start、end、left、right),bleed=false取消出血。设了bleed: true的分格样式让使用它的分格出血。
#内缩与嵌入格
pad让分格比它的格子小:一到四个值,顺序与CSS外边距相同,但用逻辑方向(上、末尾、下、起始),每个值是长度或格子的百分比。::panel{art=x pad="0 15%"}得到一个比所在行窄、在行中居中的分格。
写了inset="x y w h"的分格不占格子:它叠在前一个分格上,位置和大小以那个分格框的百分比表示,比如角上的一个小头像,或者广角画面上的一个特写。它的脚本嵌在它自己里面。
:::page{split="*"}
::panel{art=storm-wide}
caption{at=bottom-start}: 暴风雨在午夜来临。
::panel{art=maya-window inset="62% 6% 32% 30%"}
maya{whisper}: 爷爷?
:::#分格与画面
::panel{…}一行开始一个分格;它的脚本一直延续到下一个::panel行或结束围栏。没有art的分格用底色画成空格,纯文字的分格就是这样做的。第一个::panel之前的文字作为第一格的旁白框嵌字,并报告comicStrayText。
| 属性 | 作用 |
|---|---|
art=<id> | 分格的画面:一个位图或SVG资源的id。未知的id让分格留空,并报告comicUnknownArt。 |
fit=cover、fit=contain | cover裁切画面以填满格子,但绝不切进安全区;contain完整显示画面,空出的部分是分格底色的色带。未设置时用分格样式的值(cover)。 |
focus="70% 40%" | 安全区留有选择余地时,要保持居中的画面上的点。 |
style=… | 这一格使用的命名分格样式。 |
border=none、border=0.5mm | 不画边框,或者画这个粗细的边框。 |
bg=#1b1b2f | 分格底色:一个颜色、一个调色板id或none。 |
bleed、bleed="top start"、bleed=false | 见出血。 |
pad="…" | 见内缩与嵌入格。 |
inset="x y w h" | 把这一格叠在前一格上,而不给它格子。 |
pop=<id> | 画在边框之上的抠图(见出格画面)。 |
mirror、mirror=false | 左右翻转这一格的画面,或者在页面翻转画面时保持原画(见阅读方向)。 |
alt="…" | 分格的替代文字,用于带标签的PDF、HTML和EPUB。未设置时用资源的altText。 |
id=… | 分格的id,HTML和EPUB输出用它作锚点。 |
#把画面裁进格子
画分格画面时,并不知道它最后会落进什么形状的格子:页面可能被重新切分,分格线可能被拖动,译本可能需要更高的一行。画面的安全区(Resource.safeArea,见文档格式 › 安全区)说明哪些部分必须留在画面中,裁切按每个格子分别计算:
- 用
fit=cover时,可见窗口取格子的比例,并在画面允许的范围内尽量大。 - 窗口的位置要让安全区落在其中。还有余地时,分格设了
focus就以它为中心;否则安全区以外的余量按各自的大小成比例裁去,所以偏在中心左边的主体仍然偏在左边。 - 如果没有一个格子比例的窗口能装下安全区(很宽的格子配一个瘦高的主体),画面就完整显示安全区,格子的其余部分用分格底色填满,页面报告
comicPanelLetterbox。对于W × H的画面,只要格子的宽高比在sw·W/H和W/(sh·H)之间,安全区就能完整保留;sw和sh是安全区的宽度和高度所占的比例。
没有安全区的画面围绕中心裁切,或者围绕focus裁切。没有固有尺寸的SVG按原样填满格子。渲染器把绘制限定在格子的多边形内,按未裁切的尺寸画出整幅画面,所以裁切从不对图像重新采样。
#分格样式
分格的外观来自comics.panel(1 pt黑色边框、白色底、cover),来自页面或分格用style=选取的comics.panelStyles中的命名样式,以及分格自己的border和bg。边框可以是solid、none或rough:rough是略带抖动的手绘线,每次排版都一样,因为抖动以分格为种子生成。每个字段见配置 › 漫画。
#出格画面
破格,即人物或物体冲出分格的画法,需要两幅画:一幅是分格的画面,照常限定在格子内;另一幅是冲出去那部分的透明抠图,用pop指定。抠图的裁切和位置与画面完全相同(它应与画面图片尺寸相同),在边框之后绘制,不加限定,所以它盖住边框和格间距。
::panel{art=gull-sky pop=gull-cutout bleed="top"}
sfx{at="70% 20%"}: 嘎——#嵌字
每个::panel行下面是这一格的脚本,每行一个对白框:
键{属性}: 文字键是说话人的id(字母、数字、_、.和-),在每个译本中都相同;它与画面上的锚点和角色表对应。有三个键是保留的:caption(旁白框)、sfx(拟声词,没有框)和note(编者注)。冒号也可以是中文或日文输入法打出的全角:。文字是行内Markdown:*强调*和**加粗**都印成粗斜体,这是嵌字的惯例(没有斜体的文字只加粗),:tcy[12]在竖排的列中把数字横着排,:ruby把注音以一半字号排在基字上方(竖排时排在旁边),:ltr和:rtl让一段文字在相反方向的对白框里保持自己的方向。缩进两个空格或一个制表符的行接续上面的对白框(用一个空格连接,两个中文或日文字符之间不加空格);行末的反斜杠让对白框的文字在那里换行。空行被忽略;不是台词行的行作为旁白框嵌字,并报告comicStrayText。
::panel{art=lh-radio}
tomas: 来得正好。
收音机哑了,\
暴风雨又要来了。
biscuit{to="40% 70%"}:喵?
note: 电子管收音机要预热一分钟才出声。台词行的属性:
| 属性 | 作用 |
|---|---|
{whisper}、style=whisper | 对白框样式。花括号里的单个词就是样式名:thought、whisper、shout、radio、inner,或者书中自定义的样式。未知的名称报告comicUnknownBalloonStyle,这一行采用其键的样式。 |
at="62% 40%" | 把对白框的中心固定在分格画面的这个点上(分格没有画面时是格子上的点)。这个点以画面的比例表示,所以无论怎样裁切、怎样拖动分格线,它都留在画上的同一个位置。 |
at=top-start | 把对白框放在分格的一个角或一条边上:top-start、top-end、bottom-start、bottom-end、top、bottom。旁白框和注释紧贴边框。 |
to="40% 70%" | 让尾巴指向画面上的这个点,而不是说话人的锚点。尾巴转向这个点,并在到达之前停住,就像在嘴前停住一样。 |
tail=none、tail=start | 不要尾巴,或者让尾巴指向这一侧格外的说话人(top、bottom、start、end)。默认值是tail=auto。 |
join、join=false | 把这个对白框与同一说话人的前一个连起来,或者分开,不管comics.lettering.joinSameSpeaker怎样设置。 |
break | 允许对白框越过分格边框,伸进格间距或相邻的分格。这种越界是有意为之,不作为溢出报告;对白框在另一侧盖住的东西(一张脸、另一格的对白框)仍会报告,而且它的中心留在自己的分格内。 |
rotate=-8 | 把对白框或拟声词按顺时针旋转若干度。 |
skew=-8 | 让字形倾斜若干度(正值向前倾,与斜体相同;负值向后倾),在旋转之前应用:用于以透视角度看到的物体上的文字,如书封面上的书名、招牌。与rotate一起使用时,文字同时贴合物体的上边和两侧。对白框样式可用skew为其所有行设定。 |
size=1.5 | 按比例放大或缩小拟声词的字。 |
color=#c0392b | 嵌字的颜色:十六进制颜色或调色板id。 |
font="…" | 这一行的字体。 |
vertical、horizontal、mode=vertical | 这一行的书写方向,优先于全书的设置:vertical在横排的版本中把它排成一列(比如不翻译的ドン),horizontal在竖排的版本中把它排成一行(比如横写的招牌)。在另一种语言的书中排成的列,遵循它自身文字的规则(含假名时按日文规则),并采用竖排嵌字的行距。在竖排的书中,不含中文或日文字符的行本来就横排,除非写明vertical。 |
#对白框怎样生成
嵌字遵循漫画嵌字的习惯,而不是文本框的习惯:
- 全书一个字号。 每个对白框都用
comics.lettering.fontFamily、以comics.lettering.fontSize(默认7.5 pt)排字,只按样式的fontScale缩放(耳语0.9,喊叫1.15)。文字从不为了塞进对白框而缩小:放不下的对白框会改变形状、移动位置、越过边框,或者被报告出来。 - 先排字,后画框。 先把文字断成行,目标是菱形:上下两端的行短,中间的行最长,最后一行不比其他行短太多;有同样好的断点时,不让行停在冠词或介词上。然后围着这些行画出轮廓,四周留出一样的空白。
- 排印规则。 三个点变成省略号;
textTransform: 'uppercase'把文字排成大写(没有大小写的文字保持原样,德文的ß变成SS);日文和中文去掉对白框末尾的句号(dropFinalStop),竖排的列中ASCII的!和?变成全角形式,两个连用时排在一个字格里(!?排成⁉);doubleDash像美国漫画那样把破折号写成--。 - 位置。 一格中的对白框按阅读顺序放在分格内,与边框保持
comics.lettering.inset的距离(1.5 mm)。第一个放在起始一侧靠上的位置,之后的每一个不应比前一个高,除非它在阅读方向上更靠后。对白框避开画面标出的脸和避让区,避开说话人的嘴,也互相避开;它们放在说话人的上方或旁边而不是下方,尾巴尽量不交叉。先放指定了角的旁白框和注释,再放固定了位置的对白框,最后放其余的。放置是确定的:同一页总是得到同样的对白框。 - 不出纸面。 嵌字从不越过裁切线。在出血的分格中,对白框留在分格落在画框以内的部分,裁切碰不到那里;只有用
at=固定或标了break的拟声词,可以伸进它所在分格旁边的出血位。 - 放不下时。 一个对白框无法干净地放下时,先越过边框(仅在有
break时),再盖住避让区(从不盖脸),然后换成更宽或更窄的形状。窄格中的长台词排得差不多与格子一样宽,伸进格间距,而不是排成细长的一条压在说话人的脸上。仍然与什么重叠时,就放在损害最小的地方,页面报告comicBalloonOverflow,指明是哪一格、盖住了什么。用break或at=固定所要求的越界不算在这些问题里:这时警告只指明对白框盖住了什么。缩短台词、放大分格或者用at=固定位置都能解决。
#尾巴
对白的尾巴指向说话人的嘴,心声的气泡指向头部(见说话人与锚点)。尾巴从轮廓上朝向说话人的地方伸出,略带弯曲,在嘴前停住。说话人的锚点被裁到分格之外,或者画面没有为这个说话人标锚点时,尾巴指向格外,朝向锚点或最近的边框,并止于边框。这样的对白框放在声音传来的那条边框旁边,配一条短尾巴:横穿整格的尾巴看起来像一支箭头,所以移动的是对白框,而不是把尾巴拉长。to="x% y%"让尾巴指向画面上的另一个点,tail=start(或top、bottom、end)让它指向那一侧格外的说话人。旁白框、注释和拟声词没有尾巴。
#相连的对白框
同一说话人连续的两行会连在一起,这是嵌字表现说话停顿的方式:joinSameSpeaker: 'butt'(默认)让两个框体重叠,共用一条轮廓;'connector'用一段细颈把它们连起来;'none'让它们保持为两个对白框、两条尾巴。一组中只有第一个对白框有尾巴。单独一行可以用join或join=false另行指定。
#拟声词
sfx行没有框:文字排得很大(嵌字字号的2.4倍)、加粗,用文档语言的拟声词字体,字的四周有一圈白色描边,压在画面上也能看清。画面标了分格的sfx锚点(id为sfx的锚点)时,拟声词放在锚点附近,否则放在不挡脸的地方。rotate、size、color和font可以改变它,at可以固定它的位置。竖排嵌字时,它排成一列,最多五个字。拟声词在页面上所有对白框之后绘制。
一行可以设定自己的书写方向。漫画的横排版本保留日文拟声词时,把它排成一列:
sfx{vertical size=2.6 font="Dela Gothic One"}: ドン#嵌字设置
comics.lettering包含全书的字体、字号、行距、颜色和排印规则。默认字体随文档语言而定,拟声词字体也一样:
| 语言 | 嵌字 | 拟声词 |
|---|---|---|
| 拉丁、希腊、西里尔等其他文字 | Comic Neue | Bangers |
| 日文 | Zen Antique | Dela Gothic One |
| 简体中文 | Noto Sans SC | ZCOOL KuaiLe |
| 繁体中文 | LXGW WenKai TC | LXGW WenKai TC |
| 阿拉伯文、波斯文、乌尔都文 | Playpen Sans Arabic | Lalezar |
这些字体都在Google Fonts上,和其他字体一样按名称加载。行高保持默认值时,竖排以及中文或日文的对白框行距为1.5,阿拉伯文为1.45。日文、中文和阿拉伯文的嵌字从不倾斜,不管样式怎样设置;阿拉伯文从不加字距。
#角色表
comics.cast列出角色:id(说话人的键)、name,以及可选的说话所用的对白框样式(balloonStyle,比如总是用radio说话的机器人)、用来区分角色的文字颜色color和对白框填充色fill,还有fontFamily。名称就是输出中报出的名字:HTML查看器和流式EPUB在每行台词前印出“名字:台词”。在角色表中的说话人从不被报告为未知。
comics: {
cast: [
{ id: 'maya', name: '玛雅' },
{ id: 'tomas', name: '托马斯爷爷' },
{ id: 'beacon', name: '灯塔', balloonStyle: 'radio', fill: { hex: '#e8f4ff', model: 'hex' } },
],
}#对话框样式
对白框样式是一类对白框:它的轮廓、尾巴和文字。内置九种,台词行在花括号里写一个词来指定(ben{whisper}: …)。保留键按名称取各自的样式(caption:、sfx:、note:),其他每一行都用speech,或者用角色表中说话人的balloonStyle。
| 样式 | 轮廓 | 尾巴 | 其他 |
|---|---|---|---|
speech | 椭圆 | 弯尾 | 所有样式的基础:白色填充,0.6 pt黑色轮廓,0.55 em留白。 |
thought | 云朵 | 气泡 | 气泡指向头部,而不是嘴。 |
whisper | 椭圆,虚线 | 弯尾 | 文字0.9倍。 |
shout | 爆炸形 | 直尾 | 文字1.15倍,加粗;轮廓0.8 pt。 |
radio | 锯齿形(electric) | 闪电 | 来自收音机、电话或机器的声音。 |
caption | 方框 | 无 | 淡黄色,紧贴顶部起始侧的角,文字向起始侧对齐。 |
inner | 圆角框 | 无 | 灰色填充,斜体:角色的内心独白。 |
note | 方框 | 无 | 紧贴底部末尾侧的角,文字0.8倍。 |
sfx | 无 | 无 | 文字2.4倍,加粗,用拟声词字体,带1.5 pt白色描边。 |
comics.balloonStyles修改这些样式,也可以添加新样式。id与内置样式相同的条目叠加在该样式之上;新id的条目从speech开始:
comics: {
balloonStyles: [
{ id: 'caption', fill: { hex: '#ffffff', model: 'hex' }, textTransform: 'uppercase' },
{ id: 'shout', burstPoints: 18, burstDepth: 0.3 },
{ id: 'eerie', shape: 'wavy', stroke: { hex: '#5b4a8a', model: 'hex' }, italic: true },
{ id: 'writing', shape: 'none', tail: 'none', fontFamily: 'Caveat', color: { hex: '#3b2a1a', model: 'hex' } },
],
}ghost{eerie}: 离开这座房子……
caption{at=bottom-end}: 三条街以外。轮廓有oval(贴合文字行的超椭圆,roundness为2.2)、rounded、rectangle、cloud(扇贝边的个数按周长计算)、burst(burstPoints个尖刺,深度为burstDepth)、wavy、electric和none。dash画虚线轮廓,double画双线轮廓,wobble(0–1)让轮廓像手绘一样抖动,同一个对白框每次都抖得一样。尾巴有curved、wedge、bubbles、zigzag和none。每个字段及其默认值见配置 › 漫画。
渲染器按嵌字师上墨的方式画对白框:一组中的所有轮廓先以两倍粗细描边,再填充,最后排字。因此两个相连的框体和它们的尾巴画出来是一个整体,中间没有线。
#说话人与锚点
尾巴能指向人物、对白框能避开脸,是因为画面标明了人物在哪里。位图或SVG资源可以带有锚点:每个说话人一个点,只写一次,与安全区一样以画面的比例表示(从左上角起算的0–1)。
const arrive: Resource = {
id: 'lh-arrive', typeId: 'figure', kind: 'bitmap', createdAt: 0, updatedAt: 0,
bitmap: { fileId: 'lh-arrive.jpg', format: 'jpeg', width: 1100, height: 733 },
altText: '一个穿黄色雨衣的女孩沿着海边小路走向灯塔。',
safeArea: { x: 0.2, y: 0.08, width: 0.42, height: 0.82 },
anchors: [
{ id: 'maya', x: 0.29, y: 0.52, head: { x: 0.27, y: 0.47 }, face: { x: 0.22, y: 0.42, width: 0.12, height: 0.16 } },
{ id: 'biscuit', x: 0.4, y: 0.68 },
],
avoid: [{ x: 0.51, y: 0.09, width: 0.1, height: 0.43 }], // 灯塔
};id是脚本中使用的说话人键(maya: …)。id为sfx的锚点决定这一格拟声词的位置。x、y是嘴,对白的尾巴指向这里。head是心声的气泡所指的位置。未设置时为嘴。face是一个不让对白框遮挡的矩形。没有它的锚点会在嘴的上方和四周得到一个几个em大小的保护框,所以没有框出的脸也能被避开。avoid(写在资源上)列出其他不让对白框遮挡的矩形:一只手、一件关键的物品、一块招牌。迫不得已时,对白框可以盖住避让区,但从不盖脸。
锚点是画面的比例,所以随裁切移动:引擎把每个锚点经过分格的裁切和翻转映射到页面上,因此拖动分格线、换一种切分或换一种译文,都不会让尾巴离开说话人。被裁到分格以外的锚点让尾巴朝它指向格外。安全区可以防止这种情况:安全区以外的锚点报告comicAnchorOutsideSafeArea,这是给作者的提示,因为裁切可能把它切掉。一页中没有画面标出、角色表中也没有的说话人键报告comicUnknownSpeaker,通常能抓出拼错的id;画面完全没有标锚点的页面什么也不报告,所以不标锚点也能给漫画嵌字。
锚点和避让区属于画面,在每种语言中都相同。在沙盒中,它们在资源面板的图片编辑器里标出:安全区与嵌字字段打开一个对话框,有安全区、角色和避让区三种模式。在画面上单击,就在那里添加一个说话人;把它的嘴、头和脸拖到位,在旁边输入它的id;在画面的空白处拖动,画出一个避让区。每个标记都可以用方向键移动(按住Shift步长更大),按Delete删除,对话框有自己的撤销。
#漫画条
:::strip块的内容与漫画页相同,但它排在正文中,是一个一栏宽或一页宽的框,切成若干分格:报纸每天刊登的连环漫画、杂志上的四格笑话、散文书中的一小段漫画。
:::strip{split="* | * | * | *" aspect=4 span=page placement=top}
::panel{art=pip-1}
pip: 奥托,冰在化。
::panel{art=pip-2}
otto{thought}: 可别是面包店……
::panel{art=pip-3}
::panel{art=pip-4}
sfx: 扑通
:::| 属性 | 作用 |
|---|---|
span=column、span=page | 占据所在的栏(默认),或者占据版心。 |
placement=here | 默认值:排在正文中写它的地方。漫画条从不拆开;放不下时整体移到下一栏或下一页,并带走紧挨在它上面的标题。在多栏的页面上写成here的通栏漫画条,像通栏框一样在它所在的地方切断各栏:它上方的文字在每一栏中齐平结束,漫画条横跨整页,文字在它下方的各栏中继续。页面剩下的部分放不下它时,它开启下一页。 |
placement=top、bottom、auto | 像图一样浮动到从这里起第一个有空间的页面的顶部或底部。 |
width=60%、width=12cm | 漫画条的宽度:它所跨宽度(所在的栏,或span=page时的版心)的一个比例,或者一个长度(不带单位的数字以毫米计)。不会超过这个宽度。未设置时,漫画条占满整个宽度。 |
align=start、center、end | 较窄的漫画条在这个宽度中的位置。默认值是center。start和end跟随文字方向:从右到左的页面上,起点是右边缘;竖排页面上,起点是一栏的顶端。 |
height=4cm | 漫画条的高度(不带单位的数字以毫米计)。 |
aspect=4、aspect=4/1 | 宽高比,宽度指漫画条自己的宽度。未设置时,漫画条取让各分格成为正方形的比例(一行四格:4)。 |
caption="…" | 漫画条下方的题注(设置captionStyle.position: 'above'时在上方),按漫画条的宽度、以图的题注样式排出。文字是行内Markdown,用每个版本自己的语言书写。 |
type=figure | 把带题注的漫画条按该类型的资源编号:题注以类型的标签和编号开头(如“图3”),漫画条按阅读顺序与该类型的资源一起计入它的编号序列。不写type,或者写了没有哪个资源类型使用的类型时,题注不带编号,漫画条也不计数。 |
id=… | 为带编号的漫画条命名::ref{id="…"}像对图一样印出它的标签和编号,并链接到它。 |
没有split的漫画条让分格左右并排。漫画条的框就是它的画框:格间距、分格样式和嵌字都用配置中的设置,不出血。比一栏还高的漫画条连同题注一起缩到栏高。它之后的正文在基线网格的下一行继续,留出layout.inlineResourceGap要求的间距,与行内图之后一样。在竖排页面上,漫画条在转过来的框中排版,所以aspect=0.25的四格漫画* / * / * / *顺着竖排的一栏向下排,它的题注作为一栏文字跟在后面。
:::strip{split="* | * | *" width=70% caption="皮普和奥托在冰上。" type=figure id=pip-ice}
::panel{art=pip-1}
::panel{art=pip-2}
::panel{art=pip-3}
:::
如:ref{id="pip-ice"}所示,冰很薄。题注采用题注样式设定的一切:字体和字号、标签和文字的颜色、标签的字重和斜体、漫画条与题注之间的间距、对齐方式,以及题注背后的色条。类型自己的captionStyle作用于按该类型编号的漫画条。
在VDT中,漫画条是一个type: 'resource'的块,带有comic字段而没有resourceBlock;它的坐标相对于块在纸页上的框;较窄的漫画条,或排在题注下方的漫画条,位于这个框之内(这时comic.frame不从框的角上开始)。题注就是块自己的lines,用题注的字体和颜色排出,stripCaption说明哪些行是题注、它们的位置、类型和编号,以及色条。pageComics(page)给出一页的漫画,以及该页各栏和浮动体中的漫画条,全部换算到纸页上。
#跨页
:::page{spread}把一棵切分树铺在两个对页上,用于需要占满整个打开的书的全景或战斗场面。画框是两个版心隔着书脊连在一起,去掉它们的内侧边距;切分、格间距和裁切与单页相同,分格或画面可以跨过书脊。
:::page{spread split="25 [* | * | *] / *" bleed}
::panel{art=market-1}
::panel{art=market-2}
::panel{art=market-3}
::panel{art=market-panorama}
caption: 从钟楼上望去的星期六集市。
:::跨页从偶数页(verso)开始,让两半彼此相对:漫画本会从奇数页(recto)开始时,在它前面加一个空白页;在书的最开头也是如此,因为第1页单独成页。左侧装订的书中,偶数页是左页;右侧装订的书(阿拉伯文、竖排的日文和中文、从右往左读的漫画)中,偶数页是右页。每一页印它那一半:伸到它这一侧的分格在书脊处切开,跨过书脊的分格,其画面在两页上都画出。出血的分格在天头、地脚和外侧切口出血,从不在书脊处出血。书脊两侧8 mm的带状区域内不放任何嵌字,那里会被装订吞掉:固定在那里的对白框会滑向较近的一侧。每个对白框落在其中心所在的那一页上。
#阅读方向
西方漫画从左往右读:一行的第一格在左边。日本漫画和阿拉伯漫画从右往左读。Postext从comics.readingDirection读取方向:
'auto'(默认):按版本所用语言的方向。在从右到左的文档中从右往左读,所以阿拉伯文版会把页面左右翻排;竖排文档也从右往左;日文和繁体中文(ja、zh-Hant、zh-TW、zh-HK、zh-MO)同样从右往左,所以西方漫画的日文版和日本漫画一样从右往左读。其他语言,包括简体中文和韩文(如今的国漫和韩漫都从左往右读),按画稿绘制时设想的方向,即comics.artDirection(默认'ltr'):设了artDirection: 'rtl'的日本漫画在任何语言中都从右往左。'ltr'或'rtl'为全书强制一个方向,页面的direction属性为该页强制方向。
装订随漫画而定。page.binding: 'auto'时,comics部分从右往左读的书在右侧装订:第1页单独位于书脊左侧,跨页读作[3 | 2],阅读器向左翻页,PDF要求从右到左的页序,EPUB声明从右到左的翻页方向。由comics部分为全书决定,所以没有漫画页的章节与其他章节的装订相同;页面自己的direction不会移动书脊。漫画页沿用默认设置、没有comics部分的文档保持其文本的装订(写上comics: {}即可按漫画的阅读方向装订)。明确写出的page.binding: 'left'或'right'仍然优先。见装订。
同一份源文件给出两种页面版式。从右到左的页面把每一行的分格从右边排起,所以第1格在右上角;斜线随之镜像,bleed和pad的逻辑方向以及角位置关键词也一样(top-start的旁白框在右上角)。各行仍然自上而下,脚本的阅读顺序不变。嵌字规则也随之镜像:一格中的第一个对白框靠右,下一个只有在更靠左时才可以更高。
画面除非要求,否则不翻转:comics.mirrorArt: true把阅读方向与artDirection相反的页面上的每幅画左右翻转,日本漫画的西方版本长期以来就是这样做的;::panel{mirror=false}让一幅画保持原样(画里有文字,或者剑客是用右手持剑的)。锚点和安全区随翻转而变。通常最好不要翻转:分格顺序已经跟随读者,翻转的画面改变了画家画的东西。
对白框的书写方向跟随语言,而不是分格顺序。comics.lettering.writingMode: 'auto'时,日文和繁体中文竖排成从右往左读的列,每列最多maxColumnChars个字(8),遵循日文的禁则,数字用:tcy在列中横排,标点用竖排形式;以竖排排版的文档在任何语言中都竖排嵌字。简体中文、韩文和其他所有语言都横排嵌字,'horizontal'或'vertical'可以强制其中一种。阿拉伯文从右往左嵌字,其中的拉丁词和数字按自己的方向排,从不倾斜,也从不加字距。
#翻译漫画
译本是一个Markdown文件,:::page和::panel行相同,说话人键相同,文字不同。画面及其安全区、锚点和避让区,切分,以及固定的位置(at=的点以画面的比例表示)由所有版本共用。每个版本各自嵌字:文字按自己的语言和字体测量,断成自己的行,每个对白框都围绕它重新成形、重新放置。本页的例子是中文版,下面是开头那一页的英文版:
:::page{split="30 [30 | 20 | *] / *"}
::panel{art=lh-arrive}
caption: Every summer, Maya spent a week at the lighthouse.
maya: Grandpa! I'm here!
::panel{art=lh-radio}
tomas: Just in time. The radio's gone deaf,
tomas: and there's a storm on the way.
:::版本之间的差别由文档的locale决定:默认的嵌字字体和拟声词字体,日文和繁体中文的竖排嵌字,中文和日文去掉的句号,行距,以及阿拉伯文版的页面方向。西班牙文或法文的文字比英文长约五分之一,中文或日文则短得多;对白框随之变大变小,comicBalloonOverflow会指出译文需要更短的台词或固定位置的分格。拟声词也是词:可以翻译,也可以保留原文,在下面加一个小号的译文作为字幕。
#漫画的输出
每种输出画出的都是同一部漫画:分格底色、限定在分格内的画面、边框、出格画面,然后是每个连接组的对白框(先描轮廓,再填充,再排字),最后是拟声词。
- Canvas。 沙盒的Canvas、Folio的3D页面和缩略图都通过
renderPageToCanvas绘制。 - PDF。
postext-pdf用矢量轮廓绘制漫画页,并嵌入嵌字字体;带PDF印刷母版(svg.pdfFileId)的SVG画面以该母版嵌入。带标签的PDF给每个漫画页一个Div,给每个分格一个带替代文字的Figure(分格的alt,否则用资源的altText),给每个对白框一个P,按阅读顺序排在所属分格的figure之后;拟声词是一个Span,其/ActualText就是它的文字。底色、边框和轮廓都是artifact。输出通过veraPDF的PDF/UA-1检查。 - HTML。 每个分格是一个
<figure>,内含一个内联SVG(裁切路径、画面、边框、抠图)和分格的替代文字,后面按阅读顺序跟着它的对白框,每个是一个<p>元素。对白以一个视觉上隐藏的span开头,内含说话人的名字,所以屏幕阅读器读出“玛雅:爷爷!我来啦!”;拟声词作为一张以其文字为标签的图片读出。沙盒的HTML查看器的页面是一屏宽、可以一直往下滚动的长页,它把漫画页排成缩放到视口大小的印刷页(comics.viewerLeaf),从不重排。 - 固定版式EPUB。 页面就是印刷书的页面。书中还带有基于区域的导航(
regions.xhtml,EPUB Region-Based Navigation 1.0):按阅读顺序每个分格一个panel区域,其中嵌套它的对白框、旁白框和拟声词,阅读系统可以据此一格一格地浏览全书。翻页方向跟随漫画的阅读方向。renderToEpub的kindlePanelView: true选项加入Kindle Panel View:每个分格一个点击区域,以及一个放大到满屏的副本,并带上Kindle要求的元数据。 - 流式EPUB。 漫画页在这里没有要保持的页面,所以每个分格变成它的画面(裁切与页面上完全相同),后面跟着它的台词文字:“玛雅:爷爷!我来啦!”,旁白框作为段落,拟声词用斜体。
漫画条的题注与图的题注一样,随文字一起绘制。在带标签的PDF中,它是漫画条的Div中的Caption,在各分格之后读出(题注排在漫画条上方时,在分格之前读出)。在HTML、固定版式EPUB和流式EPUB中,漫画条和题注是同一个<figure>,题注是其中的<figcaption>;在竖排或从右到左的页面上,漫画条的画面叠在纸页上,HTML把题注单独留在文字中。
#在沙盒中编辑漫画
在Canvas、HTML页面和Folio的选择文本模式中,可以用指针编辑漫画页,每次编辑都会写回章的Markdown(详见沙盒指南):
- 分格线。 指针移到格间距上会变成调整大小的光标。拖动它就移动切分线;松开时,新的百分比写入页面的
split值(页面没有这个属性时会加上),页面重新排版。按住Shift以5%为步长吸附,按住Alt只移动较近的一端,使线变斜,任何格子都不会窄于5%。拖动时,画面将被完整显示、两侧留出色带的分格会被标出。每条分格线也是一个可获得焦点的分隔条:方向键每次移动1%(按住Shift为5%),Enter写入位置,Escape恢复原位。 - 分格。 分格上方的小工具栏可以用一条水平线或垂直线把它分开,为新格子在它后面加一个空的
::panel;也可以把它与下一格合并:切分线去掉,合并后的分格保留第一格的画面和两格的脚本。 - 对白框。 拖动对白框就固定它的位置:它的台词行得到
at="x% y%",以分格画面的百分比表示,相连的一组作为整体移动。移动过程中,对白框在会盖住脸或避让区、或会伸出分格的位置变成橙色,提示框说明是哪一种情况。方向键把获得焦点的对白框每次微移1%,双击已固定的对白框会取消固定。 - 尾巴。 拖动尾巴的尖端,把
to="x% y%"(尾巴所指的点)写进台词行;双击尖端会删去它,尾巴重新指向说话人。在获得焦点的对白框上按T转到它的尾巴,再用方向键移动尾巴所指的点。 - 键盘。 获得焦点的分格按H用水平线分割,按V用垂直线分割,按M与下一格合并。
- 漫画条。 所有工具同样适用于
:::strip块,无论它排在栏中、浮动还是横跨整页,无论它比所跨宽度窄还是排在题注下方,修改都写进漫画条自己的围栏行。 - 画面。 画面的安全区、说话人和避让区在资源面板中标出(见说话人与锚点)。
- 设置。 版面设计面板有一个漫画组:阅读方向、画框与格间距、默认分格与分格样式、嵌字,以及对白框样式,每一项都有预览。
- 编辑器。 文本编辑器为
:::page、::panel和台词行着色,检查面板在对应的行列出漫画警告。
#漫画警告
每条警告都指明它涉及的行或属性,沙盒的检查面板在那里列出它,标题即下表第一栏的文字。
| 警告 | 原因 | 解决办法 |
|---|---|---|
分格切分无法读取comicSplitSyntax | split的值不能完整读出:有多余的字符、[没有闭合、同一个列表里混用/和|,或者方括号列表与父列表同向。只采用能读懂的部分;完全读不懂的值得到一个分格。 | 对照语法改正这个值:每个列表只用一种分隔符,用方括号换方向。 |
分格尺寸超过100%comicSplitOverflow | 一个列表中的尺寸合计超过100%。它们按比例缩小,每个*保留5%。 | 调小尺寸,或者把其中一个换成*。 |
分格数与格子数不符comicPanelCount | 页面的::panel行比切分出的格子多(多出的分格不排),或者少(最后几个格子留空)。 | 在切分中增减格子,或者在脚本中增减分格。空的::panel行可以有意留出一个空格子。 |
不在台词行里的文字comicStrayText | 第一个::panel行之前的文字,或者一行没有键:、又不是接续上一行的文字。它作为旁白框嵌字。 | 在行首加上键(caption:、maya:),或者缩进两个空格,接续上面的对白框。 |
未知的对白框样式comicUnknownBalloonStyle | 一行在花括号里指定的样式既不是内置样式,也没有在comics.balloonStyles中定义,常常是拼写错误({wisper})。这一行采用其键的样式。 | 改正名称,或者把这个样式加到comics.balloonStyles中。 |
找不到分格画面comicUnknownArt | 分格的art或pop指定的资源不存在,或者不是位图或SVG。分格画成空的。 | 改正id,或者把画面加入书的资源。 |
画面在分格内完整显示comicPanelLetterbox | 格子的比例与画面相差太远:这种形状的任何裁切都装不下整个安全区,所以画面完整显示安全区,四周留出分格底色的色带。 | 移动分格线,换一种格子比例(沙盒在拖动时会标出这样的位置),画一个更小的安全区,或者换一幅画。 |
说话点在安全区外comicAnchorOutsideSafeArea | 画面的锚点在安全区以外,裁切可能把说话人切掉,这时尾巴会指向格外。 | 扩大安全区,把嘴包进去,或者移动锚点。如果说话人本来就会不时出现在分格之外,则不必修改。 |
对白框放不下comicBalloonOverflow | 用尽所有退路之后,对白框仍然盖住脸、另一个对白框、避让区或嘴,或者伸出了分格(用break或at=固定所要求的越界不算)。警告指明是哪一格、盖住了什么。在沙盒中拖动对白框时,它会盖住的脸或避让区、会越过的边框,在松开之前就会显示出来。 | 缩短台词或拆成两行,给分格更多空间,用at=固定对白框,或者用break让它越过边框。文字从不缩小。 |
没有说话点的角色comicUnknownSpeaker | 一个说话人键,本页没有画面用锚点标出它,角色表中也没有它,多半是拼错的键。它的尾巴指向格外。只在画面标了锚点的页面上报告;仅作提示。 | 改正键,在画面上标出这个说话人,或者为从不露面的说话人加一个comics.cast条目。 |
这些警告返回在文档的contentWarnings中,带有所涉及的行或属性的源码范围(见配置 › 文档中的警告)。
只能取几个固定词的漫画设置写了别的值时,例如对白框样式中的shape: 'ovl'或readingDirection: 'rlt',按默认值读取,并在检查面板中列为未知的设置值(unknownConfigValue),附上引擎实际使用的值和最接近的词。检查的设置有:readingDirection、artDirection,分格样式的borderStyle和fit,嵌字的writingMode、textTransform、dropFinalStop和joinSameSpeaker,以及对白框样式的shape、tail、target、position、align和textTransform。
#在代码中使用
漫画只需要buildDocument:资源带着画面及其安全区、锚点和避让区,配置可以带comics。嵌字用已加载的字体测量文字,所以嵌字字体必须和其他字体一样在排版之前加载:comicFontFamilies(config, markdown)列出一个文档的漫画所需的字体(嵌字字体、每种对白框样式的字体以及角色表中的字体),markdownHasComics(markdown)说明一段文字中有没有漫画页或漫画条。生成PDF时,字体提供函数会像被请求其他字体一样被请求这些字体。
排好的漫画在VDT中:page.comic(VDTComicPage)包含画框、阅读方向、按阅读顺序排列的分格(多边形、边框、底色,以及画面的裁切art.source和art.box)、分格线(带有它们写入的split值的源码范围)和对白框(轮廓路径、文字块、尾巴尖端、种类、说话人)。切分语法为编辑器导出:parseComicSplit、serializeComicSplit、moveComicSplitLine、splitComicCell和mergeComicCells读取并改写split值,尽可能保留其中的*。comicArtCrop和comicCropFeasibleRange计算裁切,comicArtPointToPage把画面上的一个点映射到页面上。
#已知限制
- 只有切分树。 切分用一条横贯格子的线把每个格子一分为二,所以没有任何一条线贯穿整页的版式写不出来,比如四个分格绕着第五格的风车式布局。嵌入格和出格画面能满足这类版式的大部分用途。
- 分格形状。 分格是凸多边形;圆形、毛边或无边框的晕边分格,旋转或放大的画面,以及速度线,都要画在画面里,不由Postext绘制。
- 嵌字。 基字从右到左时不排注音,其他注解在对白框中按普通文字印出。放置以分格为单位:尾巴靠代价避免交叉,而不是绕开彼此走线,从一格到下一格的视线流动也不做平衡。很小的格子里的长台词只报告,不解决。没有标出的脸的保护框按嘴和头部的点来定大小(锚点没有头部时为几个em),所以在特写中它保护的可能更多是嘴而不是整张脸;请标出脸部。
- 漫画条不能跨越少于整页的若干栏(在三栏页面上写
columns=2),并且只在章的顶层读取(标注框、表格或脚注内不读取)。漫画条题注中的引文按原样印出。 - 跨页。 在书脊处被切开的圆角分格会失去所有圆角。左侧装订的书中,从右到左的漫画仍然从左页开始,所以它的第一格落在跨页的第二页上。
- 出格画面使用分格画面的裁切,所以抠图必须与画面图片尺寸相同。
- 输出。 既没有
alt也没有altText的分格,后备替代文字是英文的“Panel N”。基于区域的导航用外接矩形描述斜切的分格。Kindle Panel View为每个分格复制一次该页的漫画,文件因此变大,而且尚未在Kindle Previewer中检查过。
#参考资料
- Nate Piekos,Comic Book Grammar & Tradition(Blambot):对白框的种类、旁白框、强调、双连字符。
- John Roshell与Comicraft,Balloon Tales:尾巴与连接,以及先描边后填充的分层法。
- Todd Klein,Digital lettering for comics。
- David Kurlander、Tim Skelly和David Salesin,Comic Chat,SIGGRAPH 96:按阅读顺序放置对白框。
- IDPF,EPUB Region-Based Navigation 1.0;Amazon KDP,Kindle Panel View。