Commit b2a9e448 authored by Guillaume Seguin's avatar Guillaume Seguin

Merge branch 'master' of github.com:kliment/Printrun

parents 7f40d5de 338b2b5e
This diff is collapsed.
...@@ -35,8 +35,8 @@ if __name__ == '__main__': ...@@ -35,8 +35,8 @@ if __name__ == '__main__':
for o, a in opts: for o, a in opts:
if o in ('-h', '--help'): if o in ('-h', '--help'):
# FIXME: Fix help # FIXME: Fix help
print "Opts are: --help, -b --baud = baudrate, -v --verbose, \ print ("Opts are: --help, -b --baud = baudrate, -v --verbose, "
-s --statusreport" "-s --statusreport")
sys.exit(1) sys.exit(1)
if o in ('-b', '--baud'): if o in ('-b', '--baud'):
baud = int(a) baud = int(a)
......
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
CairoSVG - A simple SVG converter for Cairo.
"""
import os
import sys
import optparse
from . import surface
VERSION = '0.4.4'
SURFACES = {
'SVG': surface.SVGSurface, # Tell us if you actually use this one!
'PNG': surface.PNGSurface,
'PDF': surface.PDFSurface,
'PS': surface.PSSurface}
# Generate the svg2* functions from SURFACES
for _output_format, _surface_type in SURFACES.items():
_function = (
# Two lambdas needed for the closure
lambda surface_type: lambda *args, **kwargs: # pylint: disable=W0108
surface_type.convert(*args, **kwargs))(_surface_type)
_name = 'svg2%s' % _output_format.lower()
_function.__name__ = _name
_function.__doc__ = surface.Surface.convert.__doc__.replace(
'the format for this class', _output_format)
setattr(sys.modules[__name__], _name, _function)
def main():
"""Entry-point of the executable."""
# Get command-line options
option_parser = optparse.OptionParser(
usage = "usage: %prog filename [options]", version = VERSION)
option_parser.add_option(
"-f", "--format", help = "output format")
option_parser.add_option(
"-d", "--dpi", help = "svg resolution", default = 96)
option_parser.add_option(
"-o", "--output",
default = "", help = "output filename")
options, args = option_parser.parse_args()
# Print help if no argument is given
if not args:
option_parser.print_help()
sys.exit()
kwargs = {'dpi': float(options.dpi)}
if not options.output or options.output == '-':
# Python 2/3 hack
bytes_stdout = getattr(sys.stdout, "buffer", sys.stdout)
kwargs['write_to'] = bytes_stdout
else:
kwargs['write_to'] = options.output
url = args[0]
if url == "-":
# Python 2/3 hack
bytes_stdin = getattr(sys.stdin, "buffer", sys.stdin)
kwargs['file_obj'] = bytes_stdin
else:
kwargs['url'] = url
output_format = (
options.format or
os.path.splitext(options.output)[1].lstrip(".") or
"pdf")
SURFACES[output_format.upper()].convert(**kwargs)
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Optionally handle CSS stylesheets.
"""
from .parser import HAS_LXML
# Detect optional depedencies
# pylint: disable=W0611
try:
import tinycss
import cssselect
CSS_CAPABLE = HAS_LXML
except ImportError:
CSS_CAPABLE = False
# pylint: enable=W0611
# Python 2/3 compat
iteritems = getattr(dict, "iteritems", dict.items) # pylint: disable=C0103
def find_stylesheets(tree):
"""Find the stylesheets included in ``tree``."""
# TODO: support contentStyleType on <svg>
default_type = "text/css"
for element in tree.iter():
# http://www.w3.org/TR/SVG/styling.html#StyleElement
if (element.tag == "style" and
element.get("type", default_type) == "text/css"):
# TODO: pass href for relative URLs
# TODO: support media types
# TODO: what if <style> has children elements?
yield tinycss.make_parser().parse_stylesheet(element.text)
# TODO: support <?xml-stylesheet ... ?>
def find_style_rules(tree):
"""Find the style rules in ``tree``."""
for stylesheet in find_stylesheets(tree):
# TODO: warn for each stylesheet.errors
for rule in stylesheet.rules:
# TODO: support @import and @media
if not rule.at_keyword:
yield rule
def get_declarations(rule):
"""Get the declarations in ``rule``."""
for declaration in rule.declarations:
if declaration.name.startswith("-"):
# Ignore properties prefixed by "-"
continue
# TODO: filter out invalid values
yield (
declaration.name,
declaration.value.as_css(),
bool(declaration.priority))
def match_selector(rule, tree):
"""Yield the ``(element, specificity)`` in ``tree`` matching ``rule``."""
selector_list = cssselect.parse(rule.selector.as_css())
translator = cssselect.GenericTranslator()
for selector in selector_list:
if not selector.pseudo_element:
specificity = selector.specificity()
for element in tree.xpath(translator.selector_to_xpath(selector)):
yield element, specificity
def apply_stylesheets(tree):
"""Apply the stylesheet in ``tree`` to ``tree``."""
if not CSS_CAPABLE:
# TODO: warn?
return
style_by_element = {}
for rule in find_style_rules(tree):
declarations = list(get_declarations(rule))
for element, specificity in match_selector(rule, tree):
style = style_by_element.setdefault(element, {})
for name, value, important in declarations:
weight = important, specificity
if name in style:
_old_value, old_weight = style[name]
if old_weight > weight:
continue
style[name] = value, weight
for element, style in iteritems(style_by_element):
values = ["%s: %s" % (name, value)
for name, (value, weight) in iteritems(style)]
values.append(element.get("style", ""))
element.set("style", ";".join(values))
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
SVG Parser.
"""
# Fallbacks for Python 2/3 and lxml/ElementTree
# pylint: disable=E0611,F0401,W0611
try:
import lxml.etree as ElementTree
from lxml.etree import XMLSyntaxError as ParseError
HAS_LXML = True
except ImportError:
from xml.etree import ElementTree
from xml.parsers import expat
# ElementTree's API changed between 2.6 and 2.7
# pylint: disable=C0103
ParseError = getattr(ElementTree, 'ParseError', expat.ExpatError)
# pylint: enable=C0103
HAS_LXML = False
try:
from urllib import urlopen
import urlparse
except ImportError:
from urllib.request import urlopen
from urllib import parse as urlparse # Python 3
# pylint: enable=E0611,F0401,W0611
import gzip
import os.path
from .css import apply_stylesheets
# Python 2/3 compat
# pylint: disable=C0103,W0622
try:
basestring
except NameError:
basestring = str
# pylint: enable=C0103,W0622
def remove_svg_namespace(tree):
"""Remove the SVG namespace from ``tree`` tags.
``lxml.cssselect`` does not support empty/default namespaces, so remove any
SVG namespace.
"""
prefix = "{http://www.w3.org/2000/svg}"
prefix_len = len(prefix)
iterator = (
tree.iter() if hasattr(tree, 'iter')
else tree.getiterator())
for element in iterator:
tag = element.tag
if hasattr(tag, "startswith") and tag.startswith(prefix):
element.tag = tag[prefix_len:]
class Node(dict):
"""SVG node with dict-like properties and children."""
def __init__(self, node, parent = None):
"""Create the Node from ElementTree ``node``, with ``parent`` Node."""
super(Node, self).__init__()
self.children = ()
self.root = False
self.tag = node.tag
self.text = node.text
# Inherits from parent properties
# TODO: drop other attributes that should not be inherited
if parent is not None:
items = parent.copy()
not_inherited = (
"transform", "opacity", "style", "viewBox", "stop-color",
"stop-opacity")
if self.tag in ("tspan", "pattern"):
not_inherited += ("x", "y")
for attribute in not_inherited:
if attribute in items:
del items[attribute]
self.update(items)
self.url = parent.url
self.xml_tree = parent.xml_tree
self.parent = parent
self.update(dict(node.attrib.items()))
# Handle the CSS
style = self.pop("style", "")
for declaration in style.split(";"):
if ":" in declaration:
name, value = declaration.split(":", 1)
self[name.strip()] = value.strip()
# Replace currentColor by a real color value
color_attributes = (
"fill", "stroke", "stop-color", "flood-color",
"lighting-color")
for attribute in color_attributes:
if self.get(attribute) == "currentColor":
self[attribute] = self.get("color", "black")
# Replace inherit by the parent value
for attribute, value in dict(self).items():
if value == "inherit":
if parent is not None and attribute in parent:
self[attribute] = parent.get(attribute)
else:
del self[attribute]
# Manage text by creating children
if self.tag == "text" or self.tag == "textPath":
self.children = self.text_children(node)
if not self.children:
self.children = tuple(
Node(child, self) for child in node
if isinstance(child.tag, basestring))
def text_children(self, node):
"""Create children and return them."""
children = []
for child in node:
children.append(Node(child, parent = self))
if child.tail:
anonymous = ElementTree.Element('tspan')
anonymous.text = child.tail
children.append(Node(anonymous, parent = self))
return list(children)
class Tree(Node):
"""SVG tree."""
def __init__(self, **kwargs):
"""Create the Tree from SVG ``text``."""
# Make the parameters keyword-only:
bytestring = kwargs.pop('bytestring', None)
file_obj = kwargs.pop('file_obj', None)
url = kwargs.pop('url', None)
parent = kwargs.pop('parent', None)
if bytestring is not None:
tree = ElementTree.fromstring(bytestring)
self.url = url
elif file_obj is not None:
tree = ElementTree.parse(file_obj).getroot()
if url:
self.url = url
else:
self.url = getattr(file_obj, 'name', None)
elif url is not None:
if "#" in url:
url, element_id = url.split("#", 1)
else:
element_id = None
if parent and parent.url:
if url:
url = urlparse.urljoin(parent.url, url)
elif element_id:
url = parent.url
self.url = url
if url:
if urlparse.urlparse(url).scheme:
input_ = urlopen(url)
else:
input_ = url # filename
if os.path.splitext(url)[1].lower() == "svgz":
input_ = gzip.open(url)
tree = ElementTree.parse(input_).getroot()
else:
tree = parent.xml_tree
if element_id:
iterator = (
tree.iter() if hasattr(tree, 'iter')
else tree.getiterator())
for element in iterator:
if element.get("id") == element_id:
tree = element
break
else:
raise TypeError(
'No tag with id="%s" found.' % element_id)
else:
raise TypeError(
'No input. Use one of bytestring, file_obj or url.')
remove_svg_namespace(tree)
apply_stylesheets(tree)
self.xml_tree = tree
super(Tree, self).__init__(tree, parent)
self.root = True
This diff is collapsed.
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
SVG colors.
"""
COLORS = {
"aliceblue": "rgb(240, 248, 255)",
"antiquewhite": "rgb(250, 235, 215)",
"aqua": "rgb(0, 255, 255)",
"aquamarine": "rgb(127, 255, 212)",
"azure": "rgb(240, 255, 255)",
"beige": "rgb(245, 245, 220)",
"bisque": "rgb(255, 228, 196)",
"black": "rgb(0, 0, 0)",
"blanchedalmond": "rgb(255, 235, 205)",
"blue": "rgb(0, 0, 255)",
"blueviolet": "rgb(138, 43, 226)",
"brown": "rgb(165, 42, 42)",
"burlywood": "rgb(222, 184, 135)",
"cadetblue": "rgb(95, 158, 160)",
"chartreuse": "rgb(127, 255, 0)",
"chocolate": "rgb(210, 105, 30)",
"coral": "rgb(255, 127, 80)",
"cornflowerblue": "rgb(100, 149, 237)",
"cornsilk": "rgb(255, 248, 220)",
"crimson": "rgb(220, 20, 60)",
"cyan": "rgb(0, 255, 255)",
"darkblue": "rgb(0, 0, 139)",
"darkcyan": "rgb(0, 139, 139)",
"darkgoldenrod": "rgb(184, 134, 11)",
"darkgray": "rgb(169, 169, 169)",
"darkgreen": "rgb(0, 100, 0)",
"darkgrey": "rgb(169, 169, 169)",
"darkkhaki": "rgb(189, 183, 107)",
"darkmagenta": "rgb(139, 0, 139)",
"darkolivegreen": "rgb(85, 107, 47)",
"darkorange": "rgb(255, 140, 0)",
"darkorchid": "rgb(153, 50, 204)",
"darkred": "rgb(139, 0, 0)",
"darksalmon": "rgb(233, 150, 122)",
"darkseagreen": "rgb(143, 188, 143)",
"darkslateblue": "rgb(72, 61, 139)",
"darkslategray": "rgb(47, 79, 79)",
"darkslategrey": "rgb(47, 79, 79)",
"darkturquoise": "rgb(0, 206, 209)",
"darkviolet": "rgb(148, 0, 211)",
"deeppink": "rgb(255, 20, 147)",
"deepskyblue": "rgb(0, 191, 255)",
"dimgray": "rgb(105, 105, 105)",
"dimgrey": "rgb(105, 105, 105)",
"dodgerblue": "rgb(30, 144, 255)",
"firebrick": "rgb(178, 34, 34)",
"floralwhite": "rgb(255, 250, 240)",
"forestgreen": "rgb(34, 139, 34)",
"fuchsia": "rgb(255, 0, 255)",
"gainsboro": "rgb(220, 220, 220)",
"ghostwhite": "rgb(248, 248, 255)",
"gold": "rgb(255, 215, 0)",
"goldenrod": "rgb(218, 165, 32)",
"gray": "rgb(128, 128, 128)",
"grey": "rgb(128, 128, 128)",
"green": "rgb(0, 128, 0)",
"greenyellow": "rgb(173, 255, 47)",
"honeydew": "rgb(240, 255, 240)",
"hotpink": "rgb(255, 105, 180)",
"indianred": "rgb(205, 92, 92)",
"indigo": "rgb(75, 0, 130)",
"ivory": "rgb(255, 255, 240)",
"khaki": "rgb(240, 230, 140)",
"lavender": "rgb(230, 230, 250)",
"lavenderblush": "rgb(255, 240, 245)",
"lawngreen": "rgb(124, 252, 0)",
"lemonchiffon": "rgb(255, 250, 205)",
"lightblue": "rgb(173, 216, 230)",
"lightcoral": "rgb(240, 128, 128)",
"lightcyan": "rgb(224, 255, 255)",
"lightgoldenrodyellow": "rgb(250, 250, 210)",
"lightgray": "rgb(211, 211, 211)",
"lightgreen": "rgb(144, 238, 144)",
"lightgrey": "rgb(211, 211, 211)",
"lightpink": "rgb(255, 182, 193)",
"lightsalmon": "rgb(255, 160, 122)",
"lightseagreen": "rgb(32, 178, 170)",
"lightskyblue": "rgb(135, 206, 250)",
"lightslategray": "rgb(119, 136, 153)",
"lightslategrey": "rgb(119, 136, 153)",
"lightsteelblue": "rgb(176, 196, 222)",
"lightyellow": "rgb(255, 255, 224)",
"lime": "rgb(0, 255, 0)",
"limegreen": "rgb(50, 205, 50)",
"linen": "rgb(250, 240, 230)",
"magenta": "rgb(255, 0, 255)",
"maroon": "rgb(128, 0, 0)",
"mediumaquamarine": "rgb(102, 205, 170)",
"mediumblue": "rgb(0, 0, 205)",
"mediumorchid": "rgb(186, 85, 211)",
"mediumpurple": "rgb(147, 112, 219)",
"mediumseagreen": "rgb(60, 179, 113)",
"mediumslateblue": "rgb(123, 104, 238)",
"mediumspringgreen": "rgb(0, 250, 154)",
"mediumturquoise": "rgb(72, 209, 204)",
"mediumvioletred": "rgb(199, 21, 133)",
"midnightblue": "rgb(25, 25, 112)",
"mintcream": "rgb(245, 255, 250)",
"mistyrose": "rgb(255, 228, 225)",
"moccasin": "rgb(255, 228, 181)",
"navajowhite": "rgb(255, 222, 173)",
"navy": "rgb(0, 0, 128)",
"oldlace": "rgb(253, 245, 230)",
"olive": "rgb(128, 128, 0)",
"olivedrab": "rgb(107, 142, 35)",
"orange": "rgb(255, 165, 0)",
"orangered": "rgb(255, 69, 0)",
"orchid": "rgb(218, 112, 214)",
"palegoldenrod": "rgb(238, 232, 170)",
"palegreen": "rgb(152, 251, 152)",
"paleturquoise": "rgb(175, 238, 238)",
"palevioletred": "rgb(219, 112, 147)",
"papayawhip": "rgb(255, 239, 213)",
"peachpuff": "rgb(255, 218, 185)",
"peru": "rgb(205, 133, 63)",
"pink": "rgb(255, 192, 203)",
"plum": "rgb(221, 160, 221)",
"powderblue": "rgb(176, 224, 230)",
"purple": "rgb(128, 0, 128)",
"red": "rgb(255, 0, 0)",
"rosybrown": "rgb(188, 143, 143)",
"royalblue": "rgb(65, 105, 225)",
"saddlebrown": "rgb(139, 69, 19)",
"salmon": "rgb(250, 128, 114)",
"sandybrown": "rgb(244, 164, 96)",
"seagreen": "rgb(46, 139, 87)",
"seashell": "rgb(255, 245, 238)",
"sienna": "rgb(160, 82, 45)",
"silver": "rgb(192, 192, 192)",
"skyblue": "rgb(135, 206, 235)",
"slateblue": "rgb(106, 90, 205)",
"slategray": "rgb(112, 128, 144)",
"slategrey": "rgb(112, 128, 144)",
"snow": "rgb(255, 250, 250)",
"springgreen": "rgb(0, 255, 127)",
"steelblue": "rgb(70, 130, 180)",
"tan": "rgb(210, 180, 140)",
"teal": "rgb(0, 128, 128)",
"thistle": "rgb(216, 191, 216)",
"tomato": "rgb(255, 99, 71)",
"turquoise": "rgb(64, 224, 208)",
"violet": "rgb(238, 130, 238)",
"wheat": "rgb(245, 222, 179)",
"white": "rgb(255, 255, 255)",
"whitesmoke": "rgb(245, 245, 245)",
"yellow": "rgb(255, 255, 0)",
"yellowgreen": "rgb(154, 205, 50)",
"activeborder": "#0000ff",
"activecaption": "#0000ff",
"appworkspace": "#ffffff",
"background": "#ffffff",
"buttonface": "#000000",
"buttonhighlight": "#cccccc",
"buttonshadow": "#333333",
"buttontext": "#000000",
"captiontext": "#000000",
"graytext": "#333333",
"highlight": "#0000ff",
"highlighttext": "#cccccc",
"inactiveborder": "#333333",
"inactivecaption": "#cccccc",
"inactivecaptiontext": "#333333",
"infobackground": "#cccccc",
"infotext": "#000000",
"menu": "#cccccc",
"menutext": "#333333",
"scrollbar": "#cccccc",
"threeddarkshadow": "#333333",
"threedface": "#cccccc",
"threedhighlight": "#ffffff",
"threedlightshadow": "#333333",
"threedshadow": "#333333",
"window": "#cccccc",
"windowframe": "#cccccc",
"windowtext": "#000000"}
def color(string = None, opacity = 1):
"""Replace ``string`` representing a color by a RGBA tuple."""
if not string or string in ("none", "transparent"):
return (0, 0, 0, 0)
string = string.strip().lower()
if string in COLORS:
string = COLORS[string]
if string.startswith("rgba"):
r, g, b, a = tuple(
float(i.strip(" %")) * 2.55 if "%" in i else float(i)
for i in string.strip(" rgba()").split(","))
return r / 255, g / 255, b / 255, a * opacity
elif string.startswith("rgb"):
r, g, b = tuple(
float(i.strip(" %")) / 100 if "%" in i else float(i) / 255
for i in string.strip(" rgb()").split(","))
return r, g, b, opacity
if len(string) in (4, 5):
string = "#" + "".join(2 * char for char in string[1:])
if len(string) == 9:
opacity *= int(string[7:9], 16) / 255
try:
plain_color = tuple(
int(value, 16) / 255. for value in (
string[1:3], string[3:5], string[5:7]))
except ValueError:
# Unknown color, return black
return (0, 0, 0, 1)
else:
return plain_color + (opacity,)
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Externally defined elements managers.
This module handles gradients and patterns.
"""
import cairo
from math import radians
from copy import deepcopy
from .colors import color
from .helpers import node_format, preserve_ratio, urls, transform
from .units import size
from ..parser import Tree
def parse_def(surface, node):
"""Parse the SVG definitions."""
for def_type in ("marker", "gradient", "pattern", "path"):
if def_type in node.tag.lower():
def_list = getattr(surface, def_type + "s")
name = node["id"]
href = node.get("{http://www.w3.org/1999/xlink}href")
if href and href[0] == "#" and href[1:] in def_list:
new_node = deepcopy(def_list[href[1:]])
new_node.update(node)
node = new_node
def_list[name] = node
def gradient_or_pattern(surface, node, name):
"""Gradient or pattern color."""
if name in surface.gradients:
return draw_gradient(surface, node, name)
elif name in surface.patterns:
return draw_pattern(surface, name)
def marker(surface, node):
"""Store a marker definition."""
parse_def(surface, node)
def linear_gradient(surface, node):
"""Store a linear gradient definition."""
parse_def(surface, node)
def radial_gradient(surface, node):
"""Store a radial gradient definition."""
parse_def(surface, node)
def pattern(surface, node):
"""Store a pattern definition."""
parse_def(surface, node)
def draw_gradient(surface, node, name):
"""Gradients colors."""
gradient_node = surface.gradients[name]
transform(surface, gradient_node.get("gradientTransform"))
if gradient_node.get("gradientUnits") == "userSpaceOnUse":
width_ref, height_ref = "x", "y"
diagonal_ref = "xy"
else:
x = float(size(surface, node.get("x"), "x"))
y = float(size(surface, node.get("y"), "y"))
width = float(size(surface, node.get("width"), "x"))
height = float(size(surface, node.get("height"), "y"))
width_ref = height_ref = diagonal_ref = 1
if gradient_node.tag == "linearGradient":
x1 = float(size(surface, gradient_node.get("x1", "0%"), width_ref))
x2 = float(size(surface, gradient_node.get("x2", "100%"), width_ref))
y1 = float(size(surface, gradient_node.get("y1", "0%"), height_ref))
y2 = float(size(surface, gradient_node.get("y2", "0%"), height_ref))
gradient_pattern = cairo.LinearGradient(x1, y1, x2, y2)
elif gradient_node.tag == "radialGradient":
r = float(size(surface, gradient_node.get("r", "50%"), diagonal_ref))
cx = float(size(surface, gradient_node.get("cx", "50%"), width_ref))
cy = float(size(surface, gradient_node.get("cy", "50%"), height_ref))
fx = float(size(surface, gradient_node.get("fx", str(cx)), width_ref))
fy = float(size(surface, gradient_node.get("fy", str(cy)), height_ref))
gradient_pattern = cairo.RadialGradient(fx, fy, 0, cx, cy, r)
if gradient_node.get("gradientUnits") != "userSpaceOnUse":
gradient_pattern.set_matrix(cairo.Matrix(
1 / width, 0, 0, 1 / height, -x / width, -y / height))
gradient_pattern.set_extend(getattr(
cairo, "EXTEND_%s" % node.get("spreadMethod", "pad").upper()))
offset = 0
for child in gradient_node.children:
offset = max(offset, size(surface, child.get("offset"), 1))
stop_color = color(
child.get("stop-color", "black"),
float(child.get("stop-opacity", 1)))
gradient_pattern.add_color_stop_rgba(offset, *stop_color)
gradient_pattern.set_extend(getattr(
cairo, "EXTEND_%s" % gradient_node.get("spreadMethod", "pad").upper()))
surface.context.set_source(gradient_pattern)
def draw_pattern(surface, name):
"""Draw a pattern image."""
pattern_node = surface.patterns[name]
pattern_node.tag = "g"
transform(surface, "translate(%s %s)" % (
pattern_node.get("x"), pattern_node.get("y")))
transform(surface, pattern_node.get("patternTransform"))
from . import SVGSurface # circular import
pattern_surface = SVGSurface(pattern_node, None, surface.dpi)
pattern_pattern = cairo.SurfacePattern(pattern_surface.cairo)
pattern_pattern.set_extend(cairo.EXTEND_REPEAT)
surface.context.set_source(pattern_pattern)
def draw_marker(surface, node, position = "mid"):
"""Draw a marker."""
# TODO: manage markers for other tags than path
if position == "start":
node.markers = {
"start": list(urls(node.get("marker-start", ""))),
"mid": list(urls(node.get("marker-mid", ""))),
"end": list(urls(node.get("marker-end", "")))}
all_markers = list(urls(node.get("marker", "")))
for markers_list in node.markers.values():
markers_list.extend(all_markers)
pending_marker = (
surface.context.get_current_point(), node.markers[position])
if position == "start":
node.pending_markers.append(pending_marker)
return
elif position == "end":
node.pending_markers.append(pending_marker)
while node.pending_markers:
next_point, markers = node.pending_markers.pop(0)
angle1 = node.tangents.pop(0)
angle2 = node.tangents.pop(0)
if angle1 is None:
angle1 = angle2
for active_marker in markers:
if not active_marker.startswith("#"):
continue
active_marker = active_marker[1:]
if active_marker in surface.markers:
marker_node = surface.markers[active_marker]
angle = marker_node.get("orient", "0")
if angle == "auto":
angle = float(angle1 + angle2) / 2
else:
angle = radians(float(angle))
temp_path = surface.context.copy_path()
current_x, current_y = next_point
if node.get("markerUnits") == "userSpaceOnUse":
base_scale = 1
else:
base_scale = size(
surface, surface.parent_node.get("stroke-width"))
# Returns 4 values
scale_x, scale_y, translate_x, translate_y = \
preserve_ratio(surface, marker_node)
viewbox = node_format(surface, marker_node)[-1]
viewbox_width = viewbox[2] - viewbox[0]
viewbox_height = viewbox[3] - viewbox[1]
surface.context.new_path()
for child in marker_node.children:
surface.context.save()
surface.context.translate(current_x, current_y)
surface.context.rotate(angle)
surface.context.scale(
base_scale / viewbox_width * float(scale_x),
base_scale / viewbox_height * float(scale_y))
surface.context.translate(translate_x, translate_y)
surface.draw(child)
surface.context.restore()
surface.context.append_path(temp_path)
if position == "mid":
node.pending_markers.append(pending_marker)
def use(surface, node):
"""Draw the content of another SVG file."""
surface.context.save()
surface.context.translate(
size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y"))
if "x" in node:
del node["x"]
if "y" in node:
del node["y"]
if "viewBox" in node:
del node["viewBox"]
href = node.get("{http://www.w3.org/1999/xlink}href")
url = list(urls(href))[0]
tree = Tree(url = url, parent = node)
surface.set_context_size(*node_format(surface, tree))
surface.draw(tree)
surface.context.restore()
# Restore twice, because draw does not restore at the end of svg tags
surface.context.restore()
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Surface helpers.
"""
import cairo
from math import cos, sin, tan, atan2, radians
from .units import size
# Python 2/3 management
# pylint: disable=C0103
try:
Error = cairo.Error
except AttributeError:
Error = SystemError
# pylint: enable=C0103
class PointError(Exception):
"""Exception raised when parsing a point fails."""
def distance(x1, y1, x2, y2):
"""Get the distance between two points."""
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
def filter_fill_or_stroke(value):
"""Remove unnecessary characters from fill or stroke value."""
if not value:
return
content = list(urls(value))[0]
if "url" in value:
if not content.startswith("#"):
return
content = content[1:]
return content
def node_format(surface, node):
"""Return ``(width, height, viewbox)`` of ``node``."""
width = size(surface, node.get("width"), "x")
height = size(surface, node.get("height"), "y")
viewbox = node.get("viewBox")
if viewbox:
viewbox = tuple(float(position) for position in viewbox.split())
width = width or viewbox[2]
height = height or viewbox[3]
return width, height, viewbox
def normalize(string = None):
"""Normalize a string corresponding to an array of various values."""
string = string.replace("-", " -")
string = string.replace(",", " ")
while " " in string:
string = string.replace(" ", " ")
string = string.replace("e -", "e-")
values = string.split(" ")
string = ""
for value in values:
if value.count(".") > 1:
numbers = value.split(".")
string += "%s.%s " % (numbers.pop(0), numbers.pop(0))
string += ".%s " % " .".join(numbers)
else:
string += value + " "
return string.strip()
def point(surface, string = None):
"""Return ``(x, y, trailing_text)`` from ``string``."""
if not string:
return (0, 0, "")
try:
x, y, string = (string.strip() + " ").split(" ", 2)
except ValueError:
raise PointError("The point cannot be found in string %s" % string)
return size(surface, x, "x"), size(surface, y, "y"), string
def point_angle(cx, cy, px, py):
"""Return angle between x axis and point knowing given center."""
return atan2(py - cy, px - cx)
def preserve_ratio(surface, node):
"""Manage the ratio preservation."""
if node.tag == "marker":
scale_x = size(surface, node.get("markerWidth", "3"), "x")
scale_y = size(surface, node.get("markerHeight", "3"), "y")
translate_x = -size(surface, node.get("refX"))
translate_y = -size(surface, node.get("refY"))
elif node.tag in ("svg", "image"):
width, height, _ = node_format(surface, node)
scale_x = width / node.image_width
scale_y = height / node.image_height
align = node.get("preserveAspectRatio", "xMidYMid").split(" ")[0]
if align == "none":
return scale_x, scale_y, 0, 0
else:
mos_properties = node.get("preserveAspectRatio", "").split()
meet_or_slice = (
mos_properties[1] if len(mos_properties) > 1 else None)
if meet_or_slice == "slice":
scale_value = max(scale_x, scale_y)
else:
scale_value = min(scale_x, scale_y)
scale_x = scale_y = scale_value
x_position = align[1:4].lower()
y_position = align[5:].lower()
if x_position == "min":
translate_x = 0
if y_position == "min":
translate_y = 0
if x_position == "mid":
translate_x = (width / scale_x - node.image_width) / 2.
if y_position == "mid":
translate_y = (height / scale_y - node.image_height) / 2.
if x_position == "max":
translate_x = width / scale_x - node.image_width
if y_position == "max":
translate_y = height / scale_y - node.image_height
return scale_x, scale_y, translate_x, translate_y
def quadratic_points(x1, y1, x2, y2, x3, y3):
"""Return the quadratic points to create quadratic curves."""
xq1 = x2 * 2 / 3 + x1 / 3
yq1 = y2 * 2 / 3 + y1 / 3
xq2 = x2 * 2 / 3 + x3 / 3
yq2 = y2 * 2 / 3 + y3 / 3
return xq1, yq1, xq2, yq2, x3, y3
def rotate(x, y, angle):
"""Rotate a point of an angle around the origin point."""
return x * cos(angle) - y * sin(angle), y * cos(angle) + x * sin(angle)
def transform(surface, string):
"""Update ``surface`` matrix according to transformation ``string``."""
if not string:
return
transformations = string.split(")")
matrix = cairo.Matrix()
for transformation in transformations:
for ttype in (
"scale", "translate", "matrix", "rotate", "skewX",
"skewY"):
if ttype in transformation:
transformation = transformation.replace(ttype, "")
transformation = transformation.replace("(", "")
transformation = normalize(transformation).strip() + " "
values = []
while transformation:
value, transformation = \
transformation.split(" ", 1)
# TODO: manage the x/y sizes here
values.append(size(surface, value))
if ttype == "matrix":
matrix = cairo.Matrix(*values).multiply(matrix)
elif ttype == "rotate":
angle = radians(float(values.pop(0)))
x, y = values or (0, 0)
matrix.translate(x, y)
matrix.rotate(angle)
matrix.translate(-x, -y)
elif ttype == "skewX":
tangent = tan(radians(float(values[0])))
matrix = \
cairo.Matrix(1, 0, tangent, 1, 0, 0).multiply(matrix)
elif ttype == "skewY":
tangent = tan(radians(float(values[0])))
matrix = \
cairo.Matrix(1, tangent, 0, 1, 0, 0).multiply(matrix)
elif ttype == "translate":
if len(values) == 1:
values += (0,)
matrix.translate(*values)
elif ttype == "scale":
if len(values) == 1:
values = 2 * values
matrix.scale(*values)
apply_matrix_transform(surface, matrix)
def apply_matrix_transform(surface, matrix):
try:
matrix.invert()
except Error:
# Matrix not invertible, clip the surface to an empty path
active_path = surface.context.copy_path()
surface.context.new_path()
surface.context.clip()
surface.context.append_path(active_path)
else:
matrix.invert()
surface.context.transform(matrix)
def urls(string):
"""Parse a comma-separated list of url() strings."""
for link in string.split(","):
link = link.strip()
if link.startswith("url"):
link = link[3:]
yield link.strip("() ")
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Images manager.
"""
import base64
import cairo
from io import BytesIO
try:
from urllib import urlopen, unquote
import urlparse
unquote_to_bytes = lambda data: unquote(
data.encode('ascii') if isinstance(data, unicode) else data)
except ImportError:
from urllib.request import urlopen
from urllib import parse as urlparse # Python 3
from urllib.parse import unquote_to_bytes
from .helpers import node_format, size, preserve_ratio
from ..parser import Tree
def open_data_url(url):
"""Decode URLs with the 'data' scheme. urllib can handle them
in Python 2, but that is broken in Python 3.
Inspired from Python 2.7.2’s urllib.py.
"""
# syntax of data URLs:
# dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
# mediatype := [ type "/" subtype ] *( ";" parameter )
# data := *urlchar
# parameter := attribute "=" value
try:
header, data = url.split(",", 1)
except ValueError:
raise IOError("bad data URL")
header = header[5:] # len("data:") == 5
if header:
semi = header.rfind(";")
if semi >= 0 and "=" not in header[semi:]:
encoding = header[semi + 1:]
else:
encoding = ""
else:
encoding = ""
data = unquote_to_bytes(data)
if encoding == "base64":
missing_padding = 4 - len(data) % 4
if missing_padding:
data += b"=" * missing_padding
return base64.decodestring(data)
return data
def image(surface, node):
"""Draw an image ``node``."""
url = node.get("{http://www.w3.org/1999/xlink}href")
if not url:
return
if url.startswith("data:"):
image_bytes = open_data_url(url)
else:
base_url = node.get("{http://www.w3.org/XML/1998/namespace}base")
if base_url:
url = urlparse.urljoin(base_url, url)
if node.url:
url = urlparse.urljoin(node.url, url)
if urlparse.urlparse(url).scheme:
input_ = urlopen(url)
else:
input_ = open(url, 'rb') # filename
image_bytes = input_.read()
if len(image_bytes) < 5:
return
x, y = size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
width = size(surface, node.get("width"), "x")
height = size(surface, node.get("height"), "y")
surface.context.rectangle(x, y, width, height)
surface.context.clip()
if image_bytes[:4] == b"\x89PNG":
png_bytes = image_bytes
elif image_bytes[:5] == b"\x3csvg ":
surface.context.save()
surface.context.translate(x, y)
if "x" in node:
del node["x"]
if "y" in node:
del node["y"]
if "viewBox" in node:
del node["viewBox"]
tree = Tree(bytestring = image_bytes)
tree_width, tree_height, viewbox = node_format(surface, tree)
if not tree_width or not tree_height:
tree_width = tree["width"] = width
tree_height = tree["height"] = height
node.image_width = tree_width or width
node.image_height = tree_height or height
scale_x, scale_y, translate_x, translate_y = \
preserve_ratio(surface, node)
surface.set_context_size(*node_format(surface, tree))
surface.context.translate(*surface.context.get_current_point())
surface.context.scale(scale_x, scale_y)
surface.context.translate(translate_x, translate_y)
surface.draw(tree)
surface.context.restore()
# Restore twice, because draw does not restore at the end of svg tags
surface.context.restore()
return
else:
try:
from pystacia import read_blob
png_bytes = read_blob(image_bytes).get_blob('png')
except:
# No way to handle the image
return
image_surface = cairo.ImageSurface.create_from_png(BytesIO(png_bytes))
node.image_width = image_surface.get_width()
node.image_height = image_surface.get_height()
scale_x, scale_y, translate_x, translate_y = preserve_ratio(surface, node)
surface.context.rectangle(x, y, width, height)
pattern_pattern = cairo.SurfacePattern(image_surface)
surface.context.save()
surface.context.translate(*surface.context.get_current_point())
surface.context.scale(scale_x, scale_y)
surface.context.translate(translate_x, translate_y)
surface.context.set_source(pattern_pattern)
surface.context.fill()
surface.context.restore()
This diff is collapsed.
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Shapes drawers.
"""
from math import pi
from .helpers import normalize, point, size
def circle(surface, node):
"""Draw a circle ``node`` on ``surface``."""
r = size(surface, node.get("r"))
if not r:
return
surface.context.new_sub_path()
surface.context.arc(
size(surface, node.get("x"), "x") + size(surface, node.get("cx"), "x"),
size(surface, node.get("y"), "y") + size(surface, node.get("cy"), "y"),
r, 0, 2 * pi)
def ellipse(surface, node):
"""Draw an ellipse ``node`` on ``surface``."""
rx = size(surface, node.get("rx"), "x")
ry = size(surface, node.get("ry"), "y")
if not rx or not ry:
return
ratio = ry / rx
surface.context.new_sub_path()
surface.context.save()
surface.context.scale(1, ratio)
surface.context.arc(
size(surface, node.get("x"), "x") + size(surface, node.get("cx"), "x"),
(size(surface, node.get("y"), "y") +
size(surface, node.get("cy"), "y")) / ratio,
size(surface, node.get("rx"), "x"), 0, 2 * pi)
surface.context.restore()
def line(surface, node):
"""Draw a line ``node``."""
x1, y1, x2, y2 = tuple(
size(surface, node.get(position), position[0])
for position in ("x1", "y1", "x2", "y2"))
surface.context.move_to(x1, y1)
surface.context.line_to(x2, y2)
def polygon(surface, node):
"""Draw a polygon ``node`` on ``surface``."""
polyline(surface, node)
surface.context.close_path()
def polyline(surface, node):
"""Draw a polyline ``node``."""
points = normalize(node.get("points"))
if points:
x, y, points = point(surface, points)
surface.context.move_to(x, y)
while points:
x, y, points = point(surface, points)
surface.context.line_to(x, y)
def rect(surface, node):
"""Draw a rect ``node`` on ``surface``."""
# TODO: handle ry
x, y = size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
width = size(surface, node.get("width"), "x")
height = size(surface, node.get("height"), "y")
if size(surface, node.get("rx"), "x") == 0:
surface.context.rectangle(x, y, width, height)
else:
r = size(surface, node.get("rx"), "x")
a, b, c, d = x, width + x, y, height + y
if r > width - r:
r = width / 2
surface.context.move_to(x, y + height / 2)
surface.context.arc(a + r, c + r, r, 2 * pi / 2, 3 * pi / 2)
surface.context.arc(b - r, c + r, r, 3 * pi / 2, 0 * pi / 2)
surface.context.arc(b - r, d - r, r, 0 * pi / 2, 1 * pi / 2)
surface.context.arc(a + r, d - r, r, 1 * pi / 2, 2 * pi / 2)
surface.context.close_path()
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Root tag drawer.
"""
from .helpers import preserve_ratio, node_format
def svg(surface, node):
"""Draw a svg ``node``."""
if node.get("preserveAspectRatio", "none") != "none":
width, height, viewbox = node_format(surface, node)
node.image_width, node.image_height = viewbox[2:]
scale_x, scale_y, translate_x, translate_y = \
preserve_ratio(surface, node)
surface.context.rectangle(0, 0, width, height)
surface.context.clip()
surface.context.translate(*surface.context.get_current_point())
surface.context.scale(scale_x, scale_y)
surface.context.translate(translate_x, translate_y)
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
SVG tags functions.
"""
from .defs import linear_gradient, marker, pattern, radial_gradient, use
from .image import image
from .path import path
from .shapes import circle, ellipse, line, polygon, polyline, rect
from .svg import svg
from .text import text, text_path, tspan
TAGS = {
"a": tspan,
"circle": circle,
"ellipse": ellipse,
"image": image,
"line": line,
"linearGradient": linear_gradient,
"marker": marker,
"path": path,
"pattern": pattern,
"polyline": polyline,
"polygon": polygon,
"radialGradient": radial_gradient,
"rect": rect,
"svg": svg,
"text": text,
"textPath": text_path,
"tref": use,
"tspan": tspan,
"use": use}
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Text drawers.
"""
import cairo
from math import cos, sin
# Python 2/3 management
# pylint: disable=E0611
try:
from itertools import zip_longest
except ImportError:
from itertools import izip_longest as zip_longest
# pylint: enable=E0611
from .colors import color
from .helpers import distance, normalize, point_angle
from .units import size
def path_length(path):
"""Get the length of ``path``."""
total_length = 0
for item in path:
if item[0] == cairo.PATH_MOVE_TO:
old_point = item[1]
elif item[0] == cairo.PATH_LINE_TO:
new_point = item[1]
length = distance(
old_point[0], old_point[1], new_point[0], new_point[1])
total_length += length
old_point = new_point
return total_length
def point_following_path(path, width):
"""Get the point at ``width`` distance on ``path``."""
total_length = 0
for item in path:
if item[0] == cairo.PATH_MOVE_TO:
old_point = item[1]
elif item[0] == cairo.PATH_LINE_TO:
new_point = item[1]
length = distance(
old_point[0], old_point[1], new_point[0], new_point[1])
total_length += length
if total_length < width:
old_point = new_point
else:
length -= total_length - width
angle = point_angle(
old_point[0], old_point[1], new_point[0], new_point[1])
x = cos(angle) * length + old_point[0]
y = sin(angle) * length + old_point[1]
return x, y
def text(surface, node):
"""Draw a text ``node``."""
# Set black as default text color
if not node.get("fill"):
node["fill"] = "#000000"
# TODO: find a better way to manage white spaces in text nodes
node.text = (node.text or "").lstrip()
node.text = node.text.rstrip() + " "
# TODO: manage font variant
font_size = size(surface, node.get("font-size", "12pt"))
font_family = (node.get("font-family") or "sans-serif").split(",")[0]
font_style = getattr(
cairo, ("font_slant_%s" % node.get("font-style")).upper(),
cairo.FONT_SLANT_NORMAL)
font_weight = getattr(
cairo, ("font_weight_%s" % node.get("font-weight")).upper(),
cairo.FONT_WEIGHT_NORMAL)
surface.context.select_font_face(font_family, font_style, font_weight)
surface.context.set_font_size(font_size)
text_extents = surface.context.text_extents(node.text)
x_bearing = text_extents[0]
width = text_extents[2]
x, y = size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
text_anchor = node.get("text-anchor")
if text_anchor == "middle":
x -= width / 2. + x_bearing
elif text_anchor == "end":
x -= width + x_bearing
surface.context.move_to(x, y)
surface.context.text_path(node.text)
# Remember the absolute cursor position
surface.cursor_position = surface.context.get_current_point()
def text_path(surface, node):
"""Draw text on a path."""
surface.context.save()
if "url(#" not in (node.get("fill") or ""):
surface.context.set_source_rgba(*color(node.get("fill")))
id_path = node.get("{http://www.w3.org/1999/xlink}href", "")
if not id_path.startswith("#"):
return
id_path = id_path[1:]
if id_path in surface.paths:
path = surface.paths.get(id_path)
else:
return
surface.draw(path, False)
cairo_path = surface.context.copy_path_flat()
surface.context.new_path()
start_offset = size(
surface, node.get("startOffset", 0), path_length(cairo_path))
surface.total_width += start_offset
x, y = point_following_path(cairo_path, surface.total_width)
string = (node.text or "").strip(" \n")
letter_spacing = size(surface, node.get("letter-spacing"))
for letter in string:
surface.total_width += (
surface.context.text_extents(letter)[4] + letter_spacing)
point_on_path = point_following_path(cairo_path, surface.total_width)
if point_on_path:
x2, y2 = point_on_path
else:
continue
angle = point_angle(x, y, x2, y2)
surface.context.save()
surface.context.translate(x, y)
surface.context.rotate(angle)
surface.context.translate(0, size(surface, node.get("y"), "y"))
surface.context.move_to(0, 0)
surface.context.show_text(letter)
surface.context.restore()
x, y = x2, y2
surface.context.restore()
# Remember the relative cursor position
surface.cursor_position = \
size(surface, node.get("x"), "x"), size(surface, node.get("y"), "y")
def tspan(surface, node):
"""Draw a tspan ``node``."""
x, y = [[i] for i in surface.cursor_position]
if "x" in node:
x = [size(surface, i, "x")
for i in normalize(node["x"]).strip().split(" ")]
if "y" in node:
y = [size(surface, i, "y")
for i in normalize(node["y"]).strip().split(" ")]
string = (node.text or "").strip()
if not string:
return
fill = node.get("fill")
positions = list(zip_longest(x, y))
letters_positions = list(zip(positions, string))
letters_positions = letters_positions[:-1] + [
(letters_positions[-1][0], string[len(letters_positions) - 1:])]
for (x, y), letters in letters_positions:
if x == None:
x = surface.cursor_position[0]
if y == None:
y = surface.cursor_position[1]
node["x"] = str(x + size(surface, node.get("dx"), "x"))
node["y"] = str(y + size(surface, node.get("dy"), "y"))
node["fill"] = fill
node.text = letters
if node.parent.tag == "text":
text(surface, node)
else:
node["x"] = str(x + size(surface, node.get("dx"), "x"))
node["y"] = str(y + size(surface, node.get("dy"), "y"))
text_path(surface, node)
if node.parent.children[-1] == node:
surface.total_width = 0
# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG. If not, see <http://www.gnu.org/licenses/>.
"""
Units functions.
"""
UNITS = {
"mm": 1 / 25.4,
"cm": 1 / 2.54,
"in": 1,
"pt": 1 / 72.,
"pc": 1 / 6.,
"px": None}
def size(surface, string, reference = "xy"):
"""Replace a ``string`` with units by a float value.
If ``reference`` is a float, it is used as reference for percentages. If it
is ``'x'``, we use the viewport width as reference. If it is ``'y'``, we
use the viewport height as reference. If it is ``'xy'``, we use
``(viewport_width ** 2 + viewport_height ** 2) ** .5 / 2 ** .5`` as
reference.
"""
if not string:
return 0
try:
return float(string)
except ValueError:
# Not a float, try something else
pass
if "%" in string:
if reference == "x":
reference = surface.context_width or 0
elif reference == "y":
reference = surface.context_height or 0
elif reference == "xy":
reference = (
(surface.context_width ** 2 + surface.context_height ** 2)
** .5 / 2 ** .5)
return float(string.strip(" %")) * reference / 100
elif "em" in string:
return surface.font_size * float(string.strip(" em"))
elif "ex" in string:
# Assume that 1em == 2ex
return surface.font_size * float(string.strip(" ex")) / 2
for unit, coefficient in UNITS.items():
if unit in string:
number = float(string.strip(" " + unit))
return number * (surface.dpi * coefficient if coefficient else 1)
# Try to return the number at the beginning of the string
return_string = ""
while string and (string[0].isdigit() or string[0] in "+-."):
return_string += string[0]
string = string[1:]
# Unknown size or multiple sizes
return float(return_string) if return_string else 0
...@@ -21,7 +21,7 @@ import time ...@@ -21,7 +21,7 @@ import time
import zipfile import zipfile
import tempfile import tempfile
import shutil import shutil
from printrun.cairosvg.surface import PNGSurface from cairosvg.surface import PNGSurface
import cStringIO import cStringIO
import imghdr import imghdr
import copy import copy
...@@ -89,14 +89,14 @@ class DisplayFrame(wx.Frame): ...@@ -89,14 +89,14 @@ class DisplayFrame(wx.Frame):
if self.slicer == 'Slic3r' or self.slicer == 'Skeinforge': if self.slicer == 'Slic3r' or self.slicer == 'Skeinforge':
if int(self.scale) != 1: if self.scale != 1.0:
layercopy = copy.deepcopy(image) layercopy = copy.deepcopy(image)
height = float(layercopy.get('height').replace('m','')) height = float(layercopy.get('height').replace('m',''))
width = float(layercopy.get('width').replace('m','')) width = float(layercopy.get('width').replace('m',''))
layercopy.set('height', str(height*self.scale) + 'mm') layercopy.set('height', str(height*self.scale) + 'mm')
layercopy.set('width', str(width*self.scale) + 'mm') layercopy.set('width', str(width*self.scale) + 'mm')
layercopy.set('viewBox', '0 0 ' + str(height*self.scale) + ' ' + str(width*self.scale)) layercopy.set('viewBox', '0 0 ' + str(width*self.scale) + ' ' + str(height*self.scale))
g = layercopy.find("{http://www.w3.org/2000/svg}g") g = layercopy.find("{http://www.w3.org/2000/svg}g")
g.set('transform', 'scale('+str(self.scale)+')') g.set('transform', 'scale('+str(self.scale)+')')
...@@ -104,12 +104,14 @@ class DisplayFrame(wx.Frame): ...@@ -104,12 +104,14 @@ class DisplayFrame(wx.Frame):
else: else:
stream = cStringIO.StringIO(PNGSurface.convert(dpi=self.dpi, bytestring=xml.etree.ElementTree.tostring(image))) stream = cStringIO.StringIO(PNGSurface.convert(dpi=self.dpi, bytestring=xml.etree.ElementTree.tostring(image)))
image = wx.ImageFromStream(stream) pngImage = wx.ImageFromStream(stream)
#print "w:", pngImage.Width, ", dpi:",self.dpi, ", w (mm): ",(pngImage.Width / self.dpi) * 25.4
if self.layer_red: if self.layer_red:
image = image.AdjustChannels(1,0,0,1) pngImage = pngImage.AdjustChannels(1,0,0,1)
dc.DrawBitmap(wx.BitmapFromImage(image), self.offset[0], self.offset[1], True) dc.DrawBitmap(wx.BitmapFromImage(pngImage), self.offset[0], self.offset[1], True)
elif self.slicer == 'bitmap': elif self.slicer == 'bitmap':
if isinstance(image, str): if isinstance(image, str):
...@@ -325,13 +327,13 @@ class SettingsFrame(wx.Frame): ...@@ -325,13 +327,13 @@ class SettingsFrame(wx.Frame):
# Right Column # Right Column
fieldsizer.Add(wx.StaticText(self.panel, -1, "X (px):"), pos=(0, 2), flag=wx.ALIGN_CENTER_VERTICAL) fieldsizer.Add(wx.StaticText(self.panel, -1, "X (px):"), pos=(0, 2), flag=wx.ALIGN_CENTER_VERTICAL)
self.X = wx.SpinCtrl(self.panel, -1, str(int(self._get_setting("project_x", 1024))), max=999999, size=(80,-1)) self.X = wx.SpinCtrl(self.panel, -1, str(int(self._get_setting("project_x", 1920))), max=999999, size=(80,-1))
self.X.Bind(wx.EVT_SPINCTRL, self.update_resolution) self.X.Bind(wx.EVT_SPINCTRL, self.update_resolution)
self.X.SetHelpText("The projector resolution in the X axis.") self.X.SetHelpText("The projector resolution in the X axis.")
fieldsizer.Add(self.X, pos=(0, 3)) fieldsizer.Add(self.X, pos=(0, 3))
fieldsizer.Add(wx.StaticText(self.panel, -1, "Y (px):"), pos=(1, 2), flag=wx.ALIGN_CENTER_VERTICAL) fieldsizer.Add(wx.StaticText(self.panel, -1, "Y (px):"), pos=(1, 2), flag=wx.ALIGN_CENTER_VERTICAL)
self.Y = wx.SpinCtrl(self.panel, -1, str(int(self._get_setting("project_y", 768))), max=999999, size=(80,-1)) self.Y = wx.SpinCtrl(self.panel, -1, str(int(self._get_setting("project_y", 1200))), max=999999, size=(80,-1))
self.Y.Bind(wx.EVT_SPINCTRL, self.update_resolution) self.Y.Bind(wx.EVT_SPINCTRL, self.update_resolution)
self.Y.SetHelpText("The projector resolution in the Y axis.") self.Y.SetHelpText("The projector resolution in the Y axis.")
fieldsizer.Add(self.Y, pos=(1, 3)) fieldsizer.Add(self.Y, pos=(1, 3))
...@@ -349,7 +351,7 @@ class SettingsFrame(wx.Frame): ...@@ -349,7 +351,7 @@ class SettingsFrame(wx.Frame):
fieldsizer.Add(self.offset_Y, pos=(3, 3)) fieldsizer.Add(self.offset_Y, pos=(3, 3))
fieldsizer.Add(wx.StaticText(self.panel, -1, "ProjectedX (mm):"), pos=(4, 2), flag=wx.ALIGN_CENTER_VERTICAL) fieldsizer.Add(wx.StaticText(self.panel, -1, "ProjectedX (mm):"), pos=(4, 2), flag=wx.ALIGN_CENTER_VERTICAL)
self.projected_X_mm = floatspin.FloatSpin(self.panel, -1, value=self._get_setting("project_projected_x", 415.0), increment=1, digits=1, size=(80,-1)) self.projected_X_mm = floatspin.FloatSpin(self.panel, -1, value=self._get_setting("project_projected_x", 505.0), increment=1, digits=1, size=(80,-1))
self.projected_X_mm.Bind(floatspin.EVT_FLOATSPIN, self.update_projected_Xmm) self.projected_X_mm.Bind(floatspin.EVT_FLOATSPIN, self.update_projected_Xmm)
self.projected_X_mm.SetHelpText("The actual width of the entire projected image. Use the Calibrate grid to show the full size of the projected image, and measure the width at the same level where the slice will be projected onto the resin.") self.projected_X_mm.SetHelpText("The actual width of the entire projected image. Use the Calibrate grid to show the full size of the projected image, and measure the width at the same level where the slice will be projected onto the resin.")
fieldsizer.Add(self.projected_X_mm, pos=(4, 3)) fieldsizer.Add(self.projected_X_mm, pos=(4, 3))
...@@ -498,8 +500,8 @@ class SettingsFrame(wx.Frame): ...@@ -498,8 +500,8 @@ class SettingsFrame(wx.Frame):
svgSnippet.set('height', height + 'mm') svgSnippet.set('height', height + 'mm')
svgSnippet.set('width', width + 'mm') svgSnippet.set('width', width + 'mm')
svgSnippet.set('viewBox', '0 0 ' + height + ' ' + width) svgSnippet.set('viewBox', '0 0 ' + width + ' ' + height)
svgSnippet.set('style','background-color:black') svgSnippet.set('style','background-color:black;fill:white;')
svgSnippet.append(i) svgSnippet.append(i)
ol += [svgSnippet] ol += [svgSnippet]
...@@ -534,7 +536,7 @@ class SettingsFrame(wx.Frame): ...@@ -534,7 +536,7 @@ class SettingsFrame(wx.Frame):
svgSnippet.set('height', height + 'mm') svgSnippet.set('height', height + 'mm')
svgSnippet.set('width', width + 'mm') svgSnippet.set('width', width + 'mm')
svgSnippet.set('viewBox', '0 0 ' + height + ' ' + width) svgSnippet.set('viewBox', '0 0 ' + width + ' ' + height)
svgSnippet.set('style','background-color:black;fill:white;') svgSnippet.set('style','background-color:black;fill:white;')
svgSnippet.append(g) svgSnippet.append(g)
......
...@@ -3,7 +3,7 @@ Type=Application ...@@ -3,7 +3,7 @@ Type=Application
Name=Pronterface Name=Pronterface
Comment=Pronterface Comment=Pronterface
Icon=/usr/share/pixmaps/P-face.ico Icon=/usr/share/pixmaps/P-face.ico
Exec=/usr/bin/python2 /usr/bin/pronterface.py Exec=/usr/bin/pronterface.py
Path=/usr/share/pronterface/ Path=/usr/share/pronterface/
StartupNotify=true StartupNotify=true
Terminal=false Terminal=false
......
git+https://github.com/D1plo1d/tornado.git argparse
git+https://github.com/D1plo1d/py-mdns.git pyreadline
git+https://github.com/construct-protocol/construct_server.py pyserial
wxPython
inflection>=0.2.0 numpy
mdns>=1.0.0 pyglet>=1.1
objgraph>=1.7.2 pycairo
pybonjour>=1.1.1 cairosvg
pyserial==2.6
wsgiref==0.1.2
git+https://github.com/D1plo1d/tornado.git
git+https://github.com/D1plo1d/py-mdns.git
git+https://github.com/construct-protocol/construct_server.py
inflection>=0.2.0
mdns>=1.0.0
objgraph>=1.7.2
pybonjour>=1.1.1
pyserial==2.6
wsgiref==0.1.2
...@@ -157,7 +157,7 @@ setup(name = "Printrun", ...@@ -157,7 +157,7 @@ setup(name = "Printrun",
url = "http://github.com/kliment/Printrun/", url = "http://github.com/kliment/Printrun/",
license = "GPLv3", license = "GPLv3",
data_files = data_files, data_files = data_files,
packages = ["printrun", "printrun.cairosvg", "printrun.gl", "printrun.gl.libtatlin"], packages = ["printrun", "printrun.gl", "printrun.gl.libtatlin"],
scripts = ["pronsole.py", "pronterface.py", "plater.py", "printcore.py", "prontserve.py"], scripts = ["pronsole.py", "pronterface.py", "plater.py", "printcore.py", "prontserve.py"],
cmdclass = cmdclass, cmdclass = cmdclass,
ext_modules = extensions, ext_modules = extensions,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment