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

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