diff --git a/doc/manual.typ b/doc/manual.typ index 7a6e250..16a58d7 100644 --- a/doc/manual.typ +++ b/doc/manual.typ @@ -299,6 +299,36 @@ CTyp 包提供了以下预定义的字体集合:`fandol`, `fangzheng`, `source [`..block-args`], [捕获所有其他传递到 `block` 函数的参数。], [] )] +== 列表编号设置 + +自 v0.3.0 版本起,使用 CTyp 包提供两个函数 `ItemLabel` 和 `EnumLabel` 可以实现对列表每级编号格式参数的设置。 +这两个函数分别接受一个位置参数,设置符号或者编号格式。 +此外,可以单独设置以下参数: + +/ `width`: 列表编号的宽度。 +/ `sep`: 编号与内容之间的间隔。 +/ `alignment`: 编号的对齐方式。可选值为 `left`, `center`, `right`。 + +例如,可以通过以下代码设置编号列表的编号格式: + +```typ +#let (theme, _) = ctyp( + fix-enum-args: ( + numberer: ( + EnumLabel("1.", width: 1em, alignment: left), + "a.", + EnumLabel("i)", width: 1em, alignment: right), + ) + ), + fix-list-args: ( + marker: ( + ItemLabel(sym.suit, width: 1em), + sym.dash + ) + ) +) +``` + = 页面设置 #note-ctyp-func[page-grid][设置页芯大小,优先保证宽度为整字符数,避免过多分散对齐问题。] diff --git a/src/ctyp.typ b/src/ctyp.typ index e833cc7..45b30ea 100644 --- a/src/ctyp.typ +++ b/src/ctyp.typ @@ -1,5 +1,5 @@ #import "./fonts/index.typ": * -#import "./utils/enumitem.typ": enumitem +#import "./utils/enumitem.typ": enumitem, ItemLabel, EnumLabel #import "./utils/page-grid.typ": page-grid #import "./utils/heading-numbering.typ": _config-heading-numbering diff --git a/src/index.typ b/src/index.typ index b47ae11..e2c61e0 100644 --- a/src/index.typ +++ b/src/index.typ @@ -10,3 +10,7 @@ #import "utils/page-grid.typ": ( page-grid ) +#import "utils/enumitem.typ": ( + ItemLabel, + EnumLabel, +) diff --git a/src/utils/enumitem.typ b/src/utils/enumitem.typ index 291d15c..c66929b 100644 --- a/src/utils/enumitem.typ +++ b/src/utils/enumitem.typ @@ -1,3 +1,5 @@ +#import "@preview/elembic:1.1.1" as e: field, element, types + #let mod(x, y) = { if x < y { x @@ -14,14 +16,72 @@ inset: (left: 0em) ) +#let ItemLabel = element.declare( + "ItemLabel", + prefix: "@preview/ctyp,v0.2.1", + fields: ( + field("symbol", content, required: true), + field("width", length, default: 1em), + field("sep", length, default: 0em), + field("alignment", alignment, default: left), + field("stroke", types.option(stroke), default: none) + ), + display: it => box( + width: it.width, + inset: (right: it.sep), + stroke: it.stroke, + align(it.alignment, it.symbol) + ), +) + +#let EnumLabel = element.declare( + "EnumLabel", + prefix: "@preview/ctyp,v0.2.1", + fields: ( + field("numbering", str, required: true), + field("width", length, default: 1em), + field("sep", length, default: 0em), + field("alignment", alignment, default: left), + field("stroke", types.option(stroke), default: none), + field("body", content) + ), + display: it => box( + width: it.width, + inset: (right: it.sep), + stroke: it.stroke, + align(it.alignment, it.body) + ), +) + +#let convert-content-to-marker(it) = { + let casted = e.types.cast(it, ItemLabel) + if casted.first() { + it + } else { + ItemLabel(it, width: 0.5em, sep: 0em, alignment: right) + } +} + +#let convert-str-to-numberer(it) = { + if type(it) == str { + EnumLabel(it, width: 1.5em, sep: 0em, alignment: right) + } else { + it + } +} + +#let default-list-markers = (sym.circle.filled, sym.triangle.r.filled, sym.square.filled).map(it => text(it, baseline: -.1em)).map(convert-content-to-marker) + +#let default-enum-numberers = ("1)", "a)", "i.").map(convert-str-to-numberer) + /// 自定义列表和枚举布局,修复符号和文字不对齐的问题。 #let enumitem( /// 符号列表可选用的符号。将循环使用。 /// -> array - marker: (sym.circle.filled, sym.triangle.r.filled, sym.dash), + marker: default-list-markers, /// 编号列表可选用的编号格式。将循环使用。 /// -> array - numberer: ("1)", "a)", "i)"), + numberer: default-enum-numberers, /// 是否使用紧凑布局。 /// 紧凑布局会使用 `par.leading` 作为列表项目之间的间隔, /// 否则使用 `par.spacing`。 @@ -58,6 +118,8 @@ children ) = context { let block-args = (:..default-block-args, ..block-args.named()) + let marker = marker.map(convert-content-to-marker) + let numberer = numberer.map(convert-str-to-numberer) show: block.with(..block-args) let spacing = if spacing == auto { if tight { @@ -67,29 +129,24 @@ } } let item-template( - label-width: 1em, - label-sep: label-sep, - alignment: left, - label: [], + label, body: [] - ) = block( - inset: (left: indent + label-width + body-indent), - stroke: if (debug) { green + 1pt } else { none }, - above: spacing, - below: spacing, - { - set par(first-line-indent: (amount: 0em, all: true), hanging-indent: 0em) - box( - width: 0em, - move(box( - width: label-width, - inset: (right: label-sep), - stroke: if (debug) { red } else { none }, - align(alignment, label) - ), dx: - label-width - body-indent) - ) + body - } - ) + ) = { + let label-width = e.fields(label).width + block( + inset: (left: indent + label-width + body-indent), + stroke: if (debug) { green + 1pt } else { none }, + above: spacing, + below: spacing, + { + set par(first-line-indent: (amount: 0em, all: true), hanging-indent: 0em) + box( + width: 0em, + move(label, dx: - label-width - body-indent) + ) + body + } + ) + } let queue = (( marker: none, body: [] @@ -106,7 +163,7 @@ while cur.at(0) < cur-max.at(0) { if cur.at(depth) >= cur-max.at(depth) { let qe = queue.pop() - queue.last().body += item-template(..qe) + queue.last().body += item-template(qe.label, body: qe.body) let _ = cur.pop() let _ = cur-max.pop() if cur-type.len() > 0 { @@ -153,7 +210,6 @@ if "children" in elem.body.fields() and elem.body.children.any(is-elem-item) { queue.push(( label: label, - label-width: marker-width, body: [] )) depth += 1 @@ -162,12 +218,11 @@ } else { queue.push(( label: label, - label-width: marker-width, body: elem.body )) cur.at(depth) += 1 let qe = queue.pop() - queue.last().body += item-template(..qe) + queue.last().body += item-template(qe.label, body: qe.body) } } else if elem.func() == enum.item { if cur-type.len() == 0 { @@ -190,12 +245,13 @@ } let number = cur-number.at(depth) cur-number.at(depth) += 1 - let label = numbering(numberer.at(cur-numberer), number) + let cur-numberer-fields = e.fields(numberer.at(cur-numberer)) + let cur-numberer-template = cur-numberer-fields.remove("numbering") + let label-number = numbering(cur-numberer-template, number) + let label = EnumLabel(cur-numberer-template, ..cur-numberer-fields, body: label-number) if "children" in elem.body.fields() and elem.body.children.any(is-elem-item) { queue.push(( label: label, - label-width: number-width, - alignment: right, body: [] )) depth += 1 @@ -204,13 +260,11 @@ } else { queue.push(( label: label, - label-width: number-width, - alignment: right, body: elem.body )) cur.at(depth) += 1 let qe = queue.pop() - queue.last().body += item-template(..qe) + queue.last().body += item-template(qe.label, body: qe.body) } } else { queue.last().body += elem diff --git a/tests/core/ref/3.png b/tests/core/ref/3.png index 9757eef..372c499 100644 Binary files a/tests/core/ref/3.png and b/tests/core/ref/3.png differ diff --git a/tests/core/ref/4.png b/tests/core/ref/4.png index 50b1fb2..e3b9571 100644 Binary files a/tests/core/ref/4.png and b/tests/core/ref/4.png differ diff --git a/tests/fix-list/ref/1.png b/tests/fix-list/ref/1.png index 465ff66..846bf1c 100644 Binary files a/tests/fix-list/ref/1.png and b/tests/fix-list/ref/1.png differ diff --git a/tests/fix-list/ref/2.png b/tests/fix-list/ref/2.png index 13bdc6b..5ae8da9 100644 Binary files a/tests/fix-list/ref/2.png and b/tests/fix-list/ref/2.png differ diff --git a/tests/fix-list/ref/3.png b/tests/fix-list/ref/3.png index 7a0dcdb..104aa1f 100644 Binary files a/tests/fix-list/ref/3.png and b/tests/fix-list/ref/3.png differ diff --git a/tests/fix-list/ref/5.png b/tests/fix-list/ref/5.png new file mode 100644 index 0000000..4a1f9a4 Binary files /dev/null and b/tests/fix-list/ref/5.png differ diff --git a/tests/fix-list/test.typ b/tests/fix-list/test.typ index 306bd48..e008e21 100644 --- a/tests/fix-list/test.typ +++ b/tests/fix-list/test.typ @@ -88,4 +88,32 @@ + 项目2.3.3.1 + 项目2.3.3.2 + 项目2.3.3.2.1 +] + +#testcase[ + #let (theme, _) = ctyp( + fix-enum-args: ( + numberer: ( + EnumLabel("1.", width: 1em, alignment: left), + "a.", + EnumLabel("i)", width: 1em, alignment: right), + ) + ), + fix-list-args: ( + marker: ( + ItemLabel(sym.suit, width: 1em), + sym.dash + ) + ) + ) + #show: theme + + = 设置编号格式 + + + 测试 + + 测试 + + 测试 + + - 测试 + - 测试 ] \ No newline at end of file