inkstitch/lib/lettering/glyph.py

149 wiersze
5.5 KiB
Python

# Authors: see git history
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from collections import defaultdict
from copy import copy
from unicodedata import normalize
from inkex import paths, transforms, units
from ..svg import get_correction_transform, get_guides
from ..svg.tags import (CONNECTION_END, SVG_GROUP_TAG, SVG_PATH_TAG,
SVG_USE_TAG, XLINK_HREF)
class Glyph(object):
"""Represents a single character in a single font variant.
For example, the font inkstitch_small may have variants for left-to-right,
right-to-left, etc. Each variant would have a set of Glyphs, one for each
character in that variant.
Properties:
width -- total width of this glyph including all component satins
node -- svg:g XML node containing the component satins in this character
"""
def __init__(self, group):
"""Create a Glyph.
The nodes will be copied out of their parent SVG document (with nested
transforms applied). The original nodes will be unmodified.
Arguments:
group -- an svg:g XML node containing all the paths that make up
this Glyph. Nested groups are allowed.
"""
self.name = group.label
if len(self.name) > 11:
self.name = normalize('NFKC', self.name[11:])
self._process_baseline(group.getroottree().getroot())
self.clips = self._process_clips(group)
self.node = self._process_group(group)
self._process_bbox()
self._move_to_origin()
self._process_commands()
def _process_clips(self, group):
clips = defaultdict(list)
for node in group.iterdescendants():
if node.clip:
node_id = node.get_id()
clips[node_id] = node.clip
return clips
def _process_group(self, group):
new_group = copy(group)
# new_group.attrib.pop('transform', None)
# delete references to the original group's children
del new_group[:]
for node in group:
if node.tag == SVG_GROUP_TAG:
new_group.append(self._process_group(node))
else:
node_copy = copy(node)
transform = -transforms.Transform(get_correction_transform(node, True))
if "d" in node.attrib:
node_copy.path = node.path.transform(transform).to_absolute()
if not node.tag == SVG_USE_TAG:
# Delete transforms from paths and groups, since we applied
# them to the paths already.
node_copy.attrib.pop('transform', None)
else:
oldx = node.get('x', 0)
oldy = node.get('y', 0)
x, y = transform.apply_to_point((oldx, oldy))
node_copy.set('x', x)
node_copy.set('y', y)
new_group.append(node_copy)
return new_group
def _process_baseline(self, svg):
for guide in get_guides(svg):
if guide.label == "baseline":
self.baseline = guide.position.y
break
else:
# no baseline guide found, assume 0 for lack of anything better to use...
self.baseline = 0
def _process_bbox(self):
bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG) if not node.get(CONNECTION_END, None)]
left, right = min([box.left for box in bbox]), max([box.right for box in bbox])
self.width = right - left
self.min_x = left
def _process_commands(self):
# Save object ids with commands in a dictionary: {object_id: [connector_id, symbol_id]}
self.commands = {}
for node in self.node.iter(SVG_USE_TAG):
xlink = node.get(XLINK_HREF, ' ')
if not xlink.startswith('#inkstitch_'):
continue
try:
connector = self.node.xpath(".//*[@inkscape:connection-start='#%s']" % node.get('id', ' '))[0]
command_object = connector.get(CONNECTION_END)[1:]
try:
self.commands[command_object].append([connector.get_id(), node.get_id()])
except KeyError:
self.commands[command_object] = [[connector.get_id(), node.get_id()]]
except (IndexError, TypeError):
pass
def _move_to_origin(self):
translate_x = -self.min_x
translate_y = -self.baseline
transform = transforms.Transform("translate(%s, %s)" % (translate_x, translate_y))
for node in self.node.iter(SVG_PATH_TAG):
path = paths.Path(node.get("d"))
path = path.transform(transform)
node.set('d', str(path))
node.attrib.pop('transform', None)
# Move commands as well
for node in self.node.iter(SVG_USE_TAG):
oldx = units.convert_unit(node.get("x", 0), 'px', node.unit)
oldy = units.convert_unit(node.get("y", 0), 'px', node.unit)
x, y = transform.apply_to_point((oldx, oldy))
node.set('x', x)
node.set('y', y)
# transform clips
for node in self.node.iterdescendants():
node_id = node.get_id()
if node_id in self.clips:
clip = self.clips[node_id]
for childnode in clip.iterchildren():
childnode.transform @= transform