1/*
2 * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
3 * Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies)
4 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
5 * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
6 * Copyright (C) 2008 Dirk Schulze <krit@webkit.org>
7 * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
8 * Copyright (C) 2012 Intel Corporation. All rights reserved.
9 * Copyright (C) 2013, 2014 Adobe Systems Incorporated. All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include "config.h"
34#include "CanvasRenderingContext2D.h"
35
36#include "CSSFontSelector.h"
37#include "CSSParser.h"
38#include "CSSPropertyNames.h"
39#include "ImageBuffer.h"
40#include "ImageData.h"
41#include "InspectorInstrumentation.h"
42#include "Path2D.h"
43#include "RenderTheme.h"
44#include "ResourceLoadObserver.h"
45#include "RuntimeEnabledFeatures.h"
46#include "StyleProperties.h"
47#include "StyleResolver.h"
48#include "TextMetrics.h"
49#include "TextRun.h"
50#include <wtf/CheckedArithmetic.h>
51#include <wtf/IsoMallocInlines.h>
52#include <wtf/MathExtras.h>
53#include <wtf/text/StringBuilder.h>
54
55namespace WebCore {
56
57using namespace HTMLNames;
58
59WTF_MAKE_ISO_ALLOCATED_IMPL(CanvasRenderingContext2D);
60
61std::unique_ptr<CanvasRenderingContext2D> CanvasRenderingContext2D::create(CanvasBase& canvas, bool usesCSSCompatibilityParseMode, bool usesDashboardCompatibilityMode)
62{
63 auto renderingContext = std::unique_ptr<CanvasRenderingContext2D>(new CanvasRenderingContext2D(canvas, usesCSSCompatibilityParseMode, usesDashboardCompatibilityMode));
64
65 InspectorInstrumentation::didCreateCanvasRenderingContext(*renderingContext);
66
67 return renderingContext;
68}
69
70CanvasRenderingContext2D::CanvasRenderingContext2D(CanvasBase& canvas, bool usesCSSCompatibilityParseMode, bool usesDashboardCompatibilityMode)
71 : CanvasRenderingContext2DBase(canvas, usesCSSCompatibilityParseMode, usesDashboardCompatibilityMode)
72{
73}
74
75CanvasRenderingContext2D::~CanvasRenderingContext2D() = default;
76
77void CanvasRenderingContext2D::drawFocusIfNeeded(Element& element)
78{
79 drawFocusIfNeededInternal(m_path, element);
80}
81
82void CanvasRenderingContext2D::drawFocusIfNeeded(Path2D& path, Element& element)
83{
84 drawFocusIfNeededInternal(path.path(), element);
85}
86
87void CanvasRenderingContext2D::drawFocusIfNeededInternal(const Path& path, Element& element)
88{
89 auto* context = drawingContext();
90 if (!element.focused() || !state().hasInvertibleTransform || path.isEmpty() || !element.isDescendantOf(canvas()) || !context)
91 return;
92 context->drawFocusRing(path, 1, 1, RenderTheme::singleton().focusRingColor(element.document().styleColorOptions(canvas().computedStyle())));
93}
94
95String CanvasRenderingContext2D::font() const
96{
97 if (!state().font.realized())
98 return DefaultFont;
99
100 StringBuilder serializedFont;
101 const auto& fontDescription = state().font.fontDescription();
102
103 if (fontDescription.italic())
104 serializedFont.appendLiteral("italic ");
105 if (fontDescription.variantCaps() == FontVariantCaps::Small)
106 serializedFont.appendLiteral("small-caps ");
107
108 serializedFont.appendNumber(fontDescription.computedPixelSize());
109 serializedFont.appendLiteral("px");
110
111 for (unsigned i = 0; i < fontDescription.familyCount(); ++i) {
112 if (i)
113 serializedFont.append(',');
114 // FIXME: We should append family directly to serializedFont rather than building a temporary string.
115 String family = fontDescription.familyAt(i);
116 if (family.startsWith("-webkit-"))
117 family = family.substring(8);
118 if (family.contains(' '))
119 family = makeString('"', family, '"');
120
121 serializedFont.append(' ');
122 serializedFont.append(family);
123 }
124
125 return serializedFont.toString();
126}
127
128void CanvasRenderingContext2D::setFont(const String& newFont)
129{
130 if (newFont == state().unparsedFont && state().font.realized())
131 return;
132
133 auto parsedStyle = MutableStyleProperties::create();
134 CSSParser::parseValue(parsedStyle, CSSPropertyFont, newFont, true, strictToCSSParserMode(!m_usesCSSCompatibilityParseMode));
135 if (parsedStyle->isEmpty())
136 return;
137
138 String fontValue = parsedStyle->getPropertyValue(CSSPropertyFont);
139
140 // According to http://lists.w3.org/Archives/Public/public-html/2009Jul/0947.html,
141 // the "inherit" and "initial" values must be ignored.
142 if (fontValue == "inherit" || fontValue == "initial")
143 return;
144
145 // The parse succeeded.
146 String newFontSafeCopy(newFont); // Create a string copy since newFont can be deleted inside realizeSaves.
147 realizeSaves();
148 modifiableState().unparsedFont = newFontSafeCopy;
149
150 // Map the <canvas> font into the text style. If the font uses keywords like larger/smaller, these will work
151 // relative to the canvas.
152 auto newStyle = RenderStyle::createPtr();
153
154 Document& document = canvas().document();
155 document.updateStyleIfNeeded();
156
157 if (auto* computedStyle = canvas().computedStyle())
158 newStyle->setFontDescription(FontCascadeDescription { computedStyle->fontDescription() });
159 else {
160 FontCascadeDescription defaultFontDescription;
161 defaultFontDescription.setOneFamily(DefaultFontFamily);
162 defaultFontDescription.setSpecifiedSize(DefaultFontSize);
163 defaultFontDescription.setComputedSize(DefaultFontSize);
164
165 newStyle->setFontDescription(WTFMove(defaultFontDescription));
166 }
167
168 newStyle->fontCascade().update(&document.fontSelector());
169
170 // Now map the font property longhands into the style.
171 StyleResolver& styleResolver = canvas().styleResolver();
172 styleResolver.applyPropertyToStyle(CSSPropertyFontFamily, parsedStyle->getPropertyCSSValue(CSSPropertyFontFamily).get(), WTFMove(newStyle));
173 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontStyle, parsedStyle->getPropertyCSSValue(CSSPropertyFontStyle).get());
174 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontVariantCaps, parsedStyle->getPropertyCSSValue(CSSPropertyFontVariantCaps).get());
175 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontWeight, parsedStyle->getPropertyCSSValue(CSSPropertyFontWeight).get());
176
177 // As described in BUG66291, setting font-size and line-height on a font may entail a CSSPrimitiveValue::computeLengthDouble call,
178 // which assumes the fontMetrics are available for the affected font, otherwise a crash occurs (see http://trac.webkit.org/changeset/96122).
179 // The updateFont() calls below update the fontMetrics and ensure the proper setting of font-size and line-height.
180 styleResolver.updateFont();
181 styleResolver.applyPropertyToCurrentStyle(CSSPropertyFontSize, parsedStyle->getPropertyCSSValue(CSSPropertyFontSize).get());
182 styleResolver.updateFont();
183 styleResolver.applyPropertyToCurrentStyle(CSSPropertyLineHeight, parsedStyle->getPropertyCSSValue(CSSPropertyLineHeight).get());
184
185 modifiableState().font.initialize(document.fontSelector(), *styleResolver.style());
186}
187
188static CanvasTextAlign toCanvasTextAlign(TextAlign textAlign)
189{
190 switch (textAlign) {
191 case StartTextAlign:
192 return CanvasTextAlign::Start;
193 case EndTextAlign:
194 return CanvasTextAlign::End;
195 case LeftTextAlign:
196 return CanvasTextAlign::Left;
197 case RightTextAlign:
198 return CanvasTextAlign::Right;
199 case CenterTextAlign:
200 return CanvasTextAlign::Center;
201 }
202
203 ASSERT_NOT_REACHED();
204 return CanvasTextAlign::Start;
205}
206
207static TextAlign fromCanvasTextAlign(CanvasTextAlign canvasTextAlign)
208{
209 switch (canvasTextAlign) {
210 case CanvasTextAlign::Start:
211 return StartTextAlign;
212 case CanvasTextAlign::End:
213 return EndTextAlign;
214 case CanvasTextAlign::Left:
215 return LeftTextAlign;
216 case CanvasTextAlign::Right:
217 return RightTextAlign;
218 case CanvasTextAlign::Center:
219 return CenterTextAlign;
220 }
221
222 ASSERT_NOT_REACHED();
223 return StartTextAlign;
224}
225
226CanvasTextAlign CanvasRenderingContext2D::textAlign() const
227{
228 return toCanvasTextAlign(state().textAlign);
229}
230
231void CanvasRenderingContext2D::setTextAlign(CanvasTextAlign canvasTextAlign)
232{
233 auto textAlign = fromCanvasTextAlign(canvasTextAlign);
234 if (state().textAlign == textAlign)
235 return;
236 realizeSaves();
237 modifiableState().textAlign = textAlign;
238}
239
240static CanvasTextBaseline toCanvasTextBaseline(TextBaseline textBaseline)
241{
242 switch (textBaseline) {
243 case TopTextBaseline:
244 return CanvasTextBaseline::Top;
245 case HangingTextBaseline:
246 return CanvasTextBaseline::Hanging;
247 case MiddleTextBaseline:
248 return CanvasTextBaseline::Middle;
249 case AlphabeticTextBaseline:
250 return CanvasTextBaseline::Alphabetic;
251 case IdeographicTextBaseline:
252 return CanvasTextBaseline::Ideographic;
253 case BottomTextBaseline:
254 return CanvasTextBaseline::Bottom;
255 }
256
257 ASSERT_NOT_REACHED();
258 return CanvasTextBaseline::Top;
259}
260
261static TextBaseline fromCanvasTextBaseline(CanvasTextBaseline canvasTextBaseline)
262{
263 switch (canvasTextBaseline) {
264 case CanvasTextBaseline::Top:
265 return TopTextBaseline;
266 case CanvasTextBaseline::Hanging:
267 return HangingTextBaseline;
268 case CanvasTextBaseline::Middle:
269 return MiddleTextBaseline;
270 case CanvasTextBaseline::Alphabetic:
271 return AlphabeticTextBaseline;
272 case CanvasTextBaseline::Ideographic:
273 return IdeographicTextBaseline;
274 case CanvasTextBaseline::Bottom:
275 return BottomTextBaseline;
276 }
277
278 ASSERT_NOT_REACHED();
279 return TopTextBaseline;
280}
281
282CanvasTextBaseline CanvasRenderingContext2D::textBaseline() const
283{
284 return toCanvasTextBaseline(state().textBaseline);
285}
286
287void CanvasRenderingContext2D::setTextBaseline(CanvasTextBaseline canvasTextBaseline)
288{
289 auto textBaseline = fromCanvasTextBaseline(canvasTextBaseline);
290 if (state().textBaseline == textBaseline)
291 return;
292 realizeSaves();
293 modifiableState().textBaseline = textBaseline;
294}
295
296inline TextDirection CanvasRenderingContext2D::toTextDirection(Direction direction, const RenderStyle** computedStyle) const
297{
298 auto* style = (computedStyle || direction == Direction::Inherit) ? canvas().computedStyle() : nullptr;
299 if (computedStyle)
300 *computedStyle = style;
301 switch (direction) {
302 case Direction::Inherit:
303 return style ? style->direction() : TextDirection::LTR;
304 case Direction::Rtl:
305 return TextDirection::RTL;
306 case Direction::Ltr:
307 return TextDirection::LTR;
308 }
309 ASSERT_NOT_REACHED();
310 return TextDirection::LTR;
311}
312
313CanvasDirection CanvasRenderingContext2D::direction() const
314{
315 if (state().direction == Direction::Inherit)
316 canvas().document().updateStyleIfNeeded();
317 return toTextDirection(state().direction) == TextDirection::RTL ? CanvasDirection::Rtl : CanvasDirection::Ltr;
318}
319
320void CanvasRenderingContext2D::setDirection(CanvasDirection direction)
321{
322 if (state().direction == direction)
323 return;
324
325 realizeSaves();
326 modifiableState().direction = direction;
327}
328
329void CanvasRenderingContext2D::fillText(const String& text, float x, float y, Optional<float> maxWidth)
330{
331 drawTextInternal(text, x, y, true, maxWidth);
332}
333
334void CanvasRenderingContext2D::strokeText(const String& text, float x, float y, Optional<float> maxWidth)
335{
336 drawTextInternal(text, x, y, false, maxWidth);
337}
338
339static inline bool isSpaceThatNeedsReplacing(UChar c)
340{
341 // According to specification all space characters should be replaced with 0x0020 space character.
342 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-preparation-algorithm
343 // The space characters according to specification are : U+0020, U+0009, U+000A, U+000C, and U+000D.
344 // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#space-character
345 // This function returns true for 0x000B also, so that this is backward compatible.
346 // Otherwise, the test LayoutTests/canvas/philip/tests/2d.text.draw.space.collapse.space.html will fail
347 return c == 0x0009 || c == 0x000A || c == 0x000B || c == 0x000C || c == 0x000D;
348}
349
350static void normalizeSpaces(String& text)
351{
352 size_t i = text.find(isSpaceThatNeedsReplacing);
353 if (i == notFound)
354 return;
355
356 unsigned textLength = text.length();
357 Vector<UChar> charVector(textLength);
358 StringView(text).getCharactersWithUpconvert(charVector.data());
359
360 charVector[i++] = ' ';
361
362 for (; i < textLength; ++i) {
363 if (isSpaceThatNeedsReplacing(charVector[i]))
364 charVector[i] = ' ';
365 }
366 text = String::adopt(WTFMove(charVector));
367}
368
369Ref<TextMetrics> CanvasRenderingContext2D::measureText(const String& text)
370{
371 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled()) {
372 auto& canvas = this->canvas();
373 ResourceLoadObserver::shared().logCanvasWriteOrMeasure(canvas.document(), text);
374 ResourceLoadObserver::shared().logCanvasRead(canvas.document());
375 }
376
377 Ref<TextMetrics> metrics = TextMetrics::create();
378
379 String normalizedText = text;
380 normalizeSpaces(normalizedText);
381
382 const RenderStyle* computedStyle;
383 auto direction = toTextDirection(state().direction, &computedStyle);
384 bool override = computedStyle ? isOverride(computedStyle->unicodeBidi()) : false;
385
386 TextRun textRun(normalizedText, 0, 0, AllowTrailingExpansion, direction, override, true);
387 auto& font = fontProxy();
388 auto& fontMetrics = font.fontMetrics();
389
390 GlyphOverflow glyphOverflow;
391 glyphOverflow.computeBounds = true;
392 float fontWidth = font.width(textRun, &glyphOverflow);
393 metrics->setWidth(fontWidth);
394
395 FloatPoint offset = textOffset(fontWidth, direction);
396
397 metrics->setActualBoundingBoxAscent(glyphOverflow.top - offset.y());
398 metrics->setActualBoundingBoxDescent(glyphOverflow.bottom + offset.y());
399 metrics->setFontBoundingBoxAscent(fontMetrics.ascent() - offset.y());
400 metrics->setFontBoundingBoxDescent(fontMetrics.descent() + offset.y());
401 metrics->setEmHeightAscent(fontMetrics.ascent() - offset.y());
402 metrics->setEmHeightDescent(fontMetrics.descent() + offset.y());
403 metrics->setHangingBaseline(fontMetrics.ascent() - offset.y());
404 metrics->setAlphabeticBaseline(-offset.y());
405 metrics->setIdeographicBaseline(-fontMetrics.descent() - offset.y());
406
407 metrics->setActualBoundingBoxLeft(glyphOverflow.left - offset.x());
408 metrics->setActualBoundingBoxRight(fontWidth + glyphOverflow.right + offset.x());
409
410 return metrics;
411}
412
413auto CanvasRenderingContext2D::fontProxy() -> const FontProxy& {
414 auto& canvas = downcast<HTMLCanvasElement>(canvasBase());
415 canvas.document().updateStyleIfNeeded();
416 if (!state().font.realized())
417 setFont(state().unparsedFont);
418 return state().font;
419}
420
421FloatPoint CanvasRenderingContext2D::textOffset(float width, TextDirection direction)
422{
423 auto& fontMetrics = fontProxy().fontMetrics();
424 FloatPoint offset;
425
426 switch (state().textBaseline) {
427 case TopTextBaseline:
428 case HangingTextBaseline:
429 offset.setY(fontMetrics.ascent());
430 break;
431 case BottomTextBaseline:
432 case IdeographicTextBaseline:
433 offset.setY(-fontMetrics.descent());
434 break;
435 case MiddleTextBaseline:
436 offset.setY(fontMetrics.height() / 2 - fontMetrics.descent());
437 break;
438 case AlphabeticTextBaseline:
439 default:
440 break;
441 }
442
443 bool isRTL = direction == TextDirection::RTL;
444 auto align = state().textAlign;
445 if (align == StartTextAlign)
446 align = isRTL ? RightTextAlign : LeftTextAlign;
447 else if (align == EndTextAlign)
448 align = isRTL ? LeftTextAlign : RightTextAlign;
449
450 switch (align) {
451 case CenterTextAlign:
452 offset.setX(-width / 2);
453 break;
454 case RightTextAlign:
455 offset.setX(-width);
456 break;
457 default:
458 break;
459 }
460 return offset;
461}
462
463void CanvasRenderingContext2D::drawTextInternal(const String& text, float x, float y, bool fill, Optional<float> maxWidth)
464{
465 if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
466 ResourceLoadObserver::shared().logCanvasWriteOrMeasure(this->canvas().document(), text);
467
468 auto& fontProxy = this->fontProxy();
469 const auto& fontMetrics = fontProxy.fontMetrics();
470
471 auto* c = drawingContext();
472 if (!c)
473 return;
474 if (!state().hasInvertibleTransform)
475 return;
476 if (!std::isfinite(x) | !std::isfinite(y))
477 return;
478 if (maxWidth && (!std::isfinite(maxWidth.value()) || maxWidth.value() <= 0))
479 return;
480
481 // If gradient size is zero, then paint nothing.
482 auto gradient = c->strokeGradient();
483 if (!fill && gradient && gradient->isZeroSize())
484 return;
485
486 gradient = c->fillGradient();
487 if (fill && gradient && gradient->isZeroSize())
488 return;
489
490 String normalizedText = text;
491 normalizeSpaces(normalizedText);
492
493 // FIXME: Need to turn off font smoothing.
494
495 const RenderStyle* computedStyle;
496 auto direction = toTextDirection(state().direction, &computedStyle);
497 bool override = computedStyle ? isOverride(computedStyle->unicodeBidi()) : false;
498
499 TextRun textRun(normalizedText, 0, 0, AllowTrailingExpansion, direction, override, true);
500 float fontWidth = fontProxy.width(textRun);
501 bool useMaxWidth = maxWidth && maxWidth.value() < fontWidth;
502 float width = useMaxWidth ? maxWidth.value() : fontWidth;
503 FloatPoint location(x, y);
504 location += textOffset(width, direction);
505
506 // The slop built in to this mask rect matches the heuristic used in FontCGWin.cpp for GDI text.
507 FloatRect textRect = FloatRect(location.x() - fontMetrics.height() / 2, location.y() - fontMetrics.ascent() - fontMetrics.lineGap(),
508 width + fontMetrics.height(), fontMetrics.lineSpacing());
509 if (!fill)
510 inflateStrokeRect(textRect);
511
512#if USE(CG)
513 const CanvasStyle& drawStyle = fill ? state().fillStyle : state().strokeStyle;
514 if (drawStyle.canvasGradient() || drawStyle.canvasPattern()) {
515 IntRect maskRect = enclosingIntRect(textRect);
516
517 // If we have a shadow, we need to draw it before the mask operation.
518 // Follow a procedure similar to paintTextWithShadows in TextPainter.
519
520 if (shouldDrawShadows()) {
521 GraphicsContextStateSaver stateSaver(*c);
522
523 FloatSize offset(0, 2 * maskRect.height());
524
525 FloatSize shadowOffset;
526 float shadowRadius;
527 Color shadowColor;
528 c->getShadow(shadowOffset, shadowRadius, shadowColor);
529
530 FloatRect shadowRect(maskRect);
531 shadowRect.inflate(shadowRadius * 1.4);
532 shadowRect.move(shadowOffset * -1);
533 c->clip(shadowRect);
534
535 shadowOffset += offset;
536
537 c->setLegacyShadow(shadowOffset, shadowRadius, shadowColor);
538
539 if (fill)
540 c->setFillColor(Color::black);
541 else
542 c->setStrokeColor(Color::black);
543
544 fontProxy.drawBidiText(*c, textRun, location + offset, FontCascade::UseFallbackIfFontNotReady);
545 }
546
547 auto maskImage = ImageBuffer::createCompatibleBuffer(maskRect.size(), ColorSpaceSRGB, *c);
548 if (!maskImage)
549 return;
550
551 auto& maskImageContext = maskImage->context();
552
553 if (fill)
554 maskImageContext.setFillColor(Color::black);
555 else {
556 maskImageContext.setStrokeColor(Color::black);
557 maskImageContext.setStrokeThickness(c->strokeThickness());
558 }
559
560 maskImageContext.setTextDrawingMode(fill ? TextModeFill : TextModeStroke);
561
562 if (useMaxWidth) {
563 maskImageContext.translate(location - maskRect.location());
564 // We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) still work.
565 maskImageContext.scale(FloatSize((fontWidth > 0 ? (width / fontWidth) : 0), 1));
566 fontProxy.drawBidiText(maskImageContext, textRun, FloatPoint(0, 0), FontCascade::UseFallbackIfFontNotReady);
567 } else {
568 maskImageContext.translate(-maskRect.location());
569 fontProxy.drawBidiText(maskImageContext, textRun, location, FontCascade::UseFallbackIfFontNotReady);
570 }
571
572 GraphicsContextStateSaver stateSaver(*c);
573 c->clipToImageBuffer(*maskImage, maskRect);
574 drawStyle.applyFillColor(*c);
575 c->fillRect(maskRect);
576 return;
577 }
578#endif
579
580 c->setTextDrawingMode(fill ? TextModeFill : TextModeStroke);
581
582 GraphicsContextStateSaver stateSaver(*c);
583 if (useMaxWidth) {
584 c->translate(location);
585 // We draw when fontWidth is 0 so compositing operations (eg, a "copy" op) still work.
586 c->scale(FloatSize((fontWidth > 0 ? (width / fontWidth) : 0), 1));
587 location = FloatPoint();
588 }
589
590 if (isFullCanvasCompositeMode(state().globalComposite)) {
591 beginCompositeLayer();
592 fontProxy.drawBidiText(*c, textRun, location, FontCascade::UseFallbackIfFontNotReady);
593 endCompositeLayer();
594 didDrawEntireCanvas();
595 } else if (state().globalComposite == CompositeCopy) {
596 clearCanvas();
597 fontProxy.drawBidiText(*c, textRun, location, FontCascade::UseFallbackIfFontNotReady);
598 didDrawEntireCanvas();
599 } else {
600 fontProxy.drawBidiText(*c, textRun, location, FontCascade::UseFallbackIfFontNotReady);
601 didDraw(textRect);
602 }
603}
604
605} // namespace WebCore
606