import re
import math
from xml.etree import ElementTree

from .handler import SvgHandler, NameMode
from ... import objects
from ...nvector import NVector
from ...utils import restructure
from ...utils.transform import TransformMatrix
from ...parsers import glaxnimate_helpers
from ...utils.color import Color, ColorMode
try:
    from ...utils import font
    has_font = True
except ImportError:
    has_font = False


_supported_font_weights = {
    "Thin": 100, "Hairline": 100,
    "ExtraLight": 200, "UltraLight": 200,
    "Light": 300,
    "Regular": 400, "Normal": 400, "Plain": 400, "Standard": 400, "Roman": 400,
    "Medium": 500,
    "SemiBold": 600, "Demi": 600, "DemiBold": 600,
    "Bold": 700,
    "Extra": 800, "ExtraBold": 800, "Ultra": 800, "UltraBold": 800,
    "Black": 900, "Heavy": 900,
    "ExtraBlack": 1000, "UltraBlack": 1000, "UltraHeavy": 1000,
}


class PrecompTime:
    def __init__(self, pcl: objects.PreCompLayer):
        self.pcl = pcl

    def get_time_offset(self, time, lot):
        remap = time
        if self.pcl.time_remapping:
            remapf = self.pcl.time_remapping.get_value(time)
            remap = lot.in_point * (1-remapf) + lot.out_point * remapf

        return remap - self.pcl.start_time


class SvgBuilder(SvgHandler, restructure.AbstractBuilder):
    merge_paths = True
    namestart = (
        r":_A-Za-z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF" +
        r"\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF" +
        r"\uFDF0-\uFFFD\U00010000-\U000EFFFF"
    )
    namenostart = r"-.0-9\xB7\u0300-\u036F\u203F-\u2040"
    id_re = re.compile("^[%s][%s%s]*$" % (namestart, namenostart, namestart))

    def __init__(self, time=0):
        super().__init__()
        self.svg = ElementTree.Element("svg")
        self.dom = ElementTree.ElementTree(self.svg)
        self.svg.attrib["xmlns"] = self.ns_map["svg"]
        self.ids = set()
        self.idc = 0
        self.name_mode = NameMode.Inkscape
        self.actual_time = time
        self.precomp_times = []
        self._precomps = {}
        self._assets = {}
        self._fonts = {}
        self._current_layer = []

    @property
    def time(self):
        time = self.actual_time
        if self.precomp_times:
            for pct in self.precomp_times:
                time = pct.get_time_offset(time, self._current_layer[-1])
        return time

    def gen_id(self, prefix="id"):
        while True:
            self.idc += 1
            id = "%s_%s" % (prefix, self.idc)
            if id not in self.ids:
                break
        self.ids.add(id)
        return id

    def set_clean_id(self, dom, n):
        idn = n.replace(" ", "_")
        if self.id_re.match(idn) and idn not in self.ids:
            self.ids.add(idn)
        else:
            idn = self.gen_id(dom.tag)

        dom.attrib["id"] = idn
        return idn

    def set_id(self, dom, lottieobj, inkscape_qual=None, force=False):
        n = getattr(lottieobj, "name", None)
        if n is None or self.name_mode == NameMode.NoName:
            if force:
                id = self.gen_id(dom.tag)
                dom.attrib["id"] = id
                return id
            return None

        idn = self.set_clean_id(dom, n)
        if inkscape_qual is None:
            inkscape_qual = self.qualified("inkscape", "label")
        if inkscape_qual:
            dom.attrib[inkscape_qual] = n
        return idn

    def _on_animation(self, animation: objects.Animation):
        self.svg.attrib["width"] = str(animation.width)
        self.svg.attrib["height"] = str(animation.height)
        self.svg.attrib["viewBox"] = "0 0 %s %s" % (animation.width, animation.height)
        self.svg.attrib["version"] = "1.1"
        self.set_id(self.svg, animation, self.qualified("sodipodi", "docname"))
        self.defs = ElementTree.SubElement(self.svg, "defs")
        if self.name_mode == NameMode.Inkscape:
            self.svg.attrib[self.qualified("inkscape", "export-xdpi")] = "96"
            self.svg.attrib[self.qualified("inkscape", "export-ydpi")] = "96"
            namedview = ElementTree.SubElement(self.svg, self.qualified("sodipodi", "namedview"))
            namedview.attrib[self.qualified("inkscape", "pagecheckerboard")] = "true"
            namedview.attrib["borderlayer"] = "true"
            namedview.attrib["bordercolor"] = "#666666"
            namedview.attrib["pagecolor"] = "#ffffff"
        self.svg.attrib["style"] = "fill: none; stroke: none"

        self._current_layer = [animation]
        return self.svg

    def _mask_to_def(self, mask):
        svgmask = ElementTree.SubElement(self.defs, "mask")
        mask_id = self.gen_id()
        svgmask.attrib["id"] = mask_id
        svgmask.attrib["mask-type"] = "alpha"
        path = ElementTree.SubElement(svgmask, "path")
        path.attrib["d"] = self._bezier_to_d(mask.shape.get_value(self.time))
        path.attrib["fill"] = "#fff"
        path.attrib["fill-opacity"] = str(mask.opacity.get_value(self.time) / 100)
        return mask_id

    def _matte_source_to_def(self, layer_builder):
        svgmask = ElementTree.SubElement(self.defs, "mask")
        if not layer_builder.matte_id:
            layer_builder.matte_id = self.gen_id()
        svgmask.attrib["id"] = layer_builder.matte_id
        matte_mode = layer_builder.matte_target.lottie.matte_mode

        mask_type = "alpha"
        if matte_mode == objects.MatteMode.Luma:
            mask_type = "luminance"
        svgmask.attrib["mask-type"] = mask_type
        return svgmask

    def _on_masks(self, masks):
        if len(masks) == 1:
            return self._mask_to_def(masks[0])
        mask_ids = list(map(self._mask_to_def, masks))
        mask_def = ElementTree.SubElement(self.defs, "mask")
        mask_id = self.gen_id()
        mask_def.attrib["id"] = mask_id
        g = mask_def
        for mid in mask_ids:
            g = ElementTree.SubElement(g, "g")
            g.attrib["mask"] = "url(#%s)" % mid
        full = ElementTree.SubElement(g, "rect")
        full.attrib["fill"] = "#fff"
        full.attrib["width"] = self.svg.attrib["width"]
        full.attrib["height"] = self.svg.attrib["height"]
        full.attrib["x"] = "0"
        full.attrib["y"] = "0"
        return mask_id

    def _on_layer(self, layer_builder, dom_parent):
        lot = layer_builder.lottie
        self._current_layer.append(lot)

        if not self.precomp_times and (lot.in_point > self.time or lot.out_point < self.time):
            self._current_layer.pop()
            return None

        if layer_builder.matte_target:
            dom_parent = self._matte_source_to_def(layer_builder)

        g = self.group_from_lottie(lot, dom_parent, True)

        if lot.masks:
            g.attrib["mask"] = "url(#%s)" % self._on_masks(lot.masks)
        elif layer_builder.matte_source:
            matte_id = layer_builder.matte_source.matte_id
            if not matte_id:
                matte_id = layer_builder.matte_source.matte_id = self.gen_id()
            g.attrib["mask"] = "url(#%s)" % matte_id

        if isinstance(lot, objects.PreCompLayer):
            self.precomp_times.append(PrecompTime(lot))

            for layer in self._precomps.get(lot.reference_id, []):
                self.process_layer(layer, g)

            self.precomp_times.pop()
        elif isinstance(lot, objects.NullLayer):
            g.attrib["opacity"] = "1"
        elif isinstance(lot, objects.ImageLayer):
            use = ElementTree.SubElement(g, "use")
            use.attrib[self.qualified("xlink", "href")] = "#" + self._assets[lot.image_id]
        elif isinstance(lot, objects.TextLayer):
            self._on_text_layer(g, lot)
        elif isinstance(lot, objects.SolidColorLayer):
            rect = ElementTree.SubElement(g, "rect")
            rect.attrib["width"] = str(lot.width)
            rect.attrib["height"] = str(lot.height)
            rect.attrib["fill"] = color_to_css(lot.color)

        if not lot.name:
            g.attrib[self.qualified("inkscape", "label")] = lot.__class__.__name__
        if layer_builder.shapegroup:
            g.attrib["style"] = self.group_to_style(layer_builder.shapegroup)
            self._split_stroke(layer_builder.shapegroup, g, dom_parent)
        #if lot.hidden:
            #g.attrib.setdefault("style", "")
            #g.attrib["style"] += "display: none;"

        return g

    def _on_font(self, font):
        self._fonts[font.name] = {
            "font-family": font.font_family,
            "font-weight": str(_supported_font_weights.get(font.font_style, 400)),
        }

    def _on_text_layer(self, g, lot):
        text = ElementTree.SubElement(g, "text")
        doc = lot.data.get_value(self.time)
        if doc:
            text.attrib.update(self._fonts.get(doc.font_family, {}))
            text.attrib["font-size"] = str(doc.font_size)
            if doc.line_height:
                text.attrib["line-height"] = "%s%%" % doc.line_height
            if doc.justify == objects.text.TextJustify.Left:
                text.attrib["text-anchor"] = "start"
            elif doc.justify == objects.text.TextJustify.Center:
                text.attrib["text-anchor"] = "middle"
            elif doc.justify == objects.text.TextJustify.Right:
                text.attrib["text-anchor"] = "end"

            text.attrib["fill"] = color_to_css(doc.color)
            text.text = doc.text

    def _on_layer_end(self, out_layer):
        self._current_layer.pop()

    def _on_precomp(self, id, dom_parent, layers):
        self._precomps[id] = layers

    def _on_asset(self, asset):
        if isinstance(asset, objects.assets.Image):
            img = ElementTree.SubElement(self.defs, "image")
            xmlid = self.set_clean_id(img, asset.id)
            self._assets[asset.id] = xmlid
            if asset.is_embedded:
                url = asset.image
            else:
                url = asset.path + asset.file_name
            img.attrib[self.qualified("xlink", "href")] = url
            img.attrib["width"] = str(asset.width)
            img.attrib["height"] = str(asset.height)

    def _get_value(self, prop, default=NVector(0, 0)):
        if prop:
            v = prop.get_value(self.time)
        else:
            v = default

        if v is None:
            return default
        if isinstance(v, NVector):
            return v.clone()
        return v

    def set_transform(self, dom, transform, auto_orient=False):
        if not transform:
            return

        mat = transform.to_matrix(self.time, auto_orient)
        dom.attrib["transform"] = mat.to_css_2d()

        if transform.opacity is not None:
            op = transform.opacity.get_value(self.time)
            if op != 100:
                dom.attrib["opacity"] = str(op/100)

    def _get_group_stroke(self, group):
        style = {}
        if group.stroke:
            if isinstance(group.stroke, objects.GradientStroke):
                style["stroke"] = "url(#%s)" % self.process_gradient(group.stroke)
            else:
                style["stroke"] = color_to_css(group.stroke.color.get_value(self.time))

            style["stroke-opacity"] = group.stroke.opacity.get_value(self.time) / 100
            style["stroke-width"] = group.stroke.width.get_value(self.time)
            if group.stroke.miter_limit is not None:
                style["stroke-miterlimit"] = group.stroke.miter_limit

            if group.stroke.line_cap == objects.LineCap.Round:
                style["stroke-linecap"] = "round"
            elif group.stroke.line_cap == objects.LineCap.Butt:
                style["stroke-linecap"] = "butt"
            elif group.stroke.line_cap == objects.LineCap.Square:
                style["stroke-linecap"] = "square"

            if group.stroke.line_join == objects.LineJoin.Round:
                style["stroke-linejoin"] = "round"
            elif group.stroke.line_join == objects.LineJoin.Bevel:
                style["stroke-linejoin"] = "bevel"
            elif group.stroke.line_join == objects.LineJoin.Miter:
                style["stroke-linejoin"] = "miter"

            if group.stroke.dashes:
                dasharray = []
                last = 0
                last_mode = objects.StrokeDashType.Dash
                for dash in group.stroke.dashes:
                    if last_mode == dash.type:
                        last += dash.length.get_value(self.time)
                    else:
                        if last_mode != objects.StrokeDashType.Offset:
                            dasharray.append(str(last))
                            last = 0
                        last_mode = dash.type
                style["stroke-dasharray"] = " ".join(dasharray)
        return style

    def _style_to_css(self, style):
        return ";".join(map(
            lambda x: ":".join(map(str, x)),
            style.items()
        ))

    def _split_stroke(self, group, fill_layer, out_parent):
        if not group.stroke:# or group.stroke_above:
            return

        style = self._get_group_stroke(group)
        if style.get("stroke-width", 0) <= 0 or style["stroke-opacity"] <= 0:
            return

        if group.stroke_above:
            if fill_layer.attrib.get("style", ""):
                fill_layer.attrib["style"] += ";"
            else:
                fill_layer.attrib["style"] = ""
            fill_layer.attrib["style"] += self._style_to_css(style)
            return fill_layer

        g = ElementTree.Element("g")
        self.set_clean_id(g, "stroke")
        use = ElementTree.Element("use")
        for i, e in enumerate(out_parent):
            if e is fill_layer:
                out_parent.insert(i, g)
                out_parent.remove(fill_layer)
                break
        else:
            return

        g.append(use)
        g.append(fill_layer)

        use.attrib[self.qualified("xlink", "href")] = "#" + fill_layer.attrib["id"]
        use.attrib["style"] = self._style_to_css(style)
        return g

    def group_to_style(self, group):
        style = {}
        if group.fill:
            style["fill-opacity"] = group.fill.opacity.get_value(self.time) / 100
            if isinstance(group.fill, objects.GradientFill):
                style["fill"] = "url(#%s)" % self.process_gradient(group.fill)
            else:
                style["fill"] = color_to_css(group.fill.color.get_value(self.time))

            if group.fill.fill_rule:
                style["fill-rule"] = "evenodd" if group.fill.fill_rule == objects.FillRule.EvenOdd else "nonzero"

        if group.lottie.hidden:
            style["display"] = "none"
        #if group.stroke_above:
            #style.update(self._get_group_stroke(group))

        return self._style_to_css(style)

    def process_gradient(self, gradient):
        spos = gradient.start_point.get_value(self.time)
        epos = gradient.end_point.get_value(self.time)

        if gradient.gradient_type == objects.GradientType.Linear:
            dom = ElementTree.SubElement(self.defs, "linearGradient")
            dom.attrib["x1"] = str(spos[0])
            dom.attrib["y1"] = str(spos[1])
            dom.attrib["x2"] = str(epos[0])
            dom.attrib["y2"] = str(epos[1])
        elif gradient.gradient_type == objects.GradientType.Radial:
            dom = ElementTree.SubElement(self.defs, "radialGradient")
            dom.attrib["cx"] = str(spos[0])
            dom.attrib["cy"] = str(spos[1])
            dom.attrib["r"] = str((epos-spos).length)
            a = gradient.highlight_angle.get_value(self.time) * math.pi / 180
            l = gradient.highlight_length.get_value(self.time)
            dom.attrib["fx"] = str(spos[0] + math.cos(a) * l)
            dom.attrib["fy"] = str(spos[1] + math.sin(a) * l)

        id = self.set_id(dom, gradient, force=True)
        dom.attrib["gradientUnits"] = "userSpaceOnUse"

        for off, color in gradient.colors.stops_at(self.time):
            stop = ElementTree.SubElement(dom, "stop")
            stop.attrib["offset"] = "%s%%" % (off * 100)
            stop.attrib["stop-color"] = color_to_css(color[:3])
            if len(color) > 3:
                stop.attrib["stop-opacity"] = str(color[3])

        return id

    def group_from_lottie(self, lottie, dom_parent, layer):
        g = ElementTree.SubElement(dom_parent, "g")
        if layer and self.name_mode == NameMode.Inkscape:
            g.attrib[self.qualified("inkscape", "groupmode")] = "layer"
        self.set_id(g, lottie, force=True)
        self.set_transform(g, lottie.transform, getattr(lottie, "auto_orient", False))
        return g

    def _on_shapegroup(self, group, dom_parent):
        if group.empty():
            return

        if len(group.children) == 1 and isinstance(group.children[0], restructure.RestructuredPathMerger):
            path = self.build_path(group.paths.paths, dom_parent)
            self.set_id(path, group.paths.paths[0], force=True)
            path.attrib["style"] = self.group_to_style(group)
            self.set_transform(path, group.lottie.transform)
            return self._split_stroke(group, path, dom_parent)

        g = self.group_from_lottie(group.lottie, dom_parent, group.layer)
        g.attrib["style"] = self.group_to_style(group)
        self.shapegroup_process_children(group, g)
        return self._split_stroke(group, g, dom_parent)

    def _on_merged_path(self, shape, shapegroup, out_parent):
        path = self.build_path(shape.paths, out_parent)
        self.set_id(path, shape.paths[0])
        path.attrib["style"] = self.group_to_style(shapegroup)
        #self._split_stroke(shapegroup, path, out_parent)
        return path

    def _on_shape(self, shape, shapegroup, out_parent):
        if isinstance(shape, objects.Rect):
            svgshape = self.build_rect(shape, out_parent)
        elif isinstance(shape, objects.Ellipse):
            svgshape = self.build_ellipse(shape, out_parent)
        elif isinstance(shape, objects.Star):
            svgshape = self.build_path([shape.to_bezier()], out_parent)
        elif isinstance(shape, objects.Path):
            svgshape = self.build_path([shape], out_parent)
        elif has_font and isinstance(shape, font.FontShape):
            svgshape = self.build_text(shape, out_parent)
        else:
            return
        self.set_id(svgshape, shape, force=True)
        if "style" not in svgshape.attrib:
            svgshape.attrib["style"] = ""
        svgshape.attrib["style"] += self.group_to_style(shapegroup)
        #self._split_stroke(shapegroup, svgshape, out_parent)

        if shape.hidden:
            svgshape.attrib["style"] += "display: none;"
        return svgshape

    def build_rect(self, shape, parent):
        rect = ElementTree.SubElement(parent, "rect")
        size = shape.size.get_value(self.time)
        pos = shape.position.get_value(self.time)
        rect.attrib["width"] = str(size[0])
        rect.attrib["height"] = str(size[1])
        rect.attrib["x"] = str(pos[0] - size[0] / 2)
        rect.attrib["y"] = str(pos[1] - size[1] / 2)
        rect.attrib["rx"] = str(shape.rounded.get_value(self.time))
        return rect

    def build_ellipse(self, shape, parent):
        ellipse = ElementTree.SubElement(parent, "ellipse")
        size = shape.size.get_value(self.time)
        pos = shape.position.get_value(self.time)
        ellipse.attrib["rx"] = str(size[0] / 2)
        ellipse.attrib["ry"] = str(size[1] / 2)
        ellipse.attrib["cx"] = str(pos[0])
        ellipse.attrib["cy"] = str(pos[1])
        return ellipse

    def build_path(self, shapes, parent):
        path = ElementTree.SubElement(parent, "path")
        d = ""
        for shape in shapes:
            bez = shape.shape.get_value(self.time)
            if isinstance(bez, list):
                bez = bez[0]
            if not bez.vertices:
                continue
            if d:
                d += "\n"
            d += self._bezier_to_d(bez)

        path.attrib["d"] = d
        return path

    def _bezier_tangent(self, tangent):
        _tangent_threshold = 0.5
        if tangent.length < _tangent_threshold:
            return NVector(0, 0)
        return tangent

    def _bezier_to_d(self, bez):
        d = "M %s,%s " % tuple(bez.vertices[0].components[:2])
        for i in range(1, len(bez.vertices)):
            qfrom = bez.vertices[i-1]
            h1 = self._bezier_tangent(bez.out_tangents[i-1]) + qfrom
            qto = bez.vertices[i]
            h2 = self._bezier_tangent(bez.in_tangents[i]) + qto

            d += "C %s,%s %s,%s %s,%s " % (
                h1[0], h1[1],
                h2[0], h2[1],
                qto[0], qto[1],
            )
        if bez.closed:
            qfrom = bez.vertices[-1]
            h1 = self._bezier_tangent(bez.out_tangents[-1]) + qfrom
            qto = bez.vertices[0]
            h2 = self._bezier_tangent(bez.in_tangents[0]) + qto
            d += "C %s,%s %s,%s %s,%s Z" % (
                h1[0], h1[1],
                h2[0], h2[1],
                qto[0], qto[1],
            )

        return d

    def _on_shape_modifier(self, shape, shapegroup, out_parent):
        if isinstance(shape.lottie, objects.Repeater):
            svgshape = self.build_repeater(shape.lottie, shape.child, shapegroup, out_parent)
        elif isinstance(shape.lottie, objects.RoundedCorners):
            svgshape = self.build_rouded_corners(shape.lottie, shape.child, shapegroup, out_parent)
        elif isinstance(shape.lottie, objects.Trim):
            svgshape = self.build_trim_path(shape.lottie, shape.child, shapegroup, out_parent)
        else:
            return self.shapegroup_process_child(shape.child, shapegroup, out_parent)
        return svgshape

    def build_repeater(self, shape, child, shapegroup, out_parent):
        original = self.shapegroup_process_child(child, shapegroup, out_parent)
        if not original:
            return

        ncopies = int(round(shape.copies.get_value(self.time)))
        if ncopies == 1:
            return

        out_parent.remove(original)

        g = ElementTree.SubElement(out_parent, "g")
        self.set_clean_id(g, "repeater")

        for copy in range(ncopies-1):
            use = ElementTree.SubElement(g, "use")
            use.attrib[self.qualified("xlink", "href")] = "#" + original.attrib["id"]

        orig_wrapper = ElementTree.SubElement(g, "g")
        orig_wrapper.append(original)

        transform = objects.Transform()
        so = shape.transform.start_opacity.get_value(self.time)
        eo = shape.transform.end_opacity.get_value(self.time)
        position = shape.transform.position.get_value(self.time)
        rotation = shape.transform.rotation.get_value(self.time)
        anchor_point = shape.transform.anchor_point.get_value(self.time)
        for i in range(ncopies-1, -1, -1):
            of = i / (ncopies-1)
            transform.opacity.value = so * of + eo * (1 - of)
            self.set_transform(g[i], transform)
            transform.position.value += position
            transform.rotation.value += rotation
            transform.anchor_point.value += anchor_point

        return g

    def build_rouded_corners(self, shape, child, shapegroup, out_parent):
        round_amount = shape.radius.get_value(self.time)
        return self._modifier_process(child, shapegroup, out_parent, self._build_rouded_corners_shape, round_amount)

    def _build_rouded_corners_shape(self, shape, round_amount):
        if not isinstance(shape, objects.Shape):
            return [shape]
        path = shape.to_bezier()
        bezier = path.shape.get_value(self.time).rounded(round_amount)
        path.shape.clear_animation(bezier)
        return [path]

    def build_trim_path(self, shape, child, shapegroup, out_parent):
        start = max(0, min(1, shape.start.get_value(self.time) / 100))
        end = max(0, min(1, shape.end.get_value(self.time) / 100))
        offset = shape.offset.get_value(self.time) / 360 % 1

        multidata = {}
        length = 0

        if shape.multiple == objects.TrimMultipleShapes.Individually:
            for visishape in reversed(list(self._modifier_foreach_shape(child))):
                bez = visishape.to_bezier().shape.get_value(self.time)
                local_length = bez.rough_length()
                multidata[visishape] = (bez, length, local_length)
                length += local_length

        return self._modifier_process(
            child, shapegroup, out_parent, self._build_trim_path_shape,
            start+offset, end+offset, multidata, length
        )

    def _modifier_foreach_shape(self, shape):
        if isinstance(shape, restructure.RestructuredShapeGroup):
            for child in shape.children:
                for chsh in self._modifier_foreach_shape(child):
                    yield chsh
        elif isinstance(shape, restructure.RestructuredPathMerger):
            for p in shape.paths:
                yield p
        elif isinstance(shape, objects.Shape):
            yield shape

    def _modifier_process(self, child, shapegroup, out_parent, callback, *args):
        children = self._modifier_process_child(child, shapegroup, out_parent, callback, *args)
        return [self.shapegroup_process_child(ch, shapegroup, out_parent) for ch in children]

    def _trim_offlocal(self, t, local_start, local_length, total_length):
        gt = (t * total_length - local_start) / local_length
        return max(0, min(1, gt))

    def _build_trim_path_shape(self, shape, start, end, multidata, total_length):
        if not isinstance(shape, objects.Shape):
            return [shape]

        if multidata:
            bezier, local_start, local_length = multidata[shape]
            if end > 1:
                lstart = self._trim_offlocal(start, local_start, local_length, total_length)
                lend = self._trim_offlocal(end-1, local_start, local_length, total_length)
                out = []
                if lstart < 1:
                    out.append(objects.Path(bezier.segment(lstart, 1)))
                if lend > 0:
                    out.append(objects.Path(bezier.segment(0, lend)))
                return out

            lstart = self._trim_offlocal(start, local_start, local_length, total_length)
            lend = self._trim_offlocal(end, local_start, local_length, total_length)
            if lend <= 0 or lstart >= 1:
                return []
            if lstart <= 0 and lend >= 1:
                return [objects.Path(bezier)]
            seg = bezier.segment(lstart, lend)
            return [objects.Path(seg)]

        path = shape.to_bezier()
        bezier = path.shape.get_value(self.time)
        if end > 1:
            bez1 = bezier.segment(start, 1)
            bez2 = bezier.segment(0, end-1)
            return [objects.Path(bez1), objects.Path(bez2)]
        else:
            seg = bezier.segment(start, end)
            return [objects.Path(seg)]

    def _modifier_process_children(self, shapegroup, out_parent, callback, *args):
        children = []
        for shape in shapegroup.children:
            children.extend(self._modifier_process_child(shape, shapegroup, out_parent, callback, *args))
        shapegroup.children = children

    def _modifier_process_child(self, shape, shapegroup, out_parent, callback, *args):
        if isinstance(shape, restructure.RestructuredShapeGroup):
            self._modifier_process_children(shape, out_parent, callback, *args)
            return [shape]
        elif isinstance(shape, restructure.RestructuredPathMerger):
            paths = []
            for p in shape.paths:
                paths.extend(callback(p, *args))
            shape.paths = paths
            if paths:
                return [shape]
            return []
        else:
            return callback(shape, *args)

    def _custom_object_supported(self, shape):
        if has_font and isinstance(shape, font.FontShape):
            return True
        return False

    def build_text(self, shape, parent):
        text = ElementTree.SubElement(parent, "text")
        if "family" in shape.query:
            text.attrib["font-family"] = shape.query["family"]
        if "weight" in shape.query:
            text.attrib["font-weight"] = str(shape.query.weight_to_css())
        slant = int(shape.query.get("slant", 0))
        if slant > 0 and slant < 110:
            text.attrib["font-style"] = "italic"
        elif slant >= 110:
            text.attrib["font-style"] = "oblique"

        text.attrib["font-size"] = str(shape.size)

        text.attrib["white-space"] = "pre"

        pos = shape.style.position
        text.attrib["x"] = str(pos.x)
        text.attrib["y"] = str(pos.y)
        text.text = shape.text

        return text


def color_to_css(color):
    #if len(color) == 4:
        #return ("rgba(%s, %s, %s" % tuple(map(lambda c: int(round(c*255)), color[:3]))) + ", %s)" % color[3]
    if isinstance(color, Color) and color.mode != ColorMode.RGB:
        color = color.converted(ColorMode.RGB)
    return "rgb(%s, %s, %s)" % tuple(map(lambda c: int(round(c*255)), color[:3]))


def to_svg(animation, time, animated=False):
    if animated:
        data = glaxnimate_helpers.convert(animation, "svg")
        return ElementTree.ElementTree(ElementTree.fromstring(data.decode("utf8")))

    if glaxnimate_helpers.has_glaxnimate:
        data = glaxnimate_helpers.serialize(animation, "svg")
        return ElementTree.ElementTree(ElementTree.fromstring(data.decode("utf8")))

    builder = SvgBuilder(time)
    builder.process(animation)
    return builder.dom
