Diff
Modified: trunk/PerformanceTests/ChangeLog (182827 => 182828)
--- trunk/PerformanceTests/ChangeLog 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/PerformanceTests/ChangeLog 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,3 +1,17 @@
+2015-04-14 Said Abou-Hallawa <sabouhall...@apple.com>
+
+ textPath layout performance improvement.
+ https://bugs.webkit.org/show_bug.cgi?id=141570.
+
+ Reviewed by Darin Adler.
+
+ Cut down the time spent in traversing the path for text by 50%. Instead
+ of traversing the path twice at a certain length: one time for the position
+ and the second time for the angle, we can merge these two passes into one.
+
+ * SVG/TextOnPathSimple.html: Added.
+ * SVG/resources/TextOnPathSimple.svg: Added.
+
2015-04-13 Zalan Bujtas <za...@apple.com>
Clear up the test content when test is done.
Added: trunk/PerformanceTests/SVG/TextOnPathSimple.html (0 => 182828)
--- trunk/PerformanceTests/SVG/TextOnPathSimple.html (rev 0)
+++ trunk/PerformanceTests/SVG/TextOnPathSimple.html 2015-04-15 01:34:25 UTC (rev 182828)
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<body>
+<script src=""
+<script>
+ window._onload_ = function() {
+ PerfTestRunner.measurePageLoadTime({path: "resources/TextOnPathSimple.svg"});
+ }
+</script>
+</body>
Added: trunk/PerformanceTests/SVG/resources/TextOnPathSimple.svg (0 => 182828)
--- trunk/PerformanceTests/SVG/resources/TextOnPathSimple.svg (rev 0)
+++ trunk/PerformanceTests/SVG/resources/TextOnPathSimple.svg 2015-04-15 01:34:25 UTC (rev 182828)
@@ -0,0 +1,42 @@
+<svg width="100%" height="100%" viewBox="0 0 4000 2500"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <path id="SimplePath"
+ d="M 100 200
+ C 200 100 300 0 400 100
+ C 500 200 600 300 700 200
+ C 800 100 900 0 1000 100
+ C 1100 200 1200 300 1300 200
+ C 1400 100 1500 0 1600 100
+ C 1700 200 1800 300 1900 200"/>
+ </defs>
+
+ <g id="TextOnPath25">
+ <g id="TextOnPath5">
+ <g id="TextOnPath">
+ <use xlink:href="" fill="none" stroke="red"/>
+ <text font-family="Verdana" font-size="42.5">
+ <textPath xlink:href=""
+ We go up, then we go down, then up again, then we go down, then we up again, then we go down.
+ </textPath>
+ </text>
+ </g>
+ <use xlink:href="" transform="translate(0,40)"/>
+ <use xlink:href="" transform="translate(0,80)"/>
+ <use xlink:href="" transform="translate(0,120)"/>
+ <use xlink:href="" transform="translate(0,160)"/>
+ </g>
+
+ <use xlink:href="" transform="translate(0,200)"/>
+ <use xlink:href="" transform="translate(0,400)"/>
+ <use xlink:href="" transform="translate(0,600)"/>
+ <use xlink:href="" transform="translate(0,800)"/>
+ </g>
+
+ <use xlink:href="" transform="translate(2000,0)"/>
+ <use xlink:href="" transform="translate(0,1200)"/>
+ <use xlink:href="" transform="translate(2000,1200)"/>
+
+ <rect x="1" y="1" width="3999" height="2499" fill="none" stroke="black" stroke-width="2" />
+</svg>
Modified: trunk/Source/WebCore/ChangeLog (182827 => 182828)
--- trunk/Source/WebCore/ChangeLog 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/ChangeLog 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,3 +1,195 @@
+2015-04-14 Said Abou-Hallawa <sabouhall...@apple.com>
+
+ textPath layout performance improvement.
+ https://bugs.webkit.org/show_bug.cgi?id=141570.
+
+ Reviewed by Darin Adler.
+
+ The bottleneck of the text-on-path performance is the position and angle
+ calculations for every single character. If the number of characters is
+ 'n' and the number of path elements is 'm', the total number of processing
+ the path elements is O(2 x n x m). What makes it really worse is, for every
+ curve we keep splitting the curve till the split curve is almost a straight
+ line. The changes we need to do are:
+ 1. Merge the position and the angle traversals in one pass since they are
+ returning info for the same length on the path. There is a degenerate
+ case for the starting point when calculating the angle. The original
+ code was solving this problem by passing an epsilon instead of zero but
+ because traversing the path for position and angle are now merged, we
+ will pass zero for the starting point as is. All we need is to move one
+ step ahead without moving the position. We need the extra step forward
+ to calculate the slope of the path at the starting point.
+ 2. We need to add a new mode to traversing a path. The new mode will take
+ a vector of lengths and returns a vector of arrow vectors. Every arrow
+ vector represents a position and an angle on the path at a certain length.
+ This requires changing the SVGTextLayoutEngine to calculate the lengths
+ of the characters on the curve first and then passing all of them to the
+ path traversal function. Instead of traversing the path for every length,
+ we are going to get the required point and angle from the vector of arrow
+ vectors.
+
+ This patch is addressing the first fix only. The second one will require
+ refactoring the SVGTextLayoutEngine so I am going to address it in a
+ different patch.
+
+ * platform/graphics/Path.cpp:
+ (WebCore::pathLengthApplierFunction): It is cleaner to move the function
+ of this method to PathTraversalState::processPathElement().
+
+ (WebCore::Path::length): Use new enum Action value and access methods.
+
+ (WebCore::Path::traversalStateAtLength): New function which returns the
+ traversalState at a certain length on a path.
+
+ (WebCore::Path::pointAtLength):
+ (WebCore::Path::normalAngleAtLength): Use traversalStateAtLength() to get
+ the traversalState and from it return either the position or the angle.
+
+ * platform/graphics/Path.h: Define traversalStateAtLength().
+
+ * platform/graphics/PathTraversalState.cpp:
+ (WebCore::distanceLine): Code clean up.
+
+ (WebCore::curveLength): Make the setting of m_previous and m_current happens
+ only in this function.
+
+ (WebCore::PathTraversalState::PathTraversalState): Add an optional parameter
+ for the desired length and move the initialization of the other members to
+ the class definition.
+
+ (WebCore::PathTraversalState::closeSubpath):
+ (WebCore::PathTraversalState::moveTo):
+ (WebCore::PathTraversalState::lineTo): Add the distance to the m_totalLength
+ instead of returning it since this is what all the callers were doing.
+
+ (WebCore::PathTraversalState::quadraticBezierTo):
+ (WebCore::PathTraversalState::cubicBezierTo): Add the distance to the
+ m_totalLength. Move the setting of m_previous and m_current to curveLength().
+ Remove unused members m_control1 and m_control2.
+
+ (WebCore::PathTraversalState::processSegment): Deleted.
+ (WebCore::PathTraversalState::finalizeAppendPathElement): Create a new
+ name for the function. Handle the case of the angle at the starting point
+ where m_desiredLength is set to zero. The new flag m_isZeroVector will be
+ set to notify the caller that the next iteration will be the last one and
+ it is only needed for the calculating the angle of a zero vector. m_current
+ should not change by this last iteration.
+
+ (WebCore::PathTraversalState::appendPathElement): This code is moved from
+ pathLengthApplierFunction().
+
+ (WebCore::PathTraversalState::processPathElement): This function is used
+ by the class Path. It is a wrapper for appendPathElement(). If m_isZeroVector
+ is set we append the new element to a copy for the PathTraversalState just
+ to get the angle for the zero vector.
+
+ * platform/graphics/PathTraversalState.h: Change the enum values to not
+ not include the class or the enum class. Make the data members private and
+ expose the needed ones through access methods. Make all the internal methods
+ to be private.
+
+ (WebCore::PathTraversalState::processPathElement): Another wrapper for
+ appendPathElement() which is used by SVGPathTraversalStateBuilder.
+
+ (WebCore::PathTraversalState::action):
+ (WebCore::PathTraversalState::setAction):
+ (WebCore::PathTraversalState::desiredLength):
+ (WebCore::PathTraversalState::setDesiredLength):
+ (WebCore::PathTraversalState::success):
+ (WebCore::PathTraversalState::totalLength):
+ (WebCore::PathTraversalState::current):
+ (WebCore::PathTraversalState::normalAngle): New access methods which are now
+ needed after making the data members private.
+
+ * rendering/svg/SVGRootInlineBox.cpp:
+ (WebCore::SVGRootInlineBox::layoutCharactersInTextBoxes): Make the casting
+ of the renderer on the caller side.
+
+ * rendering/svg/SVGTextChunk.cpp:
+ (WebCore::SVGTextChunk::SVGTextChunk): The constructor should append the
+ elements of m_boxes instead of making this from outside the class.
+
+ (WebCore::SVGTextChunk::totalCharacters):
+ (WebCore::SVGTextChunk::totalLength):
+ (WebCore::SVGTextChunk::calculateLength): Deleted.
+ Replace calculateLength() by totalCharacters() and totalLength() to make
+ the interface cleaner.
+
+ (WebCore::SVGTextChunk::totalAnchorShift):
+ (WebCore::SVGTextChunk::calculateTextAnchorShift): Deleted.
+ Rename the function name.
+
+ (WebCore::SVGTextChunk::layout):
+ (WebCore::SVGTextChunk::processTextLengthSpacingCorrection):
+ (WebCore::SVGTextChunk::buildBoxTransformations):
+ (WebCore::SVGTextChunk::boxSpacingAndGlyphsTransform):
+ (WebCore::SVGTextChunk::processTextAnchorCorrection):
+ Move the chunk layout code from SVGTextChunkBuilder::layoutTextChunks()
+ to the SVGTextChunk::layout(). Move all the helper functions as well.
+
+ * rendering/svg/SVGTextChunk.h:
+ (WebCore::SVGTextChunk::hasTextAnchor):
+ (WebCore::SVGTextChunk::boxes): Deleted.
+ Add the new methods and change most of the public methods to be private.
+
+ * rendering/svg/SVGTextChunkBuilder.cpp:
+ (WebCore::SVGTextChunkBuilder::totalCharacters):
+ (WebCore::SVGTextChunkBuilder::totalLength):
+ (WebCore::SVGTextChunkBuilder::totalAnchorShift): This code is moved from
+ SVGTextLayoutEngine. It scans the boxes stored in the SVGTextChunkBuilder
+ and sums up the total values.
+
+ (WebCore::SVGTextChunkBuilder::transformationForTextBox):
+ (WebCore::SVGTextChunkBuilder::buildTextChunks):
+ (WebCore::SVGTextChunkBuilder::layoutTextChunks): Code clean up.
+
+ (WebCore::SVGTextChunkBuilder::addTextChunk): Deleted.
+ (WebCore::SVGTextChunkBuilder::processTextChunk): Deleted.
+ (WebCore::SVGTextChunkBuilder::processTextLengthSpacingCorrection): Deleted.
+ (WebCore::SVGTextChunkBuilder::processTextAnchorCorrection): Deleted.
+ (WebCore::SVGTextChunkBuilder::buildSpacingAndGlyphsTransform): Deleted.
+ This code now lives in SVGTextChunk.
+
+ * rendering/svg/SVGTextChunkBuilder.h: Add new methods for code which was
+ moved from SVGTextLayoutEngine and remove methods for code which was removed
+ to SVGTextChunk.
+
+ * rendering/svg/SVGTextLayoutEngine.cpp:
+ (WebCore::SVGTextLayoutEngine::beginTextPathLayout): Use the sum up methods
+ from SVGTextChunkBuilder instead of looping through the chunks. Also get a
+ clean order for defining variables and doing the calculations.
+
+ (WebCore::SVGTextLayoutEngine::finalizeTransformMatrices): Code clean up.
+
+ (WebCore::SVGTextLayoutEngine::layoutTextOnLineOrPath): Do a single path
+ traversal to get the position and the angle for a length on a path.
+
+ * svg/SVGAnimateMotionElement.cpp:
+ (WebCore::SVGAnimateMotionElement::buildTransformForProgress): Do a single
+ path traversal to get the position and the angle at a length on a path.
+
+ * svg/SVGPathTraversalStateBuilder.cpp:
+ (WebCore::SVGPathTraversalStateBuilder::SVGPathTraversalStateBuilder):
+ (WebCore::SVGPathTraversalStateBuilder::moveTo):
+ (WebCore::SVGPathTraversalStateBuilder::lineTo):
+ (WebCore::SVGPathTraversalStateBuilder::curveToCubic):
+ (WebCore::SVGPathTraversalStateBuilder::closePath):
+ (WebCore::SVGPathTraversalStateBuilder::setDesiredLength):
+ (WebCore::SVGPathTraversalStateBuilder::continueConsuming):
+ (WebCore::SVGPathTraversalStateBuilder::totalLength):
+ (WebCore::SVGPathTraversalStateBuilder::currentPoint):
+ (WebCore::SVGPathTraversalStateBuilder::incrementPathSegmentCount): Deleted.
+ (WebCore::SVGPathTraversalStateBuilder::pathSegmentIndex): Deleted.
+ * svg/SVGPathTraversalStateBuilder.h:
+ (WebCore::SVGPathTraversalStateBuilder::pathSegmentIndex):
+ Code clean up.
+
+ * svg/SVGPathUtilities.cpp:
+ (WebCore::getSVGPathSegAtLengthFromSVGPathByteStream):
+ (WebCore::getTotalLengthOfSVGPathByteStream):
+ (WebCore::getPointAtLengthOfSVGPathByteStream): Use new TraversalState::Action
+ enum values.
+
2015-04-14 Simon Fraser <simon.fra...@apple.com>
Re-enable custom dilation for antialiased fonts
Modified: trunk/Source/WebCore/platform/graphics/Path.cpp (182827 => 182828)
--- trunk/Source/WebCore/platform/graphics/Path.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/platform/graphics/Path.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -42,56 +42,34 @@
static void pathLengthApplierFunction(void* info, const PathElement* element)
{
PathTraversalState& traversalState = *static_cast<PathTraversalState*>(info);
- if (traversalState.m_success)
- return;
- FloatPoint* points = element->points;
- float segmentLength = 0;
- switch (element->type) {
- case PathElementMoveToPoint:
- segmentLength = traversalState.moveTo(points[0]);
- break;
- case PathElementAddLineToPoint:
- segmentLength = traversalState.lineTo(points[0]);
- break;
- case PathElementAddQuadCurveToPoint:
- segmentLength = traversalState.quadraticBezierTo(points[0], points[1]);
- break;
- case PathElementAddCurveToPoint:
- segmentLength = traversalState.cubicBezierTo(points[0], points[1], points[2]);
- break;
- case PathElementCloseSubpath:
- segmentLength = traversalState.closeSubpath();
- break;
- }
- traversalState.m_totalLength += segmentLength;
- traversalState.processSegment();
+ traversalState.processPathElement(element);
}
float Path::length() const
{
- PathTraversalState traversalState(PathTraversalState::TraversalTotalLength);
+ PathTraversalState traversalState(PathTraversalState::Action::TotalLength);
apply(&traversalState, pathLengthApplierFunction);
- return traversalState.m_totalLength;
+ return traversalState.totalLength();
}
-FloatPoint Path::pointAtLength(float length, bool& ok) const
+PathTraversalState Path::traversalStateAtLength(float length, bool& success) const
{
- PathTraversalState traversalState(PathTraversalState::TraversalPointAtLength);
- traversalState.m_desiredLength = length;
+ PathTraversalState traversalState(PathTraversalState::Action::VectorAtLength, length);
apply(&traversalState, pathLengthApplierFunction);
- ok = traversalState.m_success;
- return traversalState.m_current;
+ success = traversalState.success();
+ return traversalState;
}
-float Path::normalAngleAtLength(float length, bool& ok) const
+FloatPoint Path::pointAtLength(float length, bool& success) const
{
- PathTraversalState traversalState(PathTraversalState::TraversalNormalAngleAtLength);
- traversalState.m_desiredLength = length ? length : std::numeric_limits<float>::epsilon();
- apply(&traversalState, pathLengthApplierFunction);
- ok = traversalState.m_success;
- return traversalState.m_normalAngle;
+ return traversalStateAtLength(length, success).current();
}
+float Path::normalAngleAtLength(float length, bool& success) const
+{
+ return traversalStateAtLength(length, success).normalAngle();
+}
+
void Path::addRoundedRect(const FloatRect& rect, const FloatSize& roundingRadii, RoundedRectStrategy strategy)
{
if (rect.isEmpty())
Modified: trunk/Source/WebCore/platform/graphics/Path.h (182827 => 182828)
--- trunk/Source/WebCore/platform/graphics/Path.h 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/platform/graphics/Path.h 2015-04-15 01:34:25 UTC (rev 182828)
@@ -60,6 +60,7 @@
class FloatRoundedRect;
class FloatSize;
class GraphicsContext;
+ class PathTraversalState;
class RoundedRect;
class StrokeStyleApplier;
@@ -100,10 +101,11 @@
FloatRect boundingRect() const;
FloatRect fastBoundingRect() const;
FloatRect strokeBoundingRect(StrokeStyleApplier* = 0) const;
-
+
float length() const;
- FloatPoint pointAtLength(float length, bool& ok) const;
- float normalAngleAtLength(float length, bool& ok) const;
+ PathTraversalState traversalStateAtLength(float length, bool& success) const;
+ FloatPoint pointAtLength(float length, bool& success) const;
+ float normalAngleAtLength(float length, bool& success) const;
WEBCORE_EXPORT void clear();
bool isNull() const { return !m_path; }
Modified: trunk/Source/WebCore/platform/graphics/PathTraversalState.cpp (182827 => 182828)
--- trunk/Source/WebCore/platform/graphics/PathTraversalState.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/platform/graphics/PathTraversalState.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006, 2007 Eric Seidel <e...@webkit.org>
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -34,7 +35,9 @@
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()));
+ float dx = end.x() - start.x();
+ float dy = end.y() - start.y();
+ return sqrtf(dx * dx + dy * dy);
}
struct QuadraticBezier {
@@ -114,111 +117,150 @@
// Another check which is possible up-front (to send us down the fast path) would be to check if
// approximateDistance() + current total distance > desired distance
template<class CurveType>
-static float curveLength(PathTraversalState& traversalState, CurveType curve)
+static float curveLength(const PathTraversalState& traversalState, const CurveType& originalCurve, FloatPoint& previous, FloatPoint& current)
{
static const unsigned curveStackDepthLimit = 20;
+ CurveType curve = originalCurve;
+ Vector<CurveType, curveStackDepthLimit> curveStack;
+ float totalLength = 0;
- Vector<CurveType> curveStack;
- curveStack.append(curve);
+ while (true) {
+ float length = curve.approximateDistance();
- float totalLength = 0;
- do {
- float length = curve.approximateDistance();
- if ((length - distanceLine(curve.start, curve.end)) > kPathSegmentLengthTolerance && curveStack.size() <= curveStackDepthLimit) {
+ if ((length - distanceLine(curve.start, curve.end)) > kPathSegmentLengthTolerance && curveStack.size() < curveStackDepthLimit) {
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();
+ continue;
}
- } while (!curveStack.isEmpty());
-
+
+ totalLength += length;
+ if (traversalState.action() == PathTraversalState::Action::VectorAtLength) {
+ previous = curve.start;
+ current = curve.end;
+ if (traversalState.totalLength() + totalLength > traversalState.desiredLength())
+ break;
+ }
+
+ if (curveStack.isEmpty())
+ break;
+
+ curve = curveStack.last();
+ curveStack.removeLast();
+ }
+
+ if (traversalState.action() != PathTraversalState::Action::VectorAtLength) {
+ ASSERT(curve.end == originalCurve.end);
+ previous = curve.start;
+ current = curve.end;
+ }
+
return totalLength;
}
-PathTraversalState::PathTraversalState(PathTraversalAction action)
+PathTraversalState::PathTraversalState(Action action, float desiredLength)
: m_action(action)
- , m_success(false)
- , m_totalLength(0)
- , m_segmentIndex(0)
- , m_desiredLength(0)
- , m_normalAngle(0)
+ , m_desiredLength(desiredLength)
{
+ ASSERT(action != Action::TotalLength || !desiredLength);
}
-float PathTraversalState::closeSubpath()
+void PathTraversalState::closeSubpath()
{
- float distance = distanceLine(m_current, m_start);
- m_current = m_control1 = m_control2 = m_start;
- return distance;
+ m_totalLength += distanceLine(m_current, m_start);
+ m_current = m_start;
}
-float PathTraversalState::moveTo(const FloatPoint& point)
+void PathTraversalState::moveTo(const FloatPoint& point)
{
- m_current = m_start = m_control1 = m_control2 = point;
- return 0;
+ m_previous = m_current = m_start = point;
}
-float PathTraversalState::lineTo(const FloatPoint& point)
+void PathTraversalState::lineTo(const FloatPoint& point)
{
- float distance = distanceLine(m_current, point);
- m_current = m_control1 = m_control2 = point;
- return distance;
+ m_totalLength += distanceLine(m_current, point);
+ m_current = point;
}
-float PathTraversalState::quadraticBezierTo(const FloatPoint& newControl, const FloatPoint& newEnd)
+void PathTraversalState::quadraticBezierTo(const FloatPoint& newControl, const FloatPoint& newEnd)
{
- float distance = curveLength<QuadraticBezier>(*this, QuadraticBezier(m_current, newControl, newEnd));
+ m_totalLength += curveLength<QuadraticBezier>(*this, QuadraticBezier(m_current, newControl, newEnd), m_previous, m_current);
+}
- m_control1 = newControl;
- m_control2 = newEnd;
-
- if (m_action != TraversalPointAtLength && m_action != TraversalNormalAngleAtLength)
- m_current = newEnd;
-
- return distance;
+void PathTraversalState::cubicBezierTo(const FloatPoint& newControl1, const FloatPoint& newControl2, const FloatPoint& newEnd)
+{
+ m_totalLength += curveLength<CubicBezier>(*this, CubicBezier(m_current, newControl1, newControl2, newEnd), m_previous, m_current);
}
-float PathTraversalState::cubicBezierTo(const FloatPoint& newControl1, const FloatPoint& newControl2, const FloatPoint& newEnd)
+bool PathTraversalState::finalizeAppendPathElement()
{
- float distance = curveLength<CubicBezier>(*this, CubicBezier(m_current, newControl1, newControl2, newEnd));
+ if (m_action == Action::TotalLength)
+ return false;
- m_control1 = newEnd;
- m_control2 = newControl2;
-
- if (m_action != TraversalPointAtLength && m_action != TraversalNormalAngleAtLength)
- m_current = newEnd;
+ if (m_action == Action::SegmentAtLength) {
+ if (m_totalLength >= m_desiredLength)
+ m_success = true;
+ return m_success;
+ }
- return distance;
-}
+ ASSERT(m_action == Action::VectorAtLength);
-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) {
+ if (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
+ float offset = m_desiredLength - m_totalLength;
+ m_current.move(offset * cosf(slope), offset * sinf(slope));
+
+ if (!m_isZeroVector && !m_desiredLength)
+ m_isZeroVector = true;
+ else {
+ m_success = true;
m_normalAngle = rad2deg(slope);
- m_success = true;
+ }
}
+
m_previous = m_current;
+ return m_success;
}
+bool PathTraversalState::appendPathElement(PathElementType type, const FloatPoint* points)
+{
+ switch (type) {
+ case PathElementMoveToPoint:
+ moveTo(points[0]);
+ break;
+ case PathElementAddLineToPoint:
+ lineTo(points[0]);
+ break;
+ case PathElementAddQuadCurveToPoint:
+ quadraticBezierTo(points[0], points[1]);
+ break;
+ case PathElementAddCurveToPoint:
+ cubicBezierTo(points[0], points[1], points[2]);
+ break;
+ case PathElementCloseSubpath:
+ closeSubpath();
+ break;
+ }
+
+ return finalizeAppendPathElement();
}
+bool PathTraversalState::processPathElement(PathElementType type, const FloatPoint* points)
+{
+ if (m_success)
+ return true;
+
+ if (m_isZeroVector) {
+ PathTraversalState traversalState(*this);
+ m_success = traversalState.appendPathElement(type, points);
+ m_normalAngle = traversalState.m_normalAngle;
+ return m_success;
+ }
+
+ return appendPathElement(type, points);
+}
+
+}
+
Modified: trunk/Source/WebCore/platform/graphics/PathTraversalState.h (182827 => 182828)
--- trunk/Source/WebCore/platform/graphics/PathTraversalState.h 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/platform/graphics/PathTraversalState.h 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006, 2007 Eric Seidel <e...@webkit.org>
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -27,45 +28,60 @@
#define PathTraversalState_h
#include "FloatPoint.h"
+#include "Path.h"
namespace WebCore {
class PathTraversalState {
public:
- enum PathTraversalAction {
- TraversalTotalLength,
- TraversalPointAtLength,
- TraversalSegmentAtLength,
- TraversalNormalAngleAtLength
+ enum class Action {
+ TotalLength,
+ VectorAtLength,
+ SegmentAtLength,
};
- PathTraversalState(PathTraversalAction);
+ PathTraversalState(Action, float desiredLength = 0);
- float closeSubpath();
- float moveTo(const FloatPoint&);
- float lineTo(const FloatPoint&);
- float quadraticBezierTo(const FloatPoint& newControl, const FloatPoint& newEnd);
- float cubicBezierTo(const FloatPoint& newControl1, const FloatPoint& newControl2, const FloatPoint& newEnd);
-
- void processSegment();
-
public:
- PathTraversalAction m_action;
- bool m_success;
+ bool processPathElement(PathElementType, const FloatPoint*);
+ bool processPathElement(const PathElement* element) { return processPathElement(element->type, element->points); }
+ Action action() const { return m_action; }
+ void setAction(Action action) { m_action = action; }
+ float desiredLength() const { return m_desiredLength; }
+ void setDesiredLength(float desiredLength) { m_desiredLength = desiredLength; }
+
+ // Traversing output -- should be read only
+ bool success() const { return m_success; }
+ float totalLength() const { return m_totalLength; }
+ FloatPoint current() const { return m_current; }
+ float normalAngle() const { return m_normalAngle; }
+
+private:
+ void closeSubpath();
+ void moveTo(const FloatPoint&);
+ void lineTo(const FloatPoint&);
+ void quadraticBezierTo(const FloatPoint&, const FloatPoint&);
+ void cubicBezierTo(const FloatPoint&, const FloatPoint&, const FloatPoint&);
+
+ bool finalizeAppendPathElement();
+ bool appendPathElement(PathElementType, const FloatPoint*);
+
+private:
+ Action m_action;
+ bool m_success { false };
+
FloatPoint m_current;
FloatPoint m_start;
- FloatPoint m_control1;
- FloatPoint m_control2;
- float m_totalLength;
- unsigned m_segmentIndex;
- float m_desiredLength;
+ float m_totalLength { 0 };
+ float m_desiredLength { 0 };
// For normal calculations
FloatPoint m_previous;
- float m_normalAngle; // degrees
-};
+ float m_normalAngle { 0 }; // degrees
+ bool m_isZeroVector { false };
+};
}
#endif
Modified: trunk/Source/WebCore/rendering/svg/SVGTextChunk.cpp (182827 => 182828)
--- trunk/Source/WebCore/rendering/svg/SVGTextChunk.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/rendering/svg/SVGTextChunk.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,5 +1,6 @@
/*
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -24,52 +25,96 @@
namespace WebCore {
-SVGTextChunk::SVGTextChunk(unsigned chunkStyle, float desiredTextLength)
- : m_chunkStyle(chunkStyle)
- , m_desiredTextLength(desiredTextLength)
+SVGTextChunk::SVGTextChunk(const Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned first, unsigned limit)
{
-}
+ ASSERT(first < limit);
+ ASSERT(first >= 0 && limit <= lineLayoutBoxes.size());
-void SVGTextChunk::calculateLength(float& length, unsigned& characters) const
-{
- SVGTextFragment* lastFragment = 0;
+ const SVGInlineTextBox* box = lineLayoutBoxes[first];
+ const RenderStyle& style = box->renderer().style();
+ const SVGRenderStyle& svgStyle = style.svgStyle();
- unsigned boxCount = m_boxes.size();
- for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
- SVGInlineTextBox* textBox = m_boxes.at(boxPosition);
- Vector<SVGTextFragment>& fragments = textBox->textFragments();
+ if (!style.isLeftToRightDirection())
+ m_chunkStyle |= SVGTextChunk::RightToLeftText;
- unsigned size = fragments.size();
- if (!size)
- continue;
+ if (svgStyle.isVerticalWritingMode())
+ m_chunkStyle |= SVGTextChunk::VerticalText;
+
+ switch (svgStyle.textAnchor()) {
+ case TA_START:
+ break;
+ case TA_MIDDLE:
+ m_chunkStyle |= MiddleAnchor;
+ break;
+ case TA_END:
+ m_chunkStyle |= EndAnchor;
+ break;
+ }
- for (unsigned i = 0; i < size; ++i) {
- SVGTextFragment& fragment = fragments.at(i);
- characters += fragment.length;
+ if (auto* textContentElement = SVGTextContentElement::elementFromRenderer(box->renderer().parent())) {
+ SVGLengthContext lengthContext(textContentElement);
+ m_desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
- if (m_chunkStyle & VerticalText)
- length += fragment.height;
- else
- length += fragment.width;
+ switch (textContentElement->lengthAdjust()) {
+ case SVGLengthAdjustUnknown:
+ break;
+ case SVGLengthAdjustSpacing:
+ m_chunkStyle |= LengthAdjustSpacing;
+ break;
+ case SVGLengthAdjustSpacingAndGlyphs:
+ m_chunkStyle |= LengthAdjustSpacingAndGlyphs;
+ break;
+ }
+ }
- if (!lastFragment) {
- lastFragment = &fragment;
- continue;
- }
+ for (unsigned i = first; i < limit; ++i)
+ m_boxes.append(lineLayoutBoxes[i]);
+}
- // Resepect gap between chunks.
- if (m_chunkStyle & VerticalText)
- length += fragment.y - (lastFragment->y + lastFragment->height);
- else
- length += fragment.x - (lastFragment->x + lastFragment->width);
+unsigned SVGTextChunk::totalCharacters() const
+{
+ unsigned characters = 0;
+ for (auto* box : m_boxes) {
+ for (auto& fragment : box->textFragments())
+ characters += fragment.length;
+ }
+ return characters;
+}
- lastFragment = &fragment;
+float SVGTextChunk::totalLength() const
+{
+ const SVGTextFragment* firstFragment = nullptr;
+ const SVGTextFragment* lastFragment = nullptr;
+
+ for (auto* box : m_boxes) {
+ auto& fragments = box->textFragments();
+ if (fragments.size()) {
+ firstFragment = &(*fragments.begin());
+ break;
}
}
+
+ for (auto it = m_boxes.rbegin(), end = m_boxes.rend(); it != end; ++it) {
+ auto& fragments = (*it)->textFragments();
+ if (fragments.size()) {
+ lastFragment = &(*fragments.rbegin());
+ break;
+ }
+ }
+
+ ASSERT(!firstFragment == !lastFragment);
+ if (!firstFragment)
+ return 0;
+
+ if (m_chunkStyle & VerticalText)
+ return (lastFragment->y + lastFragment->height) - firstFragment->y;
+
+ return (lastFragment->x + lastFragment->width) - firstFragment->x;
}
-float SVGTextChunk::calculateTextAnchorShift(float length) const
+float SVGTextChunk::totalAnchorShift() const
{
+ float length = totalLength();
if (m_chunkStyle & MiddleAnchor)
return -length / 2;
if (m_chunkStyle & EndAnchor)
@@ -77,4 +122,88 @@
return m_chunkStyle & RightToLeftText ? -length : 0;
}
+void SVGTextChunk::layout(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
+{
+ if (hasDesiredTextLength()) {
+ if (hasLengthAdjustSpacing())
+ processTextLengthSpacingCorrection();
+ else {
+ ASSERT(hasLengthAdjustSpacingAndGlyphs());
+ buildBoxTransformations(textBoxTransformations);
+ }
+ }
+
+ if (hasTextAnchor())
+ processTextAnchorCorrection();
+}
+
+void SVGTextChunk::processTextLengthSpacingCorrection() const
+{
+ float textLengthShift = (desiredTextLength() - totalLength()) / totalCharacters();
+ bool isVerticalText = m_chunkStyle & VerticalText;
+ unsigned atCharacter = 0;
+
+ for (auto* box : m_boxes) {
+ for (auto& fragment : box->textFragments()) {
+ if (isVerticalText)
+ fragment.y += textLengthShift * atCharacter;
+ else
+ fragment.x += textLengthShift * atCharacter;
+
+ atCharacter += fragment.length;
+ }
+ }
+}
+
+void SVGTextChunk::buildBoxTransformations(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
+{
+ AffineTransform spacingAndGlyphsTransform;
+ bool foundFirstFragment = false;
+
+ for (auto* box : m_boxes) {
+ if (!foundFirstFragment) {
+ if (!boxSpacingAndGlyphsTransform(box, spacingAndGlyphsTransform))
+ continue;
+ foundFirstFragment = true;
+ }
+
+ textBoxTransformations.set(box, spacingAndGlyphsTransform);
+ }
+}
+
+bool SVGTextChunk::boxSpacingAndGlyphsTransform(const SVGInlineTextBox* box, AffineTransform& spacingAndGlyphsTransform) const
+{
+ auto& fragments = box->textFragments();
+ if (fragments.isEmpty())
+ return false;
+
+ const SVGTextFragment& fragment = fragments.first();
+ float scale = desiredTextLength() / totalLength();
+
+ spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
+
+ if (m_chunkStyle & VerticalText)
+ spacingAndGlyphsTransform.scaleNonUniform(1, scale);
+ else
+ spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
+
+ spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
+ return true;
+}
+
+void SVGTextChunk::processTextAnchorCorrection() const
+{
+ float textAnchorShift = totalAnchorShift();
+ bool isVerticalText = m_chunkStyle & VerticalText;
+
+ for (auto* box : m_boxes) {
+ for (auto& fragment : box->textFragments()) {
+ if (isVerticalText)
+ fragment.y += textAnchorShift;
+ else
+ fragment.x += textAnchorShift;
+ }
+ }
+}
+
} // namespace WebCore
Modified: trunk/Source/WebCore/rendering/svg/SVGTextChunk.h (182827 => 182828)
--- trunk/Source/WebCore/rendering/svg/SVGTextChunk.h 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/rendering/svg/SVGTextChunk.h 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,5 +1,6 @@
/*
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -40,28 +41,34 @@
LengthAdjustSpacingAndGlyphs = 1 << 6
};
- SVGTextChunk(unsigned chunkStyle, float desiredTextLength);
+ SVGTextChunk(const Vector<SVGInlineTextBox*>&, unsigned first, unsigned limit);
- void calculateLength(float& length, unsigned& characters) const;
- float calculateTextAnchorShift(float length) const;
+ unsigned totalCharacters() const;
+ float totalLength() const;
+ float totalAnchorShift() const;
+ void layout(HashMap<SVGInlineTextBox*, AffineTransform>&) const;
+private:
+ void processTextAnchorCorrection() const;
+ void buildBoxTransformations(HashMap<SVGInlineTextBox*, AffineTransform>&) const;
+ void processTextLengthSpacingCorrection() const;
+
bool isVerticalText() const { return m_chunkStyle & VerticalText; }
float desiredTextLength() const { return m_desiredTextLength; }
- Vector<SVGInlineTextBox*>& boxes() { return m_boxes; }
- const Vector<SVGInlineTextBox*>& boxes() const { return m_boxes; }
-
bool hasDesiredTextLength() const { return m_desiredTextLength > 0 && ((m_chunkStyle & LengthAdjustSpacing) || (m_chunkStyle & LengthAdjustSpacingAndGlyphs)); }
- bool hasTextAnchor() const { return m_chunkStyle & RightToLeftText ? !(m_chunkStyle & EndAnchor) : (m_chunkStyle & MiddleAnchor) || (m_chunkStyle & EndAnchor); }
+ bool hasTextAnchor() const { return m_chunkStyle & RightToLeftText ? !(m_chunkStyle & EndAnchor) : (m_chunkStyle & (MiddleAnchor | EndAnchor)); }
bool hasLengthAdjustSpacing() const { return m_chunkStyle & LengthAdjustSpacing; }
bool hasLengthAdjustSpacingAndGlyphs() const { return m_chunkStyle & LengthAdjustSpacingAndGlyphs; }
+ bool boxSpacingAndGlyphsTransform(const SVGInlineTextBox*, AffineTransform&) const;
+
private:
// Contains all SVGInlineTextBoxes this chunk spans.
Vector<SVGInlineTextBox*> m_boxes;
- unsigned m_chunkStyle;
- float m_desiredTextLength;
+ unsigned m_chunkStyle { DefaultStyle };
+ float m_desiredTextLength { 0 };
};
} // namespace WebCore
Modified: trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp (182827 => 182828)
--- trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,5 +1,6 @@
/*
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -30,227 +31,71 @@
{
}
-void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
+unsigned SVGTextChunkBuilder::totalCharacters() const
{
- DEPRECATED_DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
- if (!m_textBoxTransformations.contains(textBox)) {
- transform = s_identityTransform;
- return;
- }
-
- transform = m_textBoxTransformations.get(textBox);
+ unsigned characters = 0;
+ for (const auto& chunk : m_textChunks)
+ characters += chunk.totalCharacters();
+ return characters;
}
-void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
+float SVGTextChunkBuilder::totalLength() const
{
- if (lineLayoutBoxes.isEmpty())
- return;
-
- bool foundStart = false;
- unsigned lastChunkStartPosition = 0;
- unsigned boxPosition = 0;
- unsigned boxCount = lineLayoutBoxes.size();
- for (; boxPosition < boxCount; ++boxPosition) {
- SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition];
- if (!textBox->startsNewTextChunk())
- continue;
-
- if (!foundStart) {
- lastChunkStartPosition = boxPosition;
- foundStart = true;
- } else {
- ASSERT_WITH_SECURITY_IMPLICATION(boxPosition > lastChunkStartPosition);
- addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
- lastChunkStartPosition = boxPosition;
- }
- }
-
- if (!foundStart)
- return;
-
- if (boxPosition - lastChunkStartPosition > 0)
- addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
+ float length = 0;
+ for (const auto& chunk : m_textChunks)
+ length += chunk.totalLength();
+ return length;
}
-void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
+float SVGTextChunkBuilder::totalAnchorShift() const
{
- buildTextChunks(lineLayoutBoxes);
- if (m_textChunks.isEmpty())
- return;
-
- unsigned chunkCount = m_textChunks.size();
- for (unsigned i = 0; i < chunkCount; ++i)
- processTextChunk(m_textChunks[i]);
-
- m_textChunks.clear();
+ float anchorShift = 0;
+ for (const auto& chunk : m_textChunks)
+ anchorShift += chunk.totalAnchorShift();
+ return anchorShift;
}
-void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
+AffineTransform SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox) const
{
- SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart];
- ASSERT(textBox);
-
- const RenderStyle& style = textBox->renderer().style();
-
- const SVGRenderStyle& svgStyle = style.svgStyle();
-
- // Build chunk style flags.
- unsigned chunkStyle = SVGTextChunk::DefaultStyle;
-
- // Handle 'direction' property.
- if (!style.isLeftToRightDirection())
- chunkStyle |= SVGTextChunk::RightToLeftText;
-
- // Handle 'writing-mode' property.
- if (svgStyle.isVerticalWritingMode())
- chunkStyle |= SVGTextChunk::VerticalText;
-
- // Handle 'text-anchor' property.
- switch (svgStyle.textAnchor()) {
- case TA_START:
- break;
- case TA_MIDDLE:
- chunkStyle |= SVGTextChunk::MiddleAnchor;
- break;
- case TA_END:
- chunkStyle |= SVGTextChunk::EndAnchor;
- break;
- };
-
- // Handle 'lengthAdjust' property.
- float desiredTextLength = 0;
- if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textBox->renderer().parent())) {
- SVGLengthContext lengthContext(textContentElement);
- desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
-
- switch (textContentElement->lengthAdjust()) {
- case SVGLengthAdjustUnknown:
- break;
- case SVGLengthAdjustSpacing:
- chunkStyle |= SVGTextChunk::LengthAdjustSpacing;
- break;
- case SVGLengthAdjustSpacingAndGlyphs:
- chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs;
- break;
- };
- }
-
- SVGTextChunk chunk(chunkStyle, desiredTextLength);
-
- Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
- for (unsigned i = boxStart; i < boxStart + boxCount; ++i)
- boxes.append(lineLayoutBoxes[i]);
-
- m_textChunks.append(chunk);
+ auto it = m_textBoxTransformations.find(textBox);
+ return it == m_textBoxTransformations.end() ? AffineTransform() : it->value;
}
-void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk)
+void SVGTextChunkBuilder::buildTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes)
{
- bool processTextLength = chunk.hasDesiredTextLength();
- bool processTextAnchor = chunk.hasTextAnchor();
- if (!processTextAnchor && !processTextLength)
+ if (lineLayoutBoxes.isEmpty())
return;
- const Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
- unsigned boxCount = boxes.size();
- if (!boxCount)
- return;
+ unsigned limit = lineLayoutBoxes.size();
+ unsigned first = limit;
- // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
- float chunkLength = 0;
- unsigned chunkCharacters = 0;
- chunk.calculateLength(chunkLength, chunkCharacters);
+ for (unsigned i = 0; i < limit; ++i) {
+ if (!lineLayoutBoxes[i]->startsNewTextChunk())
+ continue;
- bool isVerticalText = chunk.isVerticalText();
- if (processTextLength) {
- if (chunk.hasLengthAdjustSpacing()) {
- float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters;
- unsigned atCharacter = 0;
- for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
- Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
- if (fragments.isEmpty())
- continue;
- processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
- }
- } else {
- ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs());
- float textLengthScale = chunk.desiredTextLength() / chunkLength;
- AffineTransform spacingAndGlyphsTransform;
-
- bool foundFirstFragment = false;
- for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
- SVGInlineTextBox* textBox = boxes[boxPosition];
- Vector<SVGTextFragment>& fragments = textBox->textFragments();
- if (fragments.isEmpty())
- continue;
-
- if (!foundFirstFragment) {
- foundFirstFragment = true;
- buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform);
- }
-
- m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform);
- }
+ if (first == limit)
+ first = i;
+ else {
+ ASSERT_WITH_SECURITY_IMPLICATION(first != i);
+ m_textChunks.append(SVGTextChunk(lineLayoutBoxes, first, i));
+ first = i;
}
}
- if (!processTextAnchor)
- return;
-
- // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift.
- if (processTextLength && chunk.hasLengthAdjustSpacing()) {
- chunkLength = 0;
- chunkCharacters = 0;
- chunk.calculateLength(chunkLength, chunkCharacters);
- }
-
- float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength);
- for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
- Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
- if (fragments.isEmpty())
- continue;
- processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
- }
+ if (first != limit)
+ m_textChunks.append(SVGTextChunk(lineLayoutBoxes, first, limit));
}
-void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
+void SVGTextChunkBuilder::layoutTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes)
{
- unsigned fragmentCount = fragments.size();
- for (unsigned i = 0; i < fragmentCount; ++i) {
- SVGTextFragment& fragment = fragments[i];
+ buildTextChunks(lineLayoutBoxes);
+ if (m_textChunks.isEmpty())
+ return;
- if (isVerticalText)
- fragment.y += textLengthShift * atCharacter;
- else
- fragment.x += textLengthShift * atCharacter;
+ for (const auto& chunk : m_textChunks)
+ chunk.layout(m_textBoxTransformations);
- atCharacter += fragment.length;
- }
+ m_textChunks.clear();
}
-void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
-{
- unsigned fragmentCount = fragments.size();
- for (unsigned i = 0; i < fragmentCount; ++i) {
- SVGTextFragment& fragment = fragments[i];
-
- if (isVerticalText)
- fragment.y += textAnchorShift;
- else
- fragment.x += textAnchorShift;
- }
}
-
-void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
-{
- spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
-
- if (isVerticalText)
- spacingAndGlyphsTransform.scaleNonUniform(1, scale);
- else
- spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
-
- spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
-}
-
-}
Modified: trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h (182827 => 182828)
--- trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h 2015-04-15 01:34:25 UTC (rev 182828)
@@ -1,5 +1,6 @@
/*
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -40,20 +41,15 @@
SVGTextChunkBuilder();
const Vector<SVGTextChunk>& textChunks() const { return m_textChunks; }
- void transformationForTextBox(SVGInlineTextBox*, AffineTransform&) const;
+ unsigned totalCharacters() const;
+ float totalLength() const;
+ float totalAnchorShift() const;
+ AffineTransform transformationForTextBox(SVGInlineTextBox*) const;
- void buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes);
- void layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes);
+ void buildTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes);
+ void layoutTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes);
private:
- void addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxPosition, unsigned boxCount);
- void processTextChunk(const SVGTextChunk&);
-
- void processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>&, unsigned& atCharacter);
- void processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>&);
- void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment&, AffineTransform&);
-
-private:
Vector<SVGTextChunk> m_textChunks;
HashMap<SVGInlineTextBox*, AffineTransform> m_textBoxTransformations;
};
Modified: trunk/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp (182827 => 182828)
--- trunk/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -20,6 +20,7 @@
#include "config.h"
#include "SVGTextLayoutEngine.h"
+#include "PathTraversalState.h"
#include "RenderSVGTextPath.h"
#include "SVGElement.h"
#include "SVGInlineTextBox.h"
@@ -170,48 +171,32 @@
m_textPath = textPath.layoutPath();
if (m_textPath.isEmpty())
return;
+
m_textPathStartOffset = textPath.startOffset();
m_textPathLength = m_textPath.length();
if (m_textPathStartOffset > 0 && m_textPathStartOffset <= 1)
m_textPathStartOffset *= m_textPathLength;
- float totalLength = 0;
- unsigned totalCharacters = 0;
-
lineLayout.m_chunkLayoutBuilder.buildTextChunks(lineLayout.m_lineLayoutBoxes);
- const Vector<SVGTextChunk>& textChunks = lineLayout.m_chunkLayoutBuilder.textChunks();
- unsigned size = textChunks.size();
- for (unsigned i = 0; i < size; ++i) {
- const SVGTextChunk& chunk = textChunks.at(i);
-
- float length = 0;
- unsigned characters = 0;
- chunk.calculateLength(length, characters);
-
- // Handle text-anchor as additional start offset for text paths.
- m_textPathStartOffset += chunk.calculateTextAnchorShift(length);
-
- totalLength += length;
- totalCharacters += characters;
- }
-
+ // Handle text-anchor as additional start offset for text paths.
+ m_textPathStartOffset += lineLayout.m_chunkLayoutBuilder.totalAnchorShift();
m_textPathCurrentOffset = m_textPathStartOffset;
// Eventually handle textLength adjustments.
- SVGLengthAdjustType lengthAdjust = SVGLengthAdjustUnknown;
- float desiredTextLength = 0;
+ auto* textContentElement = SVGTextContentElement::elementFromRenderer(&textPath);
+ if (!textContentElement)
+ return;
- if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(&textPath)) {
- SVGLengthContext lengthContext(textContentElement);
- lengthAdjust = textContentElement->lengthAdjust();
- desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
- }
-
+ SVGLengthContext lengthContext(textContentElement);
+ float desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
if (!desiredTextLength)
return;
- if (lengthAdjust == SVGLengthAdjustSpacing)
+ float totalLength = lineLayout.m_chunkLayoutBuilder.totalLength();
+ unsigned totalCharacters = lineLayout.m_chunkLayoutBuilder.totalCharacters();
+
+ if (textContentElement->lengthAdjust() == SVGLengthAdjustSpacing)
m_textPathSpacing = (desiredTextLength - totalLength) / totalCharacters;
else
m_textPathScaling = desiredTextLength / totalLength;
@@ -290,7 +275,7 @@
unsigned fragmentCount = fragments.size();
for (unsigned i = 0; i < fragmentCount; ++i) {
- m_chunkLayoutBuilder.transformationForTextBox(textBox, textBoxTransformation);
+ textBoxTransformation = m_chunkLayoutBuilder.transformationForTextBox(textBox);
if (textBoxTransformation.isIdentity())
continue;
ASSERT(fragments[i].lengthAdjustTransform.isIdentity());
@@ -553,15 +538,16 @@
if (textPathOffset > m_textPathLength)
break;
- bool ok = false;
- FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok);
- ASSERT(ok);
+ bool success = false;
+ auto traversalState(m_textPath.traversalStateAtLength(textPathOffset, success));
+ ASSERT(success);
+ FloatPoint point = traversalState.current();
x = point.x();
y = point.y();
- angle = m_textPath.normalAngleAtLength(textPathOffset, ok);
- ASSERT(ok);
+ angle = traversalState.normalAngle();
+
// For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
if (m_isVerticalText)
angle -= 90;
Modified: trunk/Source/WebCore/svg/SVGAnimateMotionElement.cpp (182827 => 182828)
--- trunk/Source/WebCore/svg/SVGAnimateMotionElement.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/svg/SVGAnimateMotionElement.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -24,6 +24,7 @@
#include "AffineTransform.h"
#include "ElementIterator.h"
+#include "PathTraversalState.h"
#include "RenderSVGResource.h"
#include "SVGImageElement.h"
#include "SVGMPathElement.h"
@@ -212,16 +213,19 @@
{
ASSERT(!m_animationPath.isEmpty());
- bool ok = false;
+ bool success = false;
float positionOnPath = m_animationPath.length() * percentage;
- FloatPoint position = m_animationPath.pointAtLength(positionOnPath, ok);
- if (!ok)
+ auto traversalState(m_animationPath.traversalStateAtLength(positionOnPath, success));
+ if (!success)
return;
+
+ FloatPoint position = traversalState.current();
+ float angle = traversalState.normalAngle();
+
transform->translate(position.x(), position.y());
RotateMode rotateMode = this->rotateMode();
if (rotateMode != RotateAuto && rotateMode != RotateAutoReverse)
return;
- float angle = m_animationPath.normalAngleAtLength(positionOnPath, ok);
if (rotateMode == RotateAutoReverse)
angle += 180;
transform->rotate(angle);
Modified: trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.cpp (182827 => 182828)
--- trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -29,68 +29,57 @@
SVGPathTraversalStateBuilder::SVGPathTraversalStateBuilder()
: m_traversalState(0)
+ , m_segmentIndex(0)
{
}
void SVGPathTraversalStateBuilder::moveTo(const FloatPoint& targetPoint, bool, PathCoordinateMode)
{
ASSERT(m_traversalState);
- m_traversalState->m_totalLength += m_traversalState->moveTo(targetPoint);
+ m_traversalState->processPathElement(PathElementMoveToPoint, &targetPoint);
}
void SVGPathTraversalStateBuilder::lineTo(const FloatPoint& targetPoint, PathCoordinateMode)
{
ASSERT(m_traversalState);
- m_traversalState->m_totalLength += m_traversalState->lineTo(targetPoint);
+ m_traversalState->processPathElement(PathElementAddLineToPoint, &targetPoint);
}
void SVGPathTraversalStateBuilder::curveToCubic(const FloatPoint& point1, const FloatPoint& point2, const FloatPoint& targetPoint, PathCoordinateMode)
{
+ FloatPoint points[] = { point1, point2, targetPoint };
ASSERT(m_traversalState);
- m_traversalState->m_totalLength += m_traversalState->cubicBezierTo(point1, point2, targetPoint);
+ m_traversalState->processPathElement(PathElementAddCurveToPoint, points);
}
void SVGPathTraversalStateBuilder::closePath()
{
ASSERT(m_traversalState);
- m_traversalState->m_totalLength += m_traversalState->closeSubpath();
+ m_traversalState->processPathElement(PathElementCloseSubpath, nullptr);
}
void SVGPathTraversalStateBuilder::setDesiredLength(float desiredLength)
{
ASSERT(m_traversalState);
- m_traversalState->m_desiredLength = desiredLength;
+ m_traversalState->setDesiredLength(desiredLength);
}
bool SVGPathTraversalStateBuilder::continueConsuming()
{
ASSERT(m_traversalState);
- m_traversalState->processSegment();
- return !m_traversalState->m_success;
+ return !m_traversalState->success();
}
-void SVGPathTraversalStateBuilder::incrementPathSegmentCount()
-{
- ASSERT(m_traversalState);
- ++m_traversalState->m_segmentIndex;
-}
-
-unsigned SVGPathTraversalStateBuilder::pathSegmentIndex()
-{
- ASSERT(m_traversalState);
- return m_traversalState->m_segmentIndex;
-}
-
float SVGPathTraversalStateBuilder::totalLength()
{
ASSERT(m_traversalState);
- return m_traversalState->m_totalLength;
+ return m_traversalState->totalLength();
}
SVGPoint SVGPathTraversalStateBuilder::currentPoint()
{
ASSERT(m_traversalState);
- return m_traversalState->m_current;
+ return m_traversalState->current();
}
}
Modified: trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.h (182827 => 182828)
--- trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.h 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.h 2015-04-15 01:34:25 UTC (rev 182828)
@@ -32,15 +32,15 @@
public:
SVGPathTraversalStateBuilder();
- unsigned pathSegmentIndex();
+ unsigned pathSegmentIndex() { return m_segmentIndex; }
float totalLength();
SVGPoint currentPoint();
void setCurrentTraversalState(PathTraversalState* traversalState) { m_traversalState = traversalState; }
void setDesiredLength(float);
- virtual void incrementPathSegmentCount() override;
+ virtual void incrementPathSegmentCount() override { ++m_segmentIndex; }
virtual bool continueConsuming() override;
- virtual void cleanup() override { m_traversalState = 0; }
+ virtual void cleanup() override { m_traversalState = nullptr, m_segmentIndex = 0; }
private:
// Used in UnalteredParsing/NormalizedParsing modes.
@@ -59,6 +59,7 @@
virtual void arcTo(float, float, float, bool, bool, const FloatPoint&, PathCoordinateMode) override { ASSERT_NOT_REACHED(); }
PathTraversalState* m_traversalState;
+ unsigned m_segmentIndex;
};
} // namespace WebCore
Modified: trunk/Source/WebCore/svg/SVGPathUtilities.cpp (182827 => 182828)
--- trunk/Source/WebCore/svg/SVGPathUtilities.cpp 2015-04-15 00:49:03 UTC (rev 182827)
+++ trunk/Source/WebCore/svg/SVGPathUtilities.cpp 2015-04-15 01:34:25 UTC (rev 182828)
@@ -285,7 +285,7 @@
if (stream->isEmpty())
return false;
- PathTraversalState traversalState(PathTraversalState::TraversalSegmentAtLength);
+ PathTraversalState traversalState(PathTraversalState::Action::SegmentAtLength);
SVGPathTraversalStateBuilder* builder = globalSVGPathTraversalStateBuilder(traversalState, length);
auto source = std::make_unique<SVGPathByteStreamSource>(stream);
@@ -302,7 +302,7 @@
if (stream->isEmpty())
return false;
- PathTraversalState traversalState(PathTraversalState::TraversalTotalLength);
+ PathTraversalState traversalState(PathTraversalState::Action::TotalLength);
SVGPathTraversalStateBuilder* builder = globalSVGPathTraversalStateBuilder(traversalState, 0);
auto source = std::make_unique<SVGPathByteStreamSource>(stream);
@@ -319,7 +319,7 @@
if (stream->isEmpty())
return false;
- PathTraversalState traversalState(PathTraversalState::TraversalPointAtLength);
+ PathTraversalState traversalState(PathTraversalState::Action::VectorAtLength);
SVGPathTraversalStateBuilder* builder = globalSVGPathTraversalStateBuilder(traversalState, length);
auto source = std::make_unique<SVGPathByteStreamSource>(stream);