python-fontbro 0.29.0__tar.gz → 0.30.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.
Files changed (88) hide show
  1. {python_fontbro-0.29.0/python_fontbro.egg-info → python_fontbro-0.30.0}/PKG-INFO +1 -1
  2. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/data/features.json +1 -1
  3. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/data/unicode-blocks.json +28 -0
  4. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/data/unicode-scripts.json +32 -17
  5. python_fontbro-0.30.0/fontbro/features.py +124 -0
  6. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/metadata.py +1 -1
  7. {python_fontbro-0.29.0 → python_fontbro-0.30.0/python_fontbro.egg-info}/PKG-INFO +1 -1
  8. python_fontbro-0.30.0/tests/test_features.py +149 -0
  9. python_fontbro-0.29.0/fontbro/features.py +0 -48
  10. python_fontbro-0.29.0/tests/test_features.py +0 -27
  11. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/LICENSE.txt +0 -0
  12. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/MANIFEST.in +0 -0
  13. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/README.md +0 -0
  14. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/__init__.py +0 -0
  15. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/bitmap.py +0 -0
  16. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/color.py +0 -0
  17. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/data/family-classifications.json +0 -0
  18. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/embedding_permissions.py +0 -0
  19. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/exceptions.py +0 -0
  20. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/family_classification.py +0 -0
  21. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/files.py +0 -0
  22. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/fingerprint.py +0 -0
  23. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/flags.py +0 -0
  24. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/font.py +0 -0
  25. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/glyphs.py +0 -0
  26. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/math.py +0 -0
  27. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/metrics.py +0 -0
  28. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/monospace.py +0 -0
  29. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/names.py +0 -0
  30. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/pixel.py +0 -0
  31. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/py.typed +0 -0
  32. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/render.py +0 -0
  33. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/sanitize.py +0 -0
  34. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/style_flags.py +0 -0
  35. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/subset.py +0 -0
  36. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/support.py +0 -0
  37. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/tables.py +0 -0
  38. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/unicode.py +0 -0
  39. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/utils.py +0 -0
  40. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/variable.py +0 -0
  41. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/fontbro/version.py +0 -0
  42. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/pyproject.toml +0 -0
  43. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/python_fontbro.egg-info/SOURCES.txt +0 -0
  44. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/python_fontbro.egg-info/dependency_links.txt +0 -0
  45. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/python_fontbro.egg-info/requires.txt +0 -0
  46. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/python_fontbro.egg-info/top_level.txt +0 -0
  47. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/setup.cfg +0 -0
  48. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/setup.py +0 -0
  49. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_bitmap.py +0 -0
  50. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_characters.py +0 -0
  51. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_clone.py +0 -0
  52. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_close.py +0 -0
  53. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_collection.py +0 -0
  54. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_color.py +0 -0
  55. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_context_manager.py +0 -0
  56. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_embedding_permissions.py +0 -0
  57. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_family_classification.py +0 -0
  58. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_family_name.py +0 -0
  59. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_filename.py +0 -0
  60. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_fingerprint.py +0 -0
  61. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_format.py +0 -0
  62. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_glyphs.py +0 -0
  63. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_image.py +0 -0
  64. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_init.py +0 -0
  65. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_instantiation.py +0 -0
  66. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_issues.py +0 -0
  67. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_italic_angle.py +0 -0
  68. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_monospace.py +0 -0
  69. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_names.py +0 -0
  70. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_pixel.py +0 -0
  71. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_rename.py +0 -0
  72. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_sanitize.py +0 -0
  73. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_save.py +0 -0
  74. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_str.py +0 -0
  75. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_style_flags.py +0 -0
  76. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_style_name.py +0 -0
  77. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_subset.py +0 -0
  78. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_support.py +0 -0
  79. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_svg.py +0 -0
  80. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_tables.py +0 -0
  81. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_unicode.py +0 -0
  82. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_unicode_blocks_and_scripts.py +0 -0
  83. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_update_unicode_data.py +0 -0
  84. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_variable.py +0 -0
  85. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_version.py +0 -0
  86. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_vertical_metrics.py +0 -0
  87. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_weight.py +0 -0
  88. {python_fontbro-0.29.0 → python_fontbro-0.30.0}/tests/test_width.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fontbro
3
- Version: 0.29.0
3
+ Version: 0.30.0
4
4
  Summary: friendly font operations on top of fontTools.
5
5
  Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
6
6
  Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
@@ -88,7 +88,7 @@
88
88
  "tag": "clig",
89
89
  "name": "Contextual Ligatures",
90
90
  "exposed": true,
91
- "exposed_active": false
91
+ "exposed_active": true
92
92
  },
93
93
  {
94
94
  "tag": "cpct",
@@ -1015,6 +1015,10 @@
1015
1015
  "characters_total": 64,
1016
1016
  "name": "Tolong Siki"
1017
1017
  },
1018
+ {
1019
+ "characters_total": 16,
1020
+ "name": "Bengali Supplement"
1021
+ },
1018
1022
  {
1019
1023
  "characters_total": 32,
1020
1024
  "name": "Makasar"
@@ -1043,6 +1047,10 @@
1043
1047
  "characters_total": 208,
1044
1048
  "name": "Early Dynastic Cuneiform"
1045
1049
  },
1050
+ {
1051
+ "characters_total": 320,
1052
+ "name": "Archaic Cuneiform Numerals"
1053
+ },
1046
1054
  {
1047
1055
  "characters_total": 112,
1048
1056
  "name": "Cypro-Minoan"
@@ -1127,6 +1135,14 @@
1127
1135
  "characters_total": 128,
1128
1136
  "name": "Tangut Components Supplement"
1129
1137
  },
1138
+ {
1139
+ "characters_total": 928,
1140
+ "name": "Jurchen"
1141
+ },
1142
+ {
1143
+ "characters_total": 64,
1144
+ "name": "Jurchen Radicals"
1145
+ },
1130
1146
  {
1131
1147
  "characters_total": 16,
1132
1148
  "name": "Kana Extended-B"
@@ -1179,6 +1195,10 @@
1179
1195
  "characters_total": 80,
1180
1196
  "name": "Ancient Greek Musical Notation"
1181
1197
  },
1198
+ {
1199
+ "characters_total": 64,
1200
+ "name": "Musical Symbols Supplement"
1201
+ },
1182
1202
  {
1183
1203
  "characters_total": 32,
1184
1204
  "name": "Kaktovik Numerals"
@@ -1203,6 +1223,10 @@
1203
1223
  "characters_total": 688,
1204
1224
  "name": "Sutton SignWriting"
1205
1225
  },
1226
+ {
1227
+ "characters_total": 256,
1228
+ "name": "Miscellaneous Symbols and Arrows Extended"
1229
+ },
1206
1230
  {
1207
1231
  "characters_total": 256,
1208
1232
  "name": "Latin Extended-G"
@@ -1367,6 +1391,10 @@
1367
1391
  "characters_total": 4304,
1368
1392
  "name": "CJK Unified Ideographs Extension J"
1369
1393
  },
1394
+ {
1395
+ "characters_total": 11328,
1396
+ "name": "Seal"
1397
+ },
1370
1398
  {
1371
1399
  "characters_total": 128,
1372
1400
  "name": "Tags"
@@ -1,11 +1,11 @@
1
1
  [
2
2
  {
3
- "characters_total": 9123,
3
+ "characters_total": 9276,
4
4
  "name": "Common",
5
5
  "tag": "Zyyy"
6
6
  },
7
7
  {
8
- "characters_total": 1492,
8
+ "characters_total": 1653,
9
9
  "name": "Latin",
10
10
  "tag": "Latn"
11
11
  },
@@ -15,17 +15,17 @@
15
15
  "tag": "Bopo"
16
16
  },
17
17
  {
18
- "characters_total": 684,
18
+ "characters_total": 695,
19
19
  "name": "Inherited",
20
20
  "tag": "Zinh"
21
21
  },
22
22
  {
23
- "characters_total": 518,
23
+ "characters_total": 520,
24
24
  "name": "Greek",
25
25
  "tag": "Grek"
26
26
  },
27
27
  {
28
- "characters_total": 143942,
28
+ "characters_total": 143911,
29
29
  "name": "Unknown",
30
30
  "tag": "Zzzz"
31
31
  },
@@ -40,17 +40,17 @@
40
40
  "tag": "Cyrl"
41
41
  },
42
42
  {
43
- "characters_total": 96,
43
+ "characters_total": 99,
44
44
  "name": "Armenian",
45
45
  "tag": "Armn"
46
46
  },
47
47
  {
48
- "characters_total": 134,
48
+ "characters_total": 136,
49
49
  "name": "Hebrew",
50
50
  "tag": "Hebr"
51
51
  },
52
52
  {
53
- "characters_total": 1413,
53
+ "characters_total": 1452,
54
54
  "name": "Arabic",
55
55
  "tag": "Arab"
56
56
  },
@@ -80,12 +80,12 @@
80
80
  "tag": "Mand"
81
81
  },
82
82
  {
83
- "characters_total": 164,
83
+ "characters_total": 165,
84
84
  "name": "Devanagari",
85
85
  "tag": "Deva"
86
86
  },
87
87
  {
88
- "characters_total": 96,
88
+ "characters_total": 98,
89
89
  "name": "Bengali",
90
90
  "tag": "Beng"
91
91
  },
@@ -100,7 +100,7 @@
100
100
  "tag": "Gujr"
101
101
  },
102
102
  {
103
- "characters_total": 91,
103
+ "characters_total": 93,
104
104
  "name": "Oriya",
105
105
  "tag": "Orya"
106
106
  },
@@ -280,17 +280,17 @@
280
280
  "tag": "Tfng"
281
281
  },
282
282
  {
283
- "characters_total": 103351,
283
+ "characters_total": 103352,
284
284
  "name": "Han",
285
285
  "tag": "Hani"
286
286
  },
287
287
  {
288
- "characters_total": 381,
288
+ "characters_total": 382,
289
289
  "name": "Hiragana",
290
290
  "tag": "Hira"
291
291
  },
292
292
  {
293
- "characters_total": 321,
293
+ "characters_total": 327,
294
294
  "name": "Katakana",
295
295
  "tag": "Kana"
296
296
  },
@@ -745,10 +745,15 @@
745
745
  "tag": "Kawi"
746
746
  },
747
747
  {
748
- "characters_total": 1234,
748
+ "characters_total": 1393,
749
749
  "name": "Cuneiform",
750
750
  "tag": "Xsux"
751
751
  },
752
+ {
753
+ "characters_total": 164,
754
+ "name": "Proto Cuneiform",
755
+ "tag": "Pcun"
756
+ },
752
757
  {
753
758
  "characters_total": 99,
754
759
  "name": "Cypro Minoan",
@@ -810,7 +815,7 @@
810
815
  "tag": "Plrd"
811
816
  },
812
817
  {
813
- "characters_total": 7059,
818
+ "characters_total": 7061,
814
819
  "name": "Tangut",
815
820
  "tag": "Tang"
816
821
  },
@@ -820,10 +825,15 @@
820
825
  "tag": "Nshu"
821
826
  },
822
827
  {
823
- "characters_total": 472,
828
+ "characters_total": 477,
824
829
  "name": "Khitan Small Script",
825
830
  "tag": "Kits"
826
831
  },
832
+ {
833
+ "characters_total": 965,
834
+ "name": "Jurchen",
835
+ "tag": "Jurc"
836
+ },
827
837
  {
828
838
  "characters_total": 143,
829
839
  "name": "Duployan",
@@ -873,5 +883,10 @@
873
883
  "characters_total": 88,
874
884
  "name": "Adlam",
875
885
  "tag": "Adlm"
886
+ },
887
+ {
888
+ "characters_total": 11328,
889
+ "name": "Seal",
890
+ "tag": "Seal"
876
891
  }
877
892
  ]
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from fontTools.ttLib import TTFont
6
+
7
+ from fontbro.utils import read_json
8
+
9
+ # Features:
10
+ # https://docs.microsoft.com/en-gb/typography/opentype/spec/featurelist
11
+ # https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings
12
+ _FEATURES_LIST: list[dict[str, Any]] = read_json("data/features.json")
13
+ _FEATURES_BY_TAG: dict[str, dict[str, Any]] = {
14
+ feature["tag"]: feature for feature in _FEATURES_LIST
15
+ }
16
+
17
+
18
+ def get_features(
19
+ ttfont: TTFont,
20
+ ) -> list[dict[str, Any]]:
21
+ """
22
+ Gets the opentype features of the given font.
23
+ """
24
+ features_tags = get_features_tags(ttfont)
25
+ features_params = _get_features_params(ttfont)
26
+ features = []
27
+ for features_tag in features_tags:
28
+ if features_tag not in _FEATURES_BY_TAG:
29
+ continue
30
+ feature = _FEATURES_BY_TAG[features_tag].copy()
31
+ feature["params"] = _get_feature_params_dict(
32
+ ttfont, features_params.get(features_tag)
33
+ )
34
+ features.append(feature)
35
+ return features
36
+
37
+
38
+ def _get_features_params(
39
+ ttfont: TTFont,
40
+ ) -> dict[str, Any]:
41
+ """
42
+ Gets the opentype features params (if any) of the given font by tag.
43
+ """
44
+ features_params: dict[str, Any] = {}
45
+ for table_tag in ["GPOS", "GSUB"]:
46
+ if table_tag in ttfont:
47
+ table = ttfont[table_tag].table
48
+ try:
49
+ feature_record = table.FeatureList.FeatureRecord or []
50
+ except AttributeError:
51
+ feature_record = []
52
+ for feature in feature_record:
53
+ feature_params = feature.Feature.FeatureParams
54
+ if feature_params is not None:
55
+ features_params.setdefault(feature.FeatureTag, feature_params)
56
+ return features_params
57
+
58
+
59
+ def _get_feature_params_dict(
60
+ ttfont: TTFont,
61
+ feature_params: Any,
62
+ ) -> dict[str, Any]:
63
+ """
64
+ Gets the designer-defined params of a feature:
65
+ stylistic sets (ss01-ss20) and character variants (cv01-cv99).
66
+ """
67
+
68
+ def get_name(name_id: int | None) -> str | None:
69
+ if not name_id or "name" not in ttfont:
70
+ return None
71
+ name: str | None = ttfont["name"].getDebugName(name_id)
72
+ return name
73
+
74
+ params: dict[str, Any] = {
75
+ "name": None,
76
+ "tooltip": None,
77
+ "sample_text": None,
78
+ "labels": [],
79
+ "characters": [],
80
+ }
81
+ if feature_params is None:
82
+ return params
83
+ params["name"] = get_name(
84
+ getattr(feature_params, "UINameID", None)
85
+ or getattr(feature_params, "FeatUILabelNameID", None)
86
+ )
87
+ params["tooltip"] = get_name(
88
+ getattr(feature_params, "FeatUITooltipTextNameID", None)
89
+ )
90
+ params["sample_text"] = get_name(getattr(feature_params, "SampleTextNameID", None))
91
+ first_label_name_id = getattr(feature_params, "FirstParamUILabelNameID", 0)
92
+ labels_count = getattr(feature_params, "NumNamedParameters", 0)
93
+ if first_label_name_id:
94
+ # keep missing labels as None to preserve the label index / feature value match
95
+ params["labels"] = [
96
+ get_name(first_label_name_id + index) for index in range(labels_count)
97
+ ]
98
+ # characters are stored as uint24, skip values that are not unicode scalar values
99
+ characters = getattr(feature_params, "Character", None) or []
100
+ params["characters"] = [
101
+ chr(character)
102
+ for character in characters
103
+ if 0 <= character <= 0x10FFFF and not 0xD800 <= character <= 0xDFFF
104
+ ]
105
+ return params
106
+
107
+
108
+ def get_features_tags(
109
+ ttfont: TTFont,
110
+ ) -> list[str]:
111
+ """
112
+ Gets the opentype features tags of the given font.
113
+ """
114
+ features_tags = set()
115
+ for table_tag in ["GPOS", "GSUB"]:
116
+ if table_tag in ttfont:
117
+ table = ttfont[table_tag].table
118
+ try:
119
+ feature_record = table.FeatureList.FeatureRecord or []
120
+ except AttributeError:
121
+ feature_record = []
122
+ for feature in feature_record:
123
+ features_tags.add(feature.FeatureTag)
124
+ return sorted(features_tags)
@@ -4,4 +4,4 @@ __description__ = "friendly font operations on top of fontTools."
4
4
  __email__ = "fabio.caccamo@gmail.com"
5
5
  __license__ = "MIT"
6
6
  __title__ = "python-fontbro"
7
- __version__ = "0.29.0"
7
+ __version__ = "0.30.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-fontbro
3
- Version: 0.29.0
3
+ Version: 0.30.0
4
4
  Summary: friendly font operations on top of fontTools.
5
5
  Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
6
6
  Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
@@ -0,0 +1,149 @@
1
+ from tests import AbstractTestCase
2
+
3
+
4
+ class FeaturesTestCase(AbstractTestCase):
5
+ """
6
+ Test case for the methods related to the font features.
7
+ """
8
+
9
+ def test_get_features(self):
10
+ font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
11
+ features = font.get_features()
12
+ self.assertEqual(
13
+ features,
14
+ [
15
+ {
16
+ "tag": "smcp",
17
+ "name": "Small Capitals",
18
+ "exposed": True,
19
+ "exposed_active": False,
20
+ "params": {
21
+ "name": None,
22
+ "tooltip": None,
23
+ "sample_text": None,
24
+ "labels": [],
25
+ "characters": [],
26
+ },
27
+ }
28
+ ],
29
+ )
30
+
31
+ def test_get_features_with_params(self):
32
+ font = self._get_font("/Inter/static/Inter-Regular.ttf")
33
+ features = font.get_features()
34
+ features_by_tag = {feature["tag"]: feature for feature in features}
35
+ self.assertEqual(
36
+ features_by_tag["cv11"],
37
+ {
38
+ "tag": "cv11",
39
+ "name": "Character Variant 11",
40
+ "exposed": True,
41
+ "exposed_active": False,
42
+ "params": {
43
+ "name": "Single-storey a",
44
+ "tooltip": None,
45
+ "sample_text": None,
46
+ "labels": [],
47
+ "characters": [],
48
+ },
49
+ },
50
+ )
51
+ self.assertEqual(
52
+ features_by_tag["ss01"],
53
+ {
54
+ "tag": "ss01",
55
+ "name": "Stylistic Set 1",
56
+ "exposed": True,
57
+ "exposed_active": False,
58
+ "params": {
59
+ "name": "Open digits",
60
+ "tooltip": None,
61
+ "sample_text": None,
62
+ "labels": [],
63
+ "characters": [],
64
+ },
65
+ },
66
+ )
67
+ self.assertEqual(
68
+ features_by_tag["salt"]["params"],
69
+ {
70
+ "name": None,
71
+ "tooltip": None,
72
+ "sample_text": None,
73
+ "labels": [],
74
+ "characters": [],
75
+ },
76
+ )
77
+
78
+ def test_get_features_with_character_variants_params(self):
79
+ from fontTools.ttLib.tables import otTables
80
+
81
+ font = self._get_font("/Inter/static/Inter-Regular.ttf")
82
+ ttfont = font.get_ttfont()
83
+ name_table = ttfont["name"]
84
+ feature_params = otTables.FeatureParamsCharacterVariants()
85
+ feature_params.Format = 0
86
+ feature_params.FeatUILabelNameID = name_table.addName("Alternate a")
87
+ feature_params.FeatUITooltipTextNameID = name_table.addName(
88
+ "Alternate a tooltip"
89
+ )
90
+ feature_params.SampleTextNameID = name_table.addName("aaa")
91
+ feature_params.NumNamedParameters = 2
92
+ feature_params.FirstParamUILabelNameID = name_table.addName("Single-storey")
93
+ name_table.addName("Double-storey")
94
+ feature_params.CharCount = 2
95
+ feature_params.Character = [0x61, 0xE0]
96
+ for feature_record in ttfont["GSUB"].table.FeatureList.FeatureRecord:
97
+ if feature_record.FeatureTag == "cv11":
98
+ feature_record.Feature.FeatureParams = feature_params
99
+ features = font.get_features()
100
+ features_by_tag = {feature["tag"]: feature for feature in features}
101
+ self.assertEqual(
102
+ features_by_tag["cv11"]["params"],
103
+ {
104
+ "name": "Alternate a",
105
+ "tooltip": "Alternate a tooltip",
106
+ "sample_text": "aaa",
107
+ "labels": ["Single-storey", "Double-storey"],
108
+ "characters": ["a", "à"],
109
+ },
110
+ )
111
+
112
+ def test_get_features_with_character_variants_params_malformed(self):
113
+ from fontTools.ttLib.tables import otTables
114
+
115
+ font = self._get_font("/Inter/static/Inter-Regular.ttf")
116
+ ttfont = font.get_ttfont()
117
+ name_table = ttfont["name"]
118
+ feature_params = otTables.FeatureParamsCharacterVariants()
119
+ feature_params.Format = 0
120
+ feature_params.FeatUILabelNameID = name_table.addName("Alternate a")
121
+ feature_params.FeatUITooltipTextNameID = 0
122
+ feature_params.SampleTextNameID = 0
123
+ feature_params.NumNamedParameters = 3
124
+ feature_params.FirstParamUILabelNameID = name_table.addName("Single-storey")
125
+ missing_label_name_id = name_table.addName("Double-storey")
126
+ name_table.addName("Open")
127
+ name_table.removeNames(nameID=missing_label_name_id)
128
+ feature_params.CharCount = 4
129
+ feature_params.Character = [0x61, 0xD800, 0x110000, 0xE0]
130
+ for feature_record in ttfont["GSUB"].table.FeatureList.FeatureRecord:
131
+ if feature_record.FeatureTag == "cv11":
132
+ feature_record.Feature.FeatureParams = feature_params
133
+ features = font.get_features()
134
+ features_by_tag = {feature["tag"]: feature for feature in features}
135
+ self.assertEqual(
136
+ features_by_tag["cv11"]["params"],
137
+ {
138
+ "name": "Alternate a",
139
+ "tooltip": None,
140
+ "sample_text": None,
141
+ "labels": ["Single-storey", None, "Open"],
142
+ "characters": ["a", "à"],
143
+ },
144
+ )
145
+
146
+ def test_get_features_tags(self):
147
+ font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
148
+ features = font.get_features_tags()
149
+ self.assertEqual(features, ["smcp"])
@@ -1,48 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from fontTools.ttLib import TTFont
6
-
7
- from fontbro.utils import read_json
8
-
9
- # Features:
10
- # https://docs.microsoft.com/en-gb/typography/opentype/spec/featurelist
11
- # https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings
12
- _FEATURES_LIST: list[dict[str, Any]] = read_json("data/features.json")
13
- _FEATURES_BY_TAG: dict[str, dict[str, Any]] = {
14
- feature["tag"]: feature for feature in _FEATURES_LIST
15
- }
16
-
17
-
18
- def get_features(
19
- ttfont: TTFont,
20
- ) -> list[dict[str, Any]]:
21
- """
22
- Gets the opentype features of the given font.
23
- """
24
- features_tags = get_features_tags(ttfont)
25
- return [
26
- _FEATURES_BY_TAG.get(features_tag, {}).copy()
27
- for features_tag in features_tags
28
- if features_tag in _FEATURES_BY_TAG
29
- ]
30
-
31
-
32
- def get_features_tags(
33
- ttfont: TTFont,
34
- ) -> list[str]:
35
- """
36
- Gets the opentype features tags of the given font.
37
- """
38
- features_tags = set()
39
- for table_tag in ["GPOS", "GSUB"]:
40
- if table_tag in ttfont:
41
- table = ttfont[table_tag].table
42
- try:
43
- feature_record = table.FeatureList.FeatureRecord or []
44
- except AttributeError:
45
- feature_record = []
46
- for feature in feature_record:
47
- features_tags.add(feature.FeatureTag)
48
- return sorted(features_tags)
@@ -1,27 +0,0 @@
1
- from tests import AbstractTestCase
2
-
3
-
4
- class FeaturesTestCase(AbstractTestCase):
5
- """
6
- Test case for the methods related to the font features.
7
- """
8
-
9
- def test_get_features(self):
10
- font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
11
- features = font.get_features()
12
- self.assertEqual(
13
- features,
14
- [
15
- {
16
- "tag": "smcp",
17
- "name": "Small Capitals",
18
- "exposed": True,
19
- "exposed_active": False,
20
- }
21
- ],
22
- )
23
-
24
- def test_get_features_tags(self):
25
- font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
26
- features = font.get_features_tags()
27
- self.assertEqual(features, ["smcp"])