1# -*- coding: utf8 -*-
2
3from collections import Counter
4from fontTools.misc.fixedTools import otRound
5
6# ----
7# Font
8# ----
9
10
11def normalizeFileFormatVersion(value):
12    """
13    Normalizes a font's file format version.
14
15    * **value** must be a :ref:`type-int`.
16    * Returned value will be a ``int``.
17    """
18    if not isinstance(value, int):
19        raise TypeError("File format versions must be instances of "
20                        ":ref:`type-int`, not %s."
21                        % type(value).__name__)
22    return value
23
24
25def normalizeFileStructure(value):
26    """
27    Normalizes a font's file structure.
28
29    * **value** must be a :ref:`type-string`.
30    * Returned value will be a ``string``.
31    """
32    allowedFileStructures = ["zip", "package"]
33    if value not in allowedFileStructures:
34        raise TypeError("File Structure must be %s, not %s" % (", ".join(allowedFileStructures), value))
35    return value
36
37
38def normalizeLayerOrder(value, font):
39    """
40    Normalizes layer order.
41
42    ** **value** must be a ``tuple`` or ``list``.
43    * **value** items must normalize as layer names with
44      :func:`normalizeLayerName`.
45    * **value** must contain layers that exist in **font**.
46    * **value** must not contain duplicate layers.
47    * Returned ``tuple`` will be unencoded ``unicode`` strings
48      for each layer name.
49    """
50    if not isinstance(value, (tuple, list)):
51        raise TypeError("Layer order must be a list, not %s."
52                        % type(value).__name__)
53    for v in value:
54        normalizeLayerName(v)
55    fontLayers = [layer.name for layer in font.layers]
56    for name in value:
57        if name not in fontLayers:
58            raise ValueError("Layer must exist in font. %s does not exist "
59                             "in font.layers." % name)
60    duplicates = [v for v, count in Counter(value).items() if count > 1]
61    if len(duplicates) != 0:
62        raise ValueError("Duplicate layers are not allowed. Layer name(s) "
63                         "'%s' are duplicate(s)." % ", ".join(duplicates))
64    return tuple(value)
65
66
67def normalizeDefaultLayerName(value, font):
68    """
69    Normalizes default layer name.
70
71    * **value** must normalize as layer name with
72      :func:`normalizeLayerName`.
73    * **value** must be a layer in **font**.
74    * Returned value will be an unencoded ``unicode`` string.
75    """
76    value = normalizeLayerName(value)
77    if value not in font.layerOrder:
78        raise ValueError("No layer with the name '%s' exists." % value)
79    return str(value)
80
81
82def normalizeGlyphOrder(value):
83    """
84    Normalizes glyph order.
85
86    ** **value** must be a ``tuple`` or ``list``.
87    * **value** items must normalize as glyph names with
88      :func:`normalizeGlyphName`.
89    * **value** must not repeat glyph names.
90    * Returned value will be a ``tuple`` of unencoded ``unicode`` strings.
91    """
92    if not isinstance(value, (tuple, list)):
93        raise TypeError("Glyph order must be a list, not %s."
94                        % type(value).__name__)
95    for v in value:
96        normalizeGlyphName(v)
97    duplicates = sorted(v for v, count in Counter(value).items() if count > 1)
98    if len(duplicates) != 0:
99        raise ValueError("Duplicate glyph names are not allowed. Glyph "
100                         "name(s) '%s' are duplicate." % ", ".join(duplicates))
101    return tuple(value)
102
103
104# -------
105# Kerning
106# -------
107
108
109def normalizeKerningKey(value):
110    """
111    Normalizes kerning key.
112
113    * **value** must be a ``tuple`` or ``list``.
114    * **value** must contain only two members.
115    * **value** items must be :ref:`type-string`.
116    * **value** items must be at least one character long.
117    * Returned value will be a two member ``tuple`` of unencoded
118      ``unicode`` strings.
119    """
120    if not isinstance(value, (tuple, list)):
121        raise TypeError("Kerning key must be a tuple instance, not %s."
122                        % type(value).__name__)
123    if len(value) != 2:
124        raise ValueError("Kerning key must be a tuple containing two items, "
125                         "not %d." % len(value))
126    for v in value:
127        if not isinstance(v, str):
128            raise TypeError("Kerning key items must be strings, not %s."
129                            % type(v).__name__)
130        if len(v) < 1:
131            raise ValueError("Kerning key items must be at least one character long")
132    if value[0].startswith("public.") and not value[0].startswith(
133            "public.kern1."):
134        raise ValueError("Left Kerning key group must start with "
135                         "public.kern1.")
136    if value[1].startswith("public.") and not value[1].startswith(
137            "public.kern2."):
138        raise ValueError("Right Kerning key group must start with "
139                         "public.kern2.")
140    return tuple(value)
141
142
143def normalizeKerningValue(value):
144    """
145    Normalizes kerning value.
146
147    * **value** must be an :ref:`type-int-float`.
148    * Returned value is the same type as input value.
149    """
150    if not isinstance(value, (int, float)):
151        raise TypeError("Kerning value must be a int or a float, not %s."
152                        % type(value).__name__)
153    return value
154
155
156# ------
157# Groups
158# ------
159
160def normalizeGroupKey(value):
161    """
162    Normalizes group key.
163
164    * **value** must be a :ref:`type-string`.
165    * **value** must be least one character long.
166    * Returned value will be an unencoded ``unicode`` string.
167    """
168    if not isinstance(value, str):
169        raise TypeError("Group key must be a string, not %s."
170                        % type(value).__name__)
171    if len(value) < 1:
172        raise ValueError("Group key must be at least one character long.")
173    return value
174
175
176def normalizeGroupValue(value):
177    """
178    Normalizes group value.
179
180    * **value** must be a ``list``.
181    * **value** items must normalize as glyph names with
182      :func:`normalizeGlyphName`.
183    * Returned value will be a ``tuple`` of unencoded ``unicode`` strings.
184    """
185    if not isinstance(value, (tuple, list)):
186        raise TypeError("Group value must be a list, not %s."
187                        % type(value).__name__)
188    value = [normalizeGlyphName(v) for v in value]
189    return tuple(value)
190
191
192# --------
193# Features
194# --------
195
196def normalizeFeatureText(value):
197    """
198    Normalizes feature text.
199
200    * **value** must be a :ref:`type-string`.
201    * Returned value will be an unencoded ``unicode`` string.
202    """
203    if not isinstance(value, str):
204        raise TypeError("Feature text must be a string, not %s."
205                        % type(value).__name__)
206    return value
207
208
209# ---
210# Lib
211# ---
212
213def normalizeLibKey(value):
214    """
215    Normalizes lib key.
216
217    * **value** must be a :ref:`type-string`.
218    * **value** must be at least one character long.
219    * Returned value will be an unencoded ``unicode`` string.
220    """
221    if not isinstance(value, str):
222        raise TypeError("Lib key must be a string, not %s."
223                        % type(value).__name__)
224    if len(value) < 1:
225        raise ValueError("Lib key must be at least one character.")
226    return value
227
228
229def normalizeLibValue(value):
230    """
231    Normalizes lib value.
232
233    * **value** must not be ``None``.
234    * Returned value is the same type as the input value.
235    """
236    if value is None:
237        raise ValueError("Lib value must not be None.")
238    if isinstance(value, (list, tuple)):
239        for v in value:
240            normalizeLibValue(v)
241    elif isinstance(value, dict):
242        for k, v in value.items():
243            normalizeLibKey(k)
244            normalizeLibValue(v)
245    return value
246
247
248# -----
249# Layer
250# -----
251
252def normalizeLayer(value):
253    """
254    Normalizes layer.
255
256    * **value** must be a instance of :class:`BaseLayer`
257    * Returned value is the same type as the input value.
258    """
259    from fontParts.base.layer import BaseLayer
260    return normalizeInternalObjectType(value, BaseLayer, "Layer")
261
262
263def normalizeLayerName(value):
264    """
265    Normalizes layer name.
266
267    * **value** must be a :ref:`type-string`.
268    * **value** must be at least one character long.
269    * Returned value will be an unencoded ``unicode`` string.
270    """
271    if not isinstance(value, str):
272        raise TypeError("Layer names must be strings, not %s."
273                        % type(value).__name__)
274    if len(value) < 1:
275        raise ValueError("Layer names must be at least one character long.")
276    return value
277
278
279# -----
280# Glyph
281# -----
282
283def normalizeGlyph(value):
284    """
285    Normalizes glyph.
286
287    * **value** must be a instance of :class:`BaseGlyph`
288    * Returned value is the same type as the input value.
289    """
290    from fontParts.base.glyph import BaseGlyph
291    return normalizeInternalObjectType(value, BaseGlyph, "Glyph")
292
293
294def normalizeGlyphName(value):
295    """
296    Normalizes glyph name.
297
298    * **value** must be a :ref:`type-string`.
299    * **value** must be at least one character long.
300    * Returned value will be an unencoded ``unicode`` string.
301    """
302    if not isinstance(value, str):
303        raise TypeError("Glyph names must be strings, not %s."
304                        % type(value).__name__)
305    if len(value) < 1:
306        raise ValueError("Glyph names must be at least one character long.")
307    return value
308
309
310def normalizeGlyphUnicodes(value):
311    """
312    Normalizes glyph unicodes.
313
314    * **value** must be a ``list``.
315    * **value** items must normalize as glyph unicodes with
316      :func:`normalizeGlyphUnicode`.
317    * **value** must not repeat unicode values.
318    * Returned value will be a ``tuple`` of ints.
319    """
320    if not isinstance(value, (tuple, list)):
321        raise TypeError("Glyph unicodes must be a list, not %s."
322                        % type(value).__name__)
323    values = [normalizeGlyphUnicode(v) for v in value]
324    duplicates = [v for v, count in Counter(value).items() if count > 1]
325    if len(duplicates) != 0:
326        raise ValueError("Duplicate unicode values are not allowed.")
327    return tuple(values)
328
329
330def normalizeGlyphUnicode(value):
331    """
332    Normalizes glyph unicode.
333
334    * **value** must be an int or hex (represented as a string).
335    * **value** must be in a unicode range.
336    * Returned value will be an ``int``.
337    """
338    if not isinstance(value, (int, str)) or isinstance(value, bool):
339        raise TypeError("Glyph unicode must be a int or hex string, not %s."
340                        % type(value).__name__)
341    if isinstance(value, str):
342        try:
343            value = int(value, 16)
344        except ValueError:
345            raise ValueError("Glyph unicode hex must be a valid hex string.")
346    if value < 0 or value > 1114111:
347        raise ValueError("Glyph unicode must be in the Unicode range.")
348    return value
349
350
351def normalizeGlyphWidth(value):
352    """
353    Normalizes glyph width.
354
355    * **value** must be a :ref:`type-int-float`.
356    * Returned value is the same type as the input value.
357    """
358    if not isinstance(value, (int, float)):
359        raise TypeError("Glyph width must be an :ref:`type-int-float`, not %s."
360                        % type(value).__name__)
361    return value
362
363
364def normalizeGlyphLeftMargin(value):
365    """
366    Normalizes glyph left margin.
367
368    * **value** must be a :ref:`type-int-float` or `None`.
369    * Returned value is the same type as the input value.
370    """
371    if not isinstance(value, (int, float)) and value is not None:
372        raise TypeError("Glyph left margin must be an :ref:`type-int-float`, "
373                        "not %s." % type(value).__name__)
374    return value
375
376
377def normalizeGlyphRightMargin(value):
378    """
379    Normalizes glyph right margin.
380
381    * **value** must be a :ref:`type-int-float` or `None`.
382    * Returned value is the same type as the input value.
383    """
384    if not isinstance(value, (int, float)) and value is not None:
385        raise TypeError("Glyph right margin must be an :ref:`type-int-float`, "
386                        "not %s." % type(value).__name__)
387    return value
388
389
390def normalizeGlyphHeight(value):
391    """
392    Normalizes glyph height.
393
394    * **value** must be a :ref:`type-int-float`.
395    * Returned value is the same type as the input value.
396    """
397    if not isinstance(value, (int, float)):
398        raise TypeError("Glyph height must be an :ref:`type-int-float`, not "
399                        "%s." % type(value).__name__)
400    return value
401
402
403def normalizeGlyphBottomMargin(value):
404    """
405    Normalizes glyph bottom margin.
406
407    * **value** must be a :ref:`type-int-float` or `None`.
408    * Returned value is the same type as the input value.
409    """
410    if not isinstance(value, (int, float)) and value is not None:
411        raise TypeError("Glyph bottom margin must be an "
412                        ":ref:`type-int-float`, not %s."
413                        % type(value).__name__)
414    return value
415
416
417def normalizeGlyphTopMargin(value):
418    """
419    Normalizes glyph top margin.
420
421    * **value** must be a :ref:`type-int-float` or `None`.
422    * Returned value is the same type as the input value.
423    """
424    if not isinstance(value, (int, float)) and value is not None:
425        raise TypeError("Glyph top margin must be an :ref:`type-int-float`, "
426                        "not %s." % type(value).__name__)
427    return value
428
429
430def normalizeGlyphFormatVersion(value):
431    """
432    Normalizes glyph format version for saving to XML string.
433
434    * **value** must be a :ref:`type-int-float` of either 1 or 2.
435    * Returned value will be an int.
436    """
437    if not isinstance(value, (int, float)):
438        raise TypeError("Glyph Format Version must be an "
439                        ":ref:`type-int-float`, not %s."
440                        % type(value).__name__)
441    value = int(value)
442    if value not in (1, 2):
443        raise ValueError("Glyph Format Version must be either 1 or 2, not %s."
444                         % value)
445    return value
446
447# -------
448# Contour
449# -------
450
451
452def normalizeContour(value):
453    """
454    Normalizes contour.
455
456    * **value** must be a instance of :class:`BaseContour`
457    * Returned value is the same type as the input value.
458    """
459    from fontParts.base.contour import BaseContour
460    return normalizeInternalObjectType(value, BaseContour, "Contour")
461
462
463# -----
464# Point
465# -----
466
467def normalizePointType(value):
468    """
469    Normalizes point type.
470
471    * **value** must be an string.
472    * **value** must be one of the following:
473
474      +----------+
475      | move     |
476      +----------+
477      | line     |
478      +----------+
479      | offcurve |
480      +----------+
481      | curve    |
482      +----------+
483      | qcurve   |
484      +----------+
485
486    * Returned value will be an unencoded ``unicode`` string.
487    """
488    allowedTypes = ['move', 'line', 'offcurve', 'curve', 'qcurve']
489    if not isinstance(value, str):
490        raise TypeError("Point type must be a string, not %s."
491                        % type(value).__name__)
492    if value not in allowedTypes:
493        raise ValueError("Point type must be '%s'; not %r."
494                         % ("', '".join(allowedTypes), value))
495    return value
496
497
498def normalizePointName(value):
499    """
500    Normalizes point name.
501
502    * **value** must be a :ref:`type-string`.
503    * **value** must be at least one character long.
504    * Returned value will be an unencoded ``unicode`` string.
505    """
506    if not isinstance(value, str):
507        raise TypeError("Point names must be strings, not %s."
508                        % type(value).__name__)
509    if len(value) < 1:
510        raise ValueError("Point names must be at least one character long.")
511    return value
512
513
514def normalizePoint(value):
515    """
516    Normalizes point.
517
518    * **value** must be a instance of :class:`BasePoint`
519    * Returned value is the same type as the input value.
520    """
521    from fontParts.base.point import BasePoint
522    return normalizeInternalObjectType(value, BasePoint, "Point")
523
524# -------
525# Segment
526# -------
527
528
529def normalizeSegment(value):
530    """
531    Normalizes segment.
532
533    * **value** must be a instance of :class:`BaseSegment`
534    * Returned value is the same type as the input value.
535    """
536    from fontParts.base.segment import BaseSegment
537    return normalizeInternalObjectType(value, BaseSegment, "Segment")
538
539
540def normalizeSegmentType(value):
541    """
542    Normalizes segment type.
543
544    * **value** must be a :ref:`type-string`.
545    * **value** must be one of the following:
546
547    +--------+
548    | move   |
549    +--------+
550    | line   |
551    +--------+
552    | curve  |
553    +--------+
554    | qcurve |
555    +--------+
556
557    * Returned value will be an unencoded ``unicode`` string.
558    """
559    allowedTypes = ['move', 'line', 'curve', 'qcurve']
560    if not isinstance(value, str):
561        raise TypeError("Segment type must be a string, not %s."
562                        % type(value).__name__)
563    if value not in allowedTypes:
564        raise ValueError("Segment type must be '%s'; not %r."
565                         % ("', '".join(allowedTypes), value))
566    return value
567
568
569# ------
570# BPoint
571# ------
572
573def normalizeBPoint(value):
574    """
575    Normalizes bPoint.
576
577    * **value** must be a instance of :class:`BaseBPoint`
578    * Returned value is the same type as the input value.
579    """
580    from fontParts.base.bPoint import BaseBPoint
581    return normalizeInternalObjectType(value, BaseBPoint, "bPoint")
582
583
584def normalizeBPointType(value):
585    """
586    Normalizes bPoint type.
587
588    * **value** must be an string.
589    * **value** must be one of the following:
590
591      +--------+
592      | corner |
593      +--------+
594      | curve  |
595      +--------+
596
597    * Returned value will be an unencoded ``unicode`` string.
598    """
599    allowedTypes = ['corner', 'curve']
600    if not isinstance(value, str):
601        raise TypeError("bPoint type must be a string, not %s."
602                        % type(value).__name__)
603    if value not in allowedTypes:
604        raise ValueError("bPoint type must be 'corner' or 'curve', not %r."
605                         % value)
606    return value
607
608
609# ---------
610# Component
611# ---------
612
613def normalizeComponent(value):
614    """
615    Normalizes component.
616
617    * **value** must be a instance of :class:`BaseComponent`
618    * Returned value is the same type as the input value.
619    """
620    from fontParts.base.component import BaseComponent
621    return normalizeInternalObjectType(value, BaseComponent, "Component")
622
623
624def normalizeComponentScale(value):
625    """
626    Normalizes component scale.
627
628    * **value** must be a `tuple`` or ``list``.
629    * **value** must have exactly two items.
630      These items must be instances of :ref:`type-int-float`.
631    * Returned value is a ``tuple`` of two ``float``\s.
632    """
633    if not isinstance(value, (list, tuple)):
634        raise TypeError("Component scale must be a tuple "
635                        "instance, not %s." % type(value).__name__)
636    else:
637        if not len(value) == 2:
638            raise ValueError("Transformation scale tuple must contain two "
639                             "values, not %d." % len(value))
640        for v in value:
641            if not isinstance(v, (int, float)):
642                raise TypeError("Transformation scale tuple values must be an "
643                                ":ref:`type-int-float`, not %s."
644                                % type(value).__name__)
645        value = tuple([float(v) for v in value])
646    return value
647
648
649# ------
650# Anchor
651# ------
652
653def normalizeAnchor(value):
654    """
655    Normalizes anchor.
656
657    * **value** must be a instance of :class:`BaseAnchor`
658    * Returned value is the same type as the input value.
659    """
660    from fontParts.base.anchor import BaseAnchor
661    return normalizeInternalObjectType(value, BaseAnchor, "Anchor")
662
663
664def normalizeAnchorName(value):
665    """
666    Normalizes anchor name.
667
668    * **value** must be a :ref:`type-string` or ``None``.
669    * **value** must be at least one character long if :ref:`type-string`.
670    * Returned value will be an unencoded ``unicode`` string or ``None``.
671    """
672    if value is None:
673        return None
674    if not isinstance(value, str):
675        raise TypeError("Anchor names must be strings, not %s."
676                        % type(value).__name__)
677    if len(value) < 1:
678        raise ValueError(("Anchor names must be at least one character "
679                          "long or None."))
680    return value
681
682
683# ---------
684# Guideline
685# ---------
686
687def normalizeGuideline(value):
688    """
689    Normalizes guideline.
690
691    * **value** must be a instance of :class:`BaseGuideline`
692    * Returned value is the same type as the input value.
693    """
694    from fontParts.base.guideline import BaseGuideline
695    return normalizeInternalObjectType(value, BaseGuideline, "Guideline")
696
697
698def normalizeGuidelineName(value):
699    """
700    Normalizes guideline name.
701
702    * **value** must be a :ref:`type-string`.
703    * **value** must be at least one character long.
704    * Returned value will be an unencoded ``unicode`` string.
705    """
706    if not isinstance(value, str):
707        raise TypeError("Guideline names must be strings, not %s."
708                        % type(value).__name__)
709    if len(value) < 1:
710        raise ValueError("Guideline names must be at least one character "
711                         "long.")
712    return value
713
714
715# -------
716# Generic
717# -------
718
719def normalizeInternalObjectType(value, cls, name):
720    """
721    Normalizes an internal object type.
722
723    * **value** must be a instance of **cls**.
724    * Returned value is the same type as the input value.
725    """
726    if not isinstance(value, cls):
727        raise TypeError("%s must be a %s instance, not %s."
728                        % (name, name, type(value).__name__))
729    return value
730
731
732def normalizeBoolean(value):
733    """
734    Normalizes a boolean.
735
736    * **value** must be an ``int`` with value of 0 or 1, or a ``bool``.
737    * Returned value will be a boolean.
738    """
739    if isinstance(value, int) and value in (0, 1):
740        value = bool(value)
741    if not isinstance(value, bool):
742        raise ValueError("Boolean values must be True or False, not '%s'."
743                         % value)
744    return value
745
746
747# Identification
748
749def normalizeIndex(value):
750    """
751    Normalizes index.
752
753    * **value** must be an ``int`` or ``None``.
754    * Returned value is the same type as the input value.
755    """
756    if value is not None:
757        if not isinstance(value, int):
758            raise TypeError("Indexes must be None or integers, not %s."
759                            % type(value).__name__)
760    return value
761
762
763def normalizeIdentifier(value):
764    """
765    Normalizes identifier.
766
767    * **value** must be an :ref:`type-string` or `None`.
768    * **value** must not be longer than 100 characters.
769    * **value** must not contain a character out the range of 0x20 - 0x7E.
770    * Returned value is an unencoded ``unicode`` string.
771    """
772    if value is None:
773        return value
774    if not isinstance(value, str):
775        raise TypeError("Identifiers must be strings, not %s."
776                        % type(value).__name__)
777    if len(value) == 0:
778        raise ValueError("The identifier string is empty.")
779    if len(value) > 100:
780        raise ValueError("The identifier string has a length (%d) greater "
781                         "than the maximum allowed (100)." % len(value))
782    for c in value:
783        v = ord(c)
784        if v < 0x20 or v > 0x7E:
785            raise ValueError("The identifier string ('%s') contains a "
786                             "character out size of the range 0x20 - 0x7E."
787                             % value)
788    return value
789
790
791# Coordinates
792
793def normalizeX(value):
794    """
795    Normalizes x coordinate.
796
797    * **value** must be an :ref:`type-int-float`.
798    * Returned value is the same type as the input value.
799    """
800    if not isinstance(value, (int, float)):
801        raise TypeError("X coordinates must be instances of "
802                        ":ref:`type-int-float`, not %s."
803                        % type(value).__name__)
804    return value
805
806
807def normalizeY(value):
808    """
809    Normalizes y coordinate.
810
811    * **value** must be an :ref:`type-int-float`.
812    * Returned value is the same type as the input value.
813    """
814    if not isinstance(value, (int, float)):
815        raise TypeError("Y coordinates must be instances of "
816                        ":ref:`type-int-float`, not %s."
817                        % type(value).__name__)
818    return value
819
820
821def normalizeCoordinateTuple(value):
822    """
823    Normalizes coordinate tuple.
824
825    * **value** must be a ``tuple`` or ``list``.
826    * **value** must have exactly two items.
827    * **value** items must be an :ref:`type-int-float`.
828    * Returned value is a ``tuple`` of two values of the same type as
829      the input values.
830    """
831    if not isinstance(value, (tuple, list)):
832        raise TypeError("Coordinates must be tuple instances, not %s."
833                        % type(value).__name__)
834    if len(value) != 2:
835        raise ValueError("Coordinates must be tuples containing two items, "
836                         "not %d." % len(value))
837    x, y = value
838    x = normalizeX(x)
839    y = normalizeY(y)
840    return (x, y)
841
842
843def normalizeBoundingBox(value):
844    """
845    Normalizes bounding box.
846
847    * **value** must be an ``tuple`` or ``list``.
848    * **value** must have exactly four items.
849    * **value** items must be :ref:`type-int-float`.
850    * xMin and yMin must be less than or equal to the corresponding xMax, yMax.
851    * Returned value will be a tuple of four ``float``.
852    """
853    if not isinstance(value, (tuple, list)):
854        raise TypeError("Bounding box be tuple instances, not %s."
855                        % type(value).__name__)
856    if len(value) != 4:
857        raise ValueError("Bounding box be tuples containing four items, not "
858                         "%d." % len(value))
859    for v in value:
860        if not isinstance(v, (int, float)):
861            raise TypeError("Bounding box values must be instances of "
862                            ":ref:`type-int-float`, not %s."
863                            % type(value).__name__)
864    if value[0] > value[2]:
865        raise ValueError("Bounding box xMin must be less than or equal to "
866                         "xMax.")
867    if value[1] > value[3]:
868        raise ValueError("Bounding box yMin must be less than or equal to "
869                         "yMax.")
870    return tuple([float(v) for v in value])
871
872
873def normalizeArea(value):
874    """
875    Normalizes area.
876
877    * **value** must be a positive :ref:`type-int-float`.
878    """
879    if not isinstance(value, (int, float)):
880        raise TypeError("Area must be an instance of :ref:`type-int-float`, "
881                        "not %s." % type(value).__name__)
882    if value < 0:
883        raise ValueError("Area must be a positive :ref:`type-int-float`, "
884                         "not %s." % repr(value))
885    return float(value)
886
887
888def normalizeRotationAngle(value):
889    """
890    Normalizes an angle.
891
892    * Value must be a :ref:`type-int-float`.
893    * Value must be between -360 and 360.
894    * If the value is negative, it is normalized by adding it to 360
895    * Returned value is a ``float`` between 0 and 360.
896    """
897    if not isinstance(value, (int, float)):
898        raise TypeError("Angle must be instances of "
899                        ":ref:`type-int-float`, not %s."
900                        % type(value).__name__)
901    if abs(value) > 360:
902        raise ValueError("Angle must be between -360 and 360.")
903    if value < 0:
904        value = value + 360
905    return float(value)
906
907
908# Color
909
910def normalizeColor(value):
911    """
912    Normalizes :ref:`type-color`.
913
914    * **value** must be an ``tuple`` or ``list``.
915    * **value** must have exactly four items.
916    * **value** color components must be between 0 and 1.
917    * Returned value is a ``tuple`` containing four ``float`` values.
918    """
919    from fontParts.base.color import Color
920    if not isinstance(value, (tuple, list, Color)):
921        raise TypeError("Colors must be tuple instances, not %s."
922                        % type(value).__name__)
923    if not len(value) == 4:
924        raise ValueError("Colors must contain four values, not %d."
925                         % len(value))
926    for component, v in zip("rgba", value):
927        if not isinstance(v, (int, float)):
928            raise TypeError("The value for the %s component (%s) is not "
929                            "an int or float." % (component, v))
930        if v < 0 or v > 1:
931            raise ValueError("The value for the %s component (%s) is not "
932                             "between 0 and 1." % (component, v))
933    return tuple([float(v) for v in value])
934
935
936# Note
937
938def normalizeGlyphNote(value):
939    """
940    Normalizes Glyph Note.
941
942    * **value** must be a :ref:`type-string`.
943    * Returned value is an unencoded ``unicode`` string
944    """
945    if not isinstance(value, str):
946        raise TypeError("Note must be a string, not %s."
947                        % type(value).__name__)
948    return value
949
950
951# File Path
952
953def normalizeFilePath(value):
954    """
955    Normalizes file path.
956
957    * **value** must be a :ref:`type-string`.
958    * Returned value is an unencoded ``unicode`` string
959    """
960    if not isinstance(value, str):
961        raise TypeError("File paths must be strings, not %s."
962                        % type(value).__name__)
963    return value
964
965
966# Interpolation
967
968def normalizeInterpolationFactor(value):
969    """
970    Normalizes interpolation factor.
971
972    * **value** must be an :ref:`type-int-float`, ``tuple`` or ``list``.
973    * If **value** is a ``tuple`` or ``list``, it must have exactly two items.
974      These items must be instances of :ref:`type-int-float`.
975    * Returned value is a ``tuple`` of two ``float``.
976    """
977    if not isinstance(value, (int, float, list, tuple)):
978        raise TypeError("Interpolation factor must be an int, float, or tuple "
979                        "instances, not %s." % type(value).__name__)
980    if isinstance(value, (int, float)):
981        value = (float(value), float(value))
982    else:
983        if not len(value) == 2:
984            raise ValueError("Interpolation factor tuple must contain two "
985                             "values, not %d." % len(value))
986        for v in value:
987            if not isinstance(v, (int, float)):
988                raise TypeError("Interpolation factor tuple values must be an "
989                                ":ref:`type-int-float`, not %s."
990                                % type(value).__name__)
991        value = tuple([float(v) for v in value])
992    return value
993
994
995# ---------------
996# Transformations
997# ---------------
998
999def normalizeTransformationMatrix(value):
1000    """
1001    Normalizes transformation matrix.
1002
1003    * **value** must be an ``tuple`` or ``list``.
1004    * **value** must have exactly six items. Each of these
1005      items must be an instance of :ref:`type-int-float`.
1006    * Returned value is a ``tuple`` of six ``float``.
1007    """
1008    if not isinstance(value, (tuple, list)):
1009        raise TypeError("Transformation matrices must be tuple instances, "
1010                        "not %s." % type(value).__name__)
1011    if not len(value) == 6:
1012        raise ValueError("Transformation matrices must contain six values, "
1013                         "not %d." % len(value))
1014    for v in value:
1015        if not isinstance(v, (int, float)):
1016            raise TypeError("Transformation matrix values must be instances "
1017                            "of :ref:`type-int-float`, not %s."
1018                            % type(v).__name__)
1019    return tuple([float(v) for v in value])
1020
1021
1022def normalizeTransformationOffset(value):
1023    """
1024    Normalizes transformation offset.
1025
1026    * **value** must be an ``tuple``.
1027    * **value** must have exactly two items. Each item
1028      must be an instance of :ref:`type-int-float`.
1029    * Returned value is a ``tuple`` of two ``float``.
1030    """
1031    return normalizeCoordinateTuple(value)
1032
1033
1034def normalizeTransformationSkewAngle(value):
1035    """
1036    Normalizes transformation skew angle.
1037
1038    * **value** must be an :ref:`type-int-float`, ``tuple`` or ``list``.
1039    * If **value** is a ``tuple`` or ``list``, it must have exactly two items.
1040      These items must be instances of :ref:`type-int-float`.
1041    * **value** items must be between -360 and 360.
1042    * If the value is negative, it is normalized by adding it to 360
1043    * Returned value is a ``tuple`` of two ``float`` between 0 and 360.
1044    """
1045    if not isinstance(value, (int, float, list, tuple)):
1046        raise TypeError("Transformation skew angle must be an int, float, or "
1047                        "tuple instances, not %s." % type(value).__name__)
1048    if isinstance(value, (int, float)):
1049        value = (float(value), 0)
1050    else:
1051        if not len(value) == 2:
1052            raise ValueError("Transformation skew angle tuple must contain "
1053                             "two values, not %d." % len(value))
1054        for v in value:
1055            if not isinstance(v, (int, float)):
1056                raise TypeError("Transformation skew angle tuple values must "
1057                                "be an :ref:`type-int-float`, not %s."
1058                                % type(value).__name__)
1059        value = tuple([float(v) for v in value])
1060    for v in value:
1061        if abs(v) > 360:
1062            raise ValueError("Transformation skew angle must be between -360 "
1063                             "and 360.")
1064    return tuple([float(v + 360) if v < 0 else float(v) for v in value])
1065
1066
1067def normalizeTransformationScale(value):
1068    """
1069    Normalizes transformation scale.
1070
1071    * **value** must be an :ref:`type-int-float`, ``tuple`` or ``list``.
1072    * If **value** is a ``tuple`` or ``list``, it must have exactly two items.
1073      These items must be instances of :ref:`type-int-float`.
1074    * Returned value is a ``tuple`` of two ``float``\s.
1075    """
1076    if not isinstance(value, (int, float, list, tuple)):
1077        raise TypeError("Transformation scale must be an int, float, or tuple "
1078                        "instances, not %s." % type(value).__name__)
1079    if isinstance(value, (int, float)):
1080        value = (float(value), float(value))
1081    else:
1082        if not len(value) == 2:
1083            raise ValueError("Transformation scale tuple must contain two "
1084                             "values, not %d." % len(value))
1085        for v in value:
1086            if not isinstance(v, (int, float)):
1087                raise TypeError("Transformation scale tuple values must be an "
1088                                ":ref:`type-int-float`, not %s."
1089                                % type(value).__name__)
1090        value = tuple([float(v) for v in value])
1091    return value
1092
1093
1094def normalizeVisualRounding(value):
1095    """
1096    Normalizes rounding.
1097    Python 3 uses banker’s rounding, meaning anything that is at 0.5
1098    will go to the even number. This isn't always ideal for point
1099    coordinates, so instead round to the higher number.
1100
1101    * **value** must be an :ref:`type-int-float`
1102    * Returned value is a ``int``
1103    """
1104
1105    if not isinstance(value, (int, float)):
1106        raise TypeError("Value to round must be an int or float, not %s."
1107                        % type(value).__name__)
1108    return otRound(value)
1109