Skip to content

Commit

Permalink
Merge pull request #5 from anthrotype/desubroutinize
Browse files Browse the repository at this point in the history
add function to desubroutinize
  • Loading branch information
anthrotype committed May 29, 2020
2 parents 3945694 + fdd316e commit 93067d2
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 6 deletions.
57 changes: 52 additions & 5 deletions src/cffsubr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from fontTools import ttLib


__all__ = ["subroutinize", "Error"]
__all__ = ["subroutinize", "desubroutinize", "has_subroutines", "Error"]


try:
Expand Down Expand Up @@ -117,15 +117,18 @@ def _tx_subroutinize(data: bytes, output_format: str = CFFTableTag.CFF) -> bytes
return output_data


def _sniff_cff_table_format(otf: ttLib.TTFont) -> Optional[CFFTableTag]:
return next(
def _sniff_cff_table_format(otf: ttLib.TTFont) -> CFFTableTag:
cff_tag = next(
(
CFFTableTag(tag)
for tag in otf.keys()
if tag in CFFTableTag.__members__.values()
),
None,
)
if not cff_tag:
raise Error("Invalid OTF: no 'CFF ' or 'CFF2' tables found")
return cff_tag


def subroutinize(
Expand Down Expand Up @@ -158,8 +161,6 @@ def subroutinize(
or if subroutinization process fails.
"""
input_format = _sniff_cff_table_format(otf)
if not input_format:
raise Error("Invalid OTF: no 'CFF ' or 'CFF2' tables found")

if cff_version is None:
output_format = input_format
Expand Down Expand Up @@ -197,3 +198,49 @@ def subroutinize(
post_table.glyphOrder = glyphOrder

return otf


def has_subroutines(otf: ttLib.TTFont) -> bool:
"""Return True if the font's CFF or CFF2 table contains any subroutines."""
table_tag = _sniff_cff_table_format(otf)
top_dict = otf[table_tag].cff.topDictIndex[0]
all_subrs = [top_dict.GlobalSubrs]
if hasattr(top_dict, "FDArray"):
all_subrs.extend(
fd.Private.Subrs for fd in top_dict.FDArray if hasattr(fd.Private, "Subrs")
)
elif hasattr(top_dict.Private, "Subrs"):
all_subrs.append(top_dict.Private.Subrs)
return any(all_subrs)


def desubroutinize(otf: ttLib.TTFont, inplace=True) -> ttLib.TTFont:
"""Remove all subroutines from the font.
Args:
otf (ttLib.TTFont): the input font object.
inplace (bool): whether to create a copy or modify the input font. By default
the input font is modified.
Returns:
The modified font containing the desubroutinized CFF or CFF2 table.
This will be a different TTFont object if inplace=False.
Raises:
cffsubr.Error if the font doesn't contain 'CFF ' or 'CFF2' table,
or if desubroutinization process fails.
"""
# the 'desubroutinize' method is dynamically added to the CFF table class
# as a side-effect of importing the fontTools.subset.cff module...
from fontTools.subset import cff as _

if not inplace:
otf = copy.deepcopy(otf)

table_tag = _sniff_cff_table_format(otf)
try:
otf[table_tag].desubroutinize()
except Exception as e:
raise Error("Desubroutinization failed") from e

return otf
11 changes: 10 additions & 1 deletion src/cffsubr/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ def main(args=None):
action="store_false",
help="whether to drop postscript glyph names when converting from CFF to CFF2.",
)
parser.add_argument(
"-d",
"--desubroutinize",
action="store_true",
help="Don't subroutinize, instead remove all subroutines (in any).",
)
options = parser.parse_args(args)

if options.inplace:
Expand All @@ -47,7 +53,10 @@ def main(args=None):
options.output_file = sys.stdout.buffer

with ttLib.TTFont(options.input_file, lazy=True) as font:
cffsubr.subroutinize(font, options.cff_version, options.keep_glyph_names)
if options.desubroutinize:
cffsubr.desubroutinize(font)
else:
cffsubr.subroutinize(font, options.cff_version, options.keep_glyph_names)
font.save(options.output_file)


Expand Down
43 changes: 43 additions & 0 deletions tests/cffsubr_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,46 @@ def test_drop_glyph_names(self):
font2 = ttLib.TTFont(buf)

assert font2.getGlyphOrder() != glyph_order


@pytest.mark.parametrize(
"testfile, table_tag",
[
("SourceSansPro-Regular.subset.ttx", "CFF "),
("SourceSansVariable-Roman.subset.ttx", "CFF2"),
],
)
def test_sniff_cff_table_format(testfile, table_tag):
font = load_test_font(testfile)

assert cffsubr._sniff_cff_table_format(font) == table_tag


def test_sniff_cff_table_format_invalid():
with pytest.raises(cffsubr.Error, match="Invalid OTF"):
cffsubr._sniff_cff_table_format(ttLib.TTFont())


@pytest.mark.parametrize(
"testfile",
["SourceSansPro-Regular.subset.ttx", "SourceSansVariable-Roman.subset.ttx"],
)
def test_has_subroutines(testfile):
font = load_test_font(testfile)

assert not cffsubr.has_subroutines(font)
assert cffsubr.has_subroutines(cffsubr.subroutinize(font))


@pytest.mark.parametrize(
"testfile",
["SourceSansPro-Regular.subset.ttx", "SourceSansVariable-Roman.subset.ttx"],
)
def test_desubroutinize(testfile):
font = load_test_font(testfile)
cffsubr.subroutinize(font)

font2 = cffsubr.desubroutinize(font, inplace=False)

assert cffsubr.has_subroutines(font)
assert not cffsubr.has_subroutines(font2)

0 comments on commit 93067d2

Please sign in to comment.