#include <ft2build.h>
#include FT_FREETYPE_H
#include <stdlib.h> // 引入標準庫標頭檔案,提供一些通用的函式和宏定義
#include <stdio.h> // 引入標準輸入輸出標頭檔案,提供輸入輸出相關的函式
#include <string.h> // 引入字串處理標頭檔案,提供字串操作相關的函式
#include <iostream> // 引入輸入輸出流標頭檔案,提供輸入輸出流的操作
#include <vector> // 引入向量容器標頭檔案,提供動態陣列的功能
#include <cstdint> // 引入固定寬度整數型別標頭檔案,提供固定寬度整數型別的定義
#include <setjmp.h> // 引入setjmp/longjmp標頭檔案,提供異常處理功能
#include <wchar.h> // 引入寬字元處理標頭檔案,提供寬字元操作相關的函式
#include "hpdf.h" // 引入Haru PDF庫標頭檔案,提供建立、編輯PDF文件的功能
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
//#define FONT_PATH "C:/fonts/Arial-Unicode-MS.ttf" // Arial Unicode MS字型檔案路徑
#define FONT_PATH "C:/fonts/SimSun-01.ttf" // Arial Unicode MS字型檔案路徑
#define START_CHAR (000000) // Unicode編碼 起始
#define END_CHAR (65536) // Unicode編碼 結束
#define CHARACTERS_PER_LINE 10 // 每行 10 個字元
#define X_SPACEING 50 // 每一行每個字元之間的間隔
#define PAGE_NUM 200 //每頁200個字元
float height, x, y;
jmp_buf* env; // 定義全域性變數env,用於儲存setjmp的環境
// 將Unicode編碼轉換為UTF-8編碼的函式
std::vector<uint8_t> unicode_code_point_to_utf8(uint32_t code_point)
{
////std::vector<uint8_t>型別的變數utf8_bytes,用於儲存轉換後的UTF-8位元組序列
std::vector<uint8_t> utf8_bytes;
/*
1.如果code_point小於等於0x7F(即ASCII字元),
直接將code_point作為UTF-8位元組新增到utf8_bytes中。
2.
如果code_point在0x80到0x7FF之間,
將其轉換為兩個位元組的UTF-8編碼,並將這兩個位元組新增到utf8_bytes中。
3.如果code_point在0x800到0xFFFF之間,
將其轉換為三個位元組的UTF-8編碼,並將這三個位元組新增到utf8_bytes中。
4.如果code_point在0x10000到0x10FFFF之間(即需要使用代理對錶示的Unicode碼點),
則先將其分解為前導代理和後尾代理,然後分別將它們轉換為UTF-8編碼,
最後將這兩個UTF-8編碼合併到utf8_bytes中。
5.如果code_point超出了有效範圍,輸出錯誤資訊
*/
if (code_point <= 0x7F)
{
utf8_bytes.push_back(code_point);
}
else if (code_point <= 0x7FF)
{
utf8_bytes.push_back(0xC0 | ((code_point >> 6) & 0x1F));
utf8_bytes.push_back(0x80 | (code_point & 0x3F));
}
else if (code_point <= 0xFFFF)
{
utf8_bytes.push_back(0xE0 | ((code_point >> 12) & 0x0F));
utf8_bytes.push_back(0x80 | ((code_point >> 6) & 0x3F));
utf8_bytes.push_back(0x80 | (code_point & 0x3F));
}
else if (code_point <= 0x10FFFF)
{
// 分解
uint32_t lead_surrogate = (code_point >> 10) + 0xD800;
uint32_t trail_surrogate = (code_point & 0x3FF) + 0xDC00;
// 轉換兩個 UTF-8
std::vector<uint8_t> lead_bytes = unicode_code_point_to_utf8(lead_surrogate);
std::vector<uint8_t> trail_bytes = unicode_code_point_to_utf8(trail_surrogate);
// 將兩個 UTF-8 編碼合併
utf8_bytes.insert(utf8_bytes.end(), lead_bytes.begin(), lead_bytes.end());
utf8_bytes.insert(utf8_bytes.end(), trail_bytes.begin(), trail_bytes.end());
}
else
{
std::cerr << "Invalid Unicode code point." << std::endl;
}
//返回轉換後的UTF-8位元組序列utf8_bytes
return utf8_bytes;
}
void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void* user_data)
{
printf("ERROR: error_no=%04X, detail_no=%u\n", (HPDF_UINT)error_no, (HPDF_UINT)detail_no);
longjmp(*env, 1);
}
// 函式:獲取指定字型檔案中所有存在的Unicode字元,並以十六進位制字串形式儲存
void getUnicodeCharsFromFont(const std::string& fontPath, std::vector<std::string>& unicodeCharsHex) {
FT_Library library;
FT_Face face;
// 初始化FreeType庫
if (FT_Init_FreeType(&library)) {
std::cerr << "FT_Init_FreeType failed!" << std::endl;
return;
}
// 載入字型檔案
if (FT_New_Face(library, fontPath.c_str(), 0, &face)) {
std::cerr << "FT_New_Face failed!" << std::endl;
FT_Done_FreeType(library);
return;
}
// 遍歷cmap表中的所有子表
FT_UInt num_charmaps = face->num_charmaps;
for (FT_UInt i = 0; i < num_charmaps; ++i) {
FT_CharMap charmap = face->charmaps[i];
// 只處理Unicode編碼的cmap表
if (charmap->encoding == FT_ENCODING_UNICODE) {
for (FT_ULong charcode = 0; charcode < 0x110000; ++charcode) {
FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);
if (glyph_index != 0) {
//std::stringstream ss;
//ss << std::hex << charcode; // 轉換為十六進位制字串
//unicodeCharsHex.push_back(ss.str()); // 儲存到容器中
unicodeCharsHex.push_back(std::to_string(charcode)); // 將Unicode值轉換為字串並儲存
}
}
}
}
// 清理資源
FT_Done_Face(face);
FT_Done_FreeType(library);
}
int main(int argc, char** argv)
{
HPDF_Doc pdf=nullptr; // 定義一個 PDF 文件物件
HPDF_Font font=nullptr; // 定義一個字型物件
HPDF_Page page=nullptr; // 定義一個頁面物件
jmp_buf env; // 定義一個跳轉緩衝區,用於錯誤處理
unsigned char buf[8];// 定義一個字元緩衝區,用於儲存轉換後的字元
unsigned int i; // 定義一個無符號整數變數,用於迴圈計數
std::vector<std::string> unicodeCharsDec;
getUnicodeCharsFromFont(FONT_PATH, unicodeCharsDec);
///* for (auto i : unicodeCharsHex)
// {
// std::cout << i << std::endl;
// }*/
// // 列印結果
// for (const auto& utf8Bytes : utf8BytesList)
// {
// for (uint8_t byte : utf8Bytes)
// {
// std::cout << std::hex << static_cast<int>(byte) << " ";
// }
// std::cout << std::endl;
// }
//#if 1
/*
建立一個新的 PDF 文件物件,並設定錯誤處理器和跳轉緩衝區
當在使用libharu庫建立HPDF_Doc物件時發生錯誤時,
libharu會呼叫error_handler函式來處理錯誤。
*/
pdf = HPDF_New(error_handler, env);
if (!pdf)
{
printf("ERROR: cannot create pdf object.\n");
return 1;
}
/*
這是一個錯誤處理機制,
1.當執行到setjmp(env)時會將當前的執行狀態儲存到env緩衝區,並返回一個非零的值。
2.setjmp返回的值非零,說明發生了錯誤並透過longjmp跳轉到了相應的錯誤處理位置。
在這個錯誤處理塊中,首先呼叫HPDF_Free釋放之前建立的HPDF_Doc物件,然後返回1,表示出現錯誤。
3.setjmp返回的值為0,說明沒有發生錯誤,程式會繼續執行後續的程式碼。
*/
if (setjmp(env))
{
HPDF_Free(pdf);
return 1;
}
/* */
/*
啟用UTF編碼支援。透過呼叫HPDF_UseUTFEncodings(pdf);函式,
libharu庫會啟用UTF-8和UTF-16編碼方式,以便支援Unicode字符集。
*/
HPDF_UseUTFEncodings(pdf);
/*
用於設定當前的編碼器。
透過呼叫此函式並傳遞"UTF-8"作為引數,可以將當前的編碼器設定為UTF - 8編碼器,
使得PDF文件中的文字內容可以以UTF - 8編碼進行處理和顯示。
*/
HPDF_SetCurrentEncoder(pdf, "UTF-8");
// 載入 Arial Unicode MS 字型
float height = HPDF_Page_GetHeight(page);
float x = X_SPACEING * 11, y = height;
/*
載入一個TrueType字型檔案,並獲取該字型的UTF-8編碼
FONT_PATH自定義字型檔案路徑
HPDF_TRUE的功能是在載入TrueType字型時啟用子集化選項,表示只有需要的字形會被載入到記憶體中,而不是整個字型檔案。
*/
const char* my_font = HPDF_LoadTTFontFromFile(pdf, FONT_PATH, HPDF_TRUE);
font = HPDF_GetFont(pdf, my_font, "UTF-8");
// 新增頁
page = HPDF_AddPage(pdf);
// 設定頁的大小
/*
HPDF_PAGE_SIZE_A4:這是一個常量,表示A4紙張的大小,通常為210mm × 297mm。
HPDF_PAGE_PORTRAIT:這是一個常量,表示頁面的方向為縱向。
也可以使用HPDF_PAGE_LANDSCAPE常量來表示頁面的方向為橫向。
*/
HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);
/*
透過將此page作為引數傳遞給HPDF_Page_BeginText函式,
可以指定在page頁面上開始繪製文字。*/
HPDF_Page_BeginText(page);
// 設定字型大小
HPDF_Page_SetFontAndSize(page, font, 12);
// 獲取頁高
/* */
height = HPDF_Page_GetHeight(page);
/* */
x = X_SPACEING * 11;
y = height;
/* */
HPDF_Page_MoveTextPos(page, x, y);
/* */
int char_count = 0;
int idx = 0;
//START_CHAR (000000) // Unicode編碼 起始
//END_CHAR(65536) // Unicode編碼 結束
//CHARACTERS_PER_LINE 10 // 每行 10 個字元
//LINES_PER_PAGE 20 // 每頁20行
//X_SPACEING 50 // 每一行每個字元之間的間隔
//PAGE_NUM 200 //每頁200個字元
//遍歷從START_CHAR到END_CHAR之間的所有字元。
//for (i = START_CHAR; i <= END_CHAR; i++)
for(auto i : unicodeCharsDec)
{
//判斷當前字元是否是每行的最後一個字元。
if (char_count % CHARACTERS_PER_LINE == 0)
{
//將文字游標移動到下一行的起始位置。
HPDF_Page_MoveTextPos(page, -X_SPACEING * 10, -40);
}
//清空緩衝區buf
memset(buf, 0, sizeof(buf));
// 將當前字元i轉換為UTF-8編碼。
// unicode 轉 utf-8
std::vector<uint8_t> tmp = unicode_code_point_to_utf8(static_cast<uint32_t>(std::stoul(i, nullptr, 10)));
/* for (auto k : tmp)
{
std::cout << "unicode_code_point_to_utf8 " << k<< std::endl;
}*/
//std::cout << "unicode_code_point_to_utf8 " << i << std::endl;
//遍歷轉換後的UTF-8編碼
for (int j = 0; j < tmp.size(); j++)
{
//將UTF-8編碼儲存到緩衝區buf中。
buf[j] = tmp[j];
}
//在PDF頁面上顯示當前字元。
HPDF_Page_ShowText(page, (const char*)buf);
//將文字游標向右移動一個字元寬度。
HPDF_Page_MoveTextPos(page, X_SPACEING, 0);
//更新文字游標的橫座標。
x += X_SPACEING;
//更新已處理的字元計數
char_count++;
//char_count==PAGE_NUM需要進行分頁處理
if (char_count % PAGE_NUM == 0)
{
//結束當前頁面的文字繪製
HPDF_Page_EndText(page);
//重置已處理的字元計數
char_count = 0;
//設定下一個頁面的文字游標的橫座標
x = X_SPACEING * 11;
//設定下一個頁面的文字游標的縱座標
y = height;
//新增一個新的PDF頁面
page = HPDF_AddPage(pdf);
//開始在新頁面上繪製文字
HPDF_Page_BeginText(page);
//設定新頁面上的字型和字號
HPDF_Page_SetFontAndSize(page, font, 12);
//將文字游標移動到新頁面的指定位置
HPDF_Page_MoveTextPos(page, x, y);
}
}
//結束當前頁面的文字繪製
HPDF_Page_EndText(page);
// 生成的PDF文件儲存到名為"mypdf.pdf"的檔案中。
HPDF_SaveToFile(pdf, "mypdf.pdf");
//釋放PDF文件物件
HPDF_Free(pdf);
//#endif
return 0;
}