diff -r 000000000000 -r 4f2f89ce4247 WebCore/rendering/SVGTextLayoutUtilities.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/rendering/SVGTextLayoutUtilities.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,379 @@ +/* + Copyright (C) 2007 Nikolas Zimmermann + 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 "SVGTextLayoutUtilities.h" + +#if ENABLE(SVG) +#include "FloatPoint.h" +#include "InlineTextBox.h" +#include "RenderObject.h" +#include "SVGCharacterData.h" +#include "SVGCharacterLayoutInfo.h" +#include "SVGFontElement.h" +#include "SVGRenderStyle.h" +#include "SVGTextChunkLayoutInfo.h" +#include "TextRun.h" +#include "UnicodeRange.h" + +#include + +namespace WebCore { + +bool isVerticalWritingMode(const SVGRenderStyle* style) +{ + return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB; +} + +static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) +{ + ASSERT(text); + + const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; + ASSERT(style); + + const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; + + EDominantBaseline baseline = style->dominantBaseline(); + if (baseline == DB_AUTO) { + if (isVerticalText) + baseline = DB_CENTRAL; + else + baseline = DB_ALPHABETIC; + } + + switch (baseline) { + case DB_USE_SCRIPT: + // TODO: The dominant-baseline and the baseline-table components are set by + // determining the predominant script of the character data content. + return AB_ALPHABETIC; + case DB_NO_CHANGE: + { + if (parentStyle) + return dominantBaselineToShift(isVerticalText, text->parent(), font); + + ASSERT_NOT_REACHED(); + return AB_AUTO; + } + case DB_RESET_SIZE: + { + if (parentStyle) + return dominantBaselineToShift(isVerticalText, text->parent(), font); + + ASSERT_NOT_REACHED(); + return AB_AUTO; + } + case DB_IDEOGRAPHIC: + return AB_IDEOGRAPHIC; + case DB_ALPHABETIC: + return AB_ALPHABETIC; + case DB_HANGING: + return AB_HANGING; + case DB_MATHEMATICAL: + return AB_MATHEMATICAL; + case DB_CENTRAL: + return AB_CENTRAL; + case DB_MIDDLE: + return AB_MIDDLE; + case DB_TEXT_AFTER_EDGE: + return AB_TEXT_AFTER_EDGE; + case DB_TEXT_BEFORE_EDGE: + return AB_TEXT_BEFORE_EDGE; + default: + ASSERT_NOT_REACHED(); + return AB_AUTO; + } +} + +float alignmentBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) +{ + ASSERT(text); + + const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; + ASSERT(style); + + const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; + + EAlignmentBaseline baseline = style->alignmentBaseline(); + if (baseline == AB_AUTO) { + if (parentStyle && style->dominantBaseline() == DB_AUTO) + baseline = dominantBaselineToShift(isVerticalText, text->parent(), font); + else + baseline = dominantBaselineToShift(isVerticalText, text, font); + + ASSERT(baseline != AB_AUTO); + } + + // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling + switch (baseline) { + case AB_BASELINE: + { + if (parentStyle) + return dominantBaselineToShift(isVerticalText, text->parent(), font); + + return 0.0f; + } + case AB_BEFORE_EDGE: + case AB_TEXT_BEFORE_EDGE: + return font.ascent(); + case AB_MIDDLE: + return font.xHeight() / 2.0f; + case AB_CENTRAL: + // Not needed, we're taking this into account already for vertical text! + // return (font.ascent() - font.descent()) / 2.0f; + return 0.0f; + case AB_AFTER_EDGE: + case AB_TEXT_AFTER_EDGE: + case AB_IDEOGRAPHIC: + return font.descent(); + case AB_ALPHABETIC: + return 0.0f; + case AB_HANGING: + return font.ascent() * 8.0f / 10.0f; + case AB_MATHEMATICAL: + return font.ascent() / 2.0f; + default: + ASSERT_NOT_REACHED(); + return 0.0f; + } +} + +float glyphOrientationToAngle(const SVGRenderStyle* svgStyle, bool isVerticalText, const UChar& character) +{ + switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->glyphOrientationHorizontal()) { + case GO_AUTO: + { + // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees. + // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees. + unsigned int unicodeRange = findCharUnicodeRange(character); + if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic) + return 90.0f; + + return 0.0f; + } + case GO_90DEG: + return 90.0f; + case GO_180DEG: + return 180.0f; + case GO_270DEG: + return 270.0f; + case GO_0DEG: + default: + return 0.0f; + } +} + +static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle) +{ + return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f; +} + +float applyGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font& font, SVGChar& svgChar, float& xOrientationShift, float& yOrientationShift) +{ + bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle); + + // The function is based on spec requirements: + // + // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of + // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph. + // + // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of + // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph. + + // vertical orientation handling + if (isVerticalText) { + if (orientationAngle == 0.0f) { + xOrientationShift = -glyphWidth / 2.0f; + yOrientationShift = font.ascent(); + } else if (orientationAngle == 90.0f) { + xOrientationShift = -glyphHeight; + yOrientationShift = font.descent(); + svgChar.orientationShiftY = -font.ascent(); + } else if (orientationAngle == 270.0f) { + xOrientationShift = glyphHeight; + yOrientationShift = font.descent(); + svgChar.orientationShiftX = -glyphWidth; + svgChar.orientationShiftY = -font.ascent(); + } else if (orientationAngle == 180.0f) { + yOrientationShift = font.ascent(); + svgChar.orientationShiftX = -glyphWidth / 2.0f; + svgChar.orientationShiftY = font.ascent() - font.descent(); + } + + // vertical advance calculation + if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) + return glyphWidth; + + return glyphHeight; + } + + // horizontal orientation handling + if (orientationAngle == 90.0f) { + xOrientationShift = glyphWidth / 2.0f; + yOrientationShift = -font.descent(); + svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent(); + svgChar.orientationShiftY = font.descent(); + } else if (orientationAngle == 270.0f) { + xOrientationShift = -glyphWidth / 2.0f; + yOrientationShift = -font.descent(); + svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent(); + svgChar.orientationShiftY = glyphHeight; + } else if (orientationAngle == 180.0f) { + xOrientationShift = glyphWidth / 2.0f; + svgChar.orientationShiftX = -glyphWidth / 2.0f; + svgChar.orientationShiftY = font.ascent() - font.descent(); + } + + // horizontal advance calculation + if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) + return glyphHeight; + + return glyphWidth; +} + +FloatPoint topLeftPositionOfCharacterRange(Vector::iterator it, Vector::iterator end) +{ + float lowX = FLT_MAX, lowY = FLT_MAX; + for (; it != end; ++it) { + if (it->isHidden()) + continue; + + float x = (*it).x; + float y = (*it).y; + + if (x < lowX) + lowX = x; + + if (y < lowY) + lowY = y; + } + + return FloatPoint(lowX, lowY); +} + +float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) +{ + ASSERT(!range.isOpen()); + ASSERT(range.isClosed()); + ASSERT(range.box->isSVGInlineTextBox()); + + InlineTextBox* textBox = static_cast(range.box); + RenderText* text = textBox->textRenderer(); + RenderStyle* style = text->style(); + return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox)); +} + +float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) +{ + ASSERT(!range.isOpen()); + ASSERT(range.isClosed()); + ASSERT(range.box->isSVGInlineTextBox()); + + InlineTextBox* textBox = static_cast(range.box); + return (range.endOffset - range.startOffset) * textBox->textRenderer()->style()->font().height(); +} + +TextRun svgTextRunForInlineTextBox(const UChar* characters, int length, const RenderStyle* style, const InlineTextBox* textBox) +{ + ASSERT(textBox); + ASSERT(style); + + TextRun run(characters + , length + , false /* allowTabs */ + , 0 /* xPos, only relevant with allowTabs=true */ + , 0 /* padding, only relevant for justified text, not relevant for SVG */ + , textBox->direction() == RTL + , textBox->m_dirOverride || style->visuallyOrdered() /* directionalOverride */); + +#if ENABLE(SVG_FONTS) + run.setReferencingRenderObject(textBox->textRenderer()->parent()); +#endif + + // Disable any word/character rounding. + run.disableRoundingHacks(); + + // We handle letter & word spacing ourselves. + run.disableSpacing(); + return run; +} + +float calculateCSSKerning(const RenderStyle* style) +{ + const Font& font = style->font(); + const SVGRenderStyle* svgStyle = style->svgStyle(); + + float kerning = 0.0f; + if (CSSPrimitiveValue* primitive = static_cast(svgStyle->kerning())) { + kerning = primitive->getFloatValue(); + + if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize()) + kerning = kerning / 100.0f * font.pixelSize(); + } + + return kerning; +} + +bool applySVGKerning(SVGCharacterLayoutInfo& info, const RenderStyle* style, SVGLastGlyphInfo& lastGlyph, const String& unicodeString, const String& glyphName, bool isVerticalText) +{ +#if ENABLE(SVG_FONTS) + float kerning = 0.0f; + + const Font& font = style->font(); + if (!font.isSVGFont()) { + lastGlyph.isValid = false; + return false; + } + + SVGFontElement* svgFont = font.svgFont(); + ASSERT(svgFont); + + if (lastGlyph.isValid) { + if (isVerticalText) + kerning = svgFont->verticalKerningForPairOfStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeString, glyphName); + else + kerning = svgFont->horizontalKerningForPairOfStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeString, glyphName); + } + + lastGlyph.unicode = unicodeString; + lastGlyph.glyphName = glyphName; + lastGlyph.isValid = true; + kerning *= style->font().size() / style->font().primaryFont()->unitsPerEm(); + + if (kerning != 0.0f) { + if (isVerticalText) + info.cury -= kerning; + else + info.curx -= kerning; + return true; + } +#else + UNUSED_PARAM(info); + UNUSED_PARAM(item); + UNUSED_PARAM(lastGlyph); + UNUSED_PARAM(unicodeString); + UNUSED_PARAM(glyphName); +#endif + return false; +} + +} + +#endif