| 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 |