handling of viewBox and units in importSVG

The absolute values in the svg element are used in combination with the
viewBox Attribute to scale the svg to milimeter units.
If there is no viewbox attribute. 90dpi input is assumed.
This commit is contained in:
Sebastian Hoogen 2012-02-13 14:04:51 +01:00
parent 1279c0927c
commit 96e167edc7

View File

@ -36,7 +36,7 @@ currently unsupported: use, image
# debug Problem with 'Sans' font from Inkscape
# debug Problem with fill color
# implement inherting fill style from group
# handle viewbox and units
# handle relative units
import xml.sax, string, FreeCAD, os, math, re, Draft
from draftlibs import fcvec
@ -216,10 +216,50 @@ def getcolor(color):
b = float(v[2]/255.0)
return (r,g,b,0.0)
def getsize(length):
"""extracts a number from the given string (removes unit suffixes)"""
def getsize(length,mode='discard',base=None):
"""parses length values containing number and unit
with mode 'discard': extracts a number from the given string (removes unit suffixes)
with mode 'tuple': return number and unit as a tuple
with mode 'css': convert the unit to px assuming 90dpi
with mode 'mm': convert the unit to millimeter assuming 90dpi"""
tomm={
'' : 25.4/90, #default
'px' : 25.4/90,
'pt' : 1.25*25.4/90,
'pc' : 15*25.4/90,
'mm' : 1.0,
'cm' : 10.0,
'in' : 25.4,
'em': 15*2.54/90, #arbitrarily chosen; has to depend on font size
'ex': 10*2.54/90, #arbitrarily chosen; has to depend on font size
'%': 100 #arbitrarily chosen; has to depend on vieport size or (for filling patterns) on bounding box
}
topx={
'' : 1.0, #default
'px' : 1.0,
'pt' : 1.25,
'pc' : 15,
'mm' : 90.0/25.4,
'cm' : 90.0/254.0,
'in' : 90,
'em': 15, #arbitrarily chosen; has to depend on font size
'ex': 10, #arbitrarily chosen; has to depend on font size
'%': 100 #arbitrarily chosen; has to depend on vieport size or (for filling patterns) on bounding box
}
number, exponent, unit=re.findall('([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)(px|pt|pc|mm|cm|in|em|ex|%)?',length)[0]
return float(number)
if mode =='discard':
return float(number)
elif mode == 'tuple':
return float(number),unit
elif mode == 'mm':
return float(number)*tomm[unit]
elif mode == 'css':
if unit != '%':
return float(number)*topx[unit]
else:
return float(number)*(base or 1)
def makewire(path,checkclosed=False,donttry=False):
'''try to make a wire out of the list of edges. If the 'Wire' functions fails or the wire is not
@ -281,7 +321,7 @@ def arcend2center(lastvec,currentvec,rx,ry,xrotation=0.0,correction=False):
try:
scalefacpos = math.sqrt(numer/denom)
except ValueError:
print 'sqrt(%f/%f)' % (numer,denom)
FreeCAD.Console.PrintMessage('sqrt(%f/%f)\n' % (numer,denom))
scalefacpos = 0
for scalefacsign in (1,-1):
scalefac = scalefacpos * scalefacsign
@ -318,6 +358,7 @@ class svgHandler(xml.sax.ContentHandler):
self.transform = None
self.grouptransform = []
self.lastdim = None
self.viewbox = None
global Part
import Part
@ -349,8 +390,8 @@ class svgHandler(xml.sax.ContentHandler):
self.count += 1
print "processing element ",self.count,": ",name
print "existing group transform: ", self.grouptransform
FreeCAD.Console.PrintMessage('processing element %d: %s\n'%(self.count,name))
FreeCAD.Console.PrintMessage('existing group transform: %s\n'%(str(self.grouptransform)))
data = {}
for (keyword,content) in attrs.items():
@ -370,7 +411,7 @@ class svgHandler(xml.sax.ContentHandler):
for k in ['x','y','x1','y1','x2','y2','r','rx','ry','cx','cy','width','height']:
if k in data:
data[k] = getsize(data[k][0])
data[k] = getsize(data[k][0],'css')
for k in ['fill','stroke','stroke-width','font-size']:
if k in data:
@ -384,6 +425,29 @@ class svgHandler(xml.sax.ContentHandler):
self.width = None
self.text = None
if name == 'svg':
m=FreeCAD.Matrix()
if 'width' in data and 'height' in data and \
'viewBox' in data:
x0,y0,x1,y1=[float(n) for n in data['viewBox']]
vbw = x1-x0
vbh = y1-y0
self.viewbox=(vbw,vbh)
abw = getsize(attrs.getValue('width'),'mm')
abh = getsize(attrs.getValue('height'),'mm')
sx=abw/vbw
sy=abh/vbh
m.scale(Vector(sx,sy,1))
if round(sx/sy,5) != 1:
FreeCAD.Console.PrintWarning('Scaling Factors do not match!!!\n')
#FreeCAD.Console.PrintMessage('attrs: %s %s\n'%(attrs.getValue('width'),attrs.getValue('height')))
#FreeCAD.Console.PrintMessage('vb: %f %f %f %f\n'%(x0,y0,x1,y1))
#FreeCAD.Console.PrintMessage('absolute: %f %f\n'%(abw,abh))
else:
#fallback to 90 dpi
m.scale(Vector(25.4/90.0,25.4/90.0,1))
self.grouptransform.append(m)
if 'fill' in data:
if data['fill'][0] != 'none':
self.fill = getcolor(data['fill'])
@ -392,7 +456,7 @@ class svgHandler(xml.sax.ContentHandler):
self.color = getcolor(data['stroke'])
if 'stroke-width' in data:
if data['stroke-width'] != 'none':
self.width = getsize(data['stroke-width'])
self.width = getsize(data['stroke-width'],'css')
if 'transform' in data:
m = self.getMatrix(attrs.getValue('transform'))
if name == "g":
@ -410,12 +474,12 @@ class svgHandler(xml.sax.ContentHandler):
pathname = None
if 'id' in data:
pathname = data['id'][0]
print "name: ",pathname
FreeCAD.Console.PrintMessage('name: %s\n'%pathname)
# processing paths
if name == "path":
print data
FreeCAD.Console.PrintMessage('data: %s\n'%str(data))
if not pathname: pathname = 'Path'
@ -462,7 +526,7 @@ class svgHandler(xml.sax.ContentHandler):
else:
lastvec = Vector(x,-y,0)
firstvec = lastvec
print "move ",lastvec
FreeCAD.Console.PrintMessage('move %s\n'%str(lastvec))
lastpole = None
if (d == "L" or d == "l") or \
((d == 'm' or d == 'M') and pointlist) :
@ -473,7 +537,7 @@ class svgHandler(xml.sax.ContentHandler):
currentvec = Vector(x,-y,0)
if not fcvec.equals(lastvec,currentvec):
seg = Part.Line(lastvec,currentvec).toShape()
print "line ",lastvec,currentvec
FreeCAD.Console.PrintMessage("line %s %s\n" %(lastvec,currentvec))
lastvec = currentvec
path.append(seg)
lastpole = None
@ -482,8 +546,7 @@ class svgHandler(xml.sax.ContentHandler):
if relative:
currentvec = lastvec.add(Vector(x,0,0))
else:
lasty = path[-1].y
currentvec = Vector(x,lasty,0)
currentvec = Vector(x,lastvec.y,0)
seg = Part.Line(lastvec,currentvec).toShape()
lastvec = currentvec
lastpole = None
@ -493,8 +556,7 @@ class svgHandler(xml.sax.ContentHandler):
if relative:
currentvec = lastvec.add(Vector(0,-y,0))
else:
lastx = path[-1].x
currentvec = Vector(lastx,-y,0)
currentvec = Vector(lastvec.x,-y,0)
seg = Part.Line(lastvec,currentvec).toShape()
lastvec = currentvec
lastpole = None
@ -529,14 +591,14 @@ class svgHandler(xml.sax.ContentHandler):
solution,(rx,ry) = arcend2center(lastvec,currentvec,rx,ry,math.radians(-xrotation),True)
negsol = (largeflag != sweepflag)
vcenter,angle1,angledelta = solution[negsol]
print angle1
print angledelta
#print angle1
#print angledelta
if ry > rx:
rx,ry=ry,rx
swapaxis = True
else:
swapaxis = False
print 'Elliptical arc %s rx=%f ry=%f' % (vcenter,rx,ry)
#print 'Elliptical arc %s rx=%f ry=%f' % (vcenter,rx,ry)
e1 = Part.Ellipse(vcenter,rx,ry)
if sweepflag:
#angledelta=-(-angledelta % (math.pi *2)) # Step4
@ -595,18 +657,18 @@ class svgHandler(xml.sax.ContentHandler):
mainv = currentvec.sub(lastvec)
pole1v = lastvec.add(pole1)
pole2v = currentvec.add(pole2)
print "cubic curve data:",mainv.normalize(),pole1v.normalize(),pole2v.normalize()
#print "cubic curve data:",mainv.normalize(),pole1v.normalize(),pole2v.normalize()
if True and \
pole1.distanceToLine(lastvec,currentvec) < 10**(-1*(2+Draft.precision())) and \
pole2.distanceToLine(lastvec,currentvec) < 10**(-1*(2+Draft.precision())):
print "straight segment"
#print "straight segment"
seg = Part.Line(lastvec,currentvec).toShape()
else:
print "cubic bezier segment"
#print "cubic bezier segment"
b = Part.BezierCurve()
b.setPoles([lastvec,pole1,pole2,currentvec])
seg = b.toShape()
print "connect ",lastvec,currentvec
#print "connect ",lastvec,currentvec
lastvec = currentvec
lastpole = ('cubic',pole2)
path.append(seg)
@ -636,14 +698,14 @@ class svgHandler(xml.sax.ContentHandler):
if not fcvec.equals(currentvec,lastvec):
if True and \
pole.distanceToLine(lastvec,currentvec) < 20**(-1*(2+Draft.precision())):
print "straight segment"
#print "straight segment"
seg = Part.Line(lastvec,currentvec).toShape()
else:
print "quadratic bezier segment"
#print "quadratic bezier segment"
b = Part.BezierCurve()
b.setPoles([lastvec,pole,currentvec])
seg = b.toShape()
print "connect ",lastvec,currentvec
#print "connect ",lastvec,currentvec
lastvec = currentvec
lastpole = ('quadratic',pole)
path.append(seg)
@ -765,7 +827,7 @@ class svgHandler(xml.sax.ContentHandler):
but there would be more difficlult to search for duplicate points beforehand.'''
if not pathname: pathname = 'Polyline'
points=[float(d) for d in data['points']]
print points
FreeCAD.Console.PrintMessage('points %s\n'%str(points))
lenpoints=len(points)
if lenpoints>=4 and lenpoints % 2 == 0:
lastvec = Vector(points[0],-points[1],0)
@ -776,7 +838,7 @@ class svgHandler(xml.sax.ContentHandler):
currentvec = Vector(svgx,-svgy,0)
if not fcvec.equals(lastvec,currentvec):
seg = Part.Line(lastvec,currentvec).toShape()
print "polyline seg ",lastvec,currentvec
#print "polyline seg ",lastvec,currentvec
lastvec = currentvec
path.append(seg)
if path:
@ -824,7 +886,7 @@ class svgHandler(xml.sax.ContentHandler):
if name in ["text","tspan"]:
if not("freecad:skip" in data):
print "processing a text"
FreeCAD.Console.PrintMessage("processing a text\n")
if 'x' in data:
self.x = data['x']
else:
@ -835,28 +897,28 @@ class svgHandler(xml.sax.ContentHandler):
self.y = 0
if 'font-size' in data:
if data['font-size'] != 'none':
self.text = getsize(data['font-size'])
self.text = getsize(data['font-size'],'css')
else:
self.text = 1
else:
if self.lastdim:
self.lastdim.ViewObject.FontSize = int(getsize(data['font-size']))
print "done processing element ",self.count
FreeCAD.Console.PrintMessage("done processing element %d\n"%self.count)
def characters(self,content):
if self.text:
print "reading characters", str(content)
FreeCAD.Console.PrintMessage("reading characters %s\n" % str(content))
obj=self.doc.addObject("App::Annotation",'Text')
obj.LabelText = content.encode('latin1')
vec = Vector(self.x,-self.y,0)
if self.transform:
vec = self.translateVec(vec,self.transform)
print "own transform: ",self.transform, vec
#print "own transform: ",self.transform, vec
for transform in self.grouptransform[::-1]:
#vec = self.translateVec(vec,transform)
vec = transform.multiply(vec)
print "applying vector: ",vec
#print "applying vector: ",vec
obj.Position = vec
if gui:
obj.ViewObject.FontSize = int(self.text)
@ -867,17 +929,17 @@ class svgHandler(xml.sax.ContentHandler):
if not name in ["tspan"]:
self.transform = None
self.text = None
if name == "g":
print "closing group"
if name == "g" or name == "svg":
FreeCAD.Console.PrintMessage("closing group\n")
self.grouptransform.pop()
def applyTrans(self,sh):
if isinstance(sh,Part.Shape):
if self.transform:
print "applying object transform: ",self.transform
FreeCAD.Console.PrintMessage("applying object transform: %s\n" % self.transform)
sh = sh.transformGeometry(self.transform)
for transform in self.grouptransform[::-1]:
print "applying group transform: ", transform
FreeCAD.Console.PrintMessage("applying group transform: %s\n" % transform)
sh = sh.transformGeometry(transform)
return sh
elif Draft.getType(sh) == "Dimension":
@ -885,10 +947,10 @@ class svgHandler(xml.sax.ContentHandler):
for p in [sh.Start,sh.End,sh.Dimline]:
cp = Vector(p)
if self.transform:
print "applying object transform: ",self.transform
FreeCAD.Console.PrintMessage("applying object transform: %s\n" % self.transform)
cp = self.transform.multiply(cp)
for transform in self.grouptransform[::-1]:
print "applying group transform: ",transform
FreeCAD.Console.PrintMessage("applying group transform: %s\n" % transform)
cp = transform.multiply(cp)
pts.append(cp)
sh.Start = pts[0]
@ -906,7 +968,7 @@ class svgHandler(xml.sax.ContentHandler):
for transformation, arguments in transformre.findall(tr):
argsplit=[float(arg) for arg in arguments.replace(',',' ').split()]
#m.multiply(FreeCAD.Matrix (1,0,0,0,0,-1))
print '%s:%s %s %d' % (transformation, arguments,argsplit,len(argsplit))
#print '%s:%s %s %d' % (transformation, arguments,argsplit,len(argsplit))
if transformation == 'translate':
tx = argsplit[0]
ty = argsplit[1] if len(argsplit) > 1 else 0.0
@ -936,10 +998,10 @@ class svgHandler(xml.sax.ContentHandler):
# (+0 -0 +1 +0) (0 0 1 0)
# (+0 -0 +0 +1) (0 0 0 1)'''
m=m.multiply(FreeCAD.Matrix(argsplit[0],-argsplit[2],0,argsplit[4],-argsplit[1],argsplit[3],0,-argsplit[5]))
else:
print 'SKIPPED %s' % transformation
print "m= ",m
print "generating transformation: ",m
#else:
#print 'SKIPPED %s' % transformation
#print "m= ",m
#print "generating transformation: ",m
return m
def decodeName(name):
@ -950,7 +1012,8 @@ def decodeName(name):
try:
decodedName = (name.decode("latin1"))
except UnicodeDecodeError:
print "svg: error: couldn't determine character encoding"
FreeCAD.Console.PrintError("svg: error: couldn't determine character encoding\n")
decodedName = name
return decodedName
@ -1005,7 +1068,7 @@ def export(exportList,filename):
svg_export_style = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetInt("svg_export_style")
if svg_export_style != 0 and svg_export_style != 1:
print "unknown svg export style, switching to Translated"
FreeCAD.Console.PrintMessage("unknown svg export style, switching to Translated\n")
svg_export_style = 0
# finding sheet size