Blob Blame History Raw
# -*- coding: utf-8 -*-
# vim:et sts=4 sw=4
#
# ibus-typing-booster - A completion input method for IBus
#
# Copyright (c) 2015-2016 Mike FABIAN <mfabian@redhat.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>

'''A module to find out which fonts are used by pango to render a string
'''

import sys
import ctypes

class glib__GSList(ctypes.Structure):
    pass
glib__GSList._fields_ = [
    ('data', ctypes.c_void_p),
    ('next', ctypes.POINTER(glib__GSList)),
]
class libgtk3__GtkWidget(ctypes.Structure):
    pass
class libpango__PangoAttribute(ctypes.Structure):
    pass
class libpango__PangoAttrList(ctypes.Structure):
    pass
class libpango__PangoContext(ctypes.Structure):
    pass
class libpango__PangoLayout(ctypes.Structure):
    pass
class libpango__PangoFontDescription(ctypes.Structure):
    pass
class libpango__PangoLayoutLine(ctypes.Structure):
    pass
libpango__PangoLayoutLine._fields_ = [
    ('layout', ctypes.POINTER(libpango__PangoLayout)),
    ('start_index', ctypes.c_int), # start of line as byte index into layout->text
    ('length', ctypes.c_int), # length of line in bytes
    ('runs', ctypes.POINTER(glib__GSList)), 
    ('is_paragraph_start', ctypes.c_uint), # TRUE if this is the first line of the paragraph
    ('resolved_dir', ctypes.c_uint), # Resolved PangoDirection of line
]
class libpango__PangoGlyphString(ctypes.Structure):
    pass
class libpango__PangoEngineShape(ctypes.Structure):
    pass
class libpango__PangoEngineLang(ctypes.Structure):
    pass
class libpango__PangoFont(ctypes.Structure):
    pass
class libpango__PangoLanguage(ctypes.Structure):
    pass
class libpango__PangoAnalysis(ctypes.Structure):
    _fields_ = [
        ('shape_engine', ctypes.POINTER(libpango__PangoEngineShape)),
        ('lang_engine', ctypes.POINTER(libpango__PangoEngineLang)),
        ('font', ctypes.POINTER(libpango__PangoFont)),
        ('level', ctypes.c_uint8),
        ('gravity', ctypes.c_uint8),
        ('flags', ctypes.c_uint8),
        ('script', ctypes.c_uint8),
        ('language', ctypes.POINTER(libpango__PangoLanguage)),
        ('extra_attrs', ctypes.POINTER(glib__GSList)),
    ]
class libpango__PangoItem(ctypes.Structure):
    pass
libpango__PangoItem._fields_ = [
    ('offset', ctypes.c_int),
    ('length', ctypes.c_int),
    ('num_chars', ctypes.c_int),
    ('analysis', libpango__PangoAnalysis),
]
class libpango__PangoGlyphItem(ctypes.Structure):
    pass
libpango__PangoGlyphItem._fields_ = [
    ('item', ctypes.POINTER(libpango__PangoItem)),
    ('glyphs', ctypes.POINTER(libpango__PangoGlyphString)),
]

libglib__lib = None
libgtk3__lib = None
libpango__lib = None
libglib__g_slist_length = None
libglib__g_slist_nth_data = None
libgtk3__gtk_init = None
libgtk3__gtk_label_new = None
libgtk3__gtk_widget_get_pango_context = None
libpango__pango_layout_new = None
libpango__pango_font_description_from_string = None
libpango__pango_layout_set_font_description = None
libpango__pango_layout_set_text = None
libpango__pango_attr_list_new = None
libpango__pango_attr_list_unref = None
libpango__pango_attr_fallback_new = None
libpango__pango_attribute_destroy = None
libpango__pango_attr_list_insert = None
libpango__pango_layout_set_attributes = None
libpango__pango_layout_get_line_readonly = None
libpango__pango_font_describe = None
libpango__pango_font_description_get_family = None

def get_fonts_used_for_text(font, text, fallback=True):
    '''Return a list of fonts which were really used to render a text

    :param font: The font requested to render the text in
    :type font: String
    :param text: The text to render
    :type text: String
    :param fallback: Whether to enable font fallback. If disabled, then
                     glyphs will only be used from the closest matching
                     font on the system. No fallback will be done to other
                     fonts on the system that might contain the glyphs needed
                     for the text.
    :type fallback: Boolean
    :rtype: List of strings

    Examples:

    >>> get_fonts_used_for_text('DejaVu Sans Mono', '😀 ')
    [('😀', 'Noto Color Emoji'), (' ', 'DejaVu Sans Mono')]

    >>> get_fonts_used_for_text('DejaVu Sans', '日本語 नमस्ते')
    [('日本語', 'IPAPGothic'), (' ', 'DejaVu Sans'), ('नमस्ते', 'Lohit Hindi')]

    >>> get_fonts_used_for_text('DejaVu Sans', '日本語 🕉️')
    [('日本語', 'IPAPGothic'), (' ', 'DejaVu Sans'), ('🕉️', 'Noto Color Emoji')]
    '''
    fonts_used = []
    label = libgtk3__gtk_label_new(ctypes.c_char_p(b''))
    pango_context_p = libgtk3__gtk_widget_get_pango_context(label)
    pango_layout_p = libpango__pango_layout_new(pango_context_p)
    pango_font_description_p = libpango__pango_font_description_from_string(
        ctypes.c_char_p(font.encode('UTF-8', errors='replace')))
    libpango__pango_layout_set_font_description(
        pango_layout_p, pango_font_description_p)
    pango_attr_list_p = libpango__pango_attr_list_new()
    pango_attr_p = libpango__pango_attr_fallback_new(
        ctypes.c_bool(fallback))
    libpango__pango_attr_list_insert(
        pango_attr_list_p, pango_attr_p)
    libpango__pango_layout_set_attributes(
        pango_layout_p, pango_attr_list_p)
    text_utf8 = text.encode('UTF-8', errors='replace')
    libpango__pango_layout_set_text(
        pango_layout_p,
        ctypes.c_char_p(text_utf8),
        ctypes.c_int(-1))
    pango_layout_line_p = libpango__pango_layout_get_line_readonly(
        pango_layout_p, ctypes.c_int(0))
    gs_list = pango_layout_line_p.contents.runs.contents
    number_of_runs = libglib__g_slist_length(gs_list)
    for index in range(0, number_of_runs):
        gpointer = libglib__g_slist_nth_data(gs_list, ctypes.c_uint(index))
        pango_glyph_item = ctypes.cast(
            gpointer,
            ctypes.POINTER(libpango__PangoGlyphItem)).contents
        pango_item_p = pango_glyph_item.item
        offset = pango_item_p.contents.offset
        length = pango_item_p.contents.length
        num_chars = pango_item_p.contents.num_chars
        pango_analysis = pango_item_p.contents.analysis
        pango_font_p = pango_analysis.font
        font_description_used = libpango__pango_font_describe(pango_font_p)
        run_text = text_utf8[offset:offset + length].decode('UTF-8', errors='replace')
        run_family = libpango__pango_font_description_get_family(
            font_description_used).decode('UTF-8', errors='replace')
        fonts_used.append((run_text, run_family))
    libpango__pango_attr_list_unref(pango_attr_list_p)
    libpango__pango_attribute_destroy(pango_attr_p)
    return fonts_used

def _init():
    global libglib__lib
    libglib__lib = ctypes.CDLL('libglib-2.0.so.0', mode=ctypes.RTLD_GLOBAL)
    global libgtk3__lib
    libgtk3__lib = ctypes.CDLL('libgtk-3.so.0', mode=ctypes.RTLD_GLOBAL)
    global libpango__lib
    libpango__lib = ctypes.CDLL('libpango-1.0.so.0', mode=ctypes.RTLD_GLOBAL)
    global libglib__g_slist_length
    libglib__g_slist_length = libglib__lib.g_slist_length
    libglib__g_slist_length.argtypes = [
        ctypes.POINTER(glib__GSList)]
    libglib__g_slist_length.restype = ctypes.c_uint
    global libglib__g_slist_nth_data
    libglib__g_slist_nth_data = libglib__lib.g_slist_nth_data
    libglib__g_slist_nth_data.argtypes = [
        ctypes.POINTER(glib__GSList), ctypes.c_uint]
    libglib__g_slist_nth_data.restype = ctypes.c_void_p
    global libgtk3__gtk_init
    libgtk3__gtk_init = libgtk3__lib.gtk_init
    libgtk3__gtk_init.argtypes = [
        ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p))]
    global libgtk3__gtk_label_new
    libgtk3__gtk_label_new = libgtk3__lib.gtk_label_new
    libgtk3__gtk_label_new.argtypes = [ctypes.c_char_p]
    libgtk3__gtk_label_new.restype = ctypes.POINTER(
        libgtk3__GtkWidget)
    global libgtk3__gtk_widget_get_pango_context
    libgtk3__gtk_widget_get_pango_context = libgtk3__lib.gtk_widget_get_pango_context
    libgtk3__gtk_widget_get_pango_context.argtypes = [ctypes.POINTER(libgtk3__GtkWidget)]
    libgtk3__gtk_widget_get_pango_context.restype = ctypes.POINTER(
        libpango__PangoContext)
    global libpango__pango_layout_new
    libpango__pango_layout_new = libpango__lib.pango_layout_new
    libpango__pango_layout_new.argtypes = [ctypes.POINTER(libpango__PangoContext)]
    libpango__pango_layout_new.restype = ctypes.POINTER(
        libpango__PangoLayout)
    global libpango__pango_font_description_from_string
    libpango__pango_font_description_from_string = libpango__lib.pango_font_description_from_string
    libpango__pango_font_description_from_string.argtypes = [ctypes.c_char_p]
    libpango__pango_font_description_from_string.restype = ctypes.POINTER(
        libpango__PangoFontDescription)
    global libpango__pango_layout_set_font_description
    libpango__pango_layout_set_font_description = libpango__lib.pango_layout_set_font_description
    libpango__pango_layout_set_font_description.argtypes = [
        ctypes.POINTER(libpango__PangoLayout),
        ctypes.POINTER(libpango__PangoFontDescription)]
    global libpango__pango_layout_set_text
    libpango__pango_layout_set_text = libpango__lib.pango_layout_set_text
    libpango__pango_layout_set_text.argtypes = [
        ctypes.POINTER(libpango__PangoLayout), ctypes.c_char_p, ctypes.c_int]
    global libpango__pango_attr_list_new
    libpango__pango_attr_list_new = libpango__lib.pango_attr_list_new
    libpango__pango_attr_list_new.argtypes = []
    libpango__pango_attr_list_new.restype = ctypes.POINTER(
        libpango__PangoAttrList)
    global libpango__pango_attr_list_unref
    libpango__pango_attr_list_unref = libpango__lib.pango_attr_list_unref
    libpango__pango_attr_list_unref.argtypes = [
        ctypes.POINTER(libpango__PangoAttrList)]
    global libpango__pango_attr_fallback_new
    libpango__pango_attr_fallback_new = libpango__lib.pango_attr_fallback_new
    libpango__pango_attr_fallback_new.argtypes = [
        ctypes.c_bool]
    libpango__pango_attr_fallback_new.restype = ctypes.POINTER(
        libpango__PangoAttribute)
    global libpango__pango_attribute_destroy
    libpango__pango_attribute_destroy = libpango__lib.pango_attribute_destroy
    libpango__pango_attribute_destroy.argtypes = [
        ctypes.POINTER(libpango__PangoAttribute)]
    global libpango__pango_attr_list_insert
    libpango__pango_attr_list_insert = libpango__lib.pango_attr_list_insert
    libpango__pango_attr_list_insert.argtypes = [
        ctypes.POINTER(libpango__PangoAttrList),
        ctypes.POINTER(libpango__PangoAttribute)]
    global libpango__pango_layout_set_attributes
    libpango__pango_layout_set_attributes = libpango__lib.pango_layout_set_attributes
    libpango__pango_layout_set_attributes.argtypes = [
        ctypes.POINTER(libpango__PangoLayout),
        ctypes.POINTER(libpango__PangoAttrList)]
    global libpango__pango_layout_get_line_readonly
    libpango__pango_layout_get_line_readonly = libpango__lib.pango_layout_get_line_readonly
    libpango__pango_layout_get_line_readonly.argtypes = [
        ctypes.POINTER(libpango__PangoLayout), ctypes.c_int]
    libpango__pango_layout_get_line_readonly.restype = ctypes.POINTER(
        libpango__PangoLayoutLine)
    global libpango__pango_font_describe
    libpango__pango_font_describe = libpango__lib.pango_font_describe
    libpango__pango_font_describe.argtypes = [
        ctypes.POINTER(libpango__PangoFont)]
    libpango__pango_font_describe.restype = ctypes.POINTER(
        libpango__PangoFontDescription)
    global libpango__pango_font_description_get_family
    libpango__pango_font_description_get_family = libpango__lib.pango_font_description_get_family
    libpango__pango_font_description_get_family.argtypes = [
        ctypes.POINTER(libpango__PangoFontDescription)]
    libpango__pango_font_description_get_family.restype = ctypes.c_char_p
    libgtk3__gtk_init(
        ctypes.byref(ctypes.c_int(0)),
        ctypes.byref(ctypes.pointer(ctypes.c_char_p(b''))))

def _del():
    '''Cleanup'''
    pass

class __ModuleInitializer:
    def __init__(self):
        _init()
        return

    def __del__(self):
        return

__module_init = __ModuleInitializer()

if __name__ == "__main__":
    import doctest
    (failed,  attempted) = doctest.testmod()
    if failed:
        sys.exit(1)
    else:
        sys.exit(0)