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
canReturnFallbackFontsForComplexText()53 bool Font::canReturnFallbackFontsForComplexText()
54 {
55 return false;
56 }
57
canExpandAroundIdeographsInComplexText()58 bool Font::canExpandAroundIdeographsInComplexText()
59 {
60 return false;
61 }
62
isCanvasMultiLayered(SkCanvas * canvas)63 static bool isCanvasMultiLayered(SkCanvas* canvas)
64 {
65 SkCanvas::LayerIter layerIterator(canvas, false);
66 layerIterator.next();
67 return !layerIterator.done();
68 }
69
adjustTextRenderMode(SkPaint * paint,PlatformContextSkia * skiaContext)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
drawGlyphs(GraphicsContext * gc,const SimpleFontData * font,const GlyphBuffer & glyphBuffer,int from,int numGlyphs,const FloatPoint & point) const80 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.
truncateFixedPointToInteger(HB_Fixed value)172 static int truncateFixedPointToInteger(HB_Fixed value)
173 {
174 return value >> 6;
175 }
176
setupForTextPainting(SkPaint * paint,SkColor color)177 static void setupForTextPainting(SkPaint* paint, SkColor color)
178 {
179 paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
180 paint->setColor(color);
181 }
182
drawComplexText(GraphicsContext * gc,const TextRun & run,const FloatPoint & point,int from,int to) const183 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
drawEmphasisMarksForComplexText(GraphicsContext *,const TextRun &,const AtomicString &,const FloatPoint &,int,int) const239 void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const
240 {
241 notImplemented();
242 }
243
floatWidthForComplexText(const TextRun & run,HashSet<const SimpleFontData * > *,GlyphOverflow *) const244 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
glyphIndexForXPositionInScriptRun(const ComplexTextController & controller,int targetX)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.
offsetForPositionForComplexText(const TextRun & run,float xFloat,bool includePartialGlyphs) const271 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.
selectionRectForComplexText(const TextRun & run,const FloatPoint & point,int height,int from,int to) const326 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