1""" 2Various round-to-integer helpers. 3""" 4 5import math 6import functools 7import logging 8 9log = logging.getLogger(__name__) 10 11__all__ = [ 12 "noRound", 13 "otRound", 14 "maybeRound", 15 "roundFunc", 16] 17 18def noRound(value): 19 return value 20 21def otRound(value): 22 """Round float value to nearest integer towards ``+Infinity``. 23 24 The OpenType spec (in the section on `"normalization" of OpenType Font Variations <https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview#coordinate-scales-and-normalization>`_) 25 defines the required method for converting floating point values to 26 fixed-point. In particular it specifies the following rounding strategy: 27 28 for fractional values of 0.5 and higher, take the next higher integer; 29 for other fractional values, truncate. 30 31 This function rounds the floating-point value according to this strategy 32 in preparation for conversion to fixed-point. 33 34 Args: 35 value (float): The input floating-point value. 36 37 Returns 38 float: The rounded value. 39 """ 40 # See this thread for how we ended up with this implementation: 41 # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166 42 return int(math.floor(value + 0.5)) 43 44def maybeRound(v, tolerance, round=otRound): 45 rounded = round(v) 46 return rounded if abs(rounded - v) <= tolerance else v 47 48def roundFunc(tolerance, round=otRound): 49 if tolerance < 0: 50 raise ValueError("Rounding tolerance must be positive") 51 52 if tolerance == 0: 53 return noRound 54 55 if tolerance >= .5: 56 return round 57 58 return functools.partial(maybeRound, tolerance=tolerance, round=round) 59