Repository: fonttools/fonttools

Issue:
Float yMin value: required argument is not an integer
If a font file has a float value in `yMin`—and I assume equally in `xMin`, `xMax` or `yMax`—it will fail to save with the error `required argument is not an integer` ([`fontTools/misc/sstruct.py in pack at line 75`](https://github.com/fonttools/fonttools/blob/3.40.0/Lib/fontTools/misc/sstruct.py#L75), fonttools v3.40.0).

Trace:
```
fontTools/misc/sstruct.py in pack at line 75
fontTools/ttLib/tables/_h_e_a_d.py in compile at line 69
fontTools/ttLib/ttFont.py in getTableData at line 651
fontTools/ttLib/ttFont.py in _writeTable at line 633
fontTools/ttLib/ttFont.py in _save at line 212
fontTools/ttLib/ttFont.py in save at line 173
```

Variables at point of error:
```python
formatstring = ">llIIHHQQhhhhHHhhh"
elements = [
    65536, 
    65601, 
    1208942685, 
    1594834165, 
    3, 
    1000, 
    3551183604, 
    3640213847, 
    -132, 
    -170.009, 
    788, 
    835, 
    0, 
    3, 
    2, 
    0, 
    0
]
```

As you can see the value `-170.009` would trigger the error. If integers are expected then rounding should probably be applied.


Discussion:
The question is where rounding should be applied.

If I look at the code in fonttools itself that usually provides these values, I see that rounding is done by the caller.

In your case, which code was responsible for calculating the font bounding box?
Good question! In our case the data is read from a font file I believe. It goes something like:
```python
from fontTools.ttLib import TTFont

font = TTFont(path)

# Here some naming table data is adjusted, nothing to do with bounding box...

font.save(path)
```
This works on most of our files. Only a few have the `yMin` float issue. Does that mean our source font file is invalid, and we should round its `yMin`—and other related metrics—before attempting to save?

To answer your question:
> In your case, which code was responsible for calculating the font bounding box?

I believe it's `fonttools` internals that read this value from the source font file?
Hm, since the code that I looked at does do proper rounding, it's a bit of a mystery. Can you provide exact steps to reproduce?
Yes, luckily I have a [test version of the failing font](https://github.com/fonttools/fonttools/files/3160519/test-font.otf.zip) (with a testing licence and limited character set) that reproduces the issue. 

To reproduce with the above linked file:
```
font = TTFont("/path/to/test-font.otf")
font.save("/path/to/output.otf")
```
Thanks, can reproduce.

The issue is that the CFF FontBBox contains float values:

`<FontBBox value="-132 -170.009 788 835"/>`

And those values are used to populate the head table upon compile. I don't think it is illegal for FontBBox to contain floats, so we indeed need to do some rounding. Probably here:

https://github.com/fonttools/fonttools/blob/ffc98baa0f28af7c4d4c173cca9f01b8c9baac14/Lib/fontTools/ttLib/tables/_h_e_a_d.py#L66


Thanks for confirming. And yes, I was looking at that very line you linked—seems like an appropriate place.
One question, which I don't have the knowledge to answer, is whether numbers should be rounded here; or otherwise further carried through as floats all the way downstream.
We need to round because head table can only contain integers for the font bounding box values, unlike the CFF FontBBox.
I suggest using `fontTools.misc.arrayTools.intRect` function, which takes the floor/ceill thus guaranteeing the rounded rectangle is not smaller than the original.
Would you like to work on a pull request for that?
Sure! On it.

Resolving patch:
diff --git a/Lib/fontTools/ttLib/tables/_h_e_a_d.py b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
--- a/Lib/fontTools/ttLib/tables/_h_e_a_d.py
+++ b/Lib/fontTools/ttLib/tables/_h_e_a_d.py
@@ -4,6 +4,7 @@
 from fontTools.misc.textTools import safeEval, num2binary, binary2num
 from fontTools.misc.timeTools import timestampFromString, timestampToString, timestampNow
 from fontTools.misc.timeTools import epoch_diff as mac_epoch_diff # For backward compat
+from fontTools.misc.arrayTools import intRect
 from . import DefaultTable
 import logging
 
@@ -63,7 +64,7 @@ def compile(self, ttFont):
 			# For TT-flavored fonts, xMin, yMin, xMax and yMax are set in table__m_a_x_p.recalc().
 			if 'CFF ' in ttFont:
 				topDict = ttFont['CFF '].cff.topDictIndex[0]
-				self.xMin, self.yMin, self.xMax, self.yMax = topDict.FontBBox
+				self.xMin, self.yMin, self.xMax, self.yMax = intRect(topDict.FontBBox)
 		if ttFont.recalcTimestamp:
 			self.modified = timestampNow()
 		data = sstruct.pack(headFormat, self)


Test patch: