Full Code of fsmMLK/inkscapeMadeEasy for AI

master 18fe5e319125 cached
31 files
626.7 KB
159.2k tokens
249 symbols
1 requests
Download .txt
Showing preview only (648K chars total). Download the full file or copy to clipboard to get everything.
Repository: fsmMLK/inkscapeMadeEasy
Branch: master
Commit: 18fe5e319125
Files: 31
Total size: 626.7 KB

Directory structure:
gitextract_ga0zb2kn/

├── 0.9x/
│   ├── inkscapeMadeEasy_Base.py
│   ├── inkscapeMadeEasy_Draw.py
│   ├── inkscapeMadeEasy_Plot.py
│   └── textextLib/
│       ├── CircuitSymbolsLatexPreamble.tex
│       ├── __init__.py
│       ├── basicLatexPackages.tex
│       ├── textext.inx
│       └── textext.py
├── LICENSE
├── README.md
├── docs/
│   ├── Makefile
│   └── source/
│       ├── Changelog.rst
│       ├── _static/
│       │   └── style.css
│       ├── conf.py
│       ├── genindex.rst
│       ├── index.rst
│       ├── installation.rst
│       ├── mainFeatures.rst
│       ├── minimalExample.rst
│       └── moduleDefinitions.rst
├── examples/
│   ├── iME_Draw_colorPicker.inx
│   ├── iME_Draw_colorPicker.py
│   ├── iME_Draw_lineStyle_and_markers.inx
│   ├── iME_Draw_lineStyle_and_markers.py
│   ├── minimalExample.inx
│   ├── minimalExample.py
│   └── testingMinimalExample.py
└── latest/
    ├── basicLatexPackages.tex
    ├── inkscapeMadeEasy_Base.py
    ├── inkscapeMadeEasy_Draw.py
    └── inkscapeMadeEasy_Plot.py

================================================
FILE CONTENTS
================================================

================================================
FILE: 0.9x/inkscapeMadeEasy_Base.py
================================================
#!/usr/bin/python

# -----------------------------------------------------------------------------
#
#    inkscapeMadeEasy: - Helper module that extends Aaron Spike's inkex.py module,
#                        focusing productivity in inkscape extension development
#
#    Copyright (C) 2016 by Fernando Moura
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# -----------------------------------------------------------------------------

import math
import os
import re
import sys
from copy import deepcopy

import numpy as np
import inkex

"""
Base helper module that extends Aaron Spike's inkex.py module, adding basic manipulation functions

This module requires the following modules: inkex, re, lxml, numpy and os.
"""


# ---------------------------------------------
class inkscapeMadeEasy(inkex.Effect):
    """
    Base class for extensions.

    This class extends the inkex.Effect class by adding several basic functions to help
    manipulating inkscape elements. All extensions should inherit this class.

    """

    def __init__(self):
        inkex.Effect.__init__(self)
        self.inkscapeResolution_dpi = 96.0  # number of pixels per inch

        resolution_in = self.inkscapeResolution_dpi
        resolution_mm = self.inkscapeResolution_dpi / 25.4

        self.unitsDict = {'mm': resolution_mm,  # 25.4mm per inch
                          'cm': resolution_mm * 10.0,  # 1cm = 10mm
                          'm': resolution_mm * 1.0e3,  # 1m = 1000mm
                          'km': resolution_mm * 1.0e6,  # 1km = 1e6mm
                          'in': resolution_in,  # 1in = 96px
                          'ft': resolution_in * 12.0,  # foot = 12*in
                          'yd': resolution_in * 12.0 * 3.0,  # yard = 3*ft
                          'pt': resolution_in / 72.0,  # point 1pt = 1/72th of an inch
                          'px': 1.0, 'pc': resolution_in / 6.0}  # picas	1pc = 1/6th of and inch

    # coordinates o the origin of the grid. unfortunately the grid does not fit
    # x0=0
    # y0=-7.637817382813

    def displayMsg(self, msg):
        """Displays a message to the user.

        :param msg: message
        :type msg: string
        
        :returns: nothing
        :rtype: -

        """
        sys.stderr.write(msg + '\n')

    def getBasicLatexPackagesFile(self):
        """Returns the full path  of the ``basicLatexPackages.tex`` file with commonly used Latex packages 

        The default packages are::

        \\usepackage{amsmath,amsthm,amsbsy,amsfonts,amssymb}
        \\usepackage[per=slash]{siunitx}
        \\usepackage{steinmetz}

        You can add other packages to the file ``basicLatexPackages.tex``, located in the extension directory.

        :returns:  Full path  of the file with commonly used Latex packages 
        :rtype: string

        """
        directory = os.getcwd()
        return directory + '/textextLib/basicLatexPackages.tex'

    def Dump(self, obj, file='./dump_file.txt', mode='w'):
        """Function to easily output the result of ``str(obj)`` to a file

        This function was created to help debugging the code while it is running under inkscape.
        Since inkscape does not possess a terminal as today (2016), this function overcomes partially the
        issue of sending things to stdout by dumping result of the function ``str()`` in a text file.


        :param obj: object to sent to a file. Any type that can be used in ``str()``
        :param file: file path. Default: ``./dump_file.txt``
        :param mode: writing mode of the file Default: ``w`` (write)
        :type obj: any, as long as ``str(obj)`` is implemented (see ``__str__()`` metaclass definition )
        :type file: string
        :type mode: string
        :returns:  nothing
        :rtype: -

        **Example**

        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy 
        >>> x=inkscapeMadeEasy
        >>> vector1=[1,2,3,4,5,6]
        >>> x.Dump(vector1,file='~/temporary.txt',mode='w')   # writes the list to a file
        >>> vector2=[7,8,9,10]
        >>> x.Dump(vector2,file='~/temporary.txt',mode='a')   # append the list to a file


        """
        file = open(file, mode)
        file.write(str(obj) + '\n')
        file.close()

    def removeElement(self, element):
        """Function to remove one element or group. If the parent of the element is a group
        and has no other children, then the parent is also removed.

        :param element: element object
        :type element: element object
        :returns:  nothing
        :rtype: -

        **Example**

        >>> rootLayer = self.document.getroot()                              # retrieves the root layer of the file
        >>> groupA = self.createGroup(rootLayer,label='temp')                # creates a group inside rootLayer
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(groupA, [[5,0]],[0,0])       # creates a line in groupA
        >>> line2 = inkscapeMadeEasy_Draw.line.relCoords(rootLayer, [[5,0]],[0,0])    # creates a line in rootLayer
        >>> line3 = inkscapeMadeEasy_Draw.line.relCoords(groupA, [[15,0]],[10,0])    # creates a line in groupA
        >>> self.removeElement(line1)                              # removes line 1
        >>> self.removeElement(line2)                              # removes line 2
        >>> self.removeElement(line3)                              # removes line 3. Also removes groupA since this group has no other children
        >>> groupB = self.createGroup(rootLayer,label='temp1')                # creates a group inside rootLayer
        >>> line4 = inkscapeMadeEasy_Draw.line.relCoords(groupB, [[5,0]],[0,0])       # creates a line in groupB
        >>> self.removeElement(groupB)                              # removes group B and all its children

        """

        parent = element.getparent()

        parent.remove(element)

        if parent.tag == 'g' and len(parent.getchildren()) == 0:  # if object's parent is a group and has no other children, remove parent as well
            temp = parent.getparent()
            if temp is not None:
                temp.remove(parent)

    def importSVG(self, parent, fileIn, createGroup=True):
        """ Import SVG file into the current document

        :param parent: parent element where all contents will be placed
        :param fileIn: SVG file path
        :param createGroup: create a group containing all imported elements. (Default: True)
        :type parent: element object
        :type fileIn: string
        :type createGroup: bool
        :returns:  imported element objects. If createGroup==True, returns the group. Otherwise returns a list with all imported elements
        :rtype: element object or list of objects

        **Example**

        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> x=inkscapeMadeEasy
        >>> rootLayer = x.document.getroot()                             # retrieves the root layer of the file
        >>> imported1 = self.importSVG(rootLayer,'/path/to/file1.svg',True) # import contents of the file and group them. imported1 is the group element
        >>> imported2 = self.importSVG(rootLayer,'/path/to/file2.svg',False) # import contents of the file. imported2 is a list of the imported elements

        """
        documentIn = inkex.etree.parse(fileIn, parser=inkex.etree.XMLParser(huge_tree=True)).getroot()

        if createGroup:
            group = self.createGroup(parent, label='importedSVG')
            for elem in documentIn:
                if elem.tag != inkex.addNS('namedview', 'sodipodi') and elem.tag != inkex.addNS('metadata', 'svg'):
                    group.append(elem)
            self.unifyDefs()
            return group
        else:
            listElements=[]
            for elem in documentIn:
                if elem.tag != inkex.addNS('namedview', 'sodipodi') and elem.tag != inkex.addNS('metadata', 'svg'):
                    parent.append(elem)
                    if elem.tag != inkex.addNS('defs', 'svg'):
                        listElements.append(elem)
            self.unifyDefs()
            return listElements

    def exportSVG(self, element, fileOut):
        """ Export the elements in a new svgfile

        This function will export the element in a new SVG file. If a list of elements is passed as argument. All elements in the list will be exported to the same file.

        :param element: element or list of elements to be exported
        :param fileOut: file path, including the extension.
        :type element: element or list of elements
        :type file: string
        :returns:  nothing
        :rtype: -

        .. note:: All the defs of the original file will be copied to the new file. Therefore you might want to run te vacuum tool to cleanup the new SVG file

        **Example**

        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> x=inkscapeMadeEasy
        >>> rootLayer = x.document.getroot()                             # retrieves the root layer of the file
        >>> groupA = x.createGroup(rootLayer,label='temp')               # creates a group inside rootLayer
        >>> groupB = x.createGroup(rootLayer,label='child')              # creates a group inside groupA
        >>> line1 = inkDraw.line.relCoords(groupA, [[10,0]],[0,0])       # creates a line in groupA
        >>> line2 = inkDraw.line.relCoords(groupB, [[20,0]],[0,0])       # creates a line in groupB
        >>> self.exportSVG(line1,'file1.svg')                            # exports only line1
        >>> self.exportSVG(groupA,'file2.svg')                           # exports groupA (and all elements contained in it)
        >>> self.exportSVG([groupA,groupB],'file3.svg')                  # exports groupA and groupB (and all elements contained in it) to the same file

        """
        document = inkex.etree.fromstring(blankSVG)

        elem_tmp = deepcopy(element)
        # add definitions
        defs_tmp = deepcopy(self.getDefinitions())
        document.append(defs_tmp)

        # add elements
        if isinstance(elem_tmp, list):
            for e in elem_tmp:
                document.append(e)
        else:
            document.append(elem_tmp)

        et = inkex.etree.ElementTree(document)
        et.write(fileOut, pretty_print=True)

    def uniqueIdNumber(self, prefix_id):
        """ Generates a unique ID with a given prefix ID by adding a numeric suffix

        This function is used to generate a valid unique ID by concatenating a given prefix with a
        numeric suffix. The overall format is ``prefix-%05d``.

        This function makes sure the ID is unique by checking in ``doc_ids`` member.
        This function is specially useful for creating an unique ID for markers and other elements in defs.


        :param prefix_id: prefix of the ID
        :type prefix_id: string
        :returns: the unique ID
        :rtype: string

        .. note:: This function has been adapted from Aaron Spike's inkex.py  https://github.com/hacktoon/ink2canvas/blob/master/ink2canvas/lib/inkex.py

        **Example**

        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy 
        >>> x=inkscapeMadeEasy
        >>> a=x.uniqueIdNumber('myName')   # a=myName-00001
        >>> b=x.uniqueIdNumber('myName')   # b=myName-00002
        >>> c=x.uniqueIdNumber('myName')   # c=myName-00003


        """
        numberID = 1
        new_id = prefix_id + '-%05d' % numberID
        while new_id in self.doc_ids:
            numberID += 1
            new_id = prefix_id + '-%05d' % numberID
        self.doc_ids[new_id] = 1

        return new_id

    # ---------------------------------------------
    def getDefinitions(self):
        """ retrieves the Defs element of the svg file.

        This function returns the element Defs of the current svg file. This elements stores the definition (e.g. marker definition)

        if no Defs can be found, a new empty Defs is created

        :returns: the defs element
        :rtype: etree element

        """
        defs = self.getElemFromXpath('/svg:svg//svg:defs')
        if defs is None:
            defs = inkex.etree.SubElement(self.document.getroot(), inkex.addNS('defs', 'svg'))

        return defs

    # ---------------------------------------------
    def unifyDefs(self):
        """Unify all <defs> nodes in a single <defs> node.

        :returns: None
        :rtype: -

        .. note:: This function does not check whether the ids are unique
        """
        root = self.getElemFromXpath('/svg:svg')
        mainDef = self.getDefinitions()

        for d in root.findall('.//svg:defs', namespaces=inkex.NSS):
            if d != mainDef:
                for child in d:
                    mainDef.append(child)
                    if child.tag == inkex.addNS('g', 'svg') or child.tag == 'g':
                        self.ungroup(child)
                d.getparent().remove(d)

    # ---------------------------------------------
    def getDefsByTag(self, tag='marker'):
        """ retrieves the Defs elements of the svg file of a given a tag.

        :returns: a list with the def elements
        :rtype: list of etree element

        """

        return self.getDefinitions().findall('.//svg:%s' % tag, namespaces=inkex.NSS)

    # ---------------------------------------------
    def getDefsById(self,id):
        """ return a list of elements in the defs node, given (part of) the id

        :returns: a list with the def elements
        :rtype: list of etree element

        """

        return self.getDefinitions().xpath('./*[contains(@id,"%s")]' % id)

    # ---------------------------------------------
    def getElemFromXpath(self, xpath):
        """ returns the element from the xml, given its xpath

        :param xpath: tag of the element to be searched
        :type xpath: string
        :returns: element
        :rtype: element

        **Example**

        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy
        >>> x=inkscapeMadeEasy
        >>> name = x.getElemFromXpath('/svg:svg//svg:defs')   # returns the list of definitions of the document

        """
        elem = self.xpathSingle(xpath)
        return elem

    # ---------------------------------------------
    def getElemAttrib(self, elem, attribName):
        """ Returns the atribute of one element, given the atribute name


        :param elem: elem under consideration
        :param attribName: attribute to be searched. Format:  namespace:attrName
        :type elem: element
        :type attribName: string
        :returns: attribute
        :rtype: string

        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy
        >>> x=inkscapeMadeEasy
        >>> elem= x.getElemFromXpath('/svg:svg')
        >>> docNAme = x.getElemAttrib(elem,'sodipodi:docname')
        """
        # splits namespace and attrib name
        atribList = attribName.split(':')

        if len(atribList) == 1:  # if has no namespace
            attrib = attribName
        else:  # if has namespace
            namespace = inkex.NSS[atribList[0]]
            attrib = '{%s}' % namespace + atribList[1]

        return elem.attrib[attrib]

    # ---------------------------------------------
    def getDocumentScale(self):
        """returns the scale of the document

        **Example**

        >>> scale = x.getDocumentScale()

        """

        try:
            elem = self.getElemFromXpath('/svg:svg')
            width = float(self.getElemAttrib(elem, 'width').replace(self.documentUnit, ''))

            viewBox = self.getElemFromXpath('/svg:svg')
            viewBox_width = float(self.getElemAttrib(viewBox, 'viewBox').split(' ')[2])

            doc_scale = viewBox_width / width
        except:
            doc_scale = 1.0

        return doc_scale

    # ---------------------------------------------
    def getDocumentName(self):
        """returns the name of the document
        
        :returns: fileName
        :rtype: string
        
        **Example**

        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy
        >>> x=inkscapeMadeEasy
        >>> name = x.getDocumentName()
        
        """
        elem = self.getElemFromXpath('/svg:svg')
        try:
            fileName = self.getElemAttrib(elem, 'sodipodi:docname')
        except:
            fileName = None
        return fileName

    # ---------------------------------------------
    def getDocumentUnit(self):
        """returns the unit of the document
        
        :returns: unit string code. See table below
        :rtype: string
        
        **Units**
        
        The list of available units are:
        
        ==================   ============   =============
        Name                 string code    relation
        ==================   ============   =============
        millimetre           mm             1in = 25.4mm
        centimetre           cm             1cm = 10mm
        metre                m              1m = 100cm
        kilometre            km             1km = 1000m
        inch                 in             1in = 96px
        foot                 ft             1ft = 12in
        yard                 yd             1yd = 3ft
        point                pt             1in = 72pt
        pixel                px             
        pica                 pc             1in = 6pc
        ==================   ============   =============
        
        **Example**
        
        >>> docunit = self.getDocumentUnit()    #returns 'cm', 'mm', etc.
        
        
        """
        elem = self.getElemFromXpath('/svg:svg/sodipodi:namedview')
        try:
            unit = self.getElemAttrib(elem, 'inkscape:document-units')
        except:
            unit = 'px'
        return unit

    # ---------------------------------------------
    def getcurrentLayer(self):
        """returns the current layer of the document
        
        :returns: name of the current layer
        :rtype: string
        
        **Example**
        
        >>> from inkscapeMadeEasy_Base import inkscapeMadeEasy 
        >>> x=inkscapeMadeEasy
        >>> name = x.getDocumentName()
        
        """
        return self.current_layer

    # ---------------------------------------------
    def removeAbsPath(self, element):
        abspath = self.getElemAttrib(element, 'sodipodi:absref')
        fileName = os.path.basename(abspath)
        dirName = os.path.dirname(abspath)

        # removes sodipodi:absref attribute
        namespace = inkex.NSS['sodipodi']
        attrib = '{%s}' % namespace + 'absref'

        element.attrib.pop(attrib, None)

        # adds sodipodi:relref
        attrib = '{%s}' % namespace + 'relref'
        element.set(attrib, fileName)

    # ---------------------------------------------
    def unit2userUnit(self, value, unit_in):
        """Converts a value from given unit to inkscape's default unit (px)

        :param value: value to be converted
        :param unit_in: input unit string code. See table below
        :type value: float
        :type unit_in: string
        :returns: converted value
        :rtype: float
        
        **Units**
        
        The list of available units are:
        
        ==================   ============   =============
        Name                 string code    relation
        ==================   ============   =============
        millimetre           mm             1in = 25.4mm
        centimetre           cm             1cm = 10mm
        metre                m              1m = 100cm
        kilometre            km             1km = 1000m
        inch                 in             1in = 96px
        foot                 ft             1ft = 12in
        yard                 yd             1yd = 3ft
        point                pt             1in = 72pt
        pixel                px             
        pica                 pc             1in = 6pc
        ==================   ============   =============

        **Example**
        
        >>> x_cm = 5.0
        >>> x_px = self.unit2userUnit(x_cm,'cm')       # converts  5.0cm -> 188.97px
        """

        return value * self.unitsDict[unit_in.lower()]

    # ---------------------------------------------
    def userUnit2unit(self, value, unit_out):
        """Converts a value from inkscape's default unit (px) to specified unit
        
        :param value: value to be converted
        :param unit_out: output unit string code. See table below
        :type value: float
        :type unit_out: string
        :returns: converted value
        :rtype: float
        
        **Units**
        
        The list of available units are:
        
        ==================   ============   =============
        Name                 string code    relation
        ==================   ============   =============
        millimetre           mm             1in = 25.4mm
        centimetre           cm             1cm = 10mm
        metre                m              1m = 100cm
        kilometre            km             1km = 1000m
        inch                 in             1in = 96px
        foot                 ft             1ft = 12in
        yard                 yd             1yd = 3ft
        point                pt             1in = 72pt
        pixel                px             
        pica                 pc             1in = 6pc
        ==================   ============   =============

        **Example**

        >>> x_px = 5.0
        >>> x_cm = self.userUnit2unit(x_px,'cm')       # converts  5.0px -> 0.1322cm
        """
        return value / float(self.unitsDict[unit_out.lower()])

    # ---------------------------------------------
    def unit2unit(self, value, unit_in, unit_out):
        """Converts a value from one provided unit to another unit
        
        :param value: value to be converted
        :param unit_in: input unit string code. See table below
        :param unit_out: output unit string code. See table below
        :type value: float
        :type unit_in: string
        :type unit_out: string
        :returns: converted value
        :rtype: float
        
        **Units**
        
        The list of available units are:
        
        ==================   ============   =============
        Name                 string code    relation
        ==================   ============   =============
        millimetre           mm             1in = 25.4mm
        centimetre           cm             1cm = 10mm
        metre                m              1m = 100cm
        kilometre            km             1km = 1000m
        inch                 in             1in = 96px
        foot                 ft             1ft = 12in
        yard                 yd             1yd = 3ft
        point                pt             1in = 72pt
        pixel                px             
        pica                 pc             1in = 6pc
        ==================   ============   =============

        **Example**

        >>> x_in = 5.0
        >>> x_cm = self.unit2unit(x_in,'in','cm')       # converts  5.0in -> 12.7cm
        """
        return value * self.unitsDict[unit_in.lower()] / float(self.unitsDict[unit_out.lower()])

    # ---------------------------------------------
    def createGroup(self, parent, label='none'):
        """Creates a new empty group of elements.

        This function creates a new empty group of elements. In order to create new elements inside
        this groups you must create them informing the group as the parent element.


        :param parent: parent object of the group. It can be another group or the root element
        :param label: label of the group. Default: 'none'
        :type parent: element object
        :type label: string
        :returns: the group object
        :rtype: group element


        .. note:: The label does not have to be unique

        **Example**

        >>> rootLayer = self.document.getroot()                              # retrieves the root layer of the file
        >>> groupA = self.createGroup(rootLayer,label='temp')                # creates a group inside rootLayer
        >>> groupB = self.createGroup(groupA,label='child')                  # creates a group inside groupA
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(groupA, [[10,0]],[0,0])       # creates a line in groupA
        >>> line2 = inkscapeMadeEasy_Draw.line.relCoords(groupB, [[20,0]],[0,0])       # creates a line in groupB
        """
        if label != 'none':
            g_attribs = {inkex.addNS('label', 'inkscape'): label}
            group = inkex.etree.SubElement(parent, 'g', g_attribs)
        else:
            group = inkex.etree.SubElement(parent, 'g')

        return group

    # ---------------------------------------------
    def ungroup(self, group):
        """Ungroup elements

        The new parent element of the ungrouped elements will be the parent of the removed group. See example below

        :param group: group to be removed
        :type group: group element
        :returns: list of the elements previously contained in the group
        :rtype: list of elements

        **Example**


        >>> rootLayer = self.document.getroot()                              # retrieves the root layer of the file
        >>> groupA = self.createGroup(rootLayer,label='temp')                # creates a group inside rootLayer
        >>> groupB = self.createGroup(groupA,label='temp')                # creates a group inside groupA
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(groupA, [[10,0]],[0,0])       # creates a line in groupA
        >>> line2 = inkscapeMadeEasy_Draw.line.relCoords(groupB, [[20,0]],[0,0])       # creates a line in groupB
        >>> line3 = inkscapeMadeEasy_Draw.line.relCoords(groupB, [[30,0]],[0,0])       # creates a line in groupB
        >>>  # at this point, the file struct is:   groupA[ line1, groupB[ line2, line3 ] ]
        >>> elemList = self.ungroup(groupB)                                    # ungroup line2 and line3. elemList is a list containing line2 and line3 elements.
        >>>  # now the file struct is:   groupA[ line1, line2, line3 ]
        """

        if group.tag == 'g' or group.tag == inkex.addNS('g', 'svg'):  # if object is a group
            parent = group.getparent()

            listElem=[]
            if parent is not None:
                for child in group:
                    parent.append(child)
                    listElem.append(child)

                self.removeElement(group)

        return listElem

    # ---------------------------------------------
    def getTransformMatrix(self, element):
        """Returns the transformation attribute of the given element and the resulting transformation matrix (numpy Array)

        This function is used to extract the transformation operator of a given element.

        :param element: element object
        :type element: element object
        :returns: [transfAttrib, transfMatrix]

          - transfAttrib: string containing all transformations as it is in the file
          - transfMatrix: numpy array with the resulting transformation matrix
        :rtype: tuple

        If the element does not have any transformation attribute, this function returns:
           - transfAttrib=''
           - transfMatrix=identity matrix
        """

        transfAttrib = ''
        transfMatrix = np.eye(3)

        if 'transform' in element.attrib:
            transfAttrib = element.attrib['transform']

        if not transfAttrib:
            return transfAttrib, transfMatrix

        # split operation into several strings
        listOperations = [e + ')' for e in transfAttrib.replace(',', ' ').split(')') if e != ""]

        for operation in listOperations:
            if 'translate' in operation:
                data = re.compile("translate\((.*?\S)\)").match(operation.lstrip()).group(1).split()  # retrieves x and y values
                x = float(data[0])
                y = float(data[1])
                mat = np.array([[1, 0, x], [0, 1, y], [0, 0, 1]])
                transfMatrix = np.dot(transfMatrix, mat)

            if 'scale' in operation:
                data = re.compile("scale\((.*?\S)\)").match(operation.lstrip()).group(1).split()  # retrieves x and y values
                scalex = float(data[0])
                if len(data) == 2:
                    scaley = float(data[1])
                else:
                    scaley = scalex
                mat = np.diag([scalex, scaley, 1])
                transfMatrix = np.dot(transfMatrix, mat)

            if 'rotate' in operation:
                data = re.compile("rotate\((.*?\S)\)").match(operation.lstrip()).group(1).split()  # retrieves x and y values
                angleRad = -float(data[0]) * np.pi / 180.0  # negative angle because inkscape is upside down =(
                matRot = np.array([[np.cos(angleRad), np.sin(angleRad), 0], [-np.sin(angleRad), np.cos(angleRad), 0], [0, 0, 1]])
                if len(data) == 3:  # must translate before and after rotation
                    x = float(data[1])
                    y = float(data[2])
                    matBefore = np.array([[1, 0, x], [0, 1, y], [0, 0, 1]])  # translation before rotation
                    matAfter = np.array([[1, 0, -x], [0, 1, -y], [0, 0, 1]])  # translation after rotation
                    matRot = np.dot(matBefore, matRot)
                    matRot = np.dot(matRot, matAfter)

                transfMatrix = np.dot(transfMatrix, matRot)

            if 'skewX' in operation:
                data = re.compile("skewX\((.*?\S)\)").match(operation.lstrip()).group(1).split()  # retrieves x and y values
                angleRad = float(data[0]) * np.pi / 180.0
                mat = np.array([[1, np.tan(angleRad), 0], [0, 1, 0], [0, 0, 1]])
                transfMatrix = np.dot(transfMatrix, mat)

            if 'skewY' in operation:
                data = re.compile("skewY\((.*?\S)\)").match(operation.lstrip()).group(1).split()  # retrieves x and y values
                angleRad = float(data[0]) * np.pi / 180.0
                mat = np.array([[1, 0, 0], [np.tan(angleRad), 1, 0], [0, 0, 1]])
                transfMatrix = np.dot(transfMatrix, mat)

            if 'matrix' in operation:
                data = re.compile("matrix\((.*?\S)\)").match(operation.lstrip()).group(1).split()  # retrieves x and y values
                a = float(data[0])
                b = float(data[1])
                c = float(data[2])
                d = float(data[3])
                e = float(data[4])
                f = float(data[5])
                mat = np.array([[a, c, e], [b, d, f], [0, 0, 1]])
                transfMatrix = np.dot(transfMatrix, mat)

        return transfAttrib, transfMatrix

    # ---------------------------------------------
    def rotateElement(self, element, center, angleDeg):
        """Rotates the element using the transformation attribute.

        It is possible to rotate elements isolated or entire groups.

        :param element: element object to be rotated
        :param center: tuple with center point of rotation
        :param angleDeg: angle of rotation in degrees, counter-clockwise direction
        :type element: element object
        :type center: tuple
        :type angleDeg: float
        :returns:  nothing
        :rtype: -

        **Example**

        >>> rootLayer = self.document.getroot()                              # retrieves the root layer of the file
        >>> groupA = self.createGroup(rootLayer,label='temp')                # creates a group inside rootLayer
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(groupA, [[5,0]],[0,0])       # creates a line in groupA
        >>> line2 = inkscapeMadeEasy_Draw.line.relCoords(rootLayer, [[5,0]],[0,0])    # creates a line in rootLayer
        >>> self.rotateElement(line2,[0,0],120)                              # rotates line2 120 degrees around center x=0,y=0
        >>> self.rotateElement(groupA,[1,1],-90)                             # rotates groupA -90 degrees around center x=1,y=1
        """
        transfString = ''

        if angleDeg == 0:
            return

        if 'transform' in element.attrib:
            transfString = element.attrib['transform']

        # if transform attribute is present, we must add the new rotation
        if transfString:
            newTransform = 'rotate(%f %f %f) %s' % (-angleDeg, center[0], center[1], transfString)  # negative angle bc inkscape is upside down
        else:  # if no transform attribute was found
            newTransform = 'rotate(%f %f %f)' % (-angleDeg, center[0], center[1])  # negative angle bc inkscape is upside down

        element.attrib['transform'] = newTransform

    def copyElement(self, element, newParent, distance=None, angleDeg=None):
        """Copies one element to the same or other parent Element.

        It is possible to copy elements isolated or entire groups.

        :param element: element object to be copied
        :param newParent: New parent object. It can be another group or the group
        :param distance: tuple with the distance to move the object. If None, then the copy is placed at the same position
        :param angleDeg: angle of rotation in degrees, counter-clockwise direction
        :type element: element object
        :type newParent: element object
        :type distance: tuple
        :type angleDeg: float
        :returns:  newElement
        :rtype: element object

        **Example**

        >>> rootLayer = self.document.getroot()                              # retrieves the root layer of the file
        >>> groupA = self.createGroup(rootLayer,label='temp')                # creates a group inside rootLayer
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(groupA, [[5,0]],[0,0])       # creates a line in groupA
        >>> line2 = inkscapeMadeEasy_Draw.line.relCoords(rootLayer, [[5,0]],[0,0])    # creates a line in rootLayer
        >>> self.copyElement(line2,groupA)                                  # create a copy of line2 in groupA
        >>> self.moveElement(groupA,[10,-10])                                # moves line2  DeltaX=10, DdeltaY=-10
        """
        newElem = deepcopy(element)
        newParent.append(newElem)

        if distance is not None:
            self.moveElement(newElem, distance)

        if angleDeg is not None:
            self.rotateElement(newElem, self.getCenter(newElem), angleDeg)

        return newElem

    # ---------------------------------------------
    def moveElement(self, element, distance):
        """Moves the element using the transformation attribute.

        It is possible to move elements isolated or entire groups.

        :param element: element object to be rotated
        :param distance: tuple with the distance to move the object
        :type element: element object
        :type distance: tuple
        :returns:  nothing
        :rtype: -

        **Example**

        >>> rootLayer = self.document.getroot()                              # retrieves the root layer of the file
        >>> groupA = self.createGroup(rootLayer,label='temp')                # creates a group inside rootLayer
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(groupA, [[5,0]],[0,0])       # creates a line in groupA
        >>> line2 = inkscapeMadeEasy_Draw.line.relCoords(rootLayer, [[5,0]],[0,0])    # creates a line in rootLayer
        >>> self.moveElement(line2,[10,10])                                  # moves line2  DeltaX=10, DdeltaY=10
        >>> self.moveElement(groupA,[10,-10])                                # moves line2  DeltaX=10, DdeltaY=-10
        """

        if distance == 0:
            return

        transfString = ''

        if 'transform' in element.attrib:
            transfString = element.attrib['transform']

        # if transform attribute is present, we must add the new translation
        if transfString:
            newTransform = 'translate(%f %f) %s ' % (distance[0], distance[1], transfString)
        else:  # if no transform attribute was found
            newTransform = 'translate(%f %f)' % (distance[0], distance[1])

        element.attrib['transform'] = newTransform

    # ---------------------------------------------
    def scaleElement(self, element, scaleX=1.0, scaleY=0.0, center=None):
        """Scales the element using the transformation attribute.

        It is possible to scale elements isolated or entire groups.

        :param element: element object to be rotated
        :param scaleX: scaling factor in X direction. Default=1.0
        :param scaleY: scaling factor in Y direction. Default=0.0
        :param center: center point considered as the origin for the scaling. Default=None. If None, the origin is adopted
        :type element: element object
        :type scaleX: float
        :type scaleX: float
        :type center: tuple
        :returns:  nothing
        :rtype: -


        .. note:: If scaleY==0, then scaleY=scaleX is assumed (default behavior)

        **Example**

        >>> rootLayer = self.document.getroot()                              # retrieves the root layer of the file
        >>> groupA = self.createGroup(rootLayer,label='temp')                # creates a group inside rootLayer
        >>> circ1 = centerRadius(groupA,centerPoint=[0,0],radius=1.0)        # creates a line in groupA
        >>> circ2 = centerRadius(rootLayer,centerPoint=[0,0],radius=1.0)     # creates a line in rootLayer
        >>> self.scaleElement(circ1,2.0)                                     # scales x2 in both X and Y directions
        >>> self.scaleElement(circ1,2.0,3.0)                                 # scales x2 in X and x3 in Y
        >>> self.scaleElement(groupA,0.5)                                    # scales x0.5 the group in both X and Y directions
        """
        if center is not None:
            self.moveElement(element, [-center[0], -center[1]])

        transfString = ''

        if 'transform' in element.attrib:
            transfString = element.attrib['transform']

        # if transform attribute is present, we must add the new translation
        if transfString:
            if scaleY != 0.0:
                newTransform = 'scale(%f %f) %s ' % (scaleX, scaleY, transfString)
            else:
                newTransform = 'scale(%f) %s' % (scaleX, transfString)
        else:  # if no transform attribute was found
            if scaleY != 0.0:
                newTransform = 'scale(%f %f)' % (scaleX, scaleY)
            else:
                newTransform = 'scale(%f)' % scaleX

        element.attrib['transform'] = newTransform

        if center is not None:
            self.moveElement(element, [center[0], center[1]])

    # ---------------------------------------------
    def findMarker(self, markerName):
        """Search for markerName in the document.

        :param markerName: marker name
        :type markerName: string

        :returns: True if markerName was found
        :rtype: bool
        """

        for m in self.getDefsByTag(tag='marker'):
            if m.get('id') == markerName:
                return True

        return False

    # ---------------------------------------------
    def getPoints(self, element):
        """Retrieves the list of points of the element.

        This function works on paths, texts or groups. In the case of a group, the function will include recursively all its components

        :param element: element object
        :type element: element object
        :returns: list of points
        :rtype: list of list

        .. note:: This function will consider any transformation stored in transform attribute,
            that is, it will compute the resulting coordinates of each object

        **Example**

        >>> rootLayer = self.document.getroot()                                   # retrieves the root layer of the file
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(rootLayer, [[5,0],[0,6]],[0,0])   # creates a line in groupA
        >>> list = self.getPoints(line1)                                          # gets list = [[0.0, 0.0], [5.0, 0.0], [5.0, 6.0]]

        """

        # stores the list of coordinates
        listCoords = []

        # check if element is valid. 'path', 'text' and 'g' are valid
        accepted_strings = set([inkex.addNS('path', 'svg'), inkex.addNS('text', 'svg'), 'g', 'path', 'use', inkex.addNS('use', 'svg')])
        if element.tag not in accepted_strings:
            return listCoords

        if element.tag == inkex.addNS('path', 'svg') or element.tag == 'path':  # if object is path

            # adds special character between letters and splits. the first regular expression excludes e and E bc they are used to represent scientific notation  =S
            dString = re.sub('([a-df-zA-DF-Z])+?', r'#\1#', element.attrib['d']).replace('z', '').replace('Z', '').replace(',', ' ').split('#')

            dString = [i.lstrip() for i in dString]  # removes leading spaces from strings
            dString = filter(None, dString)  # removes empty elements
            Xcurrent = 0
            Ycurrent = 0

            while len(dString) > 0:
                commandType = dString[0]
                argument = [float(x) for x in dString[1].split()]  # extracts arguments from M command and converts to float
                del dString[0]
                del dString[0]

                if commandType in 'mMlLtT':  # extracts points from command 'move to' M/m or 'line to' l/L or 'smooth quadratic Bezier curveto't/T
                    X = argument[0::2]  # 2 parameters per segment, x is 1st
                    Y = argument[1::2]  # 2 parameters per segment, y is 2nd

                if commandType in 'hH':  # extracts points from command 'horizontal line' h/H
                    X = argument
                    Y = [Ycurrent] * len(X)

                if commandType in 'vV':  # extracts points from command 'vertical line' v/V
                    Y = argument
                    X = [Xcurrent] * len(Y)

                if commandType in 'cC':  # extracts points from command 'Bezier Curve' c/C
                    X = argument[4::6]  # 6 parameters per segment, x is 5th
                    Y = argument[5::6]  # 6 parameters per segment, y is 6th

                if commandType in 'sSqQ':  # extracts points from command 'quadratic Bezier Curve' q/Q or 'smooth curveto' s/S
                    X = argument[2::4]  # 4 parameters per segment, x is 3rd
                    Y = argument[3::4]  # 4 parameters per segment, y is 4th

                if commandType in 'aA':  # extracts points from command 'arc' a/A
                    X = argument[5::7]  # 7 parameters per segment, x is 6th
                    Y = argument[6::7]  # 7 parameters per segment, y is 7th

                if commandType in 'h':  # if h
                    for i in range(0, len(X)):  # convert to abs coordinates
                        if i == 0:
                            X[i] = X[i] + Xcurrent
                        else:
                            X[i] = X[i] + X[i - 1]

                if commandType in 'v':  # if v
                    for i in range(0, len(Y)):  # convert to abs coordinates
                        if i == 0:
                            Y[i] = Y[i] + Ycurrent
                        else:
                            Y[i] = Y[i] + Y[i - 1]

                if commandType in 'mltcsqa':  # if m or l
                    for i in range(0, len(X)):  # convert to abs coordinates
                        if i == 0:
                            X[i] = X[i] + Xcurrent
                            Y[i] = Y[i] + Ycurrent
                        else:
                            X[i] = X[i] + X[i - 1]
                            Y[i] = Y[i] + Y[i - 1]

                coords = zip(X, Y)
                listCoords.extend(coords)
                Xcurrent = X[-1]
                Ycurrent = Y[-1]

        if element.tag == inkex.addNS('text', 'svg'):  # if object is a text
            x = float(element.attrib['x'])
            y = float(element.attrib['y'])
            coords = [[x, y]]
            listCoords.extend(coords)

        if element.tag == 'g':  # if object is a group
            for obj in element.iterchildren("*"):
                if obj != element and obj.tag != 'defs':
                    listPoints = self.getPoints(obj)
                    listCoords.extend(listPoints)

        if element.tag == 'use' or element.tag == inkex.addNS('use', 'svg'):  # if object is a use
            listCoordsTemp = []
            x = float(element.attrib['x'])
            y = float(element.attrib['y'])
            link = self.getElemAttrib(element, 'xlink:href').replace('#','')
            elemLink = self.getElementById(link)
            for obj in elemLink.iter():
                if obj != elemLink:
                    listPoints = self.getPoints(obj)
                    listCoordsTemp.extend(listPoints)

            #apply translation
            listCoords=[[coord[0]+x,coord[1]+y] for coord in listCoordsTemp]


        # apply transformation
        if len(listCoords)>0:

            # creates numpy array with the points to be transformed
            transfMat = self.getTransformMatrix(element)[1]

            coordsNP = np.hstack((np.array(listCoords), np.ones([len(listCoords), 1]))).transpose()

            coordsTransformed = np.dot(transfMat, coordsNP)
            coordsTransformed = np.delete(coordsTransformed, 2, 0).transpose().tolist()  # remove last line, transposes and converts to list of lists

        else:
            coordsTransformed = []

        return coordsTransformed

    # ---------------------------------------------
    def getBoundingBox(self, element):
        """Retrieves the bounding Box of the element.

        This function works on paths, texts or groups. In the case of a group, the function will consider recursively all its components

        :param element: element object
        :type element: element object
        :returns: two lists: [xMin,yMin] and [xMax,yMax]
        :rtype: list

        .. note:: This function will consider any transformation stored in transform attribute,
            that is, it will compute the resulting coordinates of each object

        **Example**

        >>> rootLayer = self.document.getroot()                                   # retrieves the root layer of the file
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(rootLayer, [[5,0],[0,6]],[0,0])   # creates a line in groupA
        >>> BboxMin,BboxMax = self.getBoundingBox(line1)                          # gets BboxMin = [0.0, 0.0] and BboxMax = [5.0, 6.0]

        """
        coords = self.getPoints(element)
        coordsNP = np.array(coords)

        bboxMax = np.max(coordsNP, 0)
        bboxMin = np.min(coordsNP, 0)
        return bboxMin.tolist(), bboxMax.tolist()

    # ---------------------------------------------
    def getCenter(self, element):
        """Retrieves the center coordinates of the bounding Box of the element.

        This function works on paths, texts or groups. In the case of a group, the function will consider recursively all its components

        :param element: element object
        :type element: element object
        :returns: two lists: [xCenter,yCenter]
        :rtype: list

        .. note:: This function will consider any transformation stored in transform attribute,
            that is, it will compute the resulting coordinates of each object

        **Example**

        >>> rootLayer = self.document.getroot()                                   # retrieves the root layer of the file
        >>> line1 = inkscapeMadeEasy_Draw.line.relCoords(rootLayer, [[5,0],[0,6]],[0,0])   # creates a line in groupA
        >>> Center = self.getCenter(line1)                                        # gets Center = [2.5, 3.0]

        """

        bboxMin, bboxMax = self.getBoundingBox(element)

        bboxCenter = [(bboxMax[0] + bboxMin[0]) / 2, (bboxMax[1] + bboxMin[1]) / 2]

        return bboxCenter

    def getSegmentFromPoints(self, pointList, normalDirection='R'):
        """given two points of a straight line segment, returns the parameters of that segment:
        
        length, angle (in radians), tangent unitary vector and normal unitary vector

        :param pointList: start and end coordinates [ Pstart, Pend ]
        :param normalDirection:
        
          - 'R': normal vector points to the right of the tangent vector (Default)
          - 'L': normal vector points to the left of the tangent vector (Default)
          
        :type pointList: list of points
        :type normalDirection: string
    
        :returns: list: [length, theta, t_versor,n_versor]
        :rtype: list
        
        **Example**

        >>> segmentParam = getSegmentFromPoints([[1,1],[2,2]],'R')                               # returns [1.4142, 0.78540, [0.7071,0.7071], [0.7071,-0.7071] ]
        >>> segmentParam = getSegmentFromPoints([[1,1],[2,2]],'L')                               # returns [1.4142, 0.78540, [0.7071,0.7071], [-0.7071,0.7071] ]


        """

        # tangent versor (pointing P2)
        P1 = np.array(pointList[0])
        P2 = np.array(pointList[1])

        t_vector = P2 - P1
        length = np.linalg.norm(t_vector)
        t_versor = t_vector / length

        # normal vector: counter-clockwise with respect to tangent vector
        if normalDirection in 'rR':
            n_versor = np.array([t_versor[1], -t_versor[0]])
        if normalDirection in 'lL':
            n_versor = np.array([-t_versor[1], t_versor[0]])

        # angle
        theta = math.atan2(t_versor[1], t_versor[0])

        return [length, theta, t_versor, n_versor]

    def getSegmentParameters(self, element, normalDirection='R'):
        """given a path segment composed by only two points, returns the parameters of that segment:
        
        length, angle (in radians), start point, end point, tangent unitary vector and normal unitary vector

        This function works with paths only.

        :param element: path element object
        :param normalDirection:
        
          - 'R': normal vector points to the right of the tangent vector (Default)
          - 'L': normal vector points to the left of the tangent vector (Default)
          
        :type element: element object
        :type normalDirection: string
        
        :returns: list: [Pstart,Pend,length, theta, t_versor,n_versor]
        :rtype: list

        If the element is not a path, the function returns an empty list
        If the path element has more than two points, the function returns an empty list
        
           
        .. note:: This function will consider any transformation stored in transform attribute,
            that is, it will compute the resulting coordinates of each object

        **Example**

        >>> rootLayer = self.document.getroot()                                      # retrieves the root layer of the file
        >>> line1 = inkscapeMadeEasy_Draw.line.absCoords(rootLayer, [[1,1],[2,2]])   # creates a line in groupA
        >>> segementList = getSegmentParameters(line1,'R')                               # returns [[1,1], [2,2],1.4142, 0.78540,  [0.7071,0.7071], [0.7071,-0.7071] ]

        """

        # check if element is valid. 'path'
        accepted_strings = set([inkex.addNS('path', 'svg'), 'path'])
        if element.tag not in accepted_strings:
            return []

        listPoints = self.getPoints(element)
        if len(listPoints) > 2:  # if the path has more than two points
            return []

        data = self.getSegmentFromPoints(listPoints, normalDirection)

        return listPoints + data


blankSVG = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="210mm"
   height="297mm"
   viewBox="0 0 793.7008056641 1122.51965332"
   version="1.1"
   id="svg1406"
   inkscape:version="0.92.3 (2405546, 2018-03-11)"
   sodipodi:docname="blank.svg">
  <metadata
     id="metadata1412">
    <rdf:RDF>
      <cc:Work
         rdf:about="">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <sodipodi:namedview
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1"
     objecttolerance="10"
     gridtolerance="10"
     guidetolerance="10"
     inkscape:pageopacity="0"
     inkscape:pageshadow="2"
     inkscape:window-width="1920"
     inkscape:window-height="1056"
     id="namedview1408"
     showgrid="true"
     inkscape:snap-text-baseline="true"
     inkscape:zoom="0.7296085858586"
     inkscape:cx="396.8503937008"
     inkscape:cy="561.2598425197"
     inkscape:window-x="0"
     inkscape:window-y="0"
     inkscape:window-maximized="1"
     inkscape:current-layer="svg1406">
    <inkscape:grid
       type="xygrid"
       id="grid1957" />
  </sodipodi:namedview>
</svg>

"""


================================================
FILE: 0.9x/inkscapeMadeEasy_Draw.py
================================================
#!/usr/bin/python

# --------------------------------------------------------------------------------------
#
#    inkscapeMadeEasy: - Helper module that extends Aaron Spike's inkex.py module,
#                        focusing productivity in inkscape extension development
#
#    Copyright (C) 2016 by Fernando Moura
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# --------------------------------------------------------------------------------------

# Please uncomment (remove the # character) in the following line to disable LaTeX support via textext extension.
# useLatex=False


try:
    useLatex
except NameError:
    useLatex = True
else:
    useLatex = False

import math

import numpy as np

import inkex
import simplestyle

if useLatex:
    import textextLib.textext as textext

import sys
import os
import tempfile
import copy

"""
This module contains a set of classes and some functions to help dealing with drawings.

This module requires the following modules: inkex, math, simplestyle (from inkex module), numpy, lxml and sys

"""


def displayMsg(msg):
    """Displays a message to the user.

    :returns: nothing
    :rtype: -
    
    .. note:: Identical function has been also defined inside inkscapeMadeEasy class   
    
    """
    sys.stderr.write(msg + '\n')


def Dump(obj, file='./dump_file.txt', mode='w'):
    """Function to easily output the result of ``str(obj)`` to a file

    This function was created to help debugging the code while it is running under inkscape. Since inkscape does not possess a terminal as today (2016),
    this function overcomes partially the issue of sending things to stdout by dumping result of the function ``str()`` in a text file.


    :param obj: object to sent to the file. Any type that can be used in ``str()``
    :param file: file path. Default: ``./dump_file.txt``
    :param mode: writing mode of the file. Default: ``w`` (write)
    :type obj: any, as long as ``str(obj``) is implemented (see ``__str__()`` metaclass definition )
    :type file: string
    :type mode: string
    :returns: nothing
    :rtype: -

    .. note:: Identical function has been also defined inside inkscapeMadeEasy class   

    **Example**

    >>> vector1=[1,2,3,4,5,6]
    >>> Dump(vector1,file='~/temporary.txt',mode='w')   # writes the list to a file
    >>> vector2=[7,8,9,10]
    >>> Dump(vector2,file='~/temporary.txt',mode='a')   # append the list to a file

    """
    file = open(file, mode)
    file.write(str(obj) + '\n')
    file.close()


class color():
    """
    Class to manipulate colors.

    This class manipulates color information, generating a string in inkscape's expected format ``#RRGGBB``

    This class contains only static methods so that you don't have to inherit this in your class

    .. note:: alpha channel is not implemented yet. Assume alpha=1.0

    """

    @staticmethod
    def defined(colorName):
        """ Returns the color string representing a predefined color name

        :param colorName: color name
        :type colorName: string
        :returns:  string representing the color in inkscape's expected format ``#RRGGBB``
        :rtype: string

        **Available pre defined colors**

        .. image:: ../imagesDocs/Default_colors.png
          :width: 400px

        **Example**

        >>> colorString = color.defined('red')                   # returns #ff0000 representing red color

        """
        if colorName not in ['Dred', 'red', 'Lred', 'Dblue', 'blue', 'Lblue', 'Dgreen', 'green', 'Lgreen', 'Dyellow', 'yellow', 'Lyellow', 'Dmagen',
                             'magen', 'Lmagen', 'black', 'white']:
            sys.exit("InkscapeDraw.color.defined() :  Error. color -->" + colorName + "<-- not defined")

        if colorName == 'Dred':
            return '#800000'
        if colorName == 'red':
            return '#FF0000'
        if colorName == 'Lred':
            return '#FF8181'

        if colorName == 'Dblue':
            return '#000080'
        if colorName == 'blue':
            return '#0000FF'
        if colorName == 'Lblue':
            return '#8181FF'

        if colorName == 'Dgreen':
            return '#008000'
        if colorName == 'green':
            return '#00FF00'
        if colorName == 'Lgreen':
            return '#81FF81'

        if colorName == 'black':
            return '#000000'
        if colorName == 'white':
            return '#FFFFFF'

        if colorName == 'Dyellow':
            return '#808000'
        if colorName == 'yellow':
            return '#FFFF00'
        if colorName == 'Lyellow':
            return '#FFFF81'

        if colorName == 'Dmagen':
            return '#800080'
        if colorName == 'magen':
            return '#FF00FF'
        if colorName == 'Lmagen':
            return '#FF81FF'

    @staticmethod
    def RGB(RGBlist):
        """ returns a string representing a color specified by RGB level in the range 0-255

        :param RGBlist: list containing RGB levels in the range 0-225 each
        :type RGBlist: list
        :returns:  string representing the color in inkscape's expected format ``#RRGGBB``
        :rtype: string

        **Example**

        >>> colorString = color.RGB([120,80,0])                   # returns a string representing the color R=120, G=80, B=0

        """
        RGBhex = [''] * 3
        for i in range(3):
            if RGBlist[i] > 255:
                RGBlist[i] = 255

            if RGBlist[i] < 0:
                RGBlist[i] = 0

            if RGBlist[i] < 16:
                RGBhex[i] = '0' + hex(int(RGBlist[i]))[2:].upper()

            else:
                RGBhex[i] = hex(int(RGBlist[i]))[2:].upper()

        return '#' + '%s%s%s' % (RGBhex[0], RGBhex[1], RGBhex[2])

    # ---------------------------------------------
    @staticmethod
    def gray(percentage):
        """ returns a gray level compatible string based on white percentage between 0.0 and 1.0

        if percentage is higher than 1.0, percentage is truncated to 1.0 (white)
        if percentage is lower than 0.0, percentage is truncated to 0.0 (black)

        :param percentage: value between 0.0 (black) and 1.0 (white)
        :type percentage: float
        :returns:  string representing the color in inkscape's expected format ``#RRGGBB``
        :rtype: string

        **Example**

        >>> colorString = color.gray(0.6)                   # returns a string representing the gray level with 60% of white

        """
        RGBLevel = 255 * percentage

        if percentage > 1.0:
            RGBLevel = 255
        if percentage < 0.0:
            RGBLevel = 0

        return color.RGB([RGBLevel] * 3)

    # ---------------------------------------------
    @staticmethod
    def colorPickerToRGBalpha(colorPickerString):
        """ Function that converts the string returned by  the widget 'color' in the .inx file into 2 strings,
        one representing the color in format ``#RRGGBB`` and the other representing the alpha channel ``AA``

        :param colorPickerString: string returned by 'color' widget
        :type colorPickerString: string
        :returns: a list of strings: [color,alpha]
                    - color: string in ``#RRGGBB`` format
                    - alpha: string in ``AA`` format
        :rtype: list

        .. note:: For more information on this widget, see <http://wiki.inkscape.org/wiki/index.php/INX_Parameters> 

        .. Warning:: you probably don't need to use this function. Consider using the method ``color.parseColorPicker()``

        **usage**

        1- in your inx file you must have one attribute of the type 'color'::

          <param name="myColorPicker" type="color"></param>

        2- in your .py file, you must parse it as a string:
          >>>   self.OptionParser.add_option("--myColorPicker", action="store", type="string", dest="myColorPickerVar", default='0') 

        3- call this function to convert so.myColorPickerVar to two strings
                 - #RRGGBB   with RGB values in hex
                 - AA       with alpha value in hex

        **Example**

        Let your .inx file contains a widget of type 'color' with the name myColorPicker::

        <param name="myColorPicker" type="color"></param>

        Then in the .py file

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     inkex.Effect.__init__(self) 
        >>>     self.OptionParser.add_option("--myColorPicker", action="store", type="string", dest="myColorPickerVar", default='#000000')  # parses the input parameter
        >>> 
        >>>   def effect(self):
        >>>     color,alpha = inkDraw.color.colorPickerToRGBalpha(self.options.myColorPickerVar)       # returns the string representing the selected color and alpha channel

        """
        colorHex = hex(int(colorPickerString) & 0xffffffff)[2:].zfill(
            8).upper()  # [2:] removes the 0x ,  zfill adds the leading zeros, upper: uppercase
        RGB = '#' + colorHex[0:6]
        alpha = colorHex[6:]
        return [RGB, alpha]

    # ---------------------------------------------
    @staticmethod
    def parseColorPicker(stringColorOption, stringColorPicker):
        """ Function that converts the string returned by  the widgets 'color' and 'optiongroup' in the .inx file into 2 strings,
        one representing the color in format ``#RRGGBB`` and the other representing the alpha channel ``AA``

        You must have in your .inx both 'optiongroup' and 'color' widgets as defined below. You don't have to have all the color options presented in the example.
        That is the most complete example, considering the default colors in color.defined method.


        :param stringColorOption: string returned by 'optiongroup' widget
        :type stringColorOption: string
        :param stringColorPicker: string returned by 'color' widget
        :type stringColorPicker: string
        :returns: a list of strings: [color,alpha]
                    - color: string in ``#RRGGBB`` format
                    - alpha: string in ``AA`` format
        :rtype: list

        .. note:: For more information on this widget, see <http://wiki.inkscape.org/wiki/index.php/INX_Parameters> 

        **Example**

        It works in the following manner: The user select in the optiongroup list the desired color. All pre defined colors are listed there.
        There is also a 'my default color' where you can set your preferred default color and a 'use color picker' to select from the color picker widget.
        Keep in mind that the selected color in this widget will be considered ONLY if 'use color picker' option is selected.

        Let your .inx file contains a widget of type 'color' with the name 'myColorPicker' and another 'optiongroup' with the name 'myColorOption'::

         <param name="myColorOption" type="optiongroup" appearance="minimal" _gui-text="some text here">
             <_option value="#FF0022">my default color</_option>    <--you can set your pre define color in the form #RRGGBB
             <_option value="none">none</_option>                   <-- no color
             <_option value="black">black</_option>
             <_option value="red">red</_option>
             <_option value="blue">blue</_option>
             <_option value="yellow">yellow</_option>
             <_option value="green">green</_option>          <-- these are all standardized colors in inkscapeMadeEasy_Draw.color class!
             <_option value="magen">magenta</_option>
             <_option value="white">white</_option>
             <_option value="Lred">Lred</_option>
             <_option value="Lblue">Lblue</_option>
             <_option value="Lyellow">Lyellow</_option>
             <_option value="Lgreen">Lgreen</_option>
             <_option value="Lmagen">Lmagenta</_option>
             <_option value="Dred">Dred</_option>
             <_option value="Dblue">Dblue</_option>
             <_option value="Dyellow">Dyellow</_option>
             <_option value="Dgreen">Dgreen</_option>
             <_option value="Dmagen">Dmagenta</_option>
             <_option value="picker">use color picker</_option>     <-- indicate that the color must be taken from the colorPicker attribute
         </param>
         <param name="myColorPicker" type="color"></param>

        Then in the .py file

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     inkex.Effect.__init__(self) 
        >>>     self.OptionParser.add_option("--myColorPicker", action="store", type="string", dest="myColorPickerVar", default='0')       # parses the input parameters
        >>>     self.OptionParser.add_option("--myColorOption", action="store", type="string", dest="myColorOptionVar", default='black')   # parses the input parameter
        >>> 
        >>>   def effect(self):
        >>>     so = self.options
        >>>     [RGBstring,alpha] = inkDraw.color.parseColorPicker(so.myColorOptionVar,so.myColorPickerVar)

        """
        alphaString = 'FF'
        if stringColorOption.startswith("#"):
            return [stringColorOption, alphaString]
        else:
            if stringColorOption == 'none':
                colorString = 'none'
            else:
                if stringColorOption == 'picker':
                    [colorString, alphaString] = color.colorPickerToRGBalpha(stringColorPicker)
                else:
                    colorString = color.defined(stringColorOption)
        return [colorString, alphaString]


class marker():
    """
    Class to manipulate markers.

    This class is used to create new custom markers. Markers can be used with the lineStyle class to define line types that include start, mid and end markers

    This class contains only static methods so that you don't have to inherit this in your class

    """

    # ---------------------------------------------
    @staticmethod
    def createMarker(ExtensionBaseObj, nameID, markerPath, RenameMode=0, strokeColor=color.defined('black'), fillColor=color.defined('black'),
                     lineWidth=1.0, markerTransform=None):
        """Creates a custom line marker

        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param nameID: nameID of the marker
        :param markerPath: path definition. Must follow 'd' attribute format. See <https://www.w3.org/TR/SVG/paths.html#PathElement> for further information
        :param RenameMode: Renaming behavior mode

             - 0: (default) do not rename the marker. If nameID is already taken, the marker will not be modified.
             - 1: overwrite marker definition if nameID is already taken
             - 2: Create a new unique nameID, adding a suffix number (Please refer to inkscapeMadeEasy.uniqueIdNumber(prefix_id) ).

        :param strokeColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param fillColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param lineWidth: line width of the marker. Default: 1.0
        :param markerTransform: custom transform applied to marker's path. Default: ``None`` 

           the transform must follow 'transform' attribute format. See <https://www.w3.org/TR/SVG/coords.html#TransformAttribute> for further information

        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type nameID: string
        :type markerPath: string
        :type RenameMode: int
        :type strokeColor: string
        :type fillColor: string
        :type lineWidth: float
        :type markerTransform: string

        :returns: NameID of the new marker
        :rtype: string

        **System of coordinates**

        The system of coordinates of the marker depends on the point under consideration. The following figure presents the coordinate system for all cases

        .. image:: ../imagesDocs/marker_Orientation.png
          :width: 600px

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     nameID='myMarker'
        >>>     markerPath='M 3,0 L 0,1 L 0,-1 z'   # defines a path forming an triangle with vertices (3,0) (0,1) (0,-1)
        >>>     strokeColor=inkDraw.color.defined('red')
        >>>     fillColor=None
        >>>     RenameMode=1
        >>>     width=1
        >>>     markerTransform=None
        >>>     markerID=inkDraw.marker.createMarker(self,nameID,markerPath,RenameMode,strokeColor,fillColor,width,markerTransform)
        >>>     myLineStyle = inkDraw.lineStyle.set(1.0, markerEnd=markerID,lineColor=inkDraw.color.defined('black'))  # see lineStyle class for further information on this function
        >>> 
        >>>     #tries to make another marker with the same nameID, changing RenameMode
        >>>     strokeColor=inkDraw.color.defined('blue')
        >>>     RenameMode=0
        >>>     markerID=inkDraw.marker.createMarker(self,nameID,RenameMode,scale,strokeColor,fillColor) # this will not modify the marker
        >>>     RenameMode=1
        >>>     markerID=inkDraw.marker.createMarker(self,nameID,RenameMode,scale,strokeColor,fillColor) # modifies the marker 'myMarker'
        >>>     RenameMode=2
        >>>     markerID=inkDraw.marker.createMarker(self,nameID,RenameMode,scale,strokeColor,fillColor) # creates a new marker with nameID='myMarker-0001'

        .. note:: In next versions, path definition and transformation will be modified to make it easier. =)
        """

        # print tostring(ExtensionBaseObj.getDefinitions())

        if RenameMode == 0 and ExtensionBaseObj.findMarker(nameID):
            return nameID

        if RenameMode == 2:
            numberID = 1
            new_id = nameID + '_n%05d' % numberID
            while new_id in ExtensionBaseObj.doc_ids:
                numberID += 1

                new_id = nameID + '_n%05d' % numberID
            ExtensionBaseObj.doc_ids[new_id] = 1
            nameID = new_id

        if RenameMode == 1 and ExtensionBaseObj.findMarker(nameID):
            defs = ExtensionBaseObj.getDefinitions()
            for obj in defs.iter():
                if obj.get('id') == nameID:
                    defs.remove(obj)

        # creates a new marker
        marker_attribs = {inkex.addNS('stockid', 'inkscape'): nameID, 'orient': 'auto', 'refY': '0.0', 'refX': '0.0', 'id': nameID,
                          'style': 'overflow:visible'}

        newMarker = inkex.etree.SubElement(ExtensionBaseObj.getDefinitions(), inkex.addNS('marker', 'defs'), marker_attribs)

        if not fillColor:
            fillColor = 'none'
        if not strokeColor:
            strokeColor = 'none'

        marker_style = {'fill-rule': 'evenodd', 'fill': fillColor, 'stroke': strokeColor, 'stroke-width': str(lineWidth)}

        marker_lineline_attribs = {'d': markerPath, 'style': simplestyle.formatStyle(marker_style)}

        if markerTransform:
            marker_lineline_attribs['transform'] = markerTransform

        inkex.etree.SubElement(newMarker, inkex.addNS('path', 'defs'), marker_lineline_attribs)

        ExtensionBaseObj.doc_ids[nameID] = 1

        # print tostring(ExtensionBaseObj.getDefinitions())
        return nameID

    # ---------------------------------------------
    @staticmethod
    def createDotMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=0.4, strokeColor=color.defined('black'), fillColor=color.defined('black')):
        """Creates a dotS/M/L marker, exactly like inkscape default markers

        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param nameID: nameID of the marker
        :param RenameMode: Renaming behavior mode. For more information, see documentation of marker.createMarker(...) method.

             - 0: (default) do not rename the marker. If nameID is already taken, the marker will not be modified
             - 1: overwrite marker if nameID is already taken
             - 2: Create a new unique nameID, adding a suffix number (Please refer to inkscapeMadeEasy.uniqueIdNumber(prefix_id) ).

        :param scale: scale of the marker. To copy exactly inkscape sizes dotS/M/L, use 0.2, 0.4 and 0.8 respectively. Default: 0.4
        :param strokeColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param fillColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type nameID: string
        :type RenameMode: int
        :type scale: float
        :type strokeColor: string
        :type fillColor: string    

        :returns: NameID of the new marker
        :rtype: string

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     myMarker=inkDraw.marker.createDotMarker(self,nameID='myDotMarkerA',RenameMode=1,scale=0.5,strokeColor=inkDraw.color.defined('red'),fillColor=None)
        >>>     myLineStyle = inkDraw.lineStyle.set(1.0, markerEnd=myMarker,lineColor=inkDraw.color.defined('black'))  # see lineStyle class for further information on this function
        """

        markerPath = 'M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z '
        width = 1.0
        markerTransform = 'scale(' + str(scale) + ') translate(7.4, 1)'
        return marker.createMarker(ExtensionBaseObj, nameID, markerPath, RenameMode, strokeColor, fillColor, width, markerTransform)

    # ---------------------------------------------
    @staticmethod
    def createCrossMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=0.4, strokeColor=color.defined('black'), fillColor=color.defined('black')):
        """Creates a cross marker

        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param nameID: nameID of the marker
        :param RenameMode: Renaming behavior mode. For more information, see documentation of marker.createMarker(...) method.

             - 0: (default) do not rename the marker. If nameID is already taken, the marker will not be modified
             - 1: overwrite marker if nameID is already taken
             - 2: Create a new unique nameID, adding a suffix number (Please refer to inkscapeMadeEasy.uniqueIdNumber(prefix_id) ).

        :param scale: scale of the marker. Default: 0.4
        :param strokeColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param fillColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type nameID: string
        :type RenameMode: int
        :type scale: float
        :type strokeColor: string
        :type fillColor: string    

        :returns: NameID of the new marker
        :rtype: string

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     myMarker=inkDraw.marker.createCrossMarker(self,nameID='myDotMarkerA',RenameMode=1,scale=0.5,strokeColor=inkDraw.color.defined('red'),fillColor=None)
        >>>     myLineStyle = inkDraw.lineStyle.set(1.0, markerEnd=myMarker,lineColor=inkDraw.color.defined('black'))  # see lineStyle class for further information on this function
        """

        markerPath = 'M -5,5 L 5,-5 M 5,5 L -5,-5'
        markerTransform = 'scale(' + str(scale) + ')'
        width = 1.0
        return marker.createMarker(ExtensionBaseObj, nameID, markerPath, RenameMode, strokeColor, fillColor, width, markerTransform)

    # ---------------------------------------------
    @staticmethod
    def createArrow1Marker(ExtensionBaseObj, nameID, RenameMode=0, scale=0.4, strokeColor=color.defined('black'), fillColor=color.defined('black')):
        """Creates a arrowS/M/L arrow markers (both start and end markers), exactly like inkscape

        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param nameID: nameID of the marker. Start and End markers will have 'Start' and 'End' suffix respectively
        :param RenameMode: Renaming behavior mode. For more information, see documentation of marker.createMarker(...) method.

             - 0: (default) do not rename the marker. If nameID is already taken, the marker will not be modified
             - 1: overwrite marker if nameID is already taken
             - 2: Create a new unique nameID, adding a suffix number (Please refer to inkscapeMadeEasy.uniqueIdNumber(prefix_id) ).

        :param scale: scale of the marker. Default: 0.4
        :param strokeColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param fillColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type nameID: string
        :type RenameMode: int
        :type scale: float
        :type strokeColor: string
        :type fillColor: string    

        :returns: a list of strings: [startArrowMarker,endArrowMarker]
                    - startArrowMarker: nameID of start marker
                    - endArrowMarker: nameID of end marker
        :rtype: list

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     StartArrowMarker,EndArrowMarker=inkDraw.marker.createArrow1Marker(self,nameID='myArrow',RenameMode=1,scale=0.5,strokeColor=inkDraw.color.defined('red'),fillColor=None)
        >>>     myLineStyle = inkDraw.lineStyle.set(1.0, markerStart=StartArrowMarker,markerEnd=EndArrowMarker,lineColor='#000000')  # see lineStyle class for further information on this function
        """

        # transform="scale(0.8) rotate(180) translate(12.5,0)" />
        # transform="scale(0.4) rotate(180) translate(10,0)" />
        # transform="scale(0.2) rotate(180) translate(6,0)" />
        # translation=12.5-17.5/(scale*10)
        # linear regression from data of small medium and large
        translation = 10.17 * scale + 4.75
        width = 1.0

        markerPath = 'M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z '
        markerTransform = 'scale(' + str(scale) + ') rotate(0) translate(' + str(translation) + ',0)'
        nameStart = marker.createMarker(ExtensionBaseObj, nameID + 'Start', markerPath, RenameMode, strokeColor, fillColor, width, markerTransform)
        markerTransform = 'scale(' + str(scale) + ') rotate(180) translate(' + str(translation) + ',0)'
        nameEnd = marker.createMarker(ExtensionBaseObj, nameID + 'End', markerPath, RenameMode, strokeColor, fillColor, width, markerTransform)

        return [nameStart, nameEnd]

    # ---------------------------------------------
    @staticmethod
    def createInfLineMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=1.0, strokeColor=None, fillColor=color.defined('black')):
        """Creates ellipsis markers, both start and end markers.

        These markers differ from inkscape's default ellipsis since these markers are made such that the diameter of the dots are equal to the line width.

        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param nameID: nameID of the marker. Start and End markers will have 'Start' and 'End' suffix respectively
        :param RenameMode: Renaming behavior mode. For more information, see documentation of marker.createMarker(...) method.

             - 0: (default) do not rename the marker. If nameID is already taken, the marker will not be modified
             - 1: overwrite marker if nameID is already taken
             - 2: Create a new unique nameID, adding a suffix number (Please refer to inkscapeMadeEasy.uniqueIdNumber(prefix_id) ).

        :param scale: scale of the marker. Default 1.0
        :param strokeColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: ``None``
        :param fillColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type nameID: string
        :type RenameMode: int
        :type scale: float
        :type strokeColor: string
        :type fillColor: string    

        :returns: a list of strings: [startInfMarker,endInfMarker]
                    - startInfMarker: nameID of start marker
                    - endInfMarker: nameID of end marker
        :rtype: list

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     startInfMarker,endInfMarker=inkDraw.marker.createInfLineMarker(self,nameID='myInfMarker',RenameMode=1,scale=1.0,strokeColor=None,fillColor='#00FF00')
        >>>     myLineStyle = inkDraw.lineStyle.set(1.0, markerStart=startInfMarker,markerEnd=endInfMarker,lineColor='#000000')  # see lineStyle class for further information on this function
        """

        # build path for 3 circles
        markerPath = ''
        radius = scale / 2.0

        for i in range(3):

            prefix = 'M %f %f ' % (i * 2 + radius, 0)
            arcStringA = 'a %f %f 0 1 1 %f %f ' % (radius, radius, -2 * radius, 0)
            arcStringB = 'a %f %f 0 1 1 %f %f ' % (radius, radius, 2 * radius, 0)

            markerPath = markerPath + prefix + arcStringA + arcStringB + 'z '

        if scale != 1.0:
            markerTransform = 'translate(' + str(-6.0 * scale) + ', 0) scale(' + str(scale) + ')'
        else:
            markerTransform = 'translate(' + str(-6.0 * scale) + ', 0)'

        width = 1.0
        # add small line segment

        nameStart = marker.createMarker(ExtensionBaseObj, nameID + 'Start', markerPath, RenameMode, strokeColor, fillColor, width, markerTransform)

        if scale != 1.0:
            markerTransform = 'translate(' + str(2.0 * scale) + ', 0) scale(' + str(scale) + ')'
        else:
            markerTransform = 'translate(' + str(2.0 * scale) + ', 0)'

        nameEnd = marker.createMarker(ExtensionBaseObj, nameID + 'End', markerPath, RenameMode, strokeColor, fillColor, width, markerTransform)

        return [nameStart, nameEnd]


class lineStyle():
    """
    Class to create line styles.

    This class is used to define line styles. It is capable of setting stroke and filling colors, line width, linejoin and linecap, markers (start, mid, and end) and stroke dash array

    This class contains only static methods so that you don't have to inherit this in your class

    """

    # ---------------------------------------------
    @staticmethod
    def set(lineWidth=1.0, lineColor=color.defined('black'), fillColor=None, lineJoin='round', lineCap='round', markerStart=None, markerMid=None,
            markerEnd=None, strokeDashArray=None):
        """ Creates a new line style

        :param lineWidth: line width. Default: 1.0
        :param lineColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param fillColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: ``None``
        :param lineJoin: shape of the lines at the joints. Valid values 'miter', 'round', 'bevel'. Default: round
        :param lineCap: shape of the lines at the ends. Valid values 'butt', 'square', 'round'. Default: round
        :param markerStart: marker at the start node. Default: ``None``
        :param markerMid: marker at the mid nodes. Default: ``None``
        :param markerEnd: marker at the end node. Default: ``None``
        :param strokeDashArray: dashed line pattern definition. Default: ``None`` See <http://www.w3schools.com/svg/svg_stroking.asp> for further information

        :type lineWidth: float     
        :type lineColor: string
        :type fillColor: string
        :type lineJoin: string
        :type lineCap: string
        :type markerStart: string
        :type markerMid: string
        :type markerEnd: string
        :type strokeDashArray: string

        :returns: line definition following the provided specifications
        :rtype: string

        **Line node types**

        .. image:: ../imagesDocs/line_nodes.png
          :width: 600px

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>> 
        >>>     # creates a line style using a dot marker at its end node
        >>>     myMarker=inkDraw.marker.createDotMarker(self,nameID='myMarker',RenameMode=1,scale=0.5,strokeColor=color.defined('red'),fillColor=None)   # see marker class for further information on this function
        >>>     myLineStyle = inkDraw.lineStyle.set(lineWidth=1.0, markerEnd=myMarker,lineColor=inkDraw.color.defined('black'),fillColor=inkDraw.color('red'))
        >>> 
        >>>     # creates a line style with dashed line (5 units dash , 10 units space
        >>>     myDashedStyle = inkDraw.lineStyle.set(lineWidth=1.0,lineColor=inkDraw.color.defined('black'),fillColor=inkDraw.color,strokeDashArray='5,10') 
        >>>     # creates a line style with a more complex pattern (5 units dash , 10 units space, 2 units dash, 3 units space
        >>>     myDashedStyle = inkDraw.lineStyle.set(lineWidth=1.0,lineColor=inkDraw.color.defined('black'),fillColor=inkDraw.color,strokeDashArray='5,10,2,3') 
        """

        if not fillColor:
            fillColor = 'none'
        if not lineColor:
            lineColor = 'none'
        if not strokeDashArray:
            strokeDashArray = 'none'

        # dictionary with the styles
        lineStyle = {'stroke': lineColor, 'stroke-width': str(lineWidth), 'stroke-dasharray': strokeDashArray, 'fill': fillColor}

        # Endpoint and junctions
        lineStyle['stroke-linecap'] = lineCap
        lineStyle['stroke-linejoin'] = lineJoin

        # add markers if needed
        if markerStart:
            lineStyle['marker-start'] = 'url(#' + markerStart + ')'

        if markerMid:
            lineStyle['marker-mid'] = 'url(#' + markerMid + ')'

        if markerEnd:
            lineStyle['marker-end'] = 'url(#' + markerEnd + ')'

        return lineStyle

    # ---------------------------------------------
    @staticmethod
    def setSimpleBlack(lineWidth=1.0):
        """Defines a standard black line style.

        The only adjustable parameter is its width. The fixed parameters are: lineColor=black, fillColor=None, lineJoin='round', lineCap='round', no markers, no dash pattern

        :param lineWidth: line width. Default: 1.0
        :type lineWidth: float     

        :returns: line definition following the provided specifications
        :rtype: string

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>> 
        >>>     mySimpleStyle = inkDraw.lineStyle.setSimpleBlack(lineWidth=2.0) 

        """
        return lineStyle.set(lineWidth)


class textStyle():
    """
    Class to create text styles.

    This class is used to define text styles. It is capable of setting font size, justification, text color, font family, font style, font weight, line spacing, letter spacing and word spacing

    This class contains only static methods so that you don't have to inherit this in your class

    """

    # ---------------------------------------------
    @staticmethod
    def set(fontSize=10, justification='left', textColor=color.defined('black'), fontFamily='Sans', fontStyle='normal', fontWeight='normal',
            lineSpacing='100%', letterSpacing='0px', wordSpacing='0px'):
        """Defines a new text style

        :param fontSize: size of the font in px. Default: 10
        :param justification: text justification. ``left``, ``right``, ``center``. Default: ``left``
        :param textColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param fontFamily: font family name. Default ``Sans``
        :param fontStyle: ``normal``  or ``italic``. Default: ``normal``
        :param fontWeight: ``normal``  or ``bold``. Default: ``normal``
        :param lineSpacing: spacing between lines in percentage. Default: ``100%``
        :param letterSpacing: extra space between letters. Format: ``_px``. Default: ``0px``
        :param wordSpacing: extra space between words. Format: ``_px``. Default: ``0px``

        :type fontSize: float     
        :type justification: string
        :type textColor: string
        :type fontFamily: string
        :type fontStyle: string
        :type fontWeight: string
        :type lineSpacing: string
        :type letterSpacing: string
        :type wordSpacing: string

        :returns: text style definition following the provided specifications
        :rtype: string

        .. Warning: This method does NOT verify whether the font family is installed in your machine or not.

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>> 
        >>>     myTextStyle=inkDraw.textStyle.set(fontSize=10, justification='left', textColor=color.defined('black'), fontFamily='Sans',
        >>>                                       fontStyle='normal', fontWeight='normal', lineSpacing='100%', letterSpacing='0px', wordSpacing='0px')
        """

        if not textColor:
            textColor = 'none'

        if justification == 'left':
            justification = 'start'
            anchor = 'start'
        if justification == 'right':
            justification = 'end'
            anchor = 'end'
        if justification == 'center':
            anchor = 'middle'

        textStyle = {'font-size': str(fontSize) + 'px', 'font-style': fontStyle, 'font-weight': fontWeight, 'text-align': justification,
                     # start, center, end
                     'line-height': lineSpacing, 'letter-spacing': letterSpacing, 'word-spacing': wordSpacing, 'text-anchor': anchor,
                     # start, middle, end
                     'fill': textColor, 'fill-opacity': '1', 'stroke': 'none', 'font-family': fontFamily}

        return textStyle

    # ---------------------------------------------
    @staticmethod
    def setSimpleBlack(fontSize=10, justification='left'):
        """Defines a standard black text style

        The only adjustable parameter are font size and justification. The fixed parameters are: textColor=color.defined('black'), fontFamily='Sans',
        fontStyle='normal', fontWeight='normal', lineSpacing='100%', letterSpacing='0px', wordSpacing='0px.

        :param fontSize: size of the font in px. Default: 10
        :param justification: text justification. ``left``, ``right``, ``center``. Default: ``left``

        :type fontSize: float     
        :type justification: string

        :returns: line definition following the provided specifications
        :rtype: string

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>> 
        >>>     mySimpleStyle = inkDraw.textStyle.setSimpleBlack(fontSize=20,justification='center') 

        """
        return textStyle.set(fontSize, justification)

    # ---------------------------------------------
    @staticmethod
    def setSimpleColor(fontSize=10, justification='left', textColor=color.defined('black')):
        """Defines a standard colored text style

        The only adjustable parameter are font size justification and textColor. The fixed parameters are: fontFamily='Sans', fontStyle='normal',
        fontWeight='normal', lineSpacing='100%', letterSpacing='0px', wordSpacing='0px.

        :param fontSize: size of the font in px. Default: 10
        :param justification: text justification. ``left``, ``right``, ``center``. Default: ``left``
        :param textColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')

        :type fontSize: float     
        :type justification: string
        :type textColor: string

        :returns: line definition following the provided specifications
        :rtype: string

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>> 
        >>>     mySimpleStyle = inkDraw.textStyle.setSimpleColor(fontSize=20,justification='center',textColor=inkDraw.color.gray(0.5)) 
        """
        return textStyle.set(fontSize, justification, textColor)


class text():
    """ Class for writing texts. It is possible to add regular inkscape's text elements or LaTeX text. For the later, the excellent 'textext' extension from Pauli Virtanen's <https://pav.iki.fi/software/textext/> is incorporated here. Please refer to `Main Features`_ section for further instructions


    This class contains only static methods so that you don't have to inherit this in your class
    
    .. note:: LaTeX support is an optional feature, **enabled by default**. Please refer to :ref:`latexSupport` on how to disable it.
    
    """

    @staticmethod
    def write(ExtensionBaseObj, text, coords, parent, textStyle=textStyle.setSimpleBlack(fontSize=10, justification='left'), fontSize=None,
              justification=None, angleDeg=0.0):
        """Adds a text line to the document

        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param text: text to be drawn. Use \\\\n in the string to start a new line
        :param coords: position [x,y]
        :param parent: parent object
        :param textStyle: text style to be used. See class ``textStyle``. Default: textStyle.setSimpleBlack(fontSize=10,justification='left')
        :param fontSize: size of the font in px.
                - ``None``: Uses fontSize of textStyle argument (Default)
                - number: takes precedence over the size on textStyle
        :param justification: text justification. ``left``, ``right``, ``center``
                - ``None``: Uses justification of textStyle argument (Default)
                - ``left``, ``right``, ``center``: takes precedence over the justification set on textStyle
        :param angleDeg: angle of the text, counterclockwise, in degrees. Default: 0

        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type text: string
        :type coords: list
        :type parent: element object
        :type textStyle: textStyle object
        :type fontSize: float
        :type justification: string
        :type angleDeg: float

        :returns: the new text object
        :rtype: text Object

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     mySimpleStyle = inkDraw.textStyle.setSimpleBlack(fontSize=20,justification='center')  # creates a simple text style.
        >>>     
        >>>     #adds a two-line text, at the point x=5.0,y=6.0
        >>>     #               L1: 'foo bar who-hoo!'
        >>>     #               L2: 'second line!'
        >>>     myText='foo bar who-hoo!\\ntwo lines!'
        >>>     inkDraw.text.write(self, text=myText, coords=[5.0,6.0], parent=root_layer, textStyle=mySimpleStyle, fontSize=None, justification=None, angleDeg=0.0)
        >>> 
        >>>     # creates a group in root-layer and add text to it
        >>>     myGroup = self.createGroup(root_layer,'textGroup')
        >>>     #adds a text 'foo bar', rotated 45 degrees, at the point x=0,y=0, overriding justification of mySimpleStyle
        >>>     inkDraw.text.write(self, text='foo bar', coords=[0.0,0.0], parent=myGroup, textStyle=mySimpleStyle, fontSize=None, justification='left', angleDeg=45.0)

        """

        if justification == 'left':
            textStyle['text-align'] = 'start'
            textStyle['text-anchor'] = 'start'
        if justification == 'right':
            textStyle['text-align'] = 'end'
            textStyle['text-anchor'] = 'end'
        if justification == 'center':
            textStyle['text-align'] = 'center'
            textStyle['text-anchor'] = 'middle'

        if fontSize:
            textStyle['font-size'] = str(fontSize) + 'px'

        AttribsText = {inkex.addNS('space', 'xml'): "preserve", 'style': simplestyle.formatStyle(textStyle), 'x': str(coords[0]), 'y': str(coords[1]),
                       inkex.addNS('linespacing', 'sodipodi'): textStyle['line-height']}

        # textObj = inkex.etree.SubElement(parent, inkex.addNS('text','svg'), AttribsText )

        textObj = inkex.etree.Element(inkex.addNS('text', 'svg'), AttribsText)
        parent.append(textObj)

        AttribsLineText = {inkex.addNS('role', 'sodipodi'): "line", 'x': str(coords[0]), 'y': str(coords[1])}

        textLines = text.split('\\n')

        for n in range(len(textLines)):
            myTspan = inkex.etree.SubElement(textObj, inkex.addNS('tspan', 'svg'), AttribsLineText)
            myTspan.text = textLines[n].decode('utf-8')

        if angleDeg != 0:
            ExtensionBaseObj.rotateElement(textObj, center=coords, angleDeg=angleDeg)  # negative angle bc inkscape is upside down

        return textObj

    # ---------------------------------------------
    @staticmethod
    def latex(ExtensionBaseObj, parent, LaTeXtext, position, fontSize=10, refPoint='cc', textColor=color.defined('black'), LatexCommands=' ',
              angleDeg=0, preambleFile=None):
        """Draws a text line using LaTeX. You can use any LaTeX contents here.

        .. note:: Employs the excellent 'textext' extension from Pauli Virtanen's <https://pav.iki.fi/software/textext/> is incorporated here.
            Please refer to `Main Features`_ section for further instructions
        
        .. note:: LaTeX support is an optional feature that requires a few extra packages to be installed outside inkscape. **It is enabled by default**.
            Please refer to :ref:`latexSupport` on how to disable it. If disabled, this function will still work, internally calling the method text.write().

        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param parent: parent object        
        :param LaTeXtext: text to be drawn. Can contain any latex command
        :param position: position of the reference point [x,y]  
        :param fontSize: size of the font. Assume any text of ``\\normalsize`` will have this size. Default: 10 
        :param refPoint: text reference Point. See figure below for options. Default: ``cc``
        :param textColor: color in the format ``#RRGGBB`` (hexadecimal), or ``None`` for no color. Default: color.defined('black')
        :param LatexCommands: commands to be included before LaTeXtext (default: ' '). If LaTeX support is disabled, this parameter has no effect.
        :param angleDeg: angle of the text, counterclockwise, in degrees. Default: 0
        :param preambleFile: optional preamble file to be included. Default: None. If LaTeX support is disabled, this parameter has no effect.

        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type parent: element object 
        :type LaTeXtext: string
        :type position: list  
        :type fontSize: float
        :type refPoint: string
        :type textColor: string
        :type LatexCommands: string
        :type angleDeg: float
        :type preambleFile: string

        :returns: the new text object
        :rtype: text Object

        .. note:: This function does not use ``textStyle`` class.

        **Reference point options**

        .. image:: ../imagesDocs/LaTeX_reference_Point.png
          :width: 400px

        **Standard Preamble file**

        When a preamble file is not provided, inkscapeMadeEasy assumes a standard preamble file located at ``./textextLib/basicLatexPackages.tex``. By default, its contents is::

          \\usepackage{amsmath,amsthm,amsbsy,amsfonts,amssymb}
          \\usepackage[per=slash]{siunitx}
          \\usepackage{steinmetz}
          \\usepackage[utf8]{inputenc}

        You will need these packages installed. This file can be modified to include extra default packages and/or commands.

        **LaTeX .tex document structure**      

        LaTeX .tex document have the following structure. Note that LatexCommands lies within document environment::

          \\documentclass[landscape,a0]{article}

          [contents of Preamble file]

          \\pagestyle{empty}

          \\begin{document}
          \\noindent

          [contents of LatexCommands]

          [contens of LaTeXtext]

          \\end{document}


        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     customCommand = r'\\newcommand{\\fooBar}{\\textbf{Foo Bar Function! WhooHoo!}}'   # do not forget the r to avoid backslash escape.
        >>>     inkDraw.text.latex(self, root_layer,r'This is one equation \\begin{align} x=y^2\\end{align} And this is my \\fooBar{}',
        >>>                        position=[0.0,0.0], fontSize=10, refPoint='cc', textColor=inkDraw.color.defined('black'), LatexCommands=customCommand, angleDeg=0, preambleFile=None)
        """
        newTmp = True

        # write an empty svg file.

        if not LaTeXtext:  # check whether text is empty
            return 0

        if useLatex:  # set useLatex=False to replace latex by an standard text (much faster for debugging =)  )

            if newTmp:
                tmpf = tempfile.NamedTemporaryFile(mode='w', prefix='temp_svg_inkscapeMadeEasy_Draw_', suffix='.svg', delete=False)
                tempFilePath = tmpf.name
                tmpf.write(BlankSVG)
                tmpf.close()
            else:
                tempDir = tempfile.gettempdir()
                tempFilePath = tempDir + '/temp_svg_inkscapeMadeEasy_Draw.txt'
                Dump(BlankSVG,tempFilePath, 'w')

            # return
            # temp instance for determining font height. Draws a F letter just to find the height of the font
            if False:  # turning off this part of the code.
                texTemp = textext.TexText()  # start textText (awesome extension! =] )
                texTemp.affect([r'--text=' + 'F', '--scale-factor=1', tempFilePath], output=False)
                groupLatex = texTemp.current_layer.find('g')
                BboxMin, BboxMax = ExtensionBaseObj.getBoundingBox(groupLatex)
                Height0 = BboxMax[1] - BboxMin[1]

            # running the code above, we get a 'F' with height of 6.76, with scale 1.0 from textext. This will be used to scale the text accordingly to fit user specification 'fontSize'
            Height0 = 6.76

            scale = fontSize / Height0

            tex = textext.TexText()  # start textText (awesome extension! =] )
            if preambleFile:
                tex.affect([r'--text=' + LatexCommands + LaTeXtext, '--scale-factor=1', '--preamble-file=' + preambleFile, tempFilePath],
                           output=False)
            else:
                tex.affect(
                    [r'--text=' + LatexCommands + LaTeXtext, '--scale-factor=1', '--preamble-file=' + ExtensionBaseObj.getBasicLatexPackagesFile(),
                     tempFilePath], output=False)

            if newTmp:
                os.unlink(tmpf.name)

            groupLatex = tex.current_layer.find('g')

            # change color
            for obj in groupLatex.iter():
                if obj.tag == inkex.addNS('path', 'svg') or obj.tag == 'path' or obj.tag == 'polygon':
                    obj.set('style', 'fill:' + textColor + ';stroke-width:0')

            # remove transforms
            del groupLatex.attrib["transform"]

            ExtensionBaseObj.scaleElement(groupLatex, scaleX=scale, scaleY=-scale)  # scale to fit font size
        else:
            if refPoint[1] == 'l':
                justification = 'left'

            if refPoint[1] == 'c':
                justification = 'center'

            if refPoint[1] == 'r':
                justification = 'right'

            mytextStyle = textStyle.setSimpleColor(fontSize=fontSize / 0.76, justification='left', textColor=textColor)
            groupLatex = text.write(ExtensionBaseObj, LaTeXtext, [0, 0], parent, textStyle=mytextStyle, fontSize=fontSize / 0.76,
                                    justification=justification, angleDeg=0.0)  # attention! keep angleDeg=0.0 here bc it will be rotated below

        parent.append(groupLatex)

        BboxMin, BboxMax = ExtensionBaseObj.getBoundingBox(groupLatex)

        if useLatex:  # set useLatex=False to replace latex by an standard text (much faster for debugging =)  )
            if refPoint[0] == 't':
                refPointY = BboxMin[1]  # BboxMin bc inkscape is upside down

            if refPoint[0] == 'c':
                refPointY = (BboxMax[1] + BboxMin[1]) / 2.0

            if refPoint[0] == 'b':
                refPointY = BboxMax[1]  # BboxMax bc inkscape is upside down

            if refPoint[1] == 'l':
                refPointX = BboxMin[0]

            if refPoint[1] == 'c':
                refPointX = (BboxMax[0] + BboxMin[0]) / 2.0

            if refPoint[1] == 'r':
                refPointX = BboxMax[0]
        else:
            refPointX = BboxMin[0]
            if refPoint[0] == 't':
                refPointY = BboxMin[1] - fontSize  # BboxMin bc inkscape is upside down

            if refPoint[0] == 'c':
                refPointY = BboxMin[1] - fontSize / 2.0  # BboxMin bc inkscape is upside down

            if refPoint[0] == 'b':
                refPointY = BboxMax[1]  # BboxMax bc inkscape is upside down

        ExtensionBaseObj.moveElement(groupLatex, [-refPointX, -refPointY])  # move to origin
        ExtensionBaseObj.moveElement(groupLatex, [position[0], position[1]])
        if angleDeg != 0:
            ExtensionBaseObj.rotateElement(groupLatex, center=[position[0], position[1]], angleDeg=angleDeg)

        return groupLatex


class cubicBezier():
    """ This is a class with different methods for drawing cubic bezier lines.

    This class contains only static methods so that you don't have to inherit this in your class
    """
    @staticmethod
    def addNode(NodeList, coord=[0, 0], cPbefore=[-1, 0], cPafter=[1, 0], typeNode='corner', flagAbsCoords=True):
        """Add a new node to the list of nodes of the cubic bezier line.

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param NodeList: list of nodes to be appended with the new node.
        :param coord: list with the coordinates of the node
        :param cPbefore: list with the coordinates of the control point before the node.
        :param cPafter: list with the coordinates of the control point after the node. Used only if 'typeNode' is 'smooth' or 'corner'
        :param typeNode: type of node to be added. See image below

          - ``corner``: Node without smoothness constraint at the node. The bezier curve can have a sharp edge at the node
          - ``smooth``: Node with smoothness constraint at the node. The bezier curve will be smooth at the node.
                    If the control points do not form a straight line, then they are modified to form a straight line. See image below
          -  ``symmetric``: same as ``smooth``, but the control points are forced to be symmetric with respect to the node.


        :param flagAbsCoords: indicate absolute or relative coordinates. See section below on how reference system works.
            .. warning:: All nodes in a given list must be defined in the same reference system (absolute or relative).
        :type NodeList: list
        :type coord: list [x,y]
        :type cPbefore: list [x,y]
        :type cPafter: list [x,y]
        :type typeNode: string
        :type flagAbsCoords: bool

        :returns: None
        :rtype: -

        **Node Types**

        The image below presents the types of nodes

        .. image:: ../imagesDocs/bezier_nodeTypes.png
              :width: 500px


        Image below present the process of smoothing control nodes not completely aligned when  ``smooth`` is selected.

        .. image:: ../imagesDocs/bezier_smoothProcess.png
              :width: 500px

        **Absolute and relative coordinate systems**

        cubic bezier curves are composed by segments which are defined by 4 coordinates, two node coordinates and two control points.

        .. image:: ../imagesDocs/bezier_definitions.png
          :width: 500px

        In absolute coordinate system, all node and control point localizations are specified using the origin as reference.
        In relative coordinate system, control point localizations are specified using its node as reference, and each node
        use the previous node as reference (the first node use the origin as reference). See image below.

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        .. image:: ../imagesDocs/bezier_references.png
          :width: 700px

        **Example**

        .. note:: In the following example, the control point before the first node and after the last node are important
            when the bezier curve must be closed. See method ``draw``

        .. image:: ../imagesDocs/bezier_example.png
          :width: 400px

        >>>     # create a list of nodes using absolute coordinate system
        >>>     nodeListABS=[]
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[4,4], cPbefore=[6,6], cPafter=[2,6], typeNode='corner', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[8,12], cPbefore=[4,12], cPafter=[10,12], typeNode='smooth', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[12,8], cPbefore=[8,8], cPafter=[12,10], typeNode='corner', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[16,8], cPbefore=[14,10], cPafter=None, typeNode='symmetric', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[12,4], cPbefore=[16,4], cPafter=[10,6], typeNode='corner', flagAbsCoords=True)

        >>>     # create a list of nodes using relative coordinate system
        >>>     nodeListREL=[]
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, 4], cPbefore=[2,2], cPafter=[-2,2], typeNode='corner', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, 8], cPbefore=[-4,0], cPafter=[2,0], typeNode='smooth', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, -4], cPbefore=[-4,0], cPafter=[0,2], typeNode='corner', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, 0], cPbefore=[-2,2], cPafter=None, typeNode='symmetric', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[-4,-4], cPbefore=[4,0], cPafter=[-2,2], typeNode='corner', flagAbsCoords=False)

        """

        if typeNode.lower() == 'symmetric':
            typeNodeSodipodi = 'z'

        if typeNode.lower() == 'smooth':
            typeNodeSodipodi = 's'

        if typeNode.lower() == 'corner':
            typeNodeSodipodi = 'c'

        if typeNodeSodipodi.lower() == 'c':  # corner
            NodeList.append({'node': coord, 'cPoint_before': cPbefore, 'cPoint_after': cPafter, 'type': typeNodeSodipodi, 'absCoords': flagAbsCoords})

        if typeNodeSodipodi.lower() == 'z':  # symmetric
            if flagAbsCoords:
                deltaX = coord[0] - cPbefore[0]
                deltaY = coord[1] - cPbefore[1]
                NodeList.append({'node': coord, 'cPoint_before': cPbefore, 'cPoint_after': [coord[0] + deltaX, coord[1] + deltaY], 'type': typeNodeSodipodi, 'absCoords': flagAbsCoords})
            else:
                NodeList.append({'node': coord, 'cPoint_before': cPbefore, 'cPoint_after': [-cPbefore[0],-cPbefore[1]], 'type': typeNodeSodipodi, 'absCoords': flagAbsCoords})

        if typeNodeSodipodi.lower() == 's':  # smooth

            # projects the directions of the control points to a commom direction, perpendicular to both
            delta1 = np.array(cPbefore)
            delta2 = np.array(cPafter)

            if abs(delta1.dot(delta2)) <1.0:

                if flagAbsCoords:
                    delta1 -= np.array(coord)
                    delta2 -= np.array(coord)

                # https://math.stackexchange.com/questions/2285965/how-to-find-the-vector-formula-for-the-bisector-of-given-two-vectors
                bisectorVector = np.linalg.norm(delta2) * delta1 + np.linalg.norm(delta1) * delta2
                tangentVersor = np.array([-bisectorVector[1], bisectorVector[0]])
                tangentVersor /= np.linalg.norm(tangentVersor)

                cPbeforeNew = np.linalg.norm(delta1) * tangentVersor
                cPafterNew = np.linalg.norm(delta2) * tangentVersor

                if flagAbsCoords:
                    cPbeforeNew += np.array(coord)
                    cPafterNew += np.array(coord)

                NodeList.append({'node': coord, 'cPoint_before': cPbeforeNew.tolist(), 'cPoint_after': cPafterNew.tolist(), 'type': typeNodeSodipodi, 'absCoords': flagAbsCoords})
            else:
                NodeList.append({'node': coord, 'cPoint_before': cPbefore, 'cPoint_after': cPafter, 'type': typeNodeSodipodi, 'absCoords': flagAbsCoords})



    @staticmethod
    def draw(parent, NodeList, offset=np.array([0, 0]), label='none', lineStyle=lineStyle.setSimpleBlack(), closePath=False):
        """draws the bezier line, given a list of nodes, built using ``addNode`` method


        :param parent: parent object
        :param NodeList: list of nodes. See ``addNode`` method
        :param offset: offset coords. Default [0,0]
        :param label: label of the line. Default 'none'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()
        :param closePath: Connects the first point to the last. Default: False

        :type parent: inkscapeMadeEasy object (see example below)
        :type NodeList: list of nodes
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object
        :type closePath: bool

        :returns: the new line object
        :rtype: line Object

        **Example**

        .. note:: In the following example, the control point before the first node and after the last node are important
            when the bezier curve must be closed.

        .. image:: ../imagesDocs/bezier_example.png
          :width: 400px

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>>
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>>
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle = set(lineWidth=1.0, lineColor=color.defined('red'))

        >>>     # create a list of nodes using absolute coordinate system
        >>>     nodeListABS=[]
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[4,4], cPbefore=[6,6], cPafter=[2,6], typeNode='corner', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[8,12], cPbefore=[4,12], cPafter=[10,12], typeNode='smooth', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[12,8], cPbefore=[8,8], cPafter=[12,10], typeNode='corner', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[16,8], cPbefore=[14,10], cPafter=None, typeNode='symmetric', flagAbsCoords=True)
        >>>     inkDraw.cubicBezier.addNode(nodeListABS, coord=[12,4], cPbefore=[16,4], cPafter=[10,6], typeNode='corner', flagAbsCoords=True)

        >>>     # create a list of nodes using relative coordinate system
        >>>     nodeListREL=[]
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, 4], cPbefore=[2,2], cPafter=[-2,2], typeNode='corner', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, 8], cPbefore=[-4,0], cPafter=[2,0], typeNode='smooth', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, -4], cPbefore=[-4,0], cPafter=[0,2], typeNode='corner', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[4, 0], cPbefore=[-2,2], cPafter=None, typeNode='symmetric', flagAbsCoords=False)
        >>>     inkDraw.cubicBezier.addNode(nodeListREL, coord=[-4,-4], cPbefore=[4,0], cPafter=[-2,2], typeNode='corner', flagAbsCoords=False)

        >>>     C1 = inkDraw.cubicBezier.draw(root_layer,nodeListABS, offset=[0, 0],closePath=False)
        >>>     C2 = inkDraw.cubicBezier.draw(root_layer,nodeListABS, offset=[0, 0],closePath=True)
        >>>     C3 = inkDraw.cubicBezier.draw(root_layer,nodeListREL, offset=[0, 0],closePath=False)
        >>>     C4 = inkDraw.cubicBezier.draw(root_layer,nodeListREL, offset=[0, 0],closePath=True)

        Result of the example

        .. image:: ../imagesDocs/bezier_example_draw.png
          :width: 800px

        """

        # first node
        if NodeList[0]['absCoords']:
            string_coords = 'M %f,%f ' % (NodeList[0]['node'][0] + offset[0], NodeList[0]['node'][0] + offset[1])
        else:
            string_coords = 'M %f,%f ' % (NodeList[0]['node'][0] + offset[0], NodeList[0]['node'][0] + offset[1])

        string_nodeTypes = ''
        Ptotal=np.zeros(2)
        for i in range(len(NodeList) - 1):
            currNode = NodeList[i]
            nextNode = NodeList[i + 1]

            if currNode['absCoords']:
                bezier = 'C %f,%f ' % (currNode['cPoint_after'][0] + offset[0], currNode['cPoint_after'][1] + offset[1])  # first control point
                bezier += '%f,%f ' % (nextNode['cPoint_before'][0] + offset[0], nextNode['cPoint_before'][1] + offset[1])  # second control point
                bezier += '%f,%f ' % (nextNode['node'][0] + offset[0], nextNode['node'][1] + offset[1])  # second node
            else:
                bezier = 'c %f,%f ' % (currNode['cPoint_after'][0], currNode['cPoint_after'][1])  # first control point
                bezier += '%f,%f ' % (nextNode['cPoint_before'][0] + nextNode['node'][0], nextNode['cPoint_before'][1] + nextNode['node'][1])  # second control point
                bezier += '%f,%f ' % (nextNode['node'][0], nextNode['node'][1])  # second node
                Ptotal +=np.array(currNode['node'])

            string_nodeTypes += currNode['type']
            string_coords = string_coords + bezier

        if closePath:
            currNode = NodeList[-1]
            nextNode = copy.deepcopy(NodeList[0])

            if currNode['absCoords']:
                bezier = 'C %f,%f ' % (currNode['cPoint_after'][0] + offset[0], currNode['cPoint_after'][1] + offset[1])  # first control point
                bezier += '%f,%f ' % (nextNode['cPoint_before'][0] + offset[0], nextNode['cPoint_before'][1] + offset[1])  # second control point
                bezier += '%f,%f ' % (nextNode['node'][0] + offset[0], nextNode['node'][1] + offset[1])  # second node
            else:
                # writes the coordinates of the first node, relative to the last node.
                Ptotal +=np.array(currNode['node'])
                nextNode['node'][0] = NodeList[0]['node'][0] - Ptotal[0]
                nextNode['node'][1] = NodeList[0]['node'][1] - Ptotal[1]

                bezier = 'c %f,%f ' % (currNode['cPoint_after'][0], currNode['cPoint_after'][1])  # first control point
                bezier += '%f,%f ' % (nextNode['cPoint_before'][0] + nextNode['node'][0], nextNode['cPoint_before'][1] + nextNode['node'][1])  # second control point
                bezier += '%f,%f ' % (nextNode['node'][0], nextNode['node'][1])  # second node

            string_nodeTypes += currNode['type'] + nextNode['type']
            string_coords = string_coords + bezier + ' Z'
        else:
            string_nodeTypes += currNode['type']

        # M = move, L = line, H = horizontal line, V = vertical line, C = curve, S = smooth curve,
        # Q = quadratic Bezier curve, T = smooth quadratic Bezier curve, A = elliptical Arc,Z = closepath
        Attribs = {inkex.addNS('label', 'inkscape'): label, 'style': simplestyle.formatStyle(lineStyle), 'd': string_coords, inkex.addNS('nodetypes', 'sodipodi'): string_nodeTypes}

        return inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), Attribs)


class line():
    """ This is a class with different methods for drawing lines.

    This class contains only static methods so that you don't have to inherit this in your class
    """

    @staticmethod
    def absCoords(parent, coordsList, offset=[0, 0], label='none', lineStyle=lineStyle.setSimpleBlack(), closePath=False):
        """Draws a (poly)line based on a list of absolute coordinates

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param parent: parent object
        :param coordsList: list with coords x and y.  ex  [[x1,y1], ..., [xN,yN]]
        :param offset: offset coords. Default [0,0]
        :param label: label of the line. Default 'none'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()
        :param closePath: Connects the first point to the last. Default: False

        :type parent: inkscapeMadeEasy object (see example below)
        :type coordsList: list of list
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object
        :type closePath: bool

        :returns: the new line object
        :rtype: line Object

        **Example**

        .. image:: ../imagesDocs/lineExample.png
          :width: 250px

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle = set(lineWidth=1.0, lineColor=color.defined('red'))
        >>>     
        >>> 
        >>>     # creates a polyline passing by points (0,0) (0,1) (1,1) (1,2) (2,2) using absolute coordinates
        >>>     coords=[ [0,0], [0,1], [1,1], [1,2], [2,2] ]
        >>>     inkDraw.line.absCoords(root_layer, coordsList=coords, offset=[0, 0], label='fooBarLine', lineStyle=myLineStyle)
        >>>
        >>>     # creates the same polyline translated to point (5,6). Note we just have to change the offset
        >>>     inkDraw.line.absCoords(root_layer, coordsList=coords, offset=[5, 6], label='fooBarLine', lineStyle=myLineStyle)
        """

        # string with coordinates
        string_coords = ''

        for point in coordsList:
            string_coords = string_coords + ' ' + str(point[0] + offset[0]) + ',' + str(point[1] + offset[1])

        if closePath:
            string_coords += ' Z'

        # M = move, L = line, H = horizontal line, V = vertical line, C = curve, S = smooth curve,
        # Q = quadratic Bezier curve, T = smooth quadratic Bezier curve, A = elliptical Arc,Z = closepath
        Attribs = {inkex.addNS('label', 'inkscape'): label, 'style': simplestyle.formatStyle(lineStyle), 'd': 'M ' + string_coords}

        return inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), Attribs)

    # ---------------------------------------------
    @staticmethod
    def relCoords(parent, coordsList, offset=[0, 0], label='none', lineStyle=lineStyle.setSimpleBlack(), closePath=False):
        """Draws a (poly)line based on a list of relative coordinates

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param parent: parent object
        :param coordsList: list with distances dx and dy for all points.  ex  [[dx1,dy1], ..., [dxN,dyN]]
        :param offset: offset coords. Default [0,0]
        :param label: label of the line. Default 'none'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()
        :param closePath: Connects the first point to the last. Default: False

        :type parent: inkscapeMadeEasy object (see example below)
        :type coordsList: list of list
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object
        :type closePath: bool

        :returns: the new line object
        :rtype: line Object

        **Example**

        .. image:: ../imagesDocs/lineExample.png
          :width: 250px

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle = setSimpleBlack(lineWidth=1.0)
        >>>     
        >>> 
        >>>     # creates a polyline passing by points (0,0) (0,1) (1,1) (1,2) (2,2) using relative coordinates
        >>>     coords=[ [0,1], [1,0], [0,1], [1,0] ]
        >>>     inkDraw.line.relCoords(root_layer, coordsList=coords, offset=[0, 0], label='fooBarLine', lineStyle=myLineStyle)
        >>>
        >>>     # creates the same polyline translated to point (5,6)
        >>>     inkDraw.line.relCoords(root_layer, coordsList=coords, offset=[5, 6], label='fooBarLine', lineStyle=myLineStyle)
        """

        # string with coordinates
        string_coords = ''
        for dist in coordsList:
            string_coords = string_coords + ' ' + str(dist[0]) + ',' + str(dist[1])

        if closePath:
            string_coords += ' Z'

        # M = move, L = line, H = horizontal line, V = vertical line, C = curve, S = smooth curve,
        # Q = quadratic Bezier curve, T = smooth quadratic Bezier curve, A = elliptical Arc,Z = closepath
        Attribs = {inkex.addNS('label', 'inkscape'): label, 'style': simplestyle.formatStyle(lineStyle),
                   'd': 'm ' + str(offset[0]) + ' ' + str(offset[1]) + string_coords}

        return inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), Attribs)


class arc():
    """ This is a class with different methods for drawing arcs.

    This class contains only static methods so that you don't have to inherit this in your class
    """

    @staticmethod
    def startEndRadius(parent, Pstart, Pend, radius, offset=[0, 0], label='arc', lineStyle=lineStyle.setSimpleBlack(), flagRightOf=True,
                       flagOpen=True, largeArc=False):
        """Draws a circle arc from ``Pstart`` to ``Pend`` with a given radius

        .. image:: ../imagesDocs/arc_startEndRadius.png
          :width: 80px

        :param parent: parent object
        :param Pstart: start coordinate [x,y]
        :param Pend: end coordinate [x,y]
        :param radius: arc radius
        :param offset: extra offset coords [x,y]. Default [0,0]
        :param label: label of the line. Default 'arc'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()
        :param flagRightOf: sets the side of the vector Pend-Pstart which the arc must be drawn. See image below

          - True: Draws the arc to the right (Default)
          - False: Draws the arc to the left

        :param flagOpen: closes the arc. See image below. Default: True
        :param largeArc: Sets the largest arc to be drawn. See image below

          - True: Draws the largest arc
          - False: Draws the smallest arc (Default)

        :type parent: inkscapeMadeEasy object (see example below)
        :type Pstart: list
        :type Pend: list
        :type radius: float
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object
        :type flagRightOf: bool
        :type flagOpen: bool
        :type largeArc: bool

        :returns: the new arc object
        :rtype: line Object

        **Arc options**

        .. image:: ../imagesDocs/arc_startEndRadius_flags.png
          :width: 800px

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>> 
        >>>     P1=[10.0,0.0]
        >>>     P2=[20.0,10.0]
        >>>     R=15.0
        >>>     myLineStyle=inkDraw.lineStyle.setSimpleBlack()
        >>>     
        >>>     #draws an opened arc
        >>>     inkDraw.arc.startEndRadius(parent=root_layer, Pstart=P1, Pend=P2, radius=R, offset=[25,0], label='arc1',  lineStyle=myLineStyle, flagOpen=True)
        >>> 
        >>>     #draws a closed arc
        >>>     inkDraw.arc.startEndRadius(parent=root_layer, Pstart=P1, Pend=P2, radius=R, offset=[25,20], label='arc2',  lineStyle=myLineStyle, flagOpen=False)
        >>>     
        >>>     #draws arcs with all combinations of flagRightOf and largeArc parameters
        >>>     inkDraw.arc.startEndRadius(parent=root_layer, Pstart=P1, Pend=P2, radius=R, offset=[0,0], label='arc',  lineStyle=myLineStyle, flagRightOf=True, largeArc=True)
        >>>     inkDraw.arc.startEndRadius(parent=root_layer, Pstart=P1, Pend=P2, radius=R, offset=[25,0], label='arc4',  lineStyle=myLineStyle, flagRightOf=False, largeArc=True)
        >>>     inkDraw.arc.startEndRadius(parent=root_layer, Pstart=P1, Pend=P2, radius=R, offset=[0,40], label='arc5',  lineStyle=myLineStyle, flagRightOf=True, largeArc=False)
        >>>     inkDraw.arc.startEndRadius(parent=root_layer, Pstart=P1, Pend=P2, radius=R, offset=[25,40], label='arc6',  lineStyle=myLineStyle, flagRightOf=False, largeArc=False)
        """

        # finds the center point using some linear algebra
        StartVector = np.array(Pstart)
        EndVector = np.array(Pend)

        DistVector = EndVector - StartVector
        Dist = np.linalg.norm(DistVector)  # distance between start and end
        if Dist > 2.0 * radius:
            return None

        if (flagRightOf and largeArc) or (not flagRightOf and not largeArc):
            RadiusDirection = np.array([-DistVector[1], DistVector[0]])  # perpendicular to DistVector
        else:
            RadiusDirection = np.array([DistVector[1], -DistVector[0]])  # perpendicular to DistVector

        RadiusDirection = RadiusDirection / np.linalg.norm(RadiusDirection)  # normalize RadiusDirection
        CenterPoint = StartVector + DistVector / 2.0 + RadiusDirection * math.sqrt(radius ** 2.0 - (Dist / 2.0) ** 2.0)

        # computes the starting angle and ending angle
        temp = StartVector - CenterPoint
        AngStart = math.atan2(temp[1], temp[0])
        temp = EndVector - CenterPoint
        AngEnd = math.atan2(temp[1], temp[0])

        if flagRightOf:  # inkscape does not follow svg path format to create arcs. It uses sodipodi which is weird  =S
            sodipodiAngleStart = str(AngEnd)
            sodipodiAngleEnd = str(AngStart)
        else:
            sodipodiAngleStart = str(AngStart)
            sodipodiAngleEnd = str(AngEnd)

        # arc instructions
        if largeArc:
            largeArcFlag = 1
        else:
            largeArcFlag = 0
        if flagRightOf:
            sweepFlag = 0
        else:
            sweepFlag = 1
        arcString = ' a %f,%f 0 %d %d %f,%f' % (radius, radius, largeArcFlag, sweepFlag, EndVector[0] - StartVector[0], EndVector[1] - StartVector[1])
        if not flagOpen:  # option to close arc
            arcString = arcString + ' L ' + str(CenterPoint[0] + offset[0]) + ' ' + str(CenterPoint[1] + offset[1]) + ' z'

        # M = moveto,L = lineto,H = horizontal lineto,V = vertical lineto,C = curveto,S = smooth curveto,Q = quadratic Bezier curve,T = smooth quadratic Bezier curveto,A = elliptical Arc,Z = closepath
        Attribs = {inkex.addNS('label', 'inkscape'): label, 'style': simplestyle.formatStyle(lineStyle), inkex.addNS('type', 'sodipodi'): 'arc',
                   inkex.addNS('rx', 'sodipodi'): str(radius), inkex.addNS('ry', 'sodipodi'): str(radius),
                   inkex.addNS('cx', 'sodipodi'): str(CenterPoint[0] + offset[0]), inkex.addNS('cy', 'sodipodi'): str(CenterPoint[1] + offset[1]),
                   inkex.addNS('start', 'sodipodi'): sodipodiAngleStart, inkex.addNS('end', 'sodipodi'): sodipodiAngleEnd,
                   'd': 'M ' + str(offset[0] + StartVector[0]) + ' ' + str(offset[1] + StartVector[1]) + arcString}
        if flagOpen:
            Attribs[inkex.addNS('open', 'sodipodi')] = 'true'

        return inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), Attribs)

    # ---------------------------------------------
    @staticmethod
    def centerAngStartAngEnd(parent, centerPoint, radius, angStart, angEnd, offset=[0, 0], label='arc', lineStyle=lineStyle.setSimpleBlack(),
                             flagOpen=True, largeArc=False):
        """Draws a circle arc given its center and start and end angles

        .. image:: ../imagesDocs/arc_centerAngStartAngEnd.png
          :width: 200px

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param parent: parent object
        :param centerPoint: center coordinate [x,y]
        :param radius: arc radius
        :param angStart: start angle in degrees
        :param angEnd: end angle in degrees
        :param offset: extra offset coords [x,y]
        :param label: label of the line. Default 'arc'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()
        :param flagOpen: closes the arc. See image below. Default: True
        :param largeArc: Sets the largest arc to be drawn. See image below

          - True: Draws the largest arc
          - False: Draws the smallest arc (Default)

        :type parent: inkscapeMadeEasy object (see example below)
        :type centerPoint: list
        :type radius: float
        :type angStart: float
        :type angEnd: float
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object
        :type flagOpen: bool
        :type largeArc: bool

        :returns: the new arc object
        :rtype: line Object

        **Arc options**

        .. image:: ../imagesDocs/arc_centerAngStartAngEnd_flags.png
          :width: 700px

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle=inkDraw.lineStyle.setSimpleBlack()
        >>>     
        >>>     #draws the shortest arc
        >>>     inkDraw.arc.centerAngStartAngEnd(parent=root_layer, centerPoint=[0,0], radius=15.0, angStart=-10, angEnd=90,
        >>>                                      offset=[0,0], label='arc1',  lineStyle=myLineStyle, flagOpen=True,largeArc=False)
        >>>     #draws the longest arc
        >>>     inkDraw.arc.centerAngStartAngEnd(parent=root_layer, centerPoint=[0,0], radius=15.0, angStart=-10, angEnd=90,
        >>>                                      offset=[30,0], label='arc1',  lineStyle=myLineStyle, flagOpen=True,largeArc=True)
        """

        Pstart = [radius * math.cos(math.radians(angStart)), radius * math.sin(math.radians(angStart))]
        Pend = [radius * math.cos(math.radians(angEnd)), radius * math.sin(math.radians(angEnd))]

        pos = [centerPoint[0] + offset[0], centerPoint[1] + offset[1]]

        if abs(angEnd - angStart) <= 180:
            flagRight = largeArc
        else:
            flagRight = not largeArc

        return arc.startEndRadius(parent, Pstart, Pend, radius, pos, label, lineStyle, flagRight, flagOpen, largeArc)


class circle():
    """ This is a class with different methods for drawing circles.

    This class contains only static methods so that you don't have to inherit this in your class
    """

    @staticmethod
    def centerRadius(parent, centerPoint, radius, offset=[0, 0], label='circle', lineStyle=lineStyle.setSimpleBlack()):
        """draws a circle given its center point and radius

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param parent: parent object
        :param centerPoint: center coordinate [x,y]
        :param radius: circle's radius
        :param offset: extra offset coords [x,y]
        :param label: label of the line. Default 'circle'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()

        :type parent: inkscapeMadeEasy object (see example below)
        :type centerPoint: list
        :type radius: float
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object

        :returns: the new circle object
        :rtype: line Object

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle=inkDraw.lineStyle.setSimpleBlack()
        >>>     
        >>>     #draws the shortest arc
        >>>     inkDraw.circle.centerRadius(parent=root_layer, centerPoint=[0,0], radius=15.0, offset=[5,1], label='circle1',  lineStyle=myLineStyle)
        """

        # arc instructions
        arcStringA = ' a %f,%f 0 1 1 %f,%f' % (radius, radius, -2 * radius, 0)
        arcStringB = ' a %f,%f 0 1 1 %f,%f' % (radius, radius, 2 * radius, 0)

        # M = moveto,L = lineto,H = horizontal lineto,V = vertical lineto,C = curveto,S = smooth curveto,Q = quadratic Bezier curve,T = smooth quadratic Bezier curveto,A = elliptical Arc,Z = closepath
        Attribs = {inkex.addNS('label', 'inkscape'): label, 'style': simplestyle.formatStyle(lineStyle), inkex.addNS('type', 'sodipodi'): 'arc',
                   inkex.addNS('rx', 'sodipodi'): str(radius), inkex.addNS('ry', 'sodipodi'): str(radius),
                   inkex.addNS('cx', 'sodipodi'): str(centerPoint[0] + offset[0]), inkex.addNS('cy', 'sodipodi'): str(centerPoint[1] + offset[1]),
                   inkex.addNS('start', 'sodipodi'): '0', inkex.addNS('end', 'sodipodi'): str(2 * math.pi),
                   'd': 'M ' + str(centerPoint[0] + offset[0] + radius) + ' ' + str(
                       centerPoint[1] + offset[1]) + arcStringA + ' ' + arcStringB + ' z'}

        return inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), Attribs)


class rectangle():
    """ This is a class with different methods for drawing rectangles.

    This class contains only static methods so that you don't have to inherit this in your class
    """

    @staticmethod
    def widthHeightCenter(parent, centerPoint, width, height, radiusX=None, radiusY=None, offset=[0, 0], label='rectangle',
                          lineStyle=lineStyle.setSimpleBlack()):
        """draws a rectangle given its center point and dimensions

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param parent: parent object
        :param centerPoint: center coordinate [x,y]
        :param width: dimension in X direction
        :param height: dimension in Y direction
        :param radiusX: rounding radius in X direction. If this value is ``None``, the rectangle will have sharp corners. Default: None
        :param radiusY: rounding radius in Y direction. If this value is ``None``, then radiusX will also be used in Y direction. If radiusX is also ``None``,then the rectangle will have sharp corners. Default: None
        :param offset: extra offset coords [x,y]
        :param label: label of the line. Default 'circle'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()

        :type parent: inkscapeMadeEasy object (see example below)
        :type centerPoint: list
        :type width: float
        :type height: float
        :type radiusX: float
        :type radiusY: float
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object

        :returns: the new rectangle object
        :rtype: rectangle Object

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle=inkDraw.lineStyle.setSimpleBlack()
        >>>     
        >>>     #draws a 50x60 rectangle with radiusX=2.0 and radiusY=3.0
        >>>     inkDraw.rectangle.widthHeightCenter(parent=root_layer, centerPoint=[0,0], width=50, height=60, radiusX=2.0,radiusY=3.0, offset=[0,0], label='rect1',  lineStyle=myLineStyle)
        """
        x = centerPoint[0] - width / 2.0 + offset[0]
        y = centerPoint[1] - height / 2.0 + offset[1]

        Attribs = {inkex.addNS('label', 'inkscape'): label, 'style': simplestyle.formatStyle(lineStyle), 'width': str(width), 'height': str(height),
                   'x': str(x), 'y': str(y), 'rx': str(radiusX), 'ry': str(radiusY)}

        if radiusX and radiusX > 0.0:
            Attribs['rx'] = str(radiusX)
            if radiusY is None:
                Attribs['ry'] = str(radiusX)
            else:
                if radiusY > 0.0:
                    Attribs['ry'] = str(radiusY)

        return inkex.etree.SubElement(parent, inkex.addNS('rect', 'svg'), Attribs)

    @staticmethod
    def corners(parent, corner1, corner2, radiusX=None, radiusY=None, offset=[0, 0], label='rectangle', lineStyle=lineStyle.setSimpleBlack()):
        """draws a rectangle given the coordinates of two oposite corners

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param parent: parent object
        :param corner1: coordinates of corner 1 [x,y]
        :param corner2: coordinates of corner 1 [x,y]
        :param radiusX: rounding radius in X direction. If this value is ``None``, the rectangle will have sharp corners. Default: None
        :param radiusY: rounding radius in Y direction. If this value is ``None``, then radiusX will also be used in Y direction. If radiusX is also ``None``, then the rectangle will have sharp corners. Default: None
        :param offset: extra offset coords [x,y]
        :param label: label of the line. Default 'circle'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()

        :type parent: inkscapeMadeEasy object (see example below)
        :type corner1: list
        :type corner2: list
        :type radiusX: float
        :type radiusY: float
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object

        :returns: the new rectangle object
        :rtype: rectangle Object

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle=inkDraw.lineStyle.setSimpleBlack()
        >>>     
        >>>     #draws a rectangle with corners C1=[1,5] and C2=[6,10], with radiusX=2.0 and radiusY=3.0
        >>>     inkDraw.rectangle.corners(parent=root_layer, corner1=[1,5], corner2=[6,10], radiusX=2.0,radiusY=3.0, offset=[0,0], label='rect1',  lineStyle=myLineStyle)
        """
        x = (corner1[0] + corner2[0]) / 2.0
        y = (corner1[1] + corner2[1]) / 2.0

        width = abs(corner1[0] - corner2[0])
        height = abs(corner1[1] - corner2[1])

        return rectangle.widthHeightCenter(parent, [x, y], width, height, radiusX, radiusY, offset, label, lineStyle)


class ellipse():
    """ This is a class with different methods for drawing ellipses.

    This class contains only static methods so that you don't have to inherit this in your class
    """

    @staticmethod
    def centerRadius(parent, centerPoint, radiusX, radiusY, offset=[0, 0], label='circle', lineStyle=lineStyle.setSimpleBlack()):
        """draws an ellipse given its center point and radius

        .. warning:: Keep in mind  that Inkscape's y axis is upside down!

        :param parent: parent object
        :param centerPoint: center coordinate [x,y]
        :param radiusX: circle's radius in x direction
        :param radiusY: circle's radius in y direction
        :param offset: extra offset coords [x,y]
        :param label: label of the line. Default 'circle'
        :param lineStyle: line style to be used. See class ``lineStyle``. Default: lineStyle=lineStyle.setSimpleBlack()

        :type parent: inkscapeMadeEasy object (see example below)
        :type centerPoint: list
        :type radiusX: float
        :type radiusY: float
        :type offset: list
        :type label: string
        :type lineStyle: lineStyle object

        :returns: the new ellipse object
        :rtype: line Object

        **Example**

        >>> import inkex
        >>> import inkscapeMadeEasy_Base as inkBase
        >>> import inkscapeMadeEasy_Draw as inkDraw
        >>> 
        >>> class myExtension(inkBase.inkscapeMadeEasy):
        >>>   def __init__(self):
        >>>     ...
        >>>     ...
        >>> 
        >>>   def effect(self):
        >>>     root_layer = self.document.getroot()     # retrieves the root layer of the document
        >>>     myLineStyle=inkDraw.lineStyle.setSimpleBlack()
        >>>     
        >>>     #draws the shortest arc
        >>>     inkDraw.ellipse.centerRadius(parent=root_layer, centerPoint=[0,0], radiusX=15.0, radiusY=25.0, offset=[5,1], label='circle1',  lineStyle=myLineStyle)
        """

        # arc instructions
        arcStringA = ' a %f,%f 0 1 1 %f,%f' % (radiusX, radiusY, -2 * radiusX, 0)
        arcStringB = ' a %f,%f 0 1 1 %f,%f' % (radiusX, radiusY, 2 * radiusX, 0)

        # M = moveto,L = lineto,H = horizontal lineto,V = vertical lineto,C = curveto,S = smooth curveto,Q = quadratic Bezier curve,T = smooth quadratic Bezier curveto,A = elliptical Arc,Z = closepath
        Attribs = {inkex.addNS('label', 'inkscape'): label, 'style': simplestyle.formatStyle(lineStyle), inkex.addNS('type', 'sodipodi'): 'arc',
                   inkex.addNS('rx', 'sodipodi'): str(radiusX), inkex.addNS('ry', 'sodipodi'): str(radiusY),
                   inkex.addNS('cx', 'sodipodi'): str(centerPoint[0] + offset[0]), inkex.addNS('cy', 'sodipodi'): str(centerPoint[1] + offset[1]),
                   inkex.addNS('start', 'sodipodi'): '0', inkex.addNS('end', 'sodipodi'): str(2 * math.pi),
                   'd': 'M ' + str(centerPoint[0] + offset[0] + radiusX) + ' ' + str(
                       centerPoint[1] + offset[1]) + arcStringA + ' ' + arcStringB + ' z'}

        return inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), Attribs)


BlankSVG = r"""<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/"
     xmlns:cc="http://creativecommons.org/ns#"
     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
     xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
     width="744.09448819"
     height="1052.3622047"
     id="svg19803"
     version="1.1"
     inkscape:version="0.48.3.1 r9886"
     sodipodi:docname="New document 45">
    <defs id="defs19805"/>
    <sodipodi:namedview id="base"
                        pagecolor="#ffffff"
                        bordercolor="#666666"
                        borderopacity="1.0"
                        inkscape:pageopacity="0.0"
                        inkscape:pageshadow="2"
                        inkscape:zoom="0.35"
                        inkscape:cx="375"
                        inkscape:cy="520"
                        inkscape:document-units="px"
                        inkscape:current-layer="layer1"
                        showgrid="false"
                        inkscape:window-width="500"
                        inkscape:window-height="445"
                        inkscape:window-x="932"
                        inkscape:window-y="0"
                        inkscape:window-maximized="0"/>
    <metadata id="metadata19808">
        <rdf:RDF>
            <cc:Work rdf:about="">
                <dc:format>image/svg+xml</dc:format>
                <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
                <dc:title></dc:title>
            </cc:Work>
        </rdf:RDF>
    </metadata>
    <g inkscape:label="Layer 1"
       inkscape:groupmode="layer"
       id="layer1"/>
</svg>
"""

================================================
FILE: 0.9x/inkscapeMadeEasy_Plot.py
================================================
#!/usr/bin/python

# --------------------------------------------------------------------------------------
#
#    inkscapeMadeEasy: - Helper module that extends Aaron Spike's inkex.py module,
#                        focusing productivity in inkscape extension development
#    Copyright (C) 2016 by Fernando Moura
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# --------------------------------------------------------------------------------------

import math
import sys
import inkscapeMadeEasy_Draw as inkDraw

"""
This module contains a set of classes to help producing graphs.

This module requires the following modules: math, numpy, lxml and sys

"""


def displayMsg(msg):
    """Displays a message to the user.

    :returns: nothing
    :rtype: -
    
    .. note:: Identical function has been also defined inside inkscapeMadeEasy class   
    
    """
    sys.stderr.write(msg + '\n')


def Dump(obj, file='./dump_file.txt', mode='w'):
    """Function to easily output the result of ``str(obj)`` to a file

    This function was created to help debugging the code while it is running under inkscape. Since inkscape does not possess a terminal as today (2016),
    this function overcomes partially the issue of sending things to stdout by dumping result of the function ``str()`` in a text file.


    :param obj: object to sent to the file. Any type that can be used in ``str()``
    :param file: file path. Default: ``./dump_file.txt``
    :param mode: writing mode of the file. Default: ``w`` (write)
    :type obj: any, as long as ``str(obj``) is implemented (see ``__str__()`` metaclass definition )
    :type arg2: string
    :type mode: string
    :returns:  nothing
    :rtype: -

    .. note:: Identical function has been also defined inside inkscapeMadeEasy class   

    **Example**

    >>> vector1=[1,2,3,4,5,6]
    >>> Dump(vector1,file='~/temporary.txt',mode='w')   # writes the list to a file
    >>> vector2=[7,8,9,10]
    >>> Dump(vector2,file='~/temporary.txt',mode='a')   # append the list to a file

    """
    file = open(file, mode)
    file.write(str(obj) + '\n')
    file.close()


def generateListOfTicksLinear(axisLimits, axisOrigin, tickStep):
    """Defines list of ticks to be drawn in a linear plot

    .. note:: Internal function.
    """

    # make the list of ticks, symmetrically to the origin
    listTicksPositive = [axisOrigin]
    while listTicksPositive[-1] < axisLimits[1]:
        listTicksPositive.append(listTicksPositive[-1] + tickStep)

    listTicksNegative = [axisOrigin]

    while listTicksNegative[-1] > axisLimits[0]:
        listTicksNegative.append(listTicksNegative[-1] - tickStep)

    listTicks = listTicksPositive + listTicksNegative[1:]
    return listTicks


def generateListOfTicksLog10(axisLimits):
    """Defines list of ticks to be drawn in a log10 plot

    .. note:: Internal function."""

    # make the list of ticks, symmetrically to the origin
    listTicks = [axisLimits[0]]
    while listTicks[-1] < axisLimits[1]:
        listTicks.append(listTicks[-1] * 10)
    return listTicks


def findOrigin(axisLimits, flagLog10, scale):
    """ retrieves the position of the origin. In case of logarithmic scale, it will be axisLimits[0]

    .. note:: Internal function.
    """
    if flagLog10:
        axisOrigin = math.log10(axisLimits[0]) * scale
    else:
        if axisLimits[0] <= 0.0 and axisLimits[1] >= 0.0:
            axisOrigin = 0.0
        else:
            if axisLimits[1] < 0:
                axisOrigin = axisLimits[1] * scale
            else:
                axisOrigin = axisLimits[0] * scale

    return axisOrigin


def getPositionAndText(value, scale, flagLog10, axisUnitFactor):
    """given a value, its scale, and some flags, finds it position in the diagram and the text to be shown

    .. note:: Internal function."""
    if flagLog10:
        pos = math.log10(value) * scale
    else:
        pos = value * scale
    # try to simplify number
    if int(value) - value == 0:
        valStr = str(int(round(value, 3)))
    else:
        valStr = str(round(value, 3))

    # option to add extra factor to the axis ticks
    if flagLog10:
        exponent = str(int(math.log10(value)))
        if axisUnitFactor:
            if inkDraw.useLatex:
                Text = '10^{' + exponent + '}' + axisUnitFactor + ''
            else:
                Text = '10^' + exponent + '' + axisUnitFactor + ''
        else:
            if inkDraw.useLatex:
                Text = '10^{' + exponent + '}'
            else:
                Text = '10^' + exponent + ''
    else:
        if axisUnitFactor:
            if value == 0:
                Text = '0'
            if value == 1:
                Text = axisUnitFactor
            if value == -1:
                Text = '-' + axisUnitFactor
            if value != 0 and value != 1 and value != -1:
                Text = valStr + axisUnitFactor
        else:
            Text = valStr

    if inkDraw.useLatex:
        Text = '$' + Text + '$'

    return [pos, Text]


class axis():
    """ This is a class with different methods for making plot axes.
    
    This class contains only static methods so that you don't have to inherit this in your class

    .. note::  This class uses LaTeX in labels and tick marks if LaTeX support is enabled. This is an optional feature, **enabled by default**. Please refer to :ref:`latexSupport` on how to disable it.


    """

    @staticmethod
    def cartesian(ExtensionBaseObj, parent, xLim, yLim, position=[0, 0], xLabel='', yLabel='', xlog10scale=False, ylog10scale=False, xTicks=True, yTicks=True, xTickStep=1.0, yTickStep=1.0, xScale=20,
                  yScale=20, xAxisUnitFactor='', yAxisUnitFactor='', xGrid=False, yGrid=False, forceTextSize=0, forceLineWidth=0, drawAxis=True, ExtraLenghtAxisX=0.0, ExtraLenghtAxisY=0.0):
        """Creates the axes for cartesian plot

        .. note:: This method uses LaTeX in labels and tick marks if LaTeX support is enabled. This is an optional feature, **enabled by default**. Please refer to :ref:`latexSupport` on how to disable it.
    
        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param parent: parent object
        :param xLim: limits of the X axis [x_min,x_max]. If the axis is in log10 scale, then the limits will be rounded to complete one decade.
        :param yLim: limits of the Y axis [y_min,y_max]. If the axis is in log10 scale, then the limits will be rounded to complete one decade.
        :param position: position of the point where x and y axis cross [x0,y0]. The point where the axis cross depend on the limits.

              - If xLimits comprises the origin x=0, then the  Y axis crosses the X axis at x=0.
              - If xLimits contains only negative numbers, then the Y axis crosses the X axis at x_max.
              - If xLimits contains only positive numbers, then the Y axis crosses the X axis at x_min.

              - The same rule applies to y direction.
        :param xLabel: Label of the X axis. Default: ''

              The text can contain any LaTeX command. If you want to write mathematical text, you can enclose it between dollar signs $...$. If LaTeX support is disabled, do not use $.

        :param yLabel: Label of the Y axis. Default: ''

              The text can contain any LaTeX command. If you want to write mathematical text, you can enclose it between dollar signs $...$. If LaTeX support is disabled, do not use $.

        :param xlog10scale: sets X axis to log10 scale if True. Default: False
        :param ylog10scale: sets Y axis to log10 scale if True. Default: False
        :param xTicks: Adds axis ticks to the X axis if True. Default: True
        :param yTicks: Adds axis ticks to the Y axis if True. Default: True
        :param xTickStep: Value interval between two consecutive ticks on X axis. (Not used if X axis is in log10 scale). Default:1.0
        :param yTickStep: Value interval between two consecutive ticks on Y axis. (Not used if Y axis is in log10 scale). Default:1.0
        :param xScale:  Distance between each xTickStep in svg units. Default: 20

               - If axis is linear, then xScale is the size in svg units of each tick
               - If axis is log10, the xScale is the size in svg units of one decade

        :param yScale:  Distance between each xTickStep in svg units. Default: 20

               - If axis is linear, then xScale is the size in svg units of each tick
               - If axis is log10, the xScale is the size in svg units of one decade

        :param xAxisUnitFactor: extra text to be added to Ticks in both x and y. Default: ''

              This is useful when we want to represent interval with different units. example pi, 2pi 3pi, etc.
              The text can be any LaTeX text. Keep in mind that this text will be inserted within a mathematical environment $...$, therefore no $ is needed here.
        :param yAxisUnitFactor: extra text to be added to the ticks in Y axis. Default: ''

              This is useful when we want to represent interval with different units. example pi, 2pi 3pi, etc.
              The text can be any LaTeX text. Keep in mind that this text will be inserted within a mathematical environment $...$, therefore no $ is needed here.

        :param xGrid: adds grid lines to X axis if true. Default: False
        :param yGrid: adds grid lines to Y axis if true. Default: False
        :param forceTextSize: Size of the text. If this parameter is 0.0 then the method will compute an appropriate size. Default: 0.0
        :param forceLineWidth: Width of the lines. If this parameter is 0.0 then the method will compute an appropriate size. Default: 0.0

        :param drawAxis: control flag of the axis method

               - True: draws axis normally
               - False: returns the limits and origin position without drawing the axis itself

        :param ExtraLenghtAxisX: extra length left near the arrow pointer of X axis. Default 0.0
        :param ExtraLenghtAxisY: extra length left near the arrow pointer of Y axis. Default 0.0

        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type parent: inkscapeMadeEasy object (see example below)
        :type xLim: list
        :type yLim: list
        :type position: list
        :type xLabel: string
        :type yLabel: string
        :type xlog10scale: bool
        :type ylog10scale: bool
        :type xTicks: bool
        :type yTicks: bool
        :type xTickStep: float
        :type yTickStep: float
        :type xScale: float
        :type yScale: float
        :type xAxisUnitFactor: string
        :type yAxisUnitFactor: string
        :type xGrid: bool
        :type yGrid: bool
        :type forceTextSize: float
        :type forceLineWidth: float
        :type drawAxis: bool
        :type ExtraLenghtAxisX: float
        :type ExtraLenghtAxisY: float

        :returns: [GroupPlot, outputLimits, axisOrigin]

            - GroupPlot:  the axis area object (if drawAxis=False, this output is ``None``)
            - outputLimits: a list with tuples:[(x_min,xPos_min),(x_max,xPos_max),(y_min,yPos_min),(y_max,yPos_max)]

                  - x_min, x_max, y_min, y_max:               The limits of the axis object
                  - xPos_min, xPos_max, yPos_min, yPos_max:   The positions of the limits of the axis object, considering the scaling and units
                  - axisOrigin [X0,Y0]:                      A list with the coordinates of the point where the axes cross.
        :rtype: list

        **Examples**

        .. image:: ../imagesDocs/plot_axisCartesianParameters_01.png
          :width: 800px

        """
        if drawAxis:
            GroupPlot = ExtensionBaseObj.createGroup(parent, 'Plot')

        # sets the scale  scaleX and scaleY stores the size of one unit in both axis (linear axis) or the size of a decade in log plots
        if xlog10scale:
            scaleX = xScale
        else:
            scaleX = xScale / float(xTickStep)

        if ylog10scale:
            scaleY = -yScale
        else:
            scaleY = -yScale / float(yTickStep)  # negative bc inkscape is upside down

        # font size and other text parameters
        if forceTextSize == 0:
            textSize = 0.25 * min(xScale, yScale)
        else:
            textSize = forceTextSize

        textSizeSmall = 0.8 * textSize  # font size for axis ticks

        text_offset = textSize  # base space for text positioning
        ExtraSpaceArrowX = (2.0 + ExtraLenghtAxisX) * text_offset  # extra space for drawing arrow on axis
        ExtraSpaceArrowY = (3.0 + ExtraLenghtAxisY) * text_offset  # extra space for drawing arrow on axis
        lenghtTicks = textSize / 2.0  # length of the ticks

        # create styles
        if forceLineWidth == 0:
            lineWidth = min(xScale, yScale) / 35.0
        else:
            lineWidth = forceLineWidth

        lineWidthGrid = 0.7 * lineWidth
        lineWidthGridFine = lineWidthGrid / 2.0

        nameMarkerArrowAxis = inkDraw.marker.createArrow1Marker(ExtensionBaseObj, 'ArrowAxis', RenameMode=1, scale=0.4)
        lineStyleAxis = inkDraw.lineStyle.set(lineWidth, lineColor=inkDraw.color.gray(0.3), markerEnd=nameMarkerArrowAxis[1])
        lineStyleTicks = inkDraw.lineStyle.set(lineWidth, lineColor=inkDraw.color.gray(0.3))
        lineStyleGrid = inkDraw.lineStyle.set(lineWidthGrid, lineColor=inkDraw.color.gray(0.7))
        lineStyleGridFine = inkDraw.lineStyle.set(lineWidthGridFine, lineColor=inkDraw.color.gray(0.7))

        textStyleLarge = inkDraw.textStyle.setSimpleBlack(textSize)
        textStyleSmall = inkDraw.textStyle.setSimpleBlack(textSizeSmall, 'center')

        # check if limits are valid
        if xLim[0] >= xLim[1]:
            sys.stderr.write('Error: xLim is invalid.')
            return 0
        if yLim[0] >= yLim[1]:
            sys.stderr.write('Error: yLim is invalid.')
            return 0
        # check if the limits are valid for logarithmic scales.
        if xlog10scale:
            if xLim[0] <= 0 or xLim[1] <= 0:
                sys.stderr.write('Error: xLim is invalid in logarithmic scale')
                return 0
            else:
                xmin = pow(10, math.floor(math.log10(xLim[0])))
                xmax = pow(10, math.ceil(math.log10(xLim[1])))
                xLimits = [xmin, xmax]
        else:
            xLimits = xLim

        if ylog10scale:
            if yLim[0] <= 0 or yLim[1] <= 0:
                sys.stderr.write('Error: yLim is invalid in logarithmic scale')
                return 0
            else:
                ymin = pow(10, math.floor(math.log10(yLim[0])))
                ymax = pow(10, math.ceil(math.log10(yLim[1])))
                yLimits = [ymin, ymax]
        else:
            yLimits = yLim

        # finds the position of the Origin of axis
        axisOrigin = [0.0, 0.0]

        axisOrigin[0] = findOrigin(xLimits, xlog10scale, scaleX)
        axisOrigin[1] = findOrigin(yLimits, ylog10scale, scaleY)

        # computes the positions of the limits on svg, considering the scale

        if xlog10scale:  # convert limits to position in diagram, including scaling factor
            xLimitsPos = [math.log10(x) * scaleX for x in xLimits]
        else:
            xLimitsPos = [x * scaleX for x in xLimits]

        if ylog10scale:  # convert limits to position in diagram, including scaling factor
            yLimitsPos = [math.log10(y) * scaleY for y in yLimits]
        else:
            yLimitsPos = [y * scaleY for y in yLimits]

        # build the list of tuples with the limits of the plotting area
        outputLimits = zip([xLimits[0], xLimits[1], yLimits[0], yLimits[1]],
                           [xLimitsPos[0] - axisOrigin[0] + position[0], xLimitsPos[1] - axisOrigin[0] + position[0], yLimitsPos[0] - axisOrigin[1] + position[1],
                            yLimitsPos[1] - axisOrigin[1] + position[1]])
        if not drawAxis:
            return [None, outputLimits, axisOrigin]

        # axis ticks
        groupTicks = ExtensionBaseObj.createGroup(GroupPlot, 'Ticks')

        if xTicks or xGrid:

            if xlog10scale:
                listTicks = generateListOfTicksLog10(xLimits)
            else:
                listTicks = generateListOfTicksLinear(xLimits, axisOrigin[0] / scaleX, xTickStep)
            for x in listTicks:

                if x <= xLimits[1] and x >= xLimits[0]:

                    # get position, considering the scale and its text
                    [posX, xText] = getPositionAndText(x, scaleX, xlog10scale, xAxisUnitFactor)

                    if xGrid and posX != axisOrigin[0]:  # grid lines. Do not draw if grid line is over the axis
                        inkDraw.line.absCoords(groupTicks, [[posX, yLimitsPos[0]], [posX, yLimitsPos[1]]], [0, 0], lineStyle=lineStyleGrid)

                    # intermediate grid lines in case of logarithmic scale
                    if xGrid and xlog10scale and x < xLimits[1]:
                        for i in range(2, 10):
                            aditionalStep = math.log10(i) * scaleX
                            inkDraw.line.absCoords(groupTicks, [[posX + aditionalStep, yLimitsPos[0]], [posX + aditionalStep, yLimitsPos[1]]], [0, 0], lineStyle=lineStyleGridFine)

                    # tick
                    if xTicks:
                        if posX != axisOrigin[0]:  # don't draw if in the origin
                            inkDraw.line.relCoords(groupTicks, [[0, lenghtTicks]], [posX, axisOrigin[1] - lenghtTicks / 2.0], lineStyle=lineStyleTicks)

                    # sets justification
                    # inkDraw.text.write(ExtensionBaseObj,'orig='+str(axisOrigin),[axisOrigin[0]+10,axisOrigin[1]-30],groupTicks,fontSize=7)
                    # inkDraw.text.write(ExtensionBaseObj,'xlim='+str(xLimitsPos),[axisOrigin[0]+10,axisOrigin[1]-20],groupTicks,fontSize=7)
                    # inkDraw.text.write(ExtensionBaseObj,'ylim='+str(yLimitsPos),[axisOrigin[0]+10,axisOrigin[1]-10],groupTicks,fontSize=7)

                    if axisOrigin[1] == yLimitsPos[0]:
                        justif = 'tc'
                        offsetX = 0
                        offsetY = text_offset / 2.0  # inkDraw.circle.centerRadius(groupTicks, axisOrigin, 10, [0,0])

                    if axisOrigin[1] != yLimitsPos[0] and axisOrigin[1] != yLimitsPos[1]:
                        justif = 'tr'
                        offsetX = -text_offset / 4.0
                        offsetY = text_offset / 2.0
                        # inkDraw.circle.centerRadius(groupTicks, axisOrigin, 10, [0,0])
                        # inkDraw.text.write(ExtensionBaseObj,str(axisOrigin[1]),[axisOrigin[0]+10,axisOrigin[1]+10],groupTicks,fontSize=7)
                        # inkDraw.text.write(ExtensionBaseObj,str(yLimitsPos[0]),[axisOrigin[0]+10,axisOrigin[1]+20],groupTicks,fontSize=7)
                        if posX == axisOrigin[0]:
                            if posX == xLimitsPos[1]:
                                justif = 'tr'
                                offsetX = -text_offset / 4.0
                            else:
                                justif = 'tl'
                                offsetX = +text_offset / 4.0

                    if axisOrigin[1] == yLimitsPos[1]:
                        justif = 'bc'
                        offsetX = 0
                        offsetY = -text_offset / 2.0
                        # inkDraw.circle.centerRadius(groupTicks,axisOrigin, 10, [0,0])
                        if posX == axisOrigin[0]:
                            if posX == xLimitsPos[1]:
                                justif = 'br'
                                offsetX = -text_offset / 4.0
                            else:
                                justif = 'bl'
                                offsetX = +text_offset / 4.0

                    # value
                    if xTicks:
                        inkDraw.text.latex(ExtensionBaseObj, groupTicks, xText, [posX + offsetX, axisOrigin[1] + offsetY], textSizeSmall, refPoint=justif)

        if yTicks or yGrid:
            # approximate limits to multiples of 10
            if ylog10scale:
                listTicks = generateListOfTicksLog10(yLimits)
            else:
                listTicks = generateListOfTicksLinear(yLimits, axisOrigin[1] / scaleY, yTickStep)

            for y in listTicks:
                if y <= yLimits[1] and y >= yLimits[0]:

                    # get position, considering the scale and its text
                    [posY, yText] = getPositionAndText(y, abs(scaleY), ylog10scale, yAxisUnitFactor)
                    posY = -posY

                    if yGrid and posY != axisOrigin[1]:  # grid lines. Do not draw if grid line is over the axis
                        inkDraw.line.absCoords(groupTicks, [[xLimitsPos[0], posY], [xLimitsPos[1], posY]], [0, 0], lineStyle=lineStyleGrid)

                    # intermediate grid lines in case of logarithmic scale
                    if yGrid and ylog10scale and y < yLimits[1]:
                        for i in range(2, 10):
                            aditionalStep = math.log10(i) * scaleY
                            inkDraw.line.absCoords(groupTicks, [[xLimitsPos[0], posY + aditionalStep], [xLimitsPos[1], posY + aditionalStep]], [0, 0], lineStyle=lineStyleGridFine)

                    # tick
                    if yTicks:
                        if posY != axisOrigin[1]:  # don't draw if in the origin
                            inkDraw.line.relCoords(groupTicks, [[lenghtTicks, 0]], [axisOrigin[0] - lenghtTicks / 2.0, posY], lineStyle=lineStyleTicks)

                    # sets justification
                    # inkDraw.text.write(ExtensionBaseObj,'orig='+str(axisOrigin),[axisOrigin[0]+10,axisOrigin[1]-30],groupTicks,fontSize=7)
                    # inkDraw.text.write(ExtensionBaseObj,'xlim='+str(xLimitsPos),[axisOrigin[0]+10,axisOrigin[1]-20],groupTicks,fontSize=7)
                    # inkDraw.text.write(ExtensionBaseObj,'ylim='+str(yLimitsPos),[axisOrigin[0]+10,axisOrigin[1]-10],groupTicks,fontSize=7)

                    if axisOrigin[0] == xLimitsPos[0]:
                        justif = 'cr'
                        offsetX = -text_offset / 2.0
                        offsetY = 0  # inkDraw.circle.centerRadius(groupTicks,axisOrigin, 10, [0,0],'trash')

                    if axisOrigin[0] != xLimitsPos[0] and axisOrigin[0] != xLimitsPos[1]:
                        justif = 'tr'
                        offsetX = -text_offset / 2.0
                        offsetY = text_offset / 4.0
                        # inkDraw.circle.centerRadius(groupTicks,axisOrigin, 10, [0,0])
                        # inkDraw.text.write(ExtensionBaseObj,str(axisOrigin[0]),[axisOrigin[0]+10,axisOrigin[1]+10],groupTicks,fontSize=7)
                        # inkDraw.text.write(ExtensionBaseObj,str(yLimitsPos[0]*scaleX),[axisOrigin[0]+10,axisOrigin[1]+20],groupTicks,fontSize=7)
                        if posY == axisOrigin[1]:
                            if posY == yLimitsPos[1]:
                                justif = 'tr'
                                offsetY = text_offset / 4.0
                            else:
                                justif = 'br'
                                offsetY = -text_offset / 4.0

                    if axisOrigin[0] == xLimitsPos[1]:
                        justif = 'cl'
                        offsetX = text_offset / 2.0
                        offsetY = 0
                        # inkDraw.circle.centerRadius(groupTicks,axisOrigin, 10, [0,0])
                        if posY == axisOrigin[1]:
                            if posY == yLimitsPos[1]:
                                justif = 'tl'
                                offsetY = text_offset / 4.0
                            else:
                                justif = 'bl'
                                offsetY = -text_offset / 4.0

                    # value
                    if yTicks:
                        inkDraw.text.latex(ExtensionBaseObj, groupTicks, yText, [axisOrigin[0] + offsetX, (posY + offsetY)], textSizeSmall, refPoint=justif)

        ExtensionBaseObj.moveElement(GroupPlot, [position[0] - axisOrigin[0], position[1] - axisOrigin[1]])

        # draw axis in the end so it stays on top of other objects
        GroupAxis = ExtensionBaseObj.createGroup(GroupPlot, 'Axis')

        inkDraw.line.absCoords(GroupAxis, [[xLimitsPos[0], 0], [xLimitsPos[1] + ExtraSpaceArrowX, 0]], [0, axisOrigin[1]], 'Xaxis', lineStyle=lineStyleAxis)
        if xLabel:  # axis labels
            inkDraw.text.latex(ExtensionBaseObj, GroupAxis, xLabel, [xLimitsPos[1] + ExtraSpaceArrowX - text_offset / 3, axisOrigin[1] + text_offset / 2.0], textSize, refPoint='tl')

        inkDraw.line.absCoords(GroupAxis, [[0, yLimitsPos[0]], [0, yLimitsPos[1] - ExtraSpaceArrowY]], [axisOrigin[0], 0], 'Yaxis', lineStyle=lineStyleAxis)
        if yLabel:  # axis labels
            inkDraw.text.latex(ExtensionBaseObj, GroupAxis, yLabel, [axisOrigin[0] + text_offset / 2.0, (yLimitsPos[1] - ExtraSpaceArrowY)], textSize, refPoint='tl')

        return [GroupPlot, outputLimits, axisOrigin]

    @staticmethod
    def polar(ExtensionBaseObj, parent, rLim, tLim=[0.0, 360.0], position=[0.0, 0.0], rLabel='', rlog10scale=False, rTicks=True, tTicks=True, rTickStep=1.0, tTickStep=45.0, rScale=20,
              rAxisUnitFactor='', rGrid=False, tGrid=False, forceTextSize=0, forceLineWidth=0, drawAxis=True, ExtraLenghtAxisR=0.0):
        """Creates the axes for polar plot

        .. note:: This method uses LaTeX in labels and tick marks if LaTeS support is enabled. This is an optional feature, **enabled by default**.
            Please refer to :ref:`latexSupport` on how to disable it.
        
        :param ExtensionBaseObj: Most of the times you have to use 'self' from inkscapeMadeEasy related objects
        :param parent: parent object
        :param rLim: limits of the R axis [r_min,r_max]. If the axis is in log10 scale, then the limits will be rounded to complete one decade.
        :param tLim: limits of the theta axis [t_min,t_max]. Values in degrees. Default: [0,360]
        :param position: position of the center [x0,y0].

        :param rLabel: Label of the R axis. Default: ''

              The text can contain any LaTeX command. If you want to write mathematical text, you can enclose it between dollar signs $...$. If LaTeX support is disabled, do not use $.

        :param rlog10scale: sets R axis to log10 scale if True. Default: False

            - If rlog10scale=True, then the lower limit of rLim must be >=1

        :param rTicks: Adds axis ticks to the R axis if True. Default: True
        :param tTicks: Adds axis ticks to the theta axis if True. Default: True
        :param rTickStep: Value interval between two consecutive ticks on R axis. (Not used if R axis is in log10 scale). Default:1.0
        :param tTickStep: Value interval between two consecutive ticks on theta axis. Default:45.0
        :param rScale:  Distance between each rTickStep in svg units. Default: 20

               - If axis is linear, then rScale is the size in svg units of each tick
               - If axis is log10, the rScale is the size in svg units of one decade

        :param rAxisUnitFactor: extra text to be added to Ticks in both x and y. Default: ''

              This is useful when we want to represent interval with different units. example pi, 2pi 3pi, etc.
              The text can be any LaTeX text. Keep in mind that this text will be inserted within a mathematical environment $...$, therefore no $ is needed here.

        :param rGrid: adds grid lines to R axis if true. Default: False
        :param tGrid: adds grid lines to theta axis if true. Default: False
        :param forceTextSize: Size of the text. If this parameter is 0.0 then the method will compute an appropriate size. Default: 0.0
        :param forceLineWidth: Width of the lines. If this parameter is 0.0 then the method will compute an appropriate size. Default: 0.0

        :param drawAxis: control flag of the axis method

               - True: draws axis normally
               - False: returns the limits and origin position without drawing the axis itself

        :param ExtraLenghtAxisR: extra length between the R axis and its label. Default 0.0

        :type ExtensionBaseObj: inkscapeMadeEasy object (see example below)
        :type parent: inkscapeMadeEasy object (see example below)
        :type rLim: list
        :type tLim: list
        :type position: list
        :type rLabel: string
        :type rlog10scale: bool
        :type rTicks: bool
        :type tTicks: bool
        :type rTickStep: float
        :type tTickStep: float
        :type rScale: float
        :type rAxisUnitFactor: string
        :type rGrid: bool
        :type tGrid: bool
        :type forceTextSize: float
        :type forceLineWidth: float
        :type drawAxis: bool
        :type ExtraLenghtAxisR: float

        :returns: [GroupPlot, outputRLimits, axisOrigin]

            - GroupPlot:  the axis area object (if drawAxis=False, this output is ``None``)
            - outputRLimits: a list with tuples:[(r_min,rPos_min),(r_max,rPos_max)]

                  - r_min, r_max       :   The limits of the axis object
                  - rPos_min, rPos_max :   The positions of the limits of the axis object, considering the scaling and units
                  - axisOrigin [X0,Y0] :   A list with the coordinates of the point where the axes cross.
        :rtype: list

        **Examples**

        .. image:: ../imagesDocs/plot_axisPolarParameters_01.png
          :width: 800px

        """

        if drawAxis:
            GroupPlot = ExtensionBaseObj.createGroup(parent, 'Plot')

        # sets the scale  scaleX and scaleY stores the size of one unit in both axis (linear axis) or the size of a decade in log plots
        if rlog10scale:
            scaleR = rScale
        else:
            scaleR = rScale / float(rTickStep)

        # font size and other text parameters
        if forceTextSize == 0:
            textSize = 0.2 * rScale
        else:
            textSize = forceTextSize

        textSizeSmall = 0.8 * textSize  # font size for axis ticks

        text_offset = textSize  # base space for text positioning
        ExtraSpaceArrowR = (2.0 + ExtraLenghtAxisR) * text_offset  # extra space for drawing arrow on axis
        lenghtTicks = textSize / 2.0  # length of the ticks

        # create styles
        if forceLineWidth == 0:
            lineWidth = rScale / 30.0
        else:
            lineWidth = forceLineWidth

        lineWidthGrid = 0.7 * lineWidth
        lineWidthGridFine = lineWidthGrid / 2.0

        # nameTickerArrowAxis = inkDraw.marker.createArrow1Marker(ExtensionBaseObj, 'ArrowAxis', RenameMode=1, scale=0.4)
        lineStyleAxis = inkDraw.lineStyle.set(lineWidth, lineColor=inkDraw.color.gray(0.3))
        lineStyleTicks = inkDraw.lineStyle.set(lineWidth, lineColor=inkDraw.color.gray(0.3))
        lineStyleGrid = inkDraw.lineStyle.set(lineWidthGrid, lineColor=inkDraw.color.gray(0.7))
        lineStyleGridFine = inkDraw.lineStyle.set(lineWidthGridFine, lineColor=inkDraw.color.gray(0.7))

        textStyleLarge = inkDraw.textStyle.setSimpleBlack(textSize)
        textStyleSmall = inkDraw.textStyle.setSimpleBlack(textSizeSmall, 'center')

        # check if limits are valid
        if rLim[0] < 0.0 or rLim[0] >= rLim[1]:
            sys.stderr.write('Error: rLim is invalid')
            return 0
        if tLim[0] >= tLim[1]:
            sys.stderr.write('Error: tLim is invalid')
            return 0
        # check if the limits are valid for logarithmic scales.
        if rlog10scale:
            if rLim[0] < 1 or rLim[1] < 1:
                sys.stderr.write('Error: rLim is invalid in logarithmic scale')
                return 0
            else:
                rmin = pow(10, math.floor(math.log10(rLim[0])))
                rmax = pow(10, math.ceil(math.log10(rLim[1])))
                rLimits = [rmin, rmax]
        else:
            rLimits = rLim

        tLimits = tLim

        if abs(tLimits[1] - tLimits[0]) > 360:
            tLimits = [0, 360]

        if abs(tLimits[1] - tLimits[0]) > 180:
            largeArc = True
        else:
            largeArc = False

        # finds the position of the Origin of axis
        axisOrigin = [0.0, 0.0]
        axisOrigin[0] = findOrigin(rLimits, rlog10scale, scaleR)
        axisOrigin[1] = findOrigin(tLimits, False, 1.0)

        # computes the positions of the limits on svg, considering the scale

        if rlog10scale:  # convert limits to position in diagram, including scaling factor
            rLimitsPos = [math.log10(x) * scaleR for x in rLimits]
        else:
            rLimitsPos = [x * scaleR for x in rLimits]

        # build the list of tuples with the limits of the plotting area
        outputLimits = zip([rLimits[0], rLimits[1]], [rLimitsPos[0] - axisOrigin[0] + position[0], rLimitsPos[1] - axisOrigin[0] + position[0]])

        if not drawAxis:
            return [None, outputLimits, [0, 0]]

        # axis ticks
        groupTicks = ExtensionBaseObj.createGroup(GroupPlot, 'Ticks')

        if rTicks or rGrid:

            if rlog10scale:
                listTicks = generateListOfTicksLog10(rLimits)
            else:
                listTicks = generateListOfTicksLinear(rLimits, axisOrigin[0] / scaleR, rTickStep)
            for r in listTicks:

                if r <= rLimits[1] and r >= rLimits[0]:

                    # get position, considering the scale and its text
                    [posR, rText] = getPositionAndText(r, scaleR, rlog10scale, rAxisUnitFactor)

                    if rGrid and posR > 0.0 and r > rLimits[0] and r < rLimits[1]:  # grid lines.
                        if tLimits[1] - tLimits[0] < 360:
                            inkDraw.arc.centerAngStartAngEnd(groupTicks, [0, 0], posR, -tLimits[1], -tLimits[0], [0, 0], lineStyle=lineStyleGrid,
                                                             largeArc=largeArc)  # negative angles bc inkscape is upside down
                        else:
                            inkDraw.circle.centerRadius(groupTicks, [0, 0], posR, offset=[0, 0], lineStyle=lineStyleGrid)

                    # intermediate grid lines in case of logarithmic scale
                    if rGrid and rlog10scale and r < rLimits[1]:
                        for i in range(2, 10):
                            aditionalStep = math.log10(i) * scaleR
                            if tLimits[1] - tLimits[0] < 360:
                                inkDraw.arc.centerAngStartAngEnd(groupTicks, [0, 0], posR + aditionalStep, -tLimits[1], -tLimits[0], [0, 0], lineStyle=lineStyleGridFine,
                                                                 largeArc=largeArc)  # negative angles bc inkscape is upside down
                            else:
                                inkDraw.circle.centerRadius(groupTicks, [0, 0], posR + aditionalStep, offset=[0, 0], lineStyle=lineStyleGridFine)

                    # tick
                    if rTicks and posR > 0.0:
                        inkDraw.arc.centerAngStartAngEnd(groupTicks, [0, 0], posR, -tLimits[0] - math.degrees(lenghtTicks / float(posR * 2)), -tLimits[0] + math.degrees(lenghtTicks / float(posR * 2)),
                                                         [0, 0], lineStyle=lineStyleTicks, largeArc=False)
                    if rTicks and posR == 0.0:
                        inkDraw.line.relCoords(groupTicks, [[0, lenghtTicks]], [0, - lenghtTicks / 2.0], lineStyle=lineStyleTicks)

                    # sets justification
                    # inkDraw.text.write(ExtensionBaseObj,'orig='+str(axisOrigin),[axisOrigin[0]+10,axisOrigin[1]-30],groupTicks,fontSize=7)
                    # inkDraw.text.write(ExtensionBaseObj,'xlim='+str(xLimitsPos),[axisOrigin[0]+10,axisOrigin[1]-20],groupTicks,fontSize=7)
                    # inkDraw.text.write(ExtensionBaseObj,'ylim='+str(yLimitsPos),[axisOrigin[0]+10,axisOrigin[1]-10],groupTicks,fontSize=7)

                    if posR == 0:
                        justif = 'cc'
                        offsetX = 0
                        offsetY = text_offset * 1.2
                        posX = posR * math.cos(math.radians(-tLimits[0])) + offsetX
                        posY = posR * math.sin(math.radians(-tLimits[0])) + offsetY
                    else:
                        offsetT = text_offset * 1.2
                        if tLimits[1] - tLimits[0] > 340:
                            offsetR = text_offset / 2.0
                        else:
                            offsetR = 0
                        justif = 'cc'
                        posX = (posR + offsetR) * math.cos(math.radians(-tLimits[0])) + offsetT * math.sin(math.radians(tLimits[0]))
                        posY = (posR + offsetR) * math.sin(math.radians(-tLimits[0])) + offsetT * math.cos(math.radians(-tLimits[0]))
                    # value
                    # inkDraw.circle.centerRadius(groupTicks,[posX,posY], 1)
                    if rTicks:
                        inkDraw.text.latex(ExtensionBaseObj, groupTicks, rText, [posX, posY], textSizeSmall, refPoint=justif)

        if tTicks or tGrid:

            listTicks = generateListOfTicksLinear(tLimits, axisOrigin[1], tTickStep)
            for t in listTicks:
                if t <= tLimits[1] and t >= tLimits[0]:
                    c = math.cos(math.radians(-t))  # negative angles bc inkscape is upside down
                    s = math.sin(math.radians(-t))  # negative angles bc inkscape is upside down
                    # get position, considering the scale and its text
                    if inkDraw.useLatex:
                        tText = '$' + str(t) + '$'
                    else:
                        tText = str(t)

                    if (tGrid and t > tLimits[0] and t < tLimits[1]) or (tGrid and t == tLimits[0] and tLimits[1] - tLimits[0] >= 360):
                        if rLimitsPos[0] == 0:  # if rmin is zero, then make the lines to reach the center
                            if not rlog10scale:
                                P1 = [(rLimitsPos[0] + scaleR * rTickStep / 2) * c, (rLimitsPos[0] + scaleR * rTickStep / 2) * s]
                            else:
                                P1 = [(rLimitsPos[0] + 0.3 * scaleR) * c, (rLimitsPos[0] + 0.3 * scaleR) * s]
                        else:
                            P1 = [rLimitsPos[0] * c, rLimitsPos[0] * s]
                        P2 = [rLimitsPos[1] * c, rLimitsPos[1] * s]
                        inkDraw.line.absCoords(groupTicks, [P1, P2], [0, 0], lineStyle=lineStyleGrid)

                    # tick
                    if (tTicks and t != tLimits[1]) or (tTicks and t == tLimits[1] and tLimits[1] - tLimits[0] < 360):
                        P1 = [(rLimitsPos[1] - lenghtTicks / 2.0) * c, (rLimitsPos[1] - lenghtTicks / 2.0) * s]
                        inkDraw.line.relCoords(groupTicks, [[lenghtTicks * c, lenghtTicks * s]], P1, lineStyle=lineStyleTicks)

                    if c > 1.0e-4:
                        justif = 'cl'
                    else:
                        if c < -1.0e-4:
                            justif = 'cr'
                        else:
                            justif = 'cc'

                    offsetR = text_offset
                    posX = (rLimitsPos[1] + offsetR) * c
                    posY = (rLimitsPos[1] + offsetR) * s
                    # value
                    if (tTicks and t != tLimits[1]) or (tTicks and t == tLimits[1] and tLimits[1] - tLimits[0] < 360):
                        inkDraw.text.latex(ExtensionBaseObj, groupTicks, tText, [posX, posY], textSizeSmall, refPoint=justif)

        ExtensionBaseObj.moveElement(GroupPlot, position)

        # draw axis in the end so it stays on top of other objects
        GroupAxis = ExtensionBaseObj.createGroup(GroupPlot, 'Axis')

        c0 = math.cos(math.radians(-tLimits[0]))  # negative angles bc inkscape is upside down
        s0 = math.sin(math.radians(-tLimits[0]))  # negative angles bc inkscape is upside down
        c1 = math.cos(math.radians(-tLimits[1]))  # negative angles bc inkscape is upside down
        s1 = math.sin(math.radians(-tLimits[1]))  # negative angles bc inkscape is upside down
        P1 = [rLimitsPos[0] * c0, rLimitsPos[0] * s0]
        P2 = [rLimitsPos[1] * c0, rLimitsPos[1] * s0]
        P3 = [rLimitsPos[1] * c1, rLimitsPos[1] * s1]
        P4 = [rLimitsPos[0] * c1, rLimitsPos[0] * s1]
        if tLimits[1] - tLimits[0] < 360:
            inkDraw.line.absCoords(GroupAxis, [P1, P2], [0, 0], lineStyle=lineStyleAxis)
            inkDraw.line.absCoords(GroupAxis, [P3, P4], [0, 0], lineStyle=lineStyleAxis)
        else:
            if rTicks:
                inkDraw.line.absCoords(GroupAxis, [P1, P2], [0, 0], lineStyle=lineStyleAxis)

        if tLimits[1] - tLimits[0] < 360:
            if rLimitsPos[0] > 0:
                inkDraw.arc.startEndRadius(GroupAxis, P1, P4, rLimitsPos[0], offset=[0, 0], lineStyle=lineStyleAxis, flagRightOf=True, flagOpen=True, largeArc=largeArc)
            inkDraw.arc.startEndRadius(GroupAxis, P2, P3, rLimitsPos[1], offset=[0, 0], lineStyle=lineStyleAxis, flagRightOf=True, flagOpen=True, largeArc=largeArc)
        else:
            if rLimitsPos[0] > 0:
                inkDraw.circle.centerRadius(GroupAxis, [0, 0], rLimitsPos[0], offset=[0
Download .txt
gitextract_ga0zb2kn/

├── 0.9x/
│   ├── inkscapeMadeEasy_Base.py
│   ├── inkscapeMadeEasy_Draw.py
│   ├── inkscapeMadeEasy_Plot.py
│   └── textextLib/
│       ├── CircuitSymbolsLatexPreamble.tex
│       ├── __init__.py
│       ├── basicLatexPackages.tex
│       ├── textext.inx
│       └── textext.py
├── LICENSE
├── README.md
├── docs/
│   ├── Makefile
│   └── source/
│       ├── Changelog.rst
│       ├── _static/
│       │   └── style.css
│       ├── conf.py
│       ├── genindex.rst
│       ├── index.rst
│       ├── installation.rst
│       ├── mainFeatures.rst
│       ├── minimalExample.rst
│       └── moduleDefinitions.rst
├── examples/
│   ├── iME_Draw_colorPicker.inx
│   ├── iME_Draw_colorPicker.py
│   ├── iME_Draw_lineStyle_and_markers.inx
│   ├── iME_Draw_lineStyle_and_markers.py
│   ├── minimalExample.inx
│   ├── minimalExample.py
│   └── testingMinimalExample.py
└── latest/
    ├── basicLatexPackages.tex
    ├── inkscapeMadeEasy_Base.py
    ├── inkscapeMadeEasy_Draw.py
    └── inkscapeMadeEasy_Plot.py
Download .txt
SYMBOL INDEX (249 symbols across 11 files)

FILE: 0.9x/inkscapeMadeEasy_Base.py
  class inkscapeMadeEasy (line 42) | class inkscapeMadeEasy(inkex.Effect):
    method __init__ (line 51) | def __init__(self):
    method displayMsg (line 72) | def displayMsg(self, msg):
    method getBasicLatexPackagesFile (line 84) | def getBasicLatexPackagesFile(self):
    method Dump (line 102) | def Dump(self, obj, file='./dump_file.txt', mode='w'):
    method removeElement (line 134) | def removeElement(self, element):
    method importSVG (line 168) | def importSVG(self, parent, fileIn, createGroup=True):
    method exportSVG (line 209) | def exportSVG(self, element, fileOut):
    method uniqueIdNumber (line 255) | def uniqueIdNumber(self, prefix_id):
    method getDefinitions (line 292) | def getDefinitions(self):
    method unifyDefs (line 310) | def unifyDefs(self):
    method getDefsByTag (line 330) | def getDefsByTag(self, tag='marker'):
    method getDefsById (line 341) | def getDefsById(self,id):
    method getElemFromXpath (line 352) | def getElemFromXpath(self, xpath):
    method getElemAttrib (line 371) | def getElemAttrib(self, elem, attribName):
    method getDocumentScale (line 399) | def getDocumentScale(self):
    method getDocumentName (line 422) | def getDocumentName(self):
    method getDocumentUnit (line 443) | def getDocumentUnit(self):
    method getcurrentLayer (line 482) | def getcurrentLayer(self):
    method removeAbsPath (line 498) | def removeAbsPath(self, element):
    method unit2userUnit (line 514) | def unit2userUnit(self, value, unit_in):
    method userUnit2unit (line 552) | def userUnit2unit(self, value, unit_out):
    method unit2unit (line 589) | def unit2unit(self, value, unit_in, unit_out):
    method createGroup (line 628) | def createGroup(self, parent, label='none'):
    method ungroup (line 662) | def ungroup(self, group):
    method getTransformMatrix (line 700) | def getTransformMatrix(self, element):
    method rotateElement (line 788) | def rotateElement(self, element, center, angleDeg):
    method copyElement (line 827) | def copyElement(self, element, newParent, distance=None, angleDeg=None):
    method moveElement (line 864) | def moveElement(self, element, distance):
    method scaleElement (line 903) | def scaleElement(self, element, scaleX=1.0, scaleY=0.0, center=None):
    method findMarker (line 958) | def findMarker(self, markerName):
    method getPoints (line 975) | def getPoints(self, element):
    method getBoundingBox (line 1116) | def getBoundingBox(self, element):
    method getCenter (line 1144) | def getCenter(self, element):
    method getSegmentFromPoints (line 1171) | def getSegmentFromPoints(self, pointList, normalDirection='R'):
    method getSegmentParameters (line 1215) | def getSegmentParameters(self, element, normalDirection='R'):

FILE: 0.9x/inkscapeMadeEasy_Draw.py
  function displayMsg (line 59) | def displayMsg(msg):
  function Dump (line 71) | def Dump(obj, file='./dump_file.txt', mode='w'):
  class color (line 102) | class color():
    method defined (line 115) | def defined(colorName):
    method RGB (line 178) | def RGB(RGBlist):
    method gray (line 209) | def gray(percentage):
    method colorPickerToRGBalpha (line 236) | def colorPickerToRGBalpha(colorPickerString):
    method parseColorPicker (line 293) | def parseColorPicker(stringColorOption, stringColorPicker):
  class marker (line 375) | class marker():
    method createMarker (line 387) | def createMarker(ExtensionBaseObj, nameID, markerPath, RenameMode=0, s...
    method createDotMarker (line 508) | def createDotMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=0.4,...
    method createCrossMarker (line 555) | def createCrossMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=0....
    method createArrow1Marker (line 602) | def createArrow1Marker(ExtensionBaseObj, nameID, RenameMode=0, scale=0...
    method createInfLineMarker (line 662) | def createInfLineMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=...
  class lineStyle (line 738) | class lineStyle():
    method set (line 750) | def set(lineWidth=1.0, lineColor=color.defined('black'), fillColor=Non...
    method setSimpleBlack (line 833) | def setSimpleBlack(lineWidth=1.0):
  class textStyle (line 863) | class textStyle():
    method set (line 875) | def set(fontSize=10, justification='left', textColor=color.defined('bl...
    method setSimpleBlack (line 943) | def setSimpleBlack(fontSize=10, justification='left'):
    method setSimpleColor (line 978) | def setSimpleColor(fontSize=10, justification='left', textColor=color....
  class text (line 1013) | class text():
    method write (line 1024) | def write(ExtensionBaseObj, text, coords, parent, textStyle=textStyle....
    method latex (line 1117) | def latex(ExtensionBaseObj, parent, LaTeXtext, position, fontSize=10, ...
  class cubicBezier (line 1318) | class cubicBezier():
    method addNode (line 1324) | def addNode(NodeList, coord=[0, 0], cPbefore=[-1, 0], cPafter=[1, 0], ...
    method draw (line 1459) | def draw(parent, NodeList, offset=np.array([0, 0]), label='none', line...
  class line (line 1584) | class line():
    method absCoords (line 1591) | def absCoords(parent, coordsList, offset=[0, 0], label='none', lineSty...
    method relCoords (line 1657) | def relCoords(parent, coordsList, offset=[0, 0], label='none', lineSty...
  class arc (line 1722) | class arc():
    method startEndRadius (line 1729) | def startEndRadius(parent, Pstart, Pend, radius, offset=[0, 0], label=...
    method centerAngStartAngEnd (line 1861) | def centerAngStartAngEnd(parent, centerPoint, radius, angStart, angEnd...
  class circle (line 1939) | class circle():
    method centerRadius (line 1946) | def centerRadius(parent, centerPoint, radius, offset=[0, 0], label='ci...
  class rectangle (line 2002) | class rectangle():
    method widthHeightCenter (line 2009) | def widthHeightCenter(parent, centerPoint, width, height, radiusX=None...
    method corners (line 2073) | def corners(parent, corner1, corner2, radiusX=None, radiusY=None, offs...
  class ellipse (line 2126) | class ellipse():
    method centerRadius (line 2133) | def centerRadius(parent, centerPoint, radiusX, radiusY, offset=[0, 0],...

FILE: 0.9x/inkscapeMadeEasy_Plot.py
  function displayMsg (line 36) | def displayMsg(msg):
  function Dump (line 48) | def Dump(obj, file='./dump_file.txt', mode='w'):
  function generateListOfTicksLinear (line 79) | def generateListOfTicksLinear(axisLimits, axisOrigin, tickStep):
  function generateListOfTicksLog10 (line 99) | def generateListOfTicksLog10(axisLimits):
  function findOrigin (line 111) | def findOrigin(axisLimits, flagLog10, scale):
  function getPositionAndText (line 130) | def getPositionAndText(value, scale, flagLog10, axisUnitFactor):
  class axis (line 176) | class axis():
    method cartesian (line 187) | def cartesian(ExtensionBaseObj, parent, xLim, yLim, position=[0, 0], x...
    method polar (line 549) | def polar(ExtensionBaseObj, parent, rLim, tLim=[0.0, 360.0], position=...
  class plot (line 866) | class plot():
    method cartesian (line 877) | def cartesian(ExtensionBaseObj, parent, xData, yData, position=[0, 0],...
    method polar (line 1141) | def polar(ExtensionBaseObj, parent, rData, tData, position=[0, 0], rLa...
    method stem (line 1368) | def stem(ExtensionBaseObj, parent, xData, yData, position=[0, 0], xLab...

FILE: 0.9x/textextLib/textext.py
  class TexText (line 89) | class TexText(inkex.Effect):
    method __init__ (line 90) | def __init__(self):
    method effect (line 107) | def effect(self):
    method do_convert (line 152) | def do_convert(self, text, preamble_file, scale_factor, converter_cls,
    method get_old (line 207) | def get_old(self):
    method replace_node (line 231) | def replace_node(self, old_node, new_node):
    method copy_style (line 253) | def copy_style(self, old_node, new_node):
  class Settings (line 272) | class Settings(object):
    method __init__ (line 273) | def __init__(self):
    method load (line 283) | def load(self):
    method save (line 314) | def save(self):
    method get (line 340) | def get(self, key, typecast, default=None):
    method set (line 346) | def set(self, key, value):
  function exec_command (line 357) | def exec_command(cmd, ok_return_value=0, combine_error=False):
  function exec_command (line 381) | def exec_command(cmd, ok_return_value=0, combine_error=False):
  class LatexConverterBase (line 414) | class LatexConverterBase(object):
    method __init__ (line 421) | def __init__(self, document):
    method convert (line 434) | def convert(self, latex_text, preamble_file, scale_factor):
    method available (line 448) | def available(cls):
    method finish (line 455) | def finish(self):
    method tmp (line 464) | def tmp(self, suffix):
    method tex_to_pdf (line 472) | def tex_to_pdf(self, latex_text, preamble_file):
    method remove_temp_files (line 512) | def remove_temp_files(self):
    method try_remove (line 519) | def try_remove(self, filename):
  class PdfConverterBase (line 526) | class PdfConverterBase(LatexConverterBase):
    method convert (line 527) | def convert(self, latex_text, preamble_file, scale_factor):
    method pdf_to_svg (line 543) | def pdf_to_svg(self):
    method get_transform (line 547) | def get_transform(self, scale_factor):
    method svg_to_group (line 551) | def svg_to_group(self):
    method fix_xml_namespace (line 564) | def fix_xml_namespace(self, node):
  class SkConvert (line 580) | class SkConvert(PdfConverterBase):
    method get_transform (line 584) | def get_transform(self, scale_factor):
    method pdf_to_svg (line 587) | def pdf_to_svg(self):
    method available (line 604) | def available(cls):
  class PstoeditPlotSvg (line 613) | class PstoeditPlotSvg(PdfConverterBase):
    method get_transform (line 617) | def get_transform(self, scale_factor):
    method pdf_to_svg (line 622) | def pdf_to_svg(self):
    method available (line 633) | def available(cls):
  class Pdf2Svg (line 644) | class Pdf2Svg(PdfConverterBase):
    method __init__ (line 648) | def __init__(self, document):
    method convert (line 652) | def convert(self, *a, **kw):
    method pdf_to_svg (line 657) | def pdf_to_svg(self):
    method get_transform (line 660) | def get_transform(self, scale_factor):
    method svg_to_group (line 663) | def svg_to_group(self):
    method available (line 700) | def available(cls):

FILE: docs/source/conf.py
  function setup (line 306) | def setup(app):

FILE: examples/iME_Draw_colorPicker.py
  class myExtension (line 8) | class myExtension(inkBase.inkscapeMadeEasy):
    method __init__ (line 10) | def __init__(self):
    method effect (line 16) | def effect(self):

FILE: examples/iME_Draw_lineStyle_and_markers.py
  class myExtension (line 8) | class myExtension(inkBase.inkscapeMadeEasy):
    method __init__ (line 10) | def __init__(self):
    method effect (line 15) | def effect(self):

FILE: examples/minimalExample.py
  class MinimalExample (line 10) | class MinimalExample(inkBase.inkscapeMadeEasy):
    method __init__ (line 12) | def __init__(self):
    method effect (line 21) | def effect(self):

FILE: latest/inkscapeMadeEasy_Base.py
  class inkscapeMadeEasy (line 37) | class inkscapeMadeEasy(inkex.Effect):
    method __init__ (line 39) | def __init__(self):
    method bool (line 113) | def bool(self,valueStr):
    method displayMsg (line 139) | def displayMsg(self, msg):
    method createEmptySVG (line 151) | def createEmptySVG(self,fileName):
    method getBasicLatexPackagesFile (line 167) | def getBasicLatexPackagesFile(self):
    method Dump (line 186) | def Dump(self, obj, file='./dump_file.txt', mode='w'):
    method removeElement (line 211) | def removeElement(self, element):
    method importSVG (line 246) | def importSVG(self, parent, fileIn, createGroup=True,position=None,sca...
    method exportSVG (line 302) | def exportSVG(self, element, fileOut):
    method uniqueIdNumber (line 346) | def uniqueIdNumber(self, prefix_id):
    method getDefinitions (line 388) | def getDefinitions(self):
    method cleanDefs (line 405) | def cleanDefs(self, removeUnused=False, unifyDuplicates=False):
    method unifyDefs (line 470) | def unifyDefs(self,ungroupChild=False):
    method getDefsByTag (line 493) | def getDefsByTag(self, tag='marker'):
    method getDefsById (line 507) | def getDefsById(self,id):
    method getElemFromXpath (line 521) | def getElemFromXpath(self, xpath):
    method getElemAttrib (line 537) | def getElemAttrib(self, elem, attribName):
    method getDocumentScaleFactor (line 564) | def getDocumentScaleFactor(self):
    method getDocumentName (line 590) | def getDocumentName(self):
    method getDocumentUnit (line 609) | def getDocumentUnit(self):
    method getcurrentLayer (line 646) | def getcurrentLayer(self):
    method abs2relPath (line 659) | def abs2relPath(self, element):
    method unit2userUnit (line 674) | def unit2userUnit(self, value, unit_in):
    method userUnit2unit (line 712) | def userUnit2unit(self, value, unit_out):
    method unit2unit (line 749) | def unit2unit(self, value, unit_in, unit_out):
    method createGroup (line 788) | def createGroup(self, parent, label=None):
    method ungroup (line 819) | def ungroup(self, group):
    method getTransformMatrix (line 857) | def getTransformMatrix(self, element):
    method rotateElement (line 948) | def rotateElement(self, element, center, angleDeg):
    method copyElement (line 987) | def copyElement(self, element, newParent, distance=None, angleDeg=None):
    method moveElement (line 1024) | def moveElement(self, element, distance):
    method scaleElement (line 1063) | def scaleElement(self, element, scaleX=1.0, scaleY=None, center=None):
    method addAttribute (line 1115) | def addAttribute(self, element, attributeName, attributeValue,forceWri...
    method findMarker (line 1148) | def findMarker(self, markerName):
    method getPoints (line 1164) | def getPoints(self, element):
    method getBoundingBox (line 1329) | def getBoundingBox(self, element):
    method getCenter (line 1357) | def getCenter(self, element):
    method getSegmentFromPoints (line 1384) | def getSegmentFromPoints(self, pointList, normalDirection='R'):
    method getSegmentParameters (line 1426) | def getSegmentParameters(self, element, normalDirection='R'):

FILE: latest/inkscapeMadeEasy_Draw.py
  function displayMsg (line 54) | def displayMsg(msg):
  function Dump (line 69) | def Dump(obj, file='./dump_file.txt', mode='w'):
  function circle3Points (line 98) | def circle3Points(P1, P2, P3):
  class color (line 136) | class color():
    method defined (line 145) | def defined(colorName,alpha=1.0):
    method RGB (line 214) | def RGB(RGBlist,alpha=255):
    method rgb (line 237) | def rgb(RGBlist,alpha=1.0):
    method gray (line 261) | def gray(percentage,alpha=1.0):
    method val2hex (line 283) | def val2hex(value):
    method splitColorAlpha (line 314) | def splitColorAlpha(colorString):
    method colorPickerToRGBalpha (line 335) | def colorPickerToRGBalpha(colorPickerString):
    method parseColorPicker (line 394) | def parseColorPicker(stringColorOption, stringColorPicker):
  class marker (line 484) | class marker():
    method createMarker (line 501) | def createMarker(ExtensionBaseObj, nameID, markerPath, RenameMode=0, s...
    method createDotMarker (line 621) | def createDotMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=0.4,...
    method createCrossMarker (line 663) | def createCrossMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=0....
    method createArrow1Marker (line 703) | def createArrow1Marker(ExtensionBaseObj, nameID, RenameMode=0, scale=0...
    method createElipsisMarker (line 762) | def createElipsisMarker(ExtensionBaseObj, nameID, RenameMode=0, scale=...
  class lineStyle (line 831) | class lineStyle():
    method set (line 845) | def set(lineWidth=1.0, lineColor=color.defined('black'), fillColor=Non...
    method createDashedLinePattern (line 930) | def createDashedLinePattern(dashLength=5.0,gapLength=10.0):
    method setSimpleBlack (line 951) | def setSimpleBlack(lineWidth=1.0):
    method getStyle (line 970) | def getStyle(ExtensionBaseObj,element):
    method setStyle (line 1038) | def setStyle(ExtensionBaseObj,element,lineStyle):
  class textStyle (line 1067) | class textStyle():
    method set (line 1079) | def set(fontSize=10, justification='left', textColor=color.defined('bl...
    method setSimpleBlack (line 1143) | def setSimpleBlack(fontSize=10, justification='left'):
    method setSimpleColor (line 1167) | def setSimpleColor(fontSize=10, justification='left', textColor=color....
  class text (line 1191) | class text():
    method write (line 1203) | def write(ExtensionBaseObj, text, coords, parent, textStyle=textStyle....
    method latex (line 1286) | def latex(ExtensionBaseObj, parent, LaTeXtext, position, fontSize=10, ...
  class cubicBezier (line 1497) | class cubicBezier():
    method addNode (line 1504) | def addNode(NodeList, coord=[0, 0], cPbefore=[-1, 0], cPafter=[1, 0], ...
    method draw (line 1643) | def draw(parent, NodeList, offset=np.array([0, 0]), label='none', line...
  class line (line 1761) | class line():
    method absCoords (line 1768) | def absCoords(parent, coordsList, offset=[0, 0], label='none', lineSty...
    method relCoords (line 1825) | def relCoords(parent, coordsList, offset=[0, 0], label='none', lineSty...
  class arc (line 1880) | class arc():
    method startEndRadius (line 1887) | def startEndRadius(parent, Pstart, Pend, radius, offset=[0, 0], label=...
    method centerAngStartAngEnd (line 1958) | def centerAngStartAngEnd(parent, centerPoint, radius, angStart, angEnd...
    method threePoints (line 2019) | def threePoints(parent, Pstart, Pmid, Pend, offset=[0, 0], label='arc'...
  class circle (line 2101) | class circle():
    method centerRadius (line 2108) | def centerRadius(parent, centerPoint, radius, offset=[0, 0], label='ci...
    method threePoints (line 2155) | def threePoints(parent, P1, P2, P3, offset=[0, 0], label='circle', lin...
  class rectangle (line 2198) | class rectangle():
    method widthHeightCenter (line 2205) | def widthHeightCenter(parent, centerPoint, width, height, radiusX=None...
    method corners (line 2262) | def corners(parent, corner1, corner2, radiusX=None, radiusY=None, offs...
  class ellipseArc (line 2306) | class ellipseArc():
    method startEndRadius (line 2313) | def startEndRadius(parent, Pstart, Pend, radiusX=1.0, radiusY=2.0, off...
    method centerAngStartAngEnd (line 2457) | def centerAngStartAngEnd(parent, centerPoint, radiusX, radiusY, angSta...
  class ellipse (line 2549) | class ellipse():
    method centerRadius (line 2556) | def centerRadius(parent, centerPoint, radiusX, radiusY, offset=[0, 0],...

FILE: latest/inkscapeMadeEasy_Plot.py
  function displayMsg (line 31) | def displayMsg(msg):
  function Dump (line 46) | def Dump(obj, file='./dump_file.txt', mode='w'):
  function generateListOfTicksLinear (line 75) | def generateListOfTicksLinear(axisLimits, axisOrigin, tickStep):
  function generateListOfTicksLog10 (line 95) | def generateListOfTicksLog10(axisLimits):
  function findOrigin (line 108) | def findOrigin(axisLimits, flagLog10, scale):
  function getPositionAndText (line 127) | def getPositionAndText(value, scale, flagLog10, axisUnitFactor):
  class axis (line 173) | class axis():
    method cartesian (line 183) | def cartesian(ExtensionBaseObj, parent, xLim, yLim, position=[0, 0], x...
    method polar (line 566) | def polar(ExtensionBaseObj, parent, rLim, tLim=[0.0, 360.0], position=...
  class plot (line 896) | class plot():
    method cartesian (line 906) | def cartesian(ExtensionBaseObj, parent, xData, yData, position=[0, 0],...
    method polar (line 1170) | def polar(ExtensionBaseObj, parent, rData, tData, position=[0, 0], rLa...
    method stem (line 1386) | def stem(ExtensionBaseObj, parent, xData, yData, position=[0, 0], xLab...
Condensed preview — 31 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (661K chars).
[
  {
    "path": "0.9x/inkscapeMadeEasy_Base.py",
    "chars": 54377,
    "preview": "#!/usr/bin/python\n\n# -----------------------------------------------------------------------------\n#\n#    inkscapeMadeEa"
  },
  {
    "path": "0.9x/inkscapeMadeEasy_Draw.py",
    "chars": 102964,
    "preview": "#!/usr/bin/python\n\n# --------------------------------------------------------------------------------------\n#\n#    inksc"
  },
  {
    "path": "0.9x/inkscapeMadeEasy_Plot.py",
    "chars": 85476,
    "preview": "#!/usr/bin/python\n\n# --------------------------------------------------------------------------------------\n#\n#    inksc"
  },
  {
    "path": "0.9x/textextLib/CircuitSymbolsLatexPreamble.tex",
    "chars": 985,
    "preview": "\\usepackage{amsmath,amsthm,amsbsy,amsfonts,amssymb}\n\\usepackage[per=slash]{siunitx}\n\\usepackage{steinmetz}\n\\usepackage[u"
  },
  {
    "path": "0.9x/textextLib/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "0.9x/textextLib/basicLatexPackages.tex",
    "chars": 443,
    "preview": "\\usepackage{amsmath,amsthm,amsbsy,amsfonts,amssymb}\n\\usepackage[per=slash]{siunitx}\n\\usepackage{steinmetz}\n\\usepackage[u"
  },
  {
    "path": "0.9x/textextLib/textext.inx",
    "chars": 415,
    "preview": "<inkscape-extension>\n  <_name>Tex Text</_name>\n  <id>org.ekips.filter.textext</id>\n  <dependency type=\"executable\" locat"
  },
  {
    "path": "0.9x/textextLib/textext.py",
    "chars": 24063,
    "preview": "#!/usr/bin/env python\n\"\"\"\n=======\ntextext\n=======\n\n:Author: Pauli Virtanen <pav@iki.fi>\n:Date: 2008-04-26\n:License: BSD\n"
  },
  {
    "path": "LICENSE",
    "chars": 35141,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 4705,
    "preview": "This set of python modules is intended to extend Aaron Spike's inkex.py module, adding functions to help the\ndevelopment"
  },
  {
    "path": "docs/Makefile",
    "chars": 8347,
    "preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD "
  },
  {
    "path": "docs/source/Changelog.rst",
    "chars": 8256,
    "preview": "Changelog\n==========\n\n\n2024-10oct-23\n-------------\n\ninkscapeMadeEasy_Draw.py\n   - added methods to copy and paste line s"
  },
  {
    "path": "docs/source/_static/style.css",
    "chars": 40,
    "preview": ".wy-nav-content {\n   max-width: none;\n}\n"
  },
  {
    "path": "docs/source/conf.py",
    "chars": 9927,
    "preview": "# -*- coding: utf-8 -*-\n#\n# inkscapeMadeEasy documentation build configuration file, created by\n# sphinx-quickstart on W"
  },
  {
    "path": "docs/source/genindex.rst",
    "chars": 39,
    "preview": "Classes and methods\n==================="
  },
  {
    "path": "docs/source/index.rst",
    "chars": 6150,
    "preview": ".. inkscapeMadeEasy documentation master file, created by\n   sphinx-quickstart on Mon May  2 09:03:50 2016.\n   You can a"
  },
  {
    "path": "docs/source/installation.rst",
    "chars": 7595,
    "preview": "Inkscape Version\n================\n\n.. attention:: The procedure bellow  refers to inkscapeMadeEasy **version 1.0 or late"
  },
  {
    "path": "docs/source/mainFeatures.rst",
    "chars": 2918,
    "preview": "Optional LaTeX support via textext extension\n============================================\n\nMany of the functions impleme"
  },
  {
    "path": "docs/source/minimalExample.rst",
    "chars": 3416,
    "preview": "General Gist\n============\n\nThe general gist of inkscapeMadeEasy is:\n\n1) ``inkscapeMadeEasy_Base.inkscapeMadeEasy`` inher"
  },
  {
    "path": "docs/source/moduleDefinitions.rst",
    "chars": 2735,
    "preview": "inkscapeMadeEasy_Base\n=====================\n\nBase class for extensions.\n\nThis class extends the inkex.Effect class by ad"
  },
  {
    "path": "examples/iME_Draw_colorPicker.inx",
    "chars": 2073,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<inkscape-extension xmlns=\"http://www.inkscape.org/namespace/inkscape/extension\">"
  },
  {
    "path": "examples/iME_Draw_colorPicker.py",
    "chars": 1000,
    "preview": "#!/usr/bin/python\n\nimport inkex\nimport inkscapeMadeEasy_Base as inkBase\nimport inkscapeMadeEasy_Draw as inkDraw\n\n\nclass "
  },
  {
    "path": "examples/iME_Draw_lineStyle_and_markers.inx",
    "chars": 2028,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<inkscape-extension xmlns=\"http://www.inkscape.org/namespace/inkscape/extension\">"
  },
  {
    "path": "examples/iME_Draw_lineStyle_and_markers.py",
    "chars": 4006,
    "preview": "#!/usr/bin/python\n\nimport inkex\nimport inkscapeMadeEasy_Base as inkBase\nimport inkscapeMadeEasy_Draw as inkDraw\nimport m"
  },
  {
    "path": "examples/minimalExample.inx",
    "chars": 983,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<inkscape-extension xmlns=\"http://www.inkscape.org/namespace/inkscape/extension\">"
  },
  {
    "path": "examples/minimalExample.py",
    "chars": 2362,
    "preview": "#!/usr/bin/python\n\nimport inkscapeMadeEasy.inkscapeMadeEasy_Base as inkBase\n# in this example I am going to use both ink"
  },
  {
    "path": "examples/testingMinimalExample.py",
    "chars": 509,
    "preview": "#!/usr/bin/python\nimport os\n\nimport minimalExample as myExtension\n\n#loads the extension\nmyExt = myExtension.MinimalExamp"
  },
  {
    "path": "latest/basicLatexPackages.tex",
    "chars": 800,
    "preview": "\\usepackage{amsmath,amsthm,amsbsy,amsfonts,amssymb}\n\\usepackage[per-mode=symbol,retain-explicit-plus]{siunitx}\n\\usepacka"
  },
  {
    "path": "latest/inkscapeMadeEasy_Base.py",
    "chars": 62483,
    "preview": "#!/usr/bin/python\n\n# -----------------------------------------------------------------------------\n#\n#    inkscapeMadeEa"
  },
  {
    "path": "latest/inkscapeMadeEasy_Draw.py",
    "chars": 120136,
    "preview": "#!/usr/bin/python\n\n# --------------------------------------------------------------------------------------\n#\n#    inksc"
  },
  {
    "path": "latest/inkscapeMadeEasy_Plot.py",
    "chars": 87344,
    "preview": "#!/usr/bin/python\n\n# --------------------------------------------------------------------------------------\n#\n#    inksc"
  }
]

About this extraction

This page contains the full source code of the fsmMLK/inkscapeMadeEasy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 31 files (626.7 KB), approximately 159.2k tokens, and a symbol index with 249 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!