87b8fb87b7930ecfa31dadd45f02e986cc1a22e8
[WebKit-https.git] / Source / WebCore / platform / graphics / cairo / FontCairo.cpp
1 /*
2  * Copyright (C) 2006 Apple Inc.  All rights reserved.
3  * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
4  * Copyright (C) 2007, 2008 Alp Toker <alp@atoker.com>
5  * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
6  * Copyright (C) 2010 Holger Hans Peter Freyther
7  * Copyright (C) 2014 Igalia S.L.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
22  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26  * 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 "FontCascade.h"
33
34 #if USE(CAIRO)
35
36 #include "AffineTransform.h"
37 #include "CairoUtilities.h"
38 #include "Font.h"
39 #include "GlyphBuffer.h"
40 #include "Gradient.h"
41 #include "GraphicsContext.h"
42 #include "ImageBuffer.h"
43 #include "Pattern.h"
44 #include "PlatformContextCairo.h"
45 #include "PlatformPathCairo.h"
46 #include "ShadowBlur.h"
47
48 namespace WebCore {
49
50 static void drawGlyphsToContext(cairo_t* context, const Font& font, const cairo_glyph_t* glyphs, unsigned numGlyphs)
51 {
52     cairo_matrix_t originalTransform;
53     float syntheticBoldOffset = font.syntheticBoldOffset();
54     if (syntheticBoldOffset)
55         cairo_get_matrix(context, &originalTransform);
56
57     cairo_set_scaled_font(context, font.platformData().scaledFont());
58     cairo_show_glyphs(context, glyphs, numGlyphs);
59
60     if (syntheticBoldOffset) {
61         cairo_translate(context, syntheticBoldOffset, 0);
62         cairo_show_glyphs(context, glyphs, numGlyphs);
63     }
64
65     if (syntheticBoldOffset)
66         cairo_set_matrix(context, &originalTransform);
67 }
68
69 static void drawGlyphsShadow(GraphicsContext& graphicsContext, const FloatPoint& point, const Font& font, const cairo_glyph_t* glyphs, unsigned numGlyphs)
70 {
71     ShadowBlur& shadow = graphicsContext.platformContext()->shadowBlur();
72
73     if (!(graphicsContext.textDrawingMode() & TextModeFill) || shadow.type() == ShadowBlur::NoShadow)
74         return;
75
76     if (!graphicsContext.mustUseShadowBlur()) {
77         // Optimize non-blurry shadows, by just drawing text without the ShadowBlur.
78         cairo_t* context = graphicsContext.platformContext()->cr();
79         cairo_save(context);
80
81         FloatSize shadowOffset(graphicsContext.state().shadowOffset);
82         cairo_translate(context, shadowOffset.width(), shadowOffset.height());
83         setSourceRGBAFromColor(context, graphicsContext.state().shadowColor);
84         drawGlyphsToContext(context, font, glyphs, numGlyphs);
85
86         cairo_restore(context);
87         return;
88     }
89
90     cairo_text_extents_t extents;
91     cairo_scaled_font_glyph_extents(font.platformData().scaledFont(), glyphs, numGlyphs, &extents);
92     FloatRect fontExtentsRect(point.x() + extents.x_bearing, point.y() + extents.y_bearing, extents.width, extents.height);
93
94     if (GraphicsContext* shadowContext = shadow.beginShadowLayer(graphicsContext, fontExtentsRect)) {
95         drawGlyphsToContext(shadowContext->platformContext()->cr(), font, glyphs, numGlyphs);
96         shadow.endShadowLayer(graphicsContext);
97     }
98 }
99
100 void FontCascade::drawGlyphs(GraphicsContext& context, const Font& font, const GlyphBuffer& glyphBuffer,
101     unsigned from, unsigned numGlyphs, const FloatPoint& point, FontSmoothingMode)
102 {
103     if (!font.platformData().size())
104         return;
105
106     auto xOffset = point.x();
107     Vector<cairo_glyph_t> cairoGlyphs(numGlyphs);
108     {
109         ASSERT(from + numGlyphs <= glyphBuffer.size());
110         auto* glyphs = glyphBuffer.glyphs(from);
111         auto* advances = glyphBuffer.advances(from);
112
113         auto yOffset = point.y();
114         for (size_t i = 0; i < numGlyphs; ++i) {
115             cairoGlyphs[i] = { glyphs[i], xOffset, yOffset };
116             xOffset += advances[i].width();
117         }
118     }
119
120     PlatformContextCairo* platformContext = context.platformContext();
121     drawGlyphsShadow(context, point, font, cairoGlyphs.data(), numGlyphs);
122
123     cairo_t* cr = platformContext->cr();
124     cairo_save(cr);
125
126     if (context.textDrawingMode() & TextModeFill) {
127         platformContext->prepareForFilling(context.state(), PlatformContextCairo::AdjustPatternForGlobalAlpha);
128         drawGlyphsToContext(cr, font, cairoGlyphs.data(), numGlyphs);
129     }
130
131     // Prevent running into a long computation within cairo. If the stroke width is
132     // twice the size of the width of the text we will not ask cairo to stroke
133     // the text as even one single stroke would cover the full wdth of the text.
134     //  See https://bugs.webkit.org/show_bug.cgi?id=33759.
135     if (context.textDrawingMode() & TextModeStroke && context.strokeThickness() < 2 * xOffset) {
136         platformContext->prepareForStroking(context.state());
137         cairo_set_line_width(cr, context.strokeThickness());
138
139         // This may disturb the CTM, but we are going to call cairo_restore soon after.
140         cairo_set_scaled_font(cr, font.platformData().scaledFont());
141         cairo_glyph_path(cr, cairoGlyphs.data(), numGlyphs);
142         cairo_stroke(cr);
143     }
144
145     cairo_restore(cr);
146 }
147
148 #if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
149 struct GlyphIterationState {
150     GlyphIterationState(FloatPoint startingPoint, FloatPoint currentPoint, float centerOfLine, float minX, float maxX)
151         : startingPoint(startingPoint)
152         , currentPoint(currentPoint)
153         , centerOfLine(centerOfLine)
154         , minX(minX)
155         , maxX(maxX)
156     {
157     }
158     FloatPoint startingPoint;
159     FloatPoint currentPoint;
160     float centerOfLine;
161     float minX;
162     float maxX;
163 };
164
165 static bool findIntersectionPoint(float y, FloatPoint p1, FloatPoint p2, float& x)
166 {
167     x = p1.x() + (y - p1.y()) * (p2.x() - p1.x()) / (p2.y() - p1.y());
168     return (p1.y() < y && p2.y() > y) || (p1.y() > y && p2.y() < y);
169 }
170
171 static void updateX(GlyphIterationState& state, float x)
172 {
173     state.minX = std::min(state.minX, x);
174     state.maxX = std::max(state.maxX, x);
175 }
176
177 // This function is called by Path::apply and is therefore invoked for each contour in a glyph. This
178 // function models each contours as a straight line and calculates the intersections between each
179 // pseudo-contour and the vertical center of the underline found in GlyphIterationState::centerOfLine.
180 // It keeps track of the leftmost and rightmost intersection in  GlyphIterationState::minX and 
181 // GlyphIterationState::maxX.
182 static void findPathIntersections(GlyphIterationState& state, const PathElement& element)
183 {
184     bool doIntersection = false;
185     FloatPoint point = FloatPoint();
186     switch (element.type) {
187     case PathElementMoveToPoint:
188         state.startingPoint = element.points[0];
189         state.currentPoint = element.points[0];
190         break;
191     case PathElementAddLineToPoint:
192         doIntersection = true;
193         point = element.points[0];
194         break;
195     case PathElementAddQuadCurveToPoint:
196         doIntersection = true;
197         point = element.points[1];
198         break;
199     case PathElementAddCurveToPoint:
200         doIntersection = true;
201         point = element.points[2];
202         break;
203     case PathElementCloseSubpath:
204         doIntersection = true;
205         point = state.startingPoint;
206         break;
207     }
208
209     if (!doIntersection)
210         return;
211
212     float x;
213     if (findIntersectionPoint(state.centerOfLine, state.currentPoint, point, x))
214         updateX(state, x);
215
216     state.currentPoint = point;
217 }
218
219 class CairoGlyphToPathTranslator final : public GlyphToPathTranslator {
220 public:
221     CairoGlyphToPathTranslator(const TextRun& textRun, const GlyphBuffer& glyphBuffer, const FloatPoint& textOrigin)
222         : m_index(0)
223         , m_textRun(textRun)
224         , m_glyphBuffer(glyphBuffer)
225         , m_fontData(glyphBuffer.fontAt(m_index))
226         , m_translation(AffineTransform().translate(textOrigin.x(), textOrigin.y()))
227     {
228     }
229
230     bool containsMorePaths() final { return m_index != m_glyphBuffer.size(); }
231     Path path() final;
232     std::pair<float, float> extents() final;
233     GlyphUnderlineType underlineType() final;
234     void advance() final;
235
236 private:
237     unsigned m_index;
238     const TextRun& m_textRun;
239     const GlyphBuffer& m_glyphBuffer;
240     const Font* m_fontData;
241     AffineTransform m_translation;
242 };
243
244 Path CairoGlyphToPathTranslator::path()
245 {
246     Path path;
247     path.ensurePlatformPath();
248
249     cairo_glyph_t cairoGlyph = { m_glyphBuffer.glyphAt(m_index), 0, 0 };
250     cairo_set_scaled_font(path.platformPath()->context(), m_fontData->platformData().scaledFont());
251     cairo_glyph_path(path.platformPath()->context(), &cairoGlyph, 1);
252
253     float syntheticBoldOffset = m_fontData->syntheticBoldOffset();
254     if (syntheticBoldOffset) {
255         cairo_translate(path.platformPath()->context(), syntheticBoldOffset, 0);
256         cairo_show_glyphs(path.platformPath()->context(), &cairoGlyph, 1);
257     }
258
259     path.transform(m_translation);
260     return path;
261 }
262
263 std::pair<float, float> CairoGlyphToPathTranslator::extents()
264 {
265     FloatPoint beginning = m_translation.mapPoint(FloatPoint());
266     FloatSize end = m_translation.mapSize(m_glyphBuffer.advanceAt(m_index));
267     return std::make_pair(static_cast<float>(beginning.x()), static_cast<float>(beginning.x() + end.width()));
268 }
269
270 GlyphToPathTranslator::GlyphUnderlineType CairoGlyphToPathTranslator::underlineType()
271 {
272     return computeUnderlineType(m_textRun, m_glyphBuffer, m_index);
273 }
274
275 void CairoGlyphToPathTranslator::advance()
276 {
277     GlyphBufferAdvance advance = m_glyphBuffer.advanceAt(m_index);
278     m_translation = m_translation.translate(advance.width(), advance.height());
279     ++m_index;
280     if (m_index < m_glyphBuffer.size())
281         m_fontData = m_glyphBuffer.fontAt(m_index);
282 }
283
284 DashArray FontCascade::dashesForIntersectionsWithRect(const TextRun& run, const FloatPoint& textOrigin, const FloatRect& lineExtents) const
285 {
286     if (isLoadingCustomFonts())
287         return DashArray();
288
289     GlyphBuffer glyphBuffer;
290     glyphBuffer.saveOffsetsInString();
291     float deltaX;
292     if (codePath(run) != FontCascade::Complex)
293         deltaX = getGlyphsAndAdvancesForSimpleText(run, 0, run.length(), glyphBuffer);
294     else
295         deltaX = getGlyphsAndAdvancesForComplexText(run, 0, run.length(), glyphBuffer);
296
297     if (!glyphBuffer.size())
298         return DashArray();
299
300     // FIXME: Handle SVG + non-SVG interleaved runs. https://bugs.webkit.org/show_bug.cgi?id=133778
301     FloatPoint origin = FloatPoint(textOrigin.x() + deltaX, textOrigin.y());
302     CairoGlyphToPathTranslator translator(run, glyphBuffer, origin);
303     DashArray result;
304     for (int index = 0; translator.containsMorePaths(); ++index, translator.advance()) {
305         float centerOfLine = lineExtents.y() + (lineExtents.height() / 2);
306         GlyphIterationState info = GlyphIterationState(FloatPoint(), FloatPoint(), centerOfLine, lineExtents.x() + lineExtents.width(), lineExtents.x());
307         const Font* localFontData = glyphBuffer.fontAt(index);
308         if (!localFontData) {
309             // The advances will get all messed up if we do anything other than bail here.
310             result.clear();
311             break;
312         }
313         switch (translator.underlineType()) {
314         case GlyphToPathTranslator::GlyphUnderlineType::SkipDescenders: {
315             Path path = translator.path();
316             path.apply([&info](const PathElement& pathElement) {
317                 findPathIntersections(info, pathElement);
318             });
319             if (info.minX < info.maxX) {
320                 result.append(info.minX - lineExtents.x());
321                 result.append(info.maxX - lineExtents.x());
322             }
323             break;
324         }
325         case GlyphToPathTranslator::GlyphUnderlineType::SkipGlyph: {
326             std::pair<float, float> extents = translator.extents();
327             result.append(extents.first - lineExtents.x());
328             result.append(extents.second - lineExtents.x());
329             break;
330         }
331         case GlyphToPathTranslator::GlyphUnderlineType::DrawOverGlyph:
332             // Nothing to do
333             break;
334         }
335     }
336     return result;
337 }
338 #endif // ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
339
340 } // namespace WebCore
341
342 #endif // USE(CAIRO)