From f2e280de2fe58b938f8c01618233d1c3891b38a6 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 25 Mar 2024 17:39:42 +0000 Subject: [PATCH 1/2] remove resolve_intermediate_components from preflight, no longer needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ufo2ft can now interpolate components or composite glyphs as needed at build time while decomposing composites, as well as add empty component placeholders when keeping the glyphs as composites (as of https://github.com/googlefonts/ufo2ft/pull/826). Therefore glyphsLib does not need any more to add intermediate layers to the component base glyphs when these are defined at fewer master locations than the composite glyphs they are referenced from. --- .../builder/transformations/__init__.py | 5 +- .../transformations/intermediate_layers.py | 129 ------------------ tests/builder/preflight_test.py | 9 -- 3 files changed, 1 insertion(+), 142 deletions(-) delete mode 100644 Lib/glyphsLib/builder/transformations/intermediate_layers.py delete mode 100644 tests/builder/preflight_test.py diff --git a/Lib/glyphsLib/builder/transformations/__init__.py b/Lib/glyphsLib/builder/transformations/__init__.py index 37e59870a..158397ac0 100644 --- a/Lib/glyphsLib/builder/transformations/__init__.py +++ b/Lib/glyphsLib/builder/transformations/__init__.py @@ -1,4 +1 @@ -from .intermediate_layers import resolve_intermediate_components - - -TRANSFORMATIONS = [resolve_intermediate_components] +TRANSFORMATIONS = [] diff --git a/Lib/glyphsLib/builder/transformations/intermediate_layers.py b/Lib/glyphsLib/builder/transformations/intermediate_layers.py deleted file mode 100644 index 0439e73a1..000000000 --- a/Lib/glyphsLib/builder/transformations/intermediate_layers.py +++ /dev/null @@ -1,129 +0,0 @@ -import logging -import uuid - -from fontTools.varLib.models import VariationModel, normalizeValue - -from glyphsLib.classes import GSLayer, GSNode, GSPath -from glyphsLib.builder.axes import get_regular_master - - -logger = logging.getLogger(__name__) - - -def resolve_intermediate_components(font): - for glyph in font.glyphs: - for layer in glyph.layers: - if layer._is_brace_layer(): - # First, let's find glyphs with intermediate layers - # which have components which don't have intermediate layers - for shape in layer.components: - ensure_component_has_sparse_layer(font, shape, layer) - - -def variation_model(font, locations): - tags = [axis.axisTag for axis in font.axes] - limits = {tag: (min(x), max(x)) for tag, x in zip(tags, (zip(*locations)))} - master_locations = [] - default_location = get_regular_master(font).axes - for loc in locations: - this_loc = {} - for ix, axisTag in enumerate(tags): - axismin, axismax = limits[axisTag] - this_loc[axisTag] = normalizeValue( - loc[ix], (axismin, default_location[ix], axismax) - ) - master_locations.append(this_loc) - return VariationModel(master_locations, axisOrder=tags), limits - - -def ensure_component_has_sparse_layer(font, component, parent_layer): - tags = [axis.axisTag for axis in font.axes] - master_locations = [x.axes for x in font.masters] - _, limits = variation_model(font, master_locations) - location = parent_layer._brace_coordinates() - default_location = get_regular_master(font).axes - normalized_location = { - axisTag: normalizeValue( - location[ix], (limits[axisTag][0], default_location[ix], limits[axisTag][1]) - ) - for ix, axisTag in enumerate(tags) - } - componentglyph = component.component - for layer in componentglyph.layers: - if layer.layerId == parent_layer.layerId: - return - if "coordinates" in layer.attributes and layer._brace_coordinates() == location: - return - - # We'll add the appropriate intermediate layer to the component, that'll fix it - logger.info( - "Adding intermediate layer to %s to support %s %s", - componentglyph.name, - parent_layer.parent.name, - parent_layer.name, - ) - layer = GSLayer() - layer.attributes["coordinates"] = parent_layer.attributes["coordinates"] - layer.layerId = str(uuid.uuid4()) - layer.associatedMasterId = parent_layer.associatedMasterId - layer.name = parent_layer.name - # Create a glyph-level variation model for the component glyph, - # including any intermediate layers - interpolatable_layers = [] - locations = [] - for l in componentglyph.layers: - if l._is_brace_layer(): - locations.append(l.attributes["coordinates"]) - interpolatable_layers.append(l) - if l._is_master_layer: - locations.append(font.masters[l.associatedMasterId].axes) - interpolatable_layers.append(l) - glyph_level_model, _ = variation_model(font, locations) - - # Interpolate new layer width - all_widths = [l.width for l in interpolatable_layers] - layer.width = glyph_level_model.interpolateFromMasters( - normalized_location, all_widths - ) - - # Interpolate layer shapes - for ix, shape in enumerate(componentglyph.layers[0].shapes): - all_shapes = [l.shapes[ix] for l in interpolatable_layers] - if isinstance(shape, GSPath): - # We are making big assumptions about compatibility here - layer.shapes.append( - interpolate_path(all_shapes, glyph_level_model, normalized_location) - ) - else: - ensure_component_has_sparse_layer(font, shape, parent_layer) - layer.shapes.append( - interpolate_component( - all_shapes, glyph_level_model, normalized_location - ) - ) - componentglyph.layers.append(layer) - - -def interpolate_path(paths, model, location): - path = GSPath() - for master_nodes in zip(*[p.nodes for p in paths]): - node = GSNode() - node.type = master_nodes[0].type - node.smooth = master_nodes[0].smooth - xs = [n.position.x for n in master_nodes] - ys = [n.position.y for n in master_nodes] - node.position.x = model.interpolateFromMasters(location, xs) - node.position.y = model.interpolateFromMasters(location, ys) - path.nodes.append(node) - return path - - -def interpolate_component(components, model, location): - component = components[0].clone() - if all(c.transform == component.transform for c in components): - return component - transforms = [c.transform for c in components] - for element in range(6): - values = [t[element] for t in transforms] - component.transform[element] = model.interpolateFromMasters(location, values) - return component diff --git a/tests/builder/preflight_test.py b/tests/builder/preflight_test.py deleted file mode 100644 index 389dd0928..000000000 --- a/tests/builder/preflight_test.py +++ /dev/null @@ -1,9 +0,0 @@ -import glyphsLib -from glyphsLib.builder.transformations import resolve_intermediate_components - - -def test_intermediates_with_components_without_intermediates(datadir): - font = glyphsLib.GSFont(str(datadir.join("ComponentsWithIntermediates.glyphs"))) - assert len(font.glyphs["A"].layers) != len(font.glyphs["Astroke"].layers) - resolve_intermediate_components(font) - assert len(font.glyphs["A"].layers) == len(font.glyphs["Astroke"].layers) From c38df5da247a71fbee605e266580263190c5f00f Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 25 Mar 2024 18:20:26 +0000 Subject: [PATCH 2/2] remove unused test file as well --- tests/data/ComponentsWithIntermediates.glyphs | 359 ------------------ 1 file changed, 359 deletions(-) delete mode 100644 tests/data/ComponentsWithIntermediates.glyphs diff --git a/tests/data/ComponentsWithIntermediates.glyphs b/tests/data/ComponentsWithIntermediates.glyphs deleted file mode 100644 index dde9c5f8e..000000000 --- a/tests/data/ComponentsWithIntermediates.glyphs +++ /dev/null @@ -1,359 +0,0 @@ -{ -.appVersion = "3227"; -.formatVersion = 3; -axes = ( -{ -name = Weight; -tag = wght; -} -); -date = "2021-10-04 15:53:11 +0000"; -familyName = ComponentsWithIntermediates; -fontMaster = ( -{ -axesValues = ( -90 -); -id = "9FF96064-8718-4A86-A5C8-73C592101F67"; -metricValues = ( -{ -over = 6; -pos = 760; -}, -{ -over = 12; -pos = 714; -}, -{ -over = 11; -pos = 536; -}, -{ -over = -15; -}, -{ -over = -16; -pos = -240; -}, -{ -over = -15; -}, -{ -} -); -name = Regular; -stemValues = ( -79, -90 -); -}, -{ -axesValues = ( -190 -); -iconName = Bold; -id = "0A718E2F-231D-484E-9433-16A5230D2CE8"; -metricValues = ( -{ -over = 6; -pos = 760; -}, -{ -over = 11; -pos = 714; -}, -{ -over = 10; -pos = 553; -}, -{ -over = -15; -}, -{ -over = -16; -pos = -240; -}, -{ -over = -15; -}, -{ -} -); -name = Bold; -stemValues = ( -158, -194 -); -userData = { -GSOffsetHorizontal = 16; -GSOffsetKeepCompatible = 1; -GSOffsetVertical = 9; -com.github.googlei18n.ufo2ft.filters = ( -{ -name = flattenComponents; -pre = 1; -} -); -}; -} -); -glyphs = ( -{ -category = Letter; -glyphname = A; -kernLeft = A.right; -kernRight = A.left; -layers = ( -{ -anchors = ( -{ -name = bottom; -pos = (363,0); -}, -{ -name = center; -pos = (363,358); -}, -{ -name = top; -pos = (363,714); -}, -{ -name = topright; -pos = (636,714); -} -); -layerId = "0A718E2F-231D-484E-9433-16A5230D2CE8"; -shapes = ( -{ -closed = 1; -nodes = ( -(726,0,l), -(490,717,l), -(233,717,l), -(0,0,l), -(212,0,l), -(248,134,l), -(480,134,l), -(515,0,l) -); -}, -{ -closed = 1; -nodes = ( -(440,292,l), -(288,292,l), -(319,409,ls), -(331,456,o), -(354,550,o), -(363,599,c), -(372,550,o), -(399,447,o), -(409,409,cs) -); -} -); -width = 726; -}, -{ -anchors = ( -{ -name = bottom; -pos = (324,0); -}, -{ -name = center; -pos = (319,358); -}, -{ -name = top; -pos = (318,714); -}, -{ -name = topright; -pos = (519,714); -} -); -layerId = "9FF96064-8718-4A86-A5C8-73C592101F67"; -shapes = ( -{ -closed = 1; -nodes = ( -(638,0,l), -(360,717,l), -(279,717,l), -(0,0,l), -(91,0,l), -(176,221,l), -(459,221,l), -(545,0,l) -); -}, -{ -closed = 1; -nodes = ( -(432,301,l), -(206,301,l), -(287,517,ls), -(295,540,o), -(308,583,o), -(318,624,c), -(325,599,o), -(346,533,o), -(352,517,cs) -); -} -); -width = 639; -} -); -script = latin; -subCategory = Uppercase; -unicode = 65; -}, -{ -category = Letter; -color = 10; -glyphname = Astroke; -layers = ( -{ -anchors = ( -{ -name = bottom; -pos = (324,0); -}, -{ -name = top; -pos = (318,714); -} -); -layerId = "9FF96064-8718-4A86-A5C8-73C592101F67"; -shapes = ( -{ -closed = 1; -nodes = ( -(432,760,l), -(142,-75,l), -(209,-75,l), -(499,760,l) -); -}, -{ -alignment = -1; -ref = A; -} -); -width = 639; -}, -{ -anchors = ( -{ -name = bottom; -pos = (345,0); -}, -{ -name = top; -pos = (345,714); -} -); -associatedMasterId = "9FF96064-8718-4A86-A5C8-73C592101F67"; -attr = { -coordinates = ( -151 -); -}; -background = { -shapes = ( -{ -closed = 1; -nodes = ( -(457.52,760,l), -(167.52,-75,l), -(265.84,-75,l), -(555.84,760,l) -); -} -); -}; -layerId = "05c7f3ac-c6d8-4d29-899b-2a10de32f922"; -name = "SemiBold (intermediate)"; -shapes = ( -{ -closed = 1; -nodes = ( -(446,760,l), -(156,-75,l), -(253,-75,l), -(543,760,l) -); -}, -{ -alignment = -1; -ref = A; -} -); -width = 690; -}, -{ -anchors = ( -{ -name = bottom; -pos = (363,0); -}, -{ -name = top; -pos = (363,714); -} -); -layerId = "0A718E2F-231D-484E-9433-16A5230D2CE8"; -shapes = ( -{ -closed = 1; -nodes = ( -(476,760,l), -(186,-75,l), -(307,-75,l), -(597,760,l) -); -}, -{ -alignment = -1; -ref = A; -} -); -width = 730; -} -); -script = latin; -subCategory = Uppercase; -unicode = 570; -} -); -metrics = ( -{ -type = ascender; -}, -{ -type = "cap height"; -}, -{ -type = "x-height"; -}, -{ -type = baseline; -}, -{ -type = descender; -}, -{ -filter = "case == 3"; -type = "x-height"; -}, -{ -type = "italic angle"; -} -); -unitsPerEm = 1000; -versionMajor = 2; -versionMinor = 13; -}