<commit_before>import collections
import contextlib
import itertools
import math

from affine import Affine

from .defuzz import Defuzzer
from .euclid import collinear, Point, Line, Segment


class PathTiler:
    def __init__(self):
        self.paths = []
        self.transform = Affine.identity()
        self.curpt = None
        self.saved_state = []

    # Path creation.

    def move_to(self, x, y):
        x, y = self.transform * (x, y)
        self.paths.append([])
        self.paths[-1].append(Point(x, y))
        self.curpt = x, y

    def line_to(self, x, y):
        x, y = self.transform * (x, y)
        self.paths[-1].append(Point(x, y))
        self.curpt = x, y

    def rel_line_to(self, dx, dy):
        x, y = ~self.transform * self.curpt
        self.line_to(x + dx, y + dy)

    def close_path(self):
        self.paths[-1].append(self.paths[-1][0])
        self.curpt = None

    def in_device(self, x, y):
        """Return the transformed coordinates."""
        return self.transform * (x, y)

    def in_user(self, x, y):
        return ~self.transform * (x, y)

    # Transformation.

    def translate(self, dx, dy):
        self.transform *= Affine.translation(dx, dy)

    def rotate(self, degrees):
        self.transform *= Affine.rotation(degrees)

    def scale(self, x, y):
        self.transform *= Affine.scale(x, y)

    def reflect_x(self, x):
        self.translate(x, 0)
        self.scale(-1, 1)
        self.translate(-x, 0)

    def reflect_y(self, y):
        self.translate(0, y)
        self.scale(1, -1)
        self.translate(0, -y)

    def reflect_xy(self, x, y):
        self.reflect_x(x)
        self.reflect_y(y)

    def reflect_line(self, p1, p2):
        """Reflect across the line from p1 to p2."""
        # https://en.wikipedia.org/wiki/Transformation_matrix#Reflection
        (p1x, p1y), (p2x, p2y) = p1, p2
        dx = p2x - p1x
        dy = p2y - p1y
        denom = dx * dx + dy * dy

        a = (dx * dx - dy * dy) / denom
        b = (2 * dx * dy) / denom

        self.translate(p1x, p1y)
        self.transform *= Affine(a, b, 0, b, -a, 0)
        self.translate(-p1x, -p1y)

    # Save/Restore.

    def save(self):
        self.saved_state.append(self.transform)

    def restore(self):
        self.transform = self.saved_state.pop()

    @contextlib.contextmanager
    def saved(self):
        self.save()
        try:
            yield
        finally:
            self.restore()

    # Tiling of draw functions.
    # http://www.quadibloc.com/math/images/wall17.gif
    # https://www.math.toronto.edu/drorbn/Gallery/Symmetry/Tilings/Sanderson/index.html

    def tile_p1(self, draw_func, dwg_size, vcol, vrow, buffer=None, args=()):
        """Repeatedly call draw_func to tile the drawing."""
        # Should compute exactly the grid of parallelograms needed, but I don't
        # know how yet.
        if buffer is None:
            buffer = 3
        dwgw, dwgh = dwg_size
        (vrx, vry), (vcx, vcy) = vrow, vcol
        tiles_across = int(dwgw // vcx)
        tiles_down = int(dwgh // vry)
        for row in range(-buffer, tiles_across + buffer):
            for col in range(-buffer, tiles_down + buffer):
                with self.saved():
                    self.translate(row * vrx + col * vcx, row * vry + col * vcy)
                    draw_func(self, args)

    def tile_pmm(self, draw_func, dwg_size, dx, dy, args=()):
        def four_mirror(pt, args):
            draw_func(pt, args=args)
            with pt.saved():
                pt.reflect_x(dx)
                draw_func(pt, args)
            with pt.saved():
                pt.reflect_xy(dx, dy)
                draw_func(pt, args)
            with pt.saved():
                pt.reflect_y(dy)
                draw_func(pt, args)

        self.tile_p1(four_mirror, dwg_size, (dx*2, 0), (0, dy*2), buffer=0, args=args)

    def tile_p6(self, draw_func, dwg_size, triw, args=()):
        def six_triangles(pt, args):
            pt.translate(0, triw)
            for _ in range(6):
                self.rotate(60)
                draw_func(pt, args)

        triw3 = triw * math.sqrt(3)
        self.tile_p1(six_triangles, dwg_size, (triw3, 0), (triw3 / 2, 1.5 * triw), buffer=2, args=args)

    def tile_p6m(self, draw_func, dwg_size, triw, args=()):
        def draw_mirrored(pt, args):
            draw_func(pt, args)
            with pt.saved():
                pt.reflect_x(0)
                draw_func(pt, args)

        self.tile_p6(draw_mirrored, dwg_size, triw, args=args)

    # More stuff.

    def replay_paths(self, ctx):
        for path in self.paths:
            replay_path(path, ctx)


def replay_path(path, ctx):
    ctx.move_to(*path[0])
    for pt in path[1:-1]:
        ctx.line_to(*pt)
    if path[-1] == path[0]:
        ctx.close_path()
    else:
        ctx.line_to(*path[-1])


def path_in_box(path, ll, ur):
    return all(pt.in_box(ll, ur) for pt in path)


def penultimate(path, point):
    """The second-to-last point from whichever end ends with `point`."""
    if path[0] == point:
        return path[1]
    else:
        assert path[-1] == point
        return path[-2]

def best_join(path, join_point, possibilities):
    others = [p for p in possibilities if p != path]

    # If there's only one other path, then join to that one.
    if len(others) == 1:
        return others[0]

    # If there's more than one, find one we are collinear with.<commit_msg>Replaying a path can leave a gap at the ends<commit_after>import collections
import contextlib
import itertools
import math

from affine import Affine

from .defuzz import Defuzzer
from .euclid import collinear, Point, Line, Segment, along_the_way


class PathTiler:
    def __init__(self):
        self.paths = []
        self.transform = Affine.identity()
        self.curpt = None
        self.saved_state = []

    # Path creation.

    def move_to(self, x, y):
        x, y = self.transform * (x, y)
        self.paths.append([])
        self.paths[-1].append(Point(x, y))
        self.curpt = x, y

    def line_to(self, x, y):
        x, y = self.transform * (x, y)
        self.paths[-1].append(Point(x, y))
        self.curpt = x, y

    def rel_line_to(self, dx, dy):
        x, y = ~self.transform * self.curpt
        self.line_to(x + dx, y + dy)

    def close_path(self):
        self.paths[-1].append(self.paths[-1][0])
        self.curpt = None

    def in_device(self, x, y):
        """Return the transformed coordinates."""
        return self.transform * (x, y)

    def in_user(self, x, y):
        return ~self.transform * (x, y)

    # Transformation.

    def translate(self, dx, dy):
        self.transform *= Affine.translation(dx, dy)

    def rotate(self, degrees):
        self.transform *= Affine.rotation(degrees)

    def scale(self, x, y):
        self.transform *= Affine.scale(x, y)

    def reflect_x(self, x):
        self.translate(x, 0)
        self.scale(-1, 1)
        self.translate(-x, 0)

    def reflect_y(self, y):
        self.translate(0, y)
        self.scale(1, -1)
        self.translate(0, -y)

    def reflect_xy(self, x, y):
        self.reflect_x(x)
        self.reflect_y(y)

    def reflect_line(self, p1, p2):
        """Reflect across the line from p1 to p2."""
        # https://en.wikipedia.org/wiki/Transformation_matrix#Reflection
        (p1x, p1y), (p2x, p2y) = p1, p2
        dx = p2x - p1x
        dy = p2y - p1y
        denom = dx * dx + dy * dy

        a = (dx * dx - dy * dy) / denom
        b = (2 * dx * dy) / denom

        self.translate(p1x, p1y)
        self.transform *= Affine(a, b, 0, b, -a, 0)
        self.translate(-p1x, -p1y)

    # Save/Restore.

    def save(self):
        self.saved_state.append(self.transform)

    def restore(self):
        self.transform = self.saved_state.pop()

    @contextlib.contextmanager
    def saved(self):
        self.save()
        try:
            yield
        finally:
            self.restore()

    # Tiling of draw functions.
    # http://www.quadibloc.com/math/images/wall17.gif
    # https://www.math.toronto.edu/drorbn/Gallery/Symmetry/Tilings/Sanderson/index.html

    def tile_p1(self, draw_func, dwg_size, vcol, vrow, buffer=None, args=()):
        """Repeatedly call draw_func to tile the drawing."""
        # Should compute exactly the grid of parallelograms needed, but I don't
        # know how yet.
        if buffer is None:
            buffer = 3
        dwgw, dwgh = dwg_size
        (vrx, vry), (vcx, vcy) = vrow, vcol
        tiles_across = int(dwgw // vcx)
        tiles_down = int(dwgh // vry)
        for row in range(-buffer, tiles_across + buffer):
            for col in range(-buffer, tiles_down + buffer):
                with self.saved():
                    self.translate(row * vrx + col * vcx, row * vry + col * vcy)
                    draw_func(self, args)

    def tile_pmm(self, draw_func, dwg_size, dx, dy, args=()):
        def four_mirror(pt, args):
            draw_func(pt, args=args)
            with pt.saved():
                pt.reflect_x(dx)
                draw_func(pt, args)
            with pt.saved():
                pt.reflect_xy(dx, dy)
                draw_func(pt, args)
            with pt.saved():
                pt.reflect_y(dy)
                draw_func(pt, args)

        self.tile_p1(four_mirror, dwg_size, (dx*2, 0), (0, dy*2), buffer=0, args=args)

    def tile_p6(self, draw_func, dwg_size, triw, args=()):
        def six_triangles(pt, args):
            pt.translate(0, triw)
            for _ in range(6):
                self.rotate(60)
                draw_func(pt, args)

        triw3 = triw * math.sqrt(3)
        self.tile_p1(six_triangles, dwg_size, (triw3, 0), (triw3 / 2, 1.5 * triw), buffer=2, args=args)

    def tile_p6m(self, draw_func, dwg_size, triw, args=()):
        def draw_mirrored(pt, args):
            draw_func(pt, args)
            with pt.saved():
                pt.reflect_x(0)
                draw_func(pt, args)

        self.tile_p6(draw_mirrored, dwg_size, triw, args=args)

    # More stuff.

    def replay_paths(self, ctx):
        for path in self.paths:
            replay_path(path, ctx)


def replay_path(path, ctx, gap=0):
    if gap:
        p0 = along_the_way(path[0], path[1], gap)
    else:
        p0 = path[0]

    ctx.move_to(*p0)
    for pt in path[1:-1]:
        ctx.line_to(*pt)

    if path[-1] == path[0] and not gap:
        ctx.close_path()
    else:
        if gap:
            plast = along_the_way(path[-2], path[-1], 1-gap)
        else:
            plast = path[-1]
        ctx.line_to(*plast)


def path_in_box(path, ll, ur):
    return all(pt.in_box(ll, ur) for pt in path)


def penultimate(path, point):
    """The second-to-last point from whichever end ends with `point`."""
    if path[0] == point:
        return path[1]
    else:
        assert path[-1] == point
        return path[-2]

def best_join(path, join_point, possibilities):
    others = [p for p in possibilities if p != path]

    # If there's only one other path, then join to that one.
    if len(others) == 1:
        return others[0]

    # If there's more than one, find one we are collinear with.
{'fraction_non_alphanumeric': 0.12929184549356224, 'fraction_numerical': 0.0140379113018598, 'mean_word_length': 1.9574299312533052, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 4, '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': '729078', 'n_tokens_mistral': 3955, 'n_tokens_neox': 3809, 'n_words': 989}