fonttools 4.59.2__cp312-cp312-win_amd64.whl → 4.60.1__cp312-cp312-win_amd64.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 +1067 -946
- fontTools/cu2qu/cu2qu.cp312-win_amd64.pyd +0 -0
- fontTools/cu2qu/cu2qu.py +19 -2
- fontTools/feaLib/lexer.c +18 -12
- fontTools/feaLib/lexer.cp312-win_amd64.pyd +0 -0
- fontTools/misc/bezierTools.c +18 -12
- fontTools/misc/bezierTools.cp312-win_amd64.pyd +0 -0
- fontTools/misc/enumTools.py +23 -0
- fontTools/misc/visitor.py +24 -16
- fontTools/pens/filterPen.py +218 -26
- fontTools/pens/momentsPen.c +18 -12
- fontTools/pens/momentsPen.cp312-win_amd64.pyd +0 -0
- fontTools/pens/pointPen.py +40 -6
- fontTools/qu2cu/qu2cu.c +30 -16
- fontTools/qu2cu/qu2cu.cp312-win_amd64.pyd +0 -0
- fontTools/subset/__init__.py +178 -12
- fontTools/ttLib/tables/_p_o_s_t.py +5 -5
- fontTools/ufoLib/__init__.py +278 -175
- 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/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/interpolatableHelpers.py +3 -0
- fontTools/varLib/iup.c +24 -14
- fontTools/varLib/iup.cp312-win_amd64.pyd +0 -0
- {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/METADATA +41 -2
- {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/RECORD +44 -37
- {fonttools-4.59.2.data → fonttools-4.60.1.data}/data/share/man/man1/ttx.1 +0 -0
- {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/WHEEL +0 -0
- {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/entry_points.txt +0 -0
- {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/licenses/LICENSE +0 -0
- {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/licenses/LICENSE.external +0 -0
- {fonttools-4.59.2.dist-info → fonttools-4.60.1.dist-info}/top_level.txt +0 -0
fontTools/pens/pointPen.py
CHANGED
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import math
|
|
18
18
|
from typing import Any, Dict, List, Optional, Tuple
|
|
19
19
|
|
|
20
|
+
from fontTools.misc.enumTools import StrEnum
|
|
20
21
|
from fontTools.misc.loggingTools import LogMixin
|
|
21
22
|
from fontTools.misc.transform import DecomposedTransform, Identity
|
|
22
23
|
from fontTools.pens.basePen import AbstractPen, MissingComponentError, PenError
|
|
@@ -28,6 +29,7 @@ __all__ = [
|
|
|
28
29
|
"SegmentToPointPen",
|
|
29
30
|
"GuessSmoothPointPen",
|
|
30
31
|
"ReverseContourPointPen",
|
|
32
|
+
"ReverseFlipped",
|
|
31
33
|
]
|
|
32
34
|
|
|
33
35
|
# Some type aliases to make it easier below
|
|
@@ -39,6 +41,19 @@ SegmentType = Optional[str]
|
|
|
39
41
|
SegmentList = List[Tuple[SegmentType, SegmentPointList]]
|
|
40
42
|
|
|
41
43
|
|
|
44
|
+
class ReverseFlipped(StrEnum):
|
|
45
|
+
"""How to handle flipped components during decomposition.
|
|
46
|
+
|
|
47
|
+
NO: Don't reverse flipped components
|
|
48
|
+
KEEP_START: Reverse flipped components, keeping original starting point
|
|
49
|
+
ON_CURVE_FIRST: Reverse flipped components, ensuring first point is on-curve
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
NO = "no"
|
|
53
|
+
KEEP_START = "keep_start"
|
|
54
|
+
ON_CURVE_FIRST = "on_curve_first"
|
|
55
|
+
|
|
56
|
+
|
|
42
57
|
class AbstractPointPen:
|
|
43
58
|
"""Baseclass for all PointPens."""
|
|
44
59
|
|
|
@@ -559,15 +574,20 @@ class DecomposingPointPen(LogMixin, AbstractPointPen):
|
|
|
559
574
|
glyphSet,
|
|
560
575
|
*args,
|
|
561
576
|
skipMissingComponents=None,
|
|
562
|
-
reverseFlipped=False,
|
|
577
|
+
reverseFlipped: bool | ReverseFlipped = False,
|
|
563
578
|
**kwargs,
|
|
564
579
|
):
|
|
565
580
|
"""Takes a 'glyphSet' argument (dict), in which the glyphs that are referenced
|
|
566
581
|
as components are looked up by their name.
|
|
567
582
|
|
|
568
|
-
If the optional 'reverseFlipped' argument is True
|
|
569
|
-
matrix has a negative determinant will be decomposed
|
|
570
|
-
to compensate for the flip.
|
|
583
|
+
If the optional 'reverseFlipped' argument is True or a ReverseFlipped enum value,
|
|
584
|
+
components whose transformation matrix has a negative determinant will be decomposed
|
|
585
|
+
with a reversed path direction to compensate for the flip.
|
|
586
|
+
|
|
587
|
+
The reverseFlipped parameter can be:
|
|
588
|
+
- False or ReverseFlipped.NO: Don't reverse flipped components
|
|
589
|
+
- True or ReverseFlipped.KEEP_START: Reverse, keeping original starting point
|
|
590
|
+
- ReverseFlipped.ON_CURVE_FIRST: Reverse, ensuring first point is on-curve
|
|
571
591
|
|
|
572
592
|
The optional 'skipMissingComponents' argument can be set to True/False to
|
|
573
593
|
override the homonymous class attribute for a given pen instance.
|
|
@@ -579,7 +599,13 @@ class DecomposingPointPen(LogMixin, AbstractPointPen):
|
|
|
579
599
|
if skipMissingComponents is None
|
|
580
600
|
else skipMissingComponents
|
|
581
601
|
)
|
|
582
|
-
|
|
602
|
+
# Handle backward compatibility and validate string inputs
|
|
603
|
+
if reverseFlipped is False:
|
|
604
|
+
self.reverseFlipped = ReverseFlipped.NO
|
|
605
|
+
elif reverseFlipped is True:
|
|
606
|
+
self.reverseFlipped = ReverseFlipped.KEEP_START
|
|
607
|
+
else:
|
|
608
|
+
self.reverseFlipped = ReverseFlipped(reverseFlipped)
|
|
583
609
|
|
|
584
610
|
def addComponent(self, baseGlyphName, transformation, identifier=None, **kwargs):
|
|
585
611
|
"""Transform the points of the base glyph and draw it onto self.
|
|
@@ -600,10 +626,18 @@ class DecomposingPointPen(LogMixin, AbstractPointPen):
|
|
|
600
626
|
pen = self
|
|
601
627
|
if transformation != Identity:
|
|
602
628
|
pen = TransformPointPen(pen, transformation)
|
|
603
|
-
if self.reverseFlipped:
|
|
629
|
+
if self.reverseFlipped != ReverseFlipped.NO:
|
|
604
630
|
# if the transformation has a negative determinant, it will
|
|
605
631
|
# reverse the contour direction of the component
|
|
606
632
|
a, b, c, d = transformation[:4]
|
|
607
633
|
if a * d - b * c < 0:
|
|
608
634
|
pen = ReverseContourPointPen(pen)
|
|
635
|
+
|
|
636
|
+
if self.reverseFlipped == ReverseFlipped.ON_CURVE_FIRST:
|
|
637
|
+
from fontTools.pens.filterPen import OnCurveFirstPointPen
|
|
638
|
+
|
|
639
|
+
# Ensure the starting point is an on-curve.
|
|
640
|
+
# Wrap last so this filter runs first during drawPoints
|
|
641
|
+
pen = OnCurveFirstPointPen(pen)
|
|
642
|
+
|
|
609
643
|
glyph.drawPoints(pen)
|
fontTools/qu2cu/qu2cu.c
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Generated by Cython 3.1.
|
|
1
|
+
/* Generated by Cython 3.1.4 */
|
|
2
2
|
|
|
3
3
|
/* BEGIN: Cython Metadata
|
|
4
4
|
{
|
|
@@ -32,8 +32,8 @@ END: Cython Metadata */
|
|
|
32
32
|
#elif PY_VERSION_HEX < 0x03080000
|
|
33
33
|
#error Cython requires Python 3.8+.
|
|
34
34
|
#else
|
|
35
|
-
#define __PYX_ABI_VERSION "
|
|
36
|
-
#define CYTHON_HEX_VERSION
|
|
35
|
+
#define __PYX_ABI_VERSION "3_1_4"
|
|
36
|
+
#define CYTHON_HEX_VERSION 0x030104F0
|
|
37
37
|
#define CYTHON_FUTURE_DIVISION 1
|
|
38
38
|
/* CModulePreamble */
|
|
39
39
|
#include <stddef.h>
|
|
@@ -3887,8 +3887,12 @@ static PyObject *__pyx_f_9fontTools_5qu2cu_5qu2cu_merge_curves(PyObject *__pyx_v
|
|
|
3887
3887
|
__pyx_t_14 = __Pyx_c_diff_double(__pyx_v_p1, __pyx_v_p0);
|
|
3888
3888
|
__pyx_t_7 = __pyx_PyComplex_FromComplex(__pyx_t_14); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 146, __pyx_L1_error)
|
|
3889
3889
|
__Pyx_GOTREF(__pyx_t_7);
|
|
3890
|
-
|
|
3891
|
-
|
|
3890
|
+
{
|
|
3891
|
+
Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_v_ts);
|
|
3892
|
+
if (unlikely(((!CYTHON_ASSUME_SAFE_SIZE) && __pyx_temp < 0))) __PYX_ERR(0, 146, __pyx_L1_error)
|
|
3893
|
+
__pyx_t_9 = (__pyx_temp != 0);
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3892
3896
|
if (__pyx_t_9) {
|
|
3893
3897
|
__pyx_t_1 = __Pyx_GetItemInt_List(__pyx_v_ts, 0, long, 1, __Pyx_PyLong_From_long, 1, 0, 1, 1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 146, __pyx_L1_error)
|
|
3894
3898
|
__Pyx_GOTREF(__pyx_t_1);
|
|
@@ -3922,8 +3926,12 @@ static PyObject *__pyx_f_9fontTools_5qu2cu_5qu2cu_merge_curves(PyObject *__pyx_v
|
|
|
3922
3926
|
__pyx_t_14 = __Pyx_c_diff_double(__pyx_v_p2, __pyx_v_p3);
|
|
3923
3927
|
__pyx_t_1 = __pyx_PyComplex_FromComplex(__pyx_t_14); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 147, __pyx_L1_error)
|
|
3924
3928
|
__Pyx_GOTREF(__pyx_t_1);
|
|
3925
|
-
|
|
3926
|
-
|
|
3929
|
+
{
|
|
3930
|
+
Py_ssize_t __pyx_temp = __Pyx_PyList_GET_SIZE(__pyx_v_ts);
|
|
3931
|
+
if (unlikely(((!CYTHON_ASSUME_SAFE_SIZE) && __pyx_temp < 0))) __PYX_ERR(0, 147, __pyx_L1_error)
|
|
3932
|
+
__pyx_t_9 = (__pyx_temp != 0);
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3927
3935
|
if (__pyx_t_9) {
|
|
3928
3936
|
__pyx_t_7 = __Pyx_GetItemInt_List(__pyx_v_ts, -1L, long, 1, __Pyx_PyLong_From_long, 1, 1, 1, 1); if (unlikely(!__pyx_t_7)) __PYX_ERR(0, 147, __pyx_L1_error)
|
|
3929
3937
|
__Pyx_GOTREF(__pyx_t_7);
|
|
@@ -9031,15 +9039,16 @@ static int __Pyx_InitConstants(__pyx_mstatetype *__pyx_mstate) {
|
|
|
9031
9039
|
return -1;
|
|
9032
9040
|
}
|
|
9033
9041
|
/* #### Code section: init_codeobjects ### */
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9042
|
+
\
|
|
9043
|
+
typedef struct {
|
|
9044
|
+
unsigned int argcount : 3;
|
|
9045
|
+
unsigned int num_posonly_args : 1;
|
|
9046
|
+
unsigned int num_kwonly_args : 1;
|
|
9047
|
+
unsigned int nlocals : 6;
|
|
9048
|
+
unsigned int flags : 10;
|
|
9049
|
+
unsigned int first_line : 9;
|
|
9050
|
+
unsigned int line_table_length : 15;
|
|
9051
|
+
} __Pyx_PyCode_New_function_description;
|
|
9043
9052
|
/* NewCodeObj.proto */
|
|
9044
9053
|
static PyObject* __Pyx_PyCode_New(
|
|
9045
9054
|
const __Pyx_PyCode_New_function_description descr,
|
|
@@ -12270,6 +12279,7 @@ static int __pyx_CommonTypesMetaclass_init(PyObject *module) {
|
|
|
12270
12279
|
return -1;
|
|
12271
12280
|
}
|
|
12272
12281
|
mstate->__pyx_CommonTypesMetaclassType = __Pyx_FetchCommonTypeFromSpec(NULL, module, &__pyx_CommonTypesMetaclass_spec, bases);
|
|
12282
|
+
Py_DECREF(bases);
|
|
12273
12283
|
if (unlikely(mstate->__pyx_CommonTypesMetaclassType == NULL)) {
|
|
12274
12284
|
return -1;
|
|
12275
12285
|
}
|
|
@@ -16097,6 +16107,10 @@ static int __Pyx_check_binary_version(unsigned long ct_version, unsigned long rt
|
|
|
16097
16107
|
PyCode_NewWithPosOnlyArgs
|
|
16098
16108
|
#endif
|
|
16099
16109
|
(a, p, k, l, s, f, code, c, n, v, fv, cell, fn, name, name, fline, lnos, __pyx_mstate_global->__pyx_empty_bytes);
|
|
16110
|
+
#if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030c00A1
|
|
16111
|
+
if (likely(result))
|
|
16112
|
+
result->_co_firsttraceable = 0;
|
|
16113
|
+
#endif
|
|
16100
16114
|
return result;
|
|
16101
16115
|
}
|
|
16102
16116
|
#elif PY_VERSION_HEX >= 0x030800B2 && !CYTHON_COMPILING_IN_PYPY
|
|
Binary file
|
fontTools/subset/__init__.py
CHANGED
|
@@ -272,7 +272,7 @@ Font table options
|
|
|
272
272
|
Specify (=), add to (+=) or exclude from (-=) the comma-separated
|
|
273
273
|
set of tables that will be be dropped.
|
|
274
274
|
By default, the following tables are dropped:
|
|
275
|
-
'
|
|
275
|
+
'JSTF', 'DSIG', 'EBDT', 'EBLC', 'EBSC', 'PCLT', 'LTSH'
|
|
276
276
|
and Graphite tables: 'Feat', 'Glat', 'Gloc', 'Silf', 'Sill'.
|
|
277
277
|
The tool will attempt to subset the remaining tables.
|
|
278
278
|
|
|
@@ -827,13 +827,26 @@ def subset_glyphs(self, s):
|
|
|
827
827
|
self.MarkArray.MarkRecord, mark_indices
|
|
828
828
|
)
|
|
829
829
|
self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
|
|
830
|
-
|
|
830
|
+
class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
|
|
831
|
+
|
|
832
|
+
intersect_base_indices = self.BaseCoverage.intersect(s.glyphs)
|
|
833
|
+
base_records = self.BaseArray.BaseRecord
|
|
834
|
+
num_base_records = len(base_records)
|
|
835
|
+
base_indices = [
|
|
836
|
+
i
|
|
837
|
+
for i in intersect_base_indices
|
|
838
|
+
if i < num_base_records
|
|
839
|
+
and any(base_records[i].BaseAnchor[j] is not None for j in class_indices)
|
|
840
|
+
]
|
|
841
|
+
if not base_indices:
|
|
842
|
+
return False
|
|
843
|
+
|
|
844
|
+
self.BaseCoverage.remap(base_indices)
|
|
831
845
|
self.BaseArray.BaseRecord = _list_subset(
|
|
832
846
|
self.BaseArray.BaseRecord, base_indices
|
|
833
847
|
)
|
|
834
848
|
self.BaseArray.BaseCount = len(self.BaseArray.BaseRecord)
|
|
835
849
|
# Prune empty classes
|
|
836
|
-
class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
|
|
837
850
|
self.ClassCount = len(class_indices)
|
|
838
851
|
for m in self.MarkArray.MarkRecord:
|
|
839
852
|
m.Class = class_indices.index(m.Class)
|
|
@@ -867,13 +880,31 @@ def subset_glyphs(self, s):
|
|
|
867
880
|
self.MarkArray.MarkRecord, mark_indices
|
|
868
881
|
)
|
|
869
882
|
self.MarkArray.MarkCount = len(self.MarkArray.MarkRecord)
|
|
870
|
-
|
|
883
|
+
class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
|
|
884
|
+
|
|
885
|
+
intersect_ligature_indices = self.LigatureCoverage.intersect(s.glyphs)
|
|
886
|
+
ligature_array = self.LigatureArray.LigatureAttach
|
|
887
|
+
num_ligatures = self.LigatureArray.LigatureCount
|
|
888
|
+
|
|
889
|
+
ligature_indices = [
|
|
890
|
+
i
|
|
891
|
+
for i in intersect_ligature_indices
|
|
892
|
+
if i < num_ligatures
|
|
893
|
+
and any(
|
|
894
|
+
any(component.LigatureAnchor[j] is not None for j in class_indices)
|
|
895
|
+
for component in ligature_array[i].ComponentRecord
|
|
896
|
+
)
|
|
897
|
+
]
|
|
898
|
+
|
|
899
|
+
if not ligature_indices:
|
|
900
|
+
return False
|
|
901
|
+
|
|
902
|
+
self.LigatureCoverage.remap(ligature_indices)
|
|
871
903
|
self.LigatureArray.LigatureAttach = _list_subset(
|
|
872
904
|
self.LigatureArray.LigatureAttach, ligature_indices
|
|
873
905
|
)
|
|
874
906
|
self.LigatureArray.LigatureCount = len(self.LigatureArray.LigatureAttach)
|
|
875
907
|
# Prune empty classes
|
|
876
|
-
class_indices = _uniq_sort(v.Class for v in self.MarkArray.MarkRecord)
|
|
877
908
|
self.ClassCount = len(class_indices)
|
|
878
909
|
for m in self.MarkArray.MarkRecord:
|
|
879
910
|
m.Class = class_indices.index(m.Class)
|
|
@@ -915,13 +946,26 @@ def subset_glyphs(self, s):
|
|
|
915
946
|
self.Mark1Array.MarkRecord, mark1_indices
|
|
916
947
|
)
|
|
917
948
|
self.Mark1Array.MarkCount = len(self.Mark1Array.MarkRecord)
|
|
918
|
-
|
|
949
|
+
class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
|
|
950
|
+
|
|
951
|
+
intersect_mark2_indices = self.Mark2Coverage.intersect(s.glyphs)
|
|
952
|
+
mark2_records = self.Mark2Array.Mark2Record
|
|
953
|
+
num_mark2_records = len(mark2_records)
|
|
954
|
+
mark2_indices = [
|
|
955
|
+
i
|
|
956
|
+
for i in intersect_mark2_indices
|
|
957
|
+
if i < num_mark2_records
|
|
958
|
+
and any(mark2_records[i].Mark2Anchor[j] is not None for j in class_indices)
|
|
959
|
+
]
|
|
960
|
+
if not mark2_indices:
|
|
961
|
+
return False
|
|
962
|
+
|
|
963
|
+
self.Mark2Coverage.remap(mark2_indices)
|
|
919
964
|
self.Mark2Array.Mark2Record = _list_subset(
|
|
920
965
|
self.Mark2Array.Mark2Record, mark2_indices
|
|
921
966
|
)
|
|
922
967
|
self.Mark2Array.MarkCount = len(self.Mark2Array.Mark2Record)
|
|
923
968
|
# Prune empty classes
|
|
924
|
-
class_indices = _uniq_sort(v.Class for v in self.Mark1Array.MarkRecord)
|
|
925
969
|
self.ClassCount = len(class_indices)
|
|
926
970
|
for m in self.Mark1Array.MarkRecord:
|
|
927
971
|
m.Class = class_indices.index(m.Class)
|
|
@@ -1717,6 +1761,19 @@ def subset_features(self, feature_indices):
|
|
|
1717
1761
|
return bool(self.SubstitutionCount)
|
|
1718
1762
|
|
|
1719
1763
|
|
|
1764
|
+
@_add_method(otTables.FeatureTableSubstitution)
|
|
1765
|
+
def prune_features(self, feature_index_map):
|
|
1766
|
+
self.ensureDecompiled()
|
|
1767
|
+
self.SubstitutionRecord = [
|
|
1768
|
+
r for r in self.SubstitutionRecord if r.FeatureIndex in feature_index_map.keys()
|
|
1769
|
+
]
|
|
1770
|
+
# remap feature indices
|
|
1771
|
+
for r in self.SubstitutionRecord:
|
|
1772
|
+
r.FeatureIndex = feature_index_map[r.FeatureIndex]
|
|
1773
|
+
self.SubstitutionCount = len(self.SubstitutionRecord)
|
|
1774
|
+
return bool(self.SubstitutionCount)
|
|
1775
|
+
|
|
1776
|
+
|
|
1720
1777
|
@_add_method(otTables.FeatureVariations)
|
|
1721
1778
|
def subset_features(self, feature_indices):
|
|
1722
1779
|
self.ensureDecompiled()
|
|
@@ -1735,6 +1792,24 @@ def subset_features(self, feature_indices):
|
|
|
1735
1792
|
return bool(self.FeatureVariationCount)
|
|
1736
1793
|
|
|
1737
1794
|
|
|
1795
|
+
@_add_method(otTables.FeatureVariations)
|
|
1796
|
+
def prune_features(self, feature_index_map):
|
|
1797
|
+
self.ensureDecompiled()
|
|
1798
|
+
for r in self.FeatureVariationRecord:
|
|
1799
|
+
r.FeatureTableSubstitution.prune_features(feature_index_map)
|
|
1800
|
+
# Prune empty records at the end only
|
|
1801
|
+
# https://github.com/fonttools/fonttools/issues/1881
|
|
1802
|
+
while (
|
|
1803
|
+
self.FeatureVariationRecord
|
|
1804
|
+
and not self.FeatureVariationRecord[
|
|
1805
|
+
-1
|
|
1806
|
+
].FeatureTableSubstitution.SubstitutionCount
|
|
1807
|
+
):
|
|
1808
|
+
self.FeatureVariationRecord.pop()
|
|
1809
|
+
self.FeatureVariationCount = len(self.FeatureVariationRecord)
|
|
1810
|
+
return bool(self.FeatureVariationCount)
|
|
1811
|
+
|
|
1812
|
+
|
|
1738
1813
|
@_add_method(otTables.DefaultLangSys, otTables.LangSys)
|
|
1739
1814
|
def subset_features(self, feature_indices):
|
|
1740
1815
|
if self.ReqFeatureIndex in feature_indices:
|
|
@@ -1750,6 +1825,16 @@ def subset_features(self, feature_indices):
|
|
|
1750
1825
|
return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
|
|
1751
1826
|
|
|
1752
1827
|
|
|
1828
|
+
@_add_method(otTables.DefaultLangSys, otTables.LangSys)
|
|
1829
|
+
def prune_features(self, feature_index_map):
|
|
1830
|
+
self.ReqFeatureIndex = feature_index_map.get(self.ReqFeatureIndex, 65535)
|
|
1831
|
+
self.FeatureIndex = [
|
|
1832
|
+
feature_index_map[f] for f in self.FeatureIndex if f in feature_index_map.keys()
|
|
1833
|
+
]
|
|
1834
|
+
self.FeatureCount = len(self.FeatureIndex)
|
|
1835
|
+
return bool(self.FeatureCount or self.ReqFeatureIndex != 65535)
|
|
1836
|
+
|
|
1837
|
+
|
|
1753
1838
|
@_add_method(otTables.DefaultLangSys, otTables.LangSys)
|
|
1754
1839
|
def collect_features(self):
|
|
1755
1840
|
feature_indices = self.FeatureIndex[:]
|
|
@@ -1773,6 +1858,21 @@ def subset_features(self, feature_indices, keepEmptyDefaultLangSys=False):
|
|
|
1773
1858
|
return bool(self.LangSysCount or self.DefaultLangSys)
|
|
1774
1859
|
|
|
1775
1860
|
|
|
1861
|
+
@_add_method(otTables.Script)
|
|
1862
|
+
def prune_features(self, feature_index_map, keepEmptyDefaultLangSys=False):
|
|
1863
|
+
if (
|
|
1864
|
+
self.DefaultLangSys
|
|
1865
|
+
and not self.DefaultLangSys.prune_features(feature_index_map)
|
|
1866
|
+
and not keepEmptyDefaultLangSys
|
|
1867
|
+
):
|
|
1868
|
+
self.DefaultLangSys = None
|
|
1869
|
+
self.LangSysRecord = [
|
|
1870
|
+
l for l in self.LangSysRecord if l.LangSys.prune_features(feature_index_map)
|
|
1871
|
+
]
|
|
1872
|
+
self.LangSysCount = len(self.LangSysRecord)
|
|
1873
|
+
return bool(self.LangSysCount or self.DefaultLangSys)
|
|
1874
|
+
|
|
1875
|
+
|
|
1776
1876
|
@_add_method(otTables.Script)
|
|
1777
1877
|
def collect_features(self):
|
|
1778
1878
|
feature_indices = [l.LangSys.collect_features() for l in self.LangSysRecord]
|
|
@@ -1794,6 +1894,19 @@ def subset_features(self, feature_indices, retain_empty):
|
|
|
1794
1894
|
return bool(self.ScriptCount)
|
|
1795
1895
|
|
|
1796
1896
|
|
|
1897
|
+
@_add_method(otTables.ScriptList)
|
|
1898
|
+
def prune_features(self, feature_index_map, retain_empty):
|
|
1899
|
+
# https://bugzilla.mozilla.org/show_bug.cgi?id=1331737#c32
|
|
1900
|
+
self.ScriptRecord = [
|
|
1901
|
+
s
|
|
1902
|
+
for s in self.ScriptRecord
|
|
1903
|
+
if s.Script.prune_features(feature_index_map, s.ScriptTag == "DFLT")
|
|
1904
|
+
or retain_empty
|
|
1905
|
+
]
|
|
1906
|
+
self.ScriptCount = len(self.ScriptRecord)
|
|
1907
|
+
return bool(self.ScriptCount)
|
|
1908
|
+
|
|
1909
|
+
|
|
1797
1910
|
@_add_method(otTables.ScriptList)
|
|
1798
1911
|
def collect_features(self):
|
|
1799
1912
|
return _uniq_sort(sum((s.Script.collect_features() for s in self.ScriptRecord), []))
|
|
@@ -1981,21 +2094,74 @@ def subset_script_tags(self, tags):
|
|
|
1981
2094
|
|
|
1982
2095
|
@_add_method(ttLib.getTableClass("GSUB"), ttLib.getTableClass("GPOS"))
|
|
1983
2096
|
def prune_features(self):
|
|
1984
|
-
"""Remove unreferenced features
|
|
2097
|
+
"""Remove unreferenced and duplicate features in FeatureList
|
|
2098
|
+
Remove unreferenced features and remap duplicate feature indices in ScriptList and FeatureVariations
|
|
2099
|
+
"""
|
|
1985
2100
|
if self.table.ScriptList:
|
|
1986
2101
|
feature_indices = self.table.ScriptList.collect_features()
|
|
1987
2102
|
else:
|
|
1988
2103
|
feature_indices = []
|
|
2104
|
+
(feature_indices, feature_index_map) = self.remap_duplicate_features(
|
|
2105
|
+
feature_indices
|
|
2106
|
+
)
|
|
2107
|
+
|
|
1989
2108
|
if self.table.FeatureList:
|
|
1990
2109
|
self.table.FeatureList.subset_features(feature_indices)
|
|
1991
2110
|
if getattr(self.table, "FeatureVariations", None):
|
|
1992
|
-
self.table.FeatureVariations.
|
|
2111
|
+
self.table.FeatureVariations.prune_features(feature_index_map)
|
|
1993
2112
|
if self.table.ScriptList:
|
|
1994
|
-
self.table.ScriptList.
|
|
1995
|
-
|
|
2113
|
+
self.table.ScriptList.prune_features(
|
|
2114
|
+
feature_index_map, self.retain_empty_scripts()
|
|
1996
2115
|
)
|
|
1997
2116
|
|
|
1998
2117
|
|
|
2118
|
+
@_add_method(ttLib.getTableClass("GSUB"), ttLib.getTableClass("GPOS"))
|
|
2119
|
+
def remap_duplicate_features(self, feature_indices):
|
|
2120
|
+
"""Return retained feature indices(without duplicates) and remapped feature indices"""
|
|
2121
|
+
features = self.table.FeatureList.FeatureRecord
|
|
2122
|
+
|
|
2123
|
+
unique_features = {}
|
|
2124
|
+
duplicate_features = {}
|
|
2125
|
+
for i in feature_indices:
|
|
2126
|
+
f = features[i]
|
|
2127
|
+
tag = f.FeatureTag
|
|
2128
|
+
|
|
2129
|
+
same_tag_features = unique_features.get(tag)
|
|
2130
|
+
if same_tag_features is None:
|
|
2131
|
+
unique_features[tag] = set([i])
|
|
2132
|
+
duplicate_features[i] = i
|
|
2133
|
+
continue
|
|
2134
|
+
|
|
2135
|
+
found = False
|
|
2136
|
+
for other_i in same_tag_features:
|
|
2137
|
+
if features[other_i] == f:
|
|
2138
|
+
found = True
|
|
2139
|
+
duplicate_features[i] = other_i
|
|
2140
|
+
break
|
|
2141
|
+
|
|
2142
|
+
if not found:
|
|
2143
|
+
same_tag_features.add(i)
|
|
2144
|
+
duplicate_features[i] = i
|
|
2145
|
+
|
|
2146
|
+
## remap retained feature indices
|
|
2147
|
+
feature_map = {}
|
|
2148
|
+
new_idx = 0
|
|
2149
|
+
|
|
2150
|
+
for i in feature_indices:
|
|
2151
|
+
unique_i = duplicate_features.get(i, i)
|
|
2152
|
+
v = feature_map.get(unique_i)
|
|
2153
|
+
if v is None:
|
|
2154
|
+
feature_map[i] = new_idx
|
|
2155
|
+
new_idx += 1
|
|
2156
|
+
else:
|
|
2157
|
+
feature_map[i] = v
|
|
2158
|
+
|
|
2159
|
+
retained_feature_indices = _uniq_sort(
|
|
2160
|
+
sum((list(s) for s in unique_features.values()), [])
|
|
2161
|
+
)
|
|
2162
|
+
return (retained_feature_indices, feature_map)
|
|
2163
|
+
|
|
2164
|
+
|
|
1999
2165
|
@_add_method(ttLib.getTableClass("GSUB"), ttLib.getTableClass("GPOS"))
|
|
2000
2166
|
def prune_pre_subset(self, font, options):
|
|
2001
2167
|
# Drop undesired features
|
|
@@ -3145,7 +3311,6 @@ class Options(object):
|
|
|
3145
3311
|
|
|
3146
3312
|
# spaces in tag names (e.g. "SVG ", "cvt ") are stripped by the argument parser
|
|
3147
3313
|
_drop_tables_default = [
|
|
3148
|
-
"BASE",
|
|
3149
3314
|
"JSTF",
|
|
3150
3315
|
"DSIG",
|
|
3151
3316
|
"EBDT",
|
|
@@ -3157,6 +3322,7 @@ class Options(object):
|
|
|
3157
3322
|
_drop_tables_default += ["Feat", "Glat", "Gloc", "Silf", "Sill"] # Graphite
|
|
3158
3323
|
_no_subset_tables_default = [
|
|
3159
3324
|
"avar",
|
|
3325
|
+
"BASE",
|
|
3160
3326
|
"fvar",
|
|
3161
3327
|
"gasp",
|
|
3162
3328
|
"head",
|
|
@@ -118,6 +118,7 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
|
|
|
118
118
|
def build_psNameMapping(self, ttFont):
|
|
119
119
|
mapping = {}
|
|
120
120
|
allNames = {}
|
|
121
|
+
glyphOrderNames = set(self.glyphOrder)
|
|
121
122
|
for i in range(ttFont["maxp"].numGlyphs):
|
|
122
123
|
glyphName = psName = self.glyphOrder[i]
|
|
123
124
|
if glyphName == "":
|
|
@@ -126,16 +127,15 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
|
|
|
126
127
|
if glyphName in allNames:
|
|
127
128
|
# make up a new glyphName that's unique
|
|
128
129
|
n = allNames[glyphName]
|
|
129
|
-
# check if the exists in
|
|
130
|
-
|
|
131
|
-
while (glyphName + "." + str(n)) in names:
|
|
130
|
+
# check if the glyph name exists in the glyph order
|
|
131
|
+
while f"{glyphName}.{n}" in glyphOrderNames:
|
|
132
132
|
n += 1
|
|
133
133
|
allNames[glyphName] = n + 1
|
|
134
|
-
glyphName = glyphName
|
|
134
|
+
glyphName = f"{glyphName}.{n}"
|
|
135
135
|
|
|
136
|
-
self.glyphOrder[i] = glyphName
|
|
137
136
|
allNames[glyphName] = 1
|
|
138
137
|
if glyphName != psName:
|
|
138
|
+
self.glyphOrder[i] = glyphName
|
|
139
139
|
mapping[glyphName] = psName
|
|
140
140
|
|
|
141
141
|
self.mapping = mapping
|