diff options
Diffstat (limited to 'Lib/fontTools/misc/roundTools.py')
-rw-r--r-- | Lib/fontTools/misc/roundTools.py | 47 |
1 files changed, 47 insertions, 0 deletions
diff --git a/Lib/fontTools/misc/roundTools.py b/Lib/fontTools/misc/roundTools.py index c1d546f1..6f4aa634 100644 --- a/Lib/fontTools/misc/roundTools.py +++ b/Lib/fontTools/misc/roundTools.py @@ -56,3 +56,50 @@ def roundFunc(tolerance, round=otRound): return round return functools.partial(maybeRound, tolerance=tolerance, round=round) + + +def nearestMultipleShortestRepr(value: float, factor: float) -> str: + """Round to nearest multiple of factor and return shortest decimal representation. + + This chooses the float that is closer to a multiple of the given factor while + having the shortest decimal representation (the least number of fractional decimal + digits). + + For example, given the following: + + >>> nearestMultipleShortestRepr(-0.61883544921875, 1.0/(1<<14)) + '-0.61884' + + Useful when you need to serialize or print a fixed-point number (or multiples + thereof, such as F2Dot14 fractions of 180 degrees in COLRv1 PaintRotate) in + a human-readable form. + + Args: + value (value): The value to be rounded and serialized. + factor (float): The value which the result is a close multiple of. + + Returns: + str: A compact string representation of the value. + """ + if not value: + return "0.0" + + value = otRound(value / factor) * factor + eps = .5 * factor + lo = value - eps + hi = value + eps + # If the range of valid choices spans an integer, return the integer. + if int(lo) != int(hi): + return str(float(round(value))) + + fmt = "%.8f" + lo = fmt % lo + hi = fmt % hi + assert len(lo) == len(hi) and lo != hi + for i in range(len(lo)): + if lo[i] != hi[i]: + break + period = lo.find('.') + assert period < i + fmt = "%%.%df" % (i - period) + return fmt % value |