feat(rules): 建立共享排版契约并收束 WebKit 与 Unicode 基线

This commit is contained in:
2026-09-04 21:44:11 +08:00
parent 8ee9895247
commit 993cf576c6
42 changed files with 8511 additions and 1422 deletions
+61
View File
@@ -0,0 +1,61 @@
# 聚珍共享規則
本目錄是聚珍跨平台排版語義的機讀權威來源。Web/CSS 實作與未來其他 adapter
都應以 [`juzhen-rules.json`](./juzhen-rules.json) 為準;程式內常數不得另立一套未經
驗證的規則。`golden-vectors.json` 則固定跨 adapter 必須得到的語義結果,向量只描述
字符分類、間距、禁斷與段末保字等結果,不指定 DOM 結構或 CSS 技巧。
## 檔案
- `juzhen-rules.json`:版本化的共享規則資料。
- `juzhen-rules.schema.json`:JSON Schema 2020-12,約束共享規則格式。
- `golden-vectors.json`:跨平台黃金向量。
- `golden-vectors.schema.json`:JSON Schema 2020-12,約束向量格式。
套件公開子路徑分別為 `cjk-autospace/rules`、`cjk-autospace/rules/schema`、
`cjk-autospace/rules/vectors` 與 `cjk-autospace/rules/vectors-schema`。
## 版本
`schemaVersion` 是整數格式版本;讀取器遇到不認識的值必須拒絕,不得猜測。
`rulesetVersion` 採語義化版本:
- patch:澄清文字或新增不改變既有結果的向量;
- minor:向後相容地擴充字符或新增可選語義;
- major:既有輸入的分類、間距、禁斷、預設政策或 adapter 義務改變。
Unicode profile 另行記錄。更新 Unicode 範圍時,即使 JSON 結構不變,也必須同步提升
`rulesetVersion`、補向量並驗證所有 adapter。
## 變更協議
任何規則變更須在同一變更集中完成以下事項:
1. 更新 canonical JSON 及必要的 schema;
2. 新增或修訂能暴露行為差異的黃金向量,不以刪除反例來換取通過;
3. 更新 Web adapter 的對照測試;未來其他 adapter 亦須執行同一批語義向量;
4. 說明版本提升級別、相容性與 Unicode 依據;
5. 通過 Ajv 2020 strict schema 驗證、內部常數漂移檢查與全部 adapter 測試後方可發佈。
字符覆寫固定為追加語義:不得經 override 移除或重分類內置字符。若未來需要破壞此
規則,屬 major 變更,須先作產品政策決策,不能由單一 adapter 私自實作。
## Adapter 邊界
字符集合一律以大寫十六進位 Unicode code point 的閉區間/單點表示;陣列須按起點
遞增、彼此不重疊。adapter 可序列化成自身所需的 regex、查表或其他等價結構,但該
序列化不是 canonical 格式。
共享層只定義平台中立結果:Unicode 字符類、base+至多一枚 VS 的邏輯 atom、locale
分類與政策、標點半形語義、中西間距與作者空格處置、禁斷關係、段末保字數、功能級別
及語義依賴。
adapter 的節點表示、ownership/revert 帳務、渲染原語、生成式斷行提示、重跑生命
週期、layout 量測與平台 workaround 均不屬共享規則。例如 Web 目前可使用 `jz-*` DOM、
CSS margin/OpenType `halt` 與 MutationObserver;未來 TeX 類 adapter 也可能選擇 node、
glue、penalty 或 macro。這些都只是**非規範示例**,adapter 可採任何能通過黃金向量的
機制,不得把示例反寫為 canonical 要求。
Web 目前保留的 `hanging`/`biaodian`/`emphasis`/`ruby` 相容 no-op 亦屬
**非規範 adapter 相容性資訊**,不列入 canonical 規則,也不代表其他 adapter 必須公開
同名選項或未來功能介面已定案。
+373
View File
@@ -0,0 +1,373 @@
{
"$schema": "./golden-vectors.schema.json",
"schemaVersion": 1,
"rulesetVersion": "1.0.0",
"vectors": [
{
"id": "locale-ja",
"category": "language-classification",
"input": { "lang": "ja" },
"expected": { "langClass": "ja" }
},
{
"id": "locale-ja-jp",
"category": "language-classification",
"input": { "lang": "ja-JP" },
"expected": { "langClass": "ja" }
},
{
"id": "locale-zh-bare",
"category": "language-classification",
"input": { "lang": "zh" },
"expected": { "langClass": "zh-Hant" }
},
{
"id": "locale-zh-hant",
"category": "language-classification",
"input": { "lang": "zh-Hant" },
"expected": { "langClass": "zh-Hant" }
},
{
"id": "locale-zh-hans",
"category": "language-classification",
"input": { "lang": "zh-Hans" },
"expected": { "langClass": "zh-Hans" }
},
{
"id": "locale-zh-tw",
"category": "language-classification",
"input": { "lang": "zh-TW" },
"expected": { "langClass": "zh-Hant" }
},
{
"id": "locale-zh-hk",
"category": "language-classification",
"input": { "lang": "zh-HK" },
"expected": { "langClass": "zh-Hant" }
},
{
"id": "locale-zh-cn",
"category": "language-classification",
"input": { "lang": "zh-CN" },
"expected": { "langClass": "zh-Hans" }
},
{
"id": "locale-zhx-boundary",
"category": "language-classification",
"input": { "lang": "zhx" },
"expected": { "langClass": "other" }
},
{
"id": "locale-en",
"category": "language-classification",
"input": { "lang": "en" },
"expected": { "langClass": "other" }
},
{
"id": "punct-stop",
"category": "punctuation-classification",
"input": { "character": "." },
"expected": { "punctuationClass": "stop" }
},
{
"id": "punct-pause",
"category": "punctuation-classification",
"input": { "character": "," },
"expected": { "punctuationClass": "pause" }
},
{
"id": "punct-open",
"category": "punctuation-classification",
"input": { "character": "「" },
"expected": { "punctuationClass": "open" }
},
{
"id": "punct-close",
"category": "punctuation-classification",
"input": { "character": "」" },
"expected": { "punctuationClass": "close" }
},
{
"id": "punct-middle",
"category": "punctuation-classification",
"input": { "character": "·" },
"expected": { "punctuationClass": "middle" }
},
{
"id": "punct-liga",
"category": "punctuation-classification",
"input": { "character": "…" },
"expected": { "punctuationClass": "liga" }
},
{
"id": "punct-base-with-vs",
"category": "punctuation-classification",
"input": { "character": "。︀" },
"expected": { "punctuationClass": "stop" }
},
{
"id": "punct-other",
"category": "punctuation-classification",
"input": { "character": "A" },
"expected": { "punctuationClass": null }
},
{
"id": "style-quanjiao-pause",
"category": "punctuation-style",
"input": { "style": "quanjiao", "punctuationClass": "pause" },
"expected": { "halfWidth": false }
},
{
"id": "style-kaiming-pause",
"category": "punctuation-style",
"input": { "style": "kaiming", "punctuationClass": "pause" },
"expected": { "halfWidth": true }
},
{
"id": "style-kaiming-open",
"category": "punctuation-style",
"input": { "style": "kaiming", "punctuationClass": "open" },
"expected": { "halfWidth": true }
},
{
"id": "style-kaiming-stop",
"category": "punctuation-style",
"input": { "style": "kaiming", "punctuationClass": "stop" },
"expected": { "halfWidth": false }
},
{
"id": "style-banjiao-stop",
"category": "punctuation-style",
"input": { "style": "banjiao", "punctuationClass": "stop" },
"expected": { "halfWidth": true }
},
{
"id": "style-banjiao-liga",
"category": "punctuation-style",
"input": { "style": "banjiao", "punctuationClass": "liga" },
"expected": { "halfWidth": false }
},
{
"id": "adjacency-pause-open",
"category": "punctuation-adjacency",
"input": { "left": "pause", "right": "open" },
"expected": { "squeezeLeft": true, "squeezeRight": true }
},
{
"id": "adjacency-stop-open",
"category": "punctuation-adjacency",
"input": { "left": "stop", "right": "open" },
"expected": { "squeezeLeft": false, "squeezeRight": true }
},
{
"id": "adjacency-stop-close",
"category": "punctuation-adjacency",
"input": { "left": "stop", "right": "close" },
"expected": { "squeezeLeft": true, "squeezeRight": false }
},
{
"id": "adjacency-close-open",
"category": "punctuation-adjacency",
"input": { "left": "close", "right": "open" },
"expected": { "squeezeLeft": true, "squeezeRight": true }
},
{
"id": "adjacency-middle-liga",
"category": "punctuation-adjacency",
"input": { "left": "middle", "right": "liga" },
"expected": { "squeezeLeft": false, "squeezeRight": false }
},
{
"id": "spacing-cjk-latin",
"category": "spacing",
"input": { "text": "中A" },
"expected": { "managedGaps": 1, "normalizedText": "中A", "gapEm": 0.25 }
},
{
"id": "spacing-latin-cjk",
"category": "spacing",
"input": { "text": "A中" },
"expected": { "managedGaps": 1, "normalizedText": "A中", "gapEm": 0.25 }
},
{
"id": "spacing-cjk-at",
"category": "spacing",
"input": { "text": "中@" },
"expected": { "managedGaps": 1, "normalizedText": "中@", "gapEm": 0.25 }
},
{
"id": "spacing-at-cjk-asymmetric",
"category": "spacing",
"input": { "text": "@中" },
"expected": { "managedGaps": 0, "normalizedText": "@中" }
},
{
"id": "spacing-author-space-cjk-latin",
"category": "spacing",
"input": { "text": "中 A" },
"expected": {
"managedGaps": 1,
"normalizedText": "中A",
"authorSpaceDisposition": "remove-and-use-managed-gap"
}
},
{
"id": "spacing-author-space-latin-cjk",
"category": "spacing",
"input": { "text": "A 中" },
"expected": {
"managedGaps": 1,
"normalizedText": "A中",
"authorSpaceDisposition": "remove-and-use-managed-gap"
}
},
{
"id": "spacing-author-space-cjk-cjk",
"category": "spacing",
"input": { "text": "中 文" },
"expected": {
"managedGaps": 0,
"normalizedText": "中文",
"authorSpaceDisposition": "remove-without-gap"
}
},
{
"id": "spacing-cjk-punctuation-latin",
"category": "spacing",
"input": { "text": "中。A" },
"expected": { "managedGaps": 0, "normalizedText": "中。A" }
},
{
"id": "spacing-astral-ivs-latin",
"category": "spacing",
"input": { "text": "𠀀󠄀A" },
"expected": { "managedGaps": 1, "normalizedText": "𠀀󠄀A", "gapEm": 0.25 }
},
{
"id": "atom-bmp-vs",
"category": "ivs-atom",
"input": { "text": "中︀A" },
"expected": { "atoms": [ "中︀", "A" ] }
},
{
"id": "atom-astral-ivs",
"category": "ivs-atom",
"input": { "text": "𠀀󠄀A" },
"expected": { "atoms": [ "𠀀󠄀", "A" ] }
},
{
"id": "atom-at-most-one-vs",
"category": "ivs-atom",
"input": { "text": "中︀︁" },
"expected": { "atoms": [ "中︀", "︁" ] }
},
{
"id": "atom-isolated-vs",
"category": "ivs-atom",
"input": { "text": "︀A" },
"expected": { "atoms": [ "︀", "A" ] }
},
{
"id": "jinze-before-stop",
"category": "jinze",
"input": { "left": "A", "right": "。" },
"expected": { "prohibitedBreak": true, "reason": "before-stop" }
},
{
"id": "jinze-before-close",
"category": "jinze",
"input": { "left": "中", "right": "」" },
"expected": { "prohibitedBreak": true, "reason": "before-close" }
},
{
"id": "jinze-after-open",
"category": "jinze",
"input": { "left": "「", "right": "A" },
"expected": { "prohibitedBreak": true, "reason": "after-open" }
},
{
"id": "jinze-ordinary-boundary",
"category": "jinze",
"input": { "left": "中", "right": "A" },
"expected": { "prohibitedBreak": false }
},
{
"id": "jinze-supplementary-ivs-before-stop",
"category": "jinze",
"input": { "left": "𠀀\uDB40\uDD00", "right": "。" },
"expected": {
"prohibitedBreak": true,
"reason": "before-stop",
"atoms": [ "𠀀\uDB40\uDD00", "。" ]
}
},
{
"id": "jinze-second-vs-before-stop",
"category": "jinze",
"input": { "left": "𠀀\uDB40\uDD00\uDB40\uDD01", "right": "。" },
"expected": {
"prohibitedBreak": true,
"reason": "before-stop",
"atoms": [ "𠀀\uDB40\uDD00", "\uDB40\uDD01", "。" ]
}
},
{
"id": "jinze-open-before-second-isolated-vs",
"category": "jinze",
"input": { "left": "「\uFE00", "right": "\uDB40\uDD00" },
"expected": {
"prohibitedBreak": true,
"reason": "after-open",
"atoms": [ "「\uFE00", "\uDB40\uDD00" ]
}
},
{
"id": "orphan-default-two",
"category": "orphan",
"input": { "text": "天地玄黃", "minimum": 2 },
"expected": { "meaningfulCjkCount": 4, "protectedSuffix": "玄黃" }
},
{
"id": "orphan-trailing-punctuation",
"category": "orphan",
"input": { "text": "天地。", "minimum": 2 },
"expected": { "meaningfulCjkCount": 2, "protectedSuffix": "天地。" }
},
{
"id": "orphan-insufficient-cjk",
"category": "orphan",
"input": { "text": "天。", "minimum": 2 },
"expected": { "meaningfulCjkCount": 1, "protectedSuffix": null }
},
{
"id": "orphan-supplementary-ivs",
"category": "orphan",
"input": { "text": "甲𠀀\uDB40\uDD00", "minimum": 2 },
"expected": {
"meaningfulCjkCount": 2,
"protectedSuffix": "甲𠀀\uDB40\uDD00",
"atoms": [ "甲", "𠀀\uDB40\uDD00" ]
}
},
{
"id": "orphan-second-vs",
"category": "orphan",
"input": { "text": "甲𠀀\uDB40\uDD00\uDB40\uDD01", "minimum": 2 },
"expected": {
"meaningfulCjkCount": 2,
"protectedSuffix": "甲𠀀\uDB40\uDD00\uDB40\uDD01",
"atoms": [ "甲", "𠀀\uDB40\uDD00", "\uDB40\uDD01" ]
}
},
{
"id": "orphan-isolated-vs-prefix",
"category": "orphan",
"input": { "text": "\uDB40\uDD00甲𠀀\uDB40\uDD01", "minimum": 2 },
"expected": {
"meaningfulCjkCount": 2,
"protectedSuffix": "甲𠀀\uDB40\uDD01",
"atoms": [ "\uDB40\uDD00", "甲", "𠀀\uDB40\uDD01" ]
}
}
]
}
+267
View File
@@ -0,0 +1,267 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://juzhen.dev/schemas/golden-vectors.schema.json",
"title": "Juzhen shared rules golden vectors",
"type": "object",
"additionalProperties": false,
"required": [ "schemaVersion", "rulesetVersion", "vectors" ],
"properties": {
"$schema": { "type": "string" },
"schemaVersion": { "const": 1 },
"rulesetVersion": {
"type": "string",
"pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
},
"vectors": {
"type": "array",
"minItems": 20,
"items": {
"oneOf": [
{ "$ref": "#/$defs/languageVector" },
{ "$ref": "#/$defs/punctuationClassVector" },
{ "$ref": "#/$defs/punctuationStyleVector" },
{ "$ref": "#/$defs/punctuationAdjacencyVector" },
{ "$ref": "#/$defs/spacingVector" },
{ "$ref": "#/$defs/ivsAtomVector" },
{ "$ref": "#/$defs/jinzeVector" },
{ "$ref": "#/$defs/orphanVector" }
]
}
}
},
"$defs": {
"id": {
"type": "string",
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
},
"punctuationClass": {
"enum": [ "stop", "pause", "open", "close", "middle", "liga" ]
},
"style": {
"enum": [ "quanjiao", "kaiming", "banjiao" ]
},
"atoms": {
"type": "array",
"minItems": 1,
"items": { "type": "string", "minLength": 1 }
},
"languageVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "language-classification" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "lang" ],
"properties": {
"lang": { "type": "string" }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "langClass" ],
"properties": {
"langClass": { "enum": [ "zh-Hans", "zh-Hant", "ja", "other" ] }
}
}
}
},
"punctuationClassVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "punctuation-classification" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "character" ],
"properties": {
"character": { "type": "string", "minLength": 1 }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "punctuationClass" ],
"properties": {
"punctuationClass": {
"oneOf": [ { "$ref": "#/$defs/punctuationClass" }, { "type": "null" } ]
}
}
}
}
},
"punctuationStyleVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "punctuation-style" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "style", "punctuationClass" ],
"properties": {
"style": { "$ref": "#/$defs/style" },
"punctuationClass": { "$ref": "#/$defs/punctuationClass" }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "halfWidth" ],
"properties": {
"halfWidth": { "type": "boolean" }
}
}
}
},
"punctuationAdjacencyVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "punctuation-adjacency" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "left", "right" ],
"properties": {
"left": { "$ref": "#/$defs/punctuationClass" },
"right": { "$ref": "#/$defs/punctuationClass" }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "squeezeLeft", "squeezeRight" ],
"properties": {
"squeezeLeft": { "type": "boolean" },
"squeezeRight": { "type": "boolean" }
}
}
}
},
"spacingVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "spacing" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "text" ],
"properties": {
"text": { "type": "string" }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "managedGaps", "normalizedText" ],
"properties": {
"managedGaps": { "type": "integer", "minimum": 0 },
"normalizedText": { "type": "string" },
"gapEm": { "const": 0.25 },
"authorSpaceDisposition": {
"enum": [ "remove-and-use-managed-gap", "remove-without-gap" ]
}
}
}
}
},
"ivsAtomVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "ivs-atom" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "text" ],
"properties": {
"text": { "type": "string", "minLength": 1 }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "atoms" ],
"properties": {
"atoms": { "$ref": "#/$defs/atoms" }
}
}
}
},
"jinzeVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "jinze" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "left", "right" ],
"properties": {
"left": { "type": "string", "minLength": 1 },
"right": { "type": "string", "minLength": 1 }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "prohibitedBreak" ],
"properties": {
"prohibitedBreak": { "type": "boolean" },
"reason": { "enum": [ "before-stop", "before-close", "after-open" ] },
"atoms": { "$ref": "#/$defs/atoms" }
}
}
}
},
"orphanVector": {
"type": "object",
"additionalProperties": false,
"required": [ "id", "category", "input", "expected" ],
"properties": {
"id": { "$ref": "#/$defs/id" },
"category": { "const": "orphan" },
"input": {
"type": "object",
"additionalProperties": false,
"required": [ "text", "minimum" ],
"properties": {
"text": { "type": "string", "minLength": 1 },
"minimum": { "type": "integer", "minimum": 1 }
}
},
"expected": {
"type": "object",
"additionalProperties": false,
"required": [ "meaningfulCjkCount", "protectedSuffix" ],
"properties": {
"meaningfulCjkCount": { "type": "integer", "minimum": 0 },
"protectedSuffix": {
"oneOf": [ { "type": "string" }, { "type": "null" } ]
},
"atoms": { "$ref": "#/$defs/atoms" }
}
}
}
}
}
}
+255
View File
@@ -0,0 +1,255 @@
{
"$schema": "./juzhen-rules.schema.json",
"schemaVersion": 1,
"rulesetVersion": "1.0.0",
"unicodeProfile": {
"version": "17.0.0",
"coverage": "CJK Unified Ideographs through Extension J, compatibility ideographs, kana, bopomofo, radicals, and enclosed CJK characters"
},
"characterClasses": {
"representation": "unicode-code-point-ranges",
"cjk": [
{ "start": "2E80", "end": "2EFF" },
{ "start": "2F00", "end": "2FDF" },
{ "start": "3040", "end": "309F" },
{ "start": "30A0", "end": "30FA" },
{ "start": "30FC", "end": "30FF" },
{ "start": "3100", "end": "312F" },
{ "start": "3200", "end": "32FF" },
{ "start": "3400", "end": "4DBF" },
{ "start": "4E00", "end": "9FFF" },
{ "start": "F900", "end": "FAFF" },
{ "start": "20000", "end": "2A6DF" },
{ "start": "2A700", "end": "2B73F" },
{ "start": "2B740", "end": "2B81F" },
{ "start": "2B820", "end": "2CEAF" },
{ "start": "2CEB0", "end": "2EBEF" },
{ "start": "2EBF0", "end": "2EE5F" },
{ "start": "2F800", "end": "2FA1F" },
{ "start": "30000", "end": "3134F" },
{ "start": "31350", "end": "323AF" },
{ "start": "323B0", "end": "3347F" }
],
"ansAfterCjk": [
{ "start": "0024", "end": "0026" },
{ "start": "002A", "end": "002B" },
{ "codePoint": "002D" },
{ "start": "0030", "end": "0039" },
{ "codePoint": "003D" },
{ "codePoint": "0040" },
{ "start": "0041", "end": "005A" },
{ "codePoint": "005C" },
{ "codePoint": "005E" },
{ "start": "0061", "end": "007A" },
{ "start": "00A1", "end": "00B6" },
{ "start": "00B8", "end": "00FF" },
{ "start": "0100", "end": "024F" },
{ "start": "0370", "end": "03FF" },
{ "start": "0400", "end": "04FF" },
{ "start": "20A0", "end": "20BF" },
{ "start": "2150", "end": "21FF" },
{ "start": "2200", "end": "22FF" },
{ "start": "2700", "end": "27BF" }
],
"ansBeforeCjk": [
{ "start": "0024", "end": "0026" },
{ "start": "002A", "end": "002B" },
{ "codePoint": "002D" },
{ "start": "0030", "end": "0039" },
{ "codePoint": "003D" },
{ "start": "0041", "end": "005A" },
{ "codePoint": "005C" },
{ "codePoint": "005E" },
{ "start": "0061", "end": "007A" },
{ "start": "00A1", "end": "00B6" },
{ "start": "00B8", "end": "00FF" },
{ "start": "0100", "end": "024F" },
{ "start": "0370", "end": "03FF" },
{ "start": "0400", "end": "04FF" },
{ "start": "20A0", "end": "20BF" },
{ "start": "2150", "end": "21FF" },
{ "start": "2200", "end": "22FF" },
{ "start": "2700", "end": "27BF" }
],
"variationSelectors": [
{ "start": "FE00", "end": "FE0F" },
{ "start": "E0100", "end": "E01EF" }
],
"biaodian": {
"classificationOrder": [ "stop", "pause", "open", "close", "middle", "liga" ],
"classes": {
"stop": [
{ "codePoint": "3002" },
{ "codePoint": "FF01" },
{ "codePoint": "FF0E" },
{ "codePoint": "FF1F" }
],
"pause": [
{ "codePoint": "3001" },
{ "codePoint": "FF0C" },
{ "codePoint": "FF1A" },
{ "codePoint": "FF1B" }
],
"open": [
{ "codePoint": "2018" },
{ "codePoint": "201C" },
{ "codePoint": "3008" },
{ "codePoint": "300A" },
{ "codePoint": "300C" },
{ "codePoint": "300E" },
{ "codePoint": "3010" },
{ "codePoint": "3014" },
{ "codePoint": "3016" },
{ "codePoint": "FF08" },
{ "codePoint": "FF3B" },
{ "codePoint": "FF5B" }
],
"close": [
{ "codePoint": "2019" },
{ "codePoint": "201D" },
{ "codePoint": "3009" },
{ "codePoint": "300B" },
{ "codePoint": "300D" },
{ "codePoint": "300F" },
{ "codePoint": "3011" },
{ "codePoint": "3015" },
{ "codePoint": "3017" },
{ "codePoint": "FF09" },
{ "codePoint": "FF3D" },
{ "codePoint": "FF5D" }
],
"middle": [
{ "codePoint": "00B7" },
{ "codePoint": "2027" },
{ "codePoint": "30FB" }
],
"liga": [
{ "codePoint": "2014" },
{ "codePoint": "2026" }
]
}
},
"overrideSemantics": {
"mode": "append",
"westernTargets": [ "ansAfterCjk", "ansBeforeCjk" ],
"removalAllowed": false,
"reclassificationAllowed": false
}
},
"atoms": {
"unit": "base-code-point-plus-optional-variation-selector",
"maxVariationSelectors": 1,
"variationSelectorMustFollowBase": true,
"isolatedVariationSelector": "independent-atom"
},
"locales": {
"classes": [ "zh-Hans", "zh-Hant", "ja", "other" ],
"primaryLanguageBoundary": "exact-or-hyphen-subtag",
"classificationOrder": [
"ja-primary-language",
"zh-explicit-script",
"zh-traditional-region",
"zh-bare-default",
"zh-remaining",
"other"
],
"zh": {
"explicitTraditionalSubtag": "hant",
"explicitSimplifiedSubtag": "hans",
"traditionalRegions": [ "tw", "hk", "mo" ],
"bare": "zh-Hant",
"remaining": "zh-Hans"
},
"ja": "ja",
"defaultPolicies": {
"zh-Hans": "kaiming",
"zh-Hant": "quanjiao",
"ja": "quanjiao",
"other": "quanjiao"
},
"resolutionPriority": [
"element-attribute-or-style-policy-nearest-ancestor",
"locale-class-policy",
"global-style",
"default-policy"
]
},
"punctuationStyles": {
"baselineHalfWidthClasses": {
"quanjiao": [],
"kaiming": [ "pause", "open", "close" ],
"banjiao": [ "stop", "pause", "open", "close" ]
},
"adjacency": {
"rule": "squeeze-each-facing-blank-side-independently",
"sentenceBoundaryException": {
"left": "stop",
"right": "open",
"preserve": "left-right-blank"
}
}
},
"spacing": {
"gapEm": 0.25,
"directions": {
"cjkToAns": "ansAfterCjk",
"ansToCjk": "ansBeforeCjk",
"asymmetry": "at-sign-only-after-cjk"
},
"authorSpaceDisposition": {
"cjkAns": "remove-and-use-managed-gap",
"ansCjk": "remove-and-use-managed-gap",
"cjkCjk": "remove-without-gap",
"revertRestoresRemovedSpace": false
}
},
"lineBreaking": {
"prohibitedBreaks": {
"before": [ "close", "pause", "stop", "middle", "liga" ],
"after": [ "open" ]
},
"orphan": {
"defaultMeaningfulCjkChars": 2,
"counts": "cjk-atoms-not-classified-as-biaodian",
"excludes": [ "biaodian", "non-cjk" ]
}
},
"applicabilityLevels": {
"text": [ "spacing", "punctuation-classification-and-width" ],
"paragraph": [ "prohibited-breaks", "orphan-minimum", "long-word-hyphenation" ]
},
"semanticProcessing": {
"dependencies": [
{ "before": "atomize", "after": "classify" },
{ "before": "classify", "after": "derive-spacing" },
{ "before": "classify", "after": "derive-punctuation-width" },
{ "before": "classify", "after": "derive-break-constraints" },
{ "before": "classify", "after": "derive-orphan-constraint" }
],
"conditionalLineEdgeOutcomes": [
"punctuation-half-width-at-line-edge",
"remove-managed-gap-at-line-end"
]
},
"adapterBoundary": {
"platformNeutral": [
"unicode-character-classes",
"logical-atom-boundaries",
"locale-classification-and-policy",
"punctuation-width-semantics",
"spacing-decisions-and-distance",
"prohibited-break-relations",
"orphan-count",
"applicability-levels-and-semantic-dependencies"
],
"excludedAdapterMechanisms": [
"generated-node-or-element-representation",
"ownership-and-reversion-bookkeeping",
"rendering-primitive-selection",
"generated-line-break-hints",
"mutation-observation-or-rerun-lifecycle",
"layout-measurement",
"platform-specific-workarounds"
]
}
}
+389
View File
@@ -0,0 +1,389 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://juzhen.dev/schemas/juzhen-rules.schema.json",
"title": "Juzhen shared typography rules",
"type": "object",
"additionalProperties": false,
"required": [
"schemaVersion",
"rulesetVersion",
"unicodeProfile",
"characterClasses",
"atoms",
"locales",
"punctuationStyles",
"spacing",
"lineBreaking",
"applicabilityLevels",
"semanticProcessing",
"adapterBoundary"
],
"properties": {
"$schema": { "type": "string" },
"schemaVersion": { "const": 1 },
"rulesetVersion": {
"type": "string",
"pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)$"
},
"unicodeProfile": {
"type": "object",
"additionalProperties": false,
"required": [ "version", "coverage" ],
"properties": {
"version": { "const": "17.0.0" },
"coverage": { "type": "string", "minLength": 1 }
}
},
"characterClasses": {
"type": "object",
"additionalProperties": false,
"required": [
"representation",
"cjk",
"ansAfterCjk",
"ansBeforeCjk",
"variationSelectors",
"biaodian",
"overrideSemantics"
],
"properties": {
"representation": { "const": "unicode-code-point-ranges" },
"cjk": { "$ref": "#/$defs/codePointSet" },
"ansAfterCjk": { "$ref": "#/$defs/codePointSet" },
"ansBeforeCjk": { "$ref": "#/$defs/codePointSet" },
"variationSelectors": { "$ref": "#/$defs/codePointSet" },
"biaodian": {
"type": "object",
"additionalProperties": false,
"required": [ "classificationOrder", "classes" ],
"properties": {
"classificationOrder": {
"type": "array",
"prefixItems": [
{ "const": "stop" },
{ "const": "pause" },
{ "const": "open" },
{ "const": "close" },
{ "const": "middle" },
{ "const": "liga" }
],
"items": false,
"minItems": 6,
"maxItems": 6
},
"classes": {
"type": "object",
"additionalProperties": false,
"required": [ "stop", "pause", "open", "close", "middle", "liga" ],
"properties": {
"stop": { "$ref": "#/$defs/codePointSet" },
"pause": { "$ref": "#/$defs/codePointSet" },
"open": { "$ref": "#/$defs/codePointSet" },
"close": { "$ref": "#/$defs/codePointSet" },
"middle": { "$ref": "#/$defs/codePointSet" },
"liga": { "$ref": "#/$defs/codePointSet" }
}
}
}
},
"overrideSemantics": {
"type": "object",
"additionalProperties": false,
"required": [ "mode", "westernTargets", "removalAllowed", "reclassificationAllowed" ],
"properties": {
"mode": { "const": "append" },
"westernTargets": {
"type": "array",
"prefixItems": [ { "const": "ansAfterCjk" }, { "const": "ansBeforeCjk" } ],
"items": false,
"minItems": 2,
"maxItems": 2
},
"removalAllowed": { "const": false },
"reclassificationAllowed": { "const": false }
}
}
}
},
"atoms": {
"type": "object",
"additionalProperties": false,
"required": [
"unit",
"maxVariationSelectors",
"variationSelectorMustFollowBase",
"isolatedVariationSelector"
],
"properties": {
"unit": { "const": "base-code-point-plus-optional-variation-selector" },
"maxVariationSelectors": { "const": 1 },
"variationSelectorMustFollowBase": { "const": true },
"isolatedVariationSelector": { "const": "independent-atom" }
}
},
"locales": { "$ref": "#/$defs/locales" },
"punctuationStyles": { "$ref": "#/$defs/punctuationStyles" },
"spacing": { "$ref": "#/$defs/spacing" },
"lineBreaking": { "$ref": "#/$defs/lineBreaking" },
"applicabilityLevels": {
"type": "object",
"additionalProperties": false,
"required": [ "text", "paragraph" ],
"properties": {
"text": { "$ref": "#/$defs/stringArray" },
"paragraph": { "$ref": "#/$defs/stringArray" }
}
},
"semanticProcessing": {
"type": "object",
"additionalProperties": false,
"required": [ "dependencies", "conditionalLineEdgeOutcomes" ],
"properties": {
"dependencies": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": [ "before", "after" ],
"properties": {
"before": { "$ref": "#/$defs/semanticStage" },
"after": { "$ref": "#/$defs/semanticStage" }
}
}
},
"conditionalLineEdgeOutcomes": {
"type": "array",
"prefixItems": [
{ "const": "punctuation-half-width-at-line-edge" },
{ "const": "remove-managed-gap-at-line-end" }
],
"items": false,
"minItems": 2,
"maxItems": 2
}
}
},
"adapterBoundary": {
"type": "object",
"additionalProperties": false,
"required": [ "platformNeutral", "excludedAdapterMechanisms" ],
"properties": {
"platformNeutral": { "$ref": "#/$defs/stringArray" },
"excludedAdapterMechanisms": { "$ref": "#/$defs/stringArray" }
}
}
},
"$defs": {
"hexCodePoint": {
"type": "string",
"pattern": "^(?:[0-9A-F]{4}|[0-9A-F]{5}|10[0-9A-F]{4})$"
},
"codePointEntry": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": [ "codePoint" ],
"properties": {
"codePoint": { "$ref": "#/$defs/hexCodePoint" }
}
},
{
"type": "object",
"additionalProperties": false,
"required": [ "start", "end" ],
"properties": {
"start": { "$ref": "#/$defs/hexCodePoint" },
"end": { "$ref": "#/$defs/hexCodePoint" }
}
}
]
},
"codePointSet": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": { "$ref": "#/$defs/codePointEntry" }
},
"stringArray": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"uniqueItems": true
},
"style": {
"enum": [ "quanjiao", "kaiming", "banjiao" ]
},
"semanticStage": {
"enum": [
"atomize",
"classify",
"derive-spacing",
"derive-punctuation-width",
"derive-break-constraints",
"derive-orphan-constraint"
]
},
"locales": {
"type": "object",
"additionalProperties": false,
"required": [
"classes",
"primaryLanguageBoundary",
"classificationOrder",
"zh",
"ja",
"defaultPolicies",
"resolutionPriority"
],
"properties": {
"classes": {
"type": "array",
"prefixItems": [
{ "const": "zh-Hans" },
{ "const": "zh-Hant" },
{ "const": "ja" },
{ "const": "other" }
],
"items": false,
"minItems": 4,
"maxItems": 4
},
"primaryLanguageBoundary": { "const": "exact-or-hyphen-subtag" },
"classificationOrder": { "$ref": "#/$defs/stringArray" },
"zh": {
"type": "object",
"additionalProperties": false,
"required": [ "explicitTraditionalSubtag", "explicitSimplifiedSubtag", "traditionalRegions", "bare", "remaining" ],
"properties": {
"explicitTraditionalSubtag": { "const": "hant" },
"explicitSimplifiedSubtag": { "const": "hans" },
"traditionalRegions": {
"type": "array",
"prefixItems": [ { "const": "tw" }, { "const": "hk" }, { "const": "mo" } ],
"items": false,
"minItems": 3,
"maxItems": 3
},
"bare": { "const": "zh-Hant" },
"remaining": { "const": "zh-Hans" }
}
},
"ja": { "const": "ja" },
"defaultPolicies": {
"type": "object",
"additionalProperties": false,
"required": [ "zh-Hans", "zh-Hant", "ja", "other" ],
"properties": {
"zh-Hans": { "$ref": "#/$defs/style" },
"zh-Hant": { "$ref": "#/$defs/style" },
"ja": { "$ref": "#/$defs/style" },
"other": { "$ref": "#/$defs/style" }
}
},
"resolutionPriority": {
"type": "array",
"prefixItems": [
{ "const": "element-attribute-or-style-policy-nearest-ancestor" },
{ "const": "locale-class-policy" },
{ "const": "global-style" },
{ "const": "default-policy" }
],
"items": false,
"minItems": 4,
"maxItems": 4
}
}
},
"punctuationStyles": {
"type": "object",
"additionalProperties": false,
"required": [ "baselineHalfWidthClasses", "adjacency" ],
"properties": {
"baselineHalfWidthClasses": {
"type": "object",
"additionalProperties": false,
"required": [ "quanjiao", "kaiming", "banjiao" ],
"properties": {
"quanjiao": { "$ref": "#/$defs/stringArray" },
"kaiming": { "$ref": "#/$defs/stringArray" },
"banjiao": { "$ref": "#/$defs/stringArray" }
}
},
"adjacency": {
"type": "object",
"additionalProperties": false,
"required": [ "rule", "sentenceBoundaryException" ],
"properties": {
"rule": { "const": "squeeze-each-facing-blank-side-independently" },
"sentenceBoundaryException": {
"type": "object",
"additionalProperties": false,
"required": [ "left", "right", "preserve" ],
"properties": {
"left": { "const": "stop" },
"right": { "const": "open" },
"preserve": { "const": "left-right-blank" }
}
}
}
}
}
},
"spacing": {
"type": "object",
"additionalProperties": false,
"required": [ "gapEm", "directions", "authorSpaceDisposition" ],
"properties": {
"gapEm": { "const": 0.25 },
"directions": {
"type": "object",
"additionalProperties": false,
"required": [ "cjkToAns", "ansToCjk", "asymmetry" ],
"properties": {
"cjkToAns": { "const": "ansAfterCjk" },
"ansToCjk": { "const": "ansBeforeCjk" },
"asymmetry": { "const": "at-sign-only-after-cjk" }
}
},
"authorSpaceDisposition": {
"type": "object",
"additionalProperties": false,
"required": [ "cjkAns", "ansCjk", "cjkCjk", "revertRestoresRemovedSpace" ],
"properties": {
"cjkAns": { "const": "remove-and-use-managed-gap" },
"ansCjk": { "const": "remove-and-use-managed-gap" },
"cjkCjk": { "const": "remove-without-gap" },
"revertRestoresRemovedSpace": { "const": false }
}
}
}
},
"lineBreaking": {
"type": "object",
"additionalProperties": false,
"required": [ "prohibitedBreaks", "orphan" ],
"properties": {
"prohibitedBreaks": {
"type": "object",
"additionalProperties": false,
"required": [ "before", "after" ],
"properties": {
"before": { "$ref": "#/$defs/stringArray" },
"after": { "$ref": "#/$defs/stringArray" }
}
},
"orphan": {
"type": "object",
"additionalProperties": false,
"required": [ "defaultMeaningfulCjkChars", "counts", "excludes" ],
"properties": {
"defaultMeaningfulCjkChars": { "const": 2 },
"counts": { "const": "cjk-atoms-not-classified-as-biaodian" },
"excludes": { "$ref": "#/$defs/stringArray" }
}
}
}
}
}
}