/* * 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 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& 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& 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& 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 x/y default attributes. Vector& xValues = scope.attributes.xValues(); if (xValues.isEmpty()) xValues.append(0); Vector& 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* 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& 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)