1from fontTools.misc import transform 2from fontParts.base import normalizers 3from fontParts.base.base import ( 4 BaseObject, TransformationMixin, InterpolationMixin, SelectionMixin, 5 PointPositionMixin, IdentifierMixin, dynamicProperty, reference 6) 7from fontParts.base.compatibility import AnchorCompatibilityReporter 8from fontParts.base.color import Color 9from fontParts.base.deprecated import DeprecatedAnchor, RemovedAnchor 10 11 12class BaseAnchor( 13 BaseObject, 14 TransformationMixin, 15 DeprecatedAnchor, 16 RemovedAnchor, 17 PointPositionMixin, 18 InterpolationMixin, 19 SelectionMixin, 20 IdentifierMixin 21 ): 22 23 """ 24 An anchor object. This object is almost always 25 created with :meth:`BaseGlyph.appendAnchor`. 26 An orphan anchor can be created like this:: 27 28 >>> anchor = RAnchor() 29 """ 30 31 def _reprContents(self): 32 contents = [ 33 ("({x}, {y})".format(x=self.x, y=self.y)), 34 ] 35 if self.name is not None: 36 contents.append("name='%s'" % self.name) 37 if self.color: 38 contents.append("color=%r" % str(self.color)) 39 return contents 40 41 # ---- 42 # Copy 43 # ---- 44 45 copyAttributes = ( 46 "x", 47 "y", 48 "name", 49 "color" 50 ) 51 52 # ------- 53 # Parents 54 # ------- 55 56 # Glyph 57 58 _glyph = None 59 60 glyph = dynamicProperty("glyph", "The anchor's parent :class:`BaseGlyph`.") 61 62 def _get_glyph(self): 63 if self._glyph is None: 64 return None 65 return self._glyph() 66 67 def _set_glyph(self, glyph): 68 if self._glyph is not None: 69 raise AssertionError("glyph for anchor already set") 70 if glyph is not None: 71 glyph = reference(glyph) 72 self._glyph = glyph 73 74 # Layer 75 76 layer = dynamicProperty("layer", "The anchor's parent :class:`BaseLayer`.") 77 78 def _get_layer(self): 79 if self._glyph is None: 80 return None 81 return self.glyph.layer 82 83 # Font 84 85 font = dynamicProperty("font", "The anchor's parent :class:`BaseFont`.") 86 87 def _get_font(self): 88 if self._glyph is None: 89 return None 90 return self.glyph.font 91 92 # -------- 93 # Position 94 # -------- 95 96 # x 97 98 x = dynamicProperty( 99 "base_x", 100 """ 101 The x coordinate of the anchor. 102 It must be an :ref:`type-int-float`. :: 103 104 >>> anchor.x 105 100 106 >>> anchor.x = 101 107 """ 108 ) 109 110 def _get_base_x(self): 111 value = self._get_x() 112 value = normalizers.normalizeX(value) 113 return value 114 115 def _set_base_x(self, value): 116 value = normalizers.normalizeX(value) 117 self._set_x(value) 118 119 def _get_x(self): 120 """ 121 This is the environment implementation of 122 :attr:`BaseAnchor.x`. This must return an 123 :ref:`type-int-float`. 124 125 Subclasses must override this method. 126 """ 127 self.raiseNotImplementedError() 128 129 def _set_x(self, value): 130 """ 131 This is the environment implementation of 132 :attr:`BaseAnchor.x`. **value** will be 133 an :ref:`type-int-float`. 134 135 Subclasses must override this method. 136 """ 137 self.raiseNotImplementedError() 138 139 # y 140 141 y = dynamicProperty( 142 "base_y", 143 """ 144 The y coordinate of the anchor. 145 It must be an :ref:`type-int-float`. :: 146 147 >>> anchor.y 148 100 149 >>> anchor.y = 101 150 """ 151 ) 152 153 def _get_base_y(self): 154 value = self._get_y() 155 value = normalizers.normalizeY(value) 156 return value 157 158 def _set_base_y(self, value): 159 value = normalizers.normalizeY(value) 160 self._set_y(value) 161 162 def _get_y(self): 163 """ 164 This is the environment implementation of 165 :attr:`BaseAnchor.y`. This must return an 166 :ref:`type-int-float`. 167 168 Subclasses must override this method. 169 """ 170 self.raiseNotImplementedError() 171 172 def _set_y(self, value): 173 """ 174 This is the environment implementation of 175 :attr:`BaseAnchor.y`. **value** will be 176 an :ref:`type-int-float`. 177 178 Subclasses must override this method. 179 """ 180 self.raiseNotImplementedError() 181 182 # -------------- 183 # Identification 184 # -------------- 185 186 # index 187 188 index = dynamicProperty( 189 "base_index", 190 """ 191 The index of the anchor within the ordered 192 list of the parent glyph's anchors. This 193 attribute is read only. :: 194 195 >>> anchor.index 196 0 197 """ 198 ) 199 200 def _get_base_index(self): 201 value = self._get_index() 202 value = normalizers.normalizeIndex(value) 203 return value 204 205 def _get_index(self): 206 """ 207 Get the anchor's index. 208 This must return an ``int``. 209 210 Subclasses may override this method. 211 """ 212 glyph = self.glyph 213 if glyph is None: 214 return None 215 return glyph.anchors.index(self) 216 217 # name 218 219 name = dynamicProperty( 220 "base_name", 221 """ 222 The name of the anchor. This will be a 223 :ref:`type-string` or ``None``. 224 225 >>> anchor.name 226 'my anchor' 227 >>> anchor.name = None 228 """ 229 ) 230 231 def _get_base_name(self): 232 value = self._get_name() 233 value = normalizers.normalizeAnchorName(value) 234 return value 235 236 def _set_base_name(self, value): 237 value = normalizers.normalizeAnchorName(value) 238 self._set_name(value) 239 240 def _get_name(self): 241 """ 242 This is the environment implementation of 243 :attr:`BaseAnchor.name`. This must return a 244 :ref:`type-string` or ``None``. The returned 245 value will be normalized with 246 :func:`normalizers.normalizeAnchorName`. 247 248 Subclasses must override this method. 249 """ 250 self.raiseNotImplementedError() 251 252 def _set_name(self, value): 253 """ 254 This is the environment implementation of 255 :attr:`BaseAnchor.name`. **value** will be 256 a :ref:`type-string` or ``None``. It will 257 have been normalized with 258 :func:`normalizers.normalizeAnchorName`. 259 260 Subclasses must override this method. 261 """ 262 self.raiseNotImplementedError() 263 264 # color 265 266 color = dynamicProperty( 267 "base_color", 268 """ 269 The anchor's color. This will be a 270 :ref:`type-color` or ``None``. :: 271 272 >>> anchor.color 273 None 274 >>> anchor.color = (1, 0, 0, 0.5) 275 """ 276 ) 277 278 def _get_base_color(self): 279 value = self._get_color() 280 if value is not None: 281 value = normalizers.normalizeColor(value) 282 value = Color(value) 283 return value 284 285 def _set_base_color(self, value): 286 if value is not None: 287 value = normalizers.normalizeColor(value) 288 self._set_color(value) 289 290 def _get_color(self): 291 """ 292 This is the environment implementation of 293 :attr:`BaseAnchor.color`. This must return 294 a :ref:`type-color` or ``None``. The 295 returned value will be normalized with 296 :func:`normalizers.normalizeColor`. 297 298 Subclasses must override this method. 299 """ 300 self.raiseNotImplementedError() 301 302 def _set_color(self, value): 303 """ 304 This is the environment implementation of 305 :attr:`BaseAnchor.color`. **value** will 306 be a :ref:`type-color` or ``None``. 307 It will have been normalized with 308 :func:`normalizers.normalizeColor`. 309 310 Subclasses must override this method. 311 """ 312 self.raiseNotImplementedError() 313 314 # -------------- 315 # Transformation 316 # -------------- 317 318 def _transformBy(self, matrix, **kwargs): 319 """ 320 This is the environment implementation of 321 :meth:`BaseAnchor.transformBy`. 322 323 **matrix** will be a :ref:`type-transformation`. 324 that has been normalized with 325 :func:`normalizers.normalizeTransformationMatrix`. 326 327 Subclasses may override this method. 328 """ 329 t = transform.Transform(*matrix) 330 x, y = t.transformPoint((self.x, self.y)) 331 self.x = x 332 self.y = y 333 334 # ------------- 335 # Interpolation 336 # ------------- 337 338 compatibilityReporterClass = AnchorCompatibilityReporter 339 340 def isCompatible(self, other): 341 """ 342 Evaluate interpolation compatibility with **other**. :: 343 344 >>> compatible, report = self.isCompatible(otherAnchor) 345 >>> compatible 346 True 347 >>> compatible 348 [Warning] Anchor: "left" + "right" 349 [Warning] Anchor: "left" has name left | "right" has name right 350 351 This will return a ``bool`` indicating if the anchor is 352 compatible for interpolation with **other** and a 353 :ref:`type-string` of compatibility notes. 354 """ 355 return super(BaseAnchor, self).isCompatible(other, BaseAnchor) 356 357 def _isCompatible(self, other, reporter): 358 """ 359 This is the environment implementation of 360 :meth:`BaseAnchor.isCompatible`. 361 362 Subclasses may override this method. 363 """ 364 anchor1 = self 365 anchor2 = other 366 # base names 367 if anchor1.name != anchor2.name: 368 reporter.nameDifference = True 369 reporter.warning = True 370 371 # ------------- 372 # Normalization 373 # ------------- 374 375 def round(self): 376 """ 377 Round the anchor's coordinate. 378 379 >>> anchor.round() 380 381 This applies to the following: 382 383 * x 384 * y 385 """ 386 self._round() 387 388 def _round(self): 389 """ 390 This is the environment implementation of 391 :meth:`BaseAnchor.round`. 392 393 Subclasses may override this method. 394 """ 395 self.x = normalizers.normalizeVisualRounding(self.x) 396 self.y = normalizers.normalizeVisualRounding(self.y) 397