RenderSVGResourcePattern.cpp [plain text]
#include "config.h"
#include "RenderSVGResourcePattern.h"
#include "ElementIterator.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "RenderSVGRoot.h"
#include "SVGFitToViewBox.h"
#include "SVGRenderingContext.h"
#include "SVGResources.h"
#include "SVGResourcesCache.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(RenderSVGResourcePattern);
RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement& element, RenderStyle&& style)
: RenderSVGResourceContainer(element, WTFMove(style))
{
}
SVGPatternElement& RenderSVGResourcePattern::patternElement() const
{
return downcast<SVGPatternElement>(RenderSVGResourceContainer::element());
}
void RenderSVGResourcePattern::removeAllClientsFromCache(bool markForInvalidation)
{
m_patternMap.clear();
m_shouldCollectPatternAttributes = true;
markAllClientsForInvalidation(markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
}
void RenderSVGResourcePattern::removeClientFromCache(RenderElement& client, bool markForInvalidation)
{
m_patternMap.remove(&client);
markClientForInvalidation(client, markForInvalidation ? RepaintInvalidation : ParentOnlyInvalidation);
}
void RenderSVGResourcePattern::collectPatternAttributes(PatternAttributes& attributes) const
{
const RenderSVGResourcePattern* current = this;
while (current) {
const SVGPatternElement& pattern = current->patternElement();
pattern.collectPatternAttributes(attributes);
auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*current);
ASSERT_IMPLIES(resources && resources->linkedResource(), is<RenderSVGResourcePattern>(resources->linkedResource()));
current = resources ? downcast<RenderSVGResourcePattern>(resources->linkedResource()) : nullptr;
}
}
PatternData* RenderSVGResourcePattern::buildPattern(RenderElement& renderer, OptionSet<RenderSVGResourceMode> resourceMode, GraphicsContext& context)
{
ASSERT(!m_shouldCollectPatternAttributes);
PatternData* currentData = m_patternMap.get(&renderer);
if (currentData && currentData->pattern)
return currentData;
if (!m_attributes.patternContentElement())
return nullptr;
if (m_attributes.hasViewBox() && m_attributes.viewBox().isEmpty())
return nullptr;
FloatRect tileBoundaries;
AffineTransform tileImageTransform;
if (!buildTileImageTransform(renderer, m_attributes, patternElement(), tileBoundaries, tileImageTransform))
return nullptr;
AffineTransform absoluteTransformIgnoringRotation = SVGRenderingContext::calculateTransformationToOutermostCoordinateSystem(renderer);
SVGRenderingContext::clear2DRotation(absoluteTransformIgnoringRotation);
FloatRect absoluteTileBoundaries = absoluteTransformIgnoringRotation.mapRect(tileBoundaries);
FloatRect clampedAbsoluteTileBoundaries;
absoluteTileBoundaries.scale(static_cast<float>(m_attributes.patternTransform().xScale()),
static_cast<float>(m_attributes.patternTransform().yScale()));
auto tileImage = createTileImage(m_attributes, tileBoundaries, absoluteTileBoundaries, tileImageTransform, clampedAbsoluteTileBoundaries, context.renderingMode());
if (!tileImage)
return nullptr;
const IntSize tileImageSize = tileImage->logicalSize();
RefPtr<Image> copiedImage = ImageBuffer::sinkIntoImage(WTFMove(tileImage));
if (!copiedImage)
return nullptr;
auto patternData = std::make_unique<PatternData>();
patternData->pattern = Pattern::create(copiedImage.releaseNonNull(), true, true);
patternData->transform.translate(tileBoundaries.location());
patternData->transform.scale(tileBoundaries.size() / tileImageSize);
AffineTransform patternTransform = m_attributes.patternTransform();
if (!patternTransform.isIdentity())
patternData->transform = patternTransform * patternData->transform;
if (resourceMode.contains(RenderSVGResourceMode::ApplyToText)) {
AffineTransform additionalTextTransformation;
if (shouldTransformOnTextPainting(renderer, additionalTextTransformation))
patternData->transform *= additionalTextTransformation;
}
patternData->pattern->setPatternSpaceTransform(patternData->transform);
return m_patternMap.set(&renderer, WTFMove(patternData)).iterator->value.get();
}
bool RenderSVGResourcePattern::applyResource(RenderElement& renderer, const RenderStyle& style, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode)
{
ASSERT(context);
ASSERT(resourceMode != RenderSVGResourceMode::ApplyToDefault);
if (m_shouldCollectPatternAttributes) {
patternElement().synchronizeAnimatedSVGAttribute(anyQName());
m_attributes = PatternAttributes();
collectPatternAttributes(m_attributes);
m_shouldCollectPatternAttributes = false;
}
FloatRect objectBoundingBox = renderer.objectBoundingBox();
if (m_attributes.patternUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX && objectBoundingBox.isEmpty())
return false;
PatternData* patternData = buildPattern(renderer, resourceMode, *context);
if (!patternData)
return false;
context->save();
const SVGRenderStyle& svgStyle = style.svgStyle();
if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
context->setAlpha(svgStyle.fillOpacity());
context->setFillPattern(*patternData->pattern);
context->setFillRule(svgStyle.fillRule());
} else if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
if (svgStyle.vectorEffect() == VectorEffect::NonScalingStroke)
patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(&renderer, patternData->transform));
context->setAlpha(svgStyle.strokeOpacity());
context->setStrokePattern(*patternData->pattern);
SVGRenderSupport::applyStrokeStyleToContext(context, style, renderer);
}
if (resourceMode.contains(RenderSVGResourceMode::ApplyToText)) {
if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
context->setTextDrawingMode(TextModeFill);
#if USE(CG)
context->applyFillPattern();
#endif
} else if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
context->setTextDrawingMode(TextModeStroke);
#if USE(CG)
context->applyStrokePattern();
#endif
}
}
return true;
}
void RenderSVGResourcePattern::postApplyResource(RenderElement&, GraphicsContext*& context, OptionSet<RenderSVGResourceMode> resourceMode, const Path* path, const RenderSVGShape* shape)
{
ASSERT(context);
ASSERT(resourceMode != RenderSVGResourceMode::ApplyToDefault);
if (resourceMode.contains(RenderSVGResourceMode::ApplyToFill)) {
if (path)
context->fillPath(*path);
else if (shape)
shape->fillShape(*context);
}
if (resourceMode.contains(RenderSVGResourceMode::ApplyToStroke)) {
if (path)
context->strokePath(*path);
else if (shape)
shape->strokeShape(*context);
}
context->restore();
}
static inline FloatRect calculatePatternBoundaries(const PatternAttributes& attributes,
const FloatRect& objectBoundingBox,
const SVGPatternElement& patternElement)
{
return SVGLengthContext::resolveRectangle(&patternElement, attributes.patternUnits(), objectBoundingBox, attributes.x(), attributes.y(), attributes.width(), attributes.height());
}
bool RenderSVGResourcePattern::buildTileImageTransform(RenderElement& renderer,
const PatternAttributes& attributes,
const SVGPatternElement& patternElement,
FloatRect& patternBoundaries,
AffineTransform& tileImageTransform) const
{
FloatRect objectBoundingBox = renderer.objectBoundingBox();
patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement);
if (patternBoundaries.width() <= 0 || patternBoundaries.height() <= 0)
return false;
AffineTransform viewBoxCTM = SVGFitToViewBox::viewBoxToViewTransform(attributes.viewBox(), attributes.preserveAspectRatio(), patternBoundaries.width(), patternBoundaries.height());
if (!viewBoxCTM.isIdentity())
tileImageTransform = viewBoxCTM;
else if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
tileImageTransform.scale(objectBoundingBox.width(), objectBoundingBox.height());
return true;
}
std::unique_ptr<ImageBuffer> RenderSVGResourcePattern::createTileImage(const PatternAttributes& attributes, const FloatRect& tileBoundaries, const FloatRect& absoluteTileBoundaries, const AffineTransform& tileImageTransform, FloatRect& clampedAbsoluteTileBoundaries, RenderingMode renderingMode) const
{
clampedAbsoluteTileBoundaries = ImageBuffer::clampedRect(absoluteTileBoundaries);
auto tileImage = SVGRenderingContext::createImageBuffer(absoluteTileBoundaries, clampedAbsoluteTileBoundaries, ColorSpaceSRGB, renderingMode);
if (!tileImage)
return nullptr;
GraphicsContext& tileImageContext = tileImage->context();
tileImageContext.scale(clampedAbsoluteTileBoundaries.size() / tileBoundaries.size());
if (!tileImageTransform.isIdentity())
tileImageContext.concatCTM(tileImageTransform);
AffineTransform contentTransformation;
if (attributes.patternContentUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX)
contentTransformation = tileImageTransform;
for (auto& child : childrenOfType<SVGElement>(*attributes.patternContentElement())) {
if (!child.renderer())
continue;
if (child.renderer()->needsLayout())
return nullptr;
SVGRenderingContext::renderSubtreeToImageBuffer(tileImage.get(), *child.renderer(), contentTransformation);
}
return tileImage;
}
}