Blame lib/dbtexmf/dblatex/xetex/fcmanager.py

Packit 0f19cf
# 
Packit 0f19cf
# Slow interface to fontconfig for Dblatex, that only parses some commmand line
Packit 0f19cf
# output to store the fonts available on the system and their characteristics.
Packit 0f19cf
#
Packit 0f19cf
# An efficient solution should use some python bindings to directly call the
Packit 0f19cf
# C fontconfig library.
Packit 0f19cf
#
Packit Service f3de8e
import sys
Packit 0f19cf
import logging
Packit 0f19cf
from subprocess import Popen, PIPE
Packit 0f19cf
Packit 0f19cf
def execute(cmd):
Packit 0f19cf
    p = Popen(cmd, stdout=PIPE)
Packit 0f19cf
    data = p.communicate()[0]
Packit Service f3de8e
    if isinstance(data, bytes):
Packit Service f3de8e
        data = data.decode(sys.getdefaultencoding())
Packit 0f19cf
    rc = p.wait()
Packit 0f19cf
    if rc != 0:
Packit 0f19cf
        raise OSError("'%s' failed (%d)" % (" ".join(cmd), rc))
Packit 0f19cf
    return data
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class FcFont:
Packit 0f19cf
    """
Packit 0f19cf
    Font Object with properties filled with the fc-match command output.
Packit 0f19cf
    """
Packit 0f19cf
    def __init__(self, fontnames, partial=False):
Packit 0f19cf
        self.log = logging.getLogger("dblatex")
Packit 0f19cf
        self.name = fontnames[0]
Packit 0f19cf
        self.aliases = fontnames[1:]
Packit 0f19cf
        self._completed = False
Packit 0f19cf
        if not(partial):
Packit 0f19cf
            self.complete()
Packit 0f19cf
Packit 0f19cf
    def complete(self):
Packit 0f19cf
        if not(self._completed):
Packit 0f19cf
            d = execute(["fc-match", "--verbose", self.name])
Packit 0f19cf
            d = d.strip()
Packit 0f19cf
            self._build_attr_from(d)
Packit 0f19cf
            self._completed = True
Packit 0f19cf
Packit 0f19cf
    def _build_attr_from(self, data):
Packit 0f19cf
        ninfos = self._splitinfos(data)
Packit 0f19cf
Packit 0f19cf
        # Remove the first line
Packit 0f19cf
        ninfos[0] = ninfos[0].split("\n")[1]
Packit 0f19cf
        for i in ninfos:
Packit 0f19cf
            if i: self._buildattr(i)
Packit 0f19cf
        
Packit 0f19cf
        # Check the consistency
Packit 0f19cf
        if self.family != self.name.replace("\-", "-"):
Packit 0f19cf
            raise ValueError("Unknown font '%s' vs '%s'" % (self.name,
Packit 0f19cf
            self.family))
Packit 0f19cf
Packit 0f19cf
    def _splitinfos(self, data):
Packit 0f19cf
        ninfos = [data]
Packit 0f19cf
        for sep in ("(s)", "(w)", "(=)"):
Packit 0f19cf
            infos = ninfos
Packit 0f19cf
            ninfos = []
Packit 0f19cf
            for i in infos:
Packit 0f19cf
                ni = i.split(sep)
Packit 0f19cf
                ninfos += ni
Packit 0f19cf
        return ninfos
Packit 0f19cf
Packit 0f19cf
    def _buildattr(self, infos):
Packit 0f19cf
        """
Packit 0f19cf
        Parse things like:
Packit 0f19cf
           'fullname: "Mukti"(s)
Packit 0f19cf
            fullnamelang: "en"(s)
Packit 0f19cf
            slant: 0(i)(s)
Packit 0f19cf
            weight: 80(i)(s)
Packit 0f19cf
            width: 100(i)(s)
Packit 0f19cf
            size: 12(f)(s)'
Packit 0f19cf
        """
Packit 0f19cf
        try:
Packit 0f19cf
            attrname, attrdata = infos.split(":", 1)
Packit 0f19cf
        except:
Packit 0f19cf
            # Skip this row
Packit 0f19cf
            self.log.warning("Wrong data? '%s'" % infos)
Packit 0f19cf
            return
Packit 0f19cf
        
Packit 0f19cf
        #print infos
Packit 0f19cf
        attrname = attrname.strip() # Remove \t
Packit 0f19cf
        attrdata = attrdata.strip() # Remove space
Packit 0f19cf
Packit 0f19cf
        # Specific case
Packit 0f19cf
        if attrname == "charset":
Packit 0f19cf
            self._build_charset(attrdata)
Packit 0f19cf
            return
Packit 0f19cf
Packit 0f19cf
        # Get the data type
Packit 0f19cf
        if (not(attrdata) or (attrdata[0] == '"' and attrdata[-1] == '"')):
Packit 0f19cf
            setattr(self, attrname, attrdata.strip('"'))
Packit 0f19cf
            return
Packit 0f19cf
        
Packit 0f19cf
        if (attrdata.endswith("(i)")):
Packit 0f19cf
            setattr(self, attrname, int(attrdata.strip("(i)")))
Packit 0f19cf
            return
Packit 0f19cf
Packit 0f19cf
        if (attrdata.endswith("(f)")):
Packit 0f19cf
            setattr(self, attrname, float(attrdata.strip("(f)")))
Packit 0f19cf
            return
Packit 0f19cf
Packit 0f19cf
        if (attrdata == "FcTrue"):
Packit 0f19cf
            setattr(self, attrname, True)
Packit 0f19cf
            return
Packit 0f19cf
Packit 0f19cf
        if (attrdata == "FcFalse"):
Packit 0f19cf
            setattr(self, attrname, False)
Packit 0f19cf
            return
Packit 0f19cf
Packit 0f19cf
    def _build_charset(self, charset):
Packit 0f19cf
        """
Packit 0f19cf
        Parse something like:
Packit 0f19cf
           '0000: 00000000 ffffffff ffffffff 7fffffff 00000000 00002001 00800000 00800000
Packit 0f19cf
            0009: 00000000 00000000 00000000 00000030 fff99fee f3c5fdff b080399f 07ffffcf
Packit 0f19cf
            0020: 30003000 00000000 00000010 00000000 00000000 00001000 00000000 00000000
Packit 0f19cf
            0025: 00000000 00000000 00000000 00000000 00000000 00000000 00001000 00000000'
Packit 0f19cf
        """
Packit 0f19cf
        self.charsetstr = charset
Packit 0f19cf
        self.charset = []
Packit 0f19cf
        lines = charset.split("\n")
Packit 0f19cf
        for l in lines:
Packit 0f19cf
            umajor, row = l.strip().split(":", 1)
Packit 0f19cf
            int32s = row.split()
Packit 0f19cf
            p = 0
Packit 0f19cf
            for w in int32s:
Packit 0f19cf
                #print "=> %s" % w
Packit 0f19cf
                v = int(w, 16)
Packit 0f19cf
                for i in range(0, 32):
Packit 0f19cf
                    m = 1 << i
Packit 0f19cf
                    #m = 0x80000000 >> i
Packit 0f19cf
                    if (m & v):
Packit 0f19cf
                        uchar = umajor + "%02X" % (p + i)
Packit 0f19cf
                        #print uchar
Packit 0f19cf
                        self.charset.append(int(uchar, 16))
Packit 0f19cf
                p += 32
Packit 0f19cf
Packit 0f19cf
    def remove_char(self, char):
Packit 0f19cf
        try:
Packit 0f19cf
            self.charset.remove(char)
Packit 0f19cf
        except:
Packit 0f19cf
            pass
Packit 0f19cf
Packit 0f19cf
    def has_char(self, char):
Packit 0f19cf
        #print self.family, char, self.charset
Packit 0f19cf
        return (ord(char) in self.charset)
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class FcManager:
Packit 0f19cf
    """
Packit 0f19cf
    Collect all the fonts available in the system. The building can be partial,
Packit 0f19cf
    i.e. the font objects can be partially created, and updated later (when
Packit 0f19cf
    used).
Packit 0f19cf
Packit 0f19cf
    The class tries to build three ordered list of fonts, one per standard
Packit 0f19cf
    generic font family:
Packit 0f19cf
    - Serif      : main / body font
Packit 0f19cf
    - Sans-serif : used to render sans-serif forms
Packit 0f19cf
    - Monospace  : used to render verbatim / monospace characters
Packit 0f19cf
    """
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        self.log = logging.getLogger("dblatex")
Packit 0f19cf
        self.fonts = {}
Packit 0f19cf
        self.fonts_family = {}
Packit 0f19cf
Packit 0f19cf
    def get_font(self, fontname):
Packit 0f19cf
        font = self.fonts.get(fontname)
Packit 0f19cf
        if font:
Packit 0f19cf
            font.complete()
Packit 0f19cf
        return font
Packit 0f19cf
Packit 0f19cf
    def get_font_handling(self, char, all=False, family_type=""):
Packit 0f19cf
        if not(family_type):
Packit 0f19cf
            font_family = self.fonts.values()
Packit 0f19cf
        else:
Packit 0f19cf
            font_family = self.fonts_family.get(family_type, None)
Packit 0f19cf
        
Packit 0f19cf
        if not(font_family):
Packit 0f19cf
            return []
Packit 0f19cf
Packit 0f19cf
        fonts = self.get_font_handling_from(font_family, char, all=all)
Packit 0f19cf
        return fonts
Packit 0f19cf
Packit 0f19cf
    def get_font_handling_from(self, fontlist, char, all=False):
Packit 0f19cf
        fonts = []
Packit 0f19cf
        # Brutal method to get something...
Packit 0f19cf
        for f in fontlist:
Packit 0f19cf
            f.complete()
Packit 0f19cf
            if f.has_char(char):
Packit 0f19cf
                if all:
Packit 0f19cf
                    fonts.append(f)
Packit 0f19cf
                else:
Packit 0f19cf
                    return f
Packit 0f19cf
        return fonts
Packit 0f19cf
Packit 0f19cf
    def build_fonts(self, partial=False):
Packit 0f19cf
        self.build_fonts_all(partial=partial)
Packit 0f19cf
        self.build_fonts_family("serif")
Packit 0f19cf
        self.build_fonts_family("sans-serif")
Packit 0f19cf
        self.build_fonts_family("monospace")
Packit 0f19cf
Packit 0f19cf
    def build_fonts_all(self, partial=False):
Packit 0f19cf
        # Grab all the fonts installed on the system
Packit 0f19cf
        d = execute(["fc-list"])
Packit 0f19cf
        fonts = d.strip().split("\n")
Packit 0f19cf
        for f in fonts:
Packit 0f19cf
            fontnames = f.split(":")[0].split(",")
Packit 0f19cf
            mainname = fontnames[0]
Packit 0f19cf
            if not(mainname):
Packit 0f19cf
                continue
Packit 0f19cf
            if self.fonts.get(mainname):
Packit 0f19cf
                self.log.debug("'%s': duplicated" % mainname)
Packit 0f19cf
                continue
Packit 0f19cf
Packit 0f19cf
            #print fontnames
Packit 0f19cf
            font = FcFont(fontnames, partial=partial)
Packit 0f19cf
            self.fonts[mainname] = font
Packit 0f19cf
Packit 0f19cf
    def build_fonts_family(self, family_type):
Packit 0f19cf
        # Create a sorted list matching a generic family
Packit 0f19cf
        # Use --sort to have only fonts completing unicode range
Packit 0f19cf
        font_family = []
Packit 0f19cf
        self.fonts_family[family_type] = font_family
Packit 0f19cf
        d = execute(["fc-match", "--sort", family_type, "family"])
Packit 0f19cf
        fonts = d.strip().split("\n")
Packit 0f19cf
        for f in fonts:
Packit 0f19cf
            font = self.fonts.get(f)
Packit 0f19cf
            if not(font in font_family):
Packit 0f19cf
                font_family.append(font)
Packit 0f19cf
        #print family_type
Packit 0f19cf
        #print font_family
Packit 0f19cf