blob: 516b52dcf0beaa57a5e35ca0caf04601d219cf01 [file]
// 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;
}