Skip to content
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

type1font.py fixes and test case #4522

Merged
merged 10 commits into from Jul 3, 2015
Binary file added lib/matplotlib/tests/cmr10.pfb
Binary file not shown.
55 changes: 55 additions & 0 deletions lib/matplotlib/tests/test_type1font.py
@@ -0,0 +1,55 @@
from __future__ import (absolute_import, division, print_function,
unicode_literals)

import six

from nose.tools import assert_equal, assert_in
import matplotlib.type1font as t1f
import os.path
import difflib
import hashlib


def sha1(data):
hash = hashlib.sha1()
hash.update(data)
return hash.hexdigest()


def test_Type1Font():
filename = os.path.join(os.path.dirname(__file__), 'cmr10.pfb')
font = t1f.Type1Font(filename)
slanted = font.transform({'slant': 1})
condensed = font.transform({'extend': 0.5})
assert_equal(map(sha1, font.parts),
['f4ce890d648e67d413a91b3109fe67a732ced96f',
'af46adb6c528956580c125c6abf3b5eb9983bbc1',
'e2538a88a810bc207cfa1194b658ee8967042db8'])
assert_equal(font.parts[1:], slanted.parts[1:])
assert_equal(font.parts[1:], condensed.parts[1:])

differ = difflib.Differ()
diff = set(differ.compare(font.parts[0].splitlines(),
slanted.parts[0].splitlines()))
for line in (
# Removes UniqueID
'- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup',
'+ FontDirectory/CMR10 known{/CMR10 findfont dup',
# Alters FontMatrix
'- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def',
'+ /FontMatrix [0.001 0.0 0.001 0.001 0.0 0.0]readonly def',
# Alters ItalicAngle
'- /ItalicAngle 0 def',
'+ /ItalicAngle -45.0 def'):
assert_in(line, diff, 'diff to slanted font must contain %s' % line)

diff = set(differ.compare(font.parts[0].splitlines(),
condensed.parts[0].splitlines()))
for line in (
# Removes UniqueID
'- FontDirectory/CMR10 known{/CMR10 findfont dup/UniqueID known{dup',
'+ FontDirectory/CMR10 known{/CMR10 findfont dup',
# Alters FontMatrix
'- /FontMatrix [0.001 0 0 0.001 0 0 ]readonly def',
'+ /FontMatrix [0.0005 0.0 0.0 0.001 0.0 0.0]readonly def'):
assert_in(line, diff, 'diff to condensed font must contain %s' % line)
42 changes: 17 additions & 25 deletions lib/matplotlib/type1font.py
Expand Up @@ -256,13 +256,13 @@ def _transformer(cls, tokens, slant, extend):
def fontname(name):
result = name
if slant:
result += '_Slant_' + str(int(1000 * slant))
result += b'_Slant_' + bytes(int(1000 * slant))
if extend != 1.0:
result += '_Extend_' + str(int(1000 * extend))
result += b'_Extend_' + bytes(int(1000 * extend))
return result

def italicangle(angle):
return str(float(angle) - np.arctan(slant) / np.pi * 180)
return bytes(float(angle) - np.arctan(slant) / np.pi * 180)

def fontmatrix(array):
array = array.lstrip('[').rstrip(']').strip().split()
Expand All @@ -276,31 +276,31 @@ def fontmatrix(array):
newmatrix = np.dot(modifier, oldmatrix)
array[::2] = newmatrix[0:3, 0]
array[1::2] = newmatrix[0:3, 1]
return '[' + ' '.join(str(x) for x in array) + ']'
return b'[' + ' '.join(bytes(x) for x in array) + b']'

def replace(fun):
def replacer(tokens):
token, value = next(tokens) # name, e.g., /FontMatrix
yield value
yield bytes(value)
token, value = next(tokens) # possible whitespace
while token == 'whitespace':
yield value
while token is cls._whitespace:
yield bytes(value)
token, value = next(tokens)
if value != '[': # name/number/etc.
yield fun(value)
yield bytes(fun(value))
else: # array, e.g., [1 2 3]
array = []
while value != ']':
array += value
token, value = next(tokens)
array += value
yield fun(''.join(array))
yield bytes(fun(''.join(array)))
return replacer

def suppress(tokens):
for x in itertools.takewhile(lambda x: x[1] != 'def', tokens):
pass
yield ''
yield b''

table = {'/FontName': replace(fontname),
'/ItalicAngle': replace(italicangle),
Expand All @@ -309,7 +309,7 @@ def suppress(tokens):

while True:
token, value = next(tokens)
if token == 'name' and value in table:
if token is cls._name and value in table:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use is test instead of ==? That seems to be betting heavily on some cpython internals working as expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced these sentinel objects to replace string comparisons, which were hard to get right across Python versions. The idea would be to use them like None. Perhaps this needs better comments, or one of the other enum patterns.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry 🐑 makes sense now.

Was this the stuff that was a long time broken do to token no longer being a string?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the confusion between strings and bytes was exactly the problem.

for value in table[value](itertools.chain([(token, value)],
tokens)):
yield value
Expand All @@ -325,18 +325,10 @@ def transform(self, effects):
multiplier by which the font is to be extended (so values less
than 1.0 condense). Returns a new :class:`Type1Font` object.
"""
buffer = io.BytesIO()
try:
with io.BytesIO() as buffer:
tokenizer = self._tokens(self.parts[0])
for value in self._transformer(tokenizer,
slant=effects.get('slant', 0.0),
extend=effects.get('extend', 1.0)):
if six.PY3 and isinstance(value, int):
value = chr(value)
value = value.encode('latin-1')
buffer.write(value)
result = buffer.getvalue()
finally:
buffer.close()

return Type1Font((result, self.parts[1], self.parts[2]))
transformed = self._transformer(tokenizer,
slant=effects.get('slant', 0.0),
extend=effects.get('extend', 1.0))
map(buffer.write, transformed)
return Type1Font((buffer.getvalue(), self.parts[1], self.parts[2]))