]>
Commit | Line | Data |
---|---|---|
b75a7d8f A |
1 | |
2 | /* | |
b75a7d8f | 3 | * |
374ca955 | 4 | * (C) Copyright IBM Corp. 1998-2004 - All Rights Reserved |
b75a7d8f A |
5 | * |
6 | */ | |
7 | ||
8 | #include "LETypes.h" | |
9 | #include "LEScripts.h" | |
10 | #include "LELanguages.h" | |
11 | ||
12 | #include "LayoutEngine.h" | |
13 | #include "ArabicLayoutEngine.h" | |
374ca955 | 14 | #include "CanonShaping.h" |
b75a7d8f A |
15 | #include "HanLayoutEngine.h" |
16 | #include "IndicLayoutEngine.h" | |
17 | #include "ThaiLayoutEngine.h" | |
18 | #include "GXLayoutEngine.h" | |
19 | #include "ScriptAndLanguageTags.h" | |
374ca955 A |
20 | #include "CharSubstitutionFilter.h" |
21 | ||
22 | #include "LEGlyphStorage.h" | |
b75a7d8f A |
23 | |
24 | #include "OpenTypeUtilities.h" | |
25 | #include "GlyphSubstitutionTables.h" | |
26 | #include "MorphTables.h" | |
27 | ||
28 | #include "DefaultCharMapper.h" | |
29 | ||
30 | U_NAMESPACE_BEGIN | |
31 | ||
32 | #define ARRAY_SIZE(array) (sizeof array / sizeof array[0]) | |
33 | ||
34 | const LEUnicode32 DefaultCharMapper::controlChars[] = { | |
35 | 0x0009, 0x000A, 0x000D, | |
36 | /*0x200C, 0x200D,*/ 0x200E, 0x200F, | |
37 | 0x2028, 0x2029, 0x202A, 0x202B, 0x202C, 0x202D, 0x202E, | |
38 | 0x206A, 0x206B, 0x206C, 0x206D, 0x206E, 0x206F | |
39 | }; | |
40 | ||
41 | const le_int32 DefaultCharMapper::controlCharsCount = ARRAY_SIZE(controlChars); | |
42 | ||
43 | const LEUnicode32 DefaultCharMapper::mirroredChars[] = { | |
44 | 0x0028, 0x0029, // ascii paired punctuation | |
45 | 0x003c, 0x003e, | |
46 | 0x005b, 0x005d, | |
47 | 0x007b, 0x007d, | |
48 | 0x2045, 0x2046, // math symbols (not complete) | |
49 | 0x207d, 0x207e, | |
50 | 0x208d, 0x208e, | |
51 | 0x2264, 0x2265, | |
52 | 0x3008, 0x3009, // chinese paired punctuation | |
53 | 0x300a, 0x300b, | |
54 | 0x300c, 0x300d, | |
55 | 0x300e, 0x300f, | |
56 | 0x3010, 0x3011, | |
57 | 0x3014, 0x3015, | |
58 | 0x3016, 0x3017, | |
59 | 0x3018, 0x3019, | |
60 | 0x301a, 0x301b | |
61 | }; | |
62 | ||
63 | const le_int32 DefaultCharMapper::mirroredCharsCount = ARRAY_SIZE(mirroredChars); | |
64 | ||
65 | LEUnicode32 DefaultCharMapper::mapChar(LEUnicode32 ch) const | |
66 | { | |
67 | if (fFilterControls) { | |
68 | le_int32 index = OpenTypeUtilities::search((le_uint32)ch, (le_uint32 *)controlChars, controlCharsCount); | |
69 | ||
70 | if (controlChars[index] == ch) { | |
71 | return 0xFFFF; | |
72 | } | |
73 | } | |
74 | ||
75 | if (fMirror) { | |
76 | le_int32 index = OpenTypeUtilities::search((le_uint32) ch, (le_uint32 *)mirroredChars, mirroredCharsCount); | |
77 | ||
78 | if (mirroredChars[index] == ch) { | |
79 | le_int32 mirrorOffset = ((index & 1) == 0) ? 1 : -1; | |
80 | ||
81 | return mirroredChars[index + mirrorOffset]; | |
82 | } | |
83 | } | |
84 | ||
85 | return ch; | |
86 | } | |
87 | ||
374ca955 A |
88 | // This is here to get it out of LEGlyphFilter.h. |
89 | // No particular reason to put it here, other than | |
90 | // this is a good central location... | |
91 | LEGlyphFilter::~LEGlyphFilter() | |
92 | { | |
93 | // nothing to do | |
94 | } | |
b75a7d8f | 95 | |
374ca955 A |
96 | CharSubstitutionFilter::CharSubstitutionFilter(const LEFontInstance *fontInstance) |
97 | : fFontInstance(fontInstance) | |
b75a7d8f | 98 | { |
374ca955 | 99 | // nothing to do |
b75a7d8f A |
100 | } |
101 | ||
374ca955 | 102 | CharSubstitutionFilter::~CharSubstitutionFilter() |
b75a7d8f | 103 | { |
374ca955 A |
104 | // nothing to do |
105 | } | |
b75a7d8f | 106 | |
b75a7d8f | 107 | |
374ca955 | 108 | UOBJECT_DEFINE_RTTI_IMPLEMENTATION(LayoutEngine) |
b75a7d8f | 109 | |
374ca955 | 110 | static const LETag emptyTag = 0x00000000; |
b75a7d8f | 111 | |
374ca955 A |
112 | static const LETag ccmpFeatureTag = LE_CCMP_FEATURE_TAG; |
113 | ||
114 | static const LETag canonFeatures[] = {ccmpFeatureTag, emptyTag}; | |
115 | ||
116 | LayoutEngine::LayoutEngine(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode) | |
117 | : fGlyphStorage(NULL), fFontInstance(fontInstance), fScriptCode(scriptCode), fLanguageCode(languageCode) | |
118 | { | |
119 | fGlyphStorage = new LEGlyphStorage(); | |
120 | } | |
121 | ||
122 | le_int32 LayoutEngine::getGlyphCount() const | |
123 | { | |
124 | return fGlyphStorage->getGlyphCount(); | |
125 | }; | |
126 | ||
127 | void LayoutEngine::getCharIndices(le_int32 charIndices[], le_int32 indexBase, LEErrorCode &success) const | |
128 | { | |
129 | fGlyphStorage->getCharIndices(charIndices, indexBase, success); | |
b75a7d8f A |
130 | } |
131 | ||
132 | void LayoutEngine::getCharIndices(le_int32 charIndices[], LEErrorCode &success) const | |
133 | { | |
374ca955 | 134 | fGlyphStorage->getCharIndices(charIndices, success); |
b75a7d8f A |
135 | } |
136 | ||
137 | // Copy the glyphs into caller's (32-bit) glyph array, OR in extraBits | |
138 | void LayoutEngine::getGlyphs(le_uint32 glyphs[], le_uint32 extraBits, LEErrorCode &success) const | |
139 | { | |
374ca955 | 140 | fGlyphStorage->getGlyphs(glyphs, extraBits, success); |
b75a7d8f A |
141 | } |
142 | ||
143 | void LayoutEngine::getGlyphs(LEGlyphID glyphs[], LEErrorCode &success) const | |
144 | { | |
374ca955 | 145 | fGlyphStorage->getGlyphs(glyphs, success); |
b75a7d8f A |
146 | } |
147 | ||
148 | ||
149 | void LayoutEngine::getGlyphPositions(float positions[], LEErrorCode &success) const | |
150 | { | |
374ca955 | 151 | fGlyphStorage->getGlyphPositions(positions, success); |
b75a7d8f A |
152 | } |
153 | ||
154 | void LayoutEngine::getGlyphPosition(le_int32 glyphIndex, float &x, float &y, LEErrorCode &success) const | |
374ca955 A |
155 | { |
156 | fGlyphStorage->getGlyphPosition(glyphIndex, x, y, success); | |
157 | } | |
158 | ||
159 | le_int32 LayoutEngine::characterProcessing(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft, | |
160 | LEUnicode *&outChars, LEGlyphStorage &glyphStorage, LEErrorCode &success) | |
b75a7d8f A |
161 | { |
162 | if (LE_FAILURE(success)) { | |
374ca955 | 163 | return 0; |
b75a7d8f | 164 | } |
374ca955 A |
165 | |
166 | if (offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) { | |
167 | success = LE_ILLEGAL_ARGUMENT_ERROR; | |
168 | return 0; | |
b75a7d8f | 169 | } |
374ca955 A |
170 | |
171 | const GlyphSubstitutionTableHeader *canonGSUBTable = (GlyphSubstitutionTableHeader *) CanonShaping::glyphSubstitutionTable; | |
172 | LETag scriptTag = OpenTypeLayoutEngine::getScriptTag(fScriptCode); | |
173 | LETag langSysTag = OpenTypeLayoutEngine::getLangSysTag(fLanguageCode); | |
174 | le_int32 i, dir = 1, out = 0, outCharCount = count; | |
175 | ||
176 | if (canonGSUBTable->coversScript(scriptTag)) { | |
177 | CharSubstitutionFilter *substitutionFilter = new CharSubstitutionFilter(fFontInstance); | |
178 | ||
179 | glyphStorage.allocateGlyphArray(count, rightToLeft, success); | |
180 | glyphStorage.allocateAuxData(success); | |
181 | ||
182 | if (LE_FAILURE(success)) { | |
183 | return 0; | |
184 | } | |
185 | ||
186 | if (rightToLeft) { | |
187 | out = count - 1; | |
188 | dir = -1; | |
189 | } | |
190 | ||
191 | for (i = 0; i < count; i += 1, out += dir) { | |
192 | glyphStorage[out] = (LEGlyphID) chars[offset + i]; | |
193 | glyphStorage.setAuxData(out, (void *) canonFeatures, success); | |
194 | } | |
195 | ||
196 | outCharCount = canonGSUBTable->process(glyphStorage, rightToLeft, scriptTag, langSysTag, NULL, substitutionFilter, NULL); | |
197 | ||
198 | out = (rightToLeft? count - 1 : 0); | |
199 | ||
200 | outChars = LE_NEW_ARRAY(LEUnicode, outCharCount); | |
201 | for (i = 0; i < outCharCount; i += 1, out += dir) { | |
202 | outChars[out] = (LEUnicode) LE_GET_GLYPH(glyphStorage[i]); | |
203 | } | |
204 | ||
205 | delete substitutionFilter; | |
b75a7d8f | 206 | } |
b75a7d8f | 207 | |
374ca955 A |
208 | return outCharCount; |
209 | } | |
b75a7d8f A |
210 | |
211 | le_int32 LayoutEngine::computeGlyphs(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft, | |
374ca955 | 212 | LEGlyphStorage &glyphStorage, LEErrorCode &success) |
b75a7d8f A |
213 | { |
214 | if (LE_FAILURE(success)) { | |
215 | return 0; | |
216 | } | |
217 | ||
218 | if (chars == NULL || offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) { | |
219 | success = LE_ILLEGAL_ARGUMENT_ERROR; | |
220 | return 0; | |
221 | } | |
222 | ||
374ca955 A |
223 | LEUnicode *outChars = NULL; |
224 | le_int32 outCharCount = characterProcessing(chars, offset, count, max, rightToLeft, outChars, glyphStorage, success); | |
b75a7d8f | 225 | |
374ca955 A |
226 | if (outChars != NULL) { |
227 | mapCharsToGlyphs(outChars, 0, outCharCount, rightToLeft, rightToLeft, glyphStorage, success); | |
228 | LE_DELETE_ARRAY(outChars); // FIXME: a subclass may have allocated this, in which case this delete might not work... | |
229 | } else { | |
230 | mapCharsToGlyphs(chars, offset, count, rightToLeft, rightToLeft, glyphStorage, success); | |
231 | } | |
232 | ||
233 | return glyphStorage.getGlyphCount(); | |
b75a7d8f A |
234 | } |
235 | ||
236 | // Input: glyphs | |
237 | // Output: positions | |
374ca955 | 238 | void LayoutEngine::positionGlyphs(LEGlyphStorage &glyphStorage, float x, float y, LEErrorCode &success) |
b75a7d8f A |
239 | { |
240 | if (LE_FAILURE(success)) { | |
241 | return; | |
242 | } | |
243 | ||
374ca955 | 244 | glyphStorage.allocatePositions(success); |
b75a7d8f | 245 | |
374ca955 A |
246 | if (LE_FAILURE(success)) { |
247 | return; | |
b75a7d8f A |
248 | } |
249 | ||
374ca955 | 250 | le_int32 i, glyphCount = glyphStorage.getGlyphCount(); |
b75a7d8f A |
251 | |
252 | for (i = 0; i < glyphCount; i += 1) { | |
253 | LEPoint advance; | |
254 | ||
374ca955 | 255 | glyphStorage.setPosition(i, x, y, success); |
b75a7d8f | 256 | |
374ca955 | 257 | fFontInstance->getGlyphAdvance(glyphStorage[i], advance); |
b75a7d8f A |
258 | x += advance.fX; |
259 | y += advance.fY; | |
260 | } | |
261 | ||
374ca955 | 262 | glyphStorage.setPosition(glyphCount, x, y, success); |
b75a7d8f A |
263 | } |
264 | ||
374ca955 A |
265 | void LayoutEngine::adjustGlyphPositions(const LEUnicode chars[], le_int32 offset, le_int32 count, le_bool /*reverse*/, |
266 | LEGlyphStorage &/*glyphStorage*/, LEErrorCode &success) | |
267 | { | |
268 | if (LE_FAILURE(success)) { | |
269 | return; | |
270 | } | |
271 | ||
272 | if (chars == NULL || offset < 0 || count < 0) { | |
273 | success = LE_ILLEGAL_ARGUMENT_ERROR; | |
274 | return; | |
275 | } | |
276 | ||
277 | // default is no adjustments | |
278 | return; | |
279 | } | |
280 | ||
281 | void LayoutEngine::adjustMarkGlyphs(LEGlyphStorage &glyphStorage, LEGlyphFilter *markFilter, LEErrorCode &success) | |
b75a7d8f A |
282 | { |
283 | float xAdjust = 0; | |
374ca955 | 284 | le_int32 p, glyphCount = glyphStorage.getGlyphCount(); |
b75a7d8f A |
285 | |
286 | if (LE_FAILURE(success)) { | |
287 | return; | |
288 | } | |
289 | ||
374ca955 | 290 | if (markFilter == NULL) { |
b75a7d8f A |
291 | success = LE_ILLEGAL_ARGUMENT_ERROR; |
292 | return; | |
293 | } | |
294 | ||
374ca955 | 295 | float ignore, prev; |
b75a7d8f | 296 | |
374ca955 | 297 | glyphStorage.getGlyphPosition(0, prev, ignore, success); |
b75a7d8f | 298 | |
374ca955 A |
299 | for (p = 0; p < glyphCount; p += 1) { |
300 | float next, xAdvance; | |
301 | ||
302 | glyphStorage.getGlyphPosition(p + 1, next, ignore, success); | |
b75a7d8f | 303 | |
374ca955 A |
304 | xAdvance = next - prev; |
305 | glyphStorage.adjustPosition(p, xAdjust, 0, success); | |
306 | ||
307 | if (markFilter->accept(glyphStorage[p])) { | |
b75a7d8f A |
308 | xAdjust -= xAdvance; |
309 | } | |
374ca955 A |
310 | |
311 | prev = next; | |
b75a7d8f A |
312 | } |
313 | ||
374ca955 | 314 | glyphStorage.adjustPosition(glyphCount, xAdjust, 0, success); |
b75a7d8f A |
315 | } |
316 | ||
374ca955 | 317 | void LayoutEngine::adjustMarkGlyphs(const LEUnicode chars[], le_int32 charCount, le_bool reverse, LEGlyphStorage &glyphStorage, LEGlyphFilter *markFilter, LEErrorCode &success) |
b75a7d8f | 318 | { |
374ca955 A |
319 | float xAdjust = 0; |
320 | le_int32 c = 0, direction = 1, p; | |
321 | le_int32 glyphCount = glyphStorage.getGlyphCount(); | |
b75a7d8f | 322 | |
b75a7d8f A |
323 | if (LE_FAILURE(success)) { |
324 | return; | |
325 | } | |
326 | ||
374ca955 | 327 | if (markFilter == NULL) { |
b75a7d8f A |
328 | success = LE_ILLEGAL_ARGUMENT_ERROR; |
329 | return; | |
330 | } | |
331 | ||
374ca955 A |
332 | if (reverse) { |
333 | c = glyphCount - 1; | |
334 | direction = -1; | |
b75a7d8f A |
335 | } |
336 | ||
374ca955 | 337 | float ignore, prev; |
b75a7d8f | 338 | |
374ca955 | 339 | glyphStorage.getGlyphPosition(0, prev, ignore, success); |
b75a7d8f | 340 | |
374ca955 A |
341 | for (p = 0; p < charCount; p += 1, c += direction) { |
342 | float next, xAdvance; | |
343 | ||
344 | glyphStorage.getGlyphPosition(p + 1, next, ignore, success); | |
b75a7d8f | 345 | |
374ca955 A |
346 | xAdvance = next - prev; |
347 | glyphStorage.adjustPosition(p, xAdjust, 0, success); | |
b75a7d8f | 348 | |
374ca955 A |
349 | if (markFilter->accept(chars[c])) { |
350 | xAdjust -= xAdvance; | |
b75a7d8f | 351 | } |
374ca955 A |
352 | |
353 | prev = next; | |
354 | } | |
355 | ||
356 | glyphStorage.adjustPosition(glyphCount, xAdjust, 0, success); | |
357 | } | |
358 | ||
359 | const void *LayoutEngine::getFontTable(LETag tableTag) const | |
360 | { | |
361 | return fFontInstance->getFontTable(tableTag); | |
362 | } | |
363 | ||
364 | void LayoutEngine::mapCharsToGlyphs(const LEUnicode chars[], le_int32 offset, le_int32 count, le_bool reverse, le_bool mirror, | |
365 | LEGlyphStorage &glyphStorage, LEErrorCode &success) | |
366 | { | |
367 | if (LE_FAILURE(success)) { | |
368 | return; | |
b75a7d8f A |
369 | } |
370 | ||
374ca955 | 371 | glyphStorage.allocateGlyphArray(count, reverse, success); |
b75a7d8f | 372 | |
374ca955 A |
373 | DefaultCharMapper charMapper(TRUE, mirror); |
374 | ||
375 | fFontInstance->mapCharsToGlyphs(chars, offset, count, reverse, &charMapper, glyphStorage); | |
b75a7d8f A |
376 | } |
377 | ||
378 | // Input: characters, font? | |
379 | // Output: glyphs, positions, char indices | |
380 | // Returns: number of glyphs | |
381 | le_int32 LayoutEngine::layoutChars(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft, | |
382 | float x, float y, LEErrorCode &success) | |
383 | { | |
384 | if (LE_FAILURE(success)) { | |
385 | return 0; | |
386 | } | |
387 | ||
388 | if (chars == NULL || offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) { | |
389 | success = LE_ILLEGAL_ARGUMENT_ERROR; | |
390 | return 0; | |
391 | } | |
392 | ||
374ca955 A |
393 | le_int32 glyphCount; |
394 | ||
395 | glyphCount = computeGlyphs(chars, offset, count, max, rightToLeft, *fGlyphStorage, success); | |
396 | positionGlyphs(*fGlyphStorage, x, y, success); | |
397 | adjustGlyphPositions(chars, offset, count, rightToLeft, *fGlyphStorage, success); | |
b75a7d8f | 398 | |
374ca955 | 399 | return glyphCount; |
b75a7d8f A |
400 | } |
401 | ||
402 | void LayoutEngine::reset() | |
403 | { | |
374ca955 | 404 | fGlyphStorage->reset(); |
b75a7d8f A |
405 | } |
406 | ||
407 | LayoutEngine *LayoutEngine::layoutEngineFactory(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode, LEErrorCode &success) | |
408 | { | |
374ca955 A |
409 | static const le_uint32 gsubTableTag = LE_GSUB_TABLE_TAG; |
410 | static const le_uint32 mortTableTag = LE_MORT_TABLE_TAG; | |
b75a7d8f A |
411 | |
412 | if (LE_FAILURE(success)) { | |
413 | return NULL; | |
414 | } | |
415 | ||
416 | const GlyphSubstitutionTableHeader *gsubTable = (const GlyphSubstitutionTableHeader *) fontInstance->getFontTable(gsubTableTag); | |
417 | LayoutEngine *result = NULL; | |
418 | LETag scriptTag = 0x00000000; | |
419 | LETag languageTag = 0x00000000; | |
420 | ||
421 | if (gsubTable != NULL && gsubTable->coversScript(scriptTag = OpenTypeLayoutEngine::getScriptTag(scriptCode))) { | |
422 | switch (scriptCode) { | |
423 | case bengScriptCode: | |
424 | case devaScriptCode: | |
425 | case gujrScriptCode: | |
426 | case kndaScriptCode: | |
427 | case mlymScriptCode: | |
428 | case oryaScriptCode: | |
429 | case guruScriptCode: | |
430 | case tamlScriptCode: | |
431 | case teluScriptCode: | |
432 | result = new IndicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, gsubTable); | |
433 | break; | |
434 | ||
435 | case arabScriptCode: | |
436 | result = new ArabicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, gsubTable); | |
437 | break; | |
438 | ||
439 | case haniScriptCode: | |
440 | languageTag = OpenTypeLayoutEngine::getLangSysTag(languageCode); | |
441 | ||
442 | switch (languageCode) { | |
443 | case korLanguageCode: | |
444 | case janLanguageCode: | |
445 | case zhtLanguageCode: | |
446 | case zhsLanguageCode: | |
374ca955 | 447 | if (gsubTable->coversScriptAndLanguage(scriptTag, languageTag, TRUE)) { |
b75a7d8f A |
448 | result = new HanOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, gsubTable); |
449 | break; | |
450 | } | |
451 | ||
452 | // note: falling through to default case. | |
453 | default: | |
454 | result = new OpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, gsubTable); | |
455 | break; | |
456 | } | |
457 | ||
458 | break; | |
459 | ||
460 | default: | |
461 | result = new OpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, gsubTable); | |
462 | break; | |
463 | } | |
464 | } else { | |
465 | const MorphTableHeader *morphTable = (MorphTableHeader *) fontInstance->getFontTable(mortTableTag); | |
466 | ||
467 | if (morphTable != NULL) { | |
468 | result = new GXLayoutEngine(fontInstance, scriptCode, languageCode, morphTable); | |
469 | } else { | |
470 | switch (scriptCode) { | |
471 | case bengScriptCode: | |
472 | case devaScriptCode: | |
473 | case gujrScriptCode: | |
474 | case kndaScriptCode: | |
475 | case mlymScriptCode: | |
476 | case oryaScriptCode: | |
477 | case guruScriptCode: | |
478 | case tamlScriptCode: | |
479 | case teluScriptCode: | |
480 | { | |
481 | result = new IndicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode); | |
482 | break; | |
483 | } | |
484 | ||
485 | case arabScriptCode: | |
374ca955 | 486 | //case hebrScriptCode: |
b75a7d8f A |
487 | result = new UnicodeArabicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode); |
488 | break; | |
489 | ||
490 | //case hebrScriptCode: | |
491 | // return new HebrewOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode); | |
492 | ||
493 | case thaiScriptCode: | |
494 | result = new ThaiLayoutEngine(fontInstance, scriptCode, languageCode); | |
495 | break; | |
496 | ||
497 | default: | |
498 | result = new LayoutEngine(fontInstance, scriptCode, languageCode); | |
499 | break; | |
500 | } | |
501 | } | |
502 | } | |
503 | ||
504 | if (result == NULL) { | |
505 | success = LE_MEMORY_ALLOCATION_ERROR; | |
506 | } | |
507 | ||
508 | return result; | |
509 | } | |
510 | ||
511 | LayoutEngine::~LayoutEngine() { | |
512 | reset(); | |
513 | } | |
514 | ||
515 | U_NAMESPACE_END |