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