summaryrefslogtreecommitdiff
path: root/lib/lettering
diff options
context:
space:
mode:
Diffstat (limited to 'lib/lettering')
-rw-r--r--lib/lettering/__init__.py2
-rw-r--r--lib/lettering/font.py44
-rw-r--r--lib/lettering/font_variant.py22
-rw-r--r--lib/lettering/glyph.py31
-rw-r--r--lib/lettering/kerning.py69
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))
+ """