Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

also add getBounds and getControlBounds for Contour and Component classes #54

Merged
merged 4 commits into from
Feb 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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