fix the issue

This commit is contained in:
saqib mir
2026-06-06 11:21:31 +05:30
parent ce5ad5bb16
commit bfa8d0eddf
14 changed files with 258 additions and 31 deletions
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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
+23 -19
View File
@@ -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;
+6 -1
View File
@@ -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
+64
View File
@@ -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
+24
View File
@@ -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 ||
+20 -1
View File
@@ -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;
+48
View File
@@ -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_);
+1
View File
@@ -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;