quan
圈 /quān/ — circled numbers for Typst, Unicode-first
English
A Typst package for circled numbers. It uses native Unicode circled glyphs (⓪–㊿) within the declared font coverage range, and automatically draws a circle around the number otherwise — so it works with any font, even those with partial or no circled-digit support.
Features
- Unicode glyphs first — uses ⓪–㊿ (U+24EA–U+32BF) when the current font supports them, avoiding the inconsistency of drawn circles mixed with native glyphs
- Automatic drawn-circle fallback — renders any integer outside font coverage as a drawn circle, including numbers beyond 50; the circle tracks the current text color and weight
- Font coverage declaration —
quan-initaccepts range strings like"1-20"or"1-5,7,9-11"to declare exactly which glyphs your font provides - Per-range style rules —
quan-stylelets you tune stroke, radius, inset, baseline, text size and letter-spacing per numeric range, layered on top of the built-in tuning - SimSun-tuned defaults — built-in rules handle single digits (0–9) and the varying visual width of digit pairs from 10 to 99 out of the box; easily overridden for other fonts
- Circled footnote markers —
#show: quan-footnoteturns footnote numbers into circled digits document-wide, preserving Typst’s native footnote behaviour (label references, hyperlinks, entry indent, line spacing)
Quick Start
#import "@preview/quan:0.2.2": quan, quan-init, quan-style
// Declare your font's circled-digit coverage (default: 1-10)
#quan-init(digits: "1-20")
Step #quan(1): install. Step #quan(2): configure. Step #quan(15): done.
If quan-init is not called, the default coverage is 1–10 (supported by most Chinese fonts). Numbers outside the declared range, or beyond 50, are drawn automatically.
API Reference
quan(n) — core function
Outputs a circled number. Uses the Unicode circled glyph if n is within the coverage declared by quan-init; otherwise draws a circle.
| Parameter | Type | Description |
|---|---|---|
n |
int |
The number to render. Must be an integer; non-integer input panics. Coverage and style rules determine the output. |
#quan(1) // ① (Unicode, if in coverage)
#quan(25) // drawn circle with "25"
#quan(100) // drawn circle with "100"
quan-init(digits: none) — declare font coverage
Declares which circled-digit glyphs the current font provides. Numbers outside this range will be drawn.
| Parameter | Type | Default | Description |
|---|---|---|---|
digits |
str or none |
none (clears to empty) |
Range string, e.g. "1-20" or "1-5,7,9-11". |
#quan-init(digits: "1-20") // font has ①–⑳
#quan-init(digits: "0-50") // font has ⓪–㊿
#quan-init(digits: "1-5,7,9-11") // sparse coverage
Note — if
quan-initis never called, the default coverage is1-10.
quan-style(..args) — configure drawn-circle appearance
Named arguments update the global default style and take precedence over the built-in SimSun rules. Positional arguments are (range-str, style-dict) tuples layered on top of the built-in rules — all matching rules apply cumulatively, and later-registered rules (including later calls) win.
| Field | Type | Default | Description |
|---|---|---|---|
stroke |
length or stroke |
0.0315em |
Circle stroke. A bare length tracks the current text fill and weight (as set via set text); pass e.g. 0.05em + red to pin a color. |
radius |
ratio |
50% |
Box corner radius (50% = circle). |
inset |
dict |
(x: 0.28em, y: 0.015em) |
Inner padding. The default x serves 100+ and negative numbers; 0–99 get width-tuned insets from the built-in rules unless overridden. |
outset |
dict |
(y: 0.15em) |
Outer expansion. |
baseline |
length |
-0.06em |
Vertical alignment shift. |
size |
length |
0.825em |
Font size of the digit inside the circle. |
kern |
length |
-0.075em |
Letter-spacing (tracking) of the digit inside the circle. Negative values tighten multi-digit numbers. |
gap |
length |
0.1em |
Horizontal spacing appended after each drawn circle, mimicking the natural advance of Unicode glyphs. |
Range strings support negative endpoints, e.g. ("-9--1", (inset: (x: 0.35em, y: 0.015em))).
// Change stroke globally
#quan-style(stroke: 0.05em)
// Adjust size and kern for a specific range
#quan-style(("11-50", (size: 0.75em, kern: -0.1em)))
// Multiple overrides in one call
#quan-style(
stroke: 0.04em,
("11-99", (size: 0.8em)),
("100-999", (size: 0.65em, kern: -0.2em)),
)
quan-footnote — circled footnote markers
Show-rule wrapper that replaces footnote markers (and entry numbers) with circled numbers globally. Styling follows quan-init and quan-style.
Native footnote behaviour is preserved: label references (#footnote(<label>)) reuse the referenced note’s number, marker and entry number hyperlink to each other, the space before #footnote is absorbed and the marker never breaks onto the next line, line spacing is not affected, and the entry respects set footnote.entry(indent: ..).
Note — the numbering style is taken over by this package;
set footnote(numbering: ..)has no effect underquan-footnote. Apply the rule at the top of the document, before any content: Typst resolves footnote-entry styles from the start of the page, so entries on a page whose content begins before the rule keep the default style.
#show: quan-footnote
This is a footnote#footnote[hello]. And another#footnote[world].
SimSun Default Rules
The built-in style rules are tuned for SimSun (Windows 宋体) and cover 0–99. They adjust inset and kern based on the visual width of each digit:
| Group | Numbers | Logic |
|---|---|---|
| Single digit | 0–9 | one character; wider horizontal inset keeps the circle round |
| Both narrow | 11 | digits 1+1 |
| Narrow + medium | 12, 21 | digits 1+2, 2+1 |
| Narrow + wide | 10, 13–19, 31, 41, 51, 61, 71, 81, 91 | one digit is 1, the other is 0 or ≥ 3 |
| Wide + wide | 20–99 (units ≠ 1) | neither digit is 1 |
For other fonts, override with quan-style() as needed — your settings layer on top of these rules.
Changelog
0.2.2
quan-styleoverrides now reach 0–99 — user rules and namedinset:/kern:were silently shadowed by the built-in SimSun rules; styles now resolve in layers (built-in rules → named args → range rules, later wins), so the documented examples actually take effect and later calls override earlier ones- Drawn circles follow the text — a bare-length
strokenow inheritstext.filland scales withtext.weight, matching Unicode glyphs on the same line - Numbers ≥ 100 and negatives render cleanly — the default horizontal inset no longer lets the circle cut through the outer digits
quan-footnotefixes — label references (#footnote(<label>)) get the referenced note’s number instead of the wrong one; marker↔entry hyperlinks are back; the space before#footnoteis absorbed and markers no longer break onto the next line; markers no longer inflate the line height;set footnote.entry(indent: ..)is respected- Range strings — negative endpoints (
"-9--1") are supported; malformed input and inverted ranges ("5-1") now raise clear errors instead of failing silently
中文
用于排版带圈数字的 Typst 包。在声明的字体覆盖范围内使用原生 Unicode 带圈字形(⓪–㊿),超出范围时自动画圈——因此适用于任何字体,包括带圈字形支持不完整的字体。
功能特性
- 优先使用 Unicode 字形 — 在字体支持的范围内使用 ⓪–㊿,避免原生字形与画圈混排不一致
- 自动画圈兜底 — 对超出字体覆盖范围的整数(含大于 50 的数字)自动绘制带圈字形,圈线跟随当前文字颜色与字重
- 声明字体覆盖范围 —
quan-init接受"1-20"或"1-5,7,9-11"等范围字符串,精确声明字体支持哪些带圈字形 - 按范围配置样式 —
quan-style支持按数字范围分别设置描边、圆角、内边距、基线偏移、字号、字间距,叠加在内置调校之上 - SimSun 开箱即用 — 内置规则针对宋体(SimSun)的字宽特点,覆盖 0–9 单位数及 10–99 数字对,其他字体可通过
quan-style()覆盖 - 带圈脚注序号 —
#show: quan-footnote全局将脚注序号替换为带圈数字,并保留 Typst 原生脚注行为(标签引用、超链接、条目缩进、行距不变)
快速上手
#import "@preview/quan:0.2.2": quan, quan-init, quan-style
// 声明当前字体支持的带圈数字范围(默认:1-10)
#quan-init(digits: "1-20")
第 #quan(1) 步:安装。第 #quan(2) 步:配置。第 #quan(15) 步:完成。
若不调用 quan-init,默认覆盖范围为 1–10(大多数中文字体均支持)。超出声明范围或大于 50 的数字将自动画圈。
API 参考
quan(n) — 核心函数
输出带圈数字。若 n 在 quan-init 声明的覆盖范围内,使用 Unicode 字形;否则自动画圈。
| 参数 | 类型 | 说明 |
|---|---|---|
n |
int |
要渲染的数字,必须为整数,非整数输入会 panic。 |
#quan(1) // ①(Unicode 字形,若在覆盖范围内)
#quan(25) // 画圈,内含 "25"
#quan(100) // 画圈,内含 "100"
quan-init(digits: none) — 声明字体覆盖范围
声明当前字体支持哪些带圈数字字形。超出范围的数字将自动画圈。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
digits |
str 或 none |
none(清空为空) |
范围字符串,如 "1-20" 或 "1-5,7,9-11"。 |
#quan-init(digits: "1-20") // 字体支持 ①–⑳
#quan-init(digits: "0-50") // 字体支持 ⓪–㊿
#quan-init(digits: "1-5,7,9-11") // 稀疏覆盖
注意 — 若从未调用
quan-init,默认覆盖范围为1-10。
quan-style(..args) — 配置画圈样式
具名参数修改全局默认样式,并优先于内置的 SimSun 规则;位置参数为 (范围字符串, 样式字典) 元组,叠加在内置规则之上——所有命中的规则依次生效,后注册(含后一次调用)的优先。
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
stroke |
length 或 stroke |
0.0315em |
圆圈描边。裸长度会跟随当前文字颜色与字重(set text 设置的字重);如需固定颜色可传 0.05em + red。 |
radius |
ratio |
50% |
圆角半径(50% 即圆形)。 |
inset |
dict |
(x: 0.28em, y: 0.015em) |
内边距。默认 x 服务于三位数及负数;0–99 的内边距由内置规则按字宽精调,可覆盖。 |
outset |
dict |
(y: 0.15em) |
外扩距离。 |
baseline |
length |
-0.06em |
垂直对齐偏移。 |
size |
length |
0.825em |
圈内数字字号。 |
kern |
length |
-0.075em |
圈内数字字间距(tracking),负值收紧多位数字。 |
gap |
length |
0.1em |
每个画圈后追加的水平间距,模拟 Unicode 字形自带的字间距。 |
范围字符串支持负数端点,如 ("-9--1", (inset: (x: 0.35em, y: 0.015em)))。
// 全局修改描边
#quan-style(stroke: 0.05em)
// 按范围调整字号和字间距
#quan-style(("11-50", (size: 0.75em, kern: -0.1em)))
// 多条规则一次设置
#quan-style(
stroke: 0.04em,
("11-99", (size: 0.8em)),
("100-999", (size: 0.65em, kern: -0.2em)),
)
quan-footnote — 带圈脚注序号
用于 show 规则的包装函数,全局将脚注序号(正文上标与脚注条目)替换为带圈数字。样式跟随 quan-init 与 quan-style 的设置。
原生脚注行为均予保留:标签引用(#footnote(<label>))复用被引脚注的编号;marker 与条目编号互为超链接;#footnote 前的空格会被吸收,marker 不会被断行到下一行;不影响行距;条目缩进跟随 set footnote.entry(indent: ..)。
注意 — 编号形式由本包接管,
set footnote(numbering: ..)在quan-footnote下不生效。请将该规则置于文档开头、任何正文内容之前:Typst 从页面起点解析脚注条目样式,若本页内容先于规则出现,条目会保持默认样式。
#show: quan-footnote
这是一个脚注#footnote[你好]。再来一个#footnote[世界]。
SimSun 默认规则
内置样式规则针对 SimSun(宋体) 调校,覆盖 0–99,根据数字的视觉宽度分五组:
| 分组 | 数字范围 | 依据 |
|---|---|---|
| 单位数 | 0–9 | 单字符,较大水平内边距保持圆形视觉 |
| 双窄 | 11 | 两位均为 1 |
| 窄+中 | 12、21 | 1 与 2 的组合 |
| 窄+宽 | 10、13–19、31、41、51、61、71、81、91 | 一位为 1,另一位为 0 或 ≥ 3 |
| 宽+宽 | 20–99(个位非 1) | 两位均非 1 |
如需适配其他字体,通过 quan-style() 按需覆盖即可——用户设置叠加在内置规则之上。
更新日志
0.2.2
quan-style的覆盖对 0–99 生效了 — 此前用户规则和具名inset:/kern:会被内置 SimSun 规则静默遮蔽;现按层解析(内置规则 → 具名参数 → 范围规则,后者优先),文档示例真正生效,后一次调用可覆盖前一次- 画圈跟随文字 — 裸长度
stroke继承text.fill,并随text.weight加粗,与同行 Unicode 字形观感一致 - 三位数及负数正常渲染 — 默认水平内边距不再让圈线切穿首尾数字
quan-footnote修复 — 标签引用(#footnote(<label>))取被引脚注的编号而非错号;marker↔条目超链接恢复;#footnote前空格被吸收、marker 不再孤立断行;marker 不再撑高行距;set footnote.entry(indent: ..)生效- 范围字符串 — 支持负数端点(
"-9--1");非法输入与倒序范围("5-1")报清晰错误而非静默失败
License / 许可
MIT License — see LICENSE for details.