Skip to content

Commit

Permalink
Merge pull request #54 from fonttools/contour-component-bounds
Browse files Browse the repository at this point in the history
also add getBounds and getControlBounds for Contour and Component classes
  • Loading branch information
anthrotype authored Feb 21, 2020
2 parents 83ffcf5 + ed89903 commit 9b69d21
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 15 deletions.
8 changes: 7 additions & 1 deletion src/ufoLib2/objects/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fontTools.misc.transform import Identity, Transform
from fontTools.pens.pointPen import PointToSegmentPen

from .misc import _convert_transform
from .misc import _convert_transform, getBounds, getControlBounds


@attr.s(slots=True)
Expand All @@ -20,6 +20,12 @@ def move(self, delta):
x, y = delta
self.transformation = self.transformation.translate(x, y)

def getBounds(self, layer):
return getBounds(self, layer)

def getControlBounds(self, layer):
return getControlBounds(self, layer)

# -----------
# Pen methods
# -----------
Expand Down
12 changes: 12 additions & 0 deletions src/ufoLib2/objects/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from fontTools.pens.pointPen import PointToSegmentPen

from ufoLib2.objects.point import Point
from ufoLib2.objects.misc import getBounds, getControlBounds


@attr.s(slots=True)
Expand Down Expand Up @@ -49,6 +50,17 @@ def move(self, delta):
for point in self.points:
point.move(delta)

def getBounds(self, layer=None):
return getBounds(self, layer)

@property
def bounds(self):
# also add a property getter like defcon's, since we can...
return self.getBounds()

def getControlBounds(self, layer=None):
return getControlBounds(self, layer)

# -----------
# Pen methods
# -----------
Expand Down
13 changes: 3 additions & 10 deletions src/ufoLib2/objects/glyph.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from collections import namedtuple
from copy import deepcopy
from typing import Any, Dict, List, Optional, Union

import attr
from fontTools.misc.transform import Transform
from fontTools.pens.pointPen import PointToSegmentPen, SegmentToPointPen
from fontTools.pens.boundsPen import BoundsPen, ControlBoundsPen

from ufoLib2.objects.anchor import Anchor
from ufoLib2.objects.contour import Contour
from ufoLib2.objects.guideline import Guideline
from ufoLib2.objects.image import Image
from ufoLib2.objects.misc import getBounds, getControlBounds
from ufoLib2.pointPens.glyphPointPen import GlyphPointPen


Expand Down Expand Up @@ -244,23 +243,17 @@ def verticalOrigin(self, value):

# bounds and side-bearings

BoundingBox = namedtuple("BoundingBox", "xMin yMin xMax yMax")

def getBounds(self, layer=None):
if layer is None and self.components:
raise TypeError("layer is required to compute bounds of components")

pen = BoundsPen(layer)
self.draw(pen)
return pen.bounds if pen.bounds is None else self.BoundingBox(*pen.bounds)
return getBounds(self, layer)

def getControlBounds(self, layer=None):
if layer is None and self.components:
raise TypeError("layer is required to compute bounds of components")

pen = ControlBoundsPen(layer)
self.draw(pen)
return pen.bounds if pen.bounds is None else self.BoundingBox(*pen.bounds)
return getControlBounds(self, layer)

def getLeftMargin(self, layer=None):
bounds = self.getBounds(layer)
Expand Down
21 changes: 21 additions & 0 deletions src/ufoLib2/objects/misc.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
from collections import namedtuple
from collections.abc import Mapping, MutableMapping
from copy import deepcopy
from typing import Sequence, Union

import attr
from fontTools.misc.transform import Transform
from fontTools.pens.boundsPen import BoundsPen, ControlBoundsPen

# sentinel value to signal a "lazy" object hasn't been loaded yet
_NOT_LOADED = object()


BoundingBox = namedtuple("BoundingBox", "xMin yMin xMax yMax")


def getBounds(drawable, layer):
pen = BoundsPen(layer)
# raise 'KeyError' when a referenced component is missing from glyph set
pen.skipMissingComponents = False
drawable.draw(pen)
return pen.bounds if pen.bounds is None else BoundingBox(*pen.bounds)


def getControlBounds(drawable, layer):
pen = ControlBoundsPen(layer)
# raise 'KeyError' when a referenced component is missing from glyph set
pen.skipMissingComponents = False
drawable.draw(pen)
return pen.bounds if pen.bounds is None else BoundingBox(*pen.bounds)


def _deepcopy_unlazify_attrs(self, memo):

if getattr(self, "_lazy", True) and hasattr(self, "unlazify"):
Expand Down
34 changes: 34 additions & 0 deletions tests/objects/test_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from ufoLib2.objects import Component, Glyph, Layer

import pytest


@pytest.fixture
def layer():
a = Glyph("a")
pen = a.getPen()
pen.moveTo((0, 0))
pen.curveTo((10, 10), (10, 20), (0, 20))
pen.closePath()

layer = Layer(glyphs=[a])
return layer


def test_component_getBounds(layer):
assert Component("a", (1, 0, 0, 1, 0, 0)).getBounds(layer) == (0, 0, 7.5, 20)
assert Component("a", (1, 0, 0, 1, -5, 0)).getBounds(layer) == (-5, 0, 2.5, 20)
assert Component("a", (1, 0, 0, 1, 0, 5)).getBounds(layer) == (0, 5, 7.5, 25)


def test_component_getControlBounds(layer):
assert Component("a", (1, 0, 0, 1, 0, 0)).getControlBounds(layer) == (0, 0, 10, 20)
assert Component("a", (1, 0, 0, 1, -5, 0)).getControlBounds(layer) == (-5, 0, 5, 20)
assert Component("a", (1, 0, 0, 1, 0, 5)).getControlBounds(layer) == (0, 5, 10, 25)


def test_component_not_in_layer(layer):
with pytest.raises(KeyError, match="b"):
Component("b", (1, 0, 0, 1, 0, 0)).getBounds(layer)
with pytest.raises(KeyError, match="b"):
Component("b", (1, 0, 0, 1, 0, 0)).getControlBounds(layer)
24 changes: 24 additions & 0 deletions tests/objects/test_contour.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from ufoLib2.objects import Glyph

import pytest


@pytest.fixture
def contour():
g = Glyph("a")
pen = g.getPen()
pen.moveTo((0, 0))
pen.curveTo((10, 10), (10, 20), (0, 20))
pen.closePath()
return g.contours[0]


def test_contour_getBounds(contour):
assert contour.getBounds() == (0, 0, 7.5, 20)
assert contour.getBounds(layer={}) == (0, 0, 7.5, 20)
assert contour.bounds == (0, 0, 7.5, 20)


def test_contour_getControlBounds(contour):
assert contour.getControlBounds() == (0, 0, 10, 20)
assert contour.getControlBounds(layer={}) == (0, 0, 10, 20)
7 changes: 3 additions & 4 deletions tests/objects/test_glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Layer,
Point,
)
from ufoLib2.objects.misc import BoundingBox

import pytest

Expand Down Expand Up @@ -123,11 +124,9 @@ def test_glyph_get_bounds():

layer = Layer(glyphs=[a, b])

assert a.getBounds(layer) == Glyph.BoundingBox(xMin=0, yMin=0, xMax=7.5, yMax=20)
assert a.getBounds(layer) == BoundingBox(xMin=0, yMin=0, xMax=7.5, yMax=20)

assert a.getControlBounds(layer) == Glyph.BoundingBox(
xMin=0, yMin=0, xMax=10, yMax=20
)
assert a.getControlBounds(layer) == BoundingBox(xMin=0, yMin=0, xMax=10, yMax=20)

with pytest.raises(
TypeError, match="layer is required to compute bounds of components"
Expand Down

0 comments on commit 9b69d21

Please sign in to comment.