Changeset 182828 in webkit


Ignore:
Timestamp:
Apr 14, 2015 6:34:25 PM (9 years ago)
Author:
commit-queue@webkit.org
Message:

textPath layout performance improvement.
https://bugs.webkit.org/show_bug.cgi?id=141570.

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2015-04-14
Reviewed by Darin Adler.

PerformanceTests:

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.

Source/WebCore:

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.

Location:
trunk
Files:
2 added
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/PerformanceTests/ChangeLog

    r182744 r182828  
     12015-04-14  Said Abou-Hallawa  <sabouhallawa@apple.com>
     2
     3        textPath layout performance improvement.
     4        https://bugs.webkit.org/show_bug.cgi?id=141570.
     5
     6        Reviewed by Darin Adler.
     7
     8        Cut down the time spent in traversing the path for text by 50%. Instead
     9        of traversing the path twice at a certain length: one time for the position
     10        and the second time for the angle, we can merge these two passes into one.
     11
     12        * SVG/TextOnPathSimple.html: Added.
     13        * SVG/resources/TextOnPathSimple.svg: Added.
     14
    1152015-04-13  Zalan Bujtas  <zalan@apple.com>
    216
  • trunk/Source/WebCore/ChangeLog

    r182824 r182828  
     12015-04-14  Said Abou-Hallawa  <sabouhallawa@apple.com>
     2
     3        textPath layout performance improvement.
     4        https://bugs.webkit.org/show_bug.cgi?id=141570.
     5
     6        Reviewed by Darin Adler.
     7
     8        The bottleneck of the text-on-path performance is the position and angle
     9        calculations for every single character. If the number of characters is
     10        'n' and the number of path elements is 'm', the total number of processing
     11        the path elements is O(2 x n x m). What makes it really worse is, for every
     12        curve we keep splitting the curve till the split curve is almost a straight
     13        line. The changes we need to do are:
     14        1. Merge the position and the angle traversals in one pass since they are
     15           returning info for the same length on the path. There is a degenerate
     16           case for the starting point when calculating the angle. The original
     17           code was solving this problem by passing an epsilon instead of zero but
     18           because traversing the path for position and angle are now merged, we
     19           will pass zero for the starting point as is. All we need is to move one
     20           step ahead without moving the position. We need the extra step forward
     21           to calculate the slope of the path at the starting point.
     22        2. We need to add a new mode to traversing a path. The new mode will take
     23           a vector of lengths and returns a vector of arrow vectors. Every arrow
     24           vector represents a position and an angle on the path at a certain length.
     25           This requires changing the SVGTextLayoutEngine to calculate the lengths
     26           of the characters on the curve first and then passing all of them to the
     27           path traversal function. Instead of traversing the path for every length,
     28           we are going to get the required point and angle from the vector of arrow
     29           vectors.
     30
     31        This patch is addressing the first fix only. The second one will require
     32        refactoring the SVGTextLayoutEngine so I am going to address it in a
     33        different patch.
     34
     35        * platform/graphics/Path.cpp:
     36        (WebCore::pathLengthApplierFunction): It is cleaner to move the function
     37        of this method to PathTraversalState::processPathElement().
     38       
     39        (WebCore::Path::length): Use new enum Action value and access methods.
     40       
     41        (WebCore::Path::traversalStateAtLength): New function which returns the
     42        traversalState at a certain length on a path.
     43       
     44        (WebCore::Path::pointAtLength):
     45        (WebCore::Path::normalAngleAtLength): Use traversalStateAtLength() to get
     46        the traversalState and from it return either the position or the angle.
     47       
     48        * platform/graphics/Path.h: Define traversalStateAtLength().
     49       
     50        * platform/graphics/PathTraversalState.cpp:
     51        (WebCore::distanceLine): Code clean up.
     52       
     53        (WebCore::curveLength): Make the setting of m_previous and m_current happens
     54        only in this function.
     55       
     56        (WebCore::PathTraversalState::PathTraversalState): Add an optional parameter
     57        for the desired length and move the initialization of the other members to
     58        the class definition.
     59       
     60        (WebCore::PathTraversalState::closeSubpath):
     61        (WebCore::PathTraversalState::moveTo):
     62        (WebCore::PathTraversalState::lineTo): Add the distance to the m_totalLength
     63        instead of returning it since this is what all the callers were doing.
     64       
     65        (WebCore::PathTraversalState::quadraticBezierTo):
     66        (WebCore::PathTraversalState::cubicBezierTo): Add the distance to the
     67        m_totalLength. Move the setting of m_previous and m_current to curveLength().
     68        Remove unused members m_control1 and m_control2.
     69
     70        (WebCore::PathTraversalState::processSegment): Deleted.       
     71        (WebCore::PathTraversalState::finalizeAppendPathElement): Create a new
     72        name for the function. Handle the case of the angle at the starting point
     73        where m_desiredLength is set to zero. The new flag m_isZeroVector will be
     74        set to notify the caller that the  next iteration will be the last one and
     75        it is only needed for the calculating the angle of a zero vector. m_current
     76        should not change by this last iteration.
     77       
     78        (WebCore::PathTraversalState::appendPathElement): This code is moved from
     79        pathLengthApplierFunction().
     80       
     81        (WebCore::PathTraversalState::processPathElement): This function is used
     82        by the class Path. It is a wrapper for appendPathElement(). If m_isZeroVector
     83        is set we append the new element to a copy for the PathTraversalState just
     84        to get the angle for the zero vector.
     85
     86        * platform/graphics/PathTraversalState.h: Change the enum values to not
     87        not include the class or the enum class. Make the data members private and
     88        expose the needed ones through access methods. Make all the internal methods
     89        to be private.
     90       
     91        (WebCore::PathTraversalState::processPathElement):  Another wrapper for
     92        appendPathElement() which is used by SVGPathTraversalStateBuilder.
     93       
     94        (WebCore::PathTraversalState::action):
     95        (WebCore::PathTraversalState::setAction):
     96        (WebCore::PathTraversalState::desiredLength):
     97        (WebCore::PathTraversalState::setDesiredLength):
     98        (WebCore::PathTraversalState::success):
     99        (WebCore::PathTraversalState::totalLength):
     100        (WebCore::PathTraversalState::current):
     101        (WebCore::PathTraversalState::normalAngle): New access methods which are now
     102        needed after making the data members private.
     103       
     104        * rendering/svg/SVGRootInlineBox.cpp:
     105        (WebCore::SVGRootInlineBox::layoutCharactersInTextBoxes): Make the casting
     106        of the renderer on the caller side.
     107       
     108        * rendering/svg/SVGTextChunk.cpp:
     109        (WebCore::SVGTextChunk::SVGTextChunk): The constructor should append the
     110        elements of m_boxes instead of making this from outside the class.
     111       
     112        (WebCore::SVGTextChunk::totalCharacters):
     113        (WebCore::SVGTextChunk::totalLength):
     114        (WebCore::SVGTextChunk::calculateLength): Deleted.
     115        Replace calculateLength() by totalCharacters() and totalLength() to make
     116        the interface cleaner.
     117       
     118        (WebCore::SVGTextChunk::totalAnchorShift):
     119        (WebCore::SVGTextChunk::calculateTextAnchorShift): Deleted.
     120        Rename the function name.
     121       
     122        (WebCore::SVGTextChunk::layout):
     123        (WebCore::SVGTextChunk::processTextLengthSpacingCorrection):
     124        (WebCore::SVGTextChunk::buildBoxTransformations):
     125        (WebCore::SVGTextChunk::boxSpacingAndGlyphsTransform):
     126        (WebCore::SVGTextChunk::processTextAnchorCorrection):
     127        Move the chunk layout code from SVGTextChunkBuilder::layoutTextChunks()
     128        to the SVGTextChunk::layout(). Move all the helper functions as well.
     129       
     130        * rendering/svg/SVGTextChunk.h:
     131        (WebCore::SVGTextChunk::hasTextAnchor):
     132        (WebCore::SVGTextChunk::boxes): Deleted.
     133        Add the new methods and change most of the public methods to be private.
     134       
     135        * rendering/svg/SVGTextChunkBuilder.cpp:
     136        (WebCore::SVGTextChunkBuilder::totalCharacters):
     137        (WebCore::SVGTextChunkBuilder::totalLength):
     138        (WebCore::SVGTextChunkBuilder::totalAnchorShift): This code is moved from
     139        SVGTextLayoutEngine. It scans the boxes stored in the SVGTextChunkBuilder
     140        and sums up the total values.
     141       
     142        (WebCore::SVGTextChunkBuilder::transformationForTextBox):
     143        (WebCore::SVGTextChunkBuilder::buildTextChunks):
     144        (WebCore::SVGTextChunkBuilder::layoutTextChunks): Code clean up.
     145       
     146        (WebCore::SVGTextChunkBuilder::addTextChunk): Deleted.
     147        (WebCore::SVGTextChunkBuilder::processTextChunk): Deleted.
     148        (WebCore::SVGTextChunkBuilder::processTextLengthSpacingCorrection): Deleted.
     149        (WebCore::SVGTextChunkBuilder::processTextAnchorCorrection): Deleted.
     150        (WebCore::SVGTextChunkBuilder::buildSpacingAndGlyphsTransform): Deleted.
     151        This code now lives in SVGTextChunk.
     152       
     153        * rendering/svg/SVGTextChunkBuilder.h: Add new methods for code which was
     154        moved from SVGTextLayoutEngine and remove methods for code which was removed
     155        to SVGTextChunk.
     156       
     157        * rendering/svg/SVGTextLayoutEngine.cpp:
     158        (WebCore::SVGTextLayoutEngine::beginTextPathLayout): Use the sum up methods
     159        from SVGTextChunkBuilder instead of looping through the chunks. Also get a
     160        clean order for defining variables and doing the calculations.
     161       
     162        (WebCore::SVGTextLayoutEngine::finalizeTransformMatrices): Code clean up.
     163       
     164        (WebCore::SVGTextLayoutEngine::layoutTextOnLineOrPath): Do a single path
     165        traversal to get the position and the angle for a length on a path.
     166       
     167        * svg/SVGAnimateMotionElement.cpp:
     168        (WebCore::SVGAnimateMotionElement::buildTransformForProgress): Do a single
     169        path traversal to get the position and the angle at a length on a path.
     170       
     171        * svg/SVGPathTraversalStateBuilder.cpp:
     172        (WebCore::SVGPathTraversalStateBuilder::SVGPathTraversalStateBuilder):
     173        (WebCore::SVGPathTraversalStateBuilder::moveTo):
     174        (WebCore::SVGPathTraversalStateBuilder::lineTo):
     175        (WebCore::SVGPathTraversalStateBuilder::curveToCubic):
     176        (WebCore::SVGPathTraversalStateBuilder::closePath):
     177        (WebCore::SVGPathTraversalStateBuilder::setDesiredLength):
     178        (WebCore::SVGPathTraversalStateBuilder::continueConsuming):
     179        (WebCore::SVGPathTraversalStateBuilder::totalLength):
     180        (WebCore::SVGPathTraversalStateBuilder::currentPoint):
     181        (WebCore::SVGPathTraversalStateBuilder::incrementPathSegmentCount): Deleted.
     182        (WebCore::SVGPathTraversalStateBuilder::pathSegmentIndex): Deleted.
     183        * svg/SVGPathTraversalStateBuilder.h:
     184        (WebCore::SVGPathTraversalStateBuilder::pathSegmentIndex):
     185        Code clean up.
     186       
     187        * svg/SVGPathUtilities.cpp:
     188        (WebCore::getSVGPathSegAtLengthFromSVGPathByteStream):
     189        (WebCore::getTotalLengthOfSVGPathByteStream):
     190        (WebCore::getPointAtLengthOfSVGPathByteStream): Use new TraversalState::Action
     191        enum values.
     192
    11932015-04-14  Simon Fraser  <simon.fraser@apple.com>
    2194
  • trunk/Source/WebCore/platform/graphics/Path.cpp

    r165676 r182828  
    4343{
    4444    PathTraversalState& traversalState = *static_cast<PathTraversalState*>(info);
    45     if (traversalState.m_success)
    46         return;
    47     FloatPoint* points = element->points;
    48     float segmentLength = 0;
    49     switch (element->type) {
    50         case PathElementMoveToPoint:
    51             segmentLength = traversalState.moveTo(points[0]);
    52             break;
    53         case PathElementAddLineToPoint:
    54             segmentLength = traversalState.lineTo(points[0]);
    55             break;
    56         case PathElementAddQuadCurveToPoint:
    57             segmentLength = traversalState.quadraticBezierTo(points[0], points[1]);
    58             break;
    59         case PathElementAddCurveToPoint:
    60             segmentLength = traversalState.cubicBezierTo(points[0], points[1], points[2]);
    61             break;
    62         case PathElementCloseSubpath:
    63             segmentLength = traversalState.closeSubpath();
    64             break;
    65     }
    66     traversalState.m_totalLength += segmentLength;
    67     traversalState.processSegment();
     45    traversalState.processPathElement(element);
    6846}
    6947
    7048float Path::length() const
    7149{
    72     PathTraversalState traversalState(PathTraversalState::TraversalTotalLength);
     50    PathTraversalState traversalState(PathTraversalState::Action::TotalLength);
    7351    apply(&traversalState, pathLengthApplierFunction);
    74     return traversalState.m_totalLength;
     52    return traversalState.totalLength();
    7553}
    7654
    77 FloatPoint Path::pointAtLength(float length, bool& ok) const
     55PathTraversalState Path::traversalStateAtLength(float length, bool& success) const
    7856{
    79     PathTraversalState traversalState(PathTraversalState::TraversalPointAtLength);
    80     traversalState.m_desiredLength = length;
     57    PathTraversalState traversalState(PathTraversalState::Action::VectorAtLength, length);
    8158    apply(&traversalState, pathLengthApplierFunction);
    82     ok = traversalState.m_success;
    83     return traversalState.m_current;
     59    success = traversalState.success();
     60    return traversalState;
    8461}
    8562
    86 float Path::normalAngleAtLength(float length, bool& ok) const
     63FloatPoint Path::pointAtLength(float length, bool& success) const
    8764{
    88     PathTraversalState traversalState(PathTraversalState::TraversalNormalAngleAtLength);
    89     traversalState.m_desiredLength = length ? length : std::numeric_limits<float>::epsilon();
    90     apply(&traversalState, pathLengthApplierFunction);
    91     ok = traversalState.m_success;
    92     return traversalState.m_normalAngle;
     65    return traversalStateAtLength(length, success).current();
     66}
     67
     68float Path::normalAngleAtLength(float length, bool& success) const
     69{
     70    return traversalStateAtLength(length, success).normalAngle();
    9371}
    9472
  • trunk/Source/WebCore/platform/graphics/Path.h

    r180882 r182828  
    6161    class FloatSize;
    6262    class GraphicsContext;
     63    class PathTraversalState;
    6364    class RoundedRect;
    6465    class StrokeStyleApplier;
     
    101102        FloatRect fastBoundingRect() const;
    102103        FloatRect strokeBoundingRect(StrokeStyleApplier* = 0) const;
    103        
     104
    104105        float length() const;
    105         FloatPoint pointAtLength(float length, bool& ok) const;
    106         float normalAngleAtLength(float length, bool& ok) const;
     106        PathTraversalState traversalStateAtLength(float length, bool& success) const;
     107        FloatPoint pointAtLength(float length, bool& success) const;
     108        float normalAngleAtLength(float length, bool& success) const;
    107109
    108110        WEBCORE_EXPORT void clear();
  • trunk/Source/WebCore/platform/graphics/PathTraversalState.cpp

    r138800 r182828  
    11/*
    22 * Copyright (C) 2006, 2007 Eric Seidel <eric@webkit.org>
     3 * Copyright (C) 2015 Apple Inc.  All rights reserved.
    34 *
    45 * This library is free software; you can redistribute it and/or
     
    3536static inline float distanceLine(const FloatPoint& start, const FloatPoint& end)
    3637{
    37     return sqrtf((end.x() - start.x()) * (end.x() - start.x()) + (end.y() - start.y()) * (end.y() - start.y()));
     38    float dx = end.x() - start.x();
     39    float dy = end.y() - start.y();
     40    return sqrtf(dx * dx + dy * dy);
    3841}
    3942
     
    115118// approximateDistance() + current total distance > desired distance
    116119template<class CurveType>
    117 static float curveLength(PathTraversalState& traversalState, CurveType curve)
     120static float curveLength(const PathTraversalState& traversalState, const CurveType& originalCurve, FloatPoint& previous, FloatPoint& current)
    118121{
    119122    static const unsigned curveStackDepthLimit = 20;
    120 
    121     Vector<CurveType> curveStack;
    122     curveStack.append(curve);
    123 
     123    CurveType curve = originalCurve;
     124    Vector<CurveType, curveStackDepthLimit> curveStack;
    124125    float totalLength = 0;
    125     do {
     126
     127    while (true) {
    126128        float length = curve.approximateDistance();
    127         if ((length - distanceLine(curve.start, curve.end)) > kPathSegmentLengthTolerance && curveStack.size() <= curveStackDepthLimit) {
     129
     130        if ((length - distanceLine(curve.start, curve.end)) > kPathSegmentLengthTolerance && curveStack.size() < curveStackDepthLimit) {
    128131            CurveType leftCurve;
    129132            CurveType rightCurve;
     
    131134            curve = leftCurve;
    132135            curveStack.append(rightCurve);
    133         } else {
    134             totalLength += length;
    135             if (traversalState.m_action == PathTraversalState::TraversalPointAtLength
    136              || traversalState.m_action == PathTraversalState::TraversalNormalAngleAtLength) {
    137                 traversalState.m_previous = curve.start;
    138                 traversalState.m_current = curve.end;
    139                 if (traversalState.m_totalLength + totalLength > traversalState.m_desiredLength)
    140                     return totalLength;
    141             }
    142             curve = curveStack.last();
    143             curveStack.removeLast();
     136            continue;
    144137        }
    145     } while (!curveStack.isEmpty());
    146    
     138
     139        totalLength += length;
     140        if (traversalState.action() == PathTraversalState::Action::VectorAtLength) {
     141            previous = curve.start;
     142            current = curve.end;
     143            if (traversalState.totalLength() + totalLength > traversalState.desiredLength())
     144                break;
     145        }
     146
     147        if (curveStack.isEmpty())
     148            break;
     149
     150        curve = curveStack.last();
     151        curveStack.removeLast();
     152    }
     153
     154    if (traversalState.action() != PathTraversalState::Action::VectorAtLength) {
     155        ASSERT(curve.end == originalCurve.end);
     156        previous = curve.start;
     157        current = curve.end;
     158    }
     159
    147160    return totalLength;
    148161}
    149162
    150 PathTraversalState::PathTraversalState(PathTraversalAction action)
     163PathTraversalState::PathTraversalState(Action action, float desiredLength)
    151164    : m_action(action)
    152     , m_success(false)
    153     , m_totalLength(0)
    154     , m_segmentIndex(0)
    155     , m_desiredLength(0)
    156     , m_normalAngle(0)
    157 {
    158 }
    159 
    160 float PathTraversalState::closeSubpath()
    161 {
    162     float distance = distanceLine(m_current, m_start);
    163     m_current = m_control1 = m_control2 = m_start;
    164     return distance;
    165 }
    166 
    167 float PathTraversalState::moveTo(const FloatPoint& point)
    168 {
    169     m_current = m_start = m_control1 = m_control2 = point;
    170     return 0;
    171 }
    172 
    173 float PathTraversalState::lineTo(const FloatPoint& point)
    174 {
    175     float distance = distanceLine(m_current, point);
    176     m_current = m_control1 = m_control2 = point;
    177     return distance;
    178 }
    179 
    180 float PathTraversalState::quadraticBezierTo(const FloatPoint& newControl, const FloatPoint& newEnd)
    181 {
    182     float distance = curveLength<QuadraticBezier>(*this, QuadraticBezier(m_current, newControl, newEnd));
    183 
    184     m_control1 = newControl;
    185     m_control2 = newEnd;
    186 
    187     if (m_action != TraversalPointAtLength && m_action != TraversalNormalAngleAtLength)
    188         m_current = newEnd;
    189 
    190     return distance;
    191 }
    192 
    193 float PathTraversalState::cubicBezierTo(const FloatPoint& newControl1, const FloatPoint& newControl2, const FloatPoint& newEnd)
    194 {
    195     float distance = curveLength<CubicBezier>(*this, CubicBezier(m_current, newControl1, newControl2, newEnd));
    196 
    197     m_control1 = newEnd;
    198     m_control2 = newControl2;
    199  
    200     if (m_action != TraversalPointAtLength && m_action != TraversalNormalAngleAtLength)
    201         m_current = newEnd;
    202 
    203     return distance;
    204 }
    205 
    206 void PathTraversalState::processSegment()
    207 {
    208     if (m_action == TraversalSegmentAtLength && m_totalLength >= m_desiredLength)
    209         m_success = true;
    210        
    211     if ((m_action == TraversalPointAtLength || m_action == TraversalNormalAngleAtLength) && m_totalLength >= m_desiredLength) {
     165    , m_desiredLength(desiredLength)
     166{
     167    ASSERT(action != Action::TotalLength || !desiredLength);
     168}
     169
     170void PathTraversalState::closeSubpath()
     171{
     172    m_totalLength += distanceLine(m_current, m_start);
     173    m_current = m_start;
     174}
     175
     176void PathTraversalState::moveTo(const FloatPoint& point)
     177{
     178    m_previous = m_current = m_start = point;
     179}
     180
     181void PathTraversalState::lineTo(const FloatPoint& point)
     182{
     183    m_totalLength += distanceLine(m_current, point);
     184    m_current = point;
     185}
     186
     187void PathTraversalState::quadraticBezierTo(const FloatPoint& newControl, const FloatPoint& newEnd)
     188{
     189    m_totalLength += curveLength<QuadraticBezier>(*this, QuadraticBezier(m_current, newControl, newEnd), m_previous, m_current);
     190}
     191
     192void PathTraversalState::cubicBezierTo(const FloatPoint& newControl1, const FloatPoint& newControl2, const FloatPoint& newEnd)
     193{
     194    m_totalLength += curveLength<CubicBezier>(*this, CubicBezier(m_current, newControl1, newControl2, newEnd), m_previous, m_current);
     195}
     196
     197bool PathTraversalState::finalizeAppendPathElement()
     198{
     199    if (m_action == Action::TotalLength)
     200        return false;
     201
     202    if (m_action == Action::SegmentAtLength) {
     203        if (m_totalLength >= m_desiredLength)
     204            m_success = true;
     205        return m_success;
     206    }
     207
     208    ASSERT(m_action == Action::VectorAtLength);
     209
     210    if (m_totalLength >= m_desiredLength) {
    212211        float slope = FloatPoint(m_current - m_previous).slopeAngleRadians();
    213         if (m_action == TraversalPointAtLength) {
    214             float offset = m_desiredLength - m_totalLength;
    215             m_current.move(offset * cosf(slope), offset * sinf(slope));
    216         } else
     212        float offset = m_desiredLength - m_totalLength;
     213        m_current.move(offset * cosf(slope), offset * sinf(slope));
     214
     215        if (!m_isZeroVector && !m_desiredLength)
     216            m_isZeroVector = true;
     217        else {
     218            m_success = true;
    217219            m_normalAngle = rad2deg(slope);
    218         m_success = true;
    219     }
     220        }
     221    }
     222
    220223    m_previous = m_current;
    221 }
    222 
    223 }
    224 
     224    return m_success;
     225}
     226
     227bool PathTraversalState::appendPathElement(PathElementType type, const FloatPoint* points)
     228{
     229    switch (type) {
     230    case PathElementMoveToPoint:
     231        moveTo(points[0]);
     232        break;
     233    case PathElementAddLineToPoint:
     234        lineTo(points[0]);
     235        break;
     236    case PathElementAddQuadCurveToPoint:
     237        quadraticBezierTo(points[0], points[1]);
     238        break;
     239    case PathElementAddCurveToPoint:
     240        cubicBezierTo(points[0], points[1], points[2]);
     241        break;
     242    case PathElementCloseSubpath:
     243        closeSubpath();
     244        break;
     245    }
     246   
     247    return finalizeAppendPathElement();
     248}
     249
     250bool PathTraversalState::processPathElement(PathElementType type, const FloatPoint* points)
     251{
     252    if (m_success)
     253        return true;
     254
     255    if (m_isZeroVector) {
     256        PathTraversalState traversalState(*this);
     257        m_success = traversalState.appendPathElement(type, points);
     258        m_normalAngle = traversalState.m_normalAngle;
     259        return m_success;
     260    }
     261
     262    return appendPathElement(type, points);
     263}
     264
     265}
     266
  • trunk/Source/WebCore/platform/graphics/PathTraversalState.h

    r165676 r182828  
    11/*
    22 * Copyright (C) 2006, 2007 Eric Seidel <eric@webkit.org>
     3 * Copyright (C) 2015 Apple Inc.  All rights reserved.
    34 *
    45 * Redistribution and use in source and binary forms, with or without
     
    2829
    2930#include "FloatPoint.h"
     31#include "Path.h"
    3032
    3133namespace WebCore {
     
    3335class PathTraversalState {
    3436public:
    35     enum PathTraversalAction {
    36         TraversalTotalLength,
    37         TraversalPointAtLength,
    38         TraversalSegmentAtLength,
    39         TraversalNormalAngleAtLength
     37    enum class Action {
     38        TotalLength,
     39        VectorAtLength,
     40        SegmentAtLength,
    4041    };
    4142
    42     PathTraversalState(PathTraversalAction);
    43 
    44     float closeSubpath();
    45     float moveTo(const FloatPoint&);
    46     float lineTo(const FloatPoint&);
    47     float quadraticBezierTo(const FloatPoint& newControl, const FloatPoint& newEnd);
    48     float cubicBezierTo(const FloatPoint& newControl1, const FloatPoint& newControl2, const FloatPoint& newEnd);
    49    
    50     void processSegment();
     43    PathTraversalState(Action, float desiredLength = 0);
    5144
    5245public:
    53     PathTraversalAction m_action;
    54     bool m_success;
     46    bool processPathElement(PathElementType, const FloatPoint*);
     47    bool processPathElement(const PathElement* element) { return processPathElement(element->type, element->points); }
     48
     49    Action action() const { return m_action; }
     50    void setAction(Action action) { m_action = action; }
     51    float desiredLength() const { return m_desiredLength; }
     52    void setDesiredLength(float desiredLength) { m_desiredLength = desiredLength; }
     53
     54    // Traversing output -- should be read only
     55    bool success() const { return m_success; }
     56    float totalLength() const { return m_totalLength; }
     57    FloatPoint current() const { return m_current; }
     58    float normalAngle() const { return m_normalAngle; }
     59
     60private:
     61    void closeSubpath();
     62    void moveTo(const FloatPoint&);
     63    void lineTo(const FloatPoint&);
     64    void quadraticBezierTo(const FloatPoint&, const FloatPoint&);
     65    void cubicBezierTo(const FloatPoint&, const FloatPoint&, const FloatPoint&);
     66
     67    bool finalizeAppendPathElement();
     68    bool appendPathElement(PathElementType, const FloatPoint*);
     69
     70private:
     71    Action m_action;
     72    bool m_success { false };
    5573
    5674    FloatPoint m_current;
    5775    FloatPoint m_start;
    58     FloatPoint m_control1;
    59     FloatPoint m_control2;
    6076
    61     float m_totalLength;
    62     unsigned m_segmentIndex;
    63     float m_desiredLength;
     77    float m_totalLength { 0 };
     78    float m_desiredLength { 0 };
    6479
    6580    // For normal calculations
    6681    FloatPoint m_previous;
    67     float m_normalAngle; // degrees
    68 };   
     82    float m_normalAngle { 0 }; // degrees
     83    bool m_isZeroVector { false };
     84};
    6985}
    7086
  • trunk/Source/WebCore/rendering/svg/SVGTextChunk.cpp

    r163440 r182828  
    11/*
    22 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
     3 * Copyright (C) 2015 Apple Inc. All rights reserved.
    34 *
    45 * This library is free software; you can redistribute it and/or
     
    2526namespace WebCore {
    2627
    27 SVGTextChunk::SVGTextChunk(unsigned chunkStyle, float desiredTextLength)
    28     : m_chunkStyle(chunkStyle)
    29     , m_desiredTextLength(desiredTextLength)
    30 {
    31 }
    32 
    33 void SVGTextChunk::calculateLength(float& length, unsigned& characters) const
    34 {
    35     SVGTextFragment* lastFragment = 0;
    36 
    37     unsigned boxCount = m_boxes.size();
    38     for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    39         SVGInlineTextBox* textBox = m_boxes.at(boxPosition);
    40         Vector<SVGTextFragment>& fragments = textBox->textFragments();
    41 
    42         unsigned size = fragments.size();
    43         if (!size)
    44             continue;
    45 
    46         for (unsigned i = 0; i < size; ++i) {
    47             SVGTextFragment& fragment = fragments.at(i);
     28SVGTextChunk::SVGTextChunk(const Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned first, unsigned limit)
     29{
     30    ASSERT(first < limit);
     31    ASSERT(first >= 0 && limit <= lineLayoutBoxes.size());
     32
     33    const SVGInlineTextBox* box = lineLayoutBoxes[first];
     34    const RenderStyle& style = box->renderer().style();
     35    const SVGRenderStyle& svgStyle = style.svgStyle();
     36
     37    if (!style.isLeftToRightDirection())
     38        m_chunkStyle |= SVGTextChunk::RightToLeftText;
     39
     40    if (svgStyle.isVerticalWritingMode())
     41        m_chunkStyle |= SVGTextChunk::VerticalText;
     42   
     43    switch (svgStyle.textAnchor()) {
     44    case TA_START:
     45        break;
     46    case TA_MIDDLE:
     47        m_chunkStyle |= MiddleAnchor;
     48        break;
     49    case TA_END:
     50        m_chunkStyle |= EndAnchor;
     51        break;
     52    }
     53
     54    if (auto* textContentElement = SVGTextContentElement::elementFromRenderer(box->renderer().parent())) {
     55        SVGLengthContext lengthContext(textContentElement);
     56        m_desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
     57
     58        switch (textContentElement->lengthAdjust()) {
     59        case SVGLengthAdjustUnknown:
     60            break;
     61        case SVGLengthAdjustSpacing:
     62            m_chunkStyle |= LengthAdjustSpacing;
     63            break;
     64        case SVGLengthAdjustSpacingAndGlyphs:
     65            m_chunkStyle |= LengthAdjustSpacingAndGlyphs;
     66            break;
     67        }
     68    }
     69
     70    for (unsigned i = first; i < limit; ++i)
     71        m_boxes.append(lineLayoutBoxes[i]);
     72}
     73
     74unsigned SVGTextChunk::totalCharacters() const
     75{
     76    unsigned characters = 0;
     77    for (auto* box : m_boxes) {
     78        for (auto& fragment : box->textFragments())
    4879            characters += fragment.length;
    49 
    50             if (m_chunkStyle & VerticalText)
    51                 length += fragment.height;
    52             else
    53                 length += fragment.width;
    54 
    55             if (!lastFragment) {
    56                 lastFragment = &fragment;
    57                 continue;
    58             }
    59 
    60             // Resepect gap between chunks.
    61             if (m_chunkStyle & VerticalText)
    62                  length += fragment.y - (lastFragment->y + lastFragment->height);
    63             else
    64                  length += fragment.x - (lastFragment->x + lastFragment->width);
    65 
    66             lastFragment = &fragment;
    67         }
    68     }
    69 }
    70 
    71 float SVGTextChunk::calculateTextAnchorShift(float length) const
    72 {
     80    }
     81    return characters;
     82}
     83
     84float SVGTextChunk::totalLength() const
     85{
     86    const SVGTextFragment* firstFragment = nullptr;
     87    const SVGTextFragment* lastFragment = nullptr;
     88
     89    for (auto* box : m_boxes) {
     90        auto& fragments = box->textFragments();
     91        if (fragments.size()) {
     92            firstFragment = &(*fragments.begin());
     93            break;
     94        }
     95    }
     96
     97    for (auto it = m_boxes.rbegin(), end = m_boxes.rend(); it != end; ++it) {
     98        auto& fragments = (*it)->textFragments();
     99        if (fragments.size()) {
     100            lastFragment = &(*fragments.rbegin());
     101            break;
     102        }
     103    }
     104
     105    ASSERT(!firstFragment == !lastFragment);
     106    if (!firstFragment)
     107        return 0;
     108
     109    if (m_chunkStyle & VerticalText)
     110        return (lastFragment->y + lastFragment->height) - firstFragment->y;
     111
     112    return (lastFragment->x + lastFragment->width) - firstFragment->x;
     113}
     114
     115float SVGTextChunk::totalAnchorShift() const
     116{
     117    float length = totalLength();
    73118    if (m_chunkStyle & MiddleAnchor)
    74119        return -length / 2;
     
    78123}
    79124
     125void SVGTextChunk::layout(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
     126{
     127    if (hasDesiredTextLength()) {
     128        if (hasLengthAdjustSpacing())
     129            processTextLengthSpacingCorrection();
     130        else {
     131            ASSERT(hasLengthAdjustSpacingAndGlyphs());
     132            buildBoxTransformations(textBoxTransformations);
     133        }
     134    }
     135
     136    if (hasTextAnchor())
     137        processTextAnchorCorrection();
     138}
     139
     140void SVGTextChunk::processTextLengthSpacingCorrection() const
     141{
     142    float textLengthShift = (desiredTextLength() - totalLength()) / totalCharacters();
     143    bool isVerticalText = m_chunkStyle & VerticalText;
     144    unsigned atCharacter = 0;
     145
     146    for (auto* box : m_boxes) {
     147        for (auto& fragment : box->textFragments()) {
     148            if (isVerticalText)
     149                fragment.y += textLengthShift * atCharacter;
     150            else
     151                fragment.x += textLengthShift * atCharacter;
     152           
     153            atCharacter += fragment.length;
     154        }
     155    }
     156}
     157
     158void SVGTextChunk::buildBoxTransformations(HashMap<SVGInlineTextBox*, AffineTransform>& textBoxTransformations) const
     159{
     160    AffineTransform spacingAndGlyphsTransform;
     161    bool foundFirstFragment = false;
     162
     163    for (auto* box : m_boxes) {
     164        if (!foundFirstFragment) {
     165            if (!boxSpacingAndGlyphsTransform(box, spacingAndGlyphsTransform))
     166                continue;
     167            foundFirstFragment = true;
     168        }
     169
     170        textBoxTransformations.set(box, spacingAndGlyphsTransform);
     171    }
     172}
     173
     174bool SVGTextChunk::boxSpacingAndGlyphsTransform(const SVGInlineTextBox* box, AffineTransform& spacingAndGlyphsTransform) const
     175{
     176    auto& fragments = box->textFragments();
     177    if (fragments.isEmpty())
     178        return false;
     179
     180    const SVGTextFragment& fragment = fragments.first();
     181    float scale = desiredTextLength() / totalLength();
     182
     183    spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
     184
     185    if (m_chunkStyle & VerticalText)
     186        spacingAndGlyphsTransform.scaleNonUniform(1, scale);
     187    else
     188        spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
     189
     190    spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
     191    return true;
     192}
     193
     194void SVGTextChunk::processTextAnchorCorrection() const
     195{
     196    float textAnchorShift = totalAnchorShift();
     197    bool isVerticalText = m_chunkStyle & VerticalText;
     198
     199    for (auto* box : m_boxes) {
     200        for (auto& fragment : box->textFragments()) {
     201            if (isVerticalText)
     202                fragment.y += textAnchorShift;
     203            else
     204                fragment.x += textAnchorShift;
     205        }
     206    }
     207}
     208
    80209} // namespace WebCore
  • trunk/Source/WebCore/rendering/svg/SVGTextChunk.h

    r163440 r182828  
    11/*
    22 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
     3 * Copyright (C) 2015 Apple Inc. All rights reserved.
    34 *
    45 * This library is free software; you can redistribute it and/or
     
    4142    };
    4243
    43     SVGTextChunk(unsigned chunkStyle, float desiredTextLength);
     44    SVGTextChunk(const Vector<SVGInlineTextBox*>&, unsigned first, unsigned limit);
    4445
    45     void calculateLength(float& length, unsigned& characters) const;
    46     float calculateTextAnchorShift(float length) const;
     46    unsigned totalCharacters() const;
     47    float totalLength() const;
     48    float totalAnchorShift() const;
     49    void layout(HashMap<SVGInlineTextBox*, AffineTransform>&) const;
     50
     51private:
     52    void processTextAnchorCorrection() const;
     53    void buildBoxTransformations(HashMap<SVGInlineTextBox*, AffineTransform>&) const;
     54    void processTextLengthSpacingCorrection() const;
    4755
    4856    bool isVerticalText() const { return m_chunkStyle & VerticalText; }
    4957    float desiredTextLength() const { return m_desiredTextLength; }
    5058
    51     Vector<SVGInlineTextBox*>& boxes() { return m_boxes; }
    52     const Vector<SVGInlineTextBox*>& boxes() const { return m_boxes; }
    53 
    5459    bool hasDesiredTextLength() const { return m_desiredTextLength > 0 && ((m_chunkStyle & LengthAdjustSpacing) || (m_chunkStyle & LengthAdjustSpacingAndGlyphs)); }
    55     bool hasTextAnchor() const {  return m_chunkStyle & RightToLeftText ? !(m_chunkStyle & EndAnchor) : (m_chunkStyle & MiddleAnchor) || (m_chunkStyle & EndAnchor); }
     60    bool hasTextAnchor() const {  return m_chunkStyle & RightToLeftText ? !(m_chunkStyle & EndAnchor) : (m_chunkStyle & (MiddleAnchor | EndAnchor)); }
    5661    bool hasLengthAdjustSpacing() const { return m_chunkStyle & LengthAdjustSpacing; }
    5762    bool hasLengthAdjustSpacingAndGlyphs() const { return m_chunkStyle & LengthAdjustSpacingAndGlyphs; }
     63
     64    bool boxSpacingAndGlyphsTransform(const SVGInlineTextBox*, AffineTransform&) const;
    5865
    5966private:
     
    6168    Vector<SVGInlineTextBox*> m_boxes;
    6269
    63     unsigned m_chunkStyle;
    64     float m_desiredTextLength;
     70    unsigned m_chunkStyle { DefaultStyle };
     71    float m_desiredTextLength { 0 };
    6572};
    6673
  • trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp

    r165607 r182828  
    11/*
    22 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
     3 * Copyright (C) 2015 Apple Inc. All rights reserved.
    34 *
    45 * This library is free software; you can redistribute it and/or
     
    3132}
    3233
    33 void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const
     34unsigned SVGTextChunkBuilder::totalCharacters() const
    3435{
    35     DEPRECATED_DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ());
    36     if (!m_textBoxTransformations.contains(textBox)) {
    37         transform = s_identityTransform;
    38         return;
    39     }
    40 
    41     transform = m_textBoxTransformations.get(textBox);
     36    unsigned characters = 0;
     37    for (const auto& chunk : m_textChunks)
     38        characters += chunk.totalCharacters();
     39    return characters;
    4240}
    4341
    44 void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
     42float SVGTextChunkBuilder::totalLength() const
     43{
     44    float length = 0;
     45    for (const auto& chunk : m_textChunks)
     46        length += chunk.totalLength();
     47    return length;
     48}
     49
     50float SVGTextChunkBuilder::totalAnchorShift() const
     51{
     52    float anchorShift = 0;
     53    for (const auto& chunk : m_textChunks)
     54        anchorShift += chunk.totalAnchorShift();
     55    return anchorShift;
     56}
     57
     58AffineTransform SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox) const
     59{
     60    auto it = m_textBoxTransformations.find(textBox);
     61    return it == m_textBoxTransformations.end() ? AffineTransform() : it->value;
     62}
     63
     64void SVGTextChunkBuilder::buildTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes)
    4565{
    4666    if (lineLayoutBoxes.isEmpty())
    4767        return;
    4868
    49     bool foundStart = false;
    50     unsigned lastChunkStartPosition = 0;
    51     unsigned boxPosition = 0;
    52     unsigned boxCount = lineLayoutBoxes.size();
    53     for (; boxPosition < boxCount; ++boxPosition) {
    54         SVGInlineTextBox* textBox = lineLayoutBoxes[boxPosition];
    55         if (!textBox->startsNewTextChunk())
     69    unsigned limit = lineLayoutBoxes.size();
     70    unsigned first = limit;
     71
     72    for (unsigned i = 0; i < limit; ++i) {
     73        if (!lineLayoutBoxes[i]->startsNewTextChunk())
    5674            continue;
    5775
    58         if (!foundStart) {
    59             lastChunkStartPosition = boxPosition;
    60             foundStart = true;
    61         } else {
    62             ASSERT_WITH_SECURITY_IMPLICATION(boxPosition > lastChunkStartPosition);
    63             addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
    64             lastChunkStartPosition = boxPosition;
     76        if (first == limit)
     77            first = i;
     78        else {
     79            ASSERT_WITH_SECURITY_IMPLICATION(first != i);
     80            m_textChunks.append(SVGTextChunk(lineLayoutBoxes, first, i));
     81            first = i;
    6582        }
    6683    }
    6784
    68     if (!foundStart)
    69         return;
    70 
    71     if (boxPosition - lastChunkStartPosition > 0)
    72         addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition);
     85    if (first != limit)
     86        m_textChunks.append(SVGTextChunk(lineLayoutBoxes, first, limit));
    7387}
    7488
    75 void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes)
     89void SVGTextChunkBuilder::layoutTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes)
    7690{
    7791    buildTextChunks(lineLayoutBoxes);
     
    7993        return;
    8094
    81     unsigned chunkCount = m_textChunks.size();
    82     for (unsigned i = 0; i < chunkCount; ++i)
    83         processTextChunk(m_textChunks[i]);
     95    for (const auto& chunk : m_textChunks)
     96        chunk.layout(m_textBoxTransformations);
    8497
    8598    m_textChunks.clear();
    8699}
    87100
    88 void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount)
    89 {
    90     SVGInlineTextBox* textBox = lineLayoutBoxes[boxStart];
    91     ASSERT(textBox);
    92 
    93     const RenderStyle& style = textBox->renderer().style();
    94 
    95     const SVGRenderStyle& svgStyle = style.svgStyle();
    96 
    97     // Build chunk style flags.
    98     unsigned chunkStyle = SVGTextChunk::DefaultStyle;
    99 
    100     // Handle 'direction' property.
    101     if (!style.isLeftToRightDirection())
    102         chunkStyle |= SVGTextChunk::RightToLeftText;
    103 
    104     // Handle 'writing-mode' property.
    105     if (svgStyle.isVerticalWritingMode())
    106         chunkStyle |= SVGTextChunk::VerticalText;
    107 
    108     // Handle 'text-anchor' property.
    109     switch (svgStyle.textAnchor()) {
    110     case TA_START:
    111         break;
    112     case TA_MIDDLE:
    113         chunkStyle |= SVGTextChunk::MiddleAnchor;
    114         break;
    115     case TA_END:
    116         chunkStyle |= SVGTextChunk::EndAnchor;
    117         break;
    118     };
    119 
    120     // Handle 'lengthAdjust' property.
    121     float desiredTextLength = 0;
    122     if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textBox->renderer().parent())) {
    123         SVGLengthContext lengthContext(textContentElement);
    124         desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
    125 
    126         switch (textContentElement->lengthAdjust()) {
    127         case SVGLengthAdjustUnknown:
    128             break;
    129         case SVGLengthAdjustSpacing:
    130             chunkStyle |= SVGTextChunk::LengthAdjustSpacing;
    131             break;
    132         case SVGLengthAdjustSpacingAndGlyphs:
    133             chunkStyle |= SVGTextChunk::LengthAdjustSpacingAndGlyphs;
    134             break;
    135         };
    136     }
    137 
    138     SVGTextChunk chunk(chunkStyle, desiredTextLength);
    139 
    140     Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
    141     for (unsigned i = boxStart; i < boxStart + boxCount; ++i)
    142         boxes.append(lineLayoutBoxes[i]);
    143 
    144     m_textChunks.append(chunk);
    145101}
    146 
    147 void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk)
    148 {
    149     bool processTextLength = chunk.hasDesiredTextLength();
    150     bool processTextAnchor = chunk.hasTextAnchor();
    151     if (!processTextAnchor && !processTextLength)
    152         return;
    153 
    154     const Vector<SVGInlineTextBox*>& boxes = chunk.boxes();
    155     unsigned boxCount = boxes.size();
    156     if (!boxCount)
    157         return;
    158 
    159     // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes).
    160     float chunkLength = 0;
    161     unsigned chunkCharacters = 0;
    162     chunk.calculateLength(chunkLength, chunkCharacters);
    163 
    164     bool isVerticalText = chunk.isVerticalText();
    165     if (processTextLength) {
    166         if (chunk.hasLengthAdjustSpacing()) {
    167             float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters;
    168             unsigned atCharacter = 0;
    169             for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    170                 Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
    171                 if (fragments.isEmpty())
    172                     continue;
    173                 processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter);
    174             }
    175         } else {
    176             ASSERT(chunk.hasLengthAdjustSpacingAndGlyphs());
    177             float textLengthScale = chunk.desiredTextLength() / chunkLength;
    178             AffineTransform spacingAndGlyphsTransform;
    179 
    180             bool foundFirstFragment = false;
    181             for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    182                 SVGInlineTextBox* textBox = boxes[boxPosition];
    183                 Vector<SVGTextFragment>& fragments = textBox->textFragments();
    184                 if (fragments.isEmpty())
    185                     continue;
    186 
    187                 if (!foundFirstFragment) {
    188                     foundFirstFragment = true;
    189                     buildSpacingAndGlyphsTransform(isVerticalText, textLengthScale, fragments.first(), spacingAndGlyphsTransform);
    190                 }
    191 
    192                 m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform);
    193             }
    194         }
    195     }
    196 
    197     if (!processTextAnchor)
    198         return;
    199 
    200     // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift.
    201     if (processTextLength && chunk.hasLengthAdjustSpacing()) {
    202         chunkLength = 0;
    203         chunkCharacters = 0;
    204         chunk.calculateLength(chunkLength, chunkCharacters);
    205     }
    206 
    207     float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength);
    208     for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) {
    209         Vector<SVGTextFragment>& fragments = boxes[boxPosition]->textFragments();
    210         if (fragments.isEmpty())
    211             continue;
    212         processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments);
    213     }
    214 }
    215 
    216 void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter)
    217 {
    218     unsigned fragmentCount = fragments.size();
    219     for (unsigned i = 0; i < fragmentCount; ++i) {
    220         SVGTextFragment& fragment = fragments[i];
    221 
    222         if (isVerticalText)
    223             fragment.y += textLengthShift * atCharacter;
    224         else
    225             fragment.x += textLengthShift * atCharacter;
    226 
    227         atCharacter += fragment.length;
    228     }
    229 }
    230 
    231 void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments)
    232 {
    233     unsigned fragmentCount = fragments.size();
    234     for (unsigned i = 0; i < fragmentCount; ++i) {
    235         SVGTextFragment& fragment = fragments[i];
    236 
    237         if (isVerticalText)
    238             fragment.y += textAnchorShift;
    239         else
    240             fragment.x += textAnchorShift;
    241     }
    242 }
    243 
    244 void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform)
    245 {
    246     spacingAndGlyphsTransform.translate(fragment.x, fragment.y);
    247 
    248     if (isVerticalText)
    249         spacingAndGlyphsTransform.scaleNonUniform(1, scale);
    250     else
    251         spacingAndGlyphsTransform.scaleNonUniform(scale, 1);
    252 
    253     spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y);
    254 }
    255 
    256 }
  • trunk/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h

    r163440 r182828  
    11/*
    22 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
     3 * Copyright (C) 2015 Apple Inc. All rights reserved.
    34 *
    45 * This library is free software; you can redistribute it and/or
     
    4142
    4243    const Vector<SVGTextChunk>& textChunks() const { return m_textChunks; }
    43     void transformationForTextBox(SVGInlineTextBox*, AffineTransform&) const;
     44    unsigned totalCharacters() const;
     45    float totalLength() const;
     46    float totalAnchorShift() const;
     47    AffineTransform transformationForTextBox(SVGInlineTextBox*) const;
    4448
    45     void buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes);
    46     void layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes);
    47 
    48 private:
    49     void addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxPosition, unsigned boxCount);
    50     void processTextChunk(const SVGTextChunk&);
    51 
    52     void processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>&, unsigned& atCharacter);
    53     void processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>&);
    54     void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment&, AffineTransform&);
     49    void buildTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes);
     50    void layoutTextChunks(const Vector<SVGInlineTextBox*>& lineLayoutBoxes);
    5551
    5652private:
  • trunk/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp

    r178510 r182828  
    2121#include "SVGTextLayoutEngine.h"
    2222
     23#include "PathTraversalState.h"
    2324#include "RenderSVGTextPath.h"
    2425#include "SVGElement.h"
     
    171172    if (m_textPath.isEmpty())
    172173        return;
     174
    173175    m_textPathStartOffset = textPath.startOffset();
    174176    m_textPathLength = m_textPath.length();
     
    176178        m_textPathStartOffset *= m_textPathLength;
    177179
    178     float totalLength = 0;
    179     unsigned totalCharacters = 0;
    180 
    181180    lineLayout.m_chunkLayoutBuilder.buildTextChunks(lineLayout.m_lineLayoutBoxes);
    182     const Vector<SVGTextChunk>& textChunks = lineLayout.m_chunkLayoutBuilder.textChunks();
    183 
    184     unsigned size = textChunks.size();
    185     for (unsigned i = 0; i < size; ++i) {
    186         const SVGTextChunk& chunk = textChunks.at(i);
    187 
    188         float length = 0;
    189         unsigned characters = 0;
    190         chunk.calculateLength(length, characters);
    191 
    192         // Handle text-anchor as additional start offset for text paths.
    193         m_textPathStartOffset += chunk.calculateTextAnchorShift(length);
    194 
    195         totalLength += length;
    196         totalCharacters += characters;
    197     }
    198 
     181
     182    // Handle text-anchor as additional start offset for text paths.
     183    m_textPathStartOffset += lineLayout.m_chunkLayoutBuilder.totalAnchorShift();
    199184    m_textPathCurrentOffset = m_textPathStartOffset;
    200185
    201186    // Eventually handle textLength adjustments.
    202     SVGLengthAdjustType lengthAdjust = SVGLengthAdjustUnknown;
    203     float desiredTextLength = 0;
    204 
    205     if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(&textPath)) {
    206         SVGLengthContext lengthContext(textContentElement);
    207         lengthAdjust = textContentElement->lengthAdjust();
    208         desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
    209     }
    210 
     187    auto* textContentElement = SVGTextContentElement::elementFromRenderer(&textPath);
     188    if (!textContentElement)
     189        return;
     190
     191    SVGLengthContext lengthContext(textContentElement);
     192    float desiredTextLength = textContentElement->specifiedTextLength().value(lengthContext);
    211193    if (!desiredTextLength)
    212194        return;
    213195
    214     if (lengthAdjust == SVGLengthAdjustSpacing)
     196    float totalLength = lineLayout.m_chunkLayoutBuilder.totalLength();
     197    unsigned totalCharacters = lineLayout.m_chunkLayoutBuilder.totalCharacters();
     198
     199    if (textContentElement->lengthAdjust() == SVGLengthAdjustSpacing)
    215200        m_textPathSpacing = (desiredTextLength - totalLength) / totalCharacters;
    216201    else
     
    291276        unsigned fragmentCount = fragments.size();
    292277        for (unsigned i = 0; i < fragmentCount; ++i) {
    293             m_chunkLayoutBuilder.transformationForTextBox(textBox, textBoxTransformation);
     278            textBoxTransformation = m_chunkLayoutBuilder.transformationForTextBox(textBox);
    294279            if (textBoxTransformation.isIdentity())
    295280                continue;
     
    554539                break;
    555540
    556             bool ok = false;
    557             FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok);
    558             ASSERT(ok);
    559 
     541            bool success = false;
     542            auto traversalState(m_textPath.traversalStateAtLength(textPathOffset, success));
     543            ASSERT(success);
     544
     545            FloatPoint point = traversalState.current();
    560546            x = point.x();
    561547            y = point.y();
    562             angle = m_textPath.normalAngleAtLength(textPathOffset, ok);
    563             ASSERT(ok);
     548
     549            angle = traversalState.normalAngle();
    564550
    565551            // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle!
  • trunk/Source/WebCore/svg/SVGAnimateMotionElement.cpp

    r182121 r182828  
    2525#include "AffineTransform.h"
    2626#include "ElementIterator.h"
     27#include "PathTraversalState.h"
    2728#include "RenderSVGResource.h"
    2829#include "SVGImageElement.h"
     
    213214    ASSERT(!m_animationPath.isEmpty());
    214215
    215     bool ok = false;
     216    bool success = false;
    216217    float positionOnPath = m_animationPath.length() * percentage;
    217     FloatPoint position = m_animationPath.pointAtLength(positionOnPath, ok);
    218     if (!ok)
    219         return;
     218    auto traversalState(m_animationPath.traversalStateAtLength(positionOnPath, success));
     219    if (!success)
     220        return;
     221
     222    FloatPoint position = traversalState.current();
     223    float angle = traversalState.normalAngle();
     224
    220225    transform->translate(position.x(), position.y());
    221226    RotateMode rotateMode = this->rotateMode();
    222227    if (rotateMode != RotateAuto && rotateMode != RotateAutoReverse)
    223228        return;
    224     float angle = m_animationPath.normalAngleAtLength(positionOnPath, ok);
    225229    if (rotateMode == RotateAutoReverse)
    226230        angle += 180;
  • trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.cpp

    r163440 r182828  
    3030SVGPathTraversalStateBuilder::SVGPathTraversalStateBuilder()
    3131    : m_traversalState(0)
     32    , m_segmentIndex(0)
    3233{
    3334}
     
    3637{
    3738    ASSERT(m_traversalState);
    38     m_traversalState->m_totalLength += m_traversalState->moveTo(targetPoint);
     39    m_traversalState->processPathElement(PathElementMoveToPoint, &targetPoint);
    3940}
    4041
     
    4243{
    4344    ASSERT(m_traversalState);
    44     m_traversalState->m_totalLength += m_traversalState->lineTo(targetPoint);
     45    m_traversalState->processPathElement(PathElementAddLineToPoint, &targetPoint);
    4546}
    4647
    4748void SVGPathTraversalStateBuilder::curveToCubic(const FloatPoint& point1, const FloatPoint& point2, const FloatPoint& targetPoint, PathCoordinateMode)
    4849{
     50    FloatPoint points[] = { point1, point2, targetPoint };
    4951    ASSERT(m_traversalState);
    50     m_traversalState->m_totalLength += m_traversalState->cubicBezierTo(point1, point2, targetPoint);
     52    m_traversalState->processPathElement(PathElementAddCurveToPoint, points);
    5153}
    5254
     
    5456{
    5557    ASSERT(m_traversalState);
    56     m_traversalState->m_totalLength += m_traversalState->closeSubpath();
     58    m_traversalState->processPathElement(PathElementCloseSubpath, nullptr);
    5759}
    5860
     
    6062{
    6163    ASSERT(m_traversalState);
    62     m_traversalState->m_desiredLength = desiredLength;
     64    m_traversalState->setDesiredLength(desiredLength);
    6365}
    6466
     
    6668{
    6769    ASSERT(m_traversalState);   
    68     m_traversalState->processSegment();
    69     return !m_traversalState->m_success;
    70 }
    71 
    72 void SVGPathTraversalStateBuilder::incrementPathSegmentCount()
    73 {
    74     ASSERT(m_traversalState);
    75     ++m_traversalState->m_segmentIndex;
    76 }
    77 
    78 unsigned SVGPathTraversalStateBuilder::pathSegmentIndex()
    79 {
    80     ASSERT(m_traversalState);
    81     return m_traversalState->m_segmentIndex;
     70    return !m_traversalState->success();
    8271}
    8372
     
    8574{
    8675    ASSERT(m_traversalState);
    87     return m_traversalState->m_totalLength;
     76    return m_traversalState->totalLength();
    8877}
    8978
     
    9180{
    9281    ASSERT(m_traversalState);
    93     return m_traversalState->m_current;
     82    return m_traversalState->current();
    9483}
    9584
  • trunk/Source/WebCore/svg/SVGPathTraversalStateBuilder.h

    r163440 r182828  
    3333    SVGPathTraversalStateBuilder();
    3434
    35     unsigned pathSegmentIndex();
     35    unsigned pathSegmentIndex() { return m_segmentIndex; }
    3636    float totalLength();
    3737    SVGPoint currentPoint();
     
    3939    void setCurrentTraversalState(PathTraversalState* traversalState) { m_traversalState = traversalState; }
    4040    void setDesiredLength(float);
    41     virtual void incrementPathSegmentCount() override;
     41    virtual void incrementPathSegmentCount() override { ++m_segmentIndex; }
    4242    virtual bool continueConsuming() override;
    43     virtual void cleanup() override { m_traversalState = 0; }
     43    virtual void cleanup() override { m_traversalState = nullptr, m_segmentIndex = 0; }
    4444
    4545private:
     
    6060
    6161    PathTraversalState* m_traversalState;
     62    unsigned m_segmentIndex;
    6263};
    6364
  • trunk/Source/WebCore/svg/SVGPathUtilities.cpp

    r163440 r182828  
    286286        return false;
    287287
    288     PathTraversalState traversalState(PathTraversalState::TraversalSegmentAtLength);
     288    PathTraversalState traversalState(PathTraversalState::Action::SegmentAtLength);
    289289    SVGPathTraversalStateBuilder* builder = globalSVGPathTraversalStateBuilder(traversalState, length);
    290290
     
    303303        return false;
    304304
    305     PathTraversalState traversalState(PathTraversalState::TraversalTotalLength);
     305    PathTraversalState traversalState(PathTraversalState::Action::TotalLength);
    306306    SVGPathTraversalStateBuilder* builder = globalSVGPathTraversalStateBuilder(traversalState, 0);
    307307
     
    320320        return false;
    321321
    322     PathTraversalState traversalState(PathTraversalState::TraversalPointAtLength);
     322    PathTraversalState traversalState(PathTraversalState::Action::VectorAtLength);
    323323    SVGPathTraversalStateBuilder* builder = globalSVGPathTraversalStateBuilder(traversalState, length);
    324324
Note: See TracChangeset for help on using the changeset viewer.