/*
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 "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 <float.h>
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<SVGChar>::iterator it, Vector<SVGChar>::iterator end)
{
float lowX = FLT_MAX, lowY = FLT_MAX;
for (; it != end; ++it) {
if (it->isHidden())
continue;
float x = (*it).x;
float y = (*it).y;
if (x < lowX)
lowX = x;
if (y < lowY)
lowY = y;
}
return FloatPoint(lowX, lowY);
}
float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range)
{
ASSERT(!range.isOpen());
ASSERT(range.isClosed());
ASSERT(range.box->isSVGInlineTextBox());
InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box);
RenderText* text = textBox->textRenderer();
RenderStyle* style = text->style();
return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox));
}
float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range)
{
ASSERT(!range.isOpen());
ASSERT(range.isClosed());
ASSERT(range.box->isSVGInlineTextBox());
InlineTextBox* textBox = static_cast<InlineTextBox*>(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<CSSPrimitiveValue*>(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