This commit is contained in:
azeeee05
2026-05-25 10:35:02 +05:30
35 changed files with 3485 additions and 103 deletions
+13 -2
View File
@@ -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
View File
@@ -0,0 +1 @@
#include "fonts/cache/glyph_bitmap.hpp"
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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
+158
View File
@@ -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
-75
View File
@@ -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
View File
View File
View File
View File
@@ -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
+88
View File
@@ -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
+107
View File
@@ -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
File diff suppressed because it is too large Load Diff
Binary file not shown.