| /* |
| * 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->x(), ty + rootBox->y(), 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->renderer(); |
| ASSERT(object); |
| |
| m_savedInfo = m_paintInfo; |
| m_paintInfo.context->save(); |
| |
| // FIXME: Why is this done here instead of in RenderSVGText? |
| if (!flowBox->isRootInlineBox()) |
| SVGRenderBase::prepareToRenderSVGContent(object, m_paintInfo, m_boundingBox, m_filter, m_rootFilter); |
| } |
| |
| void chunkEndCallback(InlineBox* box) |
| { |
| ASSERT(m_chunkStarted); |
| m_chunkStarted = false; |
| |
| InlineFlowBox* flowBox = box->parent(); |
| |
| RenderObject* object = flowBox->renderer(); |
| ASSERT(object); |
| |
| // Clean up last used paint server |
| teardownFillPaintServer(); |
| teardownStrokePaintServer(); |
| |
| // Finalize text rendering |
| if (!flowBox->isRootInlineBox()) { |
| SVGRenderBase::finishRenderSVGContent(object, m_paintInfo, 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->renderer(); |
| 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->renderer(); |
| 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->textRenderer(); |
| 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 + x(), ty + y(), width(), height()); |
| |
| // Initialize text rendering |
| SVGRenderBase::prepareToRenderSVGContent(renderer(), paintInfo, boundingBox, filter); |
| |
| // 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 |
| SVGRenderBase::finishRenderSVGContent(renderer(), paintInfo, 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->textRenderer(); |
| 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->textRenderer(); |
| 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->textRenderer()->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->renderer()->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()); |
| |
| // Move target box |
| if (chunk.isVerticalText) |
| curBox->setY(curBox->y() + static_cast<int>(shift)); |
| else |
| curBox->setX(curBox->x() + 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->renderer()->node()->hasTagName(SVGNames::textTag)); |
| |
| SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(start->renderer()->node()); |
| ASSERT(positioningElement); |
| ASSERT(positioningElement->parentNode()); |
| |
| info.addLayoutInformation(positioningElement); |
| } |
| |
| LastGlyphInfo lastGlyph; |
| |
| for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { |
| if (curr->renderer()->isText()) |
| buildLayoutInformationForTextBox(info, static_cast<InlineTextBox*>(curr), lastGlyph); |
| else { |
| ASSERT(curr->isInlineFlowBox()); |
| InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr); |
| |
| if (!flowBox->renderer()->node()) |
| continue; // Skip generated content. |
| |
| bool isAnchor = flowBox->renderer()->node()->hasTagName(SVGNames::aTag); |
| bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag); |
| |
| if (!isTextPath && !isAnchor) { |
| SVGTextPositioningElement* positioningElement = static_cast<SVGTextPositioningElement*>(flowBox->renderer()->node()); |
| 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->renderer()->node(); |
| if (node && node->isSVGElement()) |
| textContent = static_cast<SVGTextContentElement*>(node); |
| ASSERT(textContent); |
| |
| ELengthAdjust lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); |
| ETextAnchor anchor = flowBox->renderer()->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->renderer()->style(); |
| if (curr->renderer()->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->setX(minX - block()->x()); |
| curr->setWidth(enclosedStringRect.width()); |
| |
| curr->setY(minY - block()->y()); |
| textBox->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->renderer()->node()) |
| continue; // Skip generated content. |
| |
| layoutInlineBoxes(flowBox, it, minX, maxX, minY, maxY); |
| |
| curr->setX(minX - block()->x()); |
| curr->setWidth(maxX - minX); |
| |
| curr->setY(minY - block()->y()); |
| static_cast<SVGInlineFlowBox*>(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->isSVGRootInlineBox()) { |
| int top = lowY - block()->y(); |
| int bottom = highY - block()->y(); |
| |
| start->setX(lowX - block()->x()); |
| start->setY(top); |
| |
| start->setWidth(highX - lowX); |
| static_cast<SVGRootInlineBox*>(start)->setHeight(highY - lowY); |
| |
| start->setVerticalOverflowPositions(top, bottom); |
| start->setVerticalSelectionPositions(top, bottom); |
| } |
| } |
| |
| void SVGRootInlineBox::buildLayoutInformationForTextBox(SVGCharacterLayoutInfo& info, InlineTextBox* textBox, LastGlyphInfo& lastGlyph) |
| { |
| RenderText* text = textBox->textRenderer(); |
| 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->textRenderer()->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->textRenderer()->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->renderer()->node()->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->renderer()->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->textRenderer(); |
| ASSERT(text); |
| ASSERT(text->node()); |
| |
| SVGTextContentElement* textContent = 0; |
| Node* node = text->node()->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->renderer()->node()) |
| continue; // Skip generated content. |
| |
| bool isTextPath = flowBox->renderer()->node()->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) |