diff options
author | Steve Block <steveblock@google.com> | 2011-05-13 06:44:40 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-05-13 06:44:40 -0700 |
commit | 08014c20784f3db5df3a89b73cce46037b77eb59 (patch) | |
tree | 47749210d31e19e6e2f64036fa8fae2ad693476f /Source/WebCore/rendering/svg | |
parent | 860220379e56aeb66424861ad602b07ee22b4055 (diff) | |
parent | 4c3661f7918f8b3f139f824efb7855bedccb4c94 (diff) | |
download | external_webkit-08014c20784f3db5df3a89b73cce46037b77eb59.zip external_webkit-08014c20784f3db5df3a89b73cce46037b77eb59.tar.gz external_webkit-08014c20784f3db5df3a89b73cce46037b77eb59.tar.bz2 |
Merge changes Ide388898,Ic49f367c,I1158a808,Iacb6ca5d,I2100dd3a,I5c1abe54,Ib0ef9902,I31dbc523,I570314b3
* changes:
Merge WebKit at r75315: Update WebKit version
Merge WebKit at r75315: Add FrameLoaderClient PageCache stubs
Merge WebKit at r75315: Stub out AXObjectCache::remove()
Merge WebKit at r75315: Fix ImageBuffer
Merge WebKit at r75315: Fix PluginData::initPlugins()
Merge WebKit at r75315: Fix conflicts
Merge WebKit at r75315: Fix Makefiles
Merge WebKit at r75315: Move Android-specific WebCore files to Source
Merge WebKit at r75315: Initial merge by git.
Diffstat (limited to 'Source/WebCore/rendering/svg')
37 files changed, 5727 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.cpp b/Source/WebCore/rendering/svg/RenderSVGInline.cpp new file mode 100644 index 0000000..4d0c533 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInline.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006 Apple Inc. All rights reserved. + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "RenderSVGInline.h" + +#include "RenderSVGResource.h" +#include "RenderSVGText.h" +#include "SVGInlineFlowBox.h" + +namespace WebCore { + +RenderSVGInline::RenderSVGInline(Node* n) + : RenderInline(n) +{ +} + +InlineFlowBox* RenderSVGInline::createInlineFlowBox() +{ + InlineFlowBox* box = new (renderArena()) SVGInlineFlowBox(this); + box->setHasVirtualLogicalHeight(); + return box; +} + +FloatRect RenderSVGInline::objectBoundingBox() const +{ + if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this)) + return object->objectBoundingBox(); + + return FloatRect(); +} + +FloatRect RenderSVGInline::strokeBoundingBox() const +{ + if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this)) + return object->strokeBoundingBox(); + + return FloatRect(); +} + +FloatRect RenderSVGInline::repaintRectInLocalCoordinates() const +{ + if (const RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this)) + return object->repaintRectInLocalCoordinates(); + + return FloatRect(); +} + +IntRect RenderSVGInline::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); +} + +void RenderSVGInline::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed); +} + +void RenderSVGInline::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState& transformState) const +{ + SVGRenderSupport::mapLocalToContainer(this, repaintContainer, useTransforms, fixed, transformState); +} + +void RenderSVGInline::absoluteQuads(Vector<FloatQuad>& quads) +{ + RenderObject* object = RenderSVGText::locateRenderSVGTextAncestor(this); + if (!object) + return; + + FloatRect textBoundingBox = object->strokeBoundingBox(); + for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) + quads.append(localToAbsoluteQuad(FloatRect(textBoundingBox.x() + box->x(), textBoundingBox.y() + box->y(), box->logicalWidth(), box->logicalHeight()))); +} + +void RenderSVGInline::destroy() +{ + SVGResourcesCache::clientDestroyed(this); + RenderInline::destroy(); +} + +void RenderSVGInline::styleWillChange(StyleDifference diff, const RenderStyle* newStyle) +{ + if (diff == StyleDifferenceLayout) + setNeedsBoundariesUpdate(); + RenderInline::styleWillChange(diff, newStyle); +} + +void RenderSVGInline::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderInline::styleDidChange(diff, oldStyle); + SVGResourcesCache::clientStyleChanged(this, diff, style()); +} + +void RenderSVGInline::updateFromElement() +{ + RenderInline::updateFromElement(); + SVGResourcesCache::clientUpdatedFromElement(this, style()); +} + + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.h b/Source/WebCore/rendering/svg/RenderSVGInline.h new file mode 100644 index 0000000..d7b7e66 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInline.h @@ -0,0 +1,68 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * + * 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. + * + */ + +#ifndef RenderSVGInline_h +#define RenderSVGInline_h + +#if ENABLE(SVG) +#include "RenderInline.h" + +#include "SVGRenderSupport.h" + +namespace WebCore { + +class RenderSVGInline : public RenderInline { +public: + explicit RenderSVGInline(Node*); + + virtual const char* renderName() const { return "RenderSVGInline"; } + virtual bool requiresLayer() const { return false; } + virtual bool isSVGInline() const { return true; } + + // Chapter 10.4 of the SVG Specification say that we should use the + // object bounding box of the parent text element. + // We search for the root text element and take its bounding box. + // It is also necessary to take the stroke and repaint rect of + // this element, since we need it for filters. + virtual FloatRect objectBoundingBox() const; + virtual FloatRect strokeBoundingBox() const; + virtual FloatRect repaintRectInLocalCoordinates() const; + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + virtual void absoluteQuads(Vector<FloatQuad>&); + +private: + virtual InlineFlowBox* createInlineFlowBox(); + + virtual void destroy(); + virtual void styleWillChange(StyleDifference, const RenderStyle* newStyle); + virtual void styleDidChange(StyleDifference, const RenderStyle* oldStyle); + virtual void updateFromElement(); +}; + +} + +#endif // ENABLE(SVG) +#endif // !RenderSVGTSpan_H diff --git a/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp b/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp new file mode 100644 index 0000000..b791f3e --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInlineText.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006 Apple Computer Inc. + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2008 Rob Buis <buis@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "RenderSVGInlineText.h" + +#include "FloatConversion.h" +#include "FloatQuad.h" +#include "RenderBlock.h" +#include "RenderSVGRoot.h" +#include "RenderSVGText.h" +#include "SVGInlineTextBox.h" +#include "SVGRootInlineBox.h" +#include "VisiblePosition.h" + +namespace WebCore { + +static PassRefPtr<StringImpl> applySVGWhitespaceRules(PassRefPtr<StringImpl> string, bool preserveWhiteSpace) +{ + if (preserveWhiteSpace) { + // Spec: When xml:space="preserve", the SVG user agent will do the following using a + // copy of the original character data content. It will convert all newline and tab + // characters into space characters. Then, it will draw all space characters, including + // leading, trailing and multiple contiguous space characters. + RefPtr<StringImpl> newString = string->replace('\t', ' '); + newString = newString->replace('\n', ' '); + newString = newString->replace('\r', ' '); + return newString.release(); + } + + // Spec: When xml:space="default", the SVG user agent will do the following using a + // copy of the original character data content. First, it will remove all newline + // characters. Then it will convert all tab characters into space characters. + // Then, it will strip off all leading and trailing space characters. + // Then, all contiguous space characters will be consolidated. + RefPtr<StringImpl> newString = string->replace('\n', StringImpl::empty()); + newString = newString->replace('\r', StringImpl::empty()); + newString = newString->replace('\t', ' '); + return newString.release(); +} + +RenderSVGInlineText::RenderSVGInlineText(Node* n, PassRefPtr<StringImpl> string) + : RenderText(n, applySVGWhitespaceRules(string, false)) +{ +} + +void RenderSVGInlineText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) +{ + RenderText::styleDidChange(diff, oldStyle); + + if (diff == StyleDifferenceLayout) { + // The text metrics may be influenced by style changes. + if (RenderSVGText* textRenderer = RenderSVGText::locateRenderSVGTextAncestor(this)) + textRenderer->setNeedsPositioningValuesUpdate(); + } + + const RenderStyle* newStyle = style(); + if (!newStyle || newStyle->whiteSpace() != PRE) + return; + + if (!oldStyle || oldStyle->whiteSpace() != PRE) + setText(applySVGWhitespaceRules(originalText(), true), true); +} + +InlineTextBox* RenderSVGInlineText::createTextBox() +{ + InlineTextBox* box = new (renderArena()) SVGInlineTextBox(this); + box->setHasVirtualLogicalHeight(); + return box; +} + +IntRect RenderSVGInlineText::localCaretRect(InlineBox* box, int caretOffset, int*) +{ + if (!box->isInlineTextBox()) + return IntRect(); + + InlineTextBox* textBox = static_cast<InlineTextBox*>(box); + if (static_cast<unsigned>(caretOffset) < textBox->start() || static_cast<unsigned>(caretOffset) > textBox->start() + textBox->len()) + return IntRect(); + + // Use the edge of the selection rect to determine the caret rect. + if (static_cast<unsigned>(caretOffset) < textBox->start() + textBox->len()) { + IntRect rect = textBox->selectionRect(0, 0, caretOffset, caretOffset + 1); + int x = box->isLeftToRightDirection() ? rect.x() : rect.right(); + return IntRect(x, rect.y(), caretWidth, rect.height()); + } + + IntRect rect = textBox->selectionRect(0, 0, caretOffset - 1, caretOffset); + int x = box->isLeftToRightDirection() ? rect.right() : rect.x(); + return IntRect(x, rect.y(), caretWidth, rect.height()); +} + +IntRect RenderSVGInlineText::linesBoundingBox() const +{ + IntRect boundingBox; + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) + boundingBox.unite(box->calculateBoundaries()); + return boundingBox; +} + +bool RenderSVGInlineText::characterStartsNewTextChunk(int position) const +{ + ASSERT(m_attributes.xValues().size() == textLength()); + ASSERT(m_attributes.yValues().size() == textLength()); + ASSERT(position >= 0); + ASSERT(position < static_cast<int>(textLength())); + + // Each <textPath> element starts a new text chunk, regardless of any x/y values. + if (!position && parent()->isSVGTextPath() && !previousSibling()) + return true; + + int currentPosition = 0; + unsigned size = m_attributes.textMetricsValues().size(); + for (unsigned i = 0; i < size; ++i) { + const SVGTextMetrics& metrics = m_attributes.textMetricsValues().at(i); + + // We found the desired character. + if (currentPosition == position) { + return m_attributes.xValues().at(position) != SVGTextLayoutAttributes::emptyValue() + || m_attributes.yValues().at(position) != SVGTextLayoutAttributes::emptyValue(); + } + + currentPosition += metrics.length(); + if (currentPosition > position) + break; + } + + // The desired position is available in the x/y list, but not in the character data values list. + // That means the previous character data described a single glyph, consisting of multiple unicode characters. + // The consequence is that the desired character does not define a new absolute x/y position, even if present in the x/y test. + // This code is tested by svg/W3C-SVG-1.1/text-text-06-t.svg (and described in detail, why this influences chunk detection). + return false; +} + +VisiblePosition RenderSVGInlineText::positionForPoint(const IntPoint& point) +{ + if (!firstTextBox() || !textLength()) + return createVisiblePosition(0, DOWNSTREAM); + + RenderStyle* style = this->style(); + ASSERT(style); + int baseline = style->font().ascent(); + + RenderBlock* containingBlock = this->containingBlock(); + ASSERT(containingBlock); + + // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates. + FloatPoint absolutePoint(point); + absolutePoint.move(containingBlock->x(), containingBlock->y()); + + float closestDistance = std::numeric_limits<float>::max(); + float closestDistancePosition = 0; + const SVGTextFragment* closestDistanceFragment = 0; + SVGInlineTextBox* closestDistanceBox = 0; + + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { + ASSERT(box->isSVGInlineTextBox()); + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + + unsigned textFragmentsSize = fragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = fragments.at(i); + FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + float distance = powf(fragmentRect.x() - absolutePoint.x(), 2) + + powf(fragmentRect.y() + fragmentRect.height() / 2 - absolutePoint.y(), 2); + + if (distance < closestDistance) { + closestDistance = distance; + closestDistanceBox = textBox; + closestDistanceFragment = &fragment; + closestDistancePosition = fragmentRect.x(); + } + } + } + + if (!closestDistanceFragment) + return createVisiblePosition(0, DOWNSTREAM); + + int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, absolutePoint.x() - closestDistancePosition, true); + return createVisiblePosition(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGInlineText.h b/Source/WebCore/rendering/svg/RenderSVGInlineText.h new file mode 100644 index 0000000..926ec43 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGInlineText.h @@ -0,0 +1,84 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * (C) 2008 Rob Buis <buis@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. + * + */ + +#ifndef RenderSVGInlineText_h +#define RenderSVGInlineText_h + +#if ENABLE(SVG) +#include "RenderText.h" +#include "SVGTextLayoutAttributes.h" + +namespace WebCore { + +class SVGInlineTextBox; + +class RenderSVGInlineText : public RenderText { +public: + RenderSVGInlineText(Node*, PassRefPtr<StringImpl>); + + bool characterStartsNewTextChunk(int position) const; + + SVGTextLayoutAttributes& layoutAttributes() { return m_attributes; } + const SVGTextLayoutAttributes& layoutAttributes() const { return m_attributes; } + void storeLayoutAttributes(const SVGTextLayoutAttributes& attributes) { m_attributes = attributes; } + +private: + virtual const char* renderName() const { return "RenderSVGInlineText"; } + + virtual void styleDidChange(StyleDifference, const RenderStyle*); + + // FIXME: We need objectBoundingBox for DRT results and filters at the moment. + // This should be fixed to give back the objectBoundingBox of the text root. + virtual FloatRect objectBoundingBox() const { return FloatRect(); } + + virtual bool requiresLayer() const { return false; } + virtual bool isSVGInlineText() const { return true; } + + virtual VisiblePosition positionForPoint(const IntPoint&); + virtual IntRect localCaretRect(InlineBox*, int caretOffset, int* extraWidthToEndOfLine = 0); + virtual IntRect linesBoundingBox() const; + virtual InlineTextBox* createTextBox(); + + SVGTextLayoutAttributes m_attributes; +}; + +inline RenderSVGInlineText* toRenderSVGInlineText(RenderObject* object) +{ + ASSERT(!object || object->isSVGInlineText()); + return static_cast<RenderSVGInlineText*>(object); +} + +inline const RenderSVGInlineText* toRenderSVGInlineText(const RenderObject* object) +{ + ASSERT(!object || object->isSVGInlineText()); + return static_cast<const RenderSVGInlineText*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGInlineText(const RenderSVGInlineText*); + +} + +#endif // ENABLE(SVG) +#endif // RenderSVGInlineText_h diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.cpp b/Source/WebCore/rendering/svg/RenderSVGPath.cpp new file mode 100644 index 0000000..0c8ac0c --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGPath.cpp @@ -0,0 +1,338 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005, 2008 Rob Buis <buis@kde.org> + 2005, 2007 Eric Seidel <eric@webkit.org> + 2009 Google, Inc. + 2009 Dirk Schulze <krit@webkit.org> + Copyright (C) Research In Motion Limited 2010. All rights reserved. + 2009 Jeff Schiller <codedread@gmail.com> + + 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 + aint 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 "RenderSVGPath.h" + +#include "FloatPoint.h" +#include "FloatQuad.h" +#include "GraphicsContext.h" +#include "HitTestRequest.h" +#include "PointerEventsHitRules.h" +#include "RenderSVGContainer.h" +#include "RenderSVGResourceMarker.h" +#include "RenderSVGResourceSolidColor.h" +#include "SVGRenderSupport.h" +#include "SVGResources.h" +#include "SVGStyledTransformableElement.h" +#include "SVGTransformList.h" +#include "SVGURIReference.h" +#include "StrokeStyleApplier.h" +#include <wtf/MathExtras.h> + +namespace WebCore { + +class BoundingRectStrokeStyleApplier : public StrokeStyleApplier { +public: + BoundingRectStrokeStyleApplier(const RenderObject* object, RenderStyle* style) + : m_object(object) + , m_style(style) + { + ASSERT(style); + ASSERT(object); + } + + void strokeStyle(GraphicsContext* gc) + { + SVGRenderSupport::applyStrokeStyleToContext(gc, m_style, m_object); + } + +private: + const RenderObject* m_object; + RenderStyle* m_style; +}; + +RenderSVGPath::RenderSVGPath(SVGStyledTransformableElement* node) + : RenderSVGModelObject(node) + , m_needsBoundariesUpdate(false) // default is false, the cached rects are empty from the beginning + , m_needsPathUpdate(true) // default is true, so we grab a Path object once from SVGStyledTransformableElement + , m_needsTransformUpdate(true) // default is true, so we grab a AffineTransform object once from SVGStyledTransformableElement +{ +} + +RenderSVGPath::~RenderSVGPath() +{ +} + +bool RenderSVGPath::fillContains(const FloatPoint& point, bool requiresFill, WindRule fillRule) +{ + if (!m_fillBoundingBox.contains(point)) + return false; + + Color fallbackColor; + if (requiresFill && !RenderSVGResource::fillPaintingResource(this, style(), fallbackColor)) + return false; + + return m_path.contains(point, fillRule); +} + +bool RenderSVGPath::strokeContains(const FloatPoint& point, bool requiresStroke) +{ + if (!m_strokeAndMarkerBoundingBox.contains(point)) + return false; + + Color fallbackColor; + if (requiresStroke && !RenderSVGResource::strokePaintingResource(this, style(), fallbackColor)) + return false; + + BoundingRectStrokeStyleApplier strokeStyle(this, style()); + return m_path.strokeContains(&strokeStyle, point); +} + +void RenderSVGPath::layout() +{ + LayoutRepainter repainter(*this, checkForRepaintDuringLayout() && selfNeedsLayout()); + SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); + + bool updateCachedBoundariesInParents = false; + + bool needsPathUpdate = m_needsPathUpdate; + if (needsPathUpdate) { + m_path.clear(); + element->toPathData(m_path); + m_needsPathUpdate = false; + updateCachedBoundariesInParents = true; + } + + if (m_needsTransformUpdate) { + m_localTransform = element->animatedLocalTransform(); + m_needsTransformUpdate = false; + updateCachedBoundariesInParents = true; + } + + if (m_needsBoundariesUpdate) + updateCachedBoundariesInParents = true; + + // Invalidate all resources of this client if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + SVGResourcesCache::clientLayoutChanged(this); + + // At this point LayoutRepainter already grabbed the old bounds, + // recalculate them now so repaintAfterLayout() uses the new bounds. + if (needsPathUpdate || m_needsBoundariesUpdate) { + updateCachedBoundaries(); + m_needsBoundariesUpdate = false; + } + + // If our bounds changed, notify the parents. + if (updateCachedBoundariesInParents) + RenderSVGModelObject::setNeedsBoundariesUpdate(); + + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +void RenderSVGPath::fillAndStrokePath(GraphicsContext* context) +{ + RenderStyle* style = this->style(); + + Color fallbackColor; + if (RenderSVGResource* fillPaintingResource = RenderSVGResource::fillPaintingResource(this, style, fallbackColor)) { + if (fillPaintingResource->applyResource(this, style, context, ApplyToFillMode)) + fillPaintingResource->postApplyResource(this, context, ApplyToFillMode, &m_path); + else if (fallbackColor.isValid()) { + RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); + fallbackResource->setColor(fallbackColor); + if (fallbackResource->applyResource(this, style, context, ApplyToFillMode)) + fallbackResource->postApplyResource(this, context, ApplyToFillMode, &m_path); + } + } + + fallbackColor = Color(); + RenderSVGResource* strokePaintingResource = RenderSVGResource::strokePaintingResource(this, style, fallbackColor); + if (!strokePaintingResource) + return; + + Path path; + + bool nonScalingStroke = style->svgStyle()->vectorEffect() == VE_NON_SCALING_STROKE; + bool restoreContext = false; + if (nonScalingStroke) { + SVGStyledTransformableElement* element = static_cast<SVGStyledTransformableElement*>(node()); + AffineTransform nonScalingStrokeTransform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate); + if (!nonScalingStrokeTransform.isInvertible()) + return; + + path = m_path; + path.transform(nonScalingStrokeTransform); + + context->save(); + context->concatCTM(nonScalingStrokeTransform.inverse()); + restoreContext = true; + } + + if (strokePaintingResource->applyResource(this, style, context, ApplyToStrokeMode)) + strokePaintingResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path); + else if (fallbackColor.isValid()) { + RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); + fallbackResource->setColor(fallbackColor); + if (fallbackResource->applyResource(this, style, context, ApplyToStrokeMode)) + fallbackResource->postApplyResource(this, context, ApplyToStrokeMode, nonScalingStroke ? &path : &m_path); + } + + if (restoreContext) + context->restore(); +} + +void RenderSVGPath::paint(PaintInfo& paintInfo, int, int) +{ + if (paintInfo.context->paintingDisabled() || style()->visibility() == HIDDEN || m_path.isEmpty()) + return; + + FloatRect boundingBox = repaintRectInLocalCoordinates(); + if (!SVGRenderSupport::paintInfoIntersectsRepaintRect(boundingBox, m_localTransform, paintInfo)) + return; + + PaintInfo childPaintInfo(paintInfo); + bool drawsOutline = style()->outlineWidth() && (childPaintInfo.phase == PaintPhaseOutline || childPaintInfo.phase == PaintPhaseSelfOutline); + if (drawsOutline || childPaintInfo.phase == PaintPhaseForeground) { + childPaintInfo.context->save(); + childPaintInfo.applyTransform(m_localTransform); + + if (childPaintInfo.phase == PaintPhaseForeground) { + PaintInfo savedInfo(childPaintInfo); + + if (SVGRenderSupport::prepareToRenderSVGContent(this, childPaintInfo)) { + const SVGRenderStyle* svgStyle = style()->svgStyle(); + if (svgStyle->shapeRendering() == SR_CRISPEDGES) + childPaintInfo.context->setShouldAntialias(false); + + fillAndStrokePath(childPaintInfo.context); + + if (svgStyle->hasMarkers()) + m_markerLayoutInfo.drawMarkers(childPaintInfo); + } + + SVGRenderSupport::finishRenderSVGContent(this, childPaintInfo, savedInfo.context); + } + + if (drawsOutline) + paintOutline(childPaintInfo.context, static_cast<int>(boundingBox.x()), static_cast<int>(boundingBox.y()), + static_cast<int>(boundingBox.width()), static_cast<int>(boundingBox.height())); + + childPaintInfo.context->restore(); + } +} + +// This method is called from inside paintOutline() since we call paintOutline() +// while transformed to our coord system, return local coords +void RenderSVGPath::addFocusRingRects(Vector<IntRect>& rects, int, int) +{ + IntRect rect = enclosingIntRect(repaintRectInLocalCoordinates()); + if (!rect.isEmpty()) + rects.append(rect); +} + +bool RenderSVGPath::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) +{ + // We only draw in the forground phase, so we only hit-test then. + if (hitTestAction != HitTestForeground) + return false; + + FloatPoint localPoint = m_localTransform.inverse().mapPoint(pointInParent); + + if (!SVGRenderSupport::pointInClippingArea(this, localPoint)) + return false; + + PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, request, style()->pointerEvents()); + bool isVisible = (style()->visibility() == VISIBLE); + if (isVisible || !hitRules.requireVisible) { + const SVGRenderStyle* svgStyle = style()->svgStyle(); + WindRule fillRule = svgStyle->fillRule(); + if (request.svgClipContent()) + fillRule = svgStyle->clipRule(); + if ((hitRules.canHitStroke && (svgStyle->hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke)) + || (hitRules.canHitFill && (svgStyle->hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill, fillRule))) { + updateHitTestResult(result, roundedIntPoint(localPoint)); + return true; + } + } + return false; +} + +FloatRect RenderSVGPath::calculateMarkerBoundsIfNeeded() +{ + SVGElement* svgElement = static_cast<SVGElement*>(node()); + ASSERT(svgElement && svgElement->document()); + if (!svgElement->isStyled()) + return FloatRect(); + + SVGStyledElement* styledElement = static_cast<SVGStyledElement*>(svgElement); + if (!styledElement->supportsMarkers()) + return FloatRect(); + + const SVGRenderStyle* svgStyle = style()->svgStyle(); + ASSERT(svgStyle->hasMarkers()); + + SVGResources* resources = SVGResourcesCache::cachedResourcesForRenderObject(this); + if (!resources) + return FloatRect(); + + RenderSVGResourceMarker* markerStart = resources->markerStart(); + RenderSVGResourceMarker* markerMid = resources->markerMid(); + RenderSVGResourceMarker* markerEnd = resources->markerEnd(); + if (!markerStart && !markerMid && !markerEnd) + return FloatRect(); + + return m_markerLayoutInfo.calculateBoundaries(markerStart, markerMid, markerEnd, svgStyle->strokeWidth().value(svgElement), m_path); +} + +void RenderSVGPath::updateCachedBoundaries() +{ + if (m_path.isEmpty()) { + m_fillBoundingBox = FloatRect(); + m_strokeAndMarkerBoundingBox = FloatRect(); + m_repaintBoundingBox = FloatRect(); + return; + } + + // Cache _unclipped_ fill bounding box, used for calculations in resources + m_fillBoundingBox = m_path.boundingRect(); + + // Cache _unclipped_ stroke bounding box, used for calculations in resources (includes marker boundaries) + m_strokeAndMarkerBoundingBox = m_fillBoundingBox; + + const SVGRenderStyle* svgStyle = style()->svgStyle(); + if (svgStyle->hasStroke()) { + BoundingRectStrokeStyleApplier strokeStyle(this, style()); + m_strokeAndMarkerBoundingBox.unite(m_path.strokeBoundingRect(&strokeStyle)); + } + + if (svgStyle->hasMarkers()) { + FloatRect markerBounds = calculateMarkerBoundsIfNeeded(); + if (!markerBounds.isEmpty()) + m_strokeAndMarkerBoundingBox.unite(markerBounds); + } + + // Cache smallest possible repaint rectangle + m_repaintBoundingBox = m_strokeAndMarkerBoundingBox; + SVGRenderSupport::intersectRepaintRectWithResources(this, m_repaintBoundingBox); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGPath.h b/Source/WebCore/rendering/svg/RenderSVGPath.h new file mode 100644 index 0000000..41b0e51 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGPath.h @@ -0,0 +1,105 @@ +/* + Copyright (C) 2004, 2005, 2007 Nikolas Zimmermann <zimmermann@kde.org> + 2004, 2005 Rob Buis <buis@kde.org> + 2005 Eric Seidel <eric@webkit.org> + 2006 Apple Computer, Inc + 2009 Google, Inc. + + 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 + aint 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. +*/ + +#ifndef RenderSVGPath_h +#define RenderSVGPath_h + +#if ENABLE(SVG) +#include "AffineTransform.h" +#include "FloatRect.h" +#include "RenderSVGModelObject.h" +#include "SVGMarkerLayoutInfo.h" + +namespace WebCore { + +class FloatPoint; +class RenderSVGContainer; +class SVGStyledTransformableElement; + +class RenderSVGPath : public RenderSVGModelObject { +public: + explicit RenderSVGPath(SVGStyledTransformableElement*); + virtual ~RenderSVGPath(); + + const Path& path() const { return m_path; } + void setNeedsPathUpdate() { m_needsPathUpdate = true; } + virtual void setNeedsBoundariesUpdate() { m_needsBoundariesUpdate = true; } + virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; } + +private: + // Hit-detection seperated for the fill and the stroke + bool fillContains(const FloatPoint&, bool requiresFill = true, WindRule fillRule = RULE_NONZERO); + bool strokeContains(const FloatPoint&, bool requiresStroke = true); + + virtual FloatRect objectBoundingBox() const { return m_fillBoundingBox; } + virtual FloatRect strokeBoundingBox() const { return m_strokeAndMarkerBoundingBox; } + virtual FloatRect repaintRectInLocalCoordinates() const { return m_repaintBoundingBox; } + virtual const AffineTransform& localToParentTransform() const { return m_localTransform; } + + virtual bool isSVGPath() const { return true; } + virtual const char* renderName() const { return "RenderSVGPath"; } + + virtual void layout(); + virtual void paint(PaintInfo&, int parentX, int parentY); + virtual void addFocusRingRects(Vector<IntRect>&, int tx, int ty); + + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + + FloatRect calculateMarkerBoundsIfNeeded(); + void updateCachedBoundaries(); + +private: + virtual AffineTransform localTransform() const { return m_localTransform; } + void fillAndStrokePath(GraphicsContext*); + + bool m_needsBoundariesUpdate : 1; + bool m_needsPathUpdate : 1; + bool m_needsTransformUpdate : 1; + + mutable Path m_path; + FloatRect m_fillBoundingBox; + FloatRect m_strokeAndMarkerBoundingBox; + FloatRect m_repaintBoundingBox; + SVGMarkerLayoutInfo m_markerLayoutInfo; + AffineTransform m_localTransform; +}; + +inline RenderSVGPath* toRenderSVGPath(RenderObject* object) +{ + ASSERT(!object || object->isSVGPath()); + return static_cast<RenderSVGPath*>(object); +} + +inline const RenderSVGPath* toRenderSVGPath(const RenderObject* object) +{ + ASSERT(!object || object->isSVGPath()); + return static_cast<const RenderSVGPath*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGPath(const RenderSVGPath*); + +} + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp b/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp new file mode 100644 index 0000000..90ff36c --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTSpan.cpp @@ -0,0 +1,38 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * + * 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 "RenderSVGTSpan.h" + +namespace WebCore { + +RenderSVGTSpan::RenderSVGTSpan(Node* n) + : RenderSVGInline(n) +{ +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGTSpan.h b/Source/WebCore/rendering/svg/RenderSVGTSpan.h new file mode 100644 index 0000000..97e0eb0 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTSpan.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * (C) 2009 Google Inc. + * + * 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. + * + */ + +#ifndef RenderSVGTSpan_h +#define RenderSVGTSpan_h + +#if ENABLE(SVG) +#include "RenderSVGInline.h" + +namespace WebCore { +class RenderSVGTSpan : public RenderSVGInline { +public: + explicit RenderSVGTSpan(Node*); + virtual const char* renderName() const { return "RenderSVGTSpan"; } +}; +} + +#endif // ENABLE(SVG) +#endif // !RenderSVGTSpan_h diff --git a/Source/WebCore/rendering/svg/RenderSVGText.cpp b/Source/WebCore/rendering/svg/RenderSVGText.cpp new file mode 100644 index 0000000..01a92b0 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGText.cpp @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2006 Alexander Kellett <lypanov@kde.org> + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2008 Rob Buis <buis@kde.org> + * Copyright (C) 2009 Dirk Schulze <krit@webkit.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "RenderSVGText.h" + +#include "FloatConversion.h" +#include "FloatQuad.h" +#include "GraphicsContext.h" +#include "HitTestRequest.h" +#include "PointerEventsHitRules.h" +#include "RenderLayer.h" +#include "RenderSVGResource.h" +#include "RenderSVGRoot.h" +#include "SVGLengthList.h" +#include "SVGRenderSupport.h" +#include "SVGRootInlineBox.h" +#include "SVGTextElement.h" +#include "SVGTextLayoutAttributesBuilder.h" +#include "SVGTransformList.h" +#include "SVGURIReference.h" +#include "SimpleFontData.h" +#include "TransformState.h" +#include "VisiblePosition.h" + +namespace WebCore { + +RenderSVGText::RenderSVGText(SVGTextElement* node) + : RenderSVGBlock(node) + , m_needsPositioningValuesUpdate(true) + , m_needsTransformUpdate(true) +{ +} + +RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(RenderObject* start) +{ + ASSERT(start); + while (start && !start->isSVGText()) + start = start->parent(); + if (!start || !start->isSVGText()) + return 0; + return toRenderSVGText(start); +} + +const RenderSVGText* RenderSVGText::locateRenderSVGTextAncestor(const RenderObject* start) +{ + ASSERT(start); + while (start && !start->isSVGText()) + start = start->parent(); + if (!start || !start->isSVGText()) + return 0; + return toRenderSVGText(start); +} + +IntRect RenderSVGText::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) +{ + return SVGRenderSupport::clippedOverflowRectForRepaint(this, repaintContainer); +} + +void RenderSVGText::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& repaintRect, bool fixed) +{ + SVGRenderSupport::computeRectForRepaint(this, repaintContainer, repaintRect, fixed); +} + +void RenderSVGText::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const +{ + SVGRenderSupport::mapLocalToContainer(this, repaintContainer, fixed, useTransforms, transformState); +} + +void RenderSVGText::layout() +{ + ASSERT(needsLayout()); + LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); + + bool updateCachedBoundariesInParents = false; + if (m_needsTransformUpdate) { + SVGTextElement* text = static_cast<SVGTextElement*>(node()); + m_localTransform = text->animatedLocalTransform(); + m_needsTransformUpdate = false; + updateCachedBoundariesInParents = true; + } + + if (m_needsPositioningValuesUpdate) { + // Perform SVG text layout phase one (see SVGTextLayoutAttributesBuilder for details). + SVGTextLayoutAttributesBuilder layoutAttributesBuilder; + layoutAttributesBuilder.buildLayoutAttributesForTextSubtree(this); + m_needsPositioningValuesUpdate = false; + updateCachedBoundariesInParents = true; + } + + // Reduced version of RenderBlock::layoutBlock(), which only takes care of SVG text. + // All if branches that could cause early exit in RenderBlocks layoutBlock() method are turned into assertions. + ASSERT(!isInline()); + ASSERT(!layoutOnlyPositionedObjects()); + ASSERT(!scrollsOverflow()); + ASSERT(!hasControlClip()); + ASSERT(!hasColumns()); + ASSERT(!positionedObjects()); + ASSERT(!m_overflow); + ASSERT(!isAnonymousBlock()); + + if (!firstChild()) + setChildrenInline(true); + + // FIXME: We need to find a way to only layout the child boxes, if needed. + FloatRect oldBoundaries = objectBoundingBox(); + ASSERT(childrenInline()); + forceLayoutInlineChildren(); + + if (!updateCachedBoundariesInParents) + updateCachedBoundariesInParents = oldBoundaries != objectBoundingBox(); + + // Invalidate all resources of this client if our layout changed. + if (m_everHadLayout && selfNeedsLayout()) + SVGResourcesCache::clientLayoutChanged(this); + + // If our bounds changed, notify the parents. + if (updateCachedBoundariesInParents) + RenderSVGBlock::setNeedsBoundariesUpdate(); + + repainter.repaintAfterLayout(); + setNeedsLayout(false); +} + +RootInlineBox* RenderSVGText::createRootInlineBox() +{ + RootInlineBox* box = new (renderArena()) SVGRootInlineBox(this); + box->setHasVirtualLogicalHeight(); + return box; +} + +bool RenderSVGText::nodeAtFloatPoint(const HitTestRequest& request, HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction) +{ + PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, style()->pointerEvents()); + bool isVisible = (style()->visibility() == VISIBLE); + if (isVisible || !hitRules.requireVisible) { + if ((hitRules.canHitStroke && (style()->svgStyle()->hasStroke() || !hitRules.requireStroke)) + || (hitRules.canHitFill && (style()->svgStyle()->hasFill() || !hitRules.requireFill))) { + FloatPoint localPoint = localToParentTransform().inverse().mapPoint(pointInParent); + + if (!SVGRenderSupport::pointInClippingArea(this, localPoint)) + return false; + + return RenderBlock::nodeAtPoint(request, result, (int)localPoint.x(), (int)localPoint.y(), 0, 0, hitTestAction); + } + } + + return false; +} + +bool RenderSVGText::nodeAtPoint(const HitTestRequest&, HitTestResult&, int, int, int, int, HitTestAction) +{ + ASSERT_NOT_REACHED(); + return false; +} + +VisiblePosition RenderSVGText::positionForPoint(const IntPoint& pointInContents) +{ + RootInlineBox* rootBox = firstRootBox(); + if (!rootBox) + return createVisiblePosition(0, DOWNSTREAM); + + ASSERT(rootBox->isSVGRootInlineBox()); + ASSERT(!rootBox->nextRootBox()); + ASSERT(childrenInline()); + + InlineBox* closestBox = static_cast<SVGRootInlineBox*>(rootBox)->closestLeafChildForPosition(pointInContents); + if (!closestBox) + return createVisiblePosition(0, DOWNSTREAM); + + return closestBox->renderer()->positionForPoint(IntPoint(pointInContents.x(), closestBox->m_y)); +} + +void RenderSVGText::absoluteQuads(Vector<FloatQuad>& quads) +{ + quads.append(localToAbsoluteQuad(strokeBoundingBox())); +} + +void RenderSVGText::paint(PaintInfo& paintInfo, int, int) +{ + if (paintInfo.context->paintingDisabled()) + return; + + if (paintInfo.phase != PaintPhaseForeground + && paintInfo.phase != PaintPhaseSelfOutline + && paintInfo.phase != PaintPhaseSelection) + return; + + PaintInfo blockInfo(paintInfo); + blockInfo.context->save(); + blockInfo.applyTransform(localToParentTransform()); + RenderBlock::paint(blockInfo, 0, 0); + blockInfo.context->restore(); +} + +FloatRect RenderSVGText::strokeBoundingBox() const +{ + FloatRect strokeBoundaries = objectBoundingBox(); + const SVGRenderStyle* svgStyle = style()->svgStyle(); + if (!svgStyle->hasStroke()) + return strokeBoundaries; + + ASSERT(node()); + ASSERT(node()->isSVGElement()); + strokeBoundaries.inflate(svgStyle->strokeWidth().value(static_cast<SVGElement*>(node()))); + return strokeBoundaries; +} + +FloatRect RenderSVGText::repaintRectInLocalCoordinates() const +{ + FloatRect repaintRect = strokeBoundingBox(); + SVGRenderSupport::intersectRepaintRectWithResources(this, repaintRect); + + if (const ShadowData* textShadow = style()->textShadow()) + textShadow->adjustRectForShadow(repaintRect); + + return repaintRect; +} + +// Fix for <rdar://problem/8048875>. We should not render :first-line CSS Style +// in a SVG text element context. +RenderBlock* RenderSVGText::firstLineBlock() const +{ + return 0; +} + +// Fix for <rdar://problem/8048875>. We should not render :first-letter CSS Style +// in a SVG text element context. +void RenderSVGText::updateFirstLetter() +{ +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGText.h b/Source/WebCore/rendering/svg/RenderSVGText.h new file mode 100644 index 0000000..deae78c --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGText.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGText_h +#define RenderSVGText_h + +#if ENABLE(SVG) + +#include "AffineTransform.h" +#include "RenderSVGBlock.h" + +namespace WebCore { + +class SVGTextElement; + +class RenderSVGText : public RenderSVGBlock { +public: + RenderSVGText(SVGTextElement* node); + + void setNeedsPositioningValuesUpdate() { m_needsPositioningValuesUpdate = true; } + virtual void setNeedsTransformUpdate() { m_needsTransformUpdate = true; } + virtual FloatRect repaintRectInLocalCoordinates() const; + + static RenderSVGText* locateRenderSVGTextAncestor(RenderObject*); + static const RenderSVGText* locateRenderSVGTextAncestor(const RenderObject*); + +private: + virtual const char* renderName() const { return "RenderSVGText"; } + virtual bool isSVGText() const { return true; } + + virtual void paint(PaintInfo&, int tx, int ty); + virtual bool nodeAtPoint(const HitTestRequest&, HitTestResult&, int x, int y, int tx, int ty, HitTestAction); + virtual bool nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint& pointInParent, HitTestAction); + virtual VisiblePosition positionForPoint(const IntPoint&); + + virtual bool requiresLayer() const { return false; } + virtual void layout(); + + virtual void absoluteQuads(Vector<FloatQuad>&); + + virtual IntRect clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer); + virtual void computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect&, bool fixed = false); + + virtual void mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool useTransforms, bool fixed, TransformState&) const; + + virtual FloatRect objectBoundingBox() const { return frameRect(); } + virtual FloatRect strokeBoundingBox() const; + + virtual const AffineTransform& localToParentTransform() const { return m_localTransform; } + virtual AffineTransform localTransform() const { return m_localTransform; } + virtual RootInlineBox* createRootInlineBox(); + + virtual RenderBlock* firstLineBlock() const; + virtual void updateFirstLetter(); + + bool m_needsPositioningValuesUpdate : 1; + bool m_needsTransformUpdate : 1; + AffineTransform m_localTransform; +}; + +inline RenderSVGText* toRenderSVGText(RenderObject* object) +{ + ASSERT(!object || object->isSVGText()); + return static_cast<RenderSVGText*>(object); +} + +inline const RenderSVGText* toRenderSVGText(const RenderObject* object) +{ + ASSERT(!object || object->isSVGText()); + return static_cast<const RenderSVGText*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGText(const RenderSVGText*); + +} + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp b/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp new file mode 100644 index 0000000..4ba2eeb --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTextPath.cpp @@ -0,0 +1,85 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (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 "RenderSVGTextPath.h" + +#include "FloatQuad.h" +#include "RenderBlock.h" +#include "SVGInlineTextBox.h" +#include "SVGNames.h" +#include "SVGPathElement.h" +#include "SVGRootInlineBox.h" +#include "SVGTextPathElement.h" +#include "SVGTransformList.h" + +namespace WebCore { + +RenderSVGTextPath::RenderSVGTextPath(Node* n) + : RenderSVGInline(n) + , m_startOffset(0.0f) + , m_exactAlignment(true) + , m_stretchMethod(false) +{ +} + +Path RenderSVGTextPath::layoutPath() const +{ + SVGTextPathElement* textPathElement = static_cast<SVGTextPathElement*>(node()); + String pathId = SVGURIReference::getTarget(textPathElement->href()); + Element* targetElement = textPathElement->document()->getElementById(pathId); + if (!targetElement || !targetElement->hasTagName(SVGNames::pathTag)) + return Path(); + + SVGPathElement* pathElement = static_cast<SVGPathElement*>(targetElement); + + Path pathData; + pathElement->toPathData(pathData); + // Spec: The transform attribute on the referenced 'path' element represents a + // supplemental transformation relative to the current user coordinate system for + // the current 'text' element, including any adjustments to the current user coordinate + // system due to a possible transform attribute on the current 'text' element. + // http://www.w3.org/TR/SVG/text.html#TextPathElement + pathData.transform(pathElement->animatedLocalTransform()); + return pathData; +} + +float RenderSVGTextPath::startOffset() const +{ + return static_cast<SVGTextPathElement*>(node())->startOffset().valueAsPercentage(); +} + +bool RenderSVGTextPath::exactAlignment() const +{ + return static_cast<SVGTextPathElement*>(node())->spacing() == SVG_TEXTPATH_SPACINGTYPE_EXACT; +} + +bool RenderSVGTextPath::stretchMethod() const +{ + return static_cast<SVGTextPathElement*>(node())->method() == SVG_TEXTPATH_METHODTYPE_STRETCH; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/RenderSVGTextPath.h b/Source/WebCore/rendering/svg/RenderSVGTextPath.h new file mode 100644 index 0000000..a71edf5 --- /dev/null +++ b/Source/WebCore/rendering/svg/RenderSVGTextPath.h @@ -0,0 +1,66 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * 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. + * + */ + +#ifndef RenderSVGTextPath_h +#define RenderSVGTextPath_h + +#if ENABLE(SVG) +#include "RenderSVGInline.h" + +namespace WebCore { + +class RenderSVGTextPath : public RenderSVGInline { +public: + RenderSVGTextPath(Node*); + + Path layoutPath() const; + float startOffset() const; + bool exactAlignment() const; + bool stretchMethod() const; + + virtual bool isSVGTextPath() const { return true; } + +private: + virtual const char* renderName() const { return "RenderSVGTextPath"; } + + float m_startOffset; + + bool m_exactAlignment : 1; + bool m_stretchMethod : 1; + + Path m_layoutPath; +}; + +inline RenderSVGTextPath* toRenderSVGTextPath(RenderObject* object) +{ + ASSERT(!object || !strcmp(object->renderName(), "RenderSVGTextPath")); + return static_cast<RenderSVGTextPath*>(object); +} + +// This will catch anyone doing an unnecessary cast. +void toRenderSVGTextPath(const RenderSVGTextPath*); + +} + +#endif // ENABLE(SVG) +#endif // RenderSVGTextPath_h diff --git a/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp b/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp new file mode 100644 index 0000000..ea806c7 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineFlowBox.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" +#include "SVGInlineFlowBox.h" + +#if ENABLE(SVG) +#include "DocumentMarkerController.h" +#include "GraphicsContext.h" +#include "RenderSVGInlineText.h" +#include "SVGInlineTextBox.h" +#include "SVGRenderSupport.h" + +using namespace std; + +namespace WebCore { + +void SVGInlineFlowBox::paintSelectionBackground(PaintInfo& paintInfo) +{ + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(!paintInfo.context->paintingDisabled()); + + PaintInfo childPaintInfo(paintInfo); + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + static_cast<SVGInlineTextBox*>(child)->paintSelectionBackground(childPaintInfo); + else if (child->isSVGInlineFlowBox()) + static_cast<SVGInlineFlowBox*>(child)->paintSelectionBackground(childPaintInfo); + } +} + +void SVGInlineFlowBox::paint(PaintInfo& paintInfo, int, int) +{ + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(!paintInfo.context->paintingDisabled()); + + RenderObject* boxRenderer = renderer(); + ASSERT(boxRenderer); + + PaintInfo childPaintInfo(paintInfo); + childPaintInfo.context->save(); + + if (SVGRenderSupport::prepareToRenderSVGContent(boxRenderer, childPaintInfo)) { + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + computeTextMatchMarkerRectForRenderer(toRenderSVGInlineText(static_cast<SVGInlineTextBox*>(child)->textRenderer())); + + child->paint(childPaintInfo, 0, 0); + } + } + + SVGRenderSupport::finishRenderSVGContent(boxRenderer, childPaintInfo, paintInfo.context); + childPaintInfo.context->restore(); +} + +IntRect SVGInlineFlowBox::calculateBoundaries() const +{ + IntRect childRect; + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) + childRect.unite(child->calculateBoundaries()); + return childRect; +} + +void SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(RenderSVGInlineText* textRenderer) +{ + ASSERT(textRenderer); + + Node* node = textRenderer->node(); + if (!node || !node->inDocument()) + return; + + RenderStyle* style = textRenderer->style(); + ASSERT(style); + + Document* document = textRenderer->document(); + Vector<DocumentMarker> markers = document->markers()->markersForNode(textRenderer->node()); + + Vector<DocumentMarker>::iterator markerEnd = markers.end(); + for (Vector<DocumentMarker>::iterator markerIt = markers.begin(); markerIt != markerEnd; ++markerIt) { + const DocumentMarker& marker = *markerIt; + + // SVG is only interessted in the TextMatch marker, for now. + if (marker.type != DocumentMarker::TextMatch) + continue; + + FloatRect markerRect; + for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { + ASSERT(box->isSVGInlineTextBox()); + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(box); + + int markerStartPosition = max<int>(marker.startOffset - textBox->start(), 0); + int markerEndPosition = min<int>(marker.endOffset - textBox->start(), textBox->len()); + + if (markerStartPosition >= markerEndPosition) + continue; + + int fragmentStartPosition = 0; + int fragmentEndPosition = 0; + + const Vector<SVGTextFragment>& fragments = textBox->textFragments(); + unsigned textFragmentsSize = fragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = fragments.at(i); + + fragmentStartPosition = markerStartPosition; + fragmentEndPosition = markerEndPosition; + if (!textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) + continue; + + FloatRect fragmentRect = textBox->selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + markerRect.unite(fragmentRect); + } + } + + document->markers()->setRenderedRectForMarker(node, marker, textRenderer->localToAbsoluteQuad(markerRect).enclosingBoundingBox()); + } +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGInlineFlowBox.h b/Source/WebCore/rendering/svg/SVGInlineFlowBox.h new file mode 100644 index 0000000..2358f2d --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineFlowBox.h @@ -0,0 +1,61 @@ +/* + * This file is part of the WebKit project. + * + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * + * 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. + * + */ + +#ifndef SVGInlineFlowBox_h +#define SVGInlineFlowBox_h + +#if ENABLE(SVG) +#include "InlineFlowBox.h" + +namespace WebCore { + +class RenderSVGInlineText; + +class SVGInlineFlowBox : public InlineFlowBox { +public: + SVGInlineFlowBox(RenderObject* obj) + : InlineFlowBox(obj) + , m_logicalHeight(0) + { + } + + virtual bool isSVGInlineFlowBox() const { return true; } + virtual int virtualLogicalHeight() const { return m_logicalHeight; } + void setLogicalHeight(int h) { m_logicalHeight = h; } + + void paintSelectionBackground(PaintInfo&); + virtual void paint(PaintInfo&, int tx, int ty); + + virtual IntRect calculateBoundaries() const; + + static void computeTextMatchMarkerRectForRenderer(RenderSVGInlineText*); + +private: + int m_logicalHeight; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) + +#endif // SVGInlineFlowBox_h diff --git a/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp b/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp new file mode 100644 index 0000000..5d0278b --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineTextBox.cpp @@ -0,0 +1,608 @@ +/** + * Copyright (C) 2007 Rob Buis <buis@kde.org> + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" +#include "SVGInlineTextBox.h" + +#if ENABLE(SVG) +#include "FloatConversion.h" +#include "GraphicsContext.h" +#include "InlineFlowBox.h" +#include "RenderBlock.h" +#include "RenderSVGInlineText.h" +#include "RenderSVGResource.h" +#include "RenderSVGResourceSolidColor.h" +#include "SVGRootInlineBox.h" + +using namespace std; + +namespace WebCore { + +SVGInlineTextBox::SVGInlineTextBox(RenderObject* object) + : InlineTextBox(object) + , m_logicalHeight(0) + , m_paintingResourceMode(ApplyToDefaultMode) + , m_startsNewTextChunk(false) + , m_paintingResource(0) +{ +} + +int SVGInlineTextBox::offsetForPosition(int, bool) const +{ + // SVG doesn't use the standard offset <-> position selection system, as it's not suitable for SVGs complex needs. + // vertical text selection, inline boxes spanning multiple lines (contrary to HTML, etc.) + ASSERT_NOT_REACHED(); + return 0; +} + +int SVGInlineTextBox::offsetForPositionInFragment(const SVGTextFragment& fragment, float position, bool includePartialGlyphs) const +{ + RenderText* textRenderer = this->textRenderer(); + ASSERT(textRenderer); + + RenderStyle* style = textRenderer->style(); + ASSERT(style); + + TextRun textRun(constructTextRun(style, fragment)); + + // Eventually handle lengthAdjust="spacingAndGlyphs". + // FIXME: Handle vertical text. + if (!fragment.transform.isIdentity()) + textRun.setHorizontalGlyphStretch(narrowPrecisionToFloat(fragment.transform.xScale())); + + return fragment.positionListOffset - start() + style->font().offsetForPosition(textRun, position, includePartialGlyphs); +} + +int SVGInlineTextBox::positionForOffset(int) const +{ + // SVG doesn't use the offset <-> position selection system. + ASSERT_NOT_REACHED(); + return 0; +} + +FloatRect SVGInlineTextBox::selectionRectForTextFragment(const SVGTextFragment& fragment, int startPosition, int endPosition, RenderStyle* style) +{ + ASSERT(startPosition < endPosition); + + const Font& font = style->font(); + FloatPoint textOrigin(fragment.x, fragment.y - font.ascent()); + return font.selectionRectForText(constructTextRun(style, fragment), textOrigin, fragment.height, startPosition, endPosition); +} + +IntRect SVGInlineTextBox::selectionRect(int, int, int startPosition, int endPosition) +{ + int boxStart = start(); + startPosition = max(startPosition - boxStart, 0); + endPosition = min(endPosition - boxStart, static_cast<int>(len())); + if (startPosition >= endPosition) + return IntRect(); + + RenderText* text = textRenderer(); + ASSERT(text); + + RenderStyle* style = text->style(); + ASSERT(style); + + FloatRect selectionRect; + int fragmentStartPosition = 0; + int fragmentEndPosition = 0; + + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = m_textFragments.at(i); + + fragmentStartPosition = startPosition; + fragmentEndPosition = endPosition; + if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) + continue; + + FloatRect fragmentRect = selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + selectionRect.unite(fragmentRect); + } + + return enclosingIntRect(selectionRect); +} + +void SVGInlineTextBox::paintSelectionBackground(PaintInfo& paintInfo) +{ + ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(truncation() == cNoTruncation); + + if (renderer()->style()->visibility() != VISIBLE) + return; + + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + ASSERT(!parentRenderer->document()->printing()); + + // Determine whether or not we're selected. + bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; + bool hasSelection = selectionState() != RenderObject::SelectionNone; + if (!hasSelection || paintSelectedTextOnly) + return; + + Color backgroundColor = renderer()->selectionBackgroundColor(); + if (!backgroundColor.isValid() || !backgroundColor.alpha()) + return; + + RenderStyle* style = parentRenderer->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + bool hasFill = svgStyle->hasFill(); + bool hasStroke = svgStyle->hasStroke(); + + RenderStyle* selectionStyle = style; + if (hasSelection) { + selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); + if (selectionStyle) { + const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); + ASSERT(svgSelectionStyle); + + if (!hasFill) + hasFill = svgSelectionStyle->hasFill(); + if (!hasStroke) + hasStroke = svgSelectionStyle->hasStroke(); + } else + selectionStyle = style; + } + + int startPosition, endPosition; + selectionStartEnd(startPosition, endPosition); + + int fragmentStartPosition = 0; + int fragmentEndPosition = 0; + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + SVGTextFragment& fragment = m_textFragments.at(i); + ASSERT(!m_paintingResource); + + fragmentStartPosition = startPosition; + fragmentEndPosition = endPosition; + if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) + continue; + + paintInfo.context->save(); + + if (!fragment.transform.isIdentity()) + paintInfo.context->concatCTM(fragment.transform); + + paintInfo.context->setFillColor(backgroundColor, style->colorSpace()); + paintInfo.context->fillRect(selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style), backgroundColor, style->colorSpace()); + + m_paintingResourceMode = ApplyToDefaultMode; + paintInfo.context->restore(); + } + + ASSERT(!m_paintingResource); +} + +void SVGInlineTextBox::paint(PaintInfo& paintInfo, int, int) +{ + ASSERT(paintInfo.shouldPaintWithinRoot(renderer())); + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(truncation() == cNoTruncation); + + if (renderer()->style()->visibility() != VISIBLE) + return; + + // Note: We're explicitely not supporting composition & custom underlines and custom highlighters - unlike InlineTextBox. + // If we ever need that for SVG, it's very easy to refactor and reuse the code. + + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + + bool paintSelectedTextOnly = paintInfo.phase == PaintPhaseSelection; + bool hasSelection = !parentRenderer->document()->printing() && selectionState() != RenderObject::SelectionNone; + if (!hasSelection && paintSelectedTextOnly) + return; + + RenderStyle* style = parentRenderer->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + bool hasFill = svgStyle->hasFill(); + bool hasStroke = svgStyle->hasStroke(); + + RenderStyle* selectionStyle = style; + if (hasSelection) { + selectionStyle = parentRenderer->getCachedPseudoStyle(SELECTION); + if (selectionStyle) { + const SVGRenderStyle* svgSelectionStyle = selectionStyle->svgStyle(); + ASSERT(svgSelectionStyle); + + if (!hasFill) + hasFill = svgSelectionStyle->hasFill(); + if (!hasStroke) + hasStroke = svgSelectionStyle->hasStroke(); + } else + selectionStyle = style; + } + + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + SVGTextFragment& fragment = m_textFragments.at(i); + ASSERT(!m_paintingResource); + + paintInfo.context->save(); + + if (!fragment.transform.isIdentity()) + paintInfo.context->concatCTM(fragment.transform); + + // Spec: All text decorations except line-through should be drawn before the text is filled and stroked; thus, the text is rendered on top of these decorations. + int decorations = style->textDecorationsInEffect(); + if (decorations & UNDERLINE) + paintDecoration(paintInfo.context, UNDERLINE, fragment); + if (decorations & OVERLINE) + paintDecoration(paintInfo.context, OVERLINE, fragment); + + // Fill text + if (hasFill) { + m_paintingResourceMode = ApplyToFillMode | ApplyToTextMode; + paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); + } + + // Stroke text + if (hasStroke) { + m_paintingResourceMode = ApplyToStrokeMode | ApplyToTextMode; + paintText(paintInfo.context, style, selectionStyle, fragment, hasSelection, paintSelectedTextOnly); + } + + // Spec: Line-through should be drawn after the text is filled and stroked; thus, the line-through is rendered on top of the text. + if (decorations & LINE_THROUGH) + paintDecoration(paintInfo.context, LINE_THROUGH, fragment); + + m_paintingResourceMode = ApplyToDefaultMode; + paintInfo.context->restore(); + } + + ASSERT(!m_paintingResource); +} + +bool SVGInlineTextBox::acquirePaintingResource(GraphicsContext*& context, RenderObject* renderer, RenderStyle* style) +{ + ASSERT(renderer); + ASSERT(style); + ASSERT(m_paintingResourceMode != ApplyToDefaultMode); + + Color fallbackColor; + if (m_paintingResourceMode & ApplyToFillMode) + m_paintingResource = RenderSVGResource::fillPaintingResource(renderer, style, fallbackColor); + else if (m_paintingResourceMode & ApplyToStrokeMode) + m_paintingResource = RenderSVGResource::strokePaintingResource(renderer, style, fallbackColor); + else { + // We're either called for stroking or filling. + ASSERT_NOT_REACHED(); + } + + if (!m_paintingResource) + return false; + + if (!m_paintingResource->applyResource(renderer, style, context, m_paintingResourceMode)) { + if (fallbackColor.isValid()) { + RenderSVGResourceSolidColor* fallbackResource = RenderSVGResource::sharedSolidPaintingResource(); + fallbackResource->setColor(fallbackColor); + + m_paintingResource = fallbackResource; + m_paintingResource->applyResource(renderer, style, context, m_paintingResourceMode); + } + } + + return true; +} + +void SVGInlineTextBox::releasePaintingResource(GraphicsContext*& context, const Path* path) +{ + ASSERT(m_paintingResource); + + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + + m_paintingResource->postApplyResource(parentRenderer, context, m_paintingResourceMode, path); + m_paintingResource = 0; +} + +bool SVGInlineTextBox::prepareGraphicsContextForTextPainting(GraphicsContext*& context, TextRun& textRun, RenderStyle* style) +{ + bool acquiredResource = acquirePaintingResource(context, parent()->renderer(), style); + +#if ENABLE(SVG_FONTS) + // SVG Fonts need access to the painting resource used to draw the current text chunk. + if (acquiredResource) + textRun.setActivePaintingResource(m_paintingResource); +#endif + + return acquiredResource; +} + +void SVGInlineTextBox::restoreGraphicsContextAfterTextPainting(GraphicsContext*& context, TextRun& textRun) +{ + releasePaintingResource(context, /* path */0); + +#if ENABLE(SVG_FONTS) + textRun.setActivePaintingResource(0); +#endif +} + +TextRun SVGInlineTextBox::constructTextRun(RenderStyle* style, const SVGTextFragment& fragment) const +{ + ASSERT(style); + ASSERT(textRenderer()); + + RenderText* text = textRenderer(); + ASSERT(text); + + TextRun run(text->characters() + fragment.positionListOffset + , fragment.length + , false /* allowTabs */ + , 0 /* xPos, only relevant with allowTabs=true */ + , 0 /* padding, only relevant for justified text, not relevant for SVG */ + , direction() == RTL + , m_dirOverride || style->visuallyOrdered() /* directionalOverride */); + +#if ENABLE(SVG_FONTS) + RenderObject* parentRenderer = parent()->renderer(); + ASSERT(parentRenderer); + + run.setReferencingRenderObject(parentRenderer); +#endif + + // Disable any word/character rounding. + run.disableRoundingHacks(); + + // We handle letter & word spacing ourselves. + run.disableSpacing(); + return run; +} + +bool SVGInlineTextBox::mapStartEndPositionsIntoFragmentCoordinates(const SVGTextFragment& fragment, int& startPosition, int& endPosition) const +{ + if (startPosition >= endPosition) + return false; + + int offset = static_cast<int>(fragment.positionListOffset) - start(); + int length = static_cast<int>(fragment.length); + + if (startPosition >= offset + length || endPosition <= offset) + return false; + + if (startPosition < offset) + startPosition = 0; + else + startPosition -= offset; + + if (endPosition > offset + length) + endPosition = length; + else { + ASSERT(endPosition >= offset); + endPosition -= offset; + } + + ASSERT(startPosition < endPosition); + return true; +} + +static inline float positionOffsetForDecoration(ETextDecoration decoration, const Font& font, float thickness) +{ + // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified. + // Compatible with Batik/Opera. + if (decoration == UNDERLINE) + return font.ascent() + thickness * 1.5f; + if (decoration == OVERLINE) + return thickness; + if (decoration == LINE_THROUGH) + return font.ascent() * 5.0f / 8.0f; + + ASSERT_NOT_REACHED(); + return 0.0f; +} + +static inline float thicknessForDecoration(ETextDecoration, const Font& font) +{ + // FIXME: For SVG Fonts we need to use the attributes defined in the <font-face> if specified. + // Compatible with Batik/Opera + return font.size() / 20.0f; +} + +static inline RenderObject* findRenderObjectDefininingTextDecoration(InlineFlowBox* parentBox) +{ + // Lookup first render object in parent hierarchy which has text-decoration set. + RenderObject* renderer = 0; + while (parentBox) { + renderer = parentBox->renderer(); + + if (renderer->style() && renderer->style()->textDecoration() != TDNONE) + break; + + parentBox = parentBox->parent(); + } + + ASSERT(renderer); + return renderer; +} + +void SVGInlineTextBox::paintDecoration(GraphicsContext* context, ETextDecoration decoration, const SVGTextFragment& fragment) +{ + if (textRenderer()->style()->textDecorationsInEffect() == TDNONE) + return; + + // Find out which render style defined the text-decoration, as its fill/stroke properties have to be used for drawing instead of ours. + RenderObject* decorationRenderer = findRenderObjectDefininingTextDecoration(parent()); + RenderStyle* decorationStyle = decorationRenderer->style(); + ASSERT(decorationStyle); + + if (decorationStyle->visibility() == HIDDEN) + return; + + const SVGRenderStyle* svgDecorationStyle = decorationStyle->svgStyle(); + ASSERT(svgDecorationStyle); + + bool hasDecorationFill = svgDecorationStyle->hasFill(); + bool hasDecorationStroke = svgDecorationStyle->hasStroke(); + + if (hasDecorationFill) { + m_paintingResourceMode = ApplyToFillMode; + paintDecorationWithStyle(context, decoration, fragment, decorationRenderer); + } + + if (hasDecorationStroke) { + m_paintingResourceMode = ApplyToStrokeMode; + paintDecorationWithStyle(context, decoration, fragment, decorationRenderer); + } +} + +void SVGInlineTextBox::paintDecorationWithStyle(GraphicsContext* context, ETextDecoration decoration, const SVGTextFragment& fragment, RenderObject* decorationRenderer) +{ + ASSERT(!m_paintingResource); + ASSERT(m_paintingResourceMode != ApplyToDefaultMode); + + RenderStyle* decorationStyle = decorationRenderer->style(); + ASSERT(decorationStyle); + + const Font& font = decorationStyle->font(); + + // The initial y value refers to overline position. + float thickness = thicknessForDecoration(decoration, font); + + if (fragment.width <= 0 && thickness <= 0) + return; + + float y = fragment.y - font.ascent() + positionOffsetForDecoration(decoration, font, thickness); + + Path path; + path.addRect(FloatRect(fragment.x, y, fragment.width, thickness)); + + context->save(); + + if (acquirePaintingResource(context, decorationRenderer, decorationStyle)) + releasePaintingResource(context, &path); + + context->restore(); +} + +void SVGInlineTextBox::paintTextWithShadows(GraphicsContext* context, RenderStyle* style, TextRun& textRun, const SVGTextFragment& fragment, int startPosition, int endPosition) +{ + const Font& font = style->font(); + const ShadowData* shadow = style->textShadow(); + + FloatPoint textOrigin(fragment.x, fragment.y); + FloatRect shadowRect(FloatPoint(textOrigin.x(), textOrigin.y() - font.ascent()), FloatSize(fragment.width, fragment.height)); + + do { + if (!prepareGraphicsContextForTextPainting(context, textRun, style)) + break; + + FloatSize extraOffset; + if (shadow) + extraOffset = applyShadowToGraphicsContext(context, shadow, shadowRect, false /* stroked */, true /* opaque */, true /* horizontal */); + + font.drawText(context, textRun, textOrigin + extraOffset, startPosition, endPosition); + restoreGraphicsContextAfterTextPainting(context, textRun); + + if (!shadow) + break; + + if (shadow->next()) + context->restore(); + else + context->clearShadow(); + + shadow = shadow->next(); + } while (shadow); +} + +void SVGInlineTextBox::paintText(GraphicsContext* context, RenderStyle* style, RenderStyle* selectionStyle, const SVGTextFragment& fragment, bool hasSelection, bool paintSelectedTextOnly) +{ + ASSERT(style); + ASSERT(selectionStyle); + + int startPosition = 0; + int endPosition = 0; + if (hasSelection) { + selectionStartEnd(startPosition, endPosition); + hasSelection = mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition); + } + + // Fast path if there is no selection, just draw the whole chunk part using the regular style + TextRun textRun(constructTextRun(style, fragment)); + if (!hasSelection || startPosition >= endPosition) { + paintTextWithShadows(context, style, textRun, fragment, 0, fragment.length); + return; + } + + // Eventually draw text using regular style until the start position of the selection + if (startPosition > 0 && !paintSelectedTextOnly) + paintTextWithShadows(context, style, textRun, fragment, 0, startPosition); + + // Draw text using selection style from the start to the end position of the selection + if (style != selectionStyle) + SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, selectionStyle); + + TextRun selectionTextRun(constructTextRun(selectionStyle, fragment)); + paintTextWithShadows(context, selectionStyle, textRun, fragment, startPosition, endPosition); + + if (style != selectionStyle) + SVGResourcesCache::clientStyleChanged(parent()->renderer(), StyleDifferenceRepaint, style); + + // Eventually draw text using regular style from the end position of the selection to the end of the current chunk part + if (endPosition < static_cast<int>(fragment.length) && !paintSelectedTextOnly) + paintTextWithShadows(context, style, textRun, fragment, endPosition, fragment.length); +} + +IntRect SVGInlineTextBox::calculateBoundaries() const +{ + FloatRect textRect; + + RenderText* textRenderer = this->textRenderer(); + ASSERT(textRenderer); + + RenderStyle* style = textRenderer->style(); + ASSERT(style); + + int baseline = baselinePosition(AlphabeticBaseline); + int heightDifference = baseline - style->font().ascent(); + + unsigned textFragmentsSize = m_textFragments.size(); + for (unsigned i = 0; i < textFragmentsSize; ++i) { + const SVGTextFragment& fragment = m_textFragments.at(i); + FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height + heightDifference); + + if (!fragment.transform.isIdentity()) + fragmentRect = fragment.transform.mapRect(fragmentRect); + + textRect.unite(fragmentRect); + } + + return enclosingIntRect(textRect); +} + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/rendering/svg/SVGInlineTextBox.h b/Source/WebCore/rendering/svg/SVGInlineTextBox.h new file mode 100644 index 0000000..acc5e9f --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGInlineTextBox.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 Rob Buis <buis@kde.org> + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef SVGInlineTextBox_h +#define SVGInlineTextBox_h + +#if ENABLE(SVG) +#include "InlineTextBox.h" +#include "SVGTextLayoutEngine.h" + +namespace WebCore { + +class RenderSVGResource; +class SVGRootInlineBox; + +class SVGInlineTextBox : public InlineTextBox { +public: + SVGInlineTextBox(RenderObject*); + + virtual bool isSVGInlineTextBox() const { return true; } + + virtual int virtualLogicalHeight() const { return m_logicalHeight; } + void setLogicalHeight(int height) { m_logicalHeight = height; } + + virtual int selectionTop() { return m_y; } + virtual int selectionHeight() { return m_logicalHeight; } + virtual int offsetForPosition(int x, bool includePartialGlyphs = true) const; + virtual int positionForOffset(int offset) const; + + void paintSelectionBackground(PaintInfo&); + virtual void paint(PaintInfo&, int tx, int ty); + virtual IntRect selectionRect(int absx, int absy, int startPosition, int endPosition); + + bool mapStartEndPositionsIntoFragmentCoordinates(const SVGTextFragment&, int& startPosition, int& endPosition) const; + + virtual IntRect calculateBoundaries() const; + + void clearTextFragments() { m_textFragments.clear(); } + Vector<SVGTextFragment>& textFragments() { return m_textFragments; } + const Vector<SVGTextFragment>& textFragments() const { return m_textFragments; } + + bool startsNewTextChunk() const { return m_startsNewTextChunk; } + void setStartsNewTextChunk(bool newTextChunk) { m_startsNewTextChunk = newTextChunk; } + + int offsetForPositionInFragment(const SVGTextFragment&, float position, bool includePartialGlyphs) const; + FloatRect selectionRectForTextFragment(const SVGTextFragment&, int fragmentStartPosition, int fragmentEndPosition, RenderStyle*); + +private: + TextRun constructTextRun(RenderStyle*, const SVGTextFragment&) const; + + bool acquirePaintingResource(GraphicsContext*&, RenderObject*, RenderStyle*); + void releasePaintingResource(GraphicsContext*&, const Path*); + + bool prepareGraphicsContextForTextPainting(GraphicsContext*&, TextRun&, RenderStyle*); + void restoreGraphicsContextAfterTextPainting(GraphicsContext*&, TextRun&); + + void paintDecoration(GraphicsContext*, ETextDecoration, const SVGTextFragment&); + void paintDecorationWithStyle(GraphicsContext*, ETextDecoration, const SVGTextFragment&, RenderObject* decorationRenderer); + void paintTextWithShadows(GraphicsContext*, RenderStyle*, TextRun&, const SVGTextFragment&, int startPosition, int endPosition); + void paintText(GraphicsContext*, RenderStyle*, RenderStyle* selectionStyle, const SVGTextFragment&, bool hasSelection, bool paintSelectedTextOnly); + +private: + int m_logicalHeight; + int m_paintingResourceMode; + bool m_startsNewTextChunk : 1; + RenderSVGResource* m_paintingResource; + Vector<SVGTextFragment> m_textFragments; +}; + +} // namespace WebCore + +#endif +#endif // SVGInlineTextBox_h diff --git a/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp b/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp new file mode 100644 index 0000000..49c76de --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGRootInlineBox.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * Copyright (C) 2006 Apple Computer Inc. + * Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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" +#include "SVGRootInlineBox.h" + +#if ENABLE(SVG) +#include "GraphicsContext.h" +#include "RenderBlock.h" +#include "RenderSVGInlineText.h" +#include "SVGInlineFlowBox.h" +#include "SVGInlineTextBox.h" +#include "SVGNames.h" +#include "SVGRenderSupport.h" +#include "SVGTextPositioningElement.h" + +namespace WebCore { + +void SVGRootInlineBox::paint(PaintInfo& paintInfo, int, int) +{ + ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); + ASSERT(!paintInfo.context->paintingDisabled()); + + RenderObject* boxRenderer = renderer(); + ASSERT(boxRenderer); + + bool isPrinting = renderer()->document()->printing(); + bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone; + + PaintInfo childPaintInfo(paintInfo); + if (hasSelection) { + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + static_cast<SVGInlineTextBox*>(child)->paintSelectionBackground(childPaintInfo); + else if (child->isSVGInlineFlowBox()) + static_cast<SVGInlineFlowBox*>(child)->paintSelectionBackground(childPaintInfo); + } + } + + childPaintInfo.context->save(); + + if (SVGRenderSupport::prepareToRenderSVGContent(boxRenderer, childPaintInfo)) { + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) + SVGInlineFlowBox::computeTextMatchMarkerRectForRenderer(toRenderSVGInlineText(static_cast<SVGInlineTextBox*>(child)->textRenderer())); + + child->paint(childPaintInfo, 0, 0); + } + } + + SVGRenderSupport::finishRenderSVGContent(boxRenderer, childPaintInfo, paintInfo.context); + childPaintInfo.context->restore(); +} + +void SVGRootInlineBox::computePerCharacterLayoutInformation() +{ + // Perform SVG text layout phase two (see SVGTextLayoutEngine for details). + SVGTextLayoutEngine characterLayout; + layoutCharactersInTextBoxes(this, characterLayout); + + // Perform SVG text layout phase three (see SVGTextChunkBuilder for details). + characterLayout.finishLayout(); + + // Perform SVG text layout phase four + // Position & resize all SVGInlineText/FlowBoxes in the inline box tree, resize the root box as well as the RenderSVGText parent block. + layoutChildBoxes(this); + layoutRootBox(); +} + +void SVGRootInlineBox::layoutCharactersInTextBoxes(InlineFlowBox* start, SVGTextLayoutEngine& characterLayout) +{ + for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) { + ASSERT(child->renderer()); + ASSERT(child->renderer()->isSVGInlineText()); + + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child); + characterLayout.layoutInlineTextBox(textBox); + } else { + ASSERT(child->isInlineFlowBox()); + + // Skip generated content. + Node* node = child->renderer()->node(); + if (!node) + continue; + + SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child); + bool isTextPath = node->hasTagName(SVGNames::textPathTag); + if (isTextPath) { + // Build text chunks for all <textPath> children, using the line layout algorithm. + // This is needeed as text-anchor is just an additional startOffset for text paths. + SVGTextLayoutEngine lineLayout; + layoutCharactersInTextBoxes(flowBox, lineLayout); + characterLayout.beginTextPathLayout(child->renderer(), lineLayout); + } + + layoutCharactersInTextBoxes(flowBox, characterLayout); + + if (isTextPath) + characterLayout.endTextPathLayout(); + } + } +} + +void SVGRootInlineBox::layoutChildBoxes(InlineFlowBox* start) +{ + for (InlineBox* child = start->firstChild(); child; child = child->nextOnLine()) { + if (child->isSVGInlineTextBox()) { + ASSERT(child->renderer()); + ASSERT(child->renderer()->isSVGInlineText()); + + SVGInlineTextBox* textBox = static_cast<SVGInlineTextBox*>(child); + IntRect boxRect = textBox->calculateBoundaries(); + textBox->setX(boxRect.x()); + textBox->setY(boxRect.y()); + textBox->setLogicalWidth(boxRect.width()); + textBox->setLogicalHeight(boxRect.height()); + } else { + ASSERT(child->isInlineFlowBox()); + + // Skip generated content. + if (!child->renderer()->node()) + continue; + + SVGInlineFlowBox* flowBox = static_cast<SVGInlineFlowBox*>(child); + layoutChildBoxes(flowBox); + + IntRect boxRect = flowBox->calculateBoundaries(); + flowBox->setX(boxRect.x()); + flowBox->setY(boxRect.y()); + flowBox->setLogicalWidth(boxRect.width()); + flowBox->setLogicalHeight(boxRect.height()); + } + } +} + +void SVGRootInlineBox::layoutRootBox() +{ + RenderBlock* parentBlock = block(); + ASSERT(parentBlock); + + IntRect childRect; + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + // Skip generated content. + if (!child->renderer()->node()) + continue; + childRect.unite(child->calculateBoundaries()); + } + + int xBlock = childRect.x(); + int yBlock = childRect.y(); + int widthBlock = childRect.width(); + int heightBlock = childRect.height(); + + // Finally, assign the root block position, now that all content is laid out. + parentBlock->setLocation(xBlock, yBlock); + parentBlock->setWidth(widthBlock); + parentBlock->setHeight(heightBlock); + + // Position all children relative to the parent block. + for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { + // Skip generated content. + if (!child->renderer()->node()) + continue; + child->adjustPosition(-xBlock, -yBlock); + } + + // Position ourselves. + setX(0); + setY(0); + setLogicalWidth(widthBlock); + setLogicalHeight(heightBlock); + setBlockLogicalHeight(heightBlock); + setLineTopBottomPositions(0, heightBlock); +} + +InlineBox* SVGRootInlineBox::closestLeafChildForPosition(const IntPoint& point) +{ + InlineBox* firstLeaf = firstLeafChild(); + InlineBox* lastLeaf = lastLeafChild(); + if (firstLeaf == lastLeaf) + return firstLeaf; + + // FIXME: Check for vertical text! + InlineBox* closestLeaf = 0; + for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) { + if (point.y() < leaf->m_y) + continue; + if (point.y() > leaf->m_y + leaf->virtualLogicalHeight()) + continue; + + closestLeaf = leaf; + if (point.x() < leaf->m_x + leaf->m_logicalWidth) + return leaf; + } + + return closestLeaf ? closestLeaf : lastLeaf; +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGRootInlineBox.h b/Source/WebCore/rendering/svg/SVGRootInlineBox.h new file mode 100644 index 0000000..418c289 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGRootInlineBox.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz> + * (C) 2006 Apple Computer Inc. + * (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + * + */ + +#ifndef SVGRootInlineBox_h +#define SVGRootInlineBox_h + +#if ENABLE(SVG) +#include "RootInlineBox.h" +#include "SVGRenderSupport.h" +#include "SVGTextLayoutEngine.h" + +namespace WebCore { + +class SVGInlineTextBox; + +class SVGRootInlineBox : public RootInlineBox { +public: + SVGRootInlineBox(RenderBlock* block) + : RootInlineBox(block) + , m_logicalHeight(0) + { + } + + virtual bool isSVGRootInlineBox() const { return true; } + + virtual int virtualLogicalHeight() const { return m_logicalHeight; } + void setLogicalHeight(int height) { m_logicalHeight = height; } + + virtual void paint(PaintInfo&, int tx, int ty); + + void computePerCharacterLayoutInformation(); + + virtual FloatRect objectBoundingBox() const { return FloatRect(); } + virtual FloatRect repaintRectInLocalCoordinates() const { return FloatRect(); } + + InlineBox* closestLeafChildForPosition(const IntPoint&); + +private: + void layoutCharactersInTextBoxes(InlineFlowBox*, SVGTextLayoutEngine&); + void layoutChildBoxes(InlineFlowBox*); + void layoutRootBox(); + +private: + int m_logicalHeight; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) + +#endif // SVGRootInlineBox_h diff --git a/Source/WebCore/rendering/svg/SVGTextChunk.cpp b/Source/WebCore/rendering/svg/SVGTextChunk.cpp new file mode 100644 index 0000000..5dea6ad --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunk.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextChunk.h" + +#include "SVGInlineTextBox.h" +#include "SVGTextFragment.h" + +namespace WebCore { + +SVGTextChunk::SVGTextChunk(bool isVerticalText, ETextAnchor textAnchor, SVGTextContentElement::SVGLengthAdjustType lengthAdjust, float desiredTextLength) + : m_isVerticalText(isVerticalText) + , m_textAnchor(textAnchor) + , m_lengthAdjust(lengthAdjust) + , m_desiredTextLength(desiredTextLength) +{ +} + +void SVGTextChunk::calculateLength(float& length, unsigned& characters) const +{ + SVGTextFragment* lastFragment = 0; + + unsigned boxCount = m_boxes.size(); + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = m_boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + + unsigned size = fragments.size(); + if (!size) + continue; + + for (unsigned i = 0; i < size; ++i) { + SVGTextFragment& fragment = fragments.at(i); + characters += fragment.length; + + if (m_isVerticalText) + length += fragment.height; + else + length += fragment.width; + + if (!lastFragment) { + lastFragment = &fragment; + continue; + } + + // Resepect gap between chunks. + if (m_isVerticalText) + length += fragment.y - (lastFragment->y + lastFragment->height); + else + length += fragment.x - (lastFragment->x + lastFragment->width); + + lastFragment = &fragment; + } + } +} + +float SVGTextChunk::calculateTextAnchorShift(float length) const +{ + switch (m_textAnchor) { + case TA_START: + return 0; + case TA_MIDDLE: + return -length / 2; + case TA_END: + return -length; + }; + + ASSERT_NOT_REACHED(); + return 0; +} + +} // namespace WebCore + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextChunk.h b/Source/WebCore/rendering/svg/SVGTextChunk.h new file mode 100644 index 0000000..ebe6d81 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunk.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextChunk_h +#define SVGTextChunk_h + +#if ENABLE(SVG) +#include "SVGRenderStyleDefs.h" +#include "SVGTextContentElement.h" + +namespace WebCore { + +class SVGInlineTextBox; + +// A SVGTextChunk describes a range of SVGTextFragments, see the SVG spec definition of a "text chunk". +class SVGTextChunk { +public: + SVGTextChunk(bool isVerticalText, ETextAnchor, SVGTextContentElement::SVGLengthAdjustType, float desiredTextLength); + + void calculateLength(float& length, unsigned& characters) const; + float calculateTextAnchorShift(float length) const; + + bool isVerticalText() const { return m_isVerticalText; } + ETextAnchor textAnchor() const { return m_textAnchor; } + SVGTextContentElement::SVGLengthAdjustType lengthAdjust() const { return m_lengthAdjust; } + float desiredTextLength() const { return m_desiredTextLength; } + + Vector<SVGInlineTextBox*>& boxes() { return m_boxes; } + const Vector<SVGInlineTextBox*>& boxes() const { return m_boxes; } + + bool hasDesiredTextLength() const { return m_lengthAdjust != SVGTextContentElement::LENGTHADJUST_UNKNOWN && m_desiredTextLength > 0; } + bool hasTextAnchor() const { return m_textAnchor != TA_START; } + +private: + // Contains all SVGInlineTextBoxes this chunk spans. + Vector<SVGInlineTextBox*> m_boxes; + + // writing-mode specific property. + bool m_isVerticalText; + + // text-anchor specific properties. + ETextAnchor m_textAnchor; + + // textLength/lengthAdjust specific properties. + SVGTextContentElement::SVGLengthAdjustType m_lengthAdjust; + float m_desiredTextLength; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp new file mode 100644 index 0000000..bbbae6c --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextChunkBuilder.h" + +#include "RenderSVGInlineText.h" +#include "SVGElement.h" +#include "SVGInlineTextBox.h" + +namespace WebCore { + +SVGTextChunkBuilder::SVGTextChunkBuilder() +{ +} + +void SVGTextChunkBuilder::transformationForTextBox(SVGInlineTextBox* textBox, AffineTransform& transform) const +{ + DEFINE_STATIC_LOCAL(const AffineTransform, s_identityTransform, ()); + if (!m_textBoxTransformations.contains(textBox)) { + transform = s_identityTransform; + return; + } + + transform = m_textBoxTransformations.get(textBox); +} + +void SVGTextChunkBuilder::buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) +{ + if (lineLayoutBoxes.isEmpty()) + return; + + bool foundStart = false; + unsigned lastChunkStartPosition = 0; + unsigned boxPosition = 0; + unsigned boxCount = lineLayoutBoxes.size(); + for (; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = lineLayoutBoxes.at(boxPosition); + if (!textBox->startsNewTextChunk()) + continue; + + if (!foundStart) { + lastChunkStartPosition = boxPosition; + foundStart = true; + } else { + ASSERT(boxPosition > lastChunkStartPosition); + addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); + lastChunkStartPosition = boxPosition; + } + } + + if (!foundStart) + return; + + if (boxPosition - lastChunkStartPosition > 0) + addTextChunk(lineLayoutBoxes, lastChunkStartPosition, boxPosition - lastChunkStartPosition); +} + +void SVGTextChunkBuilder::layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes) +{ + buildTextChunks(lineLayoutBoxes); + if (m_textChunks.isEmpty()) + return; + + unsigned chunkCount = m_textChunks.size(); + for (unsigned i = 0; i < chunkCount; ++i) + processTextChunk(m_textChunks.at(i)); + + m_textChunks.clear(); +} + +void SVGTextChunkBuilder::addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxStart, unsigned boxCount) +{ + SVGInlineTextBox* textBox = lineLayoutBoxes.at(boxStart); + ASSERT(textBox); + + RenderSVGInlineText* textRenderer = toRenderSVGInlineText(textBox->textRenderer()); + ASSERT(textRenderer); + + const RenderStyle* style = textRenderer->style(); + ASSERT(style); + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + SVGTextContentElement::SVGLengthAdjustType lengthAdjust = SVGTextContentElement::LENGTHADJUST_UNKNOWN; + float desiredTextLength = 0; + + if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textRenderer->parent())) { + lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust()); + desiredTextLength = textContentElement->textLength().value(textContentElement); + } + + SVGTextChunk chunk(svgStyle->isVerticalWritingMode(), svgStyle->textAnchor(), lengthAdjust, desiredTextLength); + + Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); + for (unsigned i = boxStart; i < boxStart + boxCount; ++i) + boxes.append(lineLayoutBoxes.at(i)); + + m_textChunks.append(chunk); +} + +void SVGTextChunkBuilder::processTextChunk(const SVGTextChunk& chunk) +{ + bool processTextLength = chunk.hasDesiredTextLength(); + bool processTextAnchor = chunk.hasTextAnchor(); + if (!processTextAnchor && !processTextLength) + return; + + const Vector<SVGInlineTextBox*>& boxes = chunk.boxes(); + unsigned boxCount = boxes.size(); + if (!boxCount) + return; + + // Calculate absolute length of whole text chunk (starting from text box 'start', spanning 'length' text boxes). + float chunkLength = 0; + unsigned chunkCharacters = 0; + chunk.calculateLength(chunkLength, chunkCharacters); + + bool isVerticalText = chunk.isVerticalText(); + if (processTextLength) { + if (chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACING) { + float textLengthShift = (chunk.desiredTextLength() - chunkLength) / chunkCharacters; + unsigned atCharacter = 0; + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + Vector<SVGTextFragment>& fragments = boxes.at(boxPosition)->textFragments(); + if (fragments.isEmpty()) + continue; + processTextLengthSpacingCorrection(isVerticalText, textLengthShift, fragments, atCharacter); + } + } else { + ASSERT(chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACINGANDGLYPHS); + float scale = chunk.desiredTextLength() / chunkLength; + AffineTransform spacingAndGlyphsTransform; + + bool foundFirstFragment = false; + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + if (fragments.isEmpty()) + continue; + + if (!foundFirstFragment) { + foundFirstFragment = true; + buildSpacingAndGlyphsTransform(isVerticalText, scale, fragments.first(), spacingAndGlyphsTransform); + } + + m_textBoxTransformations.set(textBox, spacingAndGlyphsTransform); + } + } + } + + if (!processTextAnchor) + return; + + // If we previously applied a lengthAdjust="spacing" correction, we have to recalculate the chunk length, to be able to apply the text-anchor shift. + if (processTextLength && chunk.lengthAdjust() == SVGTextContentElement::LENGTHADJUST_SPACING) { + chunkLength = 0; + chunkCharacters = 0; + chunk.calculateLength(chunkLength, chunkCharacters); + } + + float textAnchorShift = chunk.calculateTextAnchorShift(chunkLength); + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + Vector<SVGTextFragment>& fragments = boxes.at(boxPosition)->textFragments(); + if (fragments.isEmpty()) + continue; + processTextAnchorCorrection(isVerticalText, textAnchorShift, fragments); + } +} + +void SVGTextChunkBuilder::processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>& fragments, unsigned& atCharacter) +{ + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + + if (isVerticalText) + fragment.y += textLengthShift * atCharacter; + else + fragment.x += textLengthShift * atCharacter; + + atCharacter += fragment.length; + } +} + +void SVGTextChunkBuilder::processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>& fragments) +{ + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + + if (isVerticalText) + fragment.y += textAnchorShift; + else + fragment.x += textAnchorShift; + } +} + +void SVGTextChunkBuilder::buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment& fragment, AffineTransform& spacingAndGlyphsTransform) +{ + spacingAndGlyphsTransform.translate(fragment.x, fragment.y); + + if (isVerticalText) + spacingAndGlyphsTransform.scaleNonUniform(1, scale); + else + spacingAndGlyphsTransform.scaleNonUniform(scale, 1); + + spacingAndGlyphsTransform.translate(-fragment.x, -fragment.y); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h new file mode 100644 index 0000000..36342e7 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextChunkBuilder.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextChunkBuilder_h +#define SVGTextChunkBuilder_h + +#if ENABLE(SVG) +#include "SVGTextChunk.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class SVGInlineTextBox; +struct SVGTextFragment; + +// SVGTextChunkBuilder performs the third layout phase for SVG text. +// +// Phase one built the layout information from the SVG DOM stored in the RenderSVGInlineText objects (SVGTextLayoutAttributes). +// Phase two performed the actual per-character layout, computing the final positions for each character, stored in the SVGInlineTextBox objects (SVGTextFragment). +// Phase three performs all modifications that have to be applied to each individual text chunk (text-anchor & textLength). + +class SVGTextChunkBuilder : public Noncopyable { +public: + SVGTextChunkBuilder(); + + const Vector<SVGTextChunk>& textChunks() const { return m_textChunks; } + void transformationForTextBox(SVGInlineTextBox*, AffineTransform&) const; + + void buildTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes); + void layoutTextChunks(Vector<SVGInlineTextBox*>& lineLayoutBoxes); + +private: + void addTextChunk(Vector<SVGInlineTextBox*>& lineLayoutBoxes, unsigned boxPosition, unsigned boxCount); + void processTextChunk(const SVGTextChunk&); + + void processTextLengthSpacingCorrection(bool isVerticalText, float textLengthShift, Vector<SVGTextFragment>&, unsigned& atCharacter); + void processTextAnchorCorrection(bool isVerticalText, float textAnchorShift, Vector<SVGTextFragment>&); + void buildSpacingAndGlyphsTransform(bool isVerticalText, float scale, const SVGTextFragment&, AffineTransform&); + +private: + Vector<SVGTextChunk> m_textChunks; + HashMap<SVGInlineTextBox*, AffineTransform> m_textBoxTransformations; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextFragment.h b/Source/WebCore/rendering/svg/SVGTextFragment.h new file mode 100644 index 0000000..2e520da --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextFragment.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextFragment_h +#define SVGTextFragment_h + +#if ENABLE(SVG) +#include "AffineTransform.h" + +namespace WebCore { + +// A SVGTextFragment describes a text fragment of a RenderSVGInlineText which can be rendered at once. +struct SVGTextFragment { + SVGTextFragment() + : positionListOffset(0) + , length(0) + , x(0) + , y(0) + , width(0) + , height(0) + { + } + + // The first rendered character starts at RenderSVGInlineText::characters() + positionListOffset. + unsigned positionListOffset; + unsigned length; + + float x; + float y; + float width; + float height; + + // Includes rotation/glyph-orientation-(horizontal|vertical) transforms, lengthAdjust="spacingAndGlyphs" (for textPath only), + // as well as orientation related shifts (see SVGTextLayoutEngine, which builds this transformation). + AffineTransform transform; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp new file mode 100644 index 0000000..3037b77 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextLayoutAttributes.h" + +#include <stdio.h> +#include <wtf/text/CString.h> + +namespace WebCore { + +SVGTextLayoutAttributes::SVGTextLayoutAttributes() +{ +} + +void SVGTextLayoutAttributes::reserveCapacity(unsigned length) +{ + m_xValues.reserveCapacity(length); + m_yValues.reserveCapacity(length); + m_dxValues.reserveCapacity(length); + m_dyValues.reserveCapacity(length); + m_rotateValues.reserveCapacity(length); +} + +float SVGTextLayoutAttributes::emptyValue() +{ + static float s_emptyValue = std::numeric_limits<float>::max() - 1; + return s_emptyValue; +} + +static inline void dumpLayoutVector(const Vector<float>& values) +{ + if (values.isEmpty()) { + fprintf(stderr, "empty"); + return; + } + + unsigned size = values.size(); + for (unsigned i = 0; i < size; ++i) { + float value = values.at(i); + if (value == SVGTextLayoutAttributes::emptyValue()) + fprintf(stderr, "x "); + else + fprintf(stderr, "%lf ", value); + } +} + +void SVGTextLayoutAttributes::dump() const +{ + fprintf(stderr, "x values: "); + dumpLayoutVector(m_xValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "y values: "); + dumpLayoutVector(m_yValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "dx values: "); + dumpLayoutVector(m_dxValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "dy values: "); + dumpLayoutVector(m_dyValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "rotate values: "); + dumpLayoutVector(m_rotateValues); + fprintf(stderr, "\n"); + + fprintf(stderr, "character data values:\n"); + unsigned textMetricsSize = m_textMetricsValues.size(); + for (unsigned i = 0; i < textMetricsSize; ++i) { + const SVGTextMetrics& metrics = m_textMetricsValues.at(i); + fprintf(stderr, "| {length=%i, glyphName='%s', unicodeString='%s', width=%lf, height=%lf}\n", + metrics.length(), metrics.glyph().name.utf8().data(), metrics.glyph().unicodeString.utf8().data(), metrics.width(), metrics.height()); + } + fprintf(stderr, "\n"); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h new file mode 100644 index 0000000..d08d5b7 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributes.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutAttributes_h +#define SVGTextLayoutAttributes_h + +#if ENABLE(SVG) +#include "SVGTextMetrics.h" +#include <wtf/Vector.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class SVGTextLayoutAttributes { +public: + SVGTextLayoutAttributes(); + + void reserveCapacity(unsigned length); + void dump() const; + + static float emptyValue(); + + Vector<float>& xValues() { return m_xValues; } + const Vector<float>& xValues() const { return m_xValues; } + + Vector<float>& yValues() { return m_yValues; } + const Vector<float>& yValues() const { return m_yValues; } + + Vector<float>& dxValues() { return m_dxValues; } + const Vector<float>& dxValues() const { return m_dxValues; } + + Vector<float>& dyValues() { return m_dyValues; } + const Vector<float>& dyValues() const { return m_dyValues; } + + Vector<float>& rotateValues() { return m_rotateValues; } + const Vector<float>& rotateValues() const { return m_rotateValues; } + + Vector<SVGTextMetrics>& textMetricsValues() { return m_textMetricsValues; } + const Vector<SVGTextMetrics>& textMetricsValues() const { return m_textMetricsValues; } + +private: + Vector<float> m_xValues; + Vector<float> m_yValues; + Vector<float> m_dxValues; + Vector<float> m_dyValues; + Vector<float> m_rotateValues; + Vector<SVGTextMetrics> m_textMetricsValues; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp new file mode 100644 index 0000000..3122912 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextLayoutAttributesBuilder.h" + +#include "RenderSVGInlineText.h" +#include "RenderSVGText.h" +#include "SVGTextPositioningElement.h" + +// Set to a value > 0 to dump the text layout attributes +#define DUMP_TEXT_LAYOUT_ATTRIBUTES 0 + +namespace WebCore { + +SVGTextLayoutAttributesBuilder::SVGTextLayoutAttributesBuilder() +{ +} + +void SVGTextLayoutAttributesBuilder::buildLayoutAttributesForTextSubtree(RenderSVGText* textRoot) +{ + ASSERT(textRoot); + m_scopes.clear(); + + // Build list of x/y/dx/dy/rotate values for each subtree element that may define these values (tspan/textPath etc). + unsigned atCharacter = 0; + UChar lastCharacter = '\0'; + buildLayoutScopes(textRoot, atCharacter, lastCharacter); + + if (!atCharacter) + return; + + // Build list of x/y/dx/dy/rotate values for the outermost <text> element. + buildOutermostLayoutScope(textRoot, atCharacter); + + // Propagate layout attributes to each RenderSVGInlineText object. + atCharacter = 0; + lastCharacter = '\0'; + propagateLayoutAttributes(textRoot, atCharacter, lastCharacter); +} + +static inline void extractFloatValuesFromSVGLengthList(SVGElement* lengthContext, const SVGLengthList& list, Vector<float>& floatValues, unsigned textContentLength) +{ + ASSERT(lengthContext); + + unsigned length = list.size(); + if (length > textContentLength) + length = textContentLength; + floatValues.reserveCapacity(length); + + for (unsigned i = 0; i < length; ++i) { + const SVGLength& length = list.at(i); + floatValues.append(length.value(lengthContext)); + } +} + +static inline void extractFloatValuesFromSVGNumberList(const SVGNumberList& list, Vector<float>& floatValues, unsigned textContentLength) +{ + unsigned length = list.size(); + if (length > textContentLength) + length = textContentLength; + floatValues.reserveCapacity(length); + + for (unsigned i = 0; i < length; ++i) + floatValues.append(list.at(i)); +} + +void SVGTextLayoutAttributesBuilder::buildLayoutScope(LayoutScope& scope, RenderObject* renderer, unsigned textContentStart, unsigned textContentLength) const +{ + ASSERT(renderer); + ASSERT(renderer->style()); + + scope.textContentStart = textContentStart; + scope.textContentLength = textContentLength; + + SVGTextPositioningElement* element = SVGTextPositioningElement::elementFromRenderer(renderer); + if (!element) + return; + + SVGTextLayoutAttributes& attributes = scope.attributes; + extractFloatValuesFromSVGLengthList(element, element->x(), attributes.xValues(), textContentLength); + extractFloatValuesFromSVGLengthList(element, element->y(), attributes.yValues(), textContentLength); + extractFloatValuesFromSVGLengthList(element, element->dx(), attributes.dxValues(), textContentLength); + extractFloatValuesFromSVGLengthList(element, element->dy(), attributes.dyValues(), textContentLength); + extractFloatValuesFromSVGNumberList(element->rotate(), attributes.rotateValues(), textContentLength); + + // The last rotation value spans the whole scope. + Vector<float>& rotateValues = attributes.rotateValues(); + if (rotateValues.isEmpty()) + return; + + unsigned rotateValuesSize = rotateValues.size(); + if (rotateValuesSize == textContentLength) + return; + + float lastRotation = rotateValues.last(); + + rotateValues.resize(textContentLength); + for (unsigned i = rotateValuesSize; i < textContentLength; ++i) + rotateValues.at(i) = lastRotation; +} + +static inline bool characterIsSpace(const UChar& character) +{ + return character == ' '; +} + +static inline bool characterIsSpaceOrNull(const UChar& character) +{ + return character == ' ' || character == '\0'; +} + +static inline bool shouldPreserveAllWhiteSpace(RenderStyle* style) +{ + ASSERT(style); + return style->whiteSpace() == PRE; +} + +void SVGTextLayoutAttributesBuilder::buildLayoutScopes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) +{ + for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { + if (child->isSVGInlineText()) { + RenderSVGInlineText* text = toRenderSVGInlineText(child); + + if (!shouldPreserveAllWhiteSpace(text->style())) { + const UChar* characters = text->characters(); + unsigned textLength = text->textLength(); + for (unsigned textPosition = 0; textPosition < textLength; ++textPosition) { + const UChar& currentCharacter = characters[textPosition]; + if (characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) + continue; + + lastCharacter = currentCharacter; + ++atCharacter; + } + } else + atCharacter += text->textLength(); + + continue; + } + + if (!child->isSVGInline()) + continue; + + unsigned textContentStart = atCharacter; + buildLayoutScopes(child, atCharacter, lastCharacter); + + LayoutScope scope; + buildLayoutScope(scope, child, textContentStart, atCharacter - textContentStart); + m_scopes.append(scope); + } +} + +void SVGTextLayoutAttributesBuilder::buildOutermostLayoutScope(RenderSVGText* textRoot, unsigned textLength) +{ + LayoutScope scope; + buildLayoutScope(scope, textRoot, 0, textLength); + + // Handle <text> x/y default attributes. + Vector<float>& xValues = scope.attributes.xValues(); + if (xValues.isEmpty()) + xValues.append(0); + + Vector<float>& yValues = scope.attributes.yValues(); + if (yValues.isEmpty()) + yValues.append(0); + + m_scopes.prepend(scope); +} + +void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) const +{ + for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { + if (child->isSVGInlineText()) { + RenderSVGInlineText* text = toRenderSVGInlineText(child); + const UChar* characters = text->characters(); + unsigned textLength = text->textLength(); + bool preserveWhiteSpace = shouldPreserveAllWhiteSpace(text->style()); + + SVGTextLayoutAttributes attributes; + attributes.reserveCapacity(textLength); + + unsigned valueListPosition = atCharacter; + unsigned metricsLength = 1; + for (unsigned textPosition = 0; textPosition < textLength; textPosition += metricsLength) { + const UChar& currentCharacter = characters[textPosition]; + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 1); + metricsLength = metrics.length(); + + if (!preserveWhiteSpace && characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) { + assignEmptyLayoutAttributesForCharacter(attributes); + attributes.textMetricsValues().append(SVGTextMetrics::emptyMetrics()); + continue; + } + + assignLayoutAttributesForCharacter(attributes, metrics, valueListPosition); + + if (metricsLength > 1) { + for (unsigned i = 0; i < metricsLength - 1; ++i) + assignEmptyLayoutAttributesForCharacter(attributes); + } + + lastCharacter = currentCharacter; + valueListPosition += metricsLength; + } + +#if DUMP_TEXT_LAYOUT_ATTRIBUTES > 0 + fprintf(stderr, "\nDumping layout attributes for RenderSVGInlineText, renderer=%p, node=%p (atCharacter: %i)\n", text, text->node(), atCharacter); + attributes.dump(); +#endif + + text->storeLayoutAttributes(attributes); + atCharacter = valueListPosition; + continue; + } + + if (!child->isSVGInline()) + continue; + + propagateLayoutAttributes(child, atCharacter, lastCharacter); + } +} + +float SVGTextLayoutAttributesBuilder::nextLayoutValue(LayoutValueType type, unsigned atCharacter) const +{ + for (int i = m_scopes.size() - 1; i >= 0; --i) { + const LayoutScope& scope = m_scopes.at(i); + if (scope.textContentStart > atCharacter || scope.textContentStart + scope.textContentLength < atCharacter) + continue; + + const Vector<float>* valuesPointer = 0; + switch (type) { + case XValueAttribute: + valuesPointer = &scope.attributes.xValues(); + break; + case YValueAttribute: + valuesPointer = &scope.attributes.yValues(); + break; + case DxValueAttribute: + valuesPointer = &scope.attributes.dxValues(); + break; + case DyValueAttribute: + valuesPointer = &scope.attributes.dyValues(); + break; + case RotateValueAttribute: + valuesPointer = &scope.attributes.rotateValues(); + break; + default: + ASSERT_NOT_REACHED(); + } + + ASSERT(valuesPointer); + const Vector<float>& values = *valuesPointer; + if (values.isEmpty()) + continue; + + unsigned position = atCharacter - scope.textContentStart; + if (position >= values.size()) + continue; + + return values.at(position); + } + + return SVGTextLayoutAttributes::emptyValue(); +} + +void SVGTextLayoutAttributesBuilder::assignLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes, SVGTextMetrics& metrics, unsigned valueListPosition) const +{ + attributes.xValues().append(nextLayoutValue(XValueAttribute, valueListPosition)); + attributes.yValues().append(nextLayoutValue(YValueAttribute, valueListPosition)); + attributes.dxValues().append(nextLayoutValue(DxValueAttribute, valueListPosition)); + attributes.dyValues().append(nextLayoutValue(DyValueAttribute, valueListPosition)); + attributes.rotateValues().append(nextLayoutValue(RotateValueAttribute, valueListPosition)); + attributes.textMetricsValues().append(metrics); +} + +void SVGTextLayoutAttributesBuilder::assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes& attributes) const +{ + attributes.xValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.yValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.dxValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.dyValues().append(SVGTextLayoutAttributes::emptyValue()); + attributes.rotateValues().append(SVGTextLayoutAttributes::emptyValue()); + // This doesn't add an empty value to textMetricsValues() on purpose! +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h new file mode 100644 index 0000000..f29ac64 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutAttributesBuilder.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutAttributesBuilder_h +#define SVGTextLayoutAttributesBuilder_h + +#if ENABLE(SVG) +#include "SVGTextLayoutAttributes.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderObject; +class RenderSVGText; + +// SVGTextLayoutAttributesBuilder performs the first layout phase for SVG text. +// +// It extracts the x/y/dx/dy/rotate values from the SVGTextPositioningElements in the DOM, +// measures all characters in the RenderSVGText subtree and extracts kerning/ligature information. +// These values are propagated to the corresponding RenderSVGInlineText renderers. +// The first layout phase only extracts the relevant information needed in RenderBlockLineLayout +// to create the InlineBox tree based on text chunk boundaries & BiDi information. +// The second layout phase is carried out by SVGTextLayoutEngine. + +class SVGTextLayoutAttributesBuilder : public Noncopyable { +public: + SVGTextLayoutAttributesBuilder(); + void buildLayoutAttributesForTextSubtree(RenderSVGText*); + +private: + struct LayoutScope { + LayoutScope() + : textContentStart(0) + , textContentLength(0) + { + } + + unsigned textContentStart; + unsigned textContentLength; + SVGTextLayoutAttributes attributes; + }; + + void buildLayoutScope(LayoutScope&, RenderObject*, unsigned textContentStart, unsigned textContentLength) const; + void buildLayoutScopes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter); + void buildOutermostLayoutScope(RenderSVGText*, unsigned textLength); + void propagateLayoutAttributes(RenderObject*, unsigned& atCharacter, UChar& lastCharacter) const; + + enum LayoutValueType { + XValueAttribute, + YValueAttribute, + DxValueAttribute, + DyValueAttribute, + RotateValueAttribute + }; + + float nextLayoutValue(LayoutValueType, unsigned atCharacter) const; + void assignLayoutAttributesForCharacter(SVGTextLayoutAttributes&, SVGTextMetrics&, unsigned valueListPosition) const; + void assignEmptyLayoutAttributesForCharacter(SVGTextLayoutAttributes&) const; + +private: + Vector<LayoutScope> m_scopes; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp new file mode 100644 index 0000000..7eefad6 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.cpp @@ -0,0 +1,579 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextLayoutEngine.h" + +#include "RenderSVGInlineText.h" +#include "RenderSVGTextPath.h" +#include "SVGElement.h" +#include "SVGInlineTextBox.h" +#include "SVGTextLayoutEngineBaseline.h" +#include "SVGTextLayoutEngineSpacing.h" + +// Set to a value > 0 to dump the text fragments +#define DUMP_TEXT_FRAGMENTS 0 + +namespace WebCore { + +SVGTextLayoutEngine::SVGTextLayoutEngine() + : m_x(0) + , m_y(0) + , m_dx(0) + , m_dy(0) + , m_isVerticalText(false) + , m_inPathLayout(false) + , m_textPathLength(0) + , m_textPathCurrentOffset(0) + , m_textPathSpacing(0) + , m_textPathScaling(1) +{ +} + +void SVGTextLayoutEngine::updateCharacerPositionIfNeeded(float& x, float& y) +{ + if (m_inPathLayout) + return; + + // Replace characters x/y position, with the current text position plus any + // relative adjustments, if it doesn't specify an absolute position itself. + if (x == SVGTextLayoutAttributes::emptyValue()) + x = m_x + m_dx; + + if (y == SVGTextLayoutAttributes::emptyValue()) + y = m_y + m_dy; + + m_dx = 0; + m_dy = 0; +} + +void SVGTextLayoutEngine::updateCurrentTextPosition(float x, float y, float glyphAdvance) +{ + // Update current text position after processing the character. + if (m_isVerticalText) { + m_x = x; + m_y = y + glyphAdvance; + } else { + m_x = x + glyphAdvance; + m_y = y; + } +} + +void SVGTextLayoutEngine::updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned positionListOffset) +{ + // Update relative positioning information. + if (dxValues.isEmpty() && dyValues.isEmpty()) + return; + + float dx = 0; + if (!dxValues.isEmpty()) { + float& dxCurrent = dxValues.at(positionListOffset); + if (dxCurrent != SVGTextLayoutAttributes::emptyValue()) + dx = dxCurrent; + } + + float dy = 0; + if (!dyValues.isEmpty()) { + float& dyCurrent = dyValues.at(positionListOffset); + if (dyCurrent != SVGTextLayoutAttributes::emptyValue()) + dy = dyCurrent; + } + + if (m_inPathLayout) { + if (m_isVerticalText) { + m_dx += dx; + m_dy = dy; + } else { + m_dx = dx; + m_dy += dy; + } + + return; + } + + m_dx = dx; + m_dy = dy; +} + +void SVGTextLayoutEngine::recordTextFragment(SVGInlineTextBox* textBox, RenderSVGInlineText* text, unsigned positionListOffset, const SVGTextMetrics& lastCharacterMetrics) +{ + ASSERT(!m_currentTextFragment.length); + + // Figure out length of fragment. + m_currentTextFragment.length = positionListOffset - m_currentTextFragment.positionListOffset; + + // Figure out fragment metrics. + if (m_currentTextFragment.length == 1) { + // Fast path, can rely on already computed per-character metrics. + m_currentTextFragment.width = lastCharacterMetrics.width(); + m_currentTextFragment.height = lastCharacterMetrics.height(); + } else { + // Need to measure the whole range (range metrics != sum of character metrics) + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(text, m_currentTextFragment.positionListOffset, m_currentTextFragment.length); + m_currentTextFragment.width = metrics.width(); + m_currentTextFragment.height = metrics.height(); + } + + textBox->textFragments().append(m_currentTextFragment); + m_currentTextFragment = SVGTextFragment(); +} + +bool SVGTextLayoutEngine::parentDefinesTextLength(RenderObject* parent) const +{ + RenderObject* currentParent = parent; + while (currentParent) { + SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(currentParent); + if (textContentElement) { + SVGTextContentElement::SVGLengthAdjustType lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust()); + if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACING && textContentElement->textLength().value(textContentElement) > 0) + return true; + } + + if (currentParent->isSVGText()) + return false; + + currentParent = currentParent->parent(); + } + + ASSERT_NOT_REACHED(); + return false; +} + +void SVGTextLayoutEngine::beginTextPathLayout(RenderObject* object, SVGTextLayoutEngine& lineLayout) +{ + ASSERT(object); + + m_inPathLayout = true; + RenderSVGTextPath* textPath = toRenderSVGTextPath(object); + + m_textPath = textPath->layoutPath(); + m_textPathStartOffset = textPath->startOffset(); + m_textPathLength = m_textPath.length(); + if (m_textPathStartOffset > 0 && m_textPathStartOffset <= 1) + m_textPathStartOffset *= m_textPathLength; + + float totalLength = 0; + unsigned totalCharacters = 0; + + lineLayout.m_chunkLayoutBuilder.buildTextChunks(lineLayout.m_lineLayoutBoxes); + const Vector<SVGTextChunk>& textChunks = lineLayout.m_chunkLayoutBuilder.textChunks(); + + unsigned size = textChunks.size(); + for (unsigned i = 0; i < size; ++i) { + const SVGTextChunk& chunk = textChunks.at(i); + + float length = 0; + unsigned characters = 0; + chunk.calculateLength(length, characters); + + // Handle text-anchor as additional start offset for text paths. + m_textPathStartOffset += chunk.calculateTextAnchorShift(length); + + totalLength += length; + totalCharacters += characters; + } + + m_textPathCurrentOffset = m_textPathStartOffset; + + // Eventually handle textLength adjustments. + SVGTextContentElement::SVGLengthAdjustType lengthAdjust = SVGTextContentElement::LENGTHADJUST_UNKNOWN; + float desiredTextLength = 0; + + if (SVGTextContentElement* textContentElement = SVGTextContentElement::elementFromRenderer(textPath)) { + lengthAdjust = static_cast<SVGTextContentElement::SVGLengthAdjustType>(textContentElement->lengthAdjust()); + desiredTextLength = textContentElement->textLength().value(textContentElement); + } + + if (!desiredTextLength) + return; + + if (lengthAdjust == SVGTextContentElement::LENGTHADJUST_SPACING) + m_textPathSpacing = (desiredTextLength - totalLength) / totalCharacters; + else + m_textPathScaling = desiredTextLength / totalLength; +} + +void SVGTextLayoutEngine::endTextPathLayout() +{ + m_inPathLayout = false; + m_textPath = Path(); + m_textPathLength = 0; + m_textPathStartOffset = 0; + m_textPathCurrentOffset = 0; + m_textPathSpacing = 0; + m_textPathScaling = 1; +} + +void SVGTextLayoutEngine::layoutInlineTextBox(SVGInlineTextBox* textBox) +{ + ASSERT(textBox); + + RenderSVGInlineText* text = toRenderSVGInlineText(textBox->textRenderer()); + ASSERT(text); + ASSERT(text->parent()); + ASSERT(text->parent()->node()); + ASSERT(text->parent()->node()->isSVGElement()); + + const RenderStyle* style = text->style(); + ASSERT(style); + + textBox->clearTextFragments(); + m_isVerticalText = style->svgStyle()->isVerticalWritingMode(); + layoutTextOnLineOrPath(textBox, text, style); + + if (m_inPathLayout) { + m_pathLayoutBoxes.append(textBox); + return; + } + + m_lineLayoutBoxes.append(textBox); +} + +void SVGTextLayoutEngine::finishLayout() +{ + // After all text fragments are stored in their correpsonding SVGInlineTextBoxes, we can layout individual text chunks. + // Chunk layouting is only performed for line layout boxes, not for path layout, where it has already been done. + m_chunkLayoutBuilder.layoutTextChunks(m_lineLayoutBoxes); + + // Finalize transform matrices, after the chunk layout corrections have been applied, and all fragment x/y positions are finalized. + if (!m_lineLayoutBoxes.isEmpty()) { +#if DUMP_TEXT_FRAGMENTS > 0 + fprintf(stderr, "Line layout: "); +#endif + + finalizeTransformMatrices(m_lineLayoutBoxes); + } + + if (!m_pathLayoutBoxes.isEmpty()) { +#if DUMP_TEXT_FRAGMENTS > 0 + fprintf(stderr, "Path layout: "); +#endif + finalizeTransformMatrices(m_pathLayoutBoxes); + } +} + +void SVGTextLayoutEngine::finalizeTransformMatrices(Vector<SVGInlineTextBox*>& boxes) +{ + unsigned boxCount = boxes.size(); + +#if DUMP_TEXT_FRAGMENTS > 0 + fprintf(stderr, "Dumping all text fragments in text sub tree, %i boxes\n", boxCount); + + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + fprintf(stderr, "-> Box %i: Dumping text fragments for SVGInlineTextBox, textBox=%p, textRenderer=%p\n", boxPosition, textBox, textBox->textRenderer()); + fprintf(stderr, " textBox properties, start=%i, len=%i\n", textBox->start(), textBox->len()); + fprintf(stderr, " textRenderer properties, textLength=%i\n", textBox->textRenderer()->textLength()); + + const UChar* characters = textBox->textRenderer()->characters(); + + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + String fragmentString(characters + fragment.positionListOffset, fragment.length); + fprintf(stderr, " -> Fragment %i, x=%lf, y=%lf, width=%lf, height=%lf, positionListOffset=%i, length=%i, characters='%s'\n" + , i, fragment.x, fragment.y, fragment.width, fragment.height, fragment.positionListOffset, fragment.length, fragmentString.utf8().data()); + } + } +#endif + + + if (!boxCount) + return; + + AffineTransform textBoxTransformation; + for (unsigned boxPosition = 0; boxPosition < boxCount; ++boxPosition) { + SVGInlineTextBox* textBox = boxes.at(boxPosition); + Vector<SVGTextFragment>& fragments = textBox->textFragments(); + + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + SVGTextFragment& fragment = fragments.at(i); + AffineTransform& transform = fragment.transform; + if (!transform.isIdentity()) { + transform.translateRight(fragment.x, fragment.y); + transform.translate(-fragment.x, -fragment.y); + } + + m_chunkLayoutBuilder.transformationForTextBox(textBox, textBoxTransformation); + if (textBoxTransformation.isIdentity()) + continue; + + if (transform.isIdentity()) + transform = textBoxTransformation; + else + transform.multiply(textBoxTransformation); + } + } + + boxes.clear(); +} + +void SVGTextLayoutEngine::layoutTextOnLineOrPath(SVGInlineTextBox* textBox, RenderSVGInlineText* text, const RenderStyle* style) +{ + SVGElement* lengthContext = static_cast<SVGElement*>(text->parent()->node()); + + RenderObject* textParent = text->parent(); + bool definesTextLength = textParent ? parentDefinesTextLength(textParent) : false; + + const SVGRenderStyle* svgStyle = style->svgStyle(); + ASSERT(svgStyle); + + SVGTextLayoutAttributes& attributes = text->layoutAttributes(); + Vector<float>& xValues = attributes.xValues(); + Vector<float>& yValues = attributes.yValues(); + Vector<float>& dxValues = attributes.dxValues(); + Vector<float>& dyValues = attributes.dyValues(); + Vector<float>& rotateValues = attributes.rotateValues(); + Vector<SVGTextMetrics>& textMetricsValues = attributes.textMetricsValues(); + + unsigned boxStart = textBox->start(); + unsigned boxLength = textBox->len(); + unsigned textMetricsSize = textMetricsValues.size(); + ASSERT(textMetricsSize <= xValues.size()); + ASSERT(textMetricsSize <= yValues.size()); + ASSERT(xValues.size() == yValues.size()); + + if (boxLength > textMetricsSize) + textMetricsSize = boxLength; + + unsigned positionListOffset = 0; + unsigned metricsListOffset = 0; + const UChar* characters = text->characters(); + + const Font& font = style->font(); + SVGTextLayoutEngineSpacing spacingLayout(font); + SVGTextLayoutEngineBaseline baselineLayout(font); + + bool didStartTextFragment = false; + bool applySpacingToNextCharacter = false; + + float lastAngle = 0; + float baselineShift = baselineLayout.calculateBaselineShift(svgStyle, lengthContext); + baselineShift -= baselineLayout.calculateAlignmentBaselineShift(m_isVerticalText, text); + + // Main layout algorithm. + unsigned positionListSize = xValues.size(); + for (; metricsListOffset < textMetricsSize && positionListOffset < positionListSize; ++metricsListOffset) { + SVGTextMetrics& metrics = textMetricsValues.at(metricsListOffset); + // Advance to text box start location. + if (positionListOffset < boxStart) { + positionListOffset += metrics.length(); + continue; + } + + // Stop if we've finished processing this text box. + if (positionListOffset >= boxStart + boxLength) + break; + + float x = xValues.at(positionListOffset); + float y = yValues.at(positionListOffset); + + // When we've advanced to the box start offset, determine using the original x/y values, + // wheter this character starts a new text chunk, before doing any further processing. + if (positionListOffset == boxStart) + textBox->setStartsNewTextChunk(text->characterStartsNewTextChunk(boxStart)); + + if (metrics == SVGTextMetrics::emptyMetrics()) { + positionListOffset += metrics.length(); + continue; + } + + const UChar* currentCharacter = characters + positionListOffset; + float angle = 0; + if (!rotateValues.isEmpty()) { + float newAngle = rotateValues.at(positionListOffset); + if (newAngle != SVGTextLayoutAttributes::emptyValue()) + angle = newAngle; + } + + // Calculate glyph orientation angle. + float orientationAngle = baselineLayout.calculateGlyphOrientationAngle(m_isVerticalText, svgStyle, *currentCharacter); + + // Calculate glyph advance & x/y orientation shifts. + float xOrientationShift = 0; + float yOrientationShift = 0; + float glyphAdvance = baselineLayout.calculateGlyphAdvanceAndOrientation(m_isVerticalText, metrics, orientationAngle, xOrientationShift, yOrientationShift); + + // Assign current text position to x/y values, if needed. + updateCharacerPositionIfNeeded(x, y); + + // Apply dx/dy value adjustments to current text position, if needed. + updateRelativePositionAdjustmentsIfNeeded(dxValues, dyValues, positionListOffset); + + // Calculate SVG Fonts kerning, if needed. + float kerning = spacingLayout.calculateSVGKerning(m_isVerticalText, metrics.glyph()); + + // Calculate CSS 'kerning', 'letter-spacing' and 'word-spacing' for next character, if needed. + float spacing = spacingLayout.calculateCSSKerningAndSpacing(svgStyle, lengthContext, currentCharacter); + + float textPathOffset = 0; + if (m_inPathLayout) { + float scaledGlyphAdvance = glyphAdvance * m_textPathScaling; + if (m_isVerticalText) { + // If there's an absolute y position available, it marks the beginning of a new position along the path. + if (y != SVGTextLayoutAttributes::emptyValue()) + m_textPathCurrentOffset = y + m_textPathStartOffset; + + m_textPathCurrentOffset += m_dy - kerning; + m_dy = 0; + + // Apply dx/dy correction and setup translations that move to the glyph midpoint. + xOrientationShift += m_dx + baselineShift; + yOrientationShift -= scaledGlyphAdvance / 2; + } else { + // If there's an absolute x position available, it marks the beginning of a new position along the path. + if (x != SVGTextLayoutAttributes::emptyValue()) + m_textPathCurrentOffset = x + m_textPathStartOffset; + + m_textPathCurrentOffset += m_dx - kerning; + m_dx = 0; + + // Apply dx/dy correction and setup translations that move to the glyph midpoint. + xOrientationShift -= scaledGlyphAdvance / 2; + yOrientationShift += m_dy - baselineShift; + } + + // Calculate current offset along path. + textPathOffset = m_textPathCurrentOffset + scaledGlyphAdvance / 2; + + // Move to next character. + m_textPathCurrentOffset += scaledGlyphAdvance + m_textPathSpacing + spacing * m_textPathScaling; + + // Skip character, if we're before the path. + if (textPathOffset < 0) { + positionListOffset += metrics.length(); + continue; + } + + // Stop processing, if the next character lies behind the path. + if (textPathOffset > m_textPathLength) + break; + + bool ok = false; + FloatPoint point = m_textPath.pointAtLength(textPathOffset, ok); + ASSERT(ok); + + x = point.x(); + y = point.y(); + angle = m_textPath.normalAngleAtLength(textPathOffset, ok); + ASSERT(ok); + + // For vertical text on path, the actual angle has to be rotated 90 degrees anti-clockwise, not the orientation angle! + if (m_isVerticalText) + angle -= 90; + } else { + // Apply all previously calculated shift values. + if (m_isVerticalText) { + x += baselineShift; + y -= kerning; + } else { + x -= kerning; + y -= baselineShift; + } + + x += m_dx; + y += m_dy; + } + + // Determine wheter we have to start a new fragment. + bool shouldStartNewFragment = false; + + if (m_dx || m_dy) + shouldStartNewFragment = true; + + if (!shouldStartNewFragment && (m_isVerticalText || m_inPathLayout)) + shouldStartNewFragment = true; + + if (!shouldStartNewFragment && (angle || angle != lastAngle || orientationAngle)) + shouldStartNewFragment = true; + + if (!shouldStartNewFragment && (kerning || applySpacingToNextCharacter || definesTextLength)) + shouldStartNewFragment = true; + + // If we already started a fragment, close it now. + if (didStartTextFragment && shouldStartNewFragment) { + applySpacingToNextCharacter = false; + recordTextFragment(textBox, text, positionListOffset, textMetricsValues.at(metricsListOffset - 1)); + } + + // Eventually start a new fragment, if not yet done. + if (!didStartTextFragment || shouldStartNewFragment) { + ASSERT(!m_currentTextFragment.positionListOffset); + ASSERT(!m_currentTextFragment.length); + + didStartTextFragment = true; + m_currentTextFragment.positionListOffset = positionListOffset; + m_currentTextFragment.x = x; + m_currentTextFragment.y = y; + + // Build fragment transformation. + if (angle) + m_currentTextFragment.transform.rotate(angle); + + if (xOrientationShift || yOrientationShift) + m_currentTextFragment.transform.translate(xOrientationShift, yOrientationShift); + + if (orientationAngle) + m_currentTextFragment.transform.rotate(orientationAngle); + + if (m_inPathLayout && m_textPathScaling != 1) { + if (m_isVerticalText) + m_currentTextFragment.transform.scaleNonUniform(1, m_textPathScaling); + else + m_currentTextFragment.transform.scaleNonUniform(m_textPathScaling, 1); + } + } + + // Update current text position, after processing of the current character finished. + if (m_inPathLayout) + updateCurrentTextPosition(x, y, glyphAdvance); + else { + // Apply CSS 'kerning', 'letter-spacing' and 'word-spacing' to next character, if needed. + if (spacing) + applySpacingToNextCharacter = true; + + float xNew = x - m_dx; + float yNew = y - m_dy; + + if (m_isVerticalText) + xNew -= baselineShift; + else + yNew += baselineShift; + + updateCurrentTextPosition(xNew, yNew, glyphAdvance + spacing); + } + + positionListOffset += metrics.length(); + lastAngle = angle; + } + + if (!didStartTextFragment) + return; + + // Close last open fragment, if needed. + recordTextFragment(textBox, text, positionListOffset, textMetricsValues.at(metricsListOffset - 1)); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h new file mode 100644 index 0000000..ad058d8 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngine.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutEngine_h +#define SVGTextLayoutEngine_h + +#if ENABLE(SVG) +#include "Path.h" +#include "SVGTextChunkBuilder.h" +#include "SVGTextFragment.h" +#include "SVGTextMetrics.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class RenderObject; +class RenderStyle; +class RenderSVGInlineText; +class SVGElement; +class SVGInlineTextBox; +class SVGRenderStyle; + +// SVGTextLayoutEngine performs the second layout phase for SVG text. +// +// The InlineBox tree was created, containing the text chunk information, necessary to apply +// certain SVG specific text layout properties (text-length adjustments and text-anchor). +// The second layout phase uses the SVGTextLayoutAttributes stored in the individual +// RenderSVGInlineText renderers to compute the final positions for each character +// which are stored in the SVGInlineTextBox objects. + +class SVGTextLayoutEngine : public Noncopyable { +public: + SVGTextLayoutEngine(); + SVGTextChunkBuilder& chunkLayoutBuilder() { return m_chunkLayoutBuilder; } + + void beginTextPathLayout(RenderObject*, SVGTextLayoutEngine& lineLayout); + void endTextPathLayout(); + + void layoutInlineTextBox(SVGInlineTextBox*); + void finishLayout(); + +private: + void updateCharacerPositionIfNeeded(float& x, float& y); + void updateCurrentTextPosition(float x, float y, float glyphAdvance); + void updateRelativePositionAdjustmentsIfNeeded(Vector<float>& dxValues, Vector<float>& dyValues, unsigned valueListPosition); + + void recordTextFragment(SVGInlineTextBox*, RenderSVGInlineText*, unsigned positionListOffset, const SVGTextMetrics& lastCharacterMetrics); + bool parentDefinesTextLength(RenderObject*) const; + + void layoutTextOnLineOrPath(SVGInlineTextBox*, RenderSVGInlineText*, const RenderStyle*); + void finalizeTransformMatrices(Vector<SVGInlineTextBox*>&); + +private: + Vector<SVGInlineTextBox*> m_lineLayoutBoxes; + Vector<SVGInlineTextBox*> m_pathLayoutBoxes; + SVGTextChunkBuilder m_chunkLayoutBuilder; + + SVGTextFragment m_currentTextFragment; + float m_x; + float m_y; + float m_dx; + float m_dy; + bool m_isVerticalText; + bool m_inPathLayout; + + // Text on path layout + Path m_textPath; + float m_textPathLength; + float m_textPathStartOffset; + float m_textPathCurrentOffset; + float m_textPathSpacing; + float m_textPathScaling; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp new file mode 100644 index 0000000..7060ac6 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextLayoutEngineBaseline.h" + +#include "Font.h" +#include "RenderObject.h" +#include "SVGRenderStyle.h" +#include "SVGTextMetrics.h" +#include "UnicodeRange.h" + +namespace WebCore { + +SVGTextLayoutEngineBaseline::SVGTextLayoutEngineBaseline(const Font& font) + : m_font(font) +{ +} + +float SVGTextLayoutEngineBaseline::calculateBaselineShift(const SVGRenderStyle* style, SVGElement* lengthContext) const +{ + if (style->baselineShift() == BS_LENGTH) { + SVGLength baselineShiftValueLength = style->baselineShiftValue(); + if (baselineShiftValueLength.unitType() == LengthTypePercentage) + return baselineShiftValueLength.valueAsPercentage() * m_font.pixelSize(); + + return baselineShiftValueLength.value(lengthContext); + } + + switch (style->baselineShift()) { + case BS_BASELINE: + return 0; + case BS_SUB: + return -m_font.height() / 2; + case BS_SUPER: + return m_font.height() / 2; + default: + ASSERT_NOT_REACHED(); + return 0; + } +} + +EAlignmentBaseline SVGTextLayoutEngineBaseline::dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const +{ + ASSERT(textRenderer); + ASSERT(textRenderer->style()); + ASSERT(textRenderer->parent()); + ASSERT(textRenderer->parent()->style()); + + const SVGRenderStyle* style = textRenderer->style()->svgStyle(); + ASSERT(style); + + EDominantBaseline baseline = style->dominantBaseline(); + if (baseline == DB_AUTO) { + if (isVerticalText) + baseline = DB_CENTRAL; + else + baseline = DB_ALPHABETIC; + } + + switch (baseline) { + case DB_USE_SCRIPT: + // FIXME: 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: + return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent()); + case DB_RESET_SIZE: + return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent()); + 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; + } +} + +float SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const +{ + ASSERT(textRenderer); + ASSERT(textRenderer->style()); + ASSERT(textRenderer->style()->svgStyle()); + ASSERT(textRenderer->parent()); + + const RenderObject* textRendererParent = textRenderer->parent(); + ASSERT(textRendererParent); + + EAlignmentBaseline baseline = textRenderer->style()->svgStyle()->alignmentBaseline(); + if (baseline == AB_AUTO) { + baseline = dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent); + ASSERT(baseline != AB_AUTO); + } + + // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + switch (baseline) { + case AB_BASELINE: + return dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent); + case AB_BEFORE_EDGE: + case AB_TEXT_BEFORE_EDGE: + return m_font.ascent(); + case AB_MIDDLE: + return m_font.xHeight() / 2; + case AB_CENTRAL: + return (m_font.ascent() - m_font.descent()) / 2; + case AB_AFTER_EDGE: + case AB_TEXT_AFTER_EDGE: + case AB_IDEOGRAPHIC: + return m_font.descent(); + case AB_ALPHABETIC: + return 0; + case AB_HANGING: + return m_font.ascent() * 8 / 10.f; + case AB_MATHEMATICAL: + return m_font.ascent() / 2; + default: + ASSERT_NOT_REACHED(); + return 0; + } +} + +float SVGTextLayoutEngineBaseline::calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle* style, const UChar& character) const +{ + ASSERT(style); + + switch (isVerticalText ? style->glyphOrientationVertical() : style->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; + + return 0; + } + case GO_90DEG: + return 90; + case GO_180DEG: + return 180; + case GO_270DEG: + return 270; + case GO_0DEG: + default: + return 0; + } +} + +static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle) +{ + return !fabsf(fmodf(orientationAngle, 180)); +} + +float SVGTextLayoutEngineBaseline::calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics& metrics, float angle, float& xOrientationShift, float& yOrientationShift) const +{ + bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(angle); + + // 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) { + float ascentMinusDescent = m_font.ascent() - m_font.descent(); + if (!angle) { + xOrientationShift = (ascentMinusDescent - metrics.width()) / 2; + yOrientationShift = m_font.ascent(); + } else if (angle == 180) + xOrientationShift = (ascentMinusDescent + metrics.width()) / 2; + else if (angle == 270) { + yOrientationShift = metrics.width(); + xOrientationShift = ascentMinusDescent; + } + + // Vertical advance calculation. + if (angle && !orientationIsMultiplyOf180Degrees) + return metrics.width(); + + return metrics.height(); + } + + // Horizontal orientation handling. + if (angle == 90) + yOrientationShift = -metrics.width(); + else if (angle == 180) { + xOrientationShift = metrics.width(); + yOrientationShift = -m_font.ascent(); + } else if (angle == 270) + xOrientationShift = metrics.width(); + + // Horizontal advance calculation. + if (angle && !orientationIsMultiplyOf180Degrees) + return metrics.height(); + + return metrics.width(); +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h new file mode 100644 index 0000000..d753b39 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineBaseline.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutEngineBaseline_h +#define SVGTextLayoutEngineBaseline_h + +#if ENABLE(SVG) +#include "SVGRenderStyleDefs.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + +class Font; +class RenderObject; +class SVGElement; +class SVGRenderStyle; +class SVGTextMetrics; + +// Helper class used by SVGTextLayoutEngine to handle 'alignment-baseline' / 'dominant-baseline' and 'baseline-shift'. +class SVGTextLayoutEngineBaseline : public Noncopyable { +public: + SVGTextLayoutEngineBaseline(const Font&); + + float calculateBaselineShift(const SVGRenderStyle*, SVGElement* lengthContext) const; + float calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const; + float calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle*, const UChar& character) const; + float calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics&, float angle, float& xOrientationShift, float& yOrientationShift) const; + +private: + EAlignmentBaseline dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const; + + const Font& m_font; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp new file mode 100644 index 0000000..6c54b67 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextLayoutEngineSpacing.h" + +#include "Font.h" +#include "SVGRenderStyle.h" + +#if ENABLE(SVG_FONTS) +#include "SVGFontElement.h" +#endif + +namespace WebCore { + +SVGTextLayoutEngineSpacing::SVGTextLayoutEngineSpacing(const Font& font) + : m_font(font) + , m_lastCharacter(0) +{ +} + +float SVGTextLayoutEngineSpacing::calculateSVGKerning(bool isVerticalText, const SVGTextMetrics::Glyph& currentGlyph) +{ +#if ENABLE(SVG_FONTS) + if (!m_font.isSVGFont()) { + m_lastGlyph.isValid = false; + return 0; + } + + SVGFontElement* svgFont = m_font.svgFont(); + ASSERT(svgFont); + + float kerning = 0; + if (m_lastGlyph.isValid) { + if (isVerticalText) + kerning = svgFont->verticalKerningForPairOfStringsAndGlyphs(m_lastGlyph.unicodeString, m_lastGlyph.name, currentGlyph.unicodeString, currentGlyph.name); + else + kerning = svgFont->horizontalKerningForPairOfStringsAndGlyphs(m_lastGlyph.unicodeString, m_lastGlyph.name, currentGlyph.unicodeString, currentGlyph.name); + } + + m_lastGlyph = currentGlyph; + m_lastGlyph.isValid = true; + kerning *= m_font.size() / m_font.primaryFont()->unitsPerEm(); + return kerning; +#else + UNUSED_PARAM(isVerticalText); + UNUSED_PARAM(currentGlyph); + return false; +#endif +} + +float SVGTextLayoutEngineSpacing::calculateCSSKerningAndSpacing(const SVGRenderStyle* style, SVGElement* lengthContext, const UChar* currentCharacter) +{ + float kerning = 0; + SVGLength kerningLength = style->kerning(); + if (kerningLength.unitType() == LengthTypePercentage) + kerning = kerningLength.valueAsPercentage() * m_font.pixelSize(); + else + kerning = kerningLength.value(lengthContext); + + const UChar* lastCharacter = m_lastCharacter; + m_lastCharacter = currentCharacter; + + if (!kerning && !m_font.letterSpacing() && !m_font.wordSpacing()) + return 0; + + float spacing = m_font.letterSpacing() + kerning; + if (currentCharacter && lastCharacter && m_font.wordSpacing()) { + if (Font::treatAsSpace(*currentCharacter) && !Font::treatAsSpace(*lastCharacter)) + spacing += m_font.wordSpacing(); + } + + return spacing; +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h new file mode 100644 index 0000000..0a6d736 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextLayoutEngineSpacing.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextLayoutEngineSpacing_h +#define SVGTextLayoutEngineSpacing_h + +#if ENABLE(SVG) +#include "SVGTextMetrics.h" + +namespace WebCore { + +class Font; +class SVGRenderStyle; +class SVGElement; + +// Helper class used by SVGTextLayoutEngine to handle 'kerning' / 'letter-spacing' and 'word-spacing'. +class SVGTextLayoutEngineSpacing : public Noncopyable { +public: + SVGTextLayoutEngineSpacing(const Font&); + + float calculateSVGKerning(bool isVerticalText, const SVGTextMetrics::Glyph& currentGlyph); + float calculateCSSKerningAndSpacing(const SVGRenderStyle*, SVGElement* lengthContext, const UChar* currentCharacter); + +private: + const Font& m_font; + const UChar* m_lastCharacter; + +#if ENABLE(SVG_FONTS) + SVGTextMetrics::Glyph m_lastGlyph; +#endif +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextMetrics.cpp b/Source/WebCore/rendering/svg/SVGTextMetrics.cpp new file mode 100644 index 0000000..58d0ad9 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextMetrics.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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 "SVGTextMetrics.h" + +#include "RenderSVGInlineText.h" + +namespace WebCore { + +SVGTextMetrics::SVGTextMetrics() + : m_width(0) + , m_height(0) + , m_length(0) +{ +} + +SVGTextMetrics::SVGTextMetrics(const Font& font, const TextRun& run, unsigned position, unsigned textLength) + : m_width(0) + , m_height(0) + , m_length(0) +{ + int extraCharsAvailable = textLength - (position + run.length()); + int length = 0; + + m_width = font.floatWidth(run, extraCharsAvailable, length, m_glyph.name); + m_height = font.height(); + m_glyph.unicodeString = String(run.characters(), length); + m_glyph.isValid = true; + + ASSERT(length >= 0); + m_length = static_cast<unsigned>(length); +} + +bool SVGTextMetrics::operator==(const SVGTextMetrics& other) +{ + return m_width == other.m_width + && m_height == other.m_height + && m_length == other.m_length + && m_glyph == other.m_glyph; +} + +SVGTextMetrics SVGTextMetrics::emptyMetrics() +{ + DEFINE_STATIC_LOCAL(SVGTextMetrics, s_emptyMetrics, ()); + s_emptyMetrics.m_length = 1; + return s_emptyMetrics; +} + +static TextRun constructTextRun(RenderSVGInlineText* text, const UChar* characters, unsigned position, unsigned length) +{ + TextRun run(characters + position, length); + +#if ENABLE(SVG_FONTS) + ASSERT(text->parent()); + run.setReferencingRenderObject(text->parent()); +#endif + + // Disable any word/character rounding. + run.disableRoundingHacks(); + + // We handle letter & word spacing ourselves. + run.disableSpacing(); + return run; +} + +SVGTextMetrics SVGTextMetrics::measureCharacterRange(RenderSVGInlineText* text, unsigned position, unsigned length) +{ + ASSERT(text); + ASSERT(text->style()); + + TextRun run(constructTextRun(text, text->characters(), position, length)); + return SVGTextMetrics(text->style()->font(), run, position, text->textLength()); +} + +void SVGTextMetrics::measureAllCharactersIndividually(RenderSVGInlineText* text, Vector<SVGTextMetrics>& allMetrics) +{ + ASSERT(text); + ASSERT(text->style()); + + const Font& font = text->style()->font(); + const UChar* characters = text->characters(); + unsigned length = text->textLength(); + + TextRun run(constructTextRun(text, 0, 0, 0)); + for (unsigned position = 0; position < length; ) { + run.setText(characters + position, 1); + + SVGTextMetrics metrics(font, run, position, text->textLength()); + allMetrics.append(metrics); + position += metrics.length(); + } +} + +} + +#endif // ENABLE(SVG) diff --git a/Source/WebCore/rendering/svg/SVGTextMetrics.h b/Source/WebCore/rendering/svg/SVGTextMetrics.h new file mode 100644 index 0000000..ba18589 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextMetrics.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) Research In Motion Limited 2010. All rights reserved. + * + * 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. + */ + +#ifndef SVGTextMetrics_h +#define SVGTextMetrics_h + +#if ENABLE(SVG) +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class Font; +class RenderSVGInlineText; +class TextRun; + +class SVGTextMetrics { +public: + static SVGTextMetrics emptyMetrics(); + static SVGTextMetrics measureCharacterRange(RenderSVGInlineText*, unsigned position, unsigned length); + static void measureAllCharactersIndividually(RenderSVGInlineText*, Vector<SVGTextMetrics>&); + + bool operator==(const SVGTextMetrics&); + + float width() const { return m_width; } + float height() const { return m_height; } + unsigned length() const { return m_length; } + + struct Glyph { + Glyph() + : isValid(false) + { + } + + bool operator==(const Glyph& other) + { + return isValid == other.isValid + && name == other.name + && unicodeString == other.unicodeString; + } + + bool isValid; + String name; + String unicodeString; + }; + + // Only useful when measuring individual characters, to lookup ligatures. + const Glyph& glyph() const { return m_glyph; } + +private: + SVGTextMetrics(); + SVGTextMetrics(const Font&, const TextRun&, unsigned position, unsigned textLength); + + float m_width; + float m_height; + unsigned m_length; + Glyph m_glyph; +}; + +} // namespace WebCore + +#endif // ENABLE(SVG) +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextQuery.cpp b/Source/WebCore/rendering/svg/SVGTextQuery.cpp new file mode 100644 index 0000000..fcc7924 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextQuery.cpp @@ -0,0 +1,562 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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" +#include "SVGTextQuery.h" + +#if ENABLE(SVG) +#include "FloatConversion.h" +#include "InlineFlowBox.h" +#include "RenderBlock.h" +#include "RenderInline.h" +#include "RenderSVGInlineText.h" +#include "SVGInlineTextBox.h" +#include "SVGTextMetrics.h" +#include "VisiblePosition.h" + +#include <wtf/MathExtras.h> + +namespace WebCore { + +// Base structure for callback user data +struct SVGTextQuery::Data { + Data() + : isVerticalText(false) + , processedCharacters(0) + , textRenderer(0) + , textBox(0) + { + } + + bool isVerticalText; + unsigned processedCharacters; + RenderSVGInlineText* textRenderer; + const SVGInlineTextBox* textBox; +}; + +static inline InlineFlowBox* flowBoxForRenderer(RenderObject* renderer) +{ + if (!renderer) + return 0; + + if (renderer->isRenderBlock()) { + // If we're given a block element, it has to be a RenderSVGText. + ASSERT(renderer->isSVGText()); + RenderBlock* renderBlock = toRenderBlock(renderer); + + // RenderSVGText only ever contains a single line box. + InlineFlowBox* flowBox = renderBlock->firstLineBox(); + ASSERT(flowBox == renderBlock->lastLineBox()); + return flowBox; + } + + if (renderer->isRenderInline()) { + // We're given a RenderSVGInline or objects that derive from it (RenderSVGTSpan / RenderSVGTextPath) + RenderInline* renderInline = toRenderInline(renderer); + + // RenderSVGInline only ever contains a single line box. + InlineFlowBox* flowBox = renderInline->firstLineBox(); + ASSERT(flowBox == renderInline->lastLineBox()); + return flowBox; + } + + ASSERT_NOT_REACHED(); + return 0; +} + +static inline float mapLengthThroughFragmentTransformation(const SVGTextFragment& fragment, bool isVerticalText, float length) +{ + if (fragment.transform.isIdentity()) + return length; + + if (isVerticalText) + return narrowPrecisionToFloat(static_cast<double>(length) * fragment.transform.yScale()); + + return narrowPrecisionToFloat(static_cast<double>(length) * fragment.transform.xScale()); +} + +SVGTextQuery::SVGTextQuery(RenderObject* renderer) +{ + collectTextBoxesInFlowBox(flowBoxForRenderer(renderer)); +} + +void SVGTextQuery::collectTextBoxesInFlowBox(InlineFlowBox* flowBox) +{ + if (!flowBox) + return; + + for (InlineBox* child = flowBox->firstChild(); child; child = child->nextOnLine()) { + if (child->isInlineFlowBox()) { + // Skip generated content. + if (!child->renderer()->node()) + continue; + + collectTextBoxesInFlowBox(static_cast<InlineFlowBox*>(child)); + continue; + } + + ASSERT(child->isSVGInlineTextBox()); + m_textBoxes.append(static_cast<SVGInlineTextBox*>(child)); + } +} + +bool SVGTextQuery::executeQuery(Data* queryData, ProcessTextFragmentCallback fragmentCallback) const +{ + ASSERT(!m_textBoxes.isEmpty()); + + unsigned processedCharacters = 0; + unsigned textBoxCount = m_textBoxes.size(); + + // Loop over all text boxes + for (unsigned textBoxPosition = 0; textBoxPosition < textBoxCount; ++textBoxPosition) { + queryData->textBox = m_textBoxes.at(textBoxPosition); + queryData->textRenderer = toRenderSVGInlineText(queryData->textBox->textRenderer()); + ASSERT(queryData->textRenderer); + ASSERT(queryData->textRenderer->style()); + ASSERT(queryData->textRenderer->style()->svgStyle()); + + queryData->isVerticalText = queryData->textRenderer->style()->svgStyle()->isVerticalWritingMode(); + const Vector<SVGTextFragment>& fragments = queryData->textBox->textFragments(); + + // Loop over all text fragments in this text box, firing a callback for each. + unsigned fragmentCount = fragments.size(); + for (unsigned i = 0; i < fragmentCount; ++i) { + const SVGTextFragment& fragment = fragments.at(i); + if ((this->*fragmentCallback)(queryData, fragment)) + return true; + + processedCharacters += fragment.length; + } + + queryData->processedCharacters = processedCharacters; + } + + return false; +} + +bool SVGTextQuery::mapStartEndPositionsIntoFragmentCoordinates(Data* queryData, const SVGTextFragment& fragment, int& startPosition, int& endPosition) const +{ + // Reuse the same logic used for text selection & painting, to map our query start/length into start/endPositions of the current text fragment. + startPosition -= queryData->processedCharacters; + endPosition -= queryData->processedCharacters; + + if (startPosition >= endPosition || startPosition < 0 || endPosition < 0) + return false; + + modifyStartEndPositionsRespectingLigatures(queryData, startPosition, endPosition); + if (!queryData->textBox->mapStartEndPositionsIntoFragmentCoordinates(fragment, startPosition, endPosition)) + return false; + + ASSERT(startPosition < endPosition); + return true; +} + +void SVGTextQuery::modifyStartEndPositionsRespectingLigatures(Data* queryData, int& startPosition, int& endPosition) const +{ + const SVGTextLayoutAttributes& layoutAttributes = queryData->textRenderer->layoutAttributes(); + const Vector<float>& xValues = layoutAttributes.xValues(); + const Vector<SVGTextMetrics>& textMetricsValues = layoutAttributes.textMetricsValues(); + + unsigned boxStart = queryData->textBox->start(); + unsigned boxLength = queryData->textBox->len(); + + unsigned textMetricsOffset = 0; + unsigned textMetricsSize = textMetricsValues.size(); + + unsigned positionOffset = 0; + unsigned positionSize = xValues.size(); + + bool alterStartPosition = true; + bool alterEndPosition = true; + + int lastPositionOffset = -1; + for (; textMetricsOffset < textMetricsSize && positionOffset < positionSize; ++textMetricsOffset) { + const SVGTextMetrics& metrics = textMetricsValues.at(textMetricsOffset); + + // Advance to text box start location. + if (positionOffset < boxStart) { + positionOffset += metrics.length(); + continue; + } + + // Stop if we've finished processing this text box. + if (positionOffset >= boxStart + boxLength) + break; + + // If the start position maps to a character in the metrics list, we don't need to modify it. + if (startPosition == static_cast<int>(positionOffset)) + alterStartPosition = false; + + // If the start position maps to a character in the metrics list, we don't need to modify it. + if (endPosition == static_cast<int>(positionOffset)) + alterEndPosition = false; + + // Detect ligatures. + if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { + if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { + startPosition = lastPositionOffset; + alterStartPosition = false; + } + + if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { + endPosition = positionOffset; + alterEndPosition = false; + } + } + + if (!alterStartPosition && !alterEndPosition) + break; + + lastPositionOffset = positionOffset; + positionOffset += metrics.length(); + } + + if (!alterStartPosition && !alterEndPosition) + return; + + if (lastPositionOffset != -1 && lastPositionOffset - positionOffset > 1) { + if (alterStartPosition && startPosition > lastPositionOffset && startPosition < static_cast<int>(positionOffset)) { + startPosition = lastPositionOffset; + alterStartPosition = false; + } + + if (alterEndPosition && endPosition > lastPositionOffset && endPosition < static_cast<int>(positionOffset)) { + endPosition = positionOffset; + alterEndPosition = false; + } + } +} + +// numberOfCharacters() implementation +bool SVGTextQuery::numberOfCharactersCallback(Data*, const SVGTextFragment&) const +{ + // no-op + return false; +} + +unsigned SVGTextQuery::numberOfCharacters() const +{ + if (m_textBoxes.isEmpty()) + return 0; + + Data data; + executeQuery(&data, &SVGTextQuery::numberOfCharactersCallback); + return data.processedCharacters; +} + +// textLength() implementation +struct TextLengthData : SVGTextQuery::Data { + TextLengthData() + : textLength(0) + { + } + + float textLength; +}; + +bool SVGTextQuery::textLengthCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + TextLengthData* data = static_cast<TextLengthData*>(queryData); + + float fragmentLength = queryData->isVerticalText ? fragment.height : fragment.width; + data->textLength += mapLengthThroughFragmentTransformation(fragment, queryData->isVerticalText, fragmentLength); + return false; +} + +float SVGTextQuery::textLength() const +{ + if (m_textBoxes.isEmpty()) + return 0; + + TextLengthData data; + executeQuery(&data, &SVGTextQuery::textLengthCallback); + return data.textLength; +} + +// subStringLength() implementation +struct SubStringLengthData : SVGTextQuery::Data { + SubStringLengthData(unsigned queryStartPosition, unsigned queryLength) + : startPosition(queryStartPosition) + , length(queryLength) + , subStringLength(0) + { + } + + unsigned startPosition; + unsigned length; + + float subStringLength; +}; + +bool SVGTextQuery::subStringLengthCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + SubStringLengthData* data = static_cast<SubStringLengthData*>(queryData); + + int startPosition = data->startPosition; + int endPosition = startPosition + data->length; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset + startPosition, endPosition - startPosition); + float fragmentLength = queryData->isVerticalText ? metrics.height() : metrics.width(); + + data->subStringLength += mapLengthThroughFragmentTransformation(fragment, queryData->isVerticalText, fragmentLength); + return false; +} + +float SVGTextQuery::subStringLength(unsigned startPosition, unsigned length) const +{ + if (m_textBoxes.isEmpty()) + return 0; + + SubStringLengthData data(startPosition, length); + executeQuery(&data, &SVGTextQuery::subStringLengthCallback); + return data.subStringLength; +} + +// startPositionOfCharacter() implementation +struct StartPositionOfCharacterData : SVGTextQuery::Data { + StartPositionOfCharacterData(unsigned queryPosition) + : position(queryPosition) + { + } + + unsigned position; + FloatPoint startPosition; +}; + +bool SVGTextQuery::startPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + StartPositionOfCharacterData* data = static_cast<StartPositionOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + data->startPosition = FloatPoint(fragment.x, fragment.y); + + if (startPosition) { + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition); + if (queryData->isVerticalText) + data->startPosition.move(0, metrics.height()); + else + data->startPosition.move(metrics.width(), 0); + } + + if (fragment.transform.isIdentity()) + return true; + + data->startPosition = fragment.transform.mapPoint(data->startPosition); + return true; +} + +FloatPoint SVGTextQuery::startPositionOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return FloatPoint(); + + StartPositionOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::startPositionOfCharacterCallback); + return data.startPosition; +} + +// endPositionOfCharacter() implementation +struct EndPositionOfCharacterData : SVGTextQuery::Data { + EndPositionOfCharacterData(unsigned queryPosition) + : position(queryPosition) + { + } + + unsigned position; + FloatPoint endPosition; +}; + +bool SVGTextQuery::endPositionOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + EndPositionOfCharacterData* data = static_cast<EndPositionOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + data->endPosition = FloatPoint(fragment.x, fragment.y); + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition + 1); + if (queryData->isVerticalText) + data->endPosition.move(0, metrics.height()); + else + data->endPosition.move(metrics.width(), 0); + + if (fragment.transform.isIdentity()) + return true; + + data->endPosition = fragment.transform.mapPoint(data->endPosition); + return true; +} + +FloatPoint SVGTextQuery::endPositionOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return FloatPoint(); + + EndPositionOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::endPositionOfCharacterCallback); + return data.endPosition; +} + +// rotationOfCharacter() implementation +struct RotationOfCharacterData : SVGTextQuery::Data { + RotationOfCharacterData(unsigned queryPosition) + : position(queryPosition) + , rotation(0) + { + } + + unsigned position; + float rotation; +}; + +bool SVGTextQuery::rotationOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + RotationOfCharacterData* data = static_cast<RotationOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + AffineTransform newTransform(fragment.transform); + newTransform.scale(1 / fragment.transform.xScale(), 1 / fragment.transform.yScale()); + data->rotation = narrowPrecisionToFloat(rad2deg(atan2(newTransform.b(), newTransform.a()))); + return true; +} + +float SVGTextQuery::rotationOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return 0; + + RotationOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::rotationOfCharacterCallback); + return data.rotation; +} + +// extentOfCharacter() implementation +struct ExtentOfCharacterData : SVGTextQuery::Data { + ExtentOfCharacterData(unsigned queryPosition) + : position(queryPosition) + { + } + + unsigned position; + FloatRect extent; +}; + +static inline void calculateGlyphBoundaries(SVGTextQuery::Data* queryData, const SVGTextFragment& fragment, int startPosition, FloatRect& extent) +{ + extent.setLocation(FloatPoint(fragment.x, fragment.y - queryData->textRenderer->style()->font().ascent())); + + if (startPosition) { + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset, startPosition); + if (queryData->isVerticalText) + extent.move(0, metrics.height()); + else + extent.move(metrics.width(), 0); + } + + SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(queryData->textRenderer, fragment.positionListOffset + startPosition, 1); + extent.setSize(FloatSize(metrics.width(), metrics.height())); + + if (fragment.transform.isIdentity()) + return; + + extent = fragment.transform.mapRect(extent); +} + +bool SVGTextQuery::extentOfCharacterCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + ExtentOfCharacterData* data = static_cast<ExtentOfCharacterData*>(queryData); + + int startPosition = data->position; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + return false; + + calculateGlyphBoundaries(queryData, fragment, startPosition, data->extent); + return true; +} + +FloatRect SVGTextQuery::extentOfCharacter(unsigned position) const +{ + if (m_textBoxes.isEmpty()) + return FloatRect(); + + ExtentOfCharacterData data(position); + executeQuery(&data, &SVGTextQuery::extentOfCharacterCallback); + return data.extent; +} + +// characterNumberAtPosition() implementation +struct CharacterNumberAtPositionData : SVGTextQuery::Data { + CharacterNumberAtPositionData(const FloatPoint& queryPosition) + : position(queryPosition) + { + } + + FloatPoint position; +}; + +bool SVGTextQuery::characterNumberAtPositionCallback(Data* queryData, const SVGTextFragment& fragment) const +{ + CharacterNumberAtPositionData* data = static_cast<CharacterNumberAtPositionData*>(queryData); + + FloatRect extent; + for (unsigned i = 0; i < fragment.length; ++i) { + int startPosition = data->processedCharacters + i; + int endPosition = startPosition + 1; + if (!mapStartEndPositionsIntoFragmentCoordinates(queryData, fragment, startPosition, endPosition)) + continue; + + calculateGlyphBoundaries(queryData, fragment, startPosition, extent); + if (extent.contains(data->position)) { + data->processedCharacters += i; + return true; + } + } + + return false; +} + +int SVGTextQuery::characterNumberAtPosition(const FloatPoint& position) const +{ + if (m_textBoxes.isEmpty()) + return -1; + + CharacterNumberAtPositionData data(position); + if (!executeQuery(&data, &SVGTextQuery::characterNumberAtPositionCallback)) + return -1; + + return data.processedCharacters; +} + +} + +#endif diff --git a/Source/WebCore/rendering/svg/SVGTextQuery.h b/Source/WebCore/rendering/svg/SVGTextQuery.h new file mode 100644 index 0000000..9a671f4 --- /dev/null +++ b/Source/WebCore/rendering/svg/SVGTextQuery.h @@ -0,0 +1,75 @@ +/* + Copyright (C) Research In Motion Limited 2010. All rights reserved. + + 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. +*/ + +#ifndef SVGTextQuery_h +#define SVGTextQuery_h + +#if ENABLE(SVG) +#include "FloatRect.h" +#include "SVGTextFragment.h" +#include <wtf/Vector.h> + +namespace WebCore { + +class InlineFlowBox; +class RenderObject; +class SVGInlineTextBox; + +class SVGTextQuery { +public: + SVGTextQuery(RenderObject*); + + unsigned numberOfCharacters() const; + float textLength() const; + float subStringLength(unsigned startPosition, unsigned length) const; + FloatPoint startPositionOfCharacter(unsigned position) const; + FloatPoint endPositionOfCharacter(unsigned position) const; + float rotationOfCharacter(unsigned position) const; + FloatRect extentOfCharacter(unsigned position) const; + int characterNumberAtPosition(const FloatPoint&) const; + + // Public helper struct. Private classes in SVGTextQuery inherit from it. + struct Data; + +private: + typedef bool (SVGTextQuery::*ProcessTextFragmentCallback)(Data*, const SVGTextFragment&) const; + bool executeQuery(Data*, ProcessTextFragmentCallback) const; + + void collectTextBoxesInFlowBox(InlineFlowBox*); + bool mapStartEndPositionsIntoFragmentCoordinates(Data*, const SVGTextFragment&, int& startPosition, int& endPosition) const; + void modifyStartEndPositionsRespectingLigatures(Data*, int& startPosition, int& endPosition) const; + +private: + bool numberOfCharactersCallback(Data*, const SVGTextFragment&) const; + bool textLengthCallback(Data*, const SVGTextFragment&) const; + bool subStringLengthCallback(Data*, const SVGTextFragment&) const; + bool startPositionOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool endPositionOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool rotationOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool extentOfCharacterCallback(Data*, const SVGTextFragment&) const; + bool characterNumberAtPositionCallback(Data*, const SVGTextFragment&) const; + +private: + Vector<SVGInlineTextBox*> m_textBoxes; +}; + +} + +#endif +#endif |