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