Skip to content
SvgFormat.py 42 KiB
Newer Older
        # return commands
        # Fixme: do later ???
        return cls.to_geometry(commands)
    ##############################################

    @classmethod
    def to_xml(cls, value):

        path_data = ''
        for command in value:
            if path_data:
                path_data += ' '
            path_data += ' '.join(list(command[0]) + [str(x) for x in command[1]])
        return path_data

    ##############################################

    @classmethod
    def as_vector(cls, args):

        number_of_args = len(args)
        number_of_vectors = number_of_args // 2
        if number_of_args != number_of_vectors * 2:
            raise ValueError('len(args) is not // 2: {}'.format(number_of_args))
        return [Vector2D(args[i:i+2]) for i in range(0, number_of_args, 2)]

    ##############################################

    @classmethod
    def to_geometry(cls, commands):

        # cls._logger.info('Path:\n' + str(commands).replace('), ', '),\n '))
        path = None
        for command, args in commands:
            command_lower = command.lower()
            absolute = command_lower != command # Upper case means absolute
            # if is_lower:
            #     cls._logger.warning('incremental command')
            #     raise NotImplementedError
            if path is None:
                if command_lower != 'm':
                    raise NameError('Path must start with m')
                path = Path2D(args) # Vector2D()
            else:
                if command_lower == 'l':
                    path.line_to(args, absolute=absolute)
                elif command == 'h':
                    path.horizontal_to(*args, absolute=False)
                elif command == 'H':
                    path.absolute_horizontal_to(*args)
                elif command_lower == 'v':
                    path.vertical_to(*args, absolute=absolute)
                elif command == 'V':
                    path.absolute_vertical_to(*args)
                elif command_lower == 'c':
                    path.cubic_to(*cls.as_vector(args), absolute=absolute)
                elif command_lower == 's':
                    path.stringed_quadratic_to(*cls.as_vector(args), absolute=absolute)
                elif command_lower == 'q':
                    path.quadratic_to(*cls.as_vector(args), absolute=absolute)
                elif command_lower == 't':
                    path.stringed_cubic_to(*cls.as_vector(args), absolute=absolute)
                elif command_lower == 'a':
                    radius_x, radius_y, angle, large_arc, sweep, x, y = args
                    point = Vector2D(x, y)
                    path.arc_to(point, radius_x, radius_y, angle, bool(large_arc), bool(sweep), absolute=absolute)
                elif command_lower == 'z':
                    path.close()

        return path

####################################################################################################

class Path(PathMixin, SvgElementMixin, XmlObjectAdaptator):

    """Defines a path"""

    __tag__ = 'path'
    __attributes__ = (
        PathDataAttribute('path_data', 'd'), # a set of commands which define the path
        FloatAttribute('path_length', 'pathLength'),
        # If present, the path will be scaled so that the computed path length of the points equals
        # this value
    )

####################################################################################################

class Pattern(IdMixin, PositionMixin, SizeMixin, XmlObjectAdaptator):

    """Defines the coordinates you want the view to show and the size of the view. Then you add shapes
    into your pattern. The pattern repeats when an edge of the view box (viewing area) is hit

    """

    __tag__ = 'pattern'

    # id="the unique id used to reference this pattern." Required.
    # patternUnits="'userSpaceOnUse' or 'objectBoundingBox'. The second value makes units of x, y, width, height a fraction (or %) of the object bounding box which uses the pattern."
    # patternContentUnits="'userSpaceOnUse' or 'objectBoundingBox'"
    # patternTransform="allows the whole pattern to be transformed"
    # x="pattern's offset from the top-left corner (default 0)"
    # y="pattern's offset from the top-left corner. (default 0)"
    # width="the width of the pattern tile (default 100%)"
    # height="the height of the pattern tile (default 100%)"
    # viewBox="the points "seen" in this SVG drawing area. 4 values separated by white space or commas. (min x, min y, width, height)"
    # xlink:href="reference to another pattern whose attribute values are used as defaults and any children are inherited. Recursive"

####################################################################################################

class Polyline(PointsMixin, PathMixin, SvgElementMixin, XmlObjectAdaptator):

    """Defines a graphic that contains at least three sides"""

    __tag__ = 'polyline'

####################################################################################################

class Polygon(Polyline, XmlObjectAdaptator):

    """Defines any shape that consists of only straight lines"""

    __tag__ = 'polyline'

    # fill-rule="part of the FillStroke presentation attributes"

####################################################################################################

class RadialGradient(XmlObjectAdaptator):

    """Defines a radial gradient. Radial gradients are created by taking a circle and smoothly changing
    values between gradient stops from the focus point to the outside radius.

    """

    __tag__ = 'radialGradient'

    # gradientUnits="'userSpaceOnUse' or 'objectBoundingBox'. Use the view box or object to determine relative position of vector points. (Default 'objectBoundingBox')"
    # gradientTransform="the transformation to apply to the gradient"
    # cx="the center point of the gradient (number or % - 50% is default)"
    # cy="the center point of the gradient. (50% default)"
    # r="the radius of the gradient. (50% default)"
    # fx="the focus point of the gradient. (0% default)"
    # fy="The focus point of the gradient. (0% default)"
    # spreadMethod="'pad' or 'reflect' or 'repeat'"
    # xlink:href="Reference to another gradient whose attribute values are used as defaults and stops included. Recursive"

####################################################################################################

class Rect(PositionMixin, RadiusMixin, SizeMixin, PathMixin, SvgElementMixin, XmlObjectAdaptator):

    """Defines a rectangle"""

    __tag__ = 'rect'

    ##############################################

    @property
    def geometry(self):

        # Fixme: width is str
        width = float(self.width)
        height = float(self.height)

        # Fixme: which one ???
        radius_x = self.rx
        radius_y = self.ry
        if radius_y == 0:
            radius = None
        else:
            radius = radius_y

        point = Vector2D(self.x, self.y)
        path = Path2D(point)
        path.horizontal_to(width)
        path.vertical_to(height, radius=radius)
        path.horizontal_to(-width, radius=radius)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        path.close(radius=radius, close_radius=radius)
####################################################################################################

class Stop(XmlObjectAdaptator):

    """The stops for a gradient"""

    __tag__ = 'stop'

    # offset="the offset for this stop (0 to 1/0% to 100%)". Required.
    # stop-color="the color of this stop"
    # stop-opacity="the opacity of this stop (0 to 1)"

####################################################################################################

class Style(TextXmlObjectAdaptator):

    """Defines style"""

    __tag__ = 'style'

####################################################################################################

class Text(PositionMixin, DeltaMixin, FontMixin, ColorMixin, SvgElementMixin, TextXmlObjectAdaptator):

    """Defines a text"""

    __tag__ = 'text'

    # x="a list of x-axis positions. The nth x-axis position is given to the nth character in the text. If there are additional characters after the positions run out they are placed after the last character. 0 is default"
    # y="a list of y-axis positions. (see x). 0 is default"
    # dx="a list of lengths which moves the characters relative to the absolute position of the last glyph drawn. (see x)"
    # dy="a list of lengths which moves the characters relative to the absolute position of the last glyph drawn. (see x)"
    # rotate="a list of rotations. The nth rotation is performed on the nth character. Additional characters are NOT given the last rotation value"
    # textLength="a target length for the text that the SVG viewer will attempt to display the text between by adjusting the spacing and/or the glyphs. (default: The text's normal length)"
    # lengthAdjust="tells the viewer what to adjust to try to accomplish rendering the text if the length is specified. The two values are 'spacing' and 'spacingAndGlyphs'"

    __attributes__ = (
        # Fixme: common ???
        StringAttribute('_class', 'class', None),
        StringAttribute('style'),
    )

####################################################################################################

class TextRef(XmlObjectAdaptator):

    """References any <text> element in the SVG document and reuse it"""

    __tag__ = 'tref'

####################################################################################################

class TextSpan(Text, XmlObjectAdaptator):

    """Identical to the <text> element but can be nested inside text tags and inside itself"""

    __tag__ = 'textspan'

####################################################################################################

class Use(PositionMixin, SizeMixin, XmlObjectAdaptator):

    """Uses a URI to reference a <g>, <svg> or other graphical element with a unique id attribute and
    replicate it. The copy is only a reference to the original so only the original exists in the
    document. Any change to the original affects all copies.

    """

    __tag__ = 'use'

    # x="the x-axis top-left corner of the cloned element"
    # y="the y-axis top-left corner of the cloned element"
    # width="the width of the cloned element"
    # height="the height of the cloned element"
    # xlink:href="a URI reference to the cloned element"