示例#1
0
    def drawOn(self, canv, x, y, _sW=0):
        if _sW and hasattr(self,'hAlign'):
            from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
            a = self.hAlign
            if a in ('CENTER','CENTRE', TA_CENTER):
                x = x + 0.5*_sW
            elif a in ('RIGHT',TA_RIGHT):
                x = x + _sW
            elif a not in ('LEFT',TA_LEFT):
                raise ValueError("Bad hAlign value "+str(a))
        height = 0
        if HAS_MATPLOTLIB:
            global fonts
            canv.saveState()
            canv.translate(x, y)
            try:
                width, height, descent, glyphs, \
                rects, used_characters = self.parser.parse(
                    enclose(self.s), 72, prop=FontProperties(size=self.fontsize))
                for ox, oy, fontname, fontsize, num, symbol_name in glyphs:
                    if not fontname in fonts:
                        fonts[fontname] = fontname
                        pdfmetrics.registerFont(TTFont(fontname, fontname))
                    canv.setFont(fontname, fontsize)
                    col_conv=ColorConverter()
                    rgb_color=col_conv.to_rgb(self.color)
                    canv.setFillColorRGB(rgb_color[0],rgb_color[1],rgb_color[2])
                    if six.PY2:
                        canv.drawString(ox, oy, unichr(num))
                    else:
                        canv.drawString(ox, oy, chr(num))

                canv.setLineWidth(0)
                canv.setDash([])
                for ox, oy, width, height in rects:
                    canv.rect(ox, oy+2*height, width, height, fill=1)
            except:
                # FIXME: report error
                col_conv=ColorConverter()
                rgb_color=col_conv.to_rgb(self.color)
                canv.setFillColorRGB(rgb_color[0],rgb_color[1],rgb_color[2])
                canv.drawString(0,0,self.s)
            canv.restoreState()
        else:
            canv.saveState()
            canv.drawString(x, y, self.s)
            canv.restoreState()
        if self.label:
            log.info('Drawing equation-%s'%self.label)
            canv.bookmarkHorizontal('equation-%s'%self.label,0,height)
示例#2
0
    def getstyle(self, client, node, style):
        try:
            if node['classes'] and node['classes'][0]:
                # FIXME: Supports only one class, sorry ;-)
                if node['classes'][0] in client.styles.StyleSheet:
                    style = client.styles[node['classes'][0]]
                else:
                    log.info("Unknown class %s, ignoring. [%s]",
                             node['classes'][0], nodeid(node))
        except TypeError:  # Happens when a docutils.node.Text reaches here
            pass

        if style is None or style == client.styles['bodytext']:
            style = client.styles.styleForNode(node)
        return style
    def getstyle(self, client, node, style):
        try:
            if node['classes'] and node['classes'][0]:
                # FIXME: Supports only one class, sorry ;-)
                if node['classes'][0] in client.styles.StyleSheet:
                    style = client.styles[node['classes'][0]]
                else:
                    log.info("Unknown class %s, ignoring. [%s]",
                        node['classes'][0], nodeid(node))
        except TypeError:  # Happens when a docutils.node.Text reaches here
            pass

        if style is None or style == client.styles['bodytext']:
            style = client.styles.styleForNode(node)
        return style
    def drawOn(self, canv, x, y, _sW=0):
        if _sW and hasattr(self, 'hAlign'):
            from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
            a = self.hAlign
            if a in ('CENTER', 'CENTRE', TA_CENTER):
                x = x + 0.5 * _sW
            elif a in ('RIGHT', TA_RIGHT):
                x = x + _sW
            elif a not in ('LEFT', TA_LEFT):
                raise ValueError("Bad hAlign value " + str(a))
        height = 0
        if HAS_MATPLOTLIB:
            global fonts
            canv.saveState()
            canv.translate(x, y)
            try:
                width, height, descent, glyphs, rects, used_characters = \
                    self.parser.parse(enclose(self.s), 72,
                                      prop=FontProperties(size=self.fontsize))
                for ox, oy, fontname, fontsize, num, symbol_name in glyphs:
                    if fontname not in fonts:
                        fonts[fontname] = fontname
                        pdfmetrics.registerFont(TTFont(fontname, fontname))
                    canv.setFont(fontname, fontsize)
                    col_conv = ColorConverter()
                    rgb_color = col_conv.to_rgb(self.color)
                    canv.setFillColorRGB(rgb_color[0], rgb_color[1], rgb_color[2])
                    canv.drawString(ox, oy, chr(num))

                canv.setLineWidth(0)
                canv.setDash([])
                for ox, oy, width, height in rects:
                    canv.rect(ox, oy + 2 * height, width, height, fill=1)
            except:
                # FIXME: report error
                col_conv = ColorConverter()
                rgb_color = col_conv.to_rgb(self.color)
                canv.setFillColorRGB(rgb_color[0], rgb_color[1], rgb_color[2])
                canv.drawString(0, 0, self.s)
            canv.restoreState()
        else:
            canv.saveState()
            canv.drawString(x, y, self.s)
            canv.restoreState()
        if self.label:
            log.info('Drawing equation-%s' % self.label)
            canv.bookmarkHorizontal('equation-%s' % self.label, 0, height)
示例#5
0
    def isSatisfied(self):
        if self._entries == self._lastEntries:
            log.debug('Table Of Contents is stable')
            return True
        else:
            if len(self._entries) != len(self._lastEntries):
                log.info('Number of items in TOC changed '\
                'from %d to %d, not satisfied' % \
                (len(self._lastEntries), len(self._entries)))
                return False

            log.info('TOC entries that moved in this pass:')
            for i in range(len(self._entries)):
                if self._entries[i] != self._lastEntries[i]:
                    log.info(str(self._entries[i]))
                    log.info(str(self._lastEntries[i]))
        return False
示例#6
0
    def isSatisfied(self):
        if self._entries == self._lastEntries:
            log.debug('Table Of Contents is stable')
            return True
        else:
            if len(self._entries) != len(self._lastEntries):
                log.info('Number of items in TOC changed '\
                'from %d to %d, not satisfied' % \
                (len(self._lastEntries), len(self._entries)))
                return False

            log.info('TOC entries that moved in this pass:')
            for i in range(len(self._entries)):
                if self._entries[i] != self._lastEntries[i]:
                    log.info(str(self._entries[i]))
                    log.info(str(self._lastEntries[i]))
        return False
示例#7
0
    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 = [os.path.expanduser(p) for p in font_path]

        if style_path is None:
            style_path = []
        style_path += ['.', os.path.join(self.PATH, 'styles'),
                       '~/.rst2pdf/styles']
        self.StyleSearchPath = [os.path.expanduser(p) for p in 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, str):
                        # 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")]
                        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.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' and
                isinstance(s['borderPadding'], list)
            ):
                log.warning(('Using a borderPadding list in style %s with ' +
                             'wordaxe <= 0.3.2. 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
示例#8
0
def loadFonts():
    """
    Search the system and build lists of available fonts.
    """
    if not afmList and not pfbList and not ttfList:
        # Find all ".afm" and ".pfb" files files
        for root in flist:
            for folder, _, names in os.walk(root):
                for f in names:
                    ext = os.path.splitext(f)[-1]
                    if ext in ['.ttf', '.ttc']:
                        ttfList.append(os.path.join(folder, f))
                    if ext == '.afm':
                        afmList.append(os.path.join(folder, f))
                    if ext == '.pfb':
                        pfbList[f[:-4]] = os.path.join(folder, f)

        for ttf in ttfList:
            #Find out how to process these
            try:
                font = TTFontFile(ttf)
            except TTFError:
                continue

            family = font.familyName.lower().decode()
            fontName = font.name.decode()
            baseName = os.path.basename(ttf)[:-4]
            fullName = font.fullName.decode()

            fonts[fontName.lower()] = (ttf, ttf, family)
            fonts[fullName.lower()] = (ttf, ttf, family)
            fonts[fullName.lower().replace('italic',
                                           'oblique')] = (ttf, ttf, family)
            bold = (FF_FORCEBOLD == FF_FORCEBOLD & font.flags)
            italic = (FF_ITALIC == FF_ITALIC & font.flags)

            # And we can try to build/fill the family mapping
            if family not in families:
                families[family] = [fontName, fontName, fontName, fontName]
            if bold and italic:
                families[family][3] = fontName
            elif bold:
                families[family][1] = fontName
            elif italic:
                families[family][2] = fontName
            # FIXME: what happens if there are Demi and Medium
            # weights? We get a random one.
            else:
                families[family][0] = fontName

        # Now we have full afm and pbf lists, process the
        # afm list to figure out family name, weight and if
        # it's italic or not, as well as where the
        # matching pfb file is

        for afm in afmList:
            family = None
            fontName = None
            italic = False
            bold = False
            for line in open(afm, 'r'):
                line = line.strip()
                if line.startswith('StartCharMetrics'):
                    break
                elif line.startswith('FamilyName'):
                    family = ' '.join(line.split(' ')[1:]).lower()
                elif line.startswith('FontName'):
                    fontName = line.split(' ')[1]
                # TODO: find a way to alias the fullname to this font
                # so you can use names like "Bitstream Charter Italic"
                elif line.startswith('FullName'):
                    fullName = ' '.join(line.split(' ')[1:])
                elif line.startswith('Weight'):
                    w = line.split(' ')[1]
                    if w == 'Bold':
                        bold = True
                elif line.startswith('ItalicAngle'):
                    if line.split(' ')[1] != '0.0':
                        italic = True

            baseName = os.path.basename(afm)[:-4]
            if family in Ignored:
                continue
            if family in Alias:
                continue
            if baseName not in pfbList:
                log.info("afm file without matching pfb file: %s" % baseName)
                continue

            # So now we have a font we know we can embed.
            fonts[fontName.lower()] = (afm, pfbList[baseName], family)
            fonts[fullName.lower()] = (afm, pfbList[baseName], family)
            fonts[fullName.lower().replace('italic', 'oblique')] = \
                (afm, pfbList[baseName], family)

            # And we can try to build/fill the family mapping
            if family not in families:
                families[family] = [fontName, fontName, fontName, fontName]
            if bold and italic:
                families[family][3] = fontName
            elif bold:
                families[family][1] = fontName
            elif italic:
                families[family][2] = fontName
            # FIXME: what happens if there are Demi and Medium
            # weights? We get a random one.
            else:
                families[family][0] = fontName
示例#9
0
    def size_for_node(self, node, client):
        """
        Given a docutils image node, return the size the image should have
        in the PDF document, and what 'kind' of size that is.
        That involves lots of guesswork.
        """
        uri = str(node.get("uri"))
        if uri.split("://")[0].lower() not in ('http', 'ftp', 'https'):
            uri = os.path.join(client.basedir, uri)
        else:
            uri, _ = urllib.request.urlretrieve(uri)
            client.to_unlink.append(uri)

        srcinfo = client, uri
        # Extract all the information from the URI
        imgname, extension, options = self.split_uri(uri)

        if not os.path.isfile(imgname):
            imgname = missing

        scale = float(node.get('scale', 100)) / 100

        # Figuring out the size to display of an image is ... annoying.
        # If the user provides a size with a unit, it's simple, adjustUnits
        # will return it in points and we're done.

        # However, often the unit wil be "%" (specially if it's meant for
        # HTML originally. In which case, we will use a percentage of
        # the containing frame.

        # Find the image size in pixels:
        kind = 'direct'
        xdpi, ydpi = client.styles.def_dpi, client.styles.def_dpi
        extension = imgname.split('.')[-1].lower()
        if extension in ['svg', 'svgz']:
            iw, ih = SVGImage(imgname, srcinfo=srcinfo).wrap(0, 0)
            # These are in pt, so convert to px
            iw = iw * xdpi / 72
            ih = ih * ydpi / 72

        elif extension == 'pdf':
            if VectorPdf is not None:
                xobj = VectorPdf.load_xobj(srcinfo)
                iw, ih = xobj.w, xobj.h
            else:
                pdf = LazyImports.pdfinfo
                if pdf is None:
                    log.warning('PDF images are not supported without pyPdf or pdfrw [%s]', nodeid(node))
                    return 0, 0, 'direct'
                reader = pdf.PdfFileReader(open(imgname, 'rb'))
                box = [float(x) for x in reader.getPage(0)['/MediaBox']]
                iw, ih = x2 - x1, y2 - y1
            # These are in pt, so convert to px
            iw = iw * xdpi / 72.0
            ih = ih * ydpi / 72.0

        else:
            keeptrying = True
            if LazyImports.PILImage:
                try:
                    img = LazyImports.PILImage.open(imgname)
                    img.load()
                    iw, ih = img.size
                    xdpi, ydpi = img.info.get('dpi', (xdpi, ydpi))
                    keeptrying = False
                except IOError:  # PIL throws this when it's a broken/unknown image
                    pass
            if keeptrying and LazyImports.PMImage:
                img = LazyImports.PMImage(imgname)
                iw = img.size().width()
                ih = img.size().height()
                density = img.density()
                # The density is in pixelspercentimeter (!?)
                xdpi = density.width() * 2.54
                ydpi = density.height() * 2.54
                keeptrying = False
            if keeptrying:
                if extension not in ['jpg', 'jpeg']:
                    log.error("The image (%s, %s) is broken or in an unknown format"
                                , imgname, nodeid(node))
                    raise ValueError
                else:
                    # Can be handled by reportlab
                    log.warning("Can't figure out size of the image (%s, %s). Install PIL for better results."
                                , imgname, nodeid(node))
                    iw = 1000
                    ih = 1000

        # Try to get the print resolution from the image itself via PIL.
        # If it fails, assume a DPI of 300, which is pretty much made up,
        # and then a 100% size would be iw*inch/300, so we pass
        # that as the second parameter to adjustUnits
        #
        # Some say the default DPI should be 72. That would mean
        # the largest printable image in A4 paper would be something
        # like 480x640. That would be awful.
        #

        w = node.get('width')
        h = node.get('height')
        if h is None and w is None:  # Nothing specified
            # Guess from iw, ih
            log.debug("Using image %s without specifying size."
                      "Calculating based on image size at %ddpi [%s]",
                      imgname, xdpi, nodeid(node))
            w = iw * inch / xdpi
            h = ih * inch / ydpi
        elif w is not None:
            # Node specifies only w
            # In this particular case, we want the default unit
            # to be pixels so we work like rst2html
            if w[-1] == '%':
                kind = 'percentage_of_container'
                w = int(w[:-1])
            else:
                # This uses default DPI setting because we
                # are not using the image's "natural size"
                # this is what LaTeX does, according to the
                # docutils mailing list discussion
                w = client.styles.adjustUnits(w, client.styles.tw,
                                              default_unit='px')

            if h is None:
                # h is set from w with right aspect ratio
                h = w * ih / iw
            else:
                h = client.styles.adjustUnits(h, ih * inch / ydpi, default_unit='px')
        elif h is not None and w is None:
            if h[-1] != '%':
                h = client.styles.adjustUnits(h, ih * inch / ydpi, default_unit='px')

                # w is set from h with right aspect ratio
                w = h * iw / ih
            else:
                log.error('Setting height as a percentage does **not** work. ' +
                          'ignoring height parameter [%s]', nodeid(node))
                # Set both from image data
                w = iw * inch / xdpi
                h = ih * inch / ydpi

        # Apply scale factor
        w = w * scale
        h = h * scale

        # And now we have this probably completely bogus size!
        log.info("Image %s size calculated:  %fcm by %fcm [%s]",
                 imgname, w / cm, h / cm, nodeid(node))

        return w, h, kind
示例#10
0
def loadFonts():
    """
    Search the system and build lists of available fonts.
    """
    if not afmList and not pfbList and not ttfList:
        # Find all ".afm" and ".pfb" files files
        for root in flist:
            for folder, _, names in os.walk(root):
                for f in names:
                    ext = os.path.splitext(f)[-1]
                    if ext in ['.ttf', '.ttc']:
                        ttfList.append(os.path.join(folder, f))
                    if ext == '.afm':
                        afmList.append(os.path.join(folder, f))
                    if ext == '.pfb':
                        pfbList[f[:-4]] = os.path.join(folder, f)

        for ttf in ttfList:
            #Find out how to process these
            try:
                font = TTFontFile(ttf)
            except TTFError:
                continue

            family = font.familyName.lower().decode()
            fontName = font.name.decode()
            baseName = os.path.basename(ttf)[:-4]
            fullName = font.fullName.decode()

            fonts[fontName.lower()] = (ttf, ttf, family)
            fonts[fullName.lower()] = (ttf, ttf, family)
            fonts[fullName.lower().replace('italic', 'oblique')] = (ttf, ttf, family)
            bold = (FF_FORCEBOLD == FF_FORCEBOLD & font.flags)
            italic = (FF_ITALIC == FF_ITALIC & font.flags)

            # And we can try to build/fill the family mapping
            if family not in families:
                families[family] = [fontName, fontName, fontName, fontName]
            if bold and italic:
                families[family][3] = fontName
            elif bold:
                families[family][1] = fontName
            elif italic:
                families[family][2] = fontName
            # FIXME: what happens if there are Demi and Medium
            # weights? We get a random one.
            else:
                families[family][0] = fontName

        # Now we have full afm and pbf lists, process the
        # afm list to figure out family name, weight and if
        # it's italic or not, as well as where the
        # matching pfb file is

        for afm in afmList:
            family = None
            fontName = None
            italic = False
            bold = False
            for line in open(afm, 'r'):
                line = line.strip()
                if line.startswith('StartCharMetrics'):
                    break
                elif line.startswith('FamilyName'):
                    family = ' '.join(line.split(' ')[1:]).lower()
                elif line.startswith('FontName'):
                    fontName = line.split(' ')[1]
                # TODO: find a way to alias the fullname to this font
                # so you can use names like "Bitstream Charter Italic"
                elif line.startswith('FullName'):
                    fullName = ' '.join(line.split(' ')[1:])
                elif line.startswith('Weight'):
                    w = line.split(' ')[1]
                    if w == 'Bold':
                        bold = True
                elif line.startswith('ItalicAngle'):
                    if line.split(' ')[1] != '0.0':
                        italic = True

            baseName = os.path.basename(afm)[:-4]
            if family in Ignored:
                continue
            if family in Alias:
                continue
            if baseName not in pfbList:
                log.info("afm file without matching pfb file: %s" % baseName)
                continue

            # So now we have a font we know we can embed.
            fonts[fontName.lower()] = (afm, pfbList[baseName], family)
            fonts[fullName.lower()] = (afm, pfbList[baseName], family)
            fonts[fullName.lower().replace('italic', 'oblique')] = \
                (afm, pfbList[baseName], family)

            # And we can try to build/fill the family mapping
            if family not in families:
                families[family] = [fontName, fontName, fontName, fontName]
            if bold and italic:
                families[family][3] = fontName
            elif bold:
                families[family][1] = fontName
            elif italic:
                families[family][2] = fontName
            # FIXME: what happens if there are Demi and Medium
            # weights? We get a random one.
            else:
                families[family][0] = fontName
示例#11
0
def autoEmbed(fname):
    """
    Given a font name, do a best-effort of embedding the font and its variants.

    Return a list of the font names it registered with ReportLab.
    """
    log.info('Trying to embed %s' % fname)
    fontList = []
    variants = []
    font = findFont(fname)
    if font:  # We have this font located
        if font[0].lower()[-4:] == '.afm':  # Type 1 font
            family = families[font[2]]

            # Register the whole family of faces
            faces = [pdfmetrics.EmbeddedType1Face(*fonts[fn.lower()][:2])
                     for fn in family]
            for face in faces:
                pdfmetrics.registerTypeFace(face)

            for face, name in zip(faces, family):
                fontList.append(name)
                font = pdfmetrics.Font(face, name, "WinAnsiEncoding")
                log.info('Registering font: %s from %s' % (face, name))
                pdfmetrics.registerFont(font)

            # Map the variants
            regular, italic, bold, bolditalic = family
            addMapping(fname, 0, 0, regular)
            addMapping(fname, 0, 1, italic)
            addMapping(fname, 1, 0, bold)
            addMapping(fname, 1, 1, bolditalic)
            addMapping(regular, 0, 0, regular)
            addMapping(regular, 0, 1, italic)
            addMapping(regular, 1, 0, bold)
            addMapping(regular, 1, 1, bolditalic)
            log.info('Embedding as %s' % fontList)
            return fontList
        else:  # A TTF font
            variants = [fonts[f.lower()][0] for f in families[font[2]]]
    if not variants:  # Try fc-match
        variants = findTTFont(fname)
    # It is a TT Font and we found it using fc-match (or found *something*)
    if variants:
        for variant in variants:
            vname = os.path.basename(variant)[:-4]
            try:
                if vname not in pdfmetrics._fonts:
                    _font = TTFont(vname, variant)
                    log.info('Registering font: %s from %s' % (vname, variant))
                    pdfmetrics.registerFont(_font)
            except TTFError:
                log.error('Error registering font: %s from %s' % (vname, variant))
            else:
                fontList.append(vname)
        regular, bold, italic, bolditalic = [
            os.path.basename(variant)[:-4] for variant in variants]
        addMapping(regular, 0, 0, regular)
        addMapping(regular, 0, 1, italic)
        addMapping(regular, 1, 0, bold)
        addMapping(regular, 1, 1, bolditalic)
        log.info('Embedding via findTTFont as %s' % fontList)
    return fontList
示例#12
0
    def size_for_node(self, node, client):
        """
        Given a docutils image node, return the size the image should have
        in the PDF document, and what 'kind' of size that is.
        That involves lots of guesswork.
        """
        uri = str(node.get("uri"))
        if uri.split("://")[0].lower() not in ('http', 'ftp', 'https'):
            uri = os.path.join(client.basedir, uri)
        else:
            uri, _ = urllib.request.urlretrieve(uri)
            client.to_unlink.append(uri)

        srcinfo = client, uri
        # Extract all the information from the URI
        imgname, extension, options = self.split_uri(uri)

        if not os.path.isfile(imgname):
            imgname = missing

        scale = float(node.get('scale', 100)) / 100

        # Figuring out the size to display of an image is ... annoying.
        # If the user provides a size with a unit, it's simple, adjustUnits
        # will return it in points and we're done.

        # However, often the unit wil be "%" (specially if it's meant for
        # HTML originally. In which case, we will use a percentage of
        # the containing frame.

        # Find the image size in pixels:
        kind = 'direct'
        xdpi, ydpi = client.styles.def_dpi, client.styles.def_dpi
        extension = imgname.split('.')[-1].lower()
        if extension in ['svg', 'svgz']:
            iw, ih = SVGImage(imgname, srcinfo=srcinfo).wrap(0, 0)
            # These are in pt, so convert to px
            iw = iw * xdpi / 72
            ih = ih * ydpi / 72

        elif extension == 'pdf':
            if VectorPdf is not None:
                xobj = VectorPdf.load_xobj(srcinfo)
                iw, ih = xobj.w, xobj.h
            else:
                pdf = LazyImports.pdfinfo
                if pdf is None:
                    log.warning(
                        'PDF images are not supported without pyPdf or pdfrw [%s]',
                        nodeid(node))
                    return 0, 0, 'direct'
                reader = pdf.PdfFileReader(open(imgname, 'rb'))
                box = [float(x) for x in reader.getPage(0)['/MediaBox']]
                iw, ih = x2 - x1, y2 - y1
            # These are in pt, so convert to px
            iw = iw * xdpi / 72.0
            ih = ih * ydpi / 72.0

        else:
            keeptrying = True
            if LazyImports.PILImage:
                try:
                    img = LazyImports.PILImage.open(imgname)
                    img.load()
                    iw, ih = img.size
                    xdpi, ydpi = img.info.get('dpi', (xdpi, ydpi))
                    keeptrying = False
                except IOError:  # PIL throws this when it's a broken/unknown image
                    pass
            if keeptrying and LazyImports.PMImage:
                img = LazyImports.PMImage(imgname)
                iw = img.size().width()
                ih = img.size().height()
                density = img.density()
                # The density is in pixelspercentimeter (!?)
                xdpi = density.width() * 2.54
                ydpi = density.height() * 2.54
                keeptrying = False
            if keeptrying:
                if extension not in ['jpg', 'jpeg']:
                    log.error(
                        "The image (%s, %s) is broken or in an unknown format",
                        imgname, nodeid(node))
                    raise ValueError
                else:
                    # Can be handled by reportlab
                    log.warning(
                        "Can't figure out size of the image (%s, %s). Install PIL for better results.",
                        imgname, nodeid(node))
                    iw = 1000
                    ih = 1000

        # Try to get the print resolution from the image itself via PIL.
        # If it fails, assume a DPI of 300, which is pretty much made up,
        # and then a 100% size would be iw*inch/300, so we pass
        # that as the second parameter to adjustUnits
        #
        # Some say the default DPI should be 72. That would mean
        # the largest printable image in A4 paper would be something
        # like 480x640. That would be awful.
        #

        w = node.get('width')
        h = node.get('height')
        if h is None and w is None:  # Nothing specified
            # Guess from iw, ih
            log.debug(
                "Using image %s without specifying size."
                "Calculating based on image size at %ddpi [%s]", imgname, xdpi,
                nodeid(node))
            w = iw * inch / xdpi
            h = ih * inch / ydpi
        elif w is not None:
            # Node specifies only w
            # In this particular case, we want the default unit
            # to be pixels so we work like rst2html
            if w[-1] == '%':
                kind = 'percentage_of_container'
                w = int(w[:-1])
            else:
                # This uses default DPI setting because we
                # are not using the image's "natural size"
                # this is what LaTeX does, according to the
                # docutils mailing list discussion
                w = client.styles.adjustUnits(w,
                                              client.styles.tw,
                                              default_unit='px')

            if h is None:
                # h is set from w with right aspect ratio
                h = w * ih / iw
            else:
                h = client.styles.adjustUnits(h,
                                              ih * inch / ydpi,
                                              default_unit='px')
        elif h is not None and w is None:
            if h[-1] != '%':
                h = client.styles.adjustUnits(h,
                                              ih * inch / ydpi,
                                              default_unit='px')

                # w is set from h with right aspect ratio
                w = h * iw / ih
            else:
                log.error(
                    'Setting height as a percentage does **not** work. ' +
                    'ignoring height parameter [%s]', nodeid(node))
                # Set both from image data
                w = iw * inch / xdpi
                h = ih * inch / ydpi

        # Apply scale factor
        w = w * scale
        h = h * scale

        # And now we have this probably completely bogus size!
        log.info("Image %s size calculated:  %fcm by %fcm [%s]", imgname,
                 w / cm, h / cm, nodeid(node))

        return w, h, kind
示例#13
0
    def get_backend(self, uri, client):
        """
        Given the filename of an image, return (fname, backend).

        fname is the filename to be used (could be the same as filename,
        or something different if the image had to be converted or is
        missing), and backend is an Image class that can handle fname.

        If uri ends with '.*' then the returned filename will be the best
        quality supported at the moment.

        That means:  PDF > SVG > anything else
        """
        backend = defaultimage

        # Extract all the information from the URI
        filename, extension, options = self.split_uri(uri)

        if '*' in filename:
            preferred = ['gif', 'jpg', 'png', 'svg', 'pdf']

            # Find out what images are available
            available = glob.glob(filename)
            cfn = available[0]
            cv = -10
            for fn in available:
                ext = fn.split('.')[-1]
                if ext in preferred:
                    v = preferred.index(ext)
                else:
                    v = -1
                if v > cv:
                    cv = v
                    cfn = fn
            # cfn should have our favourite type of
            # those available
            filename = cfn
            extension = cfn.split('.')[-1]
            uri = filename

        # If the image doesn't exist, we use a 'missing' image
        if not os.path.exists(filename):
            log.error("Missing image file: %s", filename)
            filename = missing

        if extension in ['svg', 'svgz']:
            log.info('Backend for %s is SVGIMage' % filename)
            backend = SVGImage

        elif extension in ['pdf']:
            if VectorPdf is not None and filename is not missing:
                backend = VectorPdf
                filename = uri

            # PDF images are implemented by converting via PythonMagick
            # w,h are in pixels. I need to set the density
            # of the image to  the right dpi so this
            # looks decent
            elif LazyImports.PMImage or LazyImports.gfx:
                filename = self.raster(filename, client)
            else:
                log.warning(
                    'Minimal PDF image support requires ' +
                    'PythonMagick or the vectorpdf extension [%s]', filename)
                filename = missing
        elif extension != 'jpg' and not LazyImports.PILImage:
            if LazyImports.PMImage:
                # Need to convert to JPG via PythonMagick
                filename = self.raster(filename, client)
            else:
                # No way to make this work
                log.error('To use a %s image you need PIL installed [%s]',
                          extension, filename)
                filename = missing
        return filename, backend
示例#14
0
    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 = [os.path.expanduser(p) for p in font_path]

        if style_path is None:
            style_path = []
        style_path += [
            '.', os.path.join(self.PATH, 'styles'), '~/.rst2pdf/styles'
        ]
        self.StyleSearchPath = [os.path.expanduser(p) for p in 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, str):
                        # 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")]
                        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.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'
                    and isinstance(s['borderPadding'], list)):
                log.warning(('Using a borderPadding list in style %s with ' +
                             'wordaxe <= 0.3.2. 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
示例#15
0
def autoEmbed(fname):
    """Given a font name, does a best-effort of embedding
    said font and its variants.

    Returns a list of the font names it registered with ReportLab.

    """
    log.info("Trying to embed %s" % fname)
    fontList = []
    variants = []
    f = findFont(fname)
    if f:  # We have this font located
        if f[0].lower().endswith(".afm"):  # Type 1 font
            family = families[f[2]]

            # Register the whole family of faces
            faces = [
                pdfmetrics.EmbeddedType1Face(*fonts[fn.lower()][:2]) for fn in family
            ]
            for face in faces:
                pdfmetrics.registerTypeFace(face)

            for face, name in zip(faces, family):
                fontList.append(name)
                font = pdfmetrics.Font(face, name, "WinAnsiEncoding")
                log.info("Registering font: %s from %s" % (name, face.getFontFiles()))
                pdfmetrics.registerFont(font)

            # Map the variants
            regular, italic, bold, bolditalic = family
            for n in fname, regular:
                addMapping(n, 0, 0, regular)
                addMapping(n, 0, 1, italic)
                addMapping(n, 1, 0, bold)
                addMapping(n, 1, 1, bolditalic)
            log.info("Embedding as %s" % fontList)
            return fontList
        else:  # A TTF font
            variants = [fonts[x.lower()][0] for x in families[f[2]]]
    if not variants:  # Try fc-match
        variants = findTTFont(fname)
    # It is a TT Font and we found it using fc-match (or found *something*)
    if variants:
        for variant in variants:
            vname = os.path.basename(variant)[:-4]
            try:
                if vname not in pdfmetrics._fonts:
                    _font = TTFont(vname, variant)
                    log.info("Registering font: %s from %s" % (vname, variant))
                    pdfmetrics.registerFont(_font)
            except TTFError:
                log.error("Error registering font: %s from %s" % (vname, variant))
            else:
                fontList.append(vname)
        regular, bold, italic, bolditalic = [
            os.path.basename(variant)[:-4] for variant in variants
        ]
        addMapping(regular, 0, 0, regular)
        addMapping(regular, 0, 1, italic)
        addMapping(regular, 1, 0, bold)
        addMapping(regular, 1, 1, bolditalic)
        log.info("Embedding via findTTFont as %s" % fontList)
    return fontList
示例#16
0
def loadFonts():
    """
    Search the system and build lists of available fonts.
    """

    if not any([afmList, pfbList, ttfList]):
        # FIXME: When we drop support por py2, use recursive globs
        for folder in flist:
            for root, _, files in os.walk(folder):
                for f in files:
                    if fnmatch(f, "*.ttf") or fnmatch(f, "*.ttc"):
                        ttfList.append(os.path.join(root, f))
                    elif fnmatch(f, "*.afm"):
                        afmList.append(os.path.join(root, f))
                    elif fnmatch(f, "*.pfb"):
                        pfbList[f[:-4]] = os.path.join(root, f)

        for ttf in ttfList:
            try:
                font = TTFontFile(ttf)
            except TTFError:
                log.warning("Error processing %s", ttf)
                continue

            family = make_string(font.familyName.lower())
            fontName = make_string(font.name).lower()
            baseName = os.path.basename(ttf)[:-4]
            fullName = make_string(font.fullName).lower()

            for k in (fontName, fullName, fullName.replace("italic", "oblique")):
                fonts[k] = (ttf, ttf, family)

            bold = FF_FORCEBOLD == FF_FORCEBOLD & font.flags
            italic = FF_ITALIC == FF_ITALIC & font.flags

            # And we can try to build/fill the family mapping
            if family not in families:
                families[family] = [fontName, fontName, fontName, fontName]
            if bold and italic:
                families[family][3] = fontName
            elif bold:
                families[family][1] = fontName
            elif italic:
                families[family][2] = fontName
            # FIXME: what happens if there are Demi and Medium
            # weights? We get a random one.
            else:
                families[family][0] = fontName

        # Now we have full afm and pbf lists, process the
        # afm list to figure out family name, weight and if
        # it's italic or not, as well as where the
        # matching pfb file is

        for afm in afmList:
            family = None
            fontName = None
            italic = False
            bold = False
            for line in open(afm, "r"):
                line = line.strip()
                if line.startswith("StartCharMetrics"):
                    break
                elif line.startswith("FamilyName"):
                    family = line.split(" ", 1)[1].lower()
                elif line.startswith("FontName"):
                    fontName = line.split(" ")[1]
                elif line.startswith("FullName"):
                    fullName = line.split(" ", 1)[1]
                elif line.startswith("Weight"):
                    bold = line.split(" ")[1] == "Bold"
                elif line.startswith("ItalicAngle"):
                    italic = line.split(" ")[1] != "0.0"

            baseName = os.path.basename(afm)[:-4]
            if family in Ignored or family in Alias:
                continue
            if baseName not in pfbList:
                log.info("afm file without matching pfb file: %s" % baseName)
                continue

            # So now we have a font we know we can embed.
            for n in (
                fontName.lower(),
                fullName.lower(),
                fullName.lower().replace("italic", "oblique"),
            ):
                fonts[n] = (afm, pfbList[baseName], family)

            # And we can try to build/fill the family mapping
            if family not in families:
                families[family] = [fontName, fontName, fontName, fontName]
            if bold and italic:
                families[family][3] = fontName
            elif bold:
                families[family][1] = fontName
            elif italic:
                families[family][2] = fontName
            # FIXME: what happens if there are Demi and Medium
            # weights? We get a random one.
            else:
                families[family][0] = fontName
示例#17
0
    def get_backend(self, uri, client):
        """
        Given the filename of an image, return (fname, backend).

        fname is the filename to be used (could be the same as filename,
        or something different if the image had to be converted or is
        missing), and backend is an Image class that can handle fname.

        If uri ends with '.*' then the returned filename will be the best
        quality supported at the moment.

        That means:  PDF > SVG > anything else
        """
        backend = defaultimage

        # Extract all the information from the URI
        filename, extension, options = self.split_uri(uri)

        if '*' in filename:
            preferred = ['gif', 'jpg', 'png', 'svg', 'pdf']

            # Find out what images are available
            available = glob.glob(filename)
            cfn = available[0]
            cv = -10
            for fn in available:
                ext = fn.split('.')[-1]
                if ext in preferred:
                    v = preferred.index(ext)
                else:
                    v = -1
                if v > cv:
                    cv = v
                    cfn = fn
            # cfn should have our favourite type of
            # those available
            filename = cfn
            extension = cfn.split('.')[-1]
            uri = filename

        # If the image doesn't exist, we use a 'missing' image
        if not os.path.exists(filename):
            log.error("Missing image file: %s", filename)
            filename = missing

        if extension in ['svg', 'svgz']:
            log.info('Backend for %s is SVGIMage' % filename)
            backend = SVGImage

        elif extension in ['pdf']:
            if VectorPdf is not None and filename is not missing:
                backend = VectorPdf
                filename = uri

            # PDF images are implemented by converting via PythonMagick
            # w,h are in pixels. I need to set the density
            # of the image to  the right dpi so this
            # looks decent
            elif LazyImports.PMImage or LazyImports.gfx:
                filename = self.raster(filename, client)
            else:
                log.warning('Minimal PDF image support requires ' +
                            'PythonMagick or the vectorpdf extension [%s]',
                            filename)
                filename = missing
        elif extension != 'jpg' and not LazyImports.PILImage:
            if LazyImports.PMImage:
                # Need to convert to JPG via PythonMagick
                filename = self.raster(filename, client)
            else:
                # No way to make this work
                log.error('To use a %s image you need PIL installed [%s]',
                          extension, filename)
                filename = missing
        return filename, backend