/* * This file is part of the WebKit project. * * Copyright (C) 2006 Oliver Hunt * (C) 2006 Apple Computer Inc. * (C) 2007 Nikolas Zimmermann * * 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" #if ENABLE(SVG) #include "SVGRootInlineBox.h" #include "Editor.h" #include "Frame.h" #include "GraphicsContext.h" #include "RenderBlock.h" #include "RenderSVGRoot.h" #include "SVGInlineFlowBox.h" #include "SVGInlineTextBox.h" #include "SVGFontElement.h" #include "SVGPaintServer.h" #include "SVGRenderStyleDefs.h" #include "SVGRenderSupport.h" #include "SVGResourceFilter.h" #include "SVGTextPositioningElement.h" #include "SVGURIReference.h" #include "Text.h" #include "UnicodeRange.h" #include // Text chunk creation is complex and the whole process // can easily be traced by setting this variable > 0. #define DEBUG_CHUNK_BUILDING 0 namespace WebCore { static inline bool isVerticalWritingMode(const SVGRenderStyle* style) { return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB; } static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) { ASSERT(text); const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; ASSERT(style); const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; EDominantBaseline baseline = style->dominantBaseline(); if (baseline == DB_AUTO) { if (isVerticalText) baseline = DB_CENTRAL; else baseline = DB_ALPHABETIC; } switch (baseline) { case DB_USE_SCRIPT: // TODO: The dominant-baseline and the baseline-table components are set by // determining the predominant script of the character data content. return AB_ALPHABETIC; case DB_NO_CHANGE: { if (parentStyle) return dominantBaselineToShift(isVerticalText, text->parent(), font); ASSERT_NOT_REACHED(); return AB_AUTO; } case DB_RESET_SIZE: { if (parentStyle) return dominantBaselineToShift(isVerticalText, text->parent(), font); ASSERT_NOT_REACHED(); return AB_AUTO; } case DB_IDEOGRAPHIC: return AB_IDEOGRAPHIC; case DB_ALPHABETIC: return AB_ALPHABETIC; case DB_HANGING: return AB_HANGING; case DB_MATHEMATICAL: return AB_MATHEMATICAL; case DB_CENTRAL: return AB_CENTRAL; case DB_MIDDLE: return AB_MIDDLE; case DB_TEXT_AFTER_EDGE: return AB_TEXT_AFTER_EDGE; case DB_TEXT_BEFORE_EDGE: return AB_TEXT_BEFORE_EDGE; default: ASSERT_NOT_REACHED(); return AB_AUTO; } } static inline float alignmentBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) { ASSERT(text); const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; ASSERT(style); const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; EAlignmentBaseline baseline = style->alignmentBaseline(); if (baseline == AB_AUTO) { if (parentStyle && style->dominantBaseline() == DB_AUTO) baseline = dominantBaselineToShift(isVerticalText, text->parent(), font); else baseline = dominantBaselineToShift(isVerticalText, text, font); ASSERT(baseline != AB_AUTO); } // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling switch (baseline) { case AB_BASELINE: { if (parentStyle) return dominantBaselineToShift(isVerticalText, text->parent(), font); return 0.0f; } case AB_BEFORE_EDGE: case AB_TEXT_BEFORE_EDGE: return font.ascent(); case AB_MIDDLE: return font.xHeight() / 2.0f; case AB_CENTRAL: // Not needed, we're taking this into account already for vertical text! // return (font.ascent() - font.descent()) / 2.0f; return 0.0f; case AB_AFTER_EDGE: case AB_TEXT_AFTER_EDGE: case AB_IDEOGRAPHIC: return font.descent(); case AB_ALPHABETIC: return 0.0f; case AB_HANGING: return font.ascent() * 8.0f / 10.0f; case AB_MATHEMATICAL: return font.ascent() / 2.0f; default: ASSERT_NOT_REACHED(); return 0.0f; } } static inline float glyphOrientationToAngle(const SVGRenderStyle* svgStyle, bool isVerticalText, const UChar& character) { switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->glyphOrientationHorizontal()) { case GO_AUTO: { // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees. // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees. unsigned int unicodeRange = findCharUnicodeRange(character); if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic) return 90.0f; return 0.0f; } case GO_90DEG: return 90.0f; case GO_180DEG: return 180.0f; case GO_270DEG: return 270.0f; case GO_0DEG: default: return 0.0f; } } static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle) { return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f; } static inline float calculateGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font& font, SVGChar& svgChar, float& xOrientationShift, float& yOrientationShift) { bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle); // The function is based on spec requirements: // // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph. // // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph. // vertical orientation handling if (isVerticalText) { if (orientationAngle == 0.0f) { xOrientationShift = -glyphWidth / 2.0f; yOrientationShift = font.ascent(); } else if (orientationAngle == 90.0f) { xOrientationShift = -glyphHeight; yOrientationShift = font.descent(); svgChar.orientationShiftY = -font.ascent(); } else if (orientationAngle == 270.0f) { xOrientationShift = glyphHeight; yOrientationShift = font.descent(); svgChar.orientationShiftX = -glyphWidth; svgChar.orientationShiftY = -font.ascent(); } else if (orientationAngle == 180.0f) { yOrientationShift = font.ascent(); svgChar.orientationShiftX = -glyphWidth / 2.0f; svgChar.orientationShiftY = font.ascent() - font.descent(); } // vertical advance calculation if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) return glyphWidth; return glyphHeight; } // horizontal orientation handling if (orientationAngle == 90.0f) { xOrientationShift = glyphWidth / 2.0f; yOrientationShift = -font.descent(); svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent(); svgChar.orientationShiftY = font.descent(); } else if (orientationAngle == 270.0f) { xOrientationShift = -glyphWidth / 2.0f; yOrientationShift = -font.descent(); svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent(); svgChar.orientationShiftY = glyphHeight; } else if (orientationAngle == 180.0f) { xOrientationShift = glyphWidth / 2.0f; svgChar.orientationShiftX = -glyphWidth / 2.0f; svgChar.orientationShiftY = font.ascent() - font.descent(); } // horizontal advance calculation if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) return glyphHeight; return glyphWidth; } static inline void startTextChunk(SVGTextChunkLayoutInfo& info) { info.chunk.boxes.clear(); info.chunk.boxes.append(SVGInlineBoxCharacterRange()); info.chunk.start = info.it; info.assignChunkProperties = true; } static inline void closeTextChunk(SVGTextChunkLayoutInfo& info) { ASSERT(!info.chunk.boxes.last().isOpen()); ASSERT(info.chunk.boxes.last().isClosed()); info.chunk.end = info.it; ASSERT(info.chunk.end >= info.chunk.start); info.svgTextChunks.append(info.chunk); } RenderSVGRoot* findSVGRootObject(RenderObject* start) { // Find associated root inline box while (start && !start->isSVGRoot()) start = start->parent(); ASSERT(start); ASSERT(start->isSVGRoot()); return static_cast(start); } static inline FloatPoint topLeftPositionOfCharacterRange(Vector& chars) { return topLeftPositionOfCharacterRange(chars.begin(), chars.end()); } FloatPoint topLeftPositionOfCharacterRange(Vector::iterator it, Vector::iterator end) { float lowX = FLT_MAX, lowY = FLT_MAX; for (; it != end; ++it) { if (it->isHidden()) continue; float x = (*it).x; float y = (*it).y; if (x < lowX) lowX = x; if (y < lowY) lowY = y; } return FloatPoint(lowX, lowY); } // Helper function static float calculateKerning(RenderObject* item) { const Font& font = item->style()->font(); const SVGRenderStyle* svgStyle = item->style()->svgStyle(); float kerning = 0.0f; if (CSSPrimitiveValue* primitive = static_cast(svgStyle->kerning())) { kerning = primitive->getFloatValue(); if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize() > 0) kerning = kerning / 100.0f * font.pixelSize(); } return kerning; } // Helper class for paint() struct SVGRootInlineBoxPaintWalker { SVGRootInlineBoxPaintWalker(SVGRootInlineBox* rootBox, SVGResourceFilter* rootFilter, RenderObject::PaintInfo paintInfo, int tx, int ty) : m_rootBox(rootBox) , m_chunkStarted(false) , m_paintInfo(paintInfo) , m_savedInfo(paintInfo) , m_boundingBox(tx + rootBox->xPos(), ty + rootBox->yPos(), rootBox->width(), rootBox->height()) , m_filter(0) , m_rootFilter(rootFilter) , m_fillPaintServer(0) , m_strokePaintServer(0) , m_fillPaintServerObject(0) , m_strokePaintServerObject(0) , m_tx(tx) , m_ty(ty) { } ~SVGRootInlineBoxPaintWalker() { ASSERT(!m_filter); ASSERT(!m_fillPaintServer); ASSERT(!m_fillPaintServerObject); ASSERT(!m_strokePaintServer); ASSERT(!m_strokePaintServerObject); ASSERT(!m_chunkStarted); } void teardownFillPaintServer() { if (!m_fillPaintServer) return; m_fillPaintServer->teardown(m_paintInfo.context, m_fillPaintServerObject, ApplyToFillTargetType, true); m_fillPaintServer = 0; m_fillPaintServerObject = 0; } void teardownStrokePaintServer() { if (!m_strokePaintServer) return; m_strokePaintServer->teardown(m_paintInfo.context, m_strokePaintServerObject, ApplyToStrokeTargetType, true); m_strokePaintServer = 0; m_strokePaintServerObject = 0; } void chunkStartCallback(InlineBox* box) { ASSERT(!m_chunkStarted); m_chunkStarted = true; InlineFlowBox* flowBox = box->parent(); // Initialize text rendering RenderObject* object = flowBox->object(); ASSERT(object); m_savedInfo = m_paintInfo; m_paintInfo.context->save(); if (!flowBox->isRootInlineBox()) m_paintInfo.context->concatCTM(m_rootBox->object()->localTransform()); m_paintInfo.context->concatCTM(object->localTransform()); if (!flowBox->isRootInlineBox()) { prepareToRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_rootFilter); m_paintInfo.rect = object->localTransform().inverse().mapRect(m_paintInfo.rect); } } void chunkEndCallback(InlineBox* box) { ASSERT(m_chunkStarted); m_chunkStarted = false; InlineFlowBox* flowBox = box->parent(); RenderObject* object = flowBox->object(); ASSERT(object); // Clean up last used paint server teardownFillPaintServer(); teardownStrokePaintServer(); // Finalize text rendering if (!flowBox->isRootInlineBox()) { finishRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_savedInfo.context); m_filter = 0; } // Restore context & repaint rect m_paintInfo.context->restore(); m_paintInfo.rect = m_savedInfo.rect; } bool chunkSetupFillCallback(InlineBox* box) { InlineFlowBox* flowBox = box->parent(); // Setup fill paint server RenderObject* object = flowBox->object(); ASSERT(object); ASSERT(!m_strokePaintServer); teardownFillPaintServer(); m_fillPaintServer = SVGPaintServer::fillPaintServer(object->style(), object); if (m_fillPaintServer) { m_fillPaintServer->setup(m_paintInfo.context, object, ApplyToFillTargetType, true); m_fillPaintServerObject = object; return true; } return false; } bool chunkSetupStrokeCallback(InlineBox* box) { InlineFlowBox* flowBox = box->parent(); // Setup stroke paint server RenderObject* object = flowBox->object(); ASSERT(object); // If we're both stroked & filled, teardown fill paint server before stroking. teardownFillPaintServer(); teardownStrokePaintServer(); m_strokePaintServer = SVGPaintServer::strokePaintServer(object->style(), object); if (m_strokePaintServer) { m_strokePaintServer->setup(m_paintInfo.context, object, ApplyToStrokeTargetType, true); m_strokePaintServerObject = object; return true; } return false; } void chunkPortionCallback(SVGInlineTextBox* textBox, int startOffset, const TransformationMatrix& chunkCtm, const Vector::iterator& start, const Vector::iterator& end) { RenderText* text = textBox->textObject(); ASSERT(text); RenderStyle* styleToUse = text->style(textBox->isFirstLineStyle()); ASSERT(styleToUse); startOffset += textBox->start(); int textDecorations = styleToUse->textDecorationsInEffect(); int textWidth = 0; IntPoint decorationOrigin; SVGTextDecorationInfo info; if (!chunkCtm.isIdentity()) m_paintInfo.context->concatCTM(chunkCtm); for (Vector::iterator it = start; it != end; ++it) { if (it->isHidden()) continue; // Determine how many characters - starting from the current - can be drawn at once. Vector::iterator itSearch = it + 1; while (itSearch != end) { if (itSearch->drawnSeperated || itSearch->isHidden()) break; itSearch++; } const UChar* stringStart = text->characters() + startOffset + (it - start); unsigned int stringLength = itSearch - it; // Paint decorations, that have to be drawn before the text gets drawn if (textDecorations != TDNONE && m_paintInfo.phase != PaintPhaseSelection) { textWidth = styleToUse->font().width(svgTextRunForInlineTextBox(stringStart, stringLength, styleToUse, textBox, (*it).x)); decorationOrigin = IntPoint((int) (*it).x, (int) (*it).y - styleToUse->font().ascent()); info = m_rootBox->retrievePaintServersForTextDecoration(text); } if (textDecorations & UNDERLINE && textWidth != 0.0f) textBox->paintDecoration(UNDERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info); if (textDecorations & OVERLINE && textWidth != 0.0f) textBox->paintDecoration(OVERLINE, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info); // Paint text SVGPaintServer* activePaintServer = m_fillPaintServer; if (!activePaintServer) activePaintServer = m_strokePaintServer; ASSERT(activePaintServer); textBox->paintCharacters(m_paintInfo, m_tx, m_ty, *it, stringStart, stringLength, activePaintServer); // Paint decorations, that have to be drawn afterwards if (textDecorations & LINE_THROUGH && textWidth != 0.0f) textBox->paintDecoration(LINE_THROUGH, m_paintInfo.context, decorationOrigin.x(), decorationOrigin.y(), textWidth, *it, info); // Skip processed characters it = itSearch - 1; } if (!chunkCtm.isIdentity()) m_paintInfo.context->concatCTM(chunkCtm.inverse()); } private: SVGRootInlineBox* m_rootBox; bool m_chunkStarted : 1; RenderObject::PaintInfo m_paintInfo; RenderObject::PaintInfo m_savedInfo; FloatRect m_boundingBox; SVGResourceFilter* m_filter; SVGResourceFilter* m_rootFilter; SVGPaintServer* m_fillPaintServer; SVGPaintServer* m_strokePaintServer; RenderObject* m_fillPaintServerObject; RenderObject* m_strokePaintServerObject; int m_tx; int m_ty; }; void SVGRootInlineBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) { if (paintInfo.context->paintingDisabled() || paintInfo.phase != PaintPhaseForeground) return; RenderObject::PaintInfo savedInfo(paintInfo); paintInfo.context->save(); SVGResourceFilter* filter = 0; FloatRect boundingBox(tx + xPos(), ty + yPos(), width(), height()); // Initialize text rendering paintInfo.context->concatCTM(object()->localTransform()); prepareToRenderSVGContent(object(), paintInfo, boundingBox, filter); paintInfo.context->concatCTM(object()->localTransform().inverse()); // Render text, chunk-by-chunk SVGRootInlineBoxPaintWalker walkerCallback(this, filter, paintInfo, tx, ty); SVGTextChunkWalker walker(&walkerCallback, &SVGRootInlineBoxPaintWalker::chunkPortionCallback, &SVGRootInlineBoxPaintWalker::chunkStartCallback, &SVGRootInlineBoxPaintWalker::chunkEndCallback, &SVGRootInlineBoxPaintWalker::chunkSetupFillCallback, &SVGRootInlineBoxPaintWalker::chunkSetupStrokeCallback); walkTextChunks(&walker); // Finalize text rendering finishRenderSVGContent(object(), paintInfo, boundingBox, filter, savedInfo.context); paintInfo.context->restore(); } int SVGRootInlineBox::placeBoxesHorizontally(int, int& leftPosition, int& rightPosition, bool&) { // Remove any offsets caused by RTL text layout leftPosition = 0; rightPosition = 0; return 0; } int SVGRootInlineBox::verticallyAlignBoxes(int) { // height is set by layoutInlineBoxes. return height(); } float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) { ASSERT(!range.isOpen()); ASSERT(range.isClosed()); ASSERT(range.box->isInlineTextBox()); InlineTextBox* textBox = static_cast(range.box); RenderText* text = textBox->textObject(); RenderStyle* style = text->style(); return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox, 0)); } float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) { ASSERT(!range.isOpen()); ASSERT(range.isClosed()); ASSERT(range.box->isInlineTextBox()); InlineTextBox* textBox = static_cast(range.box); RenderText* text = textBox->textObject(); const Font& font = text->style()->font(); return (range.endOffset - range.startOffset) * (font.ascent() + font.descent()); } TextRun svgTextRunForInlineTextBox(const UChar* c, int len, RenderStyle* style, const InlineTextBox* textBox, float xPos) { ASSERT(textBox); ASSERT(style); TextRun run(c, len, false, static_cast(xPos), textBox->toAdd(), textBox->direction() == RTL, textBox->m_dirOverride || style->visuallyOrdered()); #if ENABLE(SVG_FONTS) run.setReferencingRenderObject(textBox->textObject()->parent()); #endif // We handle letter & word spacing ourselves run.disableSpacing(); return run; } static float cummulatedWidthOrHeightOfTextChunk(SVGTextChunk& chunk, bool calcWidthOnly) { float length = 0.0f; Vector::iterator charIt = chunk.start; Vector::iterator it = chunk.boxes.begin(); Vector::iterator end = chunk.boxes.end(); for (; it != end; ++it) { SVGInlineBoxCharacterRange& range = *it; SVGInlineTextBox* box = static_cast(range.box); RenderStyle* style = box->object()->style(); for (int i = range.startOffset; i < range.endOffset; ++i) { ASSERT(charIt <= chunk.end); // Determine how many characters - starting from the current - can be measured at once. // Important for non-absolute positioned non-latin1 text (ie. Arabic) where ie. the width // of a string is not the sum of the boundaries of all contained glyphs. Vector::iterator itSearch = charIt + 1; Vector::iterator endSearch = charIt + range.endOffset - i; while (itSearch != endSearch) { // No need to check for 'isHidden()' here as this function is not called for text paths. if (itSearch->drawnSeperated) break; itSearch++; } unsigned int positionOffset = itSearch - charIt; // Calculate width/height of subrange SVGInlineBoxCharacterRange subRange; subRange.box = range.box; subRange.startOffset = i; subRange.endOffset = i + positionOffset; if (calcWidthOnly) length += cummulatedWidthOfInlineBoxCharacterRange(subRange); else length += cummulatedHeightOfInlineBoxCharacterRange(subRange); // Calculate gap between the previous & current range // ABCD - we need to take the gaps between A & B into account // so add "40" as width, and analogous for B & C, add "20" as width. if (itSearch > chunk.start && itSearch < chunk.end) { SVGChar& lastCharacter = *(itSearch - 1); SVGChar& currentCharacter = *itSearch; int offset = box->direction() == RTL ? box->end() - i - positionOffset + 1 : box->start() + i + positionOffset - 1; // FIXME: does this need to change to handle multichar glyphs? int charsConsumed = 1; String glyphName; if (calcWidthOnly) { float lastGlyphWidth = box->calculateGlyphWidth(style, offset, 0, charsConsumed, glyphName); length += currentCharacter.x - lastCharacter.x - lastGlyphWidth; } else { float lastGlyphHeight = box->calculateGlyphHeight(style, offset, 0); length += currentCharacter.y - lastCharacter.y - lastGlyphHeight; } } // Advance processed characters i += positionOffset - 1; charIt = itSearch; } } ASSERT(charIt == chunk.end); return length; } static float cummulatedWidthOfTextChunk(SVGTextChunk& chunk) { return cummulatedWidthOrHeightOfTextChunk(chunk, true); } static float cummulatedHeightOfTextChunk(SVGTextChunk& chunk) { return cummulatedWidthOrHeightOfTextChunk(chunk, false); } static float calculateTextAnchorShiftForTextChunk(SVGTextChunk& chunk, ETextAnchor anchor) { float shift = 0.0f; if (chunk.isVerticalText) shift = cummulatedHeightOfTextChunk(chunk); else shift = cummulatedWidthOfTextChunk(chunk); if (anchor == TA_MIDDLE) shift *= -0.5f; else shift *= -1.0f; return shift; } static void applyTextAnchorToTextChunk(SVGTextChunk& chunk) { // This method is not called for chunks containing chars aligned on a path. // -> all characters are visible, no need to check for "isHidden()" anywhere. if (chunk.anchor == TA_START) return; float shift = calculateTextAnchorShiftForTextChunk(chunk, chunk.anchor); // Apply correction to chunk Vector::iterator chunkIt = chunk.start; for (; chunkIt != chunk.end; ++chunkIt) { SVGChar& curChar = *chunkIt; if (chunk.isVerticalText) curChar.y += shift; else curChar.x += shift; } // Move inline boxes Vector::iterator boxIt = chunk.boxes.begin(); Vector::iterator boxEnd = chunk.boxes.end(); for (; boxIt != boxEnd; ++boxIt) { SVGInlineBoxCharacterRange& range = *boxIt; InlineBox* curBox = range.box; ASSERT(curBox->isInlineTextBox()); ASSERT(curBox->parent() && (curBox->parent()->isRootInlineBox() || curBox->parent()->isInlineFlowBox())); // Move target box if (chunk.isVerticalText) curBox->setYPos(curBox->yPos() + static_cast(shift)); else curBox->setXPos(curBox->xPos() + static_cast(shift)); } } static float calculateTextLengthCorrectionForTextChunk(SVGTextChunk& chunk, ELengthAdjust lengthAdjust, float& computedLength) { if (chunk.textLength <= 0.0f) return 0.0f; float computedWidth = cummulatedWidthOfTextChunk(chunk); float computedHeight = cummulatedHeightOfTextChunk(chunk); if ((computedWidth <= 0.0f && !chunk.isVerticalText) || (computedHeight <= 0.0f && chunk.isVerticalText)) return 0.0f; if (chunk.isVerticalText) computedLength = computedHeight; else computedLength = computedWidth; if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { if (chunk.isVerticalText) chunk.ctm.scale(1.0f, chunk.textLength / computedLength); else chunk.ctm.scale(chunk.textLength / computedLength, 1.0f); return 0.0f; } return (chunk.textLength - computedLength) / float(chunk.end - chunk.start); } static void applyTextLengthCorrectionToTextChunk(SVGTextChunk& chunk) { // This method is not called for chunks containing chars aligned on a path. // -> all characters are visible, no need to check for "isHidden()" anywhere. // lengthAdjust="spacingAndGlyphs" is handled by modifying chunk.ctm float computedLength = 0.0f; float spacingToApply = calculateTextLengthCorrectionForTextChunk(chunk, chunk.lengthAdjust, computedLength); if (!chunk.ctm.isIdentity() && chunk.lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { SVGChar& firstChar = *(chunk.start); // Assure we apply the chunk scaling in the right origin TransformationMatrix newChunkCtm; newChunkCtm.translate(firstChar.x, firstChar.y); newChunkCtm = chunk.ctm * newChunkCtm; newChunkCtm.translate(-firstChar.x, -firstChar.y); chunk.ctm = newChunkCtm; } // Apply correction to chunk if (spacingToApply != 0.0f) { Vector::iterator chunkIt = chunk.start; for (; chunkIt != chunk.end; ++chunkIt) { SVGChar& curChar = *chunkIt; curChar.drawnSeperated = true; if (chunk.isVerticalText) curChar.y += (chunkIt - chunk.start) * spacingToApply; else curChar.x += (chunkIt - chunk.start) * spacingToApply; } } } void SVGRootInlineBox::computePerCharacterLayoutInformation() { // Clean up any previous layout information m_svgChars.clear(); m_svgTextChunks.clear(); // Build layout information for all contained render objects SVGCharacterLayoutInfo info(m_svgChars); buildLayoutInformation(this, info); // Now all layout information are available for every character // contained in any of our child inline/flow boxes. Build list // of text chunks now, to be able to apply text-anchor shifts. buildTextChunks(m_svgChars, m_svgTextChunks, this); // Layout all text chunks // text-anchor needs to be applied to individual chunks. layoutTextChunks(); // Finally the top left position of our box is known. // Propogate this knownledge to our RenderSVGText parent. FloatPoint topLeft = topLeftPositionOfCharacterRange(m_svgChars); block()->setLocation((int) floorf(topLeft.x()), (int) floorf(topLeft.y())); // Layout all InlineText/Flow boxes // BEWARE: This requires the root top/left position to be set correctly before! layoutInlineBoxes(); } void SVGRootInlineBox::buildLayoutInformation(InlineFlowBox* start, SVGCharacterLayoutInfo& info) { if (start->isRootInlineBox()) { ASSERT(start->object()->element()->hasTagName(SVGNames::textTag)); SVGTextPositioningElement* positioningElement = static_cast(start->object()->element()); ASSERT(positioningElement); ASSERT(positioningElement->parentNode()); info.addLayoutInformation(positioningElement); } LastGlyphInfo lastGlyph; for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isText()) buildLayoutInformationForTextBox(info, static_cast(curr), lastGlyph); else { ASSERT(curr->isInlineFlowBox()); InlineFlowBox* flowBox = static_cast(curr); if (!flowBox->object()->element()) continue; // Skip generated content. bool isAnchor = flowBox->object()->element()->hasTagName(SVGNames::aTag); bool isTextPath = flowBox->object()->element()->hasTagName(SVGNames::textPathTag); if (!isTextPath && !isAnchor) { SVGTextPositioningElement* positioningElement = static_cast(flowBox->object()->element()); ASSERT(positioningElement); ASSERT(positioningElement->parentNode()); info.addLayoutInformation(positioningElement); } else if (!isAnchor) { info.setInPathLayout(true); // Handle text-anchor/textLength on path, which is special. SVGTextContentElement* textContent = 0; Node* node = flowBox->object()->element(); if (node && node->isSVGElement()) textContent = static_cast(node); ASSERT(textContent); ELengthAdjust lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); ETextAnchor anchor = flowBox->object()->style()->svgStyle()->textAnchor(); float textAnchorStartOffset = 0.0f; // Initialize sub-layout. We need to create text chunks from the textPath // children using our standard layout code, to be able to measure the // text length using our normal methods and not textPath specific hacks. Vector tempChars; Vector tempChunks; SVGCharacterLayoutInfo tempInfo(tempChars); buildLayoutInformation(flowBox, tempInfo); buildTextChunks(tempChars, tempChunks, flowBox); Vector::iterator it = tempChunks.begin(); Vector::iterator end = tempChunks.end(); TransformationMatrix ctm; float computedLength = 0.0f; for (; it != end; ++it) { SVGTextChunk& chunk = *it; // Apply text-length calculation info.pathExtraAdvance += calculateTextLengthCorrectionForTextChunk(chunk, lengthAdjust, computedLength); if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS) { info.pathTextLength += computedLength; info.pathChunkLength += chunk.textLength; } // Calculate text-anchor start offset if (anchor == TA_START) continue; textAnchorStartOffset += calculateTextAnchorShiftForTextChunk(chunk, anchor); } info.addLayoutInformation(flowBox, textAnchorStartOffset); } float shiftxSaved = info.shiftx; float shiftySaved = info.shifty; buildLayoutInformation(flowBox, info); info.processedChunk(shiftxSaved, shiftySaved); if (isTextPath) info.setInPathLayout(false); } } } void SVGRootInlineBox::layoutInlineBoxes() { int lowX = INT_MAX; int lowY = INT_MAX; int highX = INT_MIN; int highY = INT_MIN; // Layout all child boxes Vector::iterator it = m_svgChars.begin(); layoutInlineBoxes(this, it, lowX, highX, lowY, highY); ASSERT(it == m_svgChars.end()); } void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox* start, Vector::iterator& it, int& lowX, int& highX, int& lowY, int& highY) { for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { RenderStyle* style = curr->object()->style(); const Font& font = style->font(); if (curr->object()->isText()) { SVGInlineTextBox* textBox = static_cast(curr); unsigned length = textBox->len(); SVGChar curChar = *it; ASSERT(it != m_svgChars.end()); FloatRect stringRect; for (unsigned i = 0; i < length; ++i) { ASSERT(it != m_svgChars.end()); if (it->isHidden()) { ++it; continue; } stringRect.unite(textBox->calculateGlyphBoundaries(style, textBox->start() + i, *it)); ++it; } IntRect enclosedStringRect = enclosingIntRect(stringRect); int minX = enclosedStringRect.x(); int maxX = minX + enclosedStringRect.width(); int minY = enclosedStringRect.y(); int maxY = minY + enclosedStringRect.height(); curr->setXPos(minX - block()->x()); curr->setWidth(enclosedStringRect.width()); curr->setYPos(minY - block()->y()); curr->setBaseline(font.ascent()); curr->setHeight(enclosedStringRect.height()); if (minX < lowX) lowX = minX; if (maxX > highX) highX = maxX; if (minY < lowY) lowY = minY; if (maxY > highY) highY = maxY; } else { ASSERT(curr->isInlineFlowBox()); int minX = INT_MAX; int minY = INT_MAX; int maxX = INT_MIN; int maxY = INT_MIN; InlineFlowBox* flowBox = static_cast(curr); if (!flowBox->object()->element()) continue; // Skip generated content. layoutInlineBoxes(flowBox, it, minX, maxX, minY, maxY); curr->setXPos(minX - block()->x()); curr->setWidth(maxX - minX); curr->setYPos(minY - block()->y()); curr->setBaseline(font.ascent()); curr->setHeight(maxY - minY); if (minX < lowX) lowX = minX; if (maxX > highX) highX = maxX; if (minY < lowY) lowY = minY; if (maxY > highY) highY = maxY; } } if (start->isRootInlineBox()) { int top = lowY - block()->y(); int bottom = highY - block()->y(); start->setXPos(lowX - block()->x()); start->setYPos(top); start->setWidth(highX - lowX); start->setHeight(highY - lowY); start->setVerticalOverflowPositions(top, bottom); start->setVerticalSelectionPositions(top, bottom); } } void SVGRootInlineBox::buildLayoutInformationForTextBox(SVGCharacterLayoutInfo& info, InlineTextBox* textBox, LastGlyphInfo& lastGlyph) { RenderText* text = textBox->textObject(); ASSERT(text); RenderStyle* style = text->style(textBox->isFirstLineStyle()); ASSERT(style); const Font& font = style->font(); SVGInlineTextBox* svgTextBox = static_cast(textBox); unsigned length = textBox->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; int extraCharsAvailable = length - i - 1; String unicodeStr; String glyphName; if (textBox->direction() == RTL) { glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->end() - i, extraCharsAvailable, charsConsumed, glyphName); glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->end() - i, extraCharsAvailable); unicodeStr = String(textBox->textObject()->text()->characters() + textBox->end() - i, charsConsumed); } else { glyphWidth = svgTextBox->calculateGlyphWidth(style, textBox->start() + i, extraCharsAvailable, charsConsumed, glyphName); glyphHeight = svgTextBox->calculateGlyphHeight(style, textBox->start() + i, extraCharsAvailable); unicodeStr = String(textBox->textObject()->text()->characters() + textBox->start() + i, charsConsumed); } 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() + calculateKerning(textBox->object()->element()->renderer()); const UChar* currentCharacter = text->characters() + (textBox->direction() == RTL ? textBox->end() - i : textBox->start() + i); const UChar* lastCharacter = 0; if (textBox->direction() == RTL) { if (i < textBox->end()) lastCharacter = text->characters() + textBox->end() - i + 1; } else { if (i > 0) lastCharacter = text->characters() + textBox->start() + i - 1; } if (info.nextDrawnSeperated || spacing != 0.0f) { 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 = calculateGlyphAdvanceAndShiftRespectingOrientation(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 > 0.0f && info.pathChunkLength > 0.0f) { 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, text, 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; } } double kerning = 0.0; #if ENABLE(SVG_FONTS) SVGFontElement* svgFont = 0; if (style->font().isSVGFont()) svgFont = style->font().svgFont(); if (lastGlyph.isValid && style->font().isSVGFont()) { SVGHorizontalKerningPair kerningPair; if (svgFont->getHorizontalKerningPairForStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeStr, glyphName, kerningPair)) kerning = kerningPair.kerning; } if (style->font().isSVGFont()) { lastGlyph.unicode = unicodeStr; lastGlyph.glyphName = glyphName; lastGlyph.isValid = true; } else lastGlyph.isValid = false; #endif svgChar.x -= (float)kerning; // Advance to new position if (isVerticalText) { svgChar.drawnSeperated = true; info.cury += glyphAdvance + spacing; } else info.curx += glyphAdvance + spacing - (float)kerning; // Advance to next character group for (int k = 0; k < charsConsumed; ++k) { info.svgChars.append(svgChar); info.processedSingleCharacter(); svgChar.drawnSeperated = false; svgChar.newTextChunk = false; } } } void SVGRootInlineBox::buildTextChunks(Vector& svgChars, Vector& svgTextChunks, InlineFlowBox* start) { SVGTextChunkLayoutInfo info(svgTextChunks); info.it = svgChars.begin(); info.chunk.start = svgChars.begin(); info.chunk.end = svgChars.begin(); buildTextChunks(svgChars, start, info); ASSERT(info.it == svgChars.end()); } void SVGRootInlineBox::buildTextChunks(Vector& svgChars, InlineFlowBox* start, SVGTextChunkLayoutInfo& info) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> buildTextChunks(start=%p)\n", start); #endif for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isText()) { InlineTextBox* textBox = static_cast(curr); unsigned length = textBox->len(); if (!length) continue; #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Handle inline text box (%p) with %i characters (start: %i, end: %i), handlingTextPath=%i\n", textBox, length, textBox->start(), textBox->end(), (int) info.handlingTextPath); #endif RenderText* text = textBox->textObject(); ASSERT(text); ASSERT(text->element()); SVGTextContentElement* textContent = 0; Node* node = text->element()->parent(); while (node && node->isSVGElement() && !textContent) { if (static_cast(node)->isTextContent()) textContent = static_cast(node); else node = node->parentNode(); } ASSERT(textContent); // Start new character range for the first chunk bool isFirstCharacter = info.svgTextChunks.isEmpty() && info.chunk.start == info.it && info.chunk.start == info.chunk.end; if (isFirstCharacter) { ASSERT(info.chunk.boxes.isEmpty()); info.chunk.boxes.append(SVGInlineBoxCharacterRange()); } else ASSERT(!info.chunk.boxes.isEmpty()); // Walk string to find out new chunk positions, if existant for (unsigned i = 0; i < length; ++i) { ASSERT(info.it != svgChars.end()); SVGInlineBoxCharacterRange& range = info.chunk.boxes.last(); if (range.isOpen()) { range.box = curr; range.startOffset = (i == 0 ? 0 : i - 1); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Range is open! box=%p, startOffset=%i\n", range.box, range.startOffset); #endif } // If a new (or the first) chunk has been started, record it's text-anchor and writing mode. if (info.assignChunkProperties) { info.assignChunkProperties = false; info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); info.chunk.isTextPath = info.handlingTextPath; info.chunk.anchor = text->style()->svgStyle()->textAnchor(); info.chunk.textLength = textContent->textLength().value(textContent); info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", info.chunk.isVerticalText, info.chunk.anchor); #endif } if (i > 0 && !isFirstCharacter && (*info.it).newTextChunk) { // Close mid chunk & character range ASSERT(!range.isOpen()); ASSERT(!range.isClosed()); range.endOffset = i; closeTextChunk(info); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Close mid-text chunk, at endOffset: %i and starting new mid chunk!\n", range.endOffset); #endif // Prepare for next chunk, if we're not at the end startTextChunk(info); if (i + 1 == length) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Record last chunk of inline text box!\n"); #endif startTextChunk(info); SVGInlineBoxCharacterRange& range = info.chunk.boxes.last(); info.assignChunkProperties = false; info.chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); info.chunk.isTextPath = info.handlingTextPath; info.chunk.anchor = text->style()->svgStyle()->textAnchor(); info.chunk.textLength = textContent->textLength().value(textContent); info.chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); range.box = curr; range.startOffset = i; ASSERT(!range.isOpen()); ASSERT(!range.isClosed()); } } // This should only hold true for the first character of the first chunk if (isFirstCharacter) isFirstCharacter = false; ++info.it; } #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Finished inline text box!\n"); #endif SVGInlineBoxCharacterRange& range = info.chunk.boxes.last(); if (!range.isOpen() && !range.isClosed()) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Last range not closed - closing with endOffset: %i\n", length); #endif // Current text chunk is not yet closed. Finish the current range, but don't start a new chunk. range.endOffset = length; if (info.it != svgChars.end()) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Not at last character yet!\n"); #endif // If we're not at the end of the last box to be processed, and if the next // character starts a new chunk, then close the current chunk and start a new one. if ((*info.it).newTextChunk) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n"); #endif closeTextChunk(info); startTextChunk(info); } else { // Just start a new character range info.chunk.boxes.append(SVGInlineBoxCharacterRange()); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Next character does NOT start a new chunk! Starting new character range...\n"); #endif } } else { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Closing final chunk! Finished processing!\n"); #endif // Close final chunk, once we're at the end of the last box closeTextChunk(info); } } } else { ASSERT(curr->isInlineFlowBox()); InlineFlowBox* flowBox = static_cast(curr); if (!flowBox->object()->element()) continue; // Skip generated content. bool isTextPath = flowBox->object()->element()->hasTagName(SVGNames::textPathTag); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox, (int) isTextPath); #endif if (isTextPath) info.handlingTextPath = true; buildTextChunks(svgChars, flowBox, info); if (isTextPath) info.handlingTextPath = false; } } #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " <- buildTextChunks(start=%p)\n", start); #endif } const Vector& SVGRootInlineBox::svgTextChunks() const { return m_svgTextChunks; } void SVGRootInlineBox::layoutTextChunks() { Vector::iterator it = m_svgTextChunks.begin(); Vector::iterator end = m_svgTextChunks.end(); for (; it != end; ++it) { SVGTextChunk& chunk = *it; #if DEBUG_CHUNK_BUILDING > 0 { fprintf(stderr, "Handle TEXT CHUNK! anchor=%i, textLength=%f, lengthAdjust=%i, isVerticalText=%i, isTextPath=%i start=%p, end=%p -> dist: %i\n", (int) chunk.anchor, chunk.textLength, (int) chunk.lengthAdjust, (int) chunk.isVerticalText, (int) chunk.isTextPath, chunk.start, chunk.end, (unsigned int) (chunk.end - chunk.start)); Vector::iterator boxIt = chunk.boxes.begin(); Vector::iterator boxEnd = chunk.boxes.end(); unsigned int i = 0; for (; boxIt != boxEnd; ++boxIt) { SVGInlineBoxCharacterRange& range = *boxIt; i++; fprintf(stderr, " -> RANGE %i STARTOFFSET: %i, ENDOFFSET: %i, BOX: %p\n", i, range.startOffset, range.endOffset, range.box); } } #endif if (chunk.isTextPath) continue; // text-path & textLength, with lengthAdjust="spacing" is already handled for textPath layouts. applyTextLengthCorrectionToTextChunk(chunk); // text-anchor is already handled for textPath layouts. applyTextAnchorToTextChunk(chunk); } } static inline void addPaintServerToTextDecorationInfo(ETextDecoration decoration, SVGTextDecorationInfo& info, RenderObject* object) { if (object->style()->svgStyle()->hasFill()) info.fillServerMap.set(decoration, object); if (object->style()->svgStyle()->hasStroke()) info.strokeServerMap.set(decoration, object); } SVGTextDecorationInfo SVGRootInlineBox::retrievePaintServersForTextDecoration(RenderObject* start) { ASSERT(start); Vector parentChain; while ((start = start->parent())) { parentChain.prepend(start); // Stop at our direct parent. if (start->isSVGText()) break; } Vector::iterator it = parentChain.begin(); Vector::iterator end = parentChain.end(); SVGTextDecorationInfo info; for (; it != end; ++it) { RenderObject* object = *it; ASSERT(object); RenderStyle* style = object->style(); ASSERT(style); int decorations = style->textDecoration(); if (decorations != NONE) { if (decorations & OVERLINE) addPaintServerToTextDecorationInfo(OVERLINE, info, object); if (decorations & UNDERLINE) addPaintServerToTextDecorationInfo(UNDERLINE, info, object); if (decorations & LINE_THROUGH) addPaintServerToTextDecorationInfo(LINE_THROUGH, info, object); } } return info; } void SVGRootInlineBox::walkTextChunks(SVGTextChunkWalkerBase* walker, const SVGInlineTextBox* textBox) { ASSERT(walker); Vector::iterator it = m_svgTextChunks.begin(); Vector::iterator itEnd = m_svgTextChunks.end(); for (; it != itEnd; ++it) { SVGTextChunk& curChunk = *it; Vector::iterator boxIt = curChunk.boxes.begin(); Vector::iterator boxEnd = curChunk.boxes.end(); InlineBox* lastNotifiedBox = 0; InlineBox* prevBox = 0; unsigned int chunkOffset = 0; bool startedFirstChunk = false; for (; boxIt != boxEnd; ++boxIt) { SVGInlineBoxCharacterRange& range = *boxIt; ASSERT(range.box->isInlineTextBox()); SVGInlineTextBox* rangeTextBox = static_cast(range.box); if (textBox && rangeTextBox != textBox) { chunkOffset += range.endOffset - range.startOffset; continue; } // Eventually notify that we started a new chunk if (!textBox && !startedFirstChunk) { startedFirstChunk = true; lastNotifiedBox = range.box; walker->start(range.box); } else { // Eventually apply new style, as this chunk spans multiple boxes (with possible different styling) if (prevBox && prevBox != range.box) { lastNotifiedBox = range.box; walker->end(prevBox); walker->start(lastNotifiedBox); } } unsigned int length = range.endOffset - range.startOffset; Vector::iterator itCharBegin = curChunk.start + chunkOffset; Vector::iterator itCharEnd = curChunk.start + chunkOffset + length; ASSERT(itCharEnd <= curChunk.end); // Process this chunk portion if (textBox) (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); else { if (walker->setupFill(range.box)) (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); if (walker->setupStroke(range.box)) (*walker)(rangeTextBox, range.startOffset, curChunk.ctm, itCharBegin, itCharEnd); } chunkOffset += length; if (!textBox) prevBox = range.box; } if (!textBox && startedFirstChunk) walker->end(lastNotifiedBox); } } } // namespace WebCore #endif // ENABLE(SVG)