Skip to content

Commit

Permalink
Refactor the current text position update in SVGTextLayoutEngine
Browse files Browse the repository at this point in the history
The main change is around the handling of "delta" adjustments
('dx' / 'dy'), that is changed to not require keeping state.
Additionally text-on-a-path layout is changed to track the
displacement from the path (the accumulated delta adjustments
in the perpendicular direction.) Baseline adjustments are
consolidated between code-paths and part of the "fragmentation"
condition is hoisted out of the per-"glyph" loop.

BUG=486669

Review URL: https://codereview.chromium.org/1883553004

Cr-Commit-Position: refs/heads/master@{#387694}
  • Loading branch information
fs authored and Commit bot committed Apr 15, 2016
1 parent 20a58a6 commit 143115b
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 128 deletions.
213 changes: 92 additions & 121 deletions third_party/WebKit/Source/core/layout/svg/SVGTextLayoutEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,12 @@ SVGTextLayoutEngine::SVGTextLayoutEngine(Vector<SVGTextLayoutAttributes*>& layou
, m_layoutAttributesPosition(0)
, m_logicalCharacterOffset(0)
, m_logicalMetricsListOffset(0)
, m_x(0)
, m_y(0)
, m_dx(0)
, m_dy(0)
, m_isVerticalText(false)
, m_inPathLayout(false)
, m_textLengthSpacingInEffect(false)
, m_textPath(nullptr)
, m_textPathCurrentOffset(0)
, m_textPathDisplacement(0)
, m_textPathSpacing(0)
, m_textPathScaling(1)
{
Expand All @@ -55,60 +52,66 @@ SVGTextLayoutEngine::SVGTextLayoutEngine(Vector<SVGTextLayoutAttributes*>& layou

SVGTextLayoutEngine::~SVGTextLayoutEngine() = default;

void SVGTextLayoutEngine::updateCharacterPositionIfNeeded(float& x, float& y)
bool SVGTextLayoutEngine::setCurrentTextPosition(const SVGCharacterData& data)
{
if (m_inPathLayout)
return;
bool hasX = !SVGTextLayoutAttributes::isEmptyValue(data.x);
if (hasX)
m_textPosition.setX(data.x);

// Replace characters x/y position, with the current text position plus any
// relative adjustments, if it doesn't specify an absolute position itself.
if (SVGTextLayoutAttributes::isEmptyValue(x))
x = m_x + m_dx;
bool hasY = !SVGTextLayoutAttributes::isEmptyValue(data.y);
if (hasY)
m_textPosition.setY(data.y);

if (SVGTextLayoutAttributes::isEmptyValue(y))
y = m_y + m_dy;

m_dx = 0;
m_dy = 0;
// If there's an absolute x/y position available, it marks the beginning of
// a new position along the path.
if (m_inPathLayout) {
// TODO(fs): If a new chunk (== absolute position) is defined while in
// path layout mode, alignment should be based on that chunk and not
// the path as a whole. (Re: the addition of m_textPathStartOffset
// below.)
if (m_isVerticalText) {
if (hasY)
m_textPathCurrentOffset = data.y + m_textPathStartOffset;
} else {
if (hasX)
m_textPathCurrentOffset = data.x + m_textPathStartOffset;
}
}
return hasX || hasY;
}

void SVGTextLayoutEngine::updateCurrentTextPosition(float x, float y, float glyphAdvance)
void SVGTextLayoutEngine::advanceCurrentTextPosition(float glyphAdvance)
{
// Update current text position after processing the character.
if (m_isVerticalText) {
m_x = x;
m_y = y + glyphAdvance;
} else {
m_x = x + glyphAdvance;
m_y = y;
}
// TODO(fs): m_textPathCurrentOffset should preferably also be updated
// here, but that requires a bit more untangling yet.
if (m_isVerticalText)
m_textPosition.setY(m_textPosition.y() + glyphAdvance);
else
m_textPosition.setX(m_textPosition.x() + glyphAdvance);
}

void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(float dx, float dy)
bool SVGTextLayoutEngine::applyRelativePositionAdjustmentsIfNeeded(const SVGCharacterData& data)
{
// Update relative positioning information.
if (SVGTextLayoutAttributes::isEmptyValue(dx) && SVGTextLayoutAttributes::isEmptyValue(dy))
return;
FloatPoint delta;
bool hasDx = !SVGTextLayoutAttributes::isEmptyValue(data.dx);
if (hasDx)
delta.setX(data.dx);

bool hasDy = !SVGTextLayoutAttributes::isEmptyValue(data.dy);
if (hasDy)
delta.setY(data.dy);

if (SVGTextLayoutAttributes::isEmptyValue(dx))
dx = 0;
if (SVGTextLayoutAttributes::isEmptyValue(dy))
dy = 0;
// Apply dx/dy value adjustments to current text position, if needed.
m_textPosition.moveBy(delta);

if (m_inPathLayout) {
if (m_isVerticalText) {
m_dx += dx;
m_dy = dy;
} else {
m_dx = dx;
m_dy += dy;
}
if (m_isVerticalText)
delta = delta.transposedPoint();

return;
m_textPathCurrentOffset += delta.x();
m_textPathDisplacement += delta.y();
}

m_dx = dx;
m_dy = dy;
return hasDx || hasDy;
}

void SVGTextLayoutEngine::computeCurrentFragmentMetrics(SVGInlineTextBox* textBox)
Expand Down Expand Up @@ -345,10 +348,16 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Line

bool didStartTextFragment = false;
bool applySpacingToNextCharacter = false;
bool needsFragmentPerGlyph = m_isVerticalText || m_inPathLayout || m_textLengthSpacingInEffect;

float lastAngle = 0;
float baselineShift = baselineLayout.calculateBaselineShift(style);
baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, textLineLayout);
float baselineShiftValue = baselineLayout.calculateBaselineShift(style);
baselineShiftValue -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, textLineLayout);
FloatPoint baselineShift;
if (m_isVerticalText)
baselineShift.setX(baselineShiftValue);
else
baselineShift.setY(-baselineShiftValue);

// Main layout algorithm.
const unsigned boxEndOffset = textBox->start() + textBox->len();
Expand All @@ -374,72 +383,50 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Line
if (it != characterDataMap.end())
data = it->value;

float x = data.x;
float y = data.y;
// TODO(fs): Use the return value to eliminate the additional
// hash-lookup below when determining if this text box should be tagged
// as starting a new text chunk.
setCurrentTextPosition(data);

// When we've advanced to the box start offset, determine using the original x/y values,
// whether this character starts a new text chunk, before doing any further processing.
if (m_visualMetricsIterator.characterOffset() == textBox->start())
textBox->setStartsNewTextChunk(logicalAttributes->context()->characterStartsNewTextChunk(m_logicalCharacterOffset));

float angle = SVGTextLayoutAttributes::isEmptyValue(data.rotate) ? 0 : data.rotate;
bool hasRelativePosition = applyRelativePositionAdjustmentsIfNeeded(data);

// Calculate glyph orientation angle.
// Determine the orientation of the current glyph.
// Font::width() calculates the resolved FontOrientation for each character,
// but is not exposed today to avoid the API complexity.
// but that value is not exposed today to avoid the API complexity.
UChar32 currentCharacter = textLineLayout.codepointAt(m_visualMetricsIterator.characterOffset());
FontOrientation fontOrientation = font.getFontDescription().orientation();
fontOrientation = adjustOrientationForCharacterInMixedVertical(fontOrientation, currentCharacter);

// Calculate glyph advance.
// Shaping engine takes care of x/y orientation shifts for different fontOrientation values.
// The shaping engine takes care of x/y orientation shifts for different fontOrientation values.
float glyphAdvance = visualMetrics.advance(fontOrientation);

// Assign current text position to x/y values, if needed.
updateCharacterPositionIfNeeded(x, y);

// Apply dx/dy value adjustments to current text position, if needed.
updateRelativePositionAdjustmentsIfNeeded(data.dx, data.dy);

// Calculate CSS 'letter-spacing' and 'word-spacing' for next character, if needed.
// Calculate CSS 'letter-spacing' and 'word-spacing' for the character, if needed.
float spacing = spacingLayout.calculateCSSSpacing(currentCharacter);

float textPathShiftX = 0;
float textPathShiftY = 0;
FloatPoint textPathShift;
float angle = 0;
FloatPoint position;
if (m_inPathLayout) {
float scaledGlyphAdvance = glyphAdvance * m_textPathScaling;
if (m_isVerticalText) {
// If there's an absolute y position available, it marks the beginning of a new position along the path.
if (!SVGTextLayoutAttributes::isEmptyValue(y))
m_textPathCurrentOffset = y + m_textPathStartOffset;

m_textPathCurrentOffset += m_dy;
m_dy = 0;

// Apply dx/dy correction and setup translations that move to the glyph midpoint.
textPathShiftX += m_dx + baselineShift;
textPathShiftY -= scaledGlyphAdvance / 2;
} else {
// If there's an absolute x position available, it marks the beginning of a new position along the path.
if (!SVGTextLayoutAttributes::isEmptyValue(x))
m_textPathCurrentOffset = x + m_textPathStartOffset;

m_textPathCurrentOffset += m_dx;
m_dx = 0;

// Apply dx/dy correction and setup translations that move to the glyph midpoint.
textPathShiftX -= scaledGlyphAdvance / 2;
textPathShiftY += m_dy - baselineShift;
}
// Setup translations that move to the glyph midpoint.
textPathShift.set(-scaledGlyphAdvance / 2, m_textPathDisplacement);
if (m_isVerticalText)
textPathShift = textPathShift.transposedPoint();
textPathShift += baselineShift;

// Calculate current offset along path.
float textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2;

// Move to next character.
m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling;

FloatPoint point;
PathPositionMapper::PositionType positionType = m_textPath->pointAndNormalAtLength(textPathOffset, point, angle);
PathPositionMapper::PositionType positionType = m_textPath->pointAndNormalAtLength(textPathOffset, position, angle);

// Skip character, if we're before the path.
if (positionType == PathPositionMapper::BeforePath) {
Expand All @@ -452,26 +439,22 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Line
if (positionType == PathPositionMapper::AfterPath)
break;

x = point.x();
y = point.y();
m_textPosition = position;

// 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;
} else {
// Apply all previously calculated shift values.
if (m_isVerticalText)
x += baselineShift;
else
y -= baselineShift;

x += m_dx;
y += m_dy;
position = m_textPosition;
position += baselineShift;
}

if (!SVGTextLayoutAttributes::isEmptyValue(data.rotate))
angle += data.rotate;

// Determine whether we have to start a new fragment.
bool shouldStartNewFragment = m_dx || m_dy || m_isVerticalText || m_inPathLayout || angle || angle != lastAngle
|| applySpacingToNextCharacter || m_textLengthSpacingInEffect;
bool shouldStartNewFragment = needsFragmentPerGlyph || hasRelativePosition
|| angle || angle != lastAngle || applySpacingToNextCharacter;

// If we already started a fragment, close it now.
if (didStartTextFragment && shouldStartNewFragment) {
Expand All @@ -487,18 +470,18 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Line
didStartTextFragment = true;
m_currentTextFragment.characterOffset = m_visualMetricsIterator.characterOffset();
m_currentTextFragment.metricsListOffset = m_visualMetricsIterator.metricsListOffset();
m_currentTextFragment.x = x;
m_currentTextFragment.y = y;
m_currentTextFragment.x = position.x();
m_currentTextFragment.y = position.y();

// Build fragment transformation.
if (angle)
m_currentTextFragment.transform.rotate(angle);

if (textPathShiftX || textPathShiftY)
m_currentTextFragment.transform.translate(textPathShiftX, textPathShiftY);
if (textPathShift.x() || textPathShift.y())
m_currentTextFragment.transform.translate(textPathShift.x(), textPathShift.y());

// In vertical text, always rotate by 90 degrees regardless of fontOrientation.
// Shaping engine takes care of the necessary orientation.
// For vertical text, always rotate by 90 degrees regardless of fontOrientation.
// The shaping engine takes care of the necessary orientation.
if (m_isVerticalText)
m_currentTextFragment.transform.rotate(90);

Expand All @@ -508,24 +491,12 @@ void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, Line
m_currentTextFragment.lengthAdjustScale = m_textPathScaling;
}

// Update current text position, after processing of the current character finished.
if (m_inPathLayout) {
updateCurrentTextPosition(x, y, glyphAdvance);
} else {
// Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed.
if (spacing)
applySpacingToNextCharacter = true;

float xNew = x - m_dx;
float yNew = y - m_dy;
// Advance current text position after processing of the current character finished.
advanceCurrentTextPosition(glyphAdvance + spacing);

if (m_isVerticalText)
xNew -= baselineShift;
else
yNew += baselineShift;

updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing);
}
// Apply CSS 'letter-spacing' and 'word-spacing' to the next character, if needed.
if (!m_inPathLayout && spacing)
applySpacingToNextCharacter = true;

advanceToNextLogicalCharacter(logicalMetrics);
m_visualMetricsIterator.next();
Expand Down
12 changes: 5 additions & 7 deletions third_party/WebKit/Source/core/layout/svg/SVGTextLayoutEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ class SVGTextLayoutEngine {
void finishLayout();

private:
void updateCharacterPositionIfNeeded(float& x, float& y);
void updateCurrentTextPosition(float x, float y, float glyphAdvance);
void updateRelativePositionAdjustmentsIfNeeded(float dx, float dy);
bool setCurrentTextPosition(const SVGCharacterData&);
void advanceCurrentTextPosition(float glyphAdvance);
bool applyRelativePositionAdjustmentsIfNeeded(const SVGCharacterData&);

void computeCurrentFragmentMetrics(SVGInlineTextBox*);
void recordTextFragment(SVGInlineTextBox*);
Expand All @@ -85,10 +85,7 @@ class SVGTextLayoutEngine {
unsigned m_logicalCharacterOffset;
unsigned m_logicalMetricsListOffset;
SVGInlineTextMetricsIterator m_visualMetricsIterator;
float m_x;
float m_y;
float m_dx;
float m_dy;
FloatPoint m_textPosition;
bool m_isVerticalText;
bool m_inPathLayout;
bool m_textLengthSpacingInEffect;
Expand All @@ -97,6 +94,7 @@ class SVGTextLayoutEngine {
OwnPtr<PathPositionMapper> m_textPath;
float m_textPathStartOffset;
float m_textPathCurrentOffset;
float m_textPathDisplacement;
float m_textPathSpacing;
float m_textPathScaling;
};
Expand Down

0 comments on commit 143115b

Please sign in to comment.