--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/rendering/SVGInlineTextBox.cpp Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,875 @@
+/**
+ * Copyright (C) 2007 Rob Buis <buis@kde.org>
+ * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
+ * Copyright (C) Research In Motion Limited 2010. 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
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "config.h"
+#include "SVGInlineTextBox.h"
+
+#if ENABLE(SVG)
+#include "FloatConversion.h"
+#include "GraphicsContext.h"
+#include "InlineFlowBox.h"
+#include "RenderBlock.h"
+#include "RenderSVGResource.h"
+#include "SVGRootInlineBox.h"
+#include "SVGTextLayoutUtilities.h"
+
+#include <float.h>
+
+using namespace std;
+
+namespace WebCore {
+
+SVGInlineTextBox::SVGInlineTextBox(RenderObject* object)
+ : InlineTextBox(object)
+ , m_height(0)
+ , m_paintingResource(0)
+ , m_paintingResourceMode(ApplyToDefaultMode)
+{
+}
+
+SVGRootInlineBox* SVGInlineTextBox::svgRootInlineBox() const
+{
+ // Find associated root inline box
+ InlineFlowBox* parentBox = parent();
+
+ while (parentBox && !parentBox->isRootInlineBox())
+ parentBox = parentBox->parent();
+
+ ASSERT(parentBox);
+ ASSERT(parentBox->isRootInlineBox());
+
+ if (!parentBox->isSVGRootInlineBox())
+ return 0;
+
+ return static_cast<SVGRootInlineBox*>(parentBox);
+}
+
+void SVGInlineTextBox::measureCharacter(RenderStyle* style, int position, int& charsConsumed, String& glyphName, String& unicodeString, float& glyphWidth, float& glyphHeight) const
+{
+ ASSERT(style);
+
+ int offset = direction() == RTL ? end() - position : start() + position;
+ int extraCharsAvailable = len() - position - 1;
+ const UChar* characters = textRenderer()->characters();
+
+ const Font& font = style->font();
+ glyphWidth = font.floatWidth(svgTextRunForInlineTextBox(characters + offset, 1, style, this), extraCharsAvailable, charsConsumed, glyphName);
+ glyphHeight = font.height();
+
+ // The unicodeString / glyphName pair is needed for kerning calculations.
+ unicodeString = String(characters + offset, charsConsumed);
+}
+
+int SVGInlineTextBox::offsetForPosition(int xCoordinate, bool includePartialGlyphs) const
+{
+ ASSERT(!m_currentChunkPart.isValid());
+ float x = xCoordinate;
+
+ RenderText* textRenderer = this->textRenderer();
+ ASSERT(textRenderer);
+
+ RenderStyle* style = textRenderer->style();
+ ASSERT(style);
+
+ RenderBlock* containingBlock = textRenderer->containingBlock();
+ ASSERT(containingBlock);
+
+ // Move incoming relative x position to absolute position, as the character origins stored in the chunk parts use absolute coordinates
+ x += containingBlock->x();
+
+ // Figure out which text chunk part is hit
+ SVGTextChunkPart hitPart;
+
+ const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+ for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it) {
+ const SVGTextChunkPart& part = *it;
+
+ // Check whether we're past the hit part.
+ if (x < part.firstCharacter->x)
+ break;
+
+ hitPart = part;
+ }
+
+ // If we did not hit anything, just exit.
+ if (!hitPart.isValid())
+ return 0;
+
+ // Before calling Font::offsetForPosition(), subtract the start position of the first character
+ // in the hit text chunk part, to pass in coordinates, which are relative to the text chunk part, as
+ // constructTextRun() only builds a TextRun for the current chunk part, not the whole inline text box.
+ x -= hitPart.firstCharacter->x;
+
+ m_currentChunkPart = hitPart;
+ TextRun textRun(constructTextRun(style));
+ m_currentChunkPart = SVGTextChunkPart();
+
+ // Eventually handle lengthAdjust="spacingAndGlyphs".
+ // FIXME: Need to revisit the whole offsetForPosition concept for vertical text selection.
+ if (!m_chunkTransformation.isIdentity())
+ textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(m_chunkTransformation.a()));
+
+ return hitPart.offset + style->font().offsetForPosition(textRun, x, includePartialGlyphs);
+}
+
+int SVGInlineTextBox::positionForOffset(int) const
+{
+ // SVG doesn't use the offset <-> position selection system.
+ ASSERT_NOT_REACHED();
+ return 0;
+}
+
+FloatRect SVGInlineTextBox::selectionRectForTextChunkPart(const SVGTextChunkPart& part, int partStartPos, int partEndPos, RenderStyle* style)
+{
+ // Map startPos/endPos positions into chunk part
+ mapStartEndPositionsIntoChunkPartCoordinates(partStartPos, partEndPos, part);
+
+ if (partStartPos >= partEndPos)
+ return FloatRect();
+
+ // Set current chunk part, so constructTextRun() works properly.
+ m_currentChunkPart = part;
+
+ const Font& font = style->font();
+ Vector<SVGChar>::const_iterator character = part.firstCharacter;
+ FloatPoint textOrigin(character->x, character->y - font.ascent());
+
+ FloatRect partRect(font.selectionRectForText(constructTextRun(style), textOrigin, part.height, partStartPos, partEndPos));
+ m_currentChunkPart = SVGTextChunkPart();
+
+ return character->characterTransform().mapRect(partRect);
+}
+
+IntRect SVGInlineTextBox::selectionRect(int, int, int startPos, int endPos)
+{
+ ASSERT(!m_currentChunkPart.isValid());
+
+ int boxStart = start();
+ startPos = max(startPos - boxStart, 0);
+ endPos = min(endPos - boxStart, static_cast<int>(len()));
+
+ if (startPos >= endPos)
+ return IntRect();
+
+ RenderText* text = textRenderer();
+ ASSERT(text);
+
+ RenderStyle* style = text->style();
+ ASSERT(style);
+
+ FloatRect selectionRect;
+
+ // Figure out which text chunk part is hit
+ const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+ for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it)
+ selectionRect.unite(selectionRectForTextChunkPart(*it, startPos, endPos, style));
+
+ // Resepect possible chunk transformation
+ if (m_chunkTransformation.isIdentity())
+ return enclosingIntRect(selectionRect);
+
+ return enclosingIntRect(m_chunkTransformation.mapRect(selectionRect));
+}
+
+void SVGInlineTextBox::paint(PaintInfo& paintInfo, int, int)
+{
+ ASSERT(paintInfo.shouldPaintWithinRoot(renderer()));
+ ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection);
+ ASSERT(truncation() == cNoTruncation);
+
+ if (renderer()->style()->visibility() != VISIBLE)
+ return;
+
+ // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox.
+ // If we ever need that for SVG, it's very easy to refactor and reuse the code.
+
+ RenderObject* parentRenderer = parent()->renderer();
+ ASSERT(parentRenderer);
+
+ RenderStyle* style = parentRenderer->style();
+ ASSERT(style);
+
+ const SVGRenderStyle* svgStyle = style->svgStyle();
+ ASSERT(svgStyle);
+
+ bool hasFill = svgStyle->hasFill();
+ bool hasStroke = svgStyle->hasStroke();
+ bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection;
+
+ // Determine whether or not we're selected.
+ bool isPrinting = parentRenderer->document()->printing();
+ bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone;
+ if (!hasSelection && paintSelectedTextOnly)
+ return;
+
+ RenderStyle* selectionStyle = style;
+ if (hasSelection) {
+ selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION);
+ if (selectionStyle) {
+ const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle();
+ ASSERT(svgSelectionStyle);
+
+ if (!hasFill)
+ hasFill = svgSelectionStyle->hasFill();
+ if (!hasStroke)
+ hasStroke = svgSelectionStyle->hasStroke();
+ } else
+ selectionStyle = style;
+ }
+
+ // Compute text match marker rects. It needs to traverse all text chunk parts to figure
+ // out the union selection rect of all text chunk parts that contribute to the selection.
+ computeTextMatchMarkerRect(style);
+ ASSERT(!m_currentChunkPart.isValid());
+
+ const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+ for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it) {
+ ASSERT(!m_paintingResource);
+
+ // constructTextRun() uses the current chunk part to figure out what text to render.
+ m_currentChunkPart = *it;
+ paintInfo.context->save();
+
+ // Prepare context and draw text
+ if (!m_chunkTransformation.isIdentity())
+ paintInfo.context->concatCTM(m_chunkTransformation);
+
+ Vector<SVGChar>::const_iterator firstCharacter = m_currentChunkPart.firstCharacter;
+ AffineTransform characterTransform = firstCharacter->characterTransform();
+ if (!characterTransform.isIdentity())
+ paintInfo.context->concatCTM(characterTransform);
+
+ FloatPoint textOrigin(firstCharacter->x, firstCharacter->y);
+
+ // Draw background once (not in both fill/stroke phases)
+ if (!isPrinting && !paintSelectedTextOnly && hasSelection)
+ paintSelection(paintInfo.context, textOrigin, style);
+
+ // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations.
+ int decorations = style->textDecorationsInEffect();
+ if (decorations & UNDERLINE)
+ paintDecoration(paintInfo.context, textOrigin, UNDERLINE, hasSelection);
+ if (decorations & OVERLINE)
+ paintDecoration(paintInfo.context, textOrigin, OVERLINE, hasSelection);
+
+ // Fill text
+ if (hasFill) {
+ m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode;
+ paintText(paintInfo.context, textOrigin, style, selectionStyle, hasSelection, paintSelectedTextOnly);
+ }
+
+ // Stroke text
+ if (hasStroke) {
+ m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode;
+ paintText(paintInfo.context, textOrigin, style, selectionStyle, hasSelection, paintSelectedTextOnly);
+ }
+
+ // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text.
+ if (decorations & LINE_THROUGH)
+ paintDecoration(paintInfo.context, textOrigin, LINE_THROUGH, hasSelection);
+
+ m_paintingResourceMode = ApplyToDefaultMode;
+ paintInfo.context->restore();
+ }
+
+ m_currentChunkPart = SVGTextChunkPart();
+ ASSERT(!m_paintingResource);
+}
+
+bool SVGInlineTextBox::acquirePaintingResource(GraphicsContext*& context, RenderStyle* style)
+{
+ ASSERT(m_paintingResourceMode != ApplyToDefaultMode);
+
+ RenderObject* parentRenderer = parent()->renderer();
+ ASSERT(parentRenderer);
+
+ if (m_paintingResourceMode & ApplyToFillMode)
+ m_paintingResource = RenderSVGResource::fillPaintingResource(parentRenderer, style);
+ else if (m_paintingResourceMode & ApplyToStrokeMode)
+ m_paintingResource = RenderSVGResource::strokePaintingResource(parentRenderer, style);
+ else {
+ // We're either called for stroking or filling.
+ ASSERT_NOT_REACHED();
+ }
+
+ if (!m_paintingResource)
+ return false;
+
+ m_paintingResource->applyResource(parentRenderer, style, context, m_paintingResourceMode);
+ return true;
+}
+
+void SVGInlineTextBox::releasePaintingResource(GraphicsContext*& context)
+{
+ ASSERT(m_paintingResource);
+
+ RenderObject* parentRenderer = parent()->renderer();
+ ASSERT(parentRenderer);
+
+ m_paintingResource->postApplyResource(parentRenderer, context, m_paintingResourceMode);
+ m_paintingResource = 0;
+}
+
+bool SVGInlineTextBox::prepareGraphicsContextForTextPainting(GraphicsContext*& context, TextRun& textRun, RenderStyle* style)
+{
+ bool acquiredResource = acquirePaintingResource(context, style);
+
+#if ENABLE(SVG_FONTS)
+ // SVG Fonts need access to the painting resource used to draw the current text chunk.
+ if (acquiredResource)
+ textRun.setActivePaintingResource(m_paintingResource);
+#endif
+
+ return acquiredResource;
+}
+
+void SVGInlineTextBox::restoreGraphicsContextAfterTextPainting(GraphicsContext*& context, TextRun& textRun)
+{
+ releasePaintingResource(context);
+
+#if ENABLE(SVG_FONTS)
+ textRun.setActivePaintingResource(0);
+#endif
+}
+
+TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style) const
+{
+ ASSERT(m_currentChunkPart.isValid());
+ return svgTextRunForInlineTextBox(textRenderer()->text()->characters() + start() + m_currentChunkPart.offset, m_currentChunkPart.length, style, this);
+}
+
+void SVGInlineTextBox::mapStartEndPositionsIntoChunkPartCoordinates(int& startPos, int& endPos, const SVGTextChunkPart& part) const
+{
+ if (startPos >= endPos)
+ return;
+
+ // Take <text x="10 50 100">ABC</text> as example. We're called three times from paint(), because all absolute positioned
+ // characters are drawn on their own. For each of them we want to find out whehter it's selected. startPos=0, endPos=1
+ // could mean A, B or C is hit, depending on which chunk part is processed at the moment. With the offset & length values
+ // of each chunk part we can easily find out which one is meant to be selected. Bail out for the other chunk parts.
+ // If starPos is behind the current chunk or the endPos ends before this text chunk part, we're not meant to be selected.
+ if (startPos >= part.offset + part.length || endPos <= part.offset) {
+ startPos = 0;
+ endPos = -1;
+ return;
+ }
+
+ // The current processed chunk part is hit. When painting the selection, constructTextRun() builds
+ // a TextRun object whose startPos is 0 and endPos is chunk part length. The code below maps the incoming
+ // startPos/endPos range into a [0, part length] coordinate system, relative to the current chunk part.
+ if (startPos < part.offset)
+ startPos = 0;
+ else
+ startPos -= part.offset;
+
+ if (endPos > part.offset + part.length)
+ endPos = part.length;
+ else {
+ ASSERT(endPos >= part.offset);
+ endPos -= part.offset;
+ }
+
+ ASSERT(startPos < endPos);
+}
+
+void SVGInlineTextBox::selectionStartEnd(int& startPos, int& endPos)
+{
+ InlineTextBox::selectionStartEnd(startPos, endPos);
+
+ if (!m_currentChunkPart.isValid())
+ return;
+
+ mapStartEndPositionsIntoChunkPartCoordinates(startPos, endPos, m_currentChunkPart);
+}
+
+void SVGInlineTextBox::computeTextMatchMarkerRect(RenderStyle* style)
+{
+ ASSERT(!m_currentChunkPart.isValid());
+ Node* node = renderer()->node();
+ if (!node || !node->inDocument())
+ return;
+
+ Document* document = renderer()->document();
+ Vector<DocumentMarker> markers = document->markersForNode(renderer()->node());
+
+ Vector<DocumentMarker>::iterator markerEnd = markers.end();
+ for (Vector<DocumentMarker>::iterator markerIt = markers.begin(); markerIt != markerEnd; ++markerIt) {
+ const DocumentMarker& marker = *markerIt;
+
+ // SVG is only interessted in the TextMatch marker, for now.
+ if (marker.type != DocumentMarker::TextMatch)
+ continue;
+
+ FloatRect markerRect;
+ int partStartPos = max(marker.startOffset - start(), static_cast<unsigned>(0));
+ int partEndPos = min(marker.endOffset - start(), static_cast<unsigned>(len()));
+
+ // Iterate over all text chunk parts, to see which ones have to be highlighted
+ const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+ for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it)
+ markerRect.unite(selectionRectForTextChunkPart(*it, partStartPos, partEndPos, style));
+
+ if (!m_chunkTransformation.isIdentity())
+ markerRect = m_chunkTransformation.mapRect(markerRect);
+
+ document->setRenderedRectForMarker(node, marker, renderer()->localToAbsoluteQuad(markerRect).enclosingBoundingBox());
+ }
+}
+
+static inline float positionOffsetForDecoration(ETextDecoration decoration, const Font& font, float thickness)
+{
+ // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified.
+ // Compatible with Batik/Opera.
+ if (decoration == UNDERLINE)
+ return font.ascent() + thickness * 1.5f;
+ if (decoration == OVERLINE)
+ return thickness;
+ if (decoration == LINE_THROUGH)
+ return font.ascent() * 5.0f / 8.0f;
+
+ ASSERT_NOT_REACHED();
+ return 0.0f;
+}
+
+static inline float thicknessForDecoration(ETextDecoration, const Font& font)
+{
+ // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified.
+ // Compatible with Batik/Opera
+ return font.size() / 20.0f;
+}
+
+static inline RenderObject* findRenderObjectDefininingTextDecoration(InlineFlowBox* parentBox, ETextDecoration decoration)
+{
+ // Lookup render object which has text-decoration set.
+ RenderObject* renderer = 0;
+ while (parentBox) {
+ renderer = parentBox->renderer();
+
+ // Explicitely check textDecoration() not textDecorationsInEffect(), which is inherited to
+ // children, as we want to lookup the render object whose style defined the text-decoration.
+ if (renderer->style() && renderer->style()->textDecoration() & decoration)
+ break;
+
+ parentBox = parentBox->parent();
+ }
+
+ ASSERT(renderer);
+ return renderer;
+}
+
+void SVGInlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& textOrigin, ETextDecoration decoration, bool hasSelection)
+{
+ // Find out which render style defined the text-decoration, as its fill/stroke properties have to be used for drawing instead of ours.
+ RenderObject* decorationRenderer = findRenderObjectDefininingTextDecoration(parent(), decoration);
+ RenderStyle* decorationStyle = decorationRenderer->style();
+ ASSERT(decorationStyle);
+
+ const SVGRenderStyle* svgDecorationStyle = decorationStyle->svgStyle();
+ ASSERT(svgDecorationStyle);
+
+ bool hasDecorationFill = svgDecorationStyle->hasFill();
+ bool hasDecorationStroke = svgDecorationStyle->hasStroke();
+
+ if (hasSelection) {
+ if (RenderStyle* pseudoStyle = decorationRenderer->getCachedPseudoStyle(SELECTION)) {
+ decorationStyle = pseudoStyle;
+
+ svgDecorationStyle = decorationStyle->svgStyle();
+ ASSERT(svgDecorationStyle);
+
+ if (!hasDecorationFill)
+ hasDecorationFill = svgDecorationStyle->hasFill();
+ if (!hasDecorationStroke)
+ hasDecorationStroke = svgDecorationStyle->hasStroke();
+ }
+ }
+
+ if (decorationStyle->visibility() == HIDDEN)
+ return;
+
+ if (hasDecorationFill) {
+ m_paintingResourceMode = ApplyToFillMode;
+ paintDecorationWithStyle(context, textOrigin, decorationStyle, decoration);
+ }
+
+ if (hasDecorationStroke) {
+ m_paintingResourceMode = ApplyToStrokeMode;
+ paintDecorationWithStyle(context, textOrigin, decorationStyle, decoration);
+ }
+}
+
+void SVGInlineTextBox::paintDecorationWithStyle(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* decorationStyle, ETextDecoration decoration)
+{
+ ASSERT(!m_paintingResource);
+ ASSERT(m_paintingResourceMode != ApplyToDefaultMode);
+ ASSERT(m_currentChunkPart.isValid());
+
+ const Font& font = decorationStyle->font();
+
+ // The initial y value refers to overline position.
+ float thickness = thicknessForDecoration(decoration, font);
+ float x = textOrigin.x();
+ float y = textOrigin.y() - font.ascent() + positionOffsetForDecoration(decoration, font, thickness);
+
+ context->save();
+ context->beginPath();
+ context->addPath(Path::createRectangle(FloatRect(x, y, m_currentChunkPart.width, thickness)));
+
+ if (acquirePaintingResource(context, decorationStyle))
+ releasePaintingResource(context);
+
+ context->restore();
+}
+
+void SVGInlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style)
+{
+ // See if we have a selection to paint at all.
+ int startPos, endPos;
+ selectionStartEnd(startPos, endPos);
+ if (startPos >= endPos)
+ return;
+
+ Color backgroundColor = renderer()->selectionBackgroundColor();
+ if (!backgroundColor.isValid() || !backgroundColor.alpha())
+ return;
+
+ const Font& font = style->font();
+
+ FloatPoint selectionOrigin = textOrigin;
+ selectionOrigin.move(0, -font.ascent());
+
+ context->save();
+ context->setFillColor(backgroundColor, style->colorSpace());
+ context->fillRect(font.selectionRectForText(constructTextRun(style), selectionOrigin, m_currentChunkPart.height, startPos, endPos), backgroundColor, style->colorSpace());
+ context->restore();
+}
+
+void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style, TextRun& textRun, int startPos, int endPos)
+{
+ const Font& font = style->font();
+ const ShadowData* shadow = style->textShadow();
+
+ FloatRect shadowRect(FloatPoint(textOrigin.x(), textOrigin.y() - font.ascent()), FloatSize(m_currentChunkPart.width, m_currentChunkPart.height));
+ do {
+ if (!prepareGraphicsContextForTextPainting(context, textRun, style))
+ break;
+
+ FloatSize extraOffset;
+ if (shadow)
+ extraOffset = applyShadowToGraphicsContext(context, shadow, shadowRect, false /* stroked */, true /* opaque */);
+
+ font.drawText(context, textRun, textOrigin + extraOffset, startPos, endPos);
+ restoreGraphicsContextAfterTextPainting(context, textRun);
+
+ if (!shadow)
+ break;
+
+ if (shadow->next())
+ context->restore();
+ else
+ context->clearShadow();
+
+ shadow = shadow->next();
+ } while (shadow);
+}
+
+void SVGInlineTextBox::paintText(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style, RenderStyle* selectionStyle, bool hasSelection, bool paintSelectedTextOnly)
+{
+ ASSERT(style);
+ ASSERT(selectionStyle);
+ ASSERT(m_currentChunkPart.isValid());
+
+ int startPos = 0;
+ int endPos = 0;
+ selectionStartEnd(startPos, endPos);
+
+ // Fast path if there is no selection, just draw the whole chunk part using the regular style
+ TextRun textRun(constructTextRun(style));
+ if (!hasSelection || startPos >= endPos) {
+ paintTextWithShadows(context, textOrigin, style, textRun, 0, m_currentChunkPart.length);
+ return;
+ }
+
+ // Eventually draw text using regular style until the start position of the selection
+ if (startPos > 0 && !paintSelectedTextOnly)
+ paintTextWithShadows(context, textOrigin, style, textRun, 0, startPos);
+
+ // Draw text using selection style from the start to the end position of the selection
+ TextRun selectionTextRun(constructTextRun(selectionStyle));
+ paintTextWithShadows(context, textOrigin, selectionStyle, textRun, startPos, endPos);
+
+ // Eventually draw text using regular style from the end position of the selection to the end of the current chunk part
+ if (endPos < m_currentChunkPart.length && !paintSelectedTextOnly)
+ paintTextWithShadows(context, textOrigin, style, textRun, endPos, m_currentChunkPart.length);
+}
+
+void SVGInlineTextBox::buildLayoutInformation(SVGCharacterLayoutInfo& info, SVGLastGlyphInfo& lastGlyph)
+{
+ RenderText* textRenderer = this->textRenderer();
+ ASSERT(textRenderer);
+
+ RenderStyle* style = textRenderer->style();
+ ASSERT(style);
+
+ const Font& font = style->font();
+ const UChar* characters = textRenderer->characters();
+
+ TextDirection textDirection = direction();
+ unsigned startPosition = start();
+ unsigned endPosition = end();
+ unsigned length = len();
+
+ const SVGRenderStyle* svgStyle = style->svgStyle();
+ bool isVerticalText = isVerticalWritingMode(svgStyle);
+
+ int charsConsumed = 0;
+ for (unsigned i = 0; i < length; i += charsConsumed) {
+ SVGChar svgChar;
+
+ if (info.inPathLayout())
+ svgChar.pathData = SVGCharOnPath::create();
+
+ float glyphWidth = 0.0f;
+ float glyphHeight = 0.0f;
+ String glyphName;
+ String unicodeString;
+ measureCharacter(style, i, charsConsumed, glyphName, unicodeString, glyphWidth, glyphHeight);
+
+ bool assignedX = false;
+ bool assignedY = false;
+
+ if (info.xValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && !isVerticalText))) {
+ if (!isVerticalText)
+ svgChar.newTextChunk = true;
+
+ assignedX = true;
+ svgChar.drawnSeperated = true;
+ info.curx = info.xValueNext();
+ }
+
+ if (info.yValueAvailable() && (!info.inPathLayout() || (info.inPathLayout() && isVerticalText))) {
+ if (isVerticalText)
+ svgChar.newTextChunk = true;
+
+ assignedY = true;
+ svgChar.drawnSeperated = true;
+ info.cury = info.yValueNext();
+ }
+
+ float dx = 0.0f;
+ float dy = 0.0f;
+
+ // Apply x-axis shift
+ if (info.dxValueAvailable()) {
+ svgChar.drawnSeperated = true;
+
+ dx = info.dxValueNext();
+ info.dx += dx;
+
+ if (!info.inPathLayout())
+ info.curx += dx;
+ }
+
+ // Apply y-axis shift
+ if (info.dyValueAvailable()) {
+ svgChar.drawnSeperated = true;
+
+ dy = info.dyValueNext();
+ info.dy += dy;
+
+ if (!info.inPathLayout())
+ info.cury += dy;
+ }
+
+ // Take letter & word spacing and kerning into account
+ float spacing = font.letterSpacing() + calculateCSSKerning(style);
+
+ const UChar* currentCharacter = characters + (textDirection == RTL ? endPosition - i : startPosition + i);
+ const UChar* lastCharacter = 0;
+
+ if (textDirection == RTL) {
+ if (i < endPosition)
+ lastCharacter = characters + endPosition - i + 1;
+ } else {
+ if (i > 0)
+ lastCharacter = characters + startPosition + i - 1;
+ }
+
+ // FIXME: SVG Kerning doesn't get applied on texts on path.
+ bool appliedSVGKerning = applySVGKerning(info, style, lastGlyph, unicodeString, glyphName, isVerticalText);
+ if (info.nextDrawnSeperated || spacing != 0.0f || appliedSVGKerning) {
+ info.nextDrawnSeperated = false;
+ svgChar.drawnSeperated = true;
+ }
+
+ if (currentCharacter && Font::treatAsSpace(*currentCharacter) && lastCharacter && !Font::treatAsSpace(*lastCharacter)) {
+ spacing += font.wordSpacing();
+
+ if (spacing != 0.0f && !info.inPathLayout())
+ info.nextDrawnSeperated = true;
+ }
+
+ float orientationAngle = glyphOrientationToAngle(svgStyle, isVerticalText, *currentCharacter);
+
+ float xOrientationShift = 0.0f;
+ float yOrientationShift = 0.0f;
+ float glyphAdvance = applyGlyphAdvanceAndShiftRespectingOrientation(isVerticalText, orientationAngle, glyphWidth, glyphHeight,
+ font, svgChar, xOrientationShift, yOrientationShift);
+
+ // Handle textPath layout mode
+ if (info.inPathLayout()) {
+ float extraAdvance = isVerticalText ? dy : dx;
+ float newOffset = FLT_MIN;
+
+ if (assignedX && !isVerticalText)
+ newOffset = info.curx;
+ else if (assignedY && isVerticalText)
+ newOffset = info.cury;
+
+ float correctedGlyphAdvance = glyphAdvance;
+
+ // Handle lengthAdjust="spacingAndGlyphs" by specifying per-character scale operations
+ if (info.pathTextLength && info.pathChunkLength) {
+ if (isVerticalText) {
+ svgChar.pathData->yScale = info.pathChunkLength / info.pathTextLength;
+ spacing *= svgChar.pathData->yScale;
+ correctedGlyphAdvance *= svgChar.pathData->yScale;
+ } else {
+ svgChar.pathData->xScale = info.pathChunkLength / info.pathTextLength;
+ spacing *= svgChar.pathData->xScale;
+ correctedGlyphAdvance *= svgChar.pathData->xScale;
+ }
+ }
+
+ // Handle letter & word spacing on text path
+ float pathExtraAdvance = info.pathExtraAdvance;
+ info.pathExtraAdvance += spacing;
+
+ svgChar.pathData->hidden = !info.nextPathLayoutPointAndAngle(correctedGlyphAdvance, extraAdvance, newOffset);
+ svgChar.drawnSeperated = true;
+
+ info.pathExtraAdvance = pathExtraAdvance;
+ }
+
+ // Apply rotation
+ if (info.angleValueAvailable())
+ info.angle = info.angleValueNext();
+
+ // Apply baseline-shift
+ if (info.baselineShiftValueAvailable()) {
+ svgChar.drawnSeperated = true;
+ float shift = info.baselineShiftValueNext();
+
+ if (isVerticalText)
+ info.shiftx += shift;
+ else
+ info.shifty -= shift;
+ }
+
+ // Take dominant-baseline / alignment-baseline into account
+ yOrientationShift += alignmentBaselineToShift(isVerticalText, textRenderer, font);
+
+ svgChar.x = info.curx;
+ svgChar.y = info.cury;
+ svgChar.angle = info.angle;
+
+ // For text paths any shift (dx/dy/baseline-shift) has to be applied after the rotation
+ if (!info.inPathLayout()) {
+ svgChar.x += info.shiftx + xOrientationShift;
+ svgChar.y += info.shifty + yOrientationShift;
+
+ if (orientationAngle != 0.0f)
+ svgChar.angle += orientationAngle;
+
+ if (svgChar.angle != 0.0f)
+ svgChar.drawnSeperated = true;
+ } else {
+ svgChar.pathData->orientationAngle = orientationAngle;
+
+ if (isVerticalText)
+ svgChar.angle -= 90.0f;
+
+ svgChar.pathData->xShift = info.shiftx + xOrientationShift;
+ svgChar.pathData->yShift = info.shifty + yOrientationShift;
+
+ // Translate to glyph midpoint
+ if (isVerticalText) {
+ svgChar.pathData->xShift += info.dx;
+ svgChar.pathData->yShift -= glyphAdvance / 2.0f;
+ } else {
+ svgChar.pathData->xShift -= glyphAdvance / 2.0f;
+ svgChar.pathData->yShift += info.dy;
+ }
+ }
+
+ // Advance to new position
+ if (isVerticalText) {
+ svgChar.drawnSeperated = true;
+ info.cury += glyphAdvance + spacing;
+ } else
+ info.curx += glyphAdvance + spacing;
+
+ // Advance to next character group
+ for (int k = 0; k < charsConsumed; ++k) {
+ info.svgChars.append(svgChar);
+ info.processedSingleCharacter();
+ svgChar.drawnSeperated = false;
+ svgChar.newTextChunk = false;
+ }
+ }
+}
+
+FloatRect SVGInlineTextBox::calculateGlyphBoundaries(RenderStyle* style, int position, const SVGChar& character) const
+{
+ int charsConsumed = 0;
+ String glyphName;
+ String unicodeString;
+ float glyphWidth = 0.0f;
+ float glyphHeight = 0.0f;
+ measureCharacter(style, position, charsConsumed, glyphName, unicodeString, glyphWidth, glyphHeight);
+
+ FloatRect glyphRect(character.x, character.y - style->font().ascent(), glyphWidth, glyphHeight);
+ glyphRect = character.characterTransform().mapRect(glyphRect);
+ if (m_chunkTransformation.isIdentity())
+ return glyphRect;
+
+ return m_chunkTransformation.mapRect(glyphRect);
+}
+
+IntRect SVGInlineTextBox::calculateBoundaries() const
+{
+ FloatRect textRect;
+ int baseline = baselinePosition(true);
+
+ const Vector<SVGTextChunkPart>::const_iterator end = m_svgTextChunkParts.end();
+ for (Vector<SVGTextChunkPart>::const_iterator it = m_svgTextChunkParts.begin(); it != end; ++it)
+ textRect.unite(it->firstCharacter->characterTransform().mapRect(FloatRect(it->firstCharacter->x, it->firstCharacter->y - baseline, it->width, it->height)));
+
+ if (m_chunkTransformation.isIdentity())
+ return enclosingIntRect(textRect);
+
+ return enclosingIntRect(m_chunkTransformation.mapRect(textRect));
+}
+
+} // namespace WebCore
+
+#endif