1 /*
2 * Copyright (C) 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25 #include "config.h"
26 #include "platform/fonts/mac/ComplexTextController.h"
27
28 #include "platform/fonts/Character.h"
29 #include "platform/fonts/Font.h"
30 #include "platform/fonts/GlyphBuffer.h"
31 #include "platform/geometry/FloatSize.h"
32 #include "platform/text/TextBreakIterator.h"
33 #include "platform/text/TextRun.h"
34 #include "wtf/StdLibExtras.h"
35 #include "wtf/unicode/CharacterNames.h"
36 #include <ApplicationServices/ApplicationServices.h>
37
38 using namespace std;
39
40 namespace WebCore {
41
roundCGFloat(CGFloat f)42 static inline CGFloat roundCGFloat(CGFloat f)
43 {
44 if (sizeof(CGFloat) == sizeof(float))
45 return roundf(static_cast<float>(f));
46 return static_cast<CGFloat>(round(f));
47 }
48
ceilCGFloat(CGFloat f)49 static inline CGFloat ceilCGFloat(CGFloat f)
50 {
51 if (sizeof(CGFloat) == sizeof(float))
52 return ceilf(static_cast<float>(f));
53 return static_cast<CGFloat>(ceil(f));
54 }
55
ComplexTextController(const Font * font,const TextRun & run,bool mayUseNaturalWritingDirection,HashSet<const SimpleFontData * > * fallbackFonts,bool forTextEmphasis)56 ComplexTextController::ComplexTextController(const Font* font, const TextRun& run, bool mayUseNaturalWritingDirection, HashSet<const SimpleFontData*>* fallbackFonts, bool forTextEmphasis)
57 : m_font(*font)
58 , m_run(run)
59 , m_isLTROnly(true)
60 , m_mayUseNaturalWritingDirection(mayUseNaturalWritingDirection)
61 , m_forTextEmphasis(forTextEmphasis)
62 , m_currentCharacter(0)
63 , m_end(run.length())
64 , m_totalWidth(0)
65 , m_runWidthSoFar(0)
66 , m_numGlyphsSoFar(0)
67 , m_currentRun(0)
68 , m_glyphInCurrentRun(0)
69 , m_characterInCurrentGlyph(0)
70 , m_finalRoundingWidth(0)
71 , m_expansion(run.expansion())
72 , m_leadingExpansion(0)
73 , m_afterExpansion(!run.allowsLeadingExpansion())
74 , m_fallbackFonts(fallbackFonts)
75 , m_minGlyphBoundingBoxX(numeric_limits<float>::max())
76 , m_maxGlyphBoundingBoxX(numeric_limits<float>::min())
77 , m_minGlyphBoundingBoxY(numeric_limits<float>::max())
78 , m_maxGlyphBoundingBoxY(numeric_limits<float>::min())
79 , m_lastRoundingGlyph(0)
80 {
81 if (!m_expansion)
82 m_expansionPerOpportunity = 0;
83 else {
84 bool isAfterExpansion = m_afterExpansion;
85 unsigned expansionOpportunityCount;
86 if (m_run.is8Bit())
87 expansionOpportunityCount = Character::expansionOpportunityCount(m_run.characters8(), m_end, m_run.direction(), isAfterExpansion);
88 else
89 expansionOpportunityCount = Character::expansionOpportunityCount(m_run.characters16(), m_end, m_run.direction(), isAfterExpansion);
90 if (isAfterExpansion && !m_run.allowsTrailingExpansion())
91 expansionOpportunityCount--;
92
93 if (!expansionOpportunityCount)
94 m_expansionPerOpportunity = 0;
95 else
96 m_expansionPerOpportunity = m_expansion / expansionOpportunityCount;
97 }
98
99 collectComplexTextRuns();
100 adjustGlyphsAndAdvances();
101
102 if (!m_isLTROnly) {
103 m_runIndices.reserveInitialCapacity(m_complexTextRuns.size());
104
105 m_glyphCountFromStartToIndex.reserveInitialCapacity(m_complexTextRuns.size());
106 unsigned glyphCountSoFar = 0;
107 for (unsigned i = 0; i < m_complexTextRuns.size(); ++i) {
108 m_glyphCountFromStartToIndex.uncheckedAppend(glyphCountSoFar);
109 glyphCountSoFar += m_complexTextRuns[i]->glyphCount();
110 }
111 }
112
113 m_runWidthSoFar = m_leadingExpansion;
114 }
115
offsetForPosition(float h,bool includePartialGlyphs)116 int ComplexTextController::offsetForPosition(float h, bool includePartialGlyphs)
117 {
118 if (h >= m_totalWidth)
119 return m_run.ltr() ? m_end : 0;
120
121 h -= m_leadingExpansion;
122 if (h < 0)
123 return m_run.ltr() ? 0 : m_end;
124
125 CGFloat x = h;
126
127 size_t runCount = m_complexTextRuns.size();
128 size_t offsetIntoAdjustedGlyphs = 0;
129
130 for (size_t r = 0; r < runCount; ++r) {
131 const ComplexTextRun& complexTextRun = *m_complexTextRuns[r];
132 for (unsigned j = 0; j < complexTextRun.glyphCount(); ++j) {
133 CGFloat adjustedAdvance = m_adjustedAdvances[offsetIntoAdjustedGlyphs + j].width;
134 if (x < adjustedAdvance) {
135 CFIndex hitGlyphStart = complexTextRun.indexAt(j);
136 CFIndex hitGlyphEnd;
137 if (m_run.ltr())
138 hitGlyphEnd = max<CFIndex>(hitGlyphStart, j + 1 < complexTextRun.glyphCount() ? complexTextRun.indexAt(j + 1) : static_cast<CFIndex>(complexTextRun.indexEnd()));
139 else
140 hitGlyphEnd = max<CFIndex>(hitGlyphStart, j > 0 ? complexTextRun.indexAt(j - 1) : static_cast<CFIndex>(complexTextRun.indexEnd()));
141
142 // FIXME: Instead of dividing the glyph's advance equally between the characters, this
143 // could use the glyph's "ligature carets". However, there is no Core Text API to get the
144 // ligature carets.
145 CFIndex hitIndex = hitGlyphStart + (hitGlyphEnd - hitGlyphStart) * (m_run.ltr() ? x / adjustedAdvance : 1 - x / adjustedAdvance);
146 int stringLength = complexTextRun.stringLength();
147 TextBreakIterator* cursorPositionIterator = cursorMovementIterator(complexTextRun.characters(), stringLength);
148 int clusterStart;
149 if (cursorPositionIterator->isBoundary(hitIndex))
150 clusterStart = hitIndex;
151 else {
152 clusterStart = cursorPositionIterator->preceding(hitIndex);
153 if (clusterStart == TextBreakDone)
154 clusterStart = 0;
155 }
156
157 if (!includePartialGlyphs)
158 return complexTextRun.stringLocation() + clusterStart;
159
160 int clusterEnd = cursorPositionIterator->following(hitIndex);
161 if (clusterEnd == TextBreakDone)
162 clusterEnd = stringLength;
163
164 CGFloat clusterWidth;
165 // FIXME: The search stops at the boundaries of complexTextRun. In theory, it should go on into neighboring ComplexTextRuns
166 // derived from the same CTLine. In practice, we do not expect there to be more than one CTRun in a CTLine, as no
167 // reordering and no font fallback should occur within a CTLine.
168 if (clusterEnd - clusterStart > 1) {
169 clusterWidth = adjustedAdvance;
170 int firstGlyphBeforeCluster = j - 1;
171 while (firstGlyphBeforeCluster >= 0 && complexTextRun.indexAt(firstGlyphBeforeCluster) >= clusterStart && complexTextRun.indexAt(firstGlyphBeforeCluster) < clusterEnd) {
172 CGFloat width = m_adjustedAdvances[offsetIntoAdjustedGlyphs + firstGlyphBeforeCluster].width;
173 clusterWidth += width;
174 x += width;
175 firstGlyphBeforeCluster--;
176 }
177 unsigned firstGlyphAfterCluster = j + 1;
178 while (firstGlyphAfterCluster < complexTextRun.glyphCount() && complexTextRun.indexAt(firstGlyphAfterCluster) >= clusterStart && complexTextRun.indexAt(firstGlyphAfterCluster) < clusterEnd) {
179 clusterWidth += m_adjustedAdvances[offsetIntoAdjustedGlyphs + firstGlyphAfterCluster].width;
180 firstGlyphAfterCluster++;
181 }
182 } else {
183 clusterWidth = adjustedAdvance / (hitGlyphEnd - hitGlyphStart);
184 x -= clusterWidth * (m_run.ltr() ? hitIndex - hitGlyphStart : hitGlyphEnd - hitIndex - 1);
185 }
186 if (x <= clusterWidth / 2)
187 return complexTextRun.stringLocation() + (m_run.ltr() ? clusterStart : clusterEnd);
188 else
189 return complexTextRun.stringLocation() + (m_run.ltr() ? clusterEnd : clusterStart);
190 }
191 x -= adjustedAdvance;
192 }
193 offsetIntoAdjustedGlyphs += complexTextRun.glyphCount();
194 }
195
196 ASSERT_NOT_REACHED();
197 return 0;
198 }
199
advanceByCombiningCharacterSequence(const UChar * & iterator,const UChar * end,UChar32 & baseCharacter,unsigned & markCount)200 static bool advanceByCombiningCharacterSequence(const UChar*& iterator, const UChar* end, UChar32& baseCharacter, unsigned& markCount)
201 {
202 ASSERT(iterator < end);
203
204 markCount = 0;
205
206 baseCharacter = *iterator++;
207
208 if (U16_IS_SURROGATE(baseCharacter)) {
209 if (!U16_IS_LEAD(baseCharacter))
210 return false;
211 if (iterator == end)
212 return false;
213 UChar trail = *iterator++;
214 if (!U16_IS_TRAIL(trail))
215 return false;
216 baseCharacter = U16_GET_SUPPLEMENTARY(baseCharacter, trail);
217 }
218
219 // Consume marks.
220 while (iterator < end) {
221 UChar32 nextCharacter;
222 int markLength = 0;
223 U16_NEXT(iterator, markLength, end - iterator, nextCharacter);
224 if (!(U_GET_GC_MASK(nextCharacter) & U_GC_M_MASK))
225 break;
226 markCount += markLength;
227 iterator += markLength;
228 }
229
230 return true;
231 }
232
collectComplexTextRuns()233 void ComplexTextController::collectComplexTextRuns()
234 {
235 if (!m_end)
236 return;
237
238 // We break up glyph run generation for the string by FontData.
239 const UChar* cp;
240
241 if (m_run.is8Bit()) {
242 String stringFor8BitRun = String::make16BitFrom8BitSource(m_run.characters8(), m_run.length());
243 cp = stringFor8BitRun.characters16();
244 m_stringsFor8BitRuns.append(stringFor8BitRun);
245 } else
246 cp = m_run.characters16();
247
248 if (m_font.fontDescription().variant() == FontVariantSmallCaps)
249 m_smallCapsBuffer.resize(m_end);
250
251 unsigned indexOfFontTransition = 0;
252 const UChar* curr = cp;
253 const UChar* end = cp + m_end;
254
255 const SimpleFontData* fontData;
256 bool isMissingGlyph;
257 const SimpleFontData* nextFontData;
258 bool nextIsMissingGlyph;
259
260 unsigned markCount;
261 const UChar* sequenceStart = curr;
262 UChar32 baseCharacter;
263 if (!advanceByCombiningCharacterSequence(curr, end, baseCharacter, markCount))
264 return;
265
266 UChar uppercaseCharacter = 0;
267
268 bool isSmallCaps;
269 bool nextIsSmallCaps = m_font.fontDescription().variant() == FontVariantSmallCaps && !(U_GET_GC_MASK(baseCharacter) & U_GC_M_MASK) && (uppercaseCharacter = u_toupper(baseCharacter)) != baseCharacter;
270
271 if (nextIsSmallCaps) {
272 m_smallCapsBuffer[sequenceStart - cp] = uppercaseCharacter;
273 for (unsigned i = 0; i < markCount; ++i)
274 m_smallCapsBuffer[sequenceStart - cp + i + 1] = sequenceStart[i + 1];
275 }
276
277 nextIsMissingGlyph = false;
278 nextFontData = m_font.fontDataForCombiningCharacterSequence(sequenceStart, curr - sequenceStart, nextIsSmallCaps ? SmallCapsVariant : NormalVariant);
279 if (!nextFontData)
280 nextIsMissingGlyph = true;
281
282 while (curr < end) {
283 fontData = nextFontData;
284 isMissingGlyph = nextIsMissingGlyph;
285 isSmallCaps = nextIsSmallCaps;
286 int index = curr - cp;
287
288 if (!advanceByCombiningCharacterSequence(curr, end, baseCharacter, markCount))
289 return;
290
291 if (m_font.fontDescription().variant()) {
292 nextIsSmallCaps = (uppercaseCharacter = u_toupper(baseCharacter)) != baseCharacter;
293 if (nextIsSmallCaps) {
294 m_smallCapsBuffer[index] = uppercaseCharacter;
295 for (unsigned i = 0; i < markCount; ++i)
296 m_smallCapsBuffer[index + i + 1] = cp[index + i + 1];
297 }
298 }
299
300 nextIsMissingGlyph = false;
301 if (baseCharacter == zeroWidthJoiner)
302 nextFontData = fontData;
303 else {
304 nextFontData = m_font.fontDataForCombiningCharacterSequence(cp + index, curr - cp - index, nextIsSmallCaps ? SmallCapsVariant : NormalVariant);
305 if (!nextFontData)
306 nextIsMissingGlyph = true;
307 }
308
309 if (nextFontData != fontData || nextIsMissingGlyph != isMissingGlyph) {
310 int itemStart = static_cast<int>(indexOfFontTransition);
311 int itemLength = index - indexOfFontTransition;
312 collectComplexTextRunsForCharacters((isSmallCaps ? m_smallCapsBuffer.data() : cp) + itemStart, itemLength, itemStart, !isMissingGlyph ? fontData : 0);
313 indexOfFontTransition = index;
314 }
315 }
316
317 int itemLength = m_end - indexOfFontTransition;
318 if (itemLength) {
319 int itemStart = indexOfFontTransition;
320 collectComplexTextRunsForCharacters((nextIsSmallCaps ? m_smallCapsBuffer.data() : cp) + itemStart, itemLength, itemStart, !nextIsMissingGlyph ? nextFontData : 0);
321 }
322
323 if (!m_run.ltr())
324 m_complexTextRuns.reverse();
325 }
326
indexAt(size_t i) const327 CFIndex ComplexTextController::ComplexTextRun::indexAt(size_t i) const
328 {
329 return m_coreTextIndices[i];
330 }
331
setIsNonMonotonic()332 void ComplexTextController::ComplexTextRun::setIsNonMonotonic()
333 {
334 ASSERT(m_isMonotonic);
335 m_isMonotonic = false;
336
337 Vector<bool, 64> mappedIndices(m_stringLength);
338 for (size_t i = 0; i < m_glyphCount; ++i) {
339 ASSERT(indexAt(i) < static_cast<CFIndex>(m_stringLength));
340 mappedIndices[indexAt(i)] = true;
341 }
342
343 m_glyphEndOffsets.grow(m_glyphCount);
344 for (size_t i = 0; i < m_glyphCount; ++i) {
345 CFIndex nextMappedIndex = m_indexEnd;
346 for (size_t j = indexAt(i) + 1; j < m_stringLength; ++j) {
347 if (mappedIndices[j]) {
348 nextMappedIndex = j;
349 break;
350 }
351 }
352 m_glyphEndOffsets[i] = nextMappedIndex;
353 }
354 }
355
findNextRunIndex(unsigned runIndex) const356 unsigned ComplexTextController::findNextRunIndex(unsigned runIndex) const
357 {
358 const unsigned runOffset = stringEnd(*m_complexTextRuns[runIndex]);
359
360 // Finds the run with the lowest stringBegin() offset that starts at or
361 // after |runOffset|.
362 //
363 // Note that this can't just find a run whose stringBegin() equals the
364 // stringEnd() of the previous run because CoreText on Mac OS X 10.6 does
365 // not return runs covering BiDi control chars, so this has to handle the
366 // resulting gaps.
367 unsigned result = 0;
368 unsigned lowestOffset = UINT_MAX;
369 for (unsigned i = 0; i < m_complexTextRuns.size(); ++i) {
370 unsigned offset = stringBegin(*m_complexTextRuns[i]);
371 if (i != runIndex && offset >= runOffset && offset < lowestOffset) {
372 lowestOffset = offset;
373 result = i;
374 }
375 }
376
377 ASSERT(lowestOffset != UINT_MAX);
378 return result;
379 }
380
indexOfCurrentRun(unsigned & leftmostGlyph)381 unsigned ComplexTextController::indexOfCurrentRun(unsigned& leftmostGlyph)
382 {
383 leftmostGlyph = 0;
384
385 size_t runCount = m_complexTextRuns.size();
386 if (m_currentRun >= runCount)
387 return runCount;
388
389 if (m_isLTROnly) {
390 for (unsigned i = 0; i < m_currentRun; ++i)
391 leftmostGlyph += m_complexTextRuns[i]->glyphCount();
392 return m_currentRun;
393 }
394
395 if (m_runIndices.isEmpty()) {
396 unsigned firstRun = 0;
397 unsigned firstRunOffset = stringBegin(*m_complexTextRuns[0]);
398 for (unsigned i = 1; i < runCount; ++i) {
399 unsigned offset = stringBegin(*m_complexTextRuns[i]);
400 if (offset < firstRunOffset) {
401 firstRun = i;
402 firstRunOffset = offset;
403 }
404 }
405 m_runIndices.uncheckedAppend(firstRun);
406 }
407
408 while (m_runIndices.size() <= m_currentRun) {
409 m_runIndices.uncheckedAppend(findNextRunIndex(m_runIndices.last()));
410 }
411
412 unsigned currentRunIndex = m_runIndices[m_currentRun];
413 leftmostGlyph = m_glyphCountFromStartToIndex[currentRunIndex];
414 return currentRunIndex;
415 }
416
incrementCurrentRun(unsigned & leftmostGlyph)417 unsigned ComplexTextController::incrementCurrentRun(unsigned& leftmostGlyph)
418 {
419 if (m_isLTROnly) {
420 leftmostGlyph += m_complexTextRuns[m_currentRun++]->glyphCount();
421 return m_currentRun;
422 }
423
424 m_currentRun++;
425 leftmostGlyph = 0;
426 return indexOfCurrentRun(leftmostGlyph);
427 }
428
advance(unsigned offset,GlyphBuffer * glyphBuffer,GlyphIterationStyle iterationStyle,HashSet<const SimpleFontData * > * fallbackFonts)429 void ComplexTextController::advance(unsigned offset, GlyphBuffer* glyphBuffer, GlyphIterationStyle iterationStyle, HashSet<const SimpleFontData*>* fallbackFonts)
430 {
431 if (static_cast<int>(offset) > m_end)
432 offset = m_end;
433
434 if (offset <= m_currentCharacter) {
435 m_runWidthSoFar = m_leadingExpansion;
436 m_numGlyphsSoFar = 0;
437 m_currentRun = 0;
438 m_glyphInCurrentRun = 0;
439 m_characterInCurrentGlyph = 0;
440 }
441
442 m_currentCharacter = offset;
443
444 size_t runCount = m_complexTextRuns.size();
445
446 unsigned leftmostGlyph = 0;
447 unsigned currentRunIndex = indexOfCurrentRun(leftmostGlyph);
448 while (m_currentRun < runCount) {
449 const ComplexTextRun& complexTextRun = *m_complexTextRuns[currentRunIndex];
450 bool ltr = complexTextRun.isLTR();
451 size_t glyphCount = complexTextRun.glyphCount();
452 unsigned g = ltr ? m_glyphInCurrentRun : glyphCount - 1 - m_glyphInCurrentRun;
453 unsigned k = leftmostGlyph + g;
454 if (fallbackFonts && complexTextRun.fontData() != m_font.primaryFont())
455 fallbackFonts->add(complexTextRun.fontData());
456
457 while (m_glyphInCurrentRun < glyphCount) {
458 unsigned glyphStartOffset = complexTextRun.indexAt(g);
459 unsigned glyphEndOffset;
460 if (complexTextRun.isMonotonic()) {
461 if (ltr)
462 glyphEndOffset = max<unsigned>(glyphStartOffset, static_cast<unsigned>(g + 1 < glyphCount ? complexTextRun.indexAt(g + 1) : complexTextRun.indexEnd()));
463 else
464 glyphEndOffset = max<unsigned>(glyphStartOffset, static_cast<unsigned>(g > 0 ? complexTextRun.indexAt(g - 1) : complexTextRun.indexEnd()));
465 } else
466 glyphEndOffset = complexTextRun.endOffsetAt(g);
467
468 CGSize adjustedAdvance = m_adjustedAdvances[k];
469
470 if (glyphStartOffset + complexTextRun.stringLocation() >= m_currentCharacter)
471 return;
472
473 if (glyphBuffer && !m_characterInCurrentGlyph)
474 glyphBuffer->add(m_adjustedGlyphs[k], complexTextRun.fontData(), FloatSize(adjustedAdvance));
475
476 unsigned oldCharacterInCurrentGlyph = m_characterInCurrentGlyph;
477 m_characterInCurrentGlyph = min(m_currentCharacter - complexTextRun.stringLocation(), glyphEndOffset) - glyphStartOffset;
478 // FIXME: Instead of dividing the glyph's advance equally between the characters, this
479 // could use the glyph's "ligature carets". However, there is no Core Text API to get the
480 // ligature carets.
481 if (glyphStartOffset == glyphEndOffset) {
482 // When there are multiple glyphs per character we need to advance by the full width of the glyph.
483 ASSERT(m_characterInCurrentGlyph == oldCharacterInCurrentGlyph);
484 m_runWidthSoFar += adjustedAdvance.width;
485 } else if (iterationStyle == ByWholeGlyphs) {
486 if (!oldCharacterInCurrentGlyph)
487 m_runWidthSoFar += adjustedAdvance.width;
488 } else
489 m_runWidthSoFar += adjustedAdvance.width * (m_characterInCurrentGlyph - oldCharacterInCurrentGlyph) / (glyphEndOffset - glyphStartOffset);
490
491 if (glyphEndOffset + complexTextRun.stringLocation() > m_currentCharacter)
492 return;
493
494 m_numGlyphsSoFar++;
495 m_glyphInCurrentRun++;
496 m_characterInCurrentGlyph = 0;
497 if (ltr) {
498 g++;
499 k++;
500 } else {
501 g--;
502 k--;
503 }
504 }
505 currentRunIndex = incrementCurrentRun(leftmostGlyph);
506 m_glyphInCurrentRun = 0;
507 }
508 if (!m_run.ltr() && m_numGlyphsSoFar == m_adjustedAdvances.size())
509 m_runWidthSoFar += m_finalRoundingWidth;
510 }
511
adjustGlyphsAndAdvances()512 void ComplexTextController::adjustGlyphsAndAdvances()
513 {
514 CGFloat widthSinceLastCommit = 0;
515 size_t runCount = m_complexTextRuns.size();
516 bool hasExtraSpacing = (m_font.fontDescription().letterSpacing() || m_font.fontDescription().wordSpacing() || m_expansion) && !m_run.spacingDisabled();
517 for (size_t r = 0; r < runCount; ++r) {
518 ComplexTextRun& complexTextRun = *m_complexTextRuns[r];
519 unsigned glyphCount = complexTextRun.glyphCount();
520 const SimpleFontData* fontData = complexTextRun.fontData();
521
522 if (!complexTextRun.isLTR())
523 m_isLTROnly = false;
524
525 const CGGlyph* glyphs = complexTextRun.glyphs();
526 const CGSize* advances = complexTextRun.advances();
527
528 bool lastRun = r + 1 == runCount;
529 bool roundsAdvances = fontData->platformData().roundsGlyphAdvances();
530 float spaceWidth = fontData->spaceWidth() - fontData->syntheticBoldOffset();
531 CGFloat roundedSpaceWidth = roundCGFloat(spaceWidth);
532 const UChar* cp = complexTextRun.characters();
533 CGPoint glyphOrigin = CGPointZero;
534 CFIndex lastCharacterIndex = m_run.ltr() ? numeric_limits<CFIndex>::min() : numeric_limits<CFIndex>::max();
535 bool isMonotonic = true;
536
537 for (unsigned i = 0; i < glyphCount; i++) {
538 CFIndex characterIndex = complexTextRun.indexAt(i);
539 if (m_run.ltr()) {
540 if (characterIndex < lastCharacterIndex)
541 isMonotonic = false;
542 } else {
543 if (characterIndex > lastCharacterIndex)
544 isMonotonic = false;
545 }
546 UChar ch = *(cp + characterIndex);
547 bool lastGlyph = lastRun && i + 1 == glyphCount;
548 UChar nextCh;
549 if (lastGlyph)
550 nextCh = ' ';
551 else if (i + 1 < glyphCount)
552 nextCh = *(cp + complexTextRun.indexAt(i + 1));
553 else
554 nextCh = *(m_complexTextRuns[r + 1]->characters() + m_complexTextRuns[r + 1]->indexAt(0));
555
556 bool treatAsSpace = Character::treatAsSpace(ch);
557 CGGlyph glyph = treatAsSpace ? fontData->spaceGlyph() : glyphs[i];
558 CGSize advance = treatAsSpace ? CGSizeMake(spaceWidth, advances[i].height) : advances[i];
559
560 if (ch == '\t' && m_run.allowTabs()) {
561 advance.width = m_font.tabWidth(*fontData, m_run.tabSize(), m_run.xPos() + m_totalWidth + widthSinceLastCommit);
562 } else if (Character::treatAsZeroWidthSpace(ch) && !treatAsSpace) {
563 advance.width = 0;
564 glyph = fontData->spaceGlyph();
565 }
566
567 float roundedAdvanceWidth = roundf(advance.width);
568 if (roundsAdvances)
569 advance.width = roundedAdvanceWidth;
570
571 advance.width += fontData->syntheticBoldOffset();
572
573
574 // We special case spaces in two ways when applying word rounding.
575 // First, we round spaces to an adjusted width in all fonts.
576 // Second, in fixed-pitch fonts we ensure that all glyphs that
577 // match the width of the space glyph have the same width as the space glyph.
578 if (m_run.applyWordRounding() && roundedAdvanceWidth == roundedSpaceWidth && (fontData->pitch() == FixedPitch || glyph == fontData->spaceGlyph()))
579 advance.width = fontData->adjustedSpaceWidth();
580
581 if (hasExtraSpacing) {
582 // If we're a glyph with an advance, go ahead and add in letter-spacing.
583 // That way we weed out zero width lurkers. This behavior matches the fast text code path.
584 if (advance.width && m_font.fontDescription().letterSpacing())
585 advance.width += m_font.fontDescription().letterSpacing();
586
587 // Handle justification and word-spacing.
588 if (treatAsSpace || Character::isCJKIdeographOrSymbol(ch)) {
589 // Distribute the run's total expansion evenly over all expansion opportunities in the run.
590 if (m_expansion) {
591 float previousExpansion = m_expansion;
592 if (!treatAsSpace && !m_afterExpansion) {
593 // Take the expansion opportunity before this ideograph.
594 m_expansion -= m_expansionPerOpportunity;
595 float expansionAtThisOpportunity = !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
596 m_totalWidth += expansionAtThisOpportunity;
597 if (m_adjustedAdvances.isEmpty())
598 m_leadingExpansion = expansionAtThisOpportunity;
599 else
600 m_adjustedAdvances.last().width += expansionAtThisOpportunity;
601 previousExpansion = m_expansion;
602 }
603 if (!lastGlyph || m_run.allowsTrailingExpansion()) {
604 m_expansion -= m_expansionPerOpportunity;
605 advance.width += !m_run.applyWordRounding() ? m_expansionPerOpportunity : roundf(previousExpansion) - roundf(m_expansion);
606 m_afterExpansion = true;
607 }
608 } else
609 m_afterExpansion = false;
610
611 // Account for word-spacing.
612 if (treatAsSpace && (ch != '\t' || !m_run.allowTabs()) && (characterIndex > 0 || r > 0) && m_font.fontDescription().wordSpacing())
613 advance.width += m_font.fontDescription().wordSpacing();
614 } else
615 m_afterExpansion = false;
616 }
617
618 // Apply rounding hacks if needed.
619 // We adjust the width of the last character of a "word" to ensure an integer width.
620 // Force characters that are used to determine word boundaries for the rounding hack
621 // to be integer width, so the following words will start on an integer boundary.
622 if (m_run.applyWordRounding() && Character::isRoundingHackCharacter(ch))
623 advance.width = ceilCGFloat(advance.width);
624
625 // Check to see if the next character is a "rounding hack character", if so, adjust the
626 // width so that the total run width will be on an integer boundary.
627 if ((m_run.applyWordRounding() && !lastGlyph && Character::isRoundingHackCharacter(nextCh)) || (m_run.applyRunRounding() && lastGlyph)) {
628 CGFloat totalWidth = widthSinceLastCommit + advance.width;
629 widthSinceLastCommit = ceilCGFloat(totalWidth);
630 CGFloat extraWidth = widthSinceLastCommit - totalWidth;
631 if (m_run.ltr())
632 advance.width += extraWidth;
633 else {
634 if (m_lastRoundingGlyph)
635 m_adjustedAdvances[m_lastRoundingGlyph - 1].width += extraWidth;
636 else
637 m_finalRoundingWidth = extraWidth;
638 m_lastRoundingGlyph = m_adjustedAdvances.size() + 1;
639 }
640 m_totalWidth += widthSinceLastCommit;
641 widthSinceLastCommit = 0;
642 } else
643 widthSinceLastCommit += advance.width;
644
645 // FIXME: Combining marks should receive a text emphasis mark if they are combine with a space.
646 if (m_forTextEmphasis && (!Character::canReceiveTextEmphasis(ch) || (U_GET_GC_MASK(ch) & U_GC_M_MASK)))
647 glyph = 0;
648
649 advance.height *= -1;
650 m_adjustedAdvances.append(advance);
651 m_adjustedGlyphs.append(glyph);
652
653 FloatRect glyphBounds = fontData->boundsForGlyph(glyph);
654 glyphBounds.move(glyphOrigin.x, glyphOrigin.y);
655 m_minGlyphBoundingBoxX = min(m_minGlyphBoundingBoxX, glyphBounds.x());
656 m_maxGlyphBoundingBoxX = max(m_maxGlyphBoundingBoxX, glyphBounds.maxX());
657 m_minGlyphBoundingBoxY = min(m_minGlyphBoundingBoxY, glyphBounds.y());
658 m_maxGlyphBoundingBoxY = max(m_maxGlyphBoundingBoxY, glyphBounds.maxY());
659 glyphOrigin.x += advance.width;
660 glyphOrigin.y += advance.height;
661
662 lastCharacterIndex = characterIndex;
663 }
664 if (!isMonotonic)
665 complexTextRun.setIsNonMonotonic();
666 }
667 m_totalWidth += widthSinceLastCommit;
668 }
669
670 } // namespace WebCore
671