+
Skip to content

Fixes to voltLib and VoltToFea #3818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d85f057
[voltToFea] Improve tests
khaledhosny Apr 20, 2025
fdda2c7
[voltToFea] Correctly handle nested enums inside group definition
khaledhosny Mar 15, 2025
73a7019
[voltLib] Handle alternate substitution
khaledhosny Mar 15, 2025
a90a3c0
[voltToFea] Fix anchor looks mismatch between how VOLT and feature files
khaledhosny Apr 1, 2025
be235f7
[voltToFea] Make anchor names case-insensitive
khaledhosny Apr 14, 2025
7e08729
[voltToFea] Handle groups in ligature substitution
khaledhosny Apr 15, 2025
df3bd91
[voltToFea] Handle mark to ligature anchors with missing components
khaledhosny Apr 16, 2025
09231fa
[voltToFea] Fix handling of ranges
khaledhosny Apr 16, 2025
447a3f9
[voltToFea] Process glyph definitions before groups
khaledhosny Apr 16, 2025
4901029
[voltToFea] Fix detecting mark to ligature lookups
khaledhosny Apr 16, 2025
c6a193a
[voltToFea] Fix sorting group definitions
khaledhosny Apr 20, 2025
e445400
[voltToFea] Fix nested enums
khaledhosny Apr 20, 2025
95c081f
[voltToFea] Minor
khaledhosny Apr 22, 2025
02a1c29
[voltToFea] Handle aalt feature differently
khaledhosny Apr 22, 2025
2355708
[voltToFea] Add useExtension to all lookups not just GPOS ones
khaledhosny Apr 24, 2025
8706ed4
[voltToFea] Cleanup how chained lookups are created
khaledhosny Apr 26, 2025
aa36d59
[votlToFea] Re-do contextual lookups
khaledhosny Apr 27, 2025
4efb1a4
[voltToFea] make sure language tags are four chars
khaledhosny Apr 27, 2025
7761bc6
[voltLib] Ignore invalid uses of REVERSAL flag
khaledhosny Apr 27, 2025
9a7d91c
[voltToFea] Allow passing VoltFile as input
khaledhosny Mar 15, 2025
361f96b
[voltToFea] Add option to not warn about unsupported settings
khaledhosny Mar 15, 2025
322806a
[voltLib] Add preliminary support for compiling fonts
khaledhosny Apr 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 206 additions & 0 deletions Lib/fontTools/voltLib/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import argparse
import logging
import sys
from io import StringIO
from pathlib import Path

Check warning on line 5 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L1-L5

Added lines #L1 - L5 were not covered by tests

from fontTools import configLogger
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
from fontTools.feaLib.error import FeatureLibError
from fontTools.feaLib.lexer import Lexer
from fontTools.misc.cliTools import makeOutputFileName
from fontTools.ttLib import TTFont, TTLibError
from fontTools.voltLib.parser import Parser
from fontTools.voltLib.voltToFea import TABLES, VoltToFea

Check warning on line 14 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L7-L14

Added lines #L7 - L14 were not covered by tests

log = logging.getLogger("fontTools.feaLib")

Check warning on line 16 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L16

Added line #L16 was not covered by tests

SUPPORTED_TABLES = TABLES + ["cmap"]

Check warning on line 18 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L18

Added line #L18 was not covered by tests


def invalid_fea_glyph_name(name):

Check warning on line 21 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L21

Added line #L21 was not covered by tests
"""Check if the glyph name is valid according to FEA syntax."""
if name[0] not in Lexer.CHAR_NAME_START_:
return True

Check warning on line 24 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L24

Added line #L24 was not covered by tests
if any(c not in Lexer.CHAR_NAME_CONTINUATION_ for c in name[1:]):
return True
return False

Check warning on line 27 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L26-L27

Added lines #L26 - L27 were not covered by tests


def sanitize_glyph_name(name):

Check warning on line 30 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L30

Added line #L30 was not covered by tests
"""Sanitize the glyph name to ensure it is valid according to FEA syntax."""
sanitized = ""

Check warning on line 32 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L32

Added line #L32 was not covered by tests
for i, c in enumerate(name):
if i == 0 and c not in Lexer.CHAR_NAME_START_:
sanitized += "a" + c

Check warning on line 35 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L35

Added line #L35 was not covered by tests
elif c not in Lexer.CHAR_NAME_CONTINUATION_:
sanitized += "_"

Check warning on line 37 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L37

Added line #L37 was not covered by tests
else:
sanitized += c

Check warning on line 39 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L39

Added line #L39 was not covered by tests

return sanitized

Check warning on line 41 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L41

Added line #L41 was not covered by tests


def main(args=None):

Check warning on line 44 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L44

Added line #L44 was not covered by tests
"""Build tables from a MS VOLT project into an OTF font"""
parser = argparse.ArgumentParser(

Check warning on line 46 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L46

Added line #L46 was not covered by tests
description="Use fontTools to compile MS VOLT projects."
)
parser.add_argument(

Check warning on line 49 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L49

Added line #L49 was not covered by tests
"input",
metavar="INPUT",
help="Path to the input font/VTP file to process",
type=Path,
)
parser.add_argument(

Check warning on line 55 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L55

Added line #L55 was not covered by tests
"-f",
"--font",
metavar="INPUT_FONT",
help="Path to the input font (if INPUT is a VTP file)",
type=Path,
)
parser.add_argument(

Check warning on line 62 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L62

Added line #L62 was not covered by tests
"-o",
"--output",
dest="output",
metavar="OUTPUT",
help="Path to the output font.",
type=Path,
)
parser.add_argument(

Check warning on line 70 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L70

Added line #L70 was not covered by tests
"-t",
"--tables",
metavar="TABLE_TAG",
choices=SUPPORTED_TABLES,
nargs="+",
help="Specify the table(s) to be built.",
)
parser.add_argument(

Check warning on line 78 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L78

Added line #L78 was not covered by tests
"-F",
"--debug-feature-file",
help="Write the generated feature file to disk.",
action="store_true",
)
parser.add_argument(

Check warning on line 84 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L84

Added line #L84 was not covered by tests
"--ship",
help="Remove source VOLT tables from output font.",
action="store_true",
)
parser.add_argument(

Check warning on line 89 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L89

Added line #L89 was not covered by tests
"-v",
"--verbose",
help="Increase the logger verbosity. Multiple -v options are allowed.",
action="count",
default=0,
)
parser.add_argument(

Check warning on line 96 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L96

Added line #L96 was not covered by tests
"-T",
"--traceback",
help="show traceback for exceptions.",
action="store_true",
)
options = parser.parse_args(args)

Check warning on line 102 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L102

Added line #L102 was not covered by tests

levels = ["WARNING", "INFO", "DEBUG"]
configLogger(level=levels[min(len(levels) - 1, options.verbose)])

Check warning on line 105 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L104-L105

Added lines #L104 - L105 were not covered by tests

output_font = options.output or Path(

Check warning on line 107 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L107

Added line #L107 was not covered by tests
makeOutputFileName(options.font or options.input)
)
log.info(f"Compiling MS VOLT to '{output_font}'")

Check warning on line 110 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L110

Added line #L110 was not covered by tests

file_or_path = options.input
font = None

Check warning on line 113 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L112-L113

Added lines #L112 - L113 were not covered by tests

# If the input is a font file, extract the VOLT data from the "TSIV" table
try:
font = TTFont(file_or_path)

Check warning on line 117 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L116-L117

Added lines #L116 - L117 were not covered by tests
if "TSIV" in font:
file_or_path = StringIO(font["TSIV"].data.decode("utf-8"))

Check warning on line 119 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L119

Added line #L119 was not covered by tests
else:
log.error('"TSIV" table is missing')
return 1
except TTLibError:
pass

Check warning on line 124 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L121-L124

Added lines #L121 - L124 were not covered by tests

# If input is not a font file, the font must be provided
if font is None:
if not options.font:
log.error("Please provide an input font")
return 1
font = TTFont(options.font)

Check warning on line 131 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L129-L131

Added lines #L129 - L131 were not covered by tests

# FEA syntax does not allow some glyph names that VOLT accepts, so if we
# found such glyph name we will temporarily rename such glyphs.
glyphOrder = font.getGlyphOrder()
tempGlyphOrder = None

Check warning on line 136 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L135-L136

Added lines #L135 - L136 were not covered by tests
if any(invalid_fea_glyph_name(n) for n in glyphOrder):
tempGlyphOrder = []

Check warning on line 138 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L138

Added line #L138 was not covered by tests
for n in glyphOrder:
if invalid_fea_glyph_name(n):
n = sanitize_glyph_name(n)
existing = set(tempGlyphOrder) | set(glyphOrder)

Check warning on line 142 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L141-L142

Added lines #L141 - L142 were not covered by tests
while n in existing:
n = "a" + n
tempGlyphOrder.append(n)
font.setGlyphOrder(tempGlyphOrder)

Check warning on line 146 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L144-L146

Added lines #L144 - L146 were not covered by tests

doc = Parser(file_or_path).parse()

Check warning on line 148 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L148

Added line #L148 was not covered by tests

log.info("Converting VTP data to FEA")
converter = VoltToFea(doc, font)
try:
fea = converter.convert(options.tables, ignore_unsupported_settings=True)
except NotImplementedError as e:

Check warning on line 154 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L150-L154

Added lines #L150 - L154 were not covered by tests
if options.traceback:
raise
location = getattr(e.args[0], "location", None)
message = f'"{e}" is not supported'

Check warning on line 158 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L156-L158

Added lines #L156 - L158 were not covered by tests
if location:
path, line, column = location
log.error(f"{path}:{line}:{column}: {message}")

Check warning on line 161 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L160-L161

Added lines #L160 - L161 were not covered by tests
else:
log.error(message)
return 1

Check warning on line 164 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L163-L164

Added lines #L163 - L164 were not covered by tests

fea_filename = options.input

Check warning on line 166 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L166

Added line #L166 was not covered by tests
if options.debug_feature_file:
fea_filename = output_font.with_suffix(".fea")
log.info(f"Writing FEA to '{fea_filename}'")
with open(fea_filename, "w") as fp:
fp.write(fea)

Check warning on line 171 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L168-L171

Added lines #L168 - L171 were not covered by tests

log.info("Compiling FEA to OpenType tables")
try:
addOpenTypeFeaturesFromString(

Check warning on line 175 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L173-L175

Added lines #L173 - L175 were not covered by tests
font,
fea,
filename=fea_filename,
tables=options.tables,
)
except FeatureLibError as e:

Check warning on line 181 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L181

Added line #L181 was not covered by tests
if options.traceback:
raise
log.error(e)
return 1

Check warning on line 185 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L183-L185

Added lines #L183 - L185 were not covered by tests

if options.ship:
for tag in ["TSIV", "TSIS", "TSIP", "TSID"]:
if tag in font:
del font[tag]

Check warning on line 190 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L190

Added line #L190 was not covered by tests

# Restore original glyph names.
if tempGlyphOrder:
import io

Check warning on line 194 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L194

Added line #L194 was not covered by tests

f = io.BytesIO()
font.save(f)
font = TTFont(f)
font.setGlyphOrder(glyphOrder)
font["post"].extraNames = []

Check warning on line 200 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L196-L200

Added lines #L196 - L200 were not covered by tests

font.save(output_font)

Check warning on line 202 in Lib/fontTools/voltLib/__main__.py

View check run for this annotation

Codecov / codecov/patch

Lib/fontTools/voltLib/__main__.py#L202

Added line #L202 was not covered by tests


if __name__ == "__main__":
sys.exit(main())
4 changes: 4 additions & 0 deletions Lib/fontTools/voltLib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,10 @@ class SubstitutionLigatureDefinition(SubstitutionDefinition):
pass


class SubstitutionAlternateDefinition(SubstitutionDefinition):
pass


class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition):
pass

Expand Down
24 changes: 16 additions & 8 deletions Lib/fontTools/voltLib/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,19 +313,27 @@ def parse_substitution_(self, reversal):
self.expect_keyword_("END_SUBSTITUTION")
max_src = max([len(cov) for cov in src])
max_dest = max([len(cov) for cov in dest])

# many to many or mixed is invalid
if (max_src > 1 and max_dest > 1) or (
reversal and (max_src > 1 or max_dest > 1)
):
if max_src > 1 and max_dest > 1:
raise VoltLibError("Invalid substitution type", location)

mapping = dict(zip(tuple(src), tuple(dest)))
if max_src == 1 and max_dest == 1:
if reversal:
sub = ast.SubstitutionReverseChainingSingleDefinition(
mapping, location=location
)
# Alternate substitutions are represented by adding multiple
# substitutions for the same glyph, so we detect that here
glyphs = [x.glyphSet() for cov in src for x in cov] # flatten src
if len(set(glyphs)) != len(glyphs): # src has duplicates
sub = ast.SubstitutionAlternateDefinition(mapping, location=location)
else:
sub = ast.SubstitutionSingleDefinition(mapping, location=location)
if reversal:
# Reversal is valid only for single glyph substitutions
# and VOLT ignores it otherwise.
sub = ast.SubstitutionReverseChainingSingleDefinition(
mapping, location=location
)
else:
sub = ast.SubstitutionSingleDefinition(mapping, location=location)
elif max_src == 1 and max_dest > 1:
sub = ast.SubstitutionMultipleDefinition(mapping, location=location)
elif max_src > 1 and max_dest == 1:
Expand Down
Loading
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载