Exemple #1
0
 def getSubfont(self,subfontIndex):
     if self.fileKind!='TTC':
         raise TTFError('"%s" is not a TTC file: use this method' % (self.filename,self.fileKind))
     try:
         pos = self.subfontOffsets[subfontIndex]
     except IndexError:
         raise TTFError('TTC file "%s": bad subfontIndex %s not in [0,%d]' % (self.filename,subfontIndex,self.numSubfonts-1))
     self.seek(pos)
     self.readHeader()
     self.readTableDirectory()
     self.subfontNameX = bytestr('-'+str(subfontIndex))
Exemple #2
0
 def getSubfont(self,subfontIndex):
     if self.fileKind!='TTC':
         raise TTFError('"%s" is not a TTC file: use this method' % (self.filename,self.fileKind))
     try:
         pos = self.subfontOffsets[subfontIndex]
     except IndexError:
         raise TTFError('TTC file "%s": bad subfontIndex %s not in [0,%d]' % (self.filename,subfontIndex,self.numSubfonts-1))
     self.seek(pos)
     self.readHeader()
     self.readTableDirectory()
     self.subfontNameX = bytestr('-'+str(subfontIndex))
        import os
        from reportlab.lib.testutils import testsFolder
        filename = outputfile('test_no_helvetica.pdf')
        c = Canvas(filename, invariant=1, pageCompression=0, initialFontName='Times-Roman')
        c.drawString(100,700, 'Hello World')
        c.save()
        with open(filename, 'rb') as f:
            raw = f.read()
        assert b'Helvetica' not in raw and b'Times-Roman' in raw, 'Canvas initialFontName expectations not satisfied!'

def makeSuite():
    return makeSuiteForClasses(InvarTestCase)


#noruntests
if __name__ == "__main__":
    # add some diagnostics, useful in invariant tests
    import sys, os
    from reportlab.lib.utils import md5, bytestr
    verbose = ('-v' in sys.argv)
    unittest.TextTestRunner().run(makeSuite())
    if verbose:
        #tell us about the file we produced
        fileSize = os.stat(filename)[6]
        raw = open(filename,'rb').read()
        digest = md5.md5(bytestr(raw)).hexdigest()
        major, minor = sys.version_info[0:2]
        print('%s on %s (Python %d.%d):\n    %d bytes, digest %s' % (
            filename,sys.platform, major, minor, fileSize, digest))
    printLocation()
    def extractInfo(self, charInfo=1):
        """
        Extract typographic information from the loaded font file.

        The following attributes will be set::
        
            name         PostScript font name
            flags        Font flags
            ascent       Typographic ascender in 1/1000ths of a point
            descent      Typographic descender in 1/1000ths of a point
            capHeight    Cap height in 1/1000ths of a point (0 if not available)
            bbox         Glyph bounding box [l,t,r,b] in 1/1000ths of a point
            _bbox        Glyph bounding box [l,t,r,b] in unitsPerEm
            unitsPerEm   Glyph units per em
            italicAngle  Italic angle in degrees ccw
            stemV        stem weight in 1/1000ths of a point (approximate)
        
        If charInfo is true, the following will also be set::
        
            defaultWidth   default glyph width in 1/1000ths of a point
            charWidths     dictionary of character widths for every supported UCS character
                           code
        
        This will only work if the font has a Unicode cmap (platform 3,
        encoding 1, format 4 or platform 0 any encoding format 4).  Setting
        charInfo to false avoids this requirement
        
        """
        # name - Naming table
        name_offset = self.seek_table("name")
        format = self.read_ushort()
        if format != 0:
            raise TTFError("Unknown name table format (%d)" % format)
        numRecords = self.read_ushort()
        string_data_offset = name_offset + self.read_ushort()
        names = {1:None,2:None,3:None,4:None,6:None}
        K = list(names.keys())
        nameCount = len(names)
        for i in xrange(numRecords):
            platformId = self.read_ushort()
            encodingId = self.read_ushort()
            languageId = self.read_ushort()
            nameId = self.read_ushort()
            length = self.read_ushort()
            offset = self.read_ushort()
            if nameId not in K: continue
            N = None
            if platformId == 3 and encodingId == 1 and languageId == 0x409: # Microsoft, Unicode, US English, PS Name
                opos = self._pos
                try:
                    self.seek(string_data_offset + offset)
                    if length % 2 != 0:
                        raise TTFError("PostScript name is UTF-16BE string of odd length")
                    length /= 2
                    N = []
                    A = N.append
                    while length > 0:
                        char = self.read_ushort()
                        A(bytes([char]) if isPy3 else chr(char))
                        length -= 1
                    N = b''.join(N)
                finally:
                    self._pos = opos
            elif platformId == 1 and encodingId == 0 and languageId == 0: # Macintosh, Roman, English, PS Name
                # According to OpenType spec, if PS name exists, it must exist
                # both in MS Unicode and Macintosh Roman formats.  Apparently,
                # you can find live TTF fonts which only have Macintosh format.
                N = self.get_chunk(string_data_offset + offset, length)
            if N and names[nameId]==None:
                names[nameId] = N
                nameCount -= 1
                if nameCount==0: break
        if names[6] is not None:
            psName = names[6]
        elif names[4] is not None:
            psName = names[4]
        # Fine, one last try before we bail.
        elif names[1] is not None:
            psName = names[1]
        else:
            psName = None

        # Don't just assume, check for None since some shoddy fonts cause crashes here...
        if not psName:
            if rl_config.autoGenerateTTFMissingTTFName:
                fn = self._filename
                if fn:
                    bfn = os.path.splitext(os.path.basename(fn))[0]
                if not fn:
                    psName = bytestr('_RL_%s_%s_TTF' % (time.time(), self.__class__._agfnc))
                    self.__class__._agfnc += 1
                else:
                    psName = self._agfnm.get(fn,'')
                    if not psName:
                        if bfn:
                            psName = bytestr('_RL_%s_TTF' % bfn)
                        else:
                            psName = bytestr('_RL_%s_%s_TTF' % (time.time(), self.__class__._agfnc))
                            self.__class__._agfnc += 1
                        self._agfnm[fn] = psName
            else:
                raise TTFError("Could not find PostScript font name")

        psName = psName.replace(b" ", b"-")  #Dinu Gherman's fix for font names with spaces

        for c in psName:
            if char2int(c)>126 or c in b' [](){}<>/%':
                raise TTFError("psName=%r contains invalid character %s" % (psName,ascii(c)))
        self.name = psName
        self.familyName = names[1] or psName
        self.styleName = names[2] or 'Regular'
        self.fullName = names[4] or psName
        self.uniqueFontID = names[3] or psName

        # head - Font header table
        self.seek_table("head")
        ver_maj, ver_min = self.read_ushort(), self.read_ushort()
        if ver_maj != 1:
            raise TTFError('Unknown head table version %d.%04x' % (ver_maj, ver_min))
        self.fontRevision = self.read_ushort(), self.read_ushort()

        self.skip(4)
        magic = self.read_ulong()
        if magic != 0x5F0F3CF5:
            raise TTFError('Invalid head table magic %04x' % magic)
        self.skip(2)
        self.unitsPerEm = unitsPerEm = self.read_ushort()
        scale = lambda x, unitsPerEm=unitsPerEm: x * 1000. / unitsPerEm
        self.skip(16)
        xMin = self.read_short()
        yMin = self.read_short()
        xMax = self.read_short()
        yMax = self.read_short()
        self.bbox = list(map(scale, [xMin, yMin, xMax, yMax]))
        self.skip(3*2)
        indexToLocFormat = self.read_ushort()
        glyphDataFormat = self.read_ushort()

        # OS/2 - OS/2 and Windows metrics table
        # (needs data from head table)
        if "OS/2" in self.table:
            self.seek_table("OS/2")
            version = self.read_ushort()
            self.skip(2)
            usWeightClass = self.read_ushort()
            self.skip(2)
            fsType = self.read_ushort()
            if fsType == 0x0002 or (fsType & 0x0300) != 0:
                raise TTFError('Font does not allow subsetting/embedding (%04X)' % fsType)
            self.skip(58)   #11*2 + 10 + 4*4 + 4 + 3*2
            sTypoAscender = self.read_short()
            sTypoDescender = self.read_short()
            self.ascent = scale(sTypoAscender)      # XXX: for some reason it needs to be multiplied by 1.24--1.28
            self.descent = scale(sTypoDescender)

            if version > 1:
                self.skip(16)   #3*2 + 2*4 + 2
                sCapHeight = self.read_short()
                self.capHeight = scale(sCapHeight)
            else:
                self.capHeight = self.ascent
        else:
            # Microsoft TTFs require an OS/2 table; Apple ones do not.  Try to
            # cope.  The data is not very important anyway.
            usWeightClass = 500
            self.ascent = scale(yMax)
            self.descent = scale(yMin)
            self.capHeight = self.ascent

        # There's no way to get stemV from a TTF file short of analyzing actual outline data
        # This fuzzy formula is taken from pdflib sources, but we could just use 0 here
        self.stemV = 50 + int((usWeightClass / 65.0) ** 2)

        # post - PostScript table
        # (needs data from OS/2 table)
        self.seek_table("post")
        ver_maj, ver_min = self.read_ushort(), self.read_ushort()
        if ver_maj not in (1, 2, 3, 4):
            # Adobe/MS documents 1, 2, 2.5, 3; Apple also has 4.
            # From Apple docs it seems that we do not need to care
            # about the exact version, so if you get this error, you can
            # try to remove this check altogether.
            raise TTFError('Unknown post table version %d.%04x' % (ver_maj, ver_min))
        self.italicAngle = self.read_short() + self.read_ushort() / 65536.0
        self.underlinePosition = self.read_short()
        self.underlineThickness = self.read_short()
        isFixedPitch = self.read_ulong()

        self.flags = FF_SYMBOLIC        # All fonts that contain characters
                                        # outside the original Adobe character
                                        # set are considered "symbolic".
        if self.italicAngle!= 0:
            self.flags = self.flags | FF_ITALIC
        if usWeightClass >= 600:        # FW_REGULAR == 500, FW_SEMIBOLD == 600
            self.flags = self.flags | FF_FORCEBOLD
        if isFixedPitch:
            self.flags = self.flags | FF_FIXED
        # XXX: FF_SERIF?  FF_SCRIPT?  FF_ALLCAP?  FF_SMALLCAP?

        # hhea - Horizontal header table
        self.seek_table("hhea")
        ver_maj, ver_min = self.read_ushort(), self.read_ushort()
        if ver_maj != 1:
            raise TTFError('Unknown hhea table version %d.%04x' % (ver_maj, ver_min))
        self.skip(28)
        metricDataFormat = self.read_ushort()
        if metricDataFormat != 0:
            raise TTFError('Unknown horizontal metric data format (%d)' % metricDataFormat)
        numberOfHMetrics = self.read_ushort()
        if numberOfHMetrics == 0:
            raise TTFError('Number of horizontal metrics is 0')

        # maxp - Maximum profile table
        self.seek_table("maxp")
        ver_maj, ver_min = self.read_ushort(), self.read_ushort()
        if ver_maj != 1:
            raise TTFError('Unknown maxp table version %d.%04x' % (ver_maj, ver_min))
        self.numGlyphs = numGlyphs = self.read_ushort()

        if not charInfo:
            self.charToGlyph = None
            self.defaultWidth = None
            self.charWidths = None
            return

        if glyphDataFormat != 0:
            raise TTFError('Unknown glyph data format (%d)' % glyphDataFormat)

        # cmap - Character to glyph index mapping table
        cmap_offset = self.seek_table("cmap")
        cmapVersion = self.read_ushort()
        cmapTableCount = self.read_ushort()
        if cmapTableCount==0 and cmapVersion!=0:
            cmapTableCount, cmapVersion = cmapVersion, cmapTableCount
        encoffs = None
        enc = 0
        for n in xrange(cmapTableCount):
            platform = self.read_ushort()
            encoding = self.read_ushort()
            offset = self.read_ulong()
            if platform==3:
                enc = 1
                encoffs = offset
            elif platform==1 and encoding==0 and enc!=1:
                enc = 2
                encoffs = offset
            elif platform==1 and encoding==1:
                enc = 1
                encoffs = offset
            elif platform==0 and encoding!=5:
                enc = 1
                encoffs = offset
        if encoffs is None:
            raise TTFError('could not find a suitable cmap encoding')
        encoffs += cmap_offset
        self.seek(encoffs)
        fmt = self.read_ushort()
        self.charToGlyph = charToGlyph = {}
        glyphToChar = {}
        if fmt in (13,12,10,8):
            self.skip(2)    #padding
            length = self.read_ulong()
            lang = self.read_ulong()
        else:
            length = self.read_ushort()
            lang = self.read_ushort()
        if fmt==0:
            T = [self.read_uint8() for i in xrange(length-6)]
            for unichar in xrange(min(256,self.numGlyphs,len(table))):
                glyph = T[glyph]
                charToGlyph[unichar] = glyph
                glyphToChar.setdefault(glyph,[]).append(unichar)
        elif fmt==4:
            limit = encoffs + length
            segCount = int(self.read_ushort() / 2.0)
            self.skip(6)
            endCount = [self.read_ushort() for _ in xrange(segCount)]
            self.skip(2)
            startCount = [self.read_ushort() for _ in xrange(segCount)]
            idDelta = [self.read_short() for _ in xrange(segCount)]
            idRangeOffset_start = self._pos
            idRangeOffset = [self.read_ushort() for _ in xrange(segCount)]

            # Now it gets tricky.
            for n in xrange(segCount):
                for unichar in xrange(startCount[n], endCount[n] + 1):
                    if idRangeOffset[n] == 0:
                        glyph = (unichar + idDelta[n]) & 0xFFFF
                    else:
                        offset = (unichar - startCount[n]) * 2 + idRangeOffset[n]
                        offset = idRangeOffset_start + 2 * n + offset
                        if offset >= limit:
                            # workaround for broken fonts (like Thryomanes)
                            glyph = 0
                        else:
                            glyph = self.get_ushort(offset)
                            if glyph != 0:
                                glyph = (glyph + idDelta[n]) & 0xFFFF
                    charToGlyph[unichar] = glyph
                    glyphToChar.setdefault(glyph,[]).append(unichar)
        elif fmt==6:
            first = self.read_ushort()
            count = self.read_ushort()
            for glyph in xrange(first,first+count):
                unichar = self.read_ushort()
                charToGlyph[unichar] = glyph
                glyphToChar.setdefault(glyph,[]).append(unichar)
        elif fmt==10:
            first = self.read_ulong()
            count = self.read_ulong()
            for glyph in xrange(first,first+count):
                unichar = self.read_ushort()
                charToGlyph[unichar] = glyph
                glyphToChar.setdefault(glyph,[]).append(unichar)
        elif fmt==12:
            segCount = self.read_ulong()
            for n in xrange(segCount):
                start = self.read_ulong()
                end = self.read_ulong()
                inc = self.read_ulong() - start
                for unichar in xrange(start,end+1):
                    glyph = unichar + inc
                    charToGlyph[unichar] = glyph
                    glyphToChar.setdefault(glyph,[]).append(unichar)
        elif fmt==13:
            segCount = self.read_ulong()
            for n in xrange(segCount):
                start = self.read_ulong()
                end = self.read_ulong()
                gid = self.read_ulong()
                for unichar in xrange(start,end+1):
                    charToGlyph[unichar] = gid
                    glyphToChar.setdefault(gid,[]).append(unichar)
        elif fmt==2:
            T = [self.read_ushort() for i in xrange(256)]   #subheader keys
            maxSHK = max(T)
            SH = []
            for i in xrange(maxSHK+1):
                firstCode = self.read_ushort()
                entryCount = self.read_ushort()
                idDelta = self.read_ushort()
                idRangeOffset = (self.read_ushort()-(maxSHK-i)*8-2)>>1
                SH.append(CMapFmt2SubHeader(firstCode,entryCount,idDelta,idRangeOffset))
            #number of glyph indexes to read. it is the length of the entire subtable minus that bit we've read so far
            entryCount = (length-(self._pos-(cmap_offset+encoffs)))>>1
            glyphs = [self.read_short() for i in xrange(entryCount)]
            last = -1
            for unichar in xrange(256):
                if T[unichar]==0:
                    #Special case, single byte encoding entry, look unichar up in subhead
                    if last!=-1:
                        glyph = 0
                    elif (unichar<SH[0].firstCode or unichar>=SH[0].firstCode+SH[0].entryCount or
                            SH[0].idRangeOffset+(unichar-SH[0].firstCode)>=entryCount):
                        glyph = 0
                    else:
                        glyph = glyphs[SH[0].idRangeOffset+(unichar-SH[0].firstCode)]
                        if glyph!=0:
                            glyph += SH[0].idDelta
                    #assume the single byte codes are ascii
                    if glyph!=0 and glyph<self.numGlyphs:
                        charToGlyph[unichar] = glyph
                        glyphToChar.setdefault(glyph,[]).append(unichar)
                else:
                    k = T[unichar]
                    for j in xrange(SH[k].entryCount):
                        if SH[k].idRangeOffset+j>=entryCount:
                            glyph = 0
                        else:
                            glyph = glyphs[SH[k].idRangeOffset+j]
                            if glyph!= 0:
                                glyph += SH[k].idDelta
                        if glyph!=0 and glyph<self.numGlyphs:
                            enc = (unichar<<8)|(j+SH[k].firstCode)
                            charToGlyph[enc] = glyph
                            glyphToChar.setdefault(glyph,[]).append(enc)
                    if last==-1:
                        last = unichar
        else:
            raise ValueError('Unsupported cmap encoding format %d' % fmt)

        # hmtx - Horizontal metrics table
        # (needs data from hhea, maxp, and cmap tables)
        self.seek_table("hmtx")
        aw = None
        self.charWidths = {}
        self.hmetrics = []
        for glyph in xrange(numberOfHMetrics):
            # advance width and left side bearing.  lsb is actually signed
            # short, but we don't need it anyway (except for subsetting)
            aw, lsb = self.read_ushort(), self.read_ushort()
            self.hmetrics.append((aw, lsb))
            aw = scale(aw)
            if glyph == 0:
                self.defaultWidth = aw
            if glyph in glyphToChar:
                for char in glyphToChar[glyph]:
                    self.charWidths[char] = aw
        for glyph in xrange(numberOfHMetrics, numGlyphs):
            # the rest of the table only lists advance left side bearings.
            # so we reuse aw set by the last iteration of the previous loop
            lsb = self.read_ushort()
            self.hmetrics.append((aw, lsb))
            if glyph in glyphToChar:
                for char in glyphToChar[glyph]:
                    self.charWidths[char] = aw

        # loca - Index to location
        self.seek_table('loca')
        self.glyphPos = []
        if indexToLocFormat == 0:
            for n in xrange(numGlyphs + 1):
                self.glyphPos.append(self.read_ushort() << 1)
        elif indexToLocFormat == 1:
            for n in xrange(numGlyphs + 1):
                self.glyphPos.append(self.read_ulong())
        else:
            raise TTFError('Unknown location table format (%d)' % indexToLocFormat)
Exemple #5
0
                   invariant=1,
                   pageCompression=0,
                   initialFontName='Times-Roman')
        c.drawString(100, 700, 'Hello World')
        c.save()
        with open(filename, 'rb') as f:
            raw = f.read()
        assert b'Helvetica' not in raw and b'Times-Roman' in raw, 'Canvas initialFontName expectations not satisfied!'


def makeSuite():
    return makeSuiteForClasses(InvarTestCase)


#noruntests
if __name__ == "__main__":
    # add some diagnostics, useful in invariant tests
    import sys, os
    from reportlab.lib.utils import md5, bytestr
    verbose = ('-v' in sys.argv)
    unittest.TextTestRunner().run(makeSuite())
    if verbose:
        #tell us about the file we produced
        fileSize = os.stat(filename)[6]
        raw = open(filename, 'rb').read()
        digest = md5.md5(bytestr(raw)).hexdigest()
        major, minor = sys.version_info[0:2]
        print('%s on %s (Python %d.%d):\n    %d bytes, digest %s' %
              (filename, sys.platform, major, minor, fileSize, digest))
    printLocation()