# Fonts

[繁體中文](FONTS-zh.md)

## Structure

```text
core/fonts/      stable framework logic
catalog/impe-fonts-catalog.tex
modules/fonts/   special font-support modules
assets/fonts/    local font library (not tracked in Git)
```

The public subsystem entry is:

```text
core/fonts/impe-fonts-system.tex
```

## Responsibilities

### `core/fonts/`

This layer owns the stable mechanics:

- defaults
- style fallback resolution
- writing model
- script / behavior handling
- declaration interface
- family registry behavior

Current core files:

- `impe-fonts-system.tex`
  Public entry for the font subsystem. It loads the defaults layer, the family registry, and `catalog/impe-fonts-catalog.tex`.
- `impe-fonts-defaults.tex`
  Loads the internal font layers and defines the default values for scope, script class, fallback mode, writing model, behavior, and backend.
- `impe-fonts-style.tex`
  Resolves style fallback chains such as `bold`, `italic`, `bolditalic`, `sans*`, and `mono*`.
- `impe-fonts-writing.tex`
  Defines and validates the writing model fields: inline axis, inline direction, and block progression.
- `impe-fonts-script.tex`
  Applies the core defaults and validates internal routing state such as `scriptclass`, `preservespaces`, and backend choice.
- `impe-fonts-behavior.tex`
  Defines inline/block behavior routing, currently including normal, RTL, and
  Tibetan break behavior hooks.
- `impe-fonts-interface.tex`
  The main declaration engine. It parses registered family fields, resolves
  concrete font options, defines public commands, and owns the built-in
  `layout = vertical` route.
- `impe-fonts-registry.tex`
  Stores low-level declaration entries before they are turned into usable local/global families.
- `impe-fonts-registry-modes.tex`
  Tracks family loading mode (`local` / `global`) and performs on-demand family activation.
- `impe-fonts-externalized.tex`
  Provides the stable externalized-render pipeline: cache naming, external subdocument generation, shell-out, and PDF reinsertion. It invokes the portable `impe-externalized-render.lua` helper with `texlua`; the requested TeX engine is resolved from `PATH`.
- `impe-fonts-helpers.tex`
  Small shared helper primitives used by the font framework.

### `catalog/impe-fonts-catalog.tex`

This is the centralized registration file.

It declares:

- family ids
- command names
- font file paths
- script / language / feature metadata
- real local/global modes
- optional built-in layout routes and special module hooks

### `modules/fonts/`

This layer now holds only script-specific special implementations that are not part of the stable generic core.

Current modules:

- `impe-font-pahlavi.tex`
  Pahlavi-specific shaping routing
- `impe-font-khitan_small.tex`
  Khitan Small Script linear and explicit stacked rendering commands
- `impe-font-mlmodern.tex`
  NFSS/package integration for the `mlmodern` family

Notes:

- `layout = vertical` is now built into `core/fonts/impe-fonts-interface.tex`
- ordinary OpenType shaping is handled by normal registration fields: `script`, `language`, and `features`
- new families that only need standard fontspec shaping should normally be handled by registration, not by adding a new file under `modules/fonts/`

## Declaration Model

The font layer supports:

- global bindings
- local command families
- centralized family registration with `\FontRegisterFamily`

### Ordinary Local Family

This is the target format for normal user-added families. A normal local family
uses one directory under `assets/fonts/` and does not reserve future global
behavior:

```tex
\FontRegisterFamily{
  id = test,
  defaultmode = local,
  local = {
    command = TEST,
    name = test_local,
    path = \CatalogFontRoot/test/,
    regular = test_font.ttf,
    bold = test_font_bold.ttf,
    italic = test_font_italic.ttf,
    bolditalic = test_font_bolditalic.ttf,
    sans = test_sans.ttf,
    sansbold = test_sans_bold.ttf,
    sansitalic = test_sans_italic.ttf,
    sansbolditalic = test_sans_bolditalic.ttf,
    script = Devanagari,
    language = Sanskrit,
    features = { RawFeature = { script=deva } },
    fallbackmode = soft
  }
}
```

Only `command`, `name`, `path`, and `regular` are normally required. The other
style slots may be omitted and will follow the core fallback chain. `sans*`
faces are mapped automatically inside the local command; `mono*` is reserved for
global/system families and explicit advanced registrations.

Ordinary local families should not contain:

- `globalkind` / `globalstatus`
- style-specific path fields when they equal the main `path`
- `maptextsf` / `maptexttt`
- `scriptclass`, except for CJK routing
- `inlinebehavior`, `blockbehavior`, or `blockalign`, except for core-maintained
  script-specific behavior entries
- vertical/externalized fields
- `mono` / `monobold`

### Global Family

Only families with a real `global = {...}` block can be requested through the
explicit `globalfonts` override. Most global-capable families are Latin/CJK/system families such
as `cmu`, `noto`, `times`, `gentium`, `charis`, `libertinus`, `japanese`,
`shanggu`, and `sim`.
Complex-script globals such as `hindi`, `sanskrit`, and `tibetan` are
range-limited with `unicodeblocks`, so they only switch fonts for their Unicode
blocks and do not remap Latin, Han, or other text. The `hindi` range global uses
the Devanagari ranges without Sanskrit-specific line-breaking rules. The
`sanskrit` range global adds Devanagari akshara-aware line breaking for the
base Devanagari block without breaking after virama before the next consonant.
Tibetan range globals also
preserve the core tsheg behavior: line breaks are allowed after `U+0F0B` /
`U+0F0C` only before Tibetan letters/signs, never before Tibetan punctuation. If a family has no `global`
block and is
requested in global mode, the registry now reports a direct "no global mode"
error instead of relying on a reserved status placeholder.

`cmu` and `times` are system/bundled exceptions: they may name fonts directly and
do not need `path = \CatalogFontRoot/<id>/`.

### CJK/Internal Routing

`scriptclass = cjk` is an internal routing hint used to select the xeCJK path.
CJK global families, including `japanese`, replace the document CJK main/sans/mono
channels through xeCJK rather than using Unicode-range intercharacter switching.
Ordinary non-CJK families do not need `scriptclass`; OpenType shaping should be
expressed with `script`, `language`, and `features`.

In automatic range routing, Japanese and Korean claim only their
language-specific scripts (kana and Hangul, respectively), plus CJK
punctuation. Shared Han ideographs remain on the document CJK family so a
Chinese font keeps priority. Use the local `\JP{...}` or `\KR{...}` command to
request Japanese or Korean Han glyph forms explicitly. Vietnamese Han-Nom is
likewise local-only and can be selected explicitly with `\HN{...}`.

### Shaping, Layout, and Special Modules

`script`, `language`, and `features = { RawFeature = { script=... } }` are
standard fontspec/OpenType shaping options. They are not special modules.

`layout = vertical` selects the core built-in vertical layout route for existing
Mongolian, Manchu, and Old Uyghur-style entries. Its parameters are:

- `verticalstrategy`
- `verticalrotation`
- `verticalorigin`
- `verticaltopcorrection`

`specialmodule` is reserved for external or custom TeX support modules under
`modules/fonts/`, such as:

- `pahlavi`
- `khitan_small`
- custom module names created by local extensions

For backward compatibility, the core still treats old
`specialmodule = vertical` declarations as `layout = vertical` and emits a
deprecation warning. New catalog entries and generated entries should not
write `specialmodule = vertical`.

## Registration Syntax

Font families are registered centrally in:

```text
catalog/impe-fonts-catalog.tex
```

The current model is based on `\FontRegisterFamily{...}` declarations.

An ordinary local entry looks like:

```tex
\FontRegisterFamily{
  id = hebrew,
  defaultmode = local,
  local = {
    command = HE,
    name = hebrew_local,
    path = \CatalogFontRoot/hebrew/,
    regular = NotoSerifHebrew-Regular.ttf,
    bold = NotoSerifHebrew-Bold.ttf,
    script = Hebrew
  }
}
```

In practice:

- `command` defines the local command name without a leading backslash
- `path` points to the font directory
- `regular` / `bold` / `italic` / `bolditalic` define concrete files
- `local = {...}` creates a local command family
- `global = {...}` binds a family into document-wide defaults
- `unicodeblocks = {...}` makes a global binding range-limited through XeLaTeX
  Unicode block transitions instead of replacing the whole main/sans/mono stack
- `layout = vertical` selects the built-in vertical layout route
- `verticalstrategy` / `verticalrotation` / `verticalorigin` / `verticaltopcorrection` configure `layout = vertical`
- `specialmodule` is only needed for external/custom TeX module routes
- standard OpenType shaping should be expressed with `script`, `language`, and `features`, not a separate generic shaping module

### Routing precedence and scalability

Explicit local commands have final authority inside their brace scope. IMPE's
Unicode-range transitions are suspended there, so a command such as `\HI{...}`
is not recaptured by another global Devanagari owner. Leaving the command scope
restores automatic routing.

Range routing creates transitions only for XeTeX intercharacter classes that
have actually been allocated, plus the paragraph-boundary class. A
begin-document pass adds transitions for classes allocated later by other
packages. Cross-block transitions owned by the same family remain empty so a
shaping run can continue across adjacent Unicode blocks.

When the `thai` family is loaded, IMPE selects XeTeX's ICU `th_TH` line-break
locale. This enables dictionary-based break opportunities in unspaced Thai prose
without inserting manual breakpoints.

## Minimal Examples

Automatic family-loading example:

```tex
\UseTemplateSet{
  fonts = {cmu,shanggu,hebrew,arabic}
}

\HE{שלום}
\AR{السلام}
```

Deliberate global override:

```tex
\UseFont{libertinus}[global]
```

This means:

- mode-free `fonts = {...}` follows each family's registered behavior
- `cmu` and `shanggu` apply their registered global behavior
- `hebrew` and `arabic` define their registered local commands
- an explicit `[global]` mode is used only when overriding that behavior on purpose

## Registered Families

The current catalog registers the following families. `fonts = {...}` follows
each family's registered automatic behavior; `globalfonts = {...}` is an
explicit request for global bindings and fails when a family has no global
definition.

| Family id | Local command | Default mode | Global available | Notes |
|---|---|---|---|---|
| `cmu` | `-` | `global` | yes | CMU Latin family; no bundled local command |
| `noto` | `NOT` | `local` | yes | Noto Latin family |
| `times` | `TIM` | `local` | yes | Windows Times/Arial/Consolas bundle |
| `gentium` | `GEN` | `local` | yes | Gentium Plus Latin family |
| `charis` | `CHA` | `local` | yes | Charis SIL Latin family |
| `libertinus` | `LIB` | `local` | yes | TeX Live Libertinus Serif/Sans/Mono family |
| `mlmodern` | `MLM` | `local` | no | MLModern legacy package route; math follows through the `math` feature |
| `anatolian` | `CA` | `local` | no | Carian |
| `coptic` | `CO` | `local` | no | Coptic |
| `bopomofo` | `ZY` | `local` | no | Bopomofo / Zhuyin |
| `cuneiform` | `CU` | `local` | no | Cuneiform |
| `glagolitic` | `GL` | `local` | no | Glagolitic |
| `italic` | `OI` | `local` | no | Old Italic |
| `hungarian` | `OH` | `local` | no | Old Hungarian |
| `runic` | `RU` | `local` | no | Runic |
| `armenian` | `HY` | `local` | no | Armenian |
| `hindi` | `HI` | `local` | yes | Hindi; global applies only to Devanagari Unicode blocks, without Sanskrit line-breaking rules |
| `sanskrit` | `SA` | `local` | yes | Sanskrit; global applies only to Devanagari and Vedic Unicode blocks |
| `devanagari` | `DEV` | `local` | no | Devanagari generic family |
| `tamil` | `TA` | `local` | no | Tamil |
| `brahmi` | `BR` | `local` | no | Brahmi |
| `georgian` | `KA` | `local` | no | Georgian |
| `tibetan` | `TI` | `local` | yes | Tibetan; global applies only to the Tibetan Unicode block |
| `arabic` | `AR` | `local` | no | Arabic |
| `urdu` | `UR` | `local` | no | Urdu |
| `aramaic` | `IA` | `local` | no | Imperial Aramaic |
| `nabataean` | `NB` | `local` | no | Nabataean |
| `hebrew` | `HE` | `local` | no | Hebrew |
| `syriac` | `SY` | `local` | no | Syriac |
| `syriac_eastern` | `SYE` | `local` | no | Eastern Syriac |
| `kharosthi` | `KH` | `local` | no | Kharosthi |
| `khitan_small` | `KHS` | `local` | no | Khitan Small Script |
| `pahlavi_parthian` | `PAR` | `local` | no | Inscriptional Parthian |
| `pahlavi_inscriptional` | `PAH` | `local` | no | Inscriptional Pahlavi |
| `pahlavi_psalter` | `PSP` | `local` | no | Psalter Pahlavi |
| `avestan` | `AV` | `local` | no | Avestan |
| `manichaean` | `MA` | `local` | no | Manichaean |
| `phoenician` | `PH` | `local` | no | Phoenician |
| `samaritan` | `SM` | `local` | no | Samaritan |
| `sogdian` | `SG` | `local` | no | Sogdian |
| `sogdian_old` | `SGO` | `local` | no | Old Sogdian |
| `chinese_simplified` | `SC` | `local` | no | Simplified Chinese |
| `chinese_traditional` | `TC` | `local` | no | Traditional Chinese |
| `japanese` | `JP` | `local` | yes | Japanese; global uses the xeCJK CJK font channels |
| `wenjin` | `WJ` | `local` | yes | WenJin Mincho with P0 primary and P2/P3 xeCJK fallback |
| `shanggu` | `-` | `global` | yes | Global CJK family for Han text |
| `sim` | `-` | `global` | yes | Windows CJK family |
| `korean` | `KR` | `local` | no | Korean |
| `tangut` | `TG` | `local` | no | Tangut |
| `mongolian` | `MO` | `local` | no | Mongolian |
| `mongolian_baiti` | `MOb` | `local` | no | Mongolian Baiti |
| `manchu` | `MC` | `local` | no | Manchu |
| `segoe` | `SEG` | `local` | no | Segoe UI Historic |
| `thai` | `TH` | `local` | no | Thai |
| `turkic` | `OT` | `local` | no | Old Turkic |
| `uyghur` | `UY` | `local` | no | Old Uyghur |
| `vietnamese_quocngu` | `VI` | `local` | no | Vietnamese Quoc Ngu |
| `vietnamese_hannom` | `HN` | `local` | no | Vietnamese Han-Nom |

For the current Arabic-script split:
- `arabic` uses Naskh for regular/bold, Ruqaa for italic/bolditalic, Noto Sans Arabic for `sans` / `sansbold`, and Noto Kufi Arabic for `sansitalic` / `sansbolditalic`.
- `urdu` keeps Nastaliq as its dedicated local family.

Families without a local command marker (`-`) are global-only in the current catalog.

## Family Mapping Notes

Only families with non-trivial internal mapping are listed here. Simple families that only provide the usual `regular` / `bold` / `italic` / `bolditalic` files are not repeated.

- `shanggu`
  This is a global Han/CJK family rather than a local command family. It is intended to cover the main Han text channels used in Chinese-facing layouts.
- `sim`
  This is the Windows-side global CJK fallback family. It serves the same role as a global Han/CJK binding rather than a local command family.
- `times`
  Uses a mixed Windows bundle rather than a single font family:
  `regular` / `bold` / `italic` / `bolditalic` come from Times New Roman,
  `sans*` comes from Arial,
  and `mono*` comes from Consolas.
- `arabic`
  Uses Naskh for `regular` / `bold`, Ruqaa for `italic` / `bolditalic`, Noto Sans Arabic for `sans` / `sansbold`, and Noto Kufi Arabic for `sansitalic` / `sansbolditalic`. It no longer declares local `mono*` faces.
- `urdu`
  Keeps Nastaliq as its dedicated local family and does not share that mapping with `arabic`.
- `khitan_small`
  `\KHS{...}` is the linear local-font command used by the showcase.
  `\KHSstack{...}` and `\KHSstackblock{...}` invoke the explicit cluster
  composer. Input clusters are separated by spaces; Type B inserts
  `U+16FE4 KHITAN SMALL SCRIPT FILLER` after the first character.

## Font Library Model

IMPE LaTeX System now separates the Git repository from the actual font library:

- the Git repository is intended to remain source-only
- `assets/fonts/` is treated as a local font library in the working tree
- local builds and local `full` releases may include that font library
- public Git pushes do not need to carry the font files themselves

This lets the project keep:

- a lightweight public repository
- a complete local working setup
- a locally generated `full` package when needed

## Local Font Library Path

The working assumption is:

- the public Git repository stays source-only
- the local font library lives under `assets/fonts/` in your working tree
- local development and local `full` release builds read from that location

In other words, the default local path is:

```text
assets/fonts/
```

If that directory is missing:

- normal repository work can still continue
- `core` release packaging still works
- `full` release packaging will stop with an explicit error instead of silently producing an incomplete package

## Bundled vs Non-Bundled Fonts

Not every font used by IMPE LaTeX System is supplied in the same way.

In particular:

- the `cmu` family is not stored under `assets/fonts/`
- it is expected to come from a TeX installation or the local font environment
- official project page:
  https://cm-unicode.sourceforge.io/

For third-party font license texts and redistribution notes, see:

- `font_licenses/`

## Fallback Behavior

IMPE LaTeX System supports two fallback modes for font declarations:

- `strict`
  Missing fonts are treated as errors.
- `soft`
  Missing fonts emit a warning and fall back to LaTeX default families.

The current default is `soft`.

In `soft` mode:

- local font commands fall back to LaTeX defaults such as `\rmfamily`, `\sffamily`, and `\ttfamily`
- global declarations do not override the current LaTeX defaults if the target font cannot be resolved

This preserves compilation while making the missing-font state visible in the log.

## Current Layout and Special-Module Model

The catalog now distinguishes standard shaping, core layout routes, and external
TeX modules:

- `script`, `language`, and `features` are standard fontspec shaping fields
- `layout = vertical` selects the built-in vertical layout route in
  `core/fonts/impe-fonts-interface.tex`
- `verticalstrategy`, `verticalrotation`, `verticalorigin`, and
  `verticaltopcorrection` are parameters for `layout = vertical`
- `specialmodule = pahlavi`, `specialmodule = khitan_small`, and
  `specialmodule = mlmodern` import
  script-specific support modules from `modules/fonts/`
- custom extension modules are also expressed through
  `specialmodule = <custom_module_name>`

This means:

- there is no separate dispatch table file anymore
- ordinary shaping is handled by fontspec options assembled from `script`,
  `language`, and `features`
- built-in vertical capabilities are handled inside `core/fonts/impe-fonts-interface.tex`
  and are not written as a `specialmodule`
- only genuinely script-specific or user-provided TeX logic remains under
  `modules/fonts/`
- Pahlavi special handling is only attached to the families that actually need
  it, such as `pahlavi_psalter`; Parthian and Inscriptional Pahlavi remain
  standard fontspec registrations when RawFeature is sufficient

## Mongolian Local Mapping and Redistribution

The `mongolian` local family currently uses:

- `regular = mnglwhiteotf.ttf`
- `italic = mnglwritingotf.ttf`
- `bold = mngltitleotf.ttf`
- `bolditalic = mnglartotf.ttf`
- `sans = NotoSansMongolian-Regular.ttf`

`MO` remains the ordinary linear Mongolian local command, while `MOv` is the vertical variant built on top of the same local family.

The `manchu` family follows the same vertical model:

- `MC` is the ordinary linear command
- `MCv` is the vertical variant

The `uyghur` family also uses the vertical route:

- `UY` is horizontal RTL
- `UYv` is the vertical left-to-right variant

Additional local-only Mongolian-family registrations:

- `mongolian_baiti` provides `\MOb`
  - `regular = monbaiti.ttf`
  - local-only Microsoft font
- `segoe` provides `\SEG`
  - `regular = seguihis.ttf`
  - local-only Microsoft font

Important redistribution note:

- the four `mngl*.ttf` files above are kept for local use but are **not** included in the public `full` release package
- their redistribution status has not yet been confirmed clearly enough for public bundling
- users who need them should obtain them from the original source themselves:
  http://www.mongolfont.com/cn/font/index.html
- `assets/fonts/mongolian_baiti/monbaiti.ttf` is a Microsoft font and is **not** included in the public `full` release package
  - reference:
    https://learn.microsoft.com/zh-tw/typography/font-list/mongolian-baiti
- `assets/fonts/segoe/seguihis.ttf` is a Microsoft font and is **not** included in the public `full` release package
  - reference:
    https://learn.microsoft.com/en-us/typography/font-list/segoe-ui-historic

## Syriac Local Mapping

The `syriac` family uses:

- `regular = SyrCOMEdessa.otf`
- `bold = SyrCOMMidyat.otf`
- `italic = SyrCOMJerusalem.otf`
- `bolditalic = SyrCOMJerusalemBold.otf`
- `sans = NotoSansSyriac-Regular.ttf`
- `sansbold = NotoSansSyriac-Bold.ttf`
- `sansitalic = NotoSansSyriacWestern-Regular.ttf`
- `sansbolditalic = NotoSansSyriacWestern-Bold.ttf`

The `syriac_eastern` family uses:

- `regular = SyrCOMAdiabene.otf`
- `bold = SyrCOMCtesiphon.otf`
- `italic = SyrCOMJerusalem.otf`
- `bolditalic = SyrCOMJerusalemBold.otf`
- `sans = NotoSansSyriacEastern-Regular.ttf`
- `sansbold = NotoSansSyriacEastern-Bold.ttf`
- `sansitalic = NotoSansSyriacWestern-Regular.ttf`
- `sansbolditalic = NotoSansSyriacWestern-Bold.ttf`

The bundled `SyrCOM*.otf` files now have their license text stored under `font_licenses/`.

## Audit Surface

The canonical public font audit is
`manual/showcase/impe-showcase.tex`. Focused automated checks live under
`tests/`.
