/* * Copyright (C) Research In Motion Limited 2010-2011. 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); // 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'; collectTextPositioningElements(textRoot, atCharacter, lastCharacter); if (!atCharacter) return; // Collect x/y/dx/dy/rotate values for each character, stored in the m_positioningLists.xValues()/etc. lists. buildLayoutAttributesForAllCharacters(textRoot, atCharacter); // Propagate layout attributes to each RenderSVGInlineText object, and the whole list to the RenderSVGText root. Vector& allAttributes = textRoot->layoutAttributes(); allAttributes.clear(); atCharacter = 0; lastCharacter = '\0'; propagateLayoutAttributes(textRoot, allAttributes, 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)); } 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; } static inline void processRenderSVGInlineText(RenderSVGInlineText* text, unsigned& atCharacter, UChar& lastCharacter) { if (shouldPreserveAllWhiteSpace(text->style())) { atCharacter += text->textLength(); return; } 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; } } void SVGTextLayoutAttributesBuilder::collectTextPositioningElements(RenderObject* start, unsigned& atCharacter, UChar& lastCharacter) { ASSERT(!start->isSVGText() || m_textPositions.isEmpty()); for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { if (child->isSVGInlineText()) { processRenderSVGInlineText(toRenderSVGInlineText(child), atCharacter, lastCharacter); continue; } if (!child->isSVGInline()) continue; SVGTextPositioningElement* element = SVGTextPositioningElement::elementFromRenderer(child); unsigned atPosition = m_textPositions.size(); if (element) m_textPositions.append(TextPosition(element, atCharacter)); collectTextPositioningElements(child, atCharacter, lastCharacter); if (!element) continue; // Update text position, after we're back from recursion. TextPosition& position = m_textPositions[atPosition]; ASSERT(!position.length); position.length = atCharacter - position.start; } } void SVGTextLayoutAttributesBuilder::buildLayoutAttributesForAllCharacters(RenderSVGText* textRoot, unsigned textLength) { ASSERT(textLength); SVGTextPositioningElement* outermostTextElement = SVGTextPositioningElement::elementFromRenderer(textRoot); ASSERT(outermostTextElement); // Fill the lists with the special emptyValue marker. m_positioningLists.fillWithEmptyValues(textLength); // Grab outermost element value lists and insert them in the m_positioningLists. TextPosition wholeTextPosition(outermostTextElement, 0, textLength); fillAttributesAtPosition(wholeTextPosition); // Handle x/y default attributes. float& xFirst = m_positioningLists.xValues.first(); if (xFirst == SVGTextLayoutAttributes::emptyValue()) xFirst = 0; float& yFirst = m_positioningLists.yValues.first(); if (yFirst == SVGTextLayoutAttributes::emptyValue()) yFirst = 0; // Fill m_positioningLists using child text positioning elements in top-down order. unsigned size = m_textPositions.size(); for (unsigned i = 0; i < size; ++i) fillAttributesAtPosition(m_textPositions[i]); // Now m_positioningLists.contains a x/y/dx/dy/rotate value for each character in the subtree. } void SVGTextLayoutAttributesBuilder::propagateLayoutAttributes(RenderObject* start, Vector& allAttributes, 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(text); attributes.reserveCapacity(textLength); unsigned valueListPosition = atCharacter; unsigned metricsLength = 1; SVGTextMetrics lastMetrics = SVGTextMetrics::emptyMetrics(); for (unsigned textPosition = 0; textPosition < textLength; textPosition += metricsLength) { const UChar& currentCharacter = characters[textPosition]; SVGTextMetrics startToCurrentMetrics = SVGTextMetrics::measureCharacterRange(text, 0, textPosition + 1); SVGTextMetrics currentMetrics = SVGTextMetrics::measureCharacterRange(text, textPosition, 1); // Frequent case for Arabic text: when measuring a single character the arabic isolated form is taken // when rendering the glyph "in context" (with it's surrounding characters) it changes due to shaping. // So whenever runWidthAdvance != currentMetrics.width(), we are processing a text run whose length is // not equal to the sum of the individual lengths of the glyphs, when measuring them isolated. float runWidthAdvance = startToCurrentMetrics.width() - lastMetrics.width(); if (runWidthAdvance != currentMetrics.width()) currentMetrics.setWidth(runWidthAdvance); lastMetrics = startToCurrentMetrics; metricsLength = currentMetrics.length(); if (!preserveWhiteSpace && characterIsSpace(currentCharacter) && characterIsSpaceOrNull(lastCharacter)) { attributes.positioningLists().appendEmptyValues(); attributes.textMetricsValues().append(SVGTextMetrics::emptyMetrics()); continue; } SVGTextLayoutAttributes::PositioningLists& positioningLists = attributes.positioningLists(); positioningLists.appendValuesFromPosition(m_positioningLists, valueListPosition); attributes.textMetricsValues().append(currentMetrics); // Pad x/y/dx/dy/rotate value lists with empty values, if the metrics span more than one character. if (metricsLength > 1) { for (unsigned i = 0; i < metricsLength - 1; ++i) positioningLists.appendEmptyValues(); } 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); fprintf(stderr, "BiDi properties: unicode-bidi=%i, block direction=%i\n", text->style()->unicodeBidi(), text->style()->direction()); attributes.dump(); #endif text->storeLayoutAttributes(attributes); allAttributes.append(attributes); atCharacter = valueListPosition; continue; } if (!child->isSVGInline()) continue; propagateLayoutAttributes(child, allAttributes, atCharacter, lastCharacter); } } static inline void fillListAtPosition(Vector& allValues, Vector& values, unsigned start) { unsigned valuesSize = values.size(); for (unsigned i = 0; i < valuesSize; ++i) allValues[start + i] = values[i]; } void SVGTextLayoutAttributesBuilder::fillAttributesAtPosition(const TextPosition& position) { Vector values; extractFloatValuesFromSVGLengthList(position.element, position.element->x(), values, position.length); fillListAtPosition(m_positioningLists.xValues, values, position.start); values.clear(); extractFloatValuesFromSVGLengthList(position.element, position.element->y(), values, position.length); fillListAtPosition(m_positioningLists.yValues, values, position.start); values.clear(); extractFloatValuesFromSVGLengthList(position.element, position.element->dx(), values, position.length); fillListAtPosition(m_positioningLists.dxValues, values, position.start); values.clear(); extractFloatValuesFromSVGLengthList(position.element, position.element->dy(), values, position.length); fillListAtPosition(m_positioningLists.dyValues, values, position.start); values.clear(); extractFloatValuesFromSVGNumberList(position.element->rotate(), values, position.length); fillListAtPosition(m_positioningLists.rotateValues, values, position.start); // The last rotation value always spans the whole scope. if (values.isEmpty()) return; float lastValue = values.last(); for (unsigned i = values.size(); i < position.length; ++i) m_positioningLists.rotateValues[position.start + i] = lastValue; } } #endif // ENABLE(SVG)