1 from __future__ import print_function, division, absolute_import 2 from fontTools.misc.py23 import * 3 from fontTools.misc.fixedTools import otRound 4 from fontTools.pens.ttGlyphPen import TTGlyphPen 5 from fontTools.ttLib import TTFont, newTable, TTLibError 6 from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates 7 import sys 8 import array 9 import pytest 10 import re 11 import os 12 import unittest 13 14 15 class GlyphCoordinatesTest(object): 16 17 def test_translate(self): 18 g = GlyphCoordinates([(1,2)]) 19 g.translate((.5,0)) 20 assert g == GlyphCoordinates([(1.5,2.0)]) 21 22 def test_scale(self): 23 g = GlyphCoordinates([(1,2)]) 24 g.scale((.5,0)) 25 assert g == GlyphCoordinates([(0.5,0.0)]) 26 27 def test_transform(self): 28 g = GlyphCoordinates([(1,2)]) 29 g.transform(((.5,0),(.2,.5))) 30 assert g[0] == GlyphCoordinates([(0.9,1.0)])[0] 31 32 def test__eq__(self): 33 g = GlyphCoordinates([(1,2)]) 34 g2 = GlyphCoordinates([(1.0,2)]) 35 g3 = GlyphCoordinates([(1.5,2)]) 36 assert g == g2 37 assert not g == g3 38 assert not g2 == g3 39 assert not g == object() 40 41 def test__ne__(self): 42 g = GlyphCoordinates([(1,2)]) 43 g2 = GlyphCoordinates([(1.0,2)]) 44 g3 = GlyphCoordinates([(1.5,2)]) 45 assert not (g != g2) 46 assert g != g3 47 assert g2 != g3 48 assert g != object() 49 50 def test__pos__(self): 51 g = GlyphCoordinates([(1,2)]) 52 g2 = +g 53 assert g == g2 54 55 def test__neg__(self): 56 g = GlyphCoordinates([(1,2)]) 57 g2 = -g 58 assert g2 == GlyphCoordinates([(-1, -2)]) 59 60 @pytest.mark.skipif(sys.version_info[0] < 3, 61 reason="__round___ requires Python 3") 62 def test__round__(self): 63 g = GlyphCoordinates([(-1.5,2)]) 64 g2 = round(g) 65 assert g2 == GlyphCoordinates([(-1,2)]) 66 67 def test__add__(self): 68 g1 = GlyphCoordinates([(1,2)]) 69 g2 = GlyphCoordinates([(3,4)]) 70 g3 = GlyphCoordinates([(4,6)]) 71 assert g1 + g2 == g3 72 assert g1 + (1, 1) == GlyphCoordinates([(2,3)]) 73 with pytest.raises(TypeError) as excinfo: 74 assert g1 + object() 75 assert 'unsupported operand' in str(excinfo.value) 76 77 def test__sub__(self): 78 g1 = GlyphCoordinates([(1,2)]) 79 g2 = GlyphCoordinates([(3,4)]) 80 g3 = GlyphCoordinates([(-2,-2)]) 81 assert g1 - g2 == g3 82 assert g1 - (1, 1) == GlyphCoordinates([(0,1)]) 83 with pytest.raises(TypeError) as excinfo: 84 assert g1 - object() 85 assert 'unsupported operand' in str(excinfo.value) 86 87 def test__rsub__(self): 88 g = GlyphCoordinates([(1,2)]) 89 # other + (-self) 90 assert (1, 1) - g == GlyphCoordinates([(0,-1)]) 91 92 def test__mul__(self): 93 g = GlyphCoordinates([(1,2)]) 94 assert g * 3 == GlyphCoordinates([(3,6)]) 95 assert g * (3,2) == GlyphCoordinates([(3,4)]) 96 assert g * (1,1) == g 97 with pytest.raises(TypeError) as excinfo: 98 assert g * object() 99 assert 'unsupported operand' in str(excinfo.value) 100 101 def test__truediv__(self): 102 g = GlyphCoordinates([(1,2)]) 103 assert g / 2 == GlyphCoordinates([(.5,1)]) 104 assert g / (1, 2) == GlyphCoordinates([(1,1)]) 105 assert g / (1, 1) == g 106 with pytest.raises(TypeError) as excinfo: 107 assert g / object() 108 assert 'unsupported operand' in str(excinfo.value) 109 110 def test__iadd__(self): 111 g = GlyphCoordinates([(1,2)]) 112 g += (.5,0) 113 assert g == GlyphCoordinates([(1.5, 2.0)]) 114 g2 = GlyphCoordinates([(3,4)]) 115 g += g2 116 assert g == GlyphCoordinates([(4.5, 6.0)]) 117 118 def test__isub__(self): 119 g = GlyphCoordinates([(1,2)]) 120 g -= (.5, 0) 121 assert g == GlyphCoordinates([(0.5, 2.0)]) 122 g2 = GlyphCoordinates([(3,4)]) 123 g -= g2 124 assert g == GlyphCoordinates([(-2.5, -2.0)]) 125 126 def __test__imul__(self): 127 g = GlyphCoordinates([(1,2)]) 128 g *= (2,.5) 129 g *= 2 130 assert g == GlyphCoordinates([(4.0, 2.0)]) 131 g = GlyphCoordinates([(1,2)]) 132 g *= 2 133 assert g == GlyphCoordinates([(2, 4)]) 134 135 def test__itruediv__(self): 136 g = GlyphCoordinates([(1,3)]) 137 g /= (.5,1.5) 138 g /= 2 139 assert g == GlyphCoordinates([(1.0, 1.0)]) 140 141 def test__bool__(self): 142 g = GlyphCoordinates([]) 143 assert bool(g) == False 144 g = GlyphCoordinates([(0,0), (0.,0)]) 145 assert bool(g) == True 146 g = GlyphCoordinates([(0,0), (1,0)]) 147 assert bool(g) == True 148 g = GlyphCoordinates([(0,.5), (0,0)]) 149 assert bool(g) == True 150 151 def test_double_precision_float(self): 152 # https://github.com/fonttools/fonttools/issues/963 153 afloat = 242.50000000000003 154 g = GlyphCoordinates([(afloat, 0)]) 155 g.toInt() 156 # this would return 242 if the internal array.array typecode is 'f', 157 # since the Python float is truncated to a C float. 158 # when using typecode 'd' it should return the correct value 243 159 assert g[0][0] == otRound(afloat) 160 161 def test__checkFloat_overflow(self): 162 g = GlyphCoordinates([(1, 1)], typecode="h") 163 g.append((0x8000, 0)) 164 assert g.array.typecode == "d" 165 assert g.array == array.array("d", [1.0, 1.0, 32768.0, 0.0]) 166 167 168 CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) 169 DATA_DIR = os.path.join(CURR_DIR, 'data') 170 171 GLYF_TTX = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.ttx") 172 GLYF_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.glyf.bin") 173 HEAD_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.head.bin") 174 LOCA_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.loca.bin") 175 MAXP_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.maxp.bin") 176 177 178 def strip_ttLibVersion(string): 179 return re.sub(' ttLibVersion=".*"', '', string) 180 181 182 class glyfTableTest(unittest.TestCase): 183 184 def __init__(self, methodName): 185 unittest.TestCase.__init__(self, methodName) 186 # Python 3 renamed assertRaisesRegexp to assertRaisesRegex, 187 # and fires deprecation warnings if a program uses the old name. 188 if not hasattr(self, "assertRaisesRegex"): 189 self.assertRaisesRegex = self.assertRaisesRegexp 190 191 @classmethod 192 def setUpClass(cls): 193 with open(GLYF_BIN, 'rb') as f: 194 cls.glyfData = f.read() 195 with open(HEAD_BIN, 'rb') as f: 196 cls.headData = f.read() 197 with open(LOCA_BIN, 'rb') as f: 198 cls.locaData = f.read() 199 with open(MAXP_BIN, 'rb') as f: 200 cls.maxpData = f.read() 201 with open(GLYF_TTX, 'r') as f: 202 cls.glyfXML = strip_ttLibVersion(f.read()).splitlines() 203 204 def test_toXML(self): 205 font = TTFont(sfntVersion="\x00\x01\x00\x00") 206 glyfTable = font['glyf'] = newTable('glyf') 207 font['head'] = newTable('head') 208 font['loca'] = newTable('loca') 209 font['maxp'] = newTable('maxp') 210 font['maxp'].decompile(self.maxpData, font) 211 font['head'].decompile(self.headData, font) 212 font['loca'].decompile(self.locaData, font) 213 glyfTable.decompile(self.glyfData, font) 214 out = UnicodeIO() 215 font.saveXML(out) 216 glyfXML = strip_ttLibVersion(out.getvalue()).splitlines() 217 self.assertEqual(glyfXML, self.glyfXML) 218 219 def test_fromXML(self): 220 font = TTFont(sfntVersion="\x00\x01\x00\x00") 221 font.importXML(GLYF_TTX) 222 glyfTable = font['glyf'] 223 glyfData = glyfTable.compile(font) 224 self.assertEqual(glyfData, self.glyfData) 225 226 def test_recursiveComponent(self): 227 glyphSet = {} 228 pen_dummy = TTGlyphPen(glyphSet) 229 glyph_dummy = pen_dummy.glyph() 230 glyphSet["A"] = glyph_dummy 231 glyphSet["B"] = glyph_dummy 232 pen_A = TTGlyphPen(glyphSet) 233 pen_A.addComponent("B", (1, 0, 0, 1, 0, 0)) 234 pen_B = TTGlyphPen(glyphSet) 235 pen_B.addComponent("A", (1, 0, 0, 1, 0, 0)) 236 glyph_A = pen_A.glyph() 237 glyph_B = pen_B.glyph() 238 glyphSet["A"] = glyph_A 239 glyphSet["B"] = glyph_B 240 with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"): 241 glyph_A.getCoordinates(glyphSet) 242 243 244 if __name__ == "__main__": 245 import sys 246 sys.exit(unittest.main()) 247