Commit 7bfa6228 authored by Guillaume Seguin's avatar Guillaume Seguin

Reset svg to an older version as well

parent 3d0f4985
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
"""
import wx
def AddEllipticalArc(self, x, y, w, h, startAngle, endAngle, clockwise = False):
""" Draws an arc of an ellipse within bounding rect (x, y, w, h)
def AddEllipticalArc(self, x, y, w, h, startAngle, endAngle, clockwise=False):
""" Draws an arc of an ellipse within bounding rect (x,y,w,h)
from startArc to endArc (in radians, relative to the horizontal line of the eclipse)"""
if True:
import warnings
warnings.warn("elliptical arcs are not supported")
......@@ -30,7 +15,7 @@ def AddEllipticalArc(self, x, y, w, h, startAngle, endAngle, clockwise = False):
h = h/2.0
self.AddArc(x+w, y+h, ((w+h)/2), startAngle, endAngle, clockwise)
return
else:
else:
#implement in terms of AddArc by applying a transformation matrix
#Sigh this can't work, still need to patch wx to allow
#either a) AddPath that's not a closed path or
......@@ -40,18 +25,19 @@ def AddEllipticalArc(self, x, y, w, h, startAngle, endAngle, clockwise = False):
#possibly be simulated by combining the current transform with option a.
mtx = wx.GraphicsRenderer_GetDefaultRenderer().CreateMatrix()
path = wx.GraphicsRenderer_GetDefaultRenderer().CreatePath()
mtx.Translate(x+(w/2.0), y+(h/2.0))
mtx.Scale(w/2.0, y/2.0)
path.AddArc(0, 0, 1, startAngle, endAngle, clockwise)
path.Transform(mtx)
self.AddPath(path)
self.MoveToPoint(path.GetCurrentPoint())
self.CloseSubpath()
if not hasattr(wx.GraphicsPath, "AddEllipticalArc"):
wx.GraphicsPath.AddEllipticalArc = AddEllipticalArc
del AddEllipticalArc
\ No newline at end of file
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
Parsers for specific attributes
"""
import urlparse
from pyparsing import (Literal,
from pyparsing import (Literal,
Optional, oneOf, Group, StringEnd, Combine, Word, alphas, hexnums,
CaselessLiteral, SkipTo
)
......@@ -36,23 +21,23 @@ def parsePossibleURL(t):
colorDeclaration = none | currentColor | colourValue
urlEnd = (
Literal(")").suppress() +
Optional(Group(colorDeclaration), default = ()) +
Literal(")").suppress() +
Optional(Group(colorDeclaration), default=()) +
StringEnd()
)
url = (
CaselessLiteral("URL")
+
+
Literal("(").suppress()+
Group(SkipTo(urlEnd, include = True).setParseAction(parsePossibleURL))
Group(SkipTo(urlEnd, include=True).setParseAction(parsePossibleURL))
)
#paint value will parse into a (type, details) tuple.
#For none and currentColor, the details tuple will be the empty tuple
#for CSS color declarations, it will be (type, (R, G, B))
#for CSS color declarations, it will be (type, (R,G,B))
#for URLs, it will be ("URL", ((url tuple), fallback))
#The url tuple will be as returned by urlparse.urlsplit, and can be
#The url tuple will be as returned by urlparse.urlsplit, and can be
#an empty tuple if the parser has an error
#The fallback will be another (type, details) tuple as a parsed
#colorDeclaration, but may be the empty tuple if it is not present
......
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import
from .transform import transformList
from .inline import inlineStyle
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
""" CSS at-rules"""
from pyparsing import Literal, Combine
from .identifier import identifier
atkeyword = Combine(Literal("@") + identifier)
atkeyword = Combine(Literal("@") + identifier)
\ No newline at end of file
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
CSS blocks
"""
......@@ -20,3 +5,4 @@
from pyparsing import nestedExpr
block = nestedExpr(opener="{", closer="}")
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
Parsing for CSS colour values.
Supported formats:
......@@ -36,31 +21,31 @@ comma = Literal(",").suppress()
def clampColourByte(val):
val = int(val)
return min(max(0,val), 255)
def clampColourPerc(val):
val = float(val)
return min(max(0,val), 100)
def parseColorPerc(token):
val = token[0]
val = clampColourPerc(val)
#normalize to bytes
return int(255 * (val / 100.0))
colorByte = Optional(sign) + integerConstant.setParseAction(lambda t: clampColourByte(t[0]))
colorPerc = number.setParseAction(parseColorPerc) + Literal("%").suppress()
rgb = (
Literal("rgb(").setParseAction(lambda t: "RGB") +
Literal("rgb(").setParseAction(lambda t: "RGB") +
(
#integer constants, ie 255,255,255
Group(colorByte + comma + colorByte + comma + colorByte) ^
#percentage values, ie 100%, 50%
Group(colorPerc + comma + colorPerc + comma + colorPerc)
)
+
+
Literal(")").suppress() + StringEnd()
)
......@@ -69,10 +54,10 @@ def parseShortHex(t):
doubleHex = Word(hexnums, exact=2).setParseAction(lambda t: int(t[0], 16))
hexLiteral = (Literal("#").setParseAction(lambda t: "RGB") +
hexLiteral = (Literal("#").setParseAction(lambda t: "RGB") +
(
Group(doubleHex + doubleHex + doubleHex) |
Word(hexnums, exact=3).setParseAction(parseShortHex)
Word(hexnums, exact=3).setParseAction(parseShortHex)
) + StringEnd()
)
......@@ -81,7 +66,7 @@ def parseNamedColour(t):
return ["RGB", NamedColours[t[0].lower()]]
except KeyError:
return ["RGB", (0,0,0)]
namedColour = Word(alphas).setParseAction(parseNamedColour)
......@@ -261,7 +246,7 @@ NamedColours = {
def fillCSS2SystemColours():
#The system colours require a wxApp to be present to retrieve,
#so if you wnat support for them you'll need
#so if you wnat support for them you'll need
#to call this function after your wxApp instance starts
systemColors = {
"ActiveBorder": wx.SYS_COLOUR_ACTIVEBORDER,
......@@ -295,4 +280,4 @@ def fillCSS2SystemColours():
NamedColours.update(
#strip the alpha from the system colors. Is this really what we want to do?
(k.lower(), wx.SystemSettings.GetColour(v)[:3]) for (k,v) in systemColors.iteritems()
)
)
\ No newline at end of file
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
""" Parse CSS identifiers. More complicated than it sounds"""
from pyparsing import Word, Literal, Regex, Combine, Optional, White, oneOf, ZeroOrMore
......@@ -25,7 +10,7 @@ class White(White):
super(White, self).__init__(ws, min, max, exact)
escaped = (
Literal("\\").suppress() +
Literal("\\").suppress() +
#chr(20)-chr(126) + chr(128)-unichr(sys.maxunicode)
Regex(u"[\u0020-\u007e\u0080-\uffff]", re.IGNORECASE)
)
......@@ -33,7 +18,7 @@ escaped = (
def convertToUnicode(t):
return unichr(int(t[0], 16))
hex_unicode = (
Literal("\\").suppress() +
Literal("\\").suppress() +
Regex("[0-9a-f]{1,6}", re.IGNORECASE) +
Optional(White(exact=1)).suppress()
).setParseAction(convertToUnicode)
......@@ -44,9 +29,9 @@ escape = hex_unicode | escaped
#any unicode literal outside the 0-127 ascii range
nonascii = Regex(u"[^\u0000-\u007f]")
#single character for starting an identifier.
#single character for starting an identifier.
nmstart = Regex(u"[A-Z]", re.IGNORECASE) | nonascii | escape
nmchar = Regex(u"[0-9A-Z-]", re.IGNORECASE) | nonascii | escape
identifier = Combine(nmstart + ZeroOrMore(nmchar))
identifier = Combine(nmstart + ZeroOrMore(nmchar))
\ No newline at end of file
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
""" Parser for inline CSS in style attributes """
def inlineStyle(styleString):
......@@ -20,4 +5,4 @@ def inlineStyle(styleString):
return {}
styles = styleString.split(";")
rv = dict(style.split(":") for style in styles if len(style) != 0)
return rv
return rv
\ No newline at end of file
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
Parsing for CSS and CSS-style values, such as transform and filter attributes.
"""
......@@ -63,4 +48,4 @@ transformList = delimitedList(Group(transform), delim=maybeComma)
if __name__ == '__main__':
from tests.test_css import *
unittest.main()
unittest.main()
\ No newline at end of file
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
Parser for various kinds of CSS values as per CSS2 spec section 4.3
"""
......@@ -58,4 +43,4 @@ length = lengthValue + Optional(lengthUnit, default=None) + StringEnd()
length.leaveWhitespace()
#set the parse action aftward so it doesn't "infect" the parsers that build on it
number.setParseAction(asFloat)
number.setParseAction(asFloat)
\ No newline at end of file
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
SVGDocument
"""
......@@ -25,26 +10,26 @@ from functools import wraps
import pathdata
import css
from css.colour import colourValue
from css import values
from svg.css.colour import colourValue
from svg.css import values
from attributes import paintValue
document = """<?xml version = "1.0" standalone = "no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
document = """<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width = "4cm" height = "4cm" viewBox = "0 0 400 400"
xmlns = "http://www.w3.org/2000/svg" version = "1.1">
<svg width="4cm" height="4cm" viewBox="0 0 400 400"
xmlns="http://www.w3.org/2000/svg" version="1.1">
<title>Example triangle01- simple example of a 'path'</title>
<desc>A path that draws a triangle</desc>
<rect x = "1" y = "1" width = "398" height = "398"
fill = "none" stroke = "blue" />
<path d = "M 100 100 L 300 100 L 200 300 z"
fill = "red" stroke = "blue" stroke-width = "3" />
<rect x="1" y="1" width="398" height="398"
fill="none" stroke="blue" />
<path d="M 100 100 L 300 100 L 200 300 z"
fill="red" stroke="blue" stroke-width="3" />
</svg>"""
makePath = lambda: wx.GraphicsRenderer_GetDefaultRenderer().CreatePath()
def attrAsFloat(node, attr, defaultValue = "0"):
def attrAsFloat(node, attr, defaultValue="0"):
val = node.get(attr, defaultValue)
#TODO: process stuff like "inherit" by walking back up the nodes
#fast path optimization - if it's a valid float, don't
......@@ -53,8 +38,8 @@ def attrAsFloat(node, attr, defaultValue = "0"):
return float(val)
except ValueError:
return valueToPixels(val)
def valueToPixels(val, defaultUnits = "px"):
def valueToPixels(val, defaultUnits="px"):
#TODO manage default units
from pyparsing import ParseException
try:
......@@ -82,7 +67,7 @@ def pathHandler(func):
ops = self.generatePathOps(path)
return path, ops
return inner
class SVGDocument(object):
lastControl = None
......@@ -117,12 +102,12 @@ class SVGDocument(object):
def state(self):
""" Retrieve the current state, without popping"""
return self.stateStack[-1]
def processElement(self, element):
""" Process one element of the XML tree.
Returns the path representing the node,
and an operation list for drawing the node.
Parent nodes should return a path (for hittesting), but
no draw operations
"""
......@@ -141,7 +126,7 @@ class SVGDocument(object):
""" Returns an oplist for transformations.
This applies to a node, not the current state because
the transform stack is saved in the wxGraphicsContext.
This oplist does *not* include the push/pop state commands
"""
ops = []
......@@ -190,21 +175,21 @@ class SVGDocument(object):
)
if transform == 'skewX':
matrix = wx.GraphicsRenderer_GetDefaultRenderer().CreateMatrix(
1, 0, math.tan(math.radians(args[0])), 1, 0, 0
1,0,math.tan(math.radians(args[0])),1,0,0
)
ops.append(
(wx.GraphicsContext.ConcatTransform, (matrix,))
)
if transform == 'skewY':
matrix = wx.GraphicsRenderer_GetDefaultRenderer().CreateMatrix(
1, math.tan(math.radians(args[0])), 0, 1, 0, 0
1,math.tan(math.radians(args[0])),0,1,0,0
)
ops.append(
(wx.GraphicsContext.ConcatTransform, (matrix,))
)
return ops
def addGroupToDocument(self, node):
""" For parent elements: push on a state,
then process all child elements
......@@ -212,7 +197,7 @@ class SVGDocument(object):
ops = [
(wx.GraphicsContext.PushState, ())
]
path = makePath()
ops.extend(self.createTransformOpsFromNode(node))
for child in node.getchildren():
......@@ -225,7 +210,7 @@ class SVGDocument(object):
(wx.GraphicsContext.PopState, ())
)
return path, ops
def getFontFromState(self):
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
family = self.state.get("font-family")
......@@ -242,11 +227,11 @@ class SVGDocument(object):
else:
font.SetPointSize(int(val))
return font
def addTextToDocument(self, node):
x, y = [attrAsFloat(node, attr) for attr in ('x', 'y')]
def DoDrawText(context, text, x, y, brush = wx.NullGraphicsBrush):
def DoDrawText(context, text, x, y, brush=wx.NullGraphicsBrush):
#SVG spec appears to originate text from the bottom
#rather than the top as with our API. This function
#will measure and then re-orient the text as needed.
......@@ -267,14 +252,14 @@ class SVGDocument(object):
(DoDrawText, (text, x, y))
]
return None, ops
@pathHandler
def addRectToDocument(self, node, path):
x, y, w, h = (attrAsFloat(node, attr) for attr in ['x', 'y', 'width', 'height'])
rx = node.get('rx')
ry = node.get('ry')
if not (w and h):
path.MoveToPoint(x, y) #keep the current point correct
path.MoveToPoint(x,y) #keep the current point correct
return
if rx or ry:
if rx and ry:
......@@ -286,7 +271,7 @@ class SVGDocument(object):
#value clamping as per spec section 9.2
rx = min(rx, w/2)
ry = min(ry, h/2)
#origin
path.MoveToPoint(x+rx, y)
path.AddLineToPoint(x+w-rx, y)
......@@ -330,12 +315,12 @@ class SVGDocument(object):
path.AddRectangle(
x, y, w, h
)
@pathHandler
def addCircleToDocument(self, node, path):
cx, cy, r = [attrAsFloat(node, attr) for attr in ('cx', 'cy', 'r')]
path.AddCircle(cx, cy, r)
@pathHandler
def addEllipseToDocument(self, node, path):
cx, cy, rx, ry = [float(node.get(attr, 0)) for attr in ('cx', 'cy', 'rx', 'ry')]
......@@ -347,29 +332,29 @@ class SVGDocument(object):
x = cx - rx
y = cy - ry
path.AddEllipse(x, y, rx*2, ry*2)
@pathHandler
@pathHandler
def addLineToDocument(self, node, path):
x1, y1, x2, y2 = [attrAsFloat(node, attr) for attr in ('x1', 'y1', 'x2', 'y2')]
path.MoveToPoint(x1, y1)
path.AddLineToPoint(x2, y2)
@pathHandler
def addPolyLineToDocument(self, node, path):
#translate to pathdata and render that
data = "M " + node.get("points")
self.addPathDataToPath(data, path)
@pathHandler
@pathHandler
def addPolygonToDocument(self, node, path):
#translate to pathdata and render that
data = "M " + node.get("points") + " Z"
self.addPathDataToPath(data, path)
@pathHandler
def addPathDataToDocument(self, node, path):
self.addPathDataToPath(node.get('d', ''), path)
def addPathDataToPath(self, data, path):
self.lastControl = None
self.lastControlQ = None
......@@ -379,7 +364,7 @@ class SVGDocument(object):
form of (command, [list of arguments]).
We translate that to [(command, args[0]), (command, args[1])]
via a generator.
M is special cased because its subsequent arguments
become linetos.
"""
......@@ -403,10 +388,10 @@ class SVGDocument(object):
#print "parsed in", time.time()-t
for stroke in normalizeStrokes(parsed):
self.addStrokeToPath(path, stroke)
def generatePathOps(self, path):
""" Look at the current state and generate the
""" Look at the current state and generate the
draw operations (fill, stroke, neither) for the path"""
ops = []
brush = self.getBrushFromState(path)
......@@ -429,7 +414,7 @@ class SVGDocument(object):
(wx.GraphicsContext.StrokePath, (path,))
)
return ops
def getPenFromState(self):
pencolour = self.state.get('stroke', 'none')
if pencolour == 'currentColor':
......@@ -464,7 +449,7 @@ class SVGDocument(object):
pen.SetJoin(joinmap.get(self.state.get('stroke-linejoin', None), wx.JOIN_MITER))
return wx.GraphicsRenderer_GetDefaultRenderer().CreatePen(pen)
def getBrushFromState(self, path = None):
def getBrushFromState(self, path=None):
brushcolour = self.state.get('fill', 'black').strip()
type, details = paintValue.parseString(brushcolour)
if type == "URL":
......@@ -480,51 +465,51 @@ class SVGDocument(object):
box = path.GetBox()
x, y, w, h = box.Get()
return wx.GraphicsRenderer.GetDefaultRenderer().CreateLinearGradientBrush(
x, y, x+w, y+h, wx.Colour(0, 0, 255, 128), wx.RED
x,y,x+w,y+h,wx.Colour(0,0,255,128), wx.RED
)
elif element.tag == '{http://www.w3.org/2000/svg}radialGradient':
box = path.GetBox()
x, y, w, h = box.Get()
#print w
mx = wx.GraphicsRenderer.GetDefaultRenderer().CreateMatrix(x, y, w, h)
mx = wx.GraphicsRenderer.GetDefaultRenderer().CreateMatrix(x,y,w,h)
cx, cy = mx.TransformPoint(0.5, 0.5)
fx, fy = cx, cy
return wx.GraphicsRenderer.GetDefaultRenderer().CreateRadialGradientBrush(
cx, cy,
fx, fy,
(max(w, h))/2,
wx.Colour(0, 0, 255, 128), wx.RED
cx,cy,
fx,fy,
(max(w,h))/2,
wx.Colour(0,0,255,128), wx.RED
)
else:
#invlid gradient specified
return wx.NullBrush
r, g, b = 0, 0, 0
r,g,b = 0,0,0
if type == 'CURRENTCOLOR':
type, details = paintValue.parseString(self.state.get('color', 'none'))
if type == 'RGB':
r, g, b = details
r,g,b = details
elif type == "NONE":
return wx.NullBrush
opacity = self.state.get('fill-opacity', self.state.get('opacity', '1'))
opacity = float(opacity)
opacity = min(max(opacity, 0.0), 1.0)
a = 255 * opacity
#using try/except block instead of
#using try/except block instead of
#just setdefault because the wxBrush and wxColour would
#be created every time anyway in order to pass them,
#defeating the purpose of the cache
try:
return SVGDocument.brushCache[(r, g, b, a)]
return SVGDocument.brushCache[(r,g,b,a)]
except KeyError:
return SVGDocument.brushCache.setdefault((r, g, b, a), wx.Brush(wx.Colour(r, g, b, a)))
return SVGDocument.brushCache.setdefault((r,g,b,a), wx.Brush(wx.Colour(r,g,b,a)))
def resolveURL(self, urlData):
"""
Resolve a URL and return an elementTree Element from it.
Return None if unresolvable
"""
scheme, netloc, path, query, fragment = urlData
if scheme == netloc == path == '':
......@@ -539,15 +524,15 @@ class SVGDocument(object):
return None
else:
return self.resolveRemoteURL(urlData)
def resolveRemoteURL(self, url):
return None
def addStrokeToPath(self, path, stroke):
""" Given a stroke from a path command
(in the form (command, arguments)) create the path
commands that represent it.
TODO: break out into (yet another) class/module,
especially so we can get O(1) dispatch on type?
"""
......@@ -579,17 +564,17 @@ class SVGDocument(object):
)
self.lastControl = control2
path.AddCurveToPoint(
control1,
control2,
control1,
control2,
endpoint
)
#~ cp = path.GetCurrentPoint()
#~ path.AddCircle(c1x, c1y, 5)
#~ path.AddCircle(c2x, c2y, 3)
#~ path.AddCircle(x, y, 7)
#~ path.AddCircle(x,y, 7)
#~ path.MoveToPoint(cp)
#~ print "C", control1, control2, endpoint
elif type == 'S':
#control2, endpoint = arg
control2, endpoint = map(
......@@ -599,15 +584,15 @@ class SVGDocument(object):
control1 = reflectPoint(self.lastControl, path.GetCurrentPoint())
else:
control1 = path.GetCurrentPoint()
#~ print "S", self.lastControl, ":", control1, control2, endpoint
#~ print "S", self.lastControl,":",control1, control2, endpoint
self.lastControl = control2
path.AddCurveToPoint(
control1,
control2,
control1,
control2,
endpoint
)
elif type == "Q":
(cx, cy), (x, y) = map(normalizePoint, arg)
(cx, cy), (x,y) = map(normalizePoint, arg)
self.lastControlQ = (cx, cy)
path.AddQuadCurveToPoint(cx, cy, x, y)
elif type == "T":
......@@ -618,50 +603,50 @@ class SVGDocument(object):
cx, cy = path.GetCurrentPoint()
self.lastControlQ = (cx, cy)
path.AddQuadCurveToPoint(cx, cy, x, y)
elif type == "V":
_, y = normalizePoint((0, arg))
x, _ = path.GetCurrentPoint()
path.AddLineToPoint(x, y)
path.AddLineToPoint(x,y)
elif type == "H":
x, _ = normalizePoint((arg, 0))
_, y = path.GetCurrentPoint()
path.AddLineToPoint(x, y)
path.AddLineToPoint(x,y)
elif type == "A":
#wxGC currently only supports circular arcs,
#not eliptical ones
(
(rx, ry), #radii of ellipse
angle, #angle of rotation on the ellipse in degrees
(fa, fs), #arc and stroke angle flags
(x, y) #endpoint on the arc
) = arg
x, y = normalizePoint((x, y))
) = arg
x, y = normalizePoint((x,y))
cx, cy = path.GetCurrentPoint()
if (cx, cy) == (x, y):
return #noop
if (rx == 0 or ry == 0):
#no radius is effectively a line
path.AddLineToPoint(x, y)
path.AddLineToPoint(x,y)
return
#find the center point for the ellipse
#translation via the angle
angle = angle % 360
angle = math.radians(angle)
#translated endpoint
xPrime = math.cos(angle) * ((cx-x)/2)
xPrime += math.sin(angle) * ((cx-x)/2)
yPrime = -(math.sin(angle)) * ((cy-y)/2)
yPrime += (math.cos(angle)) * ((cy-y)/2)
temp = ((rx**2) * (ry**2)) - ((rx**2) * (yPrime**2)) - ((ry**2) * (xPrime**2))
temp /= ((rx**2) * (yPrime**2)) + ((ry**2)*(xPrime**2))
temp = abs(temp)
......@@ -674,7 +659,7 @@ class SVGDocument(object):
cyPrime = temp * -((ry * xPrime) / rx)
if fa == fs:
cxPrime, cyPrime = -cxPrime, -cyPrime
#reflect backwards now for the origin
cnx = math.cos(angle) * cxPrime
cnx += math.sin(angle) * cxPrime
......@@ -682,27 +667,27 @@ class SVGDocument(object):
cny += (math.cos(angle)) * cyPrime
cnx += ((cx+x)/2.0)
cny += ((cy+y)/2.0)
#calculate the angle between the two endpoints
lastArc = wx.Point2D(x-cnx, y-cny).GetVectorAngle()
firstArc = wx.Point2D(cx-cnx, cy-cny).GetVectorAngle()
lastArc = math.radians(lastArc)
firstArc = math.radians(firstArc)
#aargh buggines.
#AddArc draws a straight line between
#the endpoints of the arc.
#putting it in a subpath makes the strokes come out
#putting it in a subpath makes the strokes come out
#correctly, but it still only fills the slice
#taking out the MoveToPoint fills correctly.
path.AddEllipse(cnx-rx, cny-ry, rx*2, ry*2)
path.MoveToPoint(x, y)
#~ npath = makePath()
#~ npath.AddEllipticalArc(cnx-rx, cny-ry, rx*2, ry*2, firstArc, lastArc, False)
#~ npath.MoveToPoint(x, y)
#~ npath.MoveToPoint(x,y)
#~ path.AddPath(npath)
elif type == 'Z':
#~ Bugginess:
#~ CloseSubpath() doesn't change the
......@@ -714,17 +699,17 @@ class SVGDocument(object):
#~ results
#~ Manually closing the path *and* calling CloseSubpath() appears
#~ to give correct results on win32
pt = self.firstPoints.pop()
path.AddLineToPoint(pt)
path.CloseSubpath()
def render(self, context):
if not hasattr(self, "ops"):
return
for op, args in self.ops:
op(context, *args)
if __name__ == '__main__':
from tests.test_document import *
unittest.main()
# This file is part of the Printrun suite.
#
# Printrun 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.
#
# Printrun 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 Printrun. If not, see <http://www.gnu.org/licenses/>.
"""
SVG path data parser
Usage:
steps = svg.parseString(pathdata)
for command, arguments in steps:
pass
"""
from pyparsing import (ParserElement, Literal, Word, CaselessLiteral,
from pyparsing import (ParserElement, Literal, Word, CaselessLiteral,
Optional, Combine, Forward, ZeroOrMore, nums, oneOf, Group, ParseException, OneOrMore)
#ParserElement.enablePackrat()
def Command(char):
""" Case insensitive but case preserving"""
return CaselessPreservingLiteral(char)
def Arguments(token):
return Group(token)
class CaselessPreservingLiteral(CaselessLiteral):
""" Like CaselessLiteral, but returns the match as found
instead of as defined.
"""
def __init__( self, matchString ):
super(CaselessPreservingLiteral, self).__init__( matchString.upper() )
super(CaselessPreservingLiteral,self).__init__( matchString.upper() )
self.name = "'%s'" % matchString
self.errmsg = "Expected " + self.name
self.myException.msg = self.errmsg
def parseImpl( self, instring, loc, doActions = True ):
def parseImpl( self, instring, loc, doActions=True ):
test = instring[ loc:loc+self.matchLen ]
if test.upper() == self.match:
return loc+self.matchLen, test
......@@ -55,8 +40,8 @@ class CaselessPreservingLiteral(CaselessLiteral):
exc = self.myException
exc.loc = loc
exc.pstr = instring
raise exc
raise exc
def Sequence(token):
""" A sequence of the token"""
return OneOrMore(token+maybeComma)
......@@ -73,13 +58,13 @@ def convertToFloat(s, loc, toks):
exponent = CaselessLiteral("e")+Optional(sign)+Word(nums)
#note that almost all these fields are optional,
#note that almost all these fields are optional,
#and this can match almost anything. We rely on Pythons built-in
#float() function to clear out invalid values - loosely matching like this
#speeds up parsing quite a lot
floatingPointConstant = Combine(
Optional(sign) +
Optional(Word(nums)) +
Optional(sign) +
Optional(Word(nums)) +
Optional(Literal(".") + Optional(Word(nums)))+
Optional(exponent)
)
......@@ -90,7 +75,7 @@ number = floatingPointConstant
#same as FP constant but don't allow a - sign
nonnegativeNumber = Combine(
Optional(Word(nums)) +
Optional(Word(nums)) +
Optional(Literal(".") + Optional(Word(nums)))+
Optional(exponent)
)
......@@ -132,10 +117,10 @@ ellipticalArcArgument = Group(
arcRadius + maybeComma + #rx, ry
number + maybeComma +#rotation
arcFlags + #large-arc-flag, sweep-flag
coordinatePair #(x, y)
coordinatePair #(x,y)
)
ellipticalArc = Group(Command("A") + Arguments(Sequence(ellipticalArcArgument)))
smoothQuadraticBezierCurveto = Group(Command("T") + Arguments(coordinatePairSequence))
......@@ -170,37 +155,39 @@ def profile():
p.disable()
p.print_stats()
bpath = """M204.33 139.83 C196.33 133.33 206.68 132.82 206.58 132.58 C192.33 97.08 169.35
81.41 167.58 80.58 C162.12 78.02 159.48 78.26 160.45 76.97 C161.41 75.68 167.72 79.72 168.58
80.33 C193.83 98.33 207.58 132.33 207.58 132.33 C207.58 132.33 209.33 133.33 209.58 132.58
C219.58 103.08 239.58 87.58 246.33 81.33 C253.08 75.08 256.63 74.47 247.33 81.58 C218.58 103.58
210.34 132.23 210.83 132.33 C222.33 134.83 211.33 140.33 211.83 139.83 C214.85 136.81 214.83 145.83 214.83
145.83 C214.83 145.83 231.83 110.83 298.33 66.33 C302.43 63.59 445.83 -14.67 395.83 80.83 C393.24 85.79 375.83
bpath = """M204.33 139.83 C196.33 133.33 206.68 132.82 206.58 132.58 C192.33 97.08 169.35
81.41 167.58 80.58 C162.12 78.02 159.48 78.26 160.45 76.97 C161.41 75.68 167.72 79.72 168.58
80.33 C193.83 98.33 207.58 132.33 207.58 132.33 C207.58 132.33 209.33 133.33 209.58 132.58
C219.58 103.08 239.58 87.58 246.33 81.33 C253.08 75.08 256.63 74.47 247.33 81.58 C218.58 103.58
210.34 132.23 210.83 132.33 C222.33 134.83 211.33 140.33 211.83 139.83 C214.85 136.81 214.83 145.83 214.83
145.83 C214.83 145.83 231.83 110.83 298.33 66.33 C302.43 63.59 445.83 -14.67 395.83 80.83 C393.24 85.79 375.83
105.83 375.83 105.83 C375.83 105.83 377.33 114.33 371.33 121.33 C370.3 122.53 367.83 134.33 361.83 140.83 C360.14 142.67
361.81 139.25 361.83 140.83 C362.33 170.83 337.76 170.17 339.33 170.33 C348.83 171.33 350.19 183.66 350.33 183.83 C355.83
190.33 353.83 191.83 355.83 194.83 C366.63 211.02 355.24 210.05 356.83 212.83 C360.83 219.83 355.99 222.72 357.33 224.83
C360.83 230.33 354.75 233.84 354.83 235.33 C355.33 243.83 349.67 240.73 349.83 244.33 C350.33 255.33 346.33 250.83 343.83 254.83
361.81 139.25 361.83 140.83 C362.33 170.83 337.76 170.17 339.33 170.33 C348.83 171.33 350.19 183.66 350.33 183.83 C355.83
190.33 353.83 191.83 355.83 194.83 C366.63 211.02 355.24 210.05 356.83 212.83 C360.83 219.83 355.99 222.72 357.33 224.83
C360.83 230.33 354.75 233.84 354.83 235.33 C355.33 243.83 349.67 240.73 349.83 244.33 C350.33 255.33 346.33 250.83 343.83 254.83
C336.33 266.83 333.46 262.38 332.83 263.83 C329.83 270.83 325.81 269.15 324.33 270.83 C320.83 274.83 317.33 274.83 315.83 276.33
C308.83 283.33 304.86 278.39 303.83 278.83 C287.83 285.83 280.33 280.17 277.83 280.33 C270.33 280.83 271.48 279.67 269.33 277.83
C237.83 250.83 219.33 211.83 215.83 206.83 C214.4 204.79 211.35 193.12 212.33 195.83 C214.33 201.33 213.33 250.33 207.83 250.33
C202.33 250.33 201.83 204.33 205.33 195.83 C206.43 193.16 204.4 203.72 201.79 206.83 C196.33 213.33 179.5 250.83 147.59 277.83
C145.42 279.67 146.58 280.83 138.98 280.33 C136.46 280.17 128.85 285.83 112.65 278.83 C111.61 278.39 107.58 283.33 100.49 276.33
C98.97 274.83 95.43 274.83 91.88 270.83 C90.39 269.15 86.31 270.83 83.27 263.83 C82.64 262.38 79.73 266.83 72.13 254.83 C69.6 250.83
65.54 255.33 66.05 244.33 C66.22 240.73 60.48 243.83 60.99 235.33 C61.08 233.84 54.91 230.33 58.45 224.83 C59.81 222.72 54.91 219.83
58.96 212.83 C60.57 210.05 49.04 211.02 59.97 194.83 C62 191.83 59.97 190.33 65.54 183.83 C65.69 183.66 67.06 171.33 76.69 170.33
C78.28 170.17 53.39 170.83 53.9 140.83 C53.92 139.25 55.61 142.67 53.9 140.83 C47.82 134.33 45.32 122.53 44.27 121.33 C38.19 114.33
39.71 105.83 39.71 105.83 C39.71 105.83 22.08 85.79 19.46 80.83 C-31.19 -14.67 114.07 63.59 118.22 66.33 C185.58 110.83 202 145.83
C308.83 283.33 304.86 278.39 303.83 278.83 C287.83 285.83 280.33 280.17 277.83 280.33 C270.33 280.83 271.48 279.67 269.33 277.83
C237.83 250.83 219.33 211.83 215.83 206.83 C214.4 204.79 211.35 193.12 212.33 195.83 C214.33 201.33 213.33 250.33 207.83 250.33
C202.33 250.33 201.83 204.33 205.33 195.83 C206.43 193.16 204.4 203.72 201.79 206.83 C196.33 213.33 179.5 250.83 147.59 277.83
C145.42 279.67 146.58 280.83 138.98 280.33 C136.46 280.17 128.85 285.83 112.65 278.83 C111.61 278.39 107.58 283.33 100.49 276.33
C98.97 274.83 95.43 274.83 91.88 270.83 C90.39 269.15 86.31 270.83 83.27 263.83 C82.64 262.38 79.73 266.83 72.13 254.83 C69.6 250.83
65.54 255.33 66.05 244.33 C66.22 240.73 60.48 243.83 60.99 235.33 C61.08 233.84 54.91 230.33 58.45 224.83 C59.81 222.72 54.91 219.83
58.96 212.83 C60.57 210.05 49.04 211.02 59.97 194.83 C62 191.83 59.97 190.33 65.54 183.83 C65.69 183.66 67.06 171.33 76.69 170.33
C78.28 170.17 53.39 170.83 53.9 140.83 C53.92 139.25 55.61 142.67 53.9 140.83 C47.82 134.33 45.32 122.53 44.27 121.33 C38.19 114.33
39.71 105.83 39.71 105.83 C39.71 105.83 22.08 85.79 19.46 80.83 C-31.19 -14.67 114.07 63.59 118.22 66.33 C185.58 110.83 202 145.83
202 145.83 C202 145.83 202.36 143.28 203 141.83 C203.64 140.39 204.56 140.02 204.33 139.83 z"""
def ptest():
svg.parseString(bpath)
if __name__ == '__main__':
#~ from tests.test_pathdata import *
#~ unittest.main()
profile()
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