/* * Copyright (C) 2006, 2007 Eric Seidel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "platform/graphics/PathTraversalState.h" #include "wtf/MathExtras.h" #include "wtf/Vector.h" namespace blink { static inline FloatPoint midPoint(const FloatPoint& first, const FloatPoint& second) { return FloatPoint((first.x() + second.x()) / 2.0f, (first.y() + second.y()) / 2.0f); } static inline float distanceLine(const FloatPoint& start, const FloatPoint& end) { return sqrtf((end.x() - start.x()) * (end.x() - start.x()) + (end.y() - start.y()) * (end.y() - start.y())); } struct QuadraticBezier { DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); QuadraticBezier() { } QuadraticBezier(const FloatPoint& s, const FloatPoint& c, const FloatPoint& e) : start(s) , control(c) , end(e) , splitDepth(0) { } double magnitudeSquared() const { return ((double)(start.dot(start)) + (double)(control.dot(control)) + (double)(end.dot(end))) / 9.0; } float approximateDistance() const { return distanceLine(start, control) + distanceLine(control, end); } void split(QuadraticBezier& left, QuadraticBezier& right) const { left.control = midPoint(start, control); right.control = midPoint(control, end); FloatPoint leftControlToRightControl = midPoint(left.control, right.control); left.end = leftControlToRightControl; right.start = leftControlToRightControl; left.start = start; right.end = end; left.splitDepth = right.splitDepth = splitDepth + 1; } FloatPoint start; FloatPoint control; FloatPoint end; unsigned short splitDepth; }; struct CubicBezier { DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); CubicBezier() { } CubicBezier(const FloatPoint& s, const FloatPoint& c1, const FloatPoint& c2, const FloatPoint& e) : start(s) , control1(c1) , control2(c2) , end(e) , splitDepth(0) { } double magnitudeSquared() const { return ((double)(start.dot(start)) + (double)(control1.dot(control1)) + (double)(control2.dot(control2)) + (double)(end.dot(end))) / 16.0; } float approximateDistance() const { return distanceLine(start, control1) + distanceLine(control1, control2) + distanceLine(control2, end); } void split(CubicBezier& left, CubicBezier& right) const { FloatPoint startToControl1 = midPoint(control1, control2); left.start = start; left.control1 = midPoint(start, control1); left.control2 = midPoint(left.control1, startToControl1); right.control2 = midPoint(control2, end); right.control1 = midPoint(right.control2, startToControl1); right.end = end; FloatPoint leftControl2ToRightControl1 = midPoint(left.control2, right.control1); left.end = leftControl2ToRightControl1; right.start = leftControl2ToRightControl1; left.splitDepth = right.splitDepth = splitDepth + 1; } FloatPoint start; FloatPoint control1; FloatPoint control2; FloatPoint end; unsigned short splitDepth; }; template static float curveLength(PathTraversalState& traversalState, CurveType curve) { static const unsigned short curveSplitDepthLimit = 20; static const double pathSegmentLengthToleranceSquared = 1.e-16; double curveScaleForToleranceSquared = curve.magnitudeSquared(); if (curveScaleForToleranceSquared < pathSegmentLengthToleranceSquared) return 0; Vector curveStack; curveStack.append(curve); float totalLength = 0; do { float length = curve.approximateDistance(); double lengthDiscrepancy = length - distanceLine(curve.start, curve.end); if ((lengthDiscrepancy * lengthDiscrepancy) / curveScaleForToleranceSquared > pathSegmentLengthToleranceSquared && curve.splitDepth < curveSplitDepthLimit) { CurveType leftCurve; CurveType rightCurve; curve.split(leftCurve, rightCurve); curve = leftCurve; curveStack.append(rightCurve); } else { totalLength += length; if (traversalState.m_action == PathTraversalState::TraversalPointAtLength || traversalState.m_action == PathTraversalState::TraversalNormalAngleAtLength) { traversalState.m_previous = curve.start; traversalState.m_current = curve.end; if (traversalState.m_totalLength + totalLength > traversalState.m_desiredLength) return totalLength; } curve = curveStack.last(); curveStack.removeLast(); } } while (!curveStack.isEmpty()); return totalLength; } PathTraversalState::PathTraversalState(PathTraversalAction action) : m_action(action) , m_success(false) , m_totalLength(0) , m_desiredLength(0) , m_normalAngle(0) { } float PathTraversalState::closeSubpath() { float distance = distanceLine(m_current, m_start); m_current = m_start; return distance; } float PathTraversalState::moveTo(const FloatPoint& point) { m_current = m_start = point; return 0; } float PathTraversalState::lineTo(const FloatPoint& point) { float distance = distanceLine(m_current, point); m_current = point; return distance; } float PathTraversalState::cubicBezierTo(const FloatPoint& newControl1, const FloatPoint& newControl2, const FloatPoint& newEnd) { float distance = curveLength(*this, CubicBezier(m_current, newControl1, newControl2, newEnd)); if (m_action != TraversalPointAtLength && m_action != TraversalNormalAngleAtLength) m_current = newEnd; return distance; } void PathTraversalState::processSegment() { if (m_action == TraversalSegmentAtLength && m_totalLength >= m_desiredLength) m_success = true; if ((m_action == TraversalPointAtLength || m_action == TraversalNormalAngleAtLength) && m_totalLength >= m_desiredLength) { float slope = FloatPoint(m_current - m_previous).slopeAngleRadians(); if (m_action == TraversalPointAtLength) { float offset = m_desiredLength - m_totalLength; m_current.move(offset * cosf(slope), offset * sinf(slope)); } else { m_normalAngle = rad2deg(slope); } m_success = true; } m_previous = m_current; } } // namespace blink