New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove ttconv and implement Type-42 embedding using fontTools #20866
base: main
Are you sure you want to change the base?
Conversation
447fd1b
to
a826065
Compare
8c44828
to
2535508
Compare
de16cf0
to
daee4e5
Compare
016713a
to
7cea2d4
Compare
@@ -24,28 +25,59 @@ def get_glyphs_subset(fontfile, characters): | |||
Subset a TTF font | |||
|
|||
Reads the named fontfile and restricts the font to the characters. | |||
Returns a serialization of the subset font as file-like object. | |||
Returns a TTFont object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just remove the line; you have a Returns
section.
if fontfile.endswith('ttc'): | ||
# fix this once we support loading more fonts from a collection | ||
# https://github.com/matplotlib/matplotlib/issues/3135#issuecomment-571085541 | ||
options.font_number = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we test a ttc
in CI already, so it could be used to test this line?
The Type-42 formatted font | ||
""" | ||
version, breakpoints = _version_and_breakpoints(font.get('loca'), fontdata) | ||
post, name = font['post'], font['name'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these tables guaranteed to exist?
Read the version number of the font and determine sfnts breakpoints. | ||
When a TrueType font file is written as a Type 42 font, it has to be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read the version number of the font and determine sfnts breakpoints. | |
When a TrueType font file is written as a Type 42 font, it has to be | |
Read the version number of the font and determine sfnts breakpoints. | |
When a TrueType font file is written as a Type 42 font, it has to be |
tuple | ||
((v1, v2), breakpoints) where v1 is the major version number, | ||
v2 is the minor version number and breakpoints is a sorted list | ||
of offsets into fontdata; if loca is not available, just the table | ||
boundaries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For multiple return, you should list the entries separately, like Parameters.
breakpoints = sorted( | ||
set(tables.values()) | glyf_breakpoints | {len(fontdata)} | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
breakpoints = sorted( | |
set(tables.values()) | glyf_breakpoints | {len(fontdata)} | |
) | |
breakpoints = sorted({*tables.values(), *glyf_breakpoints, len(fontdata)}) |
s = StringIO() | ||
go = font.getGlyphOrder() | ||
s.write(f'/CharStrings {len(go)} dict dup begin\n') | ||
for i, name in enumerate(go): | ||
s.write(f'/{name} {i} def\n') | ||
s.write('end readonly def') | ||
return s.getvalue() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why you need StringIO
for this, as there is no file IO necessary.
s = StringIO() | |
go = font.getGlyphOrder() | |
s.write(f'/CharStrings {len(go)} dict dup begin\n') | |
for i, name in enumerate(go): | |
s.write(f'/{name} {i} def\n') | |
s.write('end readonly def') | |
return s.getvalue() | |
go = font.getGlyphOrder() | |
s = f'/CharStrings {len(go)} dict dup begin\n' | |
for i, name in enumerate(go): | |
s += f'/{name} {i} def\n' | |
s += 'end readonly def' | |
return s |
b = BytesIO() | ||
b.write(b'/sfnts[') | ||
pos = 0 | ||
while pos < len(fontdata): | ||
i = bisect.bisect_left(breakpoints, pos + 65534) | ||
newpos = breakpoints[i-1] | ||
if newpos <= pos: | ||
# have to accept a larger string | ||
newpos = breakpoints[-1] | ||
b.write(b'<') | ||
b.write(binascii.hexlify(fontdata[pos:newpos])) | ||
b.write(b'00>') # need an extra zero byte on every string | ||
pos = newpos | ||
b.write(b']def') | ||
s = b.getvalue().decode('ascii') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also doesn't appear to need BytesIO
:
b = BytesIO() | |
b.write(b'/sfnts[') | |
pos = 0 | |
while pos < len(fontdata): | |
i = bisect.bisect_left(breakpoints, pos + 65534) | |
newpos = breakpoints[i-1] | |
if newpos <= pos: | |
# have to accept a larger string | |
newpos = breakpoints[-1] | |
b.write(b'<') | |
b.write(binascii.hexlify(fontdata[pos:newpos])) | |
b.write(b'00>') # need an extra zero byte on every string | |
pos = newpos | |
b.write(b']def') | |
s = b.getvalue().decode('ascii') | |
s = '/sfnts[' | |
pos = 0 | |
while pos < len(fontdata): | |
i = bisect.bisect_left(breakpoints, pos + 65534) | |
newpos = breakpoints[i-1] | |
if newpos <= pos: | |
# have to accept a larger string | |
newpos = breakpoints[-1] | |
s += f'<{fontdata[pos:newpos].hex()}00>' # Always NULL terminate. | |
pos = newpos | |
s += ']def' |
fontfiles = (font_manager.findfont(fp) for fp in fps) | ||
if len(set(fontfiles)) < 6: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fontfiles = (font_manager.findfont(fp) for fp in fps) | |
if len(set(fontfiles)) < 6: | |
fontfiles = {font_manager.findfont(fp) for fp in fps} | |
if len(fontfiles) < 6: |
Wow, I missed that this came in. 💯 on dropping an extension module! |
Dropping to draft until the comments and rebase are addressed. Anyone should feel free to move back to active... |
Ping @jkseppan? |
|
||
Returns | ||
------- | ||
fontTools.ttLib.ttFont.TTFont |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long term, it could make sense to link to the documentation? (Although fontTools is not in the intersphinx_mapping yet.)
We want to try and do mpl 3.7 feature freeze around Jan 1 so I think it is unlikely we will have the bandwidth to get this rebased and reviewed by then, pushing to 3.8. |
Move the test in lib/matplotlib/tests/test_ttconv.py to lib/matplotlib/tests/test_backend_pdf.py. Actually it has not been a test of ttconv for a while, since type-3 conversion has not been using ttconv.
Split _backend_pdf_ps.get_glyphs_subset into two functions: get_glyphs_subset returns the font object (which needs to be closed after using) and font_as_file serializes this object into a BytesIO file-like object.
Fonttools cannot subset these and drops them, avoid the warning
Also don't end up in an infinite loop if there is a larger gap between breakpoints than we would like.
FT2Font throws RuntimeError for some fonts
Not the subsetted one, since in some cases fontTools.subset does not produce a good name table. Import functions from _backend_pdf_ps to help keep lines shorter.
7cea2d4
to
a1b41fe
Compare
Do we have someone who can pick this up? |
PR Summary
ttconv is now only being used for outputting fonts in Type-42 format in PostScript files, and with fontTools this turns out to be quite doable in pure Python.
PR Checklist
pytest
passes).flake8
on changed files to check).flake8-docstrings
and runflake8 --docstring-convention=all
).doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).