def render_video(videob64, args): """ Render video (webm) encoded in base64 in svg command """ #Get video size to get the ratio (to estimage the height) vidw, vidh = get_video_size(args) scale_x = float(convert_unit(args['width']))/float(vidw) width = convert_unit(args['width']) height = vidh * scale_x if document._output_format=='html5': #HTML tag for video output = """<video id='video' width="%spx" %s controls="controls" type="video/%s" src="data:video/%s;base64, %s"/>""" #Check if we need autoplay otherargs = '' if args['autoplay'] == True: otherargs += ' autoplay' output = output%(width, otherargs, args['ext'], args['ext'], videob64) else: #Get video image _, imgframe = video_first_image(args) imgframe = base64.b64encode( imgframe ) output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/jpg;base64, %s" />'%(str(width), str(height), imgframe) return output, float(width), float(height)
def __init__(self, x2, y2, **kwargs): """ Create an svg line # Arguments: -x["center"]: horizontal position in the slide -y["auto"]: vertical position in the slide -x2: the horizontal position of the end point -y2: the vertical position of the end point -color: the svg colorname to fill your rectangle -linewidth: the with of the stroke line -opacity: the value of the rectangle opacity from 0 to 1 # exemple: line(x=0, y="20px", x2="40px", y2="20px", color="crimson") """ #The input type of the module self.type = 'svg' #Add args to the module self.check_args_from_theme(kwargs) self.x2 = x2 self.y2 = y2 self.args['x2'] = self.x2 self.args['y2'] = self.y2 #convert unit of x2 and y2 self.x2 = convert_unit(self.x2) self.y2 = convert_unit(self.y2) #Build style for the rectangle beampy_svg_kword = { 'color': 'stroke', 'linewidth': 'stroke-width', 'opacity': 'opacity' } style = '' for kw in beampy_svg_kword: if hasattr(self, kw): style += '%s:%s;' % (beampy_svg_kword[kw], getattr(self, kw)) self.content = '<line x1="0" y1="0" x2="{x2}px" y2="{y2}px" style="{style}"/>'.format( x2=self.x2, y2=self.y2, style=style) #Register the module self.register()
def itemize(items_list, **kwargs): ''' Generates a list or an enumeration. See THEME['itemize'] for option TODO: ADD doc for function arguments ''' args = check_function_args(itemize, kwargs) number = 1 if args['width'] != None: in_width = float(convert_unit(args['width'])) - float( convert_unit(args['item_indent'])) else: in_width = float(document._width) - float( convert_unit(args['item_indent'])) with group(width=args['width'], x=args['x'], y=args['y']): for i, the_item in enumerate(items_list): if args['item_style'] == 'bullet': item_char = r'$\bullet$' elif args['item_style'] == 'number': item_char = str(number) + r'.' number += 1 else: item_char = args['item_style'] # Add color item_char = color_text(item_char, args['item_color']) the_item = color_text(the_item, args['text_color']) if i == 0: text(item_char + r' ' + the_item, x=args['item_indent'], y=0, width=in_width) else: text(item_char + r' ' + the_item, x=args['item_indent'], y=args['item_spacing'], width=in_width)
def process_value(self): if self.elem_id is not None and self.slide_id is not None: if self.value is None: self.run_render() if isinstance(self.value, str): self.value = float(convert_unit(self.value))
def __init__(self, title=None, **kwargs): # Add a slide to the global counter if 'slide' in document._global_counter: document._global_counter['slide'] += 1 else: document._global_counter['slide'] = 0 # Init group counter document._global_counter['group'] = 0 # check args from THEME self.args = check_function_args(slide, kwargs) # The id for this slide self.id = gcs() self.slide_num = document._global_counter['slide'] # Change from dict to class self.tmpout = '' self.contents = {} self.element_keys = [] self.cpt_anim = 0 self.num = document._global_counter['slide']+1 self.title = title self.curwidth = document._width self.num_layers = 0 # Store the number of layers in this slide # Store all outputs self.svgout = [] self.svgdefout = [] # Store module definition for slide self.htmlout = {} # Html is a dict, each key dict is a layer htmlout[0] = [html, html, html] etc... self.scriptout = [] self.animout = [] self.svgheader = '' self.svgfooter = '\n</svg>\n' self.svglayers = {} # Store slide final svg (without svg defs stored in self.svgdefout) for the given layer # Do we need to render the THEME layout on this slide self.render_layout = True # Add the slide to the document contents list document._slides[self.id] = self # Manage groups inside one slide the lower level of group is 0 and correspond # to the main slide self.groupsid = {} self.cur_group_level = -1 g0 = group(x=0, y=0, width=document._width, height=document._height) self.groupsid[0] = [g0.id] # store groups id objects in this list if title is not None: from beampy.modules.title import title as bptitle self.title_element = bptitle(title) self.ytop = float(convert_unit(self.title.reserved_y)) else: self.ytop = 0 self.title_element = None # Add ytop to the slide main group g0.yoffset = self.ytop
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 / 100.0 width = document._slides[self.slideid].curwidth * ratio if height is not None and height < 1: ratio = height / 100.0 height = document._slides[self.slideid].curheight * ratio self.width.value = width self.height.value = height
def __init__(self, title= None, **kwargs): #Add a slide to the global counter if 'slide' in document._global_counter: document._global_counter['slide'] += 1 else: document._global_counter['slide'] = 0 #Init group counter document._global_counter['group'] = 0 #check args from THEME self.args = check_function_args(slide, kwargs) out = {'title':title, 'contents': {}, 'num':document._global_counter['slide']+1, 'groups': [], "args": self.args, 'htmlout': '', #store rendered htmlelements inside the slide 'animout': [], #store svg rendered part of animatesvg 'scriptout': '', #store javascript defined in element['script'] 'cpt_anim': 0, 'element_keys': [] #list to store elements id in order } #The id for this slide self.id = gcs() #Change from dict to class self.tmpout = out self.contents = out['contents'] self.element_keys = out['element_keys'] self.cpt_anim = 0 self.num = out['num'] self.title = title #Store all outputs self.svgout = [] self.htmlout = [] self.scriptout = [] self.animout = [] self.svgheader = '' self.svgfooter = '\n</svg>\n' self.cpt_anim = 0 self.groups = [] #Add the slide to the document contents list document._contents[self.id] = out document._slides[self.id] = self if title!= None: from beampy.modules.title import title as bptitle bptitle( title ) self.ytop = float(convert_unit(self.title.reserved_y)) else: self.ytop = 0
def hline(y, **kwargs): """ Create an horizontal line at y position """ y = convert_unit(y) y = '%spx' % y return line(x=0, y=y, x2='%spx' % document._width, y2=y, **kwargs)
def center(shift=0): out = {'anchor': 'middle', 'shift': shift} if isinstance(shift, str): out['shift'] = float(convert_unit(shift)) out['unit'] = 'px' return out
def right(shift=0): out = {'anchor': 'right', 'shift': shift} if isinstance(shift, str): out['shift'] = float(convert_unit(shift)) out['unit'] = 'px' return out
def itemize( items_list, **kwargs): ''' Generates a list or an enumeration. See THEME['itemize'] for option TODO: ADD doc for function arguments ''' args = check_function_args(itemize, kwargs) number = 1 if args['width']!=None: in_width = float(convert_unit(args['width'])) - float(convert_unit(args['item_indent'])) else: in_width = float(document._width) - float(convert_unit(args['item_indent'])) with group(width=args['width'], x=args['x'], y=args['y']): for i, the_item in enumerate(items_list) : if args['item_style'] == 'bullet' : item_char = r'$\bullet$' elif args['item_style'] == 'number' : item_char = str(number) + r'.' number += 1 else : item_char = item_style # Add color item_char = color_text( item_char, args['item_color'] ) the_item = color_text( the_item, args['text_color'] ) if i == 0 : text( item_char + r' ' + the_item, x = args['item_indent'], y = 0, width=in_width ) else: text( item_char + r' ' + the_item, x = args['item_indent'], y = args['item_spacing'], width=in_width )
def __init__(self, files_in, **kwargs): # Add type self.type = 'animatesvg' # Check input args for this module self.check_args_from_theme(kwargs) # Cache is useless because we call figure function which handle the cache for each figures self.cache = False slide = document._slides[gcs()] # Add +1 to counter self.anim_num = slide.cpt_anim slide.cpt_anim += 1 input_width = self.width # Save the input width for mpl figures if self.width is None: self.width = slide.curwidth # Read all files from a given wildcard if isinstance(files_in, str): svg_files = glob.glob(files_in) # Need to sort using the first digits finded in the name svg_files = sorted( svg_files, key=lambda x: int(''.join(re.findall(r'\d+', x)))) # If the input is a list of names or mpl figures or other compatible with figure elif isinstance(files_in, list): svg_files = files_in if input_width is None: width_inch, height_inch = files_in[0].get_size_inches() self.width = convert_unit("%fin" % (width_inch)) else: print('Unknown input type for files_folder') sys.exit(0) # check how many images we wants if self.end == 'end': self.end = len(svg_files) # Add content self.content = svg_files[self.start:self.end] # Register the module self.register()
def process_right_value(right_value): """ Process the right value of the operation (could be another element Length or string with length + unit or pixels) """ if isinstance(right_value, Length): if right_value.value is None: right_value.run_render() tmp_rvalue = float(right_value.value) if isinstance(right_value, str): tmp_rvalue = float(convert_unit(right_value)) if isinstance(right_value, float) or isinstance(right_value, int): tmp_rvalue = float(right_value) return tmp_rvalue
def parse_newvalue(self, new_value): """ New_value can be a string like "+5cm" or a float 0.4 or a new dict like {"shift": 0, "align": 'left'} """ #print(type(new_value)) if type(new_value) == type(str()): self.position['shift'] = float( convert_unit(new_value) ) self.position['unit'] = 'px' elif type(new_value) == type(float()) or type(new_value) == type(int()): self.position['shift'] = new_value elif type(new_value) == type(dict()): self.position = dict_deep_update(new_value.copy(), self.position) else: print('Invalid relative coordinate type!')
def parse_newvalue(self, new_value): """ New_value can be a string like "+5cm" or a float 0.4 or a new dict like {"shift": 0, "align": 'left'} """ #print(type(new_value)) if isinstance(new_value, str): self.position['shift'] = float(convert_unit(new_value)) self.position['unit'] = 'px' elif isinstance(new_value, float) or isinstance(new_value, int): self.position['shift'] = new_value elif isinstance(new_value, dict): self.position = dict_deep_update(new_value.copy(), self.position) elif isinstance(new_value, Length): new_value.process_value() self.position['shift'] = float(new_value.value) self.position['unit'] = 'px' else: print('Invalid relative coordinate type!')
def render_figure( figurein, args ): """ function to render figures """ #For svg figure if args['ext'] in ('svg', 'pdf') : #test if we need to optimise the svg if document._optimize_svg: figurein = optimize_svg(figurein) soup = BeautifulSoup(figurein, 'xml') #Change id in svg defs to use the global id system soup = make_global_svg_defs(soup) svgtag = soup.find('svg') svg_viewbox = svgtag.get("viewBox") tmph = svgtag.get("height") tmpw = svgtag.get("width") if tmph == None or tmpw == None: fmpf, tmpname = tempfile.mkstemp(prefix="beampytmp") with open( tmpname+'.svg', 'w' ) as f: f.write(figurein) #print figurein tmph = getsvgheight( tmpname+'.svg' ) tmpw = getsvgwidth( tmpname+'.svg' ) #print tmpw, tmph os.remove(tmpname+'.svg') svgheight = convert_unit( tmph ) svgwidth = convert_unit( tmpw ) if svg_viewbox != None: svgheight = svg_viewbox.split(' ')[3] svgwidth = svg_viewbox.split(' ')[2] #SCALE OK need to keep the original viewBox !!! scale_x = float(convert_unit(args['width']))/float(svgwidth) #print svgwidth, svgheight, scale_x #scale_y = float(convert_unit(args['height']))/float(svgheight) good_scale = scale_x #BS4 get the svg tag content without <svg> and </svg> tmpfig = svgtag.renderContents() #print tmpfig[:100] #Add the correct first line and last #tmphead = '<g transform="matrix(%s,0,0,%s,%s,%s)" viewBox="%s">'%(str(good_scale), str(good_scale), convert_unit(args['x']), convert_unit(args['y']), svg_viewbox)) tmphead = '\n<g transform="scale(%0.5f)">'%(good_scale) output = tmphead + tmpfig + '</g>\n' figure_height = float(svgheight)*good_scale figure_width = convert_unit(args['width']) #Bokeh images if args['ext'] == 'bokeh': figure_height = float(convert_unit(args['height'])) figure_width = convert_unit(args['width']) output = """%s"""%figurein #For the other format if args['ext'] in ['png', 'jpeg']: #Open image with PIL to compute size tmp_img = Image.open(args['filename']) _,_,tmpwidth,tmpheight = tmp_img.getbbox() tmp_img.close() scale_x = float(convert_unit(args['width']))/float(tmpwidth) figure_height = float(tmpheight) * scale_x figure_width = convert_unit(args['width']) if args['ext'] == 'png': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/png;base64, %s" />'%(figure_width, figure_height, figurein) if args['ext'] == 'jpeg': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/jpg;base64, %s" />'%(figure_width, figure_height, figurein) #Save the heigt #args['height'] = figure_height return output, float(figure_width), float(figure_height)
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 arrow(x, y, dx, dy, style='->', color="black", lw='2pt', in_angle=None, out_angle=None, bend=None, head_style=""): """ function to draw arrows on slide Arguments --------- x: initial x position of the arrow y: initial y position dx: end of arrow relative to x dy: end of arrow relative to y style ["->"]: arrow style "-": a simple line "->": simple arrow "<->": two way arrows color ['black']: arrow color (you can use svgnames) lw ["2pt"]: set the line width in point in_angle [None]: angle at the end of the arrow out_angle [None]: angle at the start of the arrow bend [None]: direction of arrow bending "left" or "right" head_style [""]: change head_style of arrow: "latex", "stealth" """ tikz_cmd = r""" \coordinate (a) at ({x},{y}); \coordinate (b) at ({xf},{yf}); \path[{style}, {color}, line width={lw}, {headstyle}] (a) edge {options} (b); """ #Convert px to pt (tikz doesn't accept px) cmdx = "%0.1fpt" % (float(convert_unit(str(dx))) * 72.27 / 96.0) cmdy = "%0.1fpt" % (float(convert_unit(str(dy))) * 72.27 / 96.0) if head_style != "": head_style = ">=%s" % (head_style) options = [] if bend != None: options += ['bend %s' % bend] if in_angle != None: options += ['in=%0.1f' % in_angle] if out_angle != None: options += ['out=%0.1f' % out_angle] options = ','.join(options) if options != '': options = '[%s]' % options tikz_cmd = tikz_cmd.format(x=0, y=0, xf=cmdx, yf=cmdy, style=style, color=color, lw=lw, headstyle=head_style, options=options) #Run tikz command if dx < 0: tmpanchor = 'right' else: tmpanchor = 'left' if dy < 0: tmpanchor += '_top' else: tmpanchor += '_bottom' return tikz(tikz_cmd, x=x, y=y, figure_anchor=tmpanchor)
def convert_position(self): #Function to convert position of an element tmpx = DEFAULT_X.copy() tmpy = DEFAULT_Y.copy() slidects = document._contents[gcs()]['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._contents[gcs()]['element_keys'][self.id_index - 1]] else: prev_ct = None #Check if x or y are only floats if type(self.x) == type(float()) or type(self.x) == type(int()): tmpx['shift'] = self.x elif type(self.x) == type(dict()): tmpx = dict_deep_update(tmpx, self.x) elif type(self.x) == type(str()): converted = False if '+' in self.x: self.x = convert_unit( self.x.replace('+','') ) #Make relative placement if prev_ct != None: dict_old = prev_ct['positionner'].right + float( self.x ) tmpx = dict_deep_update(tmpx, dict_old) else: tmpx['shift'] = float( self.x ) tmpx['unit'] = 'px' converted = True if '-' in self.x: self.x = convert_unit( self.x.replace('-','') ) #Make relative placement if prev_ct != None: dict_old = prev_ct['positionner'].left - float( self.x ) tmpx = dict_deep_update(tmpx, dict_old) else: tmpx['shift'] = float( self.x ) 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) else: print("[Error] x position need to be a float or a dict") if type(self.y) == type(float()) or type(self.y) == type(int()): tmpy['shift'] = self.y elif type(self.y) == type(dict()): tmpy = dict_deep_update(tmpy, self.y) elif type(self.y) == type(str()): converted = False if '+' in self.y: self.y = convert_unit( self.y.replace('+','') ) #Make relative placement if prev_ct != None: dict_old = prev_ct['positionner'].bottom + float(self.y) tmpy = dict_deep_update(tmpy, dict_old) else: tmpy['shift'] = float( self.y ) tmpy['unit'] = 'px' converted = True if '-' in self.y: self.y = convert_unit( self.y.replace('-','') ) #Make relative placement if prev_ct != None : dict_old = prev_ct['positionner'].top - float(self.y) tmpy = dict_deep_update(tmpy, dict_old) else: tmpy['shift'] = float( self.y ) 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'] = float( convert_unit(self.y) ) tmpy['unit'] = 'px' except: print('[Error] y position is incorect string format') print self.y 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 #Convert position unit to pt 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']) ) ) if type(self.width) == type(str()): self.width = float( convert_unit(self.width) ) if type(self.height) == type(str()): self.height = float( convert_unit(self.height) )
def __init__(self, title=None, **kwargs): #Add a slide to the global counter if 'slide' in document._global_counter: document._global_counter['slide'] += 1 else: document._global_counter['slide'] = 0 #Init group counter document._global_counter['group'] = 0 #check args from THEME self.args = check_function_args(slide, kwargs) out = { 'title': title, 'contents': {}, 'num': document._global_counter['slide'] + 1, 'groups': [], "args": self.args, 'htmlout': '', #store rendered htmlelements inside the slide 'animout': [], #store svg rendered part of animatesvg 'scriptout': '', #store javascript defined in element['script'] 'cpt_anim': 0, 'element_keys': [] #list to store elements id in order } #The id for this slide self.id = gcs() self.slide_num = document._global_counter['slide'] #Change from dict to class self.tmpout = out self.contents = out['contents'] self.element_keys = out['element_keys'] self.cpt_anim = 0 self.num = out['num'] self.title = title #Store all outputs self.svgout = [] self.htmlout = [] self.scriptout = [] self.animout = [] self.svgheader = '' self.svgfooter = '\n</svg>\n' #Do we need to render the THEME layout on this slide self.render_layout = True #If we want to add background slide decodaration like header-bar or footer informations self.cpt_anim = 0 self.groups = [] #Add the slide to the document contents list document._contents[self.id] = out document._slides[self.id] = self if title != None: from beampy.modules.title import title as bptitle bptitle(title) self.ytop = float(convert_unit(self.title.reserved_y)) else: self.ytop = 0
def __init__(self, content, ext=None, **kwargs): # The type of the module self.type = 'svg' # Add the extra args to the module self.check_args_from_theme(kwargs) self.ext = ext # Register the content self.content = content # Special args for cache id self.args_for_cache_id = ['width', 'ext'] # Check if the given filename is a string if isinstance(self.content, str): # Check extension self.ext = guess_file_type(self.content, self.ext) else: # Check kind of objects that are passed to filename # Bokeh plot if "bokeh" in str(type(self.content)): self.ext = 'bokeh' # Mathplotlib figure if "matplotlib" in str(type(self.content)): self.ext = "matplotlib" ###################################### # Check if the input filename can be treated if self.ext is None: print("figure format can't be guessed.") sys.exit(1) # Bokeh image if self.ext == 'bokeh': self.type = 'html' # Todo get width and height from a bokeh figure if self.width is None: self.width = int(self.content.plot_width) if self.height is None: self.height = int(self.content.plot_height) # Do not cache this element if it's bokeh plot self.cache = False # Mpl figure if self.ext == 'matplotlib': # import close to force the closing of the input figure from matplotlib.pyplot import close close(self.content) #close the figure # Set figure default width when it was not given as arguement if self.width is None: width_inch, height_inch = self.content.get_size_inches() self.width = convert_unit("%fin" % (width_inch)) # Create a special args to create a unique id for caching # Generate the figure (in binary format as jpg) from the canvas with BytesIO() as tmpb: self.content.canvas.print_jpg(tmpb) tmpb.seek(0) md5t = hashlib.md5(tmpb.read()).hexdigest() #print(md5t) # Add this new arg self.args['mpl_fig_hash'] = md5t self.mpl_fig_hash = md5t self.args_for_cache_id += ['mpl_fig_hash'] # Other filetype images if self.ext not in ('matplotlib', 'bokeh'): # Add file timestamp to an arguments for caching fdate = str(os.path.getmtime(self.content)) self.args['filedate'] = fdate self.filedate = fdate self.args_for_cache_id += ['filedate'] if self.width is None and self.height is None: self.width = document._slides[gcs()].curwidth # Add this module to the current slide + add positionner self.register()
def render(self): """ function to render figures """ # Svg // pdf render if self.ext in ('svg', 'pdf', 'eps', 'matplotlib'): #Convert pdf to svg if self.ext == 'pdf': figurein = convert_pdf_to_svg(self.content) elif self.ext == 'eps': figurein = convert_eps_to_svg(self.content) #Convert matplotlib figure to svg elif self.ext == 'matplotlib': #Store mpl svg to a stringIO object with StringIO() as tmpf: self.content.savefig(tmpf, bbox_inches='tight', format='svg') tmpf.seek(0) #go to the biginig of the file #store tmpf content as string in figurein variable figurein = tmpf.read().encode('utf-8') #General case for svg format else: #Check if a filename is given for a svg file or directly read the content value if os.path.isfile(self.content): with open(self.content, 'r') as f: figurein = f.read() else: figurein = self.content #test if we need to optimise the svg if document._optimize_svg: figurein = optimize_svg(figurein) soup = BeautifulSoup(figurein, 'xml') #Change id in svg defs to use the global id system soup = make_global_svg_defs(soup) #Optimize the size of embeded svg images ! if document._resize_raster: imgs = soup.findAll('image') if imgs: for img in imgs: #True width and height of embed svg image width, height = int(float(img['width'])), int( float(img['height'])) img_ratio = height / float(width) b64content = img['xlink:href'] try: in_img = BytesIO( base64.b64decode( b64content.split(';base64,')[1])) tmp_img = Image.open(in_img) #print(tmp_img) out_img = resize_raster_image( tmp_img, max_width=self.positionner.width.value) out_b64 = base64.b64encode( out_img.read()).decode('utf8') #replace the resized image into the svg img['xlink:href'] = 'data:image/%s;base64, %s' % ( tmp_img.format.lower(), out_b64) except: print('Unable to reduce the image size') pass svgtag = soup.find('svg') svg_viewbox = svgtag.get("viewBox") tmph = svgtag.get("height") tmpw = svgtag.get("width") if tmph is None or tmpw is None: with tempfile.NamedTemporaryFile(mode='w', prefix='beampytmp', suffix='.svg') as f: try: f.write(figurein) except Exception as e: #python 2 f.write(figurein.encode('utf8')) # force to write file content to disk f.file.flush() # get svg size using inkscape tmph = getsvgheight(f.name) tmpw = getsvgwidth(f.name) svgheight = convert_unit(tmph) svgwidth = convert_unit(tmpw) if svg_viewbox is not None: svgheight = svg_viewbox.split(' ')[3] svgwidth = svg_viewbox.split(' ')[2] # BS4 get the svg tag content without <svg> and </svg> tmpfig = svgtag.renderContents().decode('utf8') # Scale the figure according to the given width if self.width.value is not None and self.height.value is None: # SCALE OK need to keep the original viewBox !!! scale = (self.positionner.width / float(svgwidth)).value figure_height = float(svgheight) * scale figure_width = self.positionner.width.value # Scale the figure according to the given height if self.height.value is not None and self.width.value is None: figure_height = self.positionner.height.value scale = (self.positionner.height / float(svgheight)).value figure_width = float(svgwidth) * scale # Dont scale the figure let the user fix the width height if self.height.value is not None and self.width.value is not None: output = tmpfig figure_height = self.positionner.height.value figure_width = self.positionner.width.value else: # Apply the scaling to the figure tmphead = '\n<g transform="scale(%0.5f)">' % (scale) output = tmphead + tmpfig + '</g>\n' #Update the final svg size self.update_size(figure_width, figure_height) #Add the final svg output of the figure self.svgout = output #Bokeh images if self.ext == 'bokeh': # Change the sizing mode (need scale_both) to adapt size of the figure self.content.sizing_mode = 'scale_both' # Run the bokeh components function to separate figure html div and js script figscript, figdiv = components(self.content, wrap_script=False) # Transform figscript to givea function name load_bokehjs tmp = figscript.splitlines() goodscript = '\n'.join(['["load_bokeh"] = function() {'] + tmp[1:-1] + ['};\n']) #Add the htmldiv to htmlout self.htmlout = "<div id='bk_resizer' width='{width}px' height='{height}px' style='width: {width}px; height: {height}px; transform-origin: left top 0px;'> {html} </div>" self.htmlout = self.htmlout.format(width=self.positionner.width, height=self.positionner.height, html=figdiv) #Add the script to scriptout self.jsout = goodscript #For the other format if self.ext in ('png', 'jpeg', 'gif'): #Open image with PIL to compute size tmp_img = Image.open(self.content) _, _, tmpwidth, tmpheight = tmp_img.getbbox() # Scale the figure according to the given width if self.width.value is not None and self.height.value is None: # SCALE OK need to keep the original viewBox !!! scale = (self.positionner.width / float(tmpwidth)).value figure_height = float(tmpheight) * scale figure_width = self.positionner.width.value # Scale the figure according to the given height if self.height.value is not None and self.width.value is None: figure_height = self.positionner.height.value scale = (self.positionner.height / float(tmpheight)).value figure_width = float(tmpwidth) * scale # Dont scale the figure let the user fix the width height if self.height.value is not None and self.width.value is not None: figure_height = self.positionner.height.value figure_width = self.positionner.width.value if document._resize_raster: #Rescale figure to the good size (to improve size and display speed) if self.ext == 'gif': print('Gif are not resized, the original size is taken!') with open(self.content, "rb") as f: figurein = base64.b64encode(f.read()).decode('utf8') else: out_img = resize_raster_image(tmp_img, max_width=figure_width) figurein = base64.b64encode(out_img.read()).decode('utf8') out_img.close() else: with open(self.content, "rb") as f: figurein = base64.b64encode(f.read()).decode('utf8') tmp_img.close() if self.ext == 'png': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/png;base64, %s" />' % ( figure_width, figure_height, figurein) if self.ext == 'jpeg': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/jpg;base64, %s" />' % ( figure_width, figure_height, figurein) if self.ext == 'gif': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/gif;base64, %s" />' % ( figure_width, figure_height, figurein) # Update the final size of the figure self.update_size(figure_width, figure_height) # Add the final svg to svgout self.svgout = output #print self.width, self.height #Update the rendered state of the module self.rendered = True
def __init__(self, files_folder, **kwargs): """ Function to create svg animation from a folder containing svg files - files_folder: Folder containing svg like "./my_folder/" - x['center']: x coordinate of the image 'center': center image relative to document._width '+1cm": place image relative to previous element - y['auto']: y coordinate of the image 'auto': distribute all slide element on document._height 'center': center image relative to document._height (ignore other slide elements) '+3cm': place image relative to previous element - start[0]: svg image number to start the sequence - end['end']: svg image number to stop the sequence - width[None]: Width of the figure (None = slide width) - fps[25]: animation framerate - autoplay[False]: autoplay animation when slide is displayed """ #Add type self.type = 'animatesvg' #Check input args for this module self.check_args_from_theme(kwargs) #Cache is useless because we call figure function which handle the cache for each figures self.cache = False input_width = self.width #Save the input width for mpl figures if self.width == None: self.width = document._width #Read all files from a given wildcard if type(files_folder) == type(str()): svg_files = glob.glob(files_folder) #Need to sort using the first digits finded in the name svg_files = sorted( svg_files, key=lambda x: int(''.join(re.findall(r'\d+', x)))) #If the input is a list of names or mpl figures or other compatible with figure elif type(files_folder) == type(list()): svg_files = files_folder if input_width == None: width_inch, height_inch = files_folder[0].get_size_inches() self.width = convert_unit("%fin" % (width_inch)) else: print('Unknow input type for files_folder') sys.exit(0) #check how many images we wants if self.end == 'end': self.end = len(svg_files) #Add content self.content = svg_files[self.start:self.end] #Register the module self.register()
def render(self): """ function to render figures """ #Svg // pdf render if self.ext in ('svg', 'pdf') : #Convert pdf to svg if self.ext == 'pdf' : figurein = convert_pdf_to_svg( self.content ) else: #Check if a filename is given for a svg file or directly read the content value if os.path.isfile(self.content): with open(self.content) as f: figurein = f.read() else: figurein = self.content #test if we need to optimise the svg if document._optimize_svg: figurein = optimize_svg(figurein) soup = BeautifulSoup(figurein, 'xml') #Change id in svg defs to use the global id system soup = make_global_svg_defs(soup) #Optimize the size of embeded svg images ! if document._resize_raster: imgs = soup.findAll('image') if imgs: for img in imgs: #True width and height of embed svg image width, height = int( float(img['width']) ) , int( float(img['height']) ) img_ratio = height/float(width) b64content = img['xlink:href'] try: in_img = BytesIO( base64.b64decode(b64content.split(';base64,')[1]) ) tmp_img = Image.open(in_img) #print(tmp_img) out_img = resize_raster_image( tmp_img ) out_b64 = base64.b64encode( out_img.read() ) #replace the resized image into the svg img['xlink:href'] = 'data:image/%s;base64, %s'%(tmp_img.format.lower(), out_b64) except: print('Unable to reduce the image size') pass svgtag = soup.find('svg') svg_viewbox = svgtag.get("viewBox") tmph = svgtag.get("height") tmpw = svgtag.get("width") if tmph == None or tmpw == None: fmpf, tmpname = tempfile.mkstemp(prefix="beampytmp") with open( tmpname+'.svg', 'w' ) as f: f.write(figurein) #print figurein tmph = getsvgheight( tmpname+'.svg' ) tmpw = getsvgwidth( tmpname+'.svg' ) #print tmpw, tmph os.remove(tmpname+'.svg') svgheight = convert_unit( tmph ) svgwidth = convert_unit( tmpw ) if svg_viewbox != None: svgheight = svg_viewbox.split(' ')[3] svgwidth = svg_viewbox.split(' ')[2] #SCALE OK need to keep the original viewBox !!! scale_x = self.positionner.width/float(svgwidth) #print svgwidth, svgheight, scale_x #scale_y = float(convert_unit(args['height']))/float(svgheight) good_scale = scale_x #BS4 get the svg tag content without <svg> and </svg> tmpfig = svgtag.renderContents() #Add the correct first line and last tmphead = '\n<g transform="scale(%0.5f)">'%(good_scale) output = tmphead + tmpfig + '</g>\n' figure_height = float(svgheight)*good_scale figure_width = self.width #Update the final svg size self.update_size(figure_width, figure_height) #Add the final svg output of the figure self.svgout = output #Bokeh images if self.ext == 'bokeh': #Run the bokeh components function to separate figure html div and js script figscript, figdiv = components(self.content, wrap_script=False) #Transform figscript to givea function name load_bokehjs tmp = figscript.splitlines() goodscript = '\n'.join( ['["load_bokeh"] = function() {'] + tmp[1:-1] + ['};\n'] ) #Add the htmldiv to htmlout self.htmlout = figdiv #Add the script to scriptout self.jsout = goodscript #For the other format if self.ext in ('png', 'jpeg'): #Open image with PIL to compute size tmp_img = Image.open(self.content) _,_,tmpwidth,tmpheight = tmp_img.getbbox() scale_x = self.positionner.width/float(tmpwidth) figure_height = float(tmpheight) * scale_x figure_width = self.positionner.width if document._resize_raster: #Rescale figure to the good size (to improve size and display speed) out_img = resize_raster_image(tmp_img) figurein = base64.b64encode(out_img.read()) out_img.close() else: with open( self.content, "r") as f: figurein = base64.b64encode(f.read()) tmp_img.close() if self.ext == 'png': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/png;base64, %s" />'%(figure_width, figure_height, figurein) if self.ext == 'jpeg': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/jpg;base64, %s" />'%(figure_width, figure_height, figurein) #Update the final size of the figure self.update_size(figure_width, figure_height) #Add the final svg to svgout self.svgout = output #print self.width, self.height #Update the rendered state of the module self.rendered = True
def arrow(x, y, dx, dy, style='->', color="black", lw='2pt', in_angle=None, out_angle=None, bend=None, head_style="", dashed=False): """ Draw an arrow on slide. Ticks is used to render the arrow. Parameters ---------- x : int or float or {'center', 'auto'} or str Horizontal position for the arrow. See positioning system of Beampy. y : int or float or {'center', 'auto'} or str Vertical position for the arrow. See positioning system of Beampy. dx : int or str Arrow horizontal displacement relative to x. `dx` could given in pixel as an integer or a float or in as string with an unit like '2cm'. It could be negative (left of x) or positive (right of x). dy : int or str Arrow vertical displacement relative to y. `dy` could given in pixel as an integer or a float or in as string with an unit like '2cm'. It could be negative (top of y) or positive (bottom of y). style : str in {'-', '->', '<-', '<->'}, optional Arrow style (the default value is '->'). The style is the one defined by Ticks style: * '->' or '<-', simple arrow. * '<->', two way arrows. * '-', a line. color : string, optional Arrow Color (the default value is 'black'). The color is given as Latex svgnames. lw : str or int, optional Arrow line width (the default is '2pt'). If the value is given as string followed by an unit, it is converted by beampy convert_unit function. It could also be given as an integer. in_angle : int or None, optional Angle at the end of the arrow (the default value is None which implies that the angle is automatically computed). out_angle : int or None, optional Starting angle of the arrow (the default value is None which implies that the angle is automatically computed). bend : {'left' or 'right'} or None, optional Direction of arrow bending (the default value is None, which implies a straight arrow). 'left' bends the arrow to the left and 'right' to the right. head_style : {'latex' or 'stealth' or ''}, optional Tikz head style of the arrow (the default value is '', which implies default head style of Ticks). dashed : True or False, optional Create a dashed arrow line (the default value is False). """ tikz_cmd = r""" \coordinate (a) at ({x},{y}); \coordinate (b) at ({xf},{yf}); \path[{style}, {color}, line width={lw}, {headstyle}] (a) edge {options}(b); """ # Convert px to pt (tikz doesn't accept px) cmdx = "%0.1fpt" % (float(convert_unit(str(dx))) * 72.27 / 96.0) cmdy = "%0.1fpt" % (-float(convert_unit(str(dy))) * 72.27 / 96.0) if head_style != "": head_style = ">=%s" % (head_style) options = [] if bend is not None: options += ['bend %s' % bend] if in_angle is not None: options += ['in=%0.1f' % in_angle] if out_angle is not None: options += ['out=%0.1f' % out_angle] if dashed: options += ['dashed'] options = ','.join(options) if options != '': options = '[%s]' % options tikz_cmd = tikz_cmd.format(x=0, y=0, xf=cmdx, yf=cmdy, style=style, color=color, lw=lw, headstyle=head_style, options=options) # Run tikz command if dx < 0 or (isinstance(dx, str) and '-' in dx): tmpanchor = 'right' else: tmpanchor = 'left' if dy < 0 or (isinstance(dy, str) and '-' in dy): tmpanchor += '_bottom' else: tmpanchor += '_top' return tikz(tikz_cmd, x=x, y=y, figure_anchor=tmpanchor)
def render_figure( ct ): """ function to render figures """ #read args in the dict args = ct['args'] #Svg // pdf render if args['ext'] in ('svg', 'pdf') : #Convert pdf to svg if args['ext'] == 'pdf' : figurein = convert_pdf_to_svg( args['filename'] ) else: #Check if a filename is given for a svg file or directly read the content value if 'filename' in args: with open(args['filename']) as f: figurein = f.read() else: figurein = ct['content'] #test if we need to optimise the svg if document._optimize_svg: figurein = optimize_svg(figurein) soup = BeautifulSoup(figurein, 'xml') #Change id in svg defs to use the global id system soup = make_global_svg_defs(soup) #Optimize the size of embeded svg images ! if document._resize_raster: imgs = soup.findAll('image') if imgs: for img in imgs: #True width and height of embed svg image width, height = int( float(img['width']) ) , int( float(img['height']) ) img_ratio = height/float(width) b64content = img['xlink:href'] try: in_img = BytesIO( base64.b64decode(b64content.split(';base64,')[1]) ) tmp_img = Image.open(in_img) #print(tmp_img) out_img = resize_raster_image( tmp_img ) out_b64 = base64.b64encode( out_img.read() ) #replace the resized image into the svg img['xlink:href'] = 'data:image/%s;base64, %s'%(tmp_img.format.lower(), out_b64) except: print('Unable to reduce the image size') pass svgtag = soup.find('svg') svg_viewbox = svgtag.get("viewBox") tmph = svgtag.get("height") tmpw = svgtag.get("width") if tmph == None or tmpw == None: fmpf, tmpname = tempfile.mkstemp(prefix="beampytmp") with open( tmpname+'.svg', 'w' ) as f: f.write(figurein) #print figurein tmph = getsvgheight( tmpname+'.svg' ) tmpw = getsvgwidth( tmpname+'.svg' ) #print tmpw, tmph os.remove(tmpname+'.svg') svgheight = convert_unit( tmph ) svgwidth = convert_unit( tmpw ) if svg_viewbox != None: svgheight = svg_viewbox.split(' ')[3] svgwidth = svg_viewbox.split(' ')[2] #SCALE OK need to keep the original viewBox !!! scale_x = ct['positionner'].width/float(svgwidth) #print svgwidth, svgheight, scale_x #scale_y = float(convert_unit(args['height']))/float(svgheight) good_scale = scale_x #BS4 get the svg tag content without <svg> and </svg> tmpfig = svgtag.renderContents() #print tmpfig[:100] #Add the correct first line and last #tmphead = '<g transform="matrix(%s,0,0,%s,%s,%s)" viewBox="%s">'%(str(good_scale), str(good_scale), convert_unit(args['x']), convert_unit(args['y']), svg_viewbox)) tmphead = '\n<g transform="scale(%0.5f)">'%(good_scale) output = tmphead + tmpfig + '</g>\n' figure_height = float(svgheight)*good_scale figure_width = ct['positionner'].width #Bokeh images if args['ext'] == 'bokeh': figurein = ct['content'] figure_height = ct['positionner'].height figure_width = ct['positionner'].width output = """%s"""%figurein #For the other format if args['ext'] in ['png', 'jpeg']: #Open image with PIL to compute size tmp_img = Image.open(args['filename']) _,_,tmpwidth,tmpheight = tmp_img.getbbox() scale_x = ct['positionner'].width/float(tmpwidth) figure_height = float(tmpheight) * scale_x figure_width = ct['positionner'].width if document._resize_raster: #Rescale figure to the good size (to improve size and display speed) out_img = resize_raster_image(tmp_img) figurein = base64.b64encode(out_img.read()) out_img.close() else: with open( args['filename'], "r") as f: figurein = base64.b64encode(f.read()) tmp_img.close() if args['ext'] == 'png': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/png;base64, %s" />'%(figure_width, figure_height, figurein) if args['ext'] == 'jpeg': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/jpg;base64, %s" />'%(figure_width, figure_height, figurein) ct['positionner'].update_size(figure_width, figure_height) return output
def __init__(self, content, ext=None, **kwargs): """ Add figure to current slide Accepted format: [svg, png, jpeg, bokeh figure] - x['center']: x coordinate of the image 'center': center image relative to document._width '+1cm": place image relative to previous element - y['auto']: y coordinate of the image 'auto': distribute all slide element on document._height 'center': center image relative to document._height (ignore other slide elements) '+3cm': place image relative to previous element - width[None]: Image width (None is the size of the image) - ext[None]: Image format, if None, format is guessed from filename. """ #The type of the module self.type = 'svg' #Add the extra args to the module self.check_args_from_theme(kwargs) self.ext = ext #Register the content self.content = content #Special args for cache id self.args_for_cache_id = ['width', 'ext'] #Check if the given filename is a string if type(self.content) == type(''): #Check extension self.ext = guess_file_type(self.content, self.ext) else: #Check kind of objects that are passed to filename #Bokeh plot if "bokeh" in str(type(self.content)): self.ext = 'bokeh' #Mathplotlib figure if "matplotlib" in str(type(self.content)): self.ext = "matplotlib" ###################################### #Check if the input filename can be treated if self.ext == None: print("figure format can't be guessed.") sys.exit(1) #Bokeh image elif self.ext == 'bokeh': #print('I got a bokeh figure') figscript, figdiv = components(self.content, wrap_script=False) #Todo get width and height from a bokeh figure if self.width == None: self.width = int(self.content.plot_width) if self.height == None: self.height = int(self.content.plot_height) #Do not cache this element if it's bokeh plot self.cache = False #Mpl figure elif self.ext == 'matplotlib': #import close to force the closing of the input figure from matplotlib.pyplot import close close(self.content) #close the figure #Set figure default width when it was not given as arguement if self.width == None: width_inch, height_inch = self.content.get_size_inches() self.width = convert_unit("%fin" % (width_inch)) #Create a special args to create a unique id for caching #Generate the figure (in binary format as jpg) from the canvas with BytesIO() as tmpb: self.content.canvas.print_jpg(tmpb) tmpb.seek(0) md5t = hashlib.md5(tmpb.read()).hexdigest() #print(md5t) #Add this new arg self.args['mpl_fig_hash'] = md5t self.mpl_fig_hash = md5t self.args_for_cache_id += ['mpl_fig_hash'] #Other filetype images else: #Add file timestamp to an arguments for caching fdate = str(os.path.getmtime(self.content)) self.args['filedate'] = fdate self.filedate = fdate self.args_for_cache_id += ['filedate'] if self.width == None: self.width = document._width #Add this module to the current slide + add positionner self.register()
def render_text( textin, args, usetex=True): """ Function to render the text using latex latex -> dvi -> dvi2svgm -> svg Use the svg output for the text in the frame """ #Check if their is width in args or if we need to use the default width if "width" in args: w = float(convert_unit(str(args['width']))) else: w = float(document._width) if usetex: #Check if a color is defined in args if 'fill' in args: if "#" in args['fill']: textin = r'{\color[HTML]{%s} %s }'%(args['fill'].replace('#','').upper(), textin) else: textin =r'{\color{%s} %s }'%(args['fill'], textin) if 'center' in args['align']: texalign = r'\centering' else: texalign = '' #fontsize{size}{interlinear_size} pretex = r""" \documentclass[crop=True]{standalone} \usepackage[utf8x]{inputenc} \usepackage{fix-cm} \usepackage{hyperref} \usepackage[svgnames]{xcolor} \renewcommand{\familydefault}{\sfdefault} \usepackage{varwidth} \usepackage{amsmath} \usepackage{amsfonts} \usepackage{amssymb} \special{html} \begin{document} \begin{varwidth}{%ipt} %s \fontsize{%i}{%i}\selectfont %s \end{varwidth} \end{document} """%(w*(72.27/96.),texalign,args['font-size'],(args['font-size']+args['font-size']*0.1),textin) #96/72.27 pt_to_px for latex #latex2svg testsvg = latex2svg( pretex ) if testsvg == '': print("Latex Compilation Error") print("Beampy Input") print(pretex) sys.exit(0) #Parse the ouput with beautifullsoup soup = BeautifulSoup(testsvg, 'xml') svgsoup = soup.find('svg') #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 #Create unique_id_ with time text_id = ("%0.2f"%time.time()).split('.')[-1] for path in soup.find_all('path'): pid = path.get('id') new_pid = '%s_%i'%(text_id, document._global_counter['path']) testsvg = re.sub(pid,new_pid, testsvg) #path['id'] = new_pid //Need to change also the id ine each use elements ... replace (above) is simpler document._global_counter['path'] += 1 #Reparse the svg soup = BeautifulSoup(testsvg, 'xml') #Change id in svg defs to use the global id system soup = make_global_svg_defs(soup) svgsoup = soup.find('svg') xinit, yinit, text_width, text_height = svgsoup.get('viewBox').split() text_width = float(text_width) text_height = float(text_height) #Use the first <use> in svg to get the y of the first letter try: uses = soup.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("Baseline one TeX error and is put to 0") #print baseline #Get the group tag to get the transform matrix to add yoffset g = soup.find('g') transform_matrix = g.get('transform') if 'va' in args and args['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['viewBox'] = svgsoup.get('viewBox') output = svgsoup.renderContents() #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 args['x'] = convert_unit(args['x']) args['y'] = convert_unit(args['y']) args = ' '.join( [str(arg)+"='"+str(val)+"'" for arg, val in args.iteritems()] ) output = "<text %s>%s</text>"%(args, textin.decode('utf-8')) #TODO: Need to fix the estimation of te width #print("[WARNING!!!] Width of classic svg text can't be estimated") text_width = 0 text_height = 0 return output, text_width, text_height
def render(self): """ function to render figures """ #Svg // pdf render if self.ext in ('svg', 'pdf', 'matplotlib') : #Convert pdf to svg if self.ext == 'pdf' : figurein = convert_pdf_to_svg( self.content ) #Convert matplotlib figure to svg elif self.ext == 'matplotlib': #Store mpl svg to a stringIO object with StringIO() as tmpf: self.content.savefig(tmpf, bbox_inches='tight', format='svg') tmpf.seek(0) #go to the biginig of the file #store tmpf content as string in figurein variable figurein = tmpf.read().encode('utf-8') #General case for svg format else: #Check if a filename is given for a svg file or directly read the content value if os.path.isfile(self.content): with open(self.content) as f: figurein = f.read() else: figurein = self.content #test if we need to optimise the svg if document._optimize_svg: figurein = optimize_svg(figurein) soup = BeautifulSoup(figurein, 'xml') #Change id in svg defs to use the global id system soup = make_global_svg_defs(soup) #Optimize the size of embeded svg images ! if document._resize_raster: imgs = soup.findAll('image') if imgs: for img in imgs: #True width and height of embed svg image width, height = int( float(img['width']) ) , int( float(img['height']) ) img_ratio = height/float(width) b64content = img['xlink:href'] try: in_img = BytesIO( base64.b64decode(b64content.split(';base64,')[1]) ) tmp_img = Image.open(in_img) #print(tmp_img) out_img = resize_raster_image( tmp_img ) out_b64 = base64.b64encode( out_img.read() ) #replace the resized image into the svg img['xlink:href'] = 'data:image/%s;base64, %s'%(tmp_img.format.lower(), out_b64) except: print('Unable to reduce the image size') pass svgtag = soup.find('svg') svg_viewbox = svgtag.get("viewBox") tmph = svgtag.get("height") tmpw = svgtag.get("width") if tmph == None or tmpw == None: fmpf, tmpname = tempfile.mkstemp(prefix="beampytmp") with open( tmpname+'.svg', 'w' ) as f: f.write(figurein) #print figurein tmph = getsvgheight( tmpname+'.svg' ) tmpw = getsvgwidth( tmpname+'.svg' ) #print tmpw, tmph os.remove(tmpname+'.svg') svgheight = convert_unit( tmph ) svgwidth = convert_unit( tmpw ) if svg_viewbox != None: svgheight = svg_viewbox.split(' ')[3] svgwidth = svg_viewbox.split(' ')[2] #SCALE OK need to keep the original viewBox !!! scale_x = self.positionner.width/float(svgwidth) #print svgwidth, svgheight, scale_x #scale_y = float(convert_unit(args['height']))/float(svgheight) good_scale = scale_x #BS4 get the svg tag content without <svg> and </svg> tmpfig = svgtag.renderContents() #Add the correct first line and last tmphead = '\n<g transform="scale(%0.5f)">'%(good_scale) output = tmphead + tmpfig + '</g>\n' figure_height = float(svgheight)*good_scale figure_width = self.width #Update the final svg size self.update_size(figure_width, figure_height) #Add the final svg output of the figure self.svgout = output #Bokeh images if self.ext == 'bokeh': # Run the bokeh components function to separate figure html div and js script figscript, figdiv = components(self.content, wrap_script=False) # Transform figscript to givea function name load_bokehjs tmp = figscript.splitlines() goodscript = '\n'.join( ['["load_bokeh"] = function() {'] + tmp[1:-1] + ['};\n'] ) #Add the htmldiv to htmlout self.htmlout = figdiv #Add the script to scriptout self.jsout = goodscript #For the other format if self.ext in ('png', 'jpeg'): #Open image with PIL to compute size tmp_img = Image.open(self.content) _,_,tmpwidth,tmpheight = tmp_img.getbbox() scale_x = self.positionner.width/float(tmpwidth) figure_height = float(tmpheight) * scale_x figure_width = self.positionner.width if document._resize_raster: #Rescale figure to the good size (to improve size and display speed) out_img = resize_raster_image(tmp_img) figurein = base64.b64encode(out_img.read()) out_img.close() else: with open( self.content, "r") as f: figurein = base64.b64encode(f.read()) tmp_img.close() if self.ext == 'png': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/png;base64, %s" />'%(figure_width, figure_height, figurein) if self.ext == 'jpeg': output = '<image x="0" y="0" width="%s" height="%s" xlink:href="data:image/jpg;base64, %s" />'%(figure_width, figure_height, figurein) #Update the final size of the figure self.update_size(figure_width, figure_height) #Add the final svg to svgout self.svgout = output #print self.width, self.height #Update the rendered state of the module self.rendered = True
def arrow(x, y, dx, dy, style='->', color="black", lw='2pt', in_angle=None, out_angle=None, bend=None, head_style=""): """ function to draw arrows on slide Arguments --------- x: initial x position of the arrow y: initial y position dx: end of arrow relative to x dy: end of arrow relative to y style ["->"]: arrow style "-": a simple line "->": simple arrow "<->": two way arrows color ['black']: arrow color (you can use svgnames) lw ["2pt"]: set the line width in point in_angle [None]: angle at the end of the arrow out_angle [None]: angle at the start of the arrow bend [None]: direction of arrow bending "left" or "right" head_style [""]: change head_style of arrow: "latex", "stealth" """ tikz_cmd = r""" \coordinate (a) at ({x},{y}); \coordinate (b) at ({xf},{yf}); \path[{style}, {color}, line width={lw}, {headstyle}] (a) edge {options} (b); """ #Convert px to pt (tikz doesn't accept px) cmdx = "%0.1fpt"%(float(convert_unit(str(dx))) * 72.27/96.0) cmdy = "%0.1fpt"%(float(convert_unit(str(dy))) * 72.27/96.0) if head_style != "": head_style = ">=%s"%(head_style) options=[] if bend != None: options += ['bend %s'%bend] if in_angle != None: options += ['in=%0.1f'%in_angle] if out_angle != None: options += ['out=%0.1f'%out_angle] options = ','.join(options) if options != '': options = '[%s]'%options tikz_cmd = tikz_cmd.format( x=0, y=0, xf=cmdx, yf=cmdy, style=style, color=color, lw=lw, headstyle=head_style, options=options ) #Run tikz command if dx < 0: tmpanchor='right' else: tmpanchor='left' if dy < 0: tmpanchor += '_top' else: tmpanchor += '_bottom' return tikz( tikz_cmd, x=x, y=y, figure_anchor=tmpanchor)
def itemize(items_list, **kwargs): ''' Generates a list or an enumeration. Parameters ---------- items_list : list of str List of item sentences. x : int or float or {'center', 'auto'} or str, optional Horizontal position for the item list (the default is 'center'). See positioning system of Beampy. y : int or float or {'center', 'auto'} or str, optional Vertical position for the item list (the default is 'auto'). See positioning system of Beampy. width : int or float or None, optional Width of the group containing items (the default is None, which implies that the width is computed to fit the longest item width). item_style : {'bullet','number'} or str, optional Style of the item markers (the default theme sets this value to 'bullet', which implies that item marker decorator is a bullet). The bullet could be replaced by any string, including latex symbols. When `item_style`='number', the item makers is an increasing number to create an enumeration. item_spacing : int or float or str, optional Vertical spacing between items (the default theme sets this value to '+1cm'). `item_spacing` accepts the same values as Beampy `x` or `y`. item_indent : int or float or str, optional Horizontal item indent (the default theme sets this value to '0cm'). `item_indent` accepts the same values as Beampy `x` or `y`. item_color : str, optional Color of item marker (the default theme sets this value to doc._theme['title']['color']). Color could be given as svg-color-names or HTML color hex values (expl: #fffff for white). text_color : str, optional Color of the item texts (the default theme sets this value to doc._theme['text']['color']). Color could be given as svg-color-names or HTML color hex values (expl: #fffff for white). item_layers : (list of int or string) or None, optional Place items into layers to animate them (the default theme sets this value to None, which implies that all items are displayed on the same layer). The list should have the same length as the `items_list`. The item in `item_layers` list could refers to a given layer number, given as int, or use python list index syntax (like ':', ':-1', '3:') given as string. >>> itemize(['item1 on all layers', 'item2 on layer 1'], item_layers=[':',1]) ''' args = check_function_args(itemize, kwargs) number = 1 if args['width'] is not None: in_width = float(convert_unit(args['width'])) - float( convert_unit(args['item_indent'])) else: in_width = float(document._width) - float( convert_unit(args['item_indent'])) if args['item_layers'] is not None: if len(items_list) != len(args['item_layers']): raise ValueError( 'Length of item_layers is not the same as the length of items_list' ) with group(width=args['width'], x=args['x'], y=args['y']) as groupitem: for i, the_item in enumerate(items_list): if args['item_style'] == 'bullet': item_char = r'$\bullet$' elif args['item_style'] == 'number': item_char = str(number) + r'.' number += 1 else: item_char = args['item_style'] # Add color item_char = color_text(item_char, args['item_color']) the_item = color_text(the_item, args['text_color']) if i == 0: t = text(item_char + r' ' + the_item, x=args['item_indent'], y=0, width=in_width) else: t = text(item_char + r' ' + the_item, x=args['item_indent'], y=args['item_spacing'], width=in_width) # Add layers to item if args['item_layers'] is not None: layer = args['item_layers'][i] if isinstance(layer, str): eval('t[%s]' % layer) else: t[args['item_layers'][i]] return groupitem