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) def gcs():
    """
        Fonction get current slide of the doc
    """

    return document._curentslide def create_element_id(bpmod, use_args=True, use_name=True,
                      use_content=True, add_slide=True, slide_position=True):
    """
        create a unique id for the beampy_module using bpmod.content
        and bpmod.args.keys() and bpmod.name
    """
    ct_to_hash = ''

    if add_slide:
        ct_to_hash += bpmod.slide_id

    if use_args and bpmod.args is not None:
        ct_to_hash += ''.join(['%s:%s' % (k, v) for k, v in bpmod.args.items()])

    if use_name and bpmod.name is not None:
        ct_to_hash += bpmod.name

    if use_content and bpmod.content is not None:
        ct_to_hash += str(bpmod.content)

    if slide_position:
        ct_to_hash += str(len(document._slides[bpmod.slide_id].element_keys))

    outid = None
    if ct_to_hash != '':
        # print(ct_to_hash)
        try:
            outid = hashlib.md5( ct_to_hash ).hexdigest()
        except:
            outid = hashlib.md5( ct_to_hash.encode('utf8') ).hexdigest()

        if outid in document._slides[bpmod.slide_id].element_keys:
            print("Id for this element already exist!")
            sys.exit(0)
            outid = None

        #print(outid)

    return outid def check_function_args( function, arg_values_dict, lenient = False ):
    """
        Function to check input function args.

        Functions args are defined in the default_theme.py
        or if a theme is added the new value is taken rather than the default one
    """

    function_name = function.__name__
    default_dict = document._theme[function_name]
    outdict = {}

    for key, value in arg_values_dict.items():
        #Check if this arguments exist for this function

        if key in default_dict:
            outdict[key] = value

        else:
            if not lenient :
                print("Error the key %s is not defined for %s module"%(key, function_name))
                print_function_args( function_name )
                sys.exit(1)


    #Check if their is ommited arguments that need to be loaded by default
    for key, value in default_dict.items():
        if key not in outdict:
            outdict[key] = value

    return outdict def get_command_line(func_name):
    """
    Function to print the line of the command in the source code file
    frame,filename,nline,function_name,lines,index = inspect.stack()[-1]
    """

    frame, filename, nline, function_name, lines, index = inspect.stack()[-1]
    # print(nline, func_name)
    if not isinstance(func_name, str):
        # func_name = func_name.func_name
        func_name = func_name.__name__

    # print(frame,filename,nline,function_name,lines,index)
    start = None
    src = document._source_code.source(stop=nline).split('\n')
    # print(src)
    for cpt, line in enumerate(src[::-1]):
        if func_name+'(' in line:
            # print(line)
            start = (nline) - (cpt + 1)
            break

    # print start
    if start is not None:
        stop = nline-1
        source = document._source_code.source(start+1, nline).replace('\n', '')
    else:
        start = 0
        stop = 0
        source = func_name

    # Remove tab and space from source
    source = remove_tabnewline.sub(' ', source)

    return (start, nline-1, source) 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 pre_cache_svg_image(svg_frames):
    """
        Function to extract raster image from svg to define them only
        once on the slide.
    """

    all_images = []
    out_svg_frames = []
    findimage = re.compile(r'<image.*?>')
    for frame in svg_frames:
        svgimages = findimage.findall(frame)
        all_images += svgimages

        #add the cleaned frame to the ouput
        out_svg_frames += [findimage.sub('\n',frame)]

    return out_svg_frames, all_images def print_function_args(function_name):
    #Pretty print of function arguments with default values
    print("Allowed arguments for %s"%(function_name))
    for key, value in document._theme[function_name].items():
        print("%s: [%s] %s"%(key, str(value), type(value))) def set_curentslide(slide_id):
    """
    Set the curent slide to the given slide_id
    """

    document._curentslide = slide_id def set_lastslide():
    '''
    Set the curent slide as the last slide added in the presentation
    '''

    last_slide_id = 'slide_%i' % (document._global_counter['slide'])
    document._curentslide = last_slide_id class positionner():

    def __init__(self, x, y, width, height, elem_id, slideid):
        """
            This class need position x and y of the element

            width and height is the size of the svg or html object (need to be
            updated after rendering)

            x, y: could be a dict, a float or a string

            Default position dict:
            ======================

                {'align': 'left', 'reference': 'slide', 'shift': 0, 'unit': 'rel'}

                align: define the alignement for the coordinate
                       ('center' (page centering),'left','right','middle',
                       'top','bottom')
                reference: 'relative' element for placement or 'slide' relative to slide
                           'slide' for placement refenreced on slide
                shift: the value of the shift
                unit: 'width': relative to page (or group) width
                      'height': relative to page (or group) height
                      'cm', 'pt', 'px': shift value unit
                anchor: 'left', 'right', 'middle', 'top', 'bottom', define the anchor on the object bounding-box
        """

        # Create and id (positition in the dict of this element)
        self.id = elem_id
        self.slideid = slideid


        try:
            self.id_index = document._slides[self.slideid].element_keys.index(self.id)
        except IndexError:
            print('Element not found in document._content[gcs()].element_keys')
            print('Positionner set for elem: %s in slide: %s' % (self.id, self.slideid))
            self.id_index = -1
            sys.exit(1)

        # Create width and Height Length
        self.width = Length(self.id, self.slideid)
        self.height = Length(self.id, self.slideid)
        # Update their values with input with, height
        self.update_size(width, height)

        # Make copy if x and y are dict input
        if isinstance(x, dict):
            self.x = x.copy()
        else:
            self.x = x

        if isinstance(y, dict):
            self.y = y.copy()
        else:
            self.y = y

        # print({"id":self.id,'y':self.y, 'x': self.x})
        # print(self.x)
        # Need to convert x, y to a standart dict
        self.convert_position()

        # Compute elements anchors
        self.compute_anchors()

    def update_x(self, x):
        """
        Update the x position and run the converter
        """

        # Make copy if x and y are dict input
        if isinstance(x, dict):
            self.x = x.copy()
        else:
            self.x = x

        self.convert_position()

    def update_y(self, y):
        """
        Update the y position and run the converter
        """

        if isinstance(y, dict):
            self.y = y.copy()
        else:
            self.y = y

        self.convert_position()

    def update_size(self, width, height):
        """
            Update width and height to computed ones by the element render.

            If width is less than 1px, use it as a percentage of the width
        """

        # Check if it's Length object
        if isinstance(width, Length):
            if width.value is None:
                width.run_render()

            width = width.value

        if isinstance(height, Length):
            if height.value is None:
                height.run_render()

            height = height.value

        # Convert width height if they are given in % of the current width
        if isinstance(width, str):
            if '%' in width:
                ratio = float(width.replace('%', ''))/100.0
                width = document._slides[self.slideid].curwidth  * ratio
            else:
                width = float(convert_unit(width))

        if isinstance(height, str):
            if '%' in height:
                ratio = float(height.replace('%', ''))/100.0
                height = document._slides[self.slideid].curheight * ratio
            else:
                height = float(convert_unit(height))

        # Convert string to float
        # Convert width height to % if they are given as a float number less than one
        if width is not None and width < 1:
            ratio = width
            width = document._slides[self.slideid].curwidth  * ratio

        if height is not None and height < 1:
            ratio = height
            height = document._slides[self.slideid].curheight * ratio

        self.width.value = width
        self.height.value = height

    def compute_anchors(self):
        """
            Compute the anchors for the element self.left self.right self.top and self.bottom
        """

        # Bottom
        self.bottom = anchor('bottom', self.id, self.slideid)
        # top
        self.top = anchor('top', self.id, self.slideid)
        # left
        self.left = anchor('left', self.id, self.slideid)
        # right
        self.right = anchor('right', self.id, self.slideid)
        # center
        self.center = anchor('center', self.id, self.slideid)

    def convert_position(self):
        # Function to convert position of an element
        tmpx = DEFAULT_X.copy()
        tmpy = DEFAULT_Y.copy()
        slidects = document._slides[self.slideid].contents

        # Get the previous content if it exist (to us "+xx" or "-yy" in x, y coords)
        if self.id_index > 0:
            prev_ct = slidects[document._slides[self.slideid].element_keys[self.id_index - 1]]
        else:
            prev_ct = None

        # Check if x or y are only floats
        if isinstance(self.x, float) or isinstance(self.x, int):
            tmpx['shift'] = self.x

        elif isinstance(self.x, dict):
            tmpx = dict_deep_update(tmpx, self.x)

        elif isinstance(self.x, str):

            converted = False

            if '+' in self.x:
                newx = convert_unit(self.x.replace('+', ''))
                # Make relative placement
                if prev_ct is not None:
                    dict_old = prev_ct.positionner.right + newx
                    tmpx = dict_deep_update(tmpx, dict_old)
                else:
                    tmpx['shift'] = newx

                tmpx['unit'] = 'px'
                converted = True

            if '-' in self.x:
                newx = convert_unit(self.x.replace('-', ''))
                # Make relative placement
                if prev_ct is not None:
                    dict_old = prev_ct.positionner.left - newx
                    tmpx = dict_deep_update(tmpx, dict_old)
                else:
                    tmpx['shift'] = newx
                tmpx['unit'] = 'px'
                converted = True


            if self.x in ['auto', 'center']:
                tmpx['shift'] = 0
                tmpx['align'] = self.x
                converted = True

            if not converted:
                try:
                    tmpx['shift'] = float(convert_unit(self.x))
                    tmpx['unit'] = 'px'
                except:
                    print('[Error] x position is incorect string format')
                    print(self.x)

        elif isinstance(self.x, Length):
            if self.x.value is None:
                self.x.run_render()

            tmpx['shift'] = self.x.value
            tmpx['unit'] = 'px'

        else:
            print("[Error] x position need to be a float or a dict")

        if isinstance(self.y, float) or isinstance(self.y, int):
            tmpy['shift'] = self.y

        elif isinstance(self.y, dict):
            tmpy = dict_deep_update(tmpy, self.y)

        elif isinstance(self.y, str):

            converted = False
            if '+' in self.y:
                newy = convert_unit(self.y.replace('+', ''))
                # Make relative placement
                if prev_ct is not None:
                    dict_old = prev_ct.positionner.bottom + newy
                    tmpy = dict_deep_update(tmpy, dict_old)
                else:
                    tmpy['shift'] = newy

                tmpy['unit'] = 'px'
                converted = True

            if '-' in self.y:
                newy = convert_unit(self.y.replace('-', ''))
                # Make relative placement
                if prev_ct is not None:
                    dict_old = prev_ct.positionner.top - newy
                    tmpy = dict_deep_update(tmpy, dict_old)
                else:
                    tmpy['shift'] = newy
                tmpy['unit'] = 'px'
                converted = True

            if self.y in ['auto', 'center']:
                tmpy['shift'] = 0
                tmpy['align'] = self.y
                converted = True

            if not converted:
                try:
                    tmpy['shift'] = convert_unit(self.y)
                    tmpy['unit'] = 'px'
                except:
                    print('[Error] y position is incorect string format')
                    print(self.y)

        elif isinstance(self.y, Length):
            if self.y.value is None:
                self.y.run_render()

            tmpy['shift'] = self.y.value
            tmpy['unit'] = 'px'

        else:
            print("[Error] y position need to be a float or an int or a dict")


        # Store the dict for positions
        self.x = tmpx
        self.y = tmpy

        # Force unit to be pixel for x > 1
        if self.x['shift'] > 1.0 and self.x['unit'] in ('width', 'height'):
            self.x['unit'] = 'px'

        # Force unit to be pixel for y > 1
        if self.y['shift'] > 1.0 and self.y['unit'] in ('width', 'height'):
            self.y['unit'] = 'px'

        # Convert position unit to px
        if self.x['unit'] in ['cm', 'pt', 'mm']:
            self.x['shift'] = float( convert_unit( '%f%s'%(self.x['shift'], self.x['unit']) ) )

        if self.y['unit'] in ['cm', 'pt', 'mm']:
            self.y['shift'] = float( convert_unit( '%f%s'%(self.y['shift'], self.y['unit']) ) )


    def place(self, available_size, ytop=0):
        """
            Method to place the given element. This method is used by renders.py in
            the place_content function

            available_size: (width, heith) is the page or group width and height

            ytop[default 0]: is the y offset (used to center verticaly taking into
                             account the title)

        """

        # Container size
        available_width, available_height = available_size


        # Check if we need to compute the shift as a % of the container
        if available_width is not None:
            if self.x['unit'] == 'width':
                self.x['shift'] *= available_width

            if self.y['unit'] == 'width':
                self.y['shift'] *= available_width

        if available_height is not None:
            if self.x['unit'] == 'height':
                self.x['shift'] *= available_height

            if self.y['unit'] == 'height':
                self.y['shift'] *= available_height

        #Centering element on slide or group
        if self.x['align'] == 'center':
            self.x['final'] = element_centering(self.width.value, available_width) + self.x['shift']

        if self.y['align'] == 'center':
            self.y['final'] = element_centering(self.height.value,
                                                available_height - ytop,
                                                pos_init=ytop) + self.y['shift']

        #Place element that are aligned left
        if self.x['reference'] == 'slide':
            if self.x['align'] == 'left':
                self.x['final'] = self.x['shift']

            if self.x['align'] == 'right':
                # self.x['final'] = (available_width - self.width) - self.x['shift']
                self.x['final'] = (available_width) - self.x['shift']

            if self.x['align'] == 'middle':
                self.x['final'] = self.x['shift'] + self.width.value/2.

        if self.y['reference'] == 'slide':
            if self.y['align'] == 'top':
                self.y['final'] = self.y['shift']

            if self.y['align'] == 'bottom':
                self.y['final'] = available_height - self.y['shift']

            if self.y['align'] == 'middle':
                self.y['final'] = self.y['shift'] + self.height.value/2.

        # Relative positioning
        if self.x['reference'] == 'relative':
            self.x['final'] = relative_placement(self.x['ref_id'], self.x, 'x')

        if self.y['reference'] == 'relative':
            self.y['final'] = relative_placement(self.y['ref_id'], self.y, 'y')

        #reduce number of floating values and set align and unit to top-left in pixel
        self.x['final'] = round(self.x['final'], 1)
        self.y['final'] = round(self.y['final'], 1)

        # Compute the shift due to object anchors
        if self.x['anchor'] == 'right':
            # print('right', self.width)
            self.x['final'] -= self.width.value

        if self.x['anchor'] == 'middle':
            # print('x middle', self.width)
            self.x['final'] -= self.width.value/2.0

        if self.y['anchor'] == 'bottom':
            # print('bottom', self.height)
            self.y['final'] -= self.height.value

        if self.y['anchor'] == 'middle':
            # print('y middle', self.height)
            self.y['final'] -= self.height.value/2.0

    def guess_size_for_group(self):
        """Function to guess parent group final position to place elements relatively
        to this group.
        """

        if 'final' not in self.x:
            if self.width.value is None:
                cur_width = document._slides[self.slideid].curwidth

                print('''Warning: Relative "x" placement to parent group without
                width could gives unexpected results. Width: %i is used.''' %
                      cur_width)

                self.width = Length(self.id, self.slideid, cur_width)

            if self.x == 'auto':
                print('''Warning: Relative "x" placement to a parent group with
                auto-positioning! This will lead to unexpected results!''')

        if 'final' not in self.y:
            if self.height.value is None:
                cur_height = document._height

                print('''Warning: Relative "y" placement to parent group without
                width could gives unexpected results. Width: %i is used.''' %
                      cur_height)

                self.height = Length(self.id, self.slideid, cur_height)

            if self.y == 'auto':
                print('''Warning: Relative "y" placement to a parent group with
                auto-positioning! This will lead to unexpected results!''') 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
from beampy import document
from beampy.functions import (gcs, create_element_id,
                              check_function_args, get_command_line,
                              convert_unit, pre_cache_svg_image,
                              print_function_args, set_curentslide,
                              set_lastslide)
from beampy.geometry import positionner, distribute
            from beampy.modules.title import title as bptitle
        from beampy.exports import display_matplotlib
import sys
import time
import inspect
import logging
        self_pos = curgroup.exports_id.index(self.id)
        
        #remove the id of the current module
        curgroup.exports_id.pop(self_pos)
        #add the current module to it's new place (below the other)
        curgroup.exports_id.insert(other_pos, self.id)

    def first(self):
        """
        Set the current object in the background
        """
        if self.type == 'group':
            pid = self.parentid
        else:
            pid = self.group_id
                    
        curgroup = document._slides[self.slide_id].contents[pid]
        self_pos = curgroup.exports_id.index(self.id)
        
        #remove the id of the current module
        curgroup.exports_id.pop(self_pos)
        #add the current module at the beginning
        curgroup.exports_id.insert(0, self.id)

    def last(self):
        """
        Set the current object in the foreground
        """
        if self.type == 'group':
            pid = self.parentid
        else:
            pid = self.group_id
                    
        curgroup = document._slides[self.slide_id].contents[pid]
        self_pos = curgroup.exports_id.index(self.id)
        
        #remove the id of the current module
        curgroup.exports_id.pop(self_pos)
        #add the current module at the end
        curgroup.exports_id.insert(len(curgroup.exports_id), self.id)


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()]