diff options
Diffstat (limited to 'lib/lettering')
| -rw-r--r-- | lib/lettering/__init__.py | 2 | ||||
| -rw-r--r-- | lib/lettering/font.py | 44 | ||||
| -rw-r--r-- | lib/lettering/font_variant.py | 22 | ||||
| -rw-r--r-- | lib/lettering/glyph.py | 31 | ||||
| -rw-r--r-- | lib/lettering/kerning.py | 69 |
5 files changed, 123 insertions, 45 deletions
diff --git a/lib/lettering/__init__.py b/lib/lettering/__init__.py index 5d20d683..5a9d345c 100644 --- a/lib/lettering/__init__.py +++ b/lib/lettering/__init__.py @@ -1 +1 @@ -from font import Font, FontError
\ No newline at end of file +from .font import Font, FontError diff --git a/lib/lettering/font.py b/lib/lettering/font.py index 0974a1cf..3ef99d47 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -1,10 +1,9 @@ -# -*- coding: UTF-8 -*- - import json import os from copy import deepcopy -import inkex +from inkex import styles +from lxml import etree from ..elements import nodes_to_elements from ..exceptions import InkstitchException @@ -80,14 +79,14 @@ class Font(object): def _load_metadata(self): try: - with open(os.path.join(self.path, "font.json")) as metadata_file: + with open(os.path.join(self.path, "font.json"), encoding="utf-8") as metadata_file: self.metadata = json.load(metadata_file) except IOError: pass def _load_license(self): try: - with open(os.path.join(self.path, "LICENSE")) as license_file: + with open(os.path.join(self.path, "LICENSE"), encoding="utf-8") as license_file: self.license = license_file.read() except IOError: pass @@ -101,13 +100,9 @@ class Font(object): # we'll deal with missing variants when we apply lettering pass - def _check_variants(self): - if self.variants.get(self.default_variant) is None: - raise FontError("font not found or has no default variant") - name = localized_font_metadata('name', '') description = localized_font_metadata('description', '') - default_glyph = font_metadata('default_glyph', u"�") + default_glyph = font_metadata('defalt_glyph', "�") leading = font_metadata('leading', 5, multiplier=PIXELS_PER_MM) kerning_pairs = font_metadata('kerning_pairs', {}) auto_satin = font_metadata('auto_satin', True) @@ -149,10 +144,13 @@ class Font(object): return None def has_variants(self): + # returns available variants font_variants = [] for variant in FontVariant.VARIANT_TYPES: if os.path.isfile(os.path.join(self.path, "%s.svg" % variant)): font_variants.append(variant) + if not font_variants: + raise FontError(_("The font '%s' has no variants.") % self.name) return font_variants def render_text(self, text, destination_group, variant=None, back_and_forth=True, trim=False): @@ -182,12 +180,23 @@ class Font(object): if self.auto_satin and len(destination_group) > 0: self._apply_auto_satin(destination_group, trim) - else: - # set stroke width because it is almost invisible otherwise (why?) - for element in destination_group.iterdescendants(SVG_PATH_TAG): - style = ['stroke-width:1px' if s.startswith('stroke-width') else s for s in element.get('style').split(';')] - style = ';'.join(style) - element.set('style', '%s' % style) + + # make sure font stroke styles have always a similar look + for element in destination_group.iterdescendants(SVG_PATH_TAG): + dash_array = "" + stroke_width = "" + style = styles.Style(element.get('style')) + + if style.get('fill') == 'none': + stroke_width = ";stroke-width:1px" + if style.get('stroke-width'): + style.pop('stroke-width') + + if style.get('stroke-dasharray') and style.get('stroke-dasharray') != 'none': + stroke_width = ";stroke-width:0.5px" + dash_array = ";stroke-dasharray:3, 1" + + element.set('style', '%s%s%s' % (style.to_str(), stroke_width, dash_array)) return destination_group @@ -209,7 +218,8 @@ class Font(object): Returns: An svg:g element containing the rendered text. """ - group = inkex.etree.Element(SVG_GROUP_TAG, { + + group = etree.Element(SVG_GROUP_TAG, { INKSCAPE_LABEL: line }) diff --git a/lib/lettering/font_variant.py b/lib/lettering/font_variant.py index 7c9fa1c0..2071b2cb 100644 --- a/lib/lettering/font_variant.py +++ b/lib/lettering/font_variant.py @@ -1,9 +1,7 @@ -# -*- coding: UTF-8 -*- - import os import inkex -import simplestyle +from lxml import etree from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL from .glyph import Glyph @@ -26,10 +24,10 @@ class FontVariant(object): # We use unicode characters rather than English strings for font file names # in order to be more approachable for languages other than English. - LEFT_TO_RIGHT = u"→" - RIGHT_TO_LEFT = u"←" - TOP_TO_BOTTOM = u"↓" - BOTTOM_TO_TOP = u"↑" + LEFT_TO_RIGHT = "→" + RIGHT_TO_LEFT = "←" + TOP_TO_BOTTOM = "↓" + BOTTOM_TO_TOP = "↑" VARIANT_TYPES = (LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP) @classmethod @@ -57,9 +55,9 @@ class FontVariant(object): self._load_glyphs() def _load_glyphs(self): - svg_path = os.path.join(self.path, u"%s.svg" % self.variant) - with open(svg_path) as svg_file: - svg = inkex.etree.parse(svg_file) + svg_path = os.path.join(self.path, "%s.svg" % self.variant) + with open(svg_path, encoding="utf-8") as svg_file: + svg = etree.parse(svg_file) glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS) for layer in glyph_layers: @@ -78,9 +76,9 @@ class FontVariant(object): if style_text: # The layer may be marked invisible, so we'll clear the 'display' # style. - style = simplestyle.parseStyle(group.get('style')) + style = dict(inkex.Style.parse_str(group.get('style'))) style.pop('display') - group.set('style', simplestyle.formatStyle(style)) + group.set('style', str(inkex.Style(style))) def __getitem__(self, character): if character in self.glyphs: diff --git a/lib/lettering/glyph.py b/lib/lettering/glyph.py index 061a930c..84517474 100644 --- a/lib/lettering/glyph.py +++ b/lib/lettering/glyph.py @@ -1,9 +1,9 @@ from copy import copy -import cubicsuperpath -import simpletransform +from inkex import paths, transforms -from ..svg import apply_transforms, get_guides +from ..svg import get_guides +from ..svg.path import get_correction_transform from ..svg.tags import SVG_GROUP_TAG, SVG_PATH_TAG @@ -37,8 +37,9 @@ class Glyph(object): def _process_group(self, group): new_group = copy(group) - new_group.attrib.pop('transform', None) - del new_group[:] # delete references to the original group's children + # 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: @@ -47,11 +48,9 @@ class Glyph(object): node_copy = copy(node) if "d" in node.attrib: - # Convert the path to absolute coordinates, incorporating all - # nested transforms. - path = cubicsuperpath.parsePath(node.get("d")) - apply_transforms(path, node) - node_copy.set("d", cubicsuperpath.formatPath(path)) + transform = -transforms.Transform(get_correction_transform(node, True)) + path = paths.Path(node.get("d")).transform(transform).to_absolute() + node_copy.set("d", str(path)) # Delete transforms from paths and groups, since we applied # them to the paths already. @@ -71,16 +70,18 @@ class Glyph(object): self.baseline = 0 def _process_bbox(self): - left, right, top, bottom = simpletransform.computeBBox(self.node.iterdescendants()) - + bbox = [paths.Path(node.get("d")).bounding_box() for node in self.node.iterdescendants(SVG_PATH_TAG)] + 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 _move_to_origin(self): translate_x = -self.min_x translate_y = -self.baseline - transform = "translate(%s, %s)" % (translate_x, translate_y) + transform = transforms.Transform("translate(%s, %s)" % (translate_x, translate_y)) for node in self.node.iter(SVG_PATH_TAG): - node.set('transform', transform) - simpletransform.fuseTransform(node) + path = paths.Path(node.get("d")) + path = path.transform(transform) + node.set('d', str(path)) + node.attrib.pop('transform', None) diff --git a/lib/lettering/kerning.py b/lib/lettering/kerning.py new file mode 100644 index 00000000..920e7d59 --- /dev/null +++ b/lib/lettering/kerning.py @@ -0,0 +1,69 @@ +from inkex import NSS +from lxml import etree + + +class FontKerning(object): + """ + This class reads kerning information from an SVG file + """ + def __init__(self, path): + with open(path) as svg: + self.svg = etree.parse(svg) + + # horiz_adv_x defines the wdith of specific letters (distance to next letter) + def horiz_adv_x(self): + # In XPath 2.0 we could use ".//svg:glyph/(@unicode|@horiz-adv-x)" + xpath = ".//svg:glyph[@unicode and @horiz-adv-x]/@*[name()='unicode' or name()='horiz-adv-x']" + hax = self.svg.xpath(xpath, namespaces=NSS) + if len(hax) == 0: + return {} + return dict(zip(hax[0::2], [int(x) for x in hax[1::2]])) + + # kerning (specific distances of two specified letters) + def hkern(self): + xpath = ".//svg:hkern[(@u1 or @g1) and (@u1 or @g1) and @k]/@*[contains(name(), '1') or contains(name(), '2') or name()='k']" + hkern = self.svg.xpath(xpath, namespaces=NSS) + for index, glyph in enumerate(hkern): + # fontTools.agl will import fontTools.misc.py23 which will output a deprecation warning + # ignore the warning for now - until the library fixed it + if index == 0: + import warnings + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + from fontTools.agl import toUnicode + if len(glyph) > 1 and not (index + 1) % 3 == 0: + glyph_names = glyph.split(",") + # the glyph name is written in various languages, second is english. Let's look it up. + if len(glyph_names) == 1: + hkern[index] = toUnicode(glyph) + else: + hkern[index] = toUnicode(glyph_names[1]) + k = [int(x) for x in hkern[2::3]] + u = [k + v for k, v in zip(hkern[0::3], hkern[1::3])] + hkern = dict(zip(u, k)) + return hkern + + # the space character + def word_spacing(self): + xpath = "string(.//svg:glyph[@glyph-name='space'][1]/@*[name()='horiz-adv-x'])" + word_spacing = self.svg.xpath(xpath, namespaces=NSS) or 26 + return int(word_spacing) + + # default letter spacing + def letter_spacing(self): + xpath = "string(.//svg:font[@horiz-adv-x][1]/@*[name()='horiz-adv-x'])" + letter_spacing = self.svg.xpath(xpath, namespaces=NSS) or 0 + return int(letter_spacing) + + # this value will be saved into the json file to preserve it for later font edits + # additionally it serves to automatically define the line height (leading) + def units_per_em(self, default=100): + xpath = "string(.//svg:font-face[@units-per-em][1]/@*[name()='units-per-em'])" + units_per_em = self.svg.xpath(xpath, namespaces=NSS) or default + return int(units_per_em) + + """ + def missing_glyph_spacing(self): + xpath = "string(.//svg:missing-glyph/@*[name()='horiz-adv-x'])" + return float(self.svg.xpath(xpath, namespaces=NSS)) + """ |
