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

Packit Service 12699a
#!/usr/bin/python3
Packit Service 76cb02
# -*- coding: utf-8 -*-
Packit Service 76cb02
# Time-stamp: <2008-06-23 22:21:26 ah>
Packit Service 76cb02
Packit Service 76cb02
"""
Packit Service 76cb02
Provide an encoder for a font specification configuration: the encoder is fed
Packit Service 76cb02
with Unicode characters one by one and determines the needed font switches
Packit Service 76cb02
between the preceding and the current character.
Packit Service 76cb02
"""
Packit Service 76cb02
Packit Service 76cb02
import re
Packit Service 76cb02
import xml.dom.minidom
Packit Service 76cb02
import logging
Packit Service 76cb02
Packit Service 76cb02
Packit Service 76cb02
def _indent(string, width=2):
Packit Service 76cb02
    """Indent the <string> lines by <width> blank characters."""
Packit Service 76cb02
    istr = ' ' * width
Packit Service 76cb02
    s = istr + istr.join(string.splitlines(1))
Packit Service 76cb02
    return s
Packit Service 76cb02
Packit Service 76cb02
class UnicodeInterval:
Packit Service 76cb02
    """Unicode codepoint interval, including all codepoints between its minimum
Packit Service 76cb02
    and maximum boundary.
Packit Service 76cb02
    For any Unicode codepoint it can be queried if it belongs to the interval.
Packit Service 76cb02
    """
Packit Service 76cb02
Packit Service 76cb02
    # Internal data attributes:
Packit Service 76cb02
    # _min_boundary: Minimum boundary of the codepoint interval (ordinal)
Packit Service 76cb02
    # _max_boundary: Maximum boundary of the codepoint interval (ordinal)
Packit Service 76cb02
Packit Service 76cb02
    _re_codepoint = re.compile(r'^[Uu]\+?([0-9A-Fa-f]+)$')
Packit Service 76cb02
Packit Service 76cb02
    def __init__(self):
Packit Service 76cb02
        self._min_boundary = 0
Packit Service 76cb02
        self._max_boundary = 0
Packit Service 76cb02
Packit Service 76cb02
    def __str__(self):
Packit Service 76cb02
        """Dump the instance's data attributes."""
Packit Service 76cb02
        string = '[' + str(self._min_boundary)
Packit Service 76cb02
        if self._max_boundary != self._min_boundary:
Packit Service 76cb02
            string += ',' + str(self._max_boundary)
Packit Service 76cb02
        string += ']'
Packit Service 76cb02
        return string
Packit Service 76cb02
Packit Service 76cb02
    def _unicode_to_ordinal(self, codepoint):
Packit Service 76cb02
        """Return the ordinal of the specified codepoint."""
Packit Service 76cb02
        m = self._re_codepoint.match(codepoint)
Packit Service 76cb02
        if m:
Packit Service 76cb02
            return int(m.group(1), 16)
Packit Service 76cb02
        else:
Packit Service cd7d79
            raise RuntimeError('Not a unicode codepoint: ' + codepoint)
Packit Service 76cb02
Packit Service 76cb02
    def from_char(self, char):
Packit Service 76cb02
        """Interval for a single character"""
Packit Service 76cb02
        self._min_boundary = ord(char)
Packit Service 76cb02
        self._max_boundary = self._min_boundary
Packit Service 76cb02
        return self
Packit Service 76cb02
Packit Service 76cb02
    def from_codepoint(self, codepoint):
Packit Service 76cb02
        """Interval for a single character defined as unicode string."""
Packit Service 76cb02
        self._min_boundary = self._unicode_to_ordinal(codepoint)
Packit Service 76cb02
        self._max_boundary = self._min_boundary
Packit Service 76cb02
        return self
Packit Service 76cb02
Packit Service 76cb02
    def from_interval(self, codepoint1, codepoint2):
Packit Service 76cb02
        """Interval from a unicode range."""
Packit Service 76cb02
        self._min_boundary = self._unicode_to_ordinal(codepoint1)
Packit Service 76cb02
        self._max_boundary = self._unicode_to_ordinal(codepoint2)
Packit Service 76cb02
        if self._min_boundary > self._max_boundary:
Packit Service 76cb02
            self._min_boundary, self._max_boundary = \
Packit Service 76cb02
                self._max_boundary, self._min_boundary
Packit Service 76cb02
        return self
Packit Service 76cb02
Packit Service 76cb02
    def contains(self, char):
Packit Service 76cb02
        """
Packit Service 76cb02
        Determine whether the specified character is contained in this
Packit Service 76cb02
        instance's interval.
Packit Service 76cb02
        """
Packit Service 76cb02
        #print "%d in [%d - %d]?" % (ord(char), self._min_boundary,self._max_boundary)
Packit Service 76cb02
        return (ord(char) >= self._min_boundary
Packit Service 76cb02
                and ord(char) <= self._max_boundary)
Packit Service 76cb02
Packit Service 76cb02
Packit Service 76cb02
class FontSpec:
Packit Service 76cb02
    """
Packit Service 76cb02
    Font specification, consisting of one or several unicode character
Packit Service 76cb02
    intervals and of fonts to select for those characters. The object
Packit Service 76cb02
    fully defines the fonts to switch to.
Packit Service 76cb02
    """
Packit Service 76cb02
Packit Service 76cb02
    # Internal data attributes:
Packit Service 76cb02
    # _intervals: UnicodeInterval list
Packit Service 76cb02
Packit Service 76cb02
    transition_types = ['enter', 'inter', 'exit']
Packit Service 76cb02
    _re_interval = re.compile(r'^([Uu][0-9A-Fa-f]+)-([Uu][0-9A-Fa-f]+)$')
Packit Service 76cb02
    _re_codepoint = re.compile(r'^([Uu][0-9A-Fa-f]+)$')
Packit Service 76cb02
Packit Service 76cb02
    def __init__(self, intervals=None, subfont_first=False):
Packit Service 76cb02
        """Create a font specification from the specified codepoint intervals.
Packit Service 76cb02
        The other data attributes will be set by the caller later.
Packit Service 76cb02
        """
Packit Service 76cb02
        self.type = ""
Packit Service 76cb02
        self.id = None
Packit Service 76cb02
        self.refmode = None
Packit Service 76cb02
        self.transitions = {}
Packit Service 76cb02
        self.fontspecs = [self]
Packit Service 76cb02
        self.subfont_first = subfont_first
Packit Service 76cb02
        self._ignored = []
Packit Service 76cb02
        self.log = logging.getLogger("dblatex")
Packit Service 76cb02
Packit Service 76cb02
        for type in self.transition_types:
Packit Service 76cb02
            self.transitions[type] = {}
Packit Service 76cb02
Packit Service 76cb02
        if not(intervals):
Packit Service 76cb02
            self._intervals = []
Packit Service 76cb02
            return
Packit Service 76cb02
Packit Service 76cb02
        try:
Packit Service 76cb02
            self._intervals = list(intervals)
Packit Service 76cb02
        except TypeError:
Packit Service 76cb02
            self._intervals = [intervals]
Packit Service 76cb02
Packit Service 76cb02
    def fromnode(self, node):
Packit Service 76cb02
        range = node.getAttribute('range')
Packit Service 76cb02
        charset = node.getAttribute('charset')
Packit Service 76cb02
        id = node.getAttribute('id')
Packit Service 76cb02
        refmode = node.getAttribute('refmode')
Packit Service 76cb02
        self.type = node.getAttribute('type')
Packit Service 76cb02
Packit Service 76cb02
        if (range):
Packit Service 76cb02
            self._intervals = self._parse_range(range)
Packit Service 76cb02
        elif (charset):
Packit Service 76cb02
            for char in charset:
Packit Service 76cb02
                self.add_char(char)
Packit Service 76cb02
Packit Service 76cb02
        # Unique identifier
Packit Service 76cb02
        if (id):
Packit Service 76cb02
            self.id = id
Packit Service 76cb02
        if (refmode):
Packit Service 76cb02
            self.refmode = refmode
Packit Service 76cb02
Packit Service 76cb02
        for transition_type in self.transition_types:
Packit Service 76cb02
            self._parse_transitions(node, transition_type)
Packit Service 76cb02
Packit Service 76cb02
    def mainfont(self):
Packit Service 76cb02
        # Try to return the most representative font of this spec
Packit Service 76cb02
        return (self.transitions["enter"].get("main") or 
Packit Service 76cb02
                self.transitions["enter"].get("sans"))
Packit Service 76cb02
Packit Service 76cb02
    def _parse_range(self, range):
Packit Service 76cb02
        """Parse the specified /fonts/fontspec@range attribute to a
Packit Service 76cb02
        UnicodeInterval list.
Packit Service 76cb02
        """
Packit Service 76cb02
        #print range
Packit Service 76cb02
        intervals = []
Packit Service 76cb02
        chunks = range.split()
Packit Service 76cb02
        for chunk in chunks:
Packit Service 76cb02
            m = self._re_interval.match(chunk)
Packit Service 76cb02
            #print match
Packit Service 76cb02
            if m:
Packit Service 76cb02
                urange = UnicodeInterval().from_interval(m.group(1), m.group(2))
Packit Service 76cb02
                intervals.append(urange)
Packit Service 76cb02
            else:
Packit Service 76cb02
                m = self._re_codepoint.match(chunk)
Packit Service 76cb02
                if m:
Packit Service 76cb02
                    intervals.append(
Packit Service 76cb02
                        UnicodeInterval().from_codepoint(m.group(1)))
Packit Service 76cb02
                else:
Packit Service cd7d79
                    raise RuntimeError('Unable to parse range: "' + range + '"')
Packit Service 76cb02
        return intervals
Packit Service 76cb02
Packit Service 76cb02
    def _parse_transitions(self, node, transition_type):
Packit Service 76cb02
        """Evaluate the font elements of the specified fontspec element for the
Packit Service 76cb02
        specified transition type (enter, inter or exit).
Packit Service 76cb02
        """
Packit Service 76cb02
        fontlist = self.transitions[transition_type]
Packit Service 76cb02
Packit Service 76cb02
        for dom_transition in node.getElementsByTagName(transition_type):
Packit Service 76cb02
            for dom_font in dom_transition.getElementsByTagName('font'):
Packit Service 76cb02
                font = ''
Packit Service 76cb02
                types = dom_font.getAttribute("type")
Packit Service 76cb02
                types = types.split()
Packit Service 76cb02
                for dom_child in dom_font.childNodes:
Packit Service 76cb02
                    if dom_child.nodeType == dom_child.TEXT_NODE:
Packit Service 76cb02
                        font += dom_child.nodeValue
Packit Service 76cb02
                if (font):
Packit Service 76cb02
                    for type in types:
Packit Service 76cb02
                        fontlist[type] = font
Packit Service 76cb02
Packit Service 76cb02
    def _switch_to(self, fonts):
Packit Service 76cb02
        """
Packit Service 76cb02
        Return a string with the XeTeX font switching commands for the
Packit Service 76cb02
        specified font types.
Packit Service 76cb02
        """
Packit Service 76cb02
        s = ''
Packit Service 76cb02
        for type, font in fonts.items():
Packit Service 76cb02
            s += '\switch%sfont{%s}' % (type, font)
Packit Service 76cb02
        if s:
Packit Service 76cb02
            s = r"\savefamily" + s + r"\loadfamily{}"
Packit Service 76cb02
        return s
Packit Service 76cb02
Packit Service 76cb02
    def enter(self):
Packit Service 76cb02
        self.log.debug("enter in %s" % self.id)
Packit Service 76cb02
        s = self._switch_to(self.transitions["enter"])
Packit Service 76cb02
        return s
Packit Service 76cb02
Packit Service 76cb02
    def exit(self):
Packit Service 76cb02
        self.log.debug("exit from %s" % self.id)
Packit Service 76cb02
        s = self._switch_to(self.transitions["exit"])
Packit Service 76cb02
        return s
Packit Service 76cb02
Packit Service 76cb02
    def interchar(self):
Packit Service 76cb02
        s = self._switch_to(self.transitions["inter"])
Packit Service 76cb02
        return s
Packit Service 76cb02
Packit Service 76cb02
    def __str__(self):
Packit Service 76cb02
        """Dump the instance's data attributes."""
Packit Service 76cb02
        string = 'FontSpec:'
Packit Service 76cb02
        string += '\n  Id: %s' % self.id
Packit Service 76cb02
        string += '\n  Refmode: %s' % self.refmode
Packit Service 76cb02
        string += '\n  subFirst: %s' % self.subfont_first
Packit Service 76cb02
        for interval in self._intervals:
Packit Service 76cb02
            string += '\n' + _indent(str(interval))
Packit Service 76cb02
        return string
Packit Service 76cb02
Packit Service 76cb02
    def add_subfont(self, fontspec):
Packit Service 76cb02
        self.log.debug("%s -> %s" % (self.id, fontspec.id))
Packit Service 76cb02
        if self.subfont_first:
Packit Service 76cb02
            self.fontspecs.insert(-1, fontspec)
Packit Service 76cb02
        else:
Packit Service 76cb02
            self.fontspecs.append(fontspec)
Packit Service 76cb02
Packit Service 76cb02
    def add_char(self, char):
Packit Service 76cb02
        self._intervals.append(UnicodeInterval().from_char(char))
Packit Service 76cb02
Packit Service 76cb02
    def add_uranges(self, ranges, depth=1):
Packit Service 76cb02
        # Recursively extend the supported character range
Packit Service 76cb02
        if depth:
Packit Service 76cb02
            for f in self.fontspecs:
Packit Service 76cb02
                if f != self:
Packit Service 76cb02
                    f.add_uranges(ranges)
Packit Service 76cb02
        self._intervals.extend(ranges)
Packit Service 76cb02
Packit Service 76cb02
    def add_ignored(self, ranges, depth=1):
Packit Service 76cb02
        if depth:
Packit Service 76cb02
            for f in self.fontspecs:
Packit Service 76cb02
                if f != self:
Packit Service 76cb02
                    f.add_ignored(ranges)
Packit Service 76cb02
        self._ignored.extend(ranges)
Packit Service 76cb02
Packit Service 76cb02
    def get_uranges(self):
Packit Service 76cb02
        return self._intervals
Packit Service 76cb02
Packit Service 76cb02
    def contains(self, char):
Packit Service 76cb02
        #print "%s: %s" % (self.id, self._intervals)
Packit Service 76cb02
        for interval in self._intervals:
Packit Service 76cb02
            if interval.contains(char):
Packit Service 76cb02
                return True
Packit Service 76cb02
        else:
Packit Service 76cb02
            return False
Packit Service 76cb02
Packit Service 76cb02
    def isignored(self, char):
Packit Service 76cb02
        self.log.debug("%s: %s" % (self.id, [ str(a) for a in self._ignored ]))
Packit Service 76cb02
        for interval in self._ignored:
Packit Service 76cb02
            if interval.contains(char):
Packit Service 76cb02
                return True
Packit Service 76cb02
        else:
Packit Service 76cb02
            return False
Packit Service 76cb02
Packit Service 76cb02
    def _loghas(self, id, char):
Packit Service 76cb02
        try:
Packit Service 76cb02
            self.log.debug("%s has '%s'" % (id, str(char)))
Packit Service 76cb02
        except:
Packit Service 76cb02
            self.log.debug("%s has '%s'" % (id, ord(char)))
Packit Service 76cb02
Packit Service 76cb02
    def match(self, char, excluded=None):
Packit Service 76cb02
        """Determine whether the font specification matches the specified
Packit Service 76cb02
        object, thereby considering refmode.
Packit Service 76cb02
        """
Packit Service 76cb02
        fontspec = None
Packit Service 76cb02
        self.log.debug( "Lookup in %s" % self.id)
Packit Service 76cb02
        if self.isignored(char):
Packit Service 76cb02
            self._loghas(self.id, char)
Packit Service 76cb02
            return self
Packit Service 76cb02
Packit Service 76cb02
        for fontspec in self.fontspecs:
Packit Service 76cb02
            # Don't waste time in scanning excluded nodes
Packit Service 76cb02
            if fontspec == excluded:
Packit Service 76cb02
                continue
Packit Service 76cb02
            #print " Look in %s" % fontspec.id
Packit Service 76cb02
            if fontspec.contains(char):
Packit Service 76cb02
                self._loghas(fontspec.id, char)
Packit Service 76cb02
                return fontspec
Packit Service 76cb02
        return None
Packit Service 76cb02