|
1 /* |
|
2 Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org> |
|
3 Copyright (C) Research In Motion Limited 2010. All rights reserved. |
|
4 |
|
5 This library is free software; you can redistribute it and/or |
|
6 modify it under the terms of the GNU Library General Public |
|
7 License as published by the Free Software Foundation; either |
|
8 version 2 of the License, or (at your option) any later version. |
|
9 |
|
10 This library is distributed in the hope that it will be useful, |
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 Library General Public License for more details. |
|
14 |
|
15 You should have received a copy of the GNU Library General Public License |
|
16 along with this library; see the file COPYING.LIB. If not, write to |
|
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
|
18 Boston, MA 02110-1301, USA. |
|
19 */ |
|
20 |
|
21 #include "config.h" |
|
22 #include "SVGTextLayoutUtilities.h" |
|
23 |
|
24 #if ENABLE(SVG) |
|
25 #include "FloatPoint.h" |
|
26 #include "InlineTextBox.h" |
|
27 #include "RenderObject.h" |
|
28 #include "SVGCharacterData.h" |
|
29 #include "SVGCharacterLayoutInfo.h" |
|
30 #include "SVGFontElement.h" |
|
31 #include "SVGRenderStyle.h" |
|
32 #include "SVGTextChunkLayoutInfo.h" |
|
33 #include "TextRun.h" |
|
34 #include "UnicodeRange.h" |
|
35 |
|
36 #include <float.h> |
|
37 |
|
38 namespace WebCore { |
|
39 |
|
40 bool isVerticalWritingMode(const SVGRenderStyle* style) |
|
41 { |
|
42 return style->writingMode() == WM_TBRL || style->writingMode() == WM_TB; |
|
43 } |
|
44 |
|
45 static inline EAlignmentBaseline dominantBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) |
|
46 { |
|
47 ASSERT(text); |
|
48 |
|
49 const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; |
|
50 ASSERT(style); |
|
51 |
|
52 const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; |
|
53 |
|
54 EDominantBaseline baseline = style->dominantBaseline(); |
|
55 if (baseline == DB_AUTO) { |
|
56 if (isVerticalText) |
|
57 baseline = DB_CENTRAL; |
|
58 else |
|
59 baseline = DB_ALPHABETIC; |
|
60 } |
|
61 |
|
62 switch (baseline) { |
|
63 case DB_USE_SCRIPT: |
|
64 // TODO: The dominant-baseline and the baseline-table components are set by |
|
65 // determining the predominant script of the character data content. |
|
66 return AB_ALPHABETIC; |
|
67 case DB_NO_CHANGE: |
|
68 { |
|
69 if (parentStyle) |
|
70 return dominantBaselineToShift(isVerticalText, text->parent(), font); |
|
71 |
|
72 ASSERT_NOT_REACHED(); |
|
73 return AB_AUTO; |
|
74 } |
|
75 case DB_RESET_SIZE: |
|
76 { |
|
77 if (parentStyle) |
|
78 return dominantBaselineToShift(isVerticalText, text->parent(), font); |
|
79 |
|
80 ASSERT_NOT_REACHED(); |
|
81 return AB_AUTO; |
|
82 } |
|
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 |
|
105 float alignmentBaselineToShift(bool isVerticalText, const RenderObject* text, const Font& font) |
|
106 { |
|
107 ASSERT(text); |
|
108 |
|
109 const SVGRenderStyle* style = text->style() ? text->style()->svgStyle() : 0; |
|
110 ASSERT(style); |
|
111 |
|
112 const SVGRenderStyle* parentStyle = text->parent() && text->parent()->style() ? text->parent()->style()->svgStyle() : 0; |
|
113 |
|
114 EAlignmentBaseline baseline = style->alignmentBaseline(); |
|
115 if (baseline == AB_AUTO) { |
|
116 if (parentStyle && style->dominantBaseline() == DB_AUTO) |
|
117 baseline = dominantBaselineToShift(isVerticalText, text->parent(), font); |
|
118 else |
|
119 baseline = dominantBaselineToShift(isVerticalText, text, font); |
|
120 |
|
121 ASSERT(baseline != AB_AUTO); |
|
122 } |
|
123 |
|
124 // Note: http://wiki.apache.org/xmlgraphics-fop/LineLayout/AlignmentHandling |
|
125 switch (baseline) { |
|
126 case AB_BASELINE: |
|
127 { |
|
128 if (parentStyle) |
|
129 return dominantBaselineToShift(isVerticalText, text->parent(), font); |
|
130 |
|
131 return 0.0f; |
|
132 } |
|
133 case AB_BEFORE_EDGE: |
|
134 case AB_TEXT_BEFORE_EDGE: |
|
135 return font.ascent(); |
|
136 case AB_MIDDLE: |
|
137 return font.xHeight() / 2.0f; |
|
138 case AB_CENTRAL: |
|
139 // Not needed, we're taking this into account already for vertical text! |
|
140 // return (font.ascent() - font.descent()) / 2.0f; |
|
141 return 0.0f; |
|
142 case AB_AFTER_EDGE: |
|
143 case AB_TEXT_AFTER_EDGE: |
|
144 case AB_IDEOGRAPHIC: |
|
145 return font.descent(); |
|
146 case AB_ALPHABETIC: |
|
147 return 0.0f; |
|
148 case AB_HANGING: |
|
149 return font.ascent() * 8.0f / 10.0f; |
|
150 case AB_MATHEMATICAL: |
|
151 return font.ascent() / 2.0f; |
|
152 default: |
|
153 ASSERT_NOT_REACHED(); |
|
154 return 0.0f; |
|
155 } |
|
156 } |
|
157 |
|
158 float glyphOrientationToAngle(const SVGRenderStyle* svgStyle, bool isVerticalText, const UChar& character) |
|
159 { |
|
160 switch (isVerticalText ? svgStyle->glyphOrientationVertical() : svgStyle->glyphOrientationHorizontal()) { |
|
161 case GO_AUTO: |
|
162 { |
|
163 // Spec: Fullwidth ideographic and fullwidth Latin text will be set with a glyph-orientation of 0-degrees. |
|
164 // Text which is not fullwidth will be set with a glyph-orientation of 90-degrees. |
|
165 unsigned int unicodeRange = findCharUnicodeRange(character); |
|
166 if (unicodeRange == cRangeSetLatin || unicodeRange == cRangeArabic) |
|
167 return 90.0f; |
|
168 |
|
169 return 0.0f; |
|
170 } |
|
171 case GO_90DEG: |
|
172 return 90.0f; |
|
173 case GO_180DEG: |
|
174 return 180.0f; |
|
175 case GO_270DEG: |
|
176 return 270.0f; |
|
177 case GO_0DEG: |
|
178 default: |
|
179 return 0.0f; |
|
180 } |
|
181 } |
|
182 |
|
183 static inline bool glyphOrientationIsMultiplyOf180Degrees(float orientationAngle) |
|
184 { |
|
185 return fabsf(fmodf(orientationAngle, 180.0f)) == 0.0f; |
|
186 } |
|
187 |
|
188 float applyGlyphAdvanceAndShiftRespectingOrientation(bool isVerticalText, float orientationAngle, float glyphWidth, float glyphHeight, const Font& font, SVGChar& svgChar, float& xOrientationShift, float& yOrientationShift) |
|
189 { |
|
190 bool orientationIsMultiplyOf180Degrees = glyphOrientationIsMultiplyOf180Degrees(orientationAngle); |
|
191 |
|
192 // The function is based on spec requirements: |
|
193 // |
|
194 // Spec: If the 'glyph-orientation-horizontal' results in an orientation angle that is not a multiple of |
|
195 // of 180 degrees, then the current text position is incremented according to the vertical metrics of the glyph. |
|
196 // |
|
197 // Spec: If if the 'glyph-orientation-vertical' results in an orientation angle that is not a multiple of |
|
198 // 180 degrees,then the current text position is incremented according to the horizontal metrics of the glyph. |
|
199 |
|
200 // vertical orientation handling |
|
201 if (isVerticalText) { |
|
202 if (orientationAngle == 0.0f) { |
|
203 xOrientationShift = -glyphWidth / 2.0f; |
|
204 yOrientationShift = font.ascent(); |
|
205 } else if (orientationAngle == 90.0f) { |
|
206 xOrientationShift = -glyphHeight; |
|
207 yOrientationShift = font.descent(); |
|
208 svgChar.orientationShiftY = -font.ascent(); |
|
209 } else if (orientationAngle == 270.0f) { |
|
210 xOrientationShift = glyphHeight; |
|
211 yOrientationShift = font.descent(); |
|
212 svgChar.orientationShiftX = -glyphWidth; |
|
213 svgChar.orientationShiftY = -font.ascent(); |
|
214 } else if (orientationAngle == 180.0f) { |
|
215 yOrientationShift = font.ascent(); |
|
216 svgChar.orientationShiftX = -glyphWidth / 2.0f; |
|
217 svgChar.orientationShiftY = font.ascent() - font.descent(); |
|
218 } |
|
219 |
|
220 // vertical advance calculation |
|
221 if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) |
|
222 return glyphWidth; |
|
223 |
|
224 return glyphHeight; |
|
225 } |
|
226 |
|
227 // horizontal orientation handling |
|
228 if (orientationAngle == 90.0f) { |
|
229 xOrientationShift = glyphWidth / 2.0f; |
|
230 yOrientationShift = -font.descent(); |
|
231 svgChar.orientationShiftX = -glyphWidth / 2.0f - font.descent(); |
|
232 svgChar.orientationShiftY = font.descent(); |
|
233 } else if (orientationAngle == 270.0f) { |
|
234 xOrientationShift = -glyphWidth / 2.0f; |
|
235 yOrientationShift = -font.descent(); |
|
236 svgChar.orientationShiftX = -glyphWidth / 2.0f + font.descent(); |
|
237 svgChar.orientationShiftY = glyphHeight; |
|
238 } else if (orientationAngle == 180.0f) { |
|
239 xOrientationShift = glyphWidth / 2.0f; |
|
240 svgChar.orientationShiftX = -glyphWidth / 2.0f; |
|
241 svgChar.orientationShiftY = font.ascent() - font.descent(); |
|
242 } |
|
243 |
|
244 // horizontal advance calculation |
|
245 if (orientationAngle != 0.0f && !orientationIsMultiplyOf180Degrees) |
|
246 return glyphHeight; |
|
247 |
|
248 return glyphWidth; |
|
249 } |
|
250 |
|
251 FloatPoint topLeftPositionOfCharacterRange(Vector<SVGChar>::iterator it, Vector<SVGChar>::iterator end) |
|
252 { |
|
253 float lowX = FLT_MAX, lowY = FLT_MAX; |
|
254 for (; it != end; ++it) { |
|
255 if (it->isHidden()) |
|
256 continue; |
|
257 |
|
258 float x = (*it).x; |
|
259 float y = (*it).y; |
|
260 |
|
261 if (x < lowX) |
|
262 lowX = x; |
|
263 |
|
264 if (y < lowY) |
|
265 lowY = y; |
|
266 } |
|
267 |
|
268 return FloatPoint(lowX, lowY); |
|
269 } |
|
270 |
|
271 float cummulatedWidthOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) |
|
272 { |
|
273 ASSERT(!range.isOpen()); |
|
274 ASSERT(range.isClosed()); |
|
275 ASSERT(range.box->isSVGInlineTextBox()); |
|
276 |
|
277 InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box); |
|
278 RenderText* text = textBox->textRenderer(); |
|
279 RenderStyle* style = text->style(); |
|
280 return style->font().floatWidth(svgTextRunForInlineTextBox(text->characters() + textBox->start() + range.startOffset, range.endOffset - range.startOffset, style, textBox)); |
|
281 } |
|
282 |
|
283 float cummulatedHeightOfInlineBoxCharacterRange(SVGInlineBoxCharacterRange& range) |
|
284 { |
|
285 ASSERT(!range.isOpen()); |
|
286 ASSERT(range.isClosed()); |
|
287 ASSERT(range.box->isSVGInlineTextBox()); |
|
288 |
|
289 InlineTextBox* textBox = static_cast<InlineTextBox*>(range.box); |
|
290 return (range.endOffset - range.startOffset) * textBox->textRenderer()->style()->font().height(); |
|
291 } |
|
292 |
|
293 TextRun svgTextRunForInlineTextBox(const UChar* characters, int length, const RenderStyle* style, const InlineTextBox* textBox) |
|
294 { |
|
295 ASSERT(textBox); |
|
296 ASSERT(style); |
|
297 |
|
298 TextRun run(characters |
|
299 , length |
|
300 , false /* allowTabs */ |
|
301 , 0 /* xPos, only relevant with allowTabs=true */ |
|
302 , 0 /* padding, only relevant for justified text, not relevant for SVG */ |
|
303 , textBox->direction() == RTL |
|
304 , textBox->m_dirOverride || style->visuallyOrdered() /* directionalOverride */); |
|
305 |
|
306 #if ENABLE(SVG_FONTS) |
|
307 run.setReferencingRenderObject(textBox->textRenderer()->parent()); |
|
308 #endif |
|
309 |
|
310 // Disable any word/character rounding. |
|
311 run.disableRoundingHacks(); |
|
312 |
|
313 // We handle letter & word spacing ourselves. |
|
314 run.disableSpacing(); |
|
315 return run; |
|
316 } |
|
317 |
|
318 float calculateCSSKerning(const RenderStyle* style) |
|
319 { |
|
320 const Font& font = style->font(); |
|
321 const SVGRenderStyle* svgStyle = style->svgStyle(); |
|
322 |
|
323 float kerning = 0.0f; |
|
324 if (CSSPrimitiveValue* primitive = static_cast<CSSPrimitiveValue*>(svgStyle->kerning())) { |
|
325 kerning = primitive->getFloatValue(); |
|
326 |
|
327 if (primitive->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE && font.pixelSize()) |
|
328 kerning = kerning / 100.0f * font.pixelSize(); |
|
329 } |
|
330 |
|
331 return kerning; |
|
332 } |
|
333 |
|
334 bool applySVGKerning(SVGCharacterLayoutInfo& info, const RenderStyle* style, SVGLastGlyphInfo& lastGlyph, const String& unicodeString, const String& glyphName, bool isVerticalText) |
|
335 { |
|
336 #if ENABLE(SVG_FONTS) |
|
337 float kerning = 0.0f; |
|
338 |
|
339 const Font& font = style->font(); |
|
340 if (!font.isSVGFont()) { |
|
341 lastGlyph.isValid = false; |
|
342 return false; |
|
343 } |
|
344 |
|
345 SVGFontElement* svgFont = font.svgFont(); |
|
346 ASSERT(svgFont); |
|
347 |
|
348 if (lastGlyph.isValid) { |
|
349 if (isVerticalText) |
|
350 kerning = svgFont->verticalKerningForPairOfStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeString, glyphName); |
|
351 else |
|
352 kerning = svgFont->horizontalKerningForPairOfStringsAndGlyphs(lastGlyph.unicode, lastGlyph.glyphName, unicodeString, glyphName); |
|
353 } |
|
354 |
|
355 lastGlyph.unicode = unicodeString; |
|
356 lastGlyph.glyphName = glyphName; |
|
357 lastGlyph.isValid = true; |
|
358 kerning *= style->font().size() / style->font().primaryFont()->unitsPerEm(); |
|
359 |
|
360 if (kerning != 0.0f) { |
|
361 if (isVerticalText) |
|
362 info.cury -= kerning; |
|
363 else |
|
364 info.curx -= kerning; |
|
365 return true; |
|
366 } |
|
367 #else |
|
368 UNUSED_PARAM(info); |
|
369 UNUSED_PARAM(item); |
|
370 UNUSED_PARAM(lastGlyph); |
|
371 UNUSED_PARAM(unicodeString); |
|
372 UNUSED_PARAM(glyphName); |
|
373 #endif |
|
374 return false; |
|
375 } |
|
376 |
|
377 } |
|
378 |
|
379 #endif |