from fontTools.ttLib import TTFont
from fontTools.ttLib import ttGlyphSet
from fontTools.ttLib.ttGlyphSet import LerpGlyphSet
from fontTools.pens.recordingPen import (
    RecordingPen,
    RecordingPointPen,
    DecomposingRecordingPen,
)
from fontTools.misc.roundTools import otRound
from fontTools.misc.transform import DecomposedTransform
import os
import pytest


class TTGlyphSetTest(object):
    @staticmethod
    def getpath(testfile):
        path = os.path.dirname(__file__)
        return os.path.join(path, "data", testfile)

    @pytest.mark.parametrize(
        "fontfile, location, expected",
        [
            (
                "I.ttf",
                None,
                [
                    ("moveTo", ((175, 0),)),
                    ("lineTo", ((367, 0),)),
                    ("lineTo", ((367, 1456),)),
                    ("lineTo", ((175, 1456),)),
                    ("closePath", ()),
                ],
            ),
            (
                "I.ttf",
                {},
                [
                    ("moveTo", ((175, 0),)),
                    ("lineTo", ((367, 0),)),
                    ("lineTo", ((367, 1456),)),
                    ("lineTo", ((175, 1456),)),
                    ("closePath", ()),
                ],
            ),
            (
                "I.ttf",
                {"wght": 100},
                [
                    ("moveTo", ((175, 0),)),
                    ("lineTo", ((271, 0),)),
                    ("lineTo", ((271, 1456),)),
                    ("lineTo", ((175, 1456),)),
                    ("closePath", ()),
                ],
            ),
            (
                "I.ttf",
                {"wght": 1000},
                [
                    ("moveTo", ((128, 0),)),
                    ("lineTo", ((550, 0),)),
                    ("lineTo", ((550, 1456),)),
                    ("lineTo", ((128, 1456),)),
                    ("closePath", ()),
                ],
            ),
            (
                "I.ttf",
                {"wght": 1000, "wdth": 25},
                [
                    ("moveTo", ((140, 0),)),
                    ("lineTo", ((553, 0),)),
                    ("lineTo", ((553, 1456),)),
                    ("lineTo", ((140, 1456),)),
                    ("closePath", ()),
                ],
            ),
            (
                "I.ttf",
                {"wght": 1000, "wdth": 50},
                [
                    ("moveTo", ((136, 0),)),
                    ("lineTo", ((552, 0),)),
                    ("lineTo", ((552, 1456),)),
                    ("lineTo", ((136, 1456),)),
                    ("closePath", ()),
                ],
            ),
            (
                "I.otf",
                {"wght": 1000},
                [
                    ("moveTo", ((179, 74),)),
                    ("lineTo", ((28, 59),)),
                    ("lineTo", ((28, 0),)),
                    ("lineTo", ((367, 0),)),
                    ("lineTo", ((367, 59),)),
                    ("lineTo", ((212, 74),)),
                    ("lineTo", ((179, 74),)),
                    ("closePath", ()),
                    ("moveTo", ((179, 578),)),
                    ("lineTo", ((212, 578),)),
                    ("lineTo", ((367, 593),)),
                    ("lineTo", ((367, 652),)),
                    ("lineTo", ((28, 652),)),
                    ("lineTo", ((28, 593),)),
                    ("lineTo", ((179, 578),)),
                    ("closePath", ()),
                    ("moveTo", ((98, 310),)),
                    ("curveTo", ((98, 205), (98, 101), (95, 0))),
                    ("lineTo", ((299, 0),)),
                    ("curveTo", ((296, 103), (296, 207), (296, 311))),
                    ("lineTo", ((296, 342),)),
                    ("curveTo", ((296, 447), (296, 551), (299, 652))),
                    ("lineTo", ((95, 652),)),
                    ("curveTo", ((98, 549), (98, 445), (98, 342))),
                    ("lineTo", ((98, 310),)),
                    ("closePath", ()),
                ],
            ),
            (
                # In this font, /I has an lsb of 30, but an xMin of 25, so an
                # offset of 5 units needs to be applied when drawing the outline.
                # See https://github.com/fonttools/fonttools/issues/2824
                "issue2824.ttf",
                None,
                [
                    ("moveTo", ((309, 180),)),
                    ("qCurveTo", ((274, 151), (187, 136), (104, 166), (74, 201))),
                    ("qCurveTo", ((45, 236), (30, 323), (59, 407), (95, 436))),
                    ("qCurveTo", ((130, 466), (217, 480), (301, 451), (330, 415))),
                    ("qCurveTo", ((360, 380), (374, 293), (345, 210), (309, 180))),
                    ("closePath", ()),
                ],
            ),
        ],
    )
    def test_glyphset(self, fontfile, location, expected):
        font = TTFont(self.getpath(fontfile))
        glyphset = font.getGlyphSet(location=location)

        assert isinstance(glyphset, ttGlyphSet._TTGlyphSet)

        assert list(glyphset.keys()) == [".notdef", "I"]

        assert "I" in glyphset
        with pytest.deprecated_call():
            assert glyphset.has_key("I")  # we should really get rid of this...

        assert len(glyphset) == 2

        pen = RecordingPen()
        glyph = glyphset["I"]

        assert glyphset.get("foobar") is None

        assert isinstance(glyph, ttGlyphSet._TTGlyph)
        is_glyf = fontfile.endswith(".ttf")
        glyphType = ttGlyphSet._TTGlyphGlyf if is_glyf else ttGlyphSet._TTGlyphCFF
        assert isinstance(glyph, glyphType)

        glyph.draw(pen)
        actual = pen.value

        assert actual == expected, (location, actual, expected)

    @pytest.mark.parametrize(
        "fontfile, locations, factor, expected",
        [
            (
                "I.ttf",
                ({"wght": 400}, {"wght": 1000}),
                0.5,
                [
                    ("moveTo", ((151.5, 0.0),)),
                    ("lineTo", ((458.5, 0.0),)),
                    ("lineTo", ((458.5, 1456.0),)),
                    ("lineTo", ((151.5, 1456.0),)),
                    ("closePath", ()),
                ],
            ),
            (
                "I.ttf",
                ({"wght": 400}, {"wght": 1000}),
                0.25,
                [
                    ("moveTo", ((163.25, 0.0),)),
                    ("lineTo", ((412.75, 0.0),)),
                    ("lineTo", ((412.75, 1456.0),)),
                    ("lineTo", ((163.25, 1456.0),)),
                    ("closePath", ()),
                ],
            ),
        ],
    )
    def test_lerp_glyphset(self, fontfile, locations, factor, expected):
        font = TTFont(self.getpath(fontfile))
        glyphset1 = font.getGlyphSet(location=locations[0])
        glyphset2 = font.getGlyphSet(location=locations[1])
        glyphset = LerpGlyphSet(glyphset1, glyphset2, factor)

        assert "I" in glyphset

        pen = RecordingPen()
        glyph = glyphset["I"]

        assert glyphset.get("foobar") is None

        glyph.draw(pen)
        actual = pen.value

        assert actual == expected, (locations, actual, expected)

    def test_glyphset_varComposite_components(self):
        font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
        glyphset = font.getGlyphSet()

        pen = RecordingPen()
        glyph = glyphset["uniAC00"]

        glyph.draw(pen)
        actual = pen.value

        expected = [
            (
                "addVarComponent",
                (
                    "glyph00003",
                    DecomposedTransform(460.0, 676.0, 0, 1, 1, 0, 0, 0, 0),
                    {
                        "0000": 0.84661865234375,
                        "0001": 0.98944091796875,
                        "0002": 0.47283935546875,
                        "0003": 0.446533203125,
                    },
                ),
            ),
            (
                "addVarComponent",
                (
                    "glyph00004",
                    DecomposedTransform(932.0, 382.0, 0, 1, 1, 0, 0, 0, 0),
                    {
                        "0000": 0.93359375,
                        "0001": 0.916015625,
                        "0002": 0.523193359375,
                        "0003": 0.32806396484375,
                        "0004": 0.85089111328125,
                    },
                ),
            ),
        ]

        assert actual == expected, (actual, expected)

    def test_glyphset_varComposite1(self):
        font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
        glyphset = font.getGlyphSet(location={"wght": 600})

        pen = DecomposingRecordingPen(glyphset)
        glyph = glyphset["uniAC00"]

        glyph.draw(pen)
        actual = pen.value

        expected = [
            ("moveTo", ((432, 678),)),
            ("lineTo", ((432, 620),)),
            (
                "qCurveTo",
                (
                    (419, 620),
                    (374, 621),
                    (324, 619),
                    (275, 618),
                    (237, 617),
                    (228, 616),
                ),
            ),
            ("qCurveTo", ((218, 616), (188, 612), (160, 605), (149, 601))),
            ("qCurveTo", ((127, 611), (83, 639), (67, 654))),
            ("qCurveTo", ((64, 657), (63, 662), (64, 666))),
            ("lineTo", ((72, 678),)),
            ("qCurveTo", ((93, 674), (144, 672), (164, 672))),
            (
                "qCurveTo",
                (
                    (173, 672),
                    (213, 672),
                    (266, 673),
                    (323, 674),
                    (377, 675),
                    (421, 678),
                    (432, 678),
                ),
            ),
            ("closePath", ()),
            ("moveTo", ((525, 619),)),
            ("lineTo", ((412, 620),)),
            ("lineTo", ((429, 678),)),
            ("lineTo", ((466, 697),)),
            ("qCurveTo", ((470, 698), (482, 698), (486, 697))),
            ("qCurveTo", ((494, 693), (515, 682), (536, 670), (541, 667))),
            ("qCurveTo", ((545, 663), (545, 656), (543, 652))),
            ("lineTo", ((525, 619),)),
            ("closePath", ()),
            ("moveTo", ((63, 118),)),
            ("lineTo", ((47, 135),)),
            ("qCurveTo", ((42, 141), (48, 146))),
            ("qCurveTo", ((135, 213), (278, 373), (383, 541), (412, 620))),
            ("lineTo", ((471, 642),)),
            ("lineTo", ((525, 619),)),
            ("qCurveTo", ((496, 529), (365, 342), (183, 179), (75, 121))),
            ("qCurveTo", ((72, 119), (65, 118), (63, 118))),
            ("closePath", ()),
            ("moveTo", ((925, 372),)),
            ("lineTo", ((739, 368),)),
            ("lineTo", ((739, 427),)),
            ("lineTo", ((822, 430),)),
            ("lineTo", ((854, 451),)),
            ("qCurveTo", ((878, 453), (930, 449), (944, 445))),
            ("qCurveTo", ((961, 441), (962, 426))),
            ("qCurveTo", ((964, 411), (956, 386), (951, 381))),
            ("qCurveTo", ((947, 376), (931, 372), (925, 372))),
            ("closePath", ()),
            ("moveTo", ((729, -113),)),
            ("lineTo", ((674, -113),)),
            ("qCurveTo", ((671, -98), (669, -42), (666, 22), (665, 83), (665, 102))),
            ("lineTo", ((665, 763),)),
            ("qCurveTo", ((654, 780), (608, 810), (582, 820))),
            ("lineTo", ((593, 850),)),
            ("qCurveTo", ((594, 852), (599, 856), (607, 856))),
            ("qCurveTo", ((628, 855), (684, 846), (736, 834), (752, 827))),
            ("qCurveTo", ((766, 818), (766, 802))),
            ("lineTo", ((762, 745),)),
            ("lineTo", ((762, 134),)),
            ("qCurveTo", ((762, 107), (757, 43), (749, -25), (737, -87), (729, -113))),
            ("closePath", ()),
        ]

        actual = [
            (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
            for op, args in actual
        ]

        assert actual == expected, (actual, expected)

        # Test that drawing twice works, we accidentally don't change the component
        pen = DecomposingRecordingPen(glyphset)
        glyph.draw(pen)
        actual = pen.value
        actual = [
            (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
            for op, args in actual
        ]
        assert actual == expected, (actual, expected)

        pen = RecordingPointPen()
        glyph.drawPoints(pen)
        assert pen.value

    def test_glyphset_varComposite2(self):
        # This test font has axis variations

        font = TTFont(self.getpath("varc-6868.ttf"))
        glyphset = font.getGlyphSet(location={"wght": 600})

        pen = DecomposingRecordingPen(glyphset)
        glyph = glyphset["uni6868"]

        glyph.draw(pen)
        actual = pen.value

        expected = [
            ("moveTo", ((460, 565),)),
            (
                "qCurveTo",
                (
                    (482, 577),
                    (526, 603),
                    (568, 632),
                    (607, 663),
                    (644, 698),
                    (678, 735),
                    (708, 775),
                    (721, 796),
                ),
            ),
            ("lineTo", ((632, 835),)),
            (
                "qCurveTo",
                (
                    (621, 817),
                    (595, 784),
                    (566, 753),
                    (534, 724),
                    (499, 698),
                    (462, 675),
                    (423, 653),
                    (403, 644),
                ),
            ),
            ("closePath", ()),
            ("moveTo", ((616, 765),)),
            ("lineTo", ((590, 682),)),
            ("lineTo", ((830, 682),)),
            ("lineTo", ((833, 682),)),
            ("lineTo", ((828, 693),)),
            (
                "qCurveTo",
                (
                    (817, 671),
                    (775, 620),
                    (709, 571),
                    (615, 525),
                    (492, 490),
                    (413, 480),
                ),
            ),
            ("lineTo", ((454, 386),)),
            (
                "qCurveTo",
                (
                    (544, 403),
                    (687, 455),
                    (798, 519),
                    (877, 590),
                    (926, 655),
                    (937, 684),
                ),
            ),
            ("lineTo", ((937, 765),)),
            ("closePath", ()),
            ("moveTo", ((723, 555),)),
            (
                "qCurveTo",
                (
                    (713, 563),
                    (693, 579),
                    (672, 595),
                    (651, 610),
                    (629, 625),
                    (606, 638),
                    (583, 651),
                    (572, 657),
                ),
            ),
            ("lineTo", ((514, 590),)),
            (
                "qCurveTo",
                (
                    (525, 584),
                    (547, 572),
                    (568, 559),
                    (589, 545),
                    (609, 531),
                    (629, 516),
                    (648, 500),
                    (657, 492),
                ),
            ),
            ("closePath", ()),
            ("moveTo", ((387, 375),)),
            ("lineTo", ((387, 830),)),
            ("lineTo", ((289, 830),)),
            ("lineTo", ((289, 375),)),
            ("closePath", ()),
            ("moveTo", ((96, 383),)),
            (
                "qCurveTo",
                (
                    (116, 390),
                    (156, 408),
                    (194, 427),
                    (231, 449),
                    (268, 472),
                    (302, 497),
                    (335, 525),
                    (351, 539),
                ),
            ),
            ("lineTo", ((307, 610),)),
            (
                "qCurveTo",
                (
                    (291, 597),
                    (257, 572),
                    (221, 549),
                    (185, 528),
                    (147, 509),
                    (108, 492),
                    (69, 476),
                    (48, 469),
                ),
            ),
            ("closePath", ()),
            ("moveTo", ((290, 653),)),
            (
                "qCurveTo",
                (
                    (281, 664),
                    (261, 687),
                    (240, 708),
                    (219, 729),
                    (196, 749),
                    (173, 768),
                    (148, 786),
                    (136, 794),
                ),
            ),
            ("lineTo", ((69, 727),)),
            (
                "qCurveTo",
                (
                    (81, 719),
                    (105, 702),
                    (129, 684),
                    (151, 665),
                    (173, 645),
                    (193, 625),
                    (213, 604),
                    (222, 593),
                ),
            ),
            ("closePath", ()),
            ("moveTo", ((913, -57),)),
            ("lineTo", ((953, 30),)),
            (
                "qCurveTo",
                (
                    (919, 41),
                    (854, 67),
                    (790, 98),
                    (729, 134),
                    (671, 173),
                    (616, 217),
                    (564, 264),
                    (540, 290),
                ),
            ),
            ("lineTo", ((522, 286),)),
            ("qCurveTo", ((511, 267), (498, 235), (493, 213), (492, 206))),
            ("lineTo", ((515, 209),)),
            ("qCurveTo", ((569, 146), (695, 44), (835, -32), (913, -57))),
            ("closePath", ()),
            ("moveTo", ((474, 274),)),
            ("lineTo", ((452, 284),)),
            (
                "qCurveTo",
                (
                    (428, 260),
                    (377, 214),
                    (323, 172),
                    (266, 135),
                    (206, 101),
                    (144, 71),
                    (80, 46),
                    (47, 36),
                ),
            ),
            ("lineTo", ((89, -53),)),
            ("qCurveTo", ((163, -29), (299, 46), (423, 142), (476, 201))),
            ("lineTo", ((498, 196),)),
            ("qCurveTo", ((498, 203), (494, 225), (482, 255), (474, 274))),
            ("closePath", ()),
            ("moveTo", ((450, 250),)),
            ("lineTo", ((550, 250),)),
            ("lineTo", ((550, 379),)),
            ("lineTo", ((450, 379),)),
            ("closePath", ()),
            ("moveTo", ((68, 215),)),
            ("lineTo", ((932, 215),)),
            ("lineTo", ((932, 305),)),
            ("lineTo", ((68, 305),)),
            ("closePath", ()),
            ("moveTo", ((450, -71),)),
            ("lineTo", ((550, -71),)),
            ("lineTo", ((550, -71),)),
            ("lineTo", ((550, 267),)),
            ("lineTo", ((450, 267),)),
            ("lineTo", ((450, -71),)),
            ("closePath", ()),
        ]

        actual = [
            (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
            for op, args in actual
        ]

        assert actual == expected, (actual, expected)

        pen = RecordingPointPen()
        glyph.drawPoints(pen)
        assert pen.value

    def test_cubic_glyf(self):
        font = TTFont(self.getpath("dot-cubic.ttf"))
        glyphset = font.getGlyphSet()

        expected = [
            ("moveTo", ((76, 181),)),
            ("curveTo", ((103, 181), (125, 158), (125, 131))),
            ("curveTo", ((125, 104), (103, 82), (76, 82))),
            ("curveTo", ((48, 82), (26, 104), (26, 131))),
            ("curveTo", ((26, 158), (48, 181), (76, 181))),
            ("closePath", ()),
        ]

        pen = RecordingPen()
        glyphset["one"].draw(pen)
        assert pen.value == expected

        expectedPoints = [
            ("beginPath", (), {}),
            ("addPoint", ((76, 181), "curve", False, None), {}),
            ("addPoint", ((103, 181), None, False, None), {}),
            ("addPoint", ((125, 158), None, False, None), {}),
            ("addPoint", ((125, 104), None, False, None), {}),
            ("addPoint", ((103, 82), None, False, None), {}),
            ("addPoint", ((76, 82), "curve", False, None), {}),
            ("addPoint", ((48, 82), None, False, None), {}),
            ("addPoint", ((26, 104), None, False, None), {}),
            ("addPoint", ((26, 158), None, False, None), {}),
            ("addPoint", ((48, 181), None, False, None), {}),
            ("endPath", (), {}),
        ]
        pen = RecordingPointPen()
        glyphset["one"].drawPoints(pen)
        assert pen.value == expectedPoints

        pen = RecordingPen()
        glyphset["two"].draw(pen)
        assert pen.value == expected

        expectedPoints = [
            ("beginPath", (), {}),
            ("addPoint", ((26, 158), None, False, None), {}),
            ("addPoint", ((48, 181), None, False, None), {}),
            ("addPoint", ((76, 181), "curve", False, None), {}),
            ("addPoint", ((103, 181), None, False, None), {}),
            ("addPoint", ((125, 158), None, False, None), {}),
            ("addPoint", ((125, 104), None, False, None), {}),
            ("addPoint", ((103, 82), None, False, None), {}),
            ("addPoint", ((76, 82), "curve", False, None), {}),
            ("addPoint", ((48, 82), None, False, None), {}),
            ("addPoint", ((26, 104), None, False, None), {}),
            ("endPath", (), {}),
        ]
        pen = RecordingPointPen()
        glyphset["two"].drawPoints(pen)
        assert pen.value == expectedPoints

        pen = RecordingPen()
        glyphset["three"].draw(pen)
        assert pen.value == expected

        expectedPoints = [
            ("beginPath", (), {}),
            ("addPoint", ((48, 82), None, False, None), {}),
            ("addPoint", ((26, 104), None, False, None), {}),
            ("addPoint", ((26, 158), None, False, None), {}),
            ("addPoint", ((48, 181), None, False, None), {}),
            ("addPoint", ((76, 181), "curve", False, None), {}),
            ("addPoint", ((103, 181), None, False, None), {}),
            ("addPoint", ((125, 158), None, False, None), {}),
            ("addPoint", ((125, 104), None, False, None), {}),
            ("addPoint", ((103, 82), None, False, None), {}),
            ("addPoint", ((76, 82), "curve", False, None), {}),
            ("endPath", (), {}),
        ]
        pen = RecordingPointPen()
        glyphset["three"].drawPoints(pen)
        assert pen.value == expectedPoints

        pen = RecordingPen()
        glyphset["four"].draw(pen)
        assert pen.value == [
            ("moveTo", ((75.5, 181),)),
            ("curveTo", ((103, 181), (125, 158), (125, 131))),
            ("curveTo", ((125, 104), (103, 82), (75.5, 82))),
            ("curveTo", ((48, 82), (26, 104), (26, 131))),
            ("curveTo", ((26, 158), (48, 181), (75.5, 181))),
            ("closePath", ()),
        ]

        # Ouch! We can't represent all-cubic-offcurves in pointPen!
        # https://github.com/fonttools/fonttools/issues/3191
        expectedPoints = [
            ("beginPath", (), {}),
            ("addPoint", ((103, 181), None, False, None), {}),
            ("addPoint", ((125, 158), None, False, None), {}),
            ("addPoint", ((125, 104), None, False, None), {}),
            ("addPoint", ((103, 82), None, False, None), {}),
            ("addPoint", ((48, 82), None, False, None), {}),
            ("addPoint", ((26, 104), None, False, None), {}),
            ("addPoint", ((26, 158), None, False, None), {}),
            ("addPoint", ((48, 181), None, False, None), {}),
            ("endPath", (), {}),
        ]
        pen = RecordingPointPen()
        glyphset["four"].drawPoints(pen)
        print(pen.value)
        assert pen.value == expectedPoints
