| // 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/cfx_folderfontinfo.h" |
| |
| #include <array> |
| #include <iterator> |
| #include <limits> |
| #include <utility> |
| |
| #include "build/build_config.h" |
| #include "core/fxcrt/byteorder.h" |
| #include "core/fxcrt/check_op.h" |
| #include "core/fxcrt/compiler_specific.h" |
| #include "core/fxcrt/containers/contains.h" |
| #include "core/fxcrt/fixed_size_data_vector.h" |
| #include "core/fxcrt/fx_codepage.h" |
| #include "core/fxcrt/fx_extension.h" |
| #include "core/fxcrt/fx_folder.h" |
| #include "core/fxcrt/fx_safe_types.h" |
| #include "core/fxcrt/fx_system.h" |
| #include "core/fxcrt/stl_util.h" |
| #include "core/fxge/cfx_fontmapper.h" |
| #include "core/fxge/fx_font.h" |
| |
| namespace { |
| |
| struct FontSubst { |
| const char* m_pName; |
| const char* m_pSubstName; |
| }; |
| |
| constexpr auto kBase14Substs = fxcrt::ToArray<const FontSubst>({ |
| {"Courier", "Courier New"}, |
| {"Courier-Bold", "Courier New Bold"}, |
| {"Courier-BoldOblique", "Courier New Bold Italic"}, |
| {"Courier-Oblique", "Courier New Italic"}, |
| {"Helvetica", "Arial"}, |
| {"Helvetica-Bold", "Arial Bold"}, |
| {"Helvetica-BoldOblique", "Arial Bold Italic"}, |
| {"Helvetica-Oblique", "Arial Italic"}, |
| {"Times-Roman", "Times New Roman"}, |
| {"Times-Bold", "Times New Roman Bold"}, |
| {"Times-BoldItalic", "Times New Roman Bold Italic"}, |
| {"Times-Italic", "Times New Roman Italic"}, |
| }); |
| |
| // Used with std::unique_ptr to automatically call fclose(). |
| struct FxFileCloser { |
| inline void operator()(FILE* h) const { |
| if (h) |
| fclose(h); |
| } |
| }; |
| |
| bool FindFamilyNameMatch(ByteStringView family_name, |
| const ByteString& installed_font_name) { |
| std::optional<size_t> result = installed_font_name.Find(family_name, 0); |
| if (!result.has_value()) |
| return false; |
| |
| size_t next_index = result.value() + family_name.GetLength(); |
| // Rule out the case that |family_name| is a substring of |
| // |installed_font_name| but their family names are actually different words. |
| // For example: "Univers" and "Universal" are not a match because they have |
| // different family names, but "Univers" and "Univers Bold" are a match. |
| if (installed_font_name.IsValidIndex(next_index) && |
| FXSYS_IsLowerASCII(installed_font_name[next_index])) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| ByteString ReadStringFromFile(FILE* pFile, uint32_t size) { |
| ByteString result; |
| { |
| // Span's lifetime must end before ReleaseBuffer() below. |
| pdfium::span<char> buffer = result.GetBuffer(size); |
| if (!fread(buffer.data(), size, 1, pFile)) |
| return ByteString(); |
| } |
| result.ReleaseBuffer(size); |
| return result; |
| } |
| |
| ByteString LoadTableFromTT(FILE* pFile, |
| const uint8_t* pTables, |
| uint32_t nTables, |
| uint32_t tag, |
| FX_FILESIZE fileSize) { |
| UNSAFE_TODO({ |
| for (uint32_t i = 0; i < nTables; i++) { |
| // TODO(tsepez): use actual span. |
| auto p = pdfium::make_span(pTables + i * 16, 16u); |
| if (fxcrt::GetUInt32MSBFirst(p) == tag) { |
| uint32_t offset = fxcrt::GetUInt32MSBFirst(p.subspan(8)); |
| uint32_t size = fxcrt::GetUInt32MSBFirst(p.subspan(12)); |
| if (offset > std::numeric_limits<uint32_t>::max() - size || |
| static_cast<FX_FILESIZE>(offset + size) > fileSize || |
| fseek(pFile, offset, SEEK_SET) < 0) { |
| return ByteString(); |
| } |
| return ReadStringFromFile(pFile, size); |
| } |
| } |
| }); |
| return ByteString(); |
| } |
| |
| uint32_t GetCharset(FX_Charset charset) { |
| switch (charset) { |
| case FX_Charset::kShiftJIS: |
| return CHARSET_FLAG_SHIFTJIS; |
| case FX_Charset::kChineseSimplified: |
| return CHARSET_FLAG_GB; |
| case FX_Charset::kChineseTraditional: |
| return CHARSET_FLAG_BIG5; |
| case FX_Charset::kHangul: |
| return CHARSET_FLAG_KOREAN; |
| case FX_Charset::kSymbol: |
| return CHARSET_FLAG_SYMBOL; |
| case FX_Charset::kANSI: |
| return CHARSET_FLAG_ANSI; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| } // namespace |
| |
| CFX_FolderFontInfo::CFX_FolderFontInfo() = default; |
| |
| CFX_FolderFontInfo::~CFX_FolderFontInfo() = default; |
| |
| void CFX_FolderFontInfo::AddPath(const ByteString& path) { |
| m_PathList.push_back(path); |
| } |
| |
| bool CFX_FolderFontInfo::EnumFontList(CFX_FontMapper* pMapper) { |
| m_pMapper = pMapper; |
| for (const auto& path : m_PathList) |
| ScanPath(path); |
| return true; |
| } |
| |
| void CFX_FolderFontInfo::ScanPath(const ByteString& path) { |
| std::unique_ptr<FX_Folder> handle = FX_Folder::OpenFolder(path); |
| if (!handle) |
| return; |
| |
| ByteString filename; |
| bool bFolder; |
| while (handle->GetNextFile(&filename, &bFolder)) { |
| if (bFolder) { |
| if (filename == "." || filename == "..") |
| continue; |
| } else { |
| ByteString ext = filename.Last(4); |
| ext.MakeLower(); |
| if (ext != ".ttf" && ext != ".ttc" && ext != ".otf") |
| continue; |
| } |
| |
| ByteString fullpath = path; |
| #if BUILDFLAG(IS_WIN) |
| fullpath += "\\"; |
| #else |
| fullpath += "/"; |
| #endif |
| |
| fullpath += filename; |
| bFolder ? ScanPath(fullpath) : ScanFile(fullpath); |
| } |
| } |
| |
| void CFX_FolderFontInfo::ScanFile(const ByteString& path) { |
| std::unique_ptr<FILE, FxFileCloser> pFile(fopen(path.c_str(), "rb")); |
| if (!pFile) |
| return; |
| |
| fseek(pFile.get(), 0, SEEK_END); |
| |
| FX_FILESIZE filesize = ftell(pFile.get()); |
| uint8_t buffer[16]; |
| fseek(pFile.get(), 0, SEEK_SET); |
| |
| size_t items_read = fread(buffer, /*size=*/12, /*nmemb=*/1, pFile.get()); |
| if (items_read != 1) { |
| return; |
| } |
| uint32_t magic = |
| fxcrt::GetUInt32MSBFirst(pdfium::make_span(buffer).first<4u>()); |
| if (magic != kTableTTCF) { |
| ReportFace(path, pFile.get(), filesize, 0); |
| return; |
| } |
| |
| uint32_t nFaces = |
| fxcrt::GetUInt32MSBFirst(pdfium::make_span(buffer).subspan<8u>()); |
| FX_SAFE_SIZE_T safe_face_bytes = nFaces; |
| safe_face_bytes *= 4; |
| if (!safe_face_bytes.IsValid()) |
| return; |
| |
| auto offsets = |
| FixedSizeDataVector<uint8_t>::Uninit(safe_face_bytes.ValueOrDie()); |
| pdfium::span<uint8_t> offsets_span = offsets.span(); |
| items_read = fread(offsets_span.data(), /*size=*/1, |
| /*nmemb=*/offsets_span.size(), pFile.get()); |
| if (items_read != offsets_span.size()) { |
| return; |
| } |
| |
| for (uint32_t i = 0; i < nFaces; i++) { |
| ReportFace(path, pFile.get(), filesize, |
| fxcrt::GetUInt32MSBFirst(offsets_span.subspan(i * 4))); |
| } |
| } |
| |
| void CFX_FolderFontInfo::ReportFace(const ByteString& path, |
| FILE* pFile, |
| FX_FILESIZE filesize, |
| uint32_t offset) { |
| char buffer[16]; |
| if (fseek(pFile, offset, SEEK_SET) < 0 || !fread(buffer, 12, 1, pFile)) |
| return; |
| |
| uint32_t nTables = |
| fxcrt::GetUInt16MSBFirst(pdfium::as_byte_span(buffer).subspan<4, 2>()); |
| ByteString tables = ReadStringFromFile(pFile, nTables * 16); |
| if (tables.IsEmpty()) |
| return; |
| |
| static constexpr uint32_t kNameTag = |
| CFX_FontMapper::MakeTag('n', 'a', 'm', 'e'); |
| ByteString names = LoadTableFromTT(pFile, tables.unsigned_str(), nTables, |
| kNameTag, filesize); |
| if (names.IsEmpty()) |
| return; |
| |
| ByteString facename = GetNameFromTT(names.unsigned_span(), 1); |
| if (facename.IsEmpty()) |
| return; |
| |
| ByteString style = GetNameFromTT(names.unsigned_span(), 2); |
| if (style != "Regular") |
| facename += " " + style; |
| |
| if (pdfium::Contains(m_FontList, facename)) |
| return; |
| |
| auto pInfo = |
| std::make_unique<FontFaceInfo>(path, facename, tables, offset, filesize); |
| static constexpr uint32_t kOs2Tag = |
| CFX_FontMapper::MakeTag('O', 'S', '/', '2'); |
| ByteString os2 = |
| LoadTableFromTT(pFile, tables.unsigned_str(), nTables, kOs2Tag, filesize); |
| if (os2.GetLength() >= 86) { |
| pdfium::span<const uint8_t> p = os2.unsigned_span().subspan(78); |
| uint32_t codepages = fxcrt::GetUInt32MSBFirst(p); |
| if (codepages & (1U << 17)) { |
| m_pMapper->AddInstalledFont(facename, FX_Charset::kShiftJIS); |
| pInfo->m_Charsets |= CHARSET_FLAG_SHIFTJIS; |
| } |
| if (codepages & (1U << 18)) { |
| m_pMapper->AddInstalledFont(facename, FX_Charset::kChineseSimplified); |
| pInfo->m_Charsets |= CHARSET_FLAG_GB; |
| } |
| if (codepages & (1U << 20)) { |
| m_pMapper->AddInstalledFont(facename, FX_Charset::kChineseTraditional); |
| pInfo->m_Charsets |= CHARSET_FLAG_BIG5; |
| } |
| if ((codepages & (1U << 19)) || (codepages & (1U << 21))) { |
| m_pMapper->AddInstalledFont(facename, FX_Charset::kHangul); |
| pInfo->m_Charsets |= CHARSET_FLAG_KOREAN; |
| } |
| if (codepages & (1U << 31)) { |
| m_pMapper->AddInstalledFont(facename, FX_Charset::kSymbol); |
| pInfo->m_Charsets |= CHARSET_FLAG_SYMBOL; |
| } |
| } |
| m_pMapper->AddInstalledFont(facename, FX_Charset::kANSI); |
| pInfo->m_Charsets |= CHARSET_FLAG_ANSI; |
| pInfo->m_Styles = 0; |
| if (style.Contains("Bold")) |
| pInfo->m_Styles |= FXFONT_FORCE_BOLD; |
| if (style.Contains("Italic") || style.Contains("Oblique")) |
| pInfo->m_Styles |= FXFONT_ITALIC; |
| if (facename.Contains("Serif")) |
| pInfo->m_Styles |= FXFONT_SERIF; |
| |
| m_FontList[facename] = std::move(pInfo); |
| } |
| |
| void* CFX_FolderFontInfo::GetSubstFont(const ByteString& face) { |
| for (size_t iBaseFont = 0; iBaseFont < std::size(kBase14Substs); |
| iBaseFont++) { |
| if (face == kBase14Substs[iBaseFont].m_pName) { |
| return GetFont(kBase14Substs[iBaseFont].m_pSubstName); |
| } |
| } |
| return nullptr; |
| } |
| |
| void* CFX_FolderFontInfo::FindFont(int weight, |
| bool bItalic, |
| FX_Charset charset, |
| int pitch_family, |
| const ByteString& family, |
| bool bMatchName) { |
| FontFaceInfo* pFind = nullptr; |
| uint32_t charset_flag = GetCharset(charset); |
| |
| int32_t iBestSimilar = 0; |
| if (bMatchName) { |
| // Try a direct lookup for either a perfect score or to determine a |
| // baseline similarity score. |
| auto direct_it = m_FontList.find(family); |
| if (direct_it != m_FontList.end()) { |
| FontFaceInfo* pFont = direct_it->second.get(); |
| if (pFont->IsEligibleForFindFont(charset_flag, charset)) { |
| iBestSimilar = |
| pFont->SimilarityScore(weight, bItalic, pitch_family, bMatchName); |
| if (iBestSimilar == FontFaceInfo::kSimilarityScoreMax) { |
| return pFont; |
| } |
| pFind = pFont; |
| } |
| } |
| } |
| // Try and find a better match. Since FindFamilyNameMatch() is expensive, |
| // avoid calling it unless there might be a better match. |
| ByteStringView bsFamily = family.AsStringView(); |
| for (const auto& it : m_FontList) { |
| const ByteString& bsName = it.first; |
| FontFaceInfo* pFont = it.second.get(); |
| if (!pFont->IsEligibleForFindFont(charset_flag, charset)) { |
| continue; |
| } |
| int32_t iSimilarValue = pFont->SimilarityScore( |
| weight, bItalic, pitch_family, |
| bMatchName && bsFamily.GetLength() == bsName.GetLength()); |
| if (iSimilarValue > iBestSimilar) { |
| if (bMatchName && !FindFamilyNameMatch(bsFamily, bsName)) { |
| continue; |
| } |
| iBestSimilar = iSimilarValue; |
| pFind = pFont; |
| } |
| } |
| |
| if (pFind) { |
| return pFind; |
| } |
| |
| if (charset == FX_Charset::kANSI && FontFamilyIsFixedPitch(pitch_family)) { |
| auto* courier_new = GetFont("Courier New"); |
| if (courier_new) |
| return courier_new; |
| } |
| |
| return nullptr; |
| } |
| |
| void* CFX_FolderFontInfo::MapFont(int weight, |
| bool bItalic, |
| FX_Charset charset, |
| int pitch_family, |
| const ByteString& face) { |
| return nullptr; |
| } |
| |
| void* CFX_FolderFontInfo::GetFont(const ByteString& face) { |
| auto it = m_FontList.find(face); |
| return it != m_FontList.end() ? it->second.get() : nullptr; |
| } |
| |
| size_t CFX_FolderFontInfo::GetFontData(void* hFont, |
| uint32_t table, |
| pdfium::span<uint8_t> buffer) { |
| if (!hFont) |
| return 0; |
| |
| const FontFaceInfo* pFont = static_cast<FontFaceInfo*>(hFont); |
| uint32_t datasize = 0; |
| uint32_t offset = 0; |
| if (table == 0) { |
| datasize = pFont->m_FontOffset ? 0 : pFont->m_FileSize; |
| } else if (table == kTableTTCF) { |
| datasize = pFont->m_FontOffset ? pFont->m_FileSize : 0; |
| } else { |
| size_t nTables = pFont->m_FontTables.GetLength() / 16; |
| for (size_t i = 0; i < nTables; i++) { |
| // TODO(tsepez): iterate over span. |
| pdfium::span<const uint8_t> p = |
| pFont->m_FontTables.unsigned_span().subspan(i * 16); |
| if (fxcrt::GetUInt32MSBFirst(p) == table) { |
| offset = fxcrt::GetUInt32MSBFirst(p.subspan(8)); |
| datasize = fxcrt::GetUInt32MSBFirst(p.subspan(12)); |
| } |
| } |
| } |
| |
| if (!datasize || buffer.size() < datasize) |
| return datasize; |
| |
| std::unique_ptr<FILE, FxFileCloser> pFile( |
| fopen(pFont->m_FilePath.c_str(), "rb")); |
| if (!pFile) |
| return 0; |
| |
| if (fseek(pFile.get(), offset, SEEK_SET) < 0 || |
| fread(buffer.data(), datasize, 1, pFile.get()) != 1) { |
| return 0; |
| } |
| return datasize; |
| } |
| |
| void CFX_FolderFontInfo::DeleteFont(void* hFont) {} |
| |
| bool CFX_FolderFontInfo::GetFaceName(void* hFont, ByteString* name) { |
| if (!hFont) |
| return false; |
| *name = static_cast<FontFaceInfo*>(hFont)->m_FaceName; |
| return true; |
| } |
| |
| bool CFX_FolderFontInfo::GetFontCharset(void* hFont, FX_Charset* charset) { |
| return false; |
| } |
| |
| CFX_FolderFontInfo::FontFaceInfo::FontFaceInfo(ByteString filePath, |
| ByteString faceName, |
| ByteString fontTables, |
| uint32_t fontOffset, |
| uint32_t fileSize) |
| : m_FilePath(filePath), |
| m_FaceName(faceName), |
| m_FontTables(fontTables), |
| m_FontOffset(fontOffset), |
| m_FileSize(fileSize) {} |
| |
| bool CFX_FolderFontInfo::FontFaceInfo::IsEligibleForFindFont( |
| uint32_t flag, |
| FX_Charset charset) const { |
| return (m_Charsets & flag) || charset == FX_Charset::kDefault; |
| } |
| |
| int32_t CFX_FolderFontInfo::FontFaceInfo::SimilarityScore( |
| int weight, |
| bool italic, |
| int pitch_family, |
| bool exact_match_bonus) const { |
| int32_t score = 0; |
| if (FontStyleIsForceBold(m_Styles) == (weight > 400)) { |
| score += 16; |
| } |
| if (FontStyleIsItalic(m_Styles) == italic) { |
| score += 16; |
| } |
| if (FontStyleIsSerif(m_Styles) == FontFamilyIsRoman(pitch_family)) { |
| score += 16; |
| } |
| if (FontStyleIsScript(m_Styles) == FontFamilyIsScript(pitch_family)) { |
| score += 8; |
| } |
| if (FontStyleIsFixedPitch(m_Styles) == FontFamilyIsFixedPitch(pitch_family)) { |
| score += 8; |
| } |
| if (exact_match_bonus) { |
| score += 4; |
| } |
| DCHECK_LE(score, kSimilarityScoreMax); |
| return score; |
| } |