vtt-transfer
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.
Installation
uv tool install vtt-transfer # or: pipx install vtt-transfer
This puts vtt-transfer, vtt-var2static and vtt-ship on the PATH. Python 3.11 or newer; the VTT
compiler comes from vttcompilepy wheels (Windows, macOS,
Linux x86_64).
From a checkout (-e picks up code changes without reinstalling):
uv tool install -e /path/to/vtt-transfer
# or only the project's local environment:
cd /path/to/vtt-transfer && uv sync
Usage
vtt-transfer OLD.VTT.ttf NEW.ttf # after installation
uv run vtt-transfer OLD.VTT.ttf NEW.ttf # from the vtt-transfer folder
uv run --project /path/to/vtt-transfer vtt-transfer OLD.VTT.ttf NEW.ttf # from any folder
Next to NEW.ttf you get:
NEW.VTT.ttf— the new font with VTT sources (TSI0–TSI3, TSI5),cvt,fpgm,prep,gaspandcvar/TSIC(if present), compiled with vttcompilepy. It opens in VTT and work continues as usual.NEW.vtt-transfer-report.html— the report.
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.
Exit code: 0 — all good, 1 — verification errors (see the report), 2 — transfer impossible (no TSI tables, different UPM).
How glyphs are matched
- 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.
- 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.
- 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. 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. - Composites: the assembly code (
OFFSET/SOFFSET) is always regenerated from the newglyf(GIDs, offsets, transformations, USE_MY_METRICS), with prefixes (OVERLAP[]etc.) as in the source. The composite's own instructions are kept as they are if the point structure of its components did not change. If it did, accent placement (SVTCA,SRP0/MDRP,SHC,CALL[]to the standard functions 86, 87 and 164) is remapped: each point number is moved to its component's new position, through an exact coordinate match inside a changed component, and each contour number likewise (the component's contour count must be the same). Any other instruction, a lost point or a change in the number of components drops them, as before, and the report flags it. - 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.
- New composites are hinted if all their components are; the report shows which components they are waiting for.
Checks
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.
Limitations
- 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-oncurvesis 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. - Remapping uses default-master coordinates: a point moved by even one unit is considered lost. This is intentional.
- Composite code with point attachment (
ANCHOR) is generated but flagged for manual review. - VOLT tables (TSIV etc.) are not transferred.
Autohinter project (.hprj)
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.
- 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).
- Renamed glyphs are replaced in the lists in place, removed ones are dropped.
- 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.
TTFFileName/XMLFileNameare rewritten to the new font's name if it differs.- Only these values change; formatting and line endings (CRLF) are preserved.
For a static cut by vtt-var2static, the VF's project (family-vf.hprj next to the VF, or --hprj) is
copied as <name>.hprj with TTFFileName/XMLFileName set to the static and every cvt/value pair
set to the static's CVT (the same as the VF's without cvar; a value that already disagreed with the
VF's CVT is corrected and printed). The glyph lists stay as they are. The stem range of each class is
checked against the strokes measured on the static (the thickest straight or round stroke, by the
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
its stem min–max is printed as a warning — bold statics need a wider range than the VF's default
master. Classes without reference glyphs (superiors, parens, accents) are not measured.
Statics cut from a hinted VF: vtt-var2static
vtt-var2static path/family-vf.VTT.ttf [-o DIR] [-i "Condensed Light" -i Bold] [--keep-overlaps] [--ship [--woff2]]
Cuts every named instance of a hinted variable font (or only the ones given with -i: subfamily or
file name, case and spaces ignored) into DIR/<name>/ (default statics/ next to the font):
<name> is the font's file name without -vf/_vf plus the instance's subfamily without spaces
(family-vf.VTT.ttf, Condensed Light → family-CondensedLight); an italic VF's _italic/-italic
is dropped when the subfamily says Italic, so family_italic-vf gives family-BoldItalic.
<name>.overlap.VTT.ttf— the instance with overlaps and VTT sources (the transfer source);<name>.ttf— overlaps removed, hinting stripped (the transfer target);<name>.VTT.ttf— the target with the hints transferred and compiled; open it in VTT;<name>.vtt-transfer-report.html— the transfer report: what was remapped, what needs re-hinting;<name>.hprj— the VF's VTThinter project for this static, if the VF has one (see below).
--keep-overlaps skips overlap removal: the instance keeps its hints as they are and is written as
<name>.VTT.ttf, with nothing to transfer. --ship also writes release fonts without VTT sources into
DIR/hinted/ (as vtt-ship, checked against <name>.ttf), --woff2 adds .woff2. Options
--report-lang, -q, --hprj, --no-hprj as in vtt-transfer. Exit code: 0 — all good, 1 — some instance failed (cut,
transfer verification or ship check), 2 — the font has no VTT sources or no named instances.
Library
vtt_transfer.static cuts the named instances of a hinted variable font
(doc/static-instances-from-vf.md): named_instances(vf, family) lists them, cut(vf_path, instance, out_dir, remove_overlaps=True) writes <name>.overlap.VTT.ttf (the instance with overlaps and VTT
sources — the transfer source) and, with overlap removal, <name>.ttf (overlaps removed, hinting
stripped — the transfer target). Overlap removal can turn a hinted on-curve point into an implied one
(exactly between its off-curve neighbours); cut puts such points back, so the outline is the same and
the remap finds them. Transferring source → target carries the hints over by exact remap.
vtt-var2static runs it for every instance. Needs skia-pathops.
VTT sources from scratch (library)
vtt_transfer.fresh.prepare(font) gives an unhinted static TrueType font VTT sources from Microsoft's
templates (src/vtt_transfer/templates/, MIT, from microsoft/VisualTrueType — see NOTICE.md), as VTT's
"Prepare Font" + "Control Program Only" would: glyf instructions and fpgm/prep/cvt dropped; the
standard font program; the Control Program template with the values of vtt_transfer.measure written
into its CVT lines (2: 690 /* cap height */ /* measured: H E F … (17/17) */, unmeasured slots marked);
character groups (TSI5) from chargrp.txt by Unicode, unencoded glyphs by their base name, else by Unicode
category; empty Talk, composites with OFFSET[R] assembly. The compiler generates the pre-program from
the Control Program. Returns the compiled font and a report (measurements, group counts).
vtt_transfer.measure.measure(font): heights from the dominant cluster of reference glyph
tops/bottoms (Latin + Cyrillic; overshoots relative to their height), stems from scanline ink runs, kept
apart by group (UpperCase / LowerCase / Figure), direction and straight/round, keyed by the template's
CVT numbers. Diagonals, serifs and italic CVTs are not
measured.
For re-measuring one CVT from glyphs the operator picks: measure_height(font, glyphs, "top"|"bottom"),
measure_overshoot(font, glyphs, side, parent_value) (round − flat) and measure_stem(font, glyphs, axis, where, band=…) take a string of characters (through the cmap) or a list of glyph names and return
{value, samples, total, glyphs, values, missing} (values: each glyph's samples, to spot the one that
disagrees; an empty or unknown set gives value None). default_glyphs(cvt) gives the set and
parameters measure uses for a template CVT, None for the ones it does not measure.
Control Program (library)
vtt_transfer.control parses and edits the VTT Control Program (TSI1 cvt source). It is a port of
the compiler's grammar (VisualTrueType CvtManager.cpp). Parsing is token based: attributes stay set
until changed, comments nest, and indentation means nothing. parse(text) returns a Program with
the GROUP/CATEGORY declarations (TSI5 group numbers), the settings, the ASM statements, every CVT
(value, ~/= inheritance, break ppem, deltas, attributes, the first two comments of its line,
absolute value) and the errors with their lines. It never raises. set_cvt(text, n, value=…, ppem=…, deltas=…, name=…, note=…) and set_setting(text, key, value) change only the edited tokens and keep
comments, spacing and line ends. add_cvt places a new CVT next to the CVTs with the same attributes
(heights get the first free number below 40, others the first free from 65) and gives a square height
the rounding pair (ASM("SVTCA[Y]"), ASM("CALL[], n, 89")) when the program does that for its other
heights. remove_cvt removes a CVT with its comments and rounding pair, and refuses a CVT that is
another CVT's parent; it does not look at glyph Talk. xscale and fresh are built on it.
X-height scaling (library)
vtt_transfer.xscale adds ttfautohint-style x-height scaling to a VTT-hinted font, in its sources. VTT
rounds every height CVT on its own, so at a size where the x-height rounds up the caps may round down;
ttfautohint rounds the x-height (with overshoot) and scales all heights by the same factor before
rounding. apply(font, Params(mode="both"|"up", limit=14, maxppem=0)) adds two functions to the fpgm
source and one ASM("CALL[] …") line in the Control Program before the first height CVT, so the
scaling runs in the pre-program before VTT's own rounding. find_heights reads the height CVTs
(SquareHeight / RoundHeight; the x-height is the LowerCase one equal to OS/2.sxHeight), detect
reports the state of the scaling in the sources (including a call made stale by CVT edits), remove
takes it out. Only TSI1 is edited; the caller compiles.
Release fonts: vtt-ship
vtt-ship path/family-vf.VTT.ttf path/family-italic-vf.VTT.ttf [--woff2] # or uv run vtt-ship …
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).
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.
Development
uv sync
uv run python tests/smoke.py # end-to-end run on a synthetic font, as in CI
License
MIT, see LICENSE. Parts are ported from or copied out of Microsoft's VisualTrueType (MIT), see NOTICE.md.
Metadata
Release files for vtt-transfer 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vtt_transfer-0.2.2.tar.gz | 127.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vtt_transfer-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 252.9 kB
Release files / vtt_transfer-0.2.2.tar.gz
| Download URL | vtt_transfer-0.2.2.tar.gz |
|---|---|
| Size | 127.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
388b85d8d9170b407dcd99ffc891dc86d0918847cf09ec0a2873d128c7fa78ef
|
|
BLAKE2b-256 checksum How to use checksums |
eb595c8f1fd5ddac5627774f43f91606149cb91113cb0ea3a9033f64ea5359db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / vtt_transfer-0.2.2-py3-none-any.whl
| Download URL | vtt_transfer-0.2.2-py3-none-any.whl |
|---|---|
| Size | 125.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0f34534b64e3e1af1442d862dfb3f0e9887f1af2a0e9e197d5da98d33a222e2c
|
|
BLAKE2b-256 checksum How to use checksums |
fba1194bb5159510ba4587bf90ca77f1db5a3aec72d5987b4a4cab5eea1e71c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for vtt-transfer 0.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for vtt_transfer-0.2.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.