字體 Unicode 區塊字符展示 PDF 生成器

Unicode 字體字符集可視化工具 - 代碼介紹

項目概述

這個工具是一個用于分析和可視化字體文件中包含的 Unicode 字符的實用程序,能夠掃描指定字體文件,提取其中包含的所有 Unicode 字符,并按 Unicode 區塊分類生成 PDF 文檔,直觀展示字體支持的所有字符。

核心功能

  1. 字體掃描:自動掃描系統字體目錄,識別 TrueType (.ttf) 和 OpenType (.otf) 字體文件
  2. 字符提取:使用 FreeType 庫提取字體中支持的所有 Unicode 字符
  3. 區塊分類:按照 Unicode 標準區塊對字符進行分類
  4. PDF 生成:使用 Haru PDF 庫生成包含所有字符的可視化文檔
  5. 智能排版:自動處理多頁文檔、分欄顯示和換行

主要組件

1. UnicodeBlock 結構體

定義 Unicode 區塊的起始碼點、結束碼點和名稱,包含完整的 Unicode 15.0 標準區塊定義。

2. FontUtils 類

封裝 FreeType 庫操作,提供以下功能:

  • 初始化 FreeType 庫
  • 從字體文件提取 Unicode 字符集
  • Unicode 碼點到 UTF-8 編碼的轉換
  • 按 Unicode 區塊過濾字符

3. PDFDocument 類

封裝 Haru PDF 庫操作,提供以下功能:

  • PDF 文檔初始化
  • 字體加載和編碼設置
  • 字符頁面生成(支持多頁)
  • 彩色標題和統計信息添加
  • 文檔保存

4. FontScanner 類

負責掃描字體目錄,提供以下功能:

  • 遞歸掃描指定目錄中的字體文件
  • 按擴展名和文件名模式過濾字體
  • 支持排除特定樣式的字體(如粗體、斜體等)

技術特點

  1. 跨平臺支持:使用標準 C++17 和跨平臺庫
  2. 完整 Unicode 支持:覆蓋所有 Unicode 15.0 標準區塊
  3. 高效處理:智能分頁和區塊分割,避免內存問題
  4. 美觀輸出:彩色漸變標題、清晰的字符網格布局
  5. 靈活配置:可調整的頁面布局參數(邊距、字體大小等)

使用示例

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;
}

依賴庫

  1. FreeType:用于字體解析和字符提取
  2. Haru PDF:用于 PDF 文檔生成
  3. C++17 標準庫:文件系統、字符串處理等
  4. STL:容器、算法等

輸出示例

生成的 PDF 文檔包含:

  1. 彩色漸變字體標題
  2. 字體統計信息(總字符數)
  3. 按 Unicode 區塊組織的字符網格
  4. 自動分頁的多頁文檔

應用場景

  1. 字體設計師驗證字體覆蓋范圍
  2. 開發人員檢查字體對特定語言的支持
  3. 多語言項目中的字體兼容性測試
  4. 字體文檔自動生成
  5. 學術研究中的字符集分析

擴展性

項目可輕松擴展以支持:

  1. 自定義 Unicode 范圍過濾
  2. 額外的輸出格式(如 HTML、CSV)
  3. 字符屬性顯示(如編碼點、名稱)
  4. 字體特性分析(如連字、變體)
#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;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/913724.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/913724.shtml
英文地址,請注明出處:http://en.pswp.cn/news/913724.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

第4章:實戰項目一 打造你的第一個AI知識庫問答機器人 (RAG)

各位老鐵&#xff0c;歡迎來到我們專欄的第一個實戰項目。 在過去的三個章節里&#xff0c;我們已經完成了所有的理論儲備和環境搭建。我們理解了LLM的本質&#xff0c;掌握了Prompt Engineering的要領&#xff0c;洞悉了Embedding和向量數據庫的魔力&#xff0c;并且熟悉了La…

身份證識別api-便捷生活與安全社會的雙重保障

身份證識別技術是人工智能和圖像處理領域的杰出產物之一&#xff0c;正逐步滲透到我們生活的方方面面。而最直觀的作用就是簡化身份證驗證流程。現如今&#xff0c;無論是銀行開戶、酒店入住還是政務辦理、線上支付&#xff0c;都需要輸入 身份證信息進行身份驗證&#xff0c;傳…

跨國企業進入中國市場:如何利用亞馬遜云科技文檔 MCP 服務器解決區域差異問題

業務場景 想象一下&#xff0c;您是一家美國科技公司的 IT 架構師&#xff0c;公司剛剛決定將業務擴展到中國市場。作為技術負責人&#xff0c;您需要規劃如何將現有的基于亞馬遜云科技的應用遷移到中國區域。然而&#xff0c;您很快發現中國區的云服務環境與您熟悉的全球區域…

WPF使用WebBrowser 解決href標簽target=_blank在瀏覽器窗口打開新鏈接而非窗體內部打開的問題

前言 最近在WPF中使用WebBrowser控件顯示網頁的時候遇到一個問題,由于網頁里面有大規模的連接標簽使用了target=_blank的屬性,導致打開的網頁不是在我們的程序內部,而是調用系統瀏覽器打開了我們的網頁內容,這種情況非常的影響用戶體驗。于是就有了這篇文章內容。本文將詳細…

制作MikTex本地包可用于離線安裝包

MikTex安裝包版本是basic-miktex-24.1-x64.exe。注&#xff1a;basic版本表示只安裝MikTex基本包&#xff0c;不安裝全部包。在能夠聯網的電腦上安裝MikTex軟件后&#xff0c;可以按以下步驟制作本地包庫。一、制作本地包庫1、新建一個文件夾&#xff0c;比如在D盤新建miktex-l…

Redis基礎的介紹與使用(一)(Redis簡介以及Redis下載和安裝)

0 引言 本系列用于和大伙兒一起入門Redis&#xff0c;主要包括Redis的下載&#xff0c;分別在終端&#xff0c;圖形顯示界面以及JAVA代碼中進行使用&#xff0c;適合給需要快速了解Redis是什么以及上手使用的朋友們&#xff0c;希望我用最簡單的語言來講清楚相關內容&#xff…

七牛云C++開發面試題及參考答案

智能指針的原理及應用場景是什么&#xff1f; 智能指針是 C 中用于管理動態分配內存的工具&#xff0c;其核心原理是通過 RAII&#xff08;資源獲取即初始化&#xff09;技術&#xff0c;將堆內存的生命周期與對象的生命周期綁定&#xff0c;從而避免手動管理內存帶來的內存泄…

【Python辦公】Excel橫板表頭轉豎版通用工具(GUI版本)橫向到縱向的數據重構

目錄 專欄導讀前言項目概述功能特性技術棧核心代碼解析1. 類結構設計2. 界面布局設計3. 滾動列表實現4. 數據轉換核心邏輯5. 預覽功能實現設計亮點1. 用戶體驗優化2. 技術實現優勢3. 代碼結構優勢使用場景擴展建議總結完整代碼結尾專欄導讀 ?? 歡迎來到Python辦公自動化專欄—…

C#項目 在Vue/React前端項目中 使用使用wkeWebBrowser引用并且內部使用iframe網頁外鏈 頁面部分白屏

如果是使用wkeWebBrowser的引用方式 非常有可能是版本問題導致的 問題分析 1. wkeWebBrowser 的局限性 不支持或不完全支持 ES6 語法&#xff08;如 let, const, Promise, async/await&#xff09; 缺少對現代 Web API 的支持&#xff08;如 Intl, fetch, WebSocket&#xff0…

系統架構設計師論文分享-論微服務架構

我的軟考歷程 摘要 2023年2月&#xff0c;我所在的公司通過了研發紗線MES系統的立項&#xff0c;該系統為國內紗線工廠提供SAAS服務&#xff0c;旨在提高紗線工廠的數字化和智能化水平。我在該項目中擔任系統架構設計師一職&#xff0c;負責該項目的架構設計工作。本文結合我…

The History of Big Data

數據洪流悄然重塑世界的進程中&#xff0c;大數據的歷史是技術迭代與需求驅動的交響。從 2003 年分布式系統雛形初現&#xff0c;到 Hadoop 掀起開源浪潮&#xff0c;再到 Spark、容器化技術與深度學習的接力革新&#xff0c;以及 Hadoop 生態的興衰起落&#xff0c;大數據發展…

【JS逆向基礎】數據分析之正則表達式

前言&#xff1a;前面介紹了關于JS逆向所需的基本知識&#xff0c;比如前端三件套等&#xff0c;從這里開始就要進入到數據分析的范圍內了&#xff0c;當然對于一些小白而言一些基本的知識還是需要知道的&#xff0c;比如正則&#xff0c;XPATNY與BS4&#xff1b;三個內容用三篇…

Mac mini 高性價比擴容 + Crossover 游戲實測 全流程手冊

Mac mini 高性價比擴容 Crossover 游戲實測 全流程手冊 本文將圖文并茂地指導你如何&#xff1a; 為 M4 Mac mini 外置擴容&#xff08;綠聯 USB4 硬盤盒 致態 TiPlus7100&#xff09;安裝并配置 Crossover/Whisky 運行 Windows 應用實測游戲運行性能、診斷常見異常一、準備工…

【PyTorch】PyTorch中torch.nn模塊的卷積層

PyTorch深度學習總結 第七章 PyTorch中torch.nn模塊的卷積層 文章目錄PyTorch深度學習總結前言一、torch.nn模塊1. 模塊的基本組成部分1.1 層&#xff08;Layers&#xff09;1.2 損失函數&#xff08;Loss Functions&#xff09;1.3 激活函數&#xff08;Activation Functions…

Rust簡潔控制流:if let與let else高效編程指南

文章目錄Rust簡潔控制流&#xff1a;if let與let else高效編程指南&#x1f3af; if let&#xff1a;專注單一匹配場景&#x1f4a1; if let核心優勢&#xff1a;&#x1f504; if let與else搭配使用&#x1f680; let else&#xff1a;錯誤處理與提前返回&#x1f48e; let el…

upload-labs靶場通關詳解:第19關 條件競爭(二)

一、分析源代碼//index.php // 初始化變量&#xff1a;標記上傳狀態和錯誤消息 $is_upload false; $msg null;// 檢查是否通過POST方式提交了表單 if (isset($_POST[submit])) {// 引入自定義上傳類require_once("./myupload.php");// 生成基于時間戳的文件名&…

一天兩道力扣(3)

解法一&#xff1a;class Solution(object):def invertTree(self, root):if not root:return Noneroot.left, root.right root.right, root.leftself.invertTree(root.right)self.invertTree(root.left)return root解析&#xff1a;遞歸解法二&#xff1a;class Solution(obje…

jenkins2025安裝、插件、郵箱發送使用

Tips&#xff1a;卸載從新安裝(需要在C盤線先刪除.jenkins文件)&#xff0c;然后換個默認瀏覽器從新安裝推薦的插件(不然安裝插件這一步會報錯&#xff0c;連接不到jenkins) 一、jenkins安裝 訪問jenkins官網&#xff1a;https://www.jenkins.io/download/ 雙擊war包開始下載…

vue中通過tabs 切換 時 顯示不同的echarts 特殊處理

需要進行特殊處理 比如強制 進行resize 的方法 不然 大小顯示會出現問題我先把全部的代碼弄上<script setup lang"ts"> import { ref, onMounted, onBeforeUnmount, nextTick } from vue import { useRoute } from vue-router import { message } from ant-des…

淺度解讀-(未完成版)淺層神經網絡-深層神經網絡

文章目錄淺層神經網絡的前向傳播計算流程矩陣在運算時形狀的變化激活函數的作用為什么要有激活函數反向傳播深層神經網絡參數超參數參數初始化初始化權重的值選擇淺層神經網絡的前向傳播 計算流程 #mermaid-svg-tMPs4IUCtqxvhJ24 {font-family:"trebuchet ms",verda…