1"""fontTools.misc.fixedTools.py -- tools for working with fixed numbers. 2""" 3 4from __future__ import print_function, division, absolute_import 5from fontTools.misc.py23 import * 6import math 7import logging 8 9log = logging.getLogger(__name__) 10 11__all__ = [ 12 "otRound", 13 "fixedToFloat", 14 "floatToFixed", 15 "floatToFixedToFloat", 16 "ensureVersionIsLong", 17 "versionToFixed", 18] 19 20 21def otRound(value): 22 """Round float value to nearest integer towards +Infinity. 23 For fractional values of 0.5 and higher, take the next higher integer; 24 for other fractional values, truncate. 25 26 https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview 27 https://github.com/fonttools/fonttools/issues/1248#issuecomment-383198166 28 """ 29 return int(math.floor(value + 0.5)) 30 31 32def fixedToFloat(value, precisionBits): 33 """Converts a fixed-point number to a float, choosing the float 34 that has the shortest decimal reprentation. Eg. to convert a 35 fixed number in a 2.14 format, use precisionBits=14. This is 36 pretty slow compared to a simple division. Use sporadically. 37 38 precisionBits is only supported up to 16. 39 """ 40 if not value: return 0.0 41 42 scale = 1 << precisionBits 43 value /= scale 44 eps = .5 / scale 45 lo = value - eps 46 hi = value + eps 47 # If the range of valid choices spans an integer, return the integer. 48 if int(lo) != int(hi): 49 return float(round(value)) 50 fmt = "%.8f" 51 lo = fmt % lo 52 hi = fmt % hi 53 assert len(lo) == len(hi) and lo != hi 54 for i in range(len(lo)): 55 if lo[i] != hi[i]: 56 break 57 period = lo.find('.') 58 assert period < i 59 fmt = "%%.%df" % (i - period) 60 value = fmt % value 61 return float(value) 62 63def floatToFixed(value, precisionBits): 64 """Converts a float to a fixed-point number given the number of 65 precisionBits. Ie. round(value * (1<<precisionBits)). 66 """ 67 return otRound(value * (1<<precisionBits)) 68 69def floatToFixedToFloat(value, precisionBits): 70 """Converts a float to a fixed-point number given the number of 71 precisionBits, round it, then convert it back to float again. 72 Ie. round(value * (1<<precisionBits)) / (1<<precisionBits) 73 Note: this is *not* equivalent to fixedToFloat(floatToFixed(value)), 74 which would return the shortest representation of the rounded value. 75 """ 76 scale = 1<<precisionBits 77 return otRound(value * scale) / scale 78 79def ensureVersionIsLong(value): 80 """Ensure a table version is an unsigned long (unsigned short major, 81 unsigned short minor) instead of a float.""" 82 if value < 0x10000: 83 newValue = floatToFixed(value, 16) 84 log.warning( 85 "Table version value is a float: %.4f; " 86 "fix to use hex instead: 0x%08x", value, newValue) 87 value = newValue 88 return value 89 90 91def versionToFixed(value): 92 """Converts a table version to a fixed""" 93 value = int(value, 0) if value.startswith("0") else float(value) 94 value = ensureVersionIsLong(value) 95 return value 96