fonttools 4.59.1__cp313-cp313-macosx_10_13_universal2.whl → 4.60.0__cp313-cp313-macosx_10_13_universal2.whl
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.
Potentially problematic release.
This version of fonttools might be problematic. Click here for more details.
- fontTools/__init__.py +1 -1
- fontTools/annotations.py +30 -0
- fontTools/cu2qu/cu2qu.c +1175 -962
- fontTools/cu2qu/cu2qu.cpython-313-darwin.so +0 -0
- fontTools/cu2qu/cu2qu.py +36 -4
- fontTools/feaLib/builder.py +9 -3
- fontTools/feaLib/lexer.c +8 -3
- fontTools/feaLib/lexer.cpython-313-darwin.so +0 -0
- fontTools/feaLib/parser.py +11 -1
- fontTools/feaLib/variableScalar.py +6 -1
- fontTools/misc/bezierTools.c +8 -3
- fontTools/misc/bezierTools.cpython-313-darwin.so +0 -0
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/textTools.py +4 -2
- fontTools/pens/filterPen.py +218 -26
- fontTools/pens/momentsPen.c +8 -3
- fontTools/pens/momentsPen.cpython-313-darwin.so +0 -0
- fontTools/pens/pointPen.py +40 -6
- fontTools/qu2cu/qu2cu.c +20 -7
- fontTools/qu2cu/qu2cu.cpython-313-darwin.so +0 -0
- fontTools/subset/__init__.py +1 -0
- fontTools/ttLib/tables/_a_v_a_r.py +4 -2
- fontTools/ttLib/tables/_n_a_m_e.py +11 -6
- fontTools/ttLib/tables/_p_o_s_t.py +5 -5
- fontTools/ufoLib/__init__.py +279 -176
- fontTools/ufoLib/converters.py +14 -5
- fontTools/ufoLib/filenames.py +16 -6
- fontTools/ufoLib/glifLib.py +286 -190
- fontTools/ufoLib/kerning.py +32 -12
- fontTools/ufoLib/utils.py +41 -13
- fontTools/ufoLib/validators.py +121 -97
- fontTools/varLib/__init__.py +80 -1
- fontTools/varLib/avar/__init__.py +0 -0
- fontTools/varLib/avar/__main__.py +72 -0
- fontTools/varLib/avar/build.py +79 -0
- fontTools/varLib/avar/map.py +108 -0
- fontTools/varLib/avar/plan.py +1004 -0
- fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
- fontTools/varLib/avarPlanner.py +3 -999
- fontTools/varLib/instancer/__init__.py +56 -18
- fontTools/varLib/interpolatableHelpers.py +3 -0
- fontTools/varLib/iup.c +14 -5
- fontTools/varLib/iup.cpython-313-darwin.so +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/METADATA +43 -2
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/RECORD +51 -44
- {fonttools-4.59.1.data → fonttools-4.60.0.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/WHEEL +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/entry_points.txt +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/licenses/LICENSE.external +0 -0
- {fonttools-4.59.1.dist-info → fonttools-4.60.0.dist-info}/top_level.txt +0 -0
fontTools/varLib/__init__.py
CHANGED
|
@@ -30,7 +30,11 @@ from fontTools.misc.fixedTools import floatToFixed as fl2fi
|
|
|
30
30
|
from fontTools.misc.textTools import Tag, tostr
|
|
31
31
|
from fontTools.ttLib import TTFont, newTable
|
|
32
32
|
from fontTools.ttLib.tables._f_v_a_r import Axis, NamedInstance
|
|
33
|
-
from fontTools.ttLib.tables._g_l_y_f import
|
|
33
|
+
from fontTools.ttLib.tables._g_l_y_f import (
|
|
34
|
+
GlyphCoordinates,
|
|
35
|
+
dropImpliedOnCurvePoints,
|
|
36
|
+
USE_MY_METRICS,
|
|
37
|
+
)
|
|
34
38
|
from fontTools.ttLib.tables.ttProgram import Program
|
|
35
39
|
from fontTools.ttLib.tables.TupleVariation import TupleVariation
|
|
36
40
|
from fontTools.ttLib.tables import otTables as ot
|
|
@@ -489,6 +493,77 @@ def _merge_TTHinting(font, masterModel, master_ttfs):
|
|
|
489
493
|
cvar.variations = variations
|
|
490
494
|
|
|
491
495
|
|
|
496
|
+
def _has_inconsistent_use_my_metrics_flag(
|
|
497
|
+
master_glyf, glyph_name, flagged_components, expected_num_components
|
|
498
|
+
) -> bool:
|
|
499
|
+
master_glyph = master_glyf.get(glyph_name)
|
|
500
|
+
# 'sparse' glyph master doesn't contribute. Besides when components don't match
|
|
501
|
+
# the VF build is going to fail anyway, so be lenient here.
|
|
502
|
+
if (
|
|
503
|
+
master_glyph is not None
|
|
504
|
+
and master_glyph.isComposite()
|
|
505
|
+
and len(master_glyph.components) == expected_num_components
|
|
506
|
+
):
|
|
507
|
+
for i, base_glyph in flagged_components:
|
|
508
|
+
comp = master_glyph.components[i]
|
|
509
|
+
if comp.glyphName != base_glyph:
|
|
510
|
+
break
|
|
511
|
+
if not (comp.flags & USE_MY_METRICS):
|
|
512
|
+
return True
|
|
513
|
+
return False
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _unset_inconsistent_use_my_metrics_flags(vf, master_fonts):
|
|
517
|
+
"""Clear USE_MY_METRICS on composite components if inconsistent across masters.
|
|
518
|
+
|
|
519
|
+
If a composite glyph's component has USE_MY_METRICS set differently among
|
|
520
|
+
the masters, the flag is removed from the variable font's glyf table so that
|
|
521
|
+
advance widths are not determined by that single component's phantom points.
|
|
522
|
+
"""
|
|
523
|
+
glyf = vf["glyf"]
|
|
524
|
+
master_glyfs = [m["glyf"] for m in master_fonts if "glyf" in m]
|
|
525
|
+
if not master_glyfs:
|
|
526
|
+
# Should not happen: at least the base master (as copied into vf) has glyf
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
for glyph_name in glyf.keys():
|
|
530
|
+
glyph = glyf[glyph_name]
|
|
531
|
+
if not glyph.isComposite():
|
|
532
|
+
continue
|
|
533
|
+
|
|
534
|
+
# collect indices of component(s) that carry the USE_MY_METRICS flag.
|
|
535
|
+
# This is supposed to be 1 component per composite, but you never know.
|
|
536
|
+
flagged_components = [
|
|
537
|
+
(i, comp.glyphName)
|
|
538
|
+
for i, comp in enumerate(glyph.components)
|
|
539
|
+
if (comp.flags & USE_MY_METRICS)
|
|
540
|
+
]
|
|
541
|
+
if not flagged_components:
|
|
542
|
+
# Nothing to fix
|
|
543
|
+
continue
|
|
544
|
+
|
|
545
|
+
# Verify that for all master glyf tables that contribute this glyph, the
|
|
546
|
+
# corresponding component (same glyphName and index) also carries USE_MY_METRICS
|
|
547
|
+
# and unset the flag if not.
|
|
548
|
+
expected_num_components = len(glyph.components)
|
|
549
|
+
if any(
|
|
550
|
+
_has_inconsistent_use_my_metrics_flag(
|
|
551
|
+
master_glyf, glyph_name, flagged_components, expected_num_components
|
|
552
|
+
)
|
|
553
|
+
for master_glyf in master_glyfs
|
|
554
|
+
):
|
|
555
|
+
comp_names = [name for _, name in flagged_components]
|
|
556
|
+
log.info(
|
|
557
|
+
"Composite glyph '%s' has inconsistent USE_MY_METRICS flags across "
|
|
558
|
+
"masters; clearing the flag on component%s %s",
|
|
559
|
+
glyph_name,
|
|
560
|
+
"s" if len(comp_names) > 1 else "",
|
|
561
|
+
comp_names if len(comp_names) > 1 else comp_names[0],
|
|
562
|
+
)
|
|
563
|
+
for i, _ in flagged_components:
|
|
564
|
+
glyph.components[i].flags &= ~USE_MY_METRICS
|
|
565
|
+
|
|
566
|
+
|
|
492
567
|
_MetricsFields = namedtuple(
|
|
493
568
|
"_MetricsFields",
|
|
494
569
|
[
|
|
@@ -1205,6 +1280,10 @@ def build(
|
|
|
1205
1280
|
if "DSIG" in vf:
|
|
1206
1281
|
del vf["DSIG"]
|
|
1207
1282
|
|
|
1283
|
+
# Clear USE_MY_METRICS composite flags if set inconsistently across masters.
|
|
1284
|
+
if "glyf" in vf:
|
|
1285
|
+
_unset_inconsistent_use_my_metrics_flags(vf, master_fonts)
|
|
1286
|
+
|
|
1208
1287
|
# TODO append masters as named-instances as well; needs .designspace change.
|
|
1209
1288
|
fvar = _add_fvar(vf, ds.axes, ds.instances)
|
|
1210
1289
|
if "STAT" not in exclude:
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
log = logging.getLogger("fontTools.varLib.avar")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main(args=None):
|
|
7
|
+
from fontTools.ttLib import TTFont
|
|
8
|
+
from fontTools.misc.cliTools import makeOutputFileName
|
|
9
|
+
from fontTools import configLogger
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
print(
|
|
14
|
+
"WARNING: This script is deprecated. Use `fonttools varLib.avar.build` "
|
|
15
|
+
"or `fonttools varLib.avar.unbuild` instead.\n",
|
|
16
|
+
file=sys.stderr,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if args is None:
|
|
20
|
+
args = sys.argv[1:]
|
|
21
|
+
|
|
22
|
+
parser = argparse.ArgumentParser(
|
|
23
|
+
"fonttools varLib.avar",
|
|
24
|
+
description="Add `avar` table from designspace file to variable font.",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"designspace",
|
|
29
|
+
metavar="family.designspace",
|
|
30
|
+
help="Designspace file.",
|
|
31
|
+
nargs="?",
|
|
32
|
+
default=None,
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"-o",
|
|
36
|
+
"--output-file",
|
|
37
|
+
type=str,
|
|
38
|
+
help="Output font file name.",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"-v", "--verbose", action="store_true", help="Run more verbosely."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
options = parser.parse_args(args)
|
|
45
|
+
|
|
46
|
+
configLogger(level=("INFO" if options.verbose else "WARNING"))
|
|
47
|
+
|
|
48
|
+
font = TTFont(options.font)
|
|
49
|
+
|
|
50
|
+
if options.designspace is None:
|
|
51
|
+
from .unbuild import unbuild
|
|
52
|
+
|
|
53
|
+
unbuild(font)
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
from .build import build
|
|
57
|
+
|
|
58
|
+
build(font, options.designspace)
|
|
59
|
+
|
|
60
|
+
if options.output_file is None:
|
|
61
|
+
outfile = makeOutputFileName(options.font, overWrite=True, suffix=".avar")
|
|
62
|
+
else:
|
|
63
|
+
outfile = options.output_file
|
|
64
|
+
if outfile:
|
|
65
|
+
log.info("Saving %s", outfile)
|
|
66
|
+
font.save(outfile)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == "__main__":
|
|
70
|
+
import sys
|
|
71
|
+
|
|
72
|
+
sys.exit(main())
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from fontTools.varLib import _add_fvar, _add_avar, load_designspace
|
|
2
|
+
from fontTools.ttLib import newTable
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
log = logging.getLogger("fontTools.varLib.avar")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build(font, designspace_file):
|
|
9
|
+
ds = load_designspace(designspace_file, require_sources=False)
|
|
10
|
+
|
|
11
|
+
if not "fvar" in font:
|
|
12
|
+
# if "name" not in font:
|
|
13
|
+
font["name"] = newTable("name")
|
|
14
|
+
_add_fvar(font, ds.axes, ds.instances)
|
|
15
|
+
|
|
16
|
+
axisTags = [a.axisTag for a in font["fvar"].axes]
|
|
17
|
+
|
|
18
|
+
if "avar" in font:
|
|
19
|
+
log.warning("avar table already present, overwriting.")
|
|
20
|
+
del font["avar"]
|
|
21
|
+
|
|
22
|
+
_add_avar(font, ds.axes, ds.axisMappings, axisTags)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main(args=None):
|
|
26
|
+
"""Add `avar` table from designspace file to variable font."""
|
|
27
|
+
|
|
28
|
+
from fontTools.ttLib import TTFont
|
|
29
|
+
from fontTools.misc.cliTools import makeOutputFileName
|
|
30
|
+
from fontTools import configLogger
|
|
31
|
+
import argparse
|
|
32
|
+
|
|
33
|
+
if args is None:
|
|
34
|
+
import sys
|
|
35
|
+
|
|
36
|
+
args = sys.argv[1:]
|
|
37
|
+
|
|
38
|
+
parser = argparse.ArgumentParser(
|
|
39
|
+
"fonttools varLib.avar.build",
|
|
40
|
+
description="Add `avar` table from designspace file to variable font.",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"designspace",
|
|
45
|
+
metavar="family.designspace",
|
|
46
|
+
help="Designspace file.",
|
|
47
|
+
default=None,
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"-o",
|
|
51
|
+
"--output-file",
|
|
52
|
+
type=str,
|
|
53
|
+
help="Output font file name.",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"-v", "--verbose", action="store_true", help="Run more verbosely."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
options = parser.parse_args(args)
|
|
60
|
+
|
|
61
|
+
configLogger(level=("INFO" if options.verbose else "WARNING"))
|
|
62
|
+
|
|
63
|
+
font = TTFont(options.font)
|
|
64
|
+
|
|
65
|
+
build(font, options.designspace)
|
|
66
|
+
|
|
67
|
+
if options.output_file is None:
|
|
68
|
+
outfile = makeOutputFileName(options.font, overWrite=True, suffix=".avar")
|
|
69
|
+
else:
|
|
70
|
+
outfile = options.output_file
|
|
71
|
+
if outfile:
|
|
72
|
+
log.info("Saving %s", outfile)
|
|
73
|
+
font.save(outfile)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
import sys
|
|
78
|
+
|
|
79
|
+
sys.exit(main())
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from fontTools.varLib.models import normalizeValue
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _denormalize(v, triplet):
|
|
5
|
+
if v >= 0:
|
|
6
|
+
return triplet[1] + v * (triplet[2] - triplet[1])
|
|
7
|
+
else:
|
|
8
|
+
return triplet[1] + v * (triplet[1] - triplet[0])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def map(
|
|
12
|
+
font, location, *, inputNormalized=False, outputNormalized=False, dropZeroes=False
|
|
13
|
+
):
|
|
14
|
+
if "fvar" not in font:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
fvar = font["fvar"]
|
|
18
|
+
axes = {a.axisTag: (a.minValue, a.defaultValue, a.maxValue) for a in fvar.axes}
|
|
19
|
+
|
|
20
|
+
if not inputNormalized:
|
|
21
|
+
location = {
|
|
22
|
+
tag: normalizeValue(value, axes[tag]) for tag, value in location.items()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if "avar" in font:
|
|
26
|
+
location = font["avar"].renormalizeLocation(location, font, dropZeroes)
|
|
27
|
+
|
|
28
|
+
if not outputNormalized:
|
|
29
|
+
location = {
|
|
30
|
+
tag: _denormalize(value, axes[tag]) for tag, value in location.items()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return location
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main(args=None):
|
|
37
|
+
"""Map variation coordinates through the `avar` table."""
|
|
38
|
+
|
|
39
|
+
from fontTools.ttLib import TTFont
|
|
40
|
+
import argparse
|
|
41
|
+
|
|
42
|
+
if args is None:
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
args = sys.argv[1:]
|
|
46
|
+
|
|
47
|
+
parser = argparse.ArgumentParser(
|
|
48
|
+
"fonttools varLib.avar.map",
|
|
49
|
+
description="Map variation coordinates through the `avar` table.",
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument("font", metavar="varfont.ttf", help="Variable-font file.")
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"coords",
|
|
54
|
+
metavar="[AXIS=value...]",
|
|
55
|
+
help="Coordinates to map, e.g. 'wght=700 wdth=75'.",
|
|
56
|
+
nargs="*",
|
|
57
|
+
default=None,
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"-f", action="store_true", help="Do not omit axes at default location."
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"-i", action="store_true", help="Input coordinates are normalized (-1..1)."
|
|
64
|
+
)
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"-o", action="store_true", help="Output coordinates as normalized (-1..1)."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
options = parser.parse_args(args)
|
|
70
|
+
|
|
71
|
+
if not options.coords:
|
|
72
|
+
parser.error(
|
|
73
|
+
"No coordinates provided. Please specify at least one axis coordinate (e.g., wght=500)"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if options.font.endswith(".designspace"):
|
|
77
|
+
from .build import build
|
|
78
|
+
|
|
79
|
+
font = TTFont()
|
|
80
|
+
build(font, options.font)
|
|
81
|
+
else:
|
|
82
|
+
font = TTFont(options.font)
|
|
83
|
+
if "fvar" not in font:
|
|
84
|
+
parser.error(f"Font '{options.font}' does not contain an 'fvar' table.")
|
|
85
|
+
|
|
86
|
+
location = {
|
|
87
|
+
tag: float(value) for tag, value in (item.split("=") for item in options.coords)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
mapped = map(
|
|
91
|
+
font,
|
|
92
|
+
location,
|
|
93
|
+
inputNormalized=options.i,
|
|
94
|
+
outputNormalized=options.o,
|
|
95
|
+
dropZeroes=not options.f,
|
|
96
|
+
)
|
|
97
|
+
assert mapped is not None
|
|
98
|
+
|
|
99
|
+
for tag in mapped:
|
|
100
|
+
v = mapped[tag]
|
|
101
|
+
v = int(v) if v == int(v) else v
|
|
102
|
+
print(f"{tag}={v:g}")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
import sys
|
|
107
|
+
|
|
108
|
+
sys.exit(main())
|