diff --git a/Lib/fontMath/mathDict.py b/Lib/fontMath/mathDict.py new file mode 100644 index 0000000..2ba5668 --- /dev/null +++ b/Lib/fontMath/mathDict.py @@ -0,0 +1,156 @@ + +from __future__ import division, absolute_import +from collections import OrderedDict +from fontMath.mathFunctions import ( + add, addPt, div, factorAngle, mul, _roundNumber, sub, subPt, round2) + + +""" + An object that behaves like an ordered dictionary + as well as a math object. + + Notes + No anisotropic support for the factors, because we do not + know the geometric nature of the values. + + Do we need a separate MathDict and MathOrderedDict? + OrderedDict is implemented in C and should perform + just like dict. (Untested assumption, fine for now.) + https://github.com/python/cpython/issues/61195 + +""" + +class MathDict(OrderedDict): + + # mathInfo + def _processMathOneNumber(self, a, b, func): + return func(a, b) + + def _processMathOneNumberList(self, a, b, func): + if len(a) != len(b): + return None + v = [] + for index, aItem in enumerate(a): + bItem = b[index] + v.append(func(aItem, bItem)) + return v + + def _processMathTwoNumber(self, v, factor, func): + return func(v, factor) + + def _processMathTwoNumberList(self, v, factor, func): + return [func(i, factor) for i in v] + + def _processMathOne(self, copiedMathDict, otherMathDict, ptFunc, func): + for k in copiedMathDict.keys(): + a = copiedMathDict.get(k) + b = otherMathDict.get(k) + if a is not None and b is not None: + if isinstance(a, (list, tuple)): + v = self._processMathOneNumberList(a, b, func) + else: + v = self._processMathOneNumber(a, b, func) + copiedMathDict[k] = v + + def _processMathTwo(self, copiedMathDict, factor, func): + for k in copiedMathDict.keys(): + v = copiedMathDict.get(k) + if v is not None and factor is not None: + if isinstance(v, (list, tuple)): + print("_processMathTwo", k, v, factor) + v = self._processMathTwoNumberList(v, factor, func) + else: + v = self._processMathTwoNumber(v, factor, func) + copiedMathDict[k] = v + + def copy(self): + copied = MathDict(self) + return copied + + def __add__(self, otherMathDict): + assert self.keys() == otherMathDict.keys() + copiedDict = self.copy() + self._processMathOne(copiedDict, otherMathDict, addPt, add) + return copiedDict + + def __sub__(self, otherMathDict): + assert self.keys() == otherMathDict.keys() + copiedDict = self.copy() + self._processMathOne(copiedDict, otherMathDict, subPt, sub) + return copiedDict + + # math with factor + # No anisotropic processing for the factor. + # because we don't actually know if the values in MathDict + # represent any x y geometry. + def __mul__(self, factor): + copiedDict = self.copy() + self._processMathTwo(copiedDict, factor, mul) + return copiedDict + + __rmul__ = __mul__ + + def __div__(self, factor): + if factor == 0: + raise ZeroDivisionError + copiedDict = self.copy() + self._processMathTwo(copiedDict, factor, div) + return copiedDict + + __truediv__ = __div__ + + __rdiv__ = __div__ + + __rtruediv__ = __rdiv__ + + +if __name__ == "__main__": + omd = MathDict(a=1, b=2, c=3) + assert list(omd.keys()) == ['a', 'b', 'c'] # keys are odict_keys(['a', 'b', 'c']) + assert list(omd.values()) == [1, 2, 3] # values are odict_values([1, 2, 3]) + + # make sure the copy has the same content + omd2 = omd.copy() + #print('omd2', omd2) + assert omd == omd2 + + # make sure the copy was not shallow + # by changing a value in the original + # and comparing it to the copy. + omd['a'] = 100 + assert omd2['a'] != omd['a'] + + # make sure the new object has the same keys + omd3 = omd + omd2 + #print('omd3', omd3) + assert omd3.keys()==omd.keys() + assert (omd3-omd) == MathDict([('a', 1), ('b', 2), ('c', 3)]) + + # addition + omd4 = MathDict(a=-100, b=-200, c=-300) + #print('omd4', omd4) + #print('omd+omd4', omd+omd4) + assert (omd+omd4) == MathDict([('a', 0), ('b', -198), ('c', -297)]) + # subtraction + assert (omd-omd4) == MathDict([('a', 200), ('b', 202), ('c', 303)]) + assert omd.keys() == omd4.keys() + + # multiplication + omd5 = omd * 2 + #print('omd5', omd5) + assert omd5 == MathDict([('a', 200), ('b', 4), ('c', 6)]) + assert omd.keys() == omd5.keys() + + # division + omd6 = omd / 100 + #print('omd6', omd6) + assert omd6 == MathDict([('a', 1.0), ('b', 0.02), ('c', 0.03)]) + assert omd.keys() == omd6.keys() + + try: + omd7 = omd / 0 + except ZeroDivisionError: + # yes + #print('omd7 zero division error caught') + pass + diff --git a/Lib/fontMath/test/test_mathDict.py b/Lib/fontMath/test/test_mathDict.py new file mode 100644 index 0000000..1c56072 --- /dev/null +++ b/Lib/fontMath/test/test_mathDict.py @@ -0,0 +1,38 @@ +import unittest + +from fontMath.mathDict import MathDict + +class MathDictTest(unittest.TestCase): + + def test_add(self): + omd1 = MathDict(a=1, b=2, c=3) + omd2 = MathDict(a=10, b=20, c=30) + omd3 = omd1 + omd2 + self.assertEqual(omd3, MathDict([('a', 11), ('b', 22), ('c', 33)])) + + def test_sub(self): + omd1 = MathDict(a=1, b=2, c=3) + omd2 = MathDict(a=10, b=20, c=30) + omd3 = omd1 - omd2 + self.assertEqual(omd3, MathDict([('a', -9), ('b', -18), ('c', -27)])) + + def test_mul(self): + omd1 = MathDict(a=1, b=2, c=3) + omd2 = omd1 * 100 + self.assertEqual(omd2, MathDict([('a', 100), ('b', 200), ('c', 300)])) + + def test_div(self): + omd1 = MathDict(a=1, b=2, c=3) + omd2 = omd1 / 5 + self.assertEqual(omd2, MathDict([('a', 0.2), ('b', 0.4), ('c', 0.6)])) + triedZeroDivision = False + try: + omd3 = omd1 / 0 + except ZeroDivisionError: + triedZeroDivision = True + self.assertEqual(triedZeroDivision, True) + + + +if __name__ == "__main__": + unittest.main()