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
22#include "core/rendering/svg/SVGTextLayoutEngineBaseline.h"
23
24#include "core/rendering/RenderObject.h"
25#include "core/rendering/style/SVGRenderStyle.h"
26#include "core/rendering/svg/SVGTextMetrics.h"
27#include "core/svg/SVGLengthContext.h"
28#include "platform/fonts/Font.h"
29#include "platform/text/UnicodeRange.h"
30
31namespace WebCore {
32
33SVGTextLayoutEngineBaseline::SVGTextLayoutEngineBaseline(const Font& font)
34    : m_font(font)
35{
36}
37
38float SVGTextLayoutEngineBaseline::calculateBaselineShift(const SVGRenderStyle* style, SVGElement* contextElement) const
39{
40    if (style->baselineShift() == BS_LENGTH) {
41        RefPtr<SVGLength> baselineShiftValueLength = style->baselineShiftValue();
42        if (baselineShiftValueLength->unitType() == LengthTypePercentage)
43            return baselineShiftValueLength->valueAsPercentage() * m_font.fontDescription().computedPixelSize();
44
45        SVGLengthContext lengthContext(contextElement);
46        return baselineShiftValueLength->value(lengthContext);
47    }
48
49    switch (style->baselineShift()) {
50    case BS_BASELINE:
51        return 0;
52    case BS_SUB:
53        return -m_font.fontMetrics().floatHeight() / 2;
54    case BS_SUPER:
55        return m_font.fontMetrics().floatHeight() / 2;
56    default:
57        ASSERT_NOT_REACHED();
58        return 0;
59    }
60}
61
62EAlignmentBaseline SVGTextLayoutEngineBaseline::dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const
63{
64    ASSERT(textRenderer);
65    ASSERT(textRenderer->style());
66    ASSERT(textRenderer->parent());
67    ASSERT(textRenderer->parent()->style());
68
69    const SVGRenderStyle* style = textRenderer->style()->svgStyle();
70    ASSERT(style);
71
72    EDominantBaseline baseline = style->dominantBaseline();
73    if (baseline == DB_AUTO) {
74        if (isVerticalText)
75            baseline = DB_CENTRAL;
76        else
77            baseline = DB_ALPHABETIC;
78    }
79
80    switch (baseline) {
81    case DB_USE_SCRIPT:
82        // FIXME: The dominant-baseline and the baseline-table components are set by determining the predominant script of the character data content.
83        return AB_ALPHABETIC;
84    case DB_NO_CHANGE:
85        return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
86    case DB_RESET_SIZE:
87        return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
88    case DB_IDEOGRAPHIC:
89        return AB_IDEOGRAPHIC;
90    case DB_ALPHABETIC:
91        return AB_ALPHABETIC;
92    case DB_HANGING:
93        return AB_HANGING;
94    case DB_MATHEMATICAL:
95        return AB_MATHEMATICAL;
96    case DB_CENTRAL:
97        return AB_CENTRAL;
98    case DB_MIDDLE:
99        return AB_MIDDLE;
100    case DB_TEXT_AFTER_EDGE:
101        return AB_TEXT_AFTER_EDGE;
102    case DB_TEXT_BEFORE_EDGE:
103        return AB_TEXT_BEFORE_EDGE;
104    default:
105        ASSERT_NOT_REACHED();
106        return AB_AUTO;
107    }
108}
109
110float SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const
111{
112    ASSERT(textRenderer);
113    ASSERT(textRenderer->style());
114    ASSERT(textRenderer->style()->svgStyle());
115    ASSERT(textRenderer->parent());
116
117    const RenderObject* textRendererParent = textRenderer->parent();
118    ASSERT(textRendererParent);
119
120    EAlignmentBaseline baseline = textRenderer->style()->svgStyle()->alignmentBaseline();
121    if (baseline == AB_AUTO) {
122        baseline = dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
123        ASSERT(baseline != AB_AUTO);
124    }
125
126    const FontMetrics& fontMetrics = m_font.fontMetrics();
127
128    // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
129    switch (baseline) {
130    case AB_BASELINE:
131        return dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
132    case AB_BEFORE_EDGE:
133    case AB_TEXT_BEFORE_EDGE:
134        return fontMetrics.floatAscent();
135    case AB_MIDDLE:
136        return fontMetrics.xHeight() / 2;
137    case AB_CENTRAL:
138        return (fontMetrics.floatAscent() - fontMetrics.floatDescent()) / 2;
139    case AB_AFTER_EDGE:
140    case AB_TEXT_AFTER_EDGE:
141    case AB_IDEOGRAPHIC:
142        return fontMetrics.floatDescent();
143    case AB_ALPHABETIC:
144        return 0;
145    case AB_HANGING:
146        return fontMetrics.floatAscent() * 8 / 10.f;
147    case AB_MATHEMATICAL:
148        return fontMetrics.floatAscent() / 2;
149    default:
150        ASSERT_NOT_REACHED();
151        return 0;
152    }
153}
154
155float SVGTextLayoutEngineBaseline::calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle* style, const UChar& character) const
156{
157    ASSERT(style);
158
159    switch (isVerticalText ? style->glyphOrientationVertical() : style->glyphOrientationHorizontal()) {
160    case GO_AUTO: {
161        // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
162        // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
163        unsigned unicodeRange = findCharUnicodeRange(character);
164        if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic)
165            return 90;
166
167        return 0;
168    }
169    case GO_90DEG:
170        return 90;
171    case GO_180DEG:
172        return 180;
173    case GO_270DEG:
174        return 270;
175    case GO_0DEG:
176    default:
177        return 0;
178    }
179}
180
181static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
182{
183    return !fabsf(fmodf(orientationAngle, 180));
184}
185
186float SVGTextLayoutEngineBaseline::calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics& metrics, float angle, float& xOrientationShift, float& yOrientationShift) const
187{
188    bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(angle);
189
190    // The function is based on spec requirements:
191    //
192    // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
193    // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
194    //
195    // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
196    // 180 degrees, then the current text position is incremented according to the horizontal metrics of the glyph.
197
198    const FontMetrics& fontMetrics = m_font.fontMetrics();
199
200    // Vertical orientation handling.
201    if (isVerticalText) {
202        float ascentMinusDescent = fontMetrics.floatAscent() - fontMetrics.floatDescent();
203        if (!angle) {
204            xOrientationShift = (ascentMinusDescent - metrics.width()) / 2;
205            yOrientationShift = fontMetrics.floatAscent();
206        } else if (angle == 180)
207            xOrientationShift = (ascentMinusDescent + metrics.width()) / 2;
208        else if (angle == 270) {
209            yOrientationShift = metrics.width();
210            xOrientationShift = ascentMinusDescent;
211        }
212
213        // Vertical advance calculation.
214        if (angle && !orientationIsMultiplyOf180Degrees)
215            return metrics.width();
216
217        return metrics.height();
218    }
219
220    // Horizontal orientation handling.
221    if (angle == 90)
222        yOrientationShift = -metrics.width();
223    else if (angle == 180) {
224        xOrientationShift = metrics.width();
225        yOrientationShift = -fontMetrics.floatAscent();
226    } else if (angle == 270)
227        xOrientationShift = metrics.width();
228
229    // Horizontal advance calculation.
230    if (angle && !orientationIsMultiplyOf180Degrees)
231        return metrics.height();
232
233    return metrics.width();
234}
235
236}
237