Handle string input to Program.fromAssembly() (#3038)
* Add tests for fromAssembly() output (#3036) * Handle string input to fromAssembly() (Fixes #3036) * Fixups suggested by @anthrotype * Add some more typing annotations * Program.assembly always returns List[str] now * Add annotation for bytecode * Move code from setter to fromAssembly * Remove property * Fix attribute names
This commit is contained in:
parent
444a349ef4
commit
f1a75bacb2
@ -1,8 +1,10 @@
|
||||
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fontTools.misc.textTools import num2binary, binary2num, readHex, strjoin
|
||||
import array
|
||||
from io import StringIO
|
||||
from typing import List
|
||||
import re
|
||||
import logging
|
||||
|
||||
@ -1064,30 +1066,35 @@ def _skipWhite(data, pos):
|
||||
|
||||
|
||||
class Program(object):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
def fromBytecode(self, bytecode):
|
||||
def fromBytecode(self, bytecode: bytes) -> None:
|
||||
self.bytecode = array.array("B", bytecode)
|
||||
if hasattr(self, "assembly"):
|
||||
del self.assembly
|
||||
|
||||
def fromAssembly(self, assembly):
|
||||
self.assembly = assembly
|
||||
def fromAssembly(self, assembly: List[str] | str) -> None:
|
||||
if isinstance(assembly, list):
|
||||
self.assembly = assembly
|
||||
elif isinstance(assembly, str):
|
||||
self.assembly = assembly.splitlines()
|
||||
else:
|
||||
raise TypeError(f"expected str or List[str], got {type(assembly).__name__}")
|
||||
if hasattr(self, "bytecode"):
|
||||
del self.bytecode
|
||||
|
||||
def getBytecode(self):
|
||||
def getBytecode(self) -> bytes:
|
||||
if not hasattr(self, "bytecode"):
|
||||
self._assemble()
|
||||
return self.bytecode.tobytes()
|
||||
|
||||
def getAssembly(self, preserve=True):
|
||||
def getAssembly(self, preserve=True) -> List[str]:
|
||||
if not hasattr(self, "assembly"):
|
||||
self._disassemble(preserve=preserve)
|
||||
return self.assembly
|
||||
|
||||
def toXML(self, writer, ttFont):
|
||||
def toXML(self, writer, ttFont) -> None:
|
||||
if (
|
||||
not hasattr(ttFont, "disassembleInstructions")
|
||||
or ttFont.disassembleInstructions
|
||||
@ -1128,7 +1135,7 @@ class Program(object):
|
||||
i = i + 1
|
||||
if m:
|
||||
nValues = int(m.group(1))
|
||||
line = []
|
||||
line: List[str] = []
|
||||
j = 0
|
||||
for j in range(nValues):
|
||||
if j and not (j % 25):
|
||||
@ -1155,7 +1162,7 @@ class Program(object):
|
||||
writer.endtag("bytecode")
|
||||
writer.newline()
|
||||
|
||||
def fromXML(self, name, attrs, content, ttFont):
|
||||
def fromXML(self, name, attrs, content, ttFont) -> None:
|
||||
if name == "assembly":
|
||||
self.fromAssembly(strjoin(content))
|
||||
self._assemble()
|
||||
@ -1164,11 +1171,9 @@ class Program(object):
|
||||
assert name == "bytecode"
|
||||
self.fromBytecode(readHex(content))
|
||||
|
||||
def _assemble(self):
|
||||
assembly = getattr(self, "assembly", [])
|
||||
if isinstance(assembly, type([])):
|
||||
assembly = " ".join(assembly)
|
||||
bytecode = []
|
||||
def _assemble(self) -> None:
|
||||
assembly = " ".join(getattr(self, "assembly", []))
|
||||
bytecode: List[int] = []
|
||||
push = bytecode.append
|
||||
lenAssembly = len(assembly)
|
||||
pos = _skipWhite(assembly, 0)
|
||||
@ -1311,7 +1316,7 @@ class Program(object):
|
||||
assert max(bytecode) < 256 and min(bytecode) >= 0
|
||||
self.bytecode = array.array("B", bytecode)
|
||||
|
||||
def _disassemble(self, preserve=False):
|
||||
def _disassemble(self, preserve=False) -> None:
|
||||
assembly = []
|
||||
i = 0
|
||||
bytecode = getattr(self, "bytecode", [])
|
||||
@ -1376,7 +1381,7 @@ class Program(object):
|
||||
i = i + 1
|
||||
self.assembly = assembly
|
||||
|
||||
def __bool__(self):
|
||||
def __bool__(self) -> bool:
|
||||
"""
|
||||
>>> p = Program()
|
||||
>>> bool(p)
|
||||
@ -1406,12 +1411,12 @@ class Program(object):
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other) -> bool:
|
||||
if type(self) != type(other):
|
||||
return NotImplemented
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
def __ne__(self, other):
|
||||
def __ne__(self, other) -> bool:
|
||||
result = self.__eq__(other)
|
||||
return result if result is NotImplemented else not result
|
||||
|
||||
|
@ -12,6 +12,13 @@ DATA_DIR = os.path.join(CURR_DIR, "data")
|
||||
TTPROGRAM_TTX = os.path.join(DATA_DIR, "ttProgram.ttx")
|
||||
# TTPROGRAM_BIN = os.path.join(DATA_DIR, "ttProgram.bin")
|
||||
|
||||
ASSEMBLY = [
|
||||
"PUSH[ ]",
|
||||
"0 4 3",
|
||||
"INSTCTRL[ ]",
|
||||
"POP[ ]",
|
||||
]
|
||||
|
||||
BYTECODE = deHexStr(
|
||||
"403b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a"
|
||||
"191817161514131211100f0e0d0c0b0a090807060504030201002c01b0184358456ab0"
|
||||
@ -90,6 +97,18 @@ class ProgramTest(unittest.TestCase):
|
||||
assert p.assembly.pop() == "SVTCA[0]"
|
||||
assert not bool(p)
|
||||
|
||||
def test_from_assembly_list(self):
|
||||
p = Program()
|
||||
p.fromAssembly(ASSEMBLY)
|
||||
asm = p.getAssembly()
|
||||
assert ASSEMBLY == asm
|
||||
|
||||
def test_from_assembly_str(self):
|
||||
p = Program()
|
||||
p.fromAssembly("\n".join(ASSEMBLY))
|
||||
asm = p.getAssembly()
|
||||
assert ASSEMBLY == asm
|
||||
|
||||
def test_roundtrip(self):
|
||||
p = Program()
|
||||
p.fromBytecode(BYTECODE)
|
||||
|
Loading…
x
Reference in New Issue
Block a user