Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/fontTools/misc/roundTools.py: 25%
40 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:33 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:33 +0000
1"""
2Various round-to-integer helpers.
3"""
5import math
6import functools
7import logging
9log = logging.getLogger(__name__)
11__all__ = [
12 "noRound",
13 "otRound",
14 "maybeRound",
15 "roundFunc",
16]
19def noRound(value):
20 return value
23def otRound(value):
24 """Round float value to nearest integer towards ``+Infinity``.
26 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>`_)
27 defines the required method for converting floating point values to
28 fixed-point. In particular it specifies the following rounding strategy:
30 for fractional values of 0.5 and higher, take the next higher integer;
31 for other fractional values, truncate.
33 This function rounds the floating-point value according to this strategy
34 in preparation for conversion to fixed-point.
36 Args:
37 value (float): The input floating-point value.
39 Returns
40 float: The rounded value.
41 """
42 # See this thread for how we ended up with this implementation:
43 # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
44 return int(math.floor(value + 0.5))
47def maybeRound(v, tolerance, round=otRound):
48 rounded = round(v)
49 return rounded if abs(rounded - v) <= tolerance else v
52def roundFunc(tolerance, round=otRound):
53 if tolerance < 0:
54 raise ValueError("Rounding tolerance must be positive")
56 if tolerance == 0:
57 return noRound
59 if tolerance >= 0.5:
60 return round
62 return functools.partial(maybeRound, tolerance=tolerance, round=round)
65def nearestMultipleShortestRepr(value: float, factor: float) -> str:
66 """Round to nearest multiple of factor and return shortest decimal representation.
68 This chooses the float that is closer to a multiple of the given factor while
69 having the shortest decimal representation (the least number of fractional decimal
70 digits).
72 For example, given the following:
74 >>> nearestMultipleShortestRepr(-0.61883544921875, 1.0/(1<<14))
75 '-0.61884'
77 Useful when you need to serialize or print a fixed-point number (or multiples
78 thereof, such as F2Dot14 fractions of 180 degrees in COLRv1 PaintRotate) in
79 a human-readable form.
81 Args:
82 value (value): The value to be rounded and serialized.
83 factor (float): The value which the result is a close multiple of.
85 Returns:
86 str: A compact string representation of the value.
87 """
88 if not value:
89 return "0.0"
91 value = otRound(value / factor) * factor
92 eps = 0.5 * factor
93 lo = value - eps
94 hi = value + eps
95 # If the range of valid choices spans an integer, return the integer.
96 if int(lo) != int(hi):
97 return str(float(round(value)))
99 fmt = "%.8f"
100 lo = fmt % lo
101 hi = fmt % hi
102 assert len(lo) == len(hi) and lo != hi
103 for i in range(len(lo)):
104 if lo[i] != hi[i]:
105 break
106 period = lo.find(".")
107 assert period < i
108 fmt = "%%.%df" % (i - period)
109 return fmt % value