1/*
2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "SVGTextLayoutEngineBaseline.h"
22
23#include "Font.h"
24#include "RenderElement.h"
25#include "SVGLengthContext.h"
26#include "SVGRenderStyle.h"
27#include "SVGTextMetrics.h"
28
29namespace WebCore {
30
31SVGTextLayoutEngineBaseline::SVGTextLayoutEngineBaseline(const Font& font)
32    : m_font(font)
33{
34}
35
36float SVGTextLayoutEngineBaseline::calculateBaselineShift(const SVGRenderStyle* style, SVGElement* contextElement) const
37{
38    if (style->baselineShift() == BS_LENGTH) {
39        SVGLength baselineShiftValueLength = style->baselineShiftValue();
40        if (baselineShiftValueLength.unitType() == LengthTypePercentage)
41            return baselineShiftValueLength.valueAsPercentage() * m_font.pixelSize();
42
43        SVGLengthContext lengthContext(contextElement);
44        return baselineShiftValueLength.value(lengthContext);
45    }
46
47    switch (style->baselineShift()) {
48    case BS_BASELINE:
49        return 0;
50    case BS_SUB:
51        return -m_font.fontMetrics().floatHeight() / 2;
52    case BS_SUPER:
53        return m_font.fontMetrics().floatHeight() / 2;
54    default:
55        ASSERT_NOT_REACHED();
56        return 0;
57    }
58}
59
60EAlignmentBaseline SVGTextLayoutEngineBaseline::dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const
61{
62    ASSERT(textRenderer);
63    ASSERT(textRenderer->parent());
64
65    const SVGRenderStyle& svgStyle = textRenderer->style().svgStyle();
66
67    EDominantBaseline baseline = svgStyle.dominantBaseline();
68    if (baseline == DB_AUTO) {
69        if (isVerticalText)
70            baseline = DB_CENTRAL;
71        else
72            baseline = DB_ALPHABETIC;
73    }
74
75    switch (baseline) {
76    case DB_USE_SCRIPT:
77        // FIXME: The dominant-baseline and the baseline-table components are set by determining the predominant script of the character data content.
78        return AB_ALPHABETIC;
79    case DB_NO_CHANGE:
80        return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
81    case DB_RESET_SIZE:
82        return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
83    case DB_IDEOGRAPHIC:
84        return AB_IDEOGRAPHIC;
85    case DB_ALPHABETIC:
86        return AB_ALPHABETIC;
87    case DB_HANGING:
88        return AB_HANGING;
89    case DB_MATHEMATICAL:
90        return AB_MATHEMATICAL;
91    case DB_CENTRAL:
92        return AB_CENTRAL;
93    case DB_MIDDLE:
94        return AB_MIDDLE;
95    case DB_TEXT_AFTER_EDGE:
96        return AB_TEXT_AFTER_EDGE;
97    case DB_TEXT_BEFORE_EDGE:
98        return AB_TEXT_BEFORE_EDGE;
99    default:
100        ASSERT_NOT_REACHED();
101        return AB_AUTO;
102    }
103}
104
105float SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const
106{
107    ASSERT(textRenderer);
108    ASSERT(textRenderer->parent());
109
110    const RenderObject* textRendererParent = textRenderer->parent();
111    ASSERT(textRendererParent);
112
113    EAlignmentBaseline baseline = textRenderer->style().svgStyle().alignmentBaseline();
114    if (baseline == AB_AUTO) {
115        baseline = dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
116        ASSERT(baseline != AB_AUTO);
117    }
118
119    const FontMetrics& fontMetrics = m_font.fontMetrics();
120
121    // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
122    switch (baseline) {
123    case AB_BASELINE:
124        return dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
125    case AB_BEFORE_EDGE:
126    case AB_TEXT_BEFORE_EDGE:
127        return fontMetrics.floatAscent();
128    case AB_MIDDLE:
129        return fontMetrics.xHeight() / 2;
130    case AB_CENTRAL:
131        return (fontMetrics.floatAscent() - fontMetrics.floatDescent()) / 2;
132    case AB_AFTER_EDGE:
133    case AB_TEXT_AFTER_EDGE:
134    case AB_IDEOGRAPHIC:
135        return fontMetrics.floatDescent();
136    case AB_ALPHABETIC:
137        return 0;
138    case AB_HANGING:
139        return fontMetrics.floatAscent() * 8 / 10.f;
140    case AB_MATHEMATICAL:
141        return fontMetrics.floatAscent() / 2;
142    default:
143        ASSERT_NOT_REACHED();
144        return 0;
145    }
146}
147
148float SVGTextLayoutEngineBaseline::calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle* style, const UChar& character) const
149{
150    ASSERT(style);
151
152    switch (isVerticalText ? style->glyphOrientationVertical() : style->glyphOrientationHorizontal()) {
153    case GO_AUTO:
154        // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
155        // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
156        // FIXME: There's not an accurate way to tell if text is fullwidth by looking at a single character.
157        switch (static_cast<UEastAsianWidth>(u_getIntPropertyValue(character, UCHAR_EAST_ASIAN_WIDTH))) {
158        case U_EA_NEUTRAL:
159        case U_EA_HALFWIDTH:
160        case U_EA_NARROW:
161            return 90;
162        case U_EA_AMBIGUOUS:
163        case U_EA_FULLWIDTH:
164        case U_EA_WIDE:
165            return 0;
166        case U_EA_COUNT:
167            ASSERT_NOT_REACHED();
168            break;
169        }
170        ASSERT_NOT_REACHED();
171        break;
172    case GO_90DEG:
173        return 90;
174    case GO_180DEG:
175        return 180;
176    case GO_270DEG:
177        return 270;
178    case GO_0DEG:
179        return 0;
180    }
181    ASSERT_NOT_REACHED();
182    return 0;
183}
184
185static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
186{
187    return !fabsf(fmodf(orientationAngle, 180));
188}
189
190float SVGTextLayoutEngineBaseline::calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics& metrics, float angle, float& xOrientationShift, float& yOrientationShift) const
191{
192    bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(angle);
193
194    // The function is based on spec requirements:
195    //
196    // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
197    // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
198    //
199    // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
200    // 180 degrees, then the current text position is incremented according to the horizontal metrics of the glyph.
201
202    const FontMetrics& fontMetrics = m_font.fontMetrics();
203
204    // Vertical orientation handling.
205    if (isVerticalText) {
206        float ascentMinusDescent = fontMetrics.floatAscent() - fontMetrics.floatDescent();
207        if (!angle) {
208            xOrientationShift = (ascentMinusDescent - metrics.width()) / 2;
209            yOrientationShift = fontMetrics.floatAscent();
210        } else if (angle == 180)
211            xOrientationShift = (ascentMinusDescent + metrics.width()) / 2;
212        else if (angle == 270) {
213            yOrientationShift = metrics.width();
214            xOrientationShift = ascentMinusDescent;
215        }
216
217        // Vertical advance calculation.
218        if (angle && !orientationIsMultiplyOf180Degrees)
219            return metrics.width();
220
221        return metrics.height();
222    }
223
224    // Horizontal orientation handling.
225    if (angle == 90)
226        yOrientationShift = -metrics.width();
227    else if (angle == 180) {
228        xOrientationShift = metrics.width();
229        yOrientationShift = -fontMetrics.floatAscent();
230    } else if (angle == 270)
231        xOrientationShift = metrics.width();
232
233    // Horizontal advance calculation.
234    if (angle && !orientationIsMultiplyOf180Degrees)
235        return metrics.height();
236
237    return metrics.width();
238}
239
240}
241