class document():

    """
       Main function to define the document style etc...
    """

    __version__ = bpversion
    # Global variables to sotre data
    _contents = {}
    _slides = {}
    _curentslide = None
    _global_counter = {}
    _width = 0
    _height = 0
    _guide = False
    _text_box = False
    _optimize_svg = True
    _output_format = 'html5'
    _theme = THEME
    _cache = None
    _pdf_animations = False
    _resize_raster = True
    _source_code = []  # Store the source code of the input script
    _rendered = False  # Store the state of the entire document (allow multiformat output)

    # Store data that need to be globally loaded in html like raster
    # contents images, video, etc...  Format of an entry in the list:
    # {'type': 'svg (or html)', 'content': the data to be loaded}
    _global_store = {}

    # Define path to external commands (see default THEME file)
    _external_cmd = {}

    # Define quiet state for docuement
    _quiet = False

    # Store extra latex packages globally
    _latex_packages = []

    # The TOC format should be TOC = ['title':'Subsublevel title', level:1]
    _TOC = []
    
    # REMOVE globals=globals(), locals=locals() they are useless
    
    def __init__(self, quiet=False, latex_packages=None, source_filename=None, **kwargs):
        """
  
          Create document to store slides
            options (see THEME)
            -------
            - width[800]: with of slides
            - height[600]: height of slides
            - guide[False]: Draw guide lines on slides to test alignements
            - text_box[False]: Draw box on slide elements to test width and height detection of elements (usefull to debug placement)
            - optimize[True]: Optimize svg using scour python script. This reduce the size but increase compilation time
            - cache[True]: Use cache system to not compile slides each times if nothing changed!
            - resize_raster[True]: Resize raster images (inside svg and for jpeg/png figures)
            - theme: Define the path to your personal THEME dictionnaryXS
        """

        if latex_packages is None:
            latex_packages = []

        if quiet:
            document._quiet = True
            sys.stdout = open(os.devnull, 'w')

        # reset if their is old variables
        self.reset()
        
        # A document is a dictionnary that contains all the slides
        self.data = self._contents
        
        # To store different counters
        self.global_counter = self._global_counter

        self.source_filename = source_filename
        
        # Check if we want to load a new theme
        if 'theme' in kwargs:
            theme = kwargs['theme']

            # Check if it's a python file which is given or a name of themes (stored in beampy/themes)
            themelist = []
            if '.py' in theme:
                themepath = os.path.abspath(os.path.dirname(theme))
                themename = os.path.basename(theme)
                sys.path.append(themepath)
                themename = themename.split('.')[0]
            else:
                available_themes = glob.glob(bppath + 'themes/*_theme.py')

                if theme in '|'.join(available_themes):
                    #print((available_themes, theme))
                    themename = 'beampy.themes.'+theme+'_theme'
                    themelist = [theme+'_theme']

                    #print(themename)
                else:
                    themename = None
            try :
                new_theme = self.dict_deep_update( document._theme, __import__( themename, fromlist=themelist ).THEME )
                self.theme =  new_theme
                self.theme_name = themename
                document._theme = new_theme

            except ImportError:
                self.theme_name = 'default'
                print("No slide theme '" + theme + "', returning to default theme.")

        # Store extra latex packages globally
        document._latex_packages = latex_packages
        
        # Load document options from THEME
        self.set_options(kwargs)

        # Load external tools
        self.link_external_programs()

        # Load the source code of the current presentation
        self.get_source_code(source_filename)


        # Output the storage of slide etc... in debug logger
        _log.debug('Document class before adding slides')
        _log.debug('From classmethod (class not instantiated)')
        _log.debug(document.print_variables())
        
        # Print the header message
        print("="*20 + " BEAMPY START " + "="*20)

    def set_options(self, input_dict):
        # Get document option from THEME
        default_options = self._theme['document']
        if 'theme' in input_dict:
            default_options['theme'] = input_dict['theme']

        good_values = {}
        for key, value in input_dict.items():
            if key in default_options:
                good_values[key] = value
            else:
                print('%s is not a valid argument for document'%key)
                print('valid arguments')
                print(default_options)
                sys.exit(1)

        # Update default if not in input
        for key, value in default_options.items():
            if key not in good_values:
                good_values[key] = value

        # Set size etc...
        document._width = good_values['width']
        document._curwidth = float(document._width)
        document._height = good_values['height']
        document._curheight = float(document._height)
        
        document._guide = good_values['guide']
        document._text_box = good_values['text_box']
        document._cache = good_values['cache']
        document._optimize_svg = good_values['optimize']
        document._resize_raster = good_values['resize_raster']
        document._output_format = good_values['format']
        
        if not document._cache:
            document._cache = None
        else:
            if self.source_filename is not None:
                cache_file = './.beampy_cache_%s' % (self.source_filename)
            else:
                cache_file = './.beampy_cache_%s' % (script_file_name)
                
            print("\nChache file to %s" % (cache_file))
            document._cache = cache_slides(cache_file, self)

        self.options = good_values

    def reset(self):
        document._contents = {}
        document._slides = {}
        document._global_counter = {}
        document._width = 0
        document._height = 0
        document._guide = False
        document._text_box = False
        document._theme = THEME
        document._cache = None
        document._external_cmd = {}
        document._resize_raster = True
        document._output_format = 'html5'
        document._TOC = []

        document._global_store = {}
        document._external_cmd = {}
        document._latex_packages = []

        
    def dict_deep_update(self, original, update):

        """
        Recursively update a dict.
        Subdict's won't be overwritten but also updated.
        from http://stackoverflow.com/questions/38987/how-can-i-merge-two-python-dictionaries-in-a-single-expression/44512#44512
        """

        for key, value in original.items():
            if not key in update:
                update[key] = value
            elif isinstance(value, dict) and isinstance(update[key], dict):
                self.dict_deep_update(value, update[key])
        return update

    def link_external_programs(self):
        # Function to link [if THEME['document']['external'] = 'auto'
        # and check external if programs exist

        #Loop over options
        missing = False
        for progname, cmd in self.options['external_app'].items():
            if cmd == 'auto':

                #Special case of video_encoder (ffmpeg or avconv)
                if progname == 'video_encoder':
                    find_ffmpeg = find_executable('ffmpeg')
                    find_avconv = find_executable('avconv')
                    if find_ffmpeg is not None:
                        document._external_cmd[progname] = find_ffmpeg
                    elif find_avconv is not None:
                        document._external_cmd[progname] = find_avconv
                    else:
                        missing = True
                else:
                    find_app = find_executable(progname)
                    if find_app is not None:
                        document._external_cmd[progname] = find_app
                    else:
                        missing = True

            else:
                document._external_cmd[progname] = cmd

            if missing:
                if progname == 'video':
                    name = 'ffmpeg or avconv'
                else:
                    name = progname

                print('Missing external tool: %s, please install it before running Beampy'%name)
                #sys.exit(1)

        outprint = '\n'.join(['%s:%s'%(k, v) for k, v in document._external_cmd.items()])
        print('Linked external programs\n%s'%outprint)

    def get_source_code(self, sourcefilename=None):
        document._source_code = SourceManager(sourcefilename)


    def __repr__(self):
        output = '''
        Document class infos:
        %s
        '''

        allvars = vars(self)
        private = ''
        other = ''
        
        for k in allvars:
            fmt = '%s: %s\n' % (k, str(allvars[k]))
            if k.startswith('_'):
                private += fmt
            else:
                other += fmt
                
        return output % (private+'\n\n'+other)

    
    @classmethod
    def print_variables(cls):
        """
        Print information on the document class and the content of its
        private data and methods.
        """
        
        # Calling cls.__repr__(cls) works for python 3.x but not for
        # python 2.7
        
        output = '''Document class infos:
        %s
        '''

        allvars = vars(cls)
        private = ''
        other = ''
        
        for k in allvars:
            fmt = '%s: %s\n' % (k, str(allvars[k]))
            if k.startswith('_'):
                private += fmt
            else:
                other += fmt
                
        return output % (private+'\n\n'+other) class rectangle(svg):
    """
    Insert an svg rectangle.

    Parameters
    ----------

    x : int or float or {'center', 'auto'} or str, optional
        Horizontal position for the rectangle (the default is 'center'). See
        positioning system of Beampy.

    y : int or float or {'center', 'auto'} or str, optional
        Vertical position for the rectangle (the default theme sets this to
        'auto'). See positioning system of Beampy.

    height : string, optional
         Height of the rectangle (the default theme sets this to '10px').
         The value is given as string with a unit accepted by svg syntax.

    width : string, optional
         Width of the rectangle (the default theme sets this to
         :py:mod:`document._width`). The value is given as string with a unit
         accepted by svg syntax.

    color : string, optional
        Color filling the rectangle (the default theme sets this to
        THEME['title']['color']). The color is given either as HTML hex value
        "#ffffff" or as svg colornames "blank".

    linewidth : string, optional
        Rectangle edge line width (the default theme sets this to '2px'). The
        value is given as string followed by an unit accepted by svg syntax.

    edgecolor : string, optional
        Color of the rectangle edge (the default theme sets this to
        THEME['text']['color']). The color is given either as HTML hex value
        "#ffffff" or as svg colornames "blank".

    opacity: float, optional
        Opacity of the rectangle (the default theme sets this to 1). The
        value ranges between 0 (transparent) and 1 (solid).

    rx: int, optional
        The number of pixels for the rounding the rectangle corners in
        x direction (The default theme sets this value to 0). 

    ry: int, optional
        The number of pixels for the rounding the rectangle corners in
        y direction (The default theme sets this value to 0). 
    
    svgfilter: string or None, optional
        Set the id of the svg filter ('#name') to apply to the
        rectangle (default value is None, which means no
        filter). Filter definitaion should be added to slide.svgdefout
        list.

    svgclip: string or None, optional
       Set the id of the clip object ('#name') to apply on the
       rectangle (the default value is None, which means no clip to
       apply). Clip definition should be added to slide.svgdefout
       list.

    """

    def __init__(self, **kwargs):

        # The input type of the module
        self.type = 'svg'

        # Add args to the module
        self.check_args_from_theme( kwargs )

        # The size can be computed easily if no filter or clip path is given
        if self.svgclip is not None or self.svgfilter is not None:
            self.inkscape_size = True
        else:
            self.inkscape_size = False

        
        # Build style for the rectangle
        beampy_svg_kword = {'color': 'fill',
                            'linewidth': 'stroke-width',
                            'opacity': 'opacity',
                            'edgecolor': 'stroke'}

        self.style = ''
        for kw in beampy_svg_kword:
            if hasattr(self, kw):
                self.style += '%s:%s;'%(beampy_svg_kword[kw], getattr(self,kw))

        self.dxdy = int(convert_unit(self.linewidth)/2)
        self.content = ''''<rect x="{dx}" y="{dy}" rx="{rx}" ry="{ry}"
        width="{width}" height="{height}" style="{style}" {filter}
        {clip}/>'''

        # Store svg definitions
        self.svgdefs = []
        self.svgdefsargs = []
        
        # Register the module
        self.register()
        
    def pre_render(self):

        if self.svgfilter is None:
            self.svgfilter = ''
        else:
            self.svgfilter = 'filter="url({id})"'.format(id=self.svgfilter)
            
        if self.svgclip is None:
            self.svgclip = ''
        else:
            self.svgclip = 'clip-path="url({id})"'.format(id=self.svgclip)
            
        # Update the width height of the rectangle
        self.content = self.content.format(width=self.width-self.dxdy*2,
                                           height=self.height-self.dxdy*2,
                                           dx=self.dxdy, dy=self.dxdy,
                                           rx=self.rx, ry=self.ry,
                                           style=self.style,
                                           filter=self.svgfilter,
                                           clip=self.svgclip) class circle(svg):
    """
    Insert an svg circle.

    Parameters
    ----------

    x : int or float or {'center', 'auto'} or str, optional
        Horizontal position for the rectangle (the default is 'center'). See
        positioning system of Beampy.

    y : int or float or {'center', 'auto'} or str, optional
        Vertical position for the rectangle (the default theme sets this to
        'auto'). See positioning system of Beampy.

    r : int or float or string, optional
         radius of the circle (the default theme sets this to 3 for '3px').
         When the value is given as string it accepts a unit allow by svg syntax.
         ("em" | "ex" | "px" | "in" | "cm" | "mm" | "pt" | "pc" | "%")
    
    color : string, optional
        Color filling the circle (the default theme sets this to
        THEME['title']['color']). The color is given either as HTML hex value
        "#ffffff" or as svg colornames "blank".

    linewidth : string, optional
        Circle edge line width (the default theme sets this to '1px'). The
        value is given as string followed by an unit accepted by svg syntax.

    edgecolor : string, optional
        Color of the circle edge (the default theme sets this to
        THEME['title']['color']). The color is given either as HTML hex value
        "#ffffff" or as svg colornames "blank".

    opacity: float, optional
        Opacity of the circle (the default theme sets this to 1). The
        value ranges between 0 (transparent) and 1 (solid).

    """

    def __init__(self, **kwargs):

        # The input type of the module
        self.type = 'svg'

        self.inkscape_size = False
        
        # Add args to the module
        self.check_args_from_theme( kwargs )
        
        # Build style for the rectangle
        beampy_svg_kword = {'color': 'fill',
                            'linewidth': 'stroke-width',
                            'opacity': 'opacity',
                            'edgecolor': 'stroke'}

        self.style = ''
        for kw in beampy_svg_kword:
            if hasattr(self, kw):
                self.style += '%s:%s;'%(beampy_svg_kword[kw], getattr(self,kw))

        self.cx = convert_unit(self.r) + int(convert_unit(self.linewidth)/2)
        self.cy = convert_unit(self.r) + int(convert_unit(self.linewidth)/2)

        self.content = '<circle cx="{cx}" cy="{cy}" r="{r}" style="{style}" />'

        # Store svg definitions
        self.svgdefs = []
        self.svgdefsargs = []
        
        # Register the module
        self.register()

    def pre_render(self):
        self.content = self.content.format(r=self.r, cx=self.cx, cy=self.cy, style=self.style)

        self.width = convert_unit(self.r) * 2 + convert_unit(self.linewidth)
        self.height = convert_unit(self.r) * 2 + convert_unit(self.linewidth)

        self.update_size(self.width, self.height) class text(beampy_module):
    r"""
    Add text to the current slide. Input text is by default processed using
    Latex and could use Latex syntax.

    Parameters
    ----------

    textin : str, optional
        Text to add. Could contain latex syntax with protected slash either by
        using double slash or by using the python **r** before string.

        >>> text(r'\sqrt{x}')

    x : int or float or {'center', 'auto'} or str, optional
        Horizontal position for the text container (the default is 'center').
        See positioning system of Beampy.

    y : int or float or {'center', 'auto'} or str, optional
        Vertical position for the text container (the default is 'auto'). See
        positioning system of Beampy.

    width : int or float or None, optional
       Width of the text container (the default is None, which implies that the
       width is the parent group with).

    size : int, optional
        The font size (the default theme sets this value to 20).

    font : str, optional
        The Tex font (the default theme sets this value to
        'CMR'). **THIS IS NOT YET IMPLEMENTED!**

    color : str, optional
        The text color (the default theme set this value to '#000000'). Color
        could be html hex values or SVG-COLOR-NAMES.

    usetex : bool, optional
        Use latex to render text (the default value is true). Latex render
        could be turned off using `usetex`=False, then the text is rendered as
        svg.

    va : {'','baseline'}, optional
       Vertical text alignment (the default value is '', which implies that the
       alignment reference is the top-left corner of text). When
       `va`='baseline', the base-line of the first text row is computed and
       used as alignment reference (baseline-left).

    extra_packages : list of string, optional
        Add latex packages to render the text, like
        [r'\usepackage{name1}', r'\usepackage{name2}']


    Example
    -------

    >>> text('this is my text', x='20', y='20')

    """

    def __init__(self, textin=None, **kwargs):

        self.type = 'text'
        self.check_args_from_theme(kwargs)
        self.content = textin

        self.svgtext = ''  # To store the svg produced by latex

        # Height of text is None (it need to be computed)
        self.height = None
        # Check width
        if self.width is None:
            self.width = document._slides[gcs()].curwidth

        # Add special args for cache id
        # Text need to be re-rendered from latex if with, color or size are changed
        self.initial_width = self.width
        self.args_for_cache_id = ['initial_width', 'color', 'size', 'align', 'opacity']

        # Initialise the global store on document._content to store letter
        if 'svg_glyphs' not in document._contents:
            document._contents['svg_glyphs'] = {}

        if self.extra_packages != []:
            auto_render = True
        else:
            auto_render = False

        # Register the function to the current slide
        self.register(auto_render=auto_render)

    def process_with(self):
        """
        Process the text inside the with
        """
        source = document._source_code.source(start=self.start_line,
                                              stop=self.stop_line)
        input_texts = small_comment_parser(source)

        self.content = r'\\'.join([r"%s" % t for t in input_texts])

    def pre_render(self):
        """
        Prepare the latex render of the text
        """

        if self.usetex:
            #Check if a color is defined in args
            if hasattr(self, 'color'):
                textin = color_text( self.content, self.color )
            else:
                textin = self.content

            if 'center' in self.align:
                texalign = r'\centering'
            elif 'right' in self.align:
                texalign = r'\flushright'
            else:
                texalign = ''

            template = r"""\begin{varwidth}{%ipt}
            %s
            \fontsize{%i}{%i}\selectfont %s

            \end{varwidth}"""

            # Matplotlib uses fontsize * 1.25 for spacing
            self.latex_text = template % (self.width.value*(72.27/96.),
                                          texalign, self.size,
                                          (self.size+self.size*0.1),
                                          textin)
            # fontsize{size}{interlinear_size}
            #96/72.27 pt_to_px for latex

        else:
            self.latex_text = ''

    def local_render(self):
        """Function to render only on text of this module.

        It's slower than writing all texts to one latex file and then
        render it to dvi then svg.
        """

        if self.latex_text != '':
            pretex = r"""
            \documentclass[crop=true]{standalone}
            \usepackage[utf8x]{inputenc}
            \usepackage{fix-cm}
            \usepackage[hypertex]{hyperref}
            \usepackage[svgnames]{xcolor}
            \renewcommand{\familydefault}{\sfdefault}
            \usepackage{varwidth}
            \usepackage{amsmath}
            \usepackage{amsfonts}
            \usepackage{amssymb}
            """

            pretex += '\n'.join(self.extra_packages + document._latex_packages)
            pretex += r'\begin{document}'
            pretex += self.latex_text
            pretex += r'\end{document}'

            #latex2svg
            self.svgtext = latex2svg( pretex )

        else:
            self.svgtext = ''

    #Define the render
    def render(self):
        """
            Text is rendered using latex if self.usetex = True if not use simple svg
        """

        if self.usetex:
            #print(self.svgtext)
            if self.svgtext == '':
                # Run the local render
                self.local_render()

                # If it's still empty their is an error
                if self.svgtext == '':
                    print("Latex Compilation Error")
                    print("Beampy Input:")
                    print(self.content)
                    sys.exit(0)

            #Parse the ouput with beautifullsoup
            soup = BeautifulSoup(self.svgtext, 'xml')
            svgsoup = soup.find('svg')
            #print(soup)

            #Find the width and height
            xinit, yinit, text_width, text_height = svgsoup.get('viewBox').split()
            text_width = float(text_width)
            text_height = float(text_height)


            #Get id of paths element to make a global counter over the entire document
            if 'path' not in document._global_counter:
                document._global_counter['path'] = 0

            #New method with a global glyph store
            svgsoup = self.parse_dvisvgm_svg( svgsoup )

            #Change id in svg defs to use the global id system
            #soup = make_global_svg_defs(soup)

            #svgsoup = soup.find('svg')

            #Find all links to apply the style defined in theme['link']
            links = svgsoup.find_all('a')
            style = ' '.join(['%s:%s;'%(str(key), str(value)) for key, value in list(document._theme['link'].items())])
            for link in links:
                link['style'] = style

            #Use the first <use> in svg to get the y of the first letter
            try:
                uses = svgsoup.find_all('use')
            except:
                print(soup)

            if len(uses) > 0:
                #TODO: need to make a more fine definition of baseline
                baseline = 0
                for use in uses:
                    if use.has_attr('y'):
                        baseline = float(use.get('y'))
                        break

                if baseline == 0:
                    print("No Baseline found in TeX and is put to 0")
                    #print baseline

                #Get the group tag to get the transform matrix to add yoffset
                g = svgsoup.find('g')
                transform_matrix = g.get('transform')


                if getattr(self, 'va', False) and self.va == 'baseline':
                    yoffset = - float(baseline)
                    xoffset = - float(xinit)
                    #for the box plot (see boxed below)
                    oldyinit = yinit
                    yinit = - float(baseline) + float(yinit)
                    baseline = -float(oldyinit) + float(baseline)

                else:
                    yoffset = -float(yinit)
                    xoffset = -float(xinit)
                    #For the box plot
                    baseline = -float(yinit) + float(baseline)
                    yinit = 0



                #print baseline, float(yinit), yoffset
                #newmatrix = 'translate(%s,%0.4f)'%(-float(xoffset),-float(yoffset) )
                tex_pt_to_px = 96/72.27
                newmatrix = 'scale(%0.3f) translate(%0.1f,%0.1f)'%(tex_pt_to_px, xoffset, yoffset)
                g['transform'] = newmatrix
                text_width = text_width * tex_pt_to_px
                text_height = text_height * tex_pt_to_px
                baseline = baseline * tex_pt_to_px
                yinit = yinit * tex_pt_to_px
                g['opacity'] = self.opacity
                #g['viewBox'] = svgsoup.get('viewBox')

            output = svgsoup.renderContents().decode('utf8', errors='replace')

            #Add red box around the text
            if document._text_box:
                boxed = '''<g transform="translate(%0.1f,%0.1f)">
                <line x1="0" y1="0" x2="%i" y2="0" style="stroke: red"/>
                <line x1="%i" y1="0" x2="%i" y2="%i" style="stroke: red"/>
                <line x1="%i" y1="%i" x2="0" y2="%i" style="stroke: red"/>
                <line x1="0" y1="%i" x2="0" y2="0" style="stroke: red"/>
                <line x1="0" y1="%i" x2="%i" y2="%i" style="stroke: green"/>
                </g>'''
                output += boxed%( 0, float(yinit),
                                 text_width,
                                 text_width,text_width,text_height,
                                 text_width,text_height,text_height,
                                 text_height,
                                 baseline,text_width,baseline)

            #print output
        else:
            #Render as svg text
            #print('[WARNING !!!]: Classic text not yet implemented')
            textin = self.content
            style = ''
            if hasattr(self, 'color'):
                style += 'color:%s'%(self.color)

            output = '<text style="%s">%s</text>'%(style, textin.decode('utf-8'))
            tmpsvg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" xmlns:xlink="http://www.w3.org/1999/xlink">%s</svg>'%output

            #Need to create a temp file
            with tempfile.NamedTemporaryFile(mode='w', suffix='.svt', prefix='beampytmp') as f:

                f.write(tmpsvg)
                # update file content to disk

                f.file.flush()
                # Get width and height
                text_width =  getsvgwidth(f.name)
                text_height = getsvgheight(f.name)

            # print(text_width, text_height)

        #Update positionner with the correct width and height of the final svg
        self.update_size(text_width, text_height)
        #Store the output svg
        logging.debug(type(output))
        self.svgout = output
        #Update the rendered state of the module
        self.rendered = True



    def parse_dvisvgm_svg(self, soup_data):
        """
        Function to transform the svg produced by dvisvgm.
        Make a global glyph store to use them as defs in svg
        to reduce the size off the global presentation.

        soup_data: BeautifulSoup parsed svg

        return: soup_data (without the defs part)
        """

        #Check if their is an entry in the global_store for the glyphs
        if 'glyphs' not in document._global_store:
            document._global_store['glyphs'] = {}

        #Extract defs containing glyphs from the svg file
        try:
            defs = soup_data.find_all('defs')[0].extract()
            for path in defs.find_all('path'):
                #store the id of the glyph given by dvisvgm

                path_id = path['id']
                #store the bezier coordinates of the glyph
                path_d = path['d']
                try:
                    hash_id = hashlib.md5(path_d).hexdigest()
                except:
                    hash_id = hashlib.md5(path_d.encode('utf8')).hexdigest()


                #print(hash_id, path_id)

                #check if the glyph is in the store or add it
                if hash_id not in document._global_store['glyphs']:
                    #Add the glyph to the store and create a new uniq id for it
                    uniq_id = "g_"+str(len(document._global_store['glyphs']))
                    new_svg = "<path d='%s' id='%s'/>"%(path_d, uniq_id)
                    document._global_store['glyphs'][ hash_id ] = {"old_id": path_id, "d": path_d, "id": uniq_id, 'svg':new_svg}


                else:
                    data_store = document._global_store['glyphs'][ hash_id ]
                    uniq_id = data_store['id']

                #Find all the xlink:href to this glyph in the use part of the svg
                for tag in soup_data.find_all('use', { 'xlink:href':'#%s'%(path_id) }):
                    #Change the dvisvgm ref to the new uniq_id ref of the glyph
                    tag['xlink:href'] = '#%s'%(uniq_id)


                #Theirs is also definition use in the defs
                for use in defs.find_all('use', {'xlink:href':'#%s'%(path_id)}):
                    # print(use)
                    #store the id of the glyph given by dvisvgm
                    u_id = use['id']
                    use_id = "g_"+str( len(document._global_store['glyphs']) )
                    use['id'] = use_id
                    use['xlink:href'] = '#%s'%(uniq_id)
                    document._global_store['glyphs'][ use_id ] = {"old_id": u_id,  "id": use_id, 'svg':str(use)}
                    # print(use, u_id, use_id)

                    #Find all the xlink:href to this glyph in the use part of the svg
                    for tag in soup_data.find_all('use', { 'xlink:href':'#%s'%(u_id) }):
                        #Change the dvisvgm ref to the new uniq_id ref of the glyph
                        tag['xlink:href'] = '#%s'%(use_id)

        except:
            defs = None
            pass


        #Need to check if we have undefined reference
        #(they could be defined in other page of the)
        # We need to exclude <a> tag as html links could include "-" in the xlink:href
        for tag in soup_data.findAll(lambda x: x.name != 'a' and x is not None and x.has_attr('xlink:href')):
            if '-' in tag['xlink:href']:
                print('A svg reference is not defined:')
                print(tag)
                print('This is a bug in multipage of your dvisvgm version (bug fix in dvisvgm version > 2.7.2)')
                print('Run a single render for this text and parse it again! this should fix missings')
                self.local_render()
                self.render()

        return soup_data class group(beampy_module):
    """Group Beampy modules together and manipulate them as a group

    Parameters
    ----------

    elements_to_group : None or list of beampy.base_module, optional
        List of Beampy module to put inside the group (the default is None).
        This argument allows to group Beampy modules, when `group` is not used
        with the python :py:mod:`with` expression.

    x : int or float or {'center', 'auto'} or str, optional
        Horizontal position for the group (the default is 'center'). See
        positioning system of Beampy.

    y : int or float or {'center', 'auto'} or str, optional
        Vertical position for the group (the default is 'auto'). See
        positioning system of Beampy.

    width : int or float or None, optional
       Width of the group (the default is None, which implies that the width
       is computed to fit the group contents width).

    height : int or float or None, optional
       Height of the group (the default is None). When height is None the
       height is computed to fit the group contents height.

    background : str or None, optional
       Svg color name of the background color for the group (the default is None).

    perentid : str or None, optional
        Beampy id of the parent group (the default is None). This parentid is
        given automatically by Beampy render.


    .. note::

       When the position of a group (`x`, `y`) are relative to a parent group
       and that the parent group has `width`=None or `height`=None and
       positions `x` or `y` equal to 'auto' or 'center', the render will use
       the `slide.curwidth` as `width` and the `document._height` as height.
       This will produce unexpected positioning of child group.


    """

    def __init__(self, elements_to_group=None, x='center', y='auto',
                 width=None, height=None, background=None, parentid=None,
                 parent_slide_id=None, opengroup=True):

        # Store the input of the module
        self.width = width
        self.height = height
        self.x = x
        self.y = y
        self.background = background
        self.type = 'group'
        self.content = []
        self.xoffset = 0
        self.yoffset = 0
        self.parentid = parentid # Store the id of the parent group
        self.grouplevel = 0
        
        # To store elements id to group
        self.elementsid = []
        # To store output ids for render (below/above)
        self.exports_id = []
        
        self.element_keys = []
        self.autoxid = []
        self.autoyid = []
        self.manualid = []
        self.htmlid = [] # Store html module id

        self.content_layer = {} # Store group rendered svg content by layers

        # Get the id of the slide 
        if parent_slide_id is None:
            slide = document._slides[gcs()]
        else:
            self.parent_slide_id = parent_slide_id
            slide = document._slides[self.parent_slide_id]

        if elements_to_group is not None:
            opengroup = False
            
        # Add the parentid if level is more than 1            
        if slide.cur_group_level >= 0:
            self.parentid = slide.contents[slide.groupsid[slide.cur_group_level][-1]].id

        #slide.cur_group_level += 1
        self.grouplevel = slide.cur_group_level + 1
        if opengroup:
            slide.cur_group_level = self.grouplevel
            
        # Add classic register to the slide
        self.register(auto_render=False)
        self.group_id = self.id

        self.init_width = self.positionner.width.value
        self.init_height = self.positionner.height.value
        
        if elements_to_group is not None:
            # print('TODO: Need to remove these elements from previous group!')
            for e in elements_to_group:
                #slide.remove_module(e.id)
                self.add_elements_to_group(e.id, e)
                
                if e.group_id is not None:
                    slide.contents[e.group_id].remove_element_in_group(e.id)

                e.group_id = self.id

    def reset_outputs(self):
        """
        Rewrite the reset_outputs function of beampy_module to add content_layer.
        """
        self.svgout = None  # The output of the render
        self.htmlout = None  # We can store also html peace of code
        self.jsout = None  # Store javascript if needed
        self.animout = None  # Store multiple rasters for animation
        self.rendered = False
        self.exported = False
        
        self.content_layer = {} #Remove group content_layer outputs 
        
    def __enter__(self):
        # Is the slide group level correctly set?
        if document._slides[self.slide_id].cur_group_level != self.grouplevel:
            #print('Change cur_group_level')
            document._slides[self.slide_id].cur_group_level = self.grouplevel

        logging.debug('Enter a new group %s with level: %i' % (self.id,
                                                               self.grouplevel))

        # Set the id to of the current group to this group id
        document._slides[self.slide_id].cur_group_id = self.id
        # Check if we need to update the curwidth of slide
        if self.width.value is not None:
            document._slides[self.slide_id].curwidth = self.width.value
        else:
            self.width.value = document._slides[self.slide_id].curwidth

        # For the height check if a height is given in the group
        if self.height.value is not None:
            document._slides[self.slide_id].curheight = self.height.value
            
        return self

    def __exit__(self, type, value, traceback):
        logging.debug('Exit group %s' % self.id)
        if self.grouplevel >= 1:
            document._slides[self.slide_id].cur_group_level = self.grouplevel - 1

        # Check if we need to update the curwidth of slide to the parent group
        if self.parentid is not None and document._slides[self.slide_id].contents[self.parentid].width.value is not None:
            document._slides[self.slide_id].curwidth = document._slides[self.slide_id].contents[self.parentid].width.value

        # Check if we need to update the curheight of slide to the parent group
        if self.parentid is not None and document._slides[self.slide_id].contents[self.parentid].height.value is not None:
            document._slides[self.slide_id].curheight = document._slides[self.slide_id].contents[self.parentid].height.value
            
        # Get the id of the parentgroup as the cur_group_id
        document._slides[self.slide_id].cur_group_id = self.parentid
        
        logging.debug('Set current group level to %i'%document._slides[self.slide_id].cur_group_level)
        
        # Compute group size
        # self.compute_group_size()
        
    def add_layers(self, layerslist):
        self.layers = layerslist

    def propagate_layers(self):
        """
        Function to recusivly propagate the layers to group elements
        :return:
        """
        slide = document._slides[self.slide_id]

        for eid in self.elementsid:
            # logging.debug('Element to propagate layer %s ' % str(slide.contents[eid].name))
            for layer in self.layers:
                if layer not in slide.contents[eid].layers and layer > min(slide.contents[eid].layers):
                    logging.debug('add layer %i to %s' % (layer, slide.contents[eid].name))
                    slide.contents[eid].layers += [layer]

            # Clean layer of elements lower than the minimum of group layer
            for elayer in slide.contents[eid].layers:
                if elayer < min(self.layers):
                    slide.contents[eid].layers.pop(slide.contents[eid].layers.index(elayer))

            # If the elements it's an group with should call the same method to do the recursion
            if slide.contents[eid].type == 'group':
                slide.contents[eid].propagate_layers()

    def add_elements_to_group(self, eid, element):
        #Function to register elements inside the group
        is_auto = False
        self.elementsid += [eid]
        self.exports_id += [eid]

        if element.x == 'auto':
            self.autoxid += [eid]
            is_auto = True

        if element.y == 'auto':
            self.autoyid += [eid]
            is_auto = True

        if not is_auto:
            self.manualid += [eid]

        if element.type == 'html':
            self.htmlid += [eid]
            
    def remove_element_in_group(self, elementid):
        """
        Function to remove element from the group key stores

        :param elementid: The id of the module to remove
        """

        for store in (self.autoxid, self.autoyid, self.manualid,self.htmlid, self.elementsid, self.exports_id):
            if elementid in store:
                store.pop(store.index(elementid))

    def add_svg_content(self, layer, svg):
        # create or append the content_layer store with the given svg

        if layer in self.content_layer:
            self.content_layer[layer] += [svg]
        else:
            self.content_layer[layer] = [svg]

        # Add layer level to group if it's not already created
        if layer not in self.layers:
            self.layers += [layer]

    def compute_group_size(self):
        """
        Function to compute the size of a given group 

        Update the size with self.update_size(width, height) at the end
        """
        
        # Check group given size
        if self.init_width is None:
            auto_width = document._width
        else:
            auto_width = self.width.value
            
        if self.init_height is None:
            auto_height = document._height
        else:
            auto_height = self.height.value

        # First look at auto positionning for elements in groups
        # Check if their is autox or autoy placement
        if len(self.autoxid) > 0:
            distribute(self.autoxid, 'hspace', auto_width,
                       offset=self.xoffset,
                       curslide=document._slides[self.slide_id])

        if len(self.autoyid) > 0:
            distribute(self.autoyid, 'vspace', auto_height,
                       offset=self.yoffset,
                       curslide=document._slides[self.slide_id])

        # Place the module with these distributed postions
        for elem in self.autoxid + self.autoyid + self.manualid:
            # Check if the element have already be placed (html could be part of serveral groups level)
            if 'final' not in document._slides[self.slide_id].contents[elem].positionner.x:
                document._slides[self.slide_id].contents[elem].positionner.place((auto_width, auto_height),
                                                                                 self.yoffset)

        # Compute the real group size if no size (width or height) are given in group args
        if self.init_width is None :

            allxw = []

            for eid in self.elementsid:
                ewidth = document._slides[self.slide_id].contents[eid].positionner.width
                """ 
                # Should not happen 
                if ewidth.value is None:
                    print('Run render from compute_group_size to get width')
                    ewidth.run_render()
                """
                
                ewidth = ewidth.value
                if ewidth is None:
                    print('Width not known for:')
                    print(document._slides[self.slide_id].contents[eid])
                    sys.exit(0)
                    
                allxw += [(document._slides[self.slide_id].contents[eid].positionner.x['final'],
                           ewidth) ]

            minx = min([i[0] for i in allxw])
            maxx = max([i[0]+i[1] for i in allxw])

            width = maxx - minx
            # print('group width %f'%width)
            self.width.value = width

            # Need to reloop over groups elements to increment dx
            # as they have been positionned with slide width
            for eid in self.elementsid:
                document._slides[self.slide_id].contents[eid].positionner.x['final'] -= minx

        if self.init_height is None:

            allyh = []
            
            for eid in self.elementsid:
                
                eheight = document._slides[self.slide_id].contents[eid].height
                """
                # Should not happen
                if eheight.value is None:
                    print('Run render from compute_group_size to get height')
                    eheight.run_render()
                """
                eheight = eheight.value
                
                if eheight is None:
                    print('Height not known for:')
                    print(document._slides[self.slide_id].contents[eid])
                    sys.exit(0)
                
                allyh += [(document._slides[self.slide_id].contents[eid].positionner.y['final'],
                           eheight) ]
                    
            miny = min([i[0] for i in allyh])
            maxy = max([i[0]+i[1] for i in allyh])
            height = maxy - miny
            # print('group height %f'%height)
            self.height.value = height

            # Need to reloop over groups elements to increment dy
            # as they have been positionned with slide height
            for eid in self.elementsid:
                document._slides[self.slide_id].contents[eid].positionner.y['final'] -= miny

        self.update_size(self.width, self.height)
                
    def render(self):
        """
            group render
        """

        slide = document._slides[self.slide_id]
        self.content = []

        # Loop over these elements id to export their svg code
        for eid in self.exports_id:

            elem = slide.contents[eid]

            if elem.type == 'group':
                for layer in elem.layers:
                    if layer in elem.content_layer:
                        self.add_svg_content(layer, elem.export_svg_layer(layer))

            else:
                if elem.svgout is not None:
                    if not elem.exported:
                        if elem.type == 'html':
                            if document._output_format != 'html5':
                                slide.add_rendered(svgdefs=elem.export_svg_def())
                                elem.exported = True
                        else:
                            slide.add_rendered(svgdefs=elem.export_svg_def())
                            elem.exported = True

                    for layer in elem.layers:
                        # Check if it's an html element, we need to only output svg for svg format not html
                        if elem.type == 'html':
                            if document._output_format != 'html5':
                                self.add_svg_content(layer, '<use xlink:href="#{id}"></use>'.format(id=elem.id))
                        else:
                            self.add_svg_content(layer, '<use xlink:href="#{id}"></use>'.format(id=elem.id))

            if elem.jsout is not None:
                slide.add_rendered(js=elem.jsout)

            if elem.animout is not None and document._output_format == 'html5':
                if not elem.exported:
                    tmpanim = elem.export_animation()
                    slide.add_rendered(animate_svg=tmpanim)
                    elem.exported = True

                for layer in elem.layers:
                    self.add_svg_content(layer, elem.export_animation_layer(layer))


            # For html objects, they need absolute positionning because they are not included in svg group
            if elem.type == 'html' and elem.htmlout is not None and self.grouplevel > 0:
                # Store the group_id that contains this html
                elem.groups_id += [self.id]
                # remove auto/center args from x and y
                elem.x = '%ipx'%elem.positionner.x['final']
                elem.y = '%ipx'%elem.positionner.y['final']
                # Add the element to the parentgroup
                slide.contents[self.parentid].add_elements_to_group(elem.id, elem)
        
        self.rendered = True

    def export_svg_content_layer(self, layer):
        """
        Function to export group content for a given layer to svg
        :param layer:
        :return:
        """

        if self.background is not None:
            pre_rect = '<rect width="%s" height="%s" style="fill:%s;" />'%(self.width.value,
             self.height.value, self.background)
        else:
            pre_rect = ''

        output = pre_rect + ''.join(self.content_layer[layer])

        return output

    def export_svg_layer(self, layer):

        out = '<g transform="translate(%s,%s)" class="%s" data-layer="%i">' % (self.positionner.x['final'],
                                                                               self.positionner.y['final'],
                                                                               self.name, layer)

        out += self.export_svg_content_layer(layer)

        if document._text_box:
            out += """<rect x="0"  y="0" width="%s" height="%s"
              style="stroke:#009900;stroke-width: 1;stroke-dasharray: 10 5;
              fill: none;" />""" % (self.positionner.width.value,
                                    self.positionner.height.value)

        if self.svg_decoration != '':
            out += self.svg_decoration.format(width=self.positionner.width.value,
                                              height=self.positionner.height.value)

        out += '</g>'

        return out def get_visibles_indices(slide_id, currentsection=False):

    currenttoc = document._slides[slide_id].TOCposition
    tocposition = document._TOC.index(currenttoc)
    currentlevel = currenttoc['level']

    visiblespos = [tocposition]

    while currentlevel != 0 and tocposition > 0:
        tocposition -= 1
        currentlevel = document._TOC[tocposition]['level']
        visiblespos += [tocposition]

    # Case where all subsection of a section need to be shown
    if currentsection:
        max_visiblepos = max(visiblespos)
        maxlevel = document._TOC[max_visiblepos]['level']

        while maxlevel != 0 and max_visiblepos < len(document._TOC)-1:
            max_visiblepos += 1
            maxlevel = document._TOC[max_visiblepos]['level']
            if maxlevel != 0:
                visiblespos += [max_visiblepos]

    return visiblespos def convert_unit(value, ppi=72):
    """
    Function to convert size given in some unit to pixels, following the
    https://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#length-units

    Parameters:
    -----------

    value, str or int:
        The given size followed by it's unit. Fixed units are (in, cm,
        mm, pt, pc). Relative units are (em, ex, %)

    ppi, int, optional:
        The number of pixel per inch (Latex use 72)
    """

    value = str(value)

    # px to px
    if 'px' in value:
        value = '%0.1f' % (float(value.replace('px', '')))

    # mm to cm
    if 'mm' in value:
        value = "%fcm" % (float(value.replace('mm',''))*10**-1)

    # cm to inch
    if "cm" in value:
        value = "%fin" % (float(value.replace('cm', ''))*(1/2.54))

    # pc to inch
    if 'pc' in value:
        value = '%fin' % (float(value.replace('pc','')*12))

    # pt to inch
    if "pt" in value:
        value = "%fin" % (float(value.replace('pt', ''))*(1/72.0))

    # inch to px
    if "in" in value:
        # 1 inch = 72px
        out = float(value.replace('in', ''))*ppi
    else:
        out = float(value)

    return out def distribute(element_list, mode, available_size, offset=0, curslide=None):
    '''
        Distribute the given elements list along the slide using the given
        mode

        element_list:
            A list of beampy module (if curslide is none)
            A list of beampy module keys, in this case curslide must be the
            curent slide where module are stored


        mode:
            'hspace': horizontal spacing from the center of each elements
            'vspace': vertical spacing from the center of each elements

        offset:
            gives the first offset of the slide (due to the title for instance)

        curslide [None]:
            If curslide is not None the element_list must refers to elements keys
            surch that

            curslide.content[elemnt_list[0]] is a beampy module

    '''

    if mode == 'vspace':

        if curslide is None:
            all_height = [elem.positionner.height for elem in element_list]
        else:
            all_height = [curslide.contents[elemk].positionner.height for elemk in element_list]

        # print(all_height)
        sumheight = sum(all_height)
        sumheight = sumheight.value

        if sumheight > available_size:
            print('Warning alignement elements overflow given height %s'%(available_size))

        available_space = (available_size - offset) - sumheight
        dy = available_space/float( len(all_height) + 1 )

        curpos = dy + offset

        for elemt in element_list:

            if curslide is None:
                elem = elemt
            else:
                elem = curslide.contents[elemt]

            H = elem.positionner.height.value

            elem.positionner.y['shift'] = curpos
            elem.positionner.y['unit'] = 'px'
            elem.positionner.y['align'] = 'top'

            curpos += dy + H


    elif mode == 'hspace':

        if curslide is None:
            all_width = [elem.positionner.width for elem in element_list]
        else:
            all_width = [curslide.contents[elemk].positionner.width for elemk in element_list]

        sumwidth = sum(all_width)
        sumwidth = sumwidth.value

        if sumwidth > available_size:
            print('Warning alignement elements overflow given width %s'%(available_size))

        available_space = (available_size - offset) - sumwidth
        dx = available_space/float(len(all_width)+1)

        curpos = dx + offset
        for elemt in element_list:

            if curslide is None:
                elem = elemt
            else:
                elem = curslide.contents[elemt]

            W = elem.positionner.width.value

            elem.positionner.x['shift'] = curpos
            elem.positionner.x['unit'] = 'px'
            elem.positionner.x['align'] = 'left'

            curpos += dx + W def draw_axes(dx=100, dy=100, show_ticks=False, grid_color='lightgray'):
def bounding_box(element): def color_text( textin, color ):

    '''
    Adds Latex color to a string.
    '''

    if "#" in color:
        textin = r'{\color[HTML]{%s} %s }' % (color.replace('#', '').upper(),
                                              textin)
    else:
        textin = r'{\color{%s} %s }' % (color, textin)

    return textin
from beampy.document import document
from beampy.modules.svg import rectangle, circle
from beampy.modules.text import text
from beampy.modules.core import group
from beampy.modules.toc import get_visibles_indices
from beampy.functions import convert_unit
from beampy.geometry import distribute
from beampy.utils import box
from beampy.functions import color_text
import datetime
import sys


######################### Define the headerbar #####################################

def create_header_bar():

    if len(document._TOC) == 0:
        bluerect = rectangle(x=0, y=0, height=28*1.6,
                             color=lead_color, edgecolor=lead_color)
        bluerect.first()
        # BUG Change the position of the title as we don't have the header bar
        print('No table of content, their is a bug to set the position of the title dynamically in BeamerFrankfurt Theme')
        print('''Please add in your file after doc = document
        doc._theme['title']['y'] -= 35
        doc._theme['title']['reserved_y'] = '1.8cm'
        ''')
        
    else:
        rtop = rectangle(x=0, y=0, height=35, color='black', edgecolor='black')
        bluerect = rectangle(x=0, y=rtop.bottom+0, height=28*1.6,
                             color=lead_color, edgecolor=lead_color)
        bluerect.first()
        
        cpt_title = 0
        cur_toc = document._slides[document._curentslide].TOCposition
        visibles_toc_pos = get_visibles_indices(document._curentslide,
                                                currentsection=True)
        last_pos = document._TOC.index(cur_toc)
        last_level1_pos = 0
        
        for i, toc in enumerate(document._TOC):
            if toc['level'] == 0:
                cpt_title += 1
            if toc['level'] == 1:
                if i in visibles_toc_pos and i <= last_pos:
                    last_level1_pos = i
        
        group_width = None
        groups = [] # store group with toc title and subtitle circle
        elems = []
        for i, toc in enumerate(document._TOC):
            if i in visibles_toc_pos and i <= last_pos:
                selected_opacity = 1
            else:
                selected_opacity = 0.5
        
            if i == last_level1_pos:
                circle_color = 'white'
            else:
                circle_color = 'black'
        
            if toc['slide'] <= document._global_counter['slide']:
                slidelink = '#%i-0' % (toc['slide'])
            else:
                slidelink = '#%i-0' % (document._global_counter['slide'])
        
            if toc['level'] == 0:
                if len(elems) > 0:
                    g = group(elems, x=0, y=5, width=group_width)
                    groups += [g]
                    elems = []
                
                t1 = text(r'\href{%s}{%s}' % (slidelink, toc['title']), x=0,
                          y=0, size=10, color='white',
                          opacity=selected_opacity, width=group_width)
                elems += [t1]
                t2 = None
                
            if toc['level'] == 1:
                if t2 is None:
                    xt = 2
                else:
                    xt = t2.right+2
                    
                t2 = circle(x=xt, y=t1.top+15, color=circle_color,
                            edgecolor='white', opacity=selected_opacity)
                elems += [t2]
                
        if len(elems) > 0:
            g = group(elems, x=0, y=0, width=group_width)
            groups += [ g ]
        
        # Compute the horizontal position for groups
        # The first element at the same x than the title
        groups[0].positionner.update_x(THEME['title']['x'])
        # groups[0].positionner.update_y((rtop.height-groups[0].height)/2)
        # The last element with the same margin as the first element 
        groups[-1].positionner.update_x(document._width-(groups[-1].width+THEME['title']['x']).value)
        groups[-1].positionner.update_y(groups[0].top+0)
        #Compute the available width
        available_width = document._width - (groups[-1].width+THEME['title']['x']).value
        
        for e in groups[1:-1]:
            e.positionner.update_y(groups[0].top+0)
            if e.width.value is None or e.height.value is None:
                e.width.run_render()
                
        if len(groups) > 2:
            distribute(groups[1:-1], 'hspace', available_width,
                       offset=(groups[0].width+THEME['title']['x']).value)
            
# Register the layour function
THEME['slide']['layout'] = create_header_bar


######## FOR THE TITLE PAGE #############

def theme_maketitle(titlein, author = [], affiliation = None, meeting = None, lead_author = None, date=None ):
    """
        Function to create the presentation title slide
    """

    # get_command_line(maketitle)
    # Check function arguments from THEME
    args = THEME['maketitle']

    if lead_author is not None and len(author) > lead_author:
        print('Color author: %s in %s' % (author[lead_author],
                                          args['lead_author_color']))
        

        author[lead_author] = color_text(author[lead_author],