| // Copyright 2019 Google LLC. |
| |
| #include <unicode/brkiter.h> |
| #include <unicode/ubidi.h> |
| #include "include/core/SkBlurTypes.h" |
| #include "include/core/SkCanvas.h" |
| #include "include/core/SkFontMgr.h" |
| #include "include/core/SkPictureRecorder.h" |
| #include "modules/skparagraph/src/FontResolver.h" |
| #include "modules/skparagraph/src/ParagraphImpl.h" |
| #include "src/core/SkSpan.h" |
| #include "src/utils/SkUTF.h" |
| |
| namespace { |
| SkUnichar utf8_next(const char** ptr, const char* end) { |
| SkUnichar val = SkUTF::NextUTF8(ptr, end); |
| return val < 0 ? 0xFFFD : val; |
| } |
| } // namespace |
| |
| // TODO: FontResolver and FontIterator have common functionality |
| namespace skia { |
| namespace textlayout { |
| |
| bool FontResolver::findFirst(const char* codepoint, SkFont* font, SkScalar* height) { |
| auto found = fFontMapping.find(codepoint); |
| if (found == nullptr) { |
| // Resolve the first character with the first found font |
| found = fFontMapping.set(codepoint, fFirstResolvedFont); |
| } |
| if (found == nullptr) { |
| return false; |
| } |
| *font = found->fFont; |
| *height = found->fHeight; |
| return true; |
| } |
| |
| bool FontResolver::findNext(const char* codepoint, SkFont* font, SkScalar* height) { |
| auto found = fFontMapping.find(codepoint); |
| if (found == nullptr) { |
| return false; |
| } |
| *font = found->fFont; |
| *height = found->fHeight; |
| return true; |
| } |
| |
| void FontResolver::findAllFontsForStyledBlock(const TextStyle& style, SkSpan<const char> text) { |
| fCodepoints.reset(); |
| fCharacters.reset(); |
| fUnresolvedIndexes.reset(); |
| fUnresolvedCodepoints.reset(); |
| |
| // Extract all unicode codepoints |
| const char* current = text.begin(); |
| while (current != text.end()) { |
| fCharacters.emplace_back(current); |
| fCodepoints.emplace_back(utf8_next(¤t, text.end())); |
| fUnresolvedIndexes.emplace_back(fUnresolvedIndexes.size()); |
| } |
| fUnresolved = fCodepoints.size(); |
| |
| // Walk through all available fonts to resolve the block |
| for (auto& fontFamily : style.getFontFamilies()) { |
| auto typeface = fFontCollection->matchTypeface(fontFamily.c_str(), style.getFontStyle()); |
| if (typeface.get() == nullptr) { |
| continue; |
| } |
| |
| // Resolve all unresolved characters |
| auto font = makeFont(typeface, style.getFontSize(), style.getHeight()); |
| resolveAllCharactersByFont(font); |
| if (fUnresolved == 0) { |
| break; |
| } |
| } |
| |
| if (fUnresolved > 0) { |
| auto typeface = fFontCollection->matchDefaultTypeface(style.getFontStyle()); |
| if (typeface.get() != nullptr) { |
| // Resolve all unresolved characters |
| auto font = makeFont(typeface, style.getFontSize(), style.getHeight()); |
| resolveAllCharactersByFont(font); |
| } |
| } |
| |
| addResolvedWhitespacesToMapping(); |
| |
| if (fUnresolved > 0 && fFontCollection->fontFallbackEnabled()) { |
| while (fUnresolved > 0) { |
| auto unicode = firstUnresolved(); |
| auto typeface = fFontCollection->defaultFallback(unicode, style.getFontStyle(), style.getLocale()); |
| if (typeface == nullptr) { |
| break; |
| } |
| auto font = makeFont(typeface, style.getFontSize(), style.getHeight()); |
| if (!resolveAllCharactersByFont(font)) { |
| // Not a single unicode character was resolved |
| break; |
| } |
| SkString name; |
| typeface->getFamilyName(&name); |
| SkDebugf("Default font fallback resolution: %s\n", name.c_str()); |
| } |
| } |
| |
| // In case something still unresolved |
| if (fResolvedFonts.count() == 0) { |
| makeFont(fFontCollection->defaultFallback(firstUnresolved(), style.getFontStyle(), style.getLocale()), |
| style.getFontSize(), |
| style.getHeight()); |
| if (fFirstResolvedFont.fFont.getTypeface() != nullptr) { |
| SkString name; |
| fFirstResolvedFont.fFont.getTypeface()->getFamilyName(&name); |
| SkDebugf("Urgent font resolution: %s\n", name.c_str()); |
| } else { |
| SkDebugf("No font!!!\n"); |
| } |
| } |
| } |
| |
| size_t FontResolver::resolveAllCharactersByFont(const FontDescr& font) { |
| // Consolidate all unresolved unicodes in one array to make a batch call |
| SkTArray<SkGlyphID> glyphs(fUnresolved); |
| glyphs.push_back_n(fUnresolved, SkGlyphID(0)); |
| font.fFont.getTypeface()->unicharsToGlyphs( |
| fUnresolved == fCodepoints.size() ? fCodepoints.data() : fUnresolvedCodepoints.data(), |
| fUnresolved, glyphs.data()); |
| |
| SkRange<size_t> resolved(0, 0); |
| SkRange<size_t> whitespaces(0, 0); |
| size_t stillUnresolved = 0; |
| |
| auto processRuns = [&]() { |
| if (resolved.width() == 0) { |
| return; |
| } |
| |
| if (resolved.width() == whitespaces.width()) { |
| // The entire run is just whitespaces; |
| // Remember the font and mark whitespaces back unresolved |
| // to calculate its mapping for the other fonts |
| for (auto w = whitespaces.start; w != whitespaces.end; ++w) { |
| if (fWhitespaces.find(w) == nullptr) { |
| fWhitespaces.set(w, font); |
| } |
| fUnresolvedIndexes[stillUnresolved++] = w; |
| fUnresolvedCodepoints.emplace_back(fCodepoints[w]); |
| } |
| } else { |
| fFontMapping.set(fCharacters[resolved.start], font); |
| } |
| }; |
| |
| // Try to resolve all the unresolved unicode points |
| for (size_t i = 0; i < glyphs.size(); ++i) { |
| auto glyph = glyphs[i]; |
| auto index = fUnresolvedIndexes[i]; |
| |
| if (glyph == 0) { |
| processRuns(); |
| |
| resolved = SkRange<size_t>(0, 0); |
| whitespaces = SkRange<size_t>(0, 0); |
| |
| fUnresolvedIndexes[stillUnresolved++] = index; |
| fUnresolvedCodepoints.emplace_back(fCodepoints[index]); |
| continue; |
| } |
| |
| if (index == resolved.end) { |
| ++resolved.end; |
| } else { |
| processRuns(); |
| resolved = SkRange<size_t>(index, index + 1); |
| } |
| if (u_isUWhiteSpace(fCodepoints[index])) { |
| if (index == whitespaces.end) { |
| ++whitespaces.end; |
| } else { |
| whitespaces = SkRange<size_t>(index, index + 1); |
| } |
| } else { |
| whitespaces = SkRange<size_t>(0, 0); |
| } |
| } |
| |
| // One last time to take care of the tail run |
| processRuns(); |
| |
| size_t wasUnresolved = fUnresolved; |
| fUnresolved = stillUnresolved; |
| return fUnresolved < wasUnresolved; |
| } |
| |
| void FontResolver::addResolvedWhitespacesToMapping() { |
| size_t resolvedWhitespaces = 0; |
| for (size_t i = 0; i < fUnresolved; ++i) { |
| auto index = fUnresolvedIndexes[i]; |
| auto found = fWhitespaces.find(index); |
| if (found != nullptr) { |
| fFontMapping.set(fCharacters[index], *found); |
| ++resolvedWhitespaces; |
| } |
| } |
| fUnresolved -= resolvedWhitespaces; |
| } |
| |
| FontResolver::FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface, |
| SkScalar size, |
| SkScalar height) { |
| SkFont font(typeface, size); |
| font.setEdging(SkFont::Edging::kAntiAlias); |
| font.setHinting(SkFontHinting::kSlight); |
| font.setSubpixel(true); |
| FontDescr descr(font, height); |
| |
| const FontDescr* foundFont = fResolvedFonts.find(descr); |
| if (foundFont == nullptr) { |
| if (fResolvedFonts.count() == 0) { |
| fFirstResolvedFont = descr; |
| } |
| fResolvedFonts.add(descr); |
| } |
| return descr; |
| } |
| |
| SkUnichar FontResolver::firstUnresolved() { |
| if (fUnresolved == 0) return 0; |
| |
| bool firstTry = fUnresolved == fCodepoints.size(); |
| auto index = firstTry ? 0 : fUnresolvedIndexes[0]; |
| return fCodepoints[index]; |
| } |
| |
| void FontResolver::findAllFontsForAllStyledBlocks(SkSpan<const char> utf8, |
| SkSpan<TextBlock> styles, |
| sk_sp<FontCollection> fontCollection) { |
| fFontCollection = fontCollection; |
| fStyles = styles; |
| fText = utf8; |
| TextBlock combined; |
| for (auto& block : fStyles) { |
| SkASSERT(combined.text().begin() == nullptr || |
| combined.text().end() == block.text().begin()); |
| |
| if (combined.text().begin() != nullptr && |
| block.style().matchOneAttribute(StyleType::kFont, combined.style())) { |
| combined.add(block.text()); |
| continue; |
| } |
| |
| if (!combined.text().empty()) { |
| this->findAllFontsForStyledBlock(combined.style(), combined.text()); |
| } |
| |
| combined = block; |
| } |
| this->findAllFontsForStyledBlock(combined.style(), combined.text()); |
| } |
| } // namespace textlayout |
| } // namespace skia |