*** empty log message ***
[m17n/m17n-pango.git] / m17n-fc.c
1 /* Pango
2  * m17n-fc.c: Generic shaper using the m17n library for FreeType-based backends
3
4    Copyright (C) 2004, 2005, 2006
5      National Institute of Advanced Industrial Science and Technology (AIST)
6      Registration Number H16PRO276
7
8    This file is part of the pango-m17n library.
9
10    The pango-m17n library is free software; you can redistribute it
11    and/or modify it under the terms of the GNU Lesser General Public
12    License as published by the Free Software Foundation; either
13    version 2.1 of the License, or (at your option) any later version.
14
15    The pango-m17n library is distributed in the hope that it will be
16    useful, but WITHOUT ANY WARRANTY; without even the implied warranty
17    of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU Lesser General Public License for more details.
19
20    You should have received a copy of the GNU Lesser General Public
21    License along with the pango-m17n library; if not, write to the
22    Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
23    MA 02111-1307, USA.  */
24
25 #include <string.h>
26 #include <stdlib.h>
27
28 #include <pango/pango-engine.h>
29 #include <pango/pango-utils.h>
30 #include <pango/pangofc-font.h>
31
32 #include <m17n-gui.h>
33 #include <m17n-misc.h>
34
35 #ifdef G_LOG_DOMAIN
36 #undef G_LOG_DOMAIN
37 #endif
38 #define G_LOG_DOMAIN "Pango-M17N-FC"
39
40 /* Control printing of debug information.  It is initialized from the
41    environment variable PANGO_M17N_FC_DEBUG.  Currently, any positive
42    value turns on all debug messages.  */
43 static int debug_level;
44
45 /* Commonly used frame on nulldevice.  */
46 static MFrame *frame;
47
48 /* Commonly used "generic" fontset.  */
49 static MFontset *fontset;
50
51 /* Initialize this module.  The main task is to open a frame on
52    nulldevice.  */
53
54 static void
55 init_module ()
56 {
57   const char *envvar = g_getenv ("PANGO_M17N_FC_DEBUG");
58   MPlist *param;
59   MFace *face;
60
61   debug_level = envvar ? atoi (envvar) : 0;
62
63   param = mplist ();
64   face = mface ();
65   fontset = (mdatabase_find (Mfontset, msymbol ("generic"), Mnil, Mnil)
66              ? mfontset ("generic") : mfontset (NULL));
67   mface_put_prop (face, Mfontset, fontset);
68   mplist_put (param, Mface, face);
69   mplist_put (param, Mdevice, Mnil);
70   frame = mframe (param);
71   m17n_object_unref (face);
72   m17n_object_unref (param);
73 }
74
75 /* Finalize this module by freeing frame.  */
76
77 static void
78 fini_module ()
79 {
80   m17n_object_unref (frame);
81   m17n_object_unref (fontset);
82 }
83
84 /* No extra fields needed for these structures.  */
85 typedef PangoEngineShape      M17NEngineFc;
86 typedef PangoEngineShapeClass M17NEngineFcClass;
87
88 #define SCRIPT_ENGINE_NAME "M17NScriptEngineFc"
89 #define RENDER_TYPE PANGO_RENDER_TYPE_FC
90
91 static PangoEngineScriptInfo m17n_scripts[] = {
92   { PANGO_SCRIPT_ARABIC,   "*" },
93   { PANGO_SCRIPT_ARMENIAN, "*" },
94   { PANGO_SCRIPT_BENGALI,  "*" },
95   { PANGO_SCRIPT_BOPOMOFO, "*" },
96   { PANGO_SCRIPT_CHEROKEE, "*" },
97   { PANGO_SCRIPT_COPTIC,   "*" },
98   { PANGO_SCRIPT_CYRILLIC, "*" },
99   { PANGO_SCRIPT_DESERET,  "*" },
100   { PANGO_SCRIPT_DEVANAGARI, "*" },
101   { PANGO_SCRIPT_ETHIOPIC, "*" },
102   { PANGO_SCRIPT_GEORGIAN, "*" },
103   { PANGO_SCRIPT_GOTHIC,   "*" },
104   { PANGO_SCRIPT_GREEK,    "*" },
105   { PANGO_SCRIPT_GUJARATI, "*" },
106   { PANGO_SCRIPT_GURMUKHI, "*" },
107   { PANGO_SCRIPT_HAN,      "*" },
108   { PANGO_SCRIPT_HANGUL,   "*" },
109   { PANGO_SCRIPT_HEBREW,   "*" },
110   { PANGO_SCRIPT_HIRAGANA, "*" },
111   { PANGO_SCRIPT_KANNADA,  "*" },
112   { PANGO_SCRIPT_KATAKANA, "*" },
113   { PANGO_SCRIPT_KHMER,    "*" },
114   { PANGO_SCRIPT_LAO,      "*" },
115   { PANGO_SCRIPT_MALAYALAM, "*" },
116   { PANGO_SCRIPT_MYANMAR,  "*" },
117   { PANGO_SCRIPT_OGHAM,    "*" },
118   { PANGO_SCRIPT_OLD_ITALIC, "*" },
119   { PANGO_SCRIPT_ORIYA,     "*" },
120   { PANGO_SCRIPT_RUNIC,     "*" },
121   { PANGO_SCRIPT_SINHALA,   "*" },
122   { PANGO_SCRIPT_SYRIAC,    "*" },
123   { PANGO_SCRIPT_TAMIL,     "*" },
124   { PANGO_SCRIPT_TELUGU,    "*" },
125   { PANGO_SCRIPT_THAANA,    "*" },
126   { PANGO_SCRIPT_THAI,      "*" },
127   { PANGO_SCRIPT_TIBETAN,   "*" },
128   { PANGO_SCRIPT_CANADIAN_ABORIGINAL, "*" },
129   { PANGO_SCRIPT_YI,       "*" },
130
131 #ifdef PANGO_SCRIPT_BRAILLE
132   { PANGO_SCRIPT_BRAILLE,  "*" },
133   { PANGO_SCRIPT_CYPRIOT,  "*" },
134   { PANGO_SCRIPT_LIMBU,    "*" },
135   { PANGO_SCRIPT_OSMANYA,  "*" },
136   { PANGO_SCRIPT_SHAVIAN,  "*" },
137   { PANGO_SCRIPT_LINEAR_B, "*" },
138   { PANGO_SCRIPT_TAI_LE,   "*" },
139   { PANGO_SCRIPT_UGARITIC, "*" },
140 #endif  /* PANGO_SCRIPT_BRAILLE */
141
142 #ifdef PANGO_SCRIPT_NEW_TAI_LUE
143   { PANGO_SCRIPT_NEW_TAI_LUE, "*" },
144   { PANGO_SCRIPT_BUGINESE, "*" },
145   { PANGO_SCRIPT_GLAGOLITIC, "*" },
146   { PANGO_SCRIPT_TIFINAGH, "*" },
147   { PANGO_SCRIPT_SYLOTI_NAGRI, "*" },
148   { PANGO_SCRIPT_OLD_PERSIAN, "*" },
149   { PANGO_SCRIPT_KHAROSHTHI, "*" },
150 #endif  /* PANGO_SCRIPT_NEW_TAI_LUE */
151
152   { PANGO_SCRIPT_UNKNOWN,  "*" },
153
154   { PANGO_SCRIPT_COMMON,   "" }
155 };
156
157 static PangoEngineInfo script_engines[] = {
158   {
159     SCRIPT_ENGINE_NAME,
160     PANGO_ENGINE_TYPE_SHAPE,
161     RENDER_TYPE,
162     m17n_scripts, G_N_ELEMENTS (m17n_scripts)
163   }
164 };
165
166 static char *
167 m17n_fc_get_family (PangoFont *pango_font)
168 {
169   PangoFcFont *pango_fc_font = PANGO_FC_FONT (pango_font);
170   FcChar8 *family;
171
172   if (FcPatternGetString (pango_fc_font->font_pattern, FC_FAMILY, 0, &family)
173       == FcResultMatch)
174     return (char *) family;
175   return "";
176 }
177
178 /* Convert PANGO_LANGUAGE to m17n-lib's language symbol.  If m17n-lib
179    doesn't know about PANGO_LANGUAGE, return Mnil.  */
180
181 static MSymbol
182 m17n_fc_get_language (PangoLanguage *pango_language)
183 {
184   static PangoLanguage *cached_pango_language;
185   static MSymbol cached_language;
186   MSymbol language;
187
188   if (cached_pango_language == pango_language)
189     {
190       return cached_language;
191     }
192
193   if (pango_language)
194     {
195       const char *lang = pango_language_to_string (pango_language);
196
197       language = msymbol (lang);
198       if (! msymbol_get (language, Mlanguage))
199         {
200           if (strlen (lang) <= 2)
201             language = Mnil;
202           else
203             {
204               /* Remove region part (e.g. "zh_CN" -> "zh").  */
205               char shortlang[3];
206           
207               shortlang[0] = lang[0], shortlang[1] = lang[1], shortlang[2] = 0;
208               language = msymbol (shortlang);
209               if (! msymbol_get (language, Mlanguage))
210                 language = Mnil;
211             }
212         }
213     }
214   else
215     language = Mnil;
216   cached_pango_language = pango_language;
217   cached_language = language;
218   return language;
219 }
220
221
222 /* Return a list of FONT-SPECs specified in the current fontset for
223    displaying SCRIPT in LANGUAGE.  Callers must unref the returned
224    plist.  */
225
226 MPlist *
227 m17n_fc_lookup_fontset (MSymbol script, MSymbol language)
228 {
229   static MSymbol cached_script, cached_language;
230   static MPlist *cached_plist;
231   MPlist *plist;
232
233   if (cached_script == script && cached_language == language)
234     {
235       m17n_object_ref (cached_plist);
236       return cached_plist;
237     }
238   plist = mfontset_lookup (fontset, script, language, Mnil);
239
240   if (mplist_key (plist) == Mnil)
241     {
242       /* List up languages that have entries for SCRIPT.  */
243       MPlist *ll = mfontset_lookup (fontset, script, Mt, Mnil);  
244
245       if (! ll)
246         {
247           /* No entry for SCRIPT.  Return fallback FONT-SPECs. */
248           m17n_object_unref (plist);
249           plist = mfontset_lookup (fontset, Mnil, Mnil, Mnil);
250         }
251       else
252         {
253           if (mplist_length (ll) == 1)
254             {
255               /* If there's just one language, return FONT-SPECs for it.  */
256               m17n_object_unref (plist);
257               language = mplist_key (ll);
258               if (language == Mt)
259                 language = Mnil;
260               plist = mfontset_lookup (fontset, script, mplist_key (ll), Mnil);
261             }
262           else
263             /* Sumup FONT-SPECs for all languages.  */
264             for (; mplist_key (ll) != Mnil; ll = mplist_next (ll))
265               {
266                 MPlist *pl;
267
268                 language = mplist_key (ll);
269                 if (language == Mt)
270                   language = Mnil;
271                 for (pl = mfontset_lookup (fontset, script, language, Mnil);
272                      mplist_key (pl) != Mnil; pl = mplist_next (pl))
273                   mplist_add (plist, mplist_key (pl), mplist_value (pl));
274             }
275         }
276     }
277   cached_language = language;
278   cached_script = script;
279   cached_plist = plist;
280   m17n_object_ref (plist);
281   return plist;
282 }
283
284
285 typedef struct
286 {
287   PangoFcFont *pango_fc_font;
288   MFont *font;
289 } M17NFontInfo;
290
291 static void
292 m17n_fc_free_font_info (gpointer data)
293 {
294   M17NFontInfo *font_info = data;
295
296   if (font_info->font)
297     {
298       mfont_close (font_info->font);
299       pango_fc_font_unlock_face (font_info->pango_fc_font);
300     }
301   g_free (data);
302 }
303
304 static MFont *
305 m17n_fc_get_font (PangoFont *pango_font)
306 {
307   GQuark info_id = g_quark_from_string ("m17n-font-info");
308   M17NFontInfo *font_info;
309
310   font_info = g_object_get_qdata (G_OBJECT (pango_font), info_id);
311   if (! font_info)
312     {
313       PangoFcFont *pango_fc_font = PANGO_FC_FONT (pango_font);
314       FT_Face ft_face = pango_fc_font_lock_face (pango_fc_font);
315
316       font_info = g_new (M17NFontInfo, 1);
317       font_info->pango_fc_font = pango_fc_font;
318       font_info->font = mfont_encapsulate (frame, Mfreetype, ft_face);
319       g_object_set_qdata_full (G_OBJECT (pango_font), info_id, font_info,
320                                (GDestroyNotify) m17n_fc_free_font_info);
321     }
322   return font_info->font;
323 }
324
325
326 /* Shaper function.  */
327
328 static void 
329 m17n_fc_engine_shape (PangoEngineShape *engine,
330                       PangoFont        *pango_font,
331                       const char       *text,
332                       int              length,
333                       const PangoAnalysis    *analysis,
334                       PangoGlyphString *glyphs)
335 {
336   /* Symbols for character property `category'.  */
337   static MSymbol MZs, MCf;
338   static MDrawControl control;
339   MSymbol language;
340   PangoGlyphInfo *g;
341   MText *mt;
342   MDrawGlyph *m_glyphs;
343   int nchars, nglyphs;
344   MFont *font;
345   int i;
346   const char *p;
347   int *offsets;
348
349   g_return_if_fail (pango_font != NULL);
350   g_return_if_fail (text != NULL);
351   g_return_if_fail (length >= 0);
352   g_return_if_fail (analysis != NULL);
353
354   if (! MZs)
355     {
356       MZs = msymbol ("Zs");
357       MCf = msymbol ("Cf");
358       memset (&control, 0, sizeof control);
359     }
360
361   language = m17n_fc_get_language (analysis->language);
362
363   mt = mtext_from_data (text, length, MTEXT_FORMAT_UTF_8);
364   nchars = mtext_len (mt);
365   if (debug_level > 0)
366     {
367       g_debug ("shape \"U+%04X\"", mtext_ref_char (mt, 0));
368     }
369
370   offsets = alloca (sizeof (int) * nchars);
371   g_return_if_fail (offsets != NULL);
372   for (i = 0, p = text; i < nchars; i++, p = g_utf8_next_char (p))
373     offsets[i] = p - text;
374
375   font = m17n_fc_get_font (pango_font);
376   g_return_if_fail (font != NULL);
377
378   mtext_put_prop (mt, 0, nchars, Mfont, font);
379 #if 0
380   if (language != Mnil)
381     mtext_put_prop (mt, 0, nchars, Mlanguage, language);
382 #endif
383
384   control.enable_bidi = 1;
385   m_glyphs = alloca (sizeof (MDrawGlyph) * nchars * 2);
386   g_return_if_fail (m_glyphs != NULL);
387   if (mdraw_glyph_list (frame, mt, 0, nchars, &control, m_glyphs,
388                         nchars * 2, &nglyphs) < 0)
389     {
390       m_glyphs = alloca (sizeof (MDrawGlyph) * nglyphs);
391       g_return_if_fail (m_glyphs != NULL);
392       mdraw_glyph_list (frame, mt, 0, nchars, &control, m_glyphs,
393                         nglyphs, &nglyphs);
394     }
395
396   pango_glyph_string_set_size (glyphs, nglyphs);
397
398   for (i = 0, g = glyphs->glyphs; i < nglyphs; i++, g++)
399     {
400       if (m_glyphs[i].glyph_code >= 0)
401         {
402           g->glyph = m_glyphs[i].glyph_code;
403           g->geometry.x_offset = m_glyphs[i].x_off * PANGO_SCALE;
404           g->geometry.y_offset = m_glyphs[i].y_off * PANGO_SCALE;
405           g->geometry.width = m_glyphs[i].x_advance * PANGO_SCALE;
406         }
407       else
408         {
409           int c = mtext_ref_char (mt, m_glyphs[i].from);
410           MSymbol category = mchar_get_prop (c, Mcategory);
411           PangoRectangle logical_rect;
412
413           if (category == MZs)
414             {
415               g->glyph = 0;
416               g->geometry.width = m_glyphs[i].x_advance * PANGO_SCALE;
417             }
418           else if (category == MCf)
419             {
420               g->glyph = 0;
421               g->geometry.width = 0;
422             }
423           else
424             {
425               g->glyph = (pango_fc_font_get_unknown_glyph
426                           (PANGO_FC_FONT (pango_font), c));
427               pango_font_get_glyph_extents (pango_font, g->glyph, NULL,
428                                             &logical_rect);
429               g->geometry.width = logical_rect.width;
430             }
431           g->geometry.x_offset = 0;
432           g->geometry.y_offset = 0;
433         }
434       g->attr.is_cluster_start
435         = (i == 0
436            || m_glyphs[i - 1].from != m_glyphs[i].from);
437       glyphs->log_clusters[i] = offsets[m_glyphs[i].from];
438     }
439
440   m17n_object_unref (mt);
441 }
442
443 /* Check if PANGO_FONT is suitable for the character WC.  */
444
445 static PangoCoverageLevel
446 m17n_fc_engine_covers (PangoEngineShape *engine,
447                        PangoFont        *pango_font,
448                        PangoLanguage    *pango_language,
449                        gunichar          wc)
450 {
451   static gunichar last_wc = 0;
452   /* Symbols for character property `script'.  */
453   static MSymbol Mlatin, Mcommon, Minherited;
454   PangoCoverage *coverage;
455   PangoCoverageLevel result;
456   MSymbol script, language;
457   MPlist *spec_list, *p;
458   MFont *font;
459
460   if (! Mlatin)
461     {
462       Mlatin = msymbol ("latin");
463       Mcommon = msymbol ("common");
464       Minherited = msymbol ("inherited");
465     }
466
467 #if 1
468   return PANGO_COVERAGE_EXACT;
469 #else
470   if (debug_level > 0 && wc != last_wc)
471     {
472       g_debug ("covers for U+%04X", wc);
473       last_wc = wc;
474     }
475
476   coverage = pango_font_get_coverage (pango_font, pango_language);
477   result = pango_coverage_get (coverage, wc);
478   pango_coverage_unref (coverage);
479
480   if (result == PANGO_COVERAGE_NONE)
481     {
482       if (debug_level > 0)
483         g_debug ("  %s none", m17n_fc_get_family (pango_font));
484       return result;
485     }
486
487   language = m17n_fc_get_language (pango_language);
488   if (wc < 0x100)
489     script = Mlatin;
490   else
491     {
492       script = mchar_get_prop ((int) wc, Mscript);
493       if (script == Mcommon || script == Minherited)
494         script = mchar_get_prop ((int) wc, msymbol ("block"));
495     }
496
497   font = m17n_fc_get_font (pango_font);
498   spec_list = m17n_fc_lookup_fontset (script, language);
499
500   result = PANGO_COVERAGE_NONE;
501   for (p = spec_list; mplist_key (p) != Mnil; p = mplist_next (p))
502     if (mfont_match_p (font, mplist_value (p)))
503       {
504         result = PANGO_COVERAGE_EXACT;
505         break;
506       }
507   m17n_object_unref (spec_list);
508   if (debug_level > 0)
509     g_debug ("  (%s,%s): %s %s",
510              (script ? msymbol_name (script) : ""),
511              (language ? msymbol_name (language) : ""),
512              m17n_fc_get_family (pango_font),
513              result == PANGO_COVERAGE_EXACT ? "exact" : "none");
514
515   return result;
516 #endif
517 }
518
519 static void
520 m17n_engine_fc_class_init (PangoEngineShapeClass *class)
521 {
522   class->script_shape = m17n_fc_engine_shape;
523   class->covers = m17n_fc_engine_covers;
524 }
525
526 PANGO_ENGINE_SHAPE_DEFINE_TYPE (M17NEngineFc, m17n_engine_fc,
527                                 m17n_engine_fc_class_init, NULL)
528
529 void 
530 PANGO_MODULE_ENTRY(init) (GTypeModule *module)
531 {
532   m17n_engine_fc_register_type (module);
533
534   M17N_INIT ();
535   init_module ();
536 }
537
538 void 
539 PANGO_MODULE_ENTRY(exit) (void)
540 {
541   fini_module ();
542   M17N_FINI ();
543 }
544
545 void 
546 PANGO_MODULE_ENTRY(list) (PangoEngineInfo **engines,
547                           int              *n_engines)
548 {
549   *engines = script_engines;
550   *n_engines = G_N_ELEMENTS (script_engines);
551 }
552
553 PangoEngine *
554 PANGO_MODULE_ENTRY(create) (const char *id)
555 {
556   return (!strcmp (id, SCRIPT_ENGINE_NAME)
557           ? g_object_new (m17n_engine_fc_type, NULL)
558           : NULL);
559 }