diff options
author | Kristian Monsen <kristianm@google.com> | 2010-06-28 16:42:48 +0100 |
---|---|---|
committer | Kristian Monsen <kristianm@google.com> | 2010-07-02 10:29:56 +0100 |
commit | 06ea8e899e48f1f2f396b70e63fae369f2f23232 (patch) | |
tree | 20c1428cd05c76f32394ab354ea35ed99acd86d8 /WebCore/rendering/SVGInlineTextBox.cpp | |
parent | 72aad67af14193199e29cdd5c4ddc095a8b9a8a8 (diff) | |
download | external_webkit-06ea8e899e48f1f2f396b70e63fae369f2f23232.zip external_webkit-06ea8e899e48f1f2f396b70e63fae369f2f23232.tar.gz external_webkit-06ea8e899e48f1f2f396b70e63fae369f2f23232.tar.bz2 |
Merge WebKit at r61871: Initial merge by git.
Change-Id: I6cff43abca9cc4782e088a469ad4f03f166a65d5
Diffstat (limited to 'WebCore/rendering/SVGInlineTextBox.cpp')
-rw-r--r-- | WebCore/rendering/SVGInlineTextBox.cpp | 866 |
1 files changed, 459 insertions, 407 deletions
diff --git a/WebCore/rendering/SVGInlineTextBox.cpp b/WebCore/rendering/SVGInlineTextBox.cpp index 8bd7fef..68b4fd0 100644 --- a/WebCore/rendering/SVGInlineTextBox.cpp +++ b/WebCore/rendering/SVGInlineTextBox.cpp @@ -21,25 +21,28 @@ */ #include "config.h" - -#if ENABLE(SVG) #include "SVGInlineTextBox.h" -#include "Frame.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 "Text.h" #include <float.h> +using namespace std; + namespace WebCore { -SVGInlineTextBox::SVGInlineTextBox(RenderObject* obj) - : InlineTextBox(obj) +SVGInlineTextBox::SVGInlineTextBox(RenderObject* object) + : InlineTextBox(object) , m_height(0) + , m_paintingResource(0) + , m_paintingResourceMode(ApplyToDefaultMode) { } @@ -60,522 +63,550 @@ SVGRootInlineBox* SVGInlineTextBox::svgRootInlineBox() const return static_cast<SVGRootInlineBox*>(parentBox); } -float SVGInlineTextBox::calculateGlyphWidth(RenderStyle* style, int offset, int extraCharsAvailable, int& charsConsumed, String& glyphName) const +void SVGInlineTextBox::measureCharacter(RenderStyle* style, int position, int& charsConsumed, String& glyphName, String& unicodeString, float& glyphWidth, float& glyphHeight) const { ASSERT(style); - return style->font().floatWidth(svgTextRunForInlineTextBox(textRenderer()->characters() + offset, 1, style, this, 0), extraCharsAvailable, charsConsumed, glyphName); -} -float SVGInlineTextBox::calculateGlyphHeight(RenderStyle* style, int, int) const -{ - // This is just a guess, and the only purpose of this function is to centralize this hack. - // In real-life top-top-bottom scripts this won't be enough, I fear. - return style->font().ascent() + style->font().descent(); + 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); } -FloatRect SVGInlineTextBox::calculateGlyphBoundaries(RenderStyle* style, int offset, const SVGChar& svgChar) const +int SVGInlineTextBox::offsetForPosition(int xCoordinate, bool includePartialGlyphs) const { - const Font& font = style->font(); + ASSERT(!m_currentChunkPart.isValid()); + float x = xCoordinate; - // Take RTL text into account and pick right glyph width/height. - float glyphWidth = 0.0f; + RenderText* textRenderer = this->textRenderer(); + ASSERT(textRenderer); - // FIXME: account for multi-character glyphs - int charsConsumed; - String glyphName; - if (direction() == LTR) - glyphWidth = calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName); - else - glyphWidth = calculateGlyphWidth(style, start() + end() - offset, 0, charsConsumed, glyphName); + RenderStyle* style = textRenderer->style(); + ASSERT(style); - float x1 = svgChar.x; - float x2 = svgChar.x + glyphWidth; + RenderBlock* containingBlock = textRenderer->containingBlock(); + ASSERT(containingBlock); - float y1 = svgChar.y - font.ascent(); - float y2 = svgChar.y + font.descent(); + // Move incoming relative x position to absolute position, as the character origins stored in the chunk parts use absolute coordinates + x += containingBlock->x(); - FloatRect glyphRect(x1, y1, x2 - x1, y2 - y1); + // Figure out which text chunk part is hit + SVGTextChunkPart hitPart; - // Take per-character transformations into account - glyphRect = svgChar.characterTransform().mapRect(glyphRect); + 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; - return glyphRect; -} + // Check whether we're past the hit part. + if (x < part.firstCharacter->x) + break; -// Helper class for closestCharacterToPosition() -struct SVGInlineTextBoxClosestCharacterToPositionWalker { - SVGInlineTextBoxClosestCharacterToPositionWalker(int x, int y) - : m_character(0) - , m_distance(FLT_MAX) - , m_x(x) - , m_y(y) - , m_offsetOfHitCharacter(0) - { + hitPart = part; } - void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform& chunkCtm, - const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end) - { - RenderStyle* style = textBox->textRenderer()->style(); + // If we did not hit anything, just exit. + if (!hitPart.isValid()) + return 0; - Vector<SVGChar>::iterator closestCharacter = 0; - unsigned int closestOffset = UINT_MAX; + // 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; - for (Vector<SVGChar>::iterator it = start; it != end; ++it) { - if (it->isHidden()) - continue; + m_currentChunkPart = hitPart; + TextRun textRun(constructTextRun(style)); + m_currentChunkPart = SVGTextChunkPart(); - unsigned int newOffset = textBox->start() + (it - start) + startOffset; - FloatRect glyphRect = chunkCtm.mapRect(textBox->calculateGlyphBoundaries(style, newOffset, *it)); + // Eventually handle lengthAdjust="spacingAndGlyphs". + if (!m_chunkTransformation.isIdentity()) + textRun.setGlyphScale(narrowPrecisionToFloat(isVerticalWritingMode(style->svgStyle()) ? m_chunkTransformation.d() : m_chunkTransformation.a())); - // Take RTL text into account and pick right glyph width/height. - // NOTE: This offset has to be corrected _after_ calling calculateGlyphBoundaries - if (textBox->direction() == RTL) - newOffset = textBox->start() + textBox->end() - newOffset; + return hitPart.offset + style->font().offsetForPosition(textRun, x, includePartialGlyphs); +} - // Calculate distances relative to the glyph mid-point. I hope this is accurate enough. - float xDistance = glyphRect.x() + glyphRect.width() / 2.0f - m_x; - float yDistance = glyphRect.y() - glyphRect.height() / 2.0f - m_y; +int SVGInlineTextBox::positionForOffset(int) const +{ + // SVG doesn't use the offset <-> position selection system. + ASSERT_NOT_REACHED(); + return 0; +} - float newDistance = sqrtf(xDistance * xDistance + yDistance * yDistance); - if (newDistance <= m_distance) { - m_distance = newDistance; - closestOffset = newOffset; - closestCharacter = it; - } - } +FloatRect SVGInlineTextBox::selectionRectForTextChunkPart(const SVGTextChunkPart& part, int partStartPos, int partEndPos, RenderStyle* style) +{ + // Map startPos/endPos positions into chunk part + mapStartEndPositionsIntoChunkPartCoordinates(partStartPos, partEndPos, part); - if (closestOffset != UINT_MAX) { - // Record current chunk, if it contains the current closest character next to the mouse. - m_character = closestCharacter; - m_offsetOfHitCharacter = closestOffset; - } - } + if (partStartPos >= partEndPos) + return FloatRect(); - SVGChar* character() const - { - return m_character; - } + // Set current chunk part, so constructTextRun() works properly. + m_currentChunkPart = part; - int offsetOfHitCharacter() const - { - if (!m_character) - return 0; + const Font& font = style->font(); + Vector<SVGChar>::const_iterator character = part.firstCharacter; + FloatPoint textOrigin(character->x, character->y - font.ascent()); - return m_offsetOfHitCharacter; - } + FloatRect partRect(font.selectionRectForText(constructTextRun(style), textOrigin, part.height, partStartPos, partEndPos)); + m_currentChunkPart = SVGTextChunkPart(); -private: - Vector<SVGChar>::iterator m_character; - float m_distance; + return character->characterTransform().mapRect(partRect); +} - int m_x; - int m_y; - int m_offsetOfHitCharacter; -}; +IntRect SVGInlineTextBox::selectionRect(int, int, int startPos, int endPos) +{ + ASSERT(!m_currentChunkPart.isValid()); -// Helper class for selectionRect() -struct SVGInlineTextBoxSelectionRectWalker { - SVGInlineTextBoxSelectionRectWalker() - { - } + int boxStart = start(); + startPos = max(startPos - boxStart, 0); + endPos = min(endPos - boxStart, static_cast<int>(len())); - void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const AffineTransform& chunkCtm, - const Vector<SVGChar>::iterator& start, const Vector<SVGChar>::iterator& end) - { - RenderStyle* style = textBox->textRenderer()->style(); + if (startPos >= endPos) + return IntRect(); - for (Vector<SVGChar>::iterator it = start; it != end; ++it) { - if (it->isHidden()) - continue; + RenderText* text = textRenderer(); + ASSERT(text); - unsigned int newOffset = textBox->start() + (it - start) + startOffset; - m_selectionRect.unite(textBox->calculateGlyphBoundaries(style, newOffset, *it)); - } + RenderStyle* style = text->style(); + ASSERT(style); - m_selectionRect = chunkCtm.mapRect(m_selectionRect); - } + FloatRect selectionRect; - FloatRect selectionRect() const - { - return m_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); -private: - FloatRect m_selectionRect; -}; + return enclosingIntRect(m_chunkTransformation.mapRect(selectionRect)); +} -SVGChar* SVGInlineTextBox::closestCharacterToPosition(int x, int y, int& offsetOfHitCharacter) const +void SVGInlineTextBox::paint(RenderObject::PaintInfo& paintInfo, int, int) { - SVGRootInlineBox* rootBox = svgRootInlineBox(); - if (!rootBox) - return 0; + ASSERT(renderer()->shouldPaintWithinRoot(paintInfo)); + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(truncation() == cNoTruncation); - SVGInlineTextBoxClosestCharacterToPositionWalker walkerCallback(x, y); - SVGTextChunkWalker<SVGInlineTextBoxClosestCharacterToPositionWalker> walker(&walkerCallback, &SVGInlineTextBoxClosestCharacterToPositionWalker::chunkPortionCallback); + if (renderer()->style()->visibility() != VISIBLE) + return; - rootBox->walkTextChunks(&walker, this); + // 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. - offsetOfHitCharacter = walkerCallback.offsetOfHitCharacter(); - return walkerCallback.character(); -} + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); -bool SVGInlineTextBox::svgCharacterHitsPosition(int x, int y, int& closestOffsetInBox) const -{ - int offsetOfHitCharacter = 0; - SVGChar* charAtPosPtr = closestCharacterToPosition(x, y, offsetOfHitCharacter); - if (!charAtPosPtr) - return false; + RenderStyle* style = parentRenderer->style(); + ASSERT(style); - SVGChar& charAtPos = *charAtPosPtr; - RenderStyle* style = textRenderer()->style(m_firstLine); - FloatRect glyphRect = calculateGlyphBoundaries(style, offsetOfHitCharacter, charAtPos); + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); - // FIXME: Why? - if (direction() == RTL) - offsetOfHitCharacter++; + bool hasFill = svgStyle->hasFill(); + bool hasStroke = svgStyle->hasStroke(); + bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; - // The caller actually the closest offset before/after the hit char - // closestCharacterToPosition returns us offsetOfHitCharacter. - closestOffsetInBox = offsetOfHitCharacter; + // Determine whether or not we're selected. + bool isPrinting = parentRenderer->document()->printing(); + bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone; + if (!hasSelection && paintSelectedTextOnly) + return; - // FIXME: (bug 13910) This code does not handle bottom-to-top/top-to-bottom vertical text. + 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; + } - // Check whether y position hits the current character - if (y < charAtPos.y - glyphRect.height() || y > charAtPos.y) - return false; + // 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); + } - // Check whether x position hits the current character - if (x < charAtPos.x) { - if (closestOffsetInBox > 0 && direction() == LTR) - return true; - if (closestOffsetInBox < static_cast<int>(end()) && direction() == RTL) - return true; + // Stroke text + if (hasStroke) { + m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; + paintText(paintInfo.context, textOrigin, style, selectionStyle, hasSelection, paintSelectedTextOnly); + } - return false; + // 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(); } - // Adjust the closest offset to after the char if x was after the char midpoint - if (x >= charAtPos.x + glyphRect.width() / 2.0) - closestOffsetInBox += direction() == RTL ? -1 : 1; + 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 we are past the last glyph of this box, don't mark it as 'hit' - if (x >= charAtPos.x + glyphRect.width() && closestOffsetInBox == (int) end()) + if (!m_paintingResource) return false; + m_paintingResource->applyResource(parentRenderer, style, context, m_paintingResourceMode); return true; } -int SVGInlineTextBox::offsetForPosition(int, bool) const +void SVGInlineTextBox::releasePaintingResource(GraphicsContext*& context) { - // SVG doesn't use the offset <-> position selection system. - ASSERT_NOT_REACHED(); - return 0; + ASSERT(m_paintingResource); + + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + + m_paintingResource->postApplyResource(parentRenderer, context, m_paintingResourceMode); + m_paintingResource = 0; } -int SVGInlineTextBox::positionForOffset(int) const +bool SVGInlineTextBox::prepareGraphicsContextForTextPainting(GraphicsContext*& context, TextRun& textRun, RenderStyle* style) { - // SVG doesn't use the offset <-> position selection system. - ASSERT_NOT_REACHED(); - return 0; + 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; } -bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest&, HitTestResult& result, int x, int y, int tx, int ty) +void SVGInlineTextBox::restoreGraphicsContextAfterTextPainting(GraphicsContext*& context, TextRun& textRun) { - ASSERT(!isLineBreak()); + releasePaintingResource(context); - IntRect rect = selectionRect(0, 0, 0, len()); - if (renderer()->style()->visibility() == VISIBLE && rect.contains(x, y)) { - renderer()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); - return true; - } +#if ENABLE(SVG_FONTS) + textRun.setActivePaintingResource(0); +#endif +} - return false; +TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style) const +{ + ASSERT(m_currentChunkPart.isValid()); + return svgTextRunForInlineTextBox(textRenderer()->text()->characters() + start() + m_currentChunkPart.offset, m_currentChunkPart.length, style, this); } -IntRect SVGInlineTextBox::selectionRect(int, int, int startPos, int endPos) +void SVGInlineTextBox::mapStartEndPositionsIntoChunkPartCoordinates(int& startPos, int& endPos, const SVGTextChunkPart& part) const { if (startPos >= endPos) - return IntRect(); + return; - // TODO: Actually respect startPos/endPos - we're returning the _full_ selectionRect - // here. This won't lead to visible bugs, but to extra work being done. Investigate. - SVGRootInlineBox* rootBox = svgRootInlineBox(); - if (!rootBox) - return IntRect(); + // 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; - SVGInlineTextBoxSelectionRectWalker walkerCallback; - SVGTextChunkWalker<SVGInlineTextBoxSelectionRectWalker> walker(&walkerCallback, &SVGInlineTextBoxSelectionRectWalker::chunkPortionCallback); + if (endPos > part.offset + part.length) + endPos = part.length; + else { + ASSERT(endPos >= part.offset); + endPos -= part.offset; + } - rootBox->walkTextChunks(&walker, this); - return enclosingIntRect(walkerCallback.selectionRect()); + ASSERT(startPos < endPos); } -bool SVGInlineTextBox::chunkSelectionStartEnd(const UChar* chunk, int chunkLength, int& selectionStart, int& selectionEnd) +void SVGInlineTextBox::selectionStartEnd(int& startPos, int& endPos) { - // NOTE: We ignore SVGInlineTextBox::m_start here because it is always 0. - // Curently SVG doesn't use HTML block-level layout, in which m_start would be set. - - int chunkStart = chunk - textRenderer()->characters(); - ASSERT(0 <= chunkStart); + InlineTextBox::selectionStartEnd(startPos, endPos); - selectionStartEnd(selectionStart, selectionEnd); - if (selectionEnd <= chunkStart) - return false; - if (chunkStart + chunkLength <= selectionStart) - return false; - - // Map indices from view-global to chunk-local. - selectionStart -= chunkStart; - selectionEnd -= chunkStart; - // Then clamp with chunk range - if (selectionStart < 0) - selectionStart = 0; - if (chunkLength < selectionEnd) - selectionEnd = chunkLength; + if (!m_currentChunkPart.isValid()) + return; - return selectionStart < selectionEnd; + mapStartEndPositionsIntoChunkPartCoordinates(startPos, endPos, m_currentChunkPart); } -void SVGInlineTextBox::paintCharacters(RenderObject::PaintInfo& paintInfo, int tx, int ty, const SVGChar& svgChar, const UChar* chars, int length, SVGTextPaintInfo& textPaintInfo) +void SVGInlineTextBox::computeTextMatchMarkerRect(RenderStyle* style) { - if (renderer()->style()->visibility() != VISIBLE || paintInfo.phase == PaintPhaseOutline) + ASSERT(!m_currentChunkPart.isValid()); + Node* node = renderer()->node(); + if (!node || !node->inDocument()) return; - ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines); + Document* document = renderer()->document(); + Vector<DocumentMarker> markers = document->markersForNode(renderer()->node()); - RenderText* text = textRenderer(); - ASSERT(text); + Vector<DocumentMarker>::iterator markerEnd = markers.end(); + for (Vector<DocumentMarker>::iterator markerIt = markers.begin(); markerIt != markerEnd; ++markerIt) { + const DocumentMarker& marker = *markerIt; - bool isPrinting = text->document()->printing(); + // SVG is only interessted in the TextMatch marker, for now. + if (marker.type != DocumentMarker::TextMatch) + continue; - // Determine whether or not we're selected. - bool haveSelection = !isPrinting && selectionState() != RenderObject::SelectionNone; - if (!haveSelection && paintInfo.phase == PaintPhaseSelection) - // When only painting the selection, don't bother to paint if there is none. - return; + FloatRect markerRect; + int partStartPos = max(marker.startOffset - start(), static_cast<unsigned>(0)); + int partEndPos = min(marker.endOffset - start(), static_cast<unsigned>(len())); - // Determine whether or not we have a composition. - bool containsComposition = text->document()->frame()->editor()->compositionNode() == text->node(); - bool useCustomUnderlines = containsComposition && text->document()->frame()->editor()->compositionUsesCustomUnderlines(); - - // Set our font - RenderStyle* styleToUse = text->style(isFirstLineStyle()); - const Font& font = styleToUse->font(); - - AffineTransform ctm = svgChar.characterTransform(); - if (!ctm.isIdentity()) - paintInfo.context->concatCTM(ctm); - - // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection - // and marked text. - if (paintInfo.phase != PaintPhaseSelection && !isPrinting && textPaintInfo.subphase == SVGTextPaintSubphaseBackground) { -#if PLATFORM(MAC) - // Custom highlighters go behind everything else. - if (styleToUse->highlight() != nullAtom && !paintInfo.context->paintingDisabled()) - paintCustomHighlight(tx, ty, styleToUse->highlight()); -#endif + // 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 (containsComposition && !useCustomUnderlines) - paintCompositionBackground(paintInfo.context, tx, ty, styleToUse, font, - text->document()->frame()->editor()->compositionStart(), - text->document()->frame()->editor()->compositionEnd()); - - paintDocumentMarkers(paintInfo.context, tx, ty, styleToUse, font, true); + if (!m_chunkTransformation.isIdentity()) + markerRect = m_chunkTransformation.mapRect(markerRect); - if (haveSelection && !useCustomUnderlines) { - int boxStartOffset = chars - text->characters() - start(); - paintSelection(boxStartOffset, svgChar, chars, length, paintInfo.context, styleToUse, font); - } + document->setRenderedRectForMarker(node, marker, renderer()->localToAbsoluteQuad(markerRect).enclosingBoundingBox()); } +} - bool isGlyphPhase = textPaintInfo.subphase == SVGTextPaintSubphaseGlyphFill || textPaintInfo.subphase == SVGTextPaintSubphaseGlyphStroke; - bool isSelectionGlyphPhase = textPaintInfo.subphase == SVGTextPaintSubphaseGlyphFillSelection || textPaintInfo.subphase == SVGTextPaintSubphaseGlyphStrokeSelection; - - if (isGlyphPhase || isSelectionGlyphPhase) { - // Set a text shadow if we have one. - // FIXME: Support multiple shadow effects. See how it's done in InlineTextBox.cpp. - bool setShadow = false; - if (styleToUse->textShadow()) { - paintInfo.context->setShadow(IntSize(styleToUse->textShadow()->x(), styleToUse->textShadow()->y()), - styleToUse->textShadow()->blur(), styleToUse->textShadow()->color(), - styleToUse->colorSpace()); - setShadow = true; - } - - IntPoint origin((int) svgChar.x, (int) svgChar.y); - TextRun run = svgTextRunForInlineTextBox(chars, length, styleToUse, this, svgChar.x); +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; -#if ENABLE(SVG_FONTS) - // SVG Fonts need access to the painting resource used to draw the current text chunk. - ASSERT(textPaintInfo.activePaintingResource); - run.setActivePaintingResource(textPaintInfo.activePaintingResource); -#endif + ASSERT_NOT_REACHED(); + return 0.0f; +} - int selectionStart = 0; - int selectionEnd = 0; - bool haveSelectedRange = haveSelection && chunkSelectionStartEnd(chars, length, selectionStart, selectionEnd); - - if (isGlyphPhase) { - if (haveSelectedRange) { - paintInfo.context->drawText(font, run, origin, 0, selectionStart); - paintInfo.context->drawText(font, run, origin, selectionEnd, run.length()); - } else - paintInfo.context->drawText(font, run, origin); - } else { - ASSERT(isSelectionGlyphPhase); - if (haveSelectedRange) - paintInfo.context->drawText(font, run, origin, selectionStart, selectionEnd); - } +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(); - if (setShadow) - paintInfo.context->clearShadow(); - } + // 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; - if (paintInfo.phase != PaintPhaseSelection && textPaintInfo.subphase == SVGTextPaintSubphaseForeground) { - paintDocumentMarkers(paintInfo.context, tx, ty, styleToUse, font, false); - - if (useCustomUnderlines) { - const Vector<CompositionUnderline>& underlines = text->document()->frame()->editor()->customCompositionUnderlines(); - size_t numUnderlines = underlines.size(); - - for (size_t index = 0; index < numUnderlines; ++index) { - const CompositionUnderline& underline = underlines[index]; - - if (underline.endOffset <= start()) - // underline is completely before this run. This might be an underline that sits - // before the first run we draw, or underlines that were within runs we skipped - // due to truncation. - continue; - - if (underline.startOffset <= end()) { - // underline intersects this run. Paint it. - paintCompositionUnderline(paintInfo.context, tx, ty, underline); - if (underline.endOffset > end() + 1) - // underline also runs into the next run. Bail now, no more marker advancement. - break; - } else - // underline is completely after this run, bail. A later run will paint it. - break; - } - } - + parentBox = parentBox->parent(); } - if (!ctm.isIdentity()) - paintInfo.context->concatCTM(ctm.inverse()); + ASSERT(renderer); + return renderer; } -void SVGInlineTextBox::paintSelection(int boxStartOffset, const SVGChar& svgChar, const UChar*, int length, GraphicsContext* p, RenderStyle* style, const Font& font) +void SVGInlineTextBox::paintDecoration(GraphicsContext* context, const FloatPoint& textOrigin, ETextDecoration decoration, bool hasSelection) { - if (selectionState() == RenderObject::SelectionNone) - return; - - int startPos, endPos; - selectionStartEnd(startPos, endPos); - - if (startPos >= endPos) - return; - - Color textColor = style->visitedDependentColor(CSSPropertyColor); - Color color = renderer()->selectionBackgroundColor(); - if (!color.isValid() || !color.alpha()) - return; + // 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); - // If the text color ends up being the same as the selection background, invert the selection - // background. This should basically never happen, since the selection has transparency. - if (textColor == color) - color = Color(0xff - color.red(), 0xff - color.green(), 0xff - color.blue()); + const SVGRenderStyle* svgDecorationStyle = decorationStyle->svgStyle(); + ASSERT(svgDecorationStyle); - // Map from text box positions and a given start offset to chunk positions - // 'boxStartOffset' represents the beginning of the text chunk. - if ((startPos > boxStartOffset && endPos > boxStartOffset + length) || boxStartOffset >= endPos) - return; + bool hasDecorationFill = svgDecorationStyle->hasFill(); + bool hasDecorationStroke = svgDecorationStyle->hasStroke(); - if (endPos > boxStartOffset + length) - endPos = boxStartOffset + length; + if (hasSelection) { + if (RenderStyle* pseudoStyle = decorationRenderer->getCachedPseudoStyle(SELECTION)) { + decorationStyle = pseudoStyle; - if (startPos < boxStartOffset) - startPos = boxStartOffset; + svgDecorationStyle = decorationStyle->svgStyle(); + ASSERT(svgDecorationStyle); - ASSERT(startPos >= boxStartOffset); - ASSERT(endPos <= boxStartOffset + length); - ASSERT(startPos < endPos); + if (!hasDecorationFill) + hasDecorationFill = svgDecorationStyle->hasFill(); + if (!hasDecorationStroke) + hasDecorationStroke = svgDecorationStyle->hasStroke(); + } + } - p->save(); + if (decorationStyle->visibility() == HIDDEN) + return; - int adjust = startPos >= boxStartOffset ? boxStartOffset : 0; - p->drawHighlightForText(font, svgTextRunForInlineTextBox(textRenderer()->characters() + start() + boxStartOffset, length, style, this, svgChar.x), - IntPoint((int) svgChar.x, (int) svgChar.y - font.ascent()), - font.ascent() + font.descent(), color, style->colorSpace(), startPos - adjust, endPos - adjust); + if (hasDecorationFill) { + m_paintingResourceMode = ApplyToFillMode; + paintDecorationWithStyle(context, textOrigin, decorationStyle, decoration); + } - p->restore(); + if (hasDecorationStroke) { + m_paintingResourceMode = ApplyToStrokeMode; + paintDecorationWithStyle(context, textOrigin, decorationStyle, decoration); + } } -static inline Path pathForDecoration(ETextDecoration decoration, RenderObject* object, float x, float y, float width) +void SVGInlineTextBox::paintDecorationWithStyle(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* decorationStyle, ETextDecoration decoration) { - float thickness = SVGRenderStyle::cssPrimitiveToLength(object, object->style()->svgStyle()->strokeWidth(), 1.0f); + ASSERT(!m_paintingResource); + ASSERT(m_paintingResourceMode != ApplyToDefaultMode); + ASSERT(m_currentChunkPart.isValid()); - const Font& font = object->style()->font(); - thickness = max(thickness * powf(font.size(), 2.0f) / font.unitsPerEm(), 1.0f); + const Font& font = decorationStyle->font(); - if (decoration == UNDERLINE) - y += thickness * 1.5f; // For compatibility with Batik/Opera - else if (decoration == OVERLINE) - y += thickness; + // 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))); - float halfThickness = thickness / 2.0f; - return Path::createRectangle(FloatRect(x + halfThickness, y, width - 2.0f * halfThickness, thickness)); + if (acquirePaintingResource(context, decorationStyle)) + releasePaintingResource(context); + + context->restore(); } -void SVGInlineTextBox::paintDecoration(ETextDecoration decoration, GraphicsContext* context, int tx, int ty, int width, const SVGChar& svgChar, const SVGTextDecorationInfo& info) +void SVGInlineTextBox::paintSelection(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style) { - if (renderer()->style()->visibility() != VISIBLE) + // See if we have a selection to paint at all. + int startPos, endPos; + selectionStartEnd(startPos, endPos); + if (startPos >= endPos) return; - // This function does NOT accept combinated text decorations. It's meant to be invoked for just one. - ASSERT(decoration == TDNONE || decoration == UNDERLINE || decoration == OVERLINE || decoration == LINE_THROUGH || decoration == BLINK); - - bool isFilled = info.fillServerMap.contains(decoration); - bool isStroked = info.strokeServerMap.contains(decoration); - - if (!isFilled && !isStroked) + Color backgroundColor = renderer()->selectionBackgroundColor(); + if (!backgroundColor.isValid() || !backgroundColor.alpha()) return; - int baseline = renderer()->style(m_firstLine)->font().ascent(); - if (decoration == UNDERLINE) - ty += baseline; - else if (decoration == LINE_THROUGH) - ty += 2 * baseline / 3; + const Font& font = style->font(); + + FloatPoint selectionOrigin = textOrigin; + selectionOrigin.move(0, -font.ascent()); context->save(); - context->beginPath(); + context->setFillColor(backgroundColor, style->colorSpace()); + context->fillRect(font.selectionRectForText(constructTextRun(style), selectionOrigin, m_currentChunkPart.height, startPos, endPos), backgroundColor, style->colorSpace()); + context->restore(); +} - AffineTransform ctm = svgChar.characterTransform(); - if (!ctm.isIdentity()) - context->concatCTM(ctm); +void SVGInlineTextBox::paintText(GraphicsContext* context, const FloatPoint& textOrigin, RenderStyle* style, RenderStyle* selectionStyle, bool hasSelection, bool paintSelectedTextOnly) +{ + ASSERT(style); + ASSERT(selectionStyle); + ASSERT(m_currentChunkPart.isValid()); - if (isFilled) { - if (RenderObject* fillObject = info.fillServerMap.get(decoration)) { - if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(fillObject, fillObject->style())) { - context->addPath(pathForDecoration(decoration, fillObject, tx, ty, width)); - if (fillPaintingResource->applyResource(fillObject, fillObject->style(), context, ApplyToFillMode)) - fillPaintingResource->postApplyResource(fillObject, context, ApplyToFillMode); - } + int startPos = 0; + int endPos = 0; + selectionStartEnd(startPos, endPos); + + const Font& font = style->font(); + TextRun textRun(constructTextRun(style)); + + // Fast path if there is no selection, just draw the whole chunk part using the regular style + if (!hasSelection || startPos >= endPos) { + if (prepareGraphicsContextForTextPainting(context, textRun, style)) { + font.drawText(context, textRun, textOrigin, 0, m_currentChunkPart.length); + restoreGraphicsContextAfterTextPainting(context, textRun); } + + return; } - if (isStroked) { - if (RenderObject* strokeObject = info.strokeServerMap.get(decoration)) { - if (RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(strokeObject, strokeObject->style())) { - context->addPath(pathForDecoration(decoration, strokeObject, tx, ty, width)); - if (strokePaintingResource->applyResource(strokeObject, strokeObject->style(), context, ApplyToStrokeMode)) - strokePaintingResource->postApplyResource(strokeObject, context, ApplyToStrokeMode); - } + // Eventually draw text using regular style until the start position of the selection + if (startPos > 0 && !paintSelectedTextOnly) { + if (prepareGraphicsContextForTextPainting(context, textRun, style)) { + font.drawText(context, textRun, textOrigin, 0, startPos); + restoreGraphicsContextAfterTextPainting(context, textRun); } } - context->restore(); + // Draw text using selection style from the start to the end position of the selection + TextRun selectionTextRun(constructTextRun(selectionStyle)); + if (prepareGraphicsContextForTextPainting(context, selectionTextRun, selectionStyle)) { + selectionStyle->font().drawText(context, selectionTextRun, textOrigin, startPos, endPos); + restoreGraphicsContextAfterTextPainting(context, selectionTextRun); + } + + // 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) { + if (prepareGraphicsContextForTextPainting(context, textRun, style)) { + font.drawText(context, textRun, textOrigin, endPos, m_currentChunkPart.length); + restoreGraphicsContextAfterTextPainting(context, textRun); + } + } } void SVGInlineTextBox::buildLayoutInformation(SVGCharacterLayoutInfo& info, SVGLastGlyphInfo& lastGlyph) @@ -583,7 +614,7 @@ void SVGInlineTextBox::buildLayoutInformation(SVGCharacterLayoutInfo& info, SVGL RenderText* textRenderer = this->textRenderer(); ASSERT(textRenderer); - RenderStyle* style = textRenderer->style(isFirstLineStyle()); + RenderStyle* style = textRenderer->style(); ASSERT(style); const Font& font = style->font(); @@ -606,20 +637,9 @@ void SVGInlineTextBox::buildLayoutInformation(SVGCharacterLayoutInfo& info, SVGL float glyphWidth = 0.0f; float glyphHeight = 0.0f; - - int extraCharsAvailable = length - i - 1; - - String unicodeStr; String glyphName; - if (textDirection == RTL) { - glyphWidth = calculateGlyphWidth(style, endPosition - i, extraCharsAvailable, charsConsumed, glyphName); - glyphHeight = calculateGlyphHeight(style, endPosition - i, extraCharsAvailable); - unicodeStr = String(characters + endPosition - i, charsConsumed); - } else { - glyphWidth = calculateGlyphWidth(style, startPosition + i, extraCharsAvailable, charsConsumed, glyphName); - glyphHeight = calculateGlyphHeight(style, startPosition + i, extraCharsAvailable); - unicodeStr = String(characters + startPosition + i, charsConsumed); - } + String unicodeString; + measureCharacter(style, i, charsConsumed, glyphName, unicodeString, glyphWidth, glyphHeight); bool assignedX = false; bool assignedY = false; @@ -682,7 +702,7 @@ void SVGInlineTextBox::buildLayoutInformation(SVGCharacterLayoutInfo& info, SVGL } // FIXME: SVG Kerning doesn't get applied on texts on path. - bool appliedSVGKerning = applySVGKerning(info, style, lastGlyph, unicodeStr, glyphName, isVerticalText); + bool appliedSVGKerning = applySVGKerning(info, style, lastGlyph, unicodeString, glyphName, isVerticalText); if (info.nextDrawnSeperated || spacing != 0.0f || appliedSVGKerning) { info.nextDrawnSeperated = false; svgChar.drawnSeperated = true; @@ -805,6 +825,38 @@ void SVGInlineTextBox::buildLayoutInformation(SVGCharacterLayoutInfo& info, SVGL } } +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 |