blob: 4d5950e0cc2bcce4aa32db27da3d807a01097d95 [file] [log] [blame]
/*
* Copyright (C) 2004-2017 Apple Inc. All rights reserved.
* Copyright (C) 2008, 2010 Nokia Corporation and/or its subsidiary(-ies)
* Copyright (C) 2007 Alp Toker <alp@atoker.com>
* Copyright (C) 2008 Eric Seidel <eric@webkit.org>
* Copyright (C) 2008 Dirk Schulze <krit@webkit.org>
* Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
* Copyright (C) 2012 Intel Corporation. All rights reserved.
* Copyright (C) 2013, 2014 Adobe Systems Incorporated. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "CanvasRenderingContext2D.h"
#include "CSSFontSelector.h"
#include "CSSPropertyNames.h"
#include "CSSPropertyParserHelpers.h"
#include "CSSPropertyParserWorkerSafe.h"
#include "Gradient.h"
#include "ImageBuffer.h"
#include "ImageData.h"
#include "InspectorInstrumentation.h"
#include "NodeRenderStyle.h"
#include "Path2D.h"
#include "PixelFormat.h"
#include "RenderTheme.h"
#include "RenderWidget.h"
#include "ResourceLoadObserver.h"
#include "RuntimeEnabledFeatures.h"
#include "ScriptDisallowedScope.h"
#include "Settings.h"
#include "StyleBuilder.h"
#include "StyleFontSizeFunctions.h"
#include "StyleProperties.h"
#include "StyleResolveForFontRaw.h"
#include "StyleTreeResolver.h"
#include "TextMetrics.h"
#include "TextRun.h"
#include <wtf/CheckedArithmetic.h>
#include <wtf/IsoMallocInlines.h>
#include <wtf/MathExtras.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
using namespace HTMLNames;
WTF_MAKE_ISO_ALLOCATED_IMPL(CanvasRenderingContext2D);
std::unique_ptr<CanvasRenderingContext2D> CanvasRenderingContext2D::create(CanvasBase& canvas, CanvasRenderingContext2DSettings&& settings, bool usesCSSCompatibilityParseMode)
{
auto renderingContext = std::unique_ptr<CanvasRenderingContext2D>(new CanvasRenderingContext2D(canvas, WTFMove(settings), usesCSSCompatibilityParseMode));
InspectorInstrumentation::didCreateCanvasRenderingContext(*renderingContext);
return renderingContext;
}
CanvasRenderingContext2D::CanvasRenderingContext2D(CanvasBase& canvas, CanvasRenderingContext2DSettings&& settings, bool usesCSSCompatibilityParseMode)
: CanvasRenderingContext2DBase(canvas, WTFMove(settings), usesCSSCompatibilityParseMode)
{
}
CanvasRenderingContext2D::~CanvasRenderingContext2D() = default;
void CanvasRenderingContext2D::drawFocusIfNeeded(Element& element)
{
drawFocusIfNeededInternal(m_path, element);
}
void CanvasRenderingContext2D::drawFocusIfNeeded(Path2D& path, Element& element)
{
drawFocusIfNeededInternal(path.path(), element);
}
void CanvasRenderingContext2D::drawFocusIfNeededInternal(const Path& path, Element& element)
{
auto* context = drawingContext();
if (!element.focused() || !state().hasInvertibleTransform || path.isEmpty() || !element.isDescendantOf(canvas()) || !context)
return;
context->drawFocusRing(path, 1, 1, RenderTheme::singleton().focusRingColor(element.document().styleColorOptions(canvas().computedStyle())));
}
void CanvasRenderingContext2D::setFont(const String& newFont)
{
Document& document = canvas().document();
document.updateStyleIfNeeded();
setFontWithoutUpdatingStyle(newFont);
}
void CanvasRenderingContext2D::setFontWithoutUpdatingStyle(const String& newFont)
{
// Intentionally don't update style here, because updating style can cause JS to run synchronously.
// This function is called in the middle of processing, and running arbitrary JS in the middle of processing can cause unexpected behavior.
// Instead, the relevant canvas entry points update style once when they begin running, and we won't touch the style after that.
// This means that the style may end up being stale here, but that's at least better than running arbitrary JS in the middle of processing.
ScriptDisallowedScope::InMainThread scriptDisallowedScope;
if (newFont.isEmpty())
return;
if (newFont == state().unparsedFont && state().font.realized())
return;
// According to http://lists.w3.org/Archives/Public/public-html/2009Jul/0947.html,
// the "inherit" and "initial" values must be ignored. CSSPropertyParserWorkerSafe::parseFont() ignores these.
auto fontRaw = CSSPropertyParserWorkerSafe::parseFont(newFont, strictToCSSParserMode(!usesCSSCompatibilityParseMode()));
if (!fontRaw)
return;
FontCascadeDescription fontDescription;
if (auto* computedStyle = canvas().computedStyle())
fontDescription = FontCascadeDescription { computedStyle->fontDescription() };
else {
static NeverDestroyed<AtomString> family = DefaultFontFamily;
fontDescription.setOneFamily(family.get());
fontDescription.setSpecifiedSize(DefaultFontSize);
fontDescription.setComputedSize(DefaultFontSize);
}
// Map the <canvas> font into the text style. If the font uses keywords like larger/smaller, these will work
// relative to the canvas.
Document& document = canvas().document();
auto fontCascade = Style::resolveForFontRaw(*fontRaw, WTFMove(fontDescription), document);
if (!fontCascade)
return;
String newFontSafeCopy(newFont); // Create a string copy since newFont can be deleted inside realizeSaves.
realizeSaves();
modifiableState().unparsedFont = newFontSafeCopy;
modifiableState().font.initialize(document.fontSelector(), *fontCascade);
ASSERT(state().font.realized());
ASSERT(state().font.isPopulated());
}
inline TextDirection CanvasRenderingContext2D::toTextDirection(Direction direction, const RenderStyle** computedStyle) const
{
auto* style = computedStyle || direction == Direction::Inherit ? canvas().existingComputedStyle() : nullptr;
if (computedStyle)
*computedStyle = style;
switch (direction) {
case Direction::Inherit:
return style ? style->direction() : TextDirection::LTR;
case Direction::Rtl:
return TextDirection::RTL;
case Direction::Ltr:
return TextDirection::LTR;
}
ASSERT_NOT_REACHED();
return TextDirection::LTR;
}
CanvasDirection CanvasRenderingContext2D::direction() const
{
if (state().direction == Direction::Inherit)
canvas().document().updateStyleIfNeeded();
return toTextDirection(state().direction) == TextDirection::RTL ? CanvasDirection::Rtl : CanvasDirection::Ltr;
}
void CanvasRenderingContext2D::fillText(const String& text, double x, double y, std::optional<double> maxWidth)
{
drawTextInternal(text, x, y, true, maxWidth);
}
void CanvasRenderingContext2D::strokeText(const String& text, double x, double y, std::optional<double> maxWidth)
{
drawTextInternal(text, x, y, false, maxWidth);
}
Ref<TextMetrics> CanvasRenderingContext2D::measureText(const String& text)
{
downcast<HTMLCanvasElement>(canvasBase()).document().updateStyleIfNeeded();
ScriptDisallowedScope::InMainThread scriptDisallowedScope;
if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled()) {
auto& canvas = this->canvas();
ResourceLoadObserver::shared().logCanvasWriteOrMeasure(canvas.document(), text);
ResourceLoadObserver::shared().logCanvasRead(canvas.document());
}
String normalizedText = normalizeSpaces(text);
const RenderStyle* computedStyle;
auto direction = toTextDirection(state().direction, &computedStyle);
bool override = computedStyle && isOverride(computedStyle->unicodeBidi());
TextRun textRun(normalizedText, 0, 0, AllowRightExpansion, direction, override, true);
return measureTextInternal(textRun);
}
auto CanvasRenderingContext2D::fontProxy() -> const FontProxy*
{
// Intentionally don't update style here, because updating style can cause JS to run synchronously.
// This function is called in the middle of processing, and running arbitrary JS in the middle of processing can cause unexpected behavior.
// Instead, the relevant canvas entry points update style once when they begin running, and we won't touch the style after that.
// This means that the style may end up being stale here, but that's at least better than running arbitrary JS in the middle of processing.
ScriptDisallowedScope::InMainThread scriptDisallowedScope;
if (!state().font.realized())
setFontWithoutUpdatingStyle(state().unparsedFont);
return &state().font;
}
void CanvasRenderingContext2D::drawTextInternal(const String& text, double x, double y, bool fill, std::optional<double> maxWidth)
{
downcast<HTMLCanvasElement>(canvasBase()).document().updateStyleIfNeeded();
ScriptDisallowedScope::InMainThread scriptDisallowedScope;
if (RuntimeEnabledFeatures::sharedFeatures().webAPIStatisticsEnabled())
ResourceLoadObserver::shared().logCanvasWriteOrMeasure(this->canvas().document(), text);
if (!canDrawText(x, y, fill, maxWidth))
return;
String normalizedText = normalizeSpaces(text);
const RenderStyle* computedStyle;
auto direction = toTextDirection(state().direction, &computedStyle);
bool override = computedStyle && isOverride(computedStyle->unicodeBidi());
TextRun textRun(normalizedText, 0, 0, AllowRightExpansion, direction, override, true);
drawTextUnchecked(textRun, x, y, fill, maxWidth);
}
} // namespace WebCore