xref: /aosp_15_r20/external/fonttools/Lib/fontTools/misc/roundTools.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes"""
2*e1fe3e4aSElliott HughesVarious round-to-integer helpers.
3*e1fe3e4aSElliott Hughes"""
4*e1fe3e4aSElliott Hughes
5*e1fe3e4aSElliott Hughesimport math
6*e1fe3e4aSElliott Hughesimport functools
7*e1fe3e4aSElliott Hughesimport logging
8*e1fe3e4aSElliott Hughes
9*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__)
10*e1fe3e4aSElliott Hughes
11*e1fe3e4aSElliott Hughes__all__ = [
12*e1fe3e4aSElliott Hughes    "noRound",
13*e1fe3e4aSElliott Hughes    "otRound",
14*e1fe3e4aSElliott Hughes    "maybeRound",
15*e1fe3e4aSElliott Hughes    "roundFunc",
16*e1fe3e4aSElliott Hughes    "nearestMultipleShortestRepr",
17*e1fe3e4aSElliott Hughes]
18*e1fe3e4aSElliott Hughes
19*e1fe3e4aSElliott Hughes
20*e1fe3e4aSElliott Hughesdef noRound(value):
21*e1fe3e4aSElliott Hughes    return value
22*e1fe3e4aSElliott Hughes
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott Hughesdef otRound(value):
25*e1fe3e4aSElliott Hughes    """Round float value to nearest integer towards ``+Infinity``.
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughes    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>`_)
28*e1fe3e4aSElliott Hughes    defines the required method for converting floating point values to
29*e1fe3e4aSElliott Hughes    fixed-point. In particular it specifies the following rounding strategy:
30*e1fe3e4aSElliott Hughes
31*e1fe3e4aSElliott Hughes            for fractional values of 0.5 and higher, take the next higher integer;
32*e1fe3e4aSElliott Hughes            for other fractional values, truncate.
33*e1fe3e4aSElliott Hughes
34*e1fe3e4aSElliott Hughes    This function rounds the floating-point value according to this strategy
35*e1fe3e4aSElliott Hughes    in preparation for conversion to fixed-point.
36*e1fe3e4aSElliott Hughes
37*e1fe3e4aSElliott Hughes    Args:
38*e1fe3e4aSElliott Hughes            value (float): The input floating-point value.
39*e1fe3e4aSElliott Hughes
40*e1fe3e4aSElliott Hughes    Returns
41*e1fe3e4aSElliott Hughes            float: The rounded value.
42*e1fe3e4aSElliott Hughes    """
43*e1fe3e4aSElliott Hughes    # See this thread for how we ended up with this implementation:
44*e1fe3e4aSElliott Hughes    # https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166
45*e1fe3e4aSElliott Hughes    return int(math.floor(value + 0.5))
46*e1fe3e4aSElliott Hughes
47*e1fe3e4aSElliott Hughes
48*e1fe3e4aSElliott Hughesdef maybeRound(v, tolerance, round=otRound):
49*e1fe3e4aSElliott Hughes    rounded = round(v)
50*e1fe3e4aSElliott Hughes    return rounded if abs(rounded - v) <= tolerance else v
51*e1fe3e4aSElliott Hughes
52*e1fe3e4aSElliott Hughes
53*e1fe3e4aSElliott Hughesdef roundFunc(tolerance, round=otRound):
54*e1fe3e4aSElliott Hughes    if tolerance < 0:
55*e1fe3e4aSElliott Hughes        raise ValueError("Rounding tolerance must be positive")
56*e1fe3e4aSElliott Hughes
57*e1fe3e4aSElliott Hughes    if tolerance == 0:
58*e1fe3e4aSElliott Hughes        return noRound
59*e1fe3e4aSElliott Hughes
60*e1fe3e4aSElliott Hughes    if tolerance >= 0.5:
61*e1fe3e4aSElliott Hughes        return round
62*e1fe3e4aSElliott Hughes
63*e1fe3e4aSElliott Hughes    return functools.partial(maybeRound, tolerance=tolerance, round=round)
64*e1fe3e4aSElliott Hughes
65*e1fe3e4aSElliott Hughes
66*e1fe3e4aSElliott Hughesdef nearestMultipleShortestRepr(value: float, factor: float) -> str:
67*e1fe3e4aSElliott Hughes    """Round to nearest multiple of factor and return shortest decimal representation.
68*e1fe3e4aSElliott Hughes
69*e1fe3e4aSElliott Hughes    This chooses the float that is closer to a multiple of the given factor while
70*e1fe3e4aSElliott Hughes    having the shortest decimal representation (the least number of fractional decimal
71*e1fe3e4aSElliott Hughes    digits).
72*e1fe3e4aSElliott Hughes
73*e1fe3e4aSElliott Hughes    For example, given the following:
74*e1fe3e4aSElliott Hughes
75*e1fe3e4aSElliott Hughes    >>> nearestMultipleShortestRepr(-0.61883544921875, 1.0/(1<<14))
76*e1fe3e4aSElliott Hughes    '-0.61884'
77*e1fe3e4aSElliott Hughes
78*e1fe3e4aSElliott Hughes    Useful when you need to serialize or print a fixed-point number (or multiples
79*e1fe3e4aSElliott Hughes    thereof, such as F2Dot14 fractions of 180 degrees in COLRv1 PaintRotate) in
80*e1fe3e4aSElliott Hughes    a human-readable form.
81*e1fe3e4aSElliott Hughes
82*e1fe3e4aSElliott Hughes    Args:
83*e1fe3e4aSElliott Hughes        value (value): The value to be rounded and serialized.
84*e1fe3e4aSElliott Hughes        factor (float): The value which the result is a close multiple of.
85*e1fe3e4aSElliott Hughes
86*e1fe3e4aSElliott Hughes    Returns:
87*e1fe3e4aSElliott Hughes        str: A compact string representation of the value.
88*e1fe3e4aSElliott Hughes    """
89*e1fe3e4aSElliott Hughes    if not value:
90*e1fe3e4aSElliott Hughes        return "0.0"
91*e1fe3e4aSElliott Hughes
92*e1fe3e4aSElliott Hughes    value = otRound(value / factor) * factor
93*e1fe3e4aSElliott Hughes    eps = 0.5 * factor
94*e1fe3e4aSElliott Hughes    lo = value - eps
95*e1fe3e4aSElliott Hughes    hi = value + eps
96*e1fe3e4aSElliott Hughes    # If the range of valid choices spans an integer, return the integer.
97*e1fe3e4aSElliott Hughes    if int(lo) != int(hi):
98*e1fe3e4aSElliott Hughes        return str(float(round(value)))
99*e1fe3e4aSElliott Hughes
100*e1fe3e4aSElliott Hughes    fmt = "%.8f"
101*e1fe3e4aSElliott Hughes    lo = fmt % lo
102*e1fe3e4aSElliott Hughes    hi = fmt % hi
103*e1fe3e4aSElliott Hughes    assert len(lo) == len(hi) and lo != hi
104*e1fe3e4aSElliott Hughes    for i in range(len(lo)):
105*e1fe3e4aSElliott Hughes        if lo[i] != hi[i]:
106*e1fe3e4aSElliott Hughes            break
107*e1fe3e4aSElliott Hughes    period = lo.find(".")
108*e1fe3e4aSElliott Hughes    assert period < i
109*e1fe3e4aSElliott Hughes    fmt = "%%.%df" % (i - period)
110*e1fe3e4aSElliott Hughes    return fmt % value
111