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

Add FlattenComponentsFilter to flatten nested components #214

Merged
merged 2 commits into from
Feb 19, 2018
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
52 changes: 52 additions & 0 deletions Lib/ufo2ft/filters/flattenComponents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from fontTools.misc.transform import Transform
from ufo2ft.filters import BaseFilter

import logging


logger = logging.getLogger(__name__)


class FlattenComponentsFilter(BaseFilter):

def __call__(self, font, glyphSet=None):
if super(FlattenComponentsFilter, self).__call__(font, glyphSet):
modified = self.context.modified
if modified:
logger.info('Flattened composite glyphs: %i' %
len(modified))
return modified

def filter(self, glyph):
flattened = False
if not glyph.components:
return flattened
pen = glyph.getPen()
for comp in list(glyph.components):
flattened_tuples = _flattenComponent(comp)
if flattened_tuples[0] != (comp.baseGlyph, comp.transformation):
flattened = True
glyph.removeComponent(comp)
for flattened_tuple in flattened_tuples:
pen.addComponent(*flattened_tuple)
if flattened:
self.context.modified.add(glyph.name)
return flattened


def _flattenComponent(component):
"""Returns list of tuples (baseGlyph, transform) of nested component."""

glyph = component.font[component.baseGlyph]
if not glyph.components:
transformation = Transform(*component.transformation)
return [(component.baseGlyph, transformation)]

all_flattened_components = []
for nested in glyph.components:
flattened_components = _flattenComponent(nested)
for i, (_, tr) in enumerate(flattened_components):
tr = tr.transform(component.transformation)
flattened_components[i] = (flattened_components[i][0], tr)
all_flattened_components.extend(flattened_components)
return all_flattened_components
104 changes: 104 additions & 0 deletions tests/filters/flattenComponents_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from __future__ import print_function, division, absolute_import
from ufo2ft.filters.flattenComponents import FlattenComponentsFilter, logger
from fontTools.misc.loggingTools import CapturingLogHandler
import defcon
import pytest

@pytest.fixture(params=[
{
'glyphs': [
{
'name': 'space',
'width': 500,
},
{
'name': 'a',
'width': 350,
'outline': [
('moveTo', ((0, 0),)),
('lineTo', ((300, 0),)),
('lineTo', ((300, 300),)),
('lineTo', ((0, 300),)),
('closePath', ()),
],
},
{
'name': 'b',
'width': 350,
'outline': [
('addComponent', ('a', (1, 0, 0, 1, 0, 0))),
],
},
{
'name': 'c',
'width': 350,
'outline': [
('addComponent', ('b', (1, 0, 0, 1, 0, 0))),
],
},
{
'name': 'd',
'width': 700,
'outline': [
('addComponent', ('a', (1, 0, 0, 1, 0, 0))),
('addComponent', ('b', (1, 0, 0, 1, 350, 0))),
('addComponent', ('c', (1, 0, 0, 1, 700, 0)))
],
},
],
}
])
def font(request):
font = defcon.Font()
for param in request.param['glyphs']:
glyph = font.newGlyph(param['name'])
glyph.width = param.get('width', 0)
pen = glyph.getPen()
for operator, operands in param.get('outline', []):
getattr(pen, operator)(*operands)
return font


class FlattenComponentsFilterTest(object):

def test_empty_glyph(self, font):
philter = FlattenComponentsFilter(include={'space'})
assert not philter(font)

def test_contour_glyph(self, font):
philter = FlattenComponentsFilter(include={'a'})
assert not philter(font)

def test_component_glyph(self, font):
philter = FlattenComponentsFilter(include={'b'})
assert not philter(font)

def test_nested_components_glyph(self, font):
philter = FlattenComponentsFilter(include={'c'})
modified = philter(font)
assert modified == set(['c'])
assert (
[(c.baseGlyph, c.transformation) for c in font['c'].components] ==
[('a', (1, 0, 0, 1, 0, 0))]
)

def test_whole_font(self, font):
philter = FlattenComponentsFilter()
modified = philter(font)
assert modified == set(['c', 'd'])
assert (
[(c.baseGlyph, c.transformation) for c in font['c'].components] ==
[('a', (1, 0, 0, 1, 0, 0))]
)
assert (
[(c.baseGlyph, c.transformation) for c in font['d'].components] ==
[('a', (1, 0, 0, 1, 0, 0)),
('a', (1, 0, 0, 1, 350, 0)),
('a', (1, 0, 0, 1, 700, 0))]
)

def test_logger(self, font):
with CapturingLogHandler(logger, level="INFO") as captor:
philter = FlattenComponentsFilter()
modified = philter(font)
captor.assertRegex('Flattened composite glyphs: 2')