summaryrefslogtreecommitdiff
path: root/lib/svg/path.py
blob: 9d92058baa476d34cb627e93f81390e592e1e16c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# Authors: see git history
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later.  See the file LICENSE for details.

import inkex

from .tags import SVG_GROUP_TAG, SVG_LINK_TAG
from .units import get_viewbox_transform


def apply_transforms(path, node):
    transform = get_node_transform(node)

    # apply the combined transform to this node's path
    path = path.transform(transform)

    return path


def compose_parent_transforms(node, mat):
    # This is adapted from Inkscape's simpletransform.py's composeParents()
    # function.  That one can't handle nodes that are detached from a DOM.

    trans = node.get('transform')
    if trans:
        mat = inkex.transforms.Transform(trans) @ mat
    if node.getparent() is not None:
        if node.getparent().tag in [SVG_GROUP_TAG, SVG_LINK_TAG]:
            mat = compose_parent_transforms(node.getparent(), mat)
    return mat


def get_node_transform(node: inkex.BaseElement):
    """
    if getattr(node, "composed_transform", None):
        return node.composed_transform()
    """

    # start with the identity transform
    transform = inkex.transforms.Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])

    # this if is because sometimes inkscape likes to create paths outside of a layer?!
    if node.getparent() is not None:
        # combine this node's transform with all parent groups' transforms
        transform = compose_parent_transforms(node, transform)

    # add in the transform implied by the viewBox
    viewbox_transform = get_viewbox_transform(node.getroottree().getroot())
    transform = viewbox_transform @ transform

    return transform


def get_correction_transform(node, child=False):
    """Get a transform to apply to new siblings or children of this SVG node

    Arguments:
        child (boolean) -- whether the new nodes we're going to add will be
                           children of node (child=True) or siblings of node
                           (child=False)

    This allows us to add a new child node that has its path specified in
    absolute coordinates.  The correction transform will undo the effects of
    the parent's and ancestors' transforms so that absolute coordinates
    work properly.
    """

    if child:
        transform = get_node_transform(node)
    else:
        # we can ignore the transform on the node itself since it won't apply
        # to the objects we add
        transform = get_node_transform(node.getparent())

    # now invert it, so that we can position our objects in absolute
    # coordinates
    transform = -transform

    return str(transform)


def line_strings_to_csp(line_strings):
    try:
        # This lets us accept a MultiLineString or a list.
        line_strings = line_strings.geoms
    except AttributeError:
        pass

    return point_lists_to_csp(ls.coords for ls in line_strings)


def point_lists_to_csp(point_lists):
    csp = []

    for point_list in point_lists:
        subpath = []
        for point in point_list:
            # cubicsuperpath is very particular that these must be lists, not tuples
            point = list(point)
            # create a straight line as a degenerate bezier
            subpath.append([point, point, point])
        csp.append(subpath)

    return csp


def line_strings_to_path(line_strings):
    csp = line_strings_to_csp(line_strings)

    return inkex.PathElement(attrib={
        "d": str(inkex.paths.CubicSuperPath(csp))
    })