| // Copyright 2016 The PDFium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com |
| |
| #include "core/fxge/android/cfpf_skiafontmgr.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <functional> |
| #include <iterator> |
| #include <utility> |
| |
| #include "core/fxcrt/cfx_read_only_container_stream.h" |
| #include "core/fxcrt/compiler_specific.h" |
| #include "core/fxcrt/containers/adapters.h" |
| #include "core/fxcrt/fx_codepage.h" |
| #include "core/fxcrt/fx_extension.h" |
| #include "core/fxcrt/fx_folder.h" |
| #include "core/fxcrt/fx_system.h" |
| #include "core/fxcrt/mapped_data_bytes.h" |
| #include "core/fxge/android/cfpf_skiafont.h" |
| #include "core/fxge/cfx_fontmgr.h" |
| #include "core/fxge/fx_font.h" |
| |
| namespace { |
| |
| constexpr char kRoboto[] = "Roboto"; |
| constexpr char kDroidSans[] = "Droid Sans"; |
| constexpr char kDroidSerif[] = "Droid Serif"; |
| constexpr char kDroidSansMono[] = "Droid Sans Mono"; |
| constexpr char kDroidSansFallback[] = "Droid Sans Fallback"; |
| |
| // Repeat the function from bytestring.cpp here, so it can be used in |
| // a constexpr context. Then make the compiler prove the mappings are |
| // correct. |
| constexpr uint32_t HashNormalizeFontName(const char* family) { |
| uint32_t hash_code = 0; |
| UNSAFE_BUFFERS({ |
| for (size_t i = 0; family[i] != '\0'; ++i) { |
| char ch = family[i]; |
| if (ch == ' ' || ch == '-' || ch == ',') { |
| continue; |
| } |
| hash_code = 31 * hash_code + FXSYS_ToLowerASCII(ch); |
| } |
| }); |
| return hash_code; |
| } |
| |
| static_assert(HashNormalizeFontName("Arial") == 0x058c5083); |
| static_assert(HashNormalizeFontName("Baskerville") == 0x3d49f40e); |
| static_assert(HashNormalizeFontName("Courier New") == 0x83746053); |
| static_assert(HashNormalizeFontName("Courier") == 0x3918fe2d); |
| static_assert(HashNormalizeFontName("Cursive") == 0x432c41c5); |
| static_assert(HashNormalizeFontName("Fantasy") == 0xbf85ff26); |
| static_assert(HashNormalizeFontName("Georgia") == 0xfb4ce0de); |
| static_assert(HashNormalizeFontName("Goudy") == 0x05dfade2); |
| static_assert(HashNormalizeFontName("Monaco") == 0xc04fe601); |
| static_assert(HashNormalizeFontName("Monospace") == 0xaaa60c03); |
| static_assert(HashNormalizeFontName("Palatino") == 0x3b98b31c); |
| static_assert(HashNormalizeFontName("STSong") == 0xcad5eaf6); |
| static_assert(HashNormalizeFontName("Sanserif") == 0x779ce19d); |
| static_assert(HashNormalizeFontName("Serif") == 0x0684317d); |
| static_assert(HashNormalizeFontName("SimHei") == 0xca3812d5); |
| static_assert(HashNormalizeFontName("SimSun") == 0xca383e15); |
| static_assert(HashNormalizeFontName("Tahoma") == 0xcb7a04c8); |
| static_assert(HashNormalizeFontName("Verdana") == 0x14ee2d13); |
| |
| constexpr int kSkiaMatchNameWeight = 62; |
| constexpr int kSkiaMatchSystemNameWeight = 60; |
| constexpr int kSkiaMatchSerifStyleWeight = 16; |
| constexpr int kSkiaMatchScriptStyleWeight = 8; |
| |
| struct SkiaFontMap { |
| uint32_t family; |
| const char* subst; |
| }; |
| |
| const SkiaFontMap kSkiaFontmap[] = { |
| {0x058c5083, kRoboto}, // Arial |
| {0x05dfade2, kDroidSerif}, // Goudy |
| {0x0684317d, kDroidSerif}, // Serif |
| {0x14ee2d13, kRoboto}, // Verdana |
| {0x3918fe2d, kDroidSansMono}, // Courier |
| {0x3b98b31c, kDroidSerif}, // Palatino |
| {0x3d49f40e, kDroidSerif}, // Baskerville |
| {0x432c41c5, kDroidSerif}, // Cursive |
| {0x491b6ad0, kDroidSerif}, // Unknown |
| {0x5612cab1, kDroidSansFallback}, // Unknown |
| {0x779ce19d, kRoboto}, // Sanserif |
| {0x7cc9510b, kDroidSansFallback}, // Unknown |
| {0x83746053, kDroidSansMono}, // Courier New |
| {0xaaa60c03, kDroidSansMono}, // Monospace |
| {0xbf85ff26, kDroidSerif}, // Fantasy |
| {0xc04fe601, kDroidSansMono}, // Monaco |
| {0xca3812d5, kDroidSansFallback}, // SimHei |
| {0xca383e15, kDroidSansFallback}, // SimSun |
| {0xcad5eaf6, kDroidSansFallback}, // STSong |
| {0xcb7a04c8, kRoboto}, // Tahoma |
| {0xfb4ce0de, kDroidSerif}, // Georgia |
| }; |
| |
| const SkiaFontMap kSkiaSansFontMap[] = { |
| {0x058c5083, kDroidSans}, // Arial |
| {0x14ee2d13, kDroidSans}, // Verdana |
| {0x779ce19d, kDroidSans}, // Sanserif |
| {0xcb7a04c8, kDroidSans}, // Tahoma |
| {0xfb4ce0de, kDroidSans}, // Georgia |
| }; |
| |
| const char* SkiaGetSubstFont(uint32_t hash, |
| pdfium::span<const SkiaFontMap> font_map) { |
| const SkiaFontMap* it = std::ranges::lower_bound( |
| font_map, hash, std::less<>{}, &SkiaFontMap::family); |
| |
| if (it != font_map.end() && it->family == hash) { |
| return it->subst; |
| } |
| return nullptr; |
| } |
| |
| // Map from OS/2 table ulCodePageRange1 bits (0-31) to FX_CharsetFlag. |
| // See https://learn.microsoft.com/en-us/typography/opentype/spec/os2 |
| constexpr auto kFPFSkiaFontFXCharsetFlags = |
| std::to_array<const FX_CharsetFlag>({ |
| FX_CharsetFlag::kANSI, // 0 (Latin 1) |
| FX_CharsetFlag::kMSWin_EasternEuropean, // 1 (Latin 2) |
| FX_CharsetFlag::kMSWin_Cyrillic, // 2 (Cyrillic) |
| FX_CharsetFlag::kMSWin_Greek, // 3 (Greek) |
| FX_CharsetFlag::kMSWin_Turkish, // 4 (Turkish) |
| FX_CharsetFlag::kMSWin_Hebrew, // 5 (Hebrew) |
| FX_CharsetFlag::kMSWin_Arabic, // 6 (Arabic) |
| FX_CharsetFlag::kMSWin_Baltic, // 7 (Baltic) |
| FX_CharsetFlag::kMSWin_Vietnamese, // 8 (Vietnamese) |
| FX_CharsetFlag::kNone, // 9 (Alt ANSI) |
| FX_CharsetFlag::kNone, // 10 (Alt ANSI) |
| FX_CharsetFlag::kNone, // 11 (Alt ANSI) |
| FX_CharsetFlag::kNone, // 12 (Alt ANSI) |
| FX_CharsetFlag::kNone, // 13 (Alt ANSI) |
| FX_CharsetFlag::kNone, // 14 (Alt ANSI) |
| FX_CharsetFlag::kNone, // 15 (Alt ANSI) |
| FX_CharsetFlag::kThai, // 16 (Thai) |
| FX_CharsetFlag::kShiftJIS, // 17 (JIS/Japan) |
| FX_CharsetFlag::kChineseSimplified, // 18 (CN Simple) |
| FX_CharsetFlag::kHangul, // 19 (Korean Wansung) |
| FX_CharsetFlag::kChineseTraditional, // 20 (CN Trad) |
| FX_CharsetFlag::kJohab, // 21 (Korean Johab) |
| FX_CharsetFlag::kNone, // 22 (Alt ANSI/OEM) |
| FX_CharsetFlag::kNone, // 23 (Alt ANSI/OEM) |
| FX_CharsetFlag::kNone, // 24 (Alt ANSI/OEM) |
| FX_CharsetFlag::kNone, // 25 (Alt ANSI/OEM) |
| FX_CharsetFlag::kNone, // 26 (Alt ANSI/OEM) |
| FX_CharsetFlag::kNone, // 27 (Alt ANSI/OEM) |
| FX_CharsetFlag::kNone, // 28 (Alt ANSI/OEM) |
| FX_CharsetFlag::kNone, // 29 (Mac US Roman) |
| FX_CharsetFlag::kOEM, // 30 (OEM) |
| FX_CharsetFlag::kSymbol, // 31 (Symbol) |
| }); |
| |
| Mask<FX_CharsetFlag> SkiaGetFaceCharsets(uint32_t code_range) { |
| static_assert(static_cast<uint32_t>(FX_CharsetFlag::kNone) == 0, |
| "FX_CharsetFlag::kNone must be 0 for ORing to be a no-op"); |
| Mask<FX_CharsetFlag> charsets; |
| for (int32_t i = 0; i < 32; i++) { |
| if (code_range & (1U << i)) { |
| charsets |= kFPFSkiaFontFXCharsetFlags[i]; |
| } |
| } |
| return charsets; |
| } |
| |
| uint32_t SkiaNormalizeFontName(ByteStringView family) { |
| uint32_t hash_code = 0; |
| for (unsigned char ch : family) { |
| if (ch == ' ' || ch == '-' || ch == ',') { |
| continue; |
| } |
| hash_code = 31 * hash_code + tolower(ch); |
| } |
| return hash_code; |
| } |
| |
| uint32_t GetFamilyHash(ByteStringView family, |
| uint32_t style, |
| FX_Charset charset) { |
| ByteString font(family); |
| if (FontStyleIsForceBold(style)) { |
| font += "Bold"; |
| } |
| if (FontStyleIsItalic(style)) { |
| font += "Italic"; |
| } |
| if (FontStyleIsSerif(style)) { |
| font += "Serif"; |
| } |
| font += static_cast<uint8_t>(charset); |
| return FX_HashCode_GetA(font.AsStringView()); |
| } |
| |
| bool SkiaIsCJK(FX_Charset charset) { |
| return FX_CharSetIsCJK(charset); |
| } |
| |
| bool SkiaMaybeSymbol(ByteStringView facename) { |
| ByteString name(facename); |
| name.MakeLower(); |
| return name.Contains("symbol"); |
| } |
| |
| bool SkiaMaybeArabic(ByteStringView facename) { |
| ByteString name(facename); |
| name.MakeLower(); |
| return name.Contains("arabic"); |
| } |
| |
| } // namespace |
| |
| CFPF_SkiaFontMgr::CFPF_SkiaFontMgr() = default; |
| |
| CFPF_SkiaFontMgr::~CFPF_SkiaFontMgr() = default; |
| |
| CFPF_SkiaFontMgr::Entry::Entry() = default; |
| |
| CFPF_SkiaFontMgr::Entry::~Entry() = default; |
| |
| void CFPF_SkiaFontMgr::LoadFonts(const char** user_paths) { |
| if (loaded_fonts_) { |
| return; |
| } |
| |
| ScanPath("/system/fonts"); |
| |
| if (user_paths) { |
| // SAFETY: nullptr-terminated array required from caller. |
| UNSAFE_BUFFERS({ |
| for (const char** path = user_paths; *path; ++path) { |
| ScanPath(*path); |
| } |
| }); |
| } |
| |
| loaded_fonts_ = true; |
| } |
| |
| CFPF_SkiaFont* CFPF_SkiaFontMgr::CreateFont(ByteStringView family_name, |
| FX_Charset charset, |
| uint32_t style) { |
| const uint32_t hash = GetFamilyHash(family_name, style, charset); |
| auto family_iter = family_font_map_.find(hash); |
| if (family_iter != family_font_map_.end()) { |
| return family_iter->second.get(); |
| } |
| |
| const uint32_t face_name_hash = SkiaNormalizeFontName(family_name); |
| const char* subst_name = SkiaGetSubstFont(face_name_hash, kSkiaFontmap); |
| const char* subst_sans_name = |
| SkiaGetSubstFont(face_name_hash, kSkiaSansFontMap); |
| const uint32_t subst_hash = |
| subst_name ? SkiaNormalizeFontName(subst_name) : 0; |
| const uint32_t subst_sans_hash = |
| subst_sans_name ? SkiaNormalizeFontName(subst_sans_name) : 0; |
| const bool maybe_symbol = SkiaMaybeSymbol(family_name); |
| if (charset != FX_Charset::kMSWin_Arabic && SkiaMaybeArabic(family_name)) { |
| charset = FX_Charset::kMSWin_Arabic; |
| } else if (charset == FX_Charset::kANSI) { |
| charset = FX_Charset::kDefault; |
| } |
| int32_t expected_score = kSkiaMatchNameWeight + |
| kSkiaMatchSerifStyleWeight * 3 + |
| kSkiaMatchScriptStyleWeight * 2; |
| const Entry* best_font = nullptr; |
| int32_t best_score = -1; |
| int32_t best_glyph_num = 0; |
| for (const std::unique_ptr<Entry>& font : pdfium::Reversed(font_faces_)) { |
| if (charset != FX_Charset::kDefault) { |
| FX_CharsetFlag flag = FX_CharsetFlagForCharset(charset); |
| if (flag != FX_CharsetFlag::kNone && !(font->charsets & flag)) { |
| continue; |
| } |
| } |
| int32_t score = 0; |
| const uint32_t sys_font_name_hash = |
| SkiaNormalizeFontName(font->family.AsStringView()); |
| if (face_name_hash == sys_font_name_hash) { |
| score += kSkiaMatchNameWeight; |
| } |
| bool matches_name = (score == kSkiaMatchNameWeight); |
| if (FontStyleIsForceBold(style) == FontStyleIsForceBold(font->style)) { |
| score += kSkiaMatchSerifStyleWeight; |
| } |
| if (FontStyleIsItalic(style) == FontStyleIsItalic(font->style)) { |
| score += kSkiaMatchSerifStyleWeight; |
| } |
| if (FontStyleIsFixedPitch(style) == FontStyleIsFixedPitch(font->style)) { |
| score += kSkiaMatchScriptStyleWeight; |
| } |
| if (FontStyleIsSerif(style) == FontStyleIsSerif(font->style)) { |
| score += kSkiaMatchSerifStyleWeight; |
| } |
| if (FontStyleIsScript(style) == FontStyleIsScript(font->style)) { |
| score += kSkiaMatchScriptStyleWeight; |
| } |
| if (subst_hash == sys_font_name_hash || |
| subst_sans_hash == sys_font_name_hash) { |
| score += kSkiaMatchSystemNameWeight; |
| matches_name = true; |
| } |
| if (charset == FX_Charset::kDefault || maybe_symbol) { |
| if (score > best_score && matches_name) { |
| best_score = score; |
| best_font = font.get(); |
| } |
| } else if (SkiaIsCJK(charset)) { |
| if (matches_name || font->glyph_num > best_glyph_num) { |
| best_font = font.get(); |
| best_glyph_num = font->glyph_num; |
| } |
| } else if (score > best_score) { |
| best_score = score; |
| best_font = font.get(); |
| } |
| if (score >= expected_score) { |
| best_font = font.get(); |
| break; |
| } |
| } |
| if (!best_font) { |
| return nullptr; |
| } |
| |
| auto face = GetFontFace(best_font->path, best_font->face_index); |
| if (!face) { |
| return nullptr; |
| } |
| auto font = std::make_unique<CFPF_SkiaFont>(std::move(face), charset); |
| |
| CFPF_SkiaFont* ret = font.get(); |
| family_font_map_[hash] = std::move(font); |
| return ret; |
| } |
| |
| RetainPtr<CFX_Face> CFPF_SkiaFontMgr::GetFontFace(const ByteString& path, |
| int32_t face_index) { |
| auto mapped_bytes = fxcrt::MappedDataBytes::Create(path); |
| if (!mapped_bytes) { |
| return nullptr; |
| } |
| return CFX_Face::New(nullptr, |
| pdfium::MakeRetain<CFX_ReadOnlyMappedDataBytesStream>( |
| std::move(mapped_bytes)), |
| face_index); |
| } |
| |
| void CFPF_SkiaFontMgr::ScanPath(const ByteString& path) { |
| std::unique_ptr<FX_Folder> handle = FX_Folder::OpenFolder(path); |
| if (!handle) { |
| return; |
| } |
| |
| ByteString filename; |
| bool is_folder = false; |
| while (handle->GetNextFile(&filename, &is_folder)) { |
| if (is_folder) { |
| if (filename == "." || filename == "..") { |
| continue; |
| } |
| } else { |
| ByteString ext = filename.Last(4); |
| ext.MakeLower(); |
| if (ext != ".ttf" && ext != ".ttc" && ext != ".otf") { |
| continue; |
| } |
| } |
| ByteString fullpath(path); |
| fullpath += "/"; |
| fullpath += filename; |
| if (is_folder) { |
| ScanPath(fullpath); |
| } else { |
| ScanFile(fullpath); |
| } |
| } |
| } |
| |
| void CFPF_SkiaFontMgr::ScanFile(const ByteString& file) { |
| constexpr int kFaceIndex = 0; |
| RetainPtr<CFX_Face> face = GetFontFace(file, kFaceIndex); |
| if (!face) { |
| return; |
| } |
| font_faces_.push_back(ReportFace(face, file, kFaceIndex)); |
| } |
| |
| std::unique_ptr<CFPF_SkiaFontMgr::Entry> CFPF_SkiaFontMgr::ReportFace( |
| RetainPtr<CFX_Face> face, |
| const ByteString& file, |
| int face_index) { |
| Mask<FX_CharsetFlag> charsets; |
| std::optional<std::array<uint32_t, 2>> cp_range = face->GetOs2CodePageRange(); |
| if (cp_range.has_value()) { |
| charsets |= SkiaGetFaceCharsets(cp_range.value()[0]); |
| } |
| auto entry = std::make_unique<Entry>(); |
| entry->path = file; |
| entry->family = face->GetFamilyName(); |
| entry->style = face->GetFontStyle(); |
| entry->face_index = face_index; |
| entry->charsets = charsets; |
| entry->glyph_num = face->GetGlyphCount(); |
| return entry; |
| } |