1/*
2 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
3 *
4 * This is part of HarfBuzz, an OpenType Layout engine library.
5 *
6 * Permission is hereby granted, without written agreement and without
7 * license or royalty fees, to use, copy, modify, and distribute this
8 * software and its documentation for any purpose, provided that the
9 * above copyright notice and the following two paragraphs appear in
10 * all copies of this software.
11 *
12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16 * DAMAGE.
17 *
18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23 */
24
25#include "harfbuzz-shaper.h"
26#include "harfbuzz-shaper-private.h"
27
28#include <assert.h>
29
30/*
31// Hangul is a syllable based script. Unicode reserves a large range
32// for precomposed hangul, where syllables are already precomposed to
33// their final glyph shape. In addition, a so called jamo range is
34// defined, that can be used to express old Hangul. Modern hangul
35// syllables can also be expressed as jamo, and should be composed
36// into syllables. The operation is rather simple and mathematical.
37
38// Every hangul jamo is classified as being either a Leading consonant
39// (L), and intermediat Vowel (V) or a trailing consonant (T). Modern
40// hangul syllables (the ones in the precomposed area can be of type
41// LV or LVT.
42//
43// Syllable breaks do _not_ occur between:
44//
45// L              L, V or precomposed
46// V, LV          V, T
47// LVT, T         T
48//
49// A standard syllable is of the form L+V+T*. The above rules allow
50// nonstandard syllables L*V*T*. To transform them into standard
51// syllables fill characters L_f and V_f can be inserted.
52*/
53
54enum {
55    Hangul_SBase = 0xac00,
56    Hangul_LBase = 0x1100,
57    Hangul_VBase = 0x1161,
58    Hangul_TBase = 0x11a7,
59    Hangul_SCount = 11172,
60    Hangul_LCount = 19,
61    Hangul_VCount = 21,
62    Hangul_TCount = 28,
63    Hangul_NCount = 21*28
64};
65
66#define hangul_isPrecomposed(uc) \
67    (uc >= Hangul_SBase && uc < Hangul_SBase + Hangul_SCount)
68
69#define hangul_isLV(uc) \
70    ((uc - Hangul_SBase) % Hangul_TCount == 0)
71
72typedef enum {
73    L,
74    V,
75    T,
76    LV,
77    LVT,
78    X
79} HangulType;
80
81static HangulType hangul_type(unsigned short uc) {
82    if (uc > Hangul_SBase && uc < Hangul_SBase + Hangul_SCount)
83        return hangul_isLV(uc) ? LV : LVT;
84    if (uc < Hangul_LBase || uc > 0x11ff)
85        return X;
86    if (uc < Hangul_VBase)
87        return L;
88    if (uc < Hangul_TBase)
89        return V;
90    return T;
91}
92
93static int hangul_nextSyllableBoundary(const HB_UChar16 *s, int start, int end)
94{
95    const HB_UChar16 *uc = s + start;
96
97    HangulType state = hangul_type(*uc);
98    int pos = 1;
99
100    while (pos < end - start) {
101        HangulType newState = hangul_type(uc[pos]);
102        switch(newState) {
103        case X:
104            goto finish;
105        case L:
106        case V:
107        case T:
108            if (state > newState)
109                goto finish;
110            state = newState;
111            break;
112        case LV:
113            if (state > L)
114                goto finish;
115            state = V;
116            break;
117        case LVT:
118            if (state > L)
119                goto finish;
120            state = T;
121        }
122        ++pos;
123    }
124
125 finish:
126    return start+pos;
127}
128
129#ifndef NO_OPENTYPE
130static const HB_OpenTypeFeature hangul_features [] = {
131    { HB_MAKE_TAG('c', 'c', 'm', 'p'), CcmpProperty },
132    { HB_MAKE_TAG('l', 'j', 'm', 'o'), CcmpProperty },
133    { HB_MAKE_TAG('v', 'j', 'm', 'o'), CcmpProperty },
134    { HB_MAKE_TAG('t', 'j', 'm', 'o'), CcmpProperty },
135    { 0, 0 }
136};
137#endif
138
139static HB_Bool hangul_shape_syllable(HB_ShaperItem *item, HB_Bool openType)
140{
141    const HB_UChar16 *ch = item->string + item->item.pos;
142    int len = item->item.length;
143#ifndef NO_OPENTYPE
144    const int availableGlyphs = item->num_glyphs;
145#endif
146
147    int i;
148    HB_UChar16 composed = 0;
149    /* see if we can compose the syllable into a modern hangul */
150    if (item->item.length == 2) {
151        int LIndex = ch[0] - Hangul_LBase;
152        int VIndex = ch[1] - Hangul_VBase;
153        if (LIndex >= 0 && LIndex < Hangul_LCount &&
154            VIndex >= 0 && VIndex < Hangul_VCount)
155            composed = (LIndex * Hangul_VCount + VIndex) * Hangul_TCount + Hangul_SBase;
156    } else if (item->item.length == 3) {
157        int LIndex = ch[0] - Hangul_LBase;
158        int VIndex = ch[1] - Hangul_VBase;
159        int TIndex = ch[2] - Hangul_TBase;
160        if (LIndex >= 0 && LIndex < Hangul_LCount &&
161            VIndex >= 0 && VIndex < Hangul_VCount &&
162            TIndex >= 0 && TIndex < Hangul_TCount)
163            composed = (LIndex * Hangul_VCount + VIndex) * Hangul_TCount + TIndex + Hangul_SBase;
164    }
165
166
167
168    /* if we have a modern hangul use the composed form */
169    if (composed) {
170        ch = &composed;
171        len = 1;
172    }
173
174    if (!item->font->klass->convertStringToGlyphIndices(item->font,
175                                                        ch, len,
176                                                        item->glyphs, &item->num_glyphs,
177                                                        item->item.bidiLevel % 2))
178        return FALSE;
179    for (i = 0; i < len; i++) {
180        item->attributes[i].mark = FALSE;
181        item->attributes[i].clusterStart = FALSE;
182        item->attributes[i].justification = 0;
183        item->attributes[i].zeroWidth = FALSE;
184        /*IDEBUG("    %d: %4x", i, ch[i].unicode()); */
185    }
186
187#ifndef NO_OPENTYPE
188    if (!composed && openType) {
189        HB_Bool positioned;
190
191        HB_STACKARRAY(unsigned short, logClusters, len);
192        for (i = 0; i < len; ++i)
193            logClusters[i] = i;
194        item->log_clusters = logClusters;
195
196        HB_OpenTypeShape(item, /*properties*/0);
197
198        positioned = HB_OpenTypePosition(item, availableGlyphs, /*doLogClusters*/FALSE);
199
200        HB_FREE_STACKARRAY(logClusters);
201
202        if (!positioned)
203            return FALSE;
204    } else {
205        HB_HeuristicPosition(item);
206    }
207#endif
208
209    item->attributes[0].clusterStart = TRUE;
210    return TRUE;
211}
212
213HB_Bool HB_HangulShape(HB_ShaperItem *item)
214{
215    const HB_UChar16 *uc = item->string + item->item.pos;
216    HB_Bool allPrecomposed = TRUE;
217    int i;
218
219    assert(item->item.script == HB_Script_Hangul);
220
221    for (i = 0; i < (int)item->item.length; ++i) {
222        if (!hangul_isPrecomposed(uc[i])) {
223            allPrecomposed = FALSE;
224            break;
225        }
226    }
227
228    if (!allPrecomposed) {
229        HB_Bool openType = FALSE;
230        unsigned short *logClusters = item->log_clusters;
231        HB_ShaperItem syllable;
232        int first_glyph = 0;
233        int sstart = item->item.pos;
234        int end = sstart + item->item.length;
235
236#ifndef NO_OPENTYPE
237        openType = HB_SelectScript(item, hangul_features);
238#endif
239        syllable = *item;
240
241        while (sstart < end) {
242            int send = hangul_nextSyllableBoundary(item->string, sstart, end);
243
244            syllable.item.pos = sstart;
245            syllable.item.length = send-sstart;
246            syllable.glyphs = item->glyphs + first_glyph;
247            syllable.attributes = item->attributes + first_glyph;
248            syllable.offsets = item->offsets + first_glyph;
249            syllable.advances = item->advances + first_glyph;
250            syllable.num_glyphs = item->num_glyphs - first_glyph;
251            if (!hangul_shape_syllable(&syllable, openType)) {
252                item->num_glyphs += syllable.num_glyphs;
253                return FALSE;
254            }
255            /* fix logcluster array */
256            for (i = sstart; i < send; ++i)
257                logClusters[i-item->item.pos] = first_glyph;
258            sstart = send;
259            first_glyph += syllable.num_glyphs;
260        }
261        item->num_glyphs = first_glyph;
262        return TRUE;
263    }
264
265    return HB_BasicShape(item);
266}
267
268
269