OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) Research In Motion Limited 2010-2012. 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 "core/layout/svg/SVGTextMetricsBuilder.h" | |
21 | |
22 #include "core/layout/api/LineLayoutSVGInlineText.h" | |
23 #include "core/layout/svg/LayoutSVGInlineText.h" | |
24 #include "core/layout/svg/SVGTextMetrics.h" | |
25 #include "platform/fonts/CharacterRange.h" | |
26 #include "platform/text/BidiCharacterRun.h" | |
27 #include "platform/text/BidiResolver.h" | |
28 #include "platform/text/TextDirection.h" | |
29 #include "platform/text/TextRun.h" | |
30 #include "platform/text/TextRunIterator.h" | |
31 #include "wtf/Vector.h" | |
32 | |
33 namespace blink { | |
34 | |
35 namespace { | |
36 | |
37 inline bool characterStartsSurrogatePair(const TextRun& run, unsigned index) | |
38 { | |
39 if (!U16_IS_LEAD(run[index])) | |
40 return false; | |
41 if (index + 1 >= static_cast<unsigned>(run.charactersLength())) | |
42 return false; | |
43 return U16_IS_TRAIL(run[index + 1]); | |
44 } | |
45 | |
46 class SVGTextMetricsCalculator { | |
47 public: | |
48 SVGTextMetricsCalculator(LayoutSVGInlineText&); | |
49 ~SVGTextMetricsCalculator(); | |
50 | |
51 bool advancePosition(); | |
52 unsigned currentPosition() const { return m_currentPosition; } | |
53 | |
54 SVGTextMetrics currentCharacterMetrics(); | |
55 | |
56 // TODO(pdr): Character-based iteration is ambiguous and error-prone. It | |
57 // should be unified under a single concept. See: https://crbug.com/593570 | |
58 bool currentCharacterStartsSurrogatePair() const | |
59 { | |
60 return characterStartsSurrogatePair(m_run, m_currentPosition); | |
61 } | |
62 bool currentCharacterIsWhiteSpace() const | |
63 { | |
64 return m_run[m_currentPosition] == ' '; | |
65 } | |
66 unsigned characterCount() const | |
67 { | |
68 return static_cast<unsigned>(m_run.charactersLength()); | |
69 } | |
70 | |
71 private: | |
72 void setupBidiRuns(); | |
73 | |
74 static TextRun constructTextRun(LayoutSVGInlineText&, unsigned position, uns
igned length, TextDirection); | |
75 | |
76 // Ensure |m_subrunRanges| is updated for the current bidi run, or the | |
77 // complete m_run if no bidi runs are present. Returns the current position | |
78 // in the subrun which can be used to index into |m_subrunRanges|. | |
79 unsigned updateSubrunRangesForCurrentPosition(); | |
80 | |
81 // Current character position in m_text. | |
82 unsigned m_currentPosition; | |
83 | |
84 LayoutSVGInlineText& m_text; | |
85 float m_fontScalingFactor; | |
86 float m_cachedFontHeight; | |
87 TextRun m_run; | |
88 | |
89 BidiCharacterRun* m_bidiRun; | |
90 BidiResolver<TextRunIterator, BidiCharacterRun> m_bidiResolver; | |
91 | |
92 // Ranges for the current bidi run if present, or the entire run otherwise. | |
93 Vector<CharacterRange> m_subrunRanges; | |
94 }; | |
95 | |
96 TextRun SVGTextMetricsCalculator::constructTextRun(LayoutSVGInlineText& text, un
signed position, unsigned length, TextDirection textDirection) | |
97 { | |
98 const ComputedStyle& style = text.styleRef(); | |
99 | |
100 TextRun run(static_cast<const LChar*>(nullptr) // characters, will be set be
low if non-zero. | |
101 , 0 // length, will be set below if non-zero. | |
102 , 0 // xPos, only relevant with allowTabs=true | |
103 , 0 // padding, only relevant for justified text, not relevant for SVG | |
104 , TextRun::AllowTrailingExpansion | |
105 , textDirection | |
106 , isOverride(style.unicodeBidi()) /* directionalOverride */); | |
107 | |
108 if (length) { | |
109 if (text.is8Bit()) | |
110 run.setText(text.characters8() + position, length); | |
111 else | |
112 run.setText(text.characters16() + position, length); | |
113 } | |
114 | |
115 // We handle letter & word spacing ourselves. | |
116 run.disableSpacing(); | |
117 | |
118 // Propagate the maximum length of the characters buffer to the TextRun, eve
n when we're only processing a substring. | |
119 run.setCharactersLength(text.textLength() - position); | |
120 ASSERT(run.charactersLength() >= run.length()); | |
121 return run; | |
122 } | |
123 | |
124 SVGTextMetricsCalculator::SVGTextMetricsCalculator(LayoutSVGInlineText& text) | |
125 : m_currentPosition(0) | |
126 , m_text(text) | |
127 , m_fontScalingFactor(m_text.scalingFactor()) | |
128 , m_cachedFontHeight(m_text.scaledFont().getFontMetrics().floatHeight() / m_
fontScalingFactor) | |
129 , m_run(constructTextRun(m_text, 0, m_text.textLength(), m_text.styleRef().d
irection())) | |
130 , m_bidiRun(nullptr) | |
131 { | |
132 setupBidiRuns(); | |
133 } | |
134 | |
135 SVGTextMetricsCalculator::~SVGTextMetricsCalculator() | |
136 { | |
137 m_bidiResolver.runs().deleteRuns(); | |
138 } | |
139 | |
140 void SVGTextMetricsCalculator::setupBidiRuns() | |
141 { | |
142 BidiRunList<BidiCharacterRun>& bidiRuns = m_bidiResolver.runs(); | |
143 bool bidiOverride = isOverride(m_text.styleRef().unicodeBidi()); | |
144 BidiStatus status(LTR, bidiOverride); | |
145 if (m_run.is8Bit() || bidiOverride) { | |
146 WTF::Unicode::CharDirection direction = WTF::Unicode::LeftToRight; | |
147 // If BiDi override is in effect, use the specified direction. | |
148 if (bidiOverride && !m_text.styleRef().isLeftToRightDirection()) | |
149 direction = WTF::Unicode::RightToLeft; | |
150 bidiRuns.addRun(new BidiCharacterRun(0, m_run.charactersLength(), status
.context.get(), direction)); | |
151 } else { | |
152 status.last = status.lastStrong = WTF::Unicode::OtherNeutral; | |
153 m_bidiResolver.setStatus(status); | |
154 m_bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&m_run,
0)); | |
155 const bool hardLineBreak = false; | |
156 const bool reorderRuns = false; | |
157 m_bidiResolver.createBidiRunsForLine(TextRunIterator(&m_run, m_run.lengt
h()), NoVisualOverride, hardLineBreak, reorderRuns); | |
158 } | |
159 m_bidiRun = bidiRuns.firstRun(); | |
160 } | |
161 | |
162 bool SVGTextMetricsCalculator::advancePosition() | |
163 { | |
164 m_currentPosition += currentCharacterStartsSurrogatePair() ? 2 : 1; | |
165 return m_currentPosition < characterCount(); | |
166 } | |
167 | |
168 // TODO(pdr): We only have per-glyph data so we need to synthesize per-grapheme | |
169 // data. E.g., if 'fi' is shaped into a single glyph, we do not know the 'i' | |
170 // position. The code below synthesizes an average glyph width when characters | |
171 // share a single position. This will incorrectly split combining diacritics. | |
172 // See: https://crbug.com/473476. | |
173 static void synthesizeGraphemeWidths(const TextRun& run, Vector<CharacterRange>&
ranges) | |
174 { | |
175 unsigned distributeCount = 0; | |
176 for (int rangeIndex = static_cast<int>(ranges.size()) - 1; rangeIndex >= 0;
--rangeIndex) { | |
177 CharacterRange& currentRange = ranges[rangeIndex]; | |
178 if (currentRange.width() == 0) { | |
179 distributeCount++; | |
180 } else if (distributeCount != 0) { | |
181 // Only count surrogate pairs as a single character. | |
182 bool surrogatePair = characterStartsSurrogatePair(run, rangeIndex); | |
183 if (!surrogatePair) | |
184 distributeCount++; | |
185 | |
186 float newWidth = currentRange.width() / distributeCount; | |
187 currentRange.end = currentRange.start + newWidth; | |
188 float lastEndPosition = currentRange.end; | |
189 for (unsigned distribute = 1; distribute < distributeCount; distribu
te++) { | |
190 // This surrogate pair check will skip processing of the second | |
191 // character forming the surrogate pair. | |
192 unsigned distributeIndex = rangeIndex + distribute + (surrogateP
air ? 1 : 0); | |
193 ranges[distributeIndex].start = lastEndPosition; | |
194 ranges[distributeIndex].end = lastEndPosition + newWidth; | |
195 lastEndPosition = ranges[distributeIndex].end; | |
196 } | |
197 | |
198 distributeCount = 0; | |
199 } | |
200 } | |
201 } | |
202 | |
203 unsigned SVGTextMetricsCalculator::updateSubrunRangesForCurrentPosition() | |
204 { | |
205 ASSERT(m_bidiRun); | |
206 if (m_currentPosition >= static_cast<unsigned>(m_bidiRun->stop())) { | |
207 m_bidiRun = m_bidiRun->next(); | |
208 // Ensure new subrange ranges are computed below. | |
209 m_subrunRanges.clear(); | |
210 } | |
211 ASSERT(m_bidiRun); | |
212 ASSERT(static_cast<int>(m_currentPosition) < m_bidiRun->stop()); | |
213 | |
214 unsigned positionInRun = m_currentPosition - m_bidiRun->start(); | |
215 if (positionInRun >= m_subrunRanges.size()) { | |
216 TextRun subRun = constructTextRun(m_text, m_bidiRun->start(), | |
217 m_bidiRun->stop() - m_bidiRun->start(), m_bidiRun->direction()); | |
218 m_subrunRanges = m_text.scaledFont().individualCharacterRanges(subRun); | |
219 synthesizeGraphemeWidths(subRun, m_subrunRanges); | |
220 } | |
221 | |
222 ASSERT(m_subrunRanges.size() && positionInRun < m_subrunRanges.size()); | |
223 return positionInRun; | |
224 } | |
225 | |
226 SVGTextMetrics SVGTextMetricsCalculator::currentCharacterMetrics() | |
227 { | |
228 unsigned currentSubrunPosition = updateSubrunRangesForCurrentPosition(); | |
229 unsigned length = currentCharacterStartsSurrogatePair() ? 2 : 1; | |
230 float width = m_subrunRanges[currentSubrunPosition].width(); | |
231 return SVGTextMetrics(length, width / m_fontScalingFactor, m_cachedFontHeigh
t); | |
232 } | |
233 | |
234 } // namespace | |
235 | |
236 void SVGTextMetricsBuilder::updateTextMetrics(LayoutSVGInlineText& text, bool& l
astCharacterWasWhiteSpace) | |
237 { | |
238 Vector<SVGTextMetrics>& metricsList = text.metricsList(); | |
239 metricsList.clear(); | |
240 | |
241 if (!text.textLength()) | |
242 return; | |
243 | |
244 // TODO(pdr): This loop is too tightly coupled to SVGTextMetricsCalculator. | |
245 // We should refactor SVGTextMetricsCalculator to be a simple bidi run | |
246 // iterator and move all subrun logic to a single function. | |
247 SVGTextMetricsCalculator calculator(text); | |
248 bool preserveWhiteSpace = text.styleRef().whiteSpace() == PRE; | |
249 do { | |
250 bool currentCharacterIsWhiteSpace = calculator.currentCharacterIsWhiteSp
ace(); | |
251 if (!preserveWhiteSpace && lastCharacterWasWhiteSpace && currentCharacte
rIsWhiteSpace) { | |
252 metricsList.append(SVGTextMetrics(SVGTextMetrics::SkippedSpaceMetric
s)); | |
253 ASSERT(calculator.currentCharacterMetrics().length() == 1); | |
254 continue; | |
255 } | |
256 | |
257 metricsList.append(calculator.currentCharacterMetrics()); | |
258 | |
259 lastCharacterWasWhiteSpace = currentCharacterIsWhiteSpace; | |
260 } while (calculator.advancePosition()); | |
261 } | |
262 | |
263 } // namespace blink | |
OLD | NEW |