ufo2ft: Produces invalid CFF OTF files under Python 3 (repro available)
What I did: Compiled a simple UFO to OTF (CFF) using ufo2ft 2.5.0 (compileOTF(ufo, optimizeCFF=CFFOptimization.NONE)
)
What I expected: Resulting OTF file to be valid as per OTS, FontVal and macOS Font Book.
What actually happened: OTF file is unreadable by macOS Font Book and fails validation by OTS.
If I compile the same source with the same settings using the same version of ufo2ft using Python 2.7 instead of Python 3.7 the resulting OTF file is valid — this seems to indicate a bug related to Python 3.
Full repro available here: https://github.com/rsms/ufo2ft-py3-bug
- Platform: macOS (Darwin 18.2.0 Darwin Kernel Version 18.2.0: Fri Oct 5 19:41:49 PDT 2018; root:xnu-4903.221.2~2/RELEASE_X86_64 x86_64)
- Python 2: 2.7.15
- Python 3: 3.7.2
- ufo2ft: 2.5.0
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Comments: 18 (13 by maintainers)
released ufo2ft 2.6.0
the culprit seems to be in the
encodeFloat
function offontTools.misc.psCharStrings
module (despite according to git blame it hasn’t changed in 17 years…)https://github.com/fonttools/fonttools/blob/3ba285e2504c73cac4970ae846f56db2b41fa4a0/Lib/fontTools/misc/psCharStrings.py#L228
it’s calling
str
on the float before encoding it to binary, and on python3 the float repr seems to be more precise than on python2.Your font UPEM is 2816 (which is not as common value as say 1000 or 2048, which perhaps explain why we haven’t caught this issue before). The FontMatrix value
1.0/2816
, when it’s converted to str, becomes'0.000355113636364'
in python 2.7 and'0.0003551136363636364'
in python 3.7.If I apply this patch to round the FontMatrix values to 15 decimal digits, the output from python2 and python3 is identical, and both your test fonts pass validation:
I’m thinking of rather changing the encodeFloat function in upstream fonttools, to prevent this and similar issues with real numbers in CFF dicts.
In makeotf, they seem to round to 8 decimal digits https://github.com/adobe-type-tools/afdko/issues/174
I’ll sleep over it, and push a fix for it tomorrow.
thanks for filing this issue and providing an exact reproducer!
Comparing the ttx dumps of the two fonts generated respectively from python2 and python3 I only get this difference in the FontMatrix operator of the CFF table:
the extra precision of the python3 float division seems to trigger this invalid font file somehow. I’ll investigate more tomorrow, but we probably need to make sure we round these floats to some reasonable number of digits.
https://github.com/googlei18n/ufo2ft/blob/39ab30c54ab71b24d96bb68e0965c41937d39b35/Lib/ufo2ft/outlineCompiler.py#L986
patch was merged upstream, a new fonttools release should be out later today, after which we shall bump the requirement in ufo2ft and release the latter as well.
by trial and error, it appears that what triggers this issue is the length (in bytes) of the encoded real number. When it is up to 10 bytes, it is accepted; if it is > 10, it is rejected as invalid. For example, the number
-9.399999999999999
, when run throughencodeFloat
(on py3) yieldsb'\x1e\xe9\xa3\x99\x99\x99\x99\x99\x99\x99\xff'
which is 11 bytes long, and thus rejected. If I remove one digit or I remove the leading sign (I make the number positive with the same number of decimal digits), it is encoded with 10 bytes and it passes…maybe the encodeFloat should be capped to max 10 bytes?
/cc @readroberts