]> git.saurik.com Git - apple/icu.git/blame_incremental - icuSources/layoutex/ParagraphLayout.cpp
ICU-461.18.tar.gz
[apple/icu.git] / icuSources / layoutex / ParagraphLayout.cpp
... / ...
CommitLineData
1/*
2 **********************************************************************
3 * Copyright (C) 2002-2010, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
6 */
7
8/*
9 * paragraphLayout doesn't make much sense without
10 * BreakIterator...
11 */
12#include "layout/LETypes.h"
13#include "layout/LEScripts.h"
14#include "layout/LELanguages.h"
15#include "layout/LayoutEngine.h"
16#include "layout/LEFontInstance.h"
17
18#include "unicode/ubidi.h"
19#include "unicode/uchriter.h"
20#include "unicode/brkiter.h"
21
22#if ! UCONFIG_NO_BREAK_ITERATION
23#include "LXUtilities.h"
24#include "usc_impl.h" /* this is currently private! */
25#include "cstring.h" /* this too! */
26
27#include "layout/ParagraphLayout.h"
28
29U_NAMESPACE_BEGIN
30
31#define ARRAY_SIZE(array) (sizeof array / sizeof array[0])
32
33/* Leave this copyright notice here! It needs to go somewhere in this library. */
34static const char copyright[] = U_COPYRIGHT_STRING;
35
36class StyleRuns
37{
38public:
39 StyleRuns(const RunArray *styleRunArrays[], le_int32 styleCount);
40
41 ~StyleRuns();
42
43 le_int32 getRuns(le_int32 runLimits[], le_int32 styleIndices[]);
44
45private:
46 le_int32 fStyleCount;
47 le_int32 fRunCount;
48
49 le_int32 *fRunLimits;
50 le_int32 *fStyleIndices;
51};
52
53StyleRuns::StyleRuns(const RunArray *styleRunArrays[], le_int32 styleCount)
54 : fStyleCount(styleCount), fRunCount(0), fRunLimits(NULL), fStyleIndices(NULL)
55{
56 le_int32 maxRunCount = 0;
57 le_int32 style, run, runStyle;
58 le_int32 *currentRun = LE_NEW_ARRAY(le_int32, styleCount);
59
60 for (int i = 0; i < styleCount; i += 1) {
61 maxRunCount += styleRunArrays[i]->getCount();
62 }
63
64 maxRunCount -= styleCount - 1;
65
66 fRunLimits = LE_NEW_ARRAY(le_int32, maxRunCount);
67 fStyleIndices = LE_NEW_ARRAY(le_int32, maxRunCount * styleCount);
68
69 for (style = 0; style < styleCount; style += 1) {
70 currentRun[style] = 0;
71 }
72
73 run = 0;
74 runStyle = 0;
75
76 /*
77 * Since the last run limit for each style run must be
78 * the same, all the styles will hit the last limit at
79 * the same time, so we know when we're done when the first
80 * style hits the last limit.
81 */
82 while (currentRun[0] < styleRunArrays[0]->getCount()) {
83 fRunLimits[run] = 0x7FFFFFFF;
84
85 // find the minimum run limit for all the styles
86 for (style = 0; style < styleCount; style += 1) {
87 if (styleRunArrays[style]->getLimit(currentRun[style]) < fRunLimits[run]) {
88 fRunLimits[run] = styleRunArrays[style]->getLimit(currentRun[style]);
89 }
90 }
91
92 // advance all styles whose current run is at this limit to the next run
93 for (style = 0; style < styleCount; style += 1) {
94 fStyleIndices[runStyle++] = currentRun[style];
95
96 if (styleRunArrays[style]->getLimit(currentRun[style]) == fRunLimits[run]) {
97 currentRun[style] += 1;
98 }
99 }
100
101 run += 1;
102 }
103
104 fRunCount = run;
105 LE_DELETE_ARRAY(currentRun);
106}
107
108StyleRuns::~StyleRuns()
109{
110 fRunCount = 0;
111
112 LE_DELETE_ARRAY(fStyleIndices);
113 fStyleIndices = NULL;
114
115 LE_DELETE_ARRAY(fRunLimits);
116 fRunLimits = NULL;
117}
118
119le_int32 StyleRuns::getRuns(le_int32 runLimits[], le_int32 styleIndices[])
120{
121 if (runLimits != NULL) {
122 LE_ARRAY_COPY(runLimits, fRunLimits, fRunCount);
123 }
124
125 if (styleIndices != NULL) {
126 LE_ARRAY_COPY(styleIndices, fStyleIndices, fRunCount * fStyleCount);
127 }
128
129 return fRunCount;
130}
131
132/*
133 * NOTE: This table only has "TRUE" values for
134 * those scripts which the LayoutEngine can currently
135 * process, rather for all scripts which require
136 * complex processing for correct rendering.
137 */
138static const le_bool complexTable[scriptCodeCount] = {
139 FALSE , /* Zyyy */
140 FALSE, /* Qaai */
141 TRUE, /* Arab */
142 FALSE, /* Armn */
143 TRUE, /* Beng */
144 FALSE, /* Bopo */
145 FALSE, /* Cher */
146 FALSE, /* Copt=Qaac */
147 FALSE, /* Cyrl */
148 FALSE, /* Dsrt */
149 TRUE, /* Deva */
150 FALSE, /* Ethi */
151 FALSE, /* Geor */
152 FALSE, /* Goth */
153 FALSE, /* Grek */
154 TRUE, /* Gujr */
155 TRUE, /* Guru */
156 FALSE, /* Hani */
157 FALSE, /* Hang */
158 TRUE, /* Hebr */
159 FALSE, /* Hira */
160 TRUE, /* Knda */
161 FALSE, /* Kana */
162 FALSE, /* Khmr */
163 FALSE, /* Laoo */
164 FALSE, /* Latn */
165 TRUE, /* Mlym */
166 FALSE, /* Mong */
167 FALSE, /* Mymr */
168 FALSE, /* Ogam */
169 FALSE, /* Ital */
170 TRUE, /* Orya */
171 FALSE, /* Runr */
172 FALSE, /* Sinh */
173 FALSE, /* Syrc */
174 TRUE, /* Taml */
175 TRUE, /* Telu */
176 FALSE, /* Thaa */
177 TRUE, /* Thai */
178 FALSE, /* Tibt */
179 FALSE, /* Cans */
180 FALSE, /* Yiii */
181 FALSE, /* Tglg */
182 FALSE, /* Hano */
183 FALSE, /* Buhd */
184 FALSE, /* Tagb */
185 FALSE, /* Brai */
186 FALSE, /* Cprt */
187 FALSE, /* Limb */
188 FALSE, /* Linb */
189 FALSE, /* Osma */
190 FALSE, /* Shaw */
191 FALSE, /* Tale */
192 FALSE, /* Ugar */
193 FALSE, /* Hrkt */
194 FALSE, /* Bugi */
195 FALSE, /* Glag */
196 FALSE, /* Khar */
197 FALSE, /* Sylo */
198 FALSE, /* Talu */
199 FALSE, /* Tfng */
200 FALSE, /* Xpeo */
201 FALSE, /* Bali */
202 FALSE, /* Batk */
203 FALSE, /* Blis */
204 FALSE, /* Brah */
205 FALSE, /* Cham */
206 FALSE, /* Cirt */
207 FALSE, /* Cyrs */
208 FALSE, /* Egyd */
209 FALSE, /* Egyh */
210 FALSE, /* Egyp */
211 FALSE, /* Geok */
212 FALSE, /* Hans */
213 FALSE, /* Hant */
214 FALSE, /* Hmng */
215 FALSE, /* Hung */
216 FALSE, /* Inds */
217 FALSE, /* Java */
218 FALSE, /* Kali */
219 FALSE, /* Latf */
220 FALSE, /* Latg */
221 FALSE, /* Lepc */
222 FALSE, /* Lina */
223 FALSE, /* Mand */
224 FALSE, /* Maya */
225 FALSE, /* Mero */
226 FALSE, /* Nkoo */
227 FALSE, /* Orkh */
228 FALSE, /* Perm */
229 FALSE, /* Phag */
230 FALSE, /* Phnx */
231 FALSE, /* Plrd */
232 FALSE, /* Roro */
233 FALSE, /* Sara */
234 FALSE, /* Syre */
235 FALSE, /* Syrj */
236 FALSE, /* Syrn */
237 FALSE, /* Teng */
238 FALSE, /* Taii */
239 FALSE, /* Visp */
240 FALSE, /* Xsux */
241 FALSE, /* Zxxx */
242 FALSE, /* Zzzz */
243 FALSE, /* Cari */
244 FALSE, /* Jpan */
245 FALSE, /* Lana */
246 FALSE, /* Lyci */
247 FALSE, /* Lydi */
248 FALSE, /* Olck */
249 FALSE, /* Rjng */
250 FALSE, /* Saur */
251 FALSE, /* Sgnw */
252 FALSE, /* Sund */
253 FALSE, /* Moon */
254 FALSE, /* Mtei */
255 FALSE, /* Armi */
256 FALSE, /* Avst */
257 FALSE, /* Cakm */
258 FALSE, /* Kore */
259 FALSE, /* Kthi */
260 FALSE, /* Mani */
261 FALSE, /* Phli */
262 FALSE, /* Phlp */
263 FALSE, /* Phlv */
264 FALSE, /* Prti */
265 FALSE, /* Samr */
266 FALSE, /* Tavt */
267 FALSE, /* Zmth */
268 FALSE, /* Zsym */
269 FALSE, /* Bamu */
270 FALSE, /* Lisu */
271 FALSE, /* Nkgb */
272 FALSE /* Sarb */
273};
274
275
276const char ParagraphLayout::fgClassID = 0;
277
278static void fillMissingCharToGlyphMapValues(le_int32 *charToGlyphMap,
279 le_int32 charCount) {
280 le_int32 lastValidGlyph = -1;
281 le_int32 ch;
282 for (ch = 0; ch <= charCount; ch += 1) {
283 if (charToGlyphMap[ch] == -1) {
284 charToGlyphMap[ch] = lastValidGlyph;
285 } else {
286 lastValidGlyph = charToGlyphMap[ch];
287 }
288 }
289}
290
291/*
292 * How to deal with composite fonts:
293 *
294 * Don't store the client's FontRuns; we'll need to compute sub-font FontRuns using Doug's
295 * LEFontInstance method. Do that by intersecting the client's FontRuns with fScriptRuns. Use
296 * that to compute fFontRuns, and then intersect fFontRuns, fScriptRuns and fLevelRuns. Doing
297 * it in this order means we do a two-way intersection and a three-way intersection.
298 *
299 * An optimization would be to only do this if there's at least one composite font...
300 *
301 * Other notes:
302 *
303 * * Return the sub-fonts as the run fonts... could keep the mapping back to the client's FontRuns
304 * but that probably makes it more complicated of everyone...
305 *
306 * * Take the LineInfo and LineRun types from Paragraph and use them here, incorporate them into the API.
307 *
308 * * Might want to change the name of the StyleRun type, and make a new one that holds fonts, scripts and levels?
309 *
310 */
311ParagraphLayout::ParagraphLayout(const LEUnicode chars[], le_int32 count,
312 const FontRuns *fontRuns,
313 const ValueRuns *levelRuns,
314 const ValueRuns *scriptRuns,
315 const LocaleRuns *localeRuns,
316 UBiDiLevel paragraphLevel, le_bool vertical,
317 LEErrorCode &status)
318 : fChars(chars), fCharCount(count),
319 fFontRuns(NULL), fLevelRuns(levelRuns), fScriptRuns(scriptRuns), fLocaleRuns(localeRuns),
320 fVertical(vertical), fClientLevels(TRUE), fClientScripts(TRUE), fClientLocales(TRUE), fEmbeddingLevels(NULL),
321 fAscent(0), fDescent(0), fLeading(0),
322 fGlyphToCharMap(NULL), fCharToMinGlyphMap(NULL), fCharToMaxGlyphMap(NULL), fGlyphWidths(NULL), fGlyphCount(0),
323 fParaBidi(NULL), fLineBidi(NULL),
324 fStyleRunLimits(NULL), fStyleIndices(NULL), fStyleRunCount(0),
325 fBreakIterator(NULL), fLineStart(-1), fLineEnd(0),
326 /*fVisualRuns(NULL), fStyleRunInfo(NULL), fVisualRunCount(-1),
327 fFirstVisualRun(-1), fLastVisualRun(-1),*/ fVisualRunLastX(0), fVisualRunLastY(0)
328{
329
330 if (LE_FAILURE(status)) {
331 fCharCount = -1;
332 return;
333 }
334
335 // FIXME: should check the limit arrays for consistency...
336
337 computeLevels(paragraphLevel);
338
339 if (scriptRuns == NULL) {
340 computeScripts();
341 }
342
343 if (localeRuns == NULL) {
344 computeLocales();
345 }
346
347 computeSubFonts(fontRuns, status);
348
349 if (LE_FAILURE(status)) {
350 //other stuff?
351 fCharCount = -1;
352 return;
353 }
354
355 // now intersect the font, direction and script runs...
356 const RunArray *styleRunArrays[] = {fFontRuns, fLevelRuns, fScriptRuns, fLocaleRuns};
357 le_int32 styleCount = sizeof styleRunArrays / sizeof styleRunArrays[0];
358 StyleRuns styleRuns(styleRunArrays, styleCount);
359 LEErrorCode layoutStatus = LE_NO_ERROR;
360
361 fStyleRunCount = styleRuns.getRuns(NULL, NULL);
362
363 fStyleRunLimits = LE_NEW_ARRAY(le_int32, fStyleRunCount);
364 fStyleIndices = LE_NEW_ARRAY(le_int32, fStyleRunCount * styleCount);
365 if ((fStyleRunLimits == NULL) || (fStyleIndices == NULL)) {
366 status = LE_MEMORY_ALLOCATION_ERROR;
367 return;
368 }
369
370 styleRuns.getRuns(fStyleRunLimits, fStyleIndices);
371
372 // now build a LayoutEngine for each style run...
373 le_int32 *styleIndices = fStyleIndices;
374 le_int32 run, runStart;
375
376 fStyleRunInfo = LE_NEW_ARRAY(StyleRunInfo, fStyleRunCount);
377 if (fStyleRunInfo == NULL) {
378 status = LE_MEMORY_ALLOCATION_ERROR;
379 return;
380 }
381 else {
382 // initialize
383 for (runStart = 0, run = 0; run < fStyleRunCount; run += 1) {
384 fStyleRunInfo[run].font = NULL;
385 fStyleRunInfo[run].runBase = 0;
386 fStyleRunInfo[run].runLimit = 0;
387 fStyleRunInfo[run].script = (UScriptCode)0;
388 fStyleRunInfo[run].locale = NULL;
389 fStyleRunInfo[run].level = 0;
390 fStyleRunInfo[run].glyphBase = 0;
391 fStyleRunInfo[run].engine = NULL;
392 fStyleRunInfo[run].glyphCount = 0;
393 fStyleRunInfo[run].glyphs = NULL;
394 fStyleRunInfo[run].positions = NULL;
395 }
396 }
397
398 fGlyphCount = 0;
399 for (runStart = 0, run = 0; run < fStyleRunCount; run += 1) {
400 fStyleRunInfo[run].font = fFontRuns->getFont(styleIndices[0]);
401 fStyleRunInfo[run].runBase = runStart;
402 fStyleRunInfo[run].runLimit = fStyleRunLimits[run];
403 fStyleRunInfo[run].script = (UScriptCode) fScriptRuns->getValue(styleIndices[2]);
404 fStyleRunInfo[run].locale = fLocaleRuns->getLocale(styleIndices[3]);
405 fStyleRunInfo[run].level = (UBiDiLevel) fLevelRuns->getValue(styleIndices[1]);
406 fStyleRunInfo[run].glyphBase = fGlyphCount;
407
408 fStyleRunInfo[run].engine = LayoutEngine::layoutEngineFactory(fStyleRunInfo[run].font,
409 fStyleRunInfo[run].script, getLanguageCode(fStyleRunInfo[run].locale), layoutStatus);
410 if (LE_FAILURE(layoutStatus)) {
411 status = layoutStatus;
412 return;
413 }
414
415 fStyleRunInfo[run].glyphCount = fStyleRunInfo[run].engine->layoutChars(fChars, runStart, fStyleRunLimits[run] - runStart, fCharCount,
416 fStyleRunInfo[run].level & 1, 0, 0, layoutStatus);
417 if (LE_FAILURE(layoutStatus)) {
418 status = layoutStatus;
419 return;
420 }
421
422 runStart = fStyleRunLimits[run];
423 styleIndices += styleCount;
424 fGlyphCount += fStyleRunInfo[run].glyphCount;
425 }
426
427 // Make big arrays for the glyph widths, glyph-to-char and char-to-glyph maps,
428 // in logical order. (Both maps need an extra entry for the end of the text.)
429 //
430 // For each layout get the positions and convert them into glyph widths, in
431 // logical order. Get the glyph-to-char mapping, offset by starting index in the
432 // character array. Swap the glyph width and glyph-to-char arrays into logical order.
433 // Finally, fill in the char-to-glyph mappings.
434 fGlyphWidths = LE_NEW_ARRAY(float, fGlyphCount);
435 fGlyphToCharMap = LE_NEW_ARRAY(le_int32, fGlyphCount + 1);
436 fCharToMinGlyphMap = LE_NEW_ARRAY(le_int32, fCharCount + 1);
437 fCharToMaxGlyphMap = LE_NEW_ARRAY(le_int32, fCharCount + 1);
438 if ((fGlyphWidths == NULL) || (fGlyphToCharMap == NULL) ||
439 (fCharToMinGlyphMap == NULL) || (fCharToMaxGlyphMap == NULL)) {
440 status = LE_MEMORY_ALLOCATION_ERROR;
441 return;
442 }
443
444 le_int32 glyph;
445
446 for (runStart = 0, run = 0; run < fStyleRunCount; run += 1) {
447 LayoutEngine *engine = fStyleRunInfo[run].engine;
448 le_int32 glyphCount = fStyleRunInfo[run].glyphCount;
449 le_int32 glyphBase = fStyleRunInfo[run].glyphBase;
450
451 fStyleRunInfo[run].glyphs = LE_NEW_ARRAY(LEGlyphID, glyphCount);
452 fStyleRunInfo[run].positions = LE_NEW_ARRAY(float, glyphCount * 2 + 2);
453 if ((fStyleRunInfo[run].glyphs == NULL) ||
454 (fStyleRunInfo[run].positions == NULL)) {
455 status = LE_MEMORY_ALLOCATION_ERROR;
456 return;
457 }
458
459 engine->getGlyphs(fStyleRunInfo[run].glyphs, layoutStatus);
460 if (LE_FAILURE(layoutStatus)) {
461 status = layoutStatus;
462 return;
463 }
464
465 engine->getGlyphPositions(fStyleRunInfo[run].positions, layoutStatus);
466 if (LE_FAILURE(layoutStatus)) {
467 status = layoutStatus;
468 return;
469 }
470
471 engine->getCharIndices(&fGlyphToCharMap[glyphBase], runStart, layoutStatus);
472 if (LE_FAILURE(layoutStatus)) {
473 status = layoutStatus;
474 return;
475 }
476
477 for (glyph = 0; glyph < glyphCount; glyph += 1) {
478 fGlyphWidths[glyphBase + glyph] = fStyleRunInfo[run].positions[glyph * 2 + 2] - fStyleRunInfo[run].positions[glyph * 2];
479 }
480
481 if ((fStyleRunInfo[run].level & 1) != 0) {
482 LXUtilities::reverse(&fGlyphWidths[glyphBase], glyphCount);
483 LXUtilities::reverse(&fGlyphToCharMap[glyphBase], glyphCount);
484 }
485
486 runStart = fStyleRunLimits[run];
487
488 delete engine;
489 fStyleRunInfo[run].engine = NULL;
490 }
491
492 fGlyphToCharMap[fGlyphCount] = fCharCount;
493
494 // Initialize the char-to-glyph maps to -1 so that we can later figure out
495 // whether any of the entries in the map aren't filled in below.
496 le_int32 chIndex;
497 for (chIndex = 0; chIndex <= fCharCount; chIndex += 1) {
498 fCharToMinGlyphMap[chIndex] = -1;
499 fCharToMaxGlyphMap[chIndex] = -1;
500 }
501
502 for (glyph = fGlyphCount - 1; glyph >= 0; glyph -= 1) {
503 le_int32 ch = fGlyphToCharMap[glyph];
504
505 fCharToMinGlyphMap[ch] = glyph;
506 }
507
508 fCharToMinGlyphMap[fCharCount] = fGlyphCount;
509
510 for (glyph = 0; glyph < fGlyphCount; glyph += 1) {
511 le_int32 ch = fGlyphToCharMap[glyph];
512
513 fCharToMaxGlyphMap[ch] = glyph;
514 }
515
516 fCharToMaxGlyphMap[fCharCount] = fGlyphCount;
517
518 // Now fill in the missing values in the char-to-glyph maps.
519 fillMissingCharToGlyphMapValues(fCharToMinGlyphMap, fCharCount);
520 fillMissingCharToGlyphMapValues(fCharToMaxGlyphMap, fCharCount);
521}
522
523ParagraphLayout::~ParagraphLayout()
524{
525 delete (FontRuns *) fFontRuns;
526
527 if (! fClientLevels) {
528 delete (ValueRuns *) fLevelRuns;
529 fLevelRuns = NULL;
530
531 fClientLevels = TRUE;
532 }
533
534 if (! fClientScripts) {
535 delete (ValueRuns *) fScriptRuns;
536 fScriptRuns = NULL;
537
538 fClientScripts = TRUE;
539 }
540
541 if (! fClientLocales) {
542 delete (LocaleRuns *) fLocaleRuns;
543 fLocaleRuns = NULL;
544
545 fClientLocales = TRUE;
546 }
547
548 if (fEmbeddingLevels != NULL) {
549 LE_DELETE_ARRAY(fEmbeddingLevels);
550 fEmbeddingLevels = NULL;
551 }
552
553 if (fGlyphToCharMap != NULL) {
554 LE_DELETE_ARRAY(fGlyphToCharMap);
555 fGlyphToCharMap = NULL;
556 }
557
558 if (fCharToMinGlyphMap != NULL) {
559 LE_DELETE_ARRAY(fCharToMinGlyphMap);
560 fCharToMinGlyphMap = NULL;
561 }
562
563 if (fCharToMaxGlyphMap != NULL) {
564 LE_DELETE_ARRAY(fCharToMaxGlyphMap);
565 fCharToMaxGlyphMap = NULL;
566 }
567
568 if (fGlyphWidths != NULL) {
569 LE_DELETE_ARRAY(fGlyphWidths);
570 fGlyphWidths = NULL;
571 }
572
573 if (fParaBidi != NULL) {
574 ubidi_close(fParaBidi);
575 fParaBidi = NULL;
576 }
577
578 if (fLineBidi != NULL) {
579 ubidi_close(fLineBidi);
580 fLineBidi = NULL;
581 }
582
583 if (fStyleRunCount > 0) {
584 le_int32 run;
585
586 LE_DELETE_ARRAY(fStyleRunLimits);
587 LE_DELETE_ARRAY(fStyleIndices);
588
589 for (run = 0; run < fStyleRunCount; run += 1) {
590 LE_DELETE_ARRAY(fStyleRunInfo[run].glyphs);
591 LE_DELETE_ARRAY(fStyleRunInfo[run].positions);
592
593 fStyleRunInfo[run].glyphs = NULL;
594 fStyleRunInfo[run].positions = NULL;
595 }
596
597 LE_DELETE_ARRAY(fStyleRunInfo);
598
599 fStyleRunLimits = NULL;
600 fStyleIndices = NULL;
601 fStyleRunInfo = NULL;
602 fStyleRunCount = 0;
603 }
604
605 if (fBreakIterator != NULL) {
606 delete fBreakIterator;
607 fBreakIterator = NULL;
608 }
609}
610
611
612le_bool ParagraphLayout::isComplex(const LEUnicode chars[], le_int32 count)
613{
614 UErrorCode scriptStatus = U_ZERO_ERROR;
615 UScriptCode scriptCode = USCRIPT_INVALID_CODE;
616 UScriptRun *sr = uscript_openRun(chars, count, &scriptStatus);
617 le_bool result = FALSE;
618
619 while (uscript_nextRun(sr, NULL, NULL, &scriptCode)) {
620 if (isComplex(scriptCode)) {
621 result = TRUE;
622 break;
623 }
624 }
625
626 uscript_closeRun(sr);
627 return result;
628}
629
630le_int32 ParagraphLayout::getAscent() const
631{
632 if (fAscent <= 0 && fCharCount > 0) {
633 ((ParagraphLayout *) this)->computeMetrics();
634 }
635
636 return fAscent;
637}
638
639le_int32 ParagraphLayout::getDescent() const
640{
641 if (fAscent <= 0 && fCharCount > 0) {
642 ((ParagraphLayout *) this)->computeMetrics();
643 }
644
645 return fDescent;
646}
647
648le_int32 ParagraphLayout::getLeading() const
649{
650 if (fAscent <= 0 && fCharCount > 0) {
651 ((ParagraphLayout *) this)->computeMetrics();
652 }
653
654 return fLeading;
655}
656
657le_bool ParagraphLayout::isDone() const
658{
659 return fLineEnd >= fCharCount;
660}
661
662ParagraphLayout::Line *ParagraphLayout::nextLine(float width)
663{
664 if (isDone()) {
665 return NULL;
666 }
667
668 fLineStart = fLineEnd;
669
670 if (width > 0) {
671 le_int32 glyph = fCharToMinGlyphMap[fLineStart];
672 float widthSoFar = 0;
673
674 while (glyph < fGlyphCount && widthSoFar + fGlyphWidths[glyph] <= width) {
675 widthSoFar += fGlyphWidths[glyph++];
676 }
677
678 // If no glyphs fit on the line, force one to fit.
679 //
680 // (There shouldn't be any zero width glyphs at the
681 // start of a line unless the paragraph consists of
682 // only zero width glyphs, because otherwise the zero
683 // width glyphs will have been included on the end of
684 // the previous line...)
685 if (widthSoFar == 0 && glyph < fGlyphCount) {
686 glyph += 1;
687 }
688
689 fLineEnd = previousBreak(fGlyphToCharMap[glyph]);
690
691 // If this break is at or before the last one,
692 // find a glyph, starting at the one which didn't
693 // fit, that produces a break after the last one.
694 while (fLineEnd <= fLineStart) {
695 fLineEnd = fGlyphToCharMap[glyph++];
696 }
697 } else {
698 fLineEnd = fCharCount;
699 }
700
701 return computeVisualRuns();
702}
703
704void ParagraphLayout::computeLevels(UBiDiLevel paragraphLevel)
705{
706 UErrorCode bidiStatus = U_ZERO_ERROR;
707
708 if (fLevelRuns != NULL) {
709 le_int32 ch;
710 le_int32 run;
711
712 fEmbeddingLevels = LE_NEW_ARRAY(UBiDiLevel, fCharCount);
713
714 for (ch = 0, run = 0; run < fLevelRuns->getCount(); run += 1) {
715 UBiDiLevel runLevel = (UBiDiLevel) fLevelRuns->getValue(run) | UBIDI_LEVEL_OVERRIDE;
716 le_int32 runLimit = fLevelRuns->getLimit(run);
717
718 while (ch < runLimit) {
719 fEmbeddingLevels[ch++] = runLevel;
720 }
721 }
722 }
723
724 fParaBidi = ubidi_openSized(fCharCount, 0, &bidiStatus);
725 ubidi_setPara(fParaBidi, fChars, fCharCount, paragraphLevel, fEmbeddingLevels, &bidiStatus);
726
727 if (fLevelRuns == NULL) {
728 le_int32 levelRunCount = ubidi_countRuns(fParaBidi, &bidiStatus);
729 ValueRuns *levelRuns = new ValueRuns(levelRunCount);
730
731 le_int32 logicalStart = 0;
732 le_int32 run;
733 le_int32 limit;
734 UBiDiLevel level;
735
736 for (run = 0; run < levelRunCount; run += 1) {
737 ubidi_getLogicalRun(fParaBidi, logicalStart, &limit, &level);
738 levelRuns->add(level, limit);
739 logicalStart = limit;
740 }
741
742 fLevelRuns = levelRuns;
743 fClientLevels = FALSE;
744 }
745}
746
747void ParagraphLayout::computeScripts()
748{
749 UErrorCode scriptStatus = U_ZERO_ERROR;
750 UScriptRun *sr = uscript_openRun(fChars, fCharCount, &scriptStatus);
751 ValueRuns *scriptRuns = new ValueRuns(0);
752 le_int32 limit;
753 UScriptCode script;
754
755 while (uscript_nextRun(sr, NULL, &limit, &script)) {
756 scriptRuns->add(script, limit);
757 }
758
759 uscript_closeRun(sr);
760
761 fScriptRuns = scriptRuns;
762 fClientScripts = FALSE;
763}
764
765void ParagraphLayout::computeLocales()
766{
767 LocaleRuns *localeRuns = new LocaleRuns(0);
768 const Locale *defaultLocale = &Locale::getDefault();
769
770 localeRuns->add(defaultLocale, fCharCount);
771
772 fLocaleRuns = localeRuns;
773 fClientLocales = FALSE;
774}
775
776void ParagraphLayout::computeSubFonts(const FontRuns *fontRuns, LEErrorCode &status)
777{
778 if (LE_FAILURE(status)) {
779 return;
780 }
781
782 const RunArray *styleRunArrays[] = {fontRuns, fScriptRuns};
783 le_int32 styleCount = sizeof styleRunArrays / sizeof styleRunArrays[0];
784 StyleRuns styleRuns(styleRunArrays, styleCount);
785 le_int32 styleRunCount = styleRuns.getRuns(NULL, NULL);
786 le_int32 *styleRunLimits = LE_NEW_ARRAY(le_int32, styleRunCount);
787 le_int32 *styleIndices = LE_NEW_ARRAY(le_int32, styleRunCount * styleCount);
788 FontRuns *subFontRuns = new FontRuns(0);
789 le_int32 run, offset, *si;
790
791 styleRuns.getRuns(styleRunLimits, styleIndices);
792
793 si = styleIndices;
794 offset = 0;
795
796 for (run = 0; run < styleRunCount; run += 1) {
797 const LEFontInstance *runFont = fontRuns->getFont(si[0]);
798 le_int32 script = fScriptRuns->getValue(si[1]);
799
800 while (offset < styleRunLimits[run]) {
801 const LEFontInstance *subFont = runFont->getSubFont(fChars, &offset, styleRunLimits[run], script, status);
802
803 if (LE_FAILURE(status)) {
804 delete subFontRuns;
805 goto cleanUp;
806 }
807
808 subFontRuns->add(subFont, offset);
809 }
810
811 si += styleCount;
812 }
813
814 fFontRuns = subFontRuns;
815
816cleanUp:
817 LE_DELETE_ARRAY(styleIndices);
818 LE_DELETE_ARRAY(styleRunLimits);
819}
820
821void ParagraphLayout::computeMetrics()
822{
823 le_int32 i, count = fFontRuns->getCount();
824 le_int32 maxDL = 0;
825
826 for (i = 0; i < count; i += 1) {
827 const LEFontInstance *font = fFontRuns->getFont(i);
828 le_int32 ascent = font->getAscent();
829 le_int32 descent = font->getDescent();
830 le_int32 leading = font->getLeading();
831 le_int32 dl = descent + leading;
832
833 if (ascent > fAscent) {
834 fAscent = ascent;
835 }
836
837 if (descent > fDescent) {
838 fDescent = descent;
839 }
840
841 if (leading > fLeading) {
842 fLeading = leading;
843 }
844
845 if (dl > maxDL) {
846 maxDL = dl;
847 }
848 }
849
850 fLeading = maxDL - fDescent;
851}
852
853#if 1
854struct LanguageMap
855{
856 const char *localeCode;
857 le_int32 languageCode;
858};
859
860static const LanguageMap languageMap[] =
861{
862 {"afr", afkLanguageCode}, // Afrikaans
863 {"ara", araLanguageCode}, // Arabic
864 {"asm", asmLanguageCode}, // Assamese
865 {"bel", belLanguageCode}, // Belarussian
866 {"ben", benLanguageCode}, // Bengali
867 {"bod", tibLanguageCode}, // Tibetan
868 {"bul", bgrLanguageCode}, // Bulgarian
869 {"cat", catLanguageCode}, // Catalan
870 {"ces", csyLanguageCode}, // Czech
871 {"che", cheLanguageCode}, // Chechen
872 {"cop", copLanguageCode}, // Coptic
873 {"cym", welLanguageCode}, // Welsh
874 {"dan", danLanguageCode}, // Danish
875 {"deu", deuLanguageCode}, // German
876 {"dzo", dznLanguageCode}, // Dzongkha
877 {"ell", ellLanguageCode}, // Greek
878 {"eng", engLanguageCode}, // English
879 {"est", etiLanguageCode}, // Estonian
880 {"eus", euqLanguageCode}, // Basque
881 {"fas", farLanguageCode}, // Farsi
882 {"fin", finLanguageCode}, // Finnish
883 {"fra", fraLanguageCode}, // French
884 {"gle", gaeLanguageCode}, // Irish Gaelic
885 {"guj", gujLanguageCode}, // Gujarati
886 {"hau", hauLanguageCode}, // Hausa
887 {"heb", iwrLanguageCode}, // Hebrew
888 {"hin", hinLanguageCode}, // Hindi
889 {"hrv", hrvLanguageCode}, // Croatian
890 {"hun", hunLanguageCode}, // Hungarian
891 {"hye", hyeLanguageCode}, // Armenian
892 {"ind", indLanguageCode}, // Indonesian
893 {"ita", itaLanguageCode}, // Italian
894 {"jpn", janLanguageCode}, // Japanese
895 {"kan", kanLanguageCode}, // Kannada
896 {"kas", kshLanguageCode}, // Kashmiri
897 {"khm", khmLanguageCode}, // Khmer
898 {"kok", kokLanguageCode}, // Konkani
899 {"kor", korLanguageCode}, // Korean
900// {"mal_XXX", malLanguageCode}, // Malayalam - Traditional
901 {"mal", mlrLanguageCode}, // Malayalam - Reformed
902 {"mar", marLanguageCode}, // Marathi
903 {"mlt", mtsLanguageCode}, // Maltese
904 {"mni", mniLanguageCode}, // Manipuri
905 {"mon", mngLanguageCode}, // Mongolian
906 {"nep", nepLanguageCode}, // Nepali
907 {"ori", oriLanguageCode}, // Oriya
908 {"pol", plkLanguageCode}, // Polish
909 {"por", ptgLanguageCode}, // Portuguese
910 {"pus", pasLanguageCode}, // Pashto
911 {"ron", romLanguageCode}, // Romanian
912 {"rus", rusLanguageCode}, // Russian
913 {"san", sanLanguageCode}, // Sanskrit
914 {"sin", snhLanguageCode}, // Sinhalese
915 {"slk", skyLanguageCode}, // Slovak
916 {"snd", sndLanguageCode}, // Sindhi
917 {"slv", slvLanguageCode}, // Slovenian
918 {"spa", espLanguageCode}, // Spanish
919 {"sqi", sqiLanguageCode}, // Albanian
920 {"srp", srbLanguageCode}, // Serbian
921 {"swe", sveLanguageCode}, // Swedish
922 {"syr", syrLanguageCode}, // Syriac
923 {"tam", tamLanguageCode}, // Tamil
924 {"tel", telLanguageCode}, // Telugu
925 {"tha", thaLanguageCode}, // Thai
926 {"tur", trkLanguageCode}, // Turkish
927 {"urd", urdLanguageCode}, // Urdu
928 {"yid", jiiLanguageCode}, // Yiddish
929// {"zhp", zhpLanguageCode}, // Chinese - Phonetic
930 {"zho", zhsLanguageCode}, // Chinese
931 {"zho_CHN", zhsLanguageCode}, // Chinese - China
932 {"zho_HKG", zhsLanguageCode}, // Chinese - Hong Kong
933 {"zho_MAC", zhtLanguageCode}, // Chinese - Macao
934 {"zho_SGP", zhsLanguageCode}, // Chinese - Singapore
935 {"zho_TWN", zhtLanguageCode} // Chinese - Taiwan
936};
937
938static const le_int32 languageMapCount = ARRAY_SIZE(languageMap);
939
940le_int32 ParagraphLayout::getLanguageCode(const Locale *locale)
941{
942 char code[8] = {0, 0, 0, 0, 0, 0, 0, 0};
943 const char *language = locale->getISO3Language();
944 const char *country = locale->getISO3Country();
945
946 uprv_strcat(code, language);
947
948 if ((uprv_strcmp(language, "zho") == 0) && country != NULL) {
949 uprv_strcat(code, "_");
950 uprv_strcat(code, country);
951 }
952
953 for (le_int32 i = 0; i < languageMapCount; i += 1) {
954 if (uprv_strcmp(code, languageMap[i].localeCode) == 0) {
955 return languageMap[i].languageCode;
956 }
957 }
958
959 return nullLanguageCode;
960}
961#else
962
963// TODO - dummy implementation for right now...
964le_int32 ParagraphLayout::getLanguageCode(const Locale *locale)
965{
966 return nullLanguageCode;
967}
968#endif
969
970le_bool ParagraphLayout::isComplex(UScriptCode script)
971{
972 if (script < 0 || script >= (UScriptCode) scriptCodeCount) {
973 return FALSE;
974 }
975
976 return complexTable[script];
977}
978
979le_int32 ParagraphLayout::previousBreak(le_int32 charIndex)
980{
981 // skip over any whitespace or control characters,
982 // because they can hang in the margin.
983 while (charIndex < fCharCount &&
984 (u_isWhitespace(fChars[charIndex]) ||
985 u_iscntrl(fChars[charIndex]))) {
986 charIndex += 1;
987 }
988
989 // Create the BreakIterator if we don't already have one
990 if (fBreakIterator == NULL) {
991 Locale thai("th");
992 UCharCharacterIterator *iter = new UCharCharacterIterator(fChars, fCharCount);
993 UErrorCode status = U_ZERO_ERROR;
994
995 fBreakIterator = BreakIterator::createLineInstance(thai, status);
996 fBreakIterator->adoptText(iter);
997 }
998
999 // return the break location that's at or before
1000 // the character we stopped on. Note: if we're
1001 // on a break, the "+ 1" will cause preceding to
1002 // back up to it.
1003 return fBreakIterator->preceding(charIndex + 1);
1004}
1005
1006ParagraphLayout::Line *ParagraphLayout::computeVisualRuns()
1007{
1008 UErrorCode bidiStatus = U_ZERO_ERROR;
1009 le_int32 dirRunCount, visualRun;
1010
1011 fVisualRunLastX = 0;
1012 fVisualRunLastY = 0;
1013 fFirstVisualRun = getCharRun(fLineStart);
1014 fLastVisualRun = getCharRun(fLineEnd - 1);
1015
1016 if (fLineBidi == NULL) {
1017 fLineBidi = ubidi_openSized(fCharCount, 0, &bidiStatus);
1018 }
1019
1020 ubidi_setLine(fParaBidi, fLineStart, fLineEnd, fLineBidi, &bidiStatus);
1021 dirRunCount = ubidi_countRuns(fLineBidi, &bidiStatus);
1022
1023 Line *line = new Line();
1024
1025 for (visualRun = 0; visualRun < dirRunCount; visualRun += 1) {
1026 le_int32 relStart, run, runLength;
1027 UBiDiDirection runDirection = ubidi_getVisualRun(fLineBidi, visualRun, &relStart, &runLength);
1028 le_int32 runStart = fLineStart + relStart;
1029 le_int32 runEnd = runStart + runLength - 1;
1030 le_int32 firstRun = getCharRun(runStart);
1031 le_int32 lastRun = getCharRun(runEnd);
1032 le_int32 startRun = (runDirection == UBIDI_LTR)? firstRun : lastRun;
1033 le_int32 stopRun = (runDirection == UBIDI_LTR)? lastRun + 1 : firstRun - 1;
1034 le_int32 dir = (runDirection == UBIDI_LTR)? 1 : -1;
1035
1036 for (run = startRun; run != stopRun; run += dir) {
1037 le_int32 firstChar = (run == firstRun)? runStart : fStyleRunInfo[run].runBase;
1038 le_int32 lastChar = (run == lastRun)? runEnd : fStyleRunInfo[run].runLimit - 1;
1039
1040 appendRun(line, run, firstChar, lastChar);
1041 }
1042 }
1043
1044 return line;
1045}
1046
1047void ParagraphLayout::appendRun(ParagraphLayout::Line *line, le_int32 run, le_int32 firstChar, le_int32 lastChar)
1048{
1049 le_int32 glyphBase = fStyleRunInfo[run].glyphBase;
1050 le_int32 inGlyph, outGlyph;
1051
1052 // Get the glyph indices for all the characters between firstChar and lastChar,
1053 // make the minimum one be leftGlyph and the maximum one be rightGlyph.
1054 // (need to do this to handle local reorderings like Indic left matras)
1055 le_int32 leftGlyph = fGlyphCount;
1056 le_int32 rightGlyph = -1;
1057 le_int32 ch;
1058
1059 for (ch = firstChar; ch <= lastChar; ch += 1) {
1060 le_int32 minGlyph = fCharToMinGlyphMap[ch];
1061 le_int32 maxGlyph = fCharToMaxGlyphMap[ch];
1062
1063 if (minGlyph < leftGlyph) {
1064 leftGlyph = minGlyph;
1065 }
1066
1067 if (maxGlyph > rightGlyph) {
1068 rightGlyph = maxGlyph;
1069 }
1070 }
1071
1072 if ((fStyleRunInfo[run].level & 1) != 0) {
1073 le_int32 swap = rightGlyph;
1074 le_int32 last = glyphBase + fStyleRunInfo[run].glyphCount - 1;
1075
1076 // Here, we want to remove the glyphBase bias...
1077 rightGlyph = last - leftGlyph;
1078 leftGlyph = last - swap;
1079 } else {
1080 rightGlyph -= glyphBase;
1081 leftGlyph -= glyphBase;
1082 }
1083
1084 // Set the position bias for the glyphs. If we're at the start of
1085 // a line, we want the first glyph to be at x = 0, even if it comes
1086 // from the middle of a layout. If we've got a right-to-left run, we
1087 // want the left-most glyph to start at the final x position of the
1088 // previous run, even though this glyph may be in the middle of the
1089 // run.
1090 fVisualRunLastX -= fStyleRunInfo[run].positions[leftGlyph * 2];
1091
1092 // Make rightGlyph be the glyph just to the right of
1093 // the run's glyphs
1094 rightGlyph += 1;
1095
1096 UBiDiDirection direction = ((fStyleRunInfo[run].level & 1) == 0)? UBIDI_LTR : UBIDI_RTL;
1097 le_int32 glyphCount = rightGlyph - leftGlyph;
1098 LEGlyphID *glyphs = LE_NEW_ARRAY(LEGlyphID, glyphCount);
1099 float *positions = LE_NEW_ARRAY(float, glyphCount * 2 + 2);
1100 le_int32 *glyphToCharMap = LE_NEW_ARRAY(le_int32, glyphCount);
1101
1102 LE_ARRAY_COPY(glyphs, &fStyleRunInfo[run].glyphs[leftGlyph], glyphCount);
1103
1104 for (outGlyph = 0, inGlyph = leftGlyph * 2; inGlyph <= rightGlyph * 2; inGlyph += 2, outGlyph += 2) {
1105 positions[outGlyph] = fStyleRunInfo[run].positions[inGlyph] + fVisualRunLastX;
1106 positions[outGlyph + 1] = fStyleRunInfo[run].positions[inGlyph + 1] + fVisualRunLastY;
1107 }
1108
1109 // Save the ending position of this run
1110 // to use for the start of the next run
1111 fVisualRunLastX = positions[outGlyph - 2];
1112 fVisualRunLastY = positions[outGlyph - 1];
1113
1114 if ((fStyleRunInfo[run].level & 1) == 0) {
1115 for (outGlyph = 0, inGlyph = leftGlyph; inGlyph < rightGlyph; inGlyph += 1, outGlyph += 1) {
1116 glyphToCharMap[outGlyph] = fGlyphToCharMap[glyphBase + inGlyph];
1117 }
1118 } else {
1119 // Because fGlyphToCharMap is stored in logical order to facilitate line breaking,
1120 // we need to map the physical glyph indices to logical indices while we copy the
1121 // character indices.
1122 le_int32 base = glyphBase + fStyleRunInfo[run].glyphCount - 1;
1123
1124 for (outGlyph = 0, inGlyph = leftGlyph; inGlyph < rightGlyph; inGlyph += 1, outGlyph += 1) {
1125 glyphToCharMap[outGlyph] = fGlyphToCharMap[base - inGlyph];
1126 }
1127 }
1128
1129 line->append(fStyleRunInfo[run].font, direction, glyphCount, glyphs, positions, glyphToCharMap);
1130}
1131
1132le_int32 ParagraphLayout::getCharRun(le_int32 charIndex)
1133{
1134 if (charIndex < 0 || charIndex > fCharCount) {
1135 return -1;
1136 }
1137
1138 le_int32 run;
1139
1140 // NOTE: as long as fStyleRunLimits is well-formed
1141 // the above range check guarantees that we'll never
1142 // fall off the end of the array.
1143 run = 0;
1144 while (charIndex >= fStyleRunLimits[run]) {
1145 run += 1;
1146 }
1147
1148 return run;
1149}
1150
1151
1152const char ParagraphLayout::Line::fgClassID = 0;
1153
1154#define INITIAL_RUN_CAPACITY 4
1155#define RUN_CAPACITY_GROW_LIMIT 16
1156
1157ParagraphLayout::Line::~Line()
1158{
1159 le_int32 i;
1160
1161 for (i = 0; i < fRunCount; i += 1) {
1162 delete fRuns[i];
1163 }
1164
1165 LE_DELETE_ARRAY(fRuns);
1166}
1167
1168le_int32 ParagraphLayout::Line::getAscent() const
1169{
1170 if (fAscent <= 0) {
1171 ((ParagraphLayout::Line *)this)->computeMetrics();
1172 }
1173
1174 return fAscent;
1175}
1176
1177le_int32 ParagraphLayout::Line::getDescent() const
1178{
1179 if (fAscent <= 0) {
1180 ((ParagraphLayout::Line *)this)->computeMetrics();
1181 }
1182
1183 return fDescent;
1184}
1185
1186le_int32 ParagraphLayout::Line::getLeading() const
1187{
1188 if (fAscent <= 0) {
1189 ((ParagraphLayout::Line *)this)->computeMetrics();
1190 }
1191
1192 return fLeading;
1193}
1194
1195le_int32 ParagraphLayout::Line::getWidth() const
1196{
1197 const VisualRun *lastRun = getVisualRun(fRunCount - 1);
1198
1199 if (lastRun == NULL) {
1200 return 0;
1201 }
1202
1203 le_int32 glyphCount = lastRun->getGlyphCount();
1204 const float *positions = lastRun->getPositions();
1205
1206 return (le_int32) positions[glyphCount * 2];
1207}
1208
1209const ParagraphLayout::VisualRun *ParagraphLayout::Line::getVisualRun(le_int32 runIndex) const
1210{
1211 if (runIndex < 0 || runIndex >= fRunCount) {
1212 return NULL;
1213 }
1214
1215 return fRuns[runIndex];
1216}
1217
1218void ParagraphLayout::Line::append(const LEFontInstance *font, UBiDiDirection direction, le_int32 glyphCount,
1219 const LEGlyphID glyphs[], const float positions[], const le_int32 glyphToCharMap[])
1220{
1221 if (fRunCount >= fRunCapacity) {
1222 if (fRunCapacity == 0) {
1223 fRunCapacity = INITIAL_RUN_CAPACITY;
1224 fRuns = LE_NEW_ARRAY(ParagraphLayout::VisualRun *, fRunCapacity);
1225 } else {
1226 fRunCapacity += (fRunCapacity < RUN_CAPACITY_GROW_LIMIT? fRunCapacity : RUN_CAPACITY_GROW_LIMIT);
1227 fRuns = (ParagraphLayout::VisualRun **) LE_GROW_ARRAY(fRuns, fRunCapacity);
1228 }
1229 }
1230
1231 fRuns[fRunCount++] = new ParagraphLayout::VisualRun(font, direction, glyphCount, glyphs, positions, glyphToCharMap);
1232}
1233
1234void ParagraphLayout::Line::computeMetrics()
1235{
1236 le_int32 maxDL = 0;
1237
1238 for (le_int32 i = 0; i < fRunCount; i += 1) {
1239 le_int32 ascent = fRuns[i]->getAscent();
1240 le_int32 descent = fRuns[i]->getDescent();
1241 le_int32 leading = fRuns[i]->getLeading();
1242 le_int32 dl = descent + leading;
1243
1244 if (ascent > fAscent) {
1245 fAscent = ascent;
1246 }
1247
1248 if (descent > fDescent) {
1249 fDescent = descent;
1250 }
1251
1252 if (leading > fLeading) {
1253 fLeading = leading;
1254 }
1255
1256 if (dl > maxDL) {
1257 maxDL = dl;
1258 }
1259 }
1260
1261 fLeading = maxDL - fDescent;
1262}
1263
1264const char ParagraphLayout::VisualRun::fgClassID = 0;
1265
1266ParagraphLayout::VisualRun::~VisualRun()
1267{
1268 LE_DELETE_ARRAY(fGlyphToCharMap);
1269 LE_DELETE_ARRAY(fPositions);
1270 LE_DELETE_ARRAY(fGlyphs);
1271}
1272
1273U_NAMESPACE_END
1274
1275#endif
1276