Index: third_party/WebKit/WebCore/rendering/SVGRootInlineBox.cpp |
=================================================================== |
--- third_party/WebKit/WebCore/rendering/SVGRootInlineBox.cpp (revision 9391) |
+++ third_party/WebKit/WebCore/rendering/SVGRootInlineBox.cpp (working copy) |
@@ -1,1719 +1,1719 @@ |
-/* |
- * This file is part of the WebKit project. |
- * |
- * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> |
- * (C) 2006 Apple Computer Inc. |
- * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> |
- * |
- * 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 <float.h> |
- |
-// 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<RenderSVGRoot*>(start); |
-} |
- |
-static inline FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>& chars) |
-{ |
- return topLeftPositionOfCharacterRange(chars.begin(), chars.end()); |
-} |
- |
-FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it, Vector<SVGChar>::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<CSSPrimitiveValue*>(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<SVGChar>::iterator& start, const Vector<SVGChar>::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<SVGChar>::iterator it = start; it != end; ++it) { |
- if (it->isHidden()) |
- continue; |
- |
- // Determine how many characters - starting from the current - can be drawn at once. |
- Vector<SVGChar>::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<SVGRootInlineBoxPaintWalker> 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<InlineTextBox*>(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<InlineTextBox*>(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<int>(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<SVGChar>::iterator charIt = chunk.start; |
- |
- Vector<SVGInlineBoxCharacterRange>::iterator it = chunk.boxes.begin(); |
- Vector<SVGInlineBoxCharacterRange>::iterator end = chunk.boxes.end(); |
- |
- for (; it != end; ++it) { |
- SVGInlineBoxCharacterRange& range = *it; |
- |
- SVGInlineTextBox* box = static_cast<SVGInlineTextBox*>(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<SVGChar>::iterator itSearch = charIt + 1; |
- Vector<SVGChar>::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 |
- // <text x="10 50 70">ABCD</text> - 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<SVGChar>::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<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); |
- Vector<SVGInlineBoxCharacterRange>::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<int>(shift)); |
- else |
- curBox->setXPos(curBox->xPos() + static_cast<int>(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<SVGChar>::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<SVGTextPositioningElement*>(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<InlineTextBox*>(curr), lastGlyph); |
- else { |
- ASSERT(curr->isInlineFlowBox()); |
- InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(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<SVGTextPositioningElement*>(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<SVGTextContentElement*>(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<SVGChar> tempChars; |
- Vector<SVGTextChunk> tempChunks; |
- |
- SVGCharacterLayoutInfo tempInfo(tempChars); |
- buildLayoutInformation(flowBox, tempInfo); |
- |
- buildTextChunks(tempChars, tempChunks, flowBox); |
- |
- Vector<SVGTextChunk>::iterator it = tempChunks.begin(); |
- Vector<SVGTextChunk>::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<SVGChar>::iterator it = m_svgChars.begin(); |
- layoutInlineBoxes(this, it, lowX, highX, lowY, highY); |
- ASSERT(it == m_svgChars.end()); |
-} |
- |
-void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox* start, Vector<SVGChar>::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<SVGInlineTextBox*>(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<InlineFlowBox*>(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<SVGInlineTextBox*>(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<SVGChar>& svgChars, Vector<SVGTextChunk>& 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<SVGChar>& 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<InlineTextBox*>(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<SVGElement*>(node)->isTextContent()) |
- textContent = static_cast<SVGTextContentElement*>(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<InlineFlowBox*>(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<SVGTextChunk>& SVGRootInlineBox::svgTextChunks() const |
-{ |
- return m_svgTextChunks; |
-} |
- |
-void SVGRootInlineBox::layoutTextChunks() |
-{ |
- Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin(); |
- Vector<SVGTextChunk>::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<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); |
- Vector<SVGInlineBoxCharacterRange>::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<RenderObject*> parentChain; |
- while ((start = start->parent())) { |
- parentChain.prepend(start); |
- |
- // Stop at our direct <text> parent. |
- if (start->isSVGText()) |
- break; |
- } |
- |
- Vector<RenderObject*>::iterator it = parentChain.begin(); |
- Vector<RenderObject*>::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<SVGTextChunk>::iterator it = m_svgTextChunks.begin(); |
- Vector<SVGTextChunk>::iterator itEnd = m_svgTextChunks.end(); |
- |
- for (; it != itEnd; ++it) { |
- SVGTextChunk& curChunk = *it; |
- |
- Vector<SVGInlineBoxCharacterRange>::iterator boxIt = curChunk.boxes.begin(); |
- Vector<SVGInlineBoxCharacterRange>::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<SVGInlineTextBox*>(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<SVGChar>::iterator itCharBegin = curChunk.start + chunkOffset; |
- Vector<SVGChar>::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) |
+/* |
+ * This file is part of the WebKit project. |
+ * |
+ * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> |
+ * (C) 2006 Apple Computer Inc. |
+ * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> |
+ * |
+ * 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 <float.h> |
+ |
+// 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<RenderSVGRoot*>(start); |
+} |
+ |
+static inline FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>& chars) |
+{ |
+ return topLeftPositionOfCharacterRange(chars.begin(), chars.end()); |
+} |
+ |
+FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it, Vector<SVGChar>::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<CSSPrimitiveValue*>(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<SVGChar>::iterator& start, const Vector<SVGChar>::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<SVGChar>::iterator it = start; it != end; ++it) { |
+ if (it->isHidden()) |
+ continue; |
+ |
+ // Determine how many characters - starting from the current - can be drawn at once. |
+ Vector<SVGChar>::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<SVGRootInlineBoxPaintWalker> 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<InlineTextBox*>(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<InlineTextBox*>(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<int>(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<SVGChar>::iterator charIt = chunk.start; |
+ |
+ Vector<SVGInlineBoxCharacterRange>::iterator it = chunk.boxes.begin(); |
+ Vector<SVGInlineBoxCharacterRange>::iterator end = chunk.boxes.end(); |
+ |
+ for (; it != end; ++it) { |
+ SVGInlineBoxCharacterRange& range = *it; |
+ |
+ SVGInlineTextBox* box = static_cast<SVGInlineTextBox*>(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<SVGChar>::iterator itSearch = charIt + 1; |
+ Vector<SVGChar>::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 |
+ // <text x="10 50 70">ABCD</text> - 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<SVGChar>::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<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); |
+ Vector<SVGInlineBoxCharacterRange>::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<int>(shift)); |
+ else |
+ curBox->setXPos(curBox->xPos() + static_cast<int>(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.scaleNonUniform(1.0f, chunk.textLength / computedLength); |
+ else |
+ chunk.ctm.scaleNonUniform(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<SVGChar>::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<SVGTextPositioningElement*>(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<InlineTextBox*>(curr), lastGlyph); |
+ else { |
+ ASSERT(curr->isInlineFlowBox()); |
+ InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(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<SVGTextPositioningElement*>(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<SVGTextContentElement*>(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<SVGChar> tempChars; |
+ Vector<SVGTextChunk> tempChunks; |
+ |
+ SVGCharacterLayoutInfo tempInfo(tempChars); |
+ buildLayoutInformation(flowBox, tempInfo); |
+ |
+ buildTextChunks(tempChars, tempChunks, flowBox); |
+ |
+ Vector<SVGTextChunk>::iterator it = tempChunks.begin(); |
+ Vector<SVGTextChunk>::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<SVGChar>::iterator it = m_svgChars.begin(); |
+ layoutInlineBoxes(this, it, lowX, highX, lowY, highY); |
+ ASSERT(it == m_svgChars.end()); |
+} |
+ |
+void SVGRootInlineBox::layoutInlineBoxes(InlineFlowBox* start, Vector<SVGChar>::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<SVGInlineTextBox*>(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<InlineFlowBox*>(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<SVGInlineTextBox*>(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<SVGChar>& svgChars, Vector<SVGTextChunk>& 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<SVGChar>& 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<InlineTextBox*>(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<SVGElement*>(node)->isTextContent()) |
+ textContent = static_cast<SVGTextContentElement*>(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<InlineFlowBox*>(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<SVGTextChunk>& SVGRootInlineBox::svgTextChunks() const |
+{ |
+ return m_svgTextChunks; |
+} |
+ |
+void SVGRootInlineBox::layoutTextChunks() |
+{ |
+ Vector<SVGTextChunk>::iterator it = m_svgTextChunks.begin(); |
+ Vector<SVGTextChunk>::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<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); |
+ Vector<SVGInlineBoxCharacterRange>::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<RenderObject*> parentChain; |
+ while ((start = start->parent())) { |
+ parentChain.prepend(start); |
+ |
+ // Stop at our direct <text> parent. |
+ if (start->isSVGText()) |
+ break; |
+ } |
+ |
+ Vector<RenderObject*>::iterator it = parentChain.begin(); |
+ Vector<RenderObject*>::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<SVGTextChunk>::iterator it = m_svgTextChunks.begin(); |
+ Vector<SVGTextChunk>::iterator itEnd = m_svgTextChunks.end(); |
+ |
+ for (; it != itEnd; ++it) { |
+ SVGTextChunk& curChunk = *it; |
+ |
+ Vector<SVGInlineBoxCharacterRange>::iterator boxIt = curChunk.boxes.begin(); |
+ Vector<SVGInlineBoxCharacterRange>::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<SVGInlineTextBox*>(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<SVGChar>::iterator itCharBegin = curChunk.start + chunkOffset; |
+ Vector<SVGChar>::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) |