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