/usr/lib/python3/dist-packages/ufo2ft/featureCompiler.py is in python3-ufo2ft 1.1.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | from __future__ import \
print_function, division, absolute_import, unicode_literals
import logging
import os
from inspect import isclass
from tempfile import NamedTemporaryFile
from fontTools import feaLib
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
from fontTools import mtiLib
from fontTools.misc.py23 import UnicodeIO, tobytes
from ufo2ft.featureWriters import DEFAULT_FEATURE_WRITERS
from ufo2ft.maxContextCalc import maxCtxFont
logger = logging.getLogger(__name__)
class FeatureCompiler(object):
"""Generates OpenType feature tables for a UFO.
*featureWriters* argument is a list that can contain either subclasses
of BaseFeatureWriter or pre-initialized instances (or a mix of the two).
Classes are initialized without arguments so will use default options.
Features will be written by each feature writer in the given order.
The default value is [KernFeatureWriter, MarkFeatureWriter].
If mtiFeatures is passed to the constructor, it should be a dictionary
mapping feature table tags to MTI feature declarations for that table.
These are passed to mtiLib for compilation.
"""
def __init__(self, font, outline,
featureWriters=None,
mtiFeatures=None):
self.font = font
self.outline = outline
if featureWriters is None:
featureWriters = DEFAULT_FEATURE_WRITERS
self.featureWriters = []
for writer in featureWriters:
if isclass(writer):
writer = writer()
self.featureWriters.append(writer)
self.mtiFeatures = mtiFeatures
def compile(self):
"""Compile the features.
Starts by generating feature syntax for the kern, mark, and mkmk
features. If they already exist, they will not be overwritten.
"""
self.setupFile_features()
self.setupFile_featureTables()
self.postProcess()
def setupFile_features(self):
"""
Make the features source file. If any tables
or the kern feature are defined in the font's
features, they will not be overwritten.
**This should not be called externally.** Subclasses
may override this method to handle the file creation
in a different way if desired.
"""
if self.mtiFeatures is not None:
return
existingFeatures = self._findLayoutFeatures()
# build features as necessary
autoFeatures = []
# the current MarkFeatureWriter writes both mark and mkmk features
# with shared markClass definitions; to prevent duplicate glyphs in
# markClass, here we write the features only if none of them is alread
# present.
# TODO: Support updating pre-existing markClass definitions to allow
# writing either mark or mkmk features indipendently from each other
# https://github.com/googlei18n/fontmake/issues/319
font = self.font
for fw in self.featureWriters:
if (fw.mode == "append" or (
fw.mode == "skip" and
all(fea not in existingFeatures for fea in fw.features))):
autoFeatures.append(fw.write(font))
# write the features
self.features = "\n\n".join([font.features.text or ""] + autoFeatures)
def _findLayoutFeatures(self):
"""Returns what OpenType layout feature tags are present in the UFO."""
featxt = self.font.features.text
if not featxt:
return set()
buf = UnicodeIO(featxt)
# the path is only used by the lexer to resolve 'include' statements
if self.font.path is not None:
buf.name = os.path.join(self.font.path, "features.fea")
glyphMap = self.outline.getReverseGlyphMap()
parser = feaLib.parser.Parser(buf, glyphMap)
doc = parser.parse()
return {f.name for f in doc.statements
if isinstance(f, feaLib.ast.FeatureBlock)}
def setupFile_featureTables(self):
"""
Compile and return OpenType feature tables from the source.
Raises a FeaLibError if the feature compilation was unsuccessful.
**This should not be called externally.** Subclasses
may override this method to handle the table compilation
in a different way if desired.
"""
if self.mtiFeatures is not None:
for tag, features in self.mtiFeatures.items():
table = mtiLib.build(features.splitlines(), self.outline)
assert table.tableTag == tag
self.outline[tag] = table
elif self.features.strip():
# the path to features.fea is only used by the lexer to resolve
# the relative "include" statements
if self.font.path is not None:
feapath = os.path.join(self.font.path, "features.fea")
else:
# in-memory UFO has no path, can't do 'include' either
feapath = None
# save generated features to a temp file if things go wrong...
data = tobytes(self.features, encoding="utf-8")
with NamedTemporaryFile(delete=False) as tmp:
tmp.write(data)
# if compilation succedes or fails for unrelated reasons, clean
# up the temporary file
try:
addOpenTypeFeaturesFromString(self.outline, self.features,
filename=feapath)
except feaLib.error.FeatureLibError:
logger.error("Compilation failed! Inspect temporary file: %r",
tmp.name)
raise
except:
os.remove(tmp.name)
raise
else:
os.remove(tmp.name)
def postProcess(self):
"""Make post-compilation calculations.
**This should not be called externally.** Subclasses
may override this method if desired.
"""
# only after compiling features can usMaxContext be calculated
self.outline['OS/2'].usMaxContext = maxCtxFont(self.outline)
|