-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbrySVG.brython.js
3 lines (3 loc) · 105 KB
/
brySVG.brython.js
1
2
3
__BRYTHON__.use_VFS = true;
var scripts = {"$timestamp": 1623063053217, "brySVG.drawcanvas": [".py", "#!/usr/bin/python\n\n\n\n\n\n\n\n\n\n\n\nfrom brySVG.dragcanvas import *\n\nclass NonBezierMixin(object):\n ''\n def setPoint(self,i,point):\n self.pointList[i]=point\n self._update()\n self._updatehittarget()\n ''\n\n\n\n\n \n def _movePoint(self,coords):\n self.setPoint(-1,coords)\n \nclass PolyshapeMixin(object):\n ''\n def _appendPoint(self,point):\n self.pointList.append(point)\n self._update()\n \n def insertPoint(self,index,point):\n self.pointList.insert(index,point)\n self._update()\n self._updatehittarget()\n \n def deletePoint(self,index):\n self.deletePoints(index,index+1)\n \n def deletePoints(self,start,end):\n del self.pointList[slice(start,end)]\n self._update()\n self._updatehittarget()\n \nclass BezierMixin(object):\n ''\n def setPointset(self,i,pointset):\n self.pointList[i]=pointset[1]\n self.pointsetList[i]=pointset\n self._update()\n self._updatehittarget()\n \n def setPointsetList(self,pointsetlist):\n self.pointList=[pointset[1]for pointset in pointsetlist]\n self.pointsetList=pointsetlist\n self._update()\n self._updatehittarget()\n \n def setPoint(self,i,point):\n self.pointList[i]=point\n self.pointsetList=self._getpointsetlist(self.pointList)\n self._update()\n self._updatehittarget()\n ''\n\n\n\n\n\n \n def _appendPoint(self,point):\n self.pointList.append(point)\n self.pointsetList=self._getpointsetlist(self.pointList)\n self._update()\n \n def deletePoints(self,start,end):\n del self.pointList[slice(start,end)]\n self.pointsetList=self._getpointsetlist(self.pointList)\n self._update()\n self._updatehittarget()\n \n def insertPoint(self,index,point):\n self.pointList.insert(index,point)\n if isinstance(self,SmoothBezierMixin):\n self.pointsetList=self._getpointsetlist(self.pointList)\n else :\n L=len(self.pointList)\n triple=[self.pointList[index -1],point,self.pointList[(index+1)%L]]\n cpoint1,cpoint2=SmoothBezierMixin._calculatecontrolpoints(self,triple)\n \n self.pointsetList.insert(index,[cpoint1,point,cpoint2])\n self.pointsetList[index -1][2]=cpoint1\n self.pointsetList[(index+1)%L][0]=cpoint2\n self._update()\n self._updatehittarget()\n \n def deletePoint(self,index):\n point=self.pointList[index]\n del self.pointList[index]\n if isinstance(self,SmoothBezierMixin):\n self.pointsetList=self._getpointsetlist(self.pointList)\n else :\n L=len(self.pointsetList)\n \n \n \n \n if index ==0 and self.pointsetList[index][0]is None :\n self.pointsetList[index+1][0]=None\n elif index ==L -1 and self.pointsetList[index][2]is None :\n self.pointsetList[index -1][2]=None\n else :\n self.pointsetList[(index -1)%L][2]=point\n self.pointsetList[(index+1)%L][0]=point\n del self.pointsetList[index]\n self._update()\n self._updatehittarget()\n \n def _movePoint(self,point):\n self.pointList[-1]=point\n self._updatepointsetlist()\n if len(self.pointList)==2:\n self._update()\n elif isinstance(self,(ClosedBezierObject,SmoothClosedBezierObject)):\n ((x1,x2),(x3,x4),(x5,x6))=self.pointsetList[-2]\n ((x7,x8),(x9,x10),(x11,x12))=self.pointsetList[-1]\n ((x13,x14),(x15,x16),(x17,x18))=self.pointsetList[0]\n self.plist=self.plist[:-16]+[x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16]\n self.plist[4:6]=[x17,x18]\n self.attrs[\"d\"]=\" \".join(str(x)for x in self.plist)\n else :\n ((x1,x2),(x3,x4),(x5,x6))=self.pointsetList[-2]\n ((x7,x8),(x9,x10),dummy)=self.pointsetList[-1]\n self.plist=self.plist[:-10]+[x1,x2,x3,x4,x5,x6,x7,x8,x9,x10]\n self.attrs[\"d\"]=\" \".join(str(x)for x in self.plist)\n \nclass DrawCanvasMixin(object):\n def setTool(self,tool):\n if self.mouseMode ==MouseMode.DRAW:self._endDraw()\n self.tool=tool\n self.mouseMode=MouseMode.EDIT if tool in {\"select\",\"insertpoint\",\"deletepoint\"}else MouseMode.DRAW\n \n def _createObject(self,coords):\n colour=\"none\"if self.tool in [\"polyline\",\"bezier\",\"smoothbezier\"]else self.fillColour\n self.mouseOwner=shapetypes[self.tool](pointlist=[coords,coords],linecolour=self.penColour,linewidth=self.penWidth,fillcolour=colour)\n self.addObject(self.mouseOwner)\n self.mouseOwner.shapeType=self.tool\n \n def _drawPoint(self,event):\n if self.tool ==\"select\":return\n if self.mouseOwner:\n if isinstance(self.mouseOwner,(PolyshapeMixin,BezierMixin)):\n coords=self.getSVGcoords(event)\n self.mouseOwner._appendPoint(coords)\n else :\n self.startx=self.currentx=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n self.starty=self.currenty=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n coords=self.getSVGcoords(event)\n self._createObject(coords)\n \n def _endDraw(self,event=None ):\n if not self.mouseOwner:return\n svgobj=self.mouseOwner\n if isinstance(svgobj,(PolyshapeMixin,BezierMixin)):\n evtype=getattr(event,\"type\",None )\n if evtype ==\"dblclick\":\n svgobj.deletePoints(-2,None )\n \n elif self.mouseDetected:\n svgobj.deletePoints(-1,None )\n \n elif svgobj.pointList[0]==svgobj.pointList[1]:\n svgobj.deletePoints(None ,1)\n \n while len(svgobj.pointList)>1 and svgobj.pointList[-1]==svgobj.pointList[-2]:\n svgobj.deletePoints(-1,None )\n if len(svgobj.pointList)==1:self.deleteObject(svgobj)\n if isinstance(svgobj,SectorObject):svgobj.pointList=svgobj.pointList[:2]+svgobj.pointList[-1:]\n elif isinstance(svgobj,NonBezierMixin):\n if svgobj.pointList[0]==svgobj.pointList[1]:self.deleteObject(svgobj)\n self.mouseOwner=None\n self.mouseMode=MouseMode.EDIT\n return svgobj\n \n def _createEditHitTargets(self):\n objlist=list(self.objectDict.values())\n for obj in objlist:\n if isinstance(obj,GroupObject):continue\n if hasattr(obj,\"hitTarget\"):continue\n if hasattr(obj,\"reference\"):continue\n if isinstance(obj,UseObject):\n newobj=RectangleObject(obj.pointList,obj.angle)\n elif obj.fixed:\n continue\n elif isinstance(obj,(PolyshapeMixin,BezierMixin)):\n newobj=HitTarget(obj,self)\n else :\n if obj.style.fill !=\"none\":continue\n newobj=obj.cloneObject()\n newobj.style.strokeWidth=10 *self.scaleFactor if self.mouseDetected else 25 *self.scaleFactor\n newobj.reference=obj\n newobj.style.opacity=0\n if not isinstance(newobj,HitTarget):\n for event in MOUSEEVENTS:newobj.bind(event,self._onHitTargetMouseEvent)\n for event in TOUCHEVENTS:newobj.bind(event,self._onHitTargetTouchEvent)\n obj.hitTarget=newobj\n self.hittargets.append(newobj)\n self.addObject(newobj)\n \n def _prepareEdit(self,event):\n if self.selectedObject:self.deselectObject()\n svgobject=self.getSelectedObject(event.target.id,getGroup=False )\n if not svgobject or svgobject.fixed:return\n self.selectedObject=svgobject\n self.createHandles(svgobject)\n if self.tool ==\"insertpoint\":\n index,point=self._insertPoint(event)\n \n def createHandles(self,svgobject):\n if isinstance(svgobject,UseObject):return\n if isinstance(svgobject,BezierMixin):\n handles=[]\n for i,(point0,point1,point2)in enumerate(svgobject.pointsetList):\n handle=Handle(svgobject,i,point1,\"red\",self)\n handle.controlHandles=[]\n ch0=None if point0 is None else ControlHandle(svgobject,i,0,point0,\"green\",self)\n ch2=None if point2 is None else ControlHandle(svgobject,i,2,point2,\"green\",self)\n if ch0:\n ch0.linkedHandle=ch2 if isinstance(svgobject,SmoothBezierMixin)else None\n handle.controlHandles.append(ch0)\n if ch2:\n ch2.linkedHandle=ch0 if isinstance(svgobject,SmoothBezierMixin)else None\n handle.controlHandles.append(ch2)\n handles.append(handle)\n self.handles=GroupObject(handles)\n self <=self.handles\n self.controlhandles=GroupObject([ch for handle in handles for ch in handle.controlHandles])\n self <=self.controlhandles\n else :\n self.handles=GroupObject([Handle(svgobject,i,coords,\"red\",self)for i,coords in enumerate(svgobject.pointList)])\n self <=self.handles\n \n def _movePoint(self,event):\n x=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n y=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n dx,dy=x -self.currentx,y -self.currenty\n if \"touch\"in event.type and abs(dx)<5 and abs(dy)<5:return\n self.currentx,self.currenty=x,y\n if self.mouseMode ==MouseMode.DRAW:\n coords=self.getSVGcoords(event)\n self.mouseOwner._movePoint(coords)\n else :\n if self.mouseMode ==MouseMode.TRANSFORM:dx,dy=x -self.startx,y -self.starty\n dx,dy=dx *self.scaleFactor,dy *self.scaleFactor\n self.mouseOwner._movePoint((dx,dy))\n \n def _insertPoint(self,event):\n if not self.selectedObject:return None ,None\n try :\n index=self.objectDict[event.target.id].segmentindex\n except (KeyError,AttributeError):\n return None ,None\n self.deleteObject(self.handles)\n self.deleteObject(self.controlhandles)\n clickpoint=self.getSVGcoords(event)\n svgobject=self.selectedObject\n svgobject.insertPoint(index,clickpoint)\n self.createHandles(svgobject)\n return index,clickpoint\n \n def _deletePoint(self,index):\n if not self.selectedObject:return None\n if not isinstance(self.selectedObject,(PolyshapeMixin,BezierObject)):return None\n svgobject=self.selectedObject\n if len(svgobject.pointList)<=2:\n self.deleteSelection()\n return None\n self.deleteObject(self.handles)\n self.deleteObject(self.controlhandles)\n svgobject.deletePoint(index)\n self.createHandles(svgobject)\n return index\n \n def _endEdit(self,event):\n if self.selectedObject:\n self.selectedObject._updatehittarget()\n if self.handles:self <=self.handles\n if self.controlhandles:self <=self.controlhandles\n self.mouseOwner=None\n \n def deselectObject(self):\n if not self.selectedObject:return\n self.deleteHandles()\n self.mouseOwner=self.selectedObject=self.selectedhandle=None\n \n def deleteHandles(self):\n self.deleteObject(self.handles)\n self.handles=None\n if isinstance(self.selectedObject,BezierMixin):\n self.deleteObject(self.controlhandles)\n self.controlhandles=None\n \nclass HitTargetSegment(LineObject):\n def __init__(self,pointlist,width,reference,index):\n LineObject.__init__(self,pointlist,linewidth=width)\n self.reference=reference\n self.segmentindex=index\n self.style.opacity=0\n \nclass BezierHitTargetSegment(BezierObject):\n def __init__(self,pointsetlist,width,reference,index):\n BezierObject.__init__(self,pointsetlist,linewidth=width)\n self.reference=reference\n self.segmentindex=index\n self.style.opacity=0\n \nclass HitTarget(GroupObject):\n def __init__(self,reference,canvas):\n GroupObject.__init__(self)\n self.reference=reference\n self.style.cursor=reference.style.cursor\n self.canvas=canvas\n self._update()\n \n def _update(self):\n self.deleteAll()\n width=10 *self.canvas.scaleFactor if self.canvas.mouseDetected else 25 *self.canvas.scaleFactor\n if isinstance(self.reference,PolyshapeMixin):\n pointlist=self.reference.pointList[:]\n if isinstance(self.reference,PolygonObject):pointlist.append(pointlist[0])\n for i in range(len(pointlist)-1):\n segment=HitTargetSegment(pointlist[i:i+2],width,self.reference,i+1)\n self.addObject(segment)\n else :\n pointsetlist=self.reference.pointsetList[:]\n if isinstance(self.reference,(ClosedBezierObject,SmoothClosedBezierObject)):pointsetlist.append(pointsetlist[0])\n for i in range(len(pointsetlist)-1):\n ps0=[None ]+pointsetlist[i][1:]\n ps1=pointsetlist[i+1][:-1]+[None ]\n segment=BezierHitTargetSegment([ps0,ps1],width,self.reference,i+1)\n self.addObject(segment)\n self.canvas.deleteObject(self)\n self.canvas.addObject(self)\n \nclass Handle(PointObject):\n def __init__(self,owner,index,coords,colour,canvas):\n pointsize=7 if canvas.mouseDetected else 15\n opacity=0.4\n strokewidth=3\n PointObject.__init__(self,coords,colour,pointsize,canvas)\n self.style.strokeWidth=strokewidth\n self.style.fillOpacity=opacity\n self.owner=owner\n self.pointIndex=index\n self.canvas=canvas\n self.bind(\"mousedown\",self._select)\n self.bind(\"touchstart\",self._select)\n \n def _select(self,event):\n if event.type ==\"mousedown\"and event.button >0:return\n event.preventDefault()\n event.stopPropagation()\n if self.canvas.tool ==\"deletepoint\":\n self.canvas._deletePoint(self.pointIndex)\n return\n self.canvas.startx=self.canvas.currentx=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n self.canvas.starty=self.canvas.currenty=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n self.canvas.mouseOwner=self\n if isinstance(self.owner,BezierMixin):\n if self.canvas.selectedhandle:\n for ch in self.canvas.selectedhandle.controlHandles:ch.style.visibility=\"hidden\"\n for ch in self.controlHandles:ch.style.visibility=\"visible\"\n self.canvas.selectedhandle=self\n \n def _movePoint(self,offset):\n self.XY +=offset\n if isinstance(self.owner,BezierMixin):\n pointset=[None ,self.XY,None ]\n for ch in self.controlHandles:\n ch.XY +=offset\n pointset[ch.subindex]=ch.XY\n self.owner.setPointset(self.pointIndex,pointset)\n else :\n self.owner.setPoint(self.pointIndex,self.XY)\n \nclass ControlHandle(PointObject):\n def __init__(self,owner,index,subindex,coords,colour,canvas):\n pointsize=7 if canvas.mouseDetected else 15\n opacity=0.4\n strokewidth=3\n PointObject.__init__(self,coords,colour,pointsize,canvas)\n self.style.fillOpacity=opacity\n self.style.strokeWidth=strokewidth\n self.style.visibility=\"hidden\"\n self.owner=owner\n self.pointIndex=index\n self.subindex=subindex\n self.canvas=canvas\n self.bind(\"mousedown\",self._select)\n self.bind(\"touchstart\",self._select)\n \n def _select(self,event):\n event.stopPropagation()\n self.canvas.startx=self.canvas.currentx=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n self.canvas.starty=self.canvas.currenty=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n self.canvas.mouseOwner=self\n \n def _movePoint(self,offset):\n self.XY +=offset\n pointset=self.owner.pointsetList[self.pointIndex]\n pointset[self.subindex]=self.XY\n if self.linkedHandle:\n point=pointset[1]\n thisoffset=self.XY -point\n otheroffset=self.linkedHandle.XY -point\n newoffset=thisoffset *(otheroffset.length()/thisoffset.length())\n newothercoords=point -newoffset\n pointset[self.linkedHandle.subindex]=newothercoords\n self.linkedHandle.XY=newothercoords\n self.owner.setPointset(self.pointIndex,pointset)\n \nnonbezier=[LineObject,RectangleObject,EllipseObject,CircleObject,SectorObject,PolylineObject,PolygonObject,ImageObject]\nfor cls in nonbezier:\n cls.__bases__=cls.__bases__+(NonBezierMixin,)\n \npolyshape=[PolylineObject,PolygonObject,SectorObject]\nfor cls in polyshape:\n cls.__bases__=cls.__bases__+(PolyshapeMixin,)\n \nbezier=[BezierObject,ClosedBezierObject,SmoothBezierObject,SmoothClosedBezierObject]\nfor cls in bezier:\n cls.__bases__=cls.__bases__+(BezierMixin,)\n \nCanvasObject.__bases__=CanvasObject.__bases__+(DrawCanvasMixin,)\n", ["brySVG.dragcanvas"]], "brySVG.dragcanvas": [".py", "#!/usr/bin/python\n\n\n\n\n\n\n\n\n\n\nimport time\nfrom browser import document,alert,window\nimport browser.svg as svg\nimport browser.html as html\nfrom math import sin,cos,atan2,pi,hypot,floor,log10\nsvgbase=svg.svg(width=0,height=0)\nbasepoint=svgbase.createSVGPoint()\nlasttaptime=0\nMOUSEEVENTS=[\"mousedown\",\"mousemove\",\"mouseup\",\"click\"]\nTOUCHEVENTS=[\"touchstart\",\"touchmove\",\"touchend\"]\n\nclass Enum(list):\n def __init__(self,name,string):\n values=string.split()\n for i,value in enumerate(values):\n setattr(self,value,i)\n self.append(i)\n \nMouseMode=Enum('MouseMode','NONE DRAG TRANSFORM DRAW EDIT PAN')\nTransformType=Enum('TransformType','NONE TRANSLATE ROTATE XSTRETCH YSTRETCH ENLARGE')\nPosition=Enum('Position','CONTAINS INSIDE OVERLAPS EQUAL DISJOINT')\n\nclass ObjectMixin(object):\n ''\n \n def cloneObject(self):\n ''\n\n \n if isinstance(self,GroupObject):\n newobject=self.__class__()\n for obj in self.objectList:\n if isinstance(obj,ObjectMixin):\n newobj=obj.cloneObject()\n newobject <=newobj\n newobj.group=newobject\n newobject.objectList.append(newobj)\n elif isinstance(self,ObjectMixin):\n if isinstance(self,ImageObject)and not self.imageloaded:raise RuntimeError(\"ImageObject cannot be cloned until fully loaded\")\n \n newobject=self.__class__()\n for attrname in [\"XY\",\"pointList\",\"pointsetList\",\"angle\",\"fixed\",\"rotatestring\",\"centre\",\"_width\",\"_height\",\n \"currentAspectRatio\",\"imageWidth\",\"imageHeight\",\"imageAspectRatio\",\"imageloaded\",\n \"startangle\",\"endangle\",\"radius\"]:\n attr=getattr(self,attrname,\"NO_SUCH_ATTRIBUTE\")\n if attr ==\"NO_SUCH_ATTRIBUTE\":continue\n newattr=list(attr)if isinstance(attr,list)else attr\n setattr(newobject,attrname,newattr)\n else :\n return None\n \n \n for key in self.attrs:\n value=self.attrs[key]\n newobject.attrs[key]=value\n newobject.id=\"\"\n return newobject\n \n def setPointList(self,pointlist):\n ''\n self.pointList=[Point(coords)for coords in pointlist]\n if isinstance(self,BezierObject):self.pointsetList=self._getpointsetlist(pointlist)\n self._update()\n self._updatehittarget()\n \n def setStyle(self,attribute,value):\n ''\n self.style={attribute:value}\n \n def _updatehittarget(self):\n ''\n hittarget=getattr(self,\"hitTarget\",None )\n if hittarget:\n hittarget.pointList=self.pointList\n if isinstance(self,BezierObject):hittarget.pointsetList=self.pointsetList\n if isinstance(self,(RectangleObject,EllipseObject,ImageObject,UseObject)):hittarget.angle=self.angle\n hittarget._update()\n \n def _transformedpointlist(self,matrix):\n ''\n newpointlist=[]\n for point in self.pointList:\n (basepoint.x,basepoint.y)=point\n newpt=basepoint.matrixTransform(matrix)\n newpointlist.append(Point((newpt.x,newpt.y)))\n return newpointlist\n \n def __repr__(self):\n return f\"{self.__class__}{self.id}\"\n \nclass SmoothBezierMixin(object):\n ''\n def _calculatecontrolpoints(self,points):\n ''\n [(x1,y1),(x2,y2),(x3,y3)]=points\n (dx1,dy1)=((x2 -x1),(y2 -y1))\n (dx3,dy3)=((x2 -x3),(y2 -y3))\n d1=hypot(dx1,dy1)\n d2=hypot(dx3,dy3)\n if d1 ==0 or d2 ==0:return ((x2,y2),(x2,y2))\n cos1,sin1=dx1 /d1,dy1 /d1\n cos2,sin2=dx3 /d2,dy3 /d2\n \n (c1x,c1y)=(x2 -d1 *(cos1 -cos2)/2,y2 -d1 *(sin1 -sin2)/2)\n (c2x,c2y)=(x2+d2 *(cos1 -cos2)/2,y2+d2 *(sin1 -sin2)/2)\n c1=((c1x+x2)/2,(c1y+y2)/2)\n c2=((c2x+x2)/2,(c2y+y2)/2)\n return (Point(c1),Point(c2))\n \n '''The following XxxObject classes share various common parameters and attributes.\n ###Common Parameters\n When created, as well as the paramters listed for each type of Object, they all share the following optional parameters:\n `objid`: id of the object (for referencing in the document or using `canvas.getSelectedObject(id)`\n (The following are not applicable to TextObject, WrappingTextObject, PointObject or UseObject)\n `linecolour`: colour of the \"stroke\" or outline of the shape\n `linewidth`: width of the outline of the shape (its \"stroke-width\")\n `fillcolour`: colour of the interior of the shape (not applicable to LineObject). Use \"none\" if no fill desired.\n\n ### Common Attributes\n After creation and adding to the canvas using `canvas.addObject()`, they have following attributes:\n `obj.fixed` (read/write): If set to `True`, the object cannot be dragged or transformed with the mouse (default `False`)\n `obj.canvas` (read only): A reference to the canvas which contains the object\n `obj.pointList` (read only): See individual object definitions for its meaning. (Not applicable to TextObject, WrappingTextObject or PointObject.)\n\n They also share the following methods:\n `obj.setPointList()`, `obj.setStyle()`, `obj.cloneObject`\n '''\nclass LineObject(svg.line,ObjectMixin):\n ''\n\n \n def __init__(self,pointlist=[(0,0),(0,0)],style=\"solid\",linecolour=\"black\",linewidth=1,fillcolour=\"none\",objid=None ):\n [(x1,y1),(x2,y2)]=pointlist\n \n if style ==\"faintdash1\":\n dasharray=\"10,5\"\n linecolour=\"grey\"\n elif style ==\"faintdash2\":\n dasharray=\"2,2\"\n linecolour=\"lightgrey\"\n else :\n dasharray=None\n \n svg.line.__init__(self,x1=x1,y1=y1,x2=x2,y2=y2,style={\"stroke\":linecolour,\"strokeDasharray\":dasharray,\"stroke-width\":linewidth,\"fill\":\"none\"})\n self.pointList=[Point(coords)for coords in pointlist]\n if objid:self.id=objid\n \n def _update(self):\n [(x1,y1),(x2,y2)]=self.pointList\n self.attrs[\"x1\"]=x1\n self.attrs[\"y1\"]=y1\n self.attrs[\"x2\"]=x2\n self.attrs[\"y2\"]=y2\n \nclass TextObject(svg.text,ObjectMixin):\n ''\n\n\n\n\n \n def __init__(self,string=\"\",anchorpoint=(0,0),anchorposition=1,fontsize=12,style=\"normal\",ignorescaling=False ,canvas=None ,objid=None ):\n (x,y)=anchorpoint\n stringlist=string.split(\"\\n\")\n rowcount=len(stringlist)\n if anchorposition in [3,6,9]:\n horizpos=\"end\"\n elif anchorposition in [2,5,8]:\n horizpos=\"middle\"\n else :\n horizpos=\"start\"\n lineheight=fontsize *1.2\n if ignorescaling and canvas:\n fontsize *=canvas.scaleFactor\n lineheight *=canvas.scaleFactor\n if anchorposition in [1,2,3]:\n yoffset=fontsize\n elif anchorposition in [4,5,6]:\n yoffset=fontsize -lineheight *rowcount /2\n else :\n yoffset=fontsize -lineheight *rowcount\n \n svg.text.__init__(self,stringlist[0],x=x,y=y+yoffset,font_size=fontsize,text_anchor=horizpos)\n for s in stringlist[1:]:\n self <=svg.tspan(s,x=x,dy=lineheight)\n if objid:self.id=objid\n \nclass WrappingTextObject(svg.text):\n ''\n \n def __init__(self,canvas,string,anchorpoint,width,anchorposition=1,fontsize=12,style=\"normal\",ignorescaling=False ,objid=None ):\n (x,y)=anchorpoint\n lineheight=fontsize *1.2\n if ignorescaling:\n fontsize *=canvas.scaleFactor\n lineheight *=canvas.scaleFactor\n words=string.split()\n svg.text.__init__(self,\"\",x=x,font_size=fontsize)\n canvas <=self\n tspan=svg.tspan(words.pop(0),x=x,dy=0)\n self <=tspan\n rowcount=1\n for word in words:\n tspan.text +=\" \"+word\n if tspan.getComputedTextLength()>width:\n tspan.text=tspan.text[:-len(word)-1]\n tspan=svg.tspan(word,x=x,dy=lineheight)\n self <=tspan\n rowcount +=1\n \n if anchorposition in [3,6,9]:\n horizpos=\"end\"\n elif anchorposition in [2,5,8]:\n horizpos=\"middle\"\n else :\n horizpos=\"start\"\n self.attrs[\"text-anchor\"]=horizpos\n if anchorposition in [1,2,3]:\n yoffset=fontsize\n elif anchorposition in [4,5,6]:\n yoffset=fontsize *(1 -rowcount /2)\n else :\n yoffset=fontsize *(1 -rowcount)\n self.attrs[\"y\"]=y+yoffset\n if objid:self.id=objid\n \nclass PolylineObject(svg.polyline,ObjectMixin):\n ''\n \n def __init__(self,pointlist=[(0,0)],linecolour=\"black\",linewidth=1,fillcolour=\"none\",objid=None ):\n svg.polyline.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n self.pointList=[Point(coords)for coords in pointlist]\n self._update()\n if objid:self.id=objid\n \n def _update(self):\n self.attrs[\"points\"]=\" \".join([str(point[0])+\",\"+str(point[1])for point in self.pointList])\n \nclass PolygonObject(svg.polygon,ObjectMixin):\n ''\n \n def __init__(self,pointlist=[(0,0)],linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n svg.polygon.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n self.attrs[\"points\"]=\" \".join([str(point[0])+\",\"+str(point[1])for point in pointlist])\n if objid:self.id=objid\n \n def _update(self):\n self.attrs[\"points\"]=\" \".join([str(point[0])+\",\"+str(point[1])for point in self._pointList])\n \n def __repr__(self):\n return f\"polygon {self.id}\"if self.id else f\"polygon {id(self)}\"\n \n def __str__(self):\n return self.__repr__()\n \n @property\n def pointList(self):\n if getattr(self,\"_pointList\",None )is None :\n \n P=self.points\n L=P.numberOfItems\n self._pointList=[Point([P.getItem(i).x,P.getItem(i).y])for i in range(L)]\n return self._pointList\n \n @pointList.setter\n def pointList(self,pointlist):\n self._pointList=[Point(coords)for coords in pointlist]\n self._update()\n \n def setPointList(self,pointlist):\n self._pointList=[Point(coords)for coords in pointlist]\n self._update()\n \n def cloneObject(self):\n newobject=self.__class__()\n for key in self.attrs:\n value=self.attrs[key]\n newobject.attrs[key]=value\n newobject.id=\"\"\n \n P=self.points\n P2=newobject.points\n L=P.numberOfItems\n P2.clear()\n for i in range(L):\n pt=P.getItem(i)\n P2.appendItem(pt)\n \n return newobject\n \n def _transformpoints(self,points,matrix):\n L=points.numberOfItems\n for i in range(L):\n pt=points.getItem(i)\n newpt=pt.matrixTransform(matrix)\n points.replaceItem(newpt,i)\n \nclass RectangleObject(svg.rect,ObjectMixin):\n ''\n\n\n\n\n\n\n\n\n \n def __init__(self,pointlist=None ,centre=(0,0),width=0,height=None ,angle=0,linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n svg.rect.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n self.angle=angle\n if pointlist:\n self.setPointList(pointlist)\n else :\n self.centre=Point(centre)\n if not height:height=width\n if height and not width:width=height\n self._width=width\n self._height=height\n self.setPosition()\n if objid:self.id=objid\n \n def setPosition(self,centre=None ,width=None ,height=None ,angle=None ,preserveaspectratio=False ):\n ''\n\n \n if centre:self.centre=Point(centre)\n if width:\n self._width=width\n if preserveaspectratio and not height:self._height=width *self.currentAspectRatio\n if height:\n self._height=height\n if preserveaspectratio and not width:self._width=height /self.currentAspectRatio\n if angle is not None :self.angle=angle\n \n (cx,cy)=self.centre\n self.pointList=[(cx -self._width /2,cy -self._height /2),(cx+self._width /2,cy+self._height /2)]\n t=svgbase.createSVGTransform()\n t.setRotate(self.angle,cx,cy)\n self.pointList=self._transformedpointlist(t.matrix)\n self._update()\n self._updatehittarget()\n \n def _update(self):\n [(x1,y1),(x2,y2)]=self.pointList\n (cx,cy)=((x1+x2)/2,(y1+y2)/2)\n self.centre=Point((cx,cy))\n self.rotatestring=self.style.transform=f\"translate({cx}px,{cy}px) rotate({self.angle}deg) translate({-cx}px,{-cy}px)\"\n \n t=svgbase.createSVGTransform()\n t.setRotate(-self.angle,cx,cy)\n basepointlist=self._transformedpointlist(t.matrix)\n [(x1,y1),(x2,y2)]=basepointlist\n self._width=abs(x2 -x1)\n self._height=abs(y2 -y1)\n if self._width !=0:self.currentAspectRatio=self._height /self._width\n self.attrs[\"x\"]=x2 if x2 <x1 else x1\n self.attrs[\"y\"]=y2 if y2 <y1 else y1\n self.attrs[\"width\"]=self._width\n self.attrs[\"height\"]=self._height\n \nclass EllipseObject(svg.ellipse,ObjectMixin):\n ''\n\n\n\n\n\n\n\n\n\n \n def __init__(self,pointlist=None ,centre=(0,0),width=0,height=None ,angle=0,linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n svg.ellipse.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n self.angle=angle\n if pointlist:\n self.setPointList(pointlist)\n else :\n self.centre=Point(centre)\n if not height:height=width\n if height and not width:width=height\n self._width=width\n self._height=height\n self.setPosition()\n if objid:self.id=objid\n \n def setPosition(self,centre=None ,width=None ,height=None ,angle=None ,preserveaspectratio=False ):\n ''\n\n \n if centre:self.centre=Point(centre)\n if width:\n self._width=width\n if preserveaspectratio and not height:self._height=width *self.currentAspectRatio\n if height:\n self._height=height\n if preserveaspectratio and not width:self._width=height /self.currentAspectRatio\n if angle is not None :self.angle=angle\n \n (cx,cy)=self.centre\n self.pointList=[(cx -self._width /2,cy -self._height /2),(cx+self._width /2,cy+self._height /2)]\n t=svgbase.createSVGTransform()\n t.setRotate(self.angle,cx,cy)\n self.pointList=self._transformedpointlist(t.matrix)\n self._update()\n self._updatehittarget()\n \n def _update(self):\n [(x1,y1),(x2,y2)]=self.pointList\n (cx,cy)=((x1+x2)/2,(y1+y2)/2)\n self.centre=Point((cx,cy))\n self.rotatestring=self.style.transform=f\"translate({cx}px,{cy}px) rotate({self.angle}deg) translate({-cx}px,{-cy}px)\"\n \n t=svgbase.createSVGTransform()\n t.setRotate(-self.angle,cx,cy)\n basepointlist=self._transformedpointlist(t.matrix)\n [(x1,y1),(x2,y2)]=basepointlist\n self._width=abs(x2 -x1)\n self._height=abs(y2 -y1)\n if self._width !=0:self.currentAspectRatio=self._height /self._width\n self.attrs[\"cx\"]=(x1+x2)/2\n self.attrs[\"cy\"]=(y1+y2)/2\n self.attrs[\"rx\"]=self._width /2\n self.attrs[\"ry\"]=self._height /2\n \nclass CircleObject(svg.circle,ObjectMixin):\n ''\n\n \n def __init__(self,centre=(0,0),radius=0,pointlist=None ,linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n if pointlist:\n self.pointList=[Point(coords)for coords in pointlist]\n else :\n (x,y)=centre\n self.pointList=[Point((x,y)),Point((x+radius,y))]\n svg.circle.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n self._update()\n if objid:self.id=objid\n \n def _update(self):\n [(x1,y1),(x2,y2)]=self.pointList\n self.attrs[\"cx\"]=x1\n self.attrs[\"cy\"]=y1\n self.attrs[\"r\"]=hypot(x2 -x1,y2 -y1)\n \nclass SectorObject(svg.path,ObjectMixin):\n def __init__(self,centre=(0,0),radius=0,startangle=0,endangle=0,pointlist=None ,linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n self.centre=Point(centre)\n self.radius=radius\n self.startangle=startangle\n self.endangle=endangle\n if pointlist:\n self.pointList=pointlist\n else :\n (cx,cy)=centre\n point1=Point((cx+radius *sin(startangle *pi /180),cy -radius *cos(startangle *pi /180)))\n point2=Point((cx+radius *sin(endangle *pi /180),cy -radius *cos(endangle *pi /180)))\n self.pointList=[self.centre,point1,point2]\n svg.path.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n self._update()\n if objid:self.id=objid\n \n def _update(self):\n [(x0,y0),(x1,y1)]=self.pointList[:2]\n (x2,y2)=self.pointList[-1]\n r=hypot(x1 -x0,y1 -y0)\n largeArcFlag=1 if (self.endangle -self.startangle)%360 >180 else 0\n self.attrs[\"d\"]=f\"M {x0} {y0} L {x1} {y1} A {r} {r} 0 {largeArcFlag} 1 {x2} {y2} Z\"\n \nclass UseObject(svg.use,ObjectMixin):\n ''\n\n\n\n\n\n\n\n\n \n def __init__(self,href=None ,origin=None ,centre=(0,0),width=None ,height=None ,angle=0,scale=None ,objid=None ):\n svg.use.__init__(self,href=href)\n document <=svgbase\n tempgroup=svg.g()\n tempgroup <=self\n svgbase <=tempgroup\n bbox=tempgroup.getBBox()\n svgbase.removeChild(tempgroup)\n document.body.removeChild(svgbase)\n self._origwidth=bbox.width\n self._origheight=bbox.height\n self._origaspectratio=self._origheight /self._origwidth\n (cx,cy)=(bbox.x+bbox.width /2,bbox.y+bbox.height /2)\n self.originoffset=Point((-cx,-cy))\n \n if width and height:\n (self._width,self._height)=(width,height)\n elif width:\n (self._width,self._height)=(width,width *self._origaspectratio)\n elif height:\n (self._width,self._height)=(height /self._origaspectratio,height)\n else :\n (self._width,self._height)=(self._origwidth,self._origheight)\n \n \n if origin:\n scalefactors=(self._width /self._origwidth,self._height /self._origheight)\n self.centre=Point(origin)-self.originoffset *scalefactors\n else :\n self.centre=Point(centre)\n self.angle=angle\n self.setPosition()\n if objid:self.id=objid\n \n def setPosition(self,origin=None ,centre=None ,width=None ,height=None ,angle=None ,preserveaspectratio=False ):\n ''\n\n\n \n if width:\n self._width=width\n if preserveaspectratio and not height:self._height=width *self.currentAspectRatio\n if height:\n self._height=height\n if preserveaspectratio and not width:self._width=height /self.currentAspectRatio\n if self._width !=0:self.currentAspectRatio=self._height /self._width\n \n if origin:\n scalefactors=(self._width /self._origwidth,self._height /self._origheight)\n self.centre=Point(origin)-self.originoffset *scalefactors\n elif centre:\n self.centre=Point(centre)\n if angle is not None :self.angle=angle\n \n (cx,cy)=self.centre\n self.pointList=[(cx -self._width /2,cy -self._height /2),(cx+self._width /2,cy+self._height /2)]\n t=svgbase.createSVGTransform()\n t.setRotate(self.angle,cx,cy)\n self.pointList=self._transformedpointlist(t.matrix)\n self._update()\n self._updatehittarget()\n \n def _update(self):\n [(x1,y1),(x2,y2)]=self.pointList\n (cx,cy)=((x1+x2)/2,(y1+y2)/2)\n self.centre=Point((cx,cy))\n self.rotatestring=f\"translate({cx}px,{cy}px) rotate({self.angle}deg) translate({-cx}px,{-cy}px)\"\n (xscale,yscale)=(self._width /self._origwidth,self._height /self._origheight)\n self.scalestring=f\"translate({cx}px,{cy}px) scale({xscale},{yscale}) translate({-cx}px,{-cy}px)\"\n self.style.transform=self.rotatestring+self.scalestring\n self.origin=self.centre+self.originoffset\n (self.attrs[\"x\"],self.attrs[\"y\"])=self.origin\n \nclass ImageObject(svg.image,ObjectMixin):\n ''\n\n\n\n\n\n\n\n\n\n \n def __init__(self,href=None ,pointlist=None ,centre=(0,0),width=0,height=None ,angle=0,objid=None ):\n def initialise(event):\n nonlocal width,height\n self.imageWidth=img.naturalWidth\n self.imageHeight=img.naturalHeight\n \n self.imageAspectRatio=self.imageHeight /self.imageWidth\n \n self.attrs[\"preserveAspectRatio\"]=\"none\"\n self.angle=angle\n if pointlist:\n self.setPointList(pointlist)\n else :\n self.centre=Point(centre)\n if not width and not height:\n width=self.imageWidth\n height=self.imageHeight\n elif not height:\n height=width *self.imageAspectRatio\n elif not width:\n width=height /self.imageAspectRatio\n self._width=width\n self._height=height\n self._setuppointlist()\n self.style.visibility=\"visible\"\n self.imageloaded=True\n loadcomplete=window.Event.new(\"loadcomplete\")\n self.dispatchEvent(loadcomplete)\n \n super().__init__()\n if objid:self.id=objid\n self.imageloaded=False\n self.style.visibility=\"hidden\"\n if not href:return\n self.attrs[\"href\"]=href\n img=html.IMG()\n img.bind(\"load\",initialise)\n img.attrs[\"src\"]=href\n \n def setPosition(self,centre=None ,width=None ,height=None ,angle=None ,preserveaspectratio=False ):\n ''\n\n \n def set_position(event=None ):\n \n if centre:self.centre=Point(centre)\n if width:\n self._width=width\n if preserveaspectratio and not height:self._height=width *self.currentAspectRatio\n if height:\n self._height=height\n if preserveaspectratio and not width:self._width=height /self.currentAspectRatio\n if angle is not None :self.angle=angle\n self._setuppointlist()\n \n if self.imageloaded:\n set_position()\n else :\n self.bind(\"loadcomplete\",set_position)\n \n def _setuppointlist(self):\n (cx,cy)=self.centre\n self.pointList=[(cx -self._width /2,cy -self._height /2),(cx+self._width /2,cy+self._height /2)]\n t=svgbase.createSVGTransform()\n t.setRotate(self.angle,cx,cy)\n self.pointList=self._transformedpointlist(t.matrix)\n self._update()\n self._updatehittarget()\n \n def _update(self):\n [(x1,y1),(x2,y2)]=self.pointList\n (cx,cy)=((x1+x2)/2,(y1+y2)/2)\n self.centre=Point((cx,cy))\n self.rotatestring=self.style.transform=f\"translate({cx}px,{cy}px) rotate({self.angle}deg) translate({-cx}px,{-cy}px)\"\n \n t=svgbase.createSVGTransform()\n t.setRotate(-self.angle,cx,cy)\n basepointlist=self._transformedpointlist(t.matrix)\n [(x1,y1),(x2,y2)]=basepointlist\n self._width=abs(x2 -x1)\n self._height=abs(y2 -y1)\n if self._width !=0:self.currentAspectRatio=self._height /self._width\n self.attrs[\"x\"]=x2 if x2 <x1 else x1\n self.attrs[\"y\"]=y2 if y2 <y1 else y1\n self.attrs[\"width\"]=self._width\n self.attrs[\"height\"]=self._height\n \nclass BezierObject(svg.path,ObjectMixin):\n ''\n\n\n\n\n \n def __init__(self,pointsetlist=None ,pointlist=[(0,0),(0,0)],linecolour=\"black\",linewidth=1,fillcolour=\"none\",objid=None ):\n def toPoint(coords):\n return None if coords is None else Point(coords)\n svg.path.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n if pointsetlist:\n self.pointList=[Point(pointset[1])for pointset in pointsetlist]\n else :\n self.pointList=[Point(coords)for coords in pointlist]\n pointsetlist=self._getpointsetlist(self.pointList)\n self.pointsetList=[[toPoint(coords)for coords in pointset]for pointset in pointsetlist]\n self._update()\n if objid:self.id=objid\n \n def _getpointsetlist(self,pointlist):\n pointsetlist=[[None ,pointlist[0],(pointlist[0]+pointlist[1])/2]]\n for i in range(1,len(pointlist)-1):\n pointsetlist.append([(pointlist[i -1]+pointlist[i])/2,pointlist[i],(pointlist[i]+pointlist[i+1])/2])\n pointsetlist.append([(pointlist[-2]+pointlist[-1])/2,pointlist[-1],None ])\n return pointsetlist\n \n def _updatepointsetlist(self):\n if len(self.pointList)==2:\n self.pointsetList=[[None ]+self.pointList,self.pointList+[None ]]\n else :\n cpoint=(self.pointList[-1]+self.pointList[-2])/2\n self.pointsetList[-1]=[cpoint,self.pointList[-1],None ]\n self.pointsetList[-2][2]=cpoint\n \n def _update(self):\n (dummy,(x1,y1),(c1x,c1y))=self.pointsetList[0]\n ((c2x,c2y),(x2,y2),dummy)=self.pointsetList[-1]\n self.plist=[\"M\",x1,y1,\"C\",c1x,c1y]+[x for p in self.pointsetList[1:-1]for c in p for x in c]+[c2x,c2y,x2,y2]\n self.attrs[\"d\"]=\" \".join(str(x)for x in self.plist)\n \nclass ClosedBezierObject(BezierObject):\n ''\n\n\n\n \n def __init__(self,pointsetlist=None ,pointlist=[(0,0),(0,0)],linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n svg.path.__init__(self,style={\"stroke\":linecolour,\"stroke-width\":linewidth,\"fill\":fillcolour})\n if pointsetlist:\n self.pointList=[Point(pointset[1])for pointset in pointsetlist]\n else :\n self.pointList=[Point(coords)for coords in pointlist]\n pointsetlist=self._getpointsetlist(self.pointList)\n self.pointsetList=[[Point(coords)for coords in pointset]for pointset in pointsetlist]\n self._update()\n if objid:self.id=objid\n \n def _getpointsetlist(self,pointlist):\n pointsetlist=[[(pointlist[0]+pointlist[-1])/2,pointlist[0],(pointlist[0]+pointlist[1])/2]]\n for i in range(1,len(pointlist)-1):\n pointsetlist.append([(pointlist[i -1]+pointlist[i])/2,pointlist[i],(pointlist[i]+pointlist[i+1])/2])\n pointsetlist.append([(pointlist[-2]+pointlist[-1])/2,pointlist[-1],(pointlist[0]+pointlist[-1])/2])\n return pointsetlist\n \n def _updatepointsetlist(self):\n if len(self.pointList)==2:\n self.pointsetList=self._getpointsetlist(self.pointList)\n else :\n cpoint1,cpoint2=(self.pointList[-1]+self.pointList[-2])/2,(self.pointList[-1]+self.pointList[0])/2\n self.pointsetList[-1]=(cpoint1,self.pointList[-1],cpoint2)\n self.pointsetList[-2][2]=cpoint1\n self.pointsetList[0][0]=cpoint2\n \n def _update(self):\n ((c1x,c1y),(x,y),(c2x,c2y))=self.pointsetList[0]\n self.plist=[\"M\",x,y,\"C\",c2x,c2y]+[x for p in self.pointsetList[1:]for c in p for x in c]+[c1x,c1y,x,y]\n self.attrs[\"d\"]=\" \".join(str(x)for x in self.plist)\n \nclass SmoothBezierObject(SmoothBezierMixin,BezierObject):\n ''\n\n \n def __init__(self,pointlist=[(0,0),(0,0)],linecolour=\"black\",linewidth=1,fillcolour=\"none\",objid=None ):\n self.pointList=[Point(coords)for coords in pointlist]\n pointsetlist=self._getpointsetlist(self.pointList)\n BezierObject.__init__(self,pointsetlist,linecolour=linecolour,linewidth=linewidth,fillcolour=fillcolour,objid=objid)\n \n def _getpointsetlist(self,pointlist):\n if len(pointlist)==2:return [[None ]+pointlist,pointlist+[None ]]\n for i in range(1,len(pointlist)-1):\n (c1,c2)=self._calculatecontrolpoints(pointlist[i -1:i+2])\n if i ==1:\n pointsetlist=[[None ,pointlist[0],(pointlist[0]+c1)/2]]\n pointsetlist.append([c1,pointlist[i],c2])\n pointsetlist.append([(pointlist[-1]+c2)/2,pointlist[-1],None ])\n return pointsetlist\n \n def _updatepointsetlist(self):\n if len(self.pointList)==2:\n self.pointsetList=[[None ]+self.pointList,self.pointList+[None ]]\n else :\n (c1,c2)=self._calculatecontrolpoints(self.pointList[-3:])\n self.pointsetList[-1]=[(self.pointList[-1]+c2)/2,self.pointList[-1],None ]\n self.pointsetList[-2]=[c1,self.pointList[-2],c2]\n \nclass SmoothClosedBezierObject(SmoothBezierMixin,ClosedBezierObject):\n ''\n\n\n \n def __init__(self,pointlist=[(0,0),(0,0)],linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n self.pointList=[Point(coords)for coords in pointlist]\n pointsetlist=self._getpointsetlist(self.pointList)\n ClosedBezierObject.__init__(self,pointsetlist,linecolour=linecolour,linewidth=linewidth,fillcolour=fillcolour,objid=objid)\n \n def _getpointsetlist(self,pointlist):\n pointlist=[pointlist[-1]]+pointlist[:]+[pointlist[0]]\n pointsetlist=[]\n for i in range(1,len(pointlist)-1):\n (c1,c2)=self._calculatecontrolpoints(pointlist[i -1:i+2])\n pointsetlist.append([c1,pointlist[i],c2])\n return pointsetlist\n \n def _updatepointsetlist(self):\n if len(self.pointList)==2:\n self.pointsetList=self._getpointsetlist(self.pointList)\n else :\n L=len(self.pointList)\n pointlist=self.pointList[:]+self.pointList[:2]\n for j in range(L -2,L+1):\n (c1,c2)=self._calculatecontrolpoints(pointlist[j -1:j+2])\n self.pointsetList[j %L]=[c1,pointlist[j],c2]\n \nclass PointObject(svg.circle,ObjectMixin):\n ''\n\n \n def __init__(self,XY=(0,0),colour=\"black\",pointsize=2,canvas=None ,objid=None ):\n (x,y)=XY\n sf=canvas.scaleFactor if canvas else 1\n svg.circle.__init__(self,cx=x,cy=y,r=pointsize *sf,style={\"stroke\":colour,\"stroke-width\":1,\"fill\":colour,\"vector-effect\":\"non-scaling-stroke\"})\n self._XY=None\n self.XY=Point(XY)\n if objid:self.id=objid\n \n def _update(self):\n pass\n \n @property\n def XY(self):\n return self._XY\n \n @XY.setter\n def XY(self,XY):\n self._XY=Point(XY)\n self.attrs[\"cx\"]=self._XY[0]\n self.attrs[\"cy\"]=self._XY[1]\n \nclass RegularPolygon(PolygonObject):\n ''\n\n\n\n \n def __init__(self,sidecount=0,centre=None ,radius=None ,startpoint=None ,sidelength=None ,offsetangle=0,linecolour=\"black\",linewidth=1,fillcolour=\"yellow\",objid=None ):\n pointlist=[]\n if sidecount >0:\n angle=2 *pi /sidecount\n radoffset=offsetangle *pi /180\n if not radius:radius=sidelength /(2 *sin(pi /sidecount))\n if not centre:\n (x,y)=startpoint\n centre=(x -radius *sin(radoffset),y+radius *cos(radoffset))\n (cx,cy)=centre\n for i in range(sidecount):\n t=radoffset+i *angle\n pointlist.append(Point((cx+radius *sin(t),cy -radius *cos(t))))\n PolygonObject.__init__(self,pointlist,linecolour,linewidth,fillcolour,objid)\n if objid:self.id=objid\n \nclass GroupObject(svg.g,ObjectMixin):\n ''\n\n\n\n\n\n\n\n \n def __init__(self,objlist=[],objid=None ):\n svg.g.__init__(self)\n if not isinstance(objlist,list):objlist=[objlist]\n self.objectList=[]\n self._canvas=None\n for obj in objlist:\n self.addObject(obj)\n if objid:self.id=objid\n \n def addObject(self,svgobject):\n canvas=self.canvas\n if canvas is not None :canvas.addObject(svgobject)\n self <=svgobject\n svgobject.group=self\n self.objectList.append(svgobject)\n \n def addObjects(self,objectlist):\n canvas=self.canvas\n for obj in objectlist:\n if canvas is not None :canvas.addObject(obj)\n self <=obj\n obj.group=self\n self.objectList.append(obj)\n \n def _update(self):\n pass\n \n def removeObject(self,svgobject):\n if not self.contains(svgobject):return\n self.removeChild(svgobject)\n self.objectList.remove(svgobject)\n svgobject.group=None\n try :\n del self.canvas.objectDict[svgobject.id]\n except (AttributeError,KeyError):\n pass\n \n def deleteAll(self):\n if self.canvas:\n for obj in self.objectList:del self.canvas.objectDict[obj.id]\n while self.firstChild:self.removeChild(self.firstChild)\n self.objectList=[]\n \n def setStyle(self,attribute,value):\n for obj in self.objectList:\n obj.setStyle(attribute,value)\n \n @property\n def canvas(self):\n return self._canvas\n \n @canvas.setter\n def canvas(self,canvas):\n self._canvas=canvas\n for obj in self.objectList:obj.canvas=canvas\n \n @property\n def fixed(self):\n return self._fixed\n \n @fixed.setter\n def fixed(self,fixedvalue):\n self._fixed=fixedvalue\n for obj in self.objectList:obj.fixed=fixedvalue\n \nclass Button(GroupObject):\n ''\n\n\n\n\n\n\n\n \n def __init__(self,position,size,text,onclick,fontsize=None ,fillcolour=\"lightgrey\",canvas=None ,objid=None ):\n GroupObject.__init__(self)\n if objid:self.id=objid\n (x,y),(width,height)=position,size\n self.button=RectangleObject([(x,y),(x+width,y+height)],fillcolour=fillcolour)\n self.button.attrs[\"rx\"]=height /3\n rowcount=text.count(\"\\n\")+1\n if not fontsize:fontsize=height *0.75 /rowcount\n self.label=TextObject(text,(x+width /2,y+height /2 -fontsize /8),anchorposition=5,fontsize=fontsize)\n self.addObjects([self.button,self.label])\n self.fixed=True\n self.bind(\"mousedown\",self._onMouseDown)\n self.bind(\"mouseup\",self._onMouseUp)\n self.bind(\"click\",onclick)\n self.bind(\"touchstart\",onclick)\n self.attrs[\"cursor\"]=\"pointer\"\n \n def _onMouseDown(self,event):\n event.stopPropagation()\n \n def _onMouseUp(self,event):\n event.stopPropagation()\n \n def setFillColour(self,colour):\n self.button.style.fill=colour\n \nclass ImageButton(GroupObject):\n ''\n\n\n \n def __init__(self,position,size,image,onclick,fontsize=None ,fillcolour=\"lightgrey\",canvas=None ,objid=None ):\n GroupObject.__init__(self)\n if objid:self.id=objid\n (x,y),(width,height)=position,size\n self.button=RectangleObject([(x,y),(x+width,y+height)],fillcolour=fillcolour)\n self.button.attrs[\"rx\"]=height /3\n image.style.transform=f\"translate({x+width/2}px,{y+height/2}px)\"\n if canvas:\n canvas <=image\n bbox=image.getBBox()\n canvas.removeChild(image)\n scalefactor=min(width /bbox.width,height /bbox.height)*0.7\n image.style.transform +=f\" scale({scalefactor})\"\n self.addObjects([self.button,image])\n self.fixed=True\n self.bind(\"mousedown\",self._onMouseDown)\n self.bind(\"mouseup\",self._onMouseUp)\n self.bind(\"click\",onclick)\n self.bind(\"touchstart\",onclick)\n self.attrs[\"cursor\"]=\"pointer\"\n \n def _onMouseDown(self,event):\n event.stopPropagation()\n \n def _onMouseUp(self,event):\n event.stopPropagation()\n \n def setFillColour(self,colour):\n self.button.style.fill=colour\n \nclass Definitions(svg.defs):\n ''\n\n \n def __init__(self,objlist=[],filename=None ):\n svg.defs.__init__(self)\n if filename:\n self.innerHTML=open(filename).read()\n for obj in objlist:self <=obj\n \nclass CanvasObject(svg.svg):\n ''\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n \n def __init__(self,width=None ,height=None ,colour=\"white\",objid=None ):\n svg.svg.__init__(self,style={\"backgroundColor\":colour})\n if width:self.style.width=width\n if height:self.style.height=height\n self.id=objid if objid else f\"canvas{id(self)}\"\n self.objectDict={}\n \n \n self.mouseMode=MouseMode.NONE\n self.vertexSnap=False\n self.snapDistance=10\n self.lineWidthScaling=True\n \n self.edgeSnap=False\n self.snapAngle=10\n self.transformTypes=TransformType\n self.penColour=\"black\"\n self.fillColour=\"yellow\"\n self.penWidth=3\n \n \n self.scaleFactor=1\n self.mouseDetected=False\n self.mouseOwner=None\n self.selectedObject=None\n self.dragStartCoords=None\n self.viewWindow=None\n self.tool=\"select\"\n \n \n self.panning=False\n self.centre=None\n self.nextid=0\n self.objectDict={}\n self.hittargets=[]\n self.handles=None\n self.controlhandles=None\n self.transformHandles=[]\n self.selectedhandle=None\n self.transformorigin=None\n self.transformBBox=RectangleObject(linecolour=\"blue\",fillcolour=\"none\")\n self.transformBBox.style.vectorEffect=\"non-scaling-stroke\"\n self.rotateLine=LineObject(linecolour=\"blue\")\n self.rotateLine.style.vectorEffect=\"non-scaling-stroke\"\n self.attrs[\"preserveAspectRatio\"]=\"xMidYMid meet\"\n \n self.bind(\"mousedown\",self._onMouseDown)\n self.bind(\"mousemove\",self._onMouseMove)\n self.bind(\"mouseup\",self._onLeftUp)\n self.bind(\"touchstart\",self._onTouchStart)\n self.bind(\"touchmove\",self._onMouseMove)\n self.bind(\"touchend\",self._onLeftUp)\n self.bind(\"dragstart\",self._onDragStart)\n self.bind(\"dblclick\",self._onDoubleClick)\n self.bind(\"contextmenu\",self._onRightClick)\n self.bind(\"wheel\",self._onWheel)\n document.bind(\"keydown\",self._onKeyDown)\n \n \n def setViewBox(self,pointlist):\n ''\n\n\n \n ((x1,y1),(x2,y2))=pointlist\n self.attrs[\"viewBox\"]=f\"{x1} {y1} {x2-x1} {y2-y1}\"\n self.viewBoxRect=[Point((x1,y1)),Point((x2,y2))]\n self.centre=Point(((x1+x2)/2,(y1+y2)/2))\n self.xScaleFactor,self.yScaleFactor=self._getScaleFactors()\n self.scaleFactor=max(self.xScaleFactor,self.yScaleFactor)\n bcr=self.getBoundingClientRect()\n pt=self.createSVGPoint()\n (pt.x,pt.y)=(bcr.left,bcr.top)\n SVGpt=pt.matrixTransform(self.getScreenCTM().inverse())\n (x1,y1)=(SVGpt.x,SVGpt.y)\n (pt.x,pt.y)=(bcr.left+bcr.width,bcr.top+bcr.height)\n SVGpt=pt.matrixTransform(self.getScreenCTM().inverse())\n (x2,y2)=(SVGpt.x,SVGpt.y)\n self.viewWindow=[Point((x1,y1)),Point((x2,y2))]\n return self.viewWindow\n \n def _getDimensions(self):\n ''\n\n \n bcr=self.getBoundingClientRect()\n self.attrs[\"width\"]=bcr.width\n self.attrs[\"height\"]=bcr.height\n return bcr.width,bcr.height\n \n def fitContents(self):\n ''\n \n bbox=self.getBBox()\n if bbox.width ==0 or bbox.height ==0:return\n wmargin,hmargin=bbox.width /50,bbox.height /50\n self.viewWindow=self.setViewBox(((bbox.x -wmargin,bbox.y -hmargin),(bbox.x+bbox.width+wmargin,bbox.y+bbox.height+hmargin)))\n return self.viewWindow\n \n def getSVGcoords(self,event):\n ''\n x=event.changedTouches[0].clientX if \"touch\"in event.type else event.clientX\n y=event.changedTouches[0].clientY if \"touch\"in event.type else event.clientY\n pt=self.createSVGPoint()\n (pt.x,pt.y)=(x,y)\n SVGpt=pt.matrixTransform(self.getScreenCTM().inverse())\n return Point((SVGpt.x,SVGpt.y))\n \n def addObject(self,svgobject,fixed=False ):\n ''\n\n\n\n\n\n \n def AddToDict(svgobj):\n if not svgobj.id:\n svgobj.id=f\"{self.id}_id{self.nextid}\"\n self.nextid +=1\n self.objectDict[svgobj.id]=svgobj\n if not getattr(svgobj.style,\"vectorEffect\",None ):\n if self.lineWidthScaling is False :svgobj.style.vectorEffect=\"non-scaling-stroke\"\n if isinstance(svgobj,GroupObject):\n for obj in svgobj.objectList:\n AddToDict(obj)\n if not hasattr(svgobject,\"fixed\"):svgobject.fixed=fixed\n self <=svgobject\n AddToDict(svgobject)\n svgobject.canvas=self\n return svgobject\n \n def addObjects(self,objectlist,fixed=False ):\n ''\n for obj in objectlist:\n if isinstance(obj,(list,tuple)):\n self.addObjects(obj,fixed=fixed)\n else :\n self.addObject(obj,fixed=fixed)\n \n def deleteObject(self,svgobject):\n ''\n def deletefromdict(svgobj):\n if isinstance(svgobj,GroupObject):\n for obj in svgobj.objectList:\n deletefromdict(obj)\n hittarget=getattr(svgobj,\"hitTarget\",None )\n if hittarget:self.deleteObject(hittarget)\n if svgobj.id in self.objectDict:del self.objectDict[svgobj.id]\n \n if not self.contains(svgobject):return\n self.removeChild(svgobject)\n deletefromdict(svgobject)\n \n def deleteAll(self,event=None ):\n ''\n while self.firstChild:\n self.removeChild(self.firstChild)\n self.objectDict={}\n \n def deleteSelection(self):\n ''\n if self.selectedObject:\n if self.handles:self.deleteObject(self.handles)\n if self.controlhandles:self.deleteObject(self.controlhandles)\n hittarget=getattr(self.selectedObject,\"hitTarget\",None )\n if hittarget:self.deleteObject(hittarget)\n self.deleteObject(self.selectedObject)\n self.selectedObject=self.handles=self.controlhandles=None\n \n def translateObject(self,svgobject,offset):\n ''\n \n offset=Point(offset)\n if isinstance(svgobject,GroupObject):\n for obj in svgobject.objectList:\n self.translateObject(obj,offset)\n boundary=getattr(svgobject,\"boundary\",None )\n if boundary:self.translateObject(boundary,offset)\n elif isinstance(svgobject,PointObject):\n svgobject.XY +=offset\n else :\n svgobject.pointList=[point+offset for point in svgobject.pointList]\n if isinstance(svgobject,BezierObject):\n svgobject.pointsetList=[(p1+offset,p2+offset,p3+offset)for (p1,p2,p3)in svgobject.pointsetList]\n svgobject._update()\n svgobject._updatehittarget()\n if isinstance(svgobject,PolygonObject):svgobject._segments=None\n \n \n def rotateElement(self,element,angle,centre=None ):\n ''\n \n if not centre:\n bbox=element.getBBox()\n centre=(bbox.x+bbox.width /2,bbox.y+bbox.height /2)\n (cx,cy)=centre\n transformstring=f\"translate({cx}px,{cy}px) rotate({angle}deg) translate({-cx}px,{-cy}px)\"\n element.style.transform=transformstring+element.style.transform\n t=svgbase.createSVGTransform()\n t.setRotate(angle,*centre)\n \n return t.matrix\n \n def scaleElement(self,element,xscale,yscale=None ):\n ''\n \n if not yscale:yscale=xscale\n transformstring=f\"scale({xscale},{yscale})\"\n element.style.transform=transformstring+element.style.transform\n t=svgbase.createSVGTransform()\n t.setScale(xscale,yscale)\n \n return t.matrix\n \n def translateElement(self,element,vector):\n ''\n (dx,dy)=vector\n transformstring=f\"translate({dx}px,{dy}px)\"\n element.style.transform=transformstring+element.style.transform\n t=svgbase.createSVGTransform()\n t.setTranslate(*vector)\n \n return t.matrix\n \n \n @property\n def mouseMode(self):\n return self._mouseMode\n \n @mouseMode.setter\n def mouseMode(self,mm):\n if mm in [MouseMode.DRAG,MouseMode.EDIT,MouseMode.TRANSFORM]:\n self.createHitTargets()\n currentmm=getattr(self,\"_mouseMode\",None )\n if currentmm ==mm:return\n if currentmm ==MouseMode.TRANSFORM:self.hideTransformHandles()\n elif currentmm ==MouseMode.EDIT:self.deselectObject()\n self._mouseMode=mm\n if mm in [MouseMode.DRAG,MouseMode.PAN,MouseMode.TRANSFORM]:\n self.tool=\"select\"\n \n @property\n def lineWidthScaling(self):\n return self._lineWidthScaling\n \n @lineWidthScaling.setter\n def lineWidthScaling(self,lws):\n currentlws=getattr(self,\"_lineWidthScaling\",None )\n if currentlws ==lws:return\n self._lineWidthScaling=lws\n for objid in self.objectDict:\n self.objectDict[objid].style.vectorEffect=\"none\"if lws else \"non-scaling-stroke\"\n \n def _getScaleFactors(self):\n ''\n width,height=self._getDimensions()\n \n vbleft,vbtop,vbwidth,vbheight=[float(x)for x in self.attrs[\"viewBox\"].split()]\n xScaleFactor=vbwidth /width if width !=0 else 1\n yScaleFactor=vbheight /height if height !=0 else 1\n return xScaleFactor,yScaleFactor\n \n def createHitTargets(self):\n try :\n self._createEditHitTargets()\n except AttributeError:\n self._createHitTargets()\n \n def _createHitTargets(self):\n objlist=list(self.objectDict.values())\n for obj in objlist:\n if isinstance(obj,GroupObject):continue\n if hasattr(obj,\"hitTarget\"):continue\n if hasattr(obj,\"reference\"):continue\n if isinstance(obj,UseObject):\n newobj=RectangleObject(pointlist=obj.pointList,angle=obj.angle)\n elif obj.style.fill !=\"none\"or obj.fixed:\n continue\n else :\n newobj=obj.cloneObject()\n newobj.style.strokeWidth=10 *self.scaleFactor if self.mouseDetected else 25 *self.scaleFactor\n newobj.style.opacity=0\n for event in MOUSEEVENTS:newobj.bind(event,self._onHitTargetMouseEvent)\n for event in TOUCHEVENTS:newobj.bind(event,self._onHitTargetTouchEvent)\n newobj.reference=obj\n obj.hitTarget=newobj\n self.hittargets.append(newobj)\n self.addObject(newobj)\n \n def _onWheel(self,event):\n if self.mouseMode ==MouseMode.PAN:\n event.preventDefault()\n zoomfactor=0.9 if event.deltaY <0 else 1.1\n newviewbox=[self.centre+zoomfactor *(point -self.centre)for point in self.viewBoxRect]\n self.setViewBox(newviewbox)\n \n def _onRightClick(self,event):\n \n pass\n \n def _onDragStart(self,event):\n event.preventDefault()\n \n def _onTouchStart(self,event):\n event.preventDefault()\n global lasttaptime\n latesttaptime=time.time()\n if event.touches.length ==1 and latesttaptime -lasttaptime <0.3:\n for function in self.events(\"dblclick\"):\n function(event)\n else :\n self._onLeftDown(event)\n lasttaptime=latesttaptime\n \n def _onMouseDown(self,event):\n event.preventDefault()\n if not self.mouseDetected:\n self.mouseDetected=True\n for obj in self.objectDict.values():\n if hasattr(obj,\"reference\"):\n if isinstance(obj.reference,UseObject):continue\n obj.style.strokeWidth=10 *self.scaleFactor\n if event.button >0:return\n self._onLeftDown(event)\n \n def _onLeftDown(self,event):\n if self.mouseMode ==MouseMode.DRAG:\n self._prepareDrag(event)\n elif self.mouseMode ==MouseMode.TRANSFORM:\n self._prepareTransform(event)\n elif self.mouseMode ==MouseMode.DRAW:\n self._drawPoint(event)\n elif self.mouseMode ==MouseMode.EDIT:\n self._prepareEdit(event)\n elif self.mouseMode ==MouseMode.PAN:\n self._preparePan(event)\n \n def _onMouseMove(self,event):\n event.preventDefault()\n if self.mouseMode ==MouseMode.PAN:\n if self.panning:self._doPan(event)\n return\n if not self.mouseOwner:return\n if self.mouseMode ==MouseMode.DRAG:\n self._doDrag(event)\n else :\n self._movePoint(event)\n \n def _onLeftUp(self,event):\n if event.type ==\"mouseup\"and event.button >0:return\n if self.mouseMode ==MouseMode.PAN:\n self._endPan(event)\n return\n if not self.mouseOwner:return\n if self.mouseMode ==MouseMode.DRAG:\n self._endDrag(event)\n elif self.mouseMode ==MouseMode.TRANSFORM:\n self._endTransform(event)\n self.mouseOwner=None\n elif self.mouseMode ==MouseMode.EDIT:\n self._endEdit(event)\n \n def _onHitTargetMouseEvent(self,event):\n eventdict={attr:getattr(event,attr)for attr in dir(event)}\n eventdict[\"bubbles\"]=False\n newevent=window.MouseEvent.new(event.type,eventdict)\n obj=self.objectDict[event.target.id]\n obj.reference.dispatchEvent(newevent)\n \n def _onHitTargetTouchEvent(self,event):\n eventdict={attr:getattr(event,attr)for attr in dir(event)}\n eventdict[\"bubbles\"]=False\n newevent=window.TouchEvent.new(event.type,eventdict)\n obj=self.objectDict[event.target.id]\n obj.reference.dispatchEvent(newevent)\n latesttime=time.time()\n if event.type ==\"touchend\"and latesttime -lasttaptime <0.6:\n eventdict[\"clientX\"]=event.changedTouches[0].clientX\n eventdict[\"clientY\"]=event.changedTouches[0].clientY\n newevent=window.MouseEvent.new(\"click\",eventdict)\n obj.reference.dispatchEvent(newevent)\n \n def _onDoubleClick(self,event):\n if self.mouseMode ==MouseMode.DRAW:\n self.setTool(\"select\")\n elif self.mouseMode ==MouseMode.PAN:\n self.fitContents()\n \n def _onKeyDown(self,event):\n if event.keyCode ==46:self.deleteSelection()\n \n def _prepareDrag(self,event):\n self.dragStartCoords=self.getSVGcoords(event)\n self.selectedObject=self.getSelectedObject(event.target.id)\n if self.selectedObject and not self.selectedObject.fixed:\n self.mouseOwner=self.selectedObject\n self <=self.mouseOwner\n if (hittarget :=getattr(self.mouseOwner,\"hitTarget\",None )):self <=hittarget\n self.startx=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n self.starty=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n \n def _doDrag(self,event):\n x=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n y=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n dx,dy=(x -self.startx)*self.scaleFactor,(y -self.starty)*self.scaleFactor\n self.mouseOwner.style.transform=f\"translate({dx}px,{dy}px)\"\n if isinstance(self.mouseOwner,[EllipseObject,RectangleObject,UseObject,ImageObject]):\n self.mouseOwner.style.transform +=self.mouseOwner.rotatestring\n if isinstance(self.mouseOwner,UseObject):self.mouseOwner.style.transform +=self.mouseOwner.scalestring\n \n def _endDrag(self,event):\n self.mouseOwner.style.transform=\"translate(0px,0px)\"\n currentcoords=self.getSVGcoords(event)\n offset=currentcoords -self.dragStartCoords\n self.translateObject(self.mouseOwner,offset)\n if self.edgeSnap:self._doEdgeSnap(self.mouseOwner)\n elif self.vertexSnap:self._doVertexSnap(self.mouseOwner)\n self.mouseOwner=None\n \n def _preparePan(self,event):\n if not self.centre:\n (width,height)=self._getDimensions()\n self.setViewBox([(0,0),(width,height)])\n if \"touch\"in event.type and event.touches.length ==2:\n point0=Point((event.touches[0].clientX,event.touches[0].clientY))\n point1=Point((event.touches[1].clientX,event.touches[1].clientY))\n self.startZoomLength=(point1 -point0).length()\n \n x=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n y=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n self.startPoint=Point((x,y))\n self.startCentre=self.centre\n self.panStart=self.viewBoxRect\n self.panning=True\n \n def _doPan(self,event):\n if \"touch\"in event.type and event.touches.length ==2:\n point0=Point((event.touches[0].clientX,event.touches[0].clientY))\n point1=Point((event.touches[1].clientX,event.touches[1].clientY))\n newzoomlength=(point1 -point0).length()\n zoomfactor=self.startZoomLength /newzoomlength\n \n newviewbox=[self.centre+zoomfactor *(point -self.centre)for point in self.viewBoxRect]\n self.setViewBox(newviewbox)\n self.startZoomLength=newzoomlength\n else :\n x=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n y=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n sf=(self.xScaleFactor,self.yScaleFactor)if self.attrs[\"preserveAspectRatio\"]==\"none\"else self.scaleFactor\n delta=(Point((x,y))-self.startPoint)*sf\n \n newviewbox=[point -delta for point in self.panStart]\n self.setViewBox(newviewbox)\n \n def _endPan(self,event):\n self.panning=False\n \n def getSelectedObject(self,objectid,getGroup=True ):\n ''\n\n\n \n try :\n svgobj=self.objectDict[objectid]\n except KeyError:\n return\n try :\n svgobj=svgobj.reference\n except AttributeError:\n pass\n if getGroup:\n while getattr(svgobj,\"group\",None ):\n svgobj=svgobj.group\n return svgobj\n \n def _doVertexSnap(self,svgobject,checkpoints=None ):\n if not hasattr(svgobject,\"pointList\"):return\n snapd=self.snapDistance\n bestdx=bestdy=bestd=None\n bbox=svgobject.getBBox()\n L,R,T,B=bbox.x,bbox.x+bbox.width,bbox.y,bbox.y+bbox.height\n \n if checkpoints is None :\n checkpoints=[]\n for objid in self.objectDict:\n if objid ==svgobject.id:continue\n obj=self.objectDict[objid]\n if not hasattr(obj,\"pointList\"):continue\n if hasattr(obj,\"reference\"):continue\n if obj.style.visibility ==\"hidden\":continue\n if objgroup :=getattr(obj,\"group\",None )and hasattr(objgroup,\"pointList\"):continue\n bbox=obj.getBBox()\n L1,R1,T1,B1=bbox.x,bbox.x+bbox.width,bbox.y,bbox.y+bbox.height\n if L1 -R >snapd or R1 -L <-snapd or T1 -B >snapd or B1 -T <-snapd:continue\n checkpoints.extend(obj.pointList)\n if not checkpoints:return\n checkpoints.sort(key=lambda p:p.coords)\n objpoints=sorted(svgobject.pointList,key=lambda p:p.coords)\n \n checkstart=0\n for i,point1 in enumerate(objpoints):\n checkpoints=checkpoints[checkstart:]\n if not checkpoints:break\n try :\n (tonextx,y)=objpoints[i+1]-point1\n except IndexError:\n tonextx=0\n checkstart=0\n for point2 in checkpoints:\n (dx,dy)=point2 -point1\n if abs(dx)<snapd and abs(dy)<snapd:\n d=hypot(dx,dy)\n if bestd is None or d <bestd:(bestd,bestdx,bestdy)=(d,dx,dy)\n if tonextx -dx >snapd:checkstart +=1\n if dx >snapd:break\n if bestd:self.translateObject(svgobject,(bestdx,bestdy))\n \nclass Point(object):\n ''\n def __init__(self,coords):\n self.coords=list(coords.coords)if isinstance(coords,Point)else list(coords)\n \n def __repr__(self):\n return str(tuple(self.coords))\n \n def __eq__(self,other):\n if isinstance(other,Point):\n return (self.coords ==other.coords)\n elif isinstance(other,list):\n return (self.coords ==other)\n elif isinstance(other,tuple):\n return (tuple(self.coords)==other)\n else :\n return False\n \n def __lt__(self,other):\n return self.coords <other.coords\n \n def __add__(self,other):\n if isinstance(other,Point):\n return Point([xi+yi for (xi,yi)in zip(self.coords,other.coords)])\n elif isinstance(other,(list,tuple)):\n return Point([xi+yi for (xi,yi)in zip(self.coords,other)])\n elif other is None :\n return None\n else :\n return NotImplemented\n \n def __radd__(self,other):\n return self+other\n \n def __iadd__(self,other):\n if isinstance(other,(list,tuple)):\n for i in range(len(self.coords)):\n self.coords[i]+=other[i]\n else :\n for i in range(len(self.coords)):\n self.coords[i]+=other.coords[i]\n return self\n \n def __sub__(self,other):\n return Point([xi -yi for (xi,yi)in zip(self.coords,other.coords)])\n \n def __rsub__(self,other):\n return Point([xi -yi for (xi,yi)in zip(other.coords,self.coords)])\n \n def __neg__(self):\n return Point([-xi for xi in self.coords])\n \n def __mul__(self,other):\n if isinstance(other,(int,float)):\n return Point([other *xi for xi in self.coords])\n elif isinstance(other,(list,tuple)):\n return Point([xi *yi for (xi,yi)in zip(self.coords,other)])\n elif isinstance(other,Point):\n return sum([xi *yi for (xi,yi)in zip(self.coords,other.coords)])\n elif isinstance(other,Matrix):\n return Point([self *col for col in other.cols])\n else :\n return NotImplemented\n \n def __rmul__(self,other):\n if isinstance(other,(int,float)):\n return Point([other *xi for xi in self.coords])\n elif isinstance(other,(list,tuple)):\n return Point([xi *yi for (xi,yi)in zip(self.coords,other)])\n elif isinstance(other,Point):\n return sum([xi *yi for (xi,yi)in zip(self.coords,other.coords)])\n else :\n return NotImplemented\n \n def __truediv__(self,other):\n return Point([xi /other for xi in self.coords])\n \n def __getitem__(self,i):\n return self.coords[i]\n \n def __hash__(self):\n return hash(tuple(self.coords))\n \n def __round__(self,n):\n (x,y)=self.coords\n return Point((round(float(x),n),round(float(y),n)))\n \n def __len__(self):\n return len(self.coords)\n \n def length(self):\n (x,y)=self.coords\n return hypot(x,y)\n \n def angle(self):\n return atan2(self.coords[1],self.coords[0])\n \n def anglefrom(self,other):\n dot=other *self\n cross=other.cross(self)\n if cross ==0:cross=-0.0\n angle=atan2(cross,dot)\n return angle\n \n def cross(self,other):\n x1,y1=self.coords\n x2,y2=other.coords\n return x1 *y2 -y1 *x2\n \n def roundsf(self,sf):\n x,y=self.coords\n return Point((roundsf(x,sf),roundsf(y,sf)))\n \nclass Matrix(object):\n def __init__(self,rows):\n self.rows=rows\n self.cols=[Point([self.rows[i][j]for i in range(len(self.rows))])for j in range(len(self.rows[0]))]\n \n def __str__(self):\n return str(\"\\n\".join(str(row)for row in self.rows))\n \n def __rmul__(self,other):\n if isinstance(other,(list,tuple)):\n return [Point([p *col for col in self.cols])for p in other]\n else :\n return Point([other *col for col in self.cols])\n \ndef roundsf(x,sf=3):\n if x ==0:return 0\n return round(x,sf -int(floor(log10(abs(x))))-1)\n \nshapetypes={\"line\":LineObject,\"polygon\":PolygonObject,\"polyline\":PolylineObject,\n\"rectangle\":RectangleObject,\"ellipse\":EllipseObject,\"circle\":CircleObject,\"sector\":SectorObject,\n\"bezier\":BezierObject,\"closedbezier\":ClosedBezierObject,\"smoothbezier\":SmoothBezierObject,\"smoothclosedbezier\":SmoothClosedBezierObject}\n", ["browser", "browser.html", "browser.svg", "math", "time"]], "brySVG.fullcanvas": [".py", "#!/usr/bin/python\n\n\n\n\n\n\n\n\n\n\n\nfrom brySVG.transformcanvas import *\nfrom brySVG.drawcanvas import *\n", ["brySVG.drawcanvas", "brySVG.transformcanvas"]], "brySVG": [".py", "", [], 1], "brySVG.transformcanvas": [".py", "#!/usr/bin/python\n\n\n\n\n\n\n\n\n\n\n\nfrom brySVG.dragcanvas import *\n\nclass TransformMixin(object):\n ''\n\n \n \n def _transformedpoint(self,matrix):\n ''\n pt=svgbase.createSVGPoint()\n (pt.x,pt.y)=self.XY\n pt=pt.matrixTransform(matrix)\n return Point((pt.x,pt.y))\n \n def _transformedpointsetlist(self,matrix):\n ''\n pt=svgbase.createSVGPoint()\n newpointsetlist=[]\n for pointset in self.pointsetList:\n newpointset=[]\n for point in pointset:\n if point is None :\n newpointset.append(None )\n else :\n (pt.x,pt.y)=point\n pt=pt.matrixTransform(matrix)\n newpointset.append(Point((pt.x,pt.y)))\n newpointsetlist.append(newpointset)\n return newpointsetlist\n \n def matrixTransform(self,matrix):\n ''\n if isinstance(self,GroupObject):\n for obj in self.objectList:\n obj.matrixTransform(matrix)\n elif isinstance(self,PointObject):\n self.XY=self._transformedpoint(matrix)\n elif isinstance(self,PolygonObject):\n \n self._transformpoints(self.points,matrix)\n self._pointList=None\n self._segments=None\n else :\n self.pointList=self._transformedpointlist(matrix)\n if isinstance(self,BezierObject):self.pointsetList=self._transformedpointsetlist(matrix)\n self._update()\n self._updatehittarget()\n \n def translate(self,vector):\n ''\n t=svgbase.createSVGTransform()\n t.setTranslate(*vector)\n self.matrixTransform(t.matrix)\n \n def rotate(self,angle,centre=None ):\n ''\n \n if not centre:\n bbox=self.getBBox()\n centre=(bbox.x+bbox.width /2,bbox.y+bbox.height /2)\n if isinstance(self,(EllipseObject,RectangleObject,ImageObject,UseObject)):\n self.angle +=angle\n t=svgbase.createSVGTransform()\n t.setRotate(angle,*centre)\n self.matrixTransform(t.matrix)\n \n def rotateAndTranslate(self,angle,centre=None ,vector=(0,0)):\n ''\n \n if not centre:\n bbox=self.getBBox()\n centre=(bbox.x+bbox.width /2,bbox.y+bbox.height /2)\n t=svgbase.createSVGTransform()\n if angle !=0:t.setRotate(angle,*centre)\n M=t.matrix.translate(*vector)if vector !=(0,0)else t.matrix\n self.matrixTransform(M)\n \n def rotateByVectors(self,vec1,vec2,centre=(0,0)):\n ''\n \n (cx,cy)=centre\n (x1,y1)=vec1\n (x2,y2)=vec2\n (x3,y3)=(x1 *x2+y1 *y2,x1 *y2 -x2 *y1)\n angle=atan2(y3,x3)*180 /pi\n if isinstance(self,(EllipseObject,RectangleObject,ImageObject,UseObject)):\n self.angle +=angle\n matrix=svgbase.createSVGMatrix()\n matrix=matrix.translate(cx,cy)\n matrix=matrix.rotateFromVector(x3,y3)\n matrix=matrix.translate(-cx,-cy)\n self.matrixTransform(matrix)\n \n def xstretch(self,xscale,cx=0):\n ''\n \n if isinstance(self,UseObject):return\n angle=0\n if isinstance(self,(EllipseObject,RectangleObject,ImageObject))and self.angle !=0:\n angle=self.angle\n self.rotate(-angle)\n matrix=svgbase.createSVGMatrix()\n matrix=matrix.translate(cx,0)\n matrix.a=xscale\n matrix=matrix.translate(-cx,0)\n self.matrixTransform(matrix)\n if angle !=0:self.rotate(angle)\n \n def ystretch(self,yscale,cy=0):\n ''\n \n if isinstance(self,UseObject):return\n angle=0\n if isinstance(self,(EllipseObject,RectangleObject,ImageObject))and self.angle !=0:\n angle=self.angle\n self.rotate(-angle)\n matrix=svgbase.createSVGMatrix()\n matrix=matrix.translate(0,cy)\n matrix.d=yscale\n matrix=matrix.translate(0,-cy)\n self.matrixTransform(matrix)\n if angle !=0:self.rotate(angle)\n \n def enlarge(self,scalefactor,centre=(0,0)):\n ''\n \n if isinstance(self,UseObject):return\n (cx,cy)=centre\n matrix=svgbase.createSVGMatrix()\n matrix=matrix.translate(cx,cy)\n matrix=matrix.scale(scalefactor)\n matrix=matrix.translate(-cx,-cy)\n self.matrixTransform(matrix)\n \nclass TransformCanvasMixin(object):\n def _prepareTransform(self,event):\n self.selectedObject=self.getSelectedObject(event.target.id)\n if self.selectedObject and not self.selectedObject.fixed:\n self <=self.selectedObject\n self.showTransformHandles(self.selectedObject)\n if TransformType.TRANSLATE in self.transformTypes:self.transformHandles[TransformType.TRANSLATE]._select(event)\n else :\n self.hideTransformHandles()\n \n def _endTransform(self,event):\n if not isinstance(self.mouseOwner,TransformHandle):return\n currentcoords=self.getSVGcoords(event)\n offset=currentcoords -self.StartPoint\n if offset.coords !=[0,0]:\n centre=(cx,cy)=self.selectedObject.centre\n vec1=(x1,y1)=self.StartPoint -centre\n vec2=(x2,y2)=currentcoords -centre\n transformtype=self.mouseOwner.transformType\n \n self.selectedObject.style.transform=\"translate(0px,0px)\"\n if transformtype ==TransformType.TRANSLATE:\n self.selectedObject.translate(offset)\n elif transformtype ==TransformType.ROTATE:\n self.selectedObject.rotateByVectors(vec1,vec2,(cx,cy))\n elif transformtype ==TransformType.XSTRETCH:\n self.selectedObject.xstretch(x2 /x1,cx)\n elif transformtype ==TransformType.YSTRETCH:\n self.selectedObject.ystretch(y2 /y1,cy)\n elif transformtype ==TransformType.ENLARGE:\n self.selectedObject.enlarge(hypot(x2,y2)/hypot(x1,y1),(cx,cy))\n \n if self.edgeSnap:self._doEdgeSnap(self.selectedObject)\n elif self.vertexSnap:self._doVertexSnap(self.selectedObject)\n \n if self.transformorigin:\n self.removeChild(self.transformorigin)\n self.transformorigin=None\n self.showTransformHandles(self.selectedObject)\n self.mouseOwner=None\n \n def showTransformHandles(self,svgobj):\n tempgroup=svg.g()\n tempgroup <=svgobj.cloneNode(True )\n self <=tempgroup\n bbox=tempgroup.getBBox()\n (x1,y1),(x2,y2)=svgobj.bbox=(bbox.x,bbox.y),(bbox.x+bbox.width,bbox.y+bbox.height)\n self.removeChild(tempgroup)\n (cx,cy)=svgobj.centre=Point(((x1+x2)/2,(y1+y2)/2))\n ((left,top),(right,bottom))=self.viewWindow\n \n if not self.transformHandles:self.transformHandles=[self.transformBBox]+[TransformHandle(None ,i,(0,0),self)for i in range(1,6)]\n for i,coords in enumerate([((x1+x2)/2,(y1+y2)/2),(x1,y1),(x2,(y1+y2)/2),((x1+x2)/2,y2),(x2,y2)]):\n self.transformHandles[i+1].XY=coords\n self.transformHandles[i+1].owner=svgobj\n self.hideTransformHandles()\n \n self.usebox=False\n if not isinstance(svgobj,UseObject):\n for ttype in self.transformTypes:\n if ttype in [TransformType.XSTRETCH,TransformType.YSTRETCH,TransformType.ENLARGE]:self.usebox=True\n \n if self.usebox:\n self.transformBBox.setPointList([Point((x1,y1)),Point((x2,y2))])\n \n self <=self.transformBBox\n self.transformBBox.style.visibility=\"visible\"\n else :\n handlelength=min((bottom -top)*0.4,(y2 -y1)*2)\n ypos=cy -handlelength\n if ypos <top:ypos=cy+handlelength\n handleposition=Point(((x1+x2)/2,ypos))\n self.transformHandles[2].XY=handleposition\n self.rotateLine.setPointList([svgobj.centre,handleposition])\n \n self <=self.rotateLine\n self.rotateLine.style.visibility=\"visible\"\n for ttype in self.transformTypes:\n if isinstance(svgobj,UseObject)and ttype in [TransformType.XSTRETCH,TransformType.YSTRETCH,TransformType.ENLARGE]:continue\n thandle=self.transformHandles[ttype]\n self <=thandle\n if ttype !=TransformType.TRANSLATE:thandle.style.visibility=\"visible\"\n return [(x1,y1),(x2,y2)]\n \n def hideTransformHandles(self):\n for obj in self.transformHandles:obj.style.visibility=\"hidden\"\n self.rotateLine.style.visibility=\"hidden\"\n \n def _showTransformOrigin(self,svgobj,transformtype):\n (cx,cy)=svgobj.centre\n (x1,y1),(x2,y2)=svgobj.bbox\n if transformtype in [TransformType.NONE,TransformType.TRANSLATE]:return\n if transformtype in [TransformType.ROTATE,TransformType.ENLARGE]:\n self.transformorigin=PointObject((cx,cy),colour=\"blue\",pointsize=4,canvas=self)\n elif transformtype ==TransformType.XSTRETCH:\n self.transformorigin=LineObject([(cx,y1),(cx,y2)],linecolour=\"blue\",linewidth=2)\n elif transformtype ==TransformType.YSTRETCH:\n self.transformorigin=LineObject([(x1,cy),(x2,cy)],linecolour=\"blue\",linewidth=2)\n self.transformorigin.style.vectorEffect=\"non-scaling-stroke\"\n self <=self.transformorigin\n \n def _movePoint(self,event):\n x=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n y=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n dx,dy=x -self.currentx,y -self.currenty\n if \"touch\"in event.type and abs(dx)<5 and abs(dy)<5:return\n self.currentx,self.currenty=x,y\n coords=self.getSVGcoords(event)\n if self.mouseMode ==MouseMode.DRAW:\n self.mouseOwner._movePoint(coords)\n else :\n if self.mouseMode ==MouseMode.TRANSFORM:dx,dy=x -self.startx,y -self.starty\n dx,dy=dx *self.scaleFactor,dy *self.scaleFactor\n self.mouseOwner._movePoint((dx,dy))\n return coords\n \nclass TransformHandle(PointObject):\n def __init__(self,owner,transformtype,coords,canvas):\n pointsize=7 if canvas.mouseDetected else 15\n opacity=0.4\n strokewidth=3\n PointObject.__init__(self,coords,\"red\",pointsize,canvas)\n self.style.fillOpacity=opacity\n self.style.strokeWidth=strokewidth\n self.style.vectorEffect=\"non-scaling-stroke\"\n self.owner=owner\n self.canvas=canvas\n self.transformType=transformtype\n self.bind(\"mousedown\",self._select)\n self.bind(\"touchstart\",self._select)\n \n def _select(self,event):\n event.stopPropagation()\n self.canvas.mouseOwner=self\n self.startx,self.starty=self.canvas.StartPoint=self.canvas.getSVGcoords(event)\n self.canvas.startx=self.canvas.currentx=event.targetTouches[0].clientX if \"touch\"in event.type else event.clientX\n self.canvas.starty=self.canvas.currenty=event.targetTouches[0].clientY if \"touch\"in event.type else event.clientY\n self.canvas.hideTransformHandles()\n if self.transformType ==TransformType.ROTATE:self.canvas.rotateLine.style.visibility=\"visible\"\n if self.transformType !=TransformType.TRANSLATE:self.style.visibility=\"visible\"\n self.canvas._showTransformOrigin(self.owner,self.transformType)\n \n def _movePoint(self,offset):\n (dx,dy)=offset\n if (dx,dy)==(0,0):return\n (x,y)=self.startx+dx,self.starty+dy\n self.XY=(x,y)\n if self.transformType ==TransformType.TRANSLATE:\n transformstring=f\"translate({dx}px,{dy}px)\"\n if isinstance(self.owner,(EllipseObject,RectangleObject,ImageObject,UseObject)):\n self.owner.style.transform=transformstring+self.owner.rotatestring\n if isinstance(self.owner,UseObject):self.owner.style.transform +=self.owner.scalestring\n else :\n self.owner.style.transform=transformstring\n return\n \n (cx,cy)=self.owner.centre\n (x1,y1)=self.startx -cx,self.starty -cy\n (x2,y2)=x -cx,y -cy\n if self.transformType ==TransformType.ROTATE:\n (x3,y3)=(x1 *x2+y1 *y2,x1 *y2 -x2 *y1)\n angle=atan2(y3,x3)*180 /pi\n transformstring=f\"translate({cx}px,{cy}px) rotate({angle}deg) translate({-cx}px,{-cy}px)\"\n if not self.canvas.usebox:\n self.canvas.rotateLine.pointList=[self.owner.centre,self.XY]\n self.canvas.rotateLine._update()\n elif self.transformType ==TransformType.XSTRETCH:\n xfactor=x2 /x1\n yfactor=xfactor if isinstance(self.owner,CircleObject)else 1\n transformstring=f\"translate({cx}px,{cy}px) scale({xfactor},{yfactor}) translate({-cx}px,{-cy}px)\"\n elif self.transformType ==TransformType.YSTRETCH:\n yfactor=y2 /y1\n xfactor=yfactor if isinstance(self.owner,CircleObject)else 1\n transformstring=f\"translate({cx}px,{cy}px) scale({xfactor},{yfactor}) translate({-cx}px,{-cy}px)\"\n elif self.transformType ==TransformType.ENLARGE:\n transformstring=f\"translate({cx}px,{cy}px) scale({hypot(x2, y2)/hypot(x1, y1)}) translate({-cx}px,{-cy}px)\"\n \n if isinstance(self.owner,(EllipseObject,RectangleObject,ImageObject,UseObject)):\n self.owner.style.transform=self.owner.rotatestring+transformstring\n if isinstance(self.owner,UseObject):self.owner.style.transform +=self.owner.scalestring\n else :\n self.owner.style.transform=transformstring\n \nclasses=[LineObject,RectangleObject,EllipseObject,CircleObject,SectorObject,PolylineObject,PolygonObject,BezierObject,\nClosedBezierObject,SmoothBezierObject,SmoothClosedBezierObject,PointObject,RegularPolygon,GroupObject,ImageObject,UseObject]\nfor cls in classes:\n cls.__bases__=cls.__bases__+(TransformMixin,)\n \nCanvasObject.__bases__=CanvasObject.__bases__+(TransformCanvasMixin,)\n", ["brySVG.dragcanvas"]], "brySVG.polygoncanvas": [".py", "#!/usr/bin/python\n\n\n\n\n\n\n\n\n\n\n\nfrom math import sin,cos,atan2,pi,log10,floor,inf\nfrom .transformcanvas import *\nimport time\n\nclass Enum(list):\n def __init__(self,name,string):\n values=string.split()\n for i,value in enumerate(values):\n setattr(self,value,i)\n self.append(i)\n \nPosition=Enum('Position','CONTAINS INSIDE OVERLAPS EQUAL DISJOINT TOUCHING')\ndp=2\ndp1=dp -1\ndp2=dp -2\n\nclass ListDict(dict):\n def __getitem__(self,key):\n try :\n return super().__getitem__(key)\n except KeyError:\n self[key]=[]\n return self[key]\n \nclass SetDict(dict):\n def __getitem__(self,key):\n try :\n return super().__getitem__(key)\n except KeyError:\n self[key]=set()\n return self[key]\n \nclass Segment(object):\n def __init__(self,startpoint,endpoint,poly,index):\n if endpoint <startpoint:\n self.leftpoint,self.rightpoint=endpoint,startpoint\n self.leftindex,self.rightindex=reversed(index)\n else :\n self.leftpoint,self.rightpoint=startpoint,endpoint\n self.leftindex,self.rightindex=index\n \n self.leftx,self.lefty=self.leftpoint\n self.rightx,self.righty=self.rightpoint\n (self.top,self.bottom)=(self.righty,self.lefty)if self.righty <self.lefty else (self.lefty,self.righty)\n self.poly=poly\n self.index=index\n self.dx,self.dy=self.rightx -self.leftx,self.righty -self.lefty\n self.gradient=inf if self.dx ==0 else self.dy /self.dx\n self.angle=atan2(self.dy,self.dx)-pi /2+pi *(self.dy <0)\n self.y=self.lefty\n self.xpos=\"L\"\n \n def __repr__(self):\n return f\"{self.poly}{self.index}: [{self.leftpoint}, {self.rightpoint}]; currenty: {self.y}, xpos: {self.xpos}, gradient: {self.gradient}, angle: {self.angle}\"\n \nclass Intersection(object):\n def __init__(self,polyrefs,point=None ):\n (poly1,index1),(poly2,index2)=polyrefs\n self.polyrefs={poly1:index1,poly2:index2}\n self.point=point if point is not None else Point((0,0))\n \n def __repr__(self):\n s=f\"{self.point} \\n\"\n polys=sorted(self.polyrefs,key=lambda poly:poly.id)\n for poly in polys:\n index=self.polyrefs[poly]\n if isinstance(index,int):\n s +=f\"at vertex {index} on polygon {poly}\\n\"\n else :\n i1,i2=index\n s +=f\"between vertices {i1} and {i2} on polygon {poly}\\n\"\n return s\n \nclass PolygonMixin(object):\n @property\n def segments(self):\n if isinstance(self,PolygonGroup):return self.boundary.segments\n if getattr(self,\"_segments\",None )is None :\n \n L=len(self.pointList)\n self._segments=[Segment(self.pointList[i -1],self.pointList[i],self,((i -1)%L,i))for i in range(L)]\n return self._segments\n \n def containspoint(self,point,dp=1):\n ''\n\n \n poly=getattr(poly,\"pointList\",poly)\n poly=[(round(x,dp),round(y,dp))for (x,y)in poly]\n point=getattr(point,\"XY\",point)\n (x,y)=(round(point[0],dp),round(point[1],dp))\n if (x,y)in poly:return \"vertex\"\n length=len(poly)\n counter=0\n (x1,y1)=poly[0]\n for i in range(1,length+1):\n (x2,y2)=poly[i %length]\n if x1 ==x2 ==x and min(y1,y2)<=y <=max(y1,y2):return \"edge\"\n if y1 ==y2:\n if y ==y1 and min(x1,x2)<=x <=max(x1,x2):return \"edge\"\n elif min(y1,y2)<y <=max(y1,y2)and x <max(x1,x2):\n xcheck=x1+(y -y1)*(x2 -x1)/(y2 -y1)\n if x ==xcheck:return \"edge\"\n elif x <xcheck:\n counter +=1\n (x1,y1)=(x2,y2)\n return \"interior\"if counter %2 ==1 else False\n \n def area(self):\n ''\n return _area(self.pointList)\n \n def getBoundingBox(self):\n ''\n \n return _getboundingbox(self.pointList)\n \n def getCentre(self):\n (left,top),(right,bottom)=_getboundingbox(self.pointList)\n return ((left+right)/2,(top+bottom)/2)\n \n def isEqual(self,other):\n ''\n return _equalpolygons(self.pointList,other.pointList)\n \n def positionRelativeTo(self,other):\n ''\n \n return relativeposition(self,other)\n \n def getIntersections(self,other):\n ''\n\n\n\n\n\n \n ixlist=findintersections([self,other])\n for ix in ixlist:\n polyrefs=ix.polyrefs\n ix.selfindex,ix.otherindex=polyrefs[self],polyrefs[other]\n return ixlist\n \n def merge(self,other):\n ''\n \n return boundary([self,other])\n \nclass PolygonGroup(GroupObject,PolygonMixin):\n def __init__(self,objlist=[],objid=None ):\n self.boundary=None\n super().__init__(objlist)\n if objid:self.id=objid\n \n def __repr__(self):\n return f\"group {self.id}\"if self.id else f\"group {id(self)}\"\n \n def __str__(self):\n return self.__repr__()\n \n def addObject(self,svgobject):\n if not isinstance(svgobject,(PolygonObject,PolygonGroup)):return False\n if self.objectList ==[]:\n self.boundary=PolygonObject(svgobject.pointList)\n else :\n newboundary=self.boundary.merge(svgobject)\n if newboundary is False :\n return False\n elif newboundary is None :\n return None\n else :\n self.boundary=newboundary\n \n super().addObject(svgobject)\n \n return True\n \n def addObjects(self,polylist,listboundary=None ):\n if polylist ==[]:return None\n blist=[self.boundary]if self.boundary else []\n if listboundary:\n blist.append(listboundary)\n else :\n blist.extend(polylist)\n newboundary=blist[0]if len(blist)==1 else boundary(blist)\n if newboundary is False :\n return False\n elif newboundary is None :\n return None\n else :\n self.boundary=newboundary\n super().addObjects(polylist)\n \n return True\n \n def removeObject(self,svgobject):\n if not self.contains(svgobject):return\n groupcopy=self.objectList[:]\n groupcopy.remove(svgobject)\n newboundary=boundary(groupcopy)\n if newboundary is False :\n return False\n elif newboundary is None :\n return None\n else :\n self.boundary=newboundary\n super().removeObject(svgobject)\n \n return True\n \n def deleteAll(self):\n self.boundary=None\n \n super().deleteAll()\n \n @property\n def pointList(self):\n return []if self.boundary is None else self.boundary.pointList\n \n @pointList.setter\n def pointList(self,pointlist):\n pass\n \n @property\n def points(self):\n return None if self.boundary is None else self.boundary.points\n \n def matrixTransform(self,matrix):\n def addalltopointslist(group):\n for obj in group.objectList:\n if isinstance(obj,PolygonGroup):\n addalltopointslist(obj)\n else :\n pointslist.append(obj.points)\n obj._pointList=None\n obj._segments=None\n pointslist.append(group.boundary.points)\n group.boundary._pointList=None\n group.boundary._segments=None\n \n pointslist=[]\n addalltopointslist(self)\n \n self._transformpoints(pointslist,matrix)\n \n def _transformpoints(self,pointslist,matrix):\n for points in pointslist:\n L=points.numberOfItems\n for i in range(L):\n pt=points.getItem(i)\n newpt=pt.matrixTransform(matrix)\n points.replaceItem(newpt,i)\n \n def cloneObject(self):\n newobject=super().cloneObject()\n newobject.boundary=self.boundary.cloneObject()\n \n return newobject\n \nclass PolygonCanvasMixin(object):\n ''\n\n\n \n def _doEdgeSnap(self,svgobject):\n tt=time.time()\n if not isinstance(svgobject,(PolygonObject,PolygonGroup)):return\n snapangle=self.snapAngle *pi /180\n snapd=self.snapDistance\n bestangle=None\n bbox=svgobject.getBBox()\n L1,R1,T1,B1=bbox.x,bbox.x+bbox.width,bbox.y,bbox.y+bbox.height\n \n checksegs=[]\n checkobjs=[]\n for objid in self.objectDict:\n if objid ==svgobject.id:continue\n obj=self.objectDict[objid]\n if getattr(obj,\"group\",None ):continue\n if not isinstance(obj,(PolygonObject,PolygonGroup)):continue\n bbox=obj.getBBox()\n L2,R2,T2,B2=bbox.x,bbox.x+bbox.width,bbox.y,bbox.y+bbox.height\n if L2 -R1 >snapd or R2 -L1 <-snapd or T2 -B1 >snapd or B2 -T1 <-snapd:continue\n checksegs.extend(obj.segments)\n checkobjs.append(obj)\n if not checksegs:\n \n return\n checksegs.sort(key=lambda seg:seg.angle)\n for seg in checksegs:\n if seg.angle >snapangle -pi /2:break\n newseg=Segment(seg.leftpoint,seg.rightpoint,seg.poly,seg.index)\n newseg.angle=seg.angle+pi\n checksegs.append(newseg)\n \n objsegs=sorted(svgobject.segments,key=lambda seg:seg.angle)\n for seg in objsegs:\n if seg.angle >snapangle -pi /2:break\n newseg=Segment(seg.leftpoint,seg.rightpoint,seg.poly,seg.index)\n newseg.angle=seg.angle+pi\n objsegs.append(newseg)\n \n \n \n \n \n \n checkstart=0\n piby4=pi /4\n found=False\n for i,seg1 in enumerate(objsegs):\n angle1=seg1.angle\n \n checksegs=checksegs[checkstart:]\n if not checksegs:break\n \n try :\n tonextangle=objsegs[i+1].angle -angle1\n except IndexError:\n tonextangle=0\n \n checkstart=0\n for seg2 in checksegs:\n angle2=seg2.angle\n \n angled=angle2 -angle1\n \n \n absangle=abs(angled)\n if (bestangle is None and absangle <snapangle)or (bestangle is not None and absangle <bestangle):\n \n (objleft,objright)=(seg1.leftx,seg1.rightx)\n (objtop,objbottom)=(seg1.top,seg1.bottom)\n (checkleft,checkright)=(seg2.leftx -snapd,seg2.rightx+snapd)\n (checktop,checkbottom)=(seg2.top -snapd,seg2.bottom+snapd)\n if not (objleft >checkright or objright <checkleft or objtop >checkbottom or objbottom <checktop):\n objp,objq=seg1.leftpoint,seg1.rightpoint\n checkp,checkq=seg2.leftpoint,seg2.rightpoint\n \n \n \n objv,checkv=objq -objp,checkq -checkp\n diff,product=checkp -objp,objv.cross(checkv)\n (t,u)=(diff.cross(objv)/product,diff.cross(checkv)/product)if product !=0 else (inf,inf)\n \n if 0 <=t <=1 and 0 <=u <=1:\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,objp+u *objv,(0,0),seg1,seg2)\n else :\n bestd=None\n (objx1,objy1),(objx2,objy2)=objp,objq\n (checkx1,checky1),(checkx2,checky2)=checkp,checkq\n if abs(angle2)<piby4:\n if checktop <=objy1 <=checkbottom:\n checkx=checkx1+(objy1 -checky1)/(checky2 -checky1)*(checkx2 -checkx1)\n diff=checkx -objx1\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(objx1,objy1),(diff,0),seg1,seg2)\n if checktop <=objy2 <=checkbottom:\n checkx=checkx1+(objy2 -checky1)/(checky2 -checky1)*(checkx2 -checkx1)\n diff=checkx -objx2\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(objx2,objy2),(diff,0),seg1,seg2)\n else :\n if checkleft <=objx1 <=checkright:\n checky=checky1+(objx1 -checkx1)/(checkx2 -checkx1)*(checky2 -checky1)\n diff=checky -objy1\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(objx1,objy1),(0,diff),seg1,seg2)\n if checkleft <=objx2 <=checkright:\n checky=checky1+(objx2 -checkx1)/(checkx2 -checkx1)*(checky2 -checky1)\n diff=checky -objy2\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(objx2,objy2),(0,diff),seg1,seg2)\n if abs(angle1)<piby4:\n if objtop <=checky1 <=objbottom:\n objx=objx1+(checky1 -objy1)/(objy2 -objy1)*(objx2 -objx1)\n diff=checkx1 -objx\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(objx,checky1),(diff,0),seg1,seg2)\n if objtop <=checky2 <=objbottom:\n objx=objx1+(checky2 -objy1)/(objy2 -objy1)*(objx2 -objx1)\n diff=checkx2 -objx\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(objx,checky2),(diff,0),seg1,seg2)\n else :\n if objleft <=checkx1 <=objright:\n objy=objy1+(checkx1 -objx1)/(objx2 -objx1)*(objy2 -objy1)\n diff=checky1 -objy\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(checkx1,objy),(0,diff),seg1,seg2)\n if objleft <=checkx2 <=objright:\n objy=objy1+(checkx2 -objx1)/(objx2 -objx1)*(objy2 -objy1)\n diff=checky2 -objy\n \n if (bestd is None and abs(diff)<=snapd)or (bestd is not None and abs(diff)<bestd):\n (bestangle,angle,centre,vector,objseg,checkseg)=(absangle,angled,(checkx2,objy),(0,diff),seg1,seg2)\n \n if tonextangle -angled >snapangle:\n checkstart +=1\n \n if angled >snapangle:\n \n break\n if bestangle ==0:\n found=True\n break\n if found:break\n \n \n if bestangle is not None :\n \n if not (angle ==0 and vector ==(0,0)):svgobject.rotateAndTranslate(angle *180 /pi,centre,vector)\n if self.vertexSnap:\n self._doVertexSnap(svgobject,[p for obj in checkobjs for p in obj.pointList])\n \n \ndef _getboundingbox(poly,xdp=None ,ydp=None ):\n xcoords=[round(x,xdp)for (x,y)in poly]if xdp else [x for (x,y)in poly]\n left=min(xcoords)\n right=max(xcoords)\n ycoords=[round(y,ydp)for (x,y)in poly]if ydp else [y for (x,y)in poly]\n top=min(ycoords)\n bottom=max(ycoords)\n return (left,top),(right,bottom)\n \ndef _compareboundingboxes(poly1,poly2,xdp=None ,ydp=None ):\n if xdp and not ydp:ydp=xdp\n ((left1,top1),(right1,bottom1))=_getboundingbox(poly1,xdp,ydp)\n ((left2,top2),(right2,bottom2))=_getboundingbox(poly2,xdp,ydp)\n if right1 <left2 or right2 <left1 or bottom1 <top2 or bottom2 <top1:return Position.DISJOINT\n if right1 ==left2 or right2 ==left1 or bottom1 ==top2 or bottom2 ==top1:return Position.TOUCHING\n if left1 <left2:\n if right1 <right2:return Position.OVERLAPS\n else :xresult=Position.CONTAINS\n elif left1 ==left2:\n xresult=Position.INSIDE if right1 <right2 else Position.EQUAL if right1 ==right2 else Position.CONTAINS\n else :\n if right1 >right2:return Position.OVERLAPS\n else :xresult=Position.INSIDE\n \n if top1 <top2:\n if bottom1 <bottom2:return Position.OVERLAPS\n else :return Position.OVERLAPS if xresult ==Position.INSIDE else Position.CONTAINS\n elif top1 ==top2:\n if bottom1 <bottom2:return Position.OVERLAPS if xresult ==Position.CONTAINS else Position.INSIDE\n elif bottom1 ==bottom2:return xresult\n else :return Position.OVERLAPS if xresult ==Position.INSIDE else Position.CONTAINS\n else :\n if bottom1 >bottom2:return Position.OVERLAPS\n else :return Position.OVERLAPS if xresult ==Position.CONTAINS else Position.INSIDE\n \ndef _area(poly):\n ''\n area=0\n (x0,y0)=poly[-1]\n for (x1,y1)in poly:\n area +=x1 *y0 -x0 *y1\n (x0,y0)=(x1,y1)\n return abs(area /2)\n \ndef _equalpolygons(poly1,poly2):\n ''\n \n start1=poly1.index(min(poly1))\n poly1=poly1[start1+1:]+poly1[:start1]\n start2=poly2.index(min(poly2))\n poly2=poly2[start2+1:]+poly2[:start2]\n return poly1 ==poly2 or poly1 ==poly2[::-1]\n \ndef _getrotatedcoords(polylist,xdp):\n def getbestangle(polylist):\n anglesfromvertical=[]\n for poly in polylist:\n segs=poly.segments\n anglesfromvertical.extend([round(abs(seg.angle),3)for seg in segs])\n \n if 0 not in anglesfromvertical:return 0\n positiveangles=[a for a in anglesfromvertical if a >0.1]\n return round(min(positiveangles)/2,1)\n \n a=getbestangle(polylist)\n cosa,sina=cos(a),sin(a)\n \n coordslists=[]\n for poly in polylist:\n \n if a ==0:\n polyrotated=poly.pointList\n else :\n polyrotated=[]\n t=svgbase.createSVGTransform()\n t.setRotate(a *180 /pi,5500,5500)\n M=t.matrix\n P=poly.points\n L=P.numberOfItems\n for i in range(L):\n pt=P.getItem(i)\n newpt=pt.matrixTransform(M)\n polyrotated.append((newpt.x,newpt.y))\n \n coords=[(round(x,xdp),y)for (x,y)in polyrotated]\n coordslists.append(coords)\n \n return coordslists\n \ndef _getsortedsegments(polylist,coordslists):\n segments=[]\n for poly,coordslist in zip(polylist,coordslists):\n L=len(coordslist)\n segments.extend([Segment(coordslist[i -1],coordslist[i],poly,((i -1)%L,i))for i in range(L)])\n segments.sort(key=lambda seg:(seg.leftx,round(seg.lefty,dp1),seg.gradient))\n return segments\n \ndef mergelists(list1,list2):\n if not list1:return list(list2)\n if not list2:return list(list1)\n \n list1iter,list2iter=iter(list1),iter(list2)\n item1=next(list1iter)\n item2=next(list2iter)\n result=[]\n \n while item1:\n while item2 and (item2.y <item1.y or (item2.y ==item1.y and item1.xpos !=\"R\"and item2.gradient <item1.gradient)):\n result.append(item2)\n item2=next(list2iter,None )\n result.append(item1)\n item1=next(list1iter,None )\n if item2:\n result.append(item2)\n result.extend(list2iter)\n return result\n \ndef relativeposition(self,other):\n ''\n \n def sweeppast(x,latestoutcome,livesegments):\n \n for seg in livesegments:\n if seg.rightx ==x:\n seg.y=round(seg.righty,dp2)\n else :\n seg.y=round(seg.lefty+(x -seg.leftx)*seg.gradient,dp2)\n \n for i,seg in enumerate(livesegments):\n for seg2 in livesegments[i+1:]:\n \n if seg.y ==inf or seg2.y ==inf:continue\n if seg.y >seg2.y and seg.poly !=seg2.poly:\n \n return Position.OVERLAPS,None\n livesegments=[seg for seg in livesegments if round(seg.rightx,dp1)>round(x,dp1)]\n \n while unusedsegments and round(unusedsegments[0].leftx,dp1)==round(x,dp1):\n newseg=unusedsegments.pop(0)\n newseg.y=round(newseg.lefty,dp2)\n livesegments.append(newseg)\n livesegments.sort(key=lambda seg:(seg.y,seg.gradient))\n \n \n yvaluesA=[seg.y for seg in livesegments if seg.poly ==polyA]\n intervalsA=list(zip(yvaluesA[::2],yvaluesA[1::2]))\n yvaluesB=[seg.y for seg in livesegments if seg.poly ==polyB]\n intervalsB=list(zip(yvaluesB[::2],yvaluesB[1::2]))\n \n \n for (startB,endB)in intervalsB:\n for (startA,endA)in intervalsA:\n if startB ==endB and (startA ==startB or endA ==endB):break\n if startA <=startB and endA >=endB:\n if latestoutcome ==Position.DISJOINT:return Position.OVERLAPS,None\n latestoutcome=Position.CONTAINS\n break\n elif startB <startA <endB or startB <endA <endB:\n return Position.OVERLAPS,None\n else :\n if latestoutcome ==Position.CONTAINS:return Position.OVERLAPS,None\n latestoutcome=Position.DISJOINT\n return latestoutcome,livesegments\n \n polyA,polyB=getattr(self,\"boundary\",self),getattr(other,\"boundary\",other)\n coordslist1,coordslist2=_getrotatedcoords([polyA,polyB],xdp=dp)\n \n transposed=False\n bboxresult=_compareboundingboxes(coordslist1,coordslist2,ydp=dp)\n \n if bboxresult in {Position.DISJOINT,Position.TOUCHING}:return Position.DISJOINT\n if bboxresult ==Position.EQUAL:\n if _equalpolygons(coordslist1,coordslist2):return Position.EQUAL\n elif _area(coordslist2)>_area(coordslist1):\n coordslist1,coordslist2=coordslist2,coordslist1\n polyA,polyB=polyB,polyA\n transposed=True\n if bboxresult ==Position.INSIDE:\n coordslist1,coordslist2=coordslist2,coordslist1\n polyA,polyB=polyB,polyA\n transposed=True\n \n \n unusedsegments=_getsortedsegments([polyA,polyB],[coordslist1,coordslist2])\n \n \n livesegments=[]\n currentoutcome=None\n xvalues=sorted(set(x for (x,y)in coordslist1+coordslist2))\n \n for x in xvalues:\n \n currentoutcome,livesegments=sweeppast(x,currentoutcome,livesegments)\n \n if currentoutcome ==Position.OVERLAPS:return currentoutcome\n \n if currentoutcome ==Position.CONTAINS and transposed:currentoutcome=Position.INSIDE\n return currentoutcome\n \ndef findintersections(polylist):\n ''\n\n\n\n\n\n \n def calculatepoint(polyrefs):\n (poly1,index1),(poly2,index2)=polyrefs\n (i1a,i1b),(i2a,i2b)=index1,index2\n p1,p2=poly1.pointList[i1a],poly2.pointList[i2a]\n v1,v2=poly1.pointList[i1b]-p1,poly2.pointList[i2b]-p2\n \n \n \n if v1.cross(v2)==0:return Point((0,0))\n t=(p2 -p1).cross(v2)/v1.cross(v2)\n point=p1+t *v1\n return point\n \n def addtoixdict(point,polyrefs):\n ixkey=round(point,dp2)\n if ixkey in ixpoints:\n \n existingrefs=ixpoints[ixkey].polyrefs\n for poly,index in polyrefs:\n if (poly not in existingrefs)or isinstance(index,int):\n existingrefs[poly]=index\n else :\n oldindex=existingrefs[poly]\n if isinstance(oldindex,tuple)and index !=oldindex:\n (a,b),(c,d)=index,oldindex\n existingrefs[poly]=b if b ==c else a\n else :\n \n ixpoints[ixkey]=Intersection(polyrefs,point)\n \n \n def sweeppast(x,livesegments):\n for seg in livesegments:\n if seg.rightx ==x:\n seg.y=round(seg.righty,dp2)\n seg.xpos=\"R\"\n seg.currentindex=seg.rightindex\n else :\n seg.y=round(seg.lefty+(x -seg.leftx)*seg.gradient,dp2)\n seg.xpos=\"M\"\n seg.currentindex=seg.index\n \n \n \n for i,seg in enumerate(livesegments):\n for seg2 in livesegments[i+1:]:\n \n if seg.y ==inf or seg2.y ==inf:continue\n if seg.y >seg2.y and seg.poly !=seg2.poly:\n \n polyrefs=[(seg.poly,seg.index),(seg2.poly,seg2.index)]\n point=calculatepoint(polyrefs)\n addtoixdict(point,polyrefs)\n \n newsegments=[]\n while unusedsegments and unusedsegments[0].leftx ==x:\n newseg=unusedsegments.pop(0)\n newseg.y=round(newseg.lefty,dp2)\n newseg.xpos=\"L\"\n newseg.currentindex=newseg.leftindex\n newsegments.append(newseg)\n \n livesegments.sort(key=lambda seg:seg.y)\n livesegments=mergelists(livesegments,newsegments)\n \n \n segiter=iter(livesegments)\n seg=next(segiter,None )\n while seg is not None :\n prevseg=seg\n seg=next(segiter,None )\n if seg is None :break\n \n if seg.y ==prevseg.y and seg.poly !=prevseg.poly:\n if seg.xpos in {\"L\",\"R\"}or prevseg.xpos in {\"L\",\"R\"}:\n \n point=seg.poly.pointList[seg.currentindex]if seg.xpos in {\"L\",\"R\"}else prevseg.poly.pointList[prevseg.currentindex]\n addtoixdict(point,[(seg.poly,seg.currentindex),(prevseg.poly,prevseg.currentindex)])\n \n livesegments=[seg for seg in livesegments if seg.xpos !=\"R\"]\n return livesegments\n \n \n \n tt=time.time()\n polylist=[getattr(poly,\"boundary\",poly)for poly in polylist]\n coordslists=_getrotatedcoords(polylist,xdp=dp)\n \n \n unusedsegments=_getsortedsegments(polylist,coordslists)\n \n \n ixpoints={}\n livesegments=[]\n xvalues=sorted(set(x for coordslist in coordslists for (x,y)in coordslist))\n \n for x in xvalues:\n livesegments=sweeppast(x,livesegments)\n \n \n return list(ixpoints.values())\n \ndef boundary(polylist):\n ''\n \n tt=time.time()\n polylist=[getattr(poly,\"boundary\",poly)for poly in polylist]\n ixlist=findintersections(polylist)\n \n if ixlist ==[]:\n maxarea=0\n for poly in polylist:\n polyarea=poly.area()\n if polyarea >maxarea:(maxarea,maxpoly)=(polyarea,poly)\n for poly in polylist:\n if poly is maxpoly:continue\n if maxpoly.positionRelativeTo(poly)!=Position.CONTAINS:return None\n return PolygonObject(maxpoly.pointList)\n \n ixlist.sort(key=lambda ix:ix.point)\n pointlists=[]\n for i,poly in enumerate(polylist):\n poly.listindex=i\n pointlists.append(poly.pointList)\n pointlists.append([ix.point for ix in ixlist])\n \n L=len(polylist)\n reflists=[[(j,i)for i in range(len(pointlists[j]))]for j in range(L)]\n \n ixdicts=[ListDict()for j in range(L)]\n regions=[]\n for i,ix in enumerate(ixlist):\n \n for (poly,index)in ix.polyrefs.items():\n \n j=poly.listindex\n if isinstance(index,tuple):\n \n ixdicts[j][index].append((L,i))\n else :\n \n reflists[j][index]=(L,i)\n polyset=set(ix.polyrefs)\n for region in regions:\n if polyset®ion:\n region.update(polyset)\n break\n else :\n regions.append(polyset)\n \n polyregions=[]\n while regions:\n \n mainregion=regions.pop(0)\n for region in regions[:]:\n if region&mainregion:\n mainregion.update(region)\n regions.remove(region)\n polyregions.append(mainregion)\n \n \n \n \n for j in range(L):\n offset=1\n for index in sorted(ixdicts[j]):\n (i,k)=index\n insert=ixdicts[j][index]\n pl=pointlists[j]\n if pl[k]<pl[i]:insert.reverse()\n reflists[j][i+offset:i+offset]=insert\n offset +=len(insert)\n \n vertexdict=SetDict()\n for j in range(L):\n last=len(reflists[j])-1\n for i in range(last+1):vertexdict[reflists[j][i]].update({reflists[j][i -1],reflists[j][(i+1)if i <last else 0]})\n \n \n \n \n \n \n minp=Point((inf,inf))\n for (j,i)in vertexdict:\n p=pointlists[j][i]\n if p <minp:minp,minref=p,(j,i)\n currentref=minref\n currentp=start=minp\n newpointlist=[currentp]\n v1=Point((0,1))\n usedrefs={(minref,v1)}\n (j,i)=minref\n startpoly=next(iter(ixlist[i].polyrefs))if j ==L else polylist[j]\n for region in polyregions:\n if startpoly in region:\n boundaryregion=region\n break\n else :\n boundaryregion={startpoly}\n \n \n while True :\n maxangle=-pi\n for (j,i)in vertexdict[currentref]:\n p=pointlists[j][i]\n v2=p -currentp\n angle=v2.anglefrom(v1)\n \n if angle >maxangle:(maxangle,bestp,bestref)=(angle,p,(j,i))\n if [currentp,bestp]==newpointlist[:2]:break\n currentref=bestref\n newpointlist.append(bestp)\n v1=bestp -currentp\n currentp=bestp\n \n if (currentref,v1)in usedrefs:break\n usedrefs.add((currentref,v1))\n (j,i)=currentref\n \n \n \n if currentp !=start:return False\n boundary=PolygonObject(newpointlist[:-1])\n if not (len(polyregions)==1 and len(boundaryregion)==L):\n for poly in polylist:\n if poly not in boundaryregion:\n \n if boundary.positionRelativeTo(poly)!=Position.CONTAINS:return None\n \n \n return boundary\n \nPolygonObject.__bases__=PolygonObject.__bases__+(PolygonMixin,)\nCanvasObject.__bases__=CanvasObject.__bases__+(PolygonCanvasMixin,)\nclass RegularPolygon(RegularPolygon,PolygonObject):\n pass\n", ["brySVG.transformcanvas", "math", "time"]]}
__BRYTHON__.update_VFS(scripts)