blob: 13f535986b1c80c27abb369870528a320916667a [file] [log] [blame]
Julia Lavrovaa3552c52019-05-30 16:12:56 -04001// Copyright 2019 Google LLC.
2#include "modules/skparagraph/src/FontIterator.h"
3#include <unicode/brkiter.h>
4#include <unicode/ubidi.h>
5#include "include/core/SkBlurTypes.h"
6#include "include/core/SkCanvas.h"
7#include "include/core/SkFontMgr.h"
8#include "include/core/SkPictureRecorder.h"
9#include "modules/skparagraph/src/ParagraphImpl.h"
10#include "src/core/SkSpan.h"
11#include "src/utils/SkUTF.h"
12
13namespace {
14SkUnichar utf8_next(const char** ptr, const char* end) {
15 SkUnichar val = SkUTF::NextUTF8(ptr, end);
16 return val < 0 ? 0xFFFD : val;
17}
18} // namespace
19
20// TODO: FontCollection and FontIterator have common functionality
21namespace skia {
22namespace textlayout {
23
24FontIterator::FontIterator(SkSpan<const char> utf8,
25 SkSpan<TextBlock>
26 styles,
27 sk_sp<FontCollection>
28 fonts,
29 bool hintingOn)
30 : fText(utf8)
31 , fStyles(styles)
32 , fCurrentChar(utf8.begin())
33 , fFontCollection(std::move(fonts))
34 , fHintingOn(hintingOn) {
35 findAllFontsForAllStyledBlocks();
36}
37
38void FontIterator::consume() {
39 SkASSERT(fCurrentChar < fText.end());
40 auto found = fFontMapping.find(fCurrentChar);
41 SkASSERT(found != nullptr);
42 fFont = found->first;
43 fLineHeight = found->second;
44
45 // Move until we find the first character that cannot be resolved with the current font
46 while (++fCurrentChar != fText.end()) {
47 found = fFontMapping.find(fCurrentChar);
48 if (found != nullptr) {
49 if (fFont == found->first && fLineHeight == found->second) {
50 continue;
51 }
52 break;
53 }
54 }
55}
56
57void FontIterator::findAllFontsForAllStyledBlocks() {
58 TextBlock combined;
59 for (auto& block : fStyles) {
60 SkASSERT(combined.text().begin() == nullptr ||
61 combined.text().end() == block.text().begin());
62
63 if (combined.text().begin() != nullptr &&
64 block.style().matchOneAttribute(StyleType::kFont, combined.style())) {
65 combined.add(block.text());
66 continue;
67 }
68
69 if (!combined.text().empty()) {
70 findAllFontsForStyledBlock(combined.style(), combined.text());
71 }
72
73 combined = block;
74 }
75 findAllFontsForStyledBlock(combined.style(), combined.text());
76
77 if (!fText.empty() && fFontMapping.find(fText.begin()) == nullptr) {
78 // Resolve the first character with the first found font
79 fFontMapping.set(fText.begin(), fFirstResolvedFont);
80 }
81}
82
83void FontIterator::findAllFontsForStyledBlock(const TextStyle& style, SkSpan<const char> text) {
84 fCodepoints.reset();
85 fCharacters.reset();
86 fUnresolvedIndexes.reset();
87 fUnresolvedCodepoints.reset();
88
89 // Extract all unicode codepoints
90 const char* current = text.begin();
91 while (current != text.end()) {
92 fCharacters.emplace_back(current);
93 fCodepoints.emplace_back(utf8_next(&current, text.end()));
94 fUnresolvedIndexes.emplace_back(fUnresolvedIndexes.size());
95 }
96 fUnresolved = fCodepoints.size();
97
98 // Walk through all available fonts to resolve the block
99 for (auto& fontFamily : style.getFontFamilies()) {
100 auto typeface = fFontCollection->matchTypeface(fontFamily.c_str(), style.getFontStyle());
101 if (typeface.get() == nullptr) {
102 continue;
103 }
104
105 // Resolve all unresolved characters
106 auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
107 resolveAllCharactersByFont(font);
108 if (fUnresolved == 0) {
109 break;
110 }
111 }
112
113 if (fUnresolved > 0) {
114 auto typeface = fFontCollection->matchDefaultTypeface(style.getFontStyle());
115 if (typeface.get() != nullptr) {
116 // Resolve all unresolved characters
117 auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
118 resolveAllCharactersByFont(font);
119 }
120 }
121
122 addResolvedWhitespacesToMapping();
123
124 if (fUnresolved > 0 && fFontCollection->fontFallbackEnabled()) {
125 while (fUnresolved > 0) {
126 auto unicode = firstUnresolved();
127 auto typeface = fFontCollection->defaultFallback(unicode, style.getFontStyle());
128 if (typeface == nullptr) {
129 break;
130 }
131 auto font = makeFont(typeface, style.getFontSize(), style.getHeight());
132 if (!resolveAllCharactersByFont(font)) {
133 // Not a single unicode character was resolved
134 break;
135 }
136 SkString name;
137 typeface->getFamilyName(&name);
138 SkDebugf("Default font fallback resolution: %s\n", name.c_str());
139 }
140 }
141
142 // In case something still unresolved
143 if (fResolvedFonts.count() == 0) {
144 makeFont(fFontCollection->defaultFallback(firstUnresolved(), style.getFontStyle()),
145 style.getFontSize(), style.getHeight());
146 if (fFirstResolvedFont.first.getTypeface() != nullptr) {
147 SkString name;
148 fFirstResolvedFont.first.getTypeface()->getFamilyName(&name);
149 SkDebugf("Urgent font resolution: %s\n", name.c_str());
150 } else {
151 SkDebugf("No font!!!\n");
152 }
153 }
154}
155
156size_t FontIterator::resolveAllCharactersByFont(std::pair<SkFont, SkScalar> font) {
157 // Consolidate all unresolved unicodes in one array to make a batch call
158 SkTArray<SkGlyphID> glyphs(fUnresolved);
159 glyphs.push_back_n(fUnresolved, SkGlyphID(0));
160 font.first.getTypeface()->unicharsToGlyphs(
161 fUnresolved == fCodepoints.size() ? fCodepoints.data() : fUnresolvedCodepoints.data(),
162 fUnresolved, glyphs.data());
163
164 SkRange<size_t> resolved(0, 0);
165 SkRange<size_t> whitespaces(0, 0);
166 size_t stillUnresolved = 0;
167
168 auto processRuns = [&]() {
169 if (resolved.width() == 0) {
170 return;
171 }
172
173 if (resolved.width() == whitespaces.width()) {
174 // The entire run is just whitespaces;
175 // Remember the font and mark whitespaces back unresolved
176 // to calculate its mapping for the other fonts
177 for (auto w = whitespaces.start; w != whitespaces.end; ++w) {
178 if (fWhitespaces.find(w) == nullptr) {
179 fWhitespaces.set(w, font);
180 }
181 fUnresolvedIndexes[stillUnresolved++] = w;
182 fUnresolvedCodepoints.emplace_back(fCodepoints[w]);
183 }
184 } else {
185 fFontMapping.set(fCharacters[resolved.start], font);
186 }
187 };
188
189 // Try to resolve all the unresolved unicode points
190 for (size_t i = 0; i < glyphs.size(); ++i) {
191 auto glyph = glyphs[i];
192 auto index = fUnresolvedIndexes[i];
193
194 if (glyph == 0) {
195 processRuns();
196
197 resolved = SkRange<size_t>(0, 0);
198 whitespaces = SkRange<size_t>(0, 0);
199
200 fUnresolvedIndexes[stillUnresolved++] = index;
201 fUnresolvedCodepoints.emplace_back(fCodepoints[index]);
202 continue;
203 }
204
205 if (index == resolved.end) {
206 ++resolved.end;
207 } else {
208 processRuns();
209 resolved = SkRange<size_t>(index, index + 1);
210 }
211 if (u_isUWhiteSpace(fCodepoints[index])) {
212 if (index == whitespaces.end) {
213 ++whitespaces.end;
214 } else {
215 whitespaces = SkRange<size_t>(index, index + 1);
216 }
217 } else {
218 whitespaces = SkRange<size_t>(0, 0);
219 }
220 }
221
222 // One last time to take care of the tail run
223 processRuns();
224
225 size_t wasUnresolved = fUnresolved;
226 fUnresolved = stillUnresolved;
227 return fUnresolved < wasUnresolved;
228}
229
230void FontIterator::addResolvedWhitespacesToMapping() {
231 size_t resolvedWhitespaces = 0;
232 for (size_t i = 0; i < fUnresolved; ++i) {
233 auto index = fUnresolvedIndexes[i];
234 auto found = fWhitespaces.find(index);
235 if (found != nullptr) {
236 fFontMapping.set(fCharacters[index], *found);
237 ++resolvedWhitespaces;
238 }
239 }
240 fUnresolved -= resolvedWhitespaces;
241}
242
243std::pair<SkFont, SkScalar> FontIterator::makeFont(sk_sp<SkTypeface> typeface,
244 SkScalar size,
245 SkScalar height) {
246 SkFont font(typeface, size);
247 font.setEdging(SkFont::Edging::kAntiAlias);
248 if (!fHintingOn) {
249 font.setHinting(SkFontHinting::kSlight);
250 font.setSubpixel(true);
251 }
252 auto pair = std::make_pair(font, height);
253
254 auto foundFont = fResolvedFonts.find(pair);
255 if (foundFont == nullptr) {
256 if (fResolvedFonts.count() == 0) {
257 fFirstResolvedFont = pair;
258 }
259 fResolvedFonts.add(pair);
260 }
261
262 return pair;
263}
264} // namespace textlayout
265} // namespace skia