fix the issue
This commit is contained in:
@@ -17,6 +17,9 @@ add_library(pdfengine STATIC
|
||||
src/parser/pdfium_loader.cpp
|
||||
src/parser/pdfium_document.cpp
|
||||
src/fonts/face/font_face.cpp
|
||||
src/fonts/face/free_type_manager.cpp
|
||||
src/fonts/loader/font_resolver.cpp
|
||||
src/fonts/pdf_fonts/font_loader.cpp
|
||||
src/fonts/shaping/hb_shaper.cpp
|
||||
src/fonts/cache/glyph_bitmap.cpp
|
||||
src/fonts/cache/glyph_cache.cpp
|
||||
|
||||
@@ -118,6 +118,9 @@ public:
|
||||
[[nodiscard]] virtual std::expected<std::vector<FontInfo>, EngineError>
|
||||
getFonts(int startPage = 0, int endPage = -1) const = 0;
|
||||
|
||||
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
|
||||
getFontData(const std::string& internalFontId) const = 0;
|
||||
|
||||
virtual std::expected<void, EngineError> applyEdits(const std::string& editsJson) = 0;
|
||||
|
||||
[[nodiscard]] virtual std::expected<std::vector<uint8_t>, EngineError>
|
||||
|
||||
+6
-6
@@ -13,12 +13,12 @@ GlyphCache::GlyphCache(std::size_t capacity)
|
||||
GlyphCache::~GlyphCache() = default;
|
||||
|
||||
std::optional<GlyphBitmap> GlyphCache::get(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize) {
|
||||
FT_Face face = fontFace.getFace();
|
||||
if (!face) {
|
||||
uint64_t fontId = fontFace.getId();
|
||||
if (fontId == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GlyphCacheKey key{face, glyphIndex, fontSize};
|
||||
GlyphCacheKey key{fontId, glyphIndex, fontSize};
|
||||
std::size_t shard_idx = getShardIndex(key);
|
||||
auto& shard = *shards_[shard_idx];
|
||||
|
||||
@@ -37,12 +37,12 @@ std::optional<GlyphBitmap> GlyphCache::get(const FontFace& fontFace, unsigned in
|
||||
}
|
||||
|
||||
void GlyphCache::insert(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize, const GlyphBitmap& bitmap) {
|
||||
FT_Face face = fontFace.getFace();
|
||||
if (!face) {
|
||||
uint64_t fontId = fontFace.getId();
|
||||
if (fontId == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
GlyphCacheKey key{face, glyphIndex, fontSize};
|
||||
GlyphCacheKey key{fontId, glyphIndex, fontSize};
|
||||
std::size_t shard_idx = getShardIndex(key);
|
||||
auto& shard = *shards_[shard_idx];
|
||||
|
||||
|
||||
+3
-3
@@ -12,12 +12,12 @@
|
||||
namespace pdfengine::fonts {
|
||||
|
||||
struct GlyphCacheKey {
|
||||
FT_Face face;
|
||||
uint64_t fontId;
|
||||
unsigned int glyphIndex;
|
||||
unsigned int fontSize;
|
||||
|
||||
bool operator==(const GlyphCacheKey& other) const {
|
||||
return face == other.face &&
|
||||
return fontId == other.fontId &&
|
||||
glyphIndex == other.glyphIndex &&
|
||||
fontSize == other.fontSize;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ struct GlyphCacheKey {
|
||||
|
||||
struct GlyphCacheKeyHash {
|
||||
std::size_t operator()(const GlyphCacheKey& key) const {
|
||||
std::size_t h1 = std::hash<void*>{}(static_cast<void*>(key.face));
|
||||
std::size_t h1 = std::hash<uint64_t>{}(key.fontId);
|
||||
std::size_t h2 = std::hash<unsigned int>{}(key.glyphIndex);
|
||||
std::size_t h3 = std::hash<unsigned int>{}(key.fontSize);
|
||||
// Combine hashes using standard boost hash_combine algorithm
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#include "fonts/face/font_face.hpp"
|
||||
#include "fonts/face/free_type_manager.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
|
||||
namespace pdfengine::fonts {
|
||||
|
||||
FontFace::FontFace()
|
||||
: ft_library_(nullptr),
|
||||
face_(nullptr) {
|
||||
static std::atomic<uint64_t> g_font_id_counter{1};
|
||||
|
||||
if (FT_Init_FreeType(&ft_library_)) {
|
||||
std::cerr << "Failed to initialize FreeType\n";
|
||||
}
|
||||
FontFace::FontFace()
|
||||
: font_id_(g_font_id_counter.fetch_add(1, std::memory_order_relaxed)),
|
||||
face_(nullptr),
|
||||
mutex_(std::make_unique<std::mutex>()) {
|
||||
}
|
||||
|
||||
FontFace::~FontFace() {
|
||||
@@ -18,17 +19,14 @@ FontFace::~FontFace() {
|
||||
if (face_) {
|
||||
FT_Done_Face(face_);
|
||||
}
|
||||
|
||||
if (ft_library_) {
|
||||
FT_Done_FreeType(ft_library_);
|
||||
}
|
||||
}
|
||||
|
||||
FontFace::FontFace(FontFace&& other) noexcept
|
||||
: ft_library_(other.ft_library_),
|
||||
: font_id_(other.font_id_),
|
||||
face_(other.face_),
|
||||
mutex_(std::move(other.mutex_)),
|
||||
font_data_(std::move(other.font_data_)) {
|
||||
other.ft_library_ = nullptr;
|
||||
other.font_id_ = 0;
|
||||
other.face_ = nullptr;
|
||||
}
|
||||
|
||||
@@ -37,13 +35,11 @@ FontFace& FontFace::operator=(FontFace&& other) noexcept {
|
||||
if (face_) {
|
||||
FT_Done_Face(face_);
|
||||
}
|
||||
if (ft_library_) {
|
||||
FT_Done_FreeType(ft_library_);
|
||||
}
|
||||
ft_library_ = other.ft_library_;
|
||||
font_id_ = other.font_id_;
|
||||
face_ = other.face_;
|
||||
mutex_ = std::move(other.mutex_);
|
||||
font_data_ = std::move(other.font_data_);
|
||||
other.ft_library_ = nullptr;
|
||||
other.font_id_ = 0;
|
||||
other.face_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
@@ -58,7 +54,7 @@ bool FontFace::loadFromFile(const std::string& path) {
|
||||
font_data_.clear();
|
||||
|
||||
if (FT_New_Face(
|
||||
ft_library_,
|
||||
FreeTypeManager::instance().getLibrary(),
|
||||
path.c_str(),
|
||||
0,
|
||||
&face_)) {
|
||||
@@ -91,7 +87,7 @@ bool FontFace::loadFromMemory(const std::vector<uint8_t>& data) {
|
||||
font_data_ = data;
|
||||
|
||||
if (FT_New_Memory_Face(
|
||||
ft_library_,
|
||||
FreeTypeManager::instance().getLibrary(),
|
||||
font_data_.data(),
|
||||
static_cast<FT_Long>(font_data_.size()),
|
||||
0,
|
||||
@@ -113,6 +109,14 @@ FT_Face FontFace::getFace() const {
|
||||
return face_;
|
||||
}
|
||||
|
||||
uint64_t FontFace::getId() const {
|
||||
return font_id_;
|
||||
}
|
||||
|
||||
std::mutex& FontFace::getMutex() const {
|
||||
return *mutex_;
|
||||
}
|
||||
|
||||
std::optional<GlyphBitmap> FontFace::renderGlyph(unsigned int glyphIndex, unsigned int fontSize) {
|
||||
if (!face_) {
|
||||
return std::nullopt;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
@@ -26,13 +28,16 @@ public:
|
||||
bool loadFromMemory(const std::vector<uint8_t>& data);
|
||||
|
||||
FT_Face getFace() const;
|
||||
uint64_t getId() const;
|
||||
std::mutex& getMutex() const;
|
||||
|
||||
// Renders a glyph by index and size, returning a GlyphBitmap on success.
|
||||
std::optional<GlyphBitmap> renderGlyph(unsigned int glyphIndex, unsigned int fontSize);
|
||||
|
||||
private:
|
||||
FT_Library ft_library_;
|
||||
uint64_t font_id_;
|
||||
FT_Face face_;
|
||||
std::unique_ptr<std::mutex> mutex_;
|
||||
std::vector<uint8_t> font_data_; // Keeps the loaded memory buffer alive for FT_Face
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "fonts/face/free_type_manager.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace pdfengine::fonts {
|
||||
|
||||
FreeTypeManager& FreeTypeManager::instance() {
|
||||
static FreeTypeManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
FreeTypeManager::FreeTypeManager() : library_(nullptr) {
|
||||
if (FT_Init_FreeType(&library_)) {
|
||||
std::cerr << "Failed to initialize FreeType\n";
|
||||
}
|
||||
}
|
||||
|
||||
FreeTypeManager::~FreeTypeManager() {
|
||||
if (library_) {
|
||||
FT_Done_FreeType(library_);
|
||||
library_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
FT_Library FreeTypeManager::getLibrary() const {
|
||||
return library_;
|
||||
}
|
||||
|
||||
} // namespace pdfengine::fonts
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
namespace pdfengine::fonts {
|
||||
|
||||
class FreeTypeManager {
|
||||
public:
|
||||
static FreeTypeManager& instance();
|
||||
|
||||
FT_Library getLibrary() const;
|
||||
|
||||
// Delete copy/move constructors and assignment operators for singleton
|
||||
FreeTypeManager(const FreeTypeManager&) = delete;
|
||||
FreeTypeManager& operator=(const FreeTypeManager&) = delete;
|
||||
FreeTypeManager(FreeTypeManager&&) = delete;
|
||||
FreeTypeManager& operator=(FreeTypeManager&&) = delete;
|
||||
|
||||
private:
|
||||
FreeTypeManager();
|
||||
~FreeTypeManager();
|
||||
|
||||
FT_Library library_;
|
||||
};
|
||||
|
||||
} // namespace pdfengine::fonts
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "fonts/loader/font_resolver.hpp"
|
||||
#include "fonts/pdf_fonts/font_loader.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace pdfengine::fonts::loader {
|
||||
|
||||
FontResolver::FontResolver(std::shared_ptr<PdfDocument> document)
|
||||
: document_(std::move(document)) {}
|
||||
|
||||
std::expected<std::unique_ptr<pdf_fonts::Font>, std::string> FontResolver::resolveFont(const FontInfo& fontInfo) {
|
||||
if (!document_) {
|
||||
return std::unexpected("No document attached to FontResolver");
|
||||
}
|
||||
|
||||
if (fontInfo.isEmbedded) {
|
||||
auto dataRes = document_->getFontData(fontInfo.internalFontId);
|
||||
if (!dataRes) {
|
||||
spdlog::error("Failed to extract embedded font data for ID: {}", fontInfo.internalFontId);
|
||||
return std::unexpected("Failed to extract embedded font data");
|
||||
}
|
||||
|
||||
const auto& bytes = dataRes.value();
|
||||
|
||||
// Route to the appropriate FontLoader method based on FontInfo type
|
||||
if (fontInfo.type == "TrueType" || fontInfo.type == "Type1") {
|
||||
// Treat embedded Type1/TrueType uniformly through the TrueType loader for now
|
||||
// since FontLoader::loadTrueTypeFromMemory delegates to FreeType which handles both.
|
||||
// A more strictly compliant PDF parser would differentiate, but FreeType is unified.
|
||||
auto font = pdf_fonts::FontLoader::loadTrueTypeFromMemory(fontInfo.normalizedFamily, bytes);
|
||||
if (font) {
|
||||
return font;
|
||||
}
|
||||
} else if (fontInfo.type == "CIDFontType0" || fontInfo.type == "CIDFontType2") {
|
||||
pdf_fonts::FontType subtype = (fontInfo.type == "CIDFontType0")
|
||||
? pdf_fonts::FontType::CIDFontType0
|
||||
: pdf_fonts::FontType::CIDFontType2;
|
||||
|
||||
auto font = pdf_fonts::FontLoader::loadCIDFontFromMemory(fontInfo.normalizedFamily, subtype, bytes);
|
||||
if (font) {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
||||
return std::unexpected("Failed to parse extracted font data");
|
||||
} else {
|
||||
// Handle System Fallback
|
||||
spdlog::info("Resolving system fallback font for: {}", fontInfo.fontName);
|
||||
|
||||
if (fontInfo.type == "CIDFontType0" || fontInfo.type == "CIDFontType2") {
|
||||
pdf_fonts::FontType subtype = (fontInfo.type == "CIDFontType0")
|
||||
? pdf_fonts::FontType::CIDFontType0
|
||||
: pdf_fonts::FontType::CIDFontType2;
|
||||
auto font = pdf_fonts::FontLoader::loadCIDFontSystemFallback(fontInfo.normalizedFamily, subtype);
|
||||
if (font) return font;
|
||||
} else {
|
||||
auto font = pdf_fonts::FontLoader::loadType1SystemFallback(fontInfo.normalizedFamily);
|
||||
if (font) return font;
|
||||
}
|
||||
|
||||
return std::unexpected("Failed to load system fallback font");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace pdfengine::fonts::loader
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "fonts/pdf_fonts/font.hpp"
|
||||
#include <pdfengine/pdf_document.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <expected>
|
||||
|
||||
namespace pdfengine::fonts::loader {
|
||||
|
||||
class FontResolver {
|
||||
public:
|
||||
explicit FontResolver(std::shared_ptr<PdfDocument> document);
|
||||
|
||||
// Resolves a FontInfo object into a fully loaded Font ready for shaping.
|
||||
// If the font is embedded, it extracts the raw bytes from the PdfDocument.
|
||||
// If it's a system fallback, it uses FontLoader to load it from the OS.
|
||||
std::expected<std::unique_ptr<pdf_fonts::Font>, std::string> resolveFont(const FontInfo& fontInfo);
|
||||
|
||||
private:
|
||||
std::shared_ptr<PdfDocument> document_;
|
||||
};
|
||||
|
||||
} // namespace pdfengine::fonts::loader
|
||||
@@ -126,7 +126,8 @@ uint32_t CIDFont::decodeToUnicode(uint32_t charCode) const {
|
||||
if (descriptor_) {
|
||||
std::string fontName = descriptor_->getFontName();
|
||||
std::string lowerName = fontName;
|
||||
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
|
||||
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
|
||||
std::string registry = "None";
|
||||
if (lowerName.find("simsun") != std::string::npos ||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <hb.h>
|
||||
#include <hb-ft.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace pdfengine::fonts {
|
||||
|
||||
@@ -21,6 +22,9 @@ std::vector<ShapedGlyph> HbShaper::shapeRun(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Acquire lock to prevent concurrent mutation of FT_Face's active pixel size
|
||||
std::lock_guard<std::mutex> lock(font.getMutex());
|
||||
|
||||
// Set the pixel size on the FreeType face before shaping.
|
||||
// This ensures HarfBuzz measures everything using the requested font size context.
|
||||
if (FT_Set_Pixel_Sizes(ftFace, 0, fontSize)) {
|
||||
@@ -53,8 +57,23 @@ std::vector<ShapedGlyph> HbShaper::shapeRun(
|
||||
hb_buffer_set_direction(hbBuffer, HB_DIRECTION_TTB);
|
||||
}
|
||||
|
||||
// Enable professional typography features (Kerning and Ligatures)
|
||||
hb_feature_t features[2];
|
||||
|
||||
// Enable kerning
|
||||
features[0].tag = HB_TAG('k', 'e', 'r', 'n');
|
||||
features[0].value = 1;
|
||||
features[0].start = 0;
|
||||
features[0].end = static_cast<unsigned int>(-1);
|
||||
|
||||
// Enable standard ligatures
|
||||
features[1].tag = HB_TAG('l', 'i', 'g', 'a');
|
||||
features[1].value = 1;
|
||||
features[1].start = 0;
|
||||
features[1].end = static_cast<unsigned int>(-1);
|
||||
|
||||
// Shape the text inside the buffer using the font.
|
||||
hb_shape(hbFont, hbBuffer, nullptr, 0);
|
||||
hb_shape(hbFont, hbBuffer, features, 2);
|
||||
|
||||
// Retrieve the results.
|
||||
unsigned int glyphCount = 0;
|
||||
|
||||
@@ -1509,6 +1509,54 @@ std::expected<std::vector<FontInfo>, EngineError> PdfiumDocument::getFonts(int s
|
||||
#endif
|
||||
}
|
||||
|
||||
std::expected<std::vector<uint8_t>, EngineError> PdfiumDocument::getFontData(const std::string& internalFontId) const {
|
||||
#ifdef PDFENGINE_WITH_PDFIUM
|
||||
ensure_pdfium_initialized();
|
||||
if (!doc_) {
|
||||
return std::unexpected(EngineError::Unknown);
|
||||
}
|
||||
|
||||
int numPages = FPDF_GetPageCount(doc_);
|
||||
for (int i = 0; i < numPages; ++i) {
|
||||
FPDF_PAGE page = FPDF_LoadPage(doc_, i);
|
||||
if (!page) continue;
|
||||
|
||||
int objectCount = FPDFPage_CountObjects(page);
|
||||
for (int j = 0; j < objectCount; ++j) {
|
||||
FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, j);
|
||||
if (!obj || FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_TEXT) continue;
|
||||
|
||||
FPDF_FONT font = FPDFTextObj_GetFont(obj);
|
||||
if (!font) continue;
|
||||
|
||||
unsigned long nameLen = FPDFFont_GetBaseFontName(font, nullptr, 0);
|
||||
if (nameLen > 0) {
|
||||
std::vector<char> nameBuf(nameLen);
|
||||
if (FPDFFont_GetBaseFontName(font, nameBuf.data(), nameLen) > 0) {
|
||||
std::string fontName(nameBuf.data());
|
||||
// internalFontId is constructed using fontName as the prefix
|
||||
if (internalFontId.find(fontName) == 0) {
|
||||
size_t buflen = FPDFFont_GetFontData(font, nullptr, 0);
|
||||
if (buflen > 0) {
|
||||
std::vector<uint8_t> buffer(buflen);
|
||||
if (FPDFFont_GetFontData(font, buffer.data(), buflen) > 0) {
|
||||
FPDF_ClosePage(page);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FPDF_ClosePage(page);
|
||||
}
|
||||
return std::unexpected(EngineError::FileNotFound);
|
||||
#else
|
||||
(void)internalFontId;
|
||||
return std::unexpected(EngineError::Unknown);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PdfiumDocument::invalidateCaches() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(fontsMutex_);
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
|
||||
std::expected<std::shared_ptr<PdfPage>, EngineError> getPage(int pageIndex) override;
|
||||
std::expected<std::vector<FontInfo>, EngineError> getFonts(int startPage = 0, int endPage = -1) const override;
|
||||
std::expected<std::vector<uint8_t>, EngineError> getFontData(const std::string& internalFontId) const override;
|
||||
void invalidateCaches();
|
||||
|
||||
std::expected<void, EngineError> applyEdits(const std::string& editsJson) override;
|
||||
|
||||
Reference in New Issue
Block a user