TTFQuery 2.1.0__tar.gz → 2.1.1__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 (35) hide show
  1. {ttfquery-2.1.0 → ttfquery-2.1.1}/PKG-INFO +3 -2
  2. {ttfquery-2.1.0 → ttfquery-2.1.1}/README.md +2 -1
  3. {ttfquery-2.1.0 → ttfquery-2.1.1}/TTFQuery.egg-info/PKG-INFO +3 -2
  4. {ttfquery-2.1.0 → ttfquery-2.1.1}/TTFQuery.egg-info/SOURCES.txt +1 -0
  5. {ttfquery-2.1.0 → ttfquery-2.1.1}/pyproject.toml +23 -1
  6. ttfquery-2.1.1/tests/test_describe.py +191 -0
  7. ttfquery-2.1.1/tests/test_glyphquery.py +451 -0
  8. ttfquery-2.1.1/tests/test_registry.py +168 -0
  9. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/__init__.py +1 -1
  10. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/describe.py +84 -38
  11. ttfquery-2.1.1/ttfquery/errors.py +31 -0
  12. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/findsystem.py +2 -2
  13. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/glyph.py +81 -16
  14. ttfquery-2.1.1/ttfquery/glyphquery.py +234 -0
  15. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/guessdescription.py +2 -2
  16. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/scriptregistry.py +3 -2
  17. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/ttffiles.py +62 -29
  18. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/ttfgroups.py +2 -2
  19. ttfquery-2.1.0/tests/test_describe.py +0 -56
  20. ttfquery-2.1.0/tests/test_glyphquery.py +0 -47
  21. ttfquery-2.1.0/tests/test_registry.py +0 -60
  22. ttfquery-2.1.0/ttfquery/glyphquery.py +0 -90
  23. {ttfquery-2.1.0 → ttfquery-2.1.1}/MANIFEST.in +0 -0
  24. {ttfquery-2.1.0 → ttfquery-2.1.1}/TTFQuery.egg-info/dependency_links.txt +0 -0
  25. {ttfquery-2.1.0 → ttfquery-2.1.1}/TTFQuery.egg-info/entry_points.txt +0 -0
  26. {ttfquery-2.1.0 → ttfquery-2.1.1}/TTFQuery.egg-info/requires.txt +0 -0
  27. {ttfquery-2.1.0 → ttfquery-2.1.1}/TTFQuery.egg-info/top_level.txt +0 -0
  28. {ttfquery-2.1.0 → ttfquery-2.1.1}/license.txt +0 -0
  29. {ttfquery-2.1.0 → ttfquery-2.1.1}/setup.cfg +0 -0
  30. {ttfquery-2.1.0 → ttfquery-2.1.1}/setup.py +0 -0
  31. {ttfquery-2.1.0 → ttfquery-2.1.1}/tests/test_findsystem_linux.py +0 -0
  32. {ttfquery-2.1.0 → ttfquery-2.1.1}/tests/test_findsystem_win32.py +0 -0
  33. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/_scriptregistry.py +0 -0
  34. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/ttffamily.py +0 -0
  35. {ttfquery-2.1.0 → ttfquery-2.1.1}/ttfquery/ttfmetadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TTFQuery
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: FontTools-based package for querying system fonts
5
5
  Home-page: http://ttfquery.sourceforge.net/
6
6
  Author: Mike C. Fletcher
@@ -79,7 +79,8 @@ TTFQuery builds on the FontTools package to allow the Python programmer to accom
79
79
  * abstract family type
80
80
  * proper font name
81
81
  * glyph outlines
82
- * build simple metadata registries for run-time font matching
82
+ * build simple metadata registries for run-time font matching, holding the
83
+ fonts text can be laid out with and reporting the files that cannot
83
84
 
84
85
  With these functionalities, it is possible to readily create OpenGL solid-text
85
86
  rendering libraries which can accept abstract font-family names as font
@@ -9,7 +9,8 @@ TTFQuery builds on the FontTools package to allow the Python programmer to accom
9
9
  * abstract family type
10
10
  * proper font name
11
11
  * glyph outlines
12
- * build simple metadata registries for run-time font matching
12
+ * build simple metadata registries for run-time font matching, holding the
13
+ fonts text can be laid out with and reporting the files that cannot
13
14
 
14
15
  With these functionalities, it is possible to readily create OpenGL solid-text
15
16
  rendering libraries which can accept abstract font-family names as font
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TTFQuery
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: FontTools-based package for querying system fonts
5
5
  Home-page: http://ttfquery.sourceforge.net/
6
6
  Author: Mike C. Fletcher
@@ -79,7 +79,8 @@ TTFQuery builds on the FontTools package to allow the Python programmer to accom
79
79
  * abstract family type
80
80
  * proper font name
81
81
  * glyph outlines
82
- * build simple metadata registries for run-time font matching
82
+ * build simple metadata registries for run-time font matching, holding the
83
+ fonts text can be laid out with and reporting the files that cannot
83
84
 
84
85
  With these functionalities, it is possible to readily create OpenGL solid-text
85
86
  rendering libraries which can accept abstract font-family names as font
@@ -17,6 +17,7 @@ tests/test_registry.py
17
17
  ttfquery/__init__.py
18
18
  ttfquery/_scriptregistry.py
19
19
  ttfquery/describe.py
20
+ ttfquery/errors.py
20
21
  ttfquery/findsystem.py
21
22
  ttfquery/glyph.py
22
23
  ttfquery/glyphquery.py
@@ -71,8 +71,30 @@ cache-keys = [{ file = "pyproject.toml" }, { file = "ttfquery/__init__.py" }]
71
71
  # Named explicitly rather than left to ruff's default, which has widened
72
72
  # between releases: a gate whose meaning moves when a tool is upgraded is not a
73
73
  # gate. `E,W,F,B` is what the other pre-2010 projects here hold to.
74
+ [tool.openglcontext-checks]
75
+ # The OGC rules (openglcontext-checks) this project is clean of, held by
76
+ # the workspace preflight. The others join as the project reaches zero of
77
+ # each; the counts are in openglcontext/plans/DEFECT-PREVENTION.md.
78
+ select = ["OGC121", "OGC131", "OGC141", "OGC201", "OGC221"]
79
+
74
80
  [tool.ruff.lint]
75
- select = ["E", "W", "F", "B"]
81
+ select = [
82
+ "E", "W", "F", "B", "PGH003", "PGH004", "PLE0604", "PLE0605",
83
+ # A noqa that silences nothing, and an exception swallowed with no trace
84
+ # or caught wider than what can be raised.
85
+ "RUF100", "S110", "S112", "E722", "TRY400", "BLE001",
86
+ # Parameters nothing reads, and datetimes with no time zone.
87
+ "ARG", "DTZ",
88
+ # A mutable class attribute, an import inside a function, and a private
89
+ # name reached from another module.
90
+ "RUF012", "PLC0415", "SLF001",
91
+ # exec, eval, unpickling, tar extraction without a filter, and URL
92
+ # opening of any scheme.
93
+ "S102", "S307", "S301", "S202", "S310",
94
+ # `X | Y` for unions where the floor allows it, and the builtin generics
95
+ # (`dict[str, int]`) rather than their `typing` aliases.
96
+ "UP007", "UP006", "UP035",
97
+ ]
76
98
  # `_name` as well as `_`: an underscore prefix is how Python says a name is
77
99
  # bound but deliberately unused.
78
100
  dummy-variable-rgx = "^(_[a-zA-Z0-9_]*|err)$"
@@ -0,0 +1,191 @@
1
+ from __future__ import print_function
2
+ from types import SimpleNamespace
3
+ import unittest
4
+
5
+ from fontTools import ttLib
6
+ import pytest
7
+
8
+ from ttfquery import describe, findsystem, glyphquery
9
+
10
+ from builtfonts import built_open, glyphNameFor, withSubtables
11
+
12
+ class TestDescribe(unittest.TestCase):
13
+ def test_describe_system_fonts(self):
14
+ for fontfile in list(findsystem.findFonts())[:100]:
15
+ try:
16
+ font = describe.openFont(fontfile)
17
+ except Exception as err:
18
+ err.args += ('Error opening font', fontfile)
19
+ raise
20
+ else:
21
+ short = describe.shortName( font )
22
+ assert short, "Null name for font %s"%(fontfile,)
23
+ family = describe.family( font )
24
+ assert len(family)==2, family
25
+ modifiers = describe.modifiers( font )
26
+ assert describe.weightName(modifiers[0])
27
+
28
+
29
+ def a_named_font(name, family, typographic=None):
30
+ """A stand-in font whose name table holds `name` and `family` records,
31
+ and a typographic family record where `typographic` is given."""
32
+ records = [
33
+ SimpleNamespace(nameID=describe.FONT_SPECIFIER_NAME_ID, string=name),
34
+ SimpleNamespace(nameID=describe.FONT_SPECIFIER_FAMILY_ID, string=family),
35
+ ]
36
+ if typographic is not None:
37
+ records.append(SimpleNamespace(
38
+ nameID=describe.FONT_TYPOGRAPHIC_FAMILY_ID, string=typographic))
39
+ return {'name': SimpleNamespace(names=records)}
40
+
41
+
42
+ class TestTheNameIsText:
43
+ """`shortName` answers what the font calls itself, as text.
44
+
45
+ A name record is bytes in one of two encodings, and answering those bytes
46
+ put `b'Open Sans'` into every message that named a font and left the font
47
+ registry decoding what this had just encoded.
48
+ """
49
+
50
+ def a_font(self):
51
+ for path in findsystem.findFonts():
52
+ try:
53
+ return describe.openFont(path)
54
+ except (OSError, ttLib.TTLibError):
55
+ continue
56
+ return None
57
+
58
+ def test_both_halves_are_strings(self):
59
+ font = self.a_font()
60
+ if font is None:
61
+ pytest.skip('no readable font on this machine')
62
+ name, family = describe.shortName(font)
63
+ assert isinstance(name, str), repr(name)
64
+ assert isinstance(family, str), repr(family)
65
+
66
+ def test_a_two_byte_record_is_read_as_utf_16(self):
67
+ font = a_named_font(
68
+ 'Ångstrom Bold'.encode('utf-16-be'), 'Ångstrom'.encode('utf-16-be'),
69
+ )
70
+ assert describe.shortName(font) == ('Ångstrom Bold', 'Ångstrom')
71
+
72
+ def test_a_single_byte_record_is_read_as_latin_1(self):
73
+ font = a_named_font(b'Open Sans Bold', b'Open Sans')
74
+ assert describe.shortName(font) == ('Open Sans Bold', 'Open Sans')
75
+
76
+ def test_text_is_left_as_it_is(self):
77
+ font = a_named_font('Open Sans Bold', 'Open Sans')
78
+ assert describe.shortName(font) == ('Open Sans Bold', 'Open Sans')
79
+
80
+
81
+ class TestTheEncodingIsAUnicodeOne:
82
+ """Which cmap sub-table a character is looked up in.
83
+
84
+ A character reaches the sub-table as `ord(char)`, a Unicode code point, so
85
+ the sub-table that answers about the character asked for is a Unicode one.
86
+ A Macintosh or symbol sub-table numbers its characters its own way and
87
+ answers with whichever glyph it keeps at that number, which is a different
88
+ character and, in the fonts where it bites, a different glyph.
89
+ """
90
+
91
+ def a_font(self, *encodings, holds=None):
92
+ """A font carrying these cmap sub-tables, in this order
93
+
94
+ holds -- (platformID, platEncID) to the character whose glyph that
95
+ sub-table is to keep at code point 97, where it is meant to
96
+ disagree with the others. Every sub-table holds `a`'s glyph there
97
+ by default, so which sub-table a lookup read shows up in the glyph
98
+ name it answers with.
99
+ """
100
+ holds = holds or {}
101
+ font = built_open({'a': 500, 'b': 500, 'c': 500, 'd': 500})
102
+ return withSubtables(font, {
103
+ encoding: {ord('a'): glyphNameFor(holds.get(encoding, 'a'))}
104
+ for encoding in encodings
105
+ })
106
+
107
+ def test_a_unicode_subtable_wins_over_a_macintosh_one(self):
108
+ font = self.a_font((1, 0), (3, 1))
109
+ assert describe.guessEncoding(font) == (3, 1)
110
+
111
+ def test_the_order_in_the_file_does_not_decide(self):
112
+ """A font that carries its Macintosh sub-table first is read the same"""
113
+ assert describe.guessEncoding(self.a_font((3, 1), (1, 0))) == \
114
+ describe.guessEncoding(self.a_font((1, 0), (3, 1)))
115
+
116
+ def test_a_symbol_subtable_is_not_a_unicode_one(self):
117
+ """Encoding 0 on platform 3 is the symbol range, not the BMP"""
118
+ font = self.a_font((3, 0), (0, 3))
119
+ assert describe.guessEncoding(font) == (0, 3)
120
+
121
+ def test_the_whole_repertoire_is_preferred_to_the_bmp(self):
122
+ font = self.a_font((3, 1), (3, 10))
123
+ assert describe.guessEncoding(font) == (3, 10)
124
+ font = self.a_font((0, 3), (0, 4))
125
+ assert describe.guessEncoding(font) == (0, 4)
126
+
127
+ def test_every_preference_is_a_unicode_encoding(self):
128
+ for encoding in describe.UNICODE_ENCODINGS:
129
+ assert encoding[0] in (0, 3), encoding
130
+ if encoding[0] == 3:
131
+ assert encoding[1] in (1, 10), encoding
132
+
133
+ def test_each_preference_is_chosen_over_a_macintosh_subtable(self):
134
+ for encoding in describe.UNICODE_ENCODINGS:
135
+ font = self.a_font((1, 0), encoding)
136
+ assert describe.guessEncoding(font) == encoding, encoding
137
+
138
+ def test_a_font_with_no_unicode_subtable_answers_with_what_it_has(self):
139
+ """A symbol font keeps its glyphs at code points of its own"""
140
+ font = self.a_font((3, 0), (1, 0))
141
+ assert describe.guessEncoding(font) == (3, 0)
142
+
143
+ def test_the_glyph_read_is_the_one_the_unicode_subtable_names(self):
144
+ """The reading this preference exists for
145
+
146
+ The two sub-tables disagree about what lives at 97: the Macintosh one
147
+ answers with another character's glyph, and the Unicode one with `a`.
148
+ """
149
+ font = self.a_font((1, 0), (3, 1), holds={(1, 0): 'd'})
150
+ assert glyphquery.explicitGlyph(font, 'a') == glyphNameFor('a')
151
+ assert glyphquery.width(font, glyphquery.explicitGlyph(font, 'a')) == 500
152
+
153
+ def test_an_encoding_the_caller_names_is_used_as_given(self):
154
+ """A caller who names a sub-table gets that sub-table"""
155
+ font = self.a_font((1, 0), (3, 1), holds={(1, 0): 'd'})
156
+ assert describe.guessEncoding(font, (1, 0)) == (1, 0)
157
+ assert glyphquery.explicitGlyph(font, 'a', (1, 0)) == glyphNameFor('d')
158
+
159
+ def test_a_platform_the_caller_names_is_searched(self):
160
+ font = self.a_font((1, 0), (3, 1))
161
+ assert describe.guessEncoding(font, 3) == (3, 1)
162
+ assert describe.guessEncoding(font, (1,)) == (1, 0)
163
+
164
+ def test_an_encoding_the_font_has_not_got_says_so(self):
165
+ font = self.a_font((3, 1))
166
+ for given, wanted in (((1, 0), 'does not appear'), (1, 'platformID==1')):
167
+ try:
168
+ describe.guessEncoding(font, given)
169
+ except ValueError as err:
170
+ assert wanted in str(err), err
171
+ else:
172
+ raise AssertionError('%r was accepted' % (given,))
173
+
174
+ def test_a_font_with_no_subtables_at_all_says_so(self):
175
+ font = withSubtables(built_open({'a': 500}), {})
176
+ try:
177
+ describe.guessEncoding(font)
178
+ except ValueError as err:
179
+ assert 'no encoding tables' in str(err), err
180
+ else:
181
+ raise AssertionError('a font with no cmap sub-table was accepted')
182
+ class TestTheFamily:
183
+ """`shortName`'s family is the typographic family where the font names one"""
184
+
185
+ def test_a_weight_outside_the_four_styles_joins_its_family(self):
186
+ font = a_named_font('Open Sans Light', 'Open Sans Light', 'Open Sans')
187
+ assert describe.shortName(font) == ('Open Sans Light', 'Open Sans')
188
+
189
+ def test_without_one_it_is_the_legacy_family(self):
190
+ font = a_named_font('Open Sans Bold', 'Open Sans')
191
+ assert describe.shortName(font) == ('Open Sans Bold', 'Open Sans')