xref: /aosp_15_r20/external/fonttools/Lib/fontTools/misc/fixedTools.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1"""
2The `OpenType specification <https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types>`_
3defines two fixed-point data types:
4
5``Fixed``
6	A 32-bit signed fixed-point number with a 16 bit twos-complement
7	magnitude component and 16 fractional bits.
8``F2DOT14``
9	A 16-bit signed fixed-point number with a 2 bit twos-complement
10	magnitude component and 14 fractional bits.
11
12To support reading and writing data with these data types, this module provides
13functions for converting between fixed-point, float and string representations.
14
15.. data:: MAX_F2DOT14
16
17	The maximum value that can still fit in an F2Dot14. (1.99993896484375)
18"""
19
20from .roundTools import otRound, nearestMultipleShortestRepr
21import logging
22
23log = logging.getLogger(__name__)
24
25__all__ = [
26    "MAX_F2DOT14",
27    "fixedToFloat",
28    "floatToFixed",
29    "floatToFixedToFloat",
30    "floatToFixedToStr",
31    "fixedToStr",
32    "strToFixed",
33    "strToFixedToFloat",
34    "ensureVersionIsLong",
35    "versionToFixed",
36]
37
38
39MAX_F2DOT14 = 0x7FFF / (1 << 14)
40
41
42def fixedToFloat(value, precisionBits):
43    """Converts a fixed-point number to a float given the number of
44    precision bits.
45
46    Args:
47            value (int): Number in fixed-point format.
48            precisionBits (int): Number of precision bits.
49
50    Returns:
51            Floating point value.
52
53    Examples::
54
55            >>> import math
56            >>> f = fixedToFloat(-10139, precisionBits=14)
57            >>> math.isclose(f, -0.61883544921875)
58            True
59    """
60    return value / (1 << precisionBits)
61
62
63def floatToFixed(value, precisionBits):
64    """Converts a float to a fixed-point number given the number of
65    precision bits.
66
67    Args:
68            value (float): Floating point value.
69            precisionBits (int): Number of precision bits.
70
71    Returns:
72            int: Fixed-point representation.
73
74    Examples::
75
76            >>> floatToFixed(-0.61883544921875, precisionBits=14)
77            -10139
78            >>> floatToFixed(-0.61884, precisionBits=14)
79            -10139
80    """
81    return otRound(value * (1 << precisionBits))
82
83
84def floatToFixedToFloat(value, precisionBits):
85    """Converts a float to a fixed-point number and back again.
86
87    By converting the float to fixed, rounding it, and converting it back
88    to float again, this returns a floating point values which is exactly
89    representable in fixed-point format.
90
91    Note: this **is** equivalent to ``fixedToFloat(floatToFixed(value))``.
92
93    Args:
94            value (float): The input floating point value.
95            precisionBits (int): Number of precision bits.
96
97    Returns:
98            float: The transformed and rounded value.
99
100    Examples::
101            >>> import math
102            >>> f1 = -0.61884
103            >>> f2 = floatToFixedToFloat(-0.61884, precisionBits=14)
104            >>> f1 != f2
105            True
106            >>> math.isclose(f2, -0.61883544921875)
107            True
108    """
109    scale = 1 << precisionBits
110    return otRound(value * scale) / scale
111
112
113def fixedToStr(value, precisionBits):
114    """Converts a fixed-point number to a string representing a decimal float.
115
116    This chooses the float that has the shortest decimal representation (the least
117    number of fractional decimal digits).
118
119    For example, to convert a fixed-point number in a 2.14 format, use
120    ``precisionBits=14``::
121
122            >>> fixedToStr(-10139, precisionBits=14)
123            '-0.61884'
124
125    This is pretty slow compared to the simple division used in ``fixedToFloat``.
126    Use sporadically when you need to serialize or print the fixed-point number in
127    a human-readable form.
128    It uses nearestMultipleShortestRepr under the hood.
129
130    Args:
131            value (int): The fixed-point value to convert.
132            precisionBits (int): Number of precision bits, *up to a maximum of 16*.
133
134    Returns:
135            str: A string representation of the value.
136    """
137    scale = 1 << precisionBits
138    return nearestMultipleShortestRepr(value / scale, factor=1.0 / scale)
139
140
141def strToFixed(string, precisionBits):
142    """Converts a string representing a decimal float to a fixed-point number.
143
144    Args:
145            string (str): A string representing a decimal float.
146            precisionBits (int): Number of precision bits, *up to a maximum of 16*.
147
148    Returns:
149            int: Fixed-point representation.
150
151    Examples::
152
153            >>> ## to convert a float string to a 2.14 fixed-point number:
154            >>> strToFixed('-0.61884', precisionBits=14)
155            -10139
156    """
157    value = float(string)
158    return otRound(value * (1 << precisionBits))
159
160
161def strToFixedToFloat(string, precisionBits):
162    """Convert a string to a decimal float with fixed-point rounding.
163
164    This first converts string to a float, then turns it into a fixed-point
165    number with ``precisionBits`` fractional binary digits, then back to a
166    float again.
167
168    This is simply a shorthand for fixedToFloat(floatToFixed(float(s))).
169
170    Args:
171            string (str): A string representing a decimal float.
172            precisionBits (int): Number of precision bits.
173
174    Returns:
175            float: The transformed and rounded value.
176
177    Examples::
178
179            >>> import math
180            >>> s = '-0.61884'
181            >>> bits = 14
182            >>> f = strToFixedToFloat(s, precisionBits=bits)
183            >>> math.isclose(f, -0.61883544921875)
184            True
185            >>> f == fixedToFloat(floatToFixed(float(s), precisionBits=bits), precisionBits=bits)
186            True
187    """
188    value = float(string)
189    scale = 1 << precisionBits
190    return otRound(value * scale) / scale
191
192
193def floatToFixedToStr(value, precisionBits):
194    """Convert float to string with fixed-point rounding.
195
196    This uses the shortest decimal representation (ie. the least
197    number of fractional decimal digits) to represent the equivalent
198    fixed-point number with ``precisionBits`` fractional binary digits.
199    It uses nearestMultipleShortestRepr under the hood.
200
201    >>> floatToFixedToStr(-0.61883544921875, precisionBits=14)
202    '-0.61884'
203
204    Args:
205            value (float): The float value to convert.
206            precisionBits (int): Number of precision bits, *up to a maximum of 16*.
207
208    Returns:
209            str: A string representation of the value.
210
211    """
212    scale = 1 << precisionBits
213    return nearestMultipleShortestRepr(value, factor=1.0 / scale)
214
215
216def ensureVersionIsLong(value):
217    """Ensure a table version is an unsigned long.
218
219    OpenType table version numbers are expressed as a single unsigned long
220    comprising of an unsigned short major version and unsigned short minor
221    version. This function detects if the value to be used as a version number
222    looks too small (i.e. is less than ``0x10000``), and converts it to
223    fixed-point using :func:`floatToFixed` if so.
224
225    Args:
226            value (Number): a candidate table version number.
227
228    Returns:
229            int: A table version number, possibly corrected to fixed-point.
230    """
231    if value < 0x10000:
232        newValue = floatToFixed(value, 16)
233        log.warning(
234            "Table version value is a float: %.4f; " "fix to use hex instead: 0x%08x",
235            value,
236            newValue,
237        )
238        value = newValue
239    return value
240
241
242def versionToFixed(value):
243    """Ensure a table version number is fixed-point.
244
245    Args:
246            value (str): a candidate table version number.
247
248    Returns:
249            int: A table version number, possibly corrected to fixed-point.
250    """
251    value = int(value, 0) if value.startswith("0") else float(value)
252    value = ensureVersionIsLong(value)
253    return value
254