1# -*- coding: utf-8 -*-
2# vim:et sts=4 sw=4
3#
4# ibus-typing-booster - A completion input method for IBus
5#
6# Copyright (c) 2015-2016 Mike FABIAN <mfabian@redhat.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program.  If not, see <http://www.gnu.org/licenses/>
20
21'''A module to find out which fonts are used by pango to render a string
22'''
23
24import sys
25import ctypes
26
27class glib__GSList(ctypes.Structure):
28    pass
29glib__GSList._fields_ = [
30    ('data', ctypes.c_void_p),
31    ('next', ctypes.POINTER(glib__GSList)),
32]
33class libgtk3__GtkWidget(ctypes.Structure):
34    pass
35class libpango__PangoAttribute(ctypes.Structure):
36    pass
37class libpango__PangoAttrList(ctypes.Structure):
38    pass
39class libpango__PangoContext(ctypes.Structure):
40    pass
41class libpango__PangoLayout(ctypes.Structure):
42    pass
43class libpango__PangoFontDescription(ctypes.Structure):
44    pass
45class libpango__PangoLayoutLine(ctypes.Structure):
46    pass
47libpango__PangoLayoutLine._fields_ = [
48    ('layout', ctypes.POINTER(libpango__PangoLayout)),
49    ('start_index', ctypes.c_int), # start of line as byte index into layout->text
50    ('length', ctypes.c_int), # length of line in bytes
51    ('runs', ctypes.POINTER(glib__GSList)),
52    ('is_paragraph_start', ctypes.c_uint), # TRUE if this is the first line of the paragraph
53    ('resolved_dir', ctypes.c_uint), # Resolved PangoDirection of line
54]
55class libpango__PangoGlyphString(ctypes.Structure):
56    pass
57class libpango__PangoEngineShape(ctypes.Structure):
58    pass
59class libpango__PangoEngineLang(ctypes.Structure):
60    pass
61class libpango__PangoFont(ctypes.Structure):
62    pass
63class libpango__PangoLanguage(ctypes.Structure):
64    pass
65class libpango__PangoAnalysis(ctypes.Structure):
66    _fields_ = [
67        ('shape_engine', ctypes.POINTER(libpango__PangoEngineShape)),
68        ('lang_engine', ctypes.POINTER(libpango__PangoEngineLang)),
69        ('font', ctypes.POINTER(libpango__PangoFont)),
70        ('level', ctypes.c_uint8),
71        ('gravity', ctypes.c_uint8),
72        ('flags', ctypes.c_uint8),
73        ('script', ctypes.c_uint8),
74        ('language', ctypes.POINTER(libpango__PangoLanguage)),
75        ('extra_attrs', ctypes.POINTER(glib__GSList)),
76    ]
77class libpango__PangoItem(ctypes.Structure):
78    pass
79libpango__PangoItem._fields_ = [
80    ('offset', ctypes.c_int),
81    ('length', ctypes.c_int),
82    ('num_chars', ctypes.c_int),
83    ('analysis', libpango__PangoAnalysis),
84]
85class libpango__PangoGlyphItem(ctypes.Structure):
86    pass
87libpango__PangoGlyphItem._fields_ = [
88    ('item', ctypes.POINTER(libpango__PangoItem)),
89    ('glyphs', ctypes.POINTER(libpango__PangoGlyphString)),
90]
91
92libglib__lib = None
93libgtk3__lib = None
94libpango__lib = None
95libglib__g_slist_length = None
96libglib__g_slist_nth_data = None
97libgtk3__gtk_init = None
98libgtk3__gtk_label_new = None
99libgtk3__gtk_widget_get_pango_context = None
100libpango__pango_layout_new = None
101libpango__pango_font_description_from_string = None
102libpango__pango_layout_set_font_description = None
103libpango__pango_layout_set_text = None
104libpango__pango_attr_list_new = None
105libpango__pango_attr_list_unref = None
106libpango__pango_attr_fallback_new = None
107libpango__pango_attribute_destroy = None
108libpango__pango_attr_list_insert = None
109libpango__pango_layout_set_attributes = None
110libpango__pango_layout_get_line_readonly = None
111libpango__pango_font_describe = None
112libpango__pango_font_description_get_family = None
113
114def get_fonts_used_for_text(font, text, fallback=True):
115    '''Return a list of fonts which were really used to render a text
116
117    :param font: The font requested to render the text in
118    :type font: String
119    :param text: The text to render
120    :type text: String
121    :param fallback: Whether to enable font fallback. If disabled, then
122                     glyphs will only be used from the closest matching
123                     font on the system. No fallback will be done to other
124                     fonts on the system that might contain the glyphs needed
125                     for the text.
126    :type fallback: Boolean
127    :rtype: List of strings
128
129    Examples:
130
131    >>> get_fonts_used_for_text('DejaVu Sans Mono', '�� ')
132    [('��', 'Noto Color Emoji'), (' ', 'DejaVu Sans Mono')]
133
134    >>> get_fonts_used_for_text('DejaVu Sans', '日本語 नमस्ते')
135    [('日本語', 'Droid Sans'), (' ', 'DejaVu Sans'), ('नमस्ते', 'Droid Sans')]
136
137    >>> get_fonts_used_for_text('DejaVu Sans', '日本語 ��️')
138    [('日本語', 'Droid Sans'), (' ', 'DejaVu Sans'), ('��️', 'Noto Color Emoji')]
139    '''
140    fonts_used = []
141    label = libgtk3__gtk_label_new(ctypes.c_char_p(b''))
142    pango_context_p = libgtk3__gtk_widget_get_pango_context(label)
143    pango_layout_p = libpango__pango_layout_new(pango_context_p)
144    pango_font_description_p = libpango__pango_font_description_from_string(
145        ctypes.c_char_p(font.encode('UTF-8', errors='replace')))
146    libpango__pango_layout_set_font_description(
147        pango_layout_p, pango_font_description_p)
148    pango_attr_list_p = libpango__pango_attr_list_new()
149    pango_attr_p = libpango__pango_attr_fallback_new(
150        ctypes.c_bool(fallback))
151    libpango__pango_attr_list_insert(
152        pango_attr_list_p, pango_attr_p)
153    libpango__pango_layout_set_attributes(
154        pango_layout_p, pango_attr_list_p)
155    text_utf8 = text.encode('UTF-8', errors='replace')
156    libpango__pango_layout_set_text(
157        pango_layout_p,
158        ctypes.c_char_p(text_utf8),
159        ctypes.c_int(-1))
160    pango_layout_line_p = libpango__pango_layout_get_line_readonly(
161        pango_layout_p, ctypes.c_int(0))
162    gs_list = pango_layout_line_p.contents.runs.contents
163    number_of_runs = libglib__g_slist_length(gs_list)
164    for index in range(0, number_of_runs):
165        gpointer = libglib__g_slist_nth_data(gs_list, ctypes.c_uint(index))
166        pango_glyph_item = ctypes.cast(
167            gpointer,
168            ctypes.POINTER(libpango__PangoGlyphItem)).contents
169        pango_item_p = pango_glyph_item.item
170        offset = pango_item_p.contents.offset
171        length = pango_item_p.contents.length
172        num_chars = pango_item_p.contents.num_chars
173        pango_analysis = pango_item_p.contents.analysis
174        pango_font_p = pango_analysis.font
175        font_description_used = libpango__pango_font_describe(pango_font_p)
176        run_text = text_utf8[offset:offset + length].decode(
177            'UTF-8', errors='replace')
178        run_family = libpango__pango_font_description_get_family(
179            font_description_used).decode('UTF-8', errors='replace')
180        fonts_used.append((run_text, run_family))
181    libpango__pango_attr_list_unref(pango_attr_list_p)
182    libpango__pango_attribute_destroy(pango_attr_p)
183    return fonts_used
184
185def _init():
186    global libglib__lib
187    libglib__lib = ctypes.CDLL('libglib-2.0.so.0', mode=ctypes.RTLD_GLOBAL)
188    global libgtk3__lib
189    libgtk3__lib = ctypes.CDLL('libgtk-3.so.0', mode=ctypes.RTLD_GLOBAL)
190    global libpango__lib
191    libpango__lib = ctypes.CDLL('libpango-1.0.so.0', mode=ctypes.RTLD_GLOBAL)
192    global libglib__g_slist_length
193    libglib__g_slist_length = libglib__lib.g_slist_length
194    libglib__g_slist_length.argtypes = [
195        ctypes.POINTER(glib__GSList)]
196    libglib__g_slist_length.restype = ctypes.c_uint
197    global libglib__g_slist_nth_data
198    libglib__g_slist_nth_data = libglib__lib.g_slist_nth_data
199    libglib__g_slist_nth_data.argtypes = [
200        ctypes.POINTER(glib__GSList), ctypes.c_uint]
201    libglib__g_slist_nth_data.restype = ctypes.c_void_p
202    global libgtk3__gtk_init
203    libgtk3__gtk_init = libgtk3__lib.gtk_init
204    libgtk3__gtk_init.argtypes = [
205        ctypes.POINTER(ctypes.c_int),
206        ctypes.POINTER(ctypes.POINTER(ctypes.c_char_p))]
207    global libgtk3__gtk_label_new
208    libgtk3__gtk_label_new = libgtk3__lib.gtk_label_new
209    libgtk3__gtk_label_new.argtypes = [ctypes.c_char_p]
210    libgtk3__gtk_label_new.restype = ctypes.POINTER(
211        libgtk3__GtkWidget)
212    global libgtk3__gtk_widget_get_pango_context
213    libgtk3__gtk_widget_get_pango_context = libgtk3__lib.gtk_widget_get_pango_context
214    libgtk3__gtk_widget_get_pango_context.argtypes = [
215        ctypes.POINTER(libgtk3__GtkWidget)]
216    libgtk3__gtk_widget_get_pango_context.restype = ctypes.POINTER(
217        libpango__PangoContext)
218    global libpango__pango_layout_new
219    libpango__pango_layout_new = libpango__lib.pango_layout_new
220    libpango__pango_layout_new.argtypes = [
221        ctypes.POINTER(libpango__PangoContext)]
222    libpango__pango_layout_new.restype = ctypes.POINTER(
223        libpango__PangoLayout)
224    global libpango__pango_font_description_from_string
225    libpango__pango_font_description_from_string = libpango__lib.pango_font_description_from_string
226    libpango__pango_font_description_from_string.argtypes = [ctypes.c_char_p]
227    libpango__pango_font_description_from_string.restype = ctypes.POINTER(
228        libpango__PangoFontDescription)
229    global libpango__pango_layout_set_font_description
230    libpango__pango_layout_set_font_description = libpango__lib.pango_layout_set_font_description
231    libpango__pango_layout_set_font_description.argtypes = [
232        ctypes.POINTER(libpango__PangoLayout),
233        ctypes.POINTER(libpango__PangoFontDescription)]
234    global libpango__pango_layout_set_text
235    libpango__pango_layout_set_text = libpango__lib.pango_layout_set_text
236    libpango__pango_layout_set_text.argtypes = [
237        ctypes.POINTER(libpango__PangoLayout), ctypes.c_char_p, ctypes.c_int]
238    global libpango__pango_attr_list_new
239    libpango__pango_attr_list_new = libpango__lib.pango_attr_list_new
240    libpango__pango_attr_list_new.argtypes = []
241    libpango__pango_attr_list_new.restype = ctypes.POINTER(
242        libpango__PangoAttrList)
243    global libpango__pango_attr_list_unref
244    libpango__pango_attr_list_unref = libpango__lib.pango_attr_list_unref
245    libpango__pango_attr_list_unref.argtypes = [
246        ctypes.POINTER(libpango__PangoAttrList)]
247    global libpango__pango_attr_fallback_new
248    libpango__pango_attr_fallback_new = libpango__lib.pango_attr_fallback_new
249    libpango__pango_attr_fallback_new.argtypes = [
250        ctypes.c_bool]
251    libpango__pango_attr_fallback_new.restype = ctypes.POINTER(
252        libpango__PangoAttribute)
253    global libpango__pango_attribute_destroy
254    libpango__pango_attribute_destroy = libpango__lib.pango_attribute_destroy
255    libpango__pango_attribute_destroy.argtypes = [
256        ctypes.POINTER(libpango__PangoAttribute)]
257    global libpango__pango_attr_list_insert
258    libpango__pango_attr_list_insert = libpango__lib.pango_attr_list_insert
259    libpango__pango_attr_list_insert.argtypes = [
260        ctypes.POINTER(libpango__PangoAttrList),
261        ctypes.POINTER(libpango__PangoAttribute)]
262    global libpango__pango_layout_set_attributes
263    libpango__pango_layout_set_attributes = libpango__lib.pango_layout_set_attributes
264    libpango__pango_layout_set_attributes.argtypes = [
265        ctypes.POINTER(libpango__PangoLayout),
266        ctypes.POINTER(libpango__PangoAttrList)]
267    global libpango__pango_layout_get_line_readonly
268    libpango__pango_layout_get_line_readonly = libpango__lib.pango_layout_get_line_readonly
269    libpango__pango_layout_get_line_readonly.argtypes = [
270        ctypes.POINTER(libpango__PangoLayout), ctypes.c_int]
271    libpango__pango_layout_get_line_readonly.restype = ctypes.POINTER(
272        libpango__PangoLayoutLine)
273    global libpango__pango_font_describe
274    libpango__pango_font_describe = libpango__lib.pango_font_describe
275    libpango__pango_font_describe.argtypes = [
276        ctypes.POINTER(libpango__PangoFont)]
277    libpango__pango_font_describe.restype = ctypes.POINTER(
278        libpango__PangoFontDescription)
279    global libpango__pango_font_description_get_family
280    libpango__pango_font_description_get_family = libpango__lib.pango_font_description_get_family
281    libpango__pango_font_description_get_family.argtypes = [
282        ctypes.POINTER(libpango__PangoFontDescription)]
283    libpango__pango_font_description_get_family.restype = ctypes.c_char_p
284    libgtk3__gtk_init(
285        ctypes.byref(ctypes.c_int(0)),
286        ctypes.byref(ctypes.pointer(ctypes.c_char_p(b''))))
287
288def _del():
289    '''Cleanup'''
290    pass
291
292class __ModuleInitializer:
293    def __init__(self):
294        _init()
295
296    def __del__(self):
297        return
298
299__module_init = __ModuleInitializer()
300
301if __name__ == "__main__":
302    import doctest
303    (FAILED, _ATTEMPTED) = doctest.testmod()
304    sys.exit(FAILED)
305