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 blink {
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
71    EDominantBaseline baseline = style.dominantBaseline();
72    if (baseline == DB_AUTO) {
73        if (isVerticalText)
74            baseline = DB_CENTRAL;
75        else
76            baseline = DB_ALPHABETIC;
77    }
78
79    switch (baseline) {
80    case DB_USE_SCRIPT:
81        // FIXME: The dominant-baseline and the baseline-table components are set by determining the predominant script of the character data content.
82        return AB_ALPHABETIC;
83    case DB_NO_CHANGE:
84        return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
85    case DB_RESET_SIZE:
86        return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
87    case DB_IDEOGRAPHIC:
88        return AB_IDEOGRAPHIC;
89    case DB_ALPHABETIC:
90        return AB_ALPHABETIC;
91    case DB_HANGING:
92        return AB_HANGING;
93    case DB_MATHEMATICAL:
94        return AB_MATHEMATICAL;
95    case DB_CENTRAL:
96        return AB_CENTRAL;
97    case DB_MIDDLE:
98        return AB_MIDDLE;
99    case DB_TEXT_AFTER_EDGE:
100        return AB_TEXT_AFTER_EDGE;
101    case DB_TEXT_BEFORE_EDGE:
102        return AB_TEXT_BEFORE_EDGE;
103    default:
104        ASSERT_NOT_REACHED();
105        return AB_AUTO;
106    }
107}
108
109float SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject* textRenderer) const
110{
111    ASSERT(textRenderer);
112    ASSERT(textRenderer->style());
113    ASSERT(textRenderer->parent());
114
115    const RenderObject* textRendererParent = textRenderer->parent();
116    ASSERT(textRendererParent);
117
118    EAlignmentBaseline baseline = textRenderer->style()->svgStyle().alignmentBaseline();
119    if (baseline == AB_AUTO || baseline == AB_BASELINE) {
120        baseline = dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
121        ASSERT(baseline != AB_AUTO && baseline != AB_BASELINE);
122    }
123
124    const FontMetrics& fontMetrics = m_font.fontMetrics();
125
126    // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
127    switch (baseline) {
128    case AB_BEFORE_EDGE:
129    case AB_TEXT_BEFORE_EDGE:
130        return fontMetrics.floatAscent();
131    case AB_MIDDLE:
132        return fontMetrics.xHeight() / 2;
133    case AB_CENTRAL:
134        return (fontMetrics.floatAscent() - fontMetrics.floatDescent()) / 2;
135    case AB_AFTER_EDGE:
136    case AB_TEXT_AFTER_EDGE:
137    case AB_IDEOGRAPHIC:
138        return -fontMetrics.floatDescent();
139    case AB_ALPHABETIC:
140        return 0;
141    case AB_HANGING:
142        return fontMetrics.floatAscent() * 8 / 10.f;
143    case AB_MATHEMATICAL:
144        return fontMetrics.floatAscent() / 2;
145    case AB_BASELINE:
146    default:
147        ASSERT_NOT_REACHED();
148        return 0;
149    }
150}
151
152float SVGTextLayoutEngineBaseline::calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle& style, const UChar& character) const
153{
154    switch (isVerticalText ? style.glyphOrientationVertical() : style.glyphOrientationHorizontal()) {
155    case GO_AUTO: {
156        // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
157        // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
158        unsigned unicodeRange = findCharUnicodeRange(character);
159        if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic)
160            return 90;
161
162        return 0;
163    }
164    case GO_90DEG:
165        return 90;
166    case GO_180DEG:
167        return 180;
168    case GO_270DEG:
169        return 270;
170    case GO_0DEG:
171    default:
172        return 0;
173    }
174}
175
176static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
177{
178    return !fabsf(fmodf(orientationAngle, 180));
179}
180
181float SVGTextLayoutEngineBaseline::calculateGlyphAdvanceAndOrientation(bool isVerticalText, const SVGTextMetrics& metrics, float angle, float& xOrientationShift, float& yOrientationShift) const
182{
183    bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(angle);
184
185    // The function is based on spec requirements:
186    //
187    // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
188    // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
189    //
190    // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
191    // 180 degrees, then the current text position is incremented according to the horizontal metrics of the glyph.
192
193    const FontMetrics& fontMetrics = m_font.fontMetrics();
194
195    // Vertical orientation handling.
196    if (isVerticalText) {
197        float ascentMinusDescent = fontMetrics.floatAscent() - fontMetrics.floatDescent();
198        if (!angle) {
199            xOrientationShift = (ascentMinusDescent - metrics.width()) / 2;
200            yOrientationShift = fontMetrics.floatAscent();
201        } else if (angle == 180)
202            xOrientationShift = (ascentMinusDescent + metrics.width()) / 2;
203        else if (angle == 270) {
204            yOrientationShift = metrics.width();
205            xOrientationShift = ascentMinusDescent;
206        }
207
208        // Vertical advance calculation.
209        if (angle && !orientationIsMultiplyOf180Degrees)
210            return metrics.width();
211
212        return metrics.height();
213    }
214
215    // Horizontal orientation handling.
216    if (angle == 90)
217        yOrientationShift = -metrics.width();
218    else if (angle == 180) {
219        xOrientationShift = metrics.width();
220        yOrientationShift = -fontMetrics.floatAscent();
221    } else if (angle == 270)
222        xOrientationShift = metrics.width();
223
224    // Horizontal advance calculation.
225    if (angle && !orientationIsMultiplyOf180Degrees)
226        return metrics.height();
227
228    return metrics.width();
229}
230
231}
232