Added the ability to recreate the PS stream (#2504)
* added the ability to recreate the PS stream This fixes #2503
This commit is contained in:
parent
8a139f921c
commit
b437417b71
@ -15,14 +15,17 @@ write(path, data, kind='OTHER', dohex=False)
|
|||||||
part should be written as hexadecimal or binary, but only if kind
|
part should be written as hexadecimal or binary, but only if kind
|
||||||
is 'OTHER'.
|
is 'OTHER'.
|
||||||
"""
|
"""
|
||||||
|
import fontTools
|
||||||
from fontTools.misc import eexec
|
from fontTools.misc import eexec
|
||||||
from fontTools.misc.macCreatorType import getMacCreatorAndType
|
from fontTools.misc.macCreatorType import getMacCreatorAndType
|
||||||
from fontTools.misc.textTools import bytechr, byteord, bytesjoin
|
from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes
|
||||||
|
from fontTools.misc.psOperators import _type1_pre_eexec_order, _type1_fontinfo_order, _type1_post_eexec_order
|
||||||
|
from fontTools.encodings.StandardEncoding import StandardEncoding
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
__author__ = "jvr"
|
__author__ = "jvr"
|
||||||
__version__ = "1.0b2"
|
__version__ = "1.0b3"
|
||||||
DEBUG = 0
|
DEBUG = 0
|
||||||
|
|
||||||
|
|
||||||
@ -65,8 +68,8 @@ class T1Font(object):
|
|||||||
write(path, self.getData(), type, dohex)
|
write(path, self.getData(), type, dohex)
|
||||||
|
|
||||||
def getData(self):
|
def getData(self):
|
||||||
# XXX Todo: if the data has been converted to Python object,
|
if not hasattr(self, "data"):
|
||||||
# recreate the PS stream
|
self.data = self.createData()
|
||||||
return self.data
|
return self.data
|
||||||
|
|
||||||
def getGlyphSet(self):
|
def getGlyphSet(self):
|
||||||
@ -102,6 +105,148 @@ class T1Font(object):
|
|||||||
subrs[i] = psCharStrings.T1CharString(charString[lenIV:], subrs=subrs)
|
subrs[i] = psCharStrings.T1CharString(charString[lenIV:], subrs=subrs)
|
||||||
del self.data
|
del self.data
|
||||||
|
|
||||||
|
def createData(self):
|
||||||
|
sf = self.font
|
||||||
|
|
||||||
|
eexec_began = False
|
||||||
|
eexec_dict = {}
|
||||||
|
lines = []
|
||||||
|
lines.extend([self._tobytes(f"%!FontType1-1.1: {sf['FontName']}"),
|
||||||
|
self._tobytes(f"%t1Font: ({fontTools.version})"),
|
||||||
|
self._tobytes(f"%%BeginResource: font {sf['FontName']}")])
|
||||||
|
# follow t1write.c:writeRegNameKeyedFont
|
||||||
|
size = 3 # Headroom for new key addition
|
||||||
|
size += 1 # FontMatrix is always counted
|
||||||
|
size += 1 + 1 # Private, CharStings
|
||||||
|
for key in font_dictionary_keys:
|
||||||
|
size += int(key in sf)
|
||||||
|
lines.append(self._tobytes(f"{size} dict dup begin"))
|
||||||
|
|
||||||
|
for key, value in sf.items():
|
||||||
|
if eexec_began:
|
||||||
|
eexec_dict[key] = value
|
||||||
|
continue
|
||||||
|
|
||||||
|
if key == "FontInfo":
|
||||||
|
fi = sf["FontInfo"]
|
||||||
|
# follow t1write.c:writeFontInfoDict
|
||||||
|
size = 3 # Headroom for new key addition
|
||||||
|
for subkey in FontInfo_dictionary_keys:
|
||||||
|
size += int(subkey in fi)
|
||||||
|
lines.append(self._tobytes(f"/FontInfo {size} dict dup begin"))
|
||||||
|
|
||||||
|
for subkey, subvalue in fi.items():
|
||||||
|
lines.extend(self._make_lines(subkey, subvalue))
|
||||||
|
lines.append(b"end def")
|
||||||
|
elif key in _type1_post_eexec_order: # usually 'Private'
|
||||||
|
eexec_dict[key] = value
|
||||||
|
eexec_began = True
|
||||||
|
else:
|
||||||
|
lines.extend(self._make_lines(key, value))
|
||||||
|
lines.append(b"end")
|
||||||
|
eexec_portion = self.encode_eexec(eexec_dict)
|
||||||
|
lines.append(bytesjoin([b"currentfile eexec ", eexec_portion]))
|
||||||
|
|
||||||
|
for _ in range(8):
|
||||||
|
lines.append(self._tobytes("0"*64))
|
||||||
|
lines.extend([b"cleartomark",
|
||||||
|
b"%%EndResource",
|
||||||
|
b"%%EOF"])
|
||||||
|
|
||||||
|
data = bytesjoin(lines, "\n")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def encode_eexec(self, eexec_dict):
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
# '-|', '|-', '|'
|
||||||
|
RD_key, ND_key, NP_key = None, None, None
|
||||||
|
|
||||||
|
for key, value in eexec_dict.items():
|
||||||
|
if key == "Private":
|
||||||
|
pr = eexec_dict["Private"]
|
||||||
|
# follow t1write.c:writePrivateDict
|
||||||
|
size = 3 # for RD, ND, NP
|
||||||
|
for subkey in Private_dictionary_keys:
|
||||||
|
size += int(subkey in pr)
|
||||||
|
lines.append(b"dup /Private")
|
||||||
|
lines.append(self._tobytes(f"{size} dict dup begin"))
|
||||||
|
for subkey, subvalue in pr.items():
|
||||||
|
if not RD_key and subvalue == RD_value:
|
||||||
|
RD_key = subkey
|
||||||
|
elif not ND_key and subvalue == ND_value:
|
||||||
|
ND_key = subkey
|
||||||
|
elif not NP_key and subvalue == PD_value:
|
||||||
|
NP_key = subkey
|
||||||
|
|
||||||
|
if subkey == 'OtherSubrs':
|
||||||
|
# XXX: assert that no flex hint is used
|
||||||
|
lines.append(self._tobytes(hintothers))
|
||||||
|
elif subkey == "Subrs":
|
||||||
|
# XXX: standard Subrs only
|
||||||
|
lines.append(b"/Subrs 5 array")
|
||||||
|
for i, subr_bin in enumerate(std_subrs):
|
||||||
|
encrypted_subr, R = eexec.encrypt(bytesjoin([char_IV, subr_bin]), 4330)
|
||||||
|
lines.append(bytesjoin([self._tobytes(f"dup {i} {len(encrypted_subr)} {RD_key} "), encrypted_subr, self._tobytes(f" {NP_key}")]))
|
||||||
|
lines.append(b'def')
|
||||||
|
|
||||||
|
lines.append(b"put")
|
||||||
|
else:
|
||||||
|
lines.extend(self._make_lines(subkey, subvalue))
|
||||||
|
elif key == "CharStrings":
|
||||||
|
lines.append(b"dup /CharStrings")
|
||||||
|
lines.append(self._tobytes(f"{len(eexec_dict['CharStrings'])} dict dup begin"))
|
||||||
|
for glyph_name, char_bin in eexec_dict["CharStrings"].items():
|
||||||
|
char_bin.compile()
|
||||||
|
encrypted_char, R = eexec.encrypt(bytesjoin([char_IV, char_bin.bytecode]), 4330)
|
||||||
|
lines.append(bytesjoin([self._tobytes(f"/{glyph_name} {len(encrypted_char)} {RD_key} "), encrypted_char, self._tobytes(f" {ND_key}")]))
|
||||||
|
lines.append(b"end put")
|
||||||
|
else:
|
||||||
|
lines.extend(self._make_lines(key, value))
|
||||||
|
|
||||||
|
lines.extend([b"end",
|
||||||
|
b"dup /FontName get exch definefont pop",
|
||||||
|
b"mark",
|
||||||
|
b"currentfile closefile\n"])
|
||||||
|
|
||||||
|
eexec_portion = bytesjoin(lines, "\n")
|
||||||
|
encrypted_eexec, R = eexec.encrypt(bytesjoin([eexec_IV, eexec_portion]), 55665)
|
||||||
|
|
||||||
|
return encrypted_eexec
|
||||||
|
|
||||||
|
def _make_lines(self, key, value):
|
||||||
|
if key == "FontName":
|
||||||
|
return [self._tobytes(f"/{key} /{value} def")]
|
||||||
|
if key in ["isFixedPitch", "ForceBold", "RndStemUp"]:
|
||||||
|
return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
|
||||||
|
elif key == "Encoding":
|
||||||
|
if value == StandardEncoding:
|
||||||
|
return [self._tobytes(f"/{key} StandardEncoding def")]
|
||||||
|
else:
|
||||||
|
# follow fontTools.misc.psOperators._type1_Encoding_repr
|
||||||
|
lines = []
|
||||||
|
lines.append(b"/Encoding 256 array")
|
||||||
|
lines.append(b"0 1 255 {1 index exch /.notdef put} for")
|
||||||
|
for i in range(256):
|
||||||
|
name = value[i]
|
||||||
|
if name != ".notdef":
|
||||||
|
lines.append(self._tobytes(f"dup {i} /{name} put"))
|
||||||
|
lines.append(b"def")
|
||||||
|
return lines
|
||||||
|
if isinstance(value, str):
|
||||||
|
return [self._tobytes(f"/{key} ({value}) def")]
|
||||||
|
elif isinstance(value, bool):
|
||||||
|
return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
|
||||||
|
elif isinstance(value, list):
|
||||||
|
return [self._tobytes(f"/{key} [{' '.join(str(v) for v in value)}] def")]
|
||||||
|
elif isinstance(value, tuple):
|
||||||
|
return [self._tobytes(f"/{key} {{{' '.join(str(v) for v in value)}}} def")]
|
||||||
|
else:
|
||||||
|
return [self._tobytes(f"/{key} {value} def")]
|
||||||
|
|
||||||
|
def _tobytes(self, s, errors="strict"):
|
||||||
|
return tobytes(s, self.encoding, errors)
|
||||||
|
|
||||||
|
|
||||||
# low level T1 data read and write functions
|
# low level T1 data read and write functions
|
||||||
|
|
||||||
@ -367,3 +512,69 @@ def stringToLong(s):
|
|||||||
for i in range(4):
|
for i in range(4):
|
||||||
l += byteord(s[i]) << (i * 8)
|
l += byteord(s[i]) << (i * 8)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
# PS stream helpers
|
||||||
|
|
||||||
|
font_dictionary_keys = list(_type1_pre_eexec_order)
|
||||||
|
# t1write.c:writeRegNameKeyedFont
|
||||||
|
# always counts following keys
|
||||||
|
font_dictionary_keys.remove("FontMatrix")
|
||||||
|
|
||||||
|
FontInfo_dictionary_keys = list(_type1_fontinfo_order)
|
||||||
|
# extend because AFDKO tx may use following keys
|
||||||
|
FontInfo_dictionary_keys.extend([
|
||||||
|
"FSType",
|
||||||
|
"Copyright",
|
||||||
|
])
|
||||||
|
|
||||||
|
Private_dictionary_keys = [
|
||||||
|
# We don't know what names will be actually used.
|
||||||
|
# "RD",
|
||||||
|
# "ND",
|
||||||
|
# "NP",
|
||||||
|
"Subrs",
|
||||||
|
"OtherSubrs",
|
||||||
|
"UniqueID",
|
||||||
|
"BlueValues",
|
||||||
|
"OtherBlues",
|
||||||
|
"FamilyBlues",
|
||||||
|
"FamilyOtherBlues",
|
||||||
|
"BlueScale",
|
||||||
|
"BlueShift",
|
||||||
|
"BlueFuzz",
|
||||||
|
"StdHW",
|
||||||
|
"StdVW",
|
||||||
|
"StemSnapH",
|
||||||
|
"StemSnapV",
|
||||||
|
"ForceBold",
|
||||||
|
"LanguageGroup",
|
||||||
|
"password",
|
||||||
|
"lenIV",
|
||||||
|
"MinFeature",
|
||||||
|
"RndStemUp",
|
||||||
|
]
|
||||||
|
|
||||||
|
# t1write_hintothers.h
|
||||||
|
hintothers = """/OtherSubrs[{}{}{}{systemdict/internaldict known not{pop 3}{1183615869
|
||||||
|
systemdict/internaldict get exec dup/startlock known{/startlock get exec}{dup
|
||||||
|
/strtlck known{/strtlck get exec}{pop 3}ifelse}ifelse}ifelse}executeonly]def"""
|
||||||
|
# t1write.c:saveStdSubrs
|
||||||
|
std_subrs = [
|
||||||
|
# 3 0 callother pop pop setcurrentpoint return
|
||||||
|
b"\x8e\x8b\x0c\x10\x0c\x11\x0c\x11\x0c\x21\x0b",
|
||||||
|
# 0 1 callother return
|
||||||
|
b"\x8b\x8c\x0c\x10\x0b",
|
||||||
|
# 0 2 callother return
|
||||||
|
b"\x8b\x8d\x0c\x10\x0b",
|
||||||
|
# return
|
||||||
|
b"\x0b",
|
||||||
|
# 3 1 3 callother pop callsubr return
|
||||||
|
b"\x8e\x8c\x8e\x0c\x10\x0c\x11\x0a\x0b"
|
||||||
|
]
|
||||||
|
# follow t1write.c:writeRegNameKeyedFont
|
||||||
|
eexec_IV = b"cccc"
|
||||||
|
char_IV = b"\x0c\x0c\x0c\x0c"
|
||||||
|
RD_value = ("string", "currentfile", "exch", "readstring", "pop")
|
||||||
|
ND_value = ("def",)
|
||||||
|
PD_value = ("put",)
|
||||||
|
73
Tests/t1Lib/data/TestT1-ellipsis-hinted.pfa
Normal file
73
Tests/t1Lib/data/TestT1-ellipsis-hinted.pfa
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
%!FontType1-1.1: TestT1-Regular 1.0
|
||||||
|
%%BeginResource: font TestT1-Regular
|
||||||
|
12 dict dup begin
|
||||||
|
/FontType 1 def
|
||||||
|
/FontName /TestT1-Regular def
|
||||||
|
/FontInfo 14 dict dup begin
|
||||||
|
/version (1.0) def
|
||||||
|
/Notice (Test T1 is not a trademark of FontTools.) def
|
||||||
|
/Copyright (Copyright c 2015 by FontTools. No rights reserved.) def
|
||||||
|
/FullName (Test T1) def
|
||||||
|
/FamilyName (Test T1) def
|
||||||
|
/Weight (Regular) def
|
||||||
|
/ItalicAngle 0.000000 def
|
||||||
|
/isFixedPitch false def
|
||||||
|
/UnderlinePosition -75.000000 def
|
||||||
|
/UnderlineThickness 50.000000 def
|
||||||
|
/FSType 0 def
|
||||||
|
end def
|
||||||
|
/PaintType 0 def
|
||||||
|
/FontMatrix [0.001 0 0 0.001 0 0] def
|
||||||
|
/Encoding 256 array
|
||||||
|
0 1 255 {1 index exch /.notdef put} for
|
||||||
|
def
|
||||||
|
/FontBBox {50.000000 0.000000 668.000000 750.000000} def
|
||||||
|
end
|
||||||
|
currentfile eexec a0b00ed5187d9c0f1ecdf51878c3aa5c80164785b2862e5e248314a0dd481e1d
|
||||||
|
8115d1b5316c01657582d7e6cf3fa3e1aa00733eacc08e989313fc4f69225c12
|
||||||
|
e1cce25e5f99b47841c1a01a3778ce0d2cd604a925e1a95952caddc379227509
|
||||||
|
e885feae9bdf476bbc2a66a211f868f2cfc17055dcf167e621fd9f41a20f2647
|
||||||
|
a3433004f074ba98472481205cebb34224112701c802754418c103b503fdcc51
|
||||||
|
ff373f66fea5ab6b7578cc881c6c46de4327adf847412c9df1700b8402f28395
|
||||||
|
fc36b2e8826ef268b1f4c0f79d3612e999d7a22412f8fcd4398cba1b4db9b0a1
|
||||||
|
a050a7777e3b04617c75f9373f76b7a1daffd6423d0bd68c190f563fa54ff699
|
||||||
|
bbe5e5ff80d2329a8f7c5f85d10fe3410cf665fb3e3271d7d92bdbeac0bd6a11
|
||||||
|
a3726b6bbc45a143b60b22ae9f5cb25b595bfd3797d0aaca765516fa103b5d01
|
||||||
|
e016ca279efa8c90b062ee151238c62dda8d109bea1814ab49ab93034196e785
|
||||||
|
a7629fc297ddb0b5ba7bb1da4d9b69c35c4038082747cf0180fb387f1925640a
|
||||||
|
239c91872b562233065484922f59063bd42241053bf69317d22dbcbfc869996a
|
||||||
|
11f5f628930da9a2d48d327804add904de6bb0bd84b6584eacdbc2760af6d44f
|
||||||
|
d4a765c1829b2b650773894ca9d7e303b8dfc43804e4b60a7eb1506139fa96b8
|
||||||
|
0f70238f69a4ce6ad2a7e20f344e9f69930fe1cc30688775b2875de4f79039e0
|
||||||
|
8c0dc7db7470d9773ad4345d50c1fbdb9089c91b6d170bc3e39b61b3d86e3a78
|
||||||
|
1e31d872384c85a6241dc349d0af38785e1f37426ac0ca1b7369bf00c947953a
|
||||||
|
191be897b89a84d903f590ee852b34433d892f9f60e6b2236e6dee1562d69c9d
|
||||||
|
e80205d7aa49f9c7d01f8afb16babf9979d3dccf28b23160934d425f503d82d1
|
||||||
|
d602bdbea6c0378fa6631f4dd3709dc6db7395663356df1eb3b6ee42d8006e0f
|
||||||
|
9c1d0402f1bc9cf656bc0f75f51db76781f447139a4babad33af6309d271b472
|
||||||
|
7607ca92ff947e561d94a66eda64788fd32c7427341bf5d365a0cb4bbf0a0534
|
||||||
|
1a92824f0a80a678ffd6320d9feb538cde66cb65a5d3a7a53d601732fa83efaf
|
||||||
|
05effa0938b101cfd2dac6a80631c10a7d0a2ea5950a2af3de5afbab29590986
|
||||||
|
27fc0526581792c06db8374568e14023384584bba23ac87921af46943a23e0ea
|
||||||
|
7aa4c0f6a57cbe7c91fac9d612e36f721f518945ce0f70cf3084cf1565480cd9
|
||||||
|
f75e6183eefb58d4936a4c8ff76b80b3b9b12d876b3f0c8773a105cf2a24d87e
|
||||||
|
99feed1ca9114d37698f07763c538a011672b1fc1aa9ed610134f7d7
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
cleartomark
|
||||||
|
%%EndResource
|
||||||
|
%%EOF
|
@ -3,6 +3,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from fontTools import t1Lib
|
from fontTools import t1Lib
|
||||||
from fontTools.pens.basePen import NullPen
|
from fontTools.pens.basePen import NullPen
|
||||||
|
from fontTools.misc.psCharStrings import T1CharString
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
@ -13,6 +14,8 @@ LWFN = os.path.join(DATADIR, 'TestT1-Regular.lwfn')
|
|||||||
PFA = os.path.join(DATADIR, 'TestT1-Regular.pfa')
|
PFA = os.path.join(DATADIR, 'TestT1-Regular.pfa')
|
||||||
PFB = os.path.join(DATADIR, 'TestT1-Regular.pfb')
|
PFB = os.path.join(DATADIR, 'TestT1-Regular.pfb')
|
||||||
WEIRD_ZEROS = os.path.join(DATADIR, 'TestT1-weird-zeros.pfa')
|
WEIRD_ZEROS = os.path.join(DATADIR, 'TestT1-weird-zeros.pfa')
|
||||||
|
# ellipsis is hinted with 55 131 296 131 537 131 vstem3 0 122 hstem
|
||||||
|
ELLIPSIS_HINTED = os.path.join(DATADIR, 'TestT1-ellipsis-hinted.pfa')
|
||||||
|
|
||||||
|
|
||||||
class FindEncryptedChunksTest(unittest.TestCase):
|
class FindEncryptedChunksTest(unittest.TestCase):
|
||||||
@ -52,23 +55,40 @@ class ReadWriteTest(unittest.TestCase):
|
|||||||
data = self.write(font, 'PFB')
|
data = self.write(font, 'PFB')
|
||||||
self.assertEqual(font.getData(), data)
|
self.assertEqual(font.getData(), data)
|
||||||
|
|
||||||
|
def test_read_and_parse_pfa_write_pfb(self):
|
||||||
|
font = t1Lib.T1Font(PFA)
|
||||||
|
font.parse()
|
||||||
|
saved_font = self.write(font, 'PFB', dohex=False, doparse=True)
|
||||||
|
self.assertTrue(same_dicts(font.font, saved_font))
|
||||||
|
|
||||||
def test_read_pfb_write_pfa(self):
|
def test_read_pfb_write_pfa(self):
|
||||||
font = t1Lib.T1Font(PFB)
|
font = t1Lib.T1Font(PFB)
|
||||||
# 'OTHER' == 'PFA'
|
# 'OTHER' == 'PFA'
|
||||||
data = self.write(font, 'OTHER', dohex=True)
|
data = self.write(font, 'OTHER', dohex=True)
|
||||||
self.assertEqual(font.getData(), data)
|
self.assertEqual(font.getData(), data)
|
||||||
|
|
||||||
|
def test_read_and_parse_pfb_write_pfa(self):
|
||||||
|
font = t1Lib.T1Font(PFB)
|
||||||
|
font.parse()
|
||||||
|
# 'OTHER' == 'PFA'
|
||||||
|
saved_font = self.write(font, 'OTHER', dohex=True, doparse=True)
|
||||||
|
self.assertTrue(same_dicts(font.font, saved_font))
|
||||||
|
|
||||||
def test_read_with_path(self):
|
def test_read_with_path(self):
|
||||||
import pathlib
|
import pathlib
|
||||||
font = t1Lib.T1Font(pathlib.Path(PFB))
|
font = t1Lib.T1Font(pathlib.Path(PFB))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write(font, outtype, dohex=False):
|
def write(font, outtype, dohex=False, doparse=False):
|
||||||
temp = os.path.join(DATADIR, 'temp.' + outtype.lower())
|
temp = os.path.join(DATADIR, 'temp.' + outtype.lower())
|
||||||
try:
|
try:
|
||||||
font.saveAs(temp, outtype, dohex=dohex)
|
font.saveAs(temp, outtype, dohex=dohex)
|
||||||
newfont = t1Lib.T1Font(temp)
|
newfont = t1Lib.T1Font(temp)
|
||||||
data = newfont.getData()
|
if doparse:
|
||||||
|
newfont.parse()
|
||||||
|
data = newfont.font
|
||||||
|
else:
|
||||||
|
data = newfont.getData()
|
||||||
finally:
|
finally:
|
||||||
if os.path.exists(temp):
|
if os.path.exists(temp):
|
||||||
os.remove(temp)
|
os.remove(temp)
|
||||||
@ -107,6 +127,75 @@ class T1FontTest(unittest.TestCase):
|
|||||||
self.assertTrue(hasattr(aglyph, 'width'))
|
self.assertTrue(hasattr(aglyph, 'width'))
|
||||||
|
|
||||||
|
|
||||||
|
class EditTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_edit_pfa(self):
|
||||||
|
font = t1Lib.T1Font(PFA)
|
||||||
|
ellipsis = font.getGlyphSet()["ellipsis"]
|
||||||
|
ellipsis.decompile()
|
||||||
|
program = []
|
||||||
|
for v in ellipsis.program:
|
||||||
|
try:
|
||||||
|
program.append(int(v))
|
||||||
|
except:
|
||||||
|
program.append(v)
|
||||||
|
if v == 'hsbw':
|
||||||
|
hints = [55, 131, 296, 131, 537, 131, 'vstem3', 0, 122, 'hstem']
|
||||||
|
program.extend(hints)
|
||||||
|
ellipsis.program = program
|
||||||
|
# 'OTHER' == 'PFA'
|
||||||
|
saved_font = self.write(font, 'OTHER', dohex=True, doparse=True)
|
||||||
|
hinted_font = t1Lib.T1Font(ELLIPSIS_HINTED)
|
||||||
|
hinted_font.parse()
|
||||||
|
self.assertTrue(same_dicts(hinted_font.font, saved_font))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write(font, outtype, dohex=False, doparse=False):
|
||||||
|
temp = os.path.join(DATADIR, 'temp.' + outtype.lower())
|
||||||
|
try:
|
||||||
|
font.saveAs(temp, outtype, dohex=dohex)
|
||||||
|
newfont = t1Lib.T1Font(temp)
|
||||||
|
if doparse:
|
||||||
|
newfont.parse()
|
||||||
|
data = newfont.font
|
||||||
|
else:
|
||||||
|
data = newfont.getData()
|
||||||
|
finally:
|
||||||
|
if os.path.exists(temp):
|
||||||
|
os.remove(temp)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def same_dicts(dict1, dict2):
|
||||||
|
if dict1.keys() != dict2.keys():
|
||||||
|
return False
|
||||||
|
for key, value in dict1.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
if not same_dicts(value, dict2[key]):
|
||||||
|
return False
|
||||||
|
elif isinstance(value, list):
|
||||||
|
if len(value) != len(dict2[key]):
|
||||||
|
return False
|
||||||
|
for elem1, elem2 in zip(value, dict2[key]):
|
||||||
|
if isinstance(elem1, T1CharString):
|
||||||
|
elem1.compile()
|
||||||
|
elem2.compile()
|
||||||
|
if elem1.bytecode != elem2.bytecode:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if elem1 != elem2:
|
||||||
|
return False
|
||||||
|
elif isinstance(value, T1CharString):
|
||||||
|
value.compile()
|
||||||
|
dict2[key].compile()
|
||||||
|
if value.bytecode != dict2[key].bytecode:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
if value != dict2[key]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
sys.exit(unittest.main())
|
sys.exit(unittest.main())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user