<commit_before># Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helpers for https://www.w3.org/TR/SVG11/coords.html#TransformAttribute.

Focuses on converting to a sequence of affine matrices.
"""
import collections
from functools import reduce
from math import cos, sin, radians, tan, hypot
import operator
import re
from typing import NamedTuple, Sequence, Tuple
from sys import float_info
from picosvg.geometric_types import (
    Point,
    Rect,
    Vector,
    DEFAULT_ALMOST_EQUAL_TOLERANCE,
    almost_equal,
)
from picosvg.svg_meta import ntos

DECOMPOSITION_ALMOST_EQUAL_TOLERANCE = 1e-6

_SVG_ARG_FIXUPS = collections.defaultdict(
    lambda: lambda _: None,
    {
        "rotate": lambda args: _fix_rotate(args),
        "skewx": lambda args: _fix_rotate(args),
        "skewy": lambda args: _fix_rotate(args),
    },
)


# 2D affine transform.
#
# View as vector of 6 values or matrix:
#
# a   c   e
# b   d   f
class Affine2D(NamedTuple):
    a: float
    b: float
    c: float
    d: float
    e: float
    f: float

    @staticmethod
    def flip_y():
        return Affine2D._flip_y

    @staticmethod
    def identity():
        return Affine2D._identity

    @staticmethod
    def degenerate():
        return Affine2D._degnerate

    @staticmethod
    def fromstring(raw_transform):
        return parse_svg_transform(raw_transform)

    def tostring(self):
        if self == Affine2D.identity().translate(*self.gettranslate()):
            return f'translate({", ".join(ntos(v) for v in self.gettranslate())})'
        return f'matrix({" ".join(ntos(v) for v in self)})'

    def __matmul__(self, other: "Affine2D") -> "Affine2D":
        """Returns the product of self × other. Order matters.

        The combined affine matrix can be thought of mapping by other before applying self.

        https://en.wikipedia.org/wiki/Matrix_multiplication

                | a₁  c₁  e₁ |
        first = | b₁  d₁  f₁ |
                | 0   0   1  |

                 | a₂  c₂  e₂ |
        second = | b₂  d₂  f₂ |
                 | 0   0   1  |

                         | a₁·a₂ + c₁·b₂ + e₁·0  a₁·c₂ + c₁·d₂ + e₁·0  a₁·e₂ + c₁·f₂ + e₁·1 |
        first × second = | b₁·a₂ + d₁·b₂ + f₁·0  b₁·c₂ + d₁·d₂ + f₁·0  b₁·e₂ + d₁·f₂ + f₁·1 |
                         |  0·a₂ +  0·b₂ +  1·0   0·c₂ +  0·d₂ +  1·0   0·e₂ +  0·f₂ +  1·1 |
        """
        if not isinstance(other, Affine2D):
            return NotImplemented
        return Affine2D(
            a=self.a * other.a + self.c * other.b,  # + self.e * 0
            b=self.b * other.a + self.d * other.b,  # + self.f * 0
            c=self.a * other.c + self.c * other.d,  # + self.e * 0
            d=self.b * other.c + self.d * other.d,  # + self.f * 0
            e=self.a * other.e + self.c * other.f + self.e,  # * 1
            f=self.b * other.e + self.d * other.f + self.f,  # * 1
        )

    __imatmul__ = __matmul__

    def matrix(self, a, b, c, d, e, f):
        return self @ Affine2D(a, b, c, d, e, f)

    # https://www.w3.org/TR/SVG11/coords.html#TranslationDefined
    def translate(self, tx, ty=0):
        if (0, 0) == (tx, ty):
            return self
        return self.matrix(1, 0, 0, 1, tx, ty)

    def gettranslate(self) -> Tuple[float, float]:
        return (self.e, self.f)

    def getscale(self) -> Tuple[float, float]:
        return (self.a, self.d)

    # https://www.w3.org/TR/SVG11/coords.html#ScalingDefined
    def scale(self, sx, sy=None):
        if sy is None:
            sy = sx
        return self.matrix(sx, 0, 0, sy, 0, 0)

    # https://www.w3.org/TR/SVG11/coords.html#RotationDefined
    # Note that rotation here is in radians
    def rotate(self, a, cx=0.0, cy=0.0):
        return (
            self.translate(cx, cy)
            .matrix(cos(a), sin(a), -sin(a), cos(a), 0, 0)
            .translate(-cx, -cy)
        )

    # https://www.w3.org/TR/SVG11/coords.html#SkewXDefined
    def skewx(self, a):
        return self.matrix(1, 0, tan(a), 1, 0, 0)

    # https://www.w3.org/TR/SVG11/coords.html#SkewYDefined
    def skewy(self, a):
        return self.matrix(1, tan(a), 0, 1, 0, 0)

    def determinant(self) -> float:
        return self.a * self.d - self.b * self.c

    def is_degenerate(self) -> bool:
        """Return True if [a b c d] matrix is degenerate (determinant is 0)."""
        return abs(self.determinant()) <= float_info.epsilon

    def inverse(self):
        """Return the inverse Affine2D transformation.
<commit_msg>Add skew x and y together<commit_after># Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helpers for https://www.w3.org/TR/SVG11/coords.html#TransformAttribute.

Focuses on converting to a sequence of affine matrices.
"""
import collections
from functools import reduce
from math import cos, sin, radians, tan, hypot
import operator
import re
from typing import NamedTuple, Sequence, Tuple
from sys import float_info
from picosvg.geometric_types import (
    Point,
    Rect,
    Vector,
    DEFAULT_ALMOST_EQUAL_TOLERANCE,
    almost_equal,
)
from picosvg.svg_meta import ntos

DECOMPOSITION_ALMOST_EQUAL_TOLERANCE = 1e-6

_SVG_ARG_FIXUPS = collections.defaultdict(
    lambda: lambda _: None,
    {
        "rotate": lambda args: _fix_rotate(args),
        "skewx": lambda args: _fix_rotate(args),
        "skewy": lambda args: _fix_rotate(args),
    },
)


# 2D affine transform.
#
# View as vector of 6 values or matrix:
#
# a   c   e
# b   d   f
class Affine2D(NamedTuple):
    a: float
    b: float
    c: float
    d: float
    e: float
    f: float

    @staticmethod
    def flip_y():
        return Affine2D._flip_y

    @staticmethod
    def identity():
        return Affine2D._identity

    @staticmethod
    def degenerate():
        return Affine2D._degnerate

    @staticmethod
    def fromstring(raw_transform):
        return parse_svg_transform(raw_transform)

    def tostring(self):
        if self == Affine2D.identity().translate(*self.gettranslate()):
            return f'translate({", ".join(ntos(v) for v in self.gettranslate())})'
        return f'matrix({" ".join(ntos(v) for v in self)})'

    def __matmul__(self, other: "Affine2D") -> "Affine2D":
        """Returns the product of self × other. Order matters.

        The combined affine matrix can be thought of mapping by other before applying self.

        https://en.wikipedia.org/wiki/Matrix_multiplication

                | a₁  c₁  e₁ |
        first = | b₁  d₁  f₁ |
                | 0   0   1  |

                 | a₂  c₂  e₂ |
        second = | b₂  d₂  f₂ |
                 | 0   0   1  |

                         | a₁·a₂ + c₁·b₂ + e₁·0  a₁·c₂ + c₁·d₂ + e₁·0  a₁·e₂ + c₁·f₂ + e₁·1 |
        first × second = | b₁·a₂ + d₁·b₂ + f₁·0  b₁·c₂ + d₁·d₂ + f₁·0  b₁·e₂ + d₁·f₂ + f₁·1 |
                         |  0·a₂ +  0·b₂ +  1·0   0·c₂ +  0·d₂ +  1·0   0·e₂ +  0·f₂ +  1·1 |
        """
        if not isinstance(other, Affine2D):
            return NotImplemented
        return Affine2D(
            a=self.a * other.a + self.c * other.b,  # + self.e * 0
            b=self.b * other.a + self.d * other.b,  # + self.f * 0
            c=self.a * other.c + self.c * other.d,  # + self.e * 0
            d=self.b * other.c + self.d * other.d,  # + self.f * 0
            e=self.a * other.e + self.c * other.f + self.e,  # * 1
            f=self.b * other.e + self.d * other.f + self.f,  # * 1
        )

    __imatmul__ = __matmul__

    def matrix(self, a, b, c, d, e, f):
        return self @ Affine2D(a, b, c, d, e, f)

    # https://www.w3.org/TR/SVG11/coords.html#TranslationDefined
    def translate(self, tx, ty=0):
        if (0, 0) == (tx, ty):
            return self
        return self.matrix(1, 0, 0, 1, tx, ty)

    def gettranslate(self) -> Tuple[float, float]:
        return (self.e, self.f)

    def getscale(self) -> Tuple[float, float]:
        return (self.a, self.d)

    # https://www.w3.org/TR/SVG11/coords.html#ScalingDefined
    def scale(self, sx, sy=None):
        if sy is None:
            sy = sx
        return self.matrix(sx, 0, 0, sy, 0, 0)

    # https://www.w3.org/TR/SVG11/coords.html#RotationDefined
    # Note that rotation here is in radians
    def rotate(self, a, cx=0.0, cy=0.0):
        return (
            self.translate(cx, cy)
            .matrix(cos(a), sin(a), -sin(a), cos(a), 0, 0)
            .translate(-cx, -cy)
        )

    # Note that andl here is in radians
    def skewx(self, a):
        return self.matrix(1, 0, tan(a), 1, 0, 0)

    # Note that angle here is in radians
    def skewy(self, a):
        return self.matrix(1, tan(a), 0, 1, 0, 0)

    # Note that angle here is in radians
    def skew(self, xAngle, yAngle):
        return self.matrix(1, tan(yAngle), tan(xAngle), 1, 0, 0)

    def determinant(self) -> float:
        return self.a * self.d - self.b * self.c

    def is_degenerate(self) -> bool:
        """Return True if [a b c d] matrix is degenerate (determinant is 0)."""
        return abs(self.determinant()) <= float_info.epsilon

    def inverse(self):
        """Return the inverse Affine2D transformation.

{'fraction_non_alphanumeric': 0.12193675889328064, 'fraction_numerical': 0.019367588932806323, 'mean_word_length': 2.2038619816397595, 'pattern_counts': {'":': 8, '<': 5, '<?xml version=': 0, '>': 13, 'https://': 12, 'lorem ipsum': 0, 'www.': 12, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4485137', 'n_tokens_mistral': 3674, 'n_tokens_neox': 3595, 'n_words': 1027}