#################################################################################################### # # Patro - A Python library to make patterns for fashion design # Copyright (C) 2017 Fabrice Salvaire # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # #################################################################################################### """This modules implements the `SVG `_ file format. References: * `SVG 1.1 (Second Edition) W3C Recommendation 16 August 2011 `_ """ #################################################################################################### __all__ = [ 'Svg', 'Anchor', 'AltGlyph', 'AltGlyphDef', 'AltGlyphItem', 'Animate', 'AnimateMotion', 'AnimateTransform', 'Circle', 'ClipPath', 'ColorProfile', 'Cursor', 'Defs', 'Desc', 'Ellipse', 'FeBlend', 'Group', 'Image', 'Line', 'LinearGradient', 'Marker', 'Mask', 'Path', 'Pattern', 'Polyline', 'Polygon', 'RadialGradient', 'Rect', 'Stop', 'Text', 'TextRef', 'TextSpan', 'Use', ] #################################################################################################### import logging from Patro.Common.Xml.Objectivity import ( # BoolAttribute, IntAttribute, FloatAttribute, FloatListAttribute, StringAttribute, XmlObjectAdaptator, TextXmlObjectAdaptator, ) # Fixme: should we mix SVG format and ... ??? from Patro.GeometryEngine.Path import Path2D from Patro.GeometryEngine.Transformation import AffineTransformation2D from Patro.GeometryEngine.Vector import Vector2D #################################################################################################### _module_logger = logging.getLogger(__name__) #################################################################################################### def split_space_list(value): return [x for x in value.split(' ') if x] #################################################################################################### # # Conditional Processing Attributes # requiredFeatures # requiredExtensions # systemLanguage # #################################################################################################### #################################################################################################### # # Core Attribute # id # xml:base # xml:lang # xml:space # #################################################################################################### class IdMixin: """Core attribute""" __attributes__ = ( StringAttribute('id'), ) #################################################################################################### # # Graphical Event Attributes # onactivate # onclick # onfocusin # onfocusout # onload # onmousedown # onmousemove # onmouseout # onmouseover # onmouseup # #################################################################################################### #################################################################################################### class PresentationAttributes: # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute # Fixme: type !!! alignment_baseline = None # check baseline_shift = None # check clip = None # check clip_path = None # check clip_rule = None # check color = None # check color_interpolation = None # check color_interpolation_filters = None # check color_profile = None # check color_rendering = None # check cursor = None # check direction = None # check display = None # check dominant_baseline = None # check enable_background = None # check fill = None fill_opacity = 1 fill_rule = 'nonzero' #! filter = None # check flood_color = None # check flood_opacity = None # check font_family = None # check font_size = None # check font_size_adjust = None # check font_stretch = None # check font_style = None # check font_variant = None # check font_weight = None # check glyph_orientation_horizontal = None # check glyph_orientation_vertical = None # check image_rendering = None # check kerning = None # check letter_spacing = None # check lighting_color = None # check marker_end = None # check marker_mid = None # check marker_start = None # check mask = None # check opacity = 1 overflow = None # check paint_order = None pointer_events = None # check shape_rendering = None # check stop_color = None # check stop_opacity = None # check stroke = None stroke_dasharray = None # check stroke_dashoffset = None # check stroke_linecap = 'butt' stroke_linejoin = 'miter' stroke_miterlimit = 4 stroke_opacity = 1 stroke_width = 1 # px style = None text_anchor = None # check text_decoration = None # check text_rendering = None # check transform = AffineTransformation2D.Identity() unicode_bidi = None # check vector_effect = None visibility = None # check word_spacing = None # check writing_mode = None # check #################################################################################################### class InheritAttribute(StringAttribute): ############################################## @classmethod def from_xml(cls, value): if value == 'inherit': return value else: return cls._from_xml(value) ############################################## @classmethod def _from_xml(cls, value): raise NotImplementedError #################################################################################################### class NumberAttribute(InheritAttribute): @classmethod def _from_xml(cls, value): return float(value) #################################################################################################### class PercentValue: ############################################## def __init__(self, value): self._value = float(value) / 100 ############################################## def __float__(self): return self._value #################################################################################################### class UnitValue: ############################################## def __init__(self, value, unit): self._value = float(value) self._unit = unit ############################################## def __float__(self): return self._value ############################################## def __str__(self): return self._unit #################################################################################################### class PercentLengthAttribute(InheritAttribute): ############################################## @classmethod def _from_xml(cls, value): # length ::= number ("em" | "ex" | "px" | "in" | "cm" | "mm" | "pt" | "pc" | "%")? if value.endswith('%'): return PercentValue(value[:-1]) elif value[-1].isalpha(): return UnitValue(value[:-2], value[-2]) else: return float(value) ############################################## @classmethod def _to_xml(cls, value): # Fixme: ok ??? if isinstance(value, PercentValue): return '{}%'.format(float(value)) elif isinstance(value, UnitValue): return '{}{}'.format(float(value), value.unit) else: return str(value) #################################################################################################### class ColorMixin: __attributes__ = ( StringAttribute('fill'), # none inherit red #ffbb00 StringAttribute('stroke'), ) #################################################################################################### class StrokeMixin: __attributes__ = ( StringAttribute('stroke_line_cap', 'stroke-linecap'), StringAttribute('stroke_line_join', 'stroke-linejoin'), NumberAttribute('stroke_miter_limit', 'stroke-miterlimit'), PercentLengthAttribute('stroke_width', 'stroke-width'), FloatListAttribute('stroke_dasharray', 'stroke-dasharray') # stroke-dasharray="20,10,5,5,5,10" ) LINE_CAP_STYLE = ( 'butt', 'round', 'square', 'inherit', ) LINE_JOIN_STYLE = ( 'miter', 'round', 'bevel', 'inherit', ) #################################################################################################### class PathMixin(ColorMixin, StrokeMixin): pass #################################################################################################### class FontMixin: __attributes__ = ( StringAttribute('font_size', 'font-size'), StringAttribute('font_family', 'font-family'), ) # font-face-format # font-face-name # font-face-src # font-face-uri # text-anchor # glyph Defines the graphics for a given glyph # glyphRef Defines a possible glyph to use # hkern #################################################################################################### #################################################################################################### class StyleMixin: __attributes__ = ( StringAttribute('style'), ) #################################################################################################### # # Shared attributes # #################################################################################################### class PositionMixin: __attributes__ = ( FloatAttribute('x'), FloatAttribute('y'), ) #################################################################################################### class CenterMixin: __attributes__ = ( FloatAttribute('cx'), # x-axis center of the circle FloatAttribute('cy'), ) #################################################################################################### class DeltaMixin: __attributes__ = ( FloatAttribute('dx'), FloatAttribute('dy'), ) #################################################################################################### class RadiusMixin: __attributes__ = ( FloatAttribute('rx'), # x-axis radius (to round the element) FloatAttribute('ry'), ) #################################################################################################### class PointsMixin: __attributes__ = ( StringAttribute('points'), # points="200,10 250,190 160,210" ) #################################################################################################### class SizeMixin: __attributes__ = ( StringAttribute('height'), StringAttribute('width'), ) #################################################################################################### class TransformAttribute(StringAttribute): TRANSFORM = ( 'matrix', 'rotate', 'scale', 'skewX', 'skewY', 'translate' ) ############################################## @classmethod def from_xml(cls, value): if isinstance(value, AffineTransformation2D): # Python value return value else: transforms = [] for transform in split_space_list(value): pos0 = value.find('(') pos1 = value.find(')') if pos0 == -1 or pos1 != len(value) -1: raise ValueError transform_type = value[:pos0] values = [float(x) for x in value[pos0+1:-1].split(',')] transforms.append((transform_type, values)) # Fixme: # return transforms return cls.to_python(transforms, concat=True) ############################################## @classmethod def to_xml(cls, value): # Fixme: wrong if value is AffineTransformation2D !!! # Fixme: to func return 'matrix({})'.format(' '.join([str(x) for x in value.to_list()])) ############################################## @classmethod def to_python(cls, transforms, concat=True): def complete(values, size): return values + [0]*(size - len(values)) global_transform = AffineTransformation2D.Identity() py_transforms = [] for name, values in transforms: transform = None if name == 'matrix': array = [values[i] for i in (0, 2, 4, 1, 3, 5)] + [0, 0, 1] transform = AffineTransformation2D(array) elif name == 'translate': vector = Vector2D(complete(values, 2)) transform = AffineTransformation2D.Translation(vector) elif name == 'scale': transform = AffineTransformation2D.Scale(*values) elif name == 'rotate': angle, *vector = complete(values, 3) vector = Vector2D(vector) transform = AffineTransformation2D.RotationAt(vector, angle) elif name == 'skewX': angle = values[0] raise NotImplementedError elif name == 'skewY': angle = values[0] raise NotImplementedError else: raise NotImplementedError if concat: global_transform = transform * global_transform else: py_transforms.append(transform) if concat: return global_transform else: return py_transforms #################################################################################################### class TransformMixin: __attributes__ = ( TransformAttribute('transform'), # matrix(1,0,0,-1,0,1.) ) #################################################################################################### #################################################################################################### # The available filter elements in SVG are: # # - filter for combining images # - filter for color transforms # # # # # # # # # # # - filter for drop shadows # # # # - filter for lighting # - filter for lighting # - filter for lighting #################################################################################################### class SvgElementMixin(IdMixin, StyleMixin, TransformMixin): pass #################################################################################################### # # Svg Root Element # #################################################################################################### class Svg(PositionMixin, SizeMixin, XmlObjectAdaptator): """Creates an SVG document fragment""" __tag__ = 'svg' # xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" __attributes__ = ( StringAttribute('version'), FloatListAttribute('view_box', 'viewBox'), # the points "seen" in this SVG drawing area. 4 values separated by white space or # commas. (min x, min y, width, height) StringAttribute('preserve_aspect_ratio', 'preserveAspectRatio'), # 'none' or any of the 9 combinations of 'xVALYVAL' where VAL is 'min', 'mid' or 'max'. # (default xMidYMid) StringAttribute('zoom_and_pan', 'zoomAndPan') # 'magnify' or 'disable'. Magnify option allows users to pan and zoom your file # (default magnify) ) # x="top left corner when embedded (default 0)" # y="top left corner when embedded (default 0)" # width="the width of the svg fragment (default 100%)" # height="the height of the svg fragment (default 100%)" #################################################################################################### # # SVG Elements by name # #################################################################################################### class Anchor(XmlObjectAdaptator): """Creates a link around SVG elements""" __tag__ = 'a' # xlink:show # xlink:actuate # xlink:href # target #################################################################################################### class AltGlyph(PositionMixin, DeltaMixin, XmlObjectAdaptator): """Provides control over the glyphs used to render particular character data""" __tag__ = 'altGlyph' # rotate # glyphRef # format # xlink:href #################################################################################################### class AltGlyphDef(IdMixin, XmlObjectAdaptator): """Defines a substitution set for glyphs""" __tag__= 'altGlyphDef' #################################################################################################### class AltGlyphItem(IdMixin, XmlObjectAdaptator): """Defines a candidate set of glyph substitutions""" __tag__ = 'altGlyphItem' #################################################################################################### class Animate(XmlObjectAdaptator): """Defines how an attribute of an element changes over time""" __tag__ = 'animate' # attributeName="the name of the target attribute" # by="a relative offset value" # from="the starting value" # to="the ending value" # dur="the duration" # repeatCount="the number of time the animation will take place" #################################################################################################### class AnimateMotion(XmlObjectAdaptator): """Causes a referenced element to move along a motion path""" __tag__ = 'animateMotion' # calcMode="the interpolation mode for the animation. Can be 'discrete', 'linear', 'paced', 'spline'" # path="the motion path" # keyPoints="how far along the motion path the object shall move at the moment in time" # rotate="applies a rotation transformation" # xlink:href="an URI reference to the element which defines the motion path" #################################################################################################### class AnimateTransform(XmlObjectAdaptator): """Animates a transformation attribute on a target element, thereby allowing animations to control translation, scaling, rotation and/or skewing """ __tag__ = 'animateTransform' # by="a relative offset value" # from="the starting value" # to="the ending value" # type="the type of transformation which is to have its values change over time. Can be 'translate', 'scale', 'rotate', 'skewX', 'skewY'" #################################################################################################### class Circle(CenterMixin, PathMixin, SvgElementMixin, XmlObjectAdaptator): """Defines a circle""" __tag__ = 'circle' __attributes__ = ( FloatAttribute('r'), # circle's radius. Required. ) #################################################################################################### class ClipPath(XmlObjectAdaptator): """Clipping is about hiding what normally would be drawn. The stencil which defines what is and what isn't drawn is called a clipping path """ __tag__ = 'clipPath' # clip-path="the referenced clipping path is intersected with the referencing clipping path" # clipPathUnits="'userSpaceOnUse' or 'objectBoundingBox'. The second value makes units of children a fraction of the object bounding box which uses the mask (default: 'userSpaceOnUse')" #################################################################################################### class ColorProfile(XmlObjectAdaptator): """Specifies a color profile description (when the document is styled using CSS)""" __tag__ = 'color-profile' # local="the unique ID for a locally stored color profile" # name="" # rendering-intent="auto|perceptual|relative-colorimetric|saturation|absolute-colorimetric" # xlink:href="the URI of an ICC profile resource" #################################################################################################### class Cursor(PositionMixin, XmlObjectAdaptator): """Defines a platform-independent custom cursor""" __tag__ = 'cursor' # x="the x-axis top-left corner of the cursor (default is 0)" # y="the y-axis top-left corner of the cursor (default is 0)" # xlink:href="the URI of the image to use as the cursor #################################################################################################### class Defs(XmlObjectAdaptator): """A container for referenced elements""" __tag__ = 'defs' #################################################################################################### class Desc(XmlObjectAdaptator): """A text-only description for container elements or graphic elements in SVG (user agents may display the text as a tooltip)""" __tag__ = 'desc' #################################################################################################### class Ellipse(CenterMixin, PathMixin, StyleMixin, XmlObjectAdaptator): """Defines an ellipse""" __tag__ = 'ellipse' __attributes__ = ( FloatAttribute('rx'), FloatAttribute('ry'), ) #################################################################################################### class FeBlend(XmlObjectAdaptator): """Composes two objects together according to a certain blending mode""" __tag__ = 'feBlend' # mode="the image blending modes: normal|multiply|screen|darken|lighten" # in="identifies input for the given filter primitive: SourceGraphic | SourceAlpha | BackgroundImage | BackgroundAlpha | FillPaint | StrokePaint | " # in2="the second input image to the blending operation" #################################################################################################### class Group(FontMixin, PathMixin, SvgElementMixin, XmlObjectAdaptator): """Used to group together elements""" __tag__ = 'g' __attributes__ = ( StringAttribute('clip_path', 'clip-path', None), StringAttribute('data_name', 'data-name'), #fill="the fill color for the group" #opacity="the opacity for the group" ) #################################################################################################### class Image(PositionMixin, SizeMixin, XmlObjectAdaptator): """Defines an image""" __tag__ = 'image' # x="the x-axis top-left corner of the image" # y="the y-axis top-left corner of the image" # width="the width of the image". Required. # height="the height of the image". Required. # xlink:href="the path to the image". Required. #################################################################################################### class Line(PathMixin, SvgElementMixin, XmlObjectAdaptator): """Defines a line""" __tag__ = 'line' __attributes__ = ( FloatAttribute('x1'), # x start point of the line FloatAttribute('y1'), FloatAttribute('x2'), # x end point of the line FloatAttribute('y2'), ) #################################################################################################### class LinearGradient(IdMixin, XmlObjectAdaptator): """Defines a linear gradient. Linear gradients fill the object by using a vector, and can be defined as horizontal, vertical or angular gradients. """ __tag__ = 'linearGradient' # id="the unique id used to reference this pattern. Required to reference it" # 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" # x1="the x start point of the gradient vector (number or % - 0% is default)" # y1="the y start point of the gradient vector. (0% default)" # x2="the x end point of the gradient vector. (100% default)" # y2="the y end point of the gradient vector. (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 Marker(XmlObjectAdaptator): """Markers can be placed on the vertices of lines, polylines, polygons and paths. These elements can use the marker attributes "marker-start", "marker-mid" and "marker-end"' which inherit by default or can be set to 'none' or the URI of a defined marker. You must first define the marker before you can reference it via its URI. Any kind of shape can be put inside marker. They are drawn on top of the element they are attached to markerUnits="'strokeWidth' or 'userSpaceOnUse'. If 'strokeWidth' is used then one unit equals one stroke width. Otherwise, the marker does not scale and uses the the same view units as the referencing element (default 'strokeWidth') """ __tag__ = 'marker' # refx="the position where the marker connects with the vertex (default 0)" # refy="the position where the marker connects with the vertex (default 0)" # orient="'auto' or an angle to always show the marker at. 'auto' will compute an angle that makes the x-axis a tangent of the vertex (default 0)" # markerWidth="the width of the marker (default 3)" # markerHeight="the height of the marker (default 3)" # viewBox="the points "seen" in this SVG drawing area. 4 values separated by white space or commas. (min x, min y, width, height)" #################################################################################################### class Mask(PositionMixin, SizeMixin, XmlObjectAdaptator): """Masking is a combination of opacity values and clipping. Like clipping you can use shapes, text or paths to define sections of the mask. The default state of a mask is fully transparent which is the opposite of clipping plane. The graphics in a mask sets how opaque portions of the mask are """ __tag__ = 'mask' # maskUnits="'userSpaceOnUse' or 'objectBoundingBox'. Set whether the clipping plane is relative the full view port or object (default: 'objectBoundingBox')" # maskContentUnits="Use the second with percentages to make mask graphic positions relative the object. 'userSpaceOnUse' or 'objectBoundingBox' (default: 'userSpaceOnUse')" # x="the clipping plane of the mask (default: -10%)" # y="the clipping plane of the mask (default: -10%)" # width="the clipping plane of the mask (default: 120%)" # height="the clipping plane of the mask (default: 120%)" #################################################################################################### class PathDataAttribute(StringAttribute): """Define a path data attribute. Path data can contain newline characters and thus can be broken up into multiple lines to improve readability. Because of line length limitations with certain related tools, it is recommended that SVG generators split long path data strings across multiple lines, with each line not exceeding 255 characters. Also note that newline characters are only allowed at certain places within path data. The syntax of path data is concise in order to allow for minimal file size and efficient downloads, since many SVG files will be dominated by their path data. Some of the ways that SVG attempts to minimize the size of path data are as follows: * All instructions are expressed as one character (e.g., a moveto is expressed as an M). * Superfluous white space and separators such as commas can be eliminated (e.g., "M 100 100 L 200 200" contains unnecessary spaces and could be expressed more compactly as "M100 100L200 200"). * The command letter can be eliminated on subsequent commands if the same command is used multiple times in a row (e.g., you can drop the second "L" in "M 100 200 L 200 100 L -100 -200" and use "M 100 200 L 200 100 -100 -200" instead). * Relative versions of all commands are available (uppercase means absolute coordinates, lowercase means relative coordinates). * Alternate forms of lineto are available to optimize the special cases of horizontal and vertical lines (absolute and relative). * Alternate forms of curve are available to optimize the special cases where some of the control points on the current segment can be determined automatically from the control points on the previous segment. The following commands are available for path data: * Move to M x y m dx dy * Line to L x y l dx dy * Horizontal Line to H x h dx * Vertical Line to V y v dy * Cubic Bézier Curve C x1 y1, x2 y2, x y c dx1 dy1, dx2 dy2, dx dy * Smooth Cubic Bézier Curve S x2 y2, x y s dx2 dy2, dx dy" * Quadratic Bézier Curve Q x1 y1, x y q dx1 dy1, dx dy * Smooth Quadratic Bézier Curve T x y t dx dy * Elliptical Arc A rx ry x-axis-rotation large-arc-flag sweep-flag x y a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy * Close Path Z """ NUMBER_OF_ARGS = { 'm':2, 'l':2, 'h':1, 'v':1, 'c':6, 's':4, 'q':4, 't':3, 'a':7, 'z':0, } COMMANDS = ''.join(NUMBER_OF_ARGS.keys()) _logger = _module_logger.getChild('PathDataAttribute') ############################################## @classmethod def from_xml(cls, svg_path): # cls._logger.info('SVG path:\n'+ svg_path) # Replace comma separator by space cleaned_svg_path = svg_path.replace(',', ' ') # Add space after letter data_path = '' for c in cleaned_svg_path: data_path += c if c.isalpha: data_path += ' ' # Convert float values parts = [] for part in split_space_list(cleaned_svg_path): if not(len(part) == 1 and part.isalpha()): part = float(part) parts.append(part) commands = [] command = None # last command number_of_args = None i = 0 while i < len(parts): part = parts[i] if isinstance(part, str): command = part command_lower = command.lower() if command_lower not in cls.COMMANDS: raise ValueError("Invalid path instruction: '{}' in\n{}".format(command, svg_path)) number_of_args = cls.NUMBER_OF_ARGS[command_lower] i += 1 # move to first arg # else repeated instruction next_i = i + number_of_args args = parts[i:next_i] commands.append((command, args)) i = next_i # for implicit line to if command == 'm': command = 'l' elif command == 'M': command = 'L' # 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) path.close(radius=radius, close_radius=radius) return path #################################################################################################### 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 element in the SVG document and reuse it""" __tag__ = 'tref' #################################################################################################### class TextSpan(Text, XmlObjectAdaptator): """Identical to the element but can be nested inside text tags and inside itself""" __tag__ = 'textspan' #################################################################################################### class Use(PositionMixin, SizeMixin, XmlObjectAdaptator): """Uses a URI to reference a , 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"