Unicode 字體字符集可視化工具 - 代碼介紹
項目概述
這個工具是一個用于分析和可視化字體文件中包含的 Unicode 字符的實用程序,能夠掃描指定字體文件,提取其中包含的所有 Unicode 字符,并按 Unicode 區塊分類生成 PDF 文檔,直觀展示字體支持的所有字符。
核心功能
- 字體掃描:自動掃描系統字體目錄,識別 TrueType (.ttf) 和 OpenType (.otf) 字體文件
- 字符提取:使用 FreeType 庫提取字體中支持的所有 Unicode 字符
- 區塊分類:按照 Unicode 標準區塊對字符進行分類
- PDF 生成:使用 Haru PDF 庫生成包含所有字符的可視化文檔
- 智能排版:自動處理多頁文檔、分欄顯示和換行
主要組件
1. UnicodeBlock 結構體
定義 Unicode 區塊的起始碼點、結束碼點和名稱,包含完整的 Unicode 15.0 標準區塊定義。
2. FontUtils 類
封裝 FreeType 庫操作,提供以下功能:
- 初始化 FreeType 庫
- 從字體文件提取 Unicode 字符集
- Unicode 碼點到 UTF-8 編碼的轉換
- 按 Unicode 區塊過濾字符
3. PDFDocument 類
封裝 Haru PDF 庫操作,提供以下功能:
- PDF 文檔初始化
- 字體加載和編碼設置
- 字符頁面生成(支持多頁)
- 彩色標題和統計信息添加
- 文檔保存
4. FontScanner 類
負責掃描字體目錄,提供以下功能:
- 遞歸掃描指定目錄中的字體文件
- 按擴展名和文件名模式過濾字體
- 支持排除特定樣式的字體(如粗體、斜體等)
技術特點
- 跨平臺支持:使用標準 C++17 和跨平臺庫
- 完整 Unicode 支持:覆蓋所有 Unicode 15.0 標準區塊
- 高效處理:智能分頁和區塊分割,避免內存問題
- 美觀輸出:彩色漸變標題、清晰的字符網格布局
- 靈活配置:可調整的頁面布局參數(邊距、字體大小等)
使用示例
int main() {try {FontUtils font_utils;fs::path font_dir = "C:/Windows/Fonts"; // 字體目錄fs::path output_dir = "output_pdfs"; // 輸出目錄FontScanner scanner(font_dir);auto font_files = scanner.GetTTfFont(); // 獲取常規字體文件for (const auto& path : font_files) {std::cout << "處理字體文件: " << path << std::endl;PDFDocument doc(path, output_dir);doc.GenerateWithUnicodeChars(font_utils);doc.Save();}}catch (const std::exception& ex) {std::cerr << "錯誤: " << ex.what() << std::endl;return 1;}return 0;
}
依賴庫
- FreeType:用于字體解析和字符提取
- Haru PDF:用于 PDF 文檔生成
- C++17 標準庫:文件系統、字符串處理等
- STL:容器、算法等
輸出示例
生成的 PDF 文檔包含:
- 彩色漸變字體標題
- 字體統計信息(總字符數)
- 按 Unicode 區塊組織的字符網格
- 自動分頁的多頁文檔
應用場景
- 字體設計師驗證字體覆蓋范圍
- 開發人員檢查字體對特定語言的支持
- 多語言項目中的字體兼容性測試
- 字體文檔自動生成
- 學術研究中的字符集分析
擴展性
項目可輕松擴展以支持:
- 自定義 Unicode 范圍過濾
- 額外的輸出格式(如 HTML、CSV)
- 字符屬性顯示(如編碼點、名稱)
- 字體特性分析(如連字、變體)
#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include <hpdf.h>
#include <cstring>
#include <set>
#include<sstream>
#include <ft2build.h>
#include <freetype/ftadvanc.h>
#include FT_FREETYPE_Hnamespace fs = std::filesystem;constexpr size_t CHARS_PER_LINE = 40; // 每行字符數
constexpr size_t LINES_PER_PAGE = 120; // 每頁行數
constexpr size_t CHARS_PER_PAGE = CHARS_PER_LINE * LINES_PER_PAGE; // 每頁字符總數
constexpr size_t MAX_CHARS_PER_BLOCK = 300; // 每個區塊最大字符數
constexpr int FONT_SIZE = 13;
constexpr float CHAR_WIDTH = 13.0f; // 每個字符的寬度
constexpr float LINE_HEIGHT = 20.0f; // 行高
constexpr float LEFT_MARGIN = 10.0f; // 左邊距
constexpr float TOP_MARGIN = 790.0f; // 上邊距struct UnicodeBlock {unsigned int start;unsigned int end;std::string name;
};// Unicode區塊定義
const std::vector<UnicodeBlock> UNICODE_BLOCKS = {{0x0000, 0x007F, "Basic Latin"},{0x0080, 0x00FF, "Latin-1 Supplement"},{0x0100, 0x017F, "Latin Extended-A"},{0x0180, 0x024F, "Latin Extended-B"},{0x0250, 0x02AF, "IPA Extensions"},{0x02B0, 0x02FF, "Spacing Modifier Letters"},{0x0300, 0x036F, "Combining Diacritical Marks"},{0x0370, 0x03FF, "Greek and Coptic"},{0x0400, 0x04FF, "Cyrillic"},{0x0500, 0x052F, "Cyrillic Supplement"},{0x0530, 0x058F, "Armenian"},{0x0590, 0x05FF, "Hebrew"},{0x0600, 0x06FF, "Arabic"},{0x0700, 0x074F, "Syriac"},{0x0750, 0x077F, "Arabic Supplement"},{0x0780, 0x07BF, "Thaana"},{0x07C0, 0x07FF, "NKo"},{0x0800, 0x083F, "Samaritan"},{0x0840, 0x085F, "Mandaic"},{0x0860, 0x086F, "Syriac Supplement"},{0x08A0, 0x08FF, "Arabic Extended-A"},{0x0900, 0x097F, "Devanagari"},{0x0980, 0x09FF, "Bengali"},{0x0A00, 0x0A7F, "Gurmukhi"},{0x0A80, 0x0AFF, "Gujarati"},{0x0B00, 0x0B7F, "Oriya"},{0x0B80, 0x0BFF, "Tamil"},{0x0C00, 0x0C7F, "Telugu"},{0x0C80, 0x0CFF, "Kannada"},{0x0D00, 0x0D7F, "Malayalam"},{0x0D80, 0x0DFF, "Sinhala"},{0x0E00, 0x0E7F, "Thai"},{0x0E80, 0x0EFF, "Lao"},{0x0F00, 0x0FFF, "Tibetan"},{0x1000, 0x109F, "Myanmar"},{0x10A0, 0x10FF, "Georgian"},{0x1100, 0x11FF, "Hangul Jamo"},{0x1200, 0x137F, "Ethiopic"},{0x1380, 0x139F, "Ethiopic Supplement"},{0x13A0, 0x13FF, "Cherokee"},{0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics"},{0x1680, 0x169F, "Ogham"},{0x16A0, 0x16FF, "Runic"},{0x1700, 0x171F, "Tagalog"},{0x1720, 0x173F, "Hanunoo"},{0x1740, 0x175F, "Buhid"},{0x1760, 0x177F, "Tagbanwa"},{0x1780, 0x17FF, "Khmer"},{0x1800, 0x18AF, "Mongolian"},{0x18B0, 0x18FF, "Unified Canadian Aboriginal Syllabics Extended"},{0x1900, 0x194F, "Limbu"},{0x1950, 0x197F, "Tai Le"},{0x1980, 0x19DF, "New Tai Lue"},{0x19E0, 0x19FF, "Khmer Symbols"},{0x1A00, 0x1A1F, "Buginese"},{0x1A20, 0x1AAF, "Tai Tham"},{0x1AB0, 0x1AFF, "Combining Diacritical Marks Extended"},{0x1B00, 0x1B7F, "Balinese"},{0x1B80, 0x1BBF, "Sundanese"},{0x1BC0, 0x1BFF, "Batak"},{0x1C00, 0x1C4F, "Lepcha"},{0x1C50, 0x1C7F, "Ol Chiki"},{0x1C80, 0x1C8F, "Cyrillic Extended-C"},{0x1C90, 0x1CBF, "Georgian Extended"},{0x1CC0, 0x1CCF, "Sundanese Supplement"},{0x1CD0, 0x1CFF, "Vedic Extensions"},{0x1D00, 0x1D7F, "Phonetic Extensions"},{0x1D80, 0x1DBF, "Phonetic Extensions Supplement"},{0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement"},{0x1E00, 0x1EFF, "Latin Extended Additional"},{0x1F00, 0x1FFF, "Greek Extended"},{0x2000, 0x206F, "General Punctuation"},{0x2070, 0x209F, "Superscripts and Subscripts"},{0x20A0, 0x20CF, "Currency Symbols"},{0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols"},{0x2100, 0x214F, "Letterlike Symbols"},{0x2150, 0x218F, "Number Forms"},{0x2190, 0x21FF, "Arrows"},{0x2200, 0x22FF, "Mathematical Operators"},{0x2300, 0x23FF, "Miscellaneous Technical"},{0x2400, 0x243F, "Control Pictures"},{0x2440, 0x245F, "Optical Character Recognition"},{0x2460, 0x24FF, "Enclosed Alphanumerics"},{0x2500, 0x257F, "Box Drawing"},{0x2580, 0x259F, "Block Elements"},{0x25A0, 0x25FF, "Geometric Shapes"},{0x2600, 0x26FF, "Miscellaneous Symbols"},{0x2700, 0x27BF, "Dingbats"},{0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A"},{0x27F0, 0x27FF, "Supplemental Arrows-A"},{0x2800, 0x28FF, "Braille Patterns"},{0x2900, 0x297F, "Supplemental Arrows-B"},{0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B"},{0x2A00, 0x2AFF, "Supplemental Mathematical Operators"},{0x2B00, 0x2BFF, "Miscellaneous Symbols and Arrows"},{0x2C00, 0x2C5F, "Glagolitic"},{0x2C60, 0x2C7F, "Latin Extended-C"},{0x2C80, 0x2CFF, "Coptic"},{0x2D00, 0x2D2F, "Georgian Supplement"},{0x2D30, 0x2D7F, "Tifinagh"},{0x2D80, 0x2DDF, "Ethiopic Extended"},{0x2DE0, 0x2DFF, "Cyrillic Extended-A"},{0x2E00, 0x2E7F, "Supplemental Punctuation"},{0x2E80, 0x2EFF, "CJK Radicals Supplement"},{0x2F00, 0x2FDF, "Kangxi Radicals"},{0x2FF0, 0x2FFF, "Ideographic Description Characters"},{0x3000, 0x303F, "CJK Symbols and Punctuation"},{0x3040, 0x309F, "Hiragana"},{0x30A0, 0x30FF, "Katakana"},{0x3100, 0x312F, "Bopomofo"},{0x3130, 0x318F, "Hangul Compatibility Jamo"},{0x3190, 0x319F, "Kanbun"},{0x31A0, 0x31BF, "Bopomofo Extended"},{0x31C0, 0x31EF, "CJK Strokes"},{0x31F0, 0x31FF, "Katakana Phonetic Extensions"},{0x3200, 0x32FF, "Enclosed CJK Letters and Months"},{0x3300, 0x33FF, "CJK Compatibility"},{0x3400, 0x4DBF, "CJK Unified Ideographs Extension A"},{0x4DC0, 0x4DFF, "Yijing Hexagram Symbols"},{0x4E00, 0x9FFF, "CJK Unified Ideographs"},{0xA000, 0xA48F, "Yi Syllables"},{0xA490, 0xA4CF, "Yi Radicals"},{0xA4D0, 0xA4FF, "Lisu"},{0xA500, 0xA63F, "Vai"},{0xA640, 0xA69F, "Cyrillic Extended-B"},{0xA6A0, 0xA6FF, "Bamum"},{0xA700, 0xA71F, "Modifier Tone Letters"},{0xA720, 0xA7FF, "Latin Extended-D"},{0xA800, 0xA82F, "Syloti Nagri"},{0xA830, 0xA83F, "Common Indic Number Forms"},{0xA840, 0xA87F, "Phags-pa"},{0xA880, 0xA8DF, "Saurashtra"},{0xA8E0, 0xA8FF, "Devanagari Extended"},{0xA900, 0xA92F, "Kayah Li"},{0xA930, 0xA95F, "Rejang"},{0xA960, 0xA97F, "Hangul Jamo Extended-A"},{0xA980, 0xA9DF, "Javanese"},{0xA9E0, 0xA9FF, "Myanmar Extended-B"},{0xAA00, 0xAA5F, "Cham"},{0xAA60, 0xAA7F, "Myanmar Extended-A"},{0xAA80, 0xAADF, "Tai Viet"},{0xAAE0, 0xAAFF, "Meetei Mayek Extensions"},{0xAB00, 0xAB2F, "Ethiopic Extended-A"},{0xAB30, 0xAB6F, "Latin Extended-E"},{0xAB70, 0xABBF, "Cherokee Supplement"},{0xABC0, 0xABFF, "Meetei Mayek"},{0xAC00, 0xD7AF, "Hangul Syllables"},{0xD7B0, 0xD7FF, "Hangul Jamo Extended-B"},{0xE000, 0xF8FF, "Private Use Area"},{0xF900, 0xFAFF, "CJK Compatibility Ideographs"},{0xFB00, 0xFB4F, "Alphabetic Presentation Forms"},{0xFB50, 0xFDFF, "Arabic Presentation Forms-A"},{0xFE00, 0xFE0F, "Variation Selectors"},{0xFE10, 0xFE1F, "Vertical Forms"},{0xFE20, 0xFE2F, "Combining Half Marks"},{0xFE30, 0xFE4F, "CJK Compatibility Forms"},{0xFE50, 0xFE6F, "Small Form Variants"},{0xFE70, 0xFEFF, "Arabic Presentation Forms-B"},{0xFF00, 0xFFEF, "Halfwidth and Fullwidth Forms"},{0xFFF0, 0xFFFF, "Specials"}
};class FontUtils {
public:FontUtils() {if (FT_Init_FreeType(&library_)) {throw std::runtime_error("無法初始化FreeType庫");}}~FontUtils() {FT_Done_FreeType(library_);}std::set<unsigned int> GetUnicodeFromFont(const fs::path& fontPath) {FT_Face face;std::set<unsigned int> unicodeSet;if (FT_New_Face(library_, fontPath.string().c_str(), 0, &face)) {std::cerr << "Error loading font file: " << fontPath << std::endl;return unicodeSet;}FT_UInt glyphIndex;FT_ULong charCode = FT_Get_First_Char(face, &glyphIndex);while (glyphIndex != 0) {unicodeSet.insert(static_cast<unsigned int>(charCode));charCode = FT_Get_Next_Char(face, charCode, &glyphIndex);}FT_Done_Face(face);return unicodeSet;}std::vector<unsigned char> unicode_code_point_to_utf8(unsigned int code_point) {std::vector<unsigned char> utf8_bytes;if (code_point <= 0x7F) {utf8_bytes.push_back(static_cast<unsigned char>(code_point));}else if (code_point <= 0x7FF) {utf8_bytes.push_back(static_cast<unsigned char>(0xC0 | ((code_point >> 6) & 0x1F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | (code_point & 0x3F)));}else if (code_point <= 0xFFFF) {utf8_bytes.push_back(static_cast<unsigned char>(0xE0 | ((code_point >> 12) & 0x0F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | ((code_point >> 6) & 0x3F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | (code_point & 0x3F)));}else if (code_point <= 0x10FFFF) {utf8_bytes.push_back(static_cast<unsigned char>(0xF0 | ((code_point >> 18) & 0x07)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | ((code_point >> 12) & 0x3F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | ((code_point >> 6) & 0x3F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | (code_point & 0x3F)));}return utf8_bytes;}std::string getFontCharactersForBlock(const fs::path& fontPath, const UnicodeBlock& block) {std::string characters;auto unicodeSet = GetUnicodeFromFont(fontPath);size_t count = 0;for (unsigned int code : unicodeSet) {if (code >= block.start && code <= block.end) {auto utf8_bytes = unicode_code_point_to_utf8(code);characters.append(reinterpret_cast<const char*>(utf8_bytes.data()), utf8_bytes.size());count++;// 限制每個區塊的最大字符數if (count >= MAX_CHARS_PER_BLOCK) {break;}}}return characters;}private:FT_Library library_;
};class PDFDocument {
public:PDFDocument(const fs::path& font_path, const fs::path& output_dir): pdf(HPDF_New(nullptr, nullptr)), output_dir(output_dir), font_path(font_path) {if (!pdf) {throw std::runtime_error("無法創建 PDF 文檔");}font_name = font_path.stem().string();pdf_filename = font_name + "_unicode_chars.pdf";HPDF_SetErrorHandler(pdf, [](HPDF_STATUS error_no, HPDF_STATUS detail_no, void* user_data) {std::cerr << "PDF錯誤: " << error_no << ", 詳情: " << detail_no << std::endl;});HPDF_UseUTFEncodings(pdf);HPDF_SetCurrentEncoder(pdf, "UTF-8");std::string extension = font_path.extension().string();const char* loaded_font = nullptr;if (extension == ".ttc") {loaded_font = HPDF_LoadTTFontFromFile2(pdf, font_path.string().c_str(), 0, HPDF_TRUE);}else if (extension == ".ttf" || extension == ".otf") {loaded_font = HPDF_LoadTTFontFromFile(pdf, font_path.string().c_str(), HPDF_TRUE);}else {HPDF_Free(pdf);throw std::runtime_error("不支持的字體文件格式: " + extension);}if (!loaded_font) {HPDF_Free(pdf);throw std::runtime_error("無法從文件加載字體: " + font_path.string());}font = HPDF_GetFont(pdf, loaded_font, "UTF-8");if (!font) {HPDF_Free(pdf);throw std::runtime_error("無法獲取字體對象");}}~PDFDocument() {if (pdf) {HPDF_Free(pdf);}}void AddCharactersPage(const std::string& chars, bool is_first_page = false) {HPDF_Page page = HPDF_AddPage(pdf);HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);float current_y = TOP_MARGIN; // 當前Y坐標// 如果是第一頁,添加彩色標題// 如果是第一頁,添加彩色漸變標題if (is_first_page) {std::string title = "the font name is " + font_name;size_t title_len = title.length();// 設置標題字體大小HPDF_Page_SetFontAndSize(page, font, 30);// 設置文字描邊(模擬加粗)HPDF_Page_SetLineWidth(page, 1); // 描邊寬度HPDF_Page_SetRGBStroke(page, 0, 0, 0); // 描邊顏色(黑色)// 2. 啟用描邊+填充模式HPDF_Page_SetTextRenderingMode(page, HPDF_FILL_THEN_STROKE);// 定位到標題起始位置HPDF_Page_BeginText(page);HPDF_Page_MoveTextPos(page, LEFT_MARGIN, current_y);// 生成漸變彩色標題(紅到藍漸變)for (size_t i = 0; i < title_len; i++) {char buf[2] = { title[i], '\0' };// 計算漸變顏色(從紅色漸變到藍色)// 彩虹色漸變(紅->黃->綠->青->藍->紫)float r = std::max(0.0f, 1.0f - std::abs((float)i / title_len - 0.5f) * 2.0f);float g = std::max(0.0f, 1.0f - std::abs((float)i / title_len - 0.25f) * 4.0f);float b = std::max(0.0f, 1.0f - std::abs((float)i / title_len - 0.75f) * 4.0f);//// 金色漸變效果//float r = 0.8f + 0.2f * sin((float)i / title_len * 3.14f);//float g = 0.6f + 0.2f * cos((float)i / title_len * 3.14f);//float b = 0.1f;// 自定義雙色漸變(如從藍到紫)//Color start(0.2, 0.4, 1.0); // 藍色//Color end(0.8, 0.2, 1.0); // 紫色//float r = start.r + (end.r - start.r) * (i / title_len);//float g = start.g + (end.g - start.g) * (i / title_len);//float b = start.b + (end.b - start.b) * (i / title_len);HPDF_Page_SetRGBFill(page, r, g, b); // 保持少量綠色分量HPDF_Page_ShowText(page, buf);}HPDF_Page_EndText(page);// 添加黑色統計信息HPDF_Page_SetRGBFill(page, 0.0f, 0.0f, 0.0f); // 重置為黑色// 4. 恢復默認填充模式(避免影響后續文本)HPDF_Page_SetTextRenderingMode(page, HPDF_FILL);HPDF_Page_BeginText(page);HPDF_Page_SetFontAndSize(page, font, 12);HPDF_Page_MoveTextPos(page, LEFT_MARGIN, current_y - 25);std::string stats = "font count: " + std::to_string(chars.size());HPDF_Page_ShowText(page, stats.c_str());HPDF_Page_EndText(page);// 調整字符顯示的起始Y位置current_y -= 40;}// 設置字符顯示字體HPDF_Page_SetFontAndSize(page, font, FONT_SIZE);float x = LEFT_MARGIN;float y = current_y; // 使用調整后的Y坐標size_t char_count = 0;size_t i = 0;while (i < chars.size()) {// 計算當前字符的UTF-8長度size_t char_len = 1;unsigned char c = chars[i];if ((c & 0xE0) == 0xC0) char_len = 2;else if ((c & 0xF0) == 0xE0) char_len = 3;else if ((c & 0xF8) == 0xF0) char_len = 4;// 提取當前字符std::string current_char = chars.substr(i, char_len);// 繪制字符HPDF_Page_BeginText(page);HPDF_Page_MoveTextPos(page, x, y);HPDF_Page_ShowText(page, current_char.c_str());HPDF_Page_EndText(page);// 更新位置x += CHAR_WIDTH;char_count++;// 換行處理if (char_count % CHARS_PER_LINE == 0) {x = LEFT_MARGIN;y -= LINE_HEIGHT;// 檢查是否超出頁面if (y < 50) { // 底部邊距// 創建新頁面繼續輸出AddCharactersPage(chars.substr(i + char_len));return;}}i += char_len;}}void GenerateWithUnicodeChars(FontUtils& font_utils) {std::string all_chars;size_t total_chars = 0;// 收集所有區塊的字符for (const auto& block : UNICODE_BLOCKS) {std::string block_chars = font_utils.getFontCharactersForBlock(font_path, block);if (!block_chars.empty()) {all_chars += block_chars;total_chars += block_chars.size();std::cout << "添加區塊: " << block.name<< " (U+" << std::hex << block.start << "-U+" << block.end << ")"<< ", 字符數: " << block_chars.size() << std::endl;}}std::cout << "總共收集 " << total_chars << " 個字符" << std::endl;if (!all_chars.empty()) {// 添加字符內容頁(第一頁包含標題)AddCharactersPage(all_chars, true);}else {std::cout << "字體中未找到任何Unicode字符" << std::endl;}}void Save() {if (!fs::exists(output_dir)) {fs::create_directories(output_dir);}fs::path full_path = fs::path(output_dir) / pdf_filename;HPDF_STATUS ret = HPDF_SaveToFile(pdf, full_path.string().c_str());if (ret != HPDF_OK) {throw std::runtime_error("保存PDF文件失敗");}std::cout << "PDF已生成: " << full_path.string() << std::endl;}private:HPDF_Doc pdf;HPDF_Font font;std::string font_name;std::string pdf_filename;fs::path output_dir;fs::path font_path;void AddTitlePage(size_t total_chars) {HPDF_Page page = HPDF_AddPage(pdf);HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);// 添加標題HPDF_Page_BeginText(page);HPDF_Page_SetFontAndSize(page, font, 24);HPDF_Page_MoveTextPos(page, LEFT_MARGIN, TOP_MARGIN);std::string title = "font " + font_name + " support Unicode character";HPDF_Page_ShowText(page, title.c_str());HPDF_Page_EndText(page);}std::string to_hex(unsigned int value) {std::stringstream ss;ss << std::hex << std::uppercase << value;return ss.str();}
};class FontScanner {
public:FontScanner(fs::path font_dir) :font_dir_(font_dir) {};std::vector<fs::path> GetTTcFont() {return ScanFontsInDirectory(font_dir_,{ ".otf", ".ttf" }, // 排除otf和 ttc 后綴{ "Bold", "Italic","Light","Cond","-" }, // 排除包含"Light"或"Bold"等其他樣式的的字體false // 啟用詳細輸出);};std::vector<fs::path> GetTTfFont() {return ScanFontsInDirectory(font_dir_,{ ".otf", ".ttc" }, // 排除otf和 ttc 后綴{ "Bold", "Italic","Light","Cond","-" }, // 排除包含"Light"或"Bold"等其他樣式的的字體false // 啟用詳細輸出);};std::vector<fs::path> ScanFontsInDirectory(const fs::path& directory,const std::set<std::string>& excludeSuffixes,const std::set<std::string>& excludeSubstrings,bool verbose){std::vector<fs::path> fontPaths;// 1. 驗證目錄路徑if (!fs::exists(directory)) {if (verbose) {std::cerr << "Error: Directory does not exist: "<< directory.string() << std::endl;}return fontPaths;}if (!fs::is_directory(directory)) {if (verbose) {std::cerr << "Error: Path is not a directory: "<< directory.string() << std::endl;}return fontPaths;}// 2. 預定義字體擴展名(小寫)static const std::set<std::string> kFontExtensions = {".ttf", ".otf", ".ttc", ".woff", ".woff2", ".pfb", ".pfm"};// 3. 預處理排除條件(轉換為小寫)std::set<std::string> excludeSuffixesLower;for (const auto& suffix : excludeSuffixes) {std::string lower = suffix;std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);excludeSuffixesLower.insert(lower);}std::set<std::string> excludeSubstringsLower;for (const auto& substr : excludeSubstrings) {std::string lower = substr;std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);excludeSubstringsLower.insert(lower);}// 4. 遍歷目錄try {for (const auto& entry : fs::recursive_directory_iterator(directory)) {if (!entry.is_regular_file()) {continue;}const auto& path = entry.path();std::string ext = path.extension().string();std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);// 4.1 檢查擴展名if (kFontExtensions.find(ext) == kFontExtensions.end()) {if (verbose) {std::cout << "Skipping non-font file: "<< path.string() << std::endl;}continue;}std::string filename = path.filename().string();std::string filenameLower = filename;std::transform(filenameLower.begin(), filenameLower.end(),filenameLower.begin(), ::tolower);// 4.2 檢查排除后綴bool excluded = false;for (const auto& suffix : excludeSuffixesLower) {if (EndsWithCI(filenameLower, suffix)) {if (verbose) {std::cout << "Excluding by suffix: "<< path.string() << std::endl;}excluded = true;break;}}if (excluded) continue;// 4.3 檢查排除子串for (const auto& substr : excludeSubstringsLower) {if (filenameLower.find(substr) != std::string::npos) {if (verbose) {std::cout << "Excluding by substring: "<< path.string() << std::endl;}excluded = true;break;}}if (excluded) continue;// 5. 添加到結果集if (verbose) {std::cout << "Adding font file: "<< path.string() << std::endl;}fontPaths.emplace_back(path);}}catch (const fs::filesystem_error& e) {if (verbose) {std::cerr << "Filesystem error: " << e.what()<< " (path: " << e.path1().string() << ")" << std::endl;}}return fontPaths;}
private:// 輔助函數:檢查字符串是否以指定后綴結尾(不區分大小寫)bool EndsWithCI(const std::string& str, const std::string& suffix) {if (suffix.size() > str.size()) return false;auto str_it = str.rbegin();auto suffix_it = suffix.rbegin();while (suffix_it != suffix.rend()) {if (tolower(*str_it++) != tolower(*suffix_it++)) {return false;}}return true;}fs::path font_dir_;
};int main() {try {FontUtils font_utils;fs::path font_dir = "C:/Windows/Fonts"; // 字體目錄fs::path output_dir = "D:/document_for_test/pdf_out"; // 輸出目錄FontScanner scanner(font_dir);auto ttc_font_files = scanner.GetTTcFont();auto ttf_font_files = scanner.GetTTfFont();for (const auto& path : ttf_font_files) {std::cout << "處理字體文件: " << path << std::endl;PDFDocument doc(path, output_dir);doc.GenerateWithUnicodeChars(font_utils);doc.Save();}}catch (const std::exception& ex) {std::cerr << "錯誤: " << ex.what() << std::endl;return 1;}return 0;
}為這個代碼起個標題
CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(FontUnicodeVisualizer VERSION 1.0 LANGUAGES CXX)# 設置 C++ 標準
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)# 查找 ZLIB 庫
find_package(ZLIB REQUIRED)
if(NOT ZLIB_FOUND)message(FATAL_ERROR "zlib library not found!")
endif()# 查找 PNG 庫
find_package(PNG REQUIRED)
if(NOT PNG_FOUND)message(FATAL_ERROR "libpng library not found!")
endif()# 查找 FreeType 庫
find_package(Freetype REQUIRED)
if(NOT Freetype_FOUND)message(FATAL_ERROR "FreeType library not found!")
endif()# 手動指定 Haru 庫的路徑(因為可能沒有 Findharu.cmake)
set(HARU_INCLUDE_DIR "/usr/local/Cellar/libharu/2.4.5/include")
set(HARU_LIBRARY "/usr/local/Cellar/libharu/2.4.5/lib/libhpdf.a")# 設置可執行文件
add_executable(font_visualizer${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)# 添加頭文件路徑
target_include_directories(font_visualizer PRIVATE${FREETYPE_INCLUDE_DIRS}${HARU_INCLUDE_DIR}${ZLIB_INCLUDE_DIRS}${PNG_INCLUDE_DIRS}
)# 鏈接庫
target_link_libraries(font_visualizer PRIVATE${FREETYPE_LIBRARIES}${HARU_LIBRARY}${PNG_LIBRARIES}${ZLIB_LIBRARIES}
)# 在 Windows 上需要額外鏈接系統庫
if(WIN32)target_link_libraries(font_visualizer PRIVATEgdi32)
endif()# 安裝配置
install(TARGETS font_visualizerRUNTIME DESTINATION bin
)
另一個版本 (跳過不能處理字體,但是不崩潰)
#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include <hpdf.h>
#include <cstring>
#include <set>
#include<sstream>
#include <ft2build.h>
#include <freetype/ftadvanc.h>
#include FT_FREETYPE_Hnamespace fs = std::filesystem;constexpr size_t CHARS_PER_LINE = 40; // 每行字符數
constexpr size_t LINES_PER_PAGE = 120; // 每頁行數
constexpr size_t CHARS_PER_PAGE = CHARS_PER_LINE * LINES_PER_PAGE; // 每頁字符總數
constexpr size_t MAX_CHARS_PER_BLOCK = 300; // 每個區塊最大字符數
constexpr int FONT_SIZE = 13;
constexpr float CHAR_WIDTH = 13.0f; // 每個字符的寬度
constexpr float LINE_HEIGHT = 20.0f; // 行高
constexpr float LEFT_MARGIN = 10.0f; // 左邊距
constexpr float TOP_MARGIN = 790.0f; // 上邊距struct UnicodeBlock {unsigned int start;unsigned int end;std::string name;
};// Unicode區塊定義
const std::vector<UnicodeBlock> UNICODE_BLOCKS = {{0x0000, 0x007F, "Basic Latin"},{0x0080, 0x00FF, "Latin-1 Supplement"},{0x0100, 0x017F, "Latin Extended-A"},{0x0180, 0x024F, "Latin Extended-B"},{0x0250, 0x02AF, "IPA Extensions"},{0x02B0, 0x02FF, "Spacing Modifier Letters"},{0x0300, 0x036F, "Combining Diacritical Marks"},{0x0370, 0x03FF, "Greek and Coptic"},{0x0400, 0x04FF, "Cyrillic"},{0x0500, 0x052F, "Cyrillic Supplement"},{0x0530, 0x058F, "Armenian"},{0x0590, 0x05FF, "Hebrew"},{0x0600, 0x06FF, "Arabic"},{0x0700, 0x074F, "Syriac"},{0x0750, 0x077F, "Arabic Supplement"},{0x0780, 0x07BF, "Thaana"},{0x07C0, 0x07FF, "NKo"},{0x0800, 0x083F, "Samaritan"},{0x0840, 0x085F, "Mandaic"},{0x0860, 0x086F, "Syriac Supplement"},{0x08A0, 0x08FF, "Arabic Extended-A"},{0x0900, 0x097F, "Devanagari"},{0x0980, 0x09FF, "Bengali"},{0x0A00, 0x0A7F, "Gurmukhi"},{0x0A80, 0x0AFF, "Gujarati"},{0x0B00, 0x0B7F, "Oriya"},{0x0B80, 0x0BFF, "Tamil"},{0x0C00, 0x0C7F, "Telugu"},{0x0C80, 0x0CFF, "Kannada"},{0x0D00, 0x0D7F, "Malayalam"},{0x0D80, 0x0DFF, "Sinhala"},{0x0E00, 0x0E7F, "Thai"},{0x0E80, 0x0EFF, "Lao"},{0x0F00, 0x0FFF, "Tibetan"},{0x1000, 0x109F, "Myanmar"},{0x10A0, 0x10FF, "Georgian"},{0x1100, 0x11FF, "Hangul Jamo"},{0x1200, 0x137F, "Ethiopic"},{0x1380, 0x139F, "Ethiopic Supplement"},{0x13A0, 0x13FF, "Cherokee"},{0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics"},{0x1680, 0x169F, "Ogham"},{0x16A0, 0x16FF, "Runic"},{0x1700, 0x171F, "Tagalog"},{0x1720, 0x173F, "Hanunoo"},{0x1740, 0x175F, "Buhid"},{0x1760, 0x177F, "Tagbanwa"},{0x1780, 0x17FF, "Khmer"},{0x1800, 0x18AF, "Mongolian"},{0x18B0, 0x18FF, "Unified Canadian Aboriginal Syllabics Extended"},{0x1900, 0x194F, "Limbu"},{0x1950, 0x197F, "Tai Le"},{0x1980, 0x19DF, "New Tai Lue"},{0x19E0, 0x19FF, "Khmer Symbols"},{0x1A00, 0x1A1F, "Buginese"},{0x1A20, 0x1AAF, "Tai Tham"},{0x1AB0, 0x1AFF, "Combining Diacritical Marks Extended"},{0x1B00, 0x1B7F, "Balinese"},{0x1B80, 0x1BBF, "Sundanese"},{0x1BC0, 0x1BFF, "Batak"},{0x1C00, 0x1C4F, "Lepcha"},{0x1C50, 0x1C7F, "Ol Chiki"},{0x1C80, 0x1C8F, "Cyrillic Extended-C"},{0x1C90, 0x1CBF, "Georgian Extended"},{0x1CC0, 0x1CCF, "Sundanese Supplement"},{0x1CD0, 0x1CFF, "Vedic Extensions"},{0x1D00, 0x1D7F, "Phonetic Extensions"},{0x1D80, 0x1DBF, "Phonetic Extensions Supplement"},{0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement"},{0x1E00, 0x1EFF, "Latin Extended Additional"},{0x1F00, 0x1FFF, "Greek Extended"},{0x2000, 0x206F, "General Punctuation"},{0x2070, 0x209F, "Superscripts and Subscripts"},{0x20A0, 0x20CF, "Currency Symbols"},{0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols"},{0x2100, 0x214F, "Letterlike Symbols"},{0x2150, 0x218F, "Number Forms"},{0x2190, 0x21FF, "Arrows"},{0x2200, 0x22FF, "Mathematical Operators"},{0x2300, 0x23FF, "Miscellaneous Technical"},{0x2400, 0x243F, "Control Pictures"},{0x2440, 0x245F, "Optical Character Recognition"},{0x2460, 0x24FF, "Enclosed Alphanumerics"},{0x2500, 0x257F, "Box Drawing"},{0x2580, 0x259F, "Block Elements"},{0x25A0, 0x25FF, "Geometric Shapes"},{0x2600, 0x26FF, "Miscellaneous Symbols"},{0x2700, 0x27BF, "Dingbats"},{0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A"},{0x27F0, 0x27FF, "Supplemental Arrows-A"},{0x2800, 0x28FF, "Braille Patterns"},{0x2900, 0x297F, "Supplemental Arrows-B"},{0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B"},{0x2A00, 0x2AFF, "Supplemental Mathematical Operators"},{0x2B00, 0x2BFF, "Miscellaneous Symbols and Arrows"},{0x2C00, 0x2C5F, "Glagolitic"},{0x2C60, 0x2C7F, "Latin Extended-C"},{0x2C80, 0x2CFF, "Coptic"},{0x2D00, 0x2D2F, "Georgian Supplement"},{0x2D30, 0x2D7F, "Tifinagh"},{0x2D80, 0x2DDF, "Ethiopic Extended"},{0x2DE0, 0x2DFF, "Cyrillic Extended-A"},{0x2E00, 0x2E7F, "Supplemental Punctuation"},{0x2E80, 0x2EFF, "CJK Radicals Supplement"},{0x2F00, 0x2FDF, "Kangxi Radicals"},{0x2FF0, 0x2FFF, "Ideographic Description Characters"},{0x3000, 0x303F, "CJK Symbols and Punctuation"},{0x3040, 0x309F, "Hiragana"},{0x30A0, 0x30FF, "Katakana"},{0x3100, 0x312F, "Bopomofo"},{0x3130, 0x318F, "Hangul Compatibility Jamo"},{0x3190, 0x319F, "Kanbun"},{0x31A0, 0x31BF, "Bopomofo Extended"},{0x31C0, 0x31EF, "CJK Strokes"},{0x31F0, 0x31FF, "Katakana Phonetic Extensions"},{0x3200, 0x32FF, "Enclosed CJK Letters and Months"},{0x3300, 0x33FF, "CJK Compatibility"},{0x3400, 0x4DBF, "CJK Unified Ideographs Extension A"},{0x4DC0, 0x4DFF, "Yijing Hexagram Symbols"},{0x4E00, 0x9FFF, "CJK Unified Ideographs"},{0xA000, 0xA48F, "Yi Syllables"},{0xA490, 0xA4CF, "Yi Radicals"},{0xA4D0, 0xA4FF, "Lisu"},{0xA500, 0xA63F, "Vai"},{0xA640, 0xA69F, "Cyrillic Extended-B"},{0xA6A0, 0xA6FF, "Bamum"},{0xA700, 0xA71F, "Modifier Tone Letters"},{0xA720, 0xA7FF, "Latin Extended-D"},{0xA800, 0xA82F, "Syloti Nagri"},{0xA830, 0xA83F, "Common Indic Number Forms"},{0xA840, 0xA87F, "Phags-pa"},{0xA880, 0xA8DF, "Saurashtra"},{0xA8E0, 0xA8FF, "Devanagari Extended"},{0xA900, 0xA92F, "Kayah Li"},{0xA930, 0xA95F, "Rejang"},{0xA960, 0xA97F, "Hangul Jamo Extended-A"},{0xA980, 0xA9DF, "Javanese"},{0xA9E0, 0xA9FF, "Myanmar Extended-B"},{0xAA00, 0xAA5F, "Cham"},{0xAA60, 0xAA7F, "Myanmar Extended-A"},{0xAA80, 0xAADF, "Tai Viet"},{0xAAE0, 0xAAFF, "Meetei Mayek Extensions"},{0xAB00, 0xAB2F, "Ethiopic Extended-A"},{0xAB30, 0xAB6F, "Latin Extended-E"},{0xAB70, 0xABBF, "Cherokee Supplement"},{0xABC0, 0xABFF, "Meetei Mayek"},{0xAC00, 0xD7AF, "Hangul Syllables"},{0xD7B0, 0xD7FF, "Hangul Jamo Extended-B"},{0xE000, 0xF8FF, "Private Use Area"},{0xF900, 0xFAFF, "CJK Compatibility Ideographs"},{0xFB00, 0xFB4F, "Alphabetic Presentation Forms"},{0xFB50, 0xFDFF, "Arabic Presentation Forms-A"},{0xFE00, 0xFE0F, "Variation Selectors"},{0xFE10, 0xFE1F, "Vertical Forms"},{0xFE20, 0xFE2F, "Combining Half Marks"},{0xFE30, 0xFE4F, "CJK Compatibility Forms"},{0xFE50, 0xFE6F, "Small Form Variants"},{0xFE70, 0xFEFF, "Arabic Presentation Forms-B"},{0xFF00, 0xFFEF, "Halfwidth and Fullwidth Forms"},{0xFFF0, 0xFFFF, "Specials"}
};class FontUtils {
public:FontUtils() {if (FT_Init_FreeType(&library_)) {throw std::runtime_error("無法初始化FreeType庫");}}~FontUtils() {FT_Done_FreeType(library_);}std::set<unsigned int> GetUnicodeFromFont(const fs::path& fontPath) {FT_Face face;std::set<unsigned int> unicodeSet;if (FT_New_Face(library_, fontPath.string().c_str(), 0, &face)) {std::cerr << "Error loading font file: " << fontPath << std::endl;return unicodeSet;}FT_UInt glyphIndex;FT_ULong charCode = FT_Get_First_Char(face, &glyphIndex);while (glyphIndex != 0) {unicodeSet.insert(static_cast<unsigned int>(charCode));charCode = FT_Get_Next_Char(face, charCode, &glyphIndex);}FT_Done_Face(face);return unicodeSet;}std::vector<unsigned char> unicode_code_point_to_utf8(unsigned int code_point) {std::vector<unsigned char> utf8_bytes;if (code_point <= 0x7F) {utf8_bytes.push_back(static_cast<unsigned char>(code_point));}else if (code_point <= 0x7FF) {utf8_bytes.push_back(static_cast<unsigned char>(0xC0 | ((code_point >> 6) & 0x1F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | (code_point & 0x3F)));}else if (code_point <= 0xFFFF) {utf8_bytes.push_back(static_cast<unsigned char>(0xE0 | ((code_point >> 12) & 0x0F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | ((code_point >> 6) & 0x3F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | (code_point & 0x3F)));}else if (code_point <= 0x10FFFF) {utf8_bytes.push_back(static_cast<unsigned char>(0xF0 | ((code_point >> 18) & 0x07)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | ((code_point >> 12) & 0x3F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | ((code_point >> 6) & 0x3F)));utf8_bytes.push_back(static_cast<unsigned char>(0x80 | (code_point & 0x3F)));}return utf8_bytes;}std::string getFontCharactersForBlock(const fs::path& fontPath, const UnicodeBlock& block) {std::string characters;auto unicodeSet = GetUnicodeFromFont(fontPath);size_t count = 0;for (unsigned int code : unicodeSet) {if (code >= block.start && code <= block.end) {auto utf8_bytes = unicode_code_point_to_utf8(code);characters.append(reinterpret_cast<const char*>(utf8_bytes.data()), utf8_bytes.size());count++;// 限制每個區塊的最大字符數if (count >= MAX_CHARS_PER_BLOCK) {break;}}}return characters;}private:FT_Library library_;
};class PDFDocument {
public:PDFDocument(const fs::path& font_path, const fs::path& output_dir): pdf(nullptr), font(nullptr), font_path(font_path), output_dir(output_dir) {try {pdf = HPDF_New(nullptr, nullptr);if (!pdf) {throw std::runtime_error("無法創建 PDF 文檔");}font_name = font_path.stem().string();pdf_filename = font_name + "_unicode_chars.pdf";HPDF_SetErrorHandler(pdf, [](HPDF_STATUS error_no, HPDF_STATUS detail_no, void* user_data) {std::cerr << "PDF錯誤: " << error_no << ", 詳情: " << detail_no << std::endl;});HPDF_UseUTFEncodings(pdf);HPDF_SetCurrentEncoder(pdf, "UTF-8");std::string extension = font_path.extension().string();const char* loaded_font = nullptr;if (extension == ".ttc") {loaded_font = HPDF_LoadTTFontFromFile2(pdf, font_path.string().c_str(), 0, HPDF_TRUE);}else if (extension == ".ttf" || extension == ".otf") {loaded_font = HPDF_LoadTTFontFromFile(pdf, font_path.string().c_str(), HPDF_TRUE);}else {throw std::runtime_error("不支持的字體文件格式: " + extension);}if (!loaded_font) {throw std::runtime_error("無法從文件加載字體: " + font_path.string());}font = HPDF_GetFont(pdf, loaded_font, "UTF-8");if (!font) {throw std::runtime_error("無法獲取字體對象");}}catch (const std::exception& e) {if (pdf) {HPDF_Free(pdf);pdf = nullptr;}throw; // 重新拋出異常}}~PDFDocument() {if (pdf) {HPDF_Free(pdf);}}bool IsValid() const {return pdf != nullptr && font != nullptr;}void AddCharactersPage(const std::string& chars, bool is_first_page = false) {if (!IsValid()) return;HPDF_Page page = HPDF_AddPage(pdf);HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);float current_y = TOP_MARGIN; // 當前Y坐標// 如果是第一頁,添加彩色標題if (is_first_page) {std::string title = "the font name is " + font_name;size_t title_len = title.length();// 設置標題字體大小HPDF_Page_SetFontAndSize(page, font, 30);// 設置文字描邊(模擬加粗)HPDF_Page_SetLineWidth(page, 1); // 描邊寬度HPDF_Page_SetRGBStroke(page, 0, 0, 0); // 描邊顏色(黑色)// 2. 啟用描邊+填充模式HPDF_Page_SetTextRenderingMode(page, HPDF_FILL_THEN_STROKE);// 定位到標題起始位置HPDF_Page_BeginText(page);HPDF_Page_MoveTextPos(page, LEFT_MARGIN, current_y);// 生成漸變彩色標題(紅到藍漸變)for (size_t i = 0; i < title_len; i++) {char buf[2] = { title[i], '\0' };// 計算漸變顏色(從紅色漸變到藍色)float r = std::max(0.0f, 1.0f - std::abs((float)i / title_len - 0.5f) * 2.0f);float g = std::max(0.0f, 1.0f - std::abs((float)i / title_len - 0.25f) * 4.0f);float b = std::max(0.0f, 1.0f - std::abs((float)i / title_len - 0.75f) * 4.0f);HPDF_Page_SetRGBFill(page, r, g, b);HPDF_Page_ShowText(page, buf);}HPDF_Page_EndText(page);// 添加黑色統計信息HPDF_Page_SetRGBFill(page, 0.0f, 0.0f, 0.0f); // 重置為黑色HPDF_Page_SetTextRenderingMode(page, HPDF_FILL);HPDF_Page_BeginText(page);HPDF_Page_SetFontAndSize(page, font, 12);HPDF_Page_MoveTextPos(page, LEFT_MARGIN, current_y - 25);std::string stats = "font count: " + std::to_string(chars.size());HPDF_Page_ShowText(page, stats.c_str());HPDF_Page_EndText(page);// 調整字符顯示的起始Y位置current_y -= 40;}// 設置字符顯示字體HPDF_Page_SetFontAndSize(page, font, FONT_SIZE);float x = LEFT_MARGIN;float y = current_y; // 使用調整后的Y坐標size_t char_count = 0;size_t i = 0;while (i < chars.size()) {// 計算當前字符的UTF-8長度size_t char_len = 1;unsigned char c = chars[i];if ((c & 0xE0) == 0xC0) char_len = 2;else if ((c & 0xF0) == 0xE0) char_len = 3;else if ((c & 0xF8) == 0xF0) char_len = 4;// 提取當前字符std::string current_char = chars.substr(i, char_len);// 繪制字符HPDF_Page_BeginText(page);HPDF_Page_MoveTextPos(page, x, y);HPDF_Page_ShowText(page, current_char.c_str());HPDF_Page_EndText(page);// 更新位置x += CHAR_WIDTH;char_count++;// 換行處理if (char_count % CHARS_PER_LINE == 0) {x = LEFT_MARGIN;y -= LINE_HEIGHT;// 檢查是否超出頁面if (y < 50) { // 底部邊距// 創建新頁面繼續輸出AddCharactersPage(chars.substr(i + char_len));return;}}i += char_len;}}void GenerateWithUnicodeChars(FontUtils& font_utils) {if (!IsValid()) return;std::string all_chars;size_t total_chars = 0;// 收集所有區塊的字符for (const auto& block : UNICODE_BLOCKS) {std::string block_chars = font_utils.getFontCharactersForBlock(font_path, block);if (!block_chars.empty()) {all_chars += block_chars;total_chars += block_chars.size();std::cout << "添加區塊: " << block.name<< " (U+" << std::hex << block.start << "-U+" << block.end << ")"<< ", 字符數: " << block_chars.size() << std::endl;}}std::cout << "總共收集 " << total_chars << " 個字符" << std::endl;if (!all_chars.empty()) {// 添加字符內容頁(第一頁包含標題)AddCharactersPage(all_chars, true);}else {std::cout << "字體中未找到任何Unicode字符" << std::endl;}}bool Save() {if (!IsValid()) return false;try {if (!fs::exists(output_dir)) {fs::create_directories(output_dir);}fs::path full_path = fs::path(output_dir) / pdf_filename;HPDF_STATUS ret = HPDF_SaveToFile(pdf, full_path.string().c_str());if (ret != HPDF_OK) {std::cerr << "保存PDF文件失敗: " << full_path << std::endl;return false;}std::cout << "PDF已生成: " << full_path.string() << std::endl;return true;}catch (const std::exception& e) {std::cerr << "保存PDF時出錯: " << e.what() << std::endl;return false;}}private:HPDF_Doc pdf;HPDF_Font font;std::string font_name;std::string pdf_filename;fs::path output_dir;fs::path font_path;
};class FontScanner {
public:FontScanner(fs::path font_dir) :font_dir_(font_dir) {};std::vector<fs::path> GetTTcFont() {return ScanFontsInDirectory(font_dir_,{ ".otf", ".ttf" }, // 排除otf和 ttc 后綴{ "Bold", "Italic","Light","Cond","-" }, // 排除包含"Light"或"Bold"等其他樣式的的字體false // 啟用詳細輸出);};std::vector<fs::path> GetTTfFont() {return ScanFontsInDirectory(font_dir_,{ ".otf", ".ttc" }, // 排除otf和 ttc 后綴{ "Bold", "Italic","Light","Cond","-" }, // 排除包含"Light"或"Bold"等其他樣式的的字體false // 啟用詳細輸出);};std::vector<fs::path> ScanFontsInDirectory(const fs::path& directory,const std::set<std::string>& excludeSuffixes,const std::set<std::string>& excludeSubstrings,bool verbose){std::vector<fs::path> fontPaths;// 1. 驗證目錄路徑if (!fs::exists(directory)) {if (verbose) {std::cerr << "Error: Directory does not exist: "<< directory.string() << std::endl;}return fontPaths;}if (!fs::is_directory(directory)) {if (verbose) {std::cerr << "Error: Path is not a directory: "<< directory.string() << std::endl;}return fontPaths;}// 2. 預定義字體擴展名(小寫)static const std::set<std::string> kFontExtensions = {".ttf", ".otf", ".ttc", ".woff", ".woff2", ".pfb", ".pfm"};// 3. 預處理排除條件(轉換為小寫)std::set<std::string> excludeSuffixesLower;for (const auto& suffix : excludeSuffixes) {std::string lower = suffix;std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);excludeSuffixesLower.insert(lower);}std::set<std::string> excludeSubstringsLower;for (const auto& substr : excludeSubstrings) {std::string lower = substr;std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);excludeSubstringsLower.insert(lower);}// 4. 遍歷目錄try {for (const auto& entry : fs::recursive_directory_iterator(directory)) {if (!entry.is_regular_file()) {continue;}const auto& path = entry.path();std::string ext = path.extension().string();std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);// 4.1 檢查擴展名if (kFontExtensions.find(ext) == kFontExtensions.end()) {if (verbose) {std::cout << "Skipping non-font file: "<< path.string() << std::endl;}continue;}std::string filename = path.filename().string();std::string filenameLower = filename;std::transform(filenameLower.begin(), filenameLower.end(),filenameLower.begin(), ::tolower);// 4.2 檢查排除后綴bool excluded = false;for (const auto& suffix : excludeSuffixesLower) {if (EndsWithCI(filenameLower, suffix)) {if (verbose) {std::cout << "Excluding by suffix: "<< path.string() << std::endl;}excluded = true;break;}}if (excluded) continue;// 4.3 檢查排除子串for (const auto& substr : excludeSubstringsLower) {if (filenameLower.find(substr) != std::string::npos) {if (verbose) {std::cout << "Excluding by substring: "<< path.string() << std::endl;}excluded = true;break;}}if (excluded) continue;// 5. 添加到結果集if (verbose) {std::cout << "Adding font file: "<< path.string() << std::endl;}fontPaths.emplace_back(path);}}catch (const fs::filesystem_error& e) {if (verbose) {std::cerr << "Filesystem error: " << e.what()<< " (path: " << e.path1().string() << ")" << std::endl;}}return fontPaths;}
private:// 輔助函數:檢查字符串是否以指定后綴結尾(不區分大小寫)bool EndsWithCI(const std::string& str, const std::string& suffix) {if (suffix.size() > str.size()) return false;auto str_it = str.rbegin();auto suffix_it = suffix.rbegin();while (suffix_it != suffix.rend()) {if (tolower(*str_it++) != tolower(*suffix_it++)) {return false;}}return true;}fs::path font_dir_;
};int main() {try {FontUtils font_utils;fs::path font_dir = "/Users/admin/Desktop/CopiedFonts"; // 字體目錄fs::path output_dir = "/Users/admin/Mac_Font_For_Test_PeoPle/pdf_out"; // 輸出目錄FontScanner scanner(font_dir);auto ttc_font_files = scanner.GetTTcFont();auto ttf_font_files = scanner.GetTTfFont();// 合并所有字體文件std::vector<fs::path> all_font_files;all_font_files.insert(all_font_files.end(), ttc_font_files.begin(), ttc_font_files.end());all_font_files.insert(all_font_files.end(), ttf_font_files.begin(), ttf_font_files.end());for (const auto& path : all_font_files) {std::cout << "\n處理字體文件: " << path << std::endl;try {PDFDocument doc(path, output_dir);if (!doc.IsValid()) {std::cerr << "警告: 無法處理字體文件 " << path << ",跳過" << std::endl;continue;}doc.GenerateWithUnicodeChars(font_utils);if (!doc.Save()) {std::cerr << "警告: 無法保存PDF文件 " << path << ",跳過" << std::endl;}}catch (const std::exception& e) {std::cerr << "處理字體文件 " << path << " 時出錯: " << e.what() << ",跳過" << std::endl;continue;}}}catch (const std::exception& ex) {std::cerr << "錯誤: " << ex.what() << std::endl;return 1;}return 0;
}