• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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