1 /* 2 * Copyright (c) 2007, 2008, 2010 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 "ComplexTextControllerLinux.h" 35 #include "FloatRect.h" 36 #include "GlyphBuffer.h" 37 #include "GraphicsContext.h" 38 #include "HarfbuzzSkia.h" 39 #include "NotImplemented.h" 40 #include "PlatformContextSkia.h" 41 #include "SimpleFontData.h" 42 43 #include "SkCanvas.h" 44 #include "SkPaint.h" 45 #include "SkTemplates.h" 46 #include "SkTypeface.h" 47 #include "SkUtils.h" 48 49 #include <wtf/unicode/Unicode.h> 50 51 namespace WebCore { 52 53 bool Font::canReturnFallbackFontsForComplexText() 54 { 55 return false; 56 } 57 58 bool Font::canExpandAroundIdeographsInComplexText() 59 { 60 return false; 61 } 62 63 static bool isCanvasMultiLayered(SkCanvas* canvas) 64 { 65 SkCanvas::LayerIter layerIterator(canvas, false); 66 layerIterator.next(); 67 return !layerIterator.done(); 68 } 69 70 static void adjustTextRenderMode(SkPaint* paint, PlatformContextSkia* skiaContext) 71 { 72 // Our layers only have a single alpha channel. This means that subpixel 73 // rendered text cannot be compositied correctly when the layer is 74 // collapsed. Therefore, subpixel text is disabled when we are drawing 75 // onto a layer or when the compositor is being used. 76 if (isCanvasMultiLayered(skiaContext->canvas()) || skiaContext->isDrawingToImageBuffer()) 77 paint->setLCDRenderText(false); 78 } 79 80 void Font::drawGlyphs(GraphicsContext* gc, const SimpleFontData* font, 81 const GlyphBuffer& glyphBuffer, int from, int numGlyphs, 82 const FloatPoint& point) const { 83 SkASSERT(sizeof(GlyphBufferGlyph) == sizeof(uint16_t)); // compile-time assert 84 85 const GlyphBufferGlyph* glyphs = glyphBuffer.glyphs(from); 86 SkScalar x = SkFloatToScalar(point.x()); 87 SkScalar y = SkFloatToScalar(point.y()); 88 89 // FIXME: text rendering speed: 90 // Android has code in their WebCore fork to special case when the 91 // GlyphBuffer has no advances other than the defaults. In that case the 92 // text drawing can proceed faster. However, it's unclear when those 93 // patches may be upstreamed to WebKit so we always use the slower path 94 // here. 95 const GlyphBufferAdvance* adv = glyphBuffer.advances(from); 96 SkAutoSTMalloc<32, SkPoint> storage(numGlyphs), storage2(numGlyphs), storage3(numGlyphs); 97 SkPoint* pos = storage.get(); 98 SkPoint* vPosBegin = storage2.get(); 99 SkPoint* vPosEnd = storage3.get(); 100 101 bool isVertical = font->platformData().orientation() == Vertical; 102 for (int i = 0; i < numGlyphs; i++) { 103 SkScalar myWidth = SkFloatToScalar(adv[i].width()); 104 pos[i].set(x, y); 105 if (isVertical) { 106 vPosBegin[i].set(x + myWidth, y); 107 vPosEnd[i].set(x + myWidth, y - myWidth); 108 } 109 x += myWidth; 110 y += SkFloatToScalar(adv[i].height()); 111 } 112 113 gc->platformContext()->prepareForSoftwareDraw(); 114 115 SkCanvas* canvas = gc->platformContext()->canvas(); 116 TextDrawingModeFlags textMode = gc->platformContext()->getTextDrawingMode(); 117 118 // We draw text up to two times (once for fill, once for stroke). 119 if (textMode & TextModeFill) { 120 SkPaint paint; 121 gc->platformContext()->setupPaintForFilling(&paint); 122 font->platformData().setupPaint(&paint); 123 adjustTextRenderMode(&paint, gc->platformContext()); 124 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 125 paint.setColor(gc->fillColor().rgb()); 126 127 if (isVertical) { 128 SkPath path; 129 for (int i = 0; i < numGlyphs; ++i) { 130 path.reset(); 131 path.moveTo(vPosBegin[i]); 132 path.lineTo(vPosEnd[i]); 133 canvas->drawTextOnPath(glyphs + i, 2, path, 0, paint); 134 } 135 } else 136 canvas->drawPosText(glyphs, numGlyphs << 1, pos, paint); 137 } 138 139 if ((textMode & TextModeStroke) 140 && gc->platformContext()->getStrokeStyle() != NoStroke 141 && gc->platformContext()->getStrokeThickness() > 0) { 142 143 SkPaint paint; 144 gc->platformContext()->setupPaintForStroking(&paint, 0, 0); 145 font->platformData().setupPaint(&paint); 146 adjustTextRenderMode(&paint, gc->platformContext()); 147 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); 148 paint.setColor(gc->strokeColor().rgb()); 149 150 if (textMode & TextModeFill) { 151 // If we also filled, we don't want to draw shadows twice. 152 // See comment in FontChromiumWin.cpp::paintSkiaText() for more details. 153 SkSafeUnref(paint.setLooper(0)); 154 } 155 156 if (isVertical) { 157 SkPath path; 158 for (int i = 0; i < numGlyphs; ++i) { 159 path.reset(); 160 path.moveTo(vPosBegin[i]); 161 path.lineTo(vPosEnd[i]); 162 canvas->drawTextOnPath(glyphs + i, 2, path, 0, paint); 163 } 164 } else 165 canvas->drawPosText(glyphs, numGlyphs << 1, pos, paint); 166 } 167 } 168 169 // Harfbuzz uses 26.6 fixed point values for pixel offsets. However, we don't 170 // handle subpixel positioning so this function is used to truncate Harfbuzz 171 // values to a number of pixels. 172 static int truncateFixedPointToInteger(HB_Fixed value) 173 { 174 return value >> 6; 175 } 176 177 static void setupForTextPainting(SkPaint* paint, SkColor color) 178 { 179 paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); 180 paint->setColor(color); 181 } 182 183 void Font::drawComplexText(GraphicsContext* gc, const TextRun& run, 184 const FloatPoint& point, int from, int to) const 185 { 186 if (!run.length()) 187 return; 188 189 SkCanvas* canvas = gc->platformContext()->canvas(); 190 TextDrawingModeFlags textMode = gc->platformContext()->getTextDrawingMode(); 191 bool fill = textMode & TextModeFill; 192 bool stroke = (textMode & TextModeStroke) 193 && gc->platformContext()->getStrokeStyle() != NoStroke 194 && gc->platformContext()->getStrokeThickness() > 0; 195 196 if (!fill && !stroke) 197 return; 198 199 SkPaint strokePaint, fillPaint; 200 if (fill) { 201 gc->platformContext()->setupPaintForFilling(&fillPaint); 202 setupForTextPainting(&fillPaint, gc->fillColor().rgb()); 203 } 204 if (stroke) { 205 gc->platformContext()->setupPaintForStroking(&strokePaint, 0, 0); 206 setupForTextPainting(&strokePaint, gc->strokeColor().rgb()); 207 } 208 209 ComplexTextController controller(run, point.x(), this); 210 controller.setWordSpacingAdjustment(wordSpacing()); 211 controller.setLetterSpacingAdjustment(letterSpacing()); 212 controller.setPadding(run.expansion()); 213 214 if (run.rtl()) { 215 // FIXME: this causes us to shape the text twice -- once to compute the width and then again 216 // below when actually rendering. Change ComplexTextController to match platform/mac and 217 // platform/chromium/win by having it store the shaped runs, so we can reuse the results. 218 controller.reset(point.x() + controller.widthOfFullRun()); 219 // We need to set the padding again because ComplexTextController layout consumed the value. 220 // Fixing the above problem would help here too. 221 controller.setPadding(run.expansion()); 222 } 223 224 while (controller.nextScriptRun()) { 225 if (fill) { 226 controller.fontPlatformDataForScriptRun()->setupPaint(&fillPaint); 227 adjustTextRenderMode(&fillPaint, gc->platformContext()); 228 canvas->drawPosTextH(controller.glyphs(), controller.length() << 1, controller.xPositions(), point.y(), fillPaint); 229 } 230 231 if (stroke) { 232 controller.fontPlatformDataForScriptRun()->setupPaint(&strokePaint); 233 adjustTextRenderMode(&strokePaint, gc->platformContext()); 234 canvas->drawPosTextH(controller.glyphs(), controller.length() << 1, controller.xPositions(), point.y(), strokePaint); 235 } 236 } 237 } 238 239 void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const 240 { 241 notImplemented(); 242 } 243 244 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* /* fallbackFonts */, GlyphOverflow* /* glyphOverflow */) const 245 { 246 ComplexTextController controller(run, 0, this); 247 controller.setWordSpacingAdjustment(wordSpacing()); 248 controller.setLetterSpacingAdjustment(letterSpacing()); 249 controller.setPadding(run.expansion()); 250 return controller.widthOfFullRun(); 251 } 252 253 static int glyphIndexForXPositionInScriptRun(const ComplexTextController& controller, int targetX) 254 { 255 // Iterate through the glyphs in logical order, seeing whether targetX falls between the previous 256 // position and halfway through the current glyph. 257 // FIXME: this code probably belongs in ComplexTextController. 258 int lastX = controller.offsetX() - (controller.rtl() ? -controller.width() : controller.width()); 259 for (int glyphIndex = 0; static_cast<unsigned>(glyphIndex) < controller.length(); ++glyphIndex) { 260 int advance = truncateFixedPointToInteger(controller.advances()[glyphIndex]); 261 int nextX = static_cast<int>(controller.xPositions()[glyphIndex]) + advance / 2; 262 if (std::min(nextX, lastX) <= targetX && targetX <= std::max(nextX, lastX)) 263 return glyphIndex; 264 lastX = nextX; 265 } 266 267 return controller.length() - 1; 268 } 269 270 // Return the code point index for the given |x| offset into the text run. 271 int Font::offsetForPositionForComplexText(const TextRun& run, float xFloat, 272 bool includePartialGlyphs) const 273 { 274 // FIXME: This truncation is not a problem for HTML, but only affects SVG, which passes floating-point numbers 275 // to Font::offsetForPosition(). Bug http://webkit.org/b/40673 tracks fixing this problem. 276 int targetX = static_cast<int>(xFloat); 277 278 // (Mac code ignores includePartialGlyphs, and they don't know what it's 279 // supposed to do, so we just ignore it as well.) 280 ComplexTextController controller(run, 0, this); 281 controller.setWordSpacingAdjustment(wordSpacing()); 282 controller.setLetterSpacingAdjustment(letterSpacing()); 283 controller.setPadding(run.expansion()); 284 if (run.rtl()) { 285 // See FIXME in drawComplexText. 286 controller.reset(controller.widthOfFullRun()); 287 controller.setPadding(run.expansion()); 288 } 289 290 unsigned basePosition = 0; 291 292 int x = controller.offsetX(); 293 while (controller.nextScriptRun()) { 294 int nextX = controller.offsetX(); 295 296 if (std::min(x, nextX) <= targetX && targetX <= std::max(x, nextX)) { 297 // The x value in question is within this script run. 298 const int glyphIndex = glyphIndexForXPositionInScriptRun(controller, targetX); 299 300 // Now that we have a glyph index, we have to turn that into a 301 // code-point index. Because of ligatures, several code-points may 302 // have gone into a single glyph. We iterate over the clusters log 303 // and find the first code-point which contributed to the glyph. 304 305 // Some shapers (i.e. Khmer) will produce cluster logs which report 306 // that /no/ code points contributed to certain glyphs. Because of 307 // this, we take any code point which contributed to the glyph in 308 // question, or any subsequent glyph. If we run off the end, then 309 // we take the last code point. 310 const unsigned short* log = controller.logClusters(); 311 for (unsigned j = 0; j < controller.numCodePoints(); ++j) { 312 if (log[j] >= glyphIndex) 313 return basePosition + j; 314 } 315 316 return basePosition + controller.numCodePoints() - 1; 317 } 318 319 basePosition += controller.numCodePoints(); 320 } 321 322 return basePosition; 323 } 324 325 // Return the rectangle for selecting the given range of code-points in the TextRun. 326 FloatRect Font::selectionRectForComplexText(const TextRun& run, 327 const FloatPoint& point, int height, 328 int from, int to) const 329 { 330 int fromX = -1, toX = -1; 331 ComplexTextController controller(run, 0, this); 332 controller.setWordSpacingAdjustment(wordSpacing()); 333 controller.setLetterSpacingAdjustment(letterSpacing()); 334 controller.setPadding(run.expansion()); 335 if (run.rtl()) { 336 // See FIXME in drawComplexText. 337 controller.reset(controller.widthOfFullRun()); 338 controller.setPadding(run.expansion()); 339 } 340 341 // Iterate through the script runs in logical order, searching for the run covering the positions of interest. 342 while (controller.nextScriptRun() && (fromX == -1 || toX == -1)) { 343 if (fromX == -1 && from >= 0 && static_cast<unsigned>(from) < controller.numCodePoints()) { 344 // |from| is within this script run. So we index the clusters log to 345 // find which glyph this code-point contributed to and find its x 346 // position. 347 int glyph = controller.logClusters()[from]; 348 fromX = controller.xPositions()[glyph]; 349 if (controller.rtl()) 350 fromX += truncateFixedPointToInteger(controller.advances()[glyph]); 351 } else 352 from -= controller.numCodePoints(); 353 354 if (toX == -1 && to >= 0 && static_cast<unsigned>(to) < controller.numCodePoints()) { 355 int glyph = controller.logClusters()[to]; 356 toX = controller.xPositions()[glyph]; 357 if (controller.rtl()) 358 toX += truncateFixedPointToInteger(controller.advances()[glyph]); 359 } else 360 to -= controller.numCodePoints(); 361 } 362 363 // The position in question might be just after the text. 364 if (fromX == -1) 365 fromX = controller.offsetX(); 366 if (toX == -1) 367 toX = controller.offsetX(); 368 369 ASSERT(fromX != -1 && toX != -1); 370 371 if (fromX < toX) 372 return FloatRect(point.x() + fromX, point.y(), toX - fromX, height); 373 374 return FloatRect(point.x() + toX, point.y(), fromX - toX, height); 375 } 376 377 } // namespace WebCore 378