pax_global_header00006660000000000000000000000064135672043040014516gustar00rootroot0000000000000052 comment=ff3b1585bb79f7a91c5b60fb712e95dd5ebb0a66 fontMath-0.5.2/000077500000000000000000000000001356720430400133025ustar00rootroot00000000000000fontMath-0.5.2/.coveragerc000066400000000000000000000013301356720430400154200ustar00rootroot00000000000000[run] # measure 'branch' coverage in addition to 'statement' coverage # See: http://coverage.readthedocs.org/en/coverage-4.0.3/branch.html#branch branch = True # list of directories to measure source = Lib/fontMath [report] # Regexes for lines to exclude from consideration exclude_lines = # keywords to use in inline comments to skip coverage pragma: no cover # don't complain if tests don't hit defensive assertion code raise AssertionError raise NotImplementedError # don't complain if non-runnable code isn't run if 0: if __name__ == .__main__.: # ignore source code that can’t be found ignore_errors = True # when running a summary report, show missing lines show_missing = True fontMath-0.5.2/.gitignore000066400000000000000000000003751356720430400152770ustar00rootroot00000000000000# Byte-compiled and optimized files __pycache__/ *.py[co] *$py.class # Distribution / Packaging *.egg-info *.eggs MANIFEST build/ dist/ # Unit test and coverage files .cache/ .coverage .coverage.* .tox/ htmlcov/ # OS X Finder .DS_Store .pytest_cache fontMath-0.5.2/.pyup.yml000066400000000000000000000002501356720430400150750ustar00rootroot00000000000000# controls the frequency of updates (undocumented beta feature) schedule: every week # do not pin dependencies unless they have explicit version specifiers pin: False fontMath-0.5.2/.travis.yml000066400000000000000000000024731356720430400154210ustar00rootroot00000000000000language: python env: global: - TWINE_USERNAME="anthrotype" - secure: vRHkHnV3UaKJQ9l/TwQrbngF06ij9c3HhEjryhg5WCHgWLKBgwxGpX++m3pmzG32rBM7eJwtDH2m9liuXWueuh1Ae81sQzRvcOSHOJRUAWvTMXQAeTGsC6Y6OMsbiMT1OCJxuapAPPbNepVTSVu9TjbeJtabQ/yfCWZzoF4ZhnG6DNgrIPwLqVf0BUQ1zoOCHtQog9ZkcYSb5vrSnx/Ig4z4mLVvygQ6ZQElDJH+x/F508pTR8xeQH/1+BxDVctvkQDbhbFaHbNHcb2nuLLYpScFk9eJiuSHw2EZaqS5W2q9tqdGb86Fjf5fjuxT3i7+EXLI3bnVzKQsqE43nWk9p8in28jcnQDFH8oEJf4o5h3q0CEVigHn4RPGv0Lks4ESbWC7MbRS3CvpXztio+NGXKX7w66cw9E2lVhVypVKtuiUiYk9+jzXMCBUoN0IdHoWj44nt9lYqkguAU//068GZM/5iu8TJyMwssi6bYj3KLQgYSC/2etDGtLtvyMShI6uuqlVEXHWhC85IDmVZmsp/zWrQlNzabSq23gCpfXqPwlJIOSzZva+FE+H7D0oEPRwUxBNsBHa6TiMtD+TxLJM4Tsx/d1NO2xOLLyDY5j0Mqn+pIzPYDGm7vQDw/JxFVpAX2XopYUEuplsHK/FiDhrm8JgwBSzSkoiWO88++MkRks= matrix: include: - python: 3.6 env: - BUILD_DIST=true - TOXENV=coverage - python: 3.7 dist: xenial install: - pip install tox-travis script: - tox after_success: # if it's a tagged commit, upload distribution packages to pypi - | if [ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_REPO_SLUG" == "robotools/fontMath" ] && [ "$BUILD_DIST" == true ]; then pip install --upgrade twine pip setuptools wheel python setup.py sdist pip wheel --no-deps --wheel-dir dist . twine upload dist/*.whl dist/*.zip fi fontMath-0.5.2/Lib/000077500000000000000000000000001356720430400140105ustar00rootroot00000000000000fontMath-0.5.2/Lib/fontMath/000077500000000000000000000000001356720430400155705ustar00rootroot00000000000000fontMath-0.5.2/Lib/fontMath/__init__.py000066400000000000000000000005131356720430400177000ustar00rootroot00000000000000""" A set of fast glyph and font objects for math operations. This was inspired, and is more or less a clone, of Erik van Blokland's brilliant glyph math in RoboFab. """ from fontMath.mathGlyph import MathGlyph from fontMath.mathInfo import MathInfo from fontMath.mathKerning import MathKerning version = __version__ = "0.5.2" fontMath-0.5.2/Lib/fontMath/mathFunctions.py000066400000000000000000000035631356720430400207730ustar00rootroot00000000000000from __future__ import division import math from fontTools.misc.py23 import round3 import sys __all__ = [ "add", "addPt", "sub", "subPt", "mul", "mulPt", "div", "divPt", "factorAngle", "_roundNumber", ] def add(v1, v2): return v1 + v2 def addPt(pt1, pt2): return pt1[0] + pt2[0], pt1[1] + pt2[1] def sub(v1, v2): return v1 - v2 def subPt(pt1, pt2): return pt1[0] - pt2[0], pt1[1] - pt2[1] def mul(v, f): return v * f def mulPt(pt1, f): (f1, f2) = f return pt1[0] * f1, pt1[1] * f2 def div(v, f): return v / f def divPt(pt, f): (f1, f2) = f return pt[0] / f1, pt[1] / f2 def factorAngle(angle, f, func): (f1, f2) = f rangle = math.radians(angle) x = math.cos(rangle) y = math.sin(rangle) return math.degrees( math.atan2( func(y, f2), func(x, f1) ) ) def setRoundIntegerFunction(func): """ Globally set function for rounding floats to integers. The function signature must be: def func(value: float) -> int """ global _ROUND_INTEGER_FUNC _ROUND_INTEGER_FUNC = func def setRoundFloatFunction(func): """ Globally set function for rounding floats within given precision. The function signature must be: def func(value: float, ndigits: int) -> float """ global _ROUND_FLOAT_FUNC _ROUND_FLOAT_FUNC = func _ROUND_INTEGER_FUNC = round3 _ROUND_FLOAT_FUNC = round3 def _roundNumber(value, ndigits=None): """Round number using the Python 3 built-in round function. You can change the default rounding functions using setRoundIntegerFunction and/or setRoundFloatFunction. """ if ndigits is not None: return _ROUND_FLOAT_FUNC(value, ndigits) return _ROUND_INTEGER_FUNC(value) if __name__ == "__main__": import sys import doctest sys.exit(doctest.testmod().failed) fontMath-0.5.2/Lib/fontMath/mathGlyph.py000066400000000000000000000706151356720430400201100ustar00rootroot00000000000000from __future__ import print_function, absolute_import from copy import deepcopy from collections import OrderedDict from fontMath.mathFunctions import ( add, addPt, div, divPt, mul, mulPt, _roundNumber, sub, subPt) from fontMath.mathGuideline import ( _compressGuideline, _expandGuideline, _pairGuidelines, _processMathOneGuidelines, _processMathTwoGuidelines, _roundGuidelines) from fontTools.pens.pointPen import AbstractPointPen # ------------------ # UFO 3 branch notes # ------------------ # # to do: # X components # X identifiers # X contours # X identifiers # X points # X identifiers # X guidelines # X height # X image # # - is there any cruft that can be removed? # X why is divPt here? move all of those to the math functions # - FilterRedundantPointPen._flushContour is a mess # X for the pt math functions, always send (x, y) factors instead # of coercing within the function. the coercion can happen at # the beginning of the _processMathTwo method. # - try list comprehensions in the point math for speed # # Questionable stuff: # X is getRef needed? # X nothing is ever set to _structure. should it be? # X should the compatibilty be a function or pen? # X the lib import is shallow and modifications to # lower level objects (ie dict) could modify the # original object. this probably isn't desirable. # deepcopy won't work here since it will try to # maintain the original class. may need to write # a custom copier. or maybe something like this # would be sufficient: # self.lib = deepcopy(dict(glyph.lib)) # the class would be maintained for everything but # the top level. that shouldn't matter for the # purposes here. # - __cmp__ is dubious but harmless i suppose. # X is generationCount needed? # X can box become bounds? have both? try: basestring, xrange range = xrange except NameError: basestring = str class MathGlyph(object): """ A very shallow glyph object for rapid math operations. Notes about glyph math: - absolute contour compatibility is required - absolute component, anchor, guideline and image compatibility is NOT required. in cases of incompatibility in this data, only compatible data is processed and returned. becuase of this, anchors and components may not be returned in the same order as the original. """ def __init__(self, glyph): self.contours = [] self.components = [] if glyph is None: self.anchors = [] self.guidelines = [] self.image = _expandImage(None) self.lib = {} self.name = None self.unicodes = None self.width = None self.height = None self.note = None else: p = MathGlyphPen(self) glyph.drawPoints(p) self.anchors = [dict(anchor) for anchor in glyph.anchors] self.guidelines = [_expandGuideline(guideline) for guideline in glyph.guidelines] self.image = _expandImage(glyph.image) self.lib = deepcopy(dict(glyph.lib)) self.name = glyph.name self.unicodes = list(glyph.unicodes) self.width = glyph.width self.height = glyph.height self.note = glyph.note def __eq__(self, other): try: return all(getattr(self, attr) == getattr(other, attr) for attr in ("name", "unicodes", "width", "height", "note", "lib", "contours", "components", "anchors", "guidelines", "image")) except AttributeError: return NotImplemented def __ne__(self, other): return not self == other # ---- # Copy # ---- def copy(self): """return a new MathGlyph containing all data in self""" return MathGlyph(self) def copyWithoutMathSubObjects(self): """ return a new MathGlyph containing all data except: contours components anchors guidelines this is used mainly for internal glyph math. """ n = MathGlyph(None) n.name = self.name if self.unicodes is not None: n.unicodes = list(self.unicodes) n.width = self.width n.height = self.height n.note = self.note n.lib = deepcopy(dict(self.lib)) return n # ---- # Math # ---- # math with other glyph def __add__(self, otherGlyph): copiedGlyph = self.copyWithoutMathSubObjects() self._processMathOne(copiedGlyph, otherGlyph, addPt, add) return copiedGlyph def __sub__(self, otherGlyph): copiedGlyph = self.copyWithoutMathSubObjects() self._processMathOne(copiedGlyph, otherGlyph, subPt, sub) return copiedGlyph def _processMathOne(self, copiedGlyph, otherGlyph, ptFunc, func): # width copiedGlyph.width = func(self.width, otherGlyph.width) # height copiedGlyph.height = func(self.height, otherGlyph.height) # contours copiedGlyph.contours = [] if self.contours: copiedGlyph.contours = _processMathOneContours(self.contours, otherGlyph.contours, ptFunc) # components copiedGlyph.components = [] if self.components: componentPairs = _pairComponents(self.components, otherGlyph.components) copiedGlyph.components = _processMathOneComponents(componentPairs, ptFunc) # anchors copiedGlyph.anchors = [] if self.anchors: anchorTree1 = _anchorTree(self.anchors) anchorTree2 = _anchorTree(otherGlyph.anchors) anchorPairs = _pairAnchors(anchorTree1, anchorTree2) copiedGlyph.anchors = _processMathOneAnchors(anchorPairs, ptFunc) # guidelines copiedGlyph.guidelines = [] if self.guidelines: guidelinePairs = _pairGuidelines(self.guidelines, otherGlyph.guidelines) copiedGlyph.guidelines = _processMathOneGuidelines(guidelinePairs, ptFunc, func) # image copiedGlyph.image = _expandImage(None) imagePair = _pairImages(self.image, otherGlyph.image) if imagePair: copiedGlyph.image = _processMathOneImage(imagePair, ptFunc) # math with factor def __mul__(self, factor): if not isinstance(factor, tuple): factor = (factor, factor) copiedGlyph = self.copyWithoutMathSubObjects() self._processMathTwo(copiedGlyph, factor, mulPt, mul) return copiedGlyph __rmul__ = __mul__ def __div__(self, factor): if not isinstance(factor, tuple): factor = (factor, factor) copiedGlyph = self.copyWithoutMathSubObjects() self._processMathTwo(copiedGlyph, factor, divPt, div) return copiedGlyph __truediv__ = __div__ __rdiv__ = __div__ __rtruediv__ = __rdiv__ def _processMathTwo(self, copiedGlyph, factor, ptFunc, func): # width copiedGlyph.width = func(self.width, factor[0]) # height copiedGlyph.height = func(self.height, factor[1]) # contours copiedGlyph.contours = [] if self.contours: copiedGlyph.contours = _processMathTwoContours(self.contours, factor, ptFunc) # components copiedGlyph.components = [] if self.components: copiedGlyph.components = _processMathTwoComponents(self.components, factor, ptFunc) # anchors copiedGlyph.anchors = [] if self.anchors: copiedGlyph.anchors = _processMathTwoAnchors(self.anchors, factor, ptFunc) # guidelines copiedGlyph.guidelines = [] if self.guidelines: copiedGlyph.guidelines = _processMathTwoGuidelines(self.guidelines, factor, func) # image if self.image: copiedGlyph.image = _processMathTwoImage(self.image, factor, ptFunc) # ------- # Additional math # ------- def round(self, digits=None): """round the geometry.""" copiedGlyph = self.copyWithoutMathSubObjects() # misc copiedGlyph.width = _roundNumber(self.width, digits) copiedGlyph.height = _roundNumber(self.height, digits) # contours copiedGlyph.contours = [] if self.contours: copiedGlyph.contours = _roundContours(self.contours, digits) # components copiedGlyph.components = [] if self.components: copiedGlyph.components = _roundComponents(self.components, digits) # guidelines copiedGlyph.guidelines = [] if self.guidelines: copiedGlyph.guidelines = _roundGuidelines(self.guidelines, digits) # anchors copiedGlyph.anchors = [] if self.anchors: copiedGlyph.anchors = _roundAnchors(self.anchors, digits) # image copiedGlyph.image = None if self.image: copiedGlyph.image = _roundImage(self.image, digits) return copiedGlyph # ------- # Pen API # ------- def getPointPen(self): """get a point pen for drawing to this object""" return MathGlyphPen(self) def drawPoints(self, pointPen, filterRedundantPoints=False): """draw self using pointPen""" if filterRedundantPoints: pointPen = FilterRedundantPointPen(pointPen) for contour in self.contours: pointPen.beginPath(identifier=contour["identifier"]) for segmentType, pt, smooth, name, identifier in contour["points"]: pointPen.addPoint(pt=pt, segmentType=segmentType, smooth=smooth, name=name, identifier=identifier) pointPen.endPath() for component in self.components: pointPen.addComponent(component["baseGlyph"], component["transformation"], identifier=component["identifier"]) def draw(self, pen, filterRedundantPoints=False): """draw self using pen""" from fontTools.pens.pointPen import PointToSegmentPen pointPen = PointToSegmentPen(pen) self.drawPoints(pointPen, filterRedundantPoints=filterRedundantPoints) # ---------- # Extraction # ---------- def extractGlyph(self, glyph, pointPen=None, onlyGeometry=False): """ "rehydrate" to a glyph. this requires a glyph as an argument. if a point pen other than the type of pen returned by glyph.getPointPen() is required for drawing, send this the needed point pen. """ if pointPen is None: pointPen = glyph.getPointPen() glyph.clearContours() glyph.clearComponents() glyph.clearAnchors() glyph.clearGuidelines() glyph.lib.clear() cleanerPen = FilterRedundantPointPen(pointPen) self.drawPoints(cleanerPen) glyph.anchors = [dict(anchor) for anchor in self.anchors] glyph.guidelines = [_compressGuideline(guideline) for guideline in self.guidelines] glyph.image = _compressImage(self.image) glyph.lib = deepcopy(dict(self.lib)) glyph.width = self.width glyph.height = self.height glyph.note = self.note if not onlyGeometry: glyph.name = self.name glyph.unicodes = list(self.unicodes) return glyph # ---------- # Point Pens # ---------- class MathGlyphPen(AbstractPointPen): """ Point pen for building MathGlyph data structures. """ def __init__(self, glyph=None): if glyph is None: self.contours = [] self.components = [] else: self.contours = glyph.contours self.components = glyph.components self._contourIdentifier = None self._points = [] def _flushContour(self): """ This normalizes the contour so that: - there are no line segments. in their place will be curve segments with the off curves positioned on top of the previous on curve and the new curve on curve. - the contour starts with an on curve """ self.contours.append( dict(identifier=self._contourIdentifier, points=[]) ) contourPoints = self.contours[-1]["points"] points = self._points # move offcurves at the beginning of the contour to the end haveOnCurve = False for point in points: if point[0] is not None: haveOnCurve = True break if haveOnCurve: while 1: if points[0][0] is None: point = points.pop(0) points.append(point) else: break # convert lines to curves holdingOffCurves = [] for index, point in enumerate(points): segmentType = point[0] if segmentType == "line": pt, smooth, name, identifier = point[1:] prevPt = points[index - 1][1] if index == 0: holdingOffCurves.append((None, prevPt, False, None, None)) holdingOffCurves.append((None, pt, False, None, None)) else: contourPoints.append((None, prevPt, False, None, None)) contourPoints.append((None, pt, False, None, None)) contourPoints.append(("curve", pt, smooth, name, identifier)) else: contourPoints.append(point) contourPoints.extend(holdingOffCurves) def beginPath(self, identifier=None): self._contourIdentifier = identifier self._points = [] def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): self._points.append((segmentType, pt, smooth, name, identifier)) def endPath(self): self._flushContour() def addComponent(self, baseGlyph, transformation, identifier=None, **kwargs): self.components.append(dict(baseGlyph=baseGlyph, transformation=transformation, identifier=identifier)) class FilterRedundantPointPen(AbstractPointPen): def __init__(self, anotherPointPen): self._pen = anotherPointPen self._points = [] def _flushContour(self): points = self._points prevOnCurve = None offCurves = [] pointsToDraw = [] # deal with the first point pt, segmentType, smooth, name, identifier = points[0] # if it is an offcurve, add it to the offcurve list if segmentType is None: offCurves.append((pt, segmentType, smooth, name, identifier)) else: # potential redundancy if segmentType == "curve": # gather preceding off curves testOffCurves = [] lastPoint = None for i in range(len(points)): i = -i - 1 testPoint = points[i] testSegmentType = testPoint[1] if testSegmentType is not None: lastPoint = testPoint[0] break testOffCurves.append(testPoint[0]) # if two offcurves exist we can test for redundancy if len(testOffCurves) == 2: if testOffCurves[1] == lastPoint and testOffCurves[0] == pt: segmentType = "line" # remove the last two points points = points[:-2] # add the point to the contour pointsToDraw.append((pt, segmentType, smooth, name, identifier)) prevOnCurve = pt for pt, segmentType, smooth, name, identifier in points[1:]: # store offcurves if segmentType is None: offCurves.append((pt, segmentType, smooth, name, identifier)) continue # curves are a potential redundancy elif segmentType == "curve": if len(offCurves) == 2: # test for redundancy if offCurves[0][0] == prevOnCurve and offCurves[1][0] == pt: offCurves = [] segmentType = "line" # add all offcurves for offCurve in offCurves: pointsToDraw.append(offCurve) # add the on curve pointsToDraw.append((pt, segmentType, smooth, name, identifier)) # reset the stored data prevOnCurve = pt offCurves = [] # catch any remaining offcurves if len(offCurves) != 0: for offCurve in offCurves: pointsToDraw.append(offCurve) # draw to the pen for pt, segmentType, smooth, name, identifier in pointsToDraw: self._pen.addPoint(pt, segmentType, smooth=smooth, name=name, identifier=identifier) def beginPath(self, identifier=None, **kwargs): self._points = [] self._pen.beginPath(identifier=identifier) def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): self._points.append((pt, segmentType, smooth, name, identifier)) def endPath(self): self._flushContour() self._pen.endPath() def addComponent(self, baseGlyph, transformation, identifier=None, **kwargs): self._pen.addComponent(baseGlyph, transformation, identifier) # ------- # Support # ------- # contours def _processMathOneContours(contours1, contours2, func): result = [] for index, contour1 in enumerate(contours1): contourIdentifier = contour1["identifier"] points1 = contour1["points"] points2 = contours2[index]["points"] resultPoints = [] for index, point in enumerate(points1): segmentType, pt1, smooth, name, identifier = point pt2 = points2[index][1] pt = func(pt1, pt2) resultPoints.append((segmentType, pt, smooth, name, identifier)) result.append(dict(identifier=contourIdentifier, points=resultPoints)) return result def _processMathTwoContours(contours, factor, func): result = [] for contour in contours: contourIdentifier = contour["identifier"] points = contour["points"] resultPoints = [] for point in points: segmentType, pt, smooth, name, identifier = point pt = func(pt, factor) resultPoints.append((segmentType, pt, smooth, name, identifier)) result.append(dict(identifier=contourIdentifier, points=resultPoints)) return result # anchors def _anchorTree(anchors): tree = OrderedDict() for anchor in anchors: x = anchor["x"] y = anchor["y"] name = anchor.get("name") identifier = anchor.get("identifier") color = anchor.get("color") if name not in tree: tree[name] = [] tree[name].append((identifier, x, y, color)) return tree def _pairAnchors(anchorDict1, anchorDict2): """ Anchors are paired using the following rules: Matching Identifiers -------------------- >>> anchors1 = { ... "test" : [ ... (None, 1, 2, None), ... ("identifier 1", 3, 4, None) ... ] ... } >>> anchors2 = { ... "test" : [ ... ("identifier 1", 1, 2, None), ... (None, 3, 4, None) ... ] ... } >>> expected = [ ... ( ... dict(name="test", identifier=None, x=1, y=2, color=None), ... dict(name="test", identifier=None, x=3, y=4, color=None) ... ), ... ( ... dict(name="test", identifier="identifier 1", x=3, y=4, color=None), ... dict(name="test", identifier="identifier 1", x=1, y=2, color=None) ... ) ... ] >>> _pairAnchors(anchors1, anchors2) == expected True Mismatched Identifiers ---------------------- >>> anchors1 = { ... "test" : [ ... ("identifier 1", 3, 4, None) ... ] ... } >>> anchors2 = { ... "test" : [ ... ("identifier 2", 1, 2, None), ... ] ... } >>> expected = [ ... ( ... dict(name="test", identifier="identifier 1", x=3, y=4, color=None), ... dict(name="test", identifier="identifier 2", x=1, y=2, color=None) ... ) ... ] >>> _pairAnchors(anchors1, anchors2) == expected True """ pairs = [] for name, anchors1 in anchorDict1.items(): if name not in anchorDict2: continue anchors2 = anchorDict2[name] # align with matching identifiers removeFromAnchors1 = [] for anchor1 in anchors1: match = None identifier = anchor1[0] for anchor2 in anchors2: if anchor2[0] == identifier: match = anchor2 break if match is not None: anchor2 = match anchors2.remove(anchor2) removeFromAnchors1.append(anchor1) a1 = dict(name=name, identifier=identifier) a1["x"], a1["y"], a1["color"] = anchor1[1:] a2 = dict(name=name, identifier=identifier) a2["x"], a2["y"], a2["color"] = anchor2[1:] pairs.append((a1, a2)) for anchor1 in removeFromAnchors1: anchors1.remove(anchor1) if not anchors1 or not anchors2: continue # align by index while 1: anchor1 = anchors1.pop(0) anchor2 = anchors2.pop(0) a1 = dict(name=name) a1["identifier"], a1["x"], a1["y"], a1["color"] = anchor1 a2 = dict(name=name, identifier=identifier) a2["identifier"], a2["x"], a2["y"], a2["color"] = anchor2 pairs.append((a1, a2)) if not anchors1: break if not anchors2: break return pairs def _processMathOneAnchors(anchorPairs, func): result = [] for anchor1, anchor2 in anchorPairs: anchor = dict(anchor1) pt1 = (anchor1["x"], anchor1["y"]) pt2 = (anchor2["x"], anchor2["y"]) anchor["x"], anchor["y"] = func(pt1, pt2) result.append(anchor) return result def _processMathTwoAnchors(anchors, factor, func): result = [] for anchor in anchors: anchor = dict(anchor) pt = (anchor["x"], anchor["y"]) anchor["x"], anchor["y"] = func(pt, factor) result.append(anchor) return result # components def _pairComponents(components1, components2): components1 = list(components1) components2 = list(components2) pairs = [] # align with matching identifiers removeFromComponents1 = [] for component1 in components1: baseGlyph = component1["baseGlyph"] identifier = component1["identifier"] match = None for component2 in components2: if component2["baseGlyph"] == baseGlyph and component2["identifier"] == identifier: match = component2 break if match is not None: component2 = match removeFromComponents1.append(component1) components2.remove(component2) pairs.append((component1, component2)) for component1 in removeFromComponents1: components1.remove(component1) # align with index for component1 in components1: baseGlyph = component1["baseGlyph"] for component2 in components2: if component2["baseGlyph"] == baseGlyph: components2.remove(component2) pairs.append((component1, component2)) break return pairs def _processMathOneComponents(componentPairs, func): result = [] for component1, component2 in componentPairs: component = dict(component1) component["transformation"] = _processMathOneTransformation(component1["transformation"], component2["transformation"], func) result.append(component) return result def _processMathTwoComponents(components, factor, func): result = [] for component in components: component = dict(component) component["transformation"] = _processMathTwoTransformation(component["transformation"], factor, func) result.append(component) return result # image _imageTransformationKeys = "xScale xyScale yxScale yScale xOffset yOffset".split(" ") _defaultImageTransformation = (1, 0, 0, 1, 0, 0) _defaultImageTransformationDict = {} for key, value in zip(_imageTransformationKeys, _defaultImageTransformation): _defaultImageTransformationDict[key] = value def _expandImage(image): if image is None: fileName = None transformation = _defaultImageTransformation color = None else: if hasattr(image, "naked"): image = image.naked() fileName = image["fileName"] color = image.get("color") transformation = tuple([ image.get(key, _defaultImageTransformationDict[key]) for key in _imageTransformationKeys ]) return dict(fileName=fileName, transformation=transformation, color=color) def _compressImage(image): fileName = image["fileName"] transformation = image["transformation"] color = image["color"] if fileName is None: return image = dict(fileName=fileName, color=color) for index, key in enumerate(_imageTransformationKeys): image[key] = transformation[index] return image def _pairImages(image1, image2): if image1["fileName"] != image2["fileName"]: return () return (image1, image2) def _processMathOneImage(imagePair, func): image1, image2 = imagePair fileName = image1["fileName"] color = image1["color"] transformation = _processMathOneTransformation(image1["transformation"], image2["transformation"], func) return dict(fileName=fileName, transformation=transformation, color=color) def _processMathTwoImage(image, factor, func): fileName = image["fileName"] color = image["color"] transformation = _processMathTwoTransformation(image["transformation"], factor, func) return dict(fileName=fileName, transformation=transformation, color=color) # transformations def _processMathOneTransformation(transformation1, transformation2, func): xScale1, xyScale1, yxScale1, yScale1, xOffset1, yOffset1 = transformation1 xScale2, xyScale2, yxScale2, yScale2, xOffset2, yOffset2 = transformation2 xScale, yScale = func((xScale1, yScale1), (xScale2, yScale2)) xyScale, yxScale = func((xyScale1, yxScale1), (xyScale2, yxScale2)) xOffset, yOffset = func((xOffset1, yOffset1), (xOffset2, yOffset2)) return (xScale, xyScale, yxScale, yScale, xOffset, yOffset) def _processMathTwoTransformation(transformation, factor, func): xScale, xyScale, yxScale, yScale, xOffset, yOffset = transformation xScale, yScale = func((xScale, yScale), factor) xyScale, yxScale = func((xyScale, yxScale), factor) xOffset, yOffset = func((xOffset, yOffset), factor) return (xScale, xyScale, yxScale, yScale, xOffset, yOffset) # rounding def _roundContours(contours, digits=None): results = [] for contour in contours: contour = dict(contour) roundedPoints = [] for segmentType, pt, smooth, name, identifier in contour["points"]: roundedPt = (_roundNumber(pt[0],digits), _roundNumber(pt[1],digits)) roundedPoints.append((segmentType, roundedPt, smooth, name, identifier)) contour["points"] = roundedPoints results.append(contour) return results def _roundTransformation(transformation, digits=None): xScale, xyScale, yxScale, yScale, xOffset, yOffset = transformation return (xScale, xyScale, yxScale, yScale, _roundNumber(xOffset, digits), _roundNumber(yOffset, digits)) def _roundImage(image, digits=None): image = dict(image) fileName = image["fileName"] color = image["color"] transformation = _roundTransformation(image["transformation"], digits) return dict(fileName=fileName, transformation=transformation, color=color) def _roundComponents(components, digits=None): result = [] for component in components: component = dict(component) component["transformation"] = _roundTransformation(component["transformation"], digits) result.append(component) return result def _roundAnchors(anchors, digits=None): result = [] for anchor in anchors: anchor = dict(anchor) anchor["x"], anchor["y"] = _roundNumber(anchor["x"], digits), _roundNumber(anchor["y"], digits) result.append(anchor) return result if __name__ == "__main__": import sys import doctest sys.exit(doctest.testmod().failed) fontMath-0.5.2/Lib/fontMath/mathGuideline.py000066400000000000000000000073511356720430400207270ustar00rootroot00000000000000from fontMath.mathFunctions import factorAngle, _roundNumber __all__ = [ "_expandGuideline", "_compressGuideline", "_pairGuidelines", "_processMathOneGuidelines", "_processMathTwoGuidelines", "_roundGuidelines" ] def _expandGuideline(guideline): guideline = dict(guideline) x = guideline.get("x") y = guideline.get("y") # horizontal if x is None: guideline["x"] = 0 guideline["angle"] = 0 # vertical elif y is None: guideline["y"] = 0 guideline["angle"] = 90 return guideline def _compressGuideline(guideline): guideline = dict(guideline) x = guideline["x"] y = guideline["y"] angle = guideline["angle"] # horizontal if x == 0 and angle in (0, 180): guideline["x"] = None guideline["angle"] = None # vertical elif y == 0 and angle in (90, 270): guideline["y"] = None guideline["angle"] = None return guideline def _pairGuidelines(guidelines1, guidelines2): guidelines1 = list(guidelines1) guidelines2 = list(guidelines2) pairs = [] # name + identifier + (x, y, angle) _findPair(guidelines1, guidelines2, pairs, ("name", "identifier", "x", "y", "angle")) # name + identifier matches _findPair(guidelines1, guidelines2, pairs, ("name", "identifier")) # name + (x, y, angle) _findPair(guidelines1, guidelines2, pairs, ("name", "x", "y", "angle")) # identifier + (x, y, angle) _findPair(guidelines1, guidelines2, pairs, ("identifier", "x", "y", "angle")) # name matches if guidelines1 and guidelines2: _findPair(guidelines1, guidelines2, pairs, ("name",)) # identifier matches if guidelines1 and guidelines2: _findPair(guidelines1, guidelines2, pairs, ("identifier",)) # done return pairs def _findPair(guidelines1, guidelines2, pairs, attrs): removeFromGuidelines1 = [] for guideline1 in guidelines1: match = None for guideline2 in guidelines2: attrMatch = False not in [guideline1.get(attr) == guideline2.get(attr) for attr in attrs] if attrMatch: match = guideline2 break if match is not None: guideline2 = match removeFromGuidelines1.append(guideline1) guidelines2.remove(guideline2) pairs.append((guideline1, guideline2)) for removeGuide in removeFromGuidelines1: guidelines1.remove(removeGuide) def _processMathOneGuidelines(guidelinePairs, ptFunc, func): result = [] for guideline1, guideline2 in guidelinePairs: guideline = dict(guideline1) pt1 = (guideline1["x"], guideline1["y"]) pt2 = (guideline2["x"], guideline2["y"]) guideline["x"], guideline["y"] = ptFunc(pt1, pt2) angle1 = guideline1["angle"] angle2 = guideline2["angle"] guideline["angle"] = func(angle1, angle2) % 360 result.append(guideline) return result def _processMathTwoGuidelines(guidelines, factor, func): result = [] for guideline in guidelines: guideline = dict(guideline) guideline["x"] = func(guideline["x"], factor[0]) guideline["y"] = func(guideline["y"], factor[1]) angle = guideline["angle"] guideline["angle"] = factorAngle(angle, factor, func) % 360 result.append(guideline) return result def _roundGuidelines(guidelines, digits=None): results = [] for guideline in guidelines: guideline = dict(guideline) guideline['x'] = _roundNumber(guideline['x'], digits) guideline['y'] = _roundNumber(guideline['y'], digits) results.append(guideline) return results if __name__ == "__main__": import sys import doctest sys.exit(doctest.testmod().failed) fontMath-0.5.2/Lib/fontMath/mathInfo.py000066400000000000000000000356041356720430400177170ustar00rootroot00000000000000from __future__ import division, absolute_import from fontMath.mathFunctions import ( add, addPt, div, factorAngle, mul, _roundNumber, sub, subPt) from fontTools.misc.py23 import round2 from fontMath.mathGuideline import ( _expandGuideline, _pairGuidelines, _processMathOneGuidelines, _processMathTwoGuidelines, _roundGuidelines) class MathInfo(object): def __init__(self, infoObject): for attr in _infoAttrs.keys(): if hasattr(infoObject, attr): setattr(self, attr, getattr(infoObject, attr)) if isinstance(infoObject, MathInfo): self.guidelines = [dict(guideline) for guideline in infoObject.guidelines] elif infoObject.guidelines is not None: self.guidelines = [_expandGuideline(guideline) for guideline in infoObject.guidelines] else: self.guidelines = [] # ---- # Copy # ---- def copy(self): copied = MathInfo(self) return copied # ---- # Math # ---- # math with other info def __add__(self, otherInfo): copiedInfo = self.copy() self._processMathOne(copiedInfo, otherInfo, addPt, add) return copiedInfo def __sub__(self, otherInfo): copiedInfo = self.copy() self._processMathOne(copiedInfo, otherInfo, subPt, sub) return copiedInfo def _processMathOne(self, copiedInfo, otherInfo, ptFunc, func): # basic attributes for attr in _infoAttrs.keys(): a = None b = None v = None if hasattr(copiedInfo, attr): a = getattr(copiedInfo, attr) if hasattr(otherInfo, attr): b = getattr(otherInfo, attr) 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) # when one of the terms is undefined, we treat addition and subtraction # differently... # https://github.com/robotools/fontMath/issues/175 # https://github.com/robotools/fontMath/issues/136 elif a is not None and b is None: if func == add: v = a else: v = None if attr in _numberListAttrs else 0 elif b is not None and a is None: if func is add: v = b else: v = None if attr in _numberListAttrs else 0 setattr(copiedInfo, attr, v) # special attributes self._processPostscriptWeightName(copiedInfo) # guidelines copiedInfo.guidelines = [] if self.guidelines: guidelinePairs = _pairGuidelines(self.guidelines, otherInfo.guidelines) copiedInfo.guidelines = _processMathOneGuidelines(guidelinePairs, ptFunc, func) 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 # math with factor def __mul__(self, factor): if not isinstance(factor, tuple): factor = (factor, factor) copiedInfo = self.copy() self._processMathTwo(copiedInfo, factor, mul) return copiedInfo __rmul__ = __mul__ def __div__(self, factor): if not isinstance(factor, tuple): factor = (factor, factor) copiedInfo = self.copy() self._processMathTwo(copiedInfo, factor, div) return copiedInfo __truediv__ = __div__ __rdiv__ = __div__ __rtruediv__ = __rdiv__ def _processMathTwo(self, copiedInfo, factor, func): # basic attributes for attr, (formatter, factorIndex) in _infoAttrs.items(): if hasattr(copiedInfo, attr): v = getattr(copiedInfo, attr) if v is not None and factor is not None: if factorIndex == 3: v = self._processMathTwoAngle(v, factor, func) else: if isinstance(v, (list, tuple)): v = self._processMathTwoNumberList(v, factor[factorIndex], func) else: v = self._processMathTwoNumber(v, factor[factorIndex], func) else: v = None setattr(copiedInfo, attr, v) # special attributes self._processPostscriptWeightName(copiedInfo) # guidelines copiedInfo.guidelines = [] if self.guidelines: copiedInfo.guidelines = _processMathTwoGuidelines(self.guidelines, factor, func) 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 _processMathTwoAngle(self, angle, factor, func): return factorAngle(angle, factor, func) # special attributes def _processPostscriptWeightName(self, copiedInfo): # handle postscriptWeightName by taking the value # of openTypeOS2WeightClass and getting the closest # value from the OS/2 specification. name = None if hasattr(copiedInfo, "openTypeOS2WeightClass") and copiedInfo.openTypeOS2WeightClass is not None: v = copiedInfo.openTypeOS2WeightClass # here we use Python 2 rounding (i.e. away from 0) instead of Python 3: # e.g. 150 -> 200 and 250 -> 300, instead of 150 -> 200 and 250 -> 200 v = int(round2(v, -2)) if v < 100: v = 100 elif v > 900: v = 900 name = _postscriptWeightNameOptions[v] copiedInfo.postscriptWeightName = name # ---------- # More math # ---------- def round(self, digits=None): excludeFromRounding = ['postscriptBlueScale', 'italicAngle'] copiedInfo = self.copy() # basic attributes for attr, (formatter, factorIndex) in _infoAttrs.items(): if attr in excludeFromRounding: continue if hasattr(copiedInfo, attr): v = getattr(copiedInfo, attr) if v is not None: if factorIndex == 3: v = _roundNumber(v) else: if isinstance(v, (list, tuple)): v = [_roundNumber(a, digits) for a in v] else: v = _roundNumber(v, digits) else: v = None setattr(copiedInfo, attr, v) # special attributes self._processPostscriptWeightName(copiedInfo) # guidelines copiedInfo.guidelines = [] if self.guidelines: copiedInfo.guidelines = _roundGuidelines(self.guidelines, digits) return copiedInfo # ---------- # Extraction # ---------- def extractInfo(self, otherInfoObject): """ >>> from fontMath.test.test_mathInfo import _TestInfoObject, _testData >>> from fontMath.mathFunctions import _roundNumber >>> info1 = MathInfo(_TestInfoObject()) >>> info2 = info1 * 2.5 >>> info3 = _TestInfoObject() >>> info2.extractInfo(info3) >>> written = {} >>> expected = {} >>> for attr, value in _testData.items(): ... if value is None: ... continue ... written[attr] = getattr(info2, attr) ... if isinstance(value, list): ... expectedValue = [_roundNumber(v * 2.5) for v in value] ... elif isinstance(value, int): ... expectedValue = _roundNumber(value * 2.5) ... else: ... expectedValue = value * 2.5 ... expected[attr] = expectedValue >>> sorted(expected) == sorted(written) True """ for attr, (formatter, factorIndex) in _infoAttrs.items(): if hasattr(self, attr): v = getattr(self, attr) if v is not None: if formatter is not None: v = formatter(v) setattr(otherInfoObject, attr, v) if hasattr(self, "postscriptWeightName"): otherInfoObject.postscriptWeightName = self.postscriptWeightName # ------- # Sorting # ------- def __lt__(self, other): if set(self.__dict__.keys()) < set(other.__dict__.keys()): return True elif set(self.__dict__.keys()) > set(other.__dict__.keys()): return False for attr, value in self.__dict__.items(): other_value = getattr(other, attr) if value is not None and other_value is not None: # guidelines is a list of dicts if attr == "guidelines": if len(value) < len(other_value): return True elif len(value) > len(other_value): return False for i, guide in enumerate(value): if set(guide) < set(other_value[i]): return True elif set(guide) > set(other_value[i]): return False for key, val in guide.items(): if key in other_value[i]: if val < other_value[i][key]: return True elif value < other_value: return True return False def __eq__(self, other): if set(self.__dict__.keys()) != set(other.__dict__.keys()): return False for attr, value in self.__dict__.items(): if hasattr(other, attr) and value != getattr(other, attr): return False return True # ---------- # Formatters # ---------- def _numberFormatter(value): v = int(value) if v == value: return v return value def _integerFormatter(value): return _roundNumber(value) def _floatFormatter(value): return float(value) def _nonNegativeNumberFormatter(value): """ >>> _nonNegativeNumberFormatter(-10) 0 """ if value < 0: return 0 return value def _nonNegativeIntegerFormatter(value): value = _integerFormatter(value) if value < 0: return 0 return value def _integerListFormatter(value): """ >>> _integerListFormatter([.9, 40.3, 16.0001]) [1, 40, 16] """ return [_integerFormatter(v) for v in value] def _numberListFormatter(value): return [_numberFormatter(v) for v in value] def _openTypeOS2WidthClassFormatter(value): """ >>> _openTypeOS2WidthClassFormatter(-2) 1 >>> _openTypeOS2WidthClassFormatter(0) 1 >>> _openTypeOS2WidthClassFormatter(5.4) 5 >>> _openTypeOS2WidthClassFormatter(9.6) 9 >>> _openTypeOS2WidthClassFormatter(12) 9 """ value = int(round2(value)) if value > 9: value = 9 elif value < 1: value = 1 return value def _openTypeOS2WeightClassFormatter(value): """ >>> _openTypeOS2WeightClassFormatter(-20) 0 >>> _openTypeOS2WeightClassFormatter(0) 0 >>> _openTypeOS2WeightClassFormatter(50.4) 50 >>> _openTypeOS2WeightClassFormatter(90.6) 91 >>> _openTypeOS2WeightClassFormatter(120) 120 """ value = _roundNumber(value) if value < 0: value = 0 return value _infoAttrs = dict( # these are structured as: # attribute name = (formatter function, factor direction) # where factor direction 0 = x, 1 = y and 3 = x, y (for angles) unitsPerEm=(_nonNegativeNumberFormatter, 1), descender=(_numberFormatter, 1), xHeight=(_numberFormatter, 1), capHeight=(_numberFormatter, 1), ascender=(_numberFormatter, 1), italicAngle=(_numberFormatter, 3), openTypeHeadLowestRecPPEM=(_nonNegativeIntegerFormatter, 1), openTypeHheaAscender=(_integerFormatter, 1), openTypeHheaDescender=(_integerFormatter, 1), openTypeHheaLineGap=(_integerFormatter, 1), openTypeHheaCaretSlopeRise=(_integerFormatter, 1), openTypeHheaCaretSlopeRun=(_integerFormatter, 1), openTypeHheaCaretOffset=(_integerFormatter, 1), openTypeOS2WidthClass=(_openTypeOS2WidthClassFormatter, 0), openTypeOS2WeightClass=(_openTypeOS2WeightClassFormatter, 0), openTypeOS2TypoAscender=(_integerFormatter, 1), openTypeOS2TypoDescender=(_integerFormatter, 1), openTypeOS2TypoLineGap=(_integerFormatter, 1), openTypeOS2WinAscent=(_nonNegativeIntegerFormatter, 1), openTypeOS2WinDescent=(_nonNegativeIntegerFormatter, 1), openTypeOS2SubscriptXSize=(_integerFormatter, 0), openTypeOS2SubscriptYSize=(_integerFormatter, 1), openTypeOS2SubscriptXOffset=(_integerFormatter, 0), openTypeOS2SubscriptYOffset=(_integerFormatter, 1), openTypeOS2SuperscriptXSize=(_integerFormatter, 0), openTypeOS2SuperscriptYSize=(_integerFormatter, 1), openTypeOS2SuperscriptXOffset=(_integerFormatter, 0), openTypeOS2SuperscriptYOffset=(_integerFormatter, 1), openTypeOS2StrikeoutSize=(_integerFormatter, 1), openTypeOS2StrikeoutPosition=(_integerFormatter, 1), openTypeVheaVertTypoAscender=(_integerFormatter, 1), openTypeVheaVertTypoDescender=(_integerFormatter, 1), openTypeVheaVertTypoLineGap=(_integerFormatter, 1), openTypeVheaCaretSlopeRise=(_integerFormatter, 1), openTypeVheaCaretSlopeRun=(_integerFormatter, 1), openTypeVheaCaretOffset=(_integerFormatter, 1), postscriptSlantAngle=(_numberFormatter, 3), postscriptUnderlineThickness=(_numberFormatter, 1), postscriptUnderlinePosition=(_numberFormatter, 1), postscriptBlueValues=(_numberListFormatter, 1), postscriptOtherBlues=(_numberListFormatter, 1), postscriptFamilyBlues=(_numberListFormatter, 1), postscriptFamilyOtherBlues=(_numberListFormatter, 1), postscriptStemSnapH=(_numberListFormatter, 0), postscriptStemSnapV=(_numberListFormatter, 1), postscriptBlueFuzz=(_numberFormatter, 1), postscriptBlueShift=(_numberFormatter, 1), postscriptBlueScale=(_floatFormatter, 1), postscriptDefaultWidthX=(_numberFormatter, 0), postscriptNominalWidthX=(_numberFormatter, 0), # this will be handled in a special way # postscriptWeightName=unicode ) _numberListAttrs = { attr for attr, (formatter, _) in _infoAttrs.items() if formatter is _numberListFormatter } _postscriptWeightNameOptions = { 100 : "Thin", 200 : "Extra-light", 300 : "Light", 400 : "Normal", 500 : "Medium", 600 : "Semi-bold", 700 : "Bold", 800 : "Extra-bold", 900 : "Black" } if __name__ == "__main__": import sys import doctest sys.exit(doctest.testmod().failed) fontMath-0.5.2/Lib/fontMath/mathKerning.py000066400000000000000000000211001356720430400204030ustar00rootroot00000000000000from __future__ import division, absolute_import from copy import deepcopy from fontMath.mathFunctions import add, sub, mul, div from fontTools.misc.py23 import round2 """ An object that serves kerning data from a class kerning dictionary. It scans a group dictionary and stores a mapping of glyph to group relationships. this map is then used to lookup kerning values. """ side1Prefix = "public.kern1." side2Prefix = "public.kern2." class MathKerning(object): def __init__(self, kerning=None, groups=None): if kerning is None: kerning = {} if groups is None: groups = {} self.update(kerning) self.updateGroups(groups) # ------- # Loading # ------- def update(self, kerning): self._kerning = dict(kerning) def updateGroups(self, groups): self._groups = {} self._side1GroupMap = {} self._side2GroupMap = {} self._side1Groups = {} self._side2Groups = {} for groupName, glyphList in groups.items(): if groupName.startswith(side1Prefix): self._groups[groupName] = list(glyphList) self._side1Groups[groupName] = glyphList for glyphName in glyphList: self._side1GroupMap[glyphName] = groupName elif groupName.startswith(side2Prefix): self._groups[groupName] = list(glyphList) self._side2Groups[groupName] = glyphList for glyphName in glyphList: self._side2GroupMap[glyphName] = groupName def addTo(self, value): for k, v in self._kerning.items(): self._kerning[k] = v + value # ------------- # dict Behavior # ------------- def keys(self): return self._kerning.keys() def values(self): return self._kerning.values() def items(self): return self._kerning.items() def groups(self): return deepcopy(self._groups) def __contains__(self, pair): return pair in self._kerning def __getitem__(self, pair): if pair in self._kerning: return self._kerning[pair] side1, side2 = pair if side1.startswith(side1Prefix): side1Group = side1 side1 = None else: side1Group = self._side1GroupMap.get(side1) if side2.startswith(side2Prefix): side2Group = side2 side2 = None else: side2Group = self._side2GroupMap.get(side2) if (side1Group, side2) in self._kerning: return self._kerning[side1Group, side2] elif (side1, side2Group) in self._kerning: return self._kerning[side1, side2Group] elif (side1Group, side2Group) in self._kerning: return self._kerning[side1Group, side2Group] else: return 0 def get(self, pair): v = self[pair] return v # --------- # Pair Type # --------- def guessPairType(self, pair): side1, side2 = pair if side1.startswith(side1Prefix): side1Group = side1 else: side1Group = self._side1GroupMap.get(side1) if side2.startswith(side2Prefix): side2Group = side2 else: side2Group = self._side2GroupMap.get(side2) side1Type = side2Type = "glyph" if pair in self: if side1 == side1Group: side1Type = "group" elif side1Group is not None: side1Type = "exception" if side2 == side2Group: side2Type = "group" elif side2Group is not None: side2Type = "exception" elif (side1Group, side2) in self: side1Type = "group" if side2Group is not None: if side2 != side2Group: side2Type = "exception" elif (side1, side2Group) in self: side2Type = "group" if side1Group is not None: if side1 != side1Group: side1Type = "exception" else: if side1Group is not None: side1Type = "group" if side2Group is not None: side2Type = "group" return side1Type, side2Type # ---- # Copy # ---- def copy(self): k = MathKerning(self._kerning, self._groups) return k # ---- # Math # ---- # math with other kerning def __add__(self, other): k = self._processMathOne(other, add) k.cleanup() return k def __sub__(self, other): k = self._processMathOne(other, sub) k.cleanup() return k def _processMathOne(self, other, funct): comboPairs = set(self._kerning.keys()) | set(other._kerning.keys()) kerning = dict.fromkeys(comboPairs, None) for k in comboPairs: v1 = self.get(k) v2 = other.get(k) v = funct(v1, v2) kerning[k] = v g1 = self.groups() g2 = other.groups() if g1 == g2 or not g1 or not g2: groups = g1 or g2 else: comboGroups = set(g1.keys()) | set(g2.keys()) groups = dict.fromkeys(comboGroups, None) for groupName in comboGroups: s1 = set(g1.get(groupName, [])) s2 = set(g2.get(groupName, [])) groups[groupName] = sorted(list(s1 | s2)) ks = MathKerning(kerning, groups) return ks # math with factor def __mul__(self, factor): if isinstance(factor, tuple): factor = factor[0] k = self._processMathTwo(factor, mul) k.cleanup() return k def __rmul__(self, factor): if isinstance(factor, tuple): factor = factor[0] k = self._processMathTwo(factor, mul) k.cleanup() return k def __div__(self, factor): if isinstance(factor, tuple): factor = factor[0] k = self._processMathTwo(factor, div) k.cleanup() return k __truediv__ = __div__ def _processMathTwo(self, factor, funct): kerning = deepcopy(self._kerning) for k, v in self._kerning.items(): v = funct(v, factor) kerning[k] = v ks = MathKerning(kerning, self._groups) return ks # --------- # More math # --------- def round(self, multiple=1): multiple = float(multiple) for k, v in self._kerning.items(): self._kerning[k] = int(round2(int(round2(v / multiple)) * multiple)) # ------- # Cleanup # ------- def cleanup(self): for (side1, side2), v in list(self._kerning.items()): if int(v) == v: v = int(v) self._kerning[side1, side2] = v if v == 0: side1Type, side2Type = self.guessPairType((side1, side2)) if side1Type != "exception" and side2Type != "exception": del self._kerning[side1, side2] # ---------- # Extraction # ---------- def extractKerning(self, font): font.kerning.clear() font.kerning.update(self._kerning) font.groups.update(self.groups()) # ------- # Sorting # ------- def _isLessish(self, first, second): if len(first) < len(second): return True elif len(first) > len(second): return False first_keys = sorted(first) second_keys = sorted(second) for i, key in enumerate(first_keys): if first_keys[i] < second_keys[i]: return True elif first_keys[i] > second_keys[i]: return False elif first[key] < second[key]: return True return None def __lt__(self, other): if other is None: return False lessish = self._isLessish(self._kerning, other._kerning) if lessish is not None: return lessish lessish = self._isLessish(self._side1Groups, other._side1Groups) if lessish is not None: return lessish lessish = self._isLessish(self._side2Groups, other._side2Groups) if lessish is not None: return lessish return False def __eq__(self, other): if self._kerning != other._kerning: return False if self._side1Groups != other._side1Groups: return False if self._side2Groups != other._side2Groups: return False return True if __name__ == "__main__": import sys import doctest sys.exit(doctest.testmod().failed) fontMath-0.5.2/Lib/fontMath/mathTransform.py000066400000000000000000000307761356720430400210040ustar00rootroot00000000000000from __future__ import print_function import math from fontTools.misc.transform import Transform """ This is a more sophisticated approach to performing math on transformation matrices. Traditionally glyphMath applies the math straight to the elements in the matrix. By decomposing the matrix into offset, scale and rotation factors, the interpoations are much more natural. Or more intuitive. This could help in complex glyphs in which the rotation of a component plays am important role. This MathTransform object itself has its own interpolation method. But in order to be able to participate in (for instance) superpolator math, it is necessary to keep the offset, scale and rotation decomposed for more than one math operation. So, MathTransform decomposes the matrix, ShallowTransform carries it through the math, then MathTransform is used again to compose the new matrix. If you don't need to math with the transformation object itself, the MathTransform object is fine. MathTransform by Frederik Berlaen Transformation decomposition algorithm from http://dojotoolkit.org/reference-guide/1.9/dojox/gfx.html#decompose-js http://dojotoolkit.org/license """ def matrixToMathTransform(matrix): """ Take a 6-tuple and return a ShallowTransform object.""" if isinstance(matrix, ShallowTransform): return matrix off, scl, rot = MathTransform(matrix).decompose() return ShallowTransform(off, scl, rot) def mathTransformToMatrix(mathTransform): """ Take a ShallowTransform object and return a 6-tuple. """ m = MathTransform().compose(mathTransform.offset, mathTransform.scale, mathTransform.rotation) return tuple(m) class ShallowTransform(object): """ A shallow math container for offset, scale and rotation. """ def __init__(self, offset, scale, rotation): self.offset = offset self.scale = scale self.rotation = rotation def __repr__(self): return ""%(self.offset[0], self.offset[1], self.scale[0], self.scale[1], self.rotation[0], self.rotation[1]) def __add__(self, other): newOffset = self.offset[0]+other.offset[0],self.offset[1]+other.offset[1] newScale = self.scale[0]+other.scale[0],self.scale[1]+other.scale[1] newRotation = self.rotation[0]+other.rotation[0],self.rotation[1]+other.rotation[1] return self.__class__(newOffset, newScale, newRotation) def __sub__(self, other): newOffset = self.offset[0]-other.offset[0],self.offset[1]-other.offset[1] newScale = self.scale[0]-other.scale[0],self.scale[1]-other.scale[1] newRotation = self.rotation[0]-other.rotation[0],self.rotation[1]-other.rotation[1] return self.__class__(newOffset, newScale, newRotation) def __mul__(self, factor): if isinstance(factor, (int, float)): fx = fy = float(factor) else: fx, fy = float(factor[0]), float(factor[1]) newOffset = self.offset[0]*fx,self.offset[1]*fy newScale = self.scale[0]*fx,self.scale[1]*fy newRotation = self.rotation[0]*fx,self.rotation[1]*fy return self.__class__(newOffset, newScale, newRotation) __rmul__ = __mul__ def __truediv__(self, factor): """ XXX why not __div__ ?""" if isinstance(factor, (int, float)): fx = fy = float(factor) else: fx, fy = float(factor) if fx==0 or fy==0: raise ZeroDivisionError((fx, fy)) newOffset = self.offset[0]/fx,self.offset[1]/fy newScale = self.scale[0]/fx,self.scale[1]/fy newRotation = self.rotation[0]/fx,self.rotation[1]/fy return self.__class__(newOffset, newScale, newRotation) def asTuple(self): m = MathTransform().compose(self.offset, self.scale, self.rotation) return tuple(m) class MathTransform(object): """ A Transform object that can compose and decompose the matrix into offset, scale and rotation.""" transformClass = Transform def __init__(self, *matrixes): matrix = self.transformClass() if matrixes: if isinstance(matrixes[0], (int, float)): matrixes = [matrixes] for m in matrixes: matrix = matrix.transform(m) self.matrix = matrix def _get_matrix(self): return (self.xx, self.xy, self.yx, self.yy, self.dx, self.dy) def _set_matrix(self, matrix): self.xx, self.xy, self.yx, self.yy, self.dx, self.dy = matrix matrix = property(_get_matrix, _set_matrix) def __repr__(self): return "< %.8f %.8f %.8f %.8f %.8f %.8f >" % (self.xx, self.xy, self.yx, self.yy, self.dx, self.dy) def __len__(self): return 6 def __getitem__(self, index): return self.matrix[index] def __getslice__(self, i, j): return self.matrix[i:j] def __eq__(self, other): return str(self) == str(other) ## transformations def translate(self, x=0, y=0): return self.__class__(self.transformClass(*self.matrix).translate(x, y)) def scale(self, x=1, y=None): return self.__class__(self.transformClass(*self.matrix).scale(x, y)) def rotate(self, angle): return self.__class__(self.transformClass(*self.matrix).rotate(angle)) def rotateDegrees(self, angle): return self.rotate(math.radians(angle)) def skew(self, x=0, y=0): return self.__class__(self.transformClass(*self.matrix).skew(x, y)) def skewDegrees(self, x=0, y=0): return self.skew(math.radians(x), math.radians(y)) def transform(self, other): return self.__class__(self.transformClass(*self.matrix).transform(other)) def reverseTransform(self, other): return self.__class__(self.transformClass(*self.matrix).reverseTransform(other)) def inverse(self): return self.__class__(self.transformClass(*self.matrix).inverse()) def copy(self): return self.__class__(self.matrix) ## tools def scaleSign(self): if self.xx * self.yy < 0 or self.xy * self.yx > 0: return -1 return 1 def eq(self, a, b): return abs(a - b) <= 1e-6 * (abs(a) + abs(b)) def calcFromValues(self, r1, m1, r2, m2): m1 = abs(m1) m2 = abs(m2) return (m1 * r1 + m2 * r2) / (m1 + m2) def transpose(self): return self.__class__(self.xx, self.yx, self.xy, self.yy, 0, 0) def decompose(self): self.translateX = self.dx self.translateY = self.dy self.scaleX = 1 self.scaleY = 1 self.angle1 = 0 self.angle2 = 0 if self.eq(self.xy, 0) and self.eq(self.yx, 0): self.scaleX = self.xx self.scaleY = self.yy elif self.eq(self.xx * self.yx, -self.xy * self.yy): self._decomposeScaleRotate() elif self.eq(self.xx * self.xy, -self.yx * self.yy): self._decomposeRotateScale() else: transpose = self.transpose() (vx1, vy1), (vx2, vy2) = self._eigenvalueDecomposition(self.matrix, transpose.matrix) u = self.__class__(vx1, vx2, vy1, vy2, 0, 0) (vx1, vy1), (vx2, vy2) = self._eigenvalueDecomposition(transpose.matrix, self.matrix) vt = self.__class__(vx1, vy1, vx2, vy2, 0, 0) s = self.__class__(self.__class__().reverseTransform(u), self, self.__class__().reverseTransform(vt)) vt._decomposeScaleRotate() self.angle1 = -vt.angle2 u._decomposeRotateScale() self.angle2 = -u.angle1 self.scaleX = s.xx * vt.scaleX * u.scaleX self.scaleY = s.yy * vt.scaleY * u.scaleY return (self.translateX, self.translateY), (self.scaleX, self.scaleY), (self.angle1, self.angle2) def _decomposeScaleRotate(self): sign = self.scaleSign() a = (math.atan2(self.yx, self.yy) + math.atan2(-sign * self.xy, sign * self.xx)) * .5 c = math.cos(a) s = math.sin(a) if c == 0: ## ???? c = 0.0000000000000000000000000000000001 if s == 0: s = 0.0000000000000000000000000000000001 self.angle2 = -a self.scaleX = self.calcFromValues(self.xx / float(c), c, -self.xy / float(s), s) self.scaleY = self.calcFromValues(self.yy / float(c), c, self.yx / float(s), s) def _decomposeRotateScale(self): sign = self.scaleSign() a = (math.atan2(sign * self.yx, sign * self.xx) + math.atan2(-self.xy, self.yy)) * .5 c = math.cos(a) s = math.sin(a) if c == 0: c = 0.0000000000000000000000000000000001 if s == 0: s = 0.0000000000000000000000000000000001 self.angle1 = -a self.scaleX = self.calcFromValues(self.xx / float(c), c, self.yx / float(s), s) self.scaleY = self.calcFromValues(self.yy / float(c), c, -self.xy / float(s), s) def _eigenvalueDecomposition(self, *matrixes): m = self.__class__(*matrixes) b = -m.xx - m.yy c = m.xx * m.yy - m.xy * m.yx d = math.sqrt(abs(b * b - 4 * c)) if b < 0: d *= -1 l1 = -(b + d) * .5 l2 = c / float(l1) vx1 = vy2 = None if l1 - m.xx != 0: vx1 = m.xy / (l1 - m.xx) vy1 = 1 elif m.xy != 0: vx1 = 1 vy1 = (l1 - m.xx) / m.xy elif m.yx != 0: vx1 = (l1 - m.yy) / m.yx vy1 = 1 elif l1 - m.yy != 0: vx1 = 1 vy1 = m.yx / (l1 - m.yy) vx2 = vy2 = None if l2 - m.xx != 0: vx2 = m.xy / (l2 - m.xx) vy2 = 1 elif m.xy != 0: vx2 = 1 vy2 = (l2 - m.xx) / m.xy elif m.yx != 0: vx2 = (l2 - m.yy) / m.yx vy2 = 1 elif l2 - m.yy != 0: vx2 = 1 vy2 = m.yx / (l2 - m.yy) if self.eq(l1, l2): vx1 = 1 vy1 = 0 vx2 = 0 vy2 = 1 d1 = math.sqrt(vx1 * vx1 + vy1 * vy1) d2 = math.sqrt(vx2 * vx2 + vy2 * vy2) vx1 /= d1 vy1 /= d1 vx2 /= d2 vy2 /= d2 return (vx1, vy1), (vx2, vy2) def compose(self, translate, scale, angle): translateX, translateY = translate scaleX, scaleY = scale angle1, angle2 = angle matrix = self.transformClass() matrix = matrix.translate(translateX, translateY) matrix = matrix.rotate(angle2) matrix = matrix.scale(scaleX, scaleY) matrix = matrix.rotate(angle1) return self.__class__(matrix) def _interpolate(self, v1, v2, value): return v1 * (1 - value) + v2 * value def interpolate(self, other, value): if isinstance(value, (int, float)): x = y = value else: x, y = value self.decompose() other.decompose() translateX = self._interpolate(self.translateX, other.translateX, x) translateY = self._interpolate(self.translateY, other.translateY, y) scaleX = self._interpolate(self.scaleX, other.scaleX, x) scaleY = self._interpolate(self.scaleY, other.scaleY, y) angle1 = self._interpolate(self.angle1, other.angle1, x) angle2 = self._interpolate(self.angle2, other.angle2, y) return self.compose((translateX, translateY), (scaleX, scaleY), (angle1, angle2)) class FontMathWarning(Exception): pass def _interpolateValue(data1, data2, value): return data1 * (1 - value) + data2 * value def _linearInterpolationTransformMatrix(matrix1, matrix2, value): """ Linear, 'oldstyle' interpolation of the transform matrix.""" return tuple(_interpolateValue(matrix1[i], matrix2[i], value) for i in range(len(matrix1))) def _polarDecomposeInterpolationTransformation(matrix1, matrix2, value): """ Interpolate using the MathTransform method. """ m1 = MathTransform(matrix1) m2 = MathTransform(matrix2) return tuple(m1.interpolate(m2, value)) def _mathPolarDecomposeInterpolationTransformation(matrix1, matrix2, value): """ Interpolation with ShallowTransfor, wrapped by decompose / compose actions.""" off, scl, rot = MathTransform(matrix1).decompose() m1 = ShallowTransform(off, scl, rot) off, scl, rot = MathTransform(matrix2).decompose() m2 = ShallowTransform(off, scl, rot) m3 = m1 + value * (m2-m1) m3 = MathTransform().compose(m3.offset, m3.scale, m3.rotation) return tuple(m3) if __name__ == "__main__": from random import random import sys import doctest sys.exit(doctest.testmod().failed) fontMath-0.5.2/Lib/fontMath/test/000077500000000000000000000000001356720430400165475ustar00rootroot00000000000000fontMath-0.5.2/Lib/fontMath/test/__init__.py000066400000000000000000000000001356720430400206460ustar00rootroot00000000000000fontMath-0.5.2/Lib/fontMath/test/test_mathFunctions.py000066400000000000000000000061771356720430400230150ustar00rootroot00000000000000import unittest from fontTools.misc.py23 import round2 from fontTools.misc.fixedTools import otRound from fontMath.mathFunctions import ( add, addPt, sub, subPt, mul, mulPt, div, divPt, factorAngle, _roundNumber, setRoundIntegerFunction, setRoundFloatFunction, _ROUND_INTEGER_FUNC, _ROUND_FLOAT_FUNC, ) class MathFunctionsTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def test_add(self): self.assertEqual(add(1, 2), 3) self.assertEqual(add(3, -4), -1) self.assertEqual(add(5, 0), 5) self.assertEqual(add(6, 7), add(7, 6)) def test_addPt(self): self.assertEqual(addPt((20, 230), (50, 40)), (70, 270)) def test_sub(self): self.assertEqual(sub(10, 10), 0) self.assertEqual(sub(10, -10), 20) self.assertEqual(sub(-10, 10), -20) def test_subPt(self): self.assertEqual(subPt((20, 230), (50, 40)), (-30, 190)) def test_mul(self): self.assertEqual(mul(1, 2), 2) self.assertEqual(mul(3, -4), -12) self.assertEqual(mul(10, 0), 0) self.assertEqual(mul(5, 6), mul(6, 5)) def test_mulPt(self): self.assertEqual(mulPt((15, 25), (2, 3)), (30, 75)) def test_div(self): self.assertEqual(div(1, 2), 0.5) with self.assertRaises(ZeroDivisionError): div(10, 0) self.assertEqual(div(12, -4), -3) def test_divPt(self): self.assertEqual(divPt((15, 75), (2, 3)), (7.5, 25.0)) def test_factorAngle(self): f = factorAngle(5, (2, 1.5), mul) self.assertEqual(_roundNumber(f, 2), 3.75) def test_roundNumber(self): # round to integer: self.assertEqual(_roundNumber(0), 0) self.assertEqual(_roundNumber(0.1), 0) self.assertEqual(_roundNumber(0.99), 1) self.assertEqual(_roundNumber(0.499), 0) self.assertEqual(_roundNumber(0.5), 0) self.assertEqual(_roundNumber(-0.499), 0) self.assertEqual(_roundNumber(-0.5), 0) self.assertEqual(_roundNumber(1.5), 2) self.assertEqual(_roundNumber(-1.5), -2) def test_roundNumber_to_float(self): # round to float with specified decimals: self.assertEqual(_roundNumber(0.3333, None), 0) self.assertEqual(_roundNumber(0.3333, 0), 0.0) self.assertEqual(_roundNumber(0.3333, 1), 0.3) self.assertEqual(_roundNumber(0.3333, 2), 0.33) self.assertEqual(_roundNumber(0.3333, 3), 0.333) def test_set_custom_round_integer_func(self): default = _ROUND_INTEGER_FUNC try: setRoundIntegerFunction(otRound) self.assertEqual(_roundNumber(0.5), 1) self.assertEqual(_roundNumber(-1.5), -1) finally: setRoundIntegerFunction(default) def test_set_custom_round_float_func(self): default = _ROUND_FLOAT_FUNC try: setRoundFloatFunction(round2) self.assertEqual(_roundNumber(0.55, 1), 0.6) self.assertEqual(_roundNumber(-1.555, 2), -1.55) finally: setRoundIntegerFunction(default) if __name__ == "__main__": unittest.main() fontMath-0.5.2/Lib/fontMath/test/test_mathGlyph.py000066400000000000000000001112741356720430400221230ustar00rootroot00000000000000from __future__ import division import unittest from fontTools.pens.pointPen import AbstractPointPen from fontMath.mathFunctions import addPt, mulPt from fontMath.mathGlyph import ( MathGlyph, MathGlyphPen, FilterRedundantPointPen, _processMathOneContours, _processMathTwoContours, _anchorTree, _pairAnchors, _processMathOneAnchors, _processMathTwoAnchors, _pairComponents, _processMathOneComponents, _processMathTwoComponents, _expandImage, _compressImage, _pairImages, _processMathOneImage, _processMathTwoImage, _processMathOneTransformation, _processMathTwoTransformation, _roundContours, _roundTransformation, _roundImage, _roundComponents, _roundAnchors ) try: basestring, xrange range = xrange except NameError: basestring = str class MathGlyphTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def _setupTestGlyph(self): glyph = MathGlyph(None) glyph.width = 0 glyph.height = 0 return glyph def test__eq__(self): glyph1 = self._setupTestGlyph() glyph2 = self._setupTestGlyph() self.assertEqual(glyph1, glyph2) glyph2.width = 1 self.assertFalse(glyph1 == glyph2) nonglyph = object() self.assertFalse(glyph1 == nonglyph) glyph1 = MathGlyph(None) glyph1.name = 'space' glyph1.width = 100 class MyGlyph(object): pass other = MyGlyph() other.name = 'space' other.width = 100 other.height = None other.contours = [] other.components = [] other.anchors = [] other.guidelines = [] other.image = {'fileName': None, 'transformation': (1, 0, 0, 1, 0, 0), 'color': None} other.lib = {} other.unicodes = None other.note = None self.assertEqual(glyph1, other) def test__ne__(self): glyph1 = self._setupTestGlyph() glyph2 = MathGlyph(None) glyph2.width = 1 glyph2.name = 'a' self.assertNotEqual(glyph1, glyph2) self.assertNotEqual(glyph1, 'foo') def test_width_add(self): glyph1 = self._setupTestGlyph() glyph1.width = 1 glyph2 = self._setupTestGlyph() glyph2.width = 2 glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.width, 3) def test_width_sub(self): glyph1 = self._setupTestGlyph() glyph1.width = 3 glyph2 = self._setupTestGlyph() glyph2.width = 2 glyph3 = glyph1 - glyph2 self.assertEqual(glyph3.width, 1) def test_width_mul(self): glyph1 = self._setupTestGlyph() glyph1.width = 2 glyph2 = glyph1 * 3 self.assertEqual(glyph2.width, 6) glyph1 = self._setupTestGlyph() glyph1.width = 2 glyph2 = glyph1 * (3, 1) self.assertEqual(glyph2.width, 6) def test_width_div(self): glyph1 = self._setupTestGlyph() glyph1.width = 7 glyph2 = glyph1 / 2 self.assertEqual(glyph2.width, 3.5) glyph1 = self._setupTestGlyph() glyph1.width = 7 glyph2 = glyph1 / (2, 1) self.assertEqual(glyph2.width, 3.5) def test_width_round(self): glyph1 = self._setupTestGlyph() glyph1.width = 6.99 glyph2 = glyph1.round() self.assertEqual(glyph2.width, 7) def test_height_add(self): glyph1 = self._setupTestGlyph() glyph1.height = 1 glyph2 = self._setupTestGlyph() glyph2.height = 2 glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.height, 3) def test_height_sub(self): glyph1 = self._setupTestGlyph() glyph1.height = 3 glyph2 = self._setupTestGlyph() glyph2.height = 2 glyph3 = glyph1 - glyph2 self.assertEqual(glyph3.height, 1) def test_height_mul(self): glyph1 = self._setupTestGlyph() glyph1.height = 2 glyph2 = glyph1 * 3 self.assertEqual(glyph2.height, 6) glyph1 = self._setupTestGlyph() glyph1.height = 2 glyph2 = glyph1 * (1, 3) self.assertEqual(glyph2.height, 6) def test_height_div(self): glyph1 = self._setupTestGlyph() glyph1.height = 7 glyph2 = glyph1 / 2 self.assertEqual(glyph2.height, 3.5) glyph1 = self._setupTestGlyph() glyph1.height = 7 glyph2 = glyph1 / (1, 2) self.assertEqual(glyph2.height, 3.5) def test_height_round(self): glyph1 = self._setupTestGlyph() glyph1.height = 6.99 glyph2 = glyph1.round() self.assertEqual(glyph2.height, 7) def test_contours_add(self): glyph1 = self._setupTestGlyph() glyph1.contours = [ dict(identifier="contour 1", points=[("line", (0.55, 3.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55, 3.1), True, "test", "1")]) ] glyph2 = self._setupTestGlyph() glyph2.contours = [ dict(identifier="contour 1", points=[("line", (1.55, 4.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (1.55, 4.1), True, "test", "1")]) ] glyph3 = glyph1 + glyph2 expected = [ dict(identifier="contour 1", points=[("line", (0.55 + 1.55, 3.1 + 4.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55 + 1.55, 3.1 + 4.1), True, "test", "1")]) ] self.assertEqual(glyph3.contours, expected) def test_contours_sub(self): glyph1 = self._setupTestGlyph() glyph1.contours = [ dict(identifier="contour 1", points=[("line", (0.55, 3.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55, 3.1), True, "test", "1")]) ] glyph2 = self._setupTestGlyph() glyph2.contours = [ dict(identifier="contour 1", points=[("line", (1.55, 4.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (1.55, 4.1), True, "test", "1")]) ] glyph3 = glyph1 - glyph2 expected = [ dict(identifier="contour 1", points=[("line", (0.55 - 1.55, 3.1 - 4.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55 - 1.55, 3.1 - 4.1), True, "test", "1")]) ] self.assertEqual(glyph3.contours, expected) def test_contours_mul(self): glyph1 = self._setupTestGlyph() glyph1.contours = [ dict(identifier="contour 1", points=[("line", (0.55, 3.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55, 3.1), True, "test", "1")]) ] glyph2 = glyph1 * 2 expected = [ dict(identifier="contour 1", points=[("line", (0.55 * 2, 3.1 * 2), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55 * 2, 3.1 * 2), True, "test", "1")]) ] self.assertEqual(glyph2.contours, expected) def test_contours_div(self): glyph1 = self._setupTestGlyph() glyph1.contours = [ dict(identifier="contour 1", points=[("line", (1, 3.4), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (2, 3.1), True, "test", "1")]) ] glyph2 = glyph1 / 2 expected = [ dict(identifier="contour 1", points=[("line", (1/2, 3.4/2), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (2/2, 3.1/2), True, "test", "1")]) ] self.assertEqual(glyph2.contours, expected) def test_contours_round(self): glyph1 = self._setupTestGlyph() glyph1.contours = [ dict(identifier="contour 1", points=[("line", (0.55, 3.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55, 3.1), True, "test", "1")]) ] glyph2 = glyph1.round() expected = [ dict(identifier="contour 1", points=[("line", (1, 3), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (1, 3), True, "test", "1")]) ] self.assertEqual(glyph2.contours, expected) def test_components_round(self): glyph1 = self._setupTestGlyph() glyph1.components = [ dict(baseGlyph="A", transformation=(1, 2, 3, 4, 5.1, 5.99), identifier="1"), ] glyph2 = glyph1.round() expected = [ dict(baseGlyph="A", transformation=(1, 2, 3, 4, 5, 6), identifier="1") ] self.assertEqual(glyph2.components, expected) def test_guidelines_add_same_name_identifier_x_y_angle(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="2", x=3, y=4, angle=2) ] glyph2 = self._setupTestGlyph() glyph2.guidelines = [ dict(name="foo", identifier="2", x=3, y=4, angle=2), dict(name="foo", identifier="1", x=1, y=2, angle=1) ] expected = [ dict(name="foo", identifier="1", x=2, y=4, angle=2), dict(name="foo", identifier="2", x=6, y=8, angle=4) ] glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.guidelines, expected) def test_guidelines_add_same_name_identifier(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="2", x=1, y=2, angle=2), ] glyph2 = self._setupTestGlyph() glyph2.guidelines = [ dict(name="foo", identifier="2", x=3, y=4, angle=3), dict(name="foo", identifier="1", x=3, y=4, angle=4) ] expected = [ dict(name="foo", identifier="1", x=4, y=6, angle=5), dict(name="foo", identifier="2", x=4, y=6, angle=5) ] glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.guidelines, expected) def test_guidelines_add_same_name_x_y_angle(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="2", x=3, y=4, angle=2), ] glyph2 = self._setupTestGlyph() glyph2.guidelines = [ dict(name="foo", identifier="3", x=3, y=4, angle=2), dict(name="foo", identifier="4", x=1, y=2, angle=1) ] expected = [ dict(name="foo", identifier="1", x=2, y=4, angle=2), dict(name="foo", identifier="2", x=6, y=8, angle=4) ] glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.guidelines, expected) def test_guidelines_add_same_identifier_x_y_angle(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="bar", identifier="2", x=3, y=4, angle=2), ] glyph2 = self._setupTestGlyph() glyph2.guidelines = [ dict(name="xxx", identifier="2", x=3, y=4, angle=2), dict(name="yyy", identifier="1", x=1, y=2, angle=1) ] expected = [ dict(name="foo", identifier="1", x=2, y=4, angle=2), dict(name="bar", identifier="2", x=6, y=8, angle=4) ] glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.guidelines, expected) def test_guidelines_add_same_name(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="bar", identifier="2", x=1, y=2, angle=2), ] glyph2 = self._setupTestGlyph() glyph2.guidelines = [ dict(name="bar", identifier="3", x=3, y=4, angle=3), dict(name="foo", identifier="4", x=3, y=4, angle=4) ] expected = [ dict(name="foo", identifier="1", x=4, y=6, angle=5), dict(name="bar", identifier="2", x=4, y=6, angle=5) ] glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.guidelines, expected) def test_guidelines_add_same_identifier(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="bar", identifier="2", x=1, y=2, angle=2), ] glyph2 = self._setupTestGlyph() glyph2.guidelines = [ dict(name="xxx", identifier="2", x=3, y=4, angle=3), dict(name="yyy", identifier="1", x=3, y=4, angle=4) ] expected = [ dict(name="foo", identifier="1", x=4, y=6, angle=5), dict(name="bar", identifier="2", x=4, y=6, angle=5) ] glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.guidelines, expected) def test_guidelines_mul(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(x=1, y=3, angle=5, name="test", identifier="1", color="0,0,0,0") ] glyph2 = glyph1 * 3 expected = [ dict(x=1 * 3, y=3 * 3, angle=5, name="test", identifier="1", color="0,0,0,0") ] self.assertEqual(glyph2.guidelines, expected) def test_guidelines_round(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(x=1.99, y=3.01, angle=5, name="test", identifier="1", color="0,0,0,0") ] glyph2 = glyph1.round() expected = [ dict(x=2, y=3, angle=5, name="test", identifier="1", color="0,0,0,0") ] self.assertEqual(glyph2.guidelines, expected) def test_guidelines_valid_angle(self): glyph1 = self._setupTestGlyph() glyph1.guidelines = [ dict(name="foo", identifier="1", x=0, y=0, angle=1) ] glyph2 = self._setupTestGlyph() glyph2.guidelines = [ dict(name="foo", identifier="1", x=0, y=0, angle=359) ] expected_add = [dict(name="foo", identifier="1", x=0, y=0, angle=0)] glyph3 = glyph1 + glyph2 self.assertEqual(glyph3.guidelines, expected_add) expected_sub = [dict(name="foo", identifier="1", x=0, y=0, angle=2)] glyph4 = glyph1 - glyph2 self.assertEqual(glyph4.guidelines, expected_sub) expected_mul = [dict(name="foo", identifier="1", x=0, y=0, angle=359)] glyph5 = glyph2 * 5 self.assertEqual(glyph5.guidelines, expected_mul) expected_div = [dict(name="foo", identifier="1", x=0, y=0, angle=359)] glyph6 = glyph2 / 5 self.assertEqual(glyph6.guidelines, expected_div) def test_anchors_add(self): glyph1 = self._setupTestGlyph() glyph1.anchors = [ dict(x=1, y=-2, name="foo", identifier="1", color="0,0,0,0") ] glyph2 = self._setupTestGlyph() glyph2.anchors = [ dict(x=3, y=-4, name="foo", identifier="1", color="0,0,0,0") ] glyph3 = glyph1 + glyph2 expected = [ dict(x=4, y=-6, name="foo", identifier="1", color="0,0,0,0") ] self.assertEqual(glyph3.anchors, expected) def test_anchors_sub(self): glyph1 = self._setupTestGlyph() glyph1.anchors = [ dict(x=1, y=-2, name="foo", identifier="1", color="0,0,0,0") ] glyph2 = self._setupTestGlyph() glyph2.anchors = [ dict(x=3, y=-4, name="foo", identifier="1", color="0,0,0,0") ] glyph3 = glyph1 - glyph2 expected = [ dict(x=-2, y=2, name="foo", identifier="1", color="0,0,0,0") ] self.assertEqual(glyph3.anchors, expected) def test_anchors_mul(self): glyph1 = self._setupTestGlyph() glyph1.anchors = [ dict(x=1, y=-2, name="foo", identifier="1", color="0,0,0,0") ] glyph2 = glyph1 * 2 expected = [ dict(x=2, y=-4, name="foo", identifier="1", color="0,0,0,0") ] self.assertEqual(glyph2.anchors, expected) def test_anchors_div(self): glyph1 = self._setupTestGlyph() glyph1.anchors = [ dict(x=1, y=-2, name="foo", identifier="1", color="0,0,0,0") ] glyph2 = glyph1 / 2 expected = [ dict(x=0.5, y=-1, name="foo", identifier="1", color="0,0,0,0") ] self.assertEqual(glyph2.anchors, expected) def test_anchors_round(self): glyph1 = self._setupTestGlyph() glyph1.anchors = [ dict(x=99.9, y=-100.1, name="foo", identifier="1", color="0,0,0,0") ] glyph2 = glyph1.round() expected = [ dict(x=100, y=-100, name="foo", identifier="1", color="0,0,0,0") ] self.assertEqual(glyph2.anchors, expected) def test_image_round(self): glyph1 = self._setupTestGlyph() glyph1.image = dict(fileName="foo", transformation=(1, 2, 3, 4, 4.99, 6.01), color="0,0,0,0") expected = dict(fileName="foo", transformation=(1, 2, 3, 4, 5, 6), color="0,0,0,0") glyph2 = glyph1.round() self.assertEqual(glyph2.image, expected) class MathGlyphPenTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def test_pen_with_lines(self): pen = MathGlyphPen() pen.beginPath(identifier="contour 1") pen.addPoint((0, 100), "line", smooth=False, name="name 1", identifier="point 1") pen.addPoint((100, 100), "line", smooth=False, name="name 2", identifier="point 2") pen.addPoint((100, 0), "line", smooth=False, name="name 3", identifier="point 3") pen.addPoint((0, 0), "line", smooth=False, name="name 4", identifier="point 4") pen.endPath() expected = [ ("curve", (0, 100), False, "name 1", "point 1"), (None, (0, 100), False, None, None), (None, (100, 100), False, None, None), ("curve", (100, 100), False, "name 2", "point 2"), (None, (100, 100), False, None, None), (None, (100, 0), False, None, None), ("curve", (100, 0), False, "name 3", "point 3"), (None, (100, 0), False, None, None), (None, (0, 0), False, None, None), ("curve", (0, 0), False, "name 4", "point 4"), (None, (0, 0), False, None, None), (None, (0, 100), False, None, None), ] self.assertEqual(pen.contours[-1]["points"], expected) self.assertEqual(pen.contours[-1]["identifier"], 'contour 1') def test_pen_with_lines_and_curves(self): pen = MathGlyphPen() pen.beginPath(identifier="contour 1") pen.addPoint((0, 50), "curve", smooth=False, name="name 1", identifier="point 1") pen.addPoint((50, 100), "line", smooth=False, name="name 2", identifier="point 2") pen.addPoint((75, 100), None) pen.addPoint((100, 75), None) pen.addPoint((100, 50), "curve", smooth=True, name="name 3", identifier="point 3") pen.addPoint((100, 25), None) pen.addPoint((75, 0), None) pen.addPoint((50, 0), "curve", smooth=False, name="name 4", identifier="point 4") pen.addPoint((25, 0), None) pen.addPoint((0, 25), None) pen.endPath() expected = [ ("curve", (0, 50), False, "name 1", "point 1"), (None, (0, 50), False, None, None), (None, (50, 100), False, None, None), ("curve", (50, 100), False, "name 2", "point 2"), (None, (75, 100), False, None, None), (None, (100, 75), False, None, None), ("curve", (100, 50), True, "name 3", "point 3"), (None, (100, 25), False, None, None), (None, (75, 0), False, None, None), ("curve", (50, 0), False, "name 4", "point 4"), (None, (25, 0), False, None, None), (None, (0, 25), False, None, None), ] self.assertEqual(pen.contours[-1]["points"], expected) self.assertEqual(pen.contours[-1]["identifier"], 'contour 1') class _TestPointPen(AbstractPointPen): def __init__(self): self._text = [] def dump(self): return "\n".join(self._text) def _prep(self, i): if isinstance(i, basestring): i = "\"%s\"" % i return str(i) def beginPath(self, identifier=None, **kwargs): self._text.append("beginPath(identifier=%s)" % self._prep(identifier)) def addPoint(self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs): self._text.append( "addPoint(%s, segmentType=%s, smooth=%s, name=%s, " "identifier=%s)" % ( self._prep(pt), self._prep(segmentType), self._prep(smooth), self._prep(name), self._prep(identifier) ) ) def endPath(self): self._text.append("endPath()") def addComponent(self, baseGlyph, transformation, identifier=None, **kwargs): self._text.append( "addComponent(baseGlyph=%s, transformation=%s, identifier=%s)" % ( self._prep(baseGlyph), self._prep(transformation), self._prep(identifier) ) ) class FilterRedundantPointPenTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def test_flushContour(self): points = [ ("curve", (0, 100), False, "name 1", "point 1"), (None, (0, 100), False, None, None), (None, (100, 100), False, None, None), ("curve", (100, 100), False, "name 2", "point 2"), (None, (100, 100), False, None, None), (None, (100, 0), False, None, None), ("curve", (100, 0), False, "name 3", "point 3"), (None, (100, 0), False, None, None), (None, (0, 0), False, None, None), ("curve", (0, 0), False, "name 4", "point 4"), (None, (0, 0), False, None, None), (None, (0, 100), False, None, None), ] testPen = _TestPointPen() filterPen = FilterRedundantPointPen(testPen) filterPen.beginPath(identifier="contour 1") for segmentType, pt, smooth, name, identifier in points: filterPen.addPoint(pt, segmentType=segmentType, smooth=smooth, name=name, identifier=identifier) filterPen.endPath() self.assertEqual( testPen.dump(), 'beginPath(identifier="contour 1")\n' 'addPoint((0, 100), segmentType="line", smooth=False, ' 'name="name 1", identifier="point 1")\n' 'addPoint((100, 100), segmentType="line", smooth=False, ' 'name="name 2", identifier="point 2")\n' 'addPoint((100, 0), segmentType="line", smooth=False, ' 'name="name 3", identifier="point 3")\n' 'addPoint((0, 0), segmentType="line", smooth=False, ' 'name="name 4", identifier="point 4")\n' 'endPath()' ) class PrivateFuncsTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def test_processMathOneContours(self): contours1 = [ dict(identifier="contour 1", points=[("line", (1, 3), False, "test", "1")]) ] contours2 = [ dict(identifier=None, points=[(None, (4, 6), True, None, None)]) ] self.assertEqual( _processMathOneContours(contours1, contours2, addPt), [ dict(identifier="contour 1", points=[("line", (5, 9), False, "test", "1")]) ] ) def test_processMathTwoContours(self): contours = [ dict(identifier="contour 1", points=[("line", (1, 3), False, "test", "1")]) ] self.assertEqual( _processMathTwoContours(contours, (2, 1.5), mulPt), [ dict(identifier="contour 1", points=[("line", (2, 4.5), False, "test", "1")]) ] ) True def test_anchorTree(self): anchors = [ dict(identifier="1", name="test", x=1, y=2, color=None), dict(name="test", x=1, y=2, color=None), dict(name="test", x=3, y=4, color=None), dict(name="test", x=2, y=3, color=None), dict(name="c", x=1, y=2, color=None), dict(name="a", x=0, y=0, color=None), ] self.assertEqual( list(_anchorTree(anchors).items()), [ ("test", [ ("1", 1, 2, None), (None, 1, 2, None), (None, 3, 4, None), (None, 2, 3, None) ]), ("c", [ (None, 1, 2, None) ]), ("a", [ (None, 0, 0, None) ]) ] ) def test_pairAnchors_matching_identifiers(self): anchors1 = { "test": [ (None, 1, 2, None), ("identifier 1", 3, 4, None) ] } anchors2 = { "test": [ ("identifier 1", 1, 2, None), (None, 3, 4, None) ] } self.assertEqual( _pairAnchors(anchors1, anchors2), [ ( dict(name="test", identifier=None, x=1, y=2, color=None), dict(name="test", identifier=None, x=3, y=4, color=None) ), ( dict(name="test", identifier="identifier 1", x=3, y=4, color=None), dict(name="test", identifier="identifier 1", x=1, y=2, color=None) ) ] ) def test_pairAnchors_mismatched_identifiers(self): anchors1 = { "test": [ ("identifier 1", 3, 4, None) ] } anchors2 = { "test": [ ("identifier 2", 1, 2, None), ] } self.assertEqual( _pairAnchors(anchors1, anchors2), [ ( dict(name="test", identifier="identifier 1", x=3, y=4, color=None), dict(name="test", identifier="identifier 2", x=1, y=2, color=None) ) ] ) def test_processMathOneAnchors(self): anchorPairs = [ ( dict(x=100, y=-100, name="foo", identifier="1", color="0,0,0,0"), dict(x=200, y=-200, name="bar", identifier="2", color="1,1,1,1") ) ] self.assertEqual( _processMathOneAnchors(anchorPairs, addPt), [ dict(x=300, y=-300, name="foo", identifier="1", color="0,0,0,0") ] ) def test_processMathTwoAnchors(self): anchors = [ dict(x=100, y=-100, name="foo", identifier="1", color="0,0,0,0") ] self.assertEqual( _processMathTwoAnchors(anchors, (2, 1.5), mulPt), [ dict(x=200, y=-150, name="foo", identifier="1", color="0,0,0,0") ] ) def test_pairComponents(self): components1 = [ dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier="1"), dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier="1"), dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None) ] components2 = [ dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None), dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier="1"), dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier="1") ] self.assertEqual( _pairComponents(components1, components2), [ ( dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier="1"), dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier="1") ), ( dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier="1"), dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier="1") ), ( dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None), dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None) ), ] ) components1 = [ dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None), dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier=None) ] components2 = [ dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier=None), dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None) ] self.assertEqual( _pairComponents(components1, components2), [ ( dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None), dict(baseGlyph="A", transformation=(0, 0, 0, 0, 0, 0), identifier=None) ), ( dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier=None), dict(baseGlyph="B", transformation=(0, 0, 0, 0, 0, 0), identifier=None) ), ] ) def test_processMathOneComponents(self): components = [ ( dict(baseGlyph="A", transformation=(1, 3, 5, 7, 9, 11), identifier="1"), dict(baseGlyph="A", transformation=(12, 14, 16, 18, 20, 22), identifier=None) ) ] self.assertEqual( _processMathOneComponents(components, addPt), [ dict(baseGlyph="A", transformation=(13, 17, 21, 25, 29, 33), identifier="1") ] ) def test_processMathTwoComponents(self): components = [ dict(baseGlyph="A", transformation=(1, 2, 3, 4, 5, 6), identifier="1"), ] self.assertEqual( _processMathTwoComponents(components, (2, 1.5), mulPt), [ dict(baseGlyph="A", transformation=(2, 4, 4.5, 6, 10, 9), identifier="1") ] ) def test_expandImage(self): self.assertEqual( _expandImage(None), dict(fileName=None, transformation=(1, 0, 0, 1, 0, 0), color=None) ) self.assertEqual( _expandImage(dict(fileName="foo")), dict(fileName="foo", transformation=(1, 0, 0, 1, 0, 0), color=None) ) def test_compressImage(self): self.assertEqual( _compressImage( dict(fileName="foo", transformation=(1, 0, 0, 1, 0, 0), color=None)), dict(fileName="foo", color=None, xScale=1, xyScale=0, yxScale=0, yScale=1, xOffset=0, yOffset=0) ) def test_pairImages(self): image1 = dict(fileName="foo", transformation=(1, 0, 0, 1, 0, 0), color=None) image2 = dict(fileName="foo", transformation=(2, 0, 0, 2, 0, 0), color="0,0,0,0") self.assertEqual( _pairImages(image1, image2), (image1, image2) ) image1 = dict(fileName="foo", transformation=(1, 0, 0, 1, 0, 0), color=None) image2 = dict(fileName="bar", transformation=(1, 0, 0, 1, 0, 0), color=None) self.assertEqual( _pairImages(image1, image2), () ) def test_processMathOneImage(self): image1 = dict(fileName="foo", transformation=(1, 3, 5, 7, 9, 11), color="0,0,0,0") image2 = dict(fileName="bar", transformation=(12, 14, 16, 18, 20, 22), color=None) self.assertEqual( _processMathOneImage((image1, image2), addPt), dict(fileName="foo", transformation=(13, 17, 21, 25, 29, 33), color="0,0,0,0") ) def test_processMathTwoImage(self): image = dict(fileName="foo", transformation=(1, 2, 3, 4, 5, 6), color="0,0,0,0") self.assertEqual( _processMathTwoImage(image, (2, 1.5), mulPt), dict(fileName="foo", transformation=(2, 4, 4.5, 6, 10, 9), color="0,0,0,0") ) def test_processMathOneTransformation(self): transformation1 = (1, 3, 5, 7, 9, 11) transformation2 = (12, 14, 16, 18, 20, 22) self.assertEqual( _processMathOneTransformation(transformation1, transformation2, addPt), (13, 17, 21, 25, 29, 33) ) def test_processMathTwoTransformation(self): transformation = (1, 2, 3, 4, 5, 6) self.assertEqual( _processMathTwoTransformation(transformation, (2, 1.5), mulPt), (2, 4, 4.5, 6, 10, 9) ) def test_roundContours(self): contour = [ dict(identifier="contour 1", points=[("line", (0.55, 3.1), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (0.55, 3.1), True, "test", "1")]) ] self.assertEqual( _roundContours(contour), [ dict(identifier="contour 1", points=[("line", (1, 3), False, "test", "1")]), dict(identifier="contour 1", points=[("line", (1, 3), True, "test", "1")]) ] ) def test_roundTransformation(self): transformation = (1, 2, 3, 4, 4.99, 6.01) self.assertEqual( _roundTransformation(transformation), (1, 2, 3, 4, 5, 6) ) def test_roundImage(self): image = dict(fileName="foo", transformation=(1, 2, 3, 4, 4.99, 6.01), color="0,0,0,0") self.assertEqual( _roundImage(image), dict(fileName="foo", transformation=(1, 2, 3, 4, 5, 6), color="0,0,0,0") ) def test_roundComponents(self): components = [ dict(baseGlyph="A", transformation=(1, 2, 3, 4, 5.1, 5.99), identifier="1"), ] self.assertEqual( _roundComponents(components), [ dict(baseGlyph="A", transformation=(1, 2, 3, 4, 5, 6), identifier="1") ] ) def test_roundAnchors(self): anchors = [ dict(x=99.9, y=-100.1, name="foo", identifier="1", color="0,0,0,0") ] self.assertEqual( _roundAnchors(anchors), [ dict(x=100, y=-100, name="foo", identifier="1", color="0,0,0,0") ] ) if __name__ == "__main__": unittest.main() fontMath-0.5.2/Lib/fontMath/test/test_mathGuideline.py000066400000000000000000000176321356720430400227500ustar00rootroot00000000000000import unittest from fontMath.mathFunctions import add, addPt, mul, _roundNumber from fontMath.mathGuideline import ( _expandGuideline, _compressGuideline, _pairGuidelines, _processMathOneGuidelines, _processMathTwoGuidelines, _roundGuidelines ) class MathGuidelineTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def test_expandGuideline(self): guideline = dict(x=100, y=None, angle=None) self.assertEqual( sorted(_expandGuideline(guideline).items()), [('angle', 90), ('x', 100), ('y', 0)] ) guideline = dict(y=100, x=None, angle=None) self.assertEqual( sorted(_expandGuideline(guideline).items()), [('angle', 0), ('x', 0), ('y', 100)] ) def test_compressGuideline(self): guideline = dict(x=100, y=0, angle=90) self.assertEqual( sorted(_compressGuideline(guideline).items()), [('angle', None), ('x', 100), ('y', None)] ) guideline = dict(x=100, y=0, angle=270) self.assertEqual( sorted(_compressGuideline(guideline).items()), [('angle', None), ('x', 100), ('y', None)] ) guideline = dict(y=100, x=0, angle=0) self.assertEqual( sorted(_compressGuideline(guideline).items()), [('angle', None), ('x', None), ('y', 100)] ) guideline = dict(y=100, x=0, angle=180) self.assertEqual( sorted(_compressGuideline(guideline).items()), [('angle', None), ('x', None), ('y', 100)] ) def test_pairGuidelines_name_identifier_x_y_angle(self): guidelines1 = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="2", x=3, y=4, angle=2), ] guidelines2 = [ dict(name="foo", identifier="2", x=3, y=4, angle=2), dict(name="foo", identifier="1", x=1, y=2, angle=1) ] expected = [ ( dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="1", x=1, y=2, angle=1) ), ( dict(name="foo", identifier="2", x=3, y=4, angle=2), dict(name="foo", identifier="2", x=3, y=4, angle=2) ) ] self.assertEqual( _pairGuidelines(guidelines1, guidelines2), expected ) def test_pairGuidelines_name_identifier(self): guidelines1 = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="2", x=1, y=2, angle=2), ] guidelines2 = [ dict(name="foo", identifier="2", x=3, y=4, angle=3), dict(name="foo", identifier="1", x=3, y=4, angle=4) ] expected = [ ( dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="1", x=3, y=4, angle=4) ), ( dict(name="foo", identifier="2", x=1, y=2, angle=2), dict(name="foo", identifier="2", x=3, y=4, angle=3) ) ] self.assertEqual( _pairGuidelines(guidelines1, guidelines2), expected ) def test_pairGuidelines_name_x_y_angle(self): guidelines1 = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="2", x=3, y=4, angle=2), ] guidelines2 = [ dict(name="foo", identifier="3", x=3, y=4, angle=2), dict(name="foo", identifier="4", x=1, y=2, angle=1) ] expected = [ ( dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="4", x=1, y=2, angle=1) ), ( dict(name="foo", identifier="2", x=3, y=4, angle=2), dict(name="foo", identifier="3", x=3, y=4, angle=2) ) ] self.assertEqual( _pairGuidelines(guidelines1, guidelines2), expected ) def test_pairGuidelines_identifier_x_y_angle(self): guidelines1 = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="bar", identifier="2", x=3, y=4, angle=2), ] guidelines2 = [ dict(name="xxx", identifier="2", x=3, y=4, angle=2), dict(name="yyy", identifier="1", x=1, y=2, angle=1) ] expected = [ ( dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="yyy", identifier="1", x=1, y=2, angle=1) ), ( dict(name="bar", identifier="2", x=3, y=4, angle=2), dict(name="xxx", identifier="2", x=3, y=4, angle=2) ) ] self.assertEqual( _pairGuidelines(guidelines1, guidelines2), expected ) def test_pairGuidelines_name(self): guidelines1 = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="bar", identifier="2", x=1, y=2, angle=2), ] guidelines2 = [ dict(name="bar", identifier="3", x=3, y=4, angle=3), dict(name="foo", identifier="4", x=3, y=4, angle=4) ] expected = [ ( dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="foo", identifier="4", x=3, y=4, angle=4) ), ( dict(name="bar", identifier="2", x=1, y=2, angle=2), dict(name="bar", identifier="3", x=3, y=4, angle=3) ) ] self.assertEqual( _pairGuidelines(guidelines1, guidelines2), expected ) def test_pairGuidelines_identifier(self): guidelines1 = [ dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="bar", identifier="2", x=1, y=2, angle=2), ] guidelines2 = [ dict(name="xxx", identifier="2", x=3, y=4, angle=3), dict(name="yyy", identifier="1", x=3, y=4, angle=4) ] expected = [ ( dict(name="foo", identifier="1", x=1, y=2, angle=1), dict(name="yyy", identifier="1", x=3, y=4, angle=4) ), ( dict(name="bar", identifier="2", x=1, y=2, angle=2), dict(name="xxx", identifier="2", x=3, y=4, angle=3) ) ] self.assertEqual( _pairGuidelines(guidelines1, guidelines2), expected ) def test_processMathOneGuidelines(self): guidelines = [ ( dict(x=1, y=3, angle=5, name="test", identifier="1", color="0,0,0,0"), dict(x=6, y=8, angle=10, name=None, identifier=None, color=None) ) ] expected = [ dict(x=7, y=11, angle=15, name="test", identifier="1", color="0,0,0,0") ] self.assertEqual( _processMathOneGuidelines(guidelines, addPt, add), expected ) def test_processMathTwoGuidelines(self): guidelines = [ dict(x=2, y=3, angle=5, name="test", identifier="1", color="0,0,0,0") ] expected = [ dict(x=4, y=4.5, angle=3.75, name="test", identifier="1", color="0,0,0,0") ] result = _processMathTwoGuidelines(guidelines, (2, 1.5), mul) result[0]["angle"] = _roundNumber(result[0]["angle"], 2) self.assertEqual(result, expected) def test_roundGuidelines(self): guidelines = [ dict(x=1.99, y=3.01, angle=5, name="test", identifier="1", color="0,0,0,0") ] expected = [ dict(x=2, y=3, angle=5, name="test", identifier="1", color="0,0,0,0") ] result = _roundGuidelines(guidelines) self.assertEqual(result, expected) fontMath-0.5.2/Lib/fontMath/test/test_mathInfo.py000066400000000000000000000267631356720430400217430ustar00rootroot00000000000000import unittest from fontMath.mathFunctions import _roundNumber from fontMath.mathInfo import MathInfo, _numberListAttrs class MathInfoTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) self.maxDiff = None def test_add(self): info1 = MathInfo(_TestInfoObject()) info2 = MathInfo(_TestInfoObject()) info3 = info1 + info2 written = {} expected = {} for attr, value in _testData.items(): if value is None: continue written[attr] = getattr(info3, attr) if isinstance(value, list): expectedValue = [v + v for v in value] else: expectedValue = value + value expected[attr] = expectedValue self.assertEqual(sorted(expected.items()), sorted(written.items())) def test_add_data_subset_1st_operand(self): info1 = MathInfo(_TestInfoObject(_testDataSubset)) info2 = MathInfo(_TestInfoObject()) info3 = info1 + info2 written = {} expected = {} for attr, value in _testData.items(): if value is None: continue written[attr] = getattr(info3, attr) if isinstance(value, list): expectedValue = [v + v for v in value] else: expectedValue = value + value expected[attr] = expectedValue self.assertEqual(sorted(expected), sorted(written)) # self.assertEqual(sorted(expected.items()), sorted(written.items())) def test_add_data_subset_2nd_operand(self): info1 = MathInfo(_TestInfoObject()) info2 = MathInfo(_TestInfoObject(_testDataSubset)) info3 = info1 + info2 written = {} expected = {} for attr, value in _testData.items(): if value is None: continue written[attr] = getattr(info3, attr) if isinstance(value, list): expectedValue = [v + v for v in value] else: expectedValue = value + value expected[attr] = expectedValue self.assertEqual(sorted(expected), sorted(written)) # self.assertEqual(sorted(expected.items()), sorted(written.items())) def test_sub(self): info1 = MathInfo(_TestInfoObject()) info2 = MathInfo(_TestInfoObject()) info3 = info1 - info2 written = {} expected = {} for attr, value in _testData.items(): if value is None: continue written[attr] = getattr(info3, attr) if isinstance(value, list): expectedValue = [v - v for v in value] else: expectedValue = value - value expected[attr] = expectedValue self.assertEqual(sorted(expected.items()), sorted(written.items())) def test_mul(self): info1 = MathInfo(_TestInfoObject()) info2 = info1 * 2.5 written = {} expected = {} for attr, value in _testData.items(): if value is None: continue written[attr] = getattr(info2, attr) if isinstance(value, list): expectedValue = [v * 2.5 for v in value] else: expectedValue = value * 2.5 expected[attr] = expectedValue self.assertEqual(sorted(expected.items()), sorted(written.items())) def test_mul_data_subset(self): info1 = MathInfo(_TestInfoObject(_testDataSubset)) info2 = info1 * 2.5 written = {} expected = {} for attr, value in _testDataSubset.items(): if value is None: continue written[attr] = getattr(info2, attr) expected = {"descender": -500.0, "guidelines": [{"y": 250.0, "x": 0.0, "identifier": "2", "angle": 0.0, "name": "bar"}], "postscriptBlueValues": [-25.0, 0.0, 1000.0, 1025.0, 1625.0], "unitsPerEm": 2500.0} self.assertEqual(sorted(expected.items()), sorted(written.items())) def test_div(self): info1 = MathInfo(_TestInfoObject()) info2 = info1 / 2 written = {} expected = {} for attr, value in _testData.items(): if value is None: continue written[attr] = getattr(info2, attr) if isinstance(value, list): expectedValue = [v / 2 for v in value] else: expectedValue = value / 2 expected[attr] = expectedValue self.assertEqual(sorted(expected), sorted(written)) def test_compare_same(self): info1 = MathInfo(_TestInfoObject()) info2 = MathInfo(_TestInfoObject()) self.assertFalse(info1 < info2) self.assertFalse(info1 > info2) self.assertEqual(info1, info2) def test_compare_different(self): info1 = MathInfo(_TestInfoObject()) info2 = MathInfo(_TestInfoObject()) info2.ascender = info2.ascender - 1 self.assertFalse(info1 < info2) self.assertTrue(info1 > info2) self.assertNotEqual(info1, info2) def test_weight_name(self): info1 = MathInfo(_TestInfoObject()) info2 = MathInfo(_TestInfoObject()) info2.openTypeOS2WeightClass = 0 info3 = info1 + info2 self.assertEqual(info3.openTypeOS2WeightClass, 500) self.assertEqual(info3.postscriptWeightName, "Medium") info2.openTypeOS2WeightClass = 49 info3 = info1 + info2 self.assertEqual(info3.openTypeOS2WeightClass, 549) self.assertEqual(info3.postscriptWeightName, "Medium") info2.openTypeOS2WeightClass = 50 info3 = info1 + info2 self.assertEqual(info3.openTypeOS2WeightClass, 550) self.assertEqual(info3.postscriptWeightName, "Semi-bold") info2.openTypeOS2WeightClass = 50 info3 = info1 - info2 self.assertEqual(info3.openTypeOS2WeightClass, 450) self.assertEqual(info3.postscriptWeightName, "Medium") info2.openTypeOS2WeightClass = 51 info3 = info1 - info2 self.assertEqual(info3.openTypeOS2WeightClass, 449) self.assertEqual(info3.postscriptWeightName, "Normal") info2.openTypeOS2WeightClass = 500 info3 = info1 - info2 self.assertEqual(info3.openTypeOS2WeightClass, 0) self.assertEqual(info3.postscriptWeightName, "Thin") info2.openTypeOS2WeightClass = 1500 info3 = info1 - info2 self.assertEqual(info3.openTypeOS2WeightClass, -1000) self.assertEqual(info3.postscriptWeightName, "Thin") info2.openTypeOS2WeightClass = 500 info3 = info1 + info2 self.assertEqual(info3.openTypeOS2WeightClass, 1000) self.assertEqual(info3.postscriptWeightName, "Black") def test_round(self): m = _TestInfoObject() m.ascender = 699.99 m.descender = -199.99 m.xHeight = 399.66 m.postscriptSlantAngle = None m.postscriptStemSnapH = [80.1, 90.2] m.guidelines = [{'y': 100.99, 'x': None, 'angle': None, 'name': 'bar'}] m.italicAngle = -9.4 m.postscriptBlueScale = 0.137 info = MathInfo(m) info = info.round() self.assertEqual(info.ascender, 700) self.assertEqual(info.descender, -200) self.assertEqual(info.xHeight, 400) self.assertEqual(m.italicAngle, -9.4) self.assertEqual(m.postscriptBlueScale, 0.137) self.assertIsNone(info.postscriptSlantAngle) self.assertEqual(info.postscriptStemSnapH, [80, 90]) self.assertEqual( [sorted(gl.items()) for gl in info.guidelines], [[('angle', 0), ('name', 'bar'), ('x', 0), ('y', 101)]] ) written = {} expected = {} for attr, value in _testData.items(): if value is None: continue written[attr] = getattr(info, attr) if isinstance(value, list): expectedValue = [_roundNumber(v) for v in value] else: expectedValue = _roundNumber(value) expected[attr] = expectedValue self.assertEqual(sorted(expected), sorted(written)) def test_sub_undefined_number_list_sets_None(self): self.assertIn("postscriptBlueValues", _numberListAttrs) info1 = _TestInfoObject() info1.postscriptBlueValues = None m1 = MathInfo(info1) info2 = _TestInfoObject() info2.postscriptBlueValues = [1, 2, 3] m2 = MathInfo(info2) m3 = m2 - m1 self.assertIsNone(m3.postscriptBlueValues) m4 = m1 - m2 self.assertIsNone(m4.postscriptBlueValues) def test_number_lists_with_different_lengths(self): self.assertIn("postscriptBlueValues", _numberListAttrs) info1 = _TestInfoObject() info1.postscriptBlueValues = [1, 2] m1 = MathInfo(info1) info2 = _TestInfoObject() info2.postscriptBlueValues = [1, 2, 3] m2 = MathInfo(info2) m3 = m2 - m1 self.assertIsNone(m3.postscriptBlueValues) m4 = m1 - m2 self.assertIsNone(m4.postscriptBlueValues) m5 = m1 + m2 self.assertIsNone(m5.postscriptBlueValues) # ---- # Test Data # ---- _testData = dict( # generic unitsPerEm=1000, descender=-200, xHeight=400, capHeight=650, ascender=700, italicAngle=0, # head openTypeHeadLowestRecPPEM=5, # hhea openTypeHheaAscender=700, openTypeHheaDescender=-200, openTypeHheaLineGap=200, openTypeHheaCaretSlopeRise=1, openTypeHheaCaretSlopeRun=1, openTypeHheaCaretOffset=1, # OS/2 openTypeOS2WidthClass=5, openTypeOS2WeightClass=500, openTypeOS2TypoAscender=700, openTypeOS2TypoDescender=-200, openTypeOS2TypoLineGap=200, openTypeOS2WinAscent=700, openTypeOS2WinDescent=-200, openTypeOS2SubscriptXSize=300, openTypeOS2SubscriptYSize=300, openTypeOS2SubscriptXOffset=0, openTypeOS2SubscriptYOffset=-200, openTypeOS2SuperscriptXSize=300, openTypeOS2SuperscriptYSize=300, openTypeOS2SuperscriptXOffset=0, openTypeOS2SuperscriptYOffset=500, openTypeOS2StrikeoutSize=50, openTypeOS2StrikeoutPosition=300, # Vhea openTypeVheaVertTypoAscender=700, openTypeVheaVertTypoDescender=-200, openTypeVheaVertTypoLineGap=200, openTypeVheaCaretSlopeRise=1, openTypeVheaCaretSlopeRun=1, openTypeVheaCaretOffset=1, # postscript postscriptSlantAngle=0, postscriptUnderlineThickness=100, postscriptUnderlinePosition=-150, postscriptBlueValues=[-10, 0, 400, 410, 650, 660, 700, 710], postscriptOtherBlues=[-210, -200], postscriptFamilyBlues=[-10, 0, 400, 410, 650, 660, 700, 710], postscriptFamilyOtherBlues=[-210, -200], postscriptStemSnapH=[80, 90], postscriptStemSnapV=[110, 130], postscriptBlueFuzz=1, postscriptBlueShift=7, postscriptBlueScale=0.039625, postscriptDefaultWidthX=400, postscriptNominalWidthX=400, # guidelines guidelines=None ) _testDataSubset = dict( # generic unitsPerEm=1000, descender=-200, xHeight=None, # postscript postscriptBlueValues=[-10, 0, 400, 410, 650], # guidelines guidelines=[ {'y': 100, 'x': None, 'angle': None, 'name': 'bar', 'identifier': '2'} ] ) class _TestInfoObject(object): def __init__(self, data=_testData): for attr, value in data.items(): setattr(self, attr, value) if __name__ == "__main__": unittest.main() fontMath-0.5.2/Lib/fontMath/test/test_mathKerning.py000066400000000000000000000427231356720430400224370ustar00rootroot00000000000000from __future__ import unicode_literals import unittest from fontMath.mathFunctions import _roundNumber from fontMath.mathKerning import MathKerning class TestKerning(object): """Mockup Kerning class""" def __init__(self): self._kerning = {} def clear(self): self._kerning = {} def asDict(self, returnIntegers=True): if not returnIntegers: return self._kerning kerning = {k: _roundNumber(v) for (k, v) in self._kerning.items()} return kerning def update(self, kerningDict): self._kerning = {k: v for (k, v) in kerningDict.items() if v != 0} class TestFont(object): """Mockup Font class""" def __init__(self): self.kerning = TestKerning() self.groups = {} class MathKerningTest(unittest.TestCase): def test_addTo(self): kerning = { ("A", "A"): 1, ("B", "B"): -1, } obj = MathKerning(kerning) obj.addTo(1) self.assertEqual(sorted(obj.items()), [(('A', 'A'), 2), (('B', 'B'), 0)]) def test_getitem(self): kerning = { ("public.kern1.A", "public.kern2.A"): 1, ("A1", "public.kern2.A"): 2, ("public.kern1.A", "A2"): 3, ("A3", "A3"): 4, } groups = { "public.kern1.A": ["A", "A1", "A2", "A3"], "public.kern2.A": ["A", "A1", "A2", "A3"], } obj = MathKerning(kerning, groups) self.assertEqual(obj["A", "A"], 1) self.assertEqual(obj["A1", "A"], 2) self.assertEqual(obj["A", "A2"], 3) self.assertEqual(obj["A3", "A3"], 4) self.assertEqual(obj["X", "X"], 0) self.assertEqual(obj["A3", "public.kern2.A"], 1) self.assertEqual(sorted(obj.keys())[1], ("A3", "A3")) self.assertEqual(sorted(obj.values())[3], 4) def test_guessPairType(self): kerning = { ("public.kern1.A", "public.kern2.A"): 1, ("A1", "public.kern2.A"): 2, ("public.kern1.A", "A2"): 3, ("A3", "A3"): 4, ("public.kern1.B", "public.kern2.B"): 5, ("public.kern1.B", "B"): 6, ("public.kern1.C", "public.kern2.C"): 7, ("C", "public.kern2.C"): 8, } groups = { "public.kern1.A": ["A", "A1", "A2", "A3"], "public.kern2.A": ["A", "A1", "A2", "A3"], "public.kern1.B": ["B"], "public.kern2.B": ["B"], "public.kern1.C": ["C"], "public.kern2.C": ["C"], } obj = MathKerning(kerning, groups) self.assertEqual(obj.guessPairType(("public.kern1.A", "public.kern2.A")), ('group', 'group')) self.assertEqual(obj.guessPairType(("A1", "public.kern2.A")), ('exception', 'group')) self.assertEqual(obj.guessPairType(("public.kern1.A", "A2")), ('group', 'exception')) self.assertEqual(obj.guessPairType(("A3", "A3")), ('exception', 'exception')) self.assertEqual(obj.guessPairType(("A", "A")), ('group', 'group')) self.assertEqual(obj.guessPairType(("B", "B")), ('group', 'exception')) self.assertEqual(obj.guessPairType(("C", "C")), ('exception', 'group')) def test_copy(self): kerning1 = { ("A", "A"): 1, ("B", "B"): 1, ("NotIn2", "NotIn2"): 1, ("public.kern1.NotIn2", "C"): 1, ("public.kern1.D", "public.kern2.D"): 1, } groups1 = { "public.kern1.NotIn1": ["C"], "public.kern1.D": ["D", "H"], "public.kern2.D": ["D", "H"], } obj1 = MathKerning(kerning1, groups1) obj2 = obj1.copy() self.assertEqual(sorted(obj1.items()), sorted(obj2.items())) def test_add(self): kerning1 = { ("A", "A"): 1, ("B", "B"): 1, ("NotIn2", "NotIn2"): 1, ("public.kern1.NotIn2", "C"): 1, ("public.kern1.D", "public.kern2.D"): 1, } groups1 = { "public.kern1.NotIn1": ["C"], "public.kern1.D": ["D", "H"], "public.kern2.D": ["D", "H"], } kerning2 = { ("A", "A"): -1, ("B", "B"): 1, ("NotIn1", "NotIn1"): 1, ("public.kern1.NotIn1", "C"): 1, ("public.kern1.D", "public.kern2.D"): 1, } groups2 = { "public.kern1.NotIn2": ["C"], "public.kern1.D": ["D", "H"], "public.kern2.D": ["D", "H"], } obj = MathKerning(kerning1, groups1) + MathKerning(kerning2, groups2) self.assertEqual( sorted(obj.items()), [(('B', 'B'), 2), (('NotIn1', 'NotIn1'), 1), (('NotIn2', 'NotIn2'), 1), (('public.kern1.D', 'public.kern2.D'), 2), (('public.kern1.NotIn1', 'C'), 1), (('public.kern1.NotIn2', 'C'), 1)]) self.assertEqual( obj.groups()["public.kern1.D"], ['D', 'H']) self.assertEqual( obj.groups()["public.kern2.D"], ['D', 'H']) def test_add_same_groups(self): kerning1 = { ("A", "A"): 1, ("B", "B"): 1, ("NotIn2", "NotIn2"): 1, ("public.kern1.NotIn2", "C"): 1, ("public.kern1.D", "public.kern2.D"): 1, } groups1 = { "public.kern1.D": ["D", "H"], "public.kern2.D": ["D", "H"], } kerning2 = { ("A", "A"): -1, ("B", "B"): 1, ("NotIn1", "NotIn1"): 1, ("public.kern1.NotIn1", "C"): 1, ("public.kern1.D", "public.kern2.D"): 1, } groups2 = { "public.kern1.D": ["D", "H"], "public.kern2.D": ["D", "H"], } obj = MathKerning(kerning1, groups1) + MathKerning(kerning2, groups2) self.assertEqual( sorted(obj.items()), [(('B', 'B'), 2), (('NotIn1', 'NotIn1'), 1), (('NotIn2', 'NotIn2'), 1), (('public.kern1.D', 'public.kern2.D'), 2), (('public.kern1.NotIn1', 'C'), 1), (('public.kern1.NotIn2', 'C'), 1)]) self.assertEqual( obj.groups()["public.kern1.D"], ['D', 'H']) self.assertEqual( obj.groups()["public.kern2.D"], ['D', 'H']) def test_sub(self): kerning1 = { ("A", "A"): 1, ("B", "B"): 1, ("NotIn2", "NotIn2"): 1, ("public.kern1.NotIn2", "C"): 1, ("public.kern1.D", "public.kern2.D"): 1, } groups1 = { "public.kern1.NotIn1": ["C"], "public.kern1.D": ["D", "H"], "public.kern2.D": ["D", "H"], } kerning2 = { ("A", "A"): -1, ("B", "B"): 1, ("NotIn1", "NotIn1"): 1, ("public.kern1.NotIn1", "C"): 1, ("public.kern1.D", "public.kern2.D"): 1, } groups2 = { "public.kern1.NotIn2": ["C"], "public.kern1.D": ["D"], "public.kern2.D": ["D", "H"], } obj = MathKerning(kerning1, groups1) - MathKerning(kerning2, groups2) self.assertEqual( sorted(obj.items()), [(('A', 'A'), 2), (('NotIn1', 'NotIn1'), -1), (('NotIn2', 'NotIn2'), 1), (('public.kern1.NotIn1', 'C'), -1), (('public.kern1.NotIn2', 'C'), 1)]) self.assertEqual( obj.groups()["public.kern1.D"], ['D', 'H']) self.assertEqual( obj.groups()["public.kern2.D"], ['D', 'H']) def test_mul(self): kerning = { ("A", "A"): 0, ("B", "B"): 1, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 2, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } obj = MathKerning(kerning, groups) * 2 self.assertEqual( sorted(obj.items()), [(('B', 'B'), 2), (('C2', 'public.kern2.C'), 0), (('public.kern1.C', 'public.kern2.C'), 4)]) def test_mul_tuple_factor(self): kerning = { ("A", "A"): 0, ("B", "B"): 1, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 2, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } obj = MathKerning(kerning, groups) * (3, 2) self.assertEqual( sorted(obj.items()), [(('B', 'B'), 3), (('C2', 'public.kern2.C'), 0), (('public.kern1.C', 'public.kern2.C'), 6)]) def test_rmul(self): kerning = { ("A", "A"): 0, ("B", "B"): 1, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 2, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } obj = 2 * MathKerning(kerning, groups) self.assertEqual( sorted(obj.items()), [(('B', 'B'), 2), (('C2', 'public.kern2.C'), 0), (('public.kern1.C', 'public.kern2.C'), 4)]) def test_rmul_tuple_factor(self): kerning = { ("A", "A"): 0, ("B", "B"): 1, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 2, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } obj = (3, 2) * MathKerning(kerning, groups) self.assertEqual( sorted(obj.items()), [(('B', 'B'), 3), (('C2', 'public.kern2.C'), 0), (('public.kern1.C', 'public.kern2.C'), 6)]) def test_div(self): kerning = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } obj = MathKerning(kerning, groups) / 2 self.assertEqual( sorted(obj.items()), [(('B', 'B'), 2), (('C2', 'public.kern2.C'), 0), (('public.kern1.C', 'public.kern2.C'), 2)]) def test_compare_same_kerning_only(self): kerning1 = { ("A", "A"): 0, ("B", "B"): 4, } kerning2 = { ("A", "A"): 0, ("B", "B"): 4, } mathKerning1 = MathKerning(kerning1, {}) mathKerning2 = MathKerning(kerning2, {}) self.assertFalse(mathKerning1 < mathKerning2) self.assertFalse(mathKerning1 > mathKerning2) self.assertEqual(mathKerning1, mathKerning2) def test_compare_same_kerning_same_groups(self): kerning1 = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } kerning2 = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } mathKerning1 = MathKerning(kerning1, groups) mathKerning2 = MathKerning(kerning2, groups) self.assertFalse(mathKerning1 < mathKerning2) self.assertFalse(mathKerning1 > mathKerning2) self.assertEqual(mathKerning1, mathKerning2) def test_compare_diff_kerning_diff_groups(self): kerning1 = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } kerning2 = { ("A", "A"): 0, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } groups1 = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2", "C3"], } groups2 = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } mathKerning1 = MathKerning(kerning1, groups1) mathKerning2 = MathKerning(kerning2, groups2) self.assertFalse(mathKerning1 < mathKerning2) self.assertTrue(mathKerning1 > mathKerning2) self.assertNotEqual(mathKerning1, mathKerning2) def test_compare_diff_kerning_same_groups(self): kerning1 = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } kerning2 = { ("A", "A"): 0, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } mathKerning1 = MathKerning(kerning1, groups) mathKerning2 = MathKerning(kerning2, groups) self.assertFalse(mathKerning1 < mathKerning2) self.assertTrue(mathKerning1 > mathKerning2) self.assertNotEqual(mathKerning1, mathKerning2) def test_compare_same_kerning_diff_groups(self): kerning1 = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } kerning2 = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } groups1 = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } groups2 = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2", "C3"], } mathKerning1 = MathKerning(kerning1, groups1) mathKerning2 = MathKerning(kerning2, groups2) self.assertTrue(mathKerning1 < mathKerning2) self.assertFalse(mathKerning1 > mathKerning2) self.assertNotEqual(mathKerning1, mathKerning2) def test_div_tuple_factor(self): kerning = { ("A", "A"): 0, ("B", "B"): 4, ("C2", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 4, } groups = { "public.kern1.C": ["C1", "C2"], "public.kern2.C": ["C1", "C2"], } obj = MathKerning(kerning, groups) / (4, 2) self.assertEqual( sorted(obj.items()), [(('B', 'B'), 1), (('C2', 'public.kern2.C'), 0), (('public.kern1.C', 'public.kern2.C'), 1)]) def test_round(self): kerning = { ("A", "A"): 1.99, ("B", "B"): 4, ("C", "C"): 7, ("D", "D"): 9.01, } obj = MathKerning(kerning) obj.round(5) self.assertEqual( sorted(obj.items()), [(('A', 'A'), 0), (('B', 'B'), 5), (('C', 'C'), 5), (('D', 'D'), 10)]) def test_cleanup(self): kerning = { ("A", "A"): 0, ("B", "B"): 1, ("C", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 1, ("D", "D"): 1.0, ("E", "E"): 1.2, } groups = { "public.kern1.C": ["C", "C1"], "public.kern2.C": ["C", "C1"] } obj = MathKerning(kerning, groups) obj.cleanup() self.assertEqual( sorted(obj.items()), [(('B', 'B'), 1), (('C', 'public.kern2.C'), 0), (('D', 'D'), 1), (('E', 'E'), 1.2), (('public.kern1.C', 'public.kern2.C'), 1)]) def test_extractKerning(self): kerning = { ("A", "A"): 0, ("B", "B"): 1, ("C", "public.kern2.C"): 0, ("public.kern1.C", "public.kern2.C"): 1, ("D", "D"): 1.0, ("E", "E"): 1.2, } groups = { "public.kern1.C": ["C", "C1"], "public.kern2.C": ["C", "C1"] } font = TestFont() self.assertEqual(font.kerning.asDict(), {}) self.assertEqual(list(font.groups.items()), []) obj = MathKerning(kerning, groups) obj.extractKerning(font) self.assertEqual( sorted(font.kerning.asDict().items()), [(('B', 'B'), 1), (('D', 'D'), 1), (('E', 'E'), 1), (('public.kern1.C', 'public.kern2.C'), 1)]) self.assertEqual( sorted(font.groups.items()), [('public.kern1.C', ['C', 'C1']), ('public.kern2.C', ['C', 'C1'])]) def test_fallback(self): groups = { "public.kern1.A" : ["A", "A.alt"], "public.kern2.O" : ["O", "O.alt"] } kerning1 = { ("A", "O") : 1000, ("public.kern1.A", "public.kern2.O") : 100 } kerning2 = { ("public.kern1.A", "public.kern2.O") : 200 } kerning1 = MathKerning(kerning1, groups) kerning2 = MathKerning(kerning2, groups) kerning3 = kerning1 + kerning2 self.assertEqual( kerning3["A", "O"], 1200) if __name__ == "__main__": unittest.main() fontMath-0.5.2/Lib/fontMath/test/test_mathTransform.py000066400000000000000000000110171356720430400230050ustar00rootroot00000000000000import unittest import math from random import random from fontMath.mathFunctions import _roundNumber from fontMath.mathTransform import ( Transform, FontMathWarning, matrixToMathTransform, mathTransformToMatrix, _polarDecomposeInterpolationTransformation, _mathPolarDecomposeInterpolationTransformation, _linearInterpolationTransformMatrix ) class MathTransformToFunctionsTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) def test_matrixToTransform(self): pass def test_TransformToMatrix(self): pass class ShallowTransformTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) _testData = [ ( Transform().rotate(math.radians(0)), Transform().rotate(math.radians(90)) ), ( Transform().skew(math.radians(60), math.radians(10)), Transform().rotate(math.radians(90)) ), ( Transform().scale(.3, 1.3), Transform().rotate(math.radians(90)) ), ( Transform().scale(.3, 1.3).rotate(math.radians(-15)), Transform().rotate(math.radians(90)).scale(.7, .3) ), ( Transform().translate(250, 250).rotate(math.radians(-15)) .translate(-250, -250), Transform().translate(0, 400).rotate(math.radians(80)) .translate(-100, 0).rotate(math.radians(80)), ), ( Transform().skew(math.radians(50)).scale(1.5).rotate(math.radians(60)), Transform().rotate(math.radians(90)) ), ] class MathTransformTest(unittest.TestCase): def __init__(self, methodName): unittest.TestCase.__init__(self, methodName) # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, # and fires deprecation warnings if a program uses the old name. if not hasattr(self, "assertRaisesRegex"): self.assertRaisesRegex = self.assertRaisesRegexp # Disabling this test as it fails intermittently and it's not clear what it # does (cf. https://github.com/typesupply/fontMath/issues/35) # # def test_functions(self): # """ # In this test various complex transformations are interpolated using # 3 different methods: # - straight linear interpolation, the way glyphMath does it now. # - using the MathTransform interpolation method. # - using the ShallowTransform with an initial decompose and final # compose. # """ # value = random() # testFunctions = [ # _polarDecomposeInterpolationTransformation, # _mathPolarDecomposeInterpolationTransformation, # _linearInterpolationTransformMatrix, # ] # with self.assertRaisesRegex( # FontMathWarning, # "Minor differences occured when " # "comparing the interpolation functions."): # for i, m in enumerate(_testData): # m1, m2 = m # results = [] # for func in testFunctions: # r = func(m1, m2, value) # results.append(r) # if not results[0] == results[1]: # raise FontMathWarning( # "Minor differences occured when " # "comparing the interpolation functions.") def _wrapUnWrap(self, precision=12): """ Wrap and unwrap a matrix with random values to establish rounding error """ t1 = [] for i in range(6): t1.append(random()) m = matrixToMathTransform(t1) t2 = mathTransformToMatrix(m) if not sum([_roundNumber(t1[i] - t2[i], precision) for i in range(len(t1))]) == 0: raise FontMathWarning( "Matrix round-tripping failed for precision value %s." % (precision)) def test_wrapUnWrap(self): self._wrapUnWrap() def test_wrapUnWrapPrecision(self): """ Wrap and unwrap should have no rounding errors at least up to a precision value of 12. Rounding errors seem to start occuring at a precision value of 14. """ for p in range(5, 13): for i in range(1000): self._wrapUnWrap(p) with self.assertRaisesRegex( FontMathWarning, "Matrix round-tripping failed for precision value"): for p in range(14, 16): for i in range(1000): self._wrapUnWrap(p) fontMath-0.5.2/License.txt000066400000000000000000000020701356720430400154240ustar00rootroot00000000000000The MIT License Copyright (c) 2005-2009 Type Supply LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.fontMath-0.5.2/MANIFEST.in000066400000000000000000000001451356720430400150400ustar00rootroot00000000000000include License.txt include requirements.txt dev-requirements.txt include tox.ini include .coveragercfontMath-0.5.2/README.md000066400000000000000000000005101356720430400145550ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/robotools/fontMath.svg?branch=master)](https://travis-ci.org/robotools/fontMath) [![codecov](https://codecov.io/gh/robotools/fontMath/branch/master/graph/badge.svg)](https://codecov.io/gh/robotools/fontMath) # fontMath A collection of objects that implement fast font, glyph, etc. math. fontMath-0.5.2/appveyor.yml000066400000000000000000000015051356720430400156730ustar00rootroot00000000000000environment: matrix: - JOB: "2.7 32-bit" PYTHON_HOME: "C:\\Python27" - JOB: "3.6 64-bit" PYTHON_HOME: "C:\\Python36-x64" - JOB: "3.7 64-bit" PYTHON_HOME: "C:\\Python37-x64" skip_branch_with_pr: true matrix: fast_finish: true install: # Prepend Python to the PATH of this build - "SET PATH=%PYTHON_HOME%;%PYTHON_HOME%\\Scripts;%PATH%" # check that we have the expected version and architecture for Python - "python --version" - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" # upgrade pip to avoid out-of-date warnings - "python -m pip install --disable-pip-version-check --user --upgrade pip" - "python -m pip --version" # install the dependencies to run the tests - "python -m pip install -r dev-requirements.txt" build: false test_script: - "tox -e py" fontMath-0.5.2/dev-requirements.txt000066400000000000000000000000461356720430400173420ustar00rootroot00000000000000pytest>=2.8 virtualenv>=15.0 tox>=2.3 fontMath-0.5.2/requirements.txt000066400000000000000000000000221356720430400165600ustar00rootroot00000000000000fonttools==3.44.0 fontMath-0.5.2/setup.cfg000066400000000000000000000015551356720430400151310ustar00rootroot00000000000000[bumpversion] current_version = 0.5.2 commit = True tag = False tag_name = {new_version} parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\.(?P[a-z]+)(?P\d+))? serialize = {major}.{minor}.{patch}.{release}{dev} {major}.{minor}.{patch} [bumpversion:part:release] optional_value = final values = dev final [bumpversion:part:dev] [bumpversion:file:Lib/fontMath/__init__.py] search = __version__ = "{current_version}" replace = __version__ = "{new_version}" [bumpversion:file:setup.py] search = version="{current_version}" replace = version="{new_version}" [wheel] universal = 1 [sdist] formats = zip [aliases] test = pytest [metadata] license_file = License.txt [tool:pytest] minversion = 2.8 testpaths = Lib/fontMath python_files = test_*.py python_classes = *Test addopts = -v -r a --doctest-modules --doctest-ignore-import-errors fontMath-0.5.2/setup.py000077500000000000000000000026271356720430400150260ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function from setuptools import setup, find_packages import sys needs_pytest = {'pytest', 'test'}.intersection(sys.argv) pytest_runner = ['pytest_runner'] if needs_pytest else [] needs_wheel = {'bdist_wheel'}.intersection(sys.argv) wheel = ['wheel'] if needs_wheel else [] # with open('README.rst', 'r') as f: # long_description = f.read() setup( name="fontMath", version="0.5.2", description="A set of objects for performing math operations on font data.", # long_description=long_description, author="Tal Leming", author_email="tal@typesupply.com", url="https://github.com/robotools/fontMath", license="MIT", package_dir={"": "Lib"}, packages=find_packages("Lib"), test_suite="fontMath", setup_requires=pytest_runner + wheel, tests_require=[ 'pytest>=3.0.3', ], install_requires=[ "fonttools>=3.32.0", ], classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Multimedia :: Graphics :: Editors :: Vector-Based', 'Topic :: Software Development :: Libraries :: Python Modules', ], ) fontMath-0.5.2/tox.ini000066400000000000000000000010431356720430400146130ustar00rootroot00000000000000[tox] envlist = py{27,36,37} skip_missing_interpreters = true [testenv] deps = pytest -rrequirements.txt install_command = {envpython} -m pip install -v {opts} {packages} commands = # run the test suite against the package installed inside tox env py.test {posargs:--pyargs fontMath} [testenv:coverage] passenv = CI TRAVIS TRAVIS_* deps = {[testenv]deps} pytest-cov codecov skip_install = true commands= # measure test coverage and create html report py.test --cov --cov-report html {posargs} codecov