void RenderSVGShape::layout() { StackStats::LayoutCheckPoint layoutCheckPoint; LayoutRepainter repainter(*this, SVGRenderSupport::checkForSVGRepaintDuringLayout(this) && selfNeedsLayout()); SVGStyledTransformableElement* element = toSVGStyledTransformableElement(node()); bool updateCachedBoundariesInParents = false; if (m_needsShapeUpdate || m_needsBoundariesUpdate) { updateShapeFromElement(); m_needsShapeUpdate = false; updateRepaintBoundingBox(); m_needsBoundariesUpdate = false; updateCachedBoundariesInParents = true; } if (m_needsTransformUpdate) { m_localTransform = element->animatedLocalTransform(); m_needsTransformUpdate = false; updateCachedBoundariesInParents = true; } // Invalidate all resources of this client if our layout changed. if (everHadLayout() && selfNeedsLayout()) SVGResourcesCache::clientLayoutChanged(this); // If our bounds changed, notify the parents. if (updateCachedBoundariesInParents) RenderSVGModelObject::setNeedsBoundariesUpdate(); repainter.repaintAfterLayout(); setNeedsLayout(false); }
void RenderPath::layout() { LayoutRepainter repainter(*this, m_everHadLayout && checkForRepaintDuringLayout()); SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); bool needsPathUpdate = m_needsPathUpdate; if (needsPathUpdate) { m_path = element->toPathData(); m_needsPathUpdate = false; } if (m_needsTransformUpdate) { m_localTransform = element->animatedLocalTransform(); m_needsTransformUpdate = false; } // Invalidate all resources of this client if our layout changed. if (m_everHadLayout && selfNeedsLayout()) SVGResourcesCache::clientLayoutChanged(this); // At this point LayoutRepainter already grabbed the old bounds, // recalculate them now so repaintAfterLayout() uses the new bounds if (needsPathUpdate || m_needsBoundariesUpdate) { updateCachedBoundaries(); m_needsBoundariesUpdate = false; } repainter.repaintAfterLayout(); setNeedsLayout(false); }
FloatRect RenderSVGResourceClipper::resourceBoundingBox(const FloatRect& objectBoundingBox) const { FloatRect clipRect; for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyledTransformable()) continue; SVGStyledTransformableElement* styled = static_cast<SVGStyledTransformableElement*>(childNode); RenderStyle* style = styled->renderer() ? styled->renderer()->style() : 0; if (!style || style->display() == NONE) continue; clipRect.unite(styled->renderer()->objectBoundingBox()); } if (clipRect.isEmpty()) return FloatRect(); if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { AffineTransform obbTransform; obbTransform.translate(objectBoundingBox.x(), objectBoundingBox.y()); obbTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); return obbTransform.mapRect(clipRect); } return clipRect; }
SVGResource* SVGClipPathElement::canvasResource() { if (!m_clipper) m_clipper = SVGResourceClipper::create(); else m_clipper->resetClipData(); bool bbox = clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; RenderStyle* clipPathStyle = styleForRenderer(parent()->renderer()); // FIXME: Manual style resolution is a hack for (Node* n = firstChild(); n; n = n->nextSibling()) { if (n->isSVGElement() && static_cast<SVGElement*>(n)->isStyledTransformable()) { SVGStyledTransformableElement* styled = static_cast<SVGStyledTransformableElement*>(n); RenderStyle* pathStyle = document()->styleSelector()->styleForElement(styled, clipPathStyle); if (pathStyle->display() != NONE) { Path pathData = styled->toClipPath(); // FIXME: How do we know the element has done a layout? pathData.transform(styled->animatedLocalTransform()); if (!pathData.isEmpty()) m_clipper->addClipData(pathData, pathStyle->svgStyle()->clipRule(), bbox); } pathStyle->deref(document()->renderArena()); } } if (m_clipper->clipData().isEmpty()) { Path pathData; pathData.addRect(FloatRect()); m_clipper->addClipData(pathData, RULE_EVENODD, bbox); } clipPathStyle->deref(document()->renderArena()); return m_clipper.get(); }
// FIXME: This does not belong here. AffineTransform RenderSVGResourceContainer::transformOnNonScalingStroke(RenderObject* object, const AffineTransform& resourceTransform) { if (!object->isSVGShape()) return resourceTransform; SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(object->node()); AffineTransform transform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate); transform *= resourceTransform; return transform; }
bool RenderSVGResourceClipper::pathOnlyClipping(GraphicsContext* context, const AffineTransform& animatedLocalTransform, const FloatRect& objectBoundingBox) { // If the current clip-path gets clipped itself, we have to fallback to masking. if (!style()->svgStyle()->clipperResource().isEmpty()) return false; WindRule clipRule = RULE_NONZERO; Path clipPath = Path(); // If clip-path only contains one visible shape or path, we can use path-based clipping. Invisible // shapes don't affect the clipping and can be ignored. If clip-path contains more than one // visible shape, the additive clipping may not work, caused by the clipRule. EvenOdd // as well as NonZero can cause self-clipping of the elements. // See also http://www.w3.org/TR/SVG/painting.html#FillRuleProperty for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { RenderObject* renderer = childNode->renderer(); if (!renderer) continue; // Only shapes or paths are supported for direct clipping. We need to fallback to masking for texts. if (renderer->isSVGText()) return false; if (!childNode->isSVGElement() || !toSVGElement(childNode)->isStyledTransformable()) continue; SVGStyledTransformableElement* styled = toSVGStyledTransformableElement(childNode); RenderStyle* style = renderer->style(); if (!style || style->display() == NONE || style->visibility() != VISIBLE) continue; const SVGRenderStyle* svgStyle = style->svgStyle(); // Current shape in clip-path gets clipped too. Fallback to masking. if (!svgStyle->clipperResource().isEmpty()) return false; // Fallback to masking, if there is more than one clipping path. if (clipPath.isEmpty()) { styled->toClipPath(clipPath); clipRule = svgStyle->clipRule(); } else return false; } // Only one visible shape/path was found. Directly continue clipping and transform the content to userspace if necessary. if (static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { AffineTransform transform; transform.translate(objectBoundingBox.x(), objectBoundingBox.y()); transform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); clipPath.transform(transform); } // Transform path by animatedLocalTransform. clipPath.transform(animatedLocalTransform); // The SVG specification wants us to clip everything, if clip-path doesn't have a child. if (clipPath.isEmpty()) clipPath.addRect(FloatRect()); context->clipPath(clipPath, clipRule); return true; }
void RenderPath::layout() { LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout()); SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); m_localTransform = element->animatedLocalTransform(); setPath(element->toPathData()); repainter.repaintAfterLayout(); setNeedsLayout(false); }
bool RenderSVGResourceClipper::applyResource(RenderObject* object, GraphicsContext* context) { ASSERT(object); ASSERT(context); m_clipper.add(object); context->beginPath(); AffineTransform obbTransform; FloatRect objectBoundingBox = object->objectBoundingBox(); bool bbox = static_cast<SVGClipPathElement*>(node())->clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX; if (bbox) { obbTransform.translate(objectBoundingBox.x(), objectBoundingBox.y()); obbTransform.scaleNonUniform(objectBoundingBox.width(), objectBoundingBox.height()); } bool hasClipPath = false; WindRule clipRule = RULE_EVENODD; for (Node* childNode = node()->firstChild(); childNode; childNode = childNode->nextSibling()) { if (!childNode->isSVGElement() || !static_cast<SVGElement*>(childNode)->isStyledTransformable()) continue; SVGStyledTransformableElement* styled = static_cast<SVGStyledTransformableElement*>(childNode); RenderStyle* style = styled->renderer() ? styled->renderer()->style() : 0; if (!style || style->display() == NONE) continue; Path pathData = styled->toClipPath(); if (pathData.isEmpty()) continue; if (bbox) pathData.transform(obbTransform); hasClipPath = true; context->addPath(pathData); clipRule = style->svgStyle()->clipRule(); } if (!hasClipPath) { Path clipPath; clipPath.addRect(FloatRect()); context->addPath(clipPath); } // FIXME! // We don't currently allow for heterogenous clip rules. // we would have to detect such, draw to a mask, and then clip // to that mask context->clipPath(clipRule); return true; }
void RenderSVGPath::fillAndStrokePath(GraphicsContext* context) { RenderStyle* style = this->style(); Color fallbackColor; if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(this, style, fallbackColor)) { if (fillPaintingResource->applyResource(this, style, context, ApplyToFillMode)) fillPaintingResource->postApplyResource(this, context, ApplyToFillMode, &m_path); else if (fallbackColor.isValid()) { RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); fallbackResource->setColor(fallbackColor); if (fallbackResource->applyResource(this, style, context, ApplyToFillMode)) fallbackResource->postApplyResource(this, context, ApplyToFillMode, &m_path); } } fallbackColor = Color(); RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(this, style, fallbackColor); if (!strokePaintingResource) return; Path path; bool nonScalingStroke = style->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE; GraphicsContextStateSaver stateSaver(*context, false); if (nonScalingStroke) { SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); AffineTransform nonScalingStrokeTransform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate); if (!nonScalingStrokeTransform.isInvertible()) return; path = m_path; path.transform(nonScalingStrokeTransform); stateSaver.save(); context->concatCTM(nonScalingStrokeTransform.inverse()); } if (strokePaintingResource->applyResource(this, style, context, ApplyToStrokeMode)) strokePaintingResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path); else if (fallbackColor.isValid()) { RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); fallbackResource->setColor(fallbackColor); if (fallbackResource->applyResource(this, style, context, ApplyToStrokeMode)) fallbackResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path); } }
void RenderSVGPath::layout() { LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout()); SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); bool updateCachedBoundariesInParents = false; bool needsPathUpdate = m_needsPathUpdate; if (needsPathUpdate) { m_path.clear(); element->toPathData(m_path); m_needsPathUpdate = false; updateCachedBoundariesInParents = true; } if (m_needsTransformUpdate) { m_localTransform = element->animatedLocalTransform(); m_needsTransformUpdate = false; updateCachedBoundariesInParents = true; } if (m_needsBoundariesUpdate) updateCachedBoundariesInParents = true; // Invalidate all resources of this client if our layout changed. if (m_everHadLayout && selfNeedsLayout()) { SVGResourcesCache::clientLayoutChanged(this); m_markerLayoutInfo.clear(); } // At this point LayoutRepainter already grabbed the old bounds, // recalculate them now so repaintAfterLayout() uses the new bounds. if (needsPathUpdate || m_needsBoundariesUpdate) { updateCachedBoundaries(); m_needsBoundariesUpdate = false; } // If our bounds changed, notify the parents. if (updateCachedBoundariesInParents) RenderSVGModelObject::setNeedsBoundariesUpdate(); repainter.repaintAfterLayout(); setNeedsLayout(false); }
bool RenderSVGTransformableContainer::calculateLocalTransform() { SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); // If we're either the renderer for a <use> element, or for any <g> element inside the shadow // tree, that was created during the use/symbol/svg expansion in SVGUseElement. These containers // need to respect the translations induced by their corresponding use elements x/y attributes. SVGUseElement* useElement = 0; if (element->hasTagName(SVGNames::useTag)) useElement = static_cast<SVGUseElement*>(element); else if (element->isInShadowTree() && element->hasTagName(SVGNames::gTag)) { SVGElement* correspondingElement = element->correspondingElement(); if (correspondingElement && correspondingElement->hasTagName(SVGNames::useTag)) useElement = static_cast<SVGUseElement*>(correspondingElement); } if (useElement) { SVGLengthContext lengthContext(useElement); FloatSize translation(useElement->x().value(lengthContext), useElement->y().value(lengthContext)); if (translation != m_lastTranslation) m_needsTransformUpdate = true; m_lastTranslation = translation; } m_didTransformToRootUpdate = m_needsTransformUpdate || SVGRenderSupport::transformToRootChanged(parent()); if (!m_needsTransformUpdate) return false; m_localTransform = element->animatedLocalTransform(); m_localTransform.translate(m_lastTranslation.width(), m_lastTranslation.height()); m_needsTransformUpdate = false; return true; }
void SVGAnimateMotionElement::applyAnimatedValueToElement() { if (!targetElement()->isStyledTransformable()) return; SVGStyledTransformableElement* transformableElement = static_cast<SVGStyledTransformableElement*>(targetElement()); RefPtr<SVGTransformList> transformList = transformableElement->transform(); if (!transformList) return; ExceptionCode ec; if (!isAdditive()) transformList->clear(ec); AffineTransform transform; transform.rotate(m_animatedAngle); transform.translate(m_animatedTranslation.width(), m_animatedTranslation.height()); if (!transform.isIdentity()) { transformList->appendItem(SVGTransform(transform), ec); transformableElement->updateLocalTransform(transformList.get()); } }
void RenderPath::fillAndStrokePath(GraphicsContext* context) { context->beginPath(); RenderStyle* style = this->style(); if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(this, style)) { context->addPath(m_path); if (fillPaintingResource->applyResource(this, style, context, ApplyToFillMode)) fillPaintingResource->postApplyResource(this, context, ApplyToFillMode); } RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(this, style); if (!strokePaintingResource) return; bool restoreContext = false; if (style->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE) { SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); AffineTransform nonScalingStrokeTransform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate); if (!nonScalingStrokeTransform.isInvertible()) return; Path transformedPath = m_path; transformedPath.transform(nonScalingStrokeTransform); context->save(); context->concatCTM(nonScalingStrokeTransform.inverse()); context->addPath(transformedPath); restoreContext = true; } else context->addPath(m_path); if (strokePaintingResource->applyResource(this, style, context, ApplyToStrokeMode)) strokePaintingResource->postApplyResource(this, context, ApplyToStrokeMode); if (restoreContext) context->restore(); }
bool RenderSVGTransformableContainer::calculateLocalTransform() { SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); bool needsUpdate = m_needsTransformUpdate; if (needsUpdate) { m_localTransform = element->animatedLocalTransform(); m_needsTransformUpdate = false; } if (!element->hasTagName(SVGNames::gTag) || !static_cast<SVGGElement*>(element)->isShadowTreeContainerElement()) return needsUpdate; FloatSize translation = static_cast<SVGShadowTreeContainerElement*>(element)->containerTranslation(); if (translation.width() == 0 && translation.height() == 0) return needsUpdate; // FIXME: Could optimize this case for use to avoid refetching the animatedLocalTransform() here, if only the containerTranslation() changed. if (!needsUpdate) m_localTransform = element->animatedLocalTransform(); m_localTransform.translate(translation.width(), translation.height()); return true; }