Merge pull request #3020 from fonttools/ttx-stdin

[ttx] support reading font/xml file from standard input as '-'
This commit is contained in:
Cosimo Lupo 2023-03-03 17:28:58 +00:00 committed by GitHub
commit e26da961ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 13 deletions

View File

@ -146,11 +146,13 @@ class TTFont(object):
else: else:
# assume "file" is a readable file object # assume "file" is a readable file object
closeStream = False closeStream = False
file.seek(0) if file.seekable():
file.seek(0)
if not self.lazy: if not self.lazy:
# read input file in memory and wrap a stream around it to allow overwriting # read input file in memory and wrap a stream around it to allow overwriting
file.seek(0) if file.seekable():
file.seek(0)
tmp = BytesIO(file.read()) tmp = BytesIO(file.read())
if hasattr(file, "name"): if hasattr(file, "name"):
# save reference to input file name # save reference to input file name

View File

@ -5,8 +5,9 @@ TTX -- From OpenType To XML And Back
If an input file is a TrueType or OpenType font file, it will be If an input file is a TrueType or OpenType font file, it will be
decompiled to a TTX file (an XML-based text format). decompiled to a TTX file (an XML-based text format).
If an input file is a TTX file, it will be compiled to whatever If an input file is a TTX file, it will be compiled to whatever
format the data is in, a TrueType or OpenType/CFF font file. format the data is in, a TrueType or OpenType/CFF font file.
A special input value of - means read from the standard input.
Output files are created so they are unique: an existing file is Output files are created so they are unique: an existing file is
never overwritten. never overwritten.
@ -278,7 +279,13 @@ def ttList(input, output, options):
@Timer(log, "Done dumping TTX in %(time).3f seconds") @Timer(log, "Done dumping TTX in %(time).3f seconds")
def ttDump(input, output, options): def ttDump(input, output, options):
log.info('Dumping "%s" to "%s"...', input, output) input_name = input
if input == "-":
input, input_name = sys.stdin.buffer, sys.stdin.name
output_name = output
if output == "-":
output, output_name = sys.stdout, sys.stdout.name
log.info('Dumping "%s" to "%s"...', input_name, output_name)
if options.unicodedata: if options.unicodedata:
setUnicodeData(options.unicodedata) setUnicodeData(options.unicodedata)
ttf = TTFont( ttf = TTFont(
@ -302,7 +309,13 @@ def ttDump(input, output, options):
@Timer(log, "Done compiling TTX in %(time).3f seconds") @Timer(log, "Done compiling TTX in %(time).3f seconds")
def ttCompile(input, output, options): def ttCompile(input, output, options):
log.info('Compiling "%s" to "%s"...' % (input, output)) input_name = input
if input == "-":
input, input_name = sys.stdin, sys.stdin.name
output_name = output
if output == "-":
output, output_name = sys.stdout.buffer, sys.stdout.name
log.info('Compiling "%s" to "%s"...' % (input_name, output))
if options.useZopfli: if options.useZopfli:
from fontTools.ttLib import sfnt from fontTools.ttLib import sfnt
@ -315,7 +328,7 @@ def ttCompile(input, output, options):
) )
ttf.importXML(input) ttf.importXML(input)
if options.recalcTimestamp is None and "head" in ttf: if options.recalcTimestamp is None and "head" in ttf and input is not sys.stdin:
# use TTX file modification time for head "modified" timestamp # use TTX file modification time for head "modified" timestamp
mtime = os.path.getmtime(input) mtime = os.path.getmtime(input)
ttf["head"].modified = timestampSinceEpoch(mtime) ttf["head"].modified = timestampSinceEpoch(mtime)
@ -324,12 +337,16 @@ def ttCompile(input, output, options):
def guessFileType(fileName): def guessFileType(fileName):
base, ext = os.path.splitext(fileName) if fileName == "-":
try: header = sys.stdin.buffer.peek(256)
with open(fileName, "rb") as f: ext = ""
header = f.read(256) else:
except IOError: base, ext = os.path.splitext(fileName)
return None try:
with open(fileName, "rb") as f:
header = f.read(256)
except IOError:
return None
if header.startswith(b"\xef\xbb\xbf<?xml"): if header.startswith(b"\xef\xbb\xbf<?xml"):
header = header.lstrip(b"\xef\xbb\xbf") header = header.lstrip(b"\xef\xbb\xbf")
@ -381,7 +398,7 @@ def parseOptions(args):
raise getopt.GetoptError("Must specify at least one input file") raise getopt.GetoptError("Must specify at least one input file")
for input in files: for input in files:
if not os.path.isfile(input): if input != "-" and not os.path.isfile(input):
raise getopt.GetoptError('File not found: "%s"' % input) raise getopt.GetoptError('File not found: "%s"' % input)
tp = guessFileType(input) tp = guessFileType(input)
if tp in ("OTF", "TTF", "TTC", "WOFF", "WOFF2"): if tp in ("OTF", "TTF", "TTC", "WOFF", "WOFF2"):
@ -402,6 +419,8 @@ def parseOptions(args):
if options.outputFile: if options.outputFile:
output = options.outputFile output = options.outputFile
else: else:
if input == "-":
raise getopt.GetoptError("Must provide -o when reading from stdin")
output = makeOutputFileName( output = makeOutputFileName(
input, options.outputDir, extension, options.overWrite input, options.outputDir, extension, options.overWrite
) )

View File

@ -6,9 +6,11 @@ import getopt
import logging import logging
import os import os
import shutil import shutil
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
from pathlib import Path
import pytest import pytest
@ -996,6 +998,24 @@ def test_main_base_exception(tmpdir, monkeypatch, caplog):
assert "Unhandled exception has occurred" in caplog.text assert "Unhandled exception has occurred" in caplog.text
def test_main_ttf_dump_stdin_to_stdout(tmp_path):
inpath = Path("Tests").joinpath("ttx", "data", "TestTTF.ttf")
outpath = tmp_path / "TestTTF.ttx"
args = [sys.executable, "-m", "fontTools.ttx", "-q", "-o", "-", "-"]
with inpath.open("rb") as infile, outpath.open("w", encoding="utf-8") as outfile:
subprocess.run(args, check=True, stdin=infile, stdout=outfile)
assert outpath.is_file()
def test_main_ttx_compile_stdin_to_stdout(tmp_path):
inpath = Path("Tests").joinpath("ttx", "data", "TestTTF.ttx")
outpath = tmp_path / "TestTTF.ttf"
args = [sys.executable, "-m", "fontTools.ttx", "-q", "-o", "-", "-"]
with inpath.open("r", encoding="utf-8") as infile, outpath.open("wb") as outfile:
subprocess.run(args, check=True, stdin=infile, stdout=outfile)
assert outpath.is_file()
# --------------------------- # ---------------------------
# support functions for tests # support functions for tests
# --------------------------- # ---------------------------