Blob Blame History Raw
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Time-stamp: <2008-06-23 22:21:26 ah>

"""
Provide an encoder for a font specification configuration: the encoder is fed
with Unicode characters one by one and determines the needed font switches
between the preceding and the current character.
"""
import sys
import re
import xml.dom.minidom

from fontspec import UnicodeInterval
from fsconfig import FontSpecConfig


class FontSpecEncoder:
    """
    Encoder working with font specifications: it is fed
    with Unicode characters one by one and it inserts the needed font switches
    between the preceding and the current character.
    """

    def __init__(self, configuration):
        """
        Create a font specification encoder from the specified configuration
        file (file name or file-like object).
        """
        self._conf = FontSpecConfig(configuration)
        self._cur_fontspec = None
        self._ref_stack = [self._conf.default_fontspec]

    def reset(self):
        # Restart from the default fontspec to avoid a useless 'enter' from None
        self._cur_fontspec = self._conf.default_fontspec
        self._ref_stack = [self._conf.default_fontspec]

    def _switch_to(self, fontspec):
        """
        Insert the transition string, according to the newly selected
        fontspec and the previously selected fontspec
        """
        s = ""
        # If the font hasn't changed, just insert optional inter-char material
        if fontspec == self._cur_fontspec:
            return fontspec.interchar()

        # A new font is selected, so exit from current font stream
        if self._cur_fontspec:
            s += self._cur_fontspec.exit()

        # Enter into the current font stream
        self._cur_fontspec = fontspec
        s += fontspec.enter()
        return s

    def _encode(self, char):
        """
        Select the fontspec matching the specified <char>, and switch to
        this font as current font.

        The principle to find out the fontspec is:
        - to find from the current font level a matching font
          (the current font leaf or the direct font children)
        - if no font is found try with the parent font, and so on,
          up to the default root font (that must exist).
        """
        fontspec = self._cur_fontspec or self._conf.default_fontspec

        print >>sys.stderr, "Current:", fontspec.id
        fontspec = fontspec.match(char)
        while not(fontspec):
            leaf = self._ref_stack.pop()
            fontspec = self._ref_stack[-1].match(char, excluded=leaf)

        if fontspec != self._ref_stack[-1]:
            self._ref_stack.append(fontspec)

        return self._switch_to(fontspec)

    def ignorechars(self, charset):
        "Characters to ignore in font selection (maintain the current one)"
        intervals = [ UnicodeInterval().from_char(c) for c in charset ]
        self._conf.default_fontspec.add_ignored(intervals)

    def encode(self, char):
        """
        Return a string consisting of the specified character prepended by
        all necessary font switching commands.
        """
        return (self._encode(char), char)

    def stop(self):
        """
        Cleanly exit from the current fontspec
        """
        if self._cur_fontspec:
            s = self._cur_fontspec.exit()
            self._cur_fontspec = None
            return s