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.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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.
32 #include "FontCascade.h"
36 #include "AffineTransform.h"
37 #include "CairoUtilities.h"
39 #include "GlyphBuffer.h"
41 #include "GraphicsContext.h"
42 #include "ImageBuffer.h"
44 #include "PlatformContextCairo.h"
45 #include "PlatformPathCairo.h"
46 #include "ShadowBlur.h"
50 static void drawGlyphsToContext(cairo_t* context, const Font& font, const cairo_glyph_t* glyphs, unsigned numGlyphs)
52 cairo_matrix_t originalTransform;
53 float syntheticBoldOffset = font.syntheticBoldOffset();
54 if (syntheticBoldOffset)
55 cairo_get_matrix(context, &originalTransform);
57 cairo_set_scaled_font(context, font.platformData().scaledFont());
58 cairo_show_glyphs(context, glyphs, numGlyphs);
60 if (syntheticBoldOffset) {
61 cairo_translate(context, syntheticBoldOffset, 0);
62 cairo_show_glyphs(context, glyphs, numGlyphs);
65 if (syntheticBoldOffset)
66 cairo_set_matrix(context, &originalTransform);
69 static void drawGlyphsShadow(GraphicsContext& graphicsContext, const FloatPoint& point, const Font& font, const cairo_glyph_t* glyphs, unsigned numGlyphs)
71 ShadowBlur& shadow = graphicsContext.platformContext()->shadowBlur();
73 if (!(graphicsContext.textDrawingMode() & TextModeFill) || shadow.type() == ShadowBlur::NoShadow)
76 if (!graphicsContext.mustUseShadowBlur()) {
77 // Optimize non-blurry shadows, by just drawing text without the ShadowBlur.
78 cairo_t* context = graphicsContext.platformContext()->cr();
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);
86 cairo_restore(context);
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);
94 if (GraphicsContext* shadowContext = shadow.beginShadowLayer(graphicsContext, fontExtentsRect)) {
95 drawGlyphsToContext(shadowContext->platformContext()->cr(), font, glyphs, numGlyphs);
96 shadow.endShadowLayer(graphicsContext);
100 void FontCascade::drawGlyphs(GraphicsContext& context, const Font& font, const GlyphBuffer& glyphBuffer,
101 unsigned from, unsigned numGlyphs, const FloatPoint& point, FontSmoothingMode)
103 if (!font.platformData().size())
106 auto xOffset = point.x();
107 Vector<cairo_glyph_t> cairoGlyphs(numGlyphs);
109 ASSERT(from + numGlyphs <= glyphBuffer.size());
110 auto* glyphs = glyphBuffer.glyphs(from);
111 auto* advances = glyphBuffer.advances(from);
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();
120 PlatformContextCairo* platformContext = context.platformContext();
121 drawGlyphsShadow(context, point, font, cairoGlyphs.data(), numGlyphs);
123 cairo_t* cr = platformContext->cr();
126 if (context.textDrawingMode() & TextModeFill) {
127 platformContext->prepareForFilling(context.state(), PlatformContextCairo::AdjustPatternForGlobalAlpha);
128 drawGlyphsToContext(cr, font, cairoGlyphs.data(), numGlyphs);
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());
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);
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)
158 FloatPoint startingPoint;
159 FloatPoint currentPoint;
165 static bool findIntersectionPoint(float y, FloatPoint p1, FloatPoint p2, float& x)
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);
171 static void updateX(GlyphIterationState& state, float x)
173 state.minX = std::min(state.minX, x);
174 state.maxX = std::max(state.maxX, x);
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)
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];
191 case PathElementAddLineToPoint:
192 doIntersection = true;
193 point = element.points[0];
195 case PathElementAddQuadCurveToPoint:
196 doIntersection = true;
197 point = element.points[1];
199 case PathElementAddCurveToPoint:
200 doIntersection = true;
201 point = element.points[2];
203 case PathElementCloseSubpath:
204 doIntersection = true;
205 point = state.startingPoint;
213 if (findIntersectionPoint(state.centerOfLine, state.currentPoint, point, x))
216 state.currentPoint = point;
219 class CairoGlyphToPathTranslator final : public GlyphToPathTranslator {
221 CairoGlyphToPathTranslator(const TextRun& textRun, const GlyphBuffer& glyphBuffer, const FloatPoint& textOrigin)
224 , m_glyphBuffer(glyphBuffer)
225 , m_fontData(glyphBuffer.fontAt(m_index))
226 , m_translation(AffineTransform().translate(textOrigin.x(), textOrigin.y()))
230 bool containsMorePaths() final { return m_index != m_glyphBuffer.size(); }
232 std::pair<float, float> extents() final;
233 GlyphUnderlineType underlineType() final;
234 void advance() final;
238 const TextRun& m_textRun;
239 const GlyphBuffer& m_glyphBuffer;
240 const Font* m_fontData;
241 AffineTransform m_translation;
244 Path CairoGlyphToPathTranslator::path()
247 path.ensurePlatformPath();
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);
253 float syntheticBoldOffset = m_fontData->syntheticBoldOffset();
254 if (syntheticBoldOffset) {
255 cairo_translate(path.platformPath()->context(), syntheticBoldOffset, 0);
256 cairo_glyph_path(path.platformPath()->context(), &cairoGlyph, 1);
259 path.transform(m_translation);
263 std::pair<float, float> CairoGlyphToPathTranslator::extents()
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()));
270 GlyphToPathTranslator::GlyphUnderlineType CairoGlyphToPathTranslator::underlineType()
272 return computeUnderlineType(m_textRun, m_glyphBuffer, m_index);
275 void CairoGlyphToPathTranslator::advance()
277 GlyphBufferAdvance advance = m_glyphBuffer.advanceAt(m_index);
278 m_translation = m_translation.translate(advance.width(), advance.height());
280 if (m_index < m_glyphBuffer.size())
281 m_fontData = m_glyphBuffer.fontAt(m_index);
284 DashArray FontCascade::dashesForIntersectionsWithRect(const TextRun& run, const FloatPoint& textOrigin, const FloatRect& lineExtents) const
286 if (isLoadingCustomFonts())
289 GlyphBuffer glyphBuffer;
290 glyphBuffer.saveOffsetsInString();
292 if (codePath(run) != FontCascade::Complex)
293 deltaX = getGlyphsAndAdvancesForSimpleText(run, 0, run.length(), glyphBuffer);
295 deltaX = getGlyphsAndAdvancesForComplexText(run, 0, run.length(), glyphBuffer);
297 if (!glyphBuffer.size())
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);
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.
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);
319 if (info.minX < info.maxX) {
320 result.append(info.minX - lineExtents.x());
321 result.append(info.maxX - lineExtents.x());
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());
331 case GlyphToPathTranslator::GlyphUnderlineType::DrawOverGlyph:
338 #endif // ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
340 } // namespace WebCore