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

1""" 

2Various round-to-integer helpers. 

3""" 

4 

5import math 

6import functools 

7import logging 

8 

9log = logging.getLogger(__name__) 

10 

11__all__ = [ 

12 "noRound", 

13 "otRound", 

14 "maybeRound", 

15 "roundFunc", 

16] 

17 

18 

19def noRound(value): 

20 return value 

21 

22 

23def otRound(value): 

24 """Round float value to nearest integer towards ``+Infinity``. 

25 

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: 

29 

30 for fractional values of 0.5 and higher, take the next higher integer; 

31 for other fractional values, truncate. 

32 

33 This function rounds the floating-point value according to this strategy 

34 in preparation for conversion to fixed-point. 

35 

36 Args: 

37 value (float): The input floating-point value. 

38 

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)) 

45 

46 

47def maybeRound(v, tolerance, round=otRound): 

48 rounded = round(v) 

49 return rounded if abs(rounded - v) <= tolerance else v 

50 

51 

52def roundFunc(tolerance, round=otRound): 

53 if tolerance < 0: 

54 raise ValueError("Rounding tolerance must be positive") 

55 

56 if tolerance == 0: 

57 return noRound 

58 

59 if tolerance >= 0.5: 

60 return round 

61 

62 return functools.partial(maybeRound, tolerance=tolerance, round=round) 

63 

64 

65def nearestMultipleShortestRepr(value: float, factor: float) -> str: 

66 """Round to nearest multiple of factor and return shortest decimal representation. 

67 

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). 

71 

72 For example, given the following: 

73 

74 >>> nearestMultipleShortestRepr(-0.61883544921875, 1.0/(1<<14)) 

75 '-0.61884' 

76 

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. 

80 

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. 

84 

85 Returns: 

86 str: A compact string representation of the value. 

87 """ 

88 if not value: 

89 return "0.0" 

90 

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))) 

98 

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