OLD | NEW |
---|---|
1 /* | 1 /* |
2 * Copyright (C) Research In Motion Limited 2010-2012. All rights reserved. | 2 * Copyright (C) Research In Motion Limited 2010-2012. All rights reserved. |
3 * | 3 * |
4 * This library is free software; you can redistribute it and/or | 4 * This library is free software; you can redistribute it and/or |
5 * modify it under the terms of the GNU Library General Public | 5 * modify it under the terms of the GNU Library General Public |
6 * License as published by the Free Software Foundation; either | 6 * License as published by the Free Software Foundation; either |
7 * version 2 of the License, or (at your option) any later version. | 7 * version 2 of the License, or (at your option) any later version. |
8 * | 8 * |
9 * This library is distributed in the hope that it will be useful, | 9 * This library is distributed in the hope that it will be useful, |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 * Library General Public License for more details. | 12 * Library General Public License for more details. |
13 * | 13 * |
14 * You should have received a copy of the GNU Library General Public License | 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 | 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, | 16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 * Boston, MA 02110-1301, USA. | 17 * Boston, MA 02110-1301, USA. |
18 */ | 18 */ |
19 | 19 |
20 #include "core/layout/svg/SVGTextMetricsBuilder.h" | 20 #include "core/layout/svg/SVGTextMetricsBuilder.h" |
21 | 21 |
22 #include "core/layout/api/LineLayoutSVGInlineText.h" | 22 #include "core/layout/api/LineLayoutSVGInlineText.h" |
23 #include "core/layout/svg/LayoutSVGInline.h" | 23 #include "core/layout/svg/LayoutSVGInline.h" |
24 #include "core/layout/svg/LayoutSVGInlineText.h" | 24 #include "core/layout/svg/LayoutSVGInlineText.h" |
25 #include "core/layout/svg/LayoutSVGText.h" | 25 #include "core/layout/svg/LayoutSVGText.h" |
26 #include "core/layout/svg/SVGTextMetrics.h" | 26 #include "core/layout/svg/SVGTextMetrics.h" |
27 #include "platform/fonts/GlyphBuffer.h" | 27 #include "platform/fonts/CharacterRange.h" |
28 #include "platform/fonts/shaping/SimpleShaper.h" | |
29 #include "platform/text/BidiCharacterRun.h" | 28 #include "platform/text/BidiCharacterRun.h" |
30 #include "platform/text/BidiResolver.h" | 29 #include "platform/text/BidiResolver.h" |
31 #include "platform/text/TextDirection.h" | 30 #include "platform/text/TextDirection.h" |
32 #include "platform/text/TextPath.h" | 31 #include "platform/text/TextPath.h" |
33 #include "platform/text/TextRun.h" | 32 #include "platform/text/TextRun.h" |
34 #include "platform/text/TextRunIterator.h" | 33 #include "platform/text/TextRunIterator.h" |
35 #include "wtf/Vector.h" | 34 #include "wtf/Vector.h" |
36 | 35 |
37 namespace blink { | 36 namespace blink { |
38 | 37 |
39 class SVGTextMetricsCalculator { | 38 class SVGTextMetricsCalculator { |
40 public: | 39 public: |
41 SVGTextMetricsCalculator(LayoutSVGInlineText*); | 40 SVGTextMetricsCalculator(LayoutSVGInlineText*); |
42 ~SVGTextMetricsCalculator(); | 41 ~SVGTextMetricsCalculator(); |
43 | 42 |
44 SVGTextMetrics computeMetricsForCharacter(unsigned textPosition); | 43 bool advancePosition(); |
45 unsigned textLength() const { return static_cast<unsigned>(m_run.charactersL ength()); } | 44 unsigned currentPosition() const { return m_currentPosition; } |
46 | 45 |
47 bool characterStartsSurrogatePair(unsigned textPosition) const | 46 SVGTextMetrics currentCharacterMetrics(); |
47 | |
48 // TODO(pdr): The surrogate pair concept should be refactored to a single | |
fs
2016/03/09 11:31:44
I think we should rather strive to move in the dir
pdr.
2016/03/10 04:55:15
Nice! I didn't know the name for this. I've rewrit
| |
49 // place to hide this complexity. | |
50 bool currentCharacterStartsSurrogatePair() const | |
48 { | 51 { |
49 return U16_IS_LEAD(m_run[textPosition]) && textPosition + 1 < textLength () && U16_IS_TRAIL(m_run[textPosition + 1]); | 52 return U16_IS_LEAD(m_run[m_currentPosition]) && m_currentPosition + 1 < characterCount() && U16_IS_TRAIL(m_run[m_currentPosition + 1]); |
50 } | 53 } |
51 bool characterIsWhiteSpace(unsigned textPosition) const | 54 bool currentCharacterIsWhiteSpace() const |
52 { | 55 { |
53 return m_run[textPosition] == ' '; | 56 return m_run[m_currentPosition] == ' '; |
57 } | |
58 unsigned characterCount() const | |
59 { | |
60 return static_cast<unsigned>(m_run.charactersLength()); | |
54 } | 61 } |
55 | 62 |
56 private: | 63 private: |
57 void setupBidiRuns(); | 64 void setupBidiRuns(); |
58 | 65 |
66 // Ensure |m_subrunRanges| is updated for the current bidi run, or the | |
67 // complete m_run if no bidi runs are present. Returns the current position | |
68 // in the subrun which can be used to index into |m_subrunRanges|. | |
69 unsigned updateSubrunRangesForCurrentPosition(); | |
70 | |
71 // Current character position in m_text. | |
72 unsigned m_currentPosition; | |
73 | |
59 LineLayoutSVGInlineText m_text; | 74 LineLayoutSVGInlineText m_text; |
75 TextRun m_run; | |
76 | |
60 BidiCharacterRun* m_bidiRun; | 77 BidiCharacterRun* m_bidiRun; |
61 TextRun m_run; | |
62 BidiResolver<TextRunIterator, BidiCharacterRun> m_bidiResolver; | 78 BidiResolver<TextRunIterator, BidiCharacterRun> m_bidiResolver; |
63 float m_totalWidth; | 79 |
64 TextDirection m_textDirection; | 80 // Ranges for the current bidi run if present, or the entire run otherwise. |
81 Vector<CharacterRange> m_subrunRanges; | |
65 }; | 82 }; |
66 | 83 |
67 SVGTextMetricsCalculator::SVGTextMetricsCalculator(LayoutSVGInlineText* text) | 84 SVGTextMetricsCalculator::SVGTextMetricsCalculator(LayoutSVGInlineText* text) |
68 : m_text(LineLayoutSVGInlineText(text)) | 85 : m_currentPosition(0) |
86 , m_text(LineLayoutSVGInlineText(text)) | |
87 , m_run(SVGTextMetrics::constructTextRun(m_text, 0, m_text.textLength(), m_t ext.styleRef().direction())) | |
69 , m_bidiRun(nullptr) | 88 , m_bidiRun(nullptr) |
70 , m_run(SVGTextMetrics::constructTextRun(m_text, 0, m_text.textLength(), m_t ext.styleRef().direction())) | |
71 , m_totalWidth(0) | |
72 { | 89 { |
73 setupBidiRuns(); | 90 setupBidiRuns(); |
74 } | 91 } |
75 | 92 |
76 SVGTextMetricsCalculator::~SVGTextMetricsCalculator() | 93 SVGTextMetricsCalculator::~SVGTextMetricsCalculator() |
77 { | 94 { |
78 if (m_bidiRun) | 95 if (m_bidiRun) |
79 m_bidiResolver.runs().deleteRuns(); | 96 m_bidiResolver.runs().deleteRuns(); |
80 } | 97 } |
81 | 98 |
82 void SVGTextMetricsCalculator::setupBidiRuns() | 99 void SVGTextMetricsCalculator::setupBidiRuns() |
83 { | 100 { |
84 const ComputedStyle& style = m_text.styleRef(); | 101 if (isOverride(m_text.styleRef().unicodeBidi())) |
85 m_textDirection = style.direction(); | |
86 if (isOverride(style.unicodeBidi())) | |
87 return; | 102 return; |
88 | |
89 BidiStatus status(LTR, false); | 103 BidiStatus status(LTR, false); |
90 status.last = status.lastStrong = WTF::Unicode::OtherNeutral; | 104 status.last = status.lastStrong = WTF::Unicode::OtherNeutral; |
91 m_bidiResolver.setStatus(status); | 105 m_bidiResolver.setStatus(status); |
92 m_bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&m_run, 0)) ; | 106 m_bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&m_run, 0)) ; |
93 const bool hardLineBreak = false; | 107 const bool hardLineBreak = false; |
94 const bool reorderRuns = false; | 108 const bool reorderRuns = false; |
95 m_bidiResolver.createBidiRunsForLine(TextRunIterator(&m_run, m_run.length()) , NoVisualOverride, hardLineBreak, reorderRuns); | 109 m_bidiResolver.createBidiRunsForLine(TextRunIterator(&m_run, m_run.length()) , NoVisualOverride, hardLineBreak, reorderRuns); |
96 BidiRunList<BidiCharacterRun>& bidiRuns = m_bidiResolver.runs(); | 110 BidiRunList<BidiCharacterRun>& bidiRuns = m_bidiResolver.runs(); |
97 m_bidiRun = bidiRuns.firstRun(); | 111 m_bidiRun = bidiRuns.firstRun(); |
98 } | 112 } |
99 | 113 |
100 SVGTextMetrics SVGTextMetricsCalculator::computeMetricsForCharacter(unsigned tex tPosition) | 114 bool SVGTextMetricsCalculator::advancePosition() |
115 { | |
116 m_currentPosition += currentCharacterStartsSurrogatePair() ? 2 : 1; | |
117 return m_currentPosition < characterCount(); | |
118 } | |
119 | |
120 unsigned SVGTextMetricsCalculator::updateSubrunRangesForCurrentPosition() | |
101 { | 121 { |
102 if (m_bidiRun) { | 122 if (m_bidiRun) { |
103 if (textPosition >= static_cast<unsigned>(m_bidiRun->stop())) { | 123 if (m_currentPosition >= static_cast<unsigned>(m_bidiRun->stop())) { |
104 m_bidiRun = m_bidiRun->next(); | 124 m_bidiRun = m_bidiRun->next(); |
105 // New BiDi run means new reference position for measurements, so re set |m_totalWidth|. | 125 // Ensure new subrange ranges are computed below. |
106 m_totalWidth = 0; | 126 m_subrunRanges.clear(); |
107 } | 127 } |
108 ASSERT(m_bidiRun); | 128 ASSERT(m_bidiRun); |
109 ASSERT(static_cast<int>(textPosition) < m_bidiRun->stop()); | 129 ASSERT(static_cast<int>(m_currentPosition) < m_bidiRun->stop()); |
110 m_textDirection = m_bidiRun->direction(); | |
111 } | 130 } |
112 | 131 |
113 unsigned metricsLength = characterStartsSurrogatePair(textPosition) ? 2 : 1; | 132 unsigned positionInRun = m_bidiRun ? m_currentPosition - m_bidiRun->start() : m_currentPosition; |
114 SVGTextMetrics metrics = SVGTextMetrics::measureCharacterRange(m_text, textP osition, metricsLength, m_textDirection); | 133 if (positionInRun >= m_subrunRanges.size()) { |
115 ASSERT(metrics.length() == metricsLength); | 134 unsigned subrunStart = m_bidiRun ? m_bidiRun->start() : 0; |
135 unsigned subrunEnd = m_bidiRun ? m_bidiRun->stop() : m_run.charactersLen gth(); | |
136 TextDirection subrunDirection = m_bidiRun ? m_bidiRun->direction() : m_t ext.styleRef().direction(); | |
137 TextRun subRun = SVGTextMetrics::constructTextRun(m_text, subrunStart, s ubrunEnd - subrunStart, subrunDirection); | |
138 m_subrunRanges = m_text.scaledFont().individualCharacterRanges(subRun, 0 , subRun.length() - 1); | |
116 | 139 |
117 unsigned startPosition = m_bidiRun ? m_bidiRun->start() : 0; | 140 // TODO(pdr): Our font APIs currently only have per-glyph data so we |
118 ASSERT(startPosition <= textPosition); | 141 // need to synthesize per-grapheme data. For example, if 'fi' is shaped |
119 SVGTextMetrics complexStartToCurrentMetrics = SVGTextMetrics::measureCharact erRange(m_text, startPosition, textPosition - startPosition + metricsLength, m_t extDirection); | 142 // into a single glyph, we do not have access to the 'i' position. The |
120 // Frequent case for Arabic text: when measuring a single character the arab ic isolated form is taken | 143 // code below synthesizes a position when two characters share the same |
fs
2016/03/09 11:31:44
Well technically "two or more" (i.e ligatures like
pdr.
2016/03/10 04:55:15
+1, good catch. I've ffixed this loop and added a
pdr.
2016/03/10 07:21:48
I looked at this some more and we're violating the
fs
2016/03/10 09:55:57
Yes, it more common for us weird people with diacr
| |
121 // when laying out the glyph "in context" (with it's surrounding characters) it changes due to shaping. | 144 // position. See: https://crbug.com/473476. |
122 // So whenever currentWidth != currentMetrics.width(), we are processing a t ext run whose length is | 145 for (unsigned rangeIndex = m_subrunRanges.size() - 1; rangeIndex > 0; -- rangeIndex) { |
123 // not equal to the sum of the individual lengths of the glyphs, when measur ing them isolated. | 146 CharacterRange& currentRange = m_subrunRanges[rangeIndex]; |
124 float currentWidth = complexStartToCurrentMetrics.width() - m_totalWidth; | 147 CharacterRange& previousRange = m_subrunRanges[rangeIndex - 1]; |
125 if (currentWidth != metrics.width()) | 148 if (currentRange.width() == 0 && previousRange.end == currentRange.s tart) { |
126 metrics.setWidth(currentWidth); | 149 float midpoint = (previousRange.end - previousRange.start) / 2; |
150 previousRange.end = previousRange.start + midpoint; | |
fs
2016/03/09 11:31:44
(previousRange.start + previousRange.end) / 2
for
pdr.
2016/03/10 04:55:15
The new loop now requires a divide 😭
| |
151 currentRange.start = previousRange.end; | |
152 } | |
153 } | |
154 } | |
127 | 155 |
128 m_totalWidth = complexStartToCurrentMetrics.width(); | 156 return positionInRun; |
129 return metrics; | 157 } |
158 | |
159 SVGTextMetrics SVGTextMetricsCalculator::currentCharacterMetrics() | |
160 { | |
161 unsigned currentSubrunPosition = updateSubrunRangesForCurrentPosition(); | |
162 unsigned length = currentCharacterStartsSurrogatePair() ? 2 : 1; | |
163 float width = m_subrunRanges[currentSubrunPosition].width(); | |
164 return SVGTextMetrics(m_text, length, width); | |
130 } | 165 } |
131 | 166 |
132 struct MeasureTextData { | 167 struct MeasureTextData { |
133 MeasureTextData(SVGCharacterDataMap* characterDataMap) | 168 MeasureTextData(SVGCharacterDataMap* characterDataMap) |
134 : allCharactersMap(characterDataMap) | 169 : allCharactersMap(characterDataMap) |
135 , lastCharacterWasWhiteSpace(true) | 170 , lastCharacterWasWhiteSpace(true) |
136 , valueListPosition(0) | 171 , valueListPosition(0) |
137 { | 172 { |
138 } | 173 } |
139 | 174 |
140 SVGCharacterDataMap* allCharactersMap; | 175 SVGCharacterDataMap* allCharactersMap; |
141 bool lastCharacterWasWhiteSpace; | 176 bool lastCharacterWasWhiteSpace; |
142 unsigned valueListPosition; | 177 unsigned valueListPosition; |
143 }; | 178 }; |
144 | 179 |
145 static void measureTextLayoutObject(LayoutSVGInlineText* text, MeasureTextData* data, bool processLayoutObject) | 180 static void measureTextLayoutObject(LayoutSVGInlineText* text, MeasureTextData* data, bool processLayoutObject) |
146 { | 181 { |
147 ASSERT(text); | 182 ASSERT(text); |
148 | 183 |
149 SVGTextLayoutAttributes* attributes = text->layoutAttributes(); | 184 SVGTextLayoutAttributes* attributes = text->layoutAttributes(); |
150 Vector<SVGTextMetrics>* textMetricsValues = &attributes->textMetricsValues() ; | 185 Vector<SVGTextMetrics>* textMetricsValues = &attributes->textMetricsValues() ; |
151 if (processLayoutObject) { | 186 if (processLayoutObject) { |
152 if (data->allCharactersMap) | 187 if (data->allCharactersMap) |
153 attributes->clear(); | 188 attributes->clear(); |
154 else | 189 else |
155 textMetricsValues->clear(); | 190 textMetricsValues->clear(); |
156 } | 191 } |
157 | 192 |
193 // TODO(pdr): This loop is too tightly coupled to SVGTextMetricsCalculator. | |
fs
2016/03/09 11:31:44
Should we file a (separate) bug for this, or will
pdr.
2016/03/10 04:55:15
Yeah, I plan to do this one in a followup but want
fs
2016/03/10 09:55:57
Roger that.
| |
194 // We should refactor SVGTextMetricsCalculator to be a simple bidi run | |
195 // iterator and move all subrun logic to a single function. | |
158 SVGTextMetricsCalculator calculator(text); | 196 SVGTextMetricsCalculator calculator(text); |
159 bool preserveWhiteSpace = text->style()->whiteSpace() == PRE; | 197 bool preserveWhiteSpace = text->style()->whiteSpace() == PRE; |
160 unsigned surrogatePairCharacters = 0; | 198 unsigned surrogatePairCharacters = 0; |
161 unsigned skippedCharacters = 0; | 199 unsigned skippedCharacters = 0; |
162 unsigned textPosition = 0; | 200 do { |
163 unsigned textLength = calculator.textLength(); | 201 SVGTextMetrics metrics = calculator.currentCharacterMetrics(); |
164 | 202 if (!metrics.length()) |
165 SVGTextMetrics currentMetrics; | |
166 for (; textPosition < textLength; textPosition += currentMetrics.length()) { | |
167 currentMetrics = calculator.computeMetricsForCharacter(textPosition); | |
168 if (!currentMetrics.length()) | |
169 break; | 203 break; |
170 | 204 |
171 bool characterIsWhiteSpace = calculator.characterIsWhiteSpace(textPositi on); | 205 bool characterIsWhiteSpace = calculator.currentCharacterIsWhiteSpace(); |
172 if (characterIsWhiteSpace && !preserveWhiteSpace && data->lastCharacterW asWhiteSpace) { | 206 if (characterIsWhiteSpace && !preserveWhiteSpace && data->lastCharacterW asWhiteSpace) { |
173 if (processLayoutObject) | 207 if (processLayoutObject) |
174 textMetricsValues->append(SVGTextMetrics(SVGTextMetrics::Skipped SpaceMetrics)); | 208 textMetricsValues->append(SVGTextMetrics(SVGTextMetrics::Skipped SpaceMetrics)); |
175 if (data->allCharactersMap) | 209 if (data->allCharactersMap) |
176 skippedCharacters += currentMetrics.length(); | 210 skippedCharacters += metrics.length(); |
177 continue; | 211 continue; |
178 } | 212 } |
179 | 213 |
180 if (processLayoutObject) { | 214 if (processLayoutObject) { |
181 if (data->allCharactersMap) { | 215 if (data->allCharactersMap) { |
182 const SVGCharacterDataMap::const_iterator it = data->allCharacte rsMap->find(data->valueListPosition + textPosition - skippedCharacters - surroga tePairCharacters + 1); | 216 const SVGCharacterDataMap::const_iterator it = data->allCharacte rsMap->find(data->valueListPosition + calculator.currentPosition() - skippedChar acters - surrogatePairCharacters + 1); |
183 if (it != data->allCharactersMap->end()) | 217 if (it != data->allCharactersMap->end()) |
184 attributes->characterDataMap().set(textPosition + 1, it->val ue); | 218 attributes->characterDataMap().set(calculator.currentPositio n() + 1, it->value); |
185 } | 219 } |
186 textMetricsValues->append(currentMetrics); | 220 textMetricsValues->append(metrics); |
187 } | 221 } |
188 | 222 |
189 if (data->allCharactersMap && calculator.characterStartsSurrogatePair(te xtPosition)) | 223 if (data->allCharactersMap && calculator.currentCharacterStartsSurrogate Pair()) |
190 surrogatePairCharacters++; | 224 surrogatePairCharacters++; |
191 | 225 |
192 data->lastCharacterWasWhiteSpace = characterIsWhiteSpace; | 226 data->lastCharacterWasWhiteSpace = characterIsWhiteSpace; |
193 } | 227 } while (calculator.advancePosition()); |
194 | 228 |
195 if (!data->allCharactersMap) | 229 if (!data->allCharactersMap) |
196 return; | 230 return; |
197 | 231 |
198 data->valueListPosition += textPosition - skippedCharacters; | 232 data->valueListPosition += calculator.currentPosition() - skippedCharacters; |
199 } | 233 } |
200 | 234 |
201 static void walkTree(LayoutSVGText* start, LayoutSVGInlineText* stopAtLeaf, Meas ureTextData* data) | 235 static void walkTree(LayoutSVGText* start, LayoutSVGInlineText* stopAtLeaf, Meas ureTextData* data) |
202 { | 236 { |
203 LayoutObject* child = start->firstChild(); | 237 LayoutObject* child = start->firstChild(); |
204 while (child) { | 238 while (child) { |
205 if (child->isSVGInlineText()) { | 239 if (child->isSVGInlineText()) { |
206 LayoutSVGInlineText* text = toLayoutSVGInlineText(child); | 240 LayoutSVGInlineText* text = toLayoutSVGInlineText(child); |
207 measureTextLayoutObject(text, data, !stopAtLeaf || stopAtLeaf == tex t); | 241 measureTextLayoutObject(text, data, !stopAtLeaf || stopAtLeaf == tex t); |
208 if (stopAtLeaf && stopAtLeaf == text) | 242 if (stopAtLeaf && stopAtLeaf == text) |
(...skipping 22 matching lines...) Expand all Loading... | |
231 } | 265 } |
232 | 266 |
233 void SVGTextMetricsBuilder::buildMetricsAndLayoutAttributes(LayoutSVGText* textR oot, LayoutSVGInlineText* stopAtLeaf, SVGCharacterDataMap& allCharactersMap) | 267 void SVGTextMetricsBuilder::buildMetricsAndLayoutAttributes(LayoutSVGText* textR oot, LayoutSVGInlineText* stopAtLeaf, SVGCharacterDataMap& allCharactersMap) |
234 { | 268 { |
235 ASSERT(textRoot); | 269 ASSERT(textRoot); |
236 MeasureTextData data(&allCharactersMap); | 270 MeasureTextData data(&allCharactersMap); |
237 walkTree(textRoot, stopAtLeaf, &data); | 271 walkTree(textRoot, stopAtLeaf, &data); |
238 } | 272 } |
239 | 273 |
240 } // namespace blink | 274 } // namespace blink |
OLD | NEW |