bool SVGPathParser::parseCurveToQuadraticSmoothSegment() { FloatPoint targetPoint; if (!m_source.parseCurveToQuadraticSmoothSegment(targetPoint)) return false; if (m_lastCommand != PathSegCurveToQuadraticAbs && m_lastCommand != PathSegCurveToQuadraticRel && m_lastCommand != PathSegCurveToQuadraticSmoothAbs && m_lastCommand != PathSegCurveToQuadraticSmoothRel) m_controlPoint = m_currentPoint; if (m_pathParsingMode == NormalizedParsing) { FloatPoint cubicPoint = m_currentPoint; cubicPoint.scale(2); cubicPoint.move(-m_controlPoint.x(), -m_controlPoint.y()); FloatPoint point1(m_currentPoint.x() + 2 * cubicPoint.x(), m_currentPoint.y() + 2 * cubicPoint.y()); FloatPoint point2(targetPoint.x() + 2 * cubicPoint.x(), targetPoint.y() + 2 * cubicPoint.y()); if (m_mode == RelativeCoordinates) { point2 += m_currentPoint; targetPoint += m_currentPoint; } point1.scale(gOneOverThree); point2.scale(gOneOverThree); m_consumer.curveToCubic(point1, point2, targetPoint, AbsoluteCoordinates); m_controlPoint = cubicPoint; m_currentPoint = targetPoint; } else m_consumer.curveToQuadraticSmooth(targetPoint, m_mode); return true; }
FloatPoint SVGPathBlender::blendAnimatedFloatPoint(const FloatPoint& fromPoint, const FloatPoint& toPoint) { if (m_addTypesCount) { ASSERT(m_fromMode == m_toMode); FloatPoint repeatedToPoint = toPoint; repeatedToPoint.scale(m_addTypesCount, m_addTypesCount); return fromPoint + repeatedToPoint; } if (m_fromMode == m_toMode) return blendFloatPoint(fromPoint, toPoint, m_progress); // Transform toPoint to the coordinate mode of fromPoint FloatPoint animatedPoint = toPoint; if (m_fromMode == AbsoluteCoordinates) animatedPoint += m_toCurrentPoint; else animatedPoint.move(-m_toCurrentPoint.x(), -m_toCurrentPoint.y()); animatedPoint = blendFloatPoint(fromPoint, animatedPoint, m_progress); if (m_isInFirstHalfOfAnimation) return animatedPoint; // Transform the animated point to the coordinate mode, needed for the current progress. FloatPoint currentPoint = blendFloatPoint(m_fromCurrentPoint, m_toCurrentPoint, m_progress); if (m_toMode == AbsoluteCoordinates) return animatedPoint + currentPoint; animatedPoint.move(-currentPoint.x(), -currentPoint.y()); return animatedPoint; }
FloatPoint VisualViewport::viewportToRootFrame(const FloatPoint& pointInViewport) const { FloatPoint pointInRootFrame = pointInViewport; pointInRootFrame.scale(1 / scale(), 1 / scale()); pointInRootFrame.moveBy(location()); return pointInRootFrame; }
FloatPoint VisualViewport::rootFrameToViewport(const FloatPoint& pointInRootFrame) const { FloatPoint pointInViewport = pointInRootFrame; pointInViewport.moveBy(-location()); pointInViewport.scale(scale(), scale()); return pointInViewport; }
FloatPoint VisualViewport::rootFrameToViewport( const FloatPoint& pointInRootFrame) const { FloatPoint pointInViewport = pointInRootFrame; pointInViewport.move(-getScrollOffset()); pointInViewport.scale(scale(), scale()); return pointInViewport; }
FloatPoint VisualViewport::viewportToRootFrame( const FloatPoint& pointInViewport) const { FloatPoint pointInRootFrame = pointInViewport; pointInRootFrame.scale(1 / scale(), 1 / scale()); pointInRootFrame.move(getScrollOffset()); return pointInRootFrame; }
FloatPoint SVGPathBlender::BlendState::blendAnimatedFloatPointSameCoordinates(const FloatPoint& fromPoint, const FloatPoint& toPoint) { if (m_addTypesCount) { FloatPoint repeatedToPoint = toPoint; repeatedToPoint.scale(m_addTypesCount, m_addTypesCount); return fromPoint + repeatedToPoint; } return blendFloatPoint(fromPoint, toPoint, m_progress); }
AffineTransform WebView::transformToScene() const { FloatPoint position = -m_contentPosition; float effectiveScale = contentScaleFactor() * m_page->deviceScaleFactor(); position.scale(effectiveScale, effectiveScale); TransformationMatrix transform = m_userViewportTransform; transform.translate(position.x(), position.y()); transform.scale(effectiveScale); return transform.toAffineTransform(); }
AffineTransform SVGSVGElement::localCoordinateSpaceTransform( SVGElement::CTMScope mode) const { AffineTransform viewBoxTransform; if (!hasEmptyViewBox()) { FloatSize size = currentViewportSize(); viewBoxTransform = viewBoxToViewTransform(size.width(), size.height()); } AffineTransform transform; if (!isOutermostSVGSVGElement()) { SVGLengthContext lengthContext(this); transform.translate(m_x->currentValue()->value(lengthContext), m_y->currentValue()->value(lengthContext)); } else if (mode == SVGElement::ScreenScope) { if (LayoutObject* layoutObject = this->layoutObject()) { FloatPoint location; float zoomFactor = 1; // At the SVG/HTML boundary (aka LayoutSVGRoot), we apply the // localToBorderBoxTransform to map an element from SVG viewport // coordinates to CSS box coordinates. LayoutSVGRoot's localToAbsolute // method expects CSS box coordinates. We also need to adjust for the // zoom level factored into CSS coordinates (bug #96361). if (layoutObject->isSVGRoot()) { location = toLayoutSVGRoot(layoutObject) ->localToBorderBoxTransform() .mapPoint(location); zoomFactor = 1 / layoutObject->style()->effectiveZoom(); } // Translate in our CSS parent coordinate space // FIXME: This doesn't work correctly with CSS transforms. location = layoutObject->localToAbsolute(location, UseTransforms); location.scale(zoomFactor, zoomFactor); // Be careful here! localToBorderBoxTransform() included the x/y offset // coming from the viewBoxToViewTransform(), so we have to subtract it // here (original cause of bug #27183) transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f()); // Respect scroll offset. if (FrameView* view = document().view()) { LayoutSize scrollOffset(view->getScrollOffset()); scrollOffset.scale(zoomFactor); transform.translate(-scrollOffset.width(), -scrollOffset.height()); } } } return transform.multiply(viewBoxTransform); }
AffineTransform SVGSVGElement::localCoordinateSpaceTransform(SVGLocatable::CTMScope mode) const { AffineTransform viewBoxTransform; if (!hasEmptyViewBox()) { FloatSize size = currentViewportSize(); viewBoxTransform = viewBoxToViewTransform(size.width(), size.height()); } AffineTransform transform; if (!isOutermostSVGSVGElement()) { SVGLengthContext lengthContext(this); transform.translate(x().value(lengthContext), y().value(lengthContext)); } else if (mode == SVGLocatable::ScreenScope) { if (auto* renderer = this->renderer()) { FloatPoint location; float zoomFactor = 1; // At the SVG/HTML boundary (aka RenderSVGRoot), we apply the localToBorderBoxTransform // to map an element from SVG viewport coordinates to CSS box coordinates. // RenderSVGRoot's localToAbsolute method expects CSS box coordinates. // We also need to adjust for the zoom level factored into CSS coordinates (bug #96361). if (is<RenderSVGRoot>(*renderer)) { location = downcast<RenderSVGRoot>(*renderer).localToBorderBoxTransform().mapPoint(location); zoomFactor = 1 / renderer->style().effectiveZoom(); } // Translate in our CSS parent coordinate space // FIXME: This doesn't work correctly with CSS transforms. location = renderer->localToAbsolute(location, UseTransforms); location.scale(zoomFactor); // Be careful here! localToBorderBoxTransform() included the x/y offset coming from the viewBoxToViewTransform(), // so we have to subtract it here (original cause of bug #27183) transform.translate(location.x() - viewBoxTransform.e(), location.y() - viewBoxTransform.f()); // Respect scroll offset. if (FrameView* view = document().view()) { LayoutPoint scrollPosition = view->scrollPosition(); scrollPosition.scale(zoomFactor); transform.translate(-scrollPosition.x(), -scrollPosition.y()); } } } return transform.multiply(viewBoxTransform); }
bool SVGPathBlender::blendArcToSegment() { float fromRx = 0; float fromRy = 0; float fromAngle = 0; bool fromLargeArc = false; bool fromSweep = false; FloatPoint fromTargetPoint; float toRx = 0; float toRy = 0; float toAngle = 0; bool toLargeArc = false; bool toSweep = false; FloatPoint toTargetPoint; if ((m_fromSource->hasMoreData() && !m_fromSource->parseArcToSegment(fromRx, fromRy, fromAngle, fromLargeArc, fromSweep, fromTargetPoint)) || !m_toSource->parseArcToSegment(toRx, toRy, toAngle, toLargeArc, toSweep, toTargetPoint)) return false; if (m_addTypesCount) { ASSERT(m_fromMode == m_toMode); FloatPoint scaledToTargetPoint = toTargetPoint; scaledToTargetPoint.scale(m_addTypesCount, m_addTypesCount); m_consumer->arcTo(fromRx + toRx * m_addTypesCount, fromRy + toRy * m_addTypesCount, fromAngle + toAngle * m_addTypesCount, fromLargeArc || toLargeArc, fromSweep || toSweep, fromTargetPoint + scaledToTargetPoint, m_fromMode); } else { m_consumer->arcTo(blend(fromRx, toRx, m_progress), blend(fromRy, toRy, m_progress), blend(fromAngle, toAngle, m_progress), m_isInFirstHalfOfAnimation ? fromLargeArc : toLargeArc, m_isInFirstHalfOfAnimation ? fromSweep : toSweep, blendAnimatedFloatPoint(fromTargetPoint, toTargetPoint), m_isInFirstHalfOfAnimation ? m_fromMode : m_toMode); } m_fromCurrentPoint = m_fromMode == AbsoluteCoordinates ? fromTargetPoint : m_fromCurrentPoint + fromTargetPoint; m_toCurrentPoint = m_toMode == AbsoluteCoordinates ? toTargetPoint : m_toCurrentPoint + toTargetPoint; return true; }
HitTestCanvasResult* CanvasRenderingContext2D::getControlAndIdIfHitRegionExists( const LayoutPoint& location) { if (hitRegionsCount() <= 0) return HitTestCanvasResult::create(String(), nullptr); LayoutBox* box = canvas()->layoutBox(); FloatPoint localPos = box->absoluteToLocal(FloatPoint(location), UseTransforms); if (box->hasBorderOrPadding()) localPos.move(-box->contentBoxOffset()); localPos.scale(canvas()->width() / box->contentWidth(), canvas()->height() / box->contentHeight()); HitRegion* hitRegion = hitRegionAtPoint(localPos); if (hitRegion) { Element* control = hitRegion->control(); if (control && canvas()->isSupportedInteractiveCanvasFallback(*control)) return HitTestCanvasResult::create(hitRegion->id(), hitRegion->control()); return HitTestCanvasResult::create(hitRegion->id(), nullptr); } return HitTestCanvasResult::create(String(), nullptr); }
// This works by converting the SVG arc to "simple" beziers. // Partly adapted from Niko's code in kdelibs/kdecore/svgicons. // See also SVG implementation notes: http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter bool SVGPathParser::decomposeArcToCubic(float angle, float rx, float ry, FloatPoint& point1, FloatPoint& point2, bool largeArcFlag, bool sweepFlag) { FloatSize midPointDistance = point1 - point2; midPointDistance.scale(0.5f); AffineTransform pointTransform; pointTransform.rotate(-angle); FloatPoint transformedMidPoint = pointTransform.mapPoint(FloatPoint(midPointDistance.width(), midPointDistance.height())); float squareRx = rx * rx; float squareRy = ry * ry; float squareX = transformedMidPoint.x() * transformedMidPoint.x(); float squareY = transformedMidPoint.y() * transformedMidPoint.y(); // Check if the radii are big enough to draw the arc, scale radii if not. // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii float radiiScale = squareX / squareRx + squareY / squareRy; if (radiiScale > 1) { rx *= sqrtf(radiiScale); ry *= sqrtf(radiiScale); } pointTransform.makeIdentity(); pointTransform.scale(1 / rx, 1 / ry); pointTransform.rotate(-angle); point1 = pointTransform.mapPoint(point1); point2 = pointTransform.mapPoint(point2); FloatSize delta = point2 - point1; float d = delta.width() * delta.width() + delta.height() * delta.height(); float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f); float scaleFactor = sqrtf(scaleFactorSquared); if (sweepFlag == largeArcFlag) scaleFactor = -scaleFactor; delta.scale(scaleFactor); FloatPoint centerPoint = point1 + point2; centerPoint.scale(0.5f); centerPoint.move(-delta.height(), delta.width()); float theta1 = FloatPoint(point1 - centerPoint).slopeAngleRadians(); float theta2 = FloatPoint(point2 - centerPoint).slopeAngleRadians(); float thetaArc = theta2 - theta1; if (thetaArc < 0 && sweepFlag) thetaArc += 2 * piFloat; else if (thetaArc > 0 && !sweepFlag) thetaArc -= 2 * piFloat; pointTransform.makeIdentity(); pointTransform.rotate(angle); pointTransform.scale(rx, ry); // Some results of atan2 on some platform implementations are not exact enough. So that we get more // cubic curves than expected here. Adding 0.001f reduces the count of sgements to the correct count. int segments = ceilf(fabsf(thetaArc / (piOverTwoFloat + 0.001f))); for (int i = 0; i < segments; ++i) { float startTheta = theta1 + i * thetaArc / segments; float endTheta = theta1 + (i + 1) * thetaArc / segments; float t = (8 / 6.f) * tanf(0.25f * (endTheta - startTheta)); if (!std::isfinite(t)) return false; float sinStartTheta = sinf(startTheta); float cosStartTheta = cosf(startTheta); float sinEndTheta = sinf(endTheta); float cosEndTheta = cosf(endTheta); point1 = FloatPoint(cosStartTheta - t * sinStartTheta, sinStartTheta + t * cosStartTheta); point1.move(centerPoint.x(), centerPoint.y()); FloatPoint targetPoint = FloatPoint(cosEndTheta, sinEndTheta); targetPoint.move(centerPoint.x(), centerPoint.y()); point2 = targetPoint; point2.move(t * sinEndTheta, -t * cosEndTheta); m_consumer.curveToCubic(pointTransform.mapPoint(point1), pointTransform.mapPoint(point2), pointTransform.mapPoint(targetPoint), AbsoluteCoordinates); } return true; }
// This works by converting the SVG arc to "simple" beziers. // Partly adapted from Niko's code in kdelibs/kdecore/svgicons. // See also SVG implementation notes: // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter bool SVGPathNormalizer::decomposeArcToCubic(const FloatPoint& currentPoint, const PathSegmentData& arcSegment) { // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a // "lineto") joining the endpoints. // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters float rx = fabsf(arcSegment.arcRadii().x()); float ry = fabsf(arcSegment.arcRadii().y()); if (!rx || !ry) return false; // If the current point and target point for the arc are identical, it should // be treated as a zero length path. This ensures continuity in animations. if (arcSegment.targetPoint == currentPoint) return false; float angle = arcSegment.arcAngle(); FloatSize midPointDistance = currentPoint - arcSegment.targetPoint; midPointDistance.scale(0.5f); AffineTransform pointTransform; pointTransform.rotate(-angle); FloatPoint transformedMidPoint = pointTransform.mapPoint( FloatPoint(midPointDistance.width(), midPointDistance.height())); float squareRx = rx * rx; float squareRy = ry * ry; float squareX = transformedMidPoint.x() * transformedMidPoint.x(); float squareY = transformedMidPoint.y() * transformedMidPoint.y(); // Check if the radii are big enough to draw the arc, scale radii if not. // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii float radiiScale = squareX / squareRx + squareY / squareRy; if (radiiScale > 1) { rx *= sqrtf(radiiScale); ry *= sqrtf(radiiScale); } pointTransform.makeIdentity(); pointTransform.scale(1 / rx, 1 / ry); pointTransform.rotate(-angle); FloatPoint point1 = pointTransform.mapPoint(currentPoint); FloatPoint point2 = pointTransform.mapPoint(arcSegment.targetPoint); FloatSize delta = point2 - point1; float d = delta.width() * delta.width() + delta.height() * delta.height(); float scaleFactorSquared = std::max(1 / d - 0.25f, 0.f); float scaleFactor = sqrtf(scaleFactorSquared); if (arcSegment.arcSweep == arcSegment.arcLarge) scaleFactor = -scaleFactor; delta.scale(scaleFactor); FloatPoint centerPoint = point1 + point2; centerPoint.scale(0.5f, 0.5f); centerPoint.move(-delta.height(), delta.width()); float theta1 = FloatPoint(point1 - centerPoint).slopeAngleRadians(); float theta2 = FloatPoint(point2 - centerPoint).slopeAngleRadians(); float thetaArc = theta2 - theta1; if (thetaArc < 0 && arcSegment.arcSweep) thetaArc += twoPiFloat; else if (thetaArc > 0 && !arcSegment.arcSweep) thetaArc -= twoPiFloat; pointTransform.makeIdentity(); pointTransform.rotate(angle); pointTransform.scale(rx, ry); // Some results of atan2 on some platform implementations are not exact // enough. So that we get more cubic curves than expected here. Adding 0.001f // reduces the count of sgements to the correct count. int segments = ceilf(fabsf(thetaArc / (piOverTwoFloat + 0.001f))); for (int i = 0; i < segments; ++i) { float startTheta = theta1 + i * thetaArc / segments; float endTheta = theta1 + (i + 1) * thetaArc / segments; float t = (8 / 6.f) * tanf(0.25f * (endTheta - startTheta)); if (!std::isfinite(t)) return false; float sinStartTheta = sinf(startTheta); float cosStartTheta = cosf(startTheta); float sinEndTheta = sinf(endTheta); float cosEndTheta = cosf(endTheta); point1 = FloatPoint(cosStartTheta - t * sinStartTheta, sinStartTheta + t * cosStartTheta); point1.move(centerPoint.x(), centerPoint.y()); FloatPoint targetPoint = FloatPoint(cosEndTheta, sinEndTheta); targetPoint.move(centerPoint.x(), centerPoint.y()); point2 = targetPoint; point2.move(t * sinEndTheta, -t * cosEndTheta); PathSegmentData cubicSegment; cubicSegment.command = PathSegCurveToCubicAbs; cubicSegment.point1 = pointTransform.mapPoint(point1); cubicSegment.point2 = pointTransform.mapPoint(point2); cubicSegment.targetPoint = pointTransform.mapPoint(targetPoint); m_consumer->emitSegment(cubicSegment); } return true; }