def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): log.info('Using stylesheets: %s' % ','.join(flist)) # find base path if hasattr(sys, 'frozen'): self.PATH = abspath(dirname(sys.executable)) else: self.PATH = abspath(dirname(__file__)) # flist is a list of stylesheet filenames. # They will be loaded and merged in order. # but the two default stylesheets will always # be loaded first flist = [join(self.PATH, 'styles', 'styles.style'), join(self.PATH, 'styles', 'default.style')] + flist self.def_dpi=def_dpi if font_path is None: font_path=[] font_path+=['.', os.path.join(self.PATH, 'fonts')] self.FontSearchPath = map(os.path.expanduser, font_path) if style_path is None: style_path=[] style_path+=['.', os.path.join(self.PATH, 'styles'), '~/.rst2pdf/styles'] self.StyleSearchPath = map(os.path.expanduser, style_path) self.FontSearchPath=list(set(self.FontSearchPath)) self.StyleSearchPath=list(set(self.StyleSearchPath)) log.info('FontPath:%s'%self.FontSearchPath) log.info('StylePath:%s'%self.StyleSearchPath) findfonts.flist = self.FontSearchPath # Page width, height self.pw = 0 self.ph = 0 # Page size [w,h] self.ps = None # Margins (top,bottom,left,right,gutter) self.tm = 0 self.bm = 0 self.lm = 0 self.rm = 0 self.gm = 0 #text width self.tw = 0 # Default emsize, later it will be the fontSize of the base style self.emsize=10 self.languages = [] ssdata = self.readSheets(flist) # Get pageSetup data from all stylessheets in order: self.ps = pagesizes.A4 self.page={} for data, ssname in ssdata: page = data.get('pageSetup', {}) if page: self.page.update(page) pgs=page.get('size', None) if pgs: # A standard size pgs=pgs.upper() if pgs in pagesizes.__dict__: self.ps = list(pagesizes.__dict__[pgs]) self.psname = pgs if 'width' in self.page: del(self.page['width']) if 'height' in self.page: del(self.page['height']) elif pgs.endswith('-LANDSCAPE'): self.psname = pgs.split('-')[0] self.ps = list(pagesizes.landscape(pagesizes.__dict__[self.psname])) if 'width' in self.page: del(self.page['width']) if 'height' in self.page: del(self.page['height']) else: log.critical('Unknown page size %s in stylesheet %s'%\ (page['size'], ssname)) continue else: #A custom size if 'size'in self.page: del(self.page['size']) # The sizes are expressed in some unit. # For example, 2cm is 2 centimeters, and we need # to do 2*cm (cm comes from reportlab.lib.units) if 'width' in page: self.ps[0] = self.adjustUnits(page['width']) if 'height' in page: self.ps[1] = self.adjustUnits(page['height']) self.pw, self.ph = self.ps if 'margin-left' in page: self.lm = self.adjustUnits(page['margin-left']) if 'margin-right' in page: self.rm = self.adjustUnits(page['margin-right']) if 'margin-top' in page: self.tm = self.adjustUnits(page['margin-top']) if 'margin-bottom' in page: self.bm = self.adjustUnits(page['margin-bottom']) if 'margin-gutter' in page: self.gm = self.adjustUnits(page['margin-gutter']) if 'spacing-header' in page: self.ts = self.adjustUnits(page['spacing-header']) if 'spacing-footer' in page: self.bs = self.adjustUnits(page['spacing-footer']) if 'firstTemplate' in page: self.firstTemplate = page['firstTemplate'] # tw is the text width. # We need it to calculate header-footer height # and compress literal blocks. self.tw = self.pw - self.lm - self.rm - self.gm # Get page templates from all stylesheets self.pageTemplates = {} for data, ssname in ssdata: templates = data.get('pageTemplates', {}) # templates is a dictionary of pageTemplates for key in templates: template = templates[key] # template is a dict. # template[´frames'] is a list of frames if key in self.pageTemplates: self.pageTemplates[key].update(template) else: self.pageTemplates[key] = template # Get font aliases from all stylesheets in order self.fontsAlias = {} for data, ssname in ssdata: self.fontsAlias.update(data.get('fontsAlias', {})) embedded_fontnames = [] self.embedded = [] # Embed all fonts indicated in all stylesheets for data, ssname in ssdata: embedded = data.get('embeddedFonts', []) for font in embedded: try: # Just a font name, try to embed it if isinstance(font, unicode): # See if we can find the font fname, pos = findfonts.guessFont(font) if font in embedded_fontnames: pass else: fontList = findfonts.autoEmbed(font) if fontList: embedded_fontnames.append(font) if not fontList: if (fname, pos) in embedded_fontnames: fontList = None else: fontList = findfonts.autoEmbed(fname) if fontList is not None: self.embedded += fontList # Maybe the font we got is not called # the same as the one we gave # so check that out suff = ["", "-Oblique", "-Bold", "-BoldOblique"] if not fontList[0].startswith(font): # We need to create font aliases, and use them for fname, aliasname in zip( fontList, [font + suffix for suffix in suff]): self.fontsAlias[aliasname] = fname continue # Each "font" is a list of four files, which will be # used for regular / bold / italic / bold+italic # versions of the font. # If your font doesn't have one of them, just repeat # the regular font. # Example, using the Tuffy font from # http://tulrich.com/fonts/ # "embeddedFonts" : [ # ["Tuffy.ttf", # "Tuffy_Bold.ttf", # "Tuffy_Italic.ttf", # "Tuffy_Bold_Italic.ttf"] # ], # The fonts will be registered with the file name, # minus the extension. if font[0].lower().endswith('.ttf'): # A True Type font for variant in font: location=self.findFont(variant) pdfmetrics.registerFont( TTFont(str(variant.split('.')[0]), location)) log.info('Registering font: %s from %s'%\ (str(variant.split('.')[0]),location)) self.embedded.append(str(variant.split('.')[0])) # And map them all together regular, bold, italic, bolditalic = [ variant.split('.')[0] for variant in font] addMapping(regular, 0, 0, regular) addMapping(regular, 0, 1, italic) addMapping(regular, 1, 0, bold) addMapping(regular, 1, 1, bolditalic) else: # A Type 1 font # For type 1 fonts we require # [FontName,regular,italic,bold,bolditalic] # where each variant is a (pfbfile,afmfile) pair. # For example, for the URW palladio from TeX: # ["Palatino",("uplr8a.pfb","uplr8a.afm"), # ("uplri8a.pfb","uplri8a.afm"), # ("uplb8a.pfb","uplb8a.afm"), # ("uplbi8a.pfb","uplbi8a.afm")] faceName = font[0] regular = pdfmetrics.EmbeddedType1Face(*font[1]) italic = pdfmetrics.EmbeddedType1Face(*font[2]) bold = pdfmetrics.EmbeddedType1Face(*font[3]) bolditalic = pdfmetrics.EmbeddedType1Face(*font[4]) except Exception, e: try: if isinstance(font, list): fname = font[0] else: fname = font log.error("Error processing font %s: %s", os.path.splitext(fname)[0], str(e)) log.error("Registering %s as Helvetica alias", fname) self.fontsAlias[fname] = 'Helvetica' except Exception, e: log.critical("Error processing font %s: %s", fname, str(e)) continue
"Courier-BoldOblique", "Courier-Oblique", "Helvetica", "Helvetica-Bold", "Helvetica-BoldOblique", "Helvetica-Oblique", "Symbol", "Times-Bold", "Times-BoldItalic", "Times-Italic", "Times-Roman", "ZapfDingbats"): continue # Now we need to do something # See if we can find the font fname, pos = findfonts.guessFont(style[key]) if style[key] in embedded_fontnames: pass else: fontList = findfonts.autoEmbed(style[key]) if fontList: embedded_fontnames.append(style[key]) if not fontList: if (fname, pos) in embedded_fontnames: fontList = None else: fontList = findfonts.autoEmbed(fname) if fontList: embedded_fontnames.append((fname, pos)) if fontList:
def __init__(self, flist, font_path=None, style_path=None, def_dpi=300): log.info('Using stylesheets: %s' % ','.join(flist)) # find base path if hasattr(sys, 'frozen'): self.PATH = abspath(dirname(sys.executable)) else: self.PATH = abspath(dirname(__file__)) # flist is a list of stylesheet filenames. # They will be loaded and merged in order. # but the two default stylesheets will always # be loaded first flist = [ join(self.PATH, 'styles', 'styles.style'), join(self.PATH, 'styles', 'default.style') ] + flist self.def_dpi = def_dpi if font_path is None: font_path = [] font_path += ['.', os.path.join(self.PATH, 'fonts')] self.FontSearchPath = map(os.path.expanduser, font_path) if style_path is None: style_path = [] style_path += [ '.', os.path.join(self.PATH, 'styles'), '~/.rst2pdf/styles' ] self.StyleSearchPath = map(os.path.expanduser, style_path) self.FontSearchPath = list(set(self.FontSearchPath)) self.StyleSearchPath = list(set(self.StyleSearchPath)) log.info('FontPath:%s' % self.FontSearchPath) log.info('StylePath:%s' % self.StyleSearchPath) findfonts.flist = self.FontSearchPath # Page width, height self.pw = 0 self.ph = 0 # Page size [w,h] self.ps = None # Margins (top,bottom,left,right,gutter) self.tm = 0 self.bm = 0 self.lm = 0 self.rm = 0 self.gm = 0 #text width self.tw = 0 # Default emsize, later it will be the fontSize of the base style self.emsize = 10 self.languages = [] ssdata = self.readSheets(flist) # Get pageSetup data from all stylessheets in order: self.ps = pagesizes.A4 self.page = {} for data, ssname in ssdata: page = data.get('pageSetup', {}) if page: self.page.update(page) pgs = page.get('size', None) if pgs: # A standard size pgs = pgs.upper() if pgs in pagesizes.__dict__: self.ps = list(pagesizes.__dict__[pgs]) self.psname = pgs if 'width' in self.page: del (self.page['width']) if 'height' in self.page: del (self.page['height']) elif pgs.endswith('-LANDSCAPE'): self.psname = pgs.split('-')[0] self.ps = list( pagesizes.landscape( pagesizes.__dict__[self.psname])) if 'width' in self.page: del (self.page['width']) if 'height' in self.page: del (self.page['height']) else: log.critical('Unknown page size %s in stylesheet %s'%\ (page['size'], ssname)) continue else: #A custom size if 'size' in self.page: del (self.page['size']) # The sizes are expressed in some unit. # For example, 2cm is 2 centimeters, and we need # to do 2*cm (cm comes from reportlab.lib.units) if 'width' in page: self.ps[0] = self.adjustUnits(page['width']) if 'height' in page: self.ps[1] = self.adjustUnits(page['height']) self.pw, self.ph = self.ps if 'margin-left' in page: self.lm = self.adjustUnits(page['margin-left']) if 'margin-right' in page: self.rm = self.adjustUnits(page['margin-right']) if 'margin-top' in page: self.tm = self.adjustUnits(page['margin-top']) if 'margin-bottom' in page: self.bm = self.adjustUnits(page['margin-bottom']) if 'margin-gutter' in page: self.gm = self.adjustUnits(page['margin-gutter']) if 'spacing-header' in page: self.ts = self.adjustUnits(page['spacing-header']) if 'spacing-footer' in page: self.bs = self.adjustUnits(page['spacing-footer']) if 'firstTemplate' in page: self.firstTemplate = page['firstTemplate'] # tw is the text width. # We need it to calculate header-footer height # and compress literal blocks. self.tw = self.pw - self.lm - self.rm - self.gm # Get page templates from all stylesheets self.pageTemplates = {} for data, ssname in ssdata: templates = data.get('pageTemplates', {}) # templates is a dictionary of pageTemplates for key in templates: template = templates[key] # template is a dict. # template[´frames'] is a list of frames if key in self.pageTemplates: self.pageTemplates[key].update(template) else: self.pageTemplates[key] = template # Get font aliases from all stylesheets in order self.fontsAlias = {} for data, ssname in ssdata: self.fontsAlias.update(data.get('fontsAlias', {})) embedded_fontnames = [] self.embedded = [] # Embed all fonts indicated in all stylesheets for data, ssname in ssdata: embedded = data.get('embeddedFonts', []) for font in embedded: try: # Just a font name, try to embed it if isinstance(font, unicode): # See if we can find the font fname, pos = findfonts.guessFont(font) if font in embedded_fontnames: pass else: fontList = findfonts.autoEmbed(font) if fontList: embedded_fontnames.append(font) if not fontList: if (fname, pos) in embedded_fontnames: fontList = None else: fontList = findfonts.autoEmbed(fname) if fontList is not None: self.embedded += fontList # Maybe the font we got is not called # the same as the one we gave # so check that out suff = ["", "-Oblique", "-Bold", "-BoldOblique"] if not fontList[0].startswith(font): # We need to create font aliases, and use them for fname, aliasname in zip( fontList, [font + suffix for suffix in suff]): self.fontsAlias[aliasname] = fname continue # Each "font" is a list of four files, which will be # used for regular / bold / italic / bold+italic # versions of the font. # If your font doesn't have one of them, just repeat # the regular font. # Example, using the Tuffy font from # http://tulrich.com/fonts/ # "embeddedFonts" : [ # ["Tuffy.ttf", # "Tuffy_Bold.ttf", # "Tuffy_Italic.ttf", # "Tuffy_Bold_Italic.ttf"] # ], # The fonts will be registered with the file name, # minus the extension. if font[0].lower().endswith('.ttf'): # A True Type font for variant in font: location = self.findFont(variant) pdfmetrics.registerFont( TTFont(str(variant.split('.')[0]), location)) log.info('Registering font: %s from %s'%\ (str(variant.split('.')[0]),location)) self.embedded.append(str(variant.split('.')[0])) # And map them all together regular, bold, italic, bolditalic = [ variant.split('.')[0] for variant in font ] addMapping(regular, 0, 0, regular) addMapping(regular, 0, 1, italic) addMapping(regular, 1, 0, bold) addMapping(regular, 1, 1, bolditalic) else: # A Type 1 font # For type 1 fonts we require # [FontName,regular,italic,bold,bolditalic] # where each variant is a (pfbfile,afmfile) pair. # For example, for the URW palladio from TeX: # ["Palatino",("uplr8a.pfb","uplr8a.afm"), # ("uplri8a.pfb","uplri8a.afm"), # ("uplb8a.pfb","uplb8a.afm"), # ("uplbi8a.pfb","uplbi8a.afm")] faceName = font[0] regular = pdfmetrics.EmbeddedType1Face(*font[1]) italic = pdfmetrics.EmbeddedType1Face(*font[2]) bold = pdfmetrics.EmbeddedType1Face(*font[3]) bolditalic = pdfmetrics.EmbeddedType1Face(*font[4]) except Exception as e: try: if isinstance(font, list): fname = font[0] else: fname = font log.error("Error processing font %s: %s", os.path.splitext(fname)[0], str(e)) log.error("Registering %s as Helvetica alias", fname) self.fontsAlias[fname] = 'Helvetica' except Exception as e: log.critical("Error processing font %s: %s", fname, str(e)) continue # Go though all styles in all stylesheets and find all fontNames. # Then decide what to do with them for data, ssname in ssdata: for [skey, style] in self.stylepairs(data): for key in style: if key == 'fontName' or key.endswith('FontName'): # It's an alias, replace it if style[key] in self.fontsAlias: style[key] = self.fontsAlias[style[key]] # Embedded already, nothing to do if style[key] in self.embedded: continue # Standard font, nothing to do if style[key] in ("Courier", "Courier-Bold", "Courier-BoldOblique", "Courier-Oblique", "Helvetica", "Helvetica-Bold", "Helvetica-BoldOblique", "Helvetica-Oblique", "Symbol", "Times-Bold", "Times-BoldItalic", "Times-Italic", "Times-Roman", "ZapfDingbats"): continue # Now we need to do something # See if we can find the font fname, pos = findfonts.guessFont(style[key]) if style[key] in embedded_fontnames: pass else: fontList = findfonts.autoEmbed(style[key]) if fontList: embedded_fontnames.append(style[key]) if not fontList: if (fname, pos) in embedded_fontnames: fontList = None else: fontList = findfonts.autoEmbed(fname) if fontList: embedded_fontnames.append((fname, pos)) if fontList: self.embedded += fontList # Maybe the font we got is not called # the same as the one we gave so check that out suff = ["", "-Bold", "-Oblique", "-BoldOblique"] if not fontList[0].startswith(style[key]): # We need to create font aliases, and use them basefname = style[key].split('-')[0] for fname, aliasname in zip( fontList, [basefname + suffix for suffix in suff]): self.fontsAlias[aliasname] = fname style[key] = self.fontsAlias[basefname +\ suff[pos]] else: log.error( "Unknown font: \"%s\"," "replacing with Helvetica", style[key]) style[key] = "Helvetica" #log.info('FontList: %s'%self.embedded) #log.info('FontAlias: %s'%self.fontsAlias) # Get styles from all stylesheets in order self.stylesheet = {} self.styles = [] self.linkColor = 'navy' # FIXME: linkColor should probably not be a global # style, and tocColor should probably not # be a special case, but for now I'm going # with the flow... self.tocColor = None for data, ssname in ssdata: self.linkColor = data.get('linkColor') or self.linkColor self.tocColor = data.get('tocColor') or self.tocColor for [skey, style] in self.stylepairs(data): sdict = {} # FIXME: this is done completely backwards for key in style: # Handle color references by name if key == 'color' or key.endswith('Color') and style[key]: style[key] = formatColor(style[key]) # Yet another workaround for the unicode bug in # reportlab's toColor elif key == 'commands': style[key] = validateCommands(style[key]) #for command in style[key]: #c=command[0].upper() #if c=='ROWBACKGROUNDS': #command[3]=[str(c) for c in command[3]] #elif c in ['BOX','INNERGRID'] or c.startswith('LINE'): #command[4]=str(command[4]) # Handle alignment constants elif key == 'alignment': style[key] = dict( TA_LEFT=0, LEFT=0, TA_CENTER=1, CENTER=1, TA_CENTRE=1, CENTRE=1, TA_RIGHT=2, RIGHT=2, TA_JUSTIFY=4, JUSTIFY=4, DECIMAL=8, )[style[key].upper()] elif key == 'language': if not style[key] in self.languages: self.languages.append(style[key]) # Make keys str instead of unicode (required by reportlab) sdict[str(key)] = style[key] sdict['name'] = skey # If the style already exists, update it if skey in self.stylesheet: self.stylesheet[skey].update(sdict) else: # New style self.stylesheet[skey] = sdict self.styles.append(sdict) # If the stylesheet has a style name docutils won't reach # make a copy with a sanitized name. # This may make name collisions possible but that should be # rare (who would have custom_name and custom-name in the # same stylesheet? ;-) # Issue 339 styles2 = [] for s in self.styles: if not re.match("^[a-z](-?[a-z0-9]+)*$", s['name']): s2 = copy(s) s2['name'] = docutils.nodes.make_id(s['name']) log.warning( '%s is an invalid docutils class name, adding alias %s' % (s['name'], s2['name'])) styles2.append(s2) self.styles.extend(styles2) # And create reportlabs stylesheet self.StyleSheet = StyleSheet1() # Patch to make the code compatible with reportlab from SVN 2.4+ and # 2.4 if not hasattr(self.StyleSheet, 'has_key'): self.StyleSheet.__class__.has_key = lambda s, k: k in s for s in self.styles: if 'parent' in s: if s['parent'] is None: if s['name'] != 'base': s['parent'] = self.StyleSheet['base'] else: del (s['parent']) else: s['parent'] = self.StyleSheet[s['parent']] else: if s['name'] != 'base': s['parent'] = self.StyleSheet['base'] # If the style has no bulletFontName but it has a fontName, set it if ('bulletFontName' not in s) and ('fontName' in s): s['bulletFontName'] = s['fontName'] hasFS = True # Adjust fontsize units if 'fontSize' not in s: s['fontSize'] = s['parent'].fontSize s['trueFontSize'] = None hasFS = False elif 'parent' in s: # This means you can set the fontSize to # "2cm" or to "150%" which will be calculated # relative to the parent style s['fontSize'] = self.adjustUnits(s['fontSize'], s['parent'].fontSize) s['trueFontSize'] = s['fontSize'] else: # If s has no parent, it's base, which has # an explicit point size by default and % # makes no sense, but guess it as % of 10pt s['fontSize'] = self.adjustUnits(s['fontSize'], 10) # If the leading is not set, but the size is, set it if 'leading' not in s and hasFS: s['leading'] = 1.2 * s['fontSize'] # If the bullet font size is not set, set it as fontSize if ('bulletFontSize' not in s) and ('fontSize' in s): s['bulletFontSize'] = s['fontSize'] # If the borderPadding is a list and wordaxe <=0.3.2, # convert it to an integer. Workaround for Issue if 'borderPadding' in s and ((HAS_WORDAXE and \ wordaxe_version <='wordaxe 0.3.2') or reportlab.Version < "2.3" )\ and isinstance(s['borderPadding'], list): log.warning('Using a borderPadding list in '\ 'style %s with wordaxe <= 0.3.2 or Reportlab < 2.3. That is not '\ 'supported, so it will probably look wrong'%s['name']) s['borderPadding'] = s['borderPadding'][0] self.StyleSheet.add(ParagraphStyle(**s)) self.emsize = self['base'].fontSize # Make stdFont the basefont, for Issue 65 reportlab.rl_config.canvas_basefontname = self['base'].fontName # Make stdFont the default font for table cell styles (Issue 65) reportlab.platypus.tables.CellStyle.fontname = self['base'].fontName