More work on bidi and contextual forms.
[WebKit-https.git] / WebKit / Misc.subproj / WebUnicode.m
1 /*      
2         WebUnicode.m
3         Copyright 2001, 2002, Apple Computer, Inc.
4 */
5 #import <WebKit/WebUnicode.h>
6
7 #import <WebCore/WebCoreUnicode.h>
8
9 #define CELL(ucs) ((unsigned char) ucs & 0xff)
10 #define ROW(ucs) ((unsigned char) (ucs>>8)&0xff)
11
12
13 static int _unicodeDigitValue(UniChar c)
14 {
15     const char *dec_row = decimal_info[ROW(c)];
16     if( !dec_row )
17         return -1;
18     return dec_row[CELL(c)];
19 }
20
21 static WebCoreUnicodeCategory _unicodeCategory(UniChar c)
22 {
23     return (WebCoreUnicodeCategory)(unicode_info[ROW(c)][CELL(c)]);
24 }
25
26 static WebCoreUnicodeDirection _unicodeDirection(UniChar c)
27 {
28     const unsigned char *rowp = direction_info[ROW(c)];
29     if(!rowp) 
30         return DirectionL;
31     return (WebCoreUnicodeDirection) ( *(rowp+CELL(c)) &0x1f );
32 }
33
34 static WebCoreUnicodeJoining _unicodeJoining(UniChar c)
35 {
36     const unsigned char *rowp = direction_info[ROW(c)];
37     if ( !rowp )
38         return JoiningOther;
39     return (WebCoreUnicodeJoining) ((*(rowp+CELL(c)) >> 5) &0x3);
40 }
41
42 static WebCoreUnicodeDecomposition _unicodeDecompositionTag(UniChar c)
43 {
44     const unsigned short *r = decomposition_info[ROW(c)];
45     if(!r)
46         return DecompositionSingle;
47
48     unsigned short pos = r[CELL(c)];
49     if(!pos)
50         return DecompositionSingle;
51
52     return (WebCoreUnicodeDecomposition) decomposition_map[pos];
53 }
54
55 static bool _unicodeMirrored(UniChar c)
56 {
57     const unsigned char *rowp = direction_info[ROW(c)];
58     if ( !rowp )
59         return FALSE;
60     return *(rowp+CELL(c))>128;
61 }
62
63 static UniChar _unicodeMirroredChar(UniChar c)
64 {
65     if(!_unicodeMirrored(c))
66         return c;
67
68     int i;
69     for (i = 0; i < symmetricPairsSize; i ++) {
70         if (symmetricPairs[i] == c)
71           return symmetricPairs[(i%2) ? (i-1) : (i+1)];
72     }
73     return c;
74 }
75
76 static WebCoreUnicodeCombiningClass _unicodeCombiningClass (UniChar c)
77 {
78     const unsigned char *rowp = combining_info[ROW(c)];
79     if ( !rowp )
80         return 0;
81     return *(rowp+CELL(c));
82 }
83
84 static UniChar _unicodeLower(UniChar c)
85 {
86     if ( _unicodeCategory(c) != Letter_Uppercase )
87         return c;
88     unsigned short lower = *( case_info[ROW(c)] + CELL(c) );
89     if ( lower == 0 )
90         return c;
91     return lower;
92 }
93
94 static UniChar _unicodeUpper(UniChar c)
95 {
96     if ( _unicodeCategory(c) != Letter_Lowercase )
97         return c;
98     unsigned short upper = *(case_info[ROW(c)]+CELL(c));
99     if ( upper == 0 )
100         return c;
101     return upper;
102 }
103
104 void WebKitInitializeUnicode(void)
105 {
106     WebCoreUnicodeDigitValueFunction = _unicodeDigitValue;
107     WebCoreUnicodeCategoryFunction = _unicodeCategory;
108     WebCoreUnicodeDirectionFunction = _unicodeDirection;
109     WebCoreUnicodeJoiningFunction = _unicodeJoining;
110     WebCoreUnicodeDecompositionTagFunction = _unicodeDecompositionTag;
111     WebCoreUnicodeMirroredFunction = _unicodeMirrored;
112     WebCoreUnicodeMirroredCharFunction = _unicodeMirroredChar;
113     WebCoreUnicodeCombiningClassFunction = _unicodeCombiningClass;
114     WebCoreUnicodeLowerFunction = _unicodeLower;
115     WebCoreUnicodeUpperFunction = _unicodeUpper;
116 }
117
118 #ifdef QT_CODE
119
120 // small class used internally in QString::Compose()
121 class QLigature
122 {
123 public:
124     QLigature( QChar c );
125
126     Q_UINT16 first() { cur = ligatures; return cur ? *cur : 0; }
127     Q_UINT16 next() { return cur && *cur ? *(cur++) : 0; }
128     Q_UINT16 current() { return cur ? *cur : 0; }
129
130     int match(QString & str, unsigned int index);
131     QChar head();
132     QChar::Decomposition tag();
133
134 private:
135     Q_UINT16 *ligatures;
136     Q_UINT16 *cur;
137 };
138
139 QLigature::QLigature( QChar c )
140 {
141     const Q_UINT16 *r = ligature_info[c.row()];
142     if( !r )
143         ligatures = 0;
144     else
145     {
146         const Q_UINT16 pos = r[c.cell()];
147         ligatures = (Q_UINT16 *)&(ligature_map[pos]);
148     }
149     cur = ligatures;
150 }
151
152 QChar QLigature::head()
153 {
154     if(current())
155         return QChar(decomposition_map[current()+1]);
156
157     return QChar::null;
158 }
159
160 QChar::Decomposition QLigature::tag()
161 {
162     if(current())
163         return (QChar::Decomposition) decomposition_map[current()];
164
165     return QChar::Canonical;
166 }
167
168 int QLigature::match(QString & str, unsigned int index)
169 {
170     unsigned int i=index;
171
172     if(!current()) return 0;
173
174     Q_UINT16 lig = current() + 2;
175     Q_UINT16 ch;
176
177     while ((i < str.length()) && (ch = decomposition_map[lig])) {
178         if (str[(int)i] != QChar(ch))
179             return 0;
180         i++; lig++;
181     }
182
183     if (!decomposition_map[lig])
184     {
185         return i-index;
186     }
187     return 0;
188 }
189
190
191 // this function is just used in QString::compose()
192 static inline bool format(QChar::Decomposition tag, QString & str,
193                           int index, int len)
194 {
195     unsigned int l = index + len;
196     unsigned int r = index;
197
198     bool left = FALSE, right = FALSE;
199
200     left = ((l < str.length()) &&
201             ((str[(int)l].joining() == QChar::Dual) ||
202              (str[(int)l].joining() == QChar::Right)));
203     if (r > 0) {
204         r--;
205         //printf("joining(right) = %d\n", str[(int)r].joining());
206         right = (str[(int)r].joining() == QChar::Dual);
207     }
208
209
210     switch (tag) {
211     case QChar::Medial:
212         return (left & right);
213     case QChar::Initial:
214         return (left && !right);
215     case QChar::Final:
216         return (right);// && !left);
217     case QChar::Isolated:
218     default:
219         return (!right && !left);
220     }
221 } // format()
222 #endif
223
224 /*
225   QString::compose() and visual() were developed by Gordon Tisher
226   <tisher@uniserve.ca>, with input from Lars Knoll <knoll@mpi-hd.mpg.de>,
227   who developed the unicode data tables.
228 */
229 /*!
230     \warning This function is not supported in Qt 3.x. It is provided
231     for experimental and illustrative purposes only. It is mainly of
232     interest to those experimenting with Arabic and other
233     composition-rich texts.
234
235     Applies possible ligatures to a QString. Useful when
236     composition-rich text requires rendering with glyph-poor fonts,
237     but it also makes compositions such as QChar(0x0041) ('A') and
238     QChar(0x0308) (Unicode accent diaresis), giving QChar(0x00c4)
239     (German A Umlaut).
240 */
241 void QString::compose()
242 {
243 #ifndef QT_NO_UNICODETABLES
244     unsigned int index=0, len;
245     unsigned int cindex = 0;
246
247     QChar code, head;
248
249     QMemArray<QChar> dia;
250
251     QString composed = *this;
252
253     while (index < length()) {
254         code = at(index);
255         //printf("\n\nligature for 0x%x:\n", code.unicode());
256         QLigature ligature(code);
257         ligature.first();
258         while(ligature.current()) {
259             if ((len = ligature.match(*this, index)) != 0) {
260                 head = ligature.head();
261                 unsigned short code = head.unicode();
262                 // we exclude Arabic presentation forms A and a few
263                 // other ligatures, which are undefined in most fonts
264                 if(!(code > 0xfb50 && code < 0xfe80) &&
265                    !(code > 0xfb00 && code < 0xfb2a)) {
266                                 // joining info is only needed for Arabic
267                     if (format(ligature.tag(), *this, index, len)) {
268                         //printf("using ligature 0x%x, len=%d\n",code,len);
269                         // replace letter
270                         composed.replace(cindex, len, QChar(head));
271                         index += len-1;
272                         // we continue searching in case we have a final
273                         // form because medial ones are preferred.
274                         if ( len != 1 || ligature.tag() !=QChar::Final )
275                             break;
276                     }
277                 }
278             }
279             ligature.next();
280         }
281         cindex++;
282         index++;
283     }
284     *this = composed;
285 #endif
286 }
287
288
289
290
291
292
293 bool QChar::isPrint() const
294 {
295     Category c = category();
296     return !(c == Other_Control || c == Other_NotAssigned);
297 }
298
299 /*!
300     Returns TRUE if the character is a separator character
301     (Separator_* categories); otherwise returns FALSE.
302 */
303 bool QChar::isSpace() const
304 {
305     if( !row() )
306         if( cell() >= 9 && cell() <=13 ) return TRUE;
307     Category c = category();
308     return c >= Separator_Space && c <= Separator_Paragraph;
309 }
310
311 /*!
312     Returns TRUE if the character is a mark (Mark_* categories);
313     otherwise returns FALSE.
314 */
315 bool QChar::isMark() const
316 {
317     Category c = category();
318     return c >= Mark_NonSpacing && c <= Mark_Enclosing;
319 }
320
321 /*!
322     Returns TRUE if the character is a punctuation mark (Punctuation_*
323     categories); otherwise returns FALSE.
324 */
325 bool QChar::isPunct() const
326 {
327     Category c = category();
328     return (c >= Punctuation_Connector && c <= Punctuation_Other);
329 }
330
331 /*!
332     Returns TRUE if the character is a letter (Letter_* categories);
333     otherwise returns FALSE.
334 */
335 bool QChar::isLetter() const
336 {
337     Category c = category();
338     return (c >= Letter_Uppercase && c <= Letter_Other);
339 }
340
341 /*!
342     Returns TRUE if the character is a number (of any sort - Number_*
343     categories); otherwise returns FALSE.
344
345     \sa isDigit()
346 */
347 bool QChar::isNumber() const
348 {
349     Category c = category();
350     return c >= Number_DecimalDigit && c <= Number_Other;
351 }
352
353 /*!
354     Returns TRUE if the character is a letter or number (Letter_* or
355     Number_* categories); otherwise returns FALSE.
356 */
357 bool QChar::isLetterOrNumber() const
358 {
359     Category c = category();
360     return (c >= Letter_Uppercase && c <= Letter_Other)
361         || (c >= Number_DecimalDigit && c <= Number_Other);
362 }
363
364
365 /*!
366     Returns TRUE if the character is a decimal digit
367     (Number_DecimalDigit); otherwise returns FALSE.
368 */
369 bool QChar::isDigit() const
370 {
371     return (category() == Number_DecimalDigit);
372 }
373
374
375 /*!
376     Returns TRUE if the character is a symbol (Symbol_* categories);
377     otherwise returns FALSE.
378 */
379 bool QChar::isSymbol() const
380 {
381     Category c = category();
382     return c >= Symbol_Math && c <= Symbol_Other;
383 }
384 #endif