2021-03-12 04:17:19 +00:00
|
|
|
# Authors: see git history
|
|
|
|
#
|
|
|
|
# Copyright (c) 2010 Authors
|
|
|
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
|
|
|
|
2018-11-15 01:23:06 +00:00
|
|
|
import os
|
2021-02-04 15:40:02 +00:00
|
|
|
|
2018-11-15 01:23:06 +00:00
|
|
|
import inkex
|
|
|
|
|
2021-10-09 16:25:29 +00:00
|
|
|
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SVG_GROUP_TAG,
|
|
|
|
SVG_PATH_TAG, SVG_USE_TAG)
|
2023-04-22 15:45:42 +00:00
|
|
|
from ..update import update_inkstitch_document
|
2018-11-15 01:23:06 +00:00
|
|
|
from .glyph import Glyph
|
|
|
|
|
|
|
|
|
|
|
|
class FontVariant(object):
|
|
|
|
"""Represents a single variant of a font.
|
|
|
|
|
|
|
|
Each font may have multiple variants for left-to-right, right-to-left,
|
|
|
|
etc. Each variant has a set of Glyphs, one per character.
|
|
|
|
|
|
|
|
A FontVariant instance can be accessed as a dict by using a unicode
|
|
|
|
character as a key.
|
|
|
|
|
|
|
|
Properties:
|
|
|
|
path -- the path to the directory containing this font
|
|
|
|
variant -- the font variant, specified using one of the constants below
|
|
|
|
glyphs -- a dict of Glyphs, with the glyphs' unicode characters as keys.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# We use unicode characters rather than English strings for font file names
|
|
|
|
# in order to be more approachable for languages other than English.
|
2021-03-04 17:40:53 +00:00
|
|
|
LEFT_TO_RIGHT = "→"
|
|
|
|
RIGHT_TO_LEFT = "←"
|
|
|
|
TOP_TO_BOTTOM = "↓"
|
|
|
|
BOTTOM_TO_TOP = "↑"
|
2018-11-15 01:23:06 +00:00
|
|
|
VARIANT_TYPES = (LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def reversed_variant(cls, variant):
|
|
|
|
if variant == cls.LEFT_TO_RIGHT:
|
|
|
|
return cls.RIGHT_TO_LEFT
|
|
|
|
elif variant == cls.RIGHT_TO_LEFT:
|
|
|
|
return cls.LEFT_TO_RIGHT
|
|
|
|
elif variant == cls.TOP_TO_BOTTOM:
|
|
|
|
return cls.BOTTOM_TO_TOP
|
|
|
|
elif variant == cls.BOTTOM_TO_TOP:
|
|
|
|
return cls.TOP_TO_BOTTOM
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def __init__(self, font_path, variant, default_glyph=None):
|
2021-02-04 15:40:02 +00:00
|
|
|
# If the font variant file does not exist, this constructor will
|
|
|
|
# raise an exception. The caller should catch it and decide
|
|
|
|
# what to do.
|
|
|
|
|
2018-11-15 01:23:06 +00:00
|
|
|
self.path = font_path
|
|
|
|
self.variant = variant
|
|
|
|
self.default_glyph = default_glyph
|
|
|
|
self.glyphs = {}
|
|
|
|
self._load_glyphs()
|
|
|
|
|
|
|
|
def _load_glyphs(self):
|
2023-02-19 09:38:25 +00:00
|
|
|
variant_file_paths = self._get_variant_file_paths()
|
|
|
|
for svg_path in variant_file_paths:
|
2023-04-22 15:45:42 +00:00
|
|
|
document = inkex.load_svg(svg_path)
|
|
|
|
update_inkstitch_document(document)
|
|
|
|
svg = document.getroot()
|
2023-02-19 09:38:25 +00:00
|
|
|
svg = self._apply_transforms(svg)
|
|
|
|
|
|
|
|
glyph_layers = svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=inkex.NSS)
|
|
|
|
for layer in glyph_layers:
|
|
|
|
self._clean_group(layer)
|
|
|
|
layer.attrib[INKSCAPE_LABEL] = layer.attrib[INKSCAPE_LABEL].replace("GlyphLayer-", "", 1)
|
|
|
|
glyph_name = layer.attrib[INKSCAPE_LABEL]
|
|
|
|
try:
|
|
|
|
self.glyphs[glyph_name] = Glyph(layer)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def _get_variant_file_paths(self):
|
|
|
|
file_paths = []
|
|
|
|
direct_path = os.path.join(self.path, "%s.svg" % self.variant)
|
|
|
|
if os.path.isfile(direct_path):
|
|
|
|
file_paths.append(direct_path)
|
|
|
|
elif os.path.isdir(os.path.join(self.path, "%s" % self.variant)):
|
|
|
|
path = os.path.join(self.path, "%s" % self.variant)
|
|
|
|
file_paths.extend([os.path.join(path, svg) for svg in os.listdir(path) if svg.endswith('.svg')])
|
|
|
|
return file_paths
|
2018-11-15 01:23:06 +00:00
|
|
|
|
|
|
|
def _clean_group(self, group):
|
|
|
|
# We'll repurpose the layer as a container group labelled with the
|
|
|
|
# glyph.
|
|
|
|
del group.attrib[INKSCAPE_GROUPMODE]
|
|
|
|
|
2021-07-25 05:24:34 +00:00
|
|
|
# The layer may be marked invisible, so we'll clear the 'display'
|
|
|
|
# style and presentation attribute.
|
|
|
|
group.style.pop('display', None)
|
|
|
|
group.attrib.pop('display', None)
|
2018-11-15 01:23:06 +00:00
|
|
|
|
2021-10-09 16:25:29 +00:00
|
|
|
def _apply_transforms(self, svg):
|
|
|
|
# apply transforms to paths and use tags
|
|
|
|
for element in svg.iterdescendants((SVG_PATH_TAG, SVG_USE_TAG)):
|
|
|
|
transform = element.composed_transform()
|
|
|
|
if element.tag == SVG_PATH_TAG:
|
|
|
|
path = element.path.transform(transform)
|
|
|
|
element.set_path(path)
|
|
|
|
element.attrib.pop("transform", None)
|
|
|
|
|
|
|
|
if element.tag == SVG_USE_TAG:
|
|
|
|
oldx = element.get('x', 0)
|
|
|
|
oldy = element.get('y', 0)
|
|
|
|
newx, newy = transform.apply_to_point((oldx, oldy))
|
|
|
|
element.set('x', newx)
|
|
|
|
element.set('y', newy)
|
|
|
|
element.attrib.pop("transform", None)
|
|
|
|
|
|
|
|
# remove transforms after they have been applied
|
|
|
|
for group in svg.iterdescendants(SVG_GROUP_TAG):
|
|
|
|
group.attrib.pop('transform', None)
|
|
|
|
|
|
|
|
return svg
|
|
|
|
|
2018-11-15 01:23:06 +00:00
|
|
|
def __getitem__(self, character):
|
|
|
|
if character in self.glyphs:
|
|
|
|
return self.glyphs[character]
|
|
|
|
else:
|
|
|
|
return self.glyphs.get(self.default_glyph, None)
|
|
|
|
|
|
|
|
def __contains__(self, character):
|
|
|
|
return character in self.glyphs
|