1 /* 2 * Copyright (c) 2007, 2008, Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "Font.h" 33 34 #include "FloatRect.h" 35 #include "GlyphBuffer.h" 36 #include "GraphicsContext.h" 37 #include "HarfbuzzSkia.h" 38 #include "NotImplemented.h" 39 #include "PlatformContextSkia.h" 40 #include "SimpleFontData.h" 41 42 #include "SkCanvas.h" 43 #include "SkPaint.h" 44 #include "SkTemplates.h" 45 #include "SkTypeface.h" 46 #include "SkUtils.h" 47 48 #include <unicode/normlzr.h> 49 #include <unicode/uchar.h> 50 #include <wtf/OwnArrayPtr.h> 51 #include <wtf/OwnPtr.h> 52 53 namespace WebCore { 54 55 bool Font::canReturnFallbackFontsForComplexText() 56 { 57 return false; 58 } 59 60 static bool isCanvasMultiLayered(SkCanvas* canvas) 61 { 62 SkCanvas::LayerIter layerIterator(canvas, false); 63 layerIterator.next(); 64 return !layerIterator.done(); 65 } 66 67 static bool adjustTextRenderMode(SkPaint* paint, bool isCanvasMultiLayered) 68 { 69 // Our layers only have a single alpha channel. This means that subpixel 70 // rendered text cannot be compositied correctly when the layer is 71 // collapsed. Therefore, subpixel text is disabled when we are drawing 72 // onto a layer. 73 if (isCanvasMultiLayered) 74 paint->setLCDRenderText(false); 75 } 76 77 void Font::drawGlyphs(GraphicsContext* gc, const SimpleFontData* font, 78 const GlyphBuffer& glyphBuffer, int from, int numGlyphs, 79 const FloatPoint& point) const { 80 SkASSERT(sizeof(GlyphBufferGlyph) == sizeof(uint16_t)); // compile-time assert 81 82 const GlyphBufferGlyph* glyphs = glyphBuffer.glyphs(from); 83 SkScalar x = SkFloatToScalar(point.x()); 84 SkScalar y = SkFloatToScalar(point.y()); 85 86 // FIXME: text rendering speed: 87 // Android has code in their WebCore fork to special case when the 88 // GlyphBuffer has no advances other than the defaults. In that case the 89 // text drawing can proceed faster. However, it's unclear when those 90 // patches may be upstreamed to WebKit so we always use the slower path 91 // here. 92 const GlyphBufferAdvance* adv = glyphBuffer.advances(from); 93 SkAutoSTMalloc<32, SkPoint> storage(numGlyphs); 94 SkPoint* pos = storage.get(); 95 96 for (int i = 0; i < numGlyphs; i++) { 97 pos[i].set(x, y); 98 x += SkFloatToScalar(adv[i].width()); 99 y += SkFloatToScalar(adv[i].height()); 100 } 101 102 SkCanvas* canvas = gc->platformContext()->canvas(); 103 int textMode = gc->platformContext()->getTextDrawingMode(); 104 bool haveMultipleLayers = isCanvasMultiLayered(canvas); 105 106 // We draw text up to two times (once for fill, once for stroke). 107 if (textMode & cTextFill) { 108 SkPaint paint; 109 gc->platformContext()->setupPaintForFilling(&paint); 110 font->platformData().setupPaint(&paint); 111 adjustTextRenderMode(&paint, haveMultipleLayers); 112 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 113 paint.setColor(gc->fillColor().rgb()); 114 canvas->drawPosText(glyphs, numGlyphs << 1, pos, paint); 115 } 116 117 if ((textMode & cTextStroke) 118 && gc->platformContext()->getStrokeStyle() != NoStroke 119 && gc->platformContext()->getStrokeThickness() > 0) { 120 121 SkPaint paint; 122 gc->platformContext()->setupPaintForStroking(&paint, 0, 0); 123 font->platformData().setupPaint(&paint); 124 adjustTextRenderMode(&paint, haveMultipleLayers); 125 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 126 paint.setColor(gc->strokeColor().rgb()); 127 128 if (textMode & cTextFill) { 129 // If we also filled, we don't want to draw shadows twice. 130 // See comment in FontChromiumWin.cpp::paintSkiaText() for more details. 131 paint.setLooper(0)->safeUnref(); 132 } 133 134 canvas->drawPosText(glyphs, numGlyphs << 1, pos, paint); 135 } 136 } 137 138 // Harfbuzz uses 26.6 fixed point values for pixel offsets. However, we don't 139 // handle subpixel positioning so this function is used to truncate Harfbuzz 140 // values to a number of pixels. 141 static int truncateFixedPointToInteger(HB_Fixed value) 142 { 143 return value >> 6; 144 } 145 146 // TextRunWalker walks a TextRun and presents each script run in sequence. A 147 // TextRun is a sequence of code-points with the same embedding level (i.e. they 148 // are all left-to-right or right-to-left). A script run is a subsequence where 149 // all the characters have the same script (e.g. Arabic, Thai etc). Shaping is 150 // only ever done with script runs since the shapers only know how to deal with 151 // a single script. 152 // 153 // After creating it, the script runs are either iterated backwards or forwards. 154 // It defaults to backwards for RTL and forwards otherwise (which matches the 155 // presentation order), however you can set it with |setBackwardsIteration|. 156 // 157 // Once you have setup the object, call |nextScriptRun| to get the first script 158 // run. This will return false when the iteration is complete. At any time you 159 // can call |reset| to start over again. 160 class TextRunWalker { 161 public: 162 TextRunWalker(const TextRun& run, unsigned startingX, const Font* font) 163 : m_font(font) 164 , m_startingX(startingX) 165 , m_offsetX(m_startingX) 166 , m_run(getTextRun(run)) 167 , m_iterateBackwards(m_run.rtl()) 168 { 169 // Do not use |run| inside this constructor. Use |m_run| instead. 170 171 memset(&m_item, 0, sizeof(m_item)); 172 // We cannot know, ahead of time, how many glyphs a given script run 173 // will produce. We take a guess that script runs will not produce more 174 // than twice as many glyphs as there are code points and fallback if 175 // we find that we are wrong. 176 m_maxGlyphs = m_run.length() * 2; 177 createGlyphArrays(); 178 179 m_item.log_clusters = new unsigned short[m_run.length()]; 180 181 m_item.face = 0; 182 m_item.font = allocHarfbuzzFont(); 183 184 m_item.string = m_run.characters(); 185 m_item.stringLength = m_run.length(); 186 m_item.item.bidiLevel = m_run.rtl(); 187 188 reset(); 189 } 190 191 ~TextRunWalker() 192 { 193 fastFree(m_item.font); 194 deleteGlyphArrays(); 195 delete[] m_item.log_clusters; 196 } 197 198 void reset() 199 { 200 if (m_iterateBackwards) 201 m_indexOfNextScriptRun = m_run.length() - 1; 202 else 203 m_indexOfNextScriptRun = 0; 204 m_offsetX = m_startingX; 205 } 206 207 // Set the x offset for the next script run. This affects the values in 208 // |xPositions| 209 void setXOffsetToZero() 210 { 211 m_offsetX = 0; 212 } 213 214 bool rtl() const 215 { 216 return m_run.rtl(); 217 } 218 219 void setBackwardsIteration(bool isBackwards) 220 { 221 m_iterateBackwards = isBackwards; 222 reset(); 223 } 224 225 // Advance to the next script run, returning false when the end of the 226 // TextRun has been reached. 227 bool nextScriptRun() 228 { 229 if (m_iterateBackwards) { 230 // In right-to-left mode we need to render the shaped glyph backwards and 231 // also render the script runs themselves backwards. So given a TextRun: 232 // AAAAAAACTTTTTTT (A = Arabic, C = Common, T = Thai) 233 // we render: 234 // TTTTTTCAAAAAAA 235 // (and the glyphs in each A, C and T section are backwards too) 236 if (!hb_utf16_script_run_prev(&m_numCodePoints, &m_item.item, m_run.characters(), m_run.length(), &m_indexOfNextScriptRun)) 237 return false; 238 } else { 239 if (!hb_utf16_script_run_next(&m_numCodePoints, &m_item.item, m_run.characters(), m_run.length(), &m_indexOfNextScriptRun)) 240 return false; 241 } 242 243 setupFontForScriptRun(); 244 245 if (!shapeGlyphs()) 246 return false; 247 setGlyphXPositions(rtl()); 248 return true; 249 } 250 251 const uint16_t* glyphs() const 252 { 253 return m_glyphs16; 254 } 255 256 // Return the length of the array returned by |glyphs| 257 const unsigned length() const 258 { 259 return m_item.num_glyphs; 260 } 261 262 // Return the x offset for each of the glyphs. Note that this is translated 263 // by the current x offset and that the x offset is updated for each script 264 // run. 265 const SkScalar* xPositions() const 266 { 267 return m_xPositions; 268 } 269 270 // Get the advances (widths) for each glyph. 271 const HB_Fixed* advances() const 272 { 273 return m_item.advances; 274 } 275 276 // Return the width (in px) of the current script run. 277 const unsigned width() const 278 { 279 return m_pixelWidth; 280 } 281 282 // Return the cluster log for the current script run. For example: 283 // script run: f i a n c (fi gets ligatured) 284 // log clutrs: 0 0 1 2 3 4 285 // So, for each input code point, the log tells you which output glyph was 286 // generated for it. 287 const unsigned short* logClusters() const 288 { 289 return m_item.log_clusters; 290 } 291 292 // return the number of code points in the current script run 293 const unsigned numCodePoints() const 294 { 295 return m_numCodePoints; 296 } 297 298 const FontPlatformData* fontPlatformDataForScriptRun() 299 { 300 return reinterpret_cast<FontPlatformData*>(m_item.font->userData); 301 } 302 303 float widthOfFullRun() 304 { 305 float widthSum = 0; 306 while (nextScriptRun()) 307 widthSum += width(); 308 309 return widthSum; 310 } 311 312 private: 313 const TextRun& getTextRun(const TextRun& originalRun) 314 { 315 // Normalize the text run in two ways: 316 // 1) Convert the |originalRun| to NFC normalized form if combining diacritical marks 317 // (U+0300..) are used in the run. This conversion is necessary since most OpenType 318 // fonts (e.g., Arial) don't have substitution rules for the diacritical marks in 319 // their GSUB tables. 320 // 321 // Note that we don't use the icu::Normalizer::isNormalized(UNORM_NFC) API here since 322 // the API returns FALSE (= not normalized) for complex runs that don't require NFC 323 // normalization (e.g., Arabic text). Unless the run contains the diacritical marks, 324 // Harfbuzz will do the same thing for us using the GSUB table. 325 // 2) Convert spacing characters into plain spaces, as some fonts will provide glyphs 326 // for characters like '\n' otherwise. 327 for (unsigned i = 0; i < originalRun.length(); ++i) { 328 UChar ch = originalRun[i]; 329 UBlockCode block = ::ublock_getCode(ch); 330 if (block == UBLOCK_COMBINING_DIACRITICAL_MARKS || (Font::treatAsSpace(ch) && ch != ' ')) { 331 return getNormalizedTextRun(originalRun); 332 } 333 } 334 return originalRun; 335 } 336 337 const TextRun& getNormalizedTextRun(const TextRun& originalRun) 338 { 339 icu::UnicodeString normalizedString; 340 UErrorCode error = U_ZERO_ERROR; 341 icu::Normalizer::normalize(icu::UnicodeString(originalRun.characters(), originalRun.length()), UNORM_NFC, 0 /* no options */, normalizedString, error); 342 if (U_FAILURE(error)) 343 return originalRun; 344 345 m_normalizedBuffer.set(new UChar[normalizedString.length() + 1]); 346 normalizedString.extract(m_normalizedBuffer.get(), normalizedString.length() + 1, error); 347 ASSERT(U_SUCCESS(error)); 348 349 for (unsigned i = 0; i < normalizedString.length(); ++i) { 350 if (Font::treatAsSpace(m_normalizedBuffer[i])) 351 m_normalizedBuffer[i] = ' '; 352 } 353 354 m_normalizedRun.set(new TextRun(originalRun)); 355 m_normalizedRun->setText(m_normalizedBuffer.get(), normalizedString.length()); 356 return *m_normalizedRun; 357 } 358 359 void setupFontForScriptRun() 360 { 361 const FontData* fontData = m_font->fontDataAt(0); 362 if (!fontData->containsCharacters(m_item.string + m_item.item.pos, m_item.item.length)) 363 fontData = m_font->fontDataForCharacters(m_item.string + m_item.item.pos, m_item.item.length); 364 const FontPlatformData& platformData = fontData->fontDataForCharacter(' ')->platformData(); 365 m_item.face = platformData.harfbuzzFace(); 366 void* opaquePlatformData = const_cast<FontPlatformData*>(&platformData); 367 m_item.font->userData = opaquePlatformData; 368 } 369 370 HB_FontRec* allocHarfbuzzFont() 371 { 372 HB_FontRec* font = reinterpret_cast<HB_FontRec*>(fastMalloc(sizeof(HB_FontRec))); 373 memset(font, 0, sizeof(HB_FontRec)); 374 font->klass = &harfbuzzSkiaClass; 375 font->userData = 0; 376 // The values which harfbuzzSkiaClass returns are already scaled to 377 // pixel units, so we just set all these to one to disable further 378 // scaling. 379 font->x_ppem = 1; 380 font->y_ppem = 1; 381 font->x_scale = 1; 382 font->y_scale = 1; 383 384 return font; 385 } 386 387 void deleteGlyphArrays() 388 { 389 delete[] m_item.glyphs; 390 delete[] m_item.attributes; 391 delete[] m_item.advances; 392 delete[] m_item.offsets; 393 delete[] m_glyphs16; 394 delete[] m_xPositions; 395 } 396 397 bool createGlyphArrays() 398 { 399 m_item.glyphs = new HB_Glyph[m_maxGlyphs]; 400 m_item.attributes = new HB_GlyphAttributes[m_maxGlyphs]; 401 m_item.advances = new HB_Fixed[m_maxGlyphs]; 402 m_item.offsets = new HB_FixedPoint[m_maxGlyphs]; 403 // HB_FixedPoint is a struct, so we must use memset to clear it. 404 memset(m_item.offsets, 0, m_maxGlyphs * sizeof(HB_FixedPoint)); 405 m_glyphs16 = new uint16_t[m_maxGlyphs]; 406 m_xPositions = new SkScalar[m_maxGlyphs]; 407 408 return m_item.glyphs 409 && m_item.attributes 410 && m_item.advances 411 && m_item.offsets 412 && m_glyphs16 413 && m_xPositions; 414 } 415 416 bool expandGlyphArrays() 417 { 418 deleteGlyphArrays(); 419 m_maxGlyphs <<= 1; 420 return createGlyphArrays(); 421 } 422 423 bool shapeGlyphs() 424 { 425 for (;;) { 426 m_item.num_glyphs = m_maxGlyphs; 427 HB_ShapeItem(&m_item); 428 if (m_item.num_glyphs < m_maxGlyphs) 429 break; 430 431 // We overflowed our arrays. Resize and retry. 432 if (!expandGlyphArrays()) 433 return false; 434 } 435 436 return true; 437 } 438 439 void setGlyphXPositions(bool isRTL) 440 { 441 double position = 0; 442 for (int iter = 0; iter < m_item.num_glyphs; ++iter) { 443 // Glyphs are stored in logical order, but for layout purposes we always go left to right. 444 int i = isRTL ? m_item.num_glyphs - iter - 1 : iter; 445 446 m_glyphs16[i] = m_item.glyphs[i]; 447 double offsetX = truncateFixedPointToInteger(m_item.offsets[i].x); 448 m_xPositions[i] = m_offsetX + position + offsetX; 449 450 double advance = truncateFixedPointToInteger(m_item.advances[i]); 451 position += advance; 452 } 453 m_pixelWidth = position; 454 m_offsetX += m_pixelWidth; 455 } 456 457 const Font* const m_font; 458 HB_ShaperItem m_item; 459 uint16_t* m_glyphs16; // A vector of 16-bit glyph ids. 460 SkScalar* m_xPositions; // A vector of x positions for each glyph. 461 ssize_t m_indexOfNextScriptRun; // Indexes the script run in |m_run|. 462 const unsigned m_startingX; // Offset in pixels of the first script run. 463 unsigned m_offsetX; // Offset in pixels to the start of the next script run. 464 unsigned m_pixelWidth; // Width (in px) of the current script run. 465 unsigned m_numCodePoints; // Code points in current script run. 466 unsigned m_maxGlyphs; // Current size of all the Harfbuzz arrays. 467 468 OwnPtr<TextRun> m_normalizedRun; 469 OwnArrayPtr<UChar> m_normalizedBuffer; // A buffer for normalized run. 470 const TextRun& m_run; 471 bool m_iterateBackwards; 472 }; 473 474 static void setupForTextPainting(SkPaint* paint, SkColor color) 475 { 476 paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); 477 paint->setColor(color); 478 } 479 480 void Font::drawComplexText(GraphicsContext* gc, const TextRun& run, 481 const FloatPoint& point, int from, int to) const 482 { 483 if (!run.length()) 484 return; 485 486 SkCanvas* canvas = gc->platformContext()->canvas(); 487 int textMode = gc->platformContext()->getTextDrawingMode(); 488 bool fill = textMode & cTextFill; 489 bool stroke = (textMode & cTextStroke) 490 && gc->platformContext()->getStrokeStyle() != NoStroke 491 && gc->platformContext()->getStrokeThickness() > 0; 492 493 if (!fill && !stroke) 494 return; 495 496 SkPaint strokePaint, fillPaint; 497 if (fill) { 498 gc->platformContext()->setupPaintForFilling(&fillPaint); 499 setupForTextPainting(&fillPaint, gc->fillColor().rgb()); 500 } 501 if (stroke) { 502 gc->platformContext()->setupPaintForStroking(&strokePaint, 0, 0); 503 setupForTextPainting(&strokePaint, gc->strokeColor().rgb()); 504 } 505 506 TextRunWalker walker(run, point.x(), this); 507 bool haveMultipleLayers = isCanvasMultiLayered(canvas); 508 509 while (walker.nextScriptRun()) { 510 if (fill) { 511 walker.fontPlatformDataForScriptRun()->setupPaint(&fillPaint); 512 adjustTextRenderMode(&fillPaint, haveMultipleLayers); 513 canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, walker.xPositions(), point.y(), fillPaint); 514 } 515 516 if (stroke) { 517 walker.fontPlatformDataForScriptRun()->setupPaint(&strokePaint); 518 adjustTextRenderMode(&strokePaint, haveMultipleLayers); 519 canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, walker.xPositions(), point.y(), strokePaint); 520 } 521 } 522 } 523 524 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* /* fallbackFonts */) const 525 { 526 TextRunWalker walker(run, 0, this); 527 return walker.widthOfFullRun(); 528 } 529 530 static int glyphIndexForXPositionInScriptRun(const TextRunWalker& walker, int x) 531 { 532 const HB_Fixed* advances = walker.advances(); 533 int glyphIndex; 534 if (walker.rtl()) { 535 for (glyphIndex = walker.length() - 1; glyphIndex >= 0; --glyphIndex) { 536 if (x < truncateFixedPointToInteger(advances[glyphIndex])) 537 break; 538 x -= truncateFixedPointToInteger(advances[glyphIndex]); 539 } 540 } else { 541 for (glyphIndex = 0; glyphIndex < walker.length(); ++glyphIndex) { 542 if (x < truncateFixedPointToInteger(advances[glyphIndex])) 543 break; 544 x -= truncateFixedPointToInteger(advances[glyphIndex]); 545 } 546 } 547 548 return glyphIndex; 549 } 550 551 // Return the code point index for the given |x| offset into the text run. 552 int Font::offsetForPositionForComplexText(const TextRun& run, int x, 553 bool includePartialGlyphs) const 554 { 555 // (Mac code ignores includePartialGlyphs, and they don't know what it's 556 // supposed to do, so we just ignore it as well.) 557 TextRunWalker walker(run, 0, this); 558 559 // If this is RTL text, the first glyph from the left is actually the last 560 // code point. So we need to know how many code points there are total in 561 // order to subtract. This is different from the length of the TextRun 562 // because UTF-16 surrogate pairs are a single code point, but 32-bits long. 563 // In LTR we leave this as 0 so that we get the correct value for 564 // |basePosition|, below. 565 unsigned totalCodePoints = 0; 566 if (walker.rtl()) { 567 ssize_t offset = 0; 568 while (offset < run.length()) { 569 utf16_to_code_point(run.characters(), run.length(), &offset); 570 totalCodePoints++; 571 } 572 } 573 574 unsigned basePosition = totalCodePoints; 575 576 // For RTL: 577 // code-point order: abcd efg hijkl 578 // on screen: lkjih gfe dcba 579 // ^ ^ 580 // | | 581 // basePosition--| | 582 // totalCodePoints----| 583 // Since basePosition is currently the total number of code-points, the 584 // first thing we do is decrement it so that it's pointing to the start of 585 // the current script-run. 586 // 587 // For LTR, basePosition is zero so it already points to the start of the 588 // first script run. 589 while (walker.nextScriptRun()) { 590 if (walker.rtl()) 591 basePosition -= walker.numCodePoints(); 592 593 if (x < walker.width()) { 594 // The x value in question is within this script run. We consider 595 // each glyph in presentation order and stop when we find the one 596 // covering this position. 597 const int glyphIndex = glyphIndexForXPositionInScriptRun(walker, x); 598 599 // Now that we have a glyph index, we have to turn that into a 600 // code-point index. Because of ligatures, several code-points may 601 // have gone into a single glyph. We iterate over the clusters log 602 // and find the first code-point which contributed to the glyph. 603 604 // Some shapers (i.e. Khmer) will produce cluster logs which report 605 // that /no/ code points contributed to certain glyphs. Because of 606 // this, we take any code point which contributed to the glyph in 607 // question, or any subsequent glyph. If we run off the end, then 608 // we take the last code point. 609 const unsigned short* log = walker.logClusters(); 610 for (unsigned j = 0; j < walker.numCodePoints(); ++j) { 611 if (log[j] >= glyphIndex) 612 return basePosition + j; 613 } 614 615 return basePosition + walker.numCodePoints() - 1; 616 } 617 618 x -= walker.width(); 619 620 if (!walker.rtl()) 621 basePosition += walker.numCodePoints(); 622 } 623 624 return basePosition; 625 } 626 627 // Return the rectangle for selecting the given range of code-points in the TextRun. 628 FloatRect Font::selectionRectForComplexText(const TextRun& run, 629 const IntPoint& point, int height, 630 int from, int to) const 631 { 632 int fromX = -1, toX = -1, fromAdvance = -1, toAdvance = -1; 633 TextRunWalker walker(run, 0, this); 634 635 // Base will point to the x offset for the current script run. Note that, in 636 // the LTR case, width will be 0. 637 int base = walker.rtl() ? walker.widthOfFullRun() : 0; 638 const int leftEdge = base; 639 640 // We want to enumerate the script runs in code point order in the following 641 // code. This call also resets |walker|. 642 walker.setBackwardsIteration(false); 643 644 while (walker.nextScriptRun() && (fromX == -1 || toX == -1)) { 645 // TextRunWalker will helpfully accululate the x offsets for different 646 // script runs for us. For this code, however, we always want the x offsets 647 // to start from zero so we call this before each script run. 648 walker.setXOffsetToZero(); 649 650 if (walker.rtl()) 651 base -= walker.width(); 652 653 if (fromX == -1 && from < walker.numCodePoints()) { 654 // |from| is within this script run. So we index the clusters log to 655 // find which glyph this code-point contributed to and find its x 656 // position. 657 int glyph = walker.logClusters()[from]; 658 fromX = base + walker.xPositions()[glyph]; 659 fromAdvance = walker.advances()[glyph]; 660 } else 661 from -= walker.numCodePoints(); 662 663 if (toX == -1 && to < walker.numCodePoints()) { 664 int glyph = walker.logClusters()[to]; 665 toX = base + walker.xPositions()[glyph]; 666 toAdvance = walker.advances()[glyph]; 667 } else 668 to -= walker.numCodePoints(); 669 670 if (!walker.rtl()) 671 base += walker.width(); 672 } 673 674 // The position in question might be just after the text. 675 const int rightEdge = base; 676 if (fromX == -1 && !from) 677 fromX = leftEdge; 678 else if (walker.rtl()) 679 fromX += truncateFixedPointToInteger(fromAdvance); 680 681 if (toX == -1 && !to) 682 toX = rightEdge; 683 684 ASSERT(fromX != -1 && toX != -1); 685 686 if (fromX < toX) 687 return FloatRect(point.x() + fromX, point.y(), toX - fromX, height); 688 689 return FloatRect(point.x() + toX, point.y(), fromX - toX, height); 690 } 691 692 } // namespace WebCore 693