Git init
[external/pango1.0.git] / modules / hangul / hangul-fc.c
1 /* Pango
2  * hangul-fc.c: Hangul shaper for FreeType based backends
3  *
4  * Copyright (C) 2002-2006 Changwoo Ryu
5  * Author: Changwoo Ryu <cwryu@debian.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include "config.h"
24 #include <string.h>
25
26 #include "pango-engine.h"
27 #include "pango-utils.h"
28 #include "pangofc-font.h"
29
30 #include "hangul-defs.h"
31 #include "tables-jamos.i"
32
33 /* No extra fields needed */
34 typedef PangoEngineShape      HangulEngineFc;
35 typedef PangoEngineShapeClass HangulEngineFcClass ;
36
37 #define SCRIPT_ENGINE_NAME "HangulScriptEngineFc"
38 #define RENDER_TYPE PANGO_RENDER_TYPE_FC
39
40 static PangoEngineScriptInfo hangul_scripts[] = {
41   { PANGO_SCRIPT_HANGUL, "*" }
42 };
43
44 static PangoEngineInfo script_engines[] = {
45   {
46     SCRIPT_ENGINE_NAME,
47     PANGO_ENGINE_TYPE_SHAPE,
48     RENDER_TYPE,
49     hangul_scripts, G_N_ELEMENTS(hangul_scripts)
50   }
51 };
52
53 static void
54 set_glyph (PangoFont *font, PangoGlyphString *glyphs, int i, int offset, PangoGlyph glyph)
55 {
56   PangoRectangle logical_rect;
57
58   glyphs->glyphs[i].glyph = glyph;
59   glyphs->glyphs[i].geometry.x_offset = 0;
60   glyphs->glyphs[i].geometry.y_offset = 0;
61   glyphs->log_clusters[i] = offset;
62
63   pango_font_get_glyph_extents (font, glyphs->glyphs[i].glyph, NULL, &logical_rect);
64   glyphs->glyphs[i].geometry.width = logical_rect.width;
65 }
66
67 /* Add a Hangul tone mark glyph in a glyph string.
68  * Non-spacing glyph works pretty much automatically.
69  * Spacing-glyph takes some care:
70  *   1. Make a room for a tone mark at the beginning(leftmost end) of a cluster
71  *   to attach it to.
72  *   2. Adjust x_offset so that it is drawn to the left of a cluster.
73  *   3. Set the logical width to zero.
74  */
75
76 static void
77 set_glyph_tone (PangoFont *font, PangoGlyphString *glyphs, int i,
78                             int offset, PangoGlyph glyph)
79 {
80   PangoRectangle logical_rect, ink_rect;
81   PangoRectangle logical_rect_cluster;
82
83   glyphs->glyphs[i].glyph = glyph;
84   glyphs->glyphs[i].geometry.y_offset = 0;
85   glyphs->log_clusters[i] = offset;
86
87   pango_font_get_glyph_extents (font, glyphs->glyphs[i].glyph,
88                                 &ink_rect, &logical_rect);
89
90   /* tone mark is not the first in a glyph string. We have info. on the
91    * preceding glyphs in a glyph string
92    */
93     {
94       int j = i - 1;
95       /* search for the beg. of the preceding cluster */
96       while (j >= 0 && glyphs->log_clusters[j] == glyphs->log_clusters[i - 1])
97         j--;
98
99       /* In .._extents_range(...,start,end,...), to my surprise  start is
100        * inclusive but end is exclusive !!
101        */
102       pango_glyph_string_extents_range (glyphs, j + 1, i, font,
103                                         NULL, &logical_rect_cluster);
104
105       /* logical_rect_cluster.width is all the offset we need so that the
106        * inherent x_offset in the glyph (ink_rect.x) should be canceled out.
107        */
108       glyphs->glyphs[i].geometry.x_offset = - logical_rect_cluster.width
109                                             - ink_rect.x ;
110
111
112       /* make an additional room for a tone mark if it has a spacing glyph
113        * because that's likely to be an indication that glyphs for other
114        * characters in the font are not designed for combining with tone marks.
115        */
116       if (logical_rect.width)
117         {
118           glyphs->glyphs[i].geometry.x_offset -= ink_rect.width;
119           glyphs->glyphs[j + 1].geometry.width += ink_rect.width;
120           glyphs->glyphs[j + 1].geometry.x_offset += ink_rect.width;
121         }
122     }
123
124   glyphs->glyphs[i].geometry.width = 0;
125 }
126
127
128 #define find_char(font,wc) \
129     pango_fc_font_get_glyph((PangoFcFont *)font, wc)
130
131 static void
132 render_tone (PangoFont *font, gunichar tone, PangoGlyphString *glyphs,
133              int *n_glyphs, int cluster_offset)
134 {
135   int index;
136
137   index = find_char (font, tone);
138   pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
139   if (index)
140     {
141       set_glyph_tone (font, glyphs, *n_glyphs, cluster_offset, index);
142     }
143   else
144     {
145       /* fall back : HTONE1(0x302e) => middle-dot, HTONE2(0x302f) => colon */
146       index = find_char (font, tone == HTONE1 ? 0x00b7 : 0x003a);
147       if (index)
148         {
149           set_glyph_tone (font, glyphs, *n_glyphs, cluster_offset, index);
150         }
151       else
152         set_glyph (font, glyphs, *n_glyphs, cluster_offset,
153                    PANGO_GET_UNKNOWN_GLYPH (tone));
154     }
155   (*n_glyphs)++;
156 }
157
158 /* This is a fallback for when we get a tone mark not preceded
159  * by a syllable.
160  */
161 static void
162 render_isolated_tone (PangoFont *font, gunichar tone, PangoGlyphString *glyphs,
163                       int *n_glyphs, int cluster_offset)
164 {
165 #if 0 /* FIXME: what kind of hack is it?  it draws dummy glyphs.  */
166   /* Find a base character to render the mark on
167    */
168   int index = find_char (font, 0x25cc); /* DOTTED CIRCLE */
169   if (!index)
170     index = find_char (font, 0x25cb);   /* WHITE CIRCLE, in KSC-5601 */
171   if (!index)
172     index = find_char (font, ' ');      /* Space */
173   if (!index)                           /* Unknown glyph box with 0000 in it */
174     index = find_char (font, PANGO_GET_UNKNOWN_GLYPH (0));
175
176   /* Add the base character
177    */
178   pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
179   set_glyph (font, glyphs, *n_glyphs, cluster_offset, index);
180   (*n_glyphs)++;
181 #endif
182
183   /* And the tone mark
184    */
185   render_tone(font, tone, glyphs, n_glyphs, cluster_offset);
186 }
187
188 static void
189 render_syllable (PangoFont *font, const char *str, int length,
190                  PangoGlyphString *glyphs, int *n_glyphs, int cluster_offset)
191 {
192   int n_prev_glyphs = *n_glyphs;
193   int index;
194   gunichar wc = 0, tone = 0, text[4];
195   int i, j, composed = 0;
196   const char *p;
197
198   /* Normalize it only when the entire sequence is equivalent to a
199    * precomposed syllable. It's usually better than prefix
200    * normalization both for poor-featured fonts and for smart fonts.
201    * I have seen no smart font which can render S+T as a syllable
202    * form.
203    */
204
205   if (length == 3 || length == 4)
206     {
207       p = str;
208       text[0] = g_utf8_get_char(p);
209       p = g_utf8_next_char(p);
210       text[1] = g_utf8_get_char(p);
211       p = g_utf8_next_char(p);
212       text[2] = g_utf8_get_char(p);
213
214       if (length == 4 && !IS_M(g_utf8_get_char(g_utf8_next_char(p))))
215         goto lvt_out;           /* draw the tone mark later */
216
217       if (IS_L_S(text[0]) && IS_V_S(text[1]) &&  IS_T_S(text[2]))
218         {
219           composed = 3;
220           wc = S_FROM_LVT(text[0], text[1], text[2]);
221           str = g_utf8_next_char(p);
222           goto normalize_out;
223         }
224     }
225  lvt_out:
226
227   if (length == 2 || length == 3)
228     {
229       p = str;
230       text[0] = g_utf8_get_char(p);
231       p = g_utf8_next_char(p);
232       text[1] = g_utf8_get_char(p);
233
234       if (length == 3 && !IS_M(g_utf8_get_char(g_utf8_next_char(p))))
235         goto lv_out;            /* draw the tone mark later */
236       if (IS_L_S(text[0]) && IS_V_S(text[1]))
237         {
238           composed = 2;
239           wc = S_FROM_LV(text[0], text[1]);
240           str = g_utf8_next_char(p);
241         }
242       else if (IS_S(text[0] && !S_HAS_T(text[0]) && IS_T_S(text[1])))
243         {
244           composed = 2;
245           wc = text[0] + (text[1] - TBASE);
246           str = g_utf8_next_char(p);
247           goto normalize_out;
248         }
249     }
250  lv_out:
251  normalize_out:
252
253   if (composed)
254     {
255       index = find_char (font, wc);
256       pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
257       if (!index)
258         set_glyph (font, glyphs, *n_glyphs, cluster_offset,
259                    PANGO_GET_UNKNOWN_GLYPH (wc));
260       else
261         set_glyph (font, glyphs, *n_glyphs, cluster_offset, index);
262       (*n_glyphs)++;
263       length -= composed;
264     }
265
266   /* Render the remaining text as uncomposed forms as a fallback.  */
267   for (i = 0; i < length; i++, str = g_utf8_next_char(str))
268     {
269       int jindex;
270       int oldlen;
271
272       wc = g_utf8_get_char(str);
273
274       if (wc == LFILL || wc == VFILL)
275         continue;
276
277       if (IS_M(wc))
278         {
279           tone = wc;
280           break;
281         }
282
283       if (IS_S(wc))
284         {
285           oldlen = *n_glyphs;
286
287           text[0] = L_FROM_S(wc);
288           text[1] = V_FROM_S(wc);
289           if (S_HAS_T(wc))
290             {
291               text[2] = T_FROM_S(wc);
292               composed = 3;
293             }
294           else
295               composed = 2;
296
297           for (j = 0; j < composed; j++)
298             {
299               index = find_char (font, text[j]);
300               if (index)
301                 {
302                   pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
303                   set_glyph (font, glyphs, *n_glyphs, cluster_offset, index);
304                   (*n_glyphs)++;
305                 }
306               else
307                 goto decompose_cancel;
308             }
309
310           continue;
311
312         decompose_cancel:
313           /* The font doesn't have jamos.  Cancel it. */
314           *n_glyphs = oldlen;
315           pango_glyph_string_set_size (glyphs, *n_glyphs);
316         }
317
318       index = find_char (font, wc);
319       if (index)
320         {
321           pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
322           set_glyph (font, glyphs, *n_glyphs, cluster_offset, index);
323           (*n_glyphs)++;
324           continue;
325         }
326       else if (IS_S(wc))
327         {
328           pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
329           set_glyph (font, glyphs, *n_glyphs, cluster_offset,
330                      PANGO_GET_UNKNOWN_GLYPH (wc));
331           (*n_glyphs)++;
332           continue;
333         }
334
335       /* This font has no glyphs on the Hangul Jamo area!  Find a
336          fallback from the Hangul Compatibility Jamo area.  */
337       jindex = wc - LBASE;
338       oldlen = *n_glyphs;
339       for (j = 0; j < 3 && (__jamo_to_ksc5601[jindex][j] != 0); j++)
340         {
341           wc = __jamo_to_ksc5601[jindex][j] - KSC_JAMOBASE + UNI_JAMOBASE;
342           index = (wc >= 0x3131) ? find_char (font, wc) : 0;
343           pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
344           if (!index)
345             {
346               *n_glyphs = oldlen;
347               pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
348               set_glyph (font, glyphs, *n_glyphs, cluster_offset,
349                          PANGO_GET_UNKNOWN_GLYPH (text[i]));
350               (*n_glyphs)++;
351               break;
352             }
353           else
354             set_glyph (font, glyphs, *n_glyphs, cluster_offset, index);
355           (*n_glyphs)++;
356         }
357     }
358   if (n_prev_glyphs == *n_glyphs)
359     {
360       index = find_char (font, 0x3164); /* U+3164 HANGUL FILLER */
361       pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
362       if (!index)
363         set_glyph (font, glyphs, *n_glyphs, cluster_offset,
364                    PANGO_GET_UNKNOWN_GLYPH (index));
365       else
366         set_glyph (font, glyphs, *n_glyphs, cluster_offset, index);
367       glyphs->log_clusters[*n_glyphs] = cluster_offset;
368       (*n_glyphs)++;
369     }
370   if (tone)
371     render_tone(font, tone, glyphs, n_glyphs, cluster_offset);
372 }
373
374 static void
375 render_basic (PangoFont *font, gunichar wc,
376               PangoGlyphString *glyphs, int *n_glyphs, int cluster_offset)
377 {
378   int index;
379
380   if (wc == 0xa0)       /* non-break-space */
381     wc = 0x20;
382
383   pango_glyph_string_set_size (glyphs, *n_glyphs + 1);
384
385   if (pango_is_zero_width (wc))
386     set_glyph (font, glyphs, *n_glyphs, cluster_offset, PANGO_GLYPH_EMPTY);
387   else
388     {
389       index = find_char (font, wc);
390       if (index)
391         set_glyph (font, glyphs, *n_glyphs, cluster_offset, index);
392       else
393         set_glyph (font, glyphs, *n_glyphs, cluster_offset, PANGO_GET_UNKNOWN_GLYPH (wc));
394     }
395   (*n_glyphs)++;
396 }
397
398 static void
399 hangul_engine_shape (PangoEngineShape *engine G_GNUC_UNUSED,
400                      PangoFont        *font,
401                      const char       *text,
402                      gint              length,
403                      const PangoAnalysis *analysis G_GNUC_UNUSED,
404                      PangoGlyphString *glyphs)
405 {
406   int n_chars = g_utf8_strlen (text, length);
407   int n_glyphs;
408   int i;
409   const char *p, *start;
410
411   int n_jamos;
412   gunichar prev = 0;
413
414   n_glyphs = 0;
415   start = p = text;
416   n_jamos = 0;
417
418   for (i = 0; i < n_chars; i++)
419     {
420       gunichar wc;
421
422       wc = g_utf8_get_char (p);
423
424       /* Check syllable boundaries. */
425       if (n_jamos && IS_BOUNDARY (prev, wc))
426         {
427           if (n_jamos == 1 && IS_S (prev))
428             /* common case which the most people use */
429             render_basic (font, prev, glyphs, &n_glyphs, start - text);
430           else
431             /* possibly complex composition */
432             render_syllable (font, start, n_jamos, glyphs,
433                              &n_glyphs, start - text);
434           n_jamos = 0;
435           start = p;
436         }
437
438       prev = wc;
439
440       if (!IS_HANGUL (wc))
441         {
442           render_basic (font, wc, glyphs, &n_glyphs, start - text);
443           start = g_utf8_next_char (p);
444         }
445       else if (IS_M (wc) && !n_jamos)
446         {
447           /* Tone mark not following syllable */
448           render_isolated_tone (font, wc, glyphs, &n_glyphs, start - text);
449           start = g_utf8_next_char (p);
450         }
451       else
452         n_jamos++;
453       p = g_utf8_next_char (p);
454     }
455
456   if (n_jamos == 1 && IS_S (prev))
457     render_basic (font, prev, glyphs, &n_glyphs, start - text);
458   else if (n_jamos > 0)
459     render_syllable (font, start, n_jamos, glyphs, &n_glyphs,
460                      start - text);
461 }
462
463 static void
464 hangul_engine_fc_class_init (PangoEngineShapeClass *class)
465 {
466   class->script_shape = hangul_engine_shape;
467 }
468
469 PANGO_ENGINE_SHAPE_DEFINE_TYPE (HangulEngineFc, hangul_engine_fc,
470                                 hangul_engine_fc_class_init, NULL)
471
472 void
473 PANGO_MODULE_ENTRY(init) (GTypeModule *module)
474 {
475   hangul_engine_fc_register_type (module);
476 }
477
478 void
479 PANGO_MODULE_ENTRY(exit) (void)
480 {
481 }
482
483 void
484 PANGO_MODULE_ENTRY(list) (PangoEngineInfo **engines,
485                           int              *n_engines)
486 {
487   *engines = script_engines;
488   *n_engines = G_N_ELEMENTS (script_engines);
489 }
490
491 PangoEngine *
492 PANGO_MODULE_ENTRY(create) (const char *id)
493 {
494   if (!strcmp (id, SCRIPT_ENGINE_NAME))
495     return g_object_new (hangul_engine_fc_type, NULL);
496   else
497     return NULL;
498 }