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 "FontCascade.h"
24#include "RenderElement.h"
25#include "SVGLengthContext.h"
26#include "SVGRenderStyle.h"
27#include "SVGTextMetrics.h"
28
29namespace WebCore {
30
31SVGTextLayoutEngineBaseline::SVGTextLayoutEngineBaseline(const FontCascade& font)
32 : m_font(font)
33{
34}
35
36float SVGTextLayoutEngineBaseline::calculateBaselineShift(const SVGRenderStyle& style, SVGElement* context) const
37{
38 if (style.baselineShift() == BaselineShift::Length) {
39 auto baselineShiftValueLength = style.baselineShiftValue();
40 if (baselineShiftValueLength.unitType() == LengthTypePercentage)
41 return baselineShiftValueLength.valueAsPercentage() * m_font.pixelSize();
42
43 SVGLengthContext lengthContext(context);
44 return baselineShiftValueLength.value(lengthContext);
45 }
46
47 switch (style.baselineShift()) {
48 case BaselineShift::Baseline:
49 return 0;
50 case BaselineShift::Sub:
51 return -m_font.fontMetrics().floatHeight() / 2;
52 case BaselineShift::Super:
53 return m_font.fontMetrics().floatHeight() / 2;
54 case BaselineShift::Length:
55 break;
56 }
57 ASSERT_NOT_REACHED();
58 return 0;
59}
60
61AlignmentBaseline SVGTextLayoutEngineBaseline::dominantBaselineToAlignmentBaseline(bool isVerticalText, const RenderObject* textRenderer) const
62{
63 ASSERT(textRenderer);
64 ASSERT(textRenderer->parent());
65
66 const SVGRenderStyle& svgStyle = textRenderer->style().svgStyle();
67
68 DominantBaseline baseline = svgStyle.dominantBaseline();
69 if (baseline == DominantBaseline::Auto) {
70 if (isVerticalText)
71 baseline = DominantBaseline::Central;
72 else
73 baseline = DominantBaseline::Alphabetic;
74 }
75
76 switch (baseline) {
77 case DominantBaseline::UseScript:
78 // FIXME: The dominant-baseline and the baseline-table components are set by determining the predominant script of the character data content.
79 return AlignmentBaseline::Alphabetic;
80 case DominantBaseline::NoChange:
81 return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
82 case DominantBaseline::ResetSize:
83 return dominantBaselineToAlignmentBaseline(isVerticalText, textRenderer->parent());
84 case DominantBaseline::Ideographic:
85 return AlignmentBaseline::Ideographic;
86 case DominantBaseline::Alphabetic:
87 return AlignmentBaseline::Alphabetic;
88 case DominantBaseline::Hanging:
89 return AlignmentBaseline::Hanging;
90 case DominantBaseline::Mathematical:
91 return AlignmentBaseline::Mathematical;
92 case DominantBaseline::Central:
93 return AlignmentBaseline::Central;
94 case DominantBaseline::Middle:
95 return AlignmentBaseline::Middle;
96 case DominantBaseline::TextAfterEdge:
97 return AlignmentBaseline::TextAfterEdge;
98 case DominantBaseline::TextBeforeEdge:
99 return AlignmentBaseline::TextBeforeEdge;
100 default:
101 ASSERT_NOT_REACHED();
102 return AlignmentBaseline::Auto;
103 }
104}
105
106float SVGTextLayoutEngineBaseline::calculateAlignmentBaselineShift(bool isVerticalText, const RenderObject& textRenderer) const
107{
108 const RenderObject* textRendererParent = textRenderer.parent();
109 ASSERT(textRendererParent);
110
111 AlignmentBaseline baseline = textRenderer.style().svgStyle().alignmentBaseline();
112 if (baseline == AlignmentBaseline::Auto) {
113 baseline = dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent);
114 ASSERT(baseline != AlignmentBaseline::Auto);
115 }
116
117 const FontMetrics& fontMetrics = m_font.fontMetrics();
118
119 // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling
120 switch (baseline) {
121 case AlignmentBaseline::Baseline:
122 // FIXME: This seems wrong. Why are we returning an enum value converted to a float?
123 return static_cast<float>(dominantBaselineToAlignmentBaseline(isVerticalText, textRendererParent));
124 case AlignmentBaseline::BeforeEdge:
125 case AlignmentBaseline::TextBeforeEdge:
126 return fontMetrics.floatAscent();
127 case AlignmentBaseline::Middle:
128 return fontMetrics.xHeight() / 2;
129 case AlignmentBaseline::Central:
130 return (fontMetrics.floatAscent() - fontMetrics.floatDescent()) / 2;
131 case AlignmentBaseline::AfterEdge:
132 case AlignmentBaseline::TextAfterEdge:
133 case AlignmentBaseline::Ideographic:
134 return fontMetrics.floatDescent();
135 case AlignmentBaseline::Alphabetic:
136 return 0;
137 case AlignmentBaseline::Hanging:
138 return fontMetrics.floatAscent() * 8 / 10.f;
139 case AlignmentBaseline::Mathematical:
140 return fontMetrics.floatAscent() / 2;
141 case AlignmentBaseline::Auto:
142 ASSERT_NOT_REACHED();
143 return 0;
144 }
145 ASSERT_NOT_REACHED();
146 return 0;
147}
148
149float SVGTextLayoutEngineBaseline::calculateGlyphOrientationAngle(bool isVerticalText, const SVGRenderStyle& style, const UChar& character) const
150{
151 switch (isVerticalText ? style.glyphOrientationVertical() : style.glyphOrientationHorizontal()) {
152 case GlyphOrientation::Auto:
153 // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees.
154 // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees.
155 // FIXME: There's not an accurate way to tell if text is fullwidth by looking at a single character.
156 switch (static_cast<UEastAsianWidth>(u_getIntPropertyValue(character, UCHAR_EAST_ASIAN_WIDTH))) {
157 case U_EA_NEUTRAL:
158 case U_EA_HALFWIDTH:
159 case U_EA_NARROW:
160 return 90;
161 case U_EA_AMBIGUOUS:
162 case U_EA_FULLWIDTH:
163 case U_EA_WIDE:
164 return 0;
165 case U_EA_COUNT:
166 ASSERT_NOT_REACHED();
167 break;
168 }
169 ASSERT_NOT_REACHED();
170 break;
171 case GlyphOrientation::Degrees90:
172 return 90;
173 case GlyphOrientation::Degrees180:
174 return 180;
175 case GlyphOrientation::Degrees270:
176 return 270;
177 case GlyphOrientation::Degrees0:
178 return 0;
179 }
180 ASSERT_NOT_REACHED();
181 return 0;
182}
183
184static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle)
185{
186 return !fabsf(fmodf(orientationAngle, 180));
187}
188
189float SVGTextLayoutEngineBaseline::calculateGlyphAdvanceAndOrientation(bool isVerticalText, SVGTextMetrics& metrics, float angle, float& xOrientationShift, float& yOrientationShift) const
190{
191 bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(angle);
192
193 // The function is based on spec requirements:
194 //
195 // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of
196 // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph.
197 //
198 // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of
199 // 180 degrees, then the current text position is incremented according to the horizontal metrics of the glyph.
200
201 const FontMetrics& fontMetrics = m_font.fontMetrics();
202
203 // Vertical orientation handling.
204 if (isVerticalText) {
205 float ascentMinusDescent = fontMetrics.floatAscent() - fontMetrics.floatDescent();
206 if (!angle) {
207 xOrientationShift = (ascentMinusDescent - metrics.width()) / 2;
208 yOrientationShift = fontMetrics.floatAscent();
209 } else if (angle == 180)
210 xOrientationShift = (ascentMinusDescent + metrics.width()) / 2;
211 else if (angle == 270) {
212 yOrientationShift = metrics.width();
213 xOrientationShift = ascentMinusDescent;
214 }
215
216 // Vertical advance calculation.
217 if (angle && !orientationIsMultiplyOf180Degrees)
218 return metrics.width();
219
220 return metrics.height();
221 }
222
223 // Horizontal orientation handling.
224 if (angle == 90)
225 yOrientationShift = -metrics.width();
226 else if (angle == 180) {
227 xOrientationShift = metrics.width();
228 yOrientationShift = -fontMetrics.floatAscent();
229 } else if (angle == 270)
230 xOrientationShift = metrics.width();
231
232 // Horizontal advance calculation.
233 if (angle && !orientationIsMultiplyOf180Degrees)
234 return metrics.height();
235
236 return metrics.width();
237}
238
239}
240