Merge branch 'dev' of https://gitea.maskantech.in/gitea_admin/pdf into azeem
This commit is contained in:
+13
-2
@@ -16,8 +16,19 @@ add_library(pdfengine STATIC
|
|||||||
src/core/skia_renderer.cpp
|
src/core/skia_renderer.cpp
|
||||||
src/parser/pdfium_loader.cpp
|
src/parser/pdfium_loader.cpp
|
||||||
src/parser/pdfium_document.cpp
|
src/parser/pdfium_document.cpp
|
||||||
src/fonts/font_face.cpp
|
src/fonts/face/font_face.cpp
|
||||||
src/fonts/hb_shaper.cpp
|
src/fonts/shaping/hb_shaper.cpp
|
||||||
|
src/fonts/cache/glyph_bitmap.cpp
|
||||||
|
src/fonts/cache/glyph_cache.cpp
|
||||||
|
src/fonts/pdf_fonts/types/truetype_font.cpp
|
||||||
|
src/fonts/pdf_fonts/types/type1_font.cpp
|
||||||
|
src/fonts/pdf_fonts/types/cid_font.cpp
|
||||||
|
src/fonts/pdf_fonts/font_loader.cpp
|
||||||
|
src/fonts/pdf_fonts/font_descriptor.cpp
|
||||||
|
src/fonts/pdf_fonts/font_fallback.cpp
|
||||||
|
src/fonts/pdf_fonts/font_subset.cpp
|
||||||
|
src/fonts/pdf_fonts/encoding/encoding.cpp
|
||||||
|
src/fonts/pdf_fonts/encoding/tounicode_parser.cpp
|
||||||
)
|
)
|
||||||
add_library(pdfengine::pdfengine ALIAS pdfengine)
|
add_library(pdfengine::pdfengine ALIAS pdfengine)
|
||||||
|
|
||||||
|
|||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
#include "fonts/cache/glyph_bitmap.hpp"
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts {
|
||||||
|
|
||||||
|
struct GlyphBitmap {
|
||||||
|
std::vector<unsigned char> pixels; // 8-bit grayscale pixels (0 = transparent, 255 = fully opaque)
|
||||||
|
int width = 0; // Width of the glyph bitmap in pixels
|
||||||
|
int height = 0; // Height of the glyph bitmap in pixels
|
||||||
|
int bearingX = 0; // Horizontal bearing X (bitmap_left) in pixels
|
||||||
|
int bearingY = 0; // Horizontal bearing Y (bitmap_top) in pixels
|
||||||
|
double advance = 0.0; // Horizontal advance in pixels
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
#include "fonts/cache/glyph_cache.hpp"
|
||||||
|
|
||||||
|
namespace pdfengine::fonts {
|
||||||
|
|
||||||
|
GlyphCache::GlyphCache(std::size_t capacity)
|
||||||
|
: capacity_(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) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlyphCacheKey key{face, glyphIndex, fontSize};
|
||||||
|
auto it = cache_map_.find(key);
|
||||||
|
if (it == cache_map_.end()) {
|
||||||
|
misses_++;
|
||||||
|
return std::nullopt; // Cache miss
|
||||||
|
}
|
||||||
|
|
||||||
|
hits_++;
|
||||||
|
// Cache hit: move the referenced key to the front of the LRU list
|
||||||
|
lru_list_.splice(lru_list_.begin(), lru_list_, it->second.second);
|
||||||
|
|
||||||
|
return it->second.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlyphCache::insert(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize, const GlyphBitmap& bitmap) {
|
||||||
|
FT_Face face = fontFace.getFace();
|
||||||
|
if (!face) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlyphCacheKey key{face, glyphIndex, fontSize};
|
||||||
|
auto it = cache_map_.find(key);
|
||||||
|
if (it != cache_map_.end()) {
|
||||||
|
// Element already exists: update bitmap and move it to the front
|
||||||
|
it->second.first = bitmap;
|
||||||
|
lru_list_.splice(lru_list_.begin(), lru_list_, it->second.second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evict oldest element if at capacity
|
||||||
|
if (cache_map_.size() >= capacity_ && capacity_ > 0) {
|
||||||
|
GlyphCacheKey oldest = lru_list_.back();
|
||||||
|
cache_map_.erase(oldest);
|
||||||
|
lru_list_.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new element
|
||||||
|
if (capacity_ > 0) {
|
||||||
|
lru_list_.push_front(key);
|
||||||
|
cache_map_[key] = std::make_pair(bitmap, lru_list_.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t GlyphCache::size() const {
|
||||||
|
return cache_map_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t GlyphCache::capacity() const {
|
||||||
|
return capacity_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlyphCache::clear() {
|
||||||
|
cache_map_.clear();
|
||||||
|
lru_list_.clear();
|
||||||
|
resetStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
double GlyphCache::hitRate() const {
|
||||||
|
std::size_t total = hits_ + misses_;
|
||||||
|
if (total == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return static_cast<double>(hits_) / total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlyphCache::resetStats() {
|
||||||
|
hits_ = 0;
|
||||||
|
misses_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fonts/face/font_face.hpp"
|
||||||
|
#include "fonts/cache/glyph_bitmap.hpp"
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <list>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts {
|
||||||
|
|
||||||
|
struct GlyphCacheKey {
|
||||||
|
FT_Face face;
|
||||||
|
unsigned int glyphIndex;
|
||||||
|
unsigned int fontSize;
|
||||||
|
|
||||||
|
bool operator==(const GlyphCacheKey& other) const {
|
||||||
|
return face == other.face &&
|
||||||
|
glyphIndex == other.glyphIndex &&
|
||||||
|
fontSize == other.fontSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 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
|
||||||
|
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)) ^ (h3 + 0x9e3779b9 + (h2 << 6) + (h2 >> 2));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class GlyphCache {
|
||||||
|
public:
|
||||||
|
explicit GlyphCache(std::size_t capacity);
|
||||||
|
~GlyphCache();
|
||||||
|
|
||||||
|
// Cache is move-only to prevent copying internal list iterators
|
||||||
|
GlyphCache(const GlyphCache&) = delete;
|
||||||
|
GlyphCache& operator=(const GlyphCache&) = delete;
|
||||||
|
GlyphCache(GlyphCache&&) noexcept = default;
|
||||||
|
GlyphCache& operator=(GlyphCache&&) noexcept = default;
|
||||||
|
|
||||||
|
// Retrieves a glyph from the cache (and marks it as most recently used on hit)
|
||||||
|
std::optional<GlyphBitmap> get(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize);
|
||||||
|
|
||||||
|
// Inserts a glyph into the cache. Evicts the least recently used glyph if full.
|
||||||
|
void insert(const FontFace& fontFace, unsigned int glyphIndex, unsigned int fontSize, const GlyphBitmap& bitmap);
|
||||||
|
|
||||||
|
// Returns the current number of cached glyphs
|
||||||
|
std::size_t size() const;
|
||||||
|
|
||||||
|
// Returns the maximum capacity of the cache
|
||||||
|
std::size_t capacity() const;
|
||||||
|
|
||||||
|
// Clears all elements from the cache
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// Returns the cache hit rate (hits / (hits + misses)). Returns 0.0 if no lookups have occurred.
|
||||||
|
double hitRate() const;
|
||||||
|
|
||||||
|
// Resets hit and miss counters
|
||||||
|
void resetStats();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::size_t capacity_;
|
||||||
|
std::size_t hits_ = 0;
|
||||||
|
std::size_t misses_ = 0;
|
||||||
|
std::list<GlyphCacheKey> lru_list_;
|
||||||
|
|
||||||
|
using CacheIterator = std::list<GlyphCacheKey>::iterator;
|
||||||
|
std::unordered_map<
|
||||||
|
GlyphCacheKey,
|
||||||
|
std::pair<GlyphBitmap, CacheIterator>,
|
||||||
|
GlyphCacheKeyHash
|
||||||
|
> cache_map_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
#include "fonts/face/font_face.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts {
|
||||||
|
|
||||||
|
FontFace::FontFace()
|
||||||
|
: ft_library_(nullptr),
|
||||||
|
face_(nullptr) {
|
||||||
|
|
||||||
|
if (FT_Init_FreeType(&ft_library_)) {
|
||||||
|
std::cerr << "Failed to initialize FreeType\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_),
|
||||||
|
face_(other.face_),
|
||||||
|
font_data_(std::move(other.font_data_)) {
|
||||||
|
other.ft_library_ = nullptr;
|
||||||
|
other.face_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontFace& FontFace::operator=(FontFace&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (face_) {
|
||||||
|
FT_Done_Face(face_);
|
||||||
|
}
|
||||||
|
if (ft_library_) {
|
||||||
|
FT_Done_FreeType(ft_library_);
|
||||||
|
}
|
||||||
|
ft_library_ = other.ft_library_;
|
||||||
|
face_ = other.face_;
|
||||||
|
font_data_ = std::move(other.font_data_);
|
||||||
|
other.ft_library_ = nullptr;
|
||||||
|
other.face_ = nullptr;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontFace::loadFromFile(const std::string& path) {
|
||||||
|
|
||||||
|
if (face_) {
|
||||||
|
FT_Done_Face(face_);
|
||||||
|
face_ = nullptr;
|
||||||
|
}
|
||||||
|
font_data_.clear();
|
||||||
|
|
||||||
|
if (FT_New_Face(
|
||||||
|
ft_library_,
|
||||||
|
path.c_str(),
|
||||||
|
0,
|
||||||
|
&face_)) {
|
||||||
|
|
||||||
|
std::cerr << "Failed to load font: "
|
||||||
|
<< path << '\n';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a default pixel size of 16px so that font coordinates and shaping
|
||||||
|
// advances are non-zero by default.
|
||||||
|
FT_Set_Pixel_Sizes(face_, 0, 16);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontFace::loadFromMemory(const std::vector<uint8_t>& data) {
|
||||||
|
if (data.empty()) {
|
||||||
|
std::cerr << "Cannot load font from empty memory buffer\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (face_) {
|
||||||
|
FT_Done_Face(face_);
|
||||||
|
face_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy to internal buffer to guarantee its lifetime aligns with face_
|
||||||
|
font_data_ = data;
|
||||||
|
|
||||||
|
if (FT_New_Memory_Face(
|
||||||
|
ft_library_,
|
||||||
|
font_data_.data(),
|
||||||
|
static_cast<FT_Long>(font_data_.size()),
|
||||||
|
0,
|
||||||
|
&face_)) {
|
||||||
|
|
||||||
|
std::cerr << "Failed to load font from memory buffer\n";
|
||||||
|
font_data_.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a default pixel size of 16px so that font coordinates and shaping
|
||||||
|
// advances are non-zero by default.
|
||||||
|
FT_Set_Pixel_Sizes(face_, 0, 16);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_Face FontFace::getFace() const {
|
||||||
|
return face_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GlyphBitmap> FontFace::renderGlyph(unsigned int glyphIndex, unsigned int fontSize) {
|
||||||
|
if (!face_) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set font size in pixels.
|
||||||
|
if (FT_Set_Pixel_Sizes(face_, 0, fontSize)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load and render the glyph bitmap into the face->glyph slot.
|
||||||
|
if (FT_Load_Glyph(face_, glyphIndex, FT_LOAD_RENDER)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_GlyphSlot slot = face_->glyph;
|
||||||
|
FT_Bitmap& bitmap = slot->bitmap;
|
||||||
|
|
||||||
|
GlyphBitmap glyph_bitmap;
|
||||||
|
glyph_bitmap.width = static_cast<int>(bitmap.width);
|
||||||
|
glyph_bitmap.height = static_cast<int>(bitmap.rows);
|
||||||
|
glyph_bitmap.bearingX = slot->bitmap_left;
|
||||||
|
glyph_bitmap.bearingY = slot->bitmap_top;
|
||||||
|
|
||||||
|
// Advance is in 26.6 fractional pixels. Convert to double.
|
||||||
|
glyph_bitmap.advance = static_cast<double>(slot->advance.x) / 64.0;
|
||||||
|
|
||||||
|
// Extract the pixels. Pitch specifies bytes per row.
|
||||||
|
if (glyph_bitmap.width > 0 && glyph_bitmap.height > 0) {
|
||||||
|
glyph_bitmap.pixels.resize(glyph_bitmap.width * glyph_bitmap.height);
|
||||||
|
for (int r = 0; r < glyph_bitmap.height; ++r) {
|
||||||
|
std::copy(
|
||||||
|
bitmap.buffer + r * bitmap.pitch,
|
||||||
|
bitmap.buffer + r * bitmap.pitch + glyph_bitmap.width,
|
||||||
|
glyph_bitmap.pixels.begin() + r * glyph_bitmap.width
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return glyph_bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "fonts/cache/glyph_bitmap.hpp"
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
@@ -19,12 +23,17 @@ public:
|
|||||||
FontFace& operator=(FontFace&& other) noexcept;
|
FontFace& operator=(FontFace&& other) noexcept;
|
||||||
|
|
||||||
bool loadFromFile(const std::string& path);
|
bool loadFromFile(const std::string& path);
|
||||||
|
bool loadFromMemory(const std::vector<uint8_t>& data);
|
||||||
|
|
||||||
FT_Face getFace() const;
|
FT_Face getFace() const;
|
||||||
|
|
||||||
|
// Renders a glyph by index and size, returning a GlyphBitmap on success.
|
||||||
|
std::optional<GlyphBitmap> renderGlyph(unsigned int glyphIndex, unsigned int fontSize);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FT_Library ft_library_;
|
FT_Library ft_library_;
|
||||||
FT_Face face_;
|
FT_Face face_;
|
||||||
|
std::vector<uint8_t> font_data_; // Keeps the loaded memory buffer alive for FT_Face
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdfengine::fonts
|
} // namespace pdfengine::fonts
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#include "font_face.hpp"
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace pdfengine::fonts {
|
|
||||||
|
|
||||||
FontFace::FontFace()
|
|
||||||
: ft_library_(nullptr),
|
|
||||||
face_(nullptr) {
|
|
||||||
|
|
||||||
if (FT_Init_FreeType(&ft_library_)) {
|
|
||||||
std::cerr << "Failed to initialize FreeType\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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_),
|
|
||||||
face_(other.face_) {
|
|
||||||
other.ft_library_ = nullptr;
|
|
||||||
other.face_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
FontFace& FontFace::operator=(FontFace&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
if (face_) {
|
|
||||||
FT_Done_Face(face_);
|
|
||||||
}
|
|
||||||
if (ft_library_) {
|
|
||||||
FT_Done_FreeType(ft_library_);
|
|
||||||
}
|
|
||||||
ft_library_ = other.ft_library_;
|
|
||||||
face_ = other.face_;
|
|
||||||
other.ft_library_ = nullptr;
|
|
||||||
other.face_ = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FontFace::loadFromFile(const std::string& path) {
|
|
||||||
|
|
||||||
if (FT_New_Face(
|
|
||||||
ft_library_,
|
|
||||||
path.c_str(),
|
|
||||||
0,
|
|
||||||
&face_)) {
|
|
||||||
|
|
||||||
std::cerr << "Failed to load font: "
|
|
||||||
<< path << '\n';
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a default pixel size of 16px so that font coordinates and shaping
|
|
||||||
// advances are non-zero by default.
|
|
||||||
FT_Set_Pixel_Sizes(face_, 0, 16);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FT_Face FontFace::getFace() const {
|
|
||||||
return face_;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace pdfengine::fonts
|
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
#include "fonts/pdf_fonts/encoding/encoding.hpp"
|
||||||
|
#include "fonts/pdf_fonts/encoding/tounicode_parser.hpp"
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Maps Windows CP-1252 exceptions (0x80 - 0x9F) for WinAnsiEncoding
|
||||||
|
uint32_t getWinAnsiException(uint32_t code) {
|
||||||
|
switch (code) {
|
||||||
|
case 128: return 0x20AC; // Euro
|
||||||
|
case 130: return 0x201A; // Single low-9 quote
|
||||||
|
case 131: return 0x0192; // Florin
|
||||||
|
case 132: return 0x201E; // Double low-9 quote
|
||||||
|
case 133: return 0x2026; // Ellipsis
|
||||||
|
case 134: return 0x2020; // Dagger
|
||||||
|
case 135: return 0x2021; // Double Dagger
|
||||||
|
case 136: return 0x02C6; // Circumflex
|
||||||
|
case 137: return 0x2030; // Per mille
|
||||||
|
case 138: return 0x0160; // S Caron
|
||||||
|
case 139: return 0x2039; // Single guillemet left
|
||||||
|
case 140: return 0x0152; // OE
|
||||||
|
case 142: return 0x017D; // Z Caron
|
||||||
|
case 145: return 0x2018; // Single quote left
|
||||||
|
case 146: return 0x2019; // Single quote right
|
||||||
|
case 147: return 0x201C; // Double quote left
|
||||||
|
case 148: return 0x201D; // Double quote right
|
||||||
|
case 149: return 0x2022; // Bullet
|
||||||
|
case 150: return 0x2013; // En dash
|
||||||
|
case 151: return 0x2014; // Em dash
|
||||||
|
case 152: return 0x02DC; // Tilde
|
||||||
|
case 153: return 0x2122; // Trademark
|
||||||
|
case 154: return 0x0161; // s Caron
|
||||||
|
case 155: return 0x203A; // Single guillemet right
|
||||||
|
case 156: return 0x0153; // oe
|
||||||
|
case 158: return 0x017E; // z Caron
|
||||||
|
case 159: return 0x0178; // Y Dieresis
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps Standard MacRoman characters from 128 to 255
|
||||||
|
const uint32_t kMacRomanHighPage[128] = {
|
||||||
|
0x00C4, 0x00C5, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00E1, // 128-135
|
||||||
|
0x00E0, 0x00E2, 0x00E4, 0x00E3, 0x00E5, 0x00E7, 0x00E9, 0x00E8, // 136-143
|
||||||
|
0x00EA, 0x00EB, 0x00ED, 0x00EC, 0x00EE, 0x00EF, 0x00F1, 0x00F3, // 144-151
|
||||||
|
0x00F2, 0x00F4, 0x00F6, 0x00F5, 0x00FA, 0x00F9, 0x00FB, 0x00FC, // 152-159
|
||||||
|
0x2020, 0x00B0, 0x00A2, 0x00A3, 0x00A7, 0x2022, 0x00B6, 0x00DF, // 160-167
|
||||||
|
0x00AE, 0x00A9, 0x2122, 0x00B4, 0x00A8, 0x2260, 0x00C6, 0x00D8, // 168-175
|
||||||
|
0x221E, 0x00B1, 0x2264, 0x2265, 0x00A5, 0x00B5, 0x2202, 0x2211, // 176-183
|
||||||
|
0x220F, 0x03C0, 0x222B, 0x00AA, 0x00BA, 0x2126, 0x00E6, 0x00F8, // 184-191
|
||||||
|
0x00BF, 0x00A1, 0x00AC, 0x221A, 0x0192, 0x2248, 0x2206, 0x00AB, // 192-199
|
||||||
|
0x00BB, 0x2026, 0x00A0, 0x00C0, 0x00C3, 0x00D5, 0x0152, 0x0153, // 200-207
|
||||||
|
0x2013, 0x2014, 0x201C, 0x201D, 0x2018, 0x2019, 0x00F7, 0x25CA, // 208-215
|
||||||
|
0x00FF, 0x0178, 0x2044, 0x00A4, 0x2039, 0x203A, 0xFB01, 0xFB02, // 216-223
|
||||||
|
0x2021, 0x00B7, 0x201A, 0x201E, 0x2030, 0x00C2, 0x00CA, 0x00C1, // 224-231
|
||||||
|
0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF, 0x00CC, 0x00D3, 0x00D4, // 232-239
|
||||||
|
0xF8FF, 0x00D2, 0x00DA, 0x00DB, 0x00D9, 0x0131, 0x02C6, 0x02DC, // 240-247
|
||||||
|
0x00AF, 0x02D8, 0x02D9, 0x02DA, 0x00B8, 0x02DD, 0x02DB, 0x02C7 // 248-255
|
||||||
|
};
|
||||||
|
|
||||||
|
// Safe hexadecimal digit conversions
|
||||||
|
bool parseHexValue(const std::string& hexStr, uint32_t& value) {
|
||||||
|
if (hexStr.empty()) return false;
|
||||||
|
std::string cleanHex;
|
||||||
|
for (char c : hexStr) {
|
||||||
|
if (c != '<' && c != '>') {
|
||||||
|
cleanHex += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cleanHex.empty()) return false;
|
||||||
|
try {
|
||||||
|
size_t idx = 0;
|
||||||
|
value = std::stoul(cleanHex, &idx, 16);
|
||||||
|
return idx == cleanHex.size();
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PredefinedEncoding Implementation
|
||||||
|
// ==========================================
|
||||||
|
PredefinedEncoding::PredefinedEncoding(SimpleEncodingType type) : type_(type) {}
|
||||||
|
|
||||||
|
uint32_t PredefinedEncoding::decode(uint32_t charCode) const {
|
||||||
|
if (type_ == SimpleEncodingType::Identity) {
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charCode > 255) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type_ == SimpleEncodingType::WinAnsi) {
|
||||||
|
uint32_t exc = getWinAnsiException(charCode);
|
||||||
|
if (exc != 0) return exc;
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type_ == SimpleEncodingType::MacRoman) {
|
||||||
|
if (charCode < 128) {
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
return kMacRomanHighPage[charCode - 128];
|
||||||
|
}
|
||||||
|
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// CustomEncoding Implementation
|
||||||
|
// ==========================================
|
||||||
|
CustomEncoding::CustomEncoding(std::unique_ptr<Encoding> baseEncoding)
|
||||||
|
: base_(std::move(baseEncoding)) {}
|
||||||
|
|
||||||
|
uint32_t CustomEncoding::decode(uint32_t charCode) const {
|
||||||
|
auto it = custom_map_.find(charCode);
|
||||||
|
if (it != custom_map_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return base_ ? base_->decode(charCode) : charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CustomEncoding::addDifference(uint32_t code, const std::string& glyphName) {
|
||||||
|
uint32_t uni = resolveGlyphNameToUnicode(glyphName);
|
||||||
|
if (uni != 0) {
|
||||||
|
custom_map_[code] = uni;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// ToUnicodeCMap Implementation
|
||||||
|
// ==========================================
|
||||||
|
uint32_t ToUnicodeCMap::decode(uint32_t charCode) const {
|
||||||
|
auto it = cmap_.find(charCode);
|
||||||
|
if (it != cmap_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ToUnicodeCMap::parseCMapStream(const std::string& streamStr) {
|
||||||
|
return ToUnicodeParser::parse(streamStr, cmap_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Adobe Glyph List (AGL) Implementation
|
||||||
|
// ==========================================
|
||||||
|
uint32_t resolveGlyphNameToUnicode(const std::string& name) {
|
||||||
|
if (name.empty()) return 0;
|
||||||
|
|
||||||
|
if (name.length() == 1) {
|
||||||
|
return static_cast<uint32_t>(name[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.length() == 7 && name.rfind("uni", 0) == 0) {
|
||||||
|
std::string hexStr = name.substr(3);
|
||||||
|
uint32_t val = 0;
|
||||||
|
if (parseHexValue(hexStr, val)) return val;
|
||||||
|
}
|
||||||
|
if (name.length() >= 5 && name[0] == 'u' && std::isxdigit(static_cast<unsigned char>(name[1]))) {
|
||||||
|
std::string hexStr = name.substr(1);
|
||||||
|
uint32_t val = 0;
|
||||||
|
if (parseHexValue(hexStr, val)) return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::unordered_map<std::string, uint32_t> kAglTable = {
|
||||||
|
{"space", 0x0020}, {"exclam", 0x0021}, {"quotedbl", 0x0022}, {"numbersign", 0x0023},
|
||||||
|
{"dollar", 0x0024}, {"percent", 0x0025}, {"ampersand", 0x0026}, {"quotesingle", 0x0027},
|
||||||
|
{"parenleft", 0x0028}, {"parenright", 0x0029}, {"asterisk", 0x002A}, {"plus", 0x002B},
|
||||||
|
{"comma", 0x002C}, {"hyphen", 0x002D}, {"period", 0x002E}, {"slash", 0x002F},
|
||||||
|
{"zero", 0x0030}, {"one", 0x0031}, {"two", 0x0032}, {"three", 0x0033},
|
||||||
|
{"four", 0x0034}, {"five", 0x0035}, {"six", 0x0036}, {"seven", 0x0037},
|
||||||
|
{"eight", 0x0038}, {"nine", 0x0039}, {"colon", 0x003A}, {"semicolon", 0x003B},
|
||||||
|
{"less", 0x003C}, {"equal", 0x003D}, {"greater", 0x003E}, {"question", 0x003F},
|
||||||
|
{"at", 0x0040}, {"bracketleft", 0x005B}, {"backslash", 0x005C}, {"bracketright", 0x005D},
|
||||||
|
{"asciicircum", 0x005E}, {"underscore", 0x005F}, {"grave", 0x0060}, {"braceleft", 0x007B},
|
||||||
|
{"bar", 0x007C}, {"braceright", 0x007D}, {"asciitilde", 0x007E}, {"minus", 0x2212},
|
||||||
|
{"bullet", 0x2022}, {"quoteleft", 0x2018}, {"quoteright", 0x2019}, {"quotedblleft", 0x201C},
|
||||||
|
{"quotedblright", 0x201D}, {"degree", 0x00B0}, {"euro", 0x20AC}, {"florin", 0x0192},
|
||||||
|
{"ellipsis", 0x2026}, {"dagger", 0x2020}, {"daggerdbld", 0x2021}, {"circumflex", 0x02C6},
|
||||||
|
{"perthousand", 0x2030}, {"Scaron", 0x0160}, {"guilsinglleft", 0x2039}, {"OE", 0x0152},
|
||||||
|
{"Zcaron", 0x017D}, {"tilde", 0x02DC}, {"trademark", 0x2122}, {"scaron", 0x0161},
|
||||||
|
{"guilsinglright", 0x203A}, {"oe", 0x0153}, {"zcaron", 0x017E}, {"Ydieresis", 0x0178},
|
||||||
|
{"Adieresis", 0x00C4}, {"Aring", 0x00C5}, {"Ccedilla", 0x00C7}, {"Eacute", 0x00C9},
|
||||||
|
{"Ntilde", 0x00D1}, {"Odieresis", 0x00D6}, {"Udieresis", 0x00DC}, {"aacute", 0x00E1},
|
||||||
|
{"agrave", 0x00E0}, {"acircumflex", 0x00E2}, {"adieresis", 0x00E4}, {"atilde", 0x00E3},
|
||||||
|
{"aring", 0x00E5}, {"ccedilla", 0x00E7}, {"eacute", 0x00E9}, {"egrave", 0x00E8},
|
||||||
|
{"ecircumflex", 0x00EA}, {"edieresis", 0x00EB}, {"iacute", 0x00ED}, {"igrave", 0x00EC},
|
||||||
|
{"icircumflex", 0x00EE}, {"idieresis", 0x00EF}, {"ntilde", 0x00E1}, {"oacute", 0x00F3},
|
||||||
|
{"ograve", 0x00F2}, {"ocircumflex", 0x00F4}, {"odieresis", 0x00F6}, {"otilde", 0x00F5},
|
||||||
|
{"uacute", 0x00FA}, {"ugrave", 0x00F9}, {"ucircumflex", 0x00FB}, {"udieresis", 0x00FC},
|
||||||
|
{"germandbls", 0x00DF}, {"ae", 0x00E6}, {"AE", 0x00C6}, {"oe", 0x0153}, {"OE", 0x0152},
|
||||||
|
{"alpha", 0x03B1}, {"beta", 0x03B2}, {"gamma", 0x03B3}, {"delta", 0x03B4}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = kAglTable.find(name);
|
||||||
|
if (it != kAglTable.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
enum class SimpleEncodingType {
|
||||||
|
Standard,
|
||||||
|
MacRoman,
|
||||||
|
WinAnsi,
|
||||||
|
MacExpert,
|
||||||
|
Identity
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base interface for PDF character code to Unicode codepoint translation
|
||||||
|
class Encoding {
|
||||||
|
public:
|
||||||
|
virtual ~Encoding() = default;
|
||||||
|
|
||||||
|
// Translates a PDF character code (typically 1-4 bytes) to a Unicode codepoint
|
||||||
|
virtual uint32_t decode(uint32_t charCode) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Standard predefined simple encodings
|
||||||
|
class PredefinedEncoding : public Encoding {
|
||||||
|
public:
|
||||||
|
explicit PredefinedEncoding(SimpleEncodingType type);
|
||||||
|
~PredefinedEncoding() override = default;
|
||||||
|
|
||||||
|
uint32_t decode(uint32_t charCode) const override;
|
||||||
|
SimpleEncodingType getType() const { return type_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SimpleEncodingType type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Custom encodings constructed with a base predefined encoding and a set of `/Differences`
|
||||||
|
class CustomEncoding : public Encoding {
|
||||||
|
public:
|
||||||
|
explicit CustomEncoding(std::unique_ptr<Encoding> baseEncoding);
|
||||||
|
~CustomEncoding() override = default;
|
||||||
|
|
||||||
|
uint32_t decode(uint32_t charCode) const override;
|
||||||
|
|
||||||
|
// Maps a character code to a postscript glyph name using Adobe Glyph List (AGL)
|
||||||
|
void addDifference(uint32_t code, const std::string& glyphName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Encoding> base_;
|
||||||
|
std::unordered_map<uint32_t, uint32_t> custom_map_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Advanced CMaps for CJK/CID /ToUnicode translation streams
|
||||||
|
class ToUnicodeCMap : public Encoding {
|
||||||
|
public:
|
||||||
|
ToUnicodeCMap() = default;
|
||||||
|
~ToUnicodeCMap() override = default;
|
||||||
|
|
||||||
|
uint32_t decode(uint32_t charCode) const override;
|
||||||
|
|
||||||
|
// Parses a `/ToUnicode` CMap definition from a PDF stream string
|
||||||
|
bool parseCMapStream(const std::string& streamStr);
|
||||||
|
|
||||||
|
// Explicitly add mapping for testing
|
||||||
|
void addMapping(uint32_t code, uint32_t unicode) {
|
||||||
|
cmap_[code] = unicode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<uint32_t, uint32_t> cmap_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adobe Glyph List resolver: translates a standard PostScript glyph name to a Unicode codepoint.
|
||||||
|
uint32_t resolveGlyphNameToUnicode(const std::string& name);
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#include "fonts/pdf_fonts/encoding/tounicode_parser.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Helper to convert hex strings (e.g. "<001E>") to uint32_t
|
||||||
|
bool parseHexValue(const std::string& hexStr, uint32_t& value) {
|
||||||
|
if (hexStr.empty()) return false;
|
||||||
|
std::string cleanHex;
|
||||||
|
for (char c : hexStr) {
|
||||||
|
if (c != '<' && c != '>') {
|
||||||
|
cleanHex += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cleanHex.empty()) return false;
|
||||||
|
try {
|
||||||
|
size_t idx = 0;
|
||||||
|
value = std::stoul(cleanHex, &idx, 16);
|
||||||
|
return idx == cleanHex.size();
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokenizes CMap stream input, ignoring comments and robustly handling delimiters to prevent hangs
|
||||||
|
std::vector<std::string> tokenizeCMap(const std::string& input) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
size_t i = 0;
|
||||||
|
size_t len = input.length();
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
// Skip whitespaces
|
||||||
|
if (std::isspace(static_cast<unsigned char>(input[i])) || input[i] == '\0') {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bypassing comments starting with %
|
||||||
|
if (input[i] == '%') {
|
||||||
|
while (i < len && input[i] != '\n' && input[i] != '\r') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for double angle brackets << and >> (delimiters)
|
||||||
|
if (i + 1 < len && input[i] == '<' && input[i + 1] == '<') {
|
||||||
|
tokens.push_back("<<");
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i + 1 < len && input[i] == '>' && input[i + 1] == '>') {
|
||||||
|
tokens.push_back(">>");
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse hex strings <001E>
|
||||||
|
if (input[i] == '<') {
|
||||||
|
std::string hexToken;
|
||||||
|
hexToken += input[i++];
|
||||||
|
while (i < len && input[i] != '>') {
|
||||||
|
hexToken += input[i++];
|
||||||
|
}
|
||||||
|
if (i < len && input[i] == '>') {
|
||||||
|
hexToken += input[i++];
|
||||||
|
}
|
||||||
|
tokens.push_back(hexToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for other standalone delimiters
|
||||||
|
if (input[i] == '[' || input[i] == ']' || input[i] == '>') {
|
||||||
|
std::string delimToken(1, input[i]);
|
||||||
|
tokens.push_back(delimToken);
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard token parsing
|
||||||
|
std::string token;
|
||||||
|
while (i < len && !std::isspace(static_cast<unsigned char>(input[i])) &&
|
||||||
|
input[i] != '\0' && input[i] != '%' && input[i] != '<' && input[i] != '>' &&
|
||||||
|
input[i] != '[' && input[i] != ']') {
|
||||||
|
token += input[i++];
|
||||||
|
}
|
||||||
|
if (!token.empty()) {
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool ToUnicodeParser::parse(const std::string& streamStr, std::unordered_map<uint32_t, uint32_t>& outMap) {
|
||||||
|
std::vector<std::string> tokens = tokenizeCMap(streamStr);
|
||||||
|
size_t size = tokens.size();
|
||||||
|
size_t idx = 0;
|
||||||
|
bool parsedAny = false;
|
||||||
|
|
||||||
|
while (idx < size) {
|
||||||
|
const std::string& token = tokens[idx];
|
||||||
|
|
||||||
|
if (token == "beginbfchar") {
|
||||||
|
idx++;
|
||||||
|
while (idx < size && tokens[idx] != "endbfchar") {
|
||||||
|
if (idx + 1 >= size) break;
|
||||||
|
const std::string& codeToken = tokens[idx];
|
||||||
|
const std::string& destToken = tokens[idx + 1];
|
||||||
|
|
||||||
|
uint32_t srcCode = 0;
|
||||||
|
uint32_t destCode = 0;
|
||||||
|
|
||||||
|
if (parseHexValue(codeToken, srcCode) && parseHexValue(destToken, destCode)) {
|
||||||
|
outMap[srcCode] = destCode;
|
||||||
|
parsedAny = true;
|
||||||
|
}
|
||||||
|
idx += 2;
|
||||||
|
}
|
||||||
|
if (idx < size && tokens[idx] == "endbfchar") {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (token == "beginbfrange") {
|
||||||
|
idx++;
|
||||||
|
while (idx < size && tokens[idx] != "endbfrange") {
|
||||||
|
if (idx + 2 >= size) break;
|
||||||
|
const std::string& startToken = tokens[idx];
|
||||||
|
const std::string& endToken = tokens[idx + 1];
|
||||||
|
const std::string& destToken = tokens[idx + 2];
|
||||||
|
|
||||||
|
uint32_t startCode = 0;
|
||||||
|
uint32_t endCode = 0;
|
||||||
|
|
||||||
|
if (parseHexValue(startToken, startCode) && parseHexValue(endToken, endCode)) {
|
||||||
|
if (destToken == "[") {
|
||||||
|
// Array mapping: e.g. <0001> <0003> [<0041> <0042> <0043>]
|
||||||
|
idx += 3; // skip start, end, "["
|
||||||
|
uint32_t currentCode = startCode;
|
||||||
|
while (idx < size && tokens[idx] != "]" && currentCode <= endCode) {
|
||||||
|
uint32_t destVal = 0;
|
||||||
|
if (parseHexValue(tokens[idx], destVal)) {
|
||||||
|
outMap[currentCode] = destVal;
|
||||||
|
parsedAny = true;
|
||||||
|
}
|
||||||
|
currentCode++;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
if (idx < size && tokens[idx] == "]") {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Sequential base mapping: e.g. <0001> <0003> <0041>
|
||||||
|
uint32_t destStart = 0;
|
||||||
|
if (parseHexValue(destToken, destStart)) {
|
||||||
|
for (uint32_t code = startCode; code <= endCode; ++code) {
|
||||||
|
outMap[code] = destStart + (code - startCode);
|
||||||
|
}
|
||||||
|
parsedAny = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
idx += 3;
|
||||||
|
}
|
||||||
|
if (idx < size && tokens[idx] == "endbfrange") {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parsedAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class ToUnicodeParser {
|
||||||
|
public:
|
||||||
|
// Parses a ToUnicode CMap stream and populates the provided mapping
|
||||||
|
// Returns true if at least one valid mapping was parsed, false otherwise
|
||||||
|
static bool parse(const std::string& streamStr, std::unordered_map<uint32_t, uint32_t>& outMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fonts/face/font_face.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_descriptor.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class Encoding;
|
||||||
|
class FontSubset;
|
||||||
|
|
||||||
|
enum class FontType {
|
||||||
|
TrueType,
|
||||||
|
Type1,
|
||||||
|
CIDFontType0,
|
||||||
|
CIDFontType2,
|
||||||
|
Type3
|
||||||
|
};
|
||||||
|
|
||||||
|
class Font {
|
||||||
|
public:
|
||||||
|
virtual ~Font() = default;
|
||||||
|
|
||||||
|
virtual std::string getBaseFont() const = 0;
|
||||||
|
virtual FontType getType() const = 0;
|
||||||
|
virtual bool isEmbedded() const = 0;
|
||||||
|
|
||||||
|
// Gets reference to underlying FontFace rendering object
|
||||||
|
virtual pdfengine::fonts::FontFace& getFontFace() = 0;
|
||||||
|
virtual const pdfengine::fonts::FontFace& getFontFace() const = 0;
|
||||||
|
|
||||||
|
// Gets the font descriptor (returns nullptr if none exists)
|
||||||
|
virtual const FontDescriptor* getDescriptor() const = 0;
|
||||||
|
|
||||||
|
// Gets the font encoding (returns nullptr if none exists)
|
||||||
|
virtual const Encoding* getEncoding() const = 0;
|
||||||
|
|
||||||
|
// Gets subsetting details (returns nullptr if font is not subsetted)
|
||||||
|
virtual const FontSubset* getSubsetInfo() const = 0;
|
||||||
|
|
||||||
|
// Translates a raw character code to a Unicode codepoint
|
||||||
|
virtual uint32_t decodeToUnicode(uint32_t charCode) const = 0;
|
||||||
|
|
||||||
|
// Translates a sequence of raw character codes to a UTF-8 string
|
||||||
|
virtual std::string decodeStringToUnicode(const std::vector<uint32_t>& charCodes) const = 0;
|
||||||
|
|
||||||
|
// Widths methods
|
||||||
|
virtual void setWidths(uint32_t firstChar, uint32_t lastChar, const std::vector<double>& widths) {
|
||||||
|
first_char_ = firstChar;
|
||||||
|
last_char_ = lastChar;
|
||||||
|
widths_ = widths;
|
||||||
|
has_widths_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool hasWidths() const {
|
||||||
|
return has_widths_;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual double getCharWidth(uint32_t charCode, double fontSize) const {
|
||||||
|
if (!has_widths_) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (charCode >= first_char_ && charCode <= last_char_) {
|
||||||
|
size_t index = charCode - first_char_;
|
||||||
|
if (index < widths_.size()) {
|
||||||
|
// PDF widths are in 1/1000 units of text space.
|
||||||
|
// Scale to requested fontSize.
|
||||||
|
return (widths_[index] / 1000.0) * fontSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to missing width if descriptor is available
|
||||||
|
const auto* desc = getDescriptor();
|
||||||
|
if (desc && desc->getMissingWidth() > 0.0) {
|
||||||
|
return (desc->getMissingWidth() / 1000.0) * fontSize;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t first_char_ = 0;
|
||||||
|
uint32_t last_char_ = 0;
|
||||||
|
std::vector<double> widths_;
|
||||||
|
bool has_widths_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
#include "fonts/pdf_fonts/font_descriptor.hpp"
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void skipWhitespace(const std::string& str, size_t& pos) {
|
||||||
|
while (pos < str.size()) {
|
||||||
|
char c = str[pos];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\0') {
|
||||||
|
pos++;
|
||||||
|
} else if (c == '%') {
|
||||||
|
// comment, skip to end of line
|
||||||
|
while (pos < str.size() && str[pos] != '\n' && str[pos] != '\r') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool FontDescriptor::parseFromDictionaryString(const std::string& dictStr) {
|
||||||
|
size_t pos = 0;
|
||||||
|
skipWhitespace(dictStr, pos);
|
||||||
|
|
||||||
|
if (pos + 2 > dictStr.size() || dictStr[pos] != '<' || dictStr[pos+1] != '<') {
|
||||||
|
return false; // must start with <<
|
||||||
|
}
|
||||||
|
pos += 2;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
skipWhitespace(dictStr, pos);
|
||||||
|
if (pos >= dictStr.size()) {
|
||||||
|
return false; // missing closing >>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos + 2 <= dictStr.size() && dictStr[pos] == '>' && dictStr[pos+1] == '>') {
|
||||||
|
pos += 2;
|
||||||
|
break; // successfully reached closing >>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dictStr[pos] != '/') {
|
||||||
|
return false; // key must start with /
|
||||||
|
}
|
||||||
|
pos++; // skip '/'
|
||||||
|
|
||||||
|
size_t keyStart = pos;
|
||||||
|
while (pos < dictStr.size()) {
|
||||||
|
char c = dictStr[pos];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\0' ||
|
||||||
|
c == '(' || c == ')' || c == '<' || c == '>' || c == '[' || c == ']' ||
|
||||||
|
c == '{' || c == '}' || c == '/' || c == '%') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (pos == keyStart) {
|
||||||
|
return false; // empty key name
|
||||||
|
}
|
||||||
|
std::string key = dictStr.substr(keyStart, pos - keyStart);
|
||||||
|
|
||||||
|
skipWhitespace(dictStr, pos);
|
||||||
|
if (pos >= dictStr.size()) {
|
||||||
|
return false; // key with no value
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string valueStr;
|
||||||
|
if (dictStr[pos] == '[') {
|
||||||
|
size_t arrStart = pos;
|
||||||
|
pos++; // skip '['
|
||||||
|
int depth = 1;
|
||||||
|
while (pos < dictStr.size() && depth > 0) {
|
||||||
|
if (dictStr[pos] == '[') depth++;
|
||||||
|
else if (dictStr[pos] == ']') depth--;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (depth > 0) {
|
||||||
|
return false; // unmatched brackets
|
||||||
|
}
|
||||||
|
valueStr = dictStr.substr(arrStart, pos - arrStart);
|
||||||
|
} else if (dictStr[pos] == '/') {
|
||||||
|
pos++; // skip '/'
|
||||||
|
size_t valStart = pos;
|
||||||
|
while (pos < dictStr.size()) {
|
||||||
|
char c = dictStr[pos];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\0' ||
|
||||||
|
c == '(' || c == ')' || c == '<' || c == '>' || c == '[' || c == ']' ||
|
||||||
|
c == '{' || c == '}' || c == '/' || c == '%') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
valueStr = "/" + dictStr.substr(valStart, pos - valStart);
|
||||||
|
} else {
|
||||||
|
size_t valStart = pos;
|
||||||
|
while (pos < dictStr.size()) {
|
||||||
|
char c = dictStr[pos];
|
||||||
|
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\0' ||
|
||||||
|
c == '(' || c == ')' || c == '<' || c == '>' || c == '[' || c == ']' ||
|
||||||
|
c == '{' || c == '}' || c == '/' || c == '%') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
valueStr = dictStr.substr(valStart, pos - valStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (key == "FontName") {
|
||||||
|
if (!valueStr.empty() && valueStr[0] == '/') {
|
||||||
|
font_name_ = valueStr.substr(1);
|
||||||
|
} else {
|
||||||
|
font_name_ = valueStr;
|
||||||
|
}
|
||||||
|
} else if (key == "Flags") {
|
||||||
|
flags_ = std::stoi(valueStr);
|
||||||
|
} else if (key == "ItalicAngle") {
|
||||||
|
italic_angle_ = std::stod(valueStr);
|
||||||
|
} else if (key == "Ascent") {
|
||||||
|
ascent_ = std::stod(valueStr);
|
||||||
|
} else if (key == "Descent") {
|
||||||
|
descent_ = std::stod(valueStr);
|
||||||
|
} else if (key == "Leading") {
|
||||||
|
leading_ = std::stod(valueStr);
|
||||||
|
} else if (key == "CapHeight") {
|
||||||
|
cap_height_ = std::stod(valueStr);
|
||||||
|
} else if (key == "XHeight") {
|
||||||
|
x_height_ = std::stod(valueStr);
|
||||||
|
} else if (key == "StemV") {
|
||||||
|
stem_v_ = std::stod(valueStr);
|
||||||
|
} else if (key == "StemH") {
|
||||||
|
stem_h_ = std::stod(valueStr);
|
||||||
|
} else if (key == "AvgWidth") {
|
||||||
|
avg_width_ = std::stod(valueStr);
|
||||||
|
} else if (key == "MaxWidth") {
|
||||||
|
max_width_ = std::stod(valueStr);
|
||||||
|
} else if (key == "MissingWidth") {
|
||||||
|
missing_width_ = std::stod(valueStr);
|
||||||
|
} else if (key == "FontBBox") {
|
||||||
|
std::vector<double> nums;
|
||||||
|
size_t idx = 0;
|
||||||
|
if (!valueStr.empty() && valueStr[0] == '[') idx++;
|
||||||
|
while (idx < valueStr.size()) {
|
||||||
|
while (idx < valueStr.size() && (valueStr[idx] == ' ' || valueStr[idx] == ',' || valueStr[idx] == '\t' || valueStr[idx] == ']' || valueStr[idx] == '\r' || valueStr[idx] == '\n')) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
if (idx >= valueStr.size() || valueStr[idx] == ']') break;
|
||||||
|
size_t processed;
|
||||||
|
double val = std::stod(valueStr.substr(idx), &processed);
|
||||||
|
nums.push_back(val);
|
||||||
|
idx += processed;
|
||||||
|
}
|
||||||
|
if (nums.size() != 4) {
|
||||||
|
return false; // FontBBox must have 4 coordinates
|
||||||
|
}
|
||||||
|
font_bbox_.llx = static_cast<int>(nums[0]);
|
||||||
|
font_bbox_.lly = static_cast<int>(nums[1]);
|
||||||
|
font_bbox_.urx = static_cast<int>(nums[2]);
|
||||||
|
font_bbox_.ury = static_cast<int>(nums[3]);
|
||||||
|
}
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return false; // parsing or range exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
struct FontBBox {
|
||||||
|
int llx = 0;
|
||||||
|
int lly = 0;
|
||||||
|
int urx = 0;
|
||||||
|
int ury = 0;
|
||||||
|
|
||||||
|
bool operator==(const FontBBox& o) const {
|
||||||
|
return llx == o.llx && lly == o.lly && urx == o.urx && ury == o.ury;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class FontDescriptor {
|
||||||
|
public:
|
||||||
|
FontDescriptor() = default;
|
||||||
|
~FontDescriptor() = default;
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
std::string getFontName() const { return font_name_; }
|
||||||
|
void setFontName(const std::string& name) { font_name_ = name; }
|
||||||
|
|
||||||
|
int getFlags() const { return flags_; }
|
||||||
|
void setFlags(int flags) { flags_ = flags; }
|
||||||
|
|
||||||
|
FontBBox getFontBBox() const { return font_bbox_; }
|
||||||
|
void setFontBBox(const FontBBox& bbox) { font_bbox_ = bbox; }
|
||||||
|
|
||||||
|
double getItalicAngle() const { return italic_angle_; }
|
||||||
|
void setItalicAngle(double angle) { italic_angle_ = angle; }
|
||||||
|
|
||||||
|
double getAscent() const { return ascent_; }
|
||||||
|
void setAscent(double ascent) { ascent_ = ascent; }
|
||||||
|
|
||||||
|
double getDescent() const { return descent_; }
|
||||||
|
void setDescent(double descent) { descent_ = descent; }
|
||||||
|
|
||||||
|
double getLeading() const { return leading_; }
|
||||||
|
void setLeading(double leading) { leading_ = leading; }
|
||||||
|
|
||||||
|
double getCapHeight() const { return cap_height_; }
|
||||||
|
void setCapHeight(double cap_height) { cap_height_ = cap_height; }
|
||||||
|
|
||||||
|
double getXHeight() const { return x_height_; }
|
||||||
|
void setXHeight(double x_height) { x_height_ = x_height; }
|
||||||
|
|
||||||
|
double getStemV() const { return stem_v_; }
|
||||||
|
void setStemV(double stem_v) { stem_v_ = stem_v; }
|
||||||
|
|
||||||
|
double getStemH() const { return stem_h_; }
|
||||||
|
void setStemH(double stem_h) { stem_h_ = stem_h; }
|
||||||
|
|
||||||
|
double getAvgWidth() const { return avg_width_; }
|
||||||
|
void setAvgWidth(double avg_width) { avg_width_ = avg_width; }
|
||||||
|
|
||||||
|
double getMaxWidth() const { return max_width_; }
|
||||||
|
void setMaxWidth(double max_width) { max_width_ = max_width; }
|
||||||
|
|
||||||
|
double getMissingWidth() const { return missing_width_; }
|
||||||
|
void setMissingWidth(double missing_width) { missing_width_ = missing_width; }
|
||||||
|
|
||||||
|
// Flags helper functions (PDF Spec Section 5.7.1)
|
||||||
|
bool isFixedPitch() const { return (flags_ & 1) != 0; }
|
||||||
|
bool isSerif() const { return (flags_ & 2) != 0; }
|
||||||
|
bool isSymbolic() const { return (flags_ & 4) != 0; }
|
||||||
|
bool isScript() const { return (flags_ & 8) != 0; }
|
||||||
|
bool isNonsymbolic() const { return (flags_ & 32) != 0; }
|
||||||
|
bool isItalic() const { return (flags_ & 64) != 0; }
|
||||||
|
bool isAllCap() const { return (flags_ & 65536) != 0; }
|
||||||
|
bool isSmallCap() const { return (flags_ & 131072) != 0; }
|
||||||
|
bool isForceBold() const { return (flags_ & 262144) != 0; }
|
||||||
|
|
||||||
|
// Parses a PDF dictionary string representing a FontDescriptor.
|
||||||
|
bool parseFromDictionaryString(const std::string& dictStr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string font_name_;
|
||||||
|
int flags_ = 0;
|
||||||
|
FontBBox font_bbox_;
|
||||||
|
double italic_angle_ = 0.0;
|
||||||
|
double ascent_ = 0.0;
|
||||||
|
double descent_ = 0.0;
|
||||||
|
double leading_ = 0.0;
|
||||||
|
double cap_height_ = 0.0;
|
||||||
|
double x_height_ = 0.0;
|
||||||
|
double stem_v_ = 0.0;
|
||||||
|
double stem_h_ = 0.0;
|
||||||
|
double avg_width_ = 0.0;
|
||||||
|
double max_width_ = 0.0;
|
||||||
|
double missing_width_ = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#include "fonts/pdf_fonts/font_fallback.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string toLower(const std::string& str) {
|
||||||
|
std::string lower = str;
|
||||||
|
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) {
|
||||||
|
return static_cast<char>(std::tolower(c));
|
||||||
|
});
|
||||||
|
return lower;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
FontFallback& FontFallback::getInstance() {
|
||||||
|
static FontFallback instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontFallback::FontFallback() {
|
||||||
|
initializeDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontFallback::initializeDefaults() {
|
||||||
|
default_rules_.clear();
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
// Helvetica / Arial fallback rules
|
||||||
|
default_rules_.push_back({"helvetica-bolditalic", {"C:\\Windows\\Fonts\\LiberationSans-BoldItalic.ttf", "C:\\Windows\\Fonts\\arialbi.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica-bold", {"C:\\Windows\\Fonts\\LiberationSans-Bold.ttf", "C:\\Windows\\Fonts\\arialbd.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica-oblique", {"C:\\Windows\\Fonts\\LiberationSans-Italic.ttf", "C:\\Windows\\Fonts\\ariali.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica", {"C:\\Windows\\Fonts\\LiberationSans-Regular.ttf", "C:\\Windows\\Fonts\\arial.ttf", "C:\\Windows\\Fonts\\tahoma.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-bolditalic", {"C:\\Windows\\Fonts\\LiberationSans-BoldItalic.ttf", "C:\\Windows\\Fonts\\arialbi.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-bold", {"C:\\Windows\\Fonts\\LiberationSans-Bold.ttf", "C:\\Windows\\Fonts\\arialbd.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-italic", {"C:\\Windows\\Fonts\\LiberationSans-Italic.ttf", "C:\\Windows\\Fonts\\ariali.ttf"}});
|
||||||
|
default_rules_.push_back({"arial", {"C:\\Windows\\Fonts\\LiberationSans-Regular.ttf", "C:\\Windows\\Fonts\\arial.ttf"}});
|
||||||
|
|
||||||
|
// Times fallback rules
|
||||||
|
default_rules_.push_back({"times-bolditalic", {"C:\\Windows\\Fonts\\LiberationSerif-BoldItalic.ttf", "C:\\Windows\\Fonts\\timesbi.ttf"}});
|
||||||
|
default_rules_.push_back({"times-bold", {"C:\\Windows\\Fonts\\LiberationSerif-Bold.ttf", "C:\\Windows\\Fonts\\timesbd.ttf"}});
|
||||||
|
default_rules_.push_back({"times-italic", {"C:\\Windows\\Fonts\\LiberationSerif-Italic.ttf", "C:\\Windows\\Fonts\\timesi.ttf"}});
|
||||||
|
default_rules_.push_back({"times", {"C:\\Windows\\Fonts\\LiberationSerif-Regular.ttf", "C:\\Windows\\Fonts\\times.ttf"}});
|
||||||
|
|
||||||
|
// Courier fallback rules
|
||||||
|
default_rules_.push_back({"courier-bolditalic", {"C:\\Windows\\Fonts\\LiberationMono-BoldItalic.ttf", "C:\\Windows\\Fonts\\courbi.ttf"}});
|
||||||
|
default_rules_.push_back({"courier-bold", {"C:\\Windows\\Fonts\\LiberationMono-Bold.ttf", "C:\\Windows\\Fonts\\courbd.ttf"}});
|
||||||
|
default_rules_.push_back({"courier-oblique", {"C:\\Windows\\Fonts\\LiberationMono-Italic.ttf", "C:\\Windows\\Fonts\\couri.ttf"}});
|
||||||
|
default_rules_.push_back({"courier", {"C:\\Windows\\Fonts\\LiberationMono-Regular.ttf", "C:\\Windows\\Fonts\\cour.ttf", "C:\\Windows\\Fonts\\consola.ttf"}});
|
||||||
|
|
||||||
|
default_rules_.push_back({"symbol", {"C:\\Windows\\Fonts\\symbol.ttf"}});
|
||||||
|
default_rules_.push_back({"zapfdingbats", {"C:\\Windows\\Fonts\\wingding.ttf"}});
|
||||||
|
|
||||||
|
default_rules_.push_back({"simsun", {"C:\\Windows\\Fonts\\simsun.ttc"}});
|
||||||
|
default_rules_.push_back({"msgothic", {"C:\\Windows\\Fonts\\msgothic.ttc"}});
|
||||||
|
default_rules_.push_back({"msmincho", {"C:\\Windows\\Fonts\\msmincho.ttc"}});
|
||||||
|
default_rules_.push_back({"malgun", {"C:\\Windows\\Fonts\\malgun.ttf"}});
|
||||||
|
default_rules_.push_back({"heiseimin", {"C:\\Windows\\Fonts\\msmincho.ttc", "C:\\Windows\\Fonts\\msgothic.ttc"}});
|
||||||
|
default_rules_.push_back({"gb", {"C:\\Windows\\Fonts\\simsun.ttc"}});
|
||||||
|
default_rules_.push_back({"chinese", {"C:\\Windows\\Fonts\\simsun.ttc"}});
|
||||||
|
default_rules_.push_back({"japanese", {"C:\\Windows\\Fonts\\msgothic.ttc"}});
|
||||||
|
default_rules_.push_back({"korean", {"C:\\Windows\\Fonts\\malgun.ttf"}});
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
// Helvetica / Arial fallback rules
|
||||||
|
default_rules_.push_back({"helvetica-bolditalic", {"/Library/Fonts/LiberationSans-BoldItalic.ttf", "/System/Library/Fonts/Supplemental/Arial Bold Italic.ttf", "/Library/Fonts/Arial Bold Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica-bold", {"/Library/Fonts/LiberationSans-Bold.ttf", "/System/Library/Fonts/Supplemental/Arial Bold.ttf", "/Library/Fonts/Arial Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica-oblique", {"/Library/Fonts/LiberationSans-Italic.ttf", "/System/Library/Fonts/Supplemental/Arial Italic.ttf", "/Library/Fonts/Arial Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica", {"/Library/Fonts/LiberationSans-Regular.ttf", "/Library/Fonts/Arial.ttf", "/System/Library/Fonts/Helvetica.ttc"}});
|
||||||
|
default_rules_.push_back({"arial-bolditalic", {"/Library/Fonts/LiberationSans-BoldItalic.ttf", "/System/Library/Fonts/Supplemental/Arial Bold Italic.ttf", "/Library/Fonts/Arial Bold Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-bold", {"/Library/Fonts/LiberationSans-Bold.ttf", "/System/Library/Fonts/Supplemental/Arial Bold.ttf", "/Library/Fonts/Arial Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-italic", {"/Library/Fonts/LiberationSans-Italic.ttf", "/System/Library/Fonts/Supplemental/Arial Italic.ttf", "/Library/Fonts/Arial Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"arial", {"/Library/Fonts/LiberationSans-Regular.ttf", "/Library/Fonts/Arial.ttf"}});
|
||||||
|
|
||||||
|
// Times fallback rules
|
||||||
|
default_rules_.push_back({"times-bolditalic", {"/Library/Fonts/LiberationSerif-BoldItalic.ttf", "/System/Library/Fonts/Supplemental/Times New Roman Bold Italic.ttf", "/Library/Fonts/Times New Roman Bold Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"times-bold", {"/Library/Fonts/LiberationSerif-Bold.ttf", "/System/Library/Fonts/Supplemental/Times New Roman Bold.ttf", "/Library/Fonts/Times New Roman Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"times-italic", {"/Library/Fonts/LiberationSerif-Italic.ttf", "/System/Library/Fonts/Supplemental/Times New Roman Italic.ttf", "/Library/Fonts/Times New Roman Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"times", {"/Library/Fonts/LiberationSerif-Regular.ttf", "/Library/Fonts/Times New Roman.ttf", "/System/Library/Fonts/Times.ttc"}});
|
||||||
|
|
||||||
|
// Courier fallback rules
|
||||||
|
default_rules_.push_back({"courier-bolditalic", {"/Library/Fonts/LiberationMono-BoldItalic.ttf", "/System/Library/Fonts/Supplemental/Courier New Bold Italic.ttf", "/Library/Fonts/Courier New Bold Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"courier-bold", {"/Library/Fonts/LiberationMono-Bold.ttf", "/System/Library/Fonts/Supplemental/Courier New Bold.ttf", "/Library/Fonts/Courier New Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"courier-oblique", {"/Library/Fonts/LiberationMono-Italic.ttf", "/System/Library/Fonts/Supplemental/Courier New Italic.ttf", "/Library/Fonts/Courier New Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"courier", {"/Library/Fonts/LiberationMono-Regular.ttf", "/Library/Fonts/Courier New.ttf", "/System/Library/Fonts/Courier.dfont"}});
|
||||||
|
#else
|
||||||
|
// Helvetica / Arial fallback rules
|
||||||
|
default_rules_.push_back({"helvetica-bolditalic", {"/usr/share/fonts/truetype/liberation/LiberationSans-BoldItalic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-BoldOblique.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica-bold", {"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica-oblique", {"/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf"}});
|
||||||
|
default_rules_.push_back({"helvetica", {"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-bolditalic", {"/usr/share/fonts/truetype/liberation/LiberationSans-BoldItalic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-BoldOblique.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-bold", {"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"arial-italic", {"/usr/share/fonts/truetype/liberation/LiberationSans-Italic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf"}});
|
||||||
|
default_rules_.push_back({"arial", {"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"}});
|
||||||
|
|
||||||
|
// Times fallback rules
|
||||||
|
default_rules_.push_back({"times-bolditalic", {"/usr/share/fonts/truetype/liberation/LiberationSerif-BoldItalic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSerif-BoldItalic.ttf"}});
|
||||||
|
default_rules_.push_back({"times-bold", {"/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"times-italic", {"/usr/share/fonts/truetype/liberation/LiberationSerif-Italic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSerif-Italic.ttf"}});
|
||||||
|
default_rules_.push_back({"times", {"/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf"}});
|
||||||
|
|
||||||
|
// Courier fallback rules
|
||||||
|
default_rules_.push_back({"courier-bolditalic", {"/usr/share/fonts/truetype/liberation/LiberationMono-BoldItalic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSansMono-BoldOblique.ttf"}});
|
||||||
|
default_rules_.push_back({"courier-bold", {"/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf"}});
|
||||||
|
default_rules_.push_back({"courier-oblique", {"/usr/share/fonts/truetype/liberation/LiberationMono-Italic.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Oblique.ttf"}});
|
||||||
|
default_rules_.push_back({"courier", {"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"}});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FontFallback::getFallbackFontPath(const std::string& fontName, bool bold, bool italic) {
|
||||||
|
std::string lowerName = toLower(fontName);
|
||||||
|
|
||||||
|
std::string stylePattern = lowerName;
|
||||||
|
if (bold && italic) {
|
||||||
|
stylePattern += "-bolditalic";
|
||||||
|
} else if (bold) {
|
||||||
|
stylePattern += "-bold";
|
||||||
|
} else if (italic) {
|
||||||
|
stylePattern += "-italic";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& rule : custom_rules_) {
|
||||||
|
if (stylePattern.find(rule.pattern) != std::string::npos || lowerName.find(rule.pattern) != std::string::npos) {
|
||||||
|
for (const auto& path : rule.preferredPaths) {
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& rule : default_rules_) {
|
||||||
|
if (stylePattern.find(rule.pattern) != std::string::npos || lowerName.find(rule.pattern) != std::string::npos) {
|
||||||
|
for (const auto& path : rule.preferredPaths) {
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
std::vector<std::string> lastResort;
|
||||||
|
if (bold && italic) lastResort = {"C:\\Windows\\Fonts\\arialbi.ttf", "C:\\Windows\\Fonts\\timesbi.ttf"};
|
||||||
|
else if (bold) lastResort = {"C:\\Windows\\Fonts\\arialbd.ttf", "C:\\Windows\\Fonts\\timesbd.ttf"};
|
||||||
|
else if (italic) lastResort = {"C:\\Windows\\Fonts\\ariali.ttf", "C:\\Windows\\Fonts\\timesi.ttf"};
|
||||||
|
else lastResort = {"C:\\Windows\\Fonts\\arial.ttf", "C:\\Windows\\Fonts\\times.ttf"};
|
||||||
|
|
||||||
|
for (const auto& path : lastResort) {
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "C:\\Windows\\Fonts\\arial.ttf";
|
||||||
|
#else
|
||||||
|
return "";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontFallback::registerFallback(const std::string& pattern, const std::string& systemFontPath) {
|
||||||
|
std::string lowerPattern = toLower(pattern);
|
||||||
|
custom_rules_.insert(custom_rules_.begin(), {lowerPattern, {systemFontPath}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void FontFallback::resetToDefaults() {
|
||||||
|
custom_rules_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class FontFallback {
|
||||||
|
public:
|
||||||
|
static FontFallback& getInstance();
|
||||||
|
|
||||||
|
std::string getFallbackFontPath(const std::string& fontName, bool bold = false, bool italic = false);
|
||||||
|
|
||||||
|
void registerFallback(const std::string& pattern, const std::string& systemFontPath);
|
||||||
|
|
||||||
|
void resetToDefaults();
|
||||||
|
|
||||||
|
private:
|
||||||
|
FontFallback();
|
||||||
|
~FontFallback() = default;
|
||||||
|
|
||||||
|
FontFallback(const FontFallback&) = delete;
|
||||||
|
FontFallback& operator=(const FontFallback&) = delete;
|
||||||
|
FontFallback(FontFallback&&) = delete;
|
||||||
|
FontFallback& operator=(FontFallback&&) = delete;
|
||||||
|
|
||||||
|
void initializeDefaults();
|
||||||
|
|
||||||
|
struct FallbackRule {
|
||||||
|
std::string pattern;
|
||||||
|
std::vector<std::string> preferredPaths;
|
||||||
|
};
|
||||||
|
std::vector<FallbackRule> default_rules_;
|
||||||
|
std::vector<FallbackRule> custom_rules_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#include "fonts/pdf_fonts/font_loader.hpp"
|
||||||
|
#include "fonts/pdf_fonts/types/truetype_font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/types/type1_font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/types/cid_font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/encoding/encoding.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_fallback.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
std::unique_ptr<Font> FontLoader::loadTrueTypeFromMemory(
|
||||||
|
const std::string& baseFont,
|
||||||
|
const std::vector<uint8_t>& streamData,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) {
|
||||||
|
auto font = std::make_unique<TrueTypeFont>(baseFont, true, std::move(descriptor), std::move(encoding));
|
||||||
|
if (!font->loadFromStream(streamData)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Font> FontLoader::loadType1FromMemory(
|
||||||
|
const std::string& baseFont,
|
||||||
|
const std::vector<uint8_t>& streamData,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) {
|
||||||
|
auto font = std::make_unique<Type1Font>(baseFont, true, std::move(descriptor), std::move(encoding));
|
||||||
|
if (!font->loadFromStream(streamData)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Font> FontLoader::loadType1SystemFallback(
|
||||||
|
const std::string& baseFont,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) {
|
||||||
|
// Determine bold/italic style modifiers from name
|
||||||
|
std::string lowerName = baseFont;
|
||||||
|
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](unsigned char c) {
|
||||||
|
return static_cast<char>(std::tolower(c));
|
||||||
|
});
|
||||||
|
|
||||||
|
bool bold = (lowerName.find("bold") != std::string::npos);
|
||||||
|
bool italic = (lowerName.find("italic") != std::string::npos || lowerName.find("oblique") != std::string::npos);
|
||||||
|
|
||||||
|
// Resolve system path dynamically via fallback manager
|
||||||
|
std::string fontPath = FontFallback::getInstance().getFallbackFontPath(baseFont, bold, italic);
|
||||||
|
|
||||||
|
auto font = std::make_unique<Type1Font>(baseFont, false, std::move(descriptor), std::move(encoding));
|
||||||
|
if (!font->loadFromFile(fontPath)) {
|
||||||
|
// If specific path fails, try standard fallback
|
||||||
|
if (!font->loadFromFile("C:\\Windows\\Fonts\\arial.ttf")) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Font> FontLoader::loadCIDFontFromMemory(
|
||||||
|
const std::string& baseFont,
|
||||||
|
FontType subtype,
|
||||||
|
const std::vector<uint8_t>& streamData,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) {
|
||||||
|
auto font = std::make_unique<CIDFont>(baseFont, subtype, true, std::move(descriptor), std::move(encoding));
|
||||||
|
if (!font->loadFromStream(streamData)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Font> FontLoader::loadCIDFontSystemFallback(
|
||||||
|
const std::string& baseFont,
|
||||||
|
FontType subtype,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) {
|
||||||
|
// Determine CJK style modifiers if any
|
||||||
|
std::string lowerName = baseFont;
|
||||||
|
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), [](unsigned char c) {
|
||||||
|
return static_cast<char>(std::tolower(c));
|
||||||
|
});
|
||||||
|
bool bold = (lowerName.find("bold") != std::string::npos);
|
||||||
|
bool italic = (lowerName.find("italic") != std::string::npos || lowerName.find("oblique") != std::string::npos);
|
||||||
|
|
||||||
|
// Resolve system path dynamically via fallback manager
|
||||||
|
std::string fontPath = FontFallback::getInstance().getFallbackFontPath(baseFont, bold, italic);
|
||||||
|
|
||||||
|
auto font = std::make_unique<CIDFont>(baseFont, subtype, false, std::move(descriptor), std::move(encoding));
|
||||||
|
if (!font->loadFromFile(fontPath)) {
|
||||||
|
// Fallback to standard arial
|
||||||
|
if (!font->loadFromFile("C:\\Windows\\Fonts\\arial.ttf")) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fonts/pdf_fonts/font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_descriptor.hpp"
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class Encoding;
|
||||||
|
|
||||||
|
class FontLoader {
|
||||||
|
public:
|
||||||
|
// Factory method to load an embedded TrueType font from its raw stream bytes
|
||||||
|
static std::unique_ptr<Font> loadTrueTypeFromMemory(
|
||||||
|
const std::string& baseFont,
|
||||||
|
const std::vector<uint8_t>& streamData,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
// Factory method to load an embedded Type1 font from raw memory bytes
|
||||||
|
static std::unique_ptr<Font> loadType1FromMemory(
|
||||||
|
const std::string& baseFont,
|
||||||
|
const std::vector<uint8_t>& streamData,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
// Factory method to load a non-embedded standard Type1 font (resolves to standard system fallback)
|
||||||
|
static std::unique_ptr<Font> loadType1SystemFallback(
|
||||||
|
const std::string& baseFont,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
// Factory method to load an embedded CIDFont from raw stream bytes
|
||||||
|
static std::unique_ptr<Font> loadCIDFontFromMemory(
|
||||||
|
const std::string& baseFont,
|
||||||
|
FontType subtype, // CIDFontType0 or CIDFontType2
|
||||||
|
const std::vector<uint8_t>& streamData,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
// Factory method to load a non-embedded CJK CIDFont (resolves to CJK system fallbacks)
|
||||||
|
static std::unique_ptr<Font> loadCIDFontSystemFallback(
|
||||||
|
const std::string& baseFont,
|
||||||
|
FontType subtype, // CIDFontType0 or CIDFontType2
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#include "fonts/pdf_fonts/font_subset.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
bool FontSubset::hasSubsetPrefix(const std::string& fontName) {
|
||||||
|
if (fontName.length() < 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fontName[6] != '+') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
if (!std::isupper(static_cast<unsigned char>(fontName[i]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FontSubset::stripSubsetPrefix(const std::string& fontName) {
|
||||||
|
if (hasSubsetPrefix(fontName)) {
|
||||||
|
return fontName.substr(7);
|
||||||
|
}
|
||||||
|
return fontName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FontSubset::getSubsetPrefix(const std::string& fontName) {
|
||||||
|
if (hasSubsetPrefix(fontName)) {
|
||||||
|
return fontName.substr(0, 6);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
FontSubset::FontSubset(const std::string& fontName)
|
||||||
|
: full_name_(fontName),
|
||||||
|
is_subset_(hasSubsetPrefix(fontName)) {}
|
||||||
|
|
||||||
|
void FontSubset::addGlyphMapping(uint32_t subsetGid, uint32_t originalGid) {
|
||||||
|
subset_to_original_map_[subsetGid] = originalGid;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t FontSubset::mapSubsetToOriginal(uint32_t subsetGid) const {
|
||||||
|
auto it = subset_to_original_map_.find(subsetGid);
|
||||||
|
if (it != subset_to_original_map_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return subsetGid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FontSubset::hasGlyphMapping(uint32_t subsetGid) const {
|
||||||
|
return subset_to_original_map_.find(subsetGid) != subset_to_original_map_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class FontSubset {
|
||||||
|
public:
|
||||||
|
static bool hasSubsetPrefix(const std::string& fontName);
|
||||||
|
|
||||||
|
static std::string stripSubsetPrefix(const std::string& fontName);
|
||||||
|
|
||||||
|
static std::string getSubsetPrefix(const std::string& fontName);
|
||||||
|
|
||||||
|
explicit FontSubset(const std::string& fontName);
|
||||||
|
~FontSubset() = default;
|
||||||
|
|
||||||
|
const std::string& getFullFontName() const { return full_name_; }
|
||||||
|
std::string getBaseFontName() const { return stripSubsetPrefix(full_name_); }
|
||||||
|
std::string getPrefix() const { return getSubsetPrefix(full_name_); }
|
||||||
|
bool isSubset() const { return is_subset_; }
|
||||||
|
|
||||||
|
void addGlyphMapping(uint32_t subsetGid, uint32_t originalGid);
|
||||||
|
uint32_t mapSubsetToOriginal(uint32_t subsetGid) const;
|
||||||
|
bool hasGlyphMapping(uint32_t subsetGid) const;
|
||||||
|
size_t getMappingCount() const { return subset_to_original_map_.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string full_name_;
|
||||||
|
bool is_subset_;
|
||||||
|
std::unordered_map<uint32_t, uint32_t> subset_to_original_map_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
#include "fonts/pdf_fonts/types/cid_font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/encoding/encoding.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_subset.hpp"
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string unicodeToUtf8(uint32_t codepoint) {
|
||||||
|
std::string utf8;
|
||||||
|
if (codepoint == 0) {
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
if (codepoint <= 0x7F) {
|
||||||
|
utf8.push_back(static_cast<char>(codepoint));
|
||||||
|
} else if (codepoint <= 0x7FF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
} else if (codepoint <= 0xFFFF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
} else if (codepoint <= 0x10FFFF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
}
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CIDFont::CIDFont(
|
||||||
|
const std::string& baseFont,
|
||||||
|
FontType subtype,
|
||||||
|
bool isEmbedded,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) : base_font_(baseFont),
|
||||||
|
subtype_(subtype),
|
||||||
|
is_embedded_(isEmbedded),
|
||||||
|
descriptor_(std::move(descriptor)),
|
||||||
|
encoding_(std::move(encoding)) {
|
||||||
|
if (FontSubset::hasSubsetPrefix(base_font_)) {
|
||||||
|
subset_info_ = std::make_unique<FontSubset>(base_font_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CIDFont::~CIDFont() = default;
|
||||||
|
|
||||||
|
bool CIDFont::loadFromStream(const std::vector<uint8_t>& streamData) {
|
||||||
|
return font_face_.loadFromMemory(streamData);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CIDFont::loadFromFile(const std::string& filePath) {
|
||||||
|
return font_face_.loadFromFile(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CIDFont::getBaseFont() const {
|
||||||
|
return base_font_;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontType CIDFont::getType() const {
|
||||||
|
return subtype_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CIDFont::isEmbedded() const {
|
||||||
|
return is_embedded_;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfengine::fonts::FontFace& CIDFont::getFontFace() {
|
||||||
|
return font_face_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfengine::fonts::FontFace& CIDFont::getFontFace() const {
|
||||||
|
return font_face_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontDescriptor* CIDFont::getDescriptor() const {
|
||||||
|
return descriptor_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Encoding* CIDFont::getEncoding() const {
|
||||||
|
return encoding_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontSubset* CIDFont::getSubsetInfo() const {
|
||||||
|
return subset_info_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t CIDFont::mapCIDToGID(uint32_t cid) const {
|
||||||
|
if (is_identity_map_) {
|
||||||
|
return cid;
|
||||||
|
}
|
||||||
|
auto it = cid_to_gid_map_.find(cid);
|
||||||
|
if (it != cid_to_gid_map_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CIDFont::setCIDToGIDMap(std::unordered_map<uint32_t, uint32_t> cidToGid) {
|
||||||
|
cid_to_gid_map_ = std::move(cidToGid);
|
||||||
|
is_identity_map_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CIDFont::setIdentityCIDToGIDMap() {
|
||||||
|
cid_to_gid_map_.clear();
|
||||||
|
is_identity_map_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t CIDFont::decodeToUnicode(uint32_t charCode) const {
|
||||||
|
if (encoding_) {
|
||||||
|
uint32_t decoded = encoding_->decode(charCode);
|
||||||
|
if (decoded != 0) {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t gid = mapCIDToGID(charCode);
|
||||||
|
uint32_t originalGid = subset_info_ ? subset_info_->mapSubsetToOriginal(gid) : gid;
|
||||||
|
|
||||||
|
FT_Face face = font_face_.getFace();
|
||||||
|
if (face) {
|
||||||
|
FT_UInt gindex;
|
||||||
|
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
|
||||||
|
while (gindex != 0) {
|
||||||
|
if (gindex == originalGid) {
|
||||||
|
return static_cast<uint32_t>(charcode);
|
||||||
|
}
|
||||||
|
charcode = FT_Get_Next_Char(face, charcode, &gindex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CIDFont::decodeStringToUnicode(const std::vector<uint32_t>& charCodes) const {
|
||||||
|
std::string result;
|
||||||
|
for (uint32_t code : charCodes) {
|
||||||
|
uint32_t unicode = decodeToUnicode(code);
|
||||||
|
if (unicode != 0) {
|
||||||
|
result += unicodeToUtf8(unicode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fonts/pdf_fonts/font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_descriptor.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class Encoding;
|
||||||
|
class FontSubset;
|
||||||
|
|
||||||
|
class CIDFont : public Font {
|
||||||
|
public:
|
||||||
|
CIDFont(
|
||||||
|
const std::string& baseFont,
|
||||||
|
FontType subtype, // CIDFontType0 or CIDFontType2
|
||||||
|
bool isEmbedded,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
~CIDFont() override;
|
||||||
|
|
||||||
|
// Load the font from raw embedded stream bytes
|
||||||
|
bool loadFromStream(const std::vector<uint8_t>& streamData);
|
||||||
|
|
||||||
|
// Load the font from a system or CJK fallback file path
|
||||||
|
bool loadFromFile(const std::string& filePath);
|
||||||
|
|
||||||
|
std::string getBaseFont() const override;
|
||||||
|
FontType getType() const override;
|
||||||
|
bool isEmbedded() const override;
|
||||||
|
|
||||||
|
pdfengine::fonts::FontFace& getFontFace() override;
|
||||||
|
const pdfengine::fonts::FontFace& getFontFace() const override;
|
||||||
|
|
||||||
|
const FontDescriptor* getDescriptor() const override;
|
||||||
|
const Encoding* getEncoding() const override;
|
||||||
|
const FontSubset* getSubsetInfo() const override;
|
||||||
|
|
||||||
|
uint32_t decodeToUnicode(uint32_t charCode) const override;
|
||||||
|
std::string decodeStringToUnicode(const std::vector<uint32_t>& charCodes) const override;
|
||||||
|
|
||||||
|
// CID to GID translation methods
|
||||||
|
uint32_t mapCIDToGID(uint32_t cid) const;
|
||||||
|
void setCIDToGIDMap(std::unordered_map<uint32_t, uint32_t> cidToGid);
|
||||||
|
void setIdentityCIDToGIDMap();
|
||||||
|
bool isIdentityMap() const { return is_identity_map_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string base_font_;
|
||||||
|
FontType subtype_;
|
||||||
|
bool is_embedded_ = false;
|
||||||
|
pdfengine::fonts::FontFace font_face_;
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor_;
|
||||||
|
std::unique_ptr<Encoding> encoding_;
|
||||||
|
|
||||||
|
bool is_identity_map_ = true;
|
||||||
|
std::unordered_map<uint32_t, uint32_t> cid_to_gid_map_;
|
||||||
|
std::unique_ptr<FontSubset> subset_info_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#include "fonts/pdf_fonts/types/truetype_font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/encoding/encoding.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_subset.hpp"
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string unicodeToUtf8(uint32_t codepoint) {
|
||||||
|
std::string utf8;
|
||||||
|
if (codepoint == 0) {
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
if (codepoint <= 0x7F) {
|
||||||
|
utf8.push_back(static_cast<char>(codepoint));
|
||||||
|
} else if (codepoint <= 0x7FF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
} else if (codepoint <= 0xFFFF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
} else if (codepoint <= 0x10FFFF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
}
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TrueTypeFont::TrueTypeFont(
|
||||||
|
const std::string& baseFont,
|
||||||
|
bool isEmbedded,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) : base_font_(baseFont),
|
||||||
|
is_embedded_(isEmbedded),
|
||||||
|
descriptor_(std::move(descriptor)),
|
||||||
|
encoding_(std::move(encoding)) {
|
||||||
|
if (FontSubset::hasSubsetPrefix(base_font_)) {
|
||||||
|
subset_info_ = std::make_unique<FontSubset>(base_font_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TrueTypeFont::~TrueTypeFont() = default;
|
||||||
|
|
||||||
|
bool TrueTypeFont::loadFromStream(const std::vector<uint8_t>& streamData) {
|
||||||
|
return font_face_.loadFromMemory(streamData);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string TrueTypeFont::getBaseFont() const {
|
||||||
|
return base_font_;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontType TrueTypeFont::getType() const {
|
||||||
|
return FontType::TrueType;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrueTypeFont::isEmbedded() const {
|
||||||
|
return is_embedded_;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfengine::fonts::FontFace& TrueTypeFont::getFontFace() {
|
||||||
|
return font_face_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfengine::fonts::FontFace& TrueTypeFont::getFontFace() const {
|
||||||
|
return font_face_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontDescriptor* TrueTypeFont::getDescriptor() const {
|
||||||
|
return descriptor_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Encoding* TrueTypeFont::getEncoding() const {
|
||||||
|
return encoding_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontSubset* TrueTypeFont::getSubsetInfo() const {
|
||||||
|
return subset_info_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t TrueTypeFont::decodeToUnicode(uint32_t charCode) const {
|
||||||
|
if (encoding_) {
|
||||||
|
uint32_t decoded = encoding_->decode(charCode);
|
||||||
|
if (decoded != 0) {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subset_info_) {
|
||||||
|
uint32_t originalGid = subset_info_->mapSubsetToOriginal(charCode);
|
||||||
|
|
||||||
|
FT_Face face = font_face_.getFace();
|
||||||
|
if (face) {
|
||||||
|
FT_UInt gindex;
|
||||||
|
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
|
||||||
|
while (gindex != 0) {
|
||||||
|
if (gindex == originalGid) {
|
||||||
|
return static_cast<uint32_t>(charcode);
|
||||||
|
}
|
||||||
|
charcode = FT_Get_Next_Char(face, charcode, &gindex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string TrueTypeFont::decodeStringToUnicode(const std::vector<uint32_t>& charCodes) const {
|
||||||
|
std::string result;
|
||||||
|
for (uint32_t code : charCodes) {
|
||||||
|
uint32_t unicode = decodeToUnicode(code);
|
||||||
|
if (unicode != 0) {
|
||||||
|
result += unicodeToUtf8(unicode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fonts/pdf_fonts/font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_descriptor.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class Encoding;
|
||||||
|
class FontSubset;
|
||||||
|
|
||||||
|
class TrueTypeFont : public Font {
|
||||||
|
public:
|
||||||
|
TrueTypeFont(
|
||||||
|
const std::string& baseFont,
|
||||||
|
bool isEmbedded,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
~TrueTypeFont() override;
|
||||||
|
|
||||||
|
// Load the font from raw embedded stream bytes
|
||||||
|
bool loadFromStream(const std::vector<uint8_t>& streamData);
|
||||||
|
|
||||||
|
std::string getBaseFont() const override;
|
||||||
|
FontType getType() const override;
|
||||||
|
bool isEmbedded() const override;
|
||||||
|
|
||||||
|
pdfengine::fonts::FontFace& getFontFace() override;
|
||||||
|
const pdfengine::fonts::FontFace& getFontFace() const override;
|
||||||
|
|
||||||
|
const FontDescriptor* getDescriptor() const override;
|
||||||
|
const Encoding* getEncoding() const override;
|
||||||
|
const FontSubset* getSubsetInfo() const override;
|
||||||
|
|
||||||
|
uint32_t decodeToUnicode(uint32_t charCode) const override;
|
||||||
|
std::string decodeStringToUnicode(const std::vector<uint32_t>& charCodes) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string base_font_;
|
||||||
|
bool is_embedded_ = false;
|
||||||
|
pdfengine::fonts::FontFace font_face_;
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor_;
|
||||||
|
std::unique_ptr<Encoding> encoding_;
|
||||||
|
std::unique_ptr<FontSubset> subset_info_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#include "fonts/pdf_fonts/types/type1_font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/encoding/encoding.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_subset.hpp"
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string unicodeToUtf8(uint32_t codepoint) {
|
||||||
|
std::string utf8;
|
||||||
|
if (codepoint == 0) {
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
if (codepoint <= 0x7F) {
|
||||||
|
utf8.push_back(static_cast<char>(codepoint));
|
||||||
|
} else if (codepoint <= 0x7FF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
} else if (codepoint <= 0xFFFF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
} else if (codepoint <= 0x10FFFF) {
|
||||||
|
utf8.push_back(static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||||
|
utf8.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||||
|
}
|
||||||
|
return utf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Type1Font::Type1Font(
|
||||||
|
const std::string& baseFont,
|
||||||
|
bool isEmbedded,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor,
|
||||||
|
std::unique_ptr<Encoding> encoding
|
||||||
|
) : base_font_(baseFont),
|
||||||
|
is_embedded_(isEmbedded),
|
||||||
|
descriptor_(std::move(descriptor)),
|
||||||
|
encoding_(std::move(encoding)) {
|
||||||
|
if (FontSubset::hasSubsetPrefix(base_font_)) {
|
||||||
|
subset_info_ = std::make_unique<FontSubset>(base_font_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type1Font::~Type1Font() = default;
|
||||||
|
|
||||||
|
bool Type1Font::loadFromStream(const std::vector<uint8_t>& streamData) {
|
||||||
|
return font_face_.loadFromMemory(streamData);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Type1Font::loadFromFile(const std::string& filePath) {
|
||||||
|
return font_face_.loadFromFile(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Type1Font::getBaseFont() const {
|
||||||
|
return base_font_;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontType Type1Font::getType() const {
|
||||||
|
return FontType::Type1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Type1Font::isEmbedded() const {
|
||||||
|
return is_embedded_;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdfengine::fonts::FontFace& Type1Font::getFontFace() {
|
||||||
|
return font_face_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfengine::fonts::FontFace& Type1Font::getFontFace() const {
|
||||||
|
return font_face_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontDescriptor* Type1Font::getDescriptor() const {
|
||||||
|
return descriptor_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Encoding* Type1Font::getEncoding() const {
|
||||||
|
return encoding_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const FontSubset* Type1Font::getSubsetInfo() const {
|
||||||
|
return subset_info_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Type1Font::decodeToUnicode(uint32_t charCode) const {
|
||||||
|
if (encoding_) {
|
||||||
|
uint32_t decoded = encoding_->decode(charCode);
|
||||||
|
if (decoded != 0) {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subset_info_) {
|
||||||
|
uint32_t originalGid = subset_info_->mapSubsetToOriginal(charCode);
|
||||||
|
|
||||||
|
FT_Face face = font_face_.getFace();
|
||||||
|
if (face) {
|
||||||
|
FT_UInt gindex;
|
||||||
|
FT_ULong charcode = FT_Get_First_Char(face, &gindex);
|
||||||
|
while (gindex != 0) {
|
||||||
|
if (gindex == originalGid) {
|
||||||
|
return static_cast<uint32_t>(charcode);
|
||||||
|
}
|
||||||
|
charcode = FT_Get_Next_Char(face, charcode, &gindex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return charCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Type1Font::decodeStringToUnicode(const std::vector<uint32_t>& charCodes) const {
|
||||||
|
std::string result;
|
||||||
|
for (uint32_t code : charCodes) {
|
||||||
|
uint32_t unicode = decodeToUnicode(code);
|
||||||
|
if (unicode != 0) {
|
||||||
|
result += unicodeToUtf8(unicode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "fonts/pdf_fonts/font.hpp"
|
||||||
|
#include "fonts/pdf_fonts/font_descriptor.hpp"
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace pdfengine::fonts::pdf_fonts {
|
||||||
|
|
||||||
|
class Encoding;
|
||||||
|
class FontSubset;
|
||||||
|
|
||||||
|
class Type1Font : public Font {
|
||||||
|
public:
|
||||||
|
Type1Font(
|
||||||
|
const std::string& baseFont,
|
||||||
|
bool isEmbedded,
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor = nullptr,
|
||||||
|
std::unique_ptr<Encoding> encoding = nullptr
|
||||||
|
);
|
||||||
|
~Type1Font() override;
|
||||||
|
|
||||||
|
// Load the font from raw embedded stream bytes
|
||||||
|
bool loadFromStream(const std::vector<uint8_t>& streamData);
|
||||||
|
|
||||||
|
// Load the font from a system or fallback file path
|
||||||
|
bool loadFromFile(const std::string& filePath);
|
||||||
|
|
||||||
|
std::string getBaseFont() const override;
|
||||||
|
FontType getType() const override;
|
||||||
|
bool isEmbedded() const override;
|
||||||
|
|
||||||
|
pdfengine::fonts::FontFace& getFontFace() override;
|
||||||
|
const pdfengine::fonts::FontFace& getFontFace() const override;
|
||||||
|
|
||||||
|
const FontDescriptor* getDescriptor() const override;
|
||||||
|
const Encoding* getEncoding() const override;
|
||||||
|
const FontSubset* getSubsetInfo() const override;
|
||||||
|
|
||||||
|
uint32_t decodeToUnicode(uint32_t charCode) const override;
|
||||||
|
std::string decodeStringToUnicode(const std::vector<uint32_t>& charCodes) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string base_font_;
|
||||||
|
bool is_embedded_ = false;
|
||||||
|
pdfengine::fonts::FontFace font_face_;
|
||||||
|
std::unique_ptr<FontDescriptor> descriptor_;
|
||||||
|
std::unique_ptr<Encoding> encoding_;
|
||||||
|
std::unique_ptr<FontSubset> subset_info_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pdfengine::fonts::pdf_fonts
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "hb_shaper.hpp"
|
#include "fonts/shaping/hb_shaper.hpp"
|
||||||
|
|
||||||
#include <hb.h>
|
#include <hb.h>
|
||||||
#include <hb-ft.h>
|
#include <hb-ft.h>
|
||||||
@@ -8,24 +8,31 @@ namespace pdfengine::fonts {
|
|||||||
HbShaper::HbShaper() = default;
|
HbShaper::HbShaper() = default;
|
||||||
HbShaper::~HbShaper() = default;
|
HbShaper::~HbShaper() = default;
|
||||||
|
|
||||||
std::vector<ShapedGlyph> HbShaper::shapeText(const FontFace& fontFace, const std::string& text) {
|
std::vector<ShapedGlyph> HbShaper::shapeRun(
|
||||||
|
const std::string& text,
|
||||||
|
FontFace& font,
|
||||||
|
unsigned int fontSize
|
||||||
|
) {
|
||||||
std::vector<ShapedGlyph> result;
|
std::vector<ShapedGlyph> result;
|
||||||
|
|
||||||
FT_Face ftFace = fontFace.getFace();
|
FT_Face ftFace = font.getFace();
|
||||||
if (!ftFace) {
|
if (!ftFace) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a HarfBuzz font wrapper around the FreeType face.
|
// Create a HarfBuzz font wrapper around the FreeType face.
|
||||||
// hb_ft_font_create_referenced increments the reference count of the FT_Face,
|
|
||||||
// making it safe even if the FontFace object changes or moves.
|
|
||||||
hb_font_t* hbFont = hb_ft_font_create_referenced(ftFace);
|
hb_font_t* hbFont = hb_ft_font_create_referenced(ftFace);
|
||||||
if (!hbFont) {
|
if (!hbFont) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the scale of HarfBuzz font to match the FreeType face size.
|
// Sync HarfBuzz font with FreeType face changes.
|
||||||
// If not set, HarfBuzz will default to using the font's design units (upem).
|
|
||||||
hb_ft_font_changed(hbFont);
|
hb_ft_font_changed(hbFont);
|
||||||
|
|
||||||
// Create a text buffer.
|
// Create a text buffer.
|
||||||
@@ -56,10 +63,10 @@ std::vector<ShapedGlyph> HbShaper::shapeText(const FontFace& fontFace, const std
|
|||||||
sg.glyphIndex = glyphInfos[i].codepoint;
|
sg.glyphIndex = glyphInfos[i].codepoint;
|
||||||
// HarfBuzz coordinates are fractional 26.6 pixels (1/64 of a pixel).
|
// HarfBuzz coordinates are fractional 26.6 pixels (1/64 of a pixel).
|
||||||
// Convert to standard double-precision float values.
|
// Convert to standard double-precision float values.
|
||||||
sg.xAdvance = static_cast<double>(glyphPositions[i].x_advance) / 64.0;
|
sg.advanceX = static_cast<double>(glyphPositions[i].x_advance) / 64.0;
|
||||||
sg.yAdvance = static_cast<double>(glyphPositions[i].y_advance) / 64.0;
|
sg.advanceY = static_cast<double>(glyphPositions[i].y_advance) / 64.0;
|
||||||
sg.xOffset = static_cast<double>(glyphPositions[i].x_offset) / 64.0;
|
sg.offsetX = static_cast<double>(glyphPositions[i].x_offset) / 64.0;
|
||||||
sg.yOffset = static_cast<double>(glyphPositions[i].y_offset) / 64.0;
|
sg.offsetY = static_cast<double>(glyphPositions[i].y_offset) / 64.0;
|
||||||
result.push_back(sg);
|
result.push_back(sg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "font_face.hpp"
|
#include "fonts/face/font_face.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace pdfengine::fonts {
|
namespace pdfengine::fonts {
|
||||||
|
|
||||||
struct ShapedGlyph {
|
struct ShapedGlyph {
|
||||||
unsigned int glyphIndex;
|
uint32_t glyphIndex;
|
||||||
double xAdvance;
|
double advanceX;
|
||||||
double yAdvance;
|
double advanceY;
|
||||||
double xOffset;
|
double offsetX;
|
||||||
double yOffset;
|
double offsetY;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HbShaper {
|
class HbShaper {
|
||||||
@@ -24,9 +25,13 @@ public:
|
|||||||
HbShaper(HbShaper&&) noexcept = default;
|
HbShaper(HbShaper&&) noexcept = default;
|
||||||
HbShaper& operator=(HbShaper&&) noexcept = default;
|
HbShaper& operator=(HbShaper&&) noexcept = default;
|
||||||
|
|
||||||
// Shapes the input UTF-8 text using the given FontFace.
|
// Shapes the input UTF-8 text run using the given FontFace and fontSize.
|
||||||
// Returns a vector of shaped glyphs.
|
// Returns a vector of shaped glyphs.
|
||||||
std::vector<ShapedGlyph> shapeText(const FontFace& fontFace, const std::string& text);
|
std::vector<ShapedGlyph> shapeRun(
|
||||||
|
const std::string& text,
|
||||||
|
FontFace& font,
|
||||||
|
unsigned int fontSize
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pdfengine::fonts
|
} // namespace pdfengine::fonts
|
||||||
+1195
-6
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Reference in New Issue
Block a user