| 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)
|
|
|