Universe

quan
圈 /quān/ — circled numbers for Typst, Unicode-first

Package version: 0.2.2 License: MIT Minimum Typst version: 0.13.0

English | 中文


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 declarationquan-init accepts range strings like "1-20" or "1-5,7,9-11" to declare exactly which glyphs your font provides
  • Per-range style rulesquan-style lets 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-footnote turns 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-init is never called, the default coverage is 1-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 under quan-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-style overrides now reach 0–99 — user rules and named inset:/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 stroke now inherits text.fill and scales with text.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-footnote fixes — label references (#footnote(<label>)) get the referenced note’s number instead of the wrong one; marker↔entry hyperlinks are back; the space before #footnote is 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) — 核心函数

输出带圈数字。若 nquan-init 声明的覆盖范围内,使用 Unicode 字形;否则自动画圈。

参数 类型 说明
n int 要渲染的数字,必须为整数,非整数输入会 panic。
#quan(1)    // ①(Unicode 字形,若在覆盖范围内)
#quan(25)   // 画圈,内含 "25"
#quan(100)  // 画圈,内含 "100"

quan-init(digits: none) — 声明字体覆盖范围

声明当前字体支持哪些带圈数字字形。超出范围的数字将自动画圈。

参数 类型 默认值 说明
digits strnone 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 lengthstroke 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-initquan-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.