vtt-transfer 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vtt_transfer-0.1.0/.gitignore +128 -0
- vtt_transfer-0.1.0/LICENSE +21 -0
- vtt_transfer-0.1.0/NOTICE.md +11 -0
- vtt_transfer-0.1.0/PKG-INFO +217 -0
- vtt_transfer-0.1.0/README.md +187 -0
- vtt_transfer-0.1.0/doc/compilers.md +69 -0
- vtt_transfer-0.1.0/doc/static-instances-from-vf.md +157 -0
- vtt_transfer-0.1.0/pyproject.toml +52 -0
- vtt_transfer-0.1.0/src/vtt_transfer/__init__.py +14 -0
- vtt_transfer-0.1.0/src/vtt_transfer/cli.py +98 -0
- vtt_transfer-0.1.0/src/vtt_transfer/control.py +1011 -0
- vtt_transfer-0.1.0/src/vtt_transfer/core.py +1036 -0
- vtt_transfer-0.1.0/src/vtt_transfer/fresh.py +190 -0
- vtt_transfer-0.1.0/src/vtt_transfer/hprj.py +222 -0
- vtt_transfer-0.1.0/src/vtt_transfer/i18n.py +429 -0
- vtt_transfer-0.1.0/src/vtt_transfer/measure.py +344 -0
- vtt_transfer-0.1.0/src/vtt_transfer/report.py +403 -0
- vtt_transfer-0.1.0/src/vtt_transfer/ship.py +174 -0
- vtt_transfer-0.1.0/src/vtt_transfer/static.py +142 -0
- vtt_transfer-0.1.0/src/vtt_transfer/talk.py +711 -0
- vtt_transfer-0.1.0/src/vtt_transfer/templates/NOTICE.md +29 -0
- vtt_transfer-0.1.0/src/vtt_transfer/templates/chargrp.txt +669 -0
- vtt_transfer-0.1.0/src/vtt_transfer/templates/cvttmpl.txt +508 -0
- vtt_transfer-0.1.0/src/vtt_transfer/templates/fpgmtmpl.txt +5329 -0
- vtt_transfer-0.1.0/src/vtt_transfer/var2static.py +139 -0
- vtt_transfer-0.1.0/src/vtt_transfer/xscale.py +448 -0
- vtt_transfer-0.1.0/tests/smoke.py +124 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
.DS_Store
|
|
2
|
+
# Byte-compiled / optimized / DLL files
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*$py.class
|
|
6
|
+
|
|
7
|
+
# C extensions
|
|
8
|
+
*.so
|
|
9
|
+
|
|
10
|
+
# Distribution / packaging
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
pip-wheel-metadata/
|
|
25
|
+
share/python-wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# PyInstaller
|
|
32
|
+
# Usually these files are written by a python script from a template
|
|
33
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
34
|
+
*.manifest
|
|
35
|
+
*.spec
|
|
36
|
+
|
|
37
|
+
# Installer logs
|
|
38
|
+
pip-log.txt
|
|
39
|
+
pip-delete-this-directory.txt
|
|
40
|
+
|
|
41
|
+
# Unit test / coverage reports
|
|
42
|
+
htmlcov/
|
|
43
|
+
.tox/
|
|
44
|
+
.nox/
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
target/
|
|
76
|
+
|
|
77
|
+
# Jupyter Notebook
|
|
78
|
+
.ipynb_checkpoints
|
|
79
|
+
|
|
80
|
+
# IPython
|
|
81
|
+
profile_default/
|
|
82
|
+
ipython_config.py
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
.python-version
|
|
86
|
+
|
|
87
|
+
# pipenv
|
|
88
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
89
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
90
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
91
|
+
# install all needed dependencies.
|
|
92
|
+
#Pipfile.lock
|
|
93
|
+
|
|
94
|
+
# celery beat schedule file
|
|
95
|
+
celerybeat-schedule
|
|
96
|
+
|
|
97
|
+
# SageMath parsed files
|
|
98
|
+
*.sage.py
|
|
99
|
+
|
|
100
|
+
# Environments
|
|
101
|
+
.env
|
|
102
|
+
.venv
|
|
103
|
+
env/
|
|
104
|
+
venv/
|
|
105
|
+
ENV/
|
|
106
|
+
env.bak/
|
|
107
|
+
venv.bak/
|
|
108
|
+
|
|
109
|
+
# Spyder project settings
|
|
110
|
+
.spyderproject
|
|
111
|
+
.spyproject
|
|
112
|
+
|
|
113
|
+
# Rope project settings
|
|
114
|
+
.ropeproject
|
|
115
|
+
|
|
116
|
+
# mkdocs documentation
|
|
117
|
+
/site
|
|
118
|
+
|
|
119
|
+
# mypy
|
|
120
|
+
.mypy_cache/
|
|
121
|
+
.dmypy.json
|
|
122
|
+
dmypy.json
|
|
123
|
+
|
|
124
|
+
# Pyre type checker
|
|
125
|
+
.pyre/
|
|
126
|
+
|
|
127
|
+
# Local working area (font builds, reports)
|
|
128
|
+
/work/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Lubovenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## Microsoft VisualTrueType
|
|
4
|
+
|
|
5
|
+
https://github.com/microsoft/VisualTrueType, MIT License, Copyright (c) Microsoft Corporation.
|
|
6
|
+
|
|
7
|
+
- `src/vtt_transfer/talk.py` ports the VTT Talk scanner, argument matching and command table of
|
|
8
|
+
`src/TMTParser.cpp`.
|
|
9
|
+
- `src/vtt_transfer/control.py` ports the Control Program parser of `src/CvtManager.cpp`.
|
|
10
|
+
- `src/vtt_transfer/templates/fpgmtmpl.txt`, `cvttmpl.txt` and `chargrp.txt` are copied unchanged from
|
|
11
|
+
`templates/` (see `src/vtt_transfer/templates/NOTICE.md`, which carries the full license text).
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: vtt-transfer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Transfer VTT (Visual TrueType) hinting sources to a new version of a TrueType font, with an HTML report of what is left to hint
|
|
5
|
+
Project-URL: Homepage, https://github.com/typedev/vtt-transfer
|
|
6
|
+
Project-URL: Repository, https://github.com/typedev/vtt-transfer
|
|
7
|
+
Project-URL: Issues, https://github.com/typedev/vtt-transfer/issues
|
|
8
|
+
Author: Alexander Lubovenko
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
License-File: NOTICE.md
|
|
12
|
+
Keywords: fonts,hinting,truetype,variable fonts,visual truetype,vtt
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
23
|
+
Classifier: Topic :: Text Processing :: Fonts
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: brotli>=1.1
|
|
26
|
+
Requires-Dist: fonttools>=4.60
|
|
27
|
+
Requires-Dist: skia-pathops>=0.8
|
|
28
|
+
Requires-Dist: vttcompilepy>=0.0.1.13
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# vtt-transfer
|
|
32
|
+
|
|
33
|
+
Transfers VTT hinting sources from a hinted version of a TrueType font (static or variable) to a new version of the same font and writes an HTML report: what was transferred, what needs extra hinting, what to check.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
uv tool install vtt-transfer # or: pipx install vtt-transfer
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This puts `vtt-transfer`, `vtt-var2static` and `vtt-ship` on the PATH. Python 3.11 or newer; the VTT
|
|
42
|
+
compiler comes from [vttcompilepy](https://pypi.org/project/vttcompilepy/) wheels (Windows, macOS,
|
|
43
|
+
Linux x86_64).
|
|
44
|
+
|
|
45
|
+
From a checkout (`-e` picks up code changes without reinstalling):
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
uv tool install -e /path/to/vtt-transfer
|
|
49
|
+
# or only the project's local environment:
|
|
50
|
+
cd /path/to/vtt-transfer && uv sync
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
vtt-transfer OLD.VTT.ttf NEW.ttf # after installation
|
|
57
|
+
uv run vtt-transfer OLD.VTT.ttf NEW.ttf # from the vtt-transfer folder
|
|
58
|
+
uv run --project /path/to/vtt-transfer vtt-transfer OLD.VTT.ttf NEW.ttf # from any folder
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Next to `NEW.ttf` you get:
|
|
62
|
+
|
|
63
|
+
- `NEW.VTT.ttf` — the new font with VTT sources (TSI0–TSI3, TSI5), `cvt`, `fpgm`, `prep`, `gasp` and `cvar`/`TSIC` (if present), compiled with vttcompilepy. It opens in VTT and work continues as usual.
|
|
64
|
+
- `NEW.vtt-transfer-report.html` — the report.
|
|
65
|
+
|
|
66
|
+
Options: `-o` output path, `--report` report path, `--report-lang en|ru` report language (default `ru`), `--no-compile` skip compilation, `--no-remap` no point remapping, `-q`.
|
|
67
|
+
|
|
68
|
+
Exit code: 0 — all good, 1 — verification errors (see the report), 2 — transfer impossible (no TSI tables, different UPM).
|
|
69
|
+
|
|
70
|
+
## How glyphs are matched
|
|
71
|
+
|
|
72
|
+
1. **By name.** If a glyph is gone, a rename is looked for: the same Unicode and the same point structure, or a single new glyph with identical points.
|
|
73
|
+
2. **Structure unchanged** (contours, points, on/off-curve, number of components): VTT Talk, assembly and bytecode are carried over as is. The GID in the headers is updated.
|
|
74
|
+
3. **Structure changed:** the reference points of the VTT Talk are looked up in the new outline by exact default-master coordinates; phantom points move with the outline. If all are found, the numbers are rewritten (status "remapped"); otherwise the hints are removed, the glyph goes to "Re-hint", and the old VTT Talk is attached in the report. VTT Talk is parsed with the command signatures of Microsoft's compiler, so every command whose point arguments are typed is remapped, points of vector overrides (`YLink(0,4:9>18)`) included; commands with untyped arguments (Call, Inline/ASM, BeginCodePath, AutoHinter) and the obsolete ones the compiler refuses (Tail, DiagSerifs…) are manual work.
|
|
75
|
+
**Composite decomposed into a simple glyph** (e.g. after overlap removal): the VTT Talk is assembled from the components' talk, with point numbers shifted by each component's position in the flattened point list, and then remapped the same way. A component's phantom point is kept only if it lands exactly on a phantom point of the glyph (taking the component offset into account); otherwise it is lost. Scaled or flipped components are not reused. The composite's own instructions are lost and flagged.
|
|
76
|
+
4. **Composites:** the assembly code (`OFFSET`/`SOFFSET`) is always regenerated from the new `glyf` (GIDs, offsets, transformations, USE_MY_METRICS), with prefixes (`OVERLAP[]` etc.) as in the source. The composite's own instructions are kept only if the point structure of its components did not change.
|
|
77
|
+
5. **New simple glyphs:** if the points exactly match a hinted old glyph, all hints are copied; if they are shifted along X only (combining accents made from spacing ones), only Y commands are copied. The rest get a character group by analogy with glyphs of the same Unicode category in the source.
|
|
78
|
+
6. **New composites** are hinted if all their components are; the report shows which components they are waiting for.
|
|
79
|
+
|
|
80
|
+
## Checks
|
|
81
|
+
|
|
82
|
+
After compilation the tool verifies the result against the new font: outlines and composites match, and tables unrelated to hinting (`gvar`, `GSUB`, `GPOS`…) have unchanged content. A compile error in a single glyph removes its hints and goes to the report; the rest is compiled.
|
|
83
|
+
|
|
84
|
+
## Limitations
|
|
85
|
+
|
|
86
|
+
- **Build the new font with the same compiler and options as the hinted source.** fontc drops implied on-curve points (exactly halfway between two off-curve points), fontmake keeps them unless `--drop-implied-oncurves` is passed. On one family a fontc build broke 14 hinted glyphs (`O`, `Q`, `©`, `®`, `°`…) and their composites that a fontmake build of the same sources transferred cleanly. The report flags reference points lost this way and warns when many glyphs are hit. See [doc/compilers.md](https://github.com/typedev/vtt-transfer/blob/master/doc/compilers.md).
|
|
87
|
+
- Remapping uses default-master coordinates: a point moved by even one unit is considered lost. This is intentional.
|
|
88
|
+
- Composite code with point attachment (`ANCHOR`) is generated but flagged for manual review.
|
|
89
|
+
- VOLT tables (TSIV etc.) are not transferred.
|
|
90
|
+
|
|
91
|
+
## Autohinter project (.hprj)
|
|
92
|
+
|
|
93
|
+
If a VTThinter project lies next to the source (`family.hprj` for `family.VTT.ttf`) or is passed with `--hprj`, an updated copy (`NEW.hprj`) is written next to the result. The original file is not touched; `--no-hprj` disables the update.
|
|
94
|
+
|
|
95
|
+
- Each class is matched to a TSI5 group by the majority of its glyphs (for example: UpperCase → 2, LowerCase → 3, Figures → 4, Superiors → 1, Parens → 8, Accents → 9).
|
|
96
|
+
- Renamed glyphs are replaced in the lists in place, removed ones are dropped.
|
|
97
|
+
- New simple glyphs are added to the class of their group (the group is assigned the same way as in TSI5). Group 0 glyphs are not put into classes. Glyphs that became composites stay in the list and are flagged in the report.
|
|
98
|
+
- `TTFFileName`/`XMLFileName` are rewritten to the new font's name if it differs.
|
|
99
|
+
- Only these values change; formatting and line endings (CRLF) are preserved.
|
|
100
|
+
|
|
101
|
+
For a static cut by `vtt-var2static`, the VF's project (`family-vf.hprj` next to the VF, or `--hprj`) is
|
|
102
|
+
copied as `<name>.hprj` with `TTFFileName`/`XMLFileName` set to the static and every `cvt`/`value` pair
|
|
103
|
+
set to the static's CVT (the same as the VF's without `cvar`; a value that already disagreed with the
|
|
104
|
+
VF's CVT is corrected and printed). The glyph lists stay as they are. The stem range of each class is
|
|
105
|
+
checked against the strokes measured on the static (the thickest straight or round stroke, by the
|
|
106
|
+
reference glyphs of `vtt_transfer.measure`: H I L / E, n m h u / z, 1 / 2 4 7, O o 0); a class outside
|
|
107
|
+
its `stem min`–`max` is printed as a warning — bold statics need a wider range than the VF's default
|
|
108
|
+
master. Classes without reference glyphs (superiors, parens, accents) are not measured.
|
|
109
|
+
|
|
110
|
+
## Statics cut from a hinted VF: vtt-var2static
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
vtt-var2static path/family-vf.VTT.ttf [-o DIR] [-i "Condensed Light" -i Bold] [--keep-overlaps] [--ship [--woff2]]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Cuts every named instance of a hinted variable font (or only the ones given with `-i`: subfamily or
|
|
117
|
+
file name, case and spaces ignored) into `DIR/<name>/` (default `statics/` next to the font):
|
|
118
|
+
|
|
119
|
+
`<name>` is the font's file name without `-vf`/`_vf` plus the instance's subfamily without spaces
|
|
120
|
+
(`family-vf.VTT.ttf`, `Condensed Light` → `family-CondensedLight`); an italic VF's `_italic`/`-italic`
|
|
121
|
+
is dropped when the subfamily says Italic, so `family_italic-vf` gives `family-BoldItalic`.
|
|
122
|
+
|
|
123
|
+
- `<name>.overlap.VTT.ttf` — the instance with overlaps and VTT sources (the transfer source);
|
|
124
|
+
- `<name>.ttf` — overlaps removed, hinting stripped (the transfer target);
|
|
125
|
+
- `<name>.VTT.ttf` — the target with the hints transferred and compiled; open it in VTT;
|
|
126
|
+
- `<name>.vtt-transfer-report.html` — the transfer report: what was remapped, what needs re-hinting;
|
|
127
|
+
- `<name>.hprj` — the VF's VTThinter project for this static, if the VF has one (see below).
|
|
128
|
+
|
|
129
|
+
`--keep-overlaps` skips overlap removal: the instance keeps its hints as they are and is written as
|
|
130
|
+
`<name>.VTT.ttf`, with nothing to transfer. `--ship` also writes release fonts without VTT sources into
|
|
131
|
+
`DIR/hinted/` (as `vtt-ship`, checked against `<name>.ttf`), `--woff2` adds `.woff2`. Options
|
|
132
|
+
`--report-lang`, `-q`, `--hprj`, `--no-hprj` as in `vtt-transfer`. Exit code: 0 — all good, 1 — some instance failed (cut,
|
|
133
|
+
transfer verification or ship check), 2 — the font has no VTT sources or no named instances.
|
|
134
|
+
|
|
135
|
+
### Library
|
|
136
|
+
|
|
137
|
+
`vtt_transfer.static` cuts the named instances of a hinted variable font
|
|
138
|
+
(`doc/static-instances-from-vf.md`): `named_instances(vf, family)` lists them, `cut(vf_path, instance,
|
|
139
|
+
out_dir, remove_overlaps=True)` writes `<name>.overlap.VTT.ttf` (the instance with overlaps and VTT
|
|
140
|
+
sources — the transfer source) and, with overlap removal, `<name>.ttf` (overlaps removed, hinting
|
|
141
|
+
stripped — the transfer target). Overlap removal can turn a hinted on-curve point into an implied one
|
|
142
|
+
(exactly between its off-curve neighbours); `cut` puts such points back, so the outline is the same and
|
|
143
|
+
the remap finds them. Transferring source → target carries the hints over by exact remap.
|
|
144
|
+
`vtt-var2static` runs it for every instance. Needs `skia-pathops`.
|
|
145
|
+
|
|
146
|
+
## VTT sources from scratch (library)
|
|
147
|
+
|
|
148
|
+
`vtt_transfer.fresh.prepare(font)` gives an unhinted static TrueType font VTT sources from Microsoft's
|
|
149
|
+
templates (`src/vtt_transfer/templates/`, MIT, from microsoft/VisualTrueType — see `NOTICE.md`), as VTT's
|
|
150
|
+
"Prepare Font" + "Control Program Only" would: glyf instructions and `fpgm`/`prep`/`cvt` dropped; the
|
|
151
|
+
standard font program; the Control Program template with the values of `vtt_transfer.measure` written
|
|
152
|
+
into its CVT lines (`2: 690 /* cap height */ /* measured: H E F … (17/17) */`, unmeasured slots marked);
|
|
153
|
+
character groups (TSI5) from `chargrp.txt` by Unicode, unencoded glyphs by their base name, else by Unicode
|
|
154
|
+
category; empty Talk, composites with `OFFSET[R]` assembly. The compiler generates the pre-program from
|
|
155
|
+
the Control Program. Returns the compiled font and a report (measurements, group counts).
|
|
156
|
+
|
|
157
|
+
`vtt_transfer.measure.measure(font)`: heights from the dominant cluster of reference glyph
|
|
158
|
+
tops/bottoms (Latin + Cyrillic; overshoots relative to their height), stems from scanline ink runs, kept
|
|
159
|
+
apart by group (UpperCase / LowerCase / Figure), direction and straight/round, keyed by the template's
|
|
160
|
+
CVT numbers. Diagonals, serifs and italic CVTs are not
|
|
161
|
+
measured.
|
|
162
|
+
|
|
163
|
+
For re-measuring one CVT from glyphs the operator picks: `measure_height(font, glyphs, "top"|"bottom")`,
|
|
164
|
+
`measure_overshoot(font, glyphs, side, parent_value)` (round − flat) and `measure_stem(font, glyphs, axis,
|
|
165
|
+
where, band=…)` take a string of characters (through the cmap) or a list of glyph names and return
|
|
166
|
+
`{value, samples, total, glyphs, values, missing}` (`values`: each glyph's samples, to spot the one that
|
|
167
|
+
disagrees; an empty or unknown set gives `value` None). `default_glyphs(cvt)` gives the set and
|
|
168
|
+
parameters `measure` uses for a template CVT, None for the ones it does not measure.
|
|
169
|
+
|
|
170
|
+
## Control Program (library)
|
|
171
|
+
|
|
172
|
+
`vtt_transfer.control` parses and edits the VTT Control Program (TSI1 `cvt` source). It is a port of
|
|
173
|
+
the compiler's grammar (VisualTrueType `CvtManager.cpp`). Parsing is token based: attributes stay set
|
|
174
|
+
until changed, comments nest, and indentation means nothing. `parse(text)` returns a `Program` with
|
|
175
|
+
the GROUP/CATEGORY declarations (TSI5 group numbers), the settings, the `ASM` statements, every CVT
|
|
176
|
+
(value, `~`/`=` inheritance, break ppem, deltas, attributes, the first two comments of its line,
|
|
177
|
+
absolute value) and the errors with their lines. It never raises. `set_cvt(text, n, value=…, ppem=…,
|
|
178
|
+
deltas=…, name=…, note=…)` and `set_setting(text, key, value)` change only the edited tokens and keep
|
|
179
|
+
comments, spacing and line ends. `add_cvt` places a new CVT next to the CVTs with the same attributes
|
|
180
|
+
(heights get the first free number below 40, others the first free from 65) and gives a square height
|
|
181
|
+
the rounding pair (`ASM("SVTCA[Y]")`, `ASM("CALL[], n, 89")`) when the program does that for its other
|
|
182
|
+
heights. `remove_cvt` removes a CVT with its comments and rounding pair, and refuses a CVT that is
|
|
183
|
+
another CVT's parent; it does not look at glyph Talk. xscale and fresh are built on it.
|
|
184
|
+
|
|
185
|
+
## X-height scaling (library)
|
|
186
|
+
|
|
187
|
+
`vtt_transfer.xscale` adds ttfautohint-style x-height scaling to a VTT-hinted font, in its sources. VTT
|
|
188
|
+
rounds every height CVT on its own, so at a size where the x-height rounds up the caps may round down;
|
|
189
|
+
ttfautohint rounds the x-height (with overshoot) and scales all heights by the same factor before
|
|
190
|
+
rounding. `apply(font, Params(mode="both"|"up", limit=14, maxppem=0))` adds two functions to the fpgm
|
|
191
|
+
source and one `ASM("CALL[] …")` line in the Control Program before the first height CVT, so the
|
|
192
|
+
scaling runs in the pre-program before VTT's own rounding. `find_heights` reads the height CVTs
|
|
193
|
+
(SquareHeight / RoundHeight; the x-height is the LowerCase one equal to `OS/2.sxHeight`), `detect`
|
|
194
|
+
reports the state of the scaling in the sources (including a call made stale by CVT edits), `remove`
|
|
195
|
+
takes it out. Only TSI1 is edited; the caller compiles.
|
|
196
|
+
|
|
197
|
+
## Release fonts: vtt-ship
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
vtt-ship path/family-vf.VTT.ttf path/family-italic-vf.VTT.ttf [--woff2] # or uv run vtt-ship …
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Compiles the VTT sources (vttcompilepy, the same compiler as in VTT) and writes a font without the TSI* tables: hints, `cvt`, `fpgm`, `prep`, `gasp` stay. The result goes to a `hinted/` subfolder next to the source, named without `.VTT` (`-o DIR` to write elsewhere).
|
|
204
|
+
|
|
205
|
+
Before writing, the result is checked against the designer's unhinted build (by default `<name without .VTT>.ttf` next to it, or `--reference`): outlines, composites with offsets and all tables unrelated to hinting must match. This catches stale composite code in VTT (VTT builds composites from the OFFSET lines in the sources, not from `glyf`). On a mismatch or a compile error the file is not written (`--force` to write anyway, `--no-check` to skip the check). Exit code 1 if any font failed.
|
|
206
|
+
|
|
207
|
+
## Development
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
uv sync
|
|
211
|
+
uv run python tests/smoke.py # end-to-end run on a synthetic font, as in CI
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT, see [LICENSE](https://github.com/typedev/vtt-transfer/blob/master/LICENSE). Parts are ported from or copied out of Microsoft's
|
|
217
|
+
[VisualTrueType](https://github.com/microsoft/VisualTrueType) (MIT), see [NOTICE.md](https://github.com/typedev/vtt-transfer/blob/master/NOTICE.md).
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# vtt-transfer
|
|
2
|
+
|
|
3
|
+
Transfers VTT hinting sources from a hinted version of a TrueType font (static or variable) to a new version of the same font and writes an HTML report: what was transferred, what needs extra hinting, what to check.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
uv tool install vtt-transfer # or: pipx install vtt-transfer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This puts `vtt-transfer`, `vtt-var2static` and `vtt-ship` on the PATH. Python 3.11 or newer; the VTT
|
|
12
|
+
compiler comes from [vttcompilepy](https://pypi.org/project/vttcompilepy/) wheels (Windows, macOS,
|
|
13
|
+
Linux x86_64).
|
|
14
|
+
|
|
15
|
+
From a checkout (`-e` picks up code changes without reinstalling):
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
uv tool install -e /path/to/vtt-transfer
|
|
19
|
+
# or only the project's local environment:
|
|
20
|
+
cd /path/to/vtt-transfer && uv sync
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
vtt-transfer OLD.VTT.ttf NEW.ttf # after installation
|
|
27
|
+
uv run vtt-transfer OLD.VTT.ttf NEW.ttf # from the vtt-transfer folder
|
|
28
|
+
uv run --project /path/to/vtt-transfer vtt-transfer OLD.VTT.ttf NEW.ttf # from any folder
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Next to `NEW.ttf` you get:
|
|
32
|
+
|
|
33
|
+
- `NEW.VTT.ttf` — the new font with VTT sources (TSI0–TSI3, TSI5), `cvt`, `fpgm`, `prep`, `gasp` and `cvar`/`TSIC` (if present), compiled with vttcompilepy. It opens in VTT and work continues as usual.
|
|
34
|
+
- `NEW.vtt-transfer-report.html` — the report.
|
|
35
|
+
|
|
36
|
+
Options: `-o` output path, `--report` report path, `--report-lang en|ru` report language (default `ru`), `--no-compile` skip compilation, `--no-remap` no point remapping, `-q`.
|
|
37
|
+
|
|
38
|
+
Exit code: 0 — all good, 1 — verification errors (see the report), 2 — transfer impossible (no TSI tables, different UPM).
|
|
39
|
+
|
|
40
|
+
## How glyphs are matched
|
|
41
|
+
|
|
42
|
+
1. **By name.** If a glyph is gone, a rename is looked for: the same Unicode and the same point structure, or a single new glyph with identical points.
|
|
43
|
+
2. **Structure unchanged** (contours, points, on/off-curve, number of components): VTT Talk, assembly and bytecode are carried over as is. The GID in the headers is updated.
|
|
44
|
+
3. **Structure changed:** the reference points of the VTT Talk are looked up in the new outline by exact default-master coordinates; phantom points move with the outline. If all are found, the numbers are rewritten (status "remapped"); otherwise the hints are removed, the glyph goes to "Re-hint", and the old VTT Talk is attached in the report. VTT Talk is parsed with the command signatures of Microsoft's compiler, so every command whose point arguments are typed is remapped, points of vector overrides (`YLink(0,4:9>18)`) included; commands with untyped arguments (Call, Inline/ASM, BeginCodePath, AutoHinter) and the obsolete ones the compiler refuses (Tail, DiagSerifs…) are manual work.
|
|
45
|
+
**Composite decomposed into a simple glyph** (e.g. after overlap removal): the VTT Talk is assembled from the components' talk, with point numbers shifted by each component's position in the flattened point list, and then remapped the same way. A component's phantom point is kept only if it lands exactly on a phantom point of the glyph (taking the component offset into account); otherwise it is lost. Scaled or flipped components are not reused. The composite's own instructions are lost and flagged.
|
|
46
|
+
4. **Composites:** the assembly code (`OFFSET`/`SOFFSET`) is always regenerated from the new `glyf` (GIDs, offsets, transformations, USE_MY_METRICS), with prefixes (`OVERLAP[]` etc.) as in the source. The composite's own instructions are kept only if the point structure of its components did not change.
|
|
47
|
+
5. **New simple glyphs:** if the points exactly match a hinted old glyph, all hints are copied; if they are shifted along X only (combining accents made from spacing ones), only Y commands are copied. The rest get a character group by analogy with glyphs of the same Unicode category in the source.
|
|
48
|
+
6. **New composites** are hinted if all their components are; the report shows which components they are waiting for.
|
|
49
|
+
|
|
50
|
+
## Checks
|
|
51
|
+
|
|
52
|
+
After compilation the tool verifies the result against the new font: outlines and composites match, and tables unrelated to hinting (`gvar`, `GSUB`, `GPOS`…) have unchanged content. A compile error in a single glyph removes its hints and goes to the report; the rest is compiled.
|
|
53
|
+
|
|
54
|
+
## Limitations
|
|
55
|
+
|
|
56
|
+
- **Build the new font with the same compiler and options as the hinted source.** fontc drops implied on-curve points (exactly halfway between two off-curve points), fontmake keeps them unless `--drop-implied-oncurves` is passed. On one family a fontc build broke 14 hinted glyphs (`O`, `Q`, `©`, `®`, `°`…) and their composites that a fontmake build of the same sources transferred cleanly. The report flags reference points lost this way and warns when many glyphs are hit. See [doc/compilers.md](https://github.com/typedev/vtt-transfer/blob/master/doc/compilers.md).
|
|
57
|
+
- Remapping uses default-master coordinates: a point moved by even one unit is considered lost. This is intentional.
|
|
58
|
+
- Composite code with point attachment (`ANCHOR`) is generated but flagged for manual review.
|
|
59
|
+
- VOLT tables (TSIV etc.) are not transferred.
|
|
60
|
+
|
|
61
|
+
## Autohinter project (.hprj)
|
|
62
|
+
|
|
63
|
+
If a VTThinter project lies next to the source (`family.hprj` for `family.VTT.ttf`) or is passed with `--hprj`, an updated copy (`NEW.hprj`) is written next to the result. The original file is not touched; `--no-hprj` disables the update.
|
|
64
|
+
|
|
65
|
+
- Each class is matched to a TSI5 group by the majority of its glyphs (for example: UpperCase → 2, LowerCase → 3, Figures → 4, Superiors → 1, Parens → 8, Accents → 9).
|
|
66
|
+
- Renamed glyphs are replaced in the lists in place, removed ones are dropped.
|
|
67
|
+
- New simple glyphs are added to the class of their group (the group is assigned the same way as in TSI5). Group 0 glyphs are not put into classes. Glyphs that became composites stay in the list and are flagged in the report.
|
|
68
|
+
- `TTFFileName`/`XMLFileName` are rewritten to the new font's name if it differs.
|
|
69
|
+
- Only these values change; formatting and line endings (CRLF) are preserved.
|
|
70
|
+
|
|
71
|
+
For a static cut by `vtt-var2static`, the VF's project (`family-vf.hprj` next to the VF, or `--hprj`) is
|
|
72
|
+
copied as `<name>.hprj` with `TTFFileName`/`XMLFileName` set to the static and every `cvt`/`value` pair
|
|
73
|
+
set to the static's CVT (the same as the VF's without `cvar`; a value that already disagreed with the
|
|
74
|
+
VF's CVT is corrected and printed). The glyph lists stay as they are. The stem range of each class is
|
|
75
|
+
checked against the strokes measured on the static (the thickest straight or round stroke, by the
|
|
76
|
+
reference glyphs of `vtt_transfer.measure`: H I L / E, n m h u / z, 1 / 2 4 7, O o 0); a class outside
|
|
77
|
+
its `stem min`–`max` is printed as a warning — bold statics need a wider range than the VF's default
|
|
78
|
+
master. Classes without reference glyphs (superiors, parens, accents) are not measured.
|
|
79
|
+
|
|
80
|
+
## Statics cut from a hinted VF: vtt-var2static
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
vtt-var2static path/family-vf.VTT.ttf [-o DIR] [-i "Condensed Light" -i Bold] [--keep-overlaps] [--ship [--woff2]]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Cuts every named instance of a hinted variable font (or only the ones given with `-i`: subfamily or
|
|
87
|
+
file name, case and spaces ignored) into `DIR/<name>/` (default `statics/` next to the font):
|
|
88
|
+
|
|
89
|
+
`<name>` is the font's file name without `-vf`/`_vf` plus the instance's subfamily without spaces
|
|
90
|
+
(`family-vf.VTT.ttf`, `Condensed Light` → `family-CondensedLight`); an italic VF's `_italic`/`-italic`
|
|
91
|
+
is dropped when the subfamily says Italic, so `family_italic-vf` gives `family-BoldItalic`.
|
|
92
|
+
|
|
93
|
+
- `<name>.overlap.VTT.ttf` — the instance with overlaps and VTT sources (the transfer source);
|
|
94
|
+
- `<name>.ttf` — overlaps removed, hinting stripped (the transfer target);
|
|
95
|
+
- `<name>.VTT.ttf` — the target with the hints transferred and compiled; open it in VTT;
|
|
96
|
+
- `<name>.vtt-transfer-report.html` — the transfer report: what was remapped, what needs re-hinting;
|
|
97
|
+
- `<name>.hprj` — the VF's VTThinter project for this static, if the VF has one (see below).
|
|
98
|
+
|
|
99
|
+
`--keep-overlaps` skips overlap removal: the instance keeps its hints as they are and is written as
|
|
100
|
+
`<name>.VTT.ttf`, with nothing to transfer. `--ship` also writes release fonts without VTT sources into
|
|
101
|
+
`DIR/hinted/` (as `vtt-ship`, checked against `<name>.ttf`), `--woff2` adds `.woff2`. Options
|
|
102
|
+
`--report-lang`, `-q`, `--hprj`, `--no-hprj` as in `vtt-transfer`. Exit code: 0 — all good, 1 — some instance failed (cut,
|
|
103
|
+
transfer verification or ship check), 2 — the font has no VTT sources or no named instances.
|
|
104
|
+
|
|
105
|
+
### Library
|
|
106
|
+
|
|
107
|
+
`vtt_transfer.static` cuts the named instances of a hinted variable font
|
|
108
|
+
(`doc/static-instances-from-vf.md`): `named_instances(vf, family)` lists them, `cut(vf_path, instance,
|
|
109
|
+
out_dir, remove_overlaps=True)` writes `<name>.overlap.VTT.ttf` (the instance with overlaps and VTT
|
|
110
|
+
sources — the transfer source) and, with overlap removal, `<name>.ttf` (overlaps removed, hinting
|
|
111
|
+
stripped — the transfer target). Overlap removal can turn a hinted on-curve point into an implied one
|
|
112
|
+
(exactly between its off-curve neighbours); `cut` puts such points back, so the outline is the same and
|
|
113
|
+
the remap finds them. Transferring source → target carries the hints over by exact remap.
|
|
114
|
+
`vtt-var2static` runs it for every instance. Needs `skia-pathops`.
|
|
115
|
+
|
|
116
|
+
## VTT sources from scratch (library)
|
|
117
|
+
|
|
118
|
+
`vtt_transfer.fresh.prepare(font)` gives an unhinted static TrueType font VTT sources from Microsoft's
|
|
119
|
+
templates (`src/vtt_transfer/templates/`, MIT, from microsoft/VisualTrueType — see `NOTICE.md`), as VTT's
|
|
120
|
+
"Prepare Font" + "Control Program Only" would: glyf instructions and `fpgm`/`prep`/`cvt` dropped; the
|
|
121
|
+
standard font program; the Control Program template with the values of `vtt_transfer.measure` written
|
|
122
|
+
into its CVT lines (`2: 690 /* cap height */ /* measured: H E F … (17/17) */`, unmeasured slots marked);
|
|
123
|
+
character groups (TSI5) from `chargrp.txt` by Unicode, unencoded glyphs by their base name, else by Unicode
|
|
124
|
+
category; empty Talk, composites with `OFFSET[R]` assembly. The compiler generates the pre-program from
|
|
125
|
+
the Control Program. Returns the compiled font and a report (measurements, group counts).
|
|
126
|
+
|
|
127
|
+
`vtt_transfer.measure.measure(font)`: heights from the dominant cluster of reference glyph
|
|
128
|
+
tops/bottoms (Latin + Cyrillic; overshoots relative to their height), stems from scanline ink runs, kept
|
|
129
|
+
apart by group (UpperCase / LowerCase / Figure), direction and straight/round, keyed by the template's
|
|
130
|
+
CVT numbers. Diagonals, serifs and italic CVTs are not
|
|
131
|
+
measured.
|
|
132
|
+
|
|
133
|
+
For re-measuring one CVT from glyphs the operator picks: `measure_height(font, glyphs, "top"|"bottom")`,
|
|
134
|
+
`measure_overshoot(font, glyphs, side, parent_value)` (round − flat) and `measure_stem(font, glyphs, axis,
|
|
135
|
+
where, band=…)` take a string of characters (through the cmap) or a list of glyph names and return
|
|
136
|
+
`{value, samples, total, glyphs, values, missing}` (`values`: each glyph's samples, to spot the one that
|
|
137
|
+
disagrees; an empty or unknown set gives `value` None). `default_glyphs(cvt)` gives the set and
|
|
138
|
+
parameters `measure` uses for a template CVT, None for the ones it does not measure.
|
|
139
|
+
|
|
140
|
+
## Control Program (library)
|
|
141
|
+
|
|
142
|
+
`vtt_transfer.control` parses and edits the VTT Control Program (TSI1 `cvt` source). It is a port of
|
|
143
|
+
the compiler's grammar (VisualTrueType `CvtManager.cpp`). Parsing is token based: attributes stay set
|
|
144
|
+
until changed, comments nest, and indentation means nothing. `parse(text)` returns a `Program` with
|
|
145
|
+
the GROUP/CATEGORY declarations (TSI5 group numbers), the settings, the `ASM` statements, every CVT
|
|
146
|
+
(value, `~`/`=` inheritance, break ppem, deltas, attributes, the first two comments of its line,
|
|
147
|
+
absolute value) and the errors with their lines. It never raises. `set_cvt(text, n, value=…, ppem=…,
|
|
148
|
+
deltas=…, name=…, note=…)` and `set_setting(text, key, value)` change only the edited tokens and keep
|
|
149
|
+
comments, spacing and line ends. `add_cvt` places a new CVT next to the CVTs with the same attributes
|
|
150
|
+
(heights get the first free number below 40, others the first free from 65) and gives a square height
|
|
151
|
+
the rounding pair (`ASM("SVTCA[Y]")`, `ASM("CALL[], n, 89")`) when the program does that for its other
|
|
152
|
+
heights. `remove_cvt` removes a CVT with its comments and rounding pair, and refuses a CVT that is
|
|
153
|
+
another CVT's parent; it does not look at glyph Talk. xscale and fresh are built on it.
|
|
154
|
+
|
|
155
|
+
## X-height scaling (library)
|
|
156
|
+
|
|
157
|
+
`vtt_transfer.xscale` adds ttfautohint-style x-height scaling to a VTT-hinted font, in its sources. VTT
|
|
158
|
+
rounds every height CVT on its own, so at a size where the x-height rounds up the caps may round down;
|
|
159
|
+
ttfautohint rounds the x-height (with overshoot) and scales all heights by the same factor before
|
|
160
|
+
rounding. `apply(font, Params(mode="both"|"up", limit=14, maxppem=0))` adds two functions to the fpgm
|
|
161
|
+
source and one `ASM("CALL[] …")` line in the Control Program before the first height CVT, so the
|
|
162
|
+
scaling runs in the pre-program before VTT's own rounding. `find_heights` reads the height CVTs
|
|
163
|
+
(SquareHeight / RoundHeight; the x-height is the LowerCase one equal to `OS/2.sxHeight`), `detect`
|
|
164
|
+
reports the state of the scaling in the sources (including a call made stale by CVT edits), `remove`
|
|
165
|
+
takes it out. Only TSI1 is edited; the caller compiles.
|
|
166
|
+
|
|
167
|
+
## Release fonts: vtt-ship
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
vtt-ship path/family-vf.VTT.ttf path/family-italic-vf.VTT.ttf [--woff2] # or uv run vtt-ship …
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Compiles the VTT sources (vttcompilepy, the same compiler as in VTT) and writes a font without the TSI* tables: hints, `cvt`, `fpgm`, `prep`, `gasp` stay. The result goes to a `hinted/` subfolder next to the source, named without `.VTT` (`-o DIR` to write elsewhere).
|
|
174
|
+
|
|
175
|
+
Before writing, the result is checked against the designer's unhinted build (by default `<name without .VTT>.ttf` next to it, or `--reference`): outlines, composites with offsets and all tables unrelated to hinting must match. This catches stale composite code in VTT (VTT builds composites from the OFFSET lines in the sources, not from `glyf`). On a mismatch or a compile error the file is not written (`--force` to write anyway, `--no-check` to skip the check). Exit code 1 if any font failed.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
uv sync
|
|
181
|
+
uv run python tests/smoke.py # end-to-end run on a synthetic font, as in CI
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT, see [LICENSE](https://github.com/typedev/vtt-transfer/blob/master/LICENSE). Parts are ported from or copied out of Microsoft's
|
|
187
|
+
[VisualTrueType](https://github.com/microsoft/VisualTrueType) (MIT), see [NOTICE.md](https://github.com/typedev/vtt-transfer/blob/master/NOTICE.md).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Build the new version with the same compiler as the hinted source
|
|
2
|
+
|
|
3
|
+
Status: observed on a hinted variable sans family, 2026-10-11.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
VTT hints refer to point numbers. A compiler that changes the point structure of glyphs whose
|
|
8
|
+
design did not change makes the transfer lose those glyphs, and every composite built from them.
|
|
9
|
+
|
|
10
|
+
- **fontc** dropped *implied on-curve points* in this build: an on-curve point lying exactly halfway
|
|
11
|
+
between two off-curve points (in all masters) can be omitted from `glyf`, since the rasterizer
|
|
12
|
+
implies it.
|
|
13
|
+
- **fontmake** (ufo2ft / fontTools varLib) keeps them. Dropping is opt-in there:
|
|
14
|
+
`fontmake --drop-implied-oncurves` (TrueType only, default off). The fontTools option was added,
|
|
15
|
+
per its PR, for diffing fontmake output against fontc.
|
|
16
|
+
|
|
17
|
+
Rule: build every version of a hinted family with the same compiler and the same point-related
|
|
18
|
+
options as the font the VTT sources were made on. For our projects so far that is
|
|
19
|
+
fontmake with default options (no `--drop-implied-oncurves`). Record the compiler in the project's
|
|
20
|
+
build profile, and treat a compiler switch like an outline change of the whole font.
|
|
21
|
+
|
|
22
|
+
## Evidence
|
|
23
|
+
|
|
24
|
+
Source: the family's hinted VF (built with fontmake), 340 hinted simple glyphs. Target:
|
|
25
|
+
the same designspace built twice on 2026-10-11 — with `variableCompiler: fontc` in the build
|
|
26
|
+
profile, and again after that option was removed (fontmake).
|
|
27
|
+
|
|
28
|
+
| | fontc build | fontmake build |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| hinted simple glyphs with the source point structure | 320 | 334 |
|
|
31
|
+
| transferred | 527 | 563 |
|
|
32
|
+
| remapped | 2 | 0 |
|
|
33
|
+
| re-hint | 16 | 4 |
|
|
34
|
+
| changed, unhinted | 28 | 0 |
|
|
35
|
+
| composite lost own instructions | 22 | 0 |
|
|
36
|
+
|
|
37
|
+
The builds are about an hour apart with design commits in between, so not every difference in the
|
|
38
|
+
lower rows is the compiler. The structural comparison isolates it: **14 hinted glyphs differ from
|
|
39
|
+
the source only in the fontc build, and only by dropped implied on-curve points**:
|
|
40
|
+
|
|
41
|
+
`O` (27), `Q` (37), `copyright` (0 5 10 15 20 30), `registered` (10 20 28),
|
|
42
|
+
`degree` (0 4 8 12 19 25), `paragraph` (5), `Oslash` (39), `uni042E` (35), `uni0472` (31),
|
|
43
|
+
`uni221E` (4 20), `uniF406` (0), `uniF6D1` (8), `uni2117` (0 5 10 15 20 30),
|
|
44
|
+
`uni2300` (0 4 8 12 16 20 24 28) — source point numbers in parentheses.
|
|
45
|
+
|
|
46
|
+
- In 13 of them the dropped points are implied at all 18 named instances of the source as well.
|
|
47
|
+
- In 12 of them a dropped point is referenced by the VTT Talk — e.g. `O`: `YShift(9,27)` moves the
|
|
48
|
+
top of the counter, and point 27 no longer exists — so the transfer cannot remap them (re-hint).
|
|
49
|
+
- In `paragraph` the dropped point (5) is not referenced: the remap succeeds, but all later point
|
|
50
|
+
numbers shift by one.
|
|
51
|
+
- Composites inherit the change: `Odieresis`, `Oacute`… lose their own instructions because the
|
|
52
|
+
point structure of their component `O` changed.
|
|
53
|
+
|
|
54
|
+
The other 6 hinted glyphs that differ from the source in both builds are design changes (e.g.
|
|
55
|
+
Greek `uni0394`, `uni03A9`, `uni03C0` redrawn), not the compiler.
|
|
56
|
+
|
|
57
|
+
## Recognising it in a report
|
|
58
|
+
|
|
59
|
+
A lost reference point that was an on-curve point exactly at the midpoint of its two off-curve
|
|
60
|
+
neighbours in the source, with the neighbours still present in the target, is almost certainly a
|
|
61
|
+
dropped implied on-curve. Many such glyphs in one transfer (round shapes: `O`, `Q`, `©`, `®`, `°`)
|
|
62
|
+
point at the build, not the design.
|
|
63
|
+
|
|
64
|
+
The transfer recognises this (`core.implied_oncurve`): such a lost point gets the reason "gone as an
|
|
65
|
+
implied on-curve between off-curve points a and b", the glyph card says how many of its lost points
|
|
66
|
+
these are, and a font-level check lists the glyphs (a warning from 3 glyphs on). The remap stays
|
|
67
|
+
exact: the point does not exist in the target, so the glyph still goes to re-hint. The same check
|
|
68
|
+
fires on statics cut before `static.restore_implied_oncurves` existed, where overlap removal dropped
|
|
69
|
+
the points.
|