LayoutUnit RenderSVGRoot::computeReplacedLogicalHeight() const { // When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size. if (!m_containerSize.isEmpty()) return m_containerSize.height(); if (style()->logicalHeight().isSpecified() || style()->logicalMaxHeight().isSpecified()) return RenderReplaced::computeReplacedLogicalHeight(); if (svgSVGElement().heightAttributeEstablishesViewport()) { Length height = svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties); if (height.isPercent()) { RenderBlock* cb = containingBlock(); ASSERT(cb); while (cb->isAnonymous() && !cb->isRenderView()) { cb = cb->containingBlock(); cb->addPercentHeightDescendant(const_cast<RenderSVGRoot&>(*this)); } } else RenderBlock::removePercentHeightDescendant(const_cast<RenderSVGRoot&>(*this)); return resolveLengthAttributeForSVG(height, style()->effectiveZoom(), containingBlock()->availableLogicalHeight(IncludeMarginBorderPadding), &view()); } // SVG embedded through object/embed/iframe. if (isEmbeddedThroughFrameContainingSVGDocument()) return frame().ownerRenderer()->availableLogicalHeight(IncludeMarginBorderPadding); // SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG. return RenderReplaced::computeReplacedLogicalHeight(); }
void RenderSVGRoot::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio) const { // Spec: http://www.w3.org/TR/SVG/coords.html#IntrinsicSizing // SVG needs to specify how to calculate some intrinsic sizing properties to enable inclusion within other languages. // The intrinsic aspect ratio of the viewport of SVG content is necessary for example, when including SVG from an ‘object’ // element in HTML styled with CSS. It is possible (indeed, common) for an SVG graphic to have an intrinsic aspect ratio but // not to have an intrinsic width or height. The intrinsic aspect ratio must be calculated based upon the following rules: // - The aspect ratio is calculated by dividing a width by a height. // - If the ‘width’ and ‘height’ of the rootmost ‘svg’ element are both specified with unit identifiers (in, mm, cm, pt, pc, // px, em, ex) or in user units, then the aspect ratio is calculated from the ‘width’ and ‘height’ attributes after // resolving both values to user units. intrinsicSize.setWidth(floatValueForLength(svgSVGElement().intrinsicWidth(), 0)); intrinsicSize.setHeight(floatValueForLength(svgSVGElement().intrinsicHeight(), 0)); if (!intrinsicSize.isEmpty()) intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height()); else { // - If either/both of the ‘width’ and ‘height’ of the rootmost ‘svg’ element are in percentage units (or omitted), the // aspect ratio is calculated from the width and height values of the ‘viewBox’ specified for the current SVG document // fragment. If the ‘viewBox’ is not correctly specified, or set to 'none', the intrinsic aspect ratio cannot be // calculated and is considered unspecified. FloatSize viewBoxSize = svgSVGElement().viewBox().size(); if (!viewBoxSize.isEmpty()) { // The viewBox can only yield an intrinsic ratio, not an intrinsic size. intrinsicRatio = viewBoxSize.width() / static_cast<double>(viewBoxSize.height()); } } }
// RenderBox methods will expect coordinates w/o any transforms in coordinates // relative to our borderBox origin. This method gives us exactly that. void RenderSVGRoot::buildLocalToBorderBoxTransform() { float scale = style()->effectiveZoom(); SVGPoint translate = svgSVGElement().currentTranslate(); LayoutSize borderAndPadding(borderLeft() + paddingLeft(), borderTop() + paddingTop()); m_localToBorderBoxTransform = svgSVGElement().viewBoxToViewTransform(contentWidth() / scale, contentHeight() / scale); if (borderAndPadding.isEmpty() && scale == 1 && translate == SVGPoint::zero()) return; m_localToBorderBoxTransform = AffineTransform(scale, 0, 0, scale, borderAndPadding.width() + translate.x(), borderAndPadding.height() + translate.y()) * m_localToBorderBoxTransform; }
void RenderSVGRoot::computeIntrinsicRatioInformation(FloatSize& intrinsicSize, double& intrinsicRatio, bool& isPercentageIntrinsicSize) const { // Spec: http://www.w3.org/TR/SVG/coords.html#IntrinsicSizing // SVG needs to specify how to calculate some intrinsic sizing properties to enable inclusion within other languages. // The intrinsic width and height of the viewport of SVG content must be determined from the ‘width’ and ‘height’ attributes. // If either of these are not specified, a value of '100%' must be assumed. Note: the ‘width’ and ‘height’ attributes are not // the same as the CSS width and height properties. Specifically, percentage values do not provide an intrinsic width or height, // and do not indicate a percentage of the containing block. Rather, once the viewport is established, they indicate the portion // of the viewport that is actually covered by image data. Length intrinsicWidthAttribute = svgSVGElement().intrinsicWidth(SVGSVGElement::IgnoreCSSProperties); Length intrinsicHeightAttribute = svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties); // The intrinsic aspect ratio of the viewport of SVG content is necessary for example, when including SVG from an ‘object’ // element in HTML styled with CSS. It is possible (indeed, common) for an SVG graphic to have an intrinsic aspect ratio but // not to have an intrinsic width or height. The intrinsic aspect ratio must be calculated based upon the following rules: // - The aspect ratio is calculated by dividing a width by a height. // - If the ‘width’ and ‘height’ of the rootmost ‘svg’ element are both specified with unit identifiers (in, mm, cm, pt, pc, // px, em, ex) or in user units, then the aspect ratio is calculated from the ‘width’ and ‘height’ attributes after // resolving both values to user units. if (intrinsicWidthAttribute.isFixed() || intrinsicHeightAttribute.isFixed()) { if (intrinsicWidthAttribute.isFixed()) intrinsicSize.setWidth(floatValueForLength(intrinsicWidthAttribute, 0)); if (intrinsicHeightAttribute.isFixed()) intrinsicSize.setHeight(floatValueForLength(intrinsicHeightAttribute, 0)); if (!intrinsicSize.isEmpty()) intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height()); return; } // - If either/both of the ‘width’ and ‘height’ of the rootmost ‘svg’ element are in percentage units (or omitted), the // aspect ratio is calculated from the width and height values of the ‘viewBox’ specified for the current SVG document // fragment. If the ‘viewBox’ is not correctly specified, or set to 'none', the intrinsic aspect ratio cannot be // calculated and is considered unspecified. intrinsicSize = svgSVGElement().viewBox().size(); if (!intrinsicSize.isEmpty()) { // The viewBox can only yield an intrinsic ratio, not an intrinsic size. intrinsicRatio = intrinsicSize.width() / static_cast<double>(intrinsicSize.height()); intrinsicSize = FloatSize(); return; } // If our intrinsic size is in percentage units, return those to the caller through the intrinsicSize. Notify the caller // about the special situation, by setting isPercentageIntrinsicSize=true, so it knows how to interpret the return values. if (intrinsicWidthAttribute.isPercent() && intrinsicHeightAttribute.isPercent()) { isPercentageIntrinsicSize = true; intrinsicSize = FloatSize(intrinsicWidthAttribute.percent(), intrinsicHeightAttribute.percent()); } }
void RenderSVGViewportContainer::calcViewport() { SVGSVGElement& svg = svgSVGElement(); FloatRect oldViewport = m_viewport; SVGLengthContext lengthContext(&svg); m_viewport = FloatRect(svg.x().value(lengthContext), svg.y().value(lengthContext), svg.width().value(lengthContext), svg.height().value(lengthContext)); SVGElement* correspondingElement = svg.correspondingElement(); if (correspondingElement && svg.isInShadowTree()) { const HashSet<SVGElementInstance*>& instances = correspondingElement->instancesForElement(); ASSERT(!instances.isEmpty()); SVGUseElement* useElement = 0; const HashSet<SVGElementInstance*>::const_iterator end = instances.end(); for (HashSet<SVGElementInstance*>::const_iterator it = instances.begin(); it != end; ++it) { const SVGElementInstance* instance = (*it); ASSERT(instance->correspondingElement()->hasTagName(SVGNames::svgTag) || instance->correspondingElement()->hasTagName(SVGNames::symbolTag)); if (instance->shadowTreeElement() == &svg) { ASSERT(correspondingElement == instance->correspondingElement()); useElement = instance->directUseElement(); if (!useElement) useElement = instance->correspondingUseElement(); break; } } ASSERT(useElement); bool isSymbolElement = correspondingElement->hasTagName(SVGNames::symbolTag); // Spec (<use> on <symbol>): This generated 'svg' will always have explicit values for attributes width and height. // If attributes width and/or height are provided on the 'use' element, then these attributes // will be transferred to the generated 'svg'. If attributes width and/or height are not specified, // the generated 'svg' element will use values of 100% for these attributes. // Spec (<use> on <svg>): If attributes width and/or height are provided on the 'use' element, then these // values will override the corresponding attributes on the 'svg' in the generated tree. SVGLengthContext lengthContext(&svg); if (useElement->hasAttribute(SVGNames::widthAttr)) m_viewport.setWidth(useElement->width().value(lengthContext)); else if (isSymbolElement && svg.hasAttribute(SVGNames::widthAttr)) { SVGLength containerWidth(LengthModeWidth, "100%"); m_viewport.setWidth(containerWidth.value(lengthContext)); } if (useElement->hasAttribute(SVGNames::heightAttr)) m_viewport.setHeight(useElement->height().value(lengthContext)); else if (isSymbolElement && svg.hasAttribute(SVGNames::heightAttr)) { SVGLength containerHeight(LengthModeHeight, "100%"); m_viewport.setHeight(containerHeight.value(lengthContext)); } } if (oldViewport != m_viewport) { setNeedsBoundariesUpdate(); setNeedsTransformUpdate(); } }
void RenderSVGViewportContainer::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { // An empty viewBox disables rendering. if (svgSVGElement().hasEmptyViewBox()) return; RenderSVGContainer::paint(paintInfo, paintOffset); }
LayoutUnit RenderSVGRoot::computeReplacedLogicalHeight() const { // When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size. if (!m_containerSize.isEmpty()) return m_containerSize.height(); if (isEmbeddedThroughFrameContainingSVGDocument()) return containingBlock()->availableLogicalHeight(IncludeMarginBorderPadding); if (style().logicalHeight().isSpecified() || style().logicalMaxHeight().isSpecified()) return RenderReplaced::computeReplacedLogicalHeight(); if (svgSVGElement().hasIntrinsicHeight()) return resolveLengthAttributeForSVG(svgSVGElement().intrinsicHeight(), style().effectiveZoom(), containingBlock()->availableLogicalHeight(IncludeMarginBorderPadding).toFloat()); // SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG. return RenderReplaced::computeReplacedLogicalHeight(); }
LayoutUnit RenderSVGRoot::computeReplacedLogicalWidth(ShouldComputePreferred shouldComputePreferred) const { // When we're embedded through SVGImage (border-image/background-image/<html:img>/...) we're forced to resize to a specific size. if (!m_containerSize.isEmpty()) return m_containerSize.width(); if (isEmbeddedThroughFrameContainingSVGDocument()) return containingBlock()->availableLogicalWidth(); if (style().logicalWidth().isSpecified() || style().logicalMaxWidth().isSpecified()) return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred); if (svgSVGElement().hasIntrinsicWidth()) return resolveLengthAttributeForSVG(svgSVGElement().intrinsicWidth(), style().effectiveZoom(), containingBlock()->availableLogicalWidth(), &view()); // SVG embedded via SVGImage (background-image/border-image/etc) / Inline SVG. return RenderReplaced::computeReplacedLogicalWidth(shouldComputePreferred); }
void RenderSVGRoot::layout() { StackStats::LayoutCheckPoint layoutCheckPoint; ASSERT(needsLayout()); m_resourcesNeedingToInvalidateClients.clear(); // Arbitrary affine transforms are incompatible with LayoutState. LayoutStateDisabler layoutStateDisabler(view()); bool needsLayout = selfNeedsLayout(); LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && needsLayout); LayoutSize oldSize = size(); updateLogicalWidth(); updateLogicalHeight(); buildLocalToBorderBoxTransform(); m_isLayoutSizeChanged = needsLayout || (svgSVGElement().hasRelativeLengths() && oldSize != size()); SVGRenderSupport::layoutChildren(*this, needsLayout || SVGRenderSupport::filtersForceContainerLayout(*this)); if (!m_resourcesNeedingToInvalidateClients.isEmpty()) { // Invalidate resource clients, which may mark some nodes for layout. for (auto& resource : m_resourcesNeedingToInvalidateClients) { resource->removeAllClientsFromCache(); SVGResourcesCache::clientStyleChanged(*resource, StyleDifferenceLayout, resource->style()); } m_isLayoutSizeChanged = false; SVGRenderSupport::layoutChildren(*this, false); } // At this point LayoutRepainter already grabbed the old bounds, // recalculate them now so repaintAfterLayout() uses the new bounds. if (m_needsBoundariesOrTransformUpdate) { updateCachedBoundaries(); m_needsBoundariesOrTransformUpdate = false; } clearOverflow(); if (!shouldApplyViewportClip()) { FloatRect contentRepaintRect = repaintRectInLocalCoordinates(); contentRepaintRect = m_localToBorderBoxTransform.mapRect(contentRepaintRect); addVisualOverflow(enclosingLayoutRect(contentRepaintRect)); } updateLayerTransform(); m_hasBoxDecorations = isDocumentElementRenderer() ? hasVisibleBoxDecorationStyle() : hasVisibleBoxDecorations(); invalidateBackgroundObscurationStatus(); repainter.repaintAfterLayout(); clearNeedsLayout(); }
void RenderSVGRoot::layout() { StackStats::LayoutCheckPoint layoutCheckPoint; ASSERT(needsLayout()); m_resourcesNeedingToInvalidateClients.clear(); // Arbitrary affine transforms are incompatible with LayoutState. LayoutStateDisabler layoutStateDisabler(&view()); bool needsLayout = selfNeedsLayout(); LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && needsLayout); LayoutSize oldSize = size(); updateLogicalWidth(); updateLogicalHeight(); buildLocalToBorderBoxTransform(); m_isLayoutSizeChanged = needsLayout || (svgSVGElement().hasRelativeLengths() && oldSize != size()); SVGRenderSupport::layoutChildren(*this, needsLayout || SVGRenderSupport::filtersForceContainerLayout(*this)); if (!m_resourcesNeedingToInvalidateClients.isEmpty()) { // Invalidate resource clients, which may mark some nodes for layout. HashSet<RenderSVGResourceContainer*>::iterator end = m_resourcesNeedingToInvalidateClients.end(); for (HashSet<RenderSVGResourceContainer*>::iterator it = m_resourcesNeedingToInvalidateClients.begin(); it != end; ++it) (*it)->removeAllClientsFromCache(); m_isLayoutSizeChanged = false; SVGRenderSupport::layoutChildren(*this, false); } // At this point LayoutRepainter already grabbed the old bounds, // recalculate them now so repaintAfterLayout() uses the new bounds. if (m_needsBoundariesOrTransformUpdate) { updateCachedBoundaries(); m_needsBoundariesOrTransformUpdate = false; } if (!shouldApplyViewportClip()) { FloatRect contentRepaintRect = repaintRectInLocalCoordinates(); contentRepaintRect = m_localToBorderBoxTransform.mapRect(contentRepaintRect); addVisualOverflow(enclosingLayoutRect(contentRepaintRect)); } updateLayerTransform(); repainter.repaintAfterLayout(); clearNeedsLayout(); }
bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { LayoutPoint pointInParent = locationInContainer.point() - toLayoutSize(accumulatedOffset); LayoutPoint pointInBorderBox = pointInParent - toLayoutSize(location()); // Only test SVG content if the point is in our content box. // FIXME: This should be an intersection when rect-based hit tests are supported by nodeAtFloatPoint. if (contentBoxRect().contains(pointInBorderBox)) { FloatPoint localPoint = localToParentTransform().inverse().mapPoint(FloatPoint(pointInParent)); for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet. if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) { updateHitTestResult(result, pointInBorderBox); if (!result.addNodeToRectBasedTestResult(child->node(), request, locationInContainer)) return true; } } } // If we didn't early exit above, we've just hit the container <svg> element. Unlike SVG 1.1, 2nd Edition allows container elements to be hit. if (hitTestAction == HitTestBlockBackground && visibleToHitTesting()) { // Only return true here, if the last hit testing phase 'BlockBackground' is executed. If we'd return true in the 'Foreground' phase, // hit testing would stop immediately. For SVG only trees this doesn't matter. Though when we have a <foreignObject> subtree we need // to be able to detect hits on the background of a <div> element. If we'd return true here in the 'Foreground' phase, we are not able // to detect these hits anymore. LayoutRect boundsRect(accumulatedOffset + location(), size()); if (locationInContainer.intersects(boundsRect)) { updateHitTestResult(result, pointInBorderBox); if (!result.addNodeToRectBasedTestResult(&svgSVGElement(), request, locationInContainer, boundsRect)) return true; } } return false; }
AffineTransform RenderSVGViewportContainer::viewportTransform() const { return svgSVGElement().viewBoxToViewTransform(m_viewport.width(), m_viewport.height()); }
bool RenderSVGRoot::hasRelativeLogicalHeight() const { return svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties).isPercent(); }
bool RenderSVGRoot::hasRelativeIntrinsicLogicalWidth() const { return svgSVGElement().intrinsicWidth(SVGSVGElement::IgnoreCSSProperties).isPercent(); }
bool RenderSVGRoot::hasRelativeDimensions() const { return svgSVGElement().intrinsicHeight(SVGSVGElement::IgnoreCSSProperties).isPercent() || svgSVGElement().intrinsicWidth(SVGSVGElement::IgnoreCSSProperties).isPercent(); }
void RenderSVGViewportContainer::determineIfLayoutSizeChanged() { m_isLayoutSizeChanged = svgSVGElement().hasRelativeLengths() && selfNeedsLayout(); }
void RenderSVGRoot::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { // An empty viewport disables rendering. if (pixelSnappedBorderBoxRect().isEmpty()) return; // Don't paint, if the context explicitly disabled it. if (paintInfo.context->paintingDisabled()) return; // An empty viewBox also disables rendering. // (http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute) if (svgSVGElement().hasEmptyViewBox()) return; Page* page = frame().page(); // Don't paint if we don't have kids, except if we have filters we should paint those. if (!firstChild()) { SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); if (!resources || !resources->filter()) { if (page && paintInfo.phase == PaintPhaseForeground) page->addRelevantUnpaintedObject(this, visualOverflowRect()); return; } } if (page && paintInfo.phase == PaintPhaseForeground) page->addRelevantRepaintedObject(this, visualOverflowRect()); // Make a copy of the PaintInfo because applyTransform will modify the damage rect. PaintInfo childPaintInfo(paintInfo); childPaintInfo.context->save(); // Apply initial viewport clip - not affected by overflow handling childPaintInfo.context->clip(pixelSnappedIntRect(overflowClipRect(paintOffset, paintInfo.renderRegion))); // Convert from container offsets (html renderers) to a relative transform (svg renderers). // Transform from our paint container's coordinate system to our local coords. IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset); childPaintInfo.applyTransform(AffineTransform::translation(adjustedPaintOffset.x(), adjustedPaintOffset.y()) * localToBorderBoxTransform()); // SVGRenderingContext must be destroyed before we restore the childPaintInfo.context, because a filter may have // changed the context and it is only reverted when the SVGRenderingContext destructor finishes applying the filter. { SVGRenderingContext renderingContext; bool continueRendering = true; if (childPaintInfo.phase == PaintPhaseForeground) { renderingContext.prepareToRenderSVGContent(*this, childPaintInfo); continueRendering = renderingContext.isRenderingPrepared(); } if (continueRendering) { childPaintInfo.updateSubtreePaintRootForChildren(this); for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { // FIXME: Can this ever have RenderText children? if (!child->isRenderElement()) continue; toRenderElement(child)->paint(childPaintInfo, location()); } } } childPaintInfo.context->restore(); }
bool RenderSVGRoot::isEmbeddedThroughSVGImage() const { return isInSVGImage(&svgSVGElement()); }
bool RenderSVGRoot::hasRelativeDimensions() const { return svgSVGElement().intrinsicHeight().isPercentOrCalculated() || svgSVGElement().intrinsicWidth().isPercentOrCalculated(); }