ReedenReeden
阅读与排版高亮规则

高亮规则写法速查

高亮规则能写的全部内容:关键词、样式属性、替换为、点击和段落条件,以及从剪贴板导入用的 JSON 格式。

适用版本:Reeden 2.0.0 及以上。这里没列出的写法不支持。

这页是高亮规则页「+」→「用 AI 写规则」时交给 AI 的写法说明。自己照着写 JSON 也行,写好后复制,在「+」→「从剪贴板导入」。完整流程见 用 AI 写规则。

1. 导入格式

从剪贴板导入时,内容是一个 JSON 对象(可以包在 ```json 代码块里):

{
  "reedenHighlightRules": 1,
  "rules": [
    {
      "name": "规则名",
      "keyword": "要匹配的文字或正则",
      "isRegex": true,
      "css": "color: {primaryColor};"
    }
  ]
}

每条规则的字段:

字段必填说明
name是规则名,简短说清用途
keyword是要匹配的文字;isRegex 为 true 时是正则(见第 2 节)。配合段落条件时可以留空,表示整段
isRegex否默认 false
isMultiline否正则的多行模式:^ $ 匹配每段的开头结尾;不开时只匹配整章(或整个标题)的开头结尾。默认 false
caseSensitive否区分大小写,默认 false
target否作用在哪:all(默认)、title(只章节标题)、body(只正文)
css否样式(见第 3 节)。可以同时写顶层声明、.名字 { } 块、@keyframes
replacement否把命中的文字换成别的(见第 4 节)。不写表示不替换;写空字符串表示删除命中的文字
options否段落条件、点击行为等(见第 5 节),是一个 JSON 对象
comment否给用户看的备注

一次可以导入多条规则。导入后的先后不一定和 rules 里的顺序一致,用户要在列表里长按拖动调整。

多条规则命中同一处文字时

规则按列表从上往下生效,同一处文字命中多条规则时:

  • 文字效果(color、font-size、font-family、font-weight、font-style、line-height):逐项比,排在后面的规则写了的为准,没写的仍用前面那条的
  • 下划线、波浪线、删除线:可以同时显示
  • 背景效果(background-color、background-image、background 简写、渐变、box-shadow、border):排在前面的占住;后面带背景效果的规则只要和它碰到同一个字,在这一处整个不显示,连文字颜色也没有
  • replacement:排在前面的占住;后面的在重叠处不替换,样式照常叠加。所有 replacement 都在同一份文字上匹配,匹配不到别的规则替换出来的内容
  • 关键词匹配的是简繁转换、替换规则、全文翻译(译文也会再过一遍替换规则)、replacement 处理之后的文字:前面换掉的字,后面按原字写的关键词就找不到
  • target: "title" 的规则同时只生效排在最前面的一条;用户开着标题样式时,所有规则在标题上都不生效

写规则时照这几条避开冲突:

  • 会和别的规则叠在一起用的单字、标点规则(引号、标点、人名)只用文字效果,不要加背景效果
  • 整句、整段的规则(对话气泡、卡片)带背景效果,和单字规则一起用时排在前面
  • 规则的生效依赖先后时(比如要排在对话气泡前面),在 comment 里写明要放在哪类规则的前面或后面,方便用户导入后拖动

完整说明见 规则生效顺序。

2. 关键词

  • 普通文字:照字面匹配。
  • 正则(isRegex: true):JavaScript 风格,Unicode 模式。可以用 \p{Han}(汉字)、后行断言 (?<=…)、命名分组 (?<名字>…)。不支持 (?i) 这类内联修饰,大小写用 caseSensitive。
  • 命名分组的名字可以在 CSS 里用 .名字 { } 单独设样式,在「替换为」里用 $<名字> 引用。
  • 写在 JSON 里时反斜杠要转义:正则 \d+ 写成 "\\d+"。
  • 匹配范围是整章:一章的各段按顺序接成一段文字,段与段之间是一个换行 \n。target: "body" 时不含标题,target: "title" 时只有标题。
    • . 不匹配换行,.* 最多走到这一段末尾。[\s\S]、\s、\n 和排除型字符类 [^…] 都能匹配换行,会跨到下一段;只想在段内找时把 \n 写进排除里,如 “[^”\n]+”。
    • 要每段开头、结尾用 ^ $,同时写 isMultiline: true。
    • 从某处一直框到章末(作者有话说、章末附言)可以写 固定开头[\s\S]*,如 作者有话要?说[::]?[\s\S]*,内置模板就是这么写的。[\s\S]* 前面一定要有固定的开头文字。
    • 要框到某个结束标记为止,用段落条件 until(第 5 节),不要写 [\s\S]*?结束标记。
    • 不要把 [\s\S]*、.* 放在关键词开头,不要在一条关键词里写好几个跨段的 [\s\S]*,也不要写会大量回溯的嵌套重复(如 (a+)+)。长章节里会很慢,太慢时这条规则在本次阅读里会被暂停。

特殊关键词(isRegex: false 时整个关键词写成下面这样):

关键词匹配
{bookRoles}本书角色的名字(配合「替换为」里的角色占位用)
{vocabulary}生词本里的词
{translated}全文翻译时插入的译文段
{translationSource}全文翻译时有译文跟着的原文段
{titleFirstLine} {titleLastLine}章节标题的第一行、最后一行(target 用 title)
{titleLeadingLine} {titleTrailingLine}多行标题里最后一行之前的行、第一行之后的行
{titleGroup}整个章节标题

3. 样式(css 字段)

css 是一段 CSS 文字,有三种内容:

  1. 顶层声明:color: red; font-weight: 700;,作用在整处命中上。
  2. 分块:.名字 { … },只作用在命中里同名的部分。名字可以是:
    • 关键词里的命名分组;
    • 「替换为」里 <span class="名字"> 包出来的部分;
    • first-para / last-para:命中跨多段时的第一段、最后一段;
    • v-数字:按数值分档时的档位(第 5 节 value)。 另外可以写 img、ruby、rt 三个标签名,作用在「替换为」插入的图片和注音上。不支持别的选择器(不支持组合、伪类、属性选择器、媒体查询)。
  3. 关键帧:@keyframes 名字 { from { … } to { … } },配合 animation 用。

约束:

  • 颜色优先用动态颜色占位符(见下表),会跟随阅读主题变;写死颜色在深色主题下可能看不清。
  • 不写 font-family:导入的字体在这里引用不到,规则导入后在编辑器的「字体」里选。
  • 图片只能用内置素材 url(builtin:名字)(名字见「背景与图片」里的列表),不支持网址。图库里的图片导入后在编辑器里换。
  • 只支持下面列出的属性,其他属性会被忽略。
  • 字号、间距优先用 em,跟随用户设置的字号。

可用的属性

下面按用途列出,「说明 / 可选值」里的 | 表示几种写法任选其一。

文字

写法说明 / 可选值
color#FFB800 | rgba(255,184,0,.8) | {primaryColor}
font-weightnormal | 400 | 700 | bold
reeden-font-bold1–9,阅读页的描边加粗等级
font-stylenormal | italic
font-size14px | 1em | 1.1rem
font-variantnormal | small-caps
line-height1.5 | 24px
letter-spacing0.3em | 1px | -0.5px
text-alignleft | center | right | justify
text-indent0 | 2em
text-transformnone | uppercase | lowercase | capitalize
white-spacenormal | nowrap
text-shadow0 1px 2px rgba(0,0,0,.18),可写多层
-webkit-text-stroke1px #000000,文字描边
-webkit-text-stroke-width1px | 0.06em
-webkit-text-stroke-color#000000 | {primaryColor}

下划线与着重号

写法说明 / 可选值
text-decorationunderline wavy {primaryColor}
text-decoration-lineunderline | line-through
text-decoration-stylesolid | wavy | dashed | dotted | double
text-decoration-color#22C55E | {primaryColor} | {roleColor}
text-decoration-thickness2px | 0.1em,线的粗细
text-underline-offset0.2em,下划线离文字的距离
text-emphasisfilled dot {accentColor},着重号,也可加 -webkit- 前缀
text-emphasis-stylefilled dot | open circle | sesame
text-emphasis-color{accentColor}
text-emphasis-positionover | under

背景与图片

写法说明 / 可选值
background-color#FFF3C4 | {primaryColor|a:0.15}
background简写:图片 位置 / 尺寸 平铺 颜色,多层用逗号隔开,颜色写在最后一层
background-imageurl(builtin:gem),内置装饰素材
builtin:名字内置装饰素材,CSS 写 url(builtin:名字),「替换为」写 <img src="名字">:bubble-pink、bubble-white、tag、scroll、torn、banner、note-card、avatar、gem、bell、pin、sakura、boom、quote-left、quote-right、pencil、ornament、flourish、cap-frame、emoji-smile、emoji-cry、emoji-wow、comment-bubble
background-positionleft top | center | right center
background-repeatno-repeat | repeat
background-sizecover | contain | auto | 100% 100% | 1em
reeden-background-heighthalf | 50% | full | 100%,半高荧光笔用 half

渐变与阴影

写法说明 / 可选值
background-imagelinear-gradient(...) | radial-gradient(...)
background-cliptext,文字渐变用;普通背景渐变不要加
-webkit-background-cliptext,文字渐变兼容写法
-webkit-text-fill-colortransparent,配合文字渐变
box-shadow0 2px 8px rgba(15, 23, 42, .20)
color + background-image + background-clip文字渐变组合
display:inline + background-image行内背景渐变,不要加 background-clip:text
display:inline + box-shadow行内阴影
display:block + box-shadow块级阴影
display:block + background-image块级背景渐变

边框、圆角与轮廓线

写法说明 / 可选值
border1px solid #22C55E
border-left / right / top / bottom4px solid #94A3B8,单边边框
border-width1px | 1px 1px 3px
border-stylesolid | dashed | dotted | double
border-color{mutedColor}
border-bottom-width3px,单边的 -width / -style / -color 都能写
border-radius12px | 0.3em | 999px
border-top-left-radius0.2em,四个角可单独写
box-decoration-breakclone | slice,行内跨行时每行是否各画完整边框
outline1.5px solid {primaryColor},不占位置
outline-width / style / color轮廓线长写
outline-offset2px

内边距与外边距

写法说明 / 可选值
padding4px 10px
padding-left / right / top / bottom8px
margin4px 8px。顶层声明里不支持负值(行内块和块级的 margin-top 除外)
margin-left / right8px | auto
margin-top / bottom8px,行内块和块级生效,行内不生效

展示与尺寸

写法说明 / 可选值
displayinline | inline-block | block,留空时自动判断,其他值按 inline。block 只在顶层声明里有效;分块(.名字 { })里写 block 不会另起一行,换行写法见第 6 节「标签压在边框上」
floatleft | right,用于首字下沉等环绕效果
opacity0–1
width / min-width / max-widthfit-content | 78% | 6em | calc(100% - 7em)
height / min-height / max-height2em
vertical-alignmiddle | top | -0.1em,行内块的上下位置
宽高、垂直对齐只对行内块和块级生效

变换与动画

写法说明 / 可选值
transformrotate(-8deg) | scale(1.2) | translateY(-0.1em),只画不排,要配 inline-block
transform-origincenter | left bottom
animationreeden-breathe 2.4s ease-in-out infinite
animation-name / duration / timing-function / delay动画长写
animation-iteration-count / direction / fill-mode / play-state动画长写
内置动画reeden-breathe | blink | shake | float | rainbow | pop | spin | fade-in | sweep
@keyframes 名字 { … }自定义动画,只播透明度、颜色、背景色和变换

分块样式

写法说明 / 可选值
.名字 { … }只作用于正则命名分组 (?<名字>…) 或「替换为」里 class 同名的部分
img / ruby / rt { … }「替换为」换出来的图片、注音,注音小字是 rt
.first-para / .last-para { … }命中跨多段时的第一段、最后一段

动态颜色占位符

写法说明 / 可选值
{textColor}当前主题文字色
{backgroundColor}当前主题背景色
{primaryColor}当前主题强调色
{accentColor}同 {primaryColor}
{mutedColor}次要文字色,介于文字色和背景色之间
{roleColor}命中角色的颜色,没有角色颜色时回退到文字色或强调色
{primaryColor|a:0.15}任一占位符加 |a:透明度,做浅色底

其余支持的属性

写法同标准 CSS: background-origin、padding-left、padding-right、padding-top、padding-bottom、margin-left、margin-right、margin-top、margin-bottom、border-left、border-right、border-top、border-bottom、outline-width、outline-style、outline-color、-webkit-text-emphasis、-webkit-text-emphasis-style、-webkit-text-emphasis-color、-webkit-text-emphasis-position、-webkit-box-decoration-break、width、min-width、max-width、height、min-height、max-height、border-top-width、border-right-width、border-left-width、border-top-style、border-right-style、border-bottom-style、border-left-style、border-top-color、border-right-color、border-bottom-color、border-left-color、border-top-right-radius、border-bottom-right-radius、border-bottom-left-radius、animation-name、animation-duration、animation-timing-function、animation-delay、animation-iteration-count、animation-direction、animation-fill-mode、animation-play-state

4. 替换为(replacement 字段)

把命中的文字换成另一段,可以夹标签。替换后的文字会作为正文,参与复制、搜索、听书和导出。只想改样式时不要写 replacement。

  • 引用:$0 整处命中,$1 第几个分组,$<名字> 命名分组,$$ 写一个 $。
  • 占位:{roleName} {roleColor} {roleAvatar}(关键词是 {bookRoles} 时可用)、{roleIntro}(只在弹出卡片里可用)。
  • 文字转换:{1|num}(中文数字转阿拉伯数字)、{1|cn}(阿拉伯数字转中文)、upper、lower、full(转全角)、half(转半角)、head:N(取前 N 个字),可以串起来:{name|half|upper}。花括号里的 1、name 是分组序号或名字。
  • 标签只有四种,不支持 style 属性(样式写在 css 里):
    • <span class="名字">…</span>:包一段文字,CSS 里用 .名字 { } 设样式;
    • <img src="…" size="text" alt="…" class="名字">:插图,src 写内置素材名或 {roleAvatar},size 是 text(跟字一样高,默认)、line(占满一行宽)、original(原图大小),alt 是图片在正文里算作的文字;
    • <ruby>文字<rt>注音</rt></ruby>:注音。
  • 转义:&lt; &gt; &amp; &quot;;任意字符用 &#数字;,换行写 &#10;。
  • 替换为里的空格、换行都会原样出现在正文里,不要为了好看加缩进或换行。

5. 扩展配置(options 字段)

options 是一个 JSON 对象,下面的键都可以不写:

{
  "match": { "scope": "paragraph", "startsWith": "作者有话说" },
  "tap": { "action": "fold", "summary": "作者有话说…" },
  "hits": true,
  "scene": "reader",
  "preview": "示例文字"
}

match:段落条件

先按条件选段落,再在选中的段落里找关键词(关键词留空就是整段)。各条件同时满足才算。章节标题、空段、只有插图的段不算一段。

键说明
scope写 "paragraph":段里有一处命中,整段都套样式
parity"odd" / "even":只要奇数段 / 偶数段
nth第几段的列表,从 1 起;负数是倒数,如 [1, -1] 是首段和末段
every每隔几段取一段(第 1、1+N、1+2N… 段)
minLength / maxLength段落字数范围(不算空白)
startsWith / endsWith段落开头 / 结尾是这段文字(按字面,不是正则)
until正则:命中一直延伸到出现它的那一段为止,用来框住「从某段到某段」
untilMissinguntil 找不到时:"chapterEnd"(到章末,默认)或 "none"(不算命中)
exclude排除列表:"quote"(在引号里的)、"title"(在标题里的),其余按正则匹配段落文字

scope: "paragraph" 和 replacement 一起用时,命中的是整段,「替换为」会把整段文字换掉:

  • 段里的正文没写进「替换为」就不显示。要保留正文,在「替换为」里用 $0(整段文字),或者让关键词从段首匹配到段尾(^…$,配 isMultiline: true),再用分组把正文写回去。
  • 关键词没有从段首匹配到段尾时取不到分组,$1、$<名字> 都是空的。
  • 只想给整段加底色、边框,不改文字时不要写 replacement。想在段首加标签、正文照常显示,用第 6 节「两栏卡片」的写法,不用 scope。

tap:点击行为

键说明
action"reveal":默认遮住,点一下显示;"fold":折成一行摘要,点开看全文;"popup":点一下弹出卡片
summaryfold 时折叠后显示的摘要,写法同「替换为」;不写时用首段前 12 个字
popuppopup 时卡片内容,写法同「替换为」
cardpopup 卡片样式:"text"(标题 + 文字,默认)、"comments"(每行一条「名字:内容」,显示成段评列表)
on只在点到这个 class 的部分(「替换为」里 <span class="…"> 包的)时响应;不写时点到整处命中都响应

reveal 的「遮住」效果在 css 里写(比如文字颜色和背景同色)。

其他

键说明
value按数值分档:{ "group": "分组名", "min": 0, "max": 100, "step": 10 },把命名分组里的数字按 step 分档,命中带上 v-档位 的 class,CSS 写 .v-70 { … }
hitstrue:点到命中时显示「本章第几处 / 全书几处」和上一处、下一处
scene只对带 replacement 的规则有用:"all"(默认)、"reader"(只在阅读时替换)、"audiobook"(只在听书时替换)
preview编辑器里预览用的示例文字,能命中关键词最好;多段用 \n 分开

6. 示例

{
  "reedenHighlightRules": 1,
  "rules": [
    {
      "name": "对话淡底",
      "keyword": "“[^”\\n]+”",
      "isRegex": true,
      "target": "body",
      "css": "background-color: {primaryColor|a:0.1}; border-radius: 0.2em;"
    },
    {
      "name": "金额加粗",
      "keyword": "(?<num>\\d+(?:\\.\\d+)?)(?<unit>万|亿)?元",
      "isRegex": true,
      "css": "color: {primaryColor};\n.num { font-weight: 700; }\n.unit { font-size: 0.85em; }"
    },
    {
      "name": "生僻词注音",
      "keyword": "魑魅魍魉",
      "replacement": "<ruby>$0<rt>chī mèi wǎng liǎng</rt></ruby>",
      "css": "rt { color: {mutedColor}; }"
    },
    {
      "name": "折叠作者有话说",
      "keyword": "",
      "css": "color: {mutedColor}; font-size: 0.9em;",
      "options": {
        "match": { "scope": "paragraph", "startsWith": "作者有话说" },
        "tap": { "action": "fold", "summary": "作者有话说…" }
      }
    }
  ]
}

两栏卡片

左边一栏标签、右边一栏正文的卡片(比如「本章概要:……」这种段落)。要点:

  • 不写 scope: "paragraph"。关键词从段首匹配到段尾(^…$,isMultiline: true),把要显示的正文抓进命名分组。
  • 顶层声明写 display: block,整段画成一张卡片。
  • 「替换为」里放两个 <span>:左栏是标签,右栏用 $<名字> 把正文写回去。
  • 两栏都写 display: inline-block; vertical-align: middle;。左栏给固定宽度,右栏宽度用 calc(100% - 左栏宽度 - 间距),两栏就排在同一行;左栏的 border-right 当中间的竖线。
  • em 按这一栏自己的字号算。右栏写了 font-size: 0.9em 时,它的 1em 只有正文的 0.9 倍,calc 里要多减一些(左栏 5em、间距 1em 时写 calc(100% - 7em))。两栏加起来超过一行,右栏就会掉到标签下面另起一行。
  • 手机上一行只有 18–20 个字。左栏控制在 5em 左右,标签的中英文上下排(各写 display: inline-block; width: 100%;),不要横排成一长条,否则右栏一行只剩几个字。
{
  "reedenHighlightRules": 1,
  "rules": [
    {
      "name": "章节概要卡片",
      "keyword": "^(?<label>本章概要|内容提要)[::]\\s*(?<body>.+)$",
      "isRegex": true,
      "isMultiline": true,
      "target": "body",
      "css": "display: block; text-indent: 0; margin: 0.9em 0; padding: 0.9em 1em; background-color: {primaryColor|a:0.06}; border-left: 3px solid {primaryColor}; border-radius: 0 6px 6px 0;\n.tag { display: inline-block; width: 5em; vertical-align: middle; text-align: center; border-right: 1px solid {mutedColor|a:0.4}; }\n.label { display: inline-block; width: 100%; color: {primaryColor}; font-weight: 700; letter-spacing: 0.1em; }\n.en { display: inline-block; width: 100%; color: {mutedColor}; font-size: 0.55em; letter-spacing: 0.24em; }\n.txt { display: inline-block; width: calc(100% - 7em); margin-left: 1em; vertical-align: middle; font-size: 0.9em; line-height: 1.75; }",
      "replacement": "<span class=\"tag\"><span class=\"label\">$<label></span><span class=\"en\">SUMMARY</span></span><span class=\"txt\">$<body></span>",
      "options": { "preview": "本章概要:一场意外的更新,揭开了沉寂已久的隐藏任务。" }
    }
  ]
}

标签压在边框上、正文另起一行

一个框住整段的边框,左上角一个小标签压在边框线上,正文从标签下面一行开始(比如「作者有话说:……」)。要点:

  • 顶层声明写 display: block 和边框,padding-top 写 0:标签会往上挪到边框线上,它原来的位置还占着,就当成正文上面的留白。
  • 标签写 display: inline-block; transform: translateY(-0.6em);。transform 只改画的位置、不改排版,所以挪上去以后不会把正文顶下来。
  • 标签再写 margin-right: 100%; white-space: nowrap;,把后面的文字挤到下一行。分块里写 display: block 不会另起一行。
{
  "reedenHighlightRules": 1,
  "rules": [
    {
      "name": "作者有话说标签框",
      "keyword": "^(?<label>作者有话说|作者的话)[::]?\\s*(?<body>.+)$",
      "isRegex": true,
      "isMultiline": true,
      "target": "body",
      "css": "display: block; text-indent: 0; margin: 1.2em 0 0.6em; padding: 0 0.9em 0.6em; border: 1.5px solid {primaryColor|a:0.45}; border-radius: 0.6em;\n.tag { display: inline-block; transform: translateY(-0.6em); margin-right: 100%; white-space: nowrap; padding: 0.1em 0.7em; font-size: 0.85em; font-weight: 700; letter-spacing: 0.1em; color: {backgroundColor}; background-color: {primaryColor}; border-radius: 0.35em; }",
      "replacement": "<span class=\"tag\">$<label></span>$<body>",
      "options": { "preview": "作者有话说:今天加班到很晚,只能更一章啦。明天补上,谢谢大家的月票!" }
    }
  ]
}

7. 常见错误

  • JSON 解析不了:反斜杠、引号没转义;reedenHighlightRules 漏写。
  • [\s\S]* 前面没有固定的开头文字,或者写成 [\s\S]*?结束标记 去框范围,长章节里很慢、被暂停。框范围用 until(第 5 节)。
  • 想按每段的开头结尾写 ^…$,却没写 isMultiline: true,只能匹配整章的开头结尾。
  • CSS 用了没列出的属性或选择器,导入后被忽略。
  • .名字 { } 的名字在关键词的命名分组和「替换为」的 class 里都找不到,这块样式不会生效。
  • 只想改样式却写了 replacement,正文文字被替换。
  • scope: "paragraph" 又写了 replacement,整段正文被换掉,$1、$<名字> 是空的(见第 5 节段落条件)。
  • 分块里写 display: block 想让后面的文字换行,结果还是接在同一行(见第 6 节「标签压在边框上」)。