aboutsummaryrefslogtreecommitdiff
path: root/Lib/fontTools/misc/roundTools.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/fontTools/misc/roundTools.py')
-rw-r--r--Lib/fontTools/misc/roundTools.py47
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