实用程序:解放双手!Python 打造 PDF 手写模拟器,轻松搞定手写作业

介绍开源 Python PDF 手写模拟器:tkinter + PyMuPDF 框选区域、中英文分参、抖动模拟真实笔迹,支持模板复用,用于实验报告等手写 PDF 填充。


前言

作为学生,想必大家都有过被海量手写实验报告、课程作业支配的痛苦。要花费大量时间一笔一划地抄写到纸质文档中再进行扫描成pdf提交,不仅耗时耗力,手写的字迹还可能参差不齐,影响作业美观度。

为了偷懒,博主开发了一款基于python的PDF手写模拟器,它能够模拟真实手写笔迹,将电脑上的文字批量填充到 PDF 指定区域,支持中文/英文分开配置、手写扰动效果自定义、配置模版复用等功能,生成的效果高度贴近真实手写,帮你彻底告别手动抄写的烦恼。首先来个界面以及效果预览:

代码已经开源在Github:https://github.com/ChenAI-TGF/PDF_HandWrite
欢迎大家下载,如果觉得有用的话可以给我点个Star,万分感谢!!
如果登不上Github的话也欢迎直接私信博主要代码

在这里插入图片描述

流程思路总览

这款 PDF 手写模拟器的整体运作流程可以分为「用户操作流程」和「技术实现流程」两层,清晰易懂:

1. 用户操作流程(简单易上手)

  1. 打开目标 PDF:选择需要填写的实验报告、作业等 PDF 文件;
    在这里插入图片描述
    在这里插入图片描述
  2. 框选填写区域:在 PDF 预览界面拖动鼠标,框选出需要插入文字的区域(红色矩形标记);
    在这里插入图片描述
  3. 配置手写参数:分别在中文、英文/数字选项卡中调整字体、大小、颜色、扰动效果等参数;
    在这里插入图片描述
  4. 输入并预览文字:在右侧文本框输入需要填写的内容,开启「实时预览」查看手写效果;
    在这里插入图片描述
    在这里插入图片描述
  5. 不满意可实时调整参数
    在这里插入图片描述
  6. 确认并保存:效果满意后确认应用笔迹,支持撤销、擦除错误内容,也可保存当前配置为模版,最后导出填写完成的 PDF。

在这里插入图片描述

2. 技术实现流程(分层协作)

  1. UI 交互层:基于 tkinter 搭建可视化界面,提供按钮、选项卡、文本框等交互组件,接收用户操作;
  2. 参数配置层:管理中文/英文手写参数,支持配置保存(JSON)与加载,实现模版复用;
  3. PDF 操作层:基于 PyMuPDF 实现 PDF 的读取、页面渲染、文字插入、擦除、保存等核心操作;
  4. 手写渲染层:核心逻辑层,实现文字排版、语言区分、手写扰动效果生成,最终将模拟手写文字插入 PDF。
┌─────────────────────┐
│  UI交互层(tkinter) │  # 可视化交互,接收操作
└─────────────┬───────┘

┌─────────────────────┐
│  参数配置层(JSON)  │  # 参数管理,模版复用
└─────────────┬───────┘

┌─────────────────────┐
│ PDF操作层(PyMuPDF) │  # PDF核心操作(读写/保存等)
└─────────────┬───────┘

┌─────────────────────┐
│  手写渲染层(核心)  │  # 手写效果生成,文字插入
└─────────────────────┘

涉及的具体技术

这款工具基于 Python 生态的常用库开发,技术栈轻量化且实用性强,核心涉及以下技术:

  1. tkinter:Python 内置 GUI 库,无需额外安装,负责搭建整个应用的可视化界面,实现按钮点击、参数调整、文本输入、PDF 预览等交互功能;
  2. PyMuPDF(fitz):核心 PDF 处理库,提供高效的 PDF 读取、写入、页面渲染、文字插入、红act标注(擦除功能)等接口,是实现 PDF 编辑的核心依赖;
  3. Pillow(PIL):图像处理库,将 PyMuPDF 渲染出的 PDF 页面像素数据,转换成 tkinter 画布可显示的图像格式,实现 PDF 页面的可视化预览;
  4. JSON:轻量级数据格式,用于保存用户配置的手写参数模版(字体、大小、扰动值等),方便后续直接复用,无需重复调整;
  5. Python 随机数与数学计算:实现手写效果的「不规则扰动」,模拟真实手写的位置偏移、大小波动、行倾斜等特性;
  6. TTF 字体支持:读取自定义 TrueType 字体文件,支持切换不同手写风格字体,适配不同用户的手写习惯。

最终效果演示

在这里插入图片描述

代码原理简单讲解(手写字体生成 + 扰动效果)

我们重点讲解核心功能 —— 手写字体生成与扰动效果的实现,忽略 UI 搭建等辅助代码,聚焦核心逻辑:

一、 手写字体生成(文字插入 PDF 核心)

这部分的核心是将文字按规则排版后,插入到 PDF 指定区域,关键步骤如下:

  1. 文字与区域预处理
    首先获取文本框中的输入文字,按换行符 \n 分割成多行,同时将用户框选的预览区域坐标(带缩放比例)转换为 PDF 实际坐标(除以缩放系数 self.zoom),得到真实的填写范围 (x1, y1, x2, y2)

    # 读取输入文字并分割行
    text = self.text_editor.get("1.0", tk.END).strip("\n")
    lines = text.split('\n')
    # 转换为 PDF 实际坐标(去除预览缩放影响)
    x1, y1 = min(self.start_x, self.end_x)/self.zoom, min(self.start_y, self.end_y)/self.zoom
    x2, y2 = max(self.start_x, self.end_x)/self.zoom, max(self.start_y, self.end_y)/self.zoom
    
  2. 语言区分与参数匹配
    通过 is_chinese 函数判断单个字符是中文还是英文/数字,分别匹配对应的配置参数(中文/英文独立的字体、大小、字距等),确保不同语言的手写效果适配合理。

    # 判断是否为中文(含中文标点)
    def is_chinese(self, char):
        if '\u4e00' <= char <= '\u9fff': return True
        if char in "。,、?!:;“”‘’()《》【】": return True
        return False
    
    # 遍历字符时匹配对应语言参数
    lang = 'zh' if self.is_chinese(char) else 'en'
    p = self.params[lang]  # 获取对应语言的配置参数
    
  3. PDF 文字插入核心 API
    使用 PyMuPDF 的 page.insert_text 方法实现文字插入,这是手写字体生成的关键,核心参数说明如下:

    • fitz.Point(final_x, final_y):文字的实际插入坐标(经排版和扰动调整后);
    • char:要插入的单个字符(逐字符插入实现精细排版);
    • fontsize:字符的实际大小(带大小抖动);
    • fontfile:TTF 手写字体文件路径,决定字体风格;
    • color:文字颜色(默认纯黑,支持用户自定义);
    • morph:变换矩阵,实现字符旋转效果(叠加行倾斜与字符旋转抖动)。
  4. 行与字符排版
    按行遍历文字,逐字符计算插入坐标,处理空格(单独预留间距),当字符横坐标超出框选区域右侧时自动换行,同时根据「行距参数」调整下一行的纵坐标,确保文字排版规整且不超出框选范围。

二、 扰动效果实现(模拟真实手写不规则性)

这是工具的灵魂所在,通过「行级扰动」和「字符级扰动」两层效果,彻底摆脱打印体的规整感,贴近真实手写,对应代码中的配置参数:

  1. 行级扰动(整行不规则性)
    针对每一行文字,添加整体的偏移和倾斜,模拟手写时「行不直、略有偏移」的特点,对应三个参数:

    • 行左右平移(line_jitter_x):随机生成 -line_jitter_x ~ line_jitter_x 范围内的偏移量 line_dx,使每行文字左右轻微晃动;
    • 行上下平移(line_jitter_y):随机生成 -line_jitter_y ~ line_jitter_y 范围内的偏移量 line_dy,使行与行之间的间距略有差异;
    • 行整行倾斜(line_tilt):随机生成倾斜角度 line_angle_deg,转换为弧度后,通过 tilt_y_offset = (curr_x - line_start_x) * math.tan(line_angle_rad) 计算每个字符的Y轴偏移,实现整行轻微倾斜。
    # 行级扰动参数计算
    line_dx = random.uniform(-p_line['line_jitter_x'].get(), p_line['line_jitter_x'].get())
    line_dy = random.uniform(-p_line['line_jitter_y'].get(), p_line['line_jitter_y'].get())
    line_angle_deg = random.uniform(-p_line['line_tilt'].get(), p_line['line_tilt'].get())
    line_angle_rad = math.radians(line_angle_deg)
    
  2. 字符级扰动(单个字符不规则性)
    针对每个字符,添加位置、大小、旋转的细微差异,模拟手写时「每个字大小不一、略有晃动」的特点,对应三个参数:

    • 字位置抖动(jitter_pos):随机生成 (-jitter_pos ~ jitter_pos) 的X、Y偏移量,使字符脱离严格的水平对齐;
    • 字大小抖动(jitter_size):在基础字体大小上,随机增减 (-jitter_size ~ jitter_size) 的数值,生成 char_jitter_fs,使字符大小略有差异;
    • 字旋转抖动(jitter_rot):随机生成 (-jitter_rot ~ jitter_rot) 的旋转角度,叠加行倾斜角度后,通过 morph 参数实现字符轻微旋转。
    # 字符级扰动参数计算
    char_jitter_x = random.uniform(-p['jitter_pos'].get(), p['jitter_pos'].get())
    char_jitter_y = random.uniform(-p['jitter_pos'].get(), p['jitter_pos'].get())
    char_jitter_rot = random.uniform(-p['jitter_rot'].get(), p['jitter_rot'].get())
    char_jitter_fs = f_size + random.uniform(-p['jitter_size'].get(), p['jitter_size'].get())
    
  3. 随机数控制(预览稳定 + 实际随机)
    为了保证实时预览时效果稳定可调整,设置随机数种子 random.seed(42) 固定扰动效果;实际应用笔迹时,不设置种子 random.seed(),生成随机的扰动效果,让每次生成的手写风格略有差异,更贴近真实场景。

    if is_preview: random.seed(42)  # 预览模式:固定随机数,效果稳定
    else: random.seed()  # 实际生成:随机种子,效果更真实
    

总结

这款 PDF 手写模拟器完美解决了手写作业、实验报告的痛点,兼具「实用性」和「灵活性」:

  1. 实用性拉满:无需手动抄写,一键生成手写风格 PDF,节省大量时间和精力,生成效果高度贴近真实手写;
  2. 自定义性强:中文/英文独立配置,支持字体、颜色、大小、字距行距调整,多层扰动参数可精细调控手写风格;
  3. 操作便捷:可视化界面友好,支持实时预览、撤销、擦除、模版复用,零基础用户也能快速上手;
  4. 技术轻量化:基于 Python 常用库开发,无需复杂环境配置,代码可灵活修改扩展。

当然,这款工具还有优化空间,比如支持连笔效果、笔迹粗细变化、批量处理多个 PDF 等。如果大家有类似需求,可以基于这份代码进行二次开发,适配自己的使用场景。希望这款工具能帮大家解放双手,把更多时间投入到核心学习和研究中!

代码已经开源在Github:https://github.com/ChenAI-TGF/PDF_HandWrite
欢迎大家下载,如果觉得有用的话可以给我点个Star,万分感谢!!
如果登不上Github的话也欢迎直接私信博主要代码

實用程式:解放雙手!Python 打造 PDF 手寫模擬器,輕鬆搞定手寫作業

介紹開源 Python PDF 手寫模擬器:tkinter + PyMuPDF 框選區域、中英文分參、抖動模擬真實筆跡,支持模板複用,用於實驗報告等手寫 PDF 填充。

來源:https://blog.csdn.net/2403_87969572/article/details/156458290

抓取時間(ISO本地):2026-05-18 05:17:27


前言

作為學生,想必大家都有過被海量手寫實驗報告、課程作業支配的痛苦。要花費大量時間一筆一劃地抄寫到紙質文件中再進行掃描成pdf提交,不僅耗時耗力,手寫的字跡還可能參差不齊,影響作業美觀度。

為了偷懶,博主開發了一款基於python的PDF手寫模擬器,它能夠模擬真實手寫筆跡,將電腦上的文字批次填充到 PDF 指定區域,支援中文/英文分開配置、手寫擾動效果自定義、配置模版複用等功能,生成的效果高度貼近真實手寫,幫你徹底告別手動抄寫的煩惱。首先來個介面以及效果預覽:

程式碼已經開源在Github:https://github.com/ChenAI-TGF/PDF_HandWrite
歡迎大家下載,如果覺得有用的話可以給我點個Star,萬分感謝!!
如果登不上Github的話也歡迎直接私信博主要程式碼

在這裡插入圖片描述

流程思路總覽

這款 PDF 手寫模擬器的整體運作流程可以分為「使用者操作流程」和「技術實現流程」兩層,清晰易懂:

1. 使用者操作流程(簡單易上手)

  1. 開啟目標 PDF:選擇需要填寫的實驗報告、作業等 PDF 檔案;
    在這裡插入圖片描述
    在這裡插入圖片描述
  2. 框選填寫區域:在 PDF 預覽介面拖動滑鼠,框選出需要插入文字的區域(紅色矩形標記);
    在這裡插入圖片描述
  3. 配置手寫引數:分別在中文、英文/數字選項卡中調整字型、大小、顏色、擾動效果等引數;
    在這裡插入圖片描述
  4. 輸入並預覽文字:在右側文字框輸入需要填寫的內容,開啟「實時預覽」檢視手寫效果;
    在這裡插入圖片描述
    在這裡插入圖片描述
  5. 不滿意可實時調整引數
    在這裡插入圖片描述
  6. 確認並儲存:效果滿意後確認應用筆跡,支援撤銷、擦除錯誤內容,也可儲存當前配置為模版,最後匯出填寫完成的 PDF。

在這裡插入圖片描述

2. 技術實現流程(分層協作)

  1. UI 互動層:基於 tkinter 搭建視覺化介面,提供按鈕、選項卡、文字框等互動元件,接收使用者操作;
  2. 引數配置層:管理中文/英文手寫引數,支援配置儲存(JSON)與載入,實現模版複用;
  3. PDF 操作層:基於 PyMuPDF 實現 PDF 的讀取、頁面渲染、文字插入、擦除、儲存等核心操作;
  4. 手寫渲染層:核心邏輯層,實現文字排版、語言區分、手寫擾動效果生成,最終將模擬手寫文字插入 PDF。
┌─────────────────────┐
│  UI互動層(tkinter) │  # 視覺化互動,接收操作
└─────────────┬───────┘

┌─────────────────────┐
│  引數配置層(JSON)  │  # 引數管理,模版複用
└─────────────┬───────┘

┌─────────────────────┐
│ PDF操作層(PyMuPDF) │  # PDF核心操作(讀寫/儲存等)
└─────────────┬───────┘

┌─────────────────────┐
│  手寫渲染層(核心)  │  # 手寫效果生成,文字插入
└─────────────────────┘

涉及的具體技術

這款工具基於 Python 生態的常用庫開發,技術棧輕量化且實用性強,核心涉及以下技術:

  1. tkinter:Python 內建 GUI 庫,無需額外安裝,負責搭建整個應用的視覺化介面,實現按鈕點選、引數調整、文字輸入、PDF 預覽等互動功能;
  2. PyMuPDF(fitz):核心 PDF 處理庫,提供高效的 PDF 讀取、寫入、頁面渲染、文字插入、紅act標註(擦除功能)等介面,是實現 PDF 編輯的核心依賴;
  3. Pillow(PIL):影象處理庫,將 PyMuPDF 渲染出的 PDF 頁面畫素資料,轉換成 tkinter 畫布可顯示的影象格式,實現 PDF 頁面的視覺化預覽;
  4. JSON:輕量級資料格式,用於儲存使用者配置的手寫引數模版(字型、大小、擾動值等),方便後續直接複用,無需重複調整;
  5. Python 隨機數與數學計算:實現手寫效果的「不規則擾動」,模擬真實手寫的位置偏移、大小波動、行傾斜等特性;
  6. TTF 字型支援:讀取自定義 TrueType 字型檔案,支援切換不同手寫風格字型,適配不同使用者的手寫習慣。

最終效果演示

在這裡插入圖片描述

程式碼原理簡單講解(手寫字型生成 + 擾動效果)

我們重點講解核心功能 —— 手寫字型生成與擾動效果的實現,忽略 UI 搭建等輔助程式碼,聚焦核心邏輯:

一、 手寫字型生成(文字插入 PDF 核心)

這部分的核心是將文字按規則排版後,插入到 PDF 指定區域,關鍵步驟如下:

  1. 文字與區域預處理
    首先獲取文字框中的輸入文字,按換行符 \n 分割成多行,同時將使用者框選的預覽區域座標(帶縮放比例)轉換為 PDF 實際座標(除以縮放係數 self.zoom),得到真實的填寫範圍 (x1, y1, x2, y2)

    # 讀取輸入文字並分割行
    text = self.text_editor.get("1.0", tk.END).strip("\n")
    lines = text.split('\n')
    # 轉換為 PDF 實際座標(去除預覽縮放影響)
    x1, y1 = min(self.start_x, self.end_x)/self.zoom, min(self.start_y, self.end_y)/self.zoom
    x2, y2 = max(self.start_x, self.end_x)/self.zoom, max(self.start_y, self.end_y)/self.zoom
    
  2. 語言區分與引數匹配
    透過 is_chinese 函式判斷單個字元是中文還是英文/數字,分別匹配對應的配置引數(中文/英文獨立的字型、大小、字距等),確保不同語言的手寫效果適配合理。

    # 判斷是否為中文(含中文標點)
    def is_chinese(self, char):
        if '\u4e00' <= char <= '\u9fff': return True
        if char in "。,、?!:;“”‘’()《》【】": return True
        return False
    
    # 遍歷字元時匹配對應語言引數
    lang = 'zh' if self.is_chinese(char) else 'en'
    p = self.params[lang]  # 獲取對應語言的配置引數
    
  3. PDF 文字插入核心 API
    使用 PyMuPDF 的 page.insert_text 方法實現文字插入,這是手寫字型生成的關鍵,核心引數說明如下:

    • fitz.Point(final_x, final_y):文字的實際插入座標(經排版和擾動調整後);
    • char:要插入的單個字元(逐字元插入實現精細排版);
    • fontsize:字元的實際大小(帶大小抖動);
    • fontfile:TTF 手寫字型檔案路徑,決定字型風格;
    • color:文字顏色(預設純黑,支援使用者自定義);
    • morph:變換矩陣,實現字元旋轉效果(疊加行傾斜與字元旋轉抖動)。
  4. 行與字元排版
    按行遍歷文字,逐字元計算插入座標,處理空格(單獨預留間距),當字元橫座標超出框選區域右側時自動換行,同時根據「行距引數」調整下一行的縱座標,確保文字排版規整且不超出框選範圍。

二、 擾動效果實現(模擬真實手寫不規則性)

這是工具的靈魂所在,透過「行級擾動」和「字元級擾動」兩層效果,徹底擺脫列印體的規整感,貼近真實手寫,對應程式碼中的配置引數:

  1. 行級擾動(整行不規則性)
    針對每一行文字,新增整體的偏移和傾斜,模擬手寫時「行不直、略有偏移」的特點,對應三個引數:

    • 行左右平移(line_jitter_x):隨機生成 -line_jitter_x ~ line_jitter_x 範圍內的偏移量 line_dx,使每行文字左右輕微晃動;
    • 行上下平移(line_jitter_y):隨機生成 -line_jitter_y ~ line_jitter_y 範圍內的偏移量 line_dy,使行與行之間的間距略有差異;
    • 行整行傾斜(line_tilt):隨機生成傾斜角度 line_angle_deg,轉換為弧度後,透過 tilt_y_offset = (curr_x - line_start_x) * math.tan(line_angle_rad) 計算每個字元的Y軸偏移,實現整行輕微傾斜。
    # 行級擾動引數計算
    line_dx = random.uniform(-p_line['line_jitter_x'].get(), p_line['line_jitter_x'].get())
    line_dy = random.uniform(-p_line['line_jitter_y'].get(), p_line['line_jitter_y'].get())
    line_angle_deg = random.uniform(-p_line['line_tilt'].get(), p_line['line_tilt'].get())
    line_angle_rad = math.radians(line_angle_deg)
    
  2. 字元級擾動(單個字元不規則性)
    針對每個字元,新增位置、大小、旋轉的細微差異,模擬手寫時「每個字大小不一、略有晃動」的特點,對應三個引數:

    • 字位置抖動(jitter_pos):隨機生成 (-jitter_pos ~ jitter_pos) 的X、Y偏移量,使字元脫離嚴格的水平對齊;
    • 字大小抖動(jitter_size):在基礎字型大小上,隨機增減 (-jitter_size ~ jitter_size) 的數值,生成 char_jitter_fs,使字元大小略有差異;
    • 字旋轉抖動(jitter_rot):隨機生成 (-jitter_rot ~ jitter_rot) 的旋轉角度,疊加行傾斜角度後,透過 morph 引數實現字元輕微旋轉。
    # 字元級擾動引數計算
    char_jitter_x = random.uniform(-p['jitter_pos'].get(), p['jitter_pos'].get())
    char_jitter_y = random.uniform(-p['jitter_pos'].get(), p['jitter_pos'].get())
    char_jitter_rot = random.uniform(-p['jitter_rot'].get(), p['jitter_rot'].get())
    char_jitter_fs = f_size + random.uniform(-p['jitter_size'].get(), p['jitter_size'].get())
    
  3. 隨機數控制(預覽穩定 + 實際隨機)
    為了保證實時預覽時效果穩定可調整,設定隨機數種子 random.seed(42) 固定擾動效果;實際應用筆跡時,不設定種子 random.seed(),生成隨機的擾動效果,讓每次生成的手寫風格略有差異,更貼近真實場景。

    if is_preview: random.seed(42)  # 預覽模式:固定隨機數,效果穩定
    else: random.seed()  # 實際生成:隨機種子,效果更真實
    

總結

這款 PDF 手寫模擬器完美解決了手寫作業、實驗報告的痛點,兼具「實用性」和「靈活性」:

  1. 實用性拉滿:無需手動抄寫,一鍵生成手寫風格 PDF,節省大量時間和精力,生成效果高度貼近真實手寫;
  2. 自定義性強:中文/英文獨立配置,支援字型、顏色、大小、字距行距調整,多層擾動引數可精細調控手寫風格;
  3. 操作便捷:視覺化介面友好,支援實時預覽、撤銷、擦除、模版複用,零基礎使用者也能快速上手;
  4. 技術輕量化:基於 Python 常用庫開發,無需複雜環境配置,程式碼可靈活修改擴充套件。

當然,這款工具還有最佳化空間,比如支援連筆效果、筆跡粗細變化、批次處理多個 PDF 等。如果大家有類似需求,可以基於這份程式碼進行二次開發,適配自己的使用場景。希望這款工具能幫大家解放雙手,把更多時間投入到核心學習和研究中!

程式碼已經開源在Github:https://github.com/ChenAI-TGF/PDF_HandWrite
歡迎大家下載,如果覺得有用的話可以給我點個Star,萬分感謝!!
如果登不上Github的話也歡迎直接私信博主要程式碼

Python PDF Handwriting Simulator — Fill Lab Reports Without Writing by Hand

Students know the pain of copying lab reports by hand. This Python PDF handwriting simulator fills selected PDF regions with realistic handwriting — separate Chinese/English settings, jitter controls, reusable JSON templates.

Captured at (local ISO): 2026-05-18 05:17:27


Preface

Students know the pain of copying lab reports by hand. This Python PDF handwriting simulator fills selected PDF regions with realistic handwriting — separate Chinese/English settings, jitter controls, reusable JSON templates. Open source:

GitHub: https://github.com/ChenAI-TGF/PDF_HandWrite — stars welcome! DM for code if GitHub is blocked.

在这里插入图片描述

Flow Overview

User flow: open PDF → drag box on preview → tune CN/EN fonts and jitter → type text with live preview → apply, undo, erase, save template → export.

Tech stack:

┌─────────────────────┐
│  UI (tkinter)       │
└─────────────┬───────┘

┌─────────────────────┐
│  Config (JSON)      │
└─────────────┬───────┘

┌─────────────────────┐
│  PDF (PyMuPDF)      │
└─────────────┬───────┘

┌─────────────────────┐
│  Handwriting render │
└─────────────────────┘

Screenshots: steps 2–9 in original (./2.png./9.png).

Stack

  • tkinter — GUI
  • PyMuPDF (fitz) — PDF read/write, insert text, redact erase
  • Pillow — preview on canvas
  • JSON — template save/load
  • random/math — jitter
  • TTF fonts — handwriting styles

Core Logic

1. Text placement

Split input by \n; map preview rectangle to PDF coords via self.zoom:

text = self.text_editor.get("1.0", tk.END).strip("\n")
lines = text.split('\n')
x1, y1 = min(self.start_x, self.end_x)/self.zoom, min(self.start_y, self.end_y)/self.zoom
x2, y2 = max(self.start_x, self.end_x)/self.zoom, max(self.start_y, self.end_y)/self.zoom

Per-character Chinese vs. English params via is_chinese(); insert with page.insert_text (position, fontsize, fontfile, color, morph rotation). Wrap when exceeding box width.

def is_chinese(self, char):
    if '\u4e00' <= char <= '\u9fff': return True
    if char in "。,、?!:;“”‘’()《》【】": return True
    return False
lang = 'zh' if self.is_chinese(char) else 'en'
p = self.params[lang]

2. Jitter (realistic handwriting)

Line-level: line_jitter_x/y, line_tilt — shift and tilt entire lines.

line_dx = random.uniform(-p_line['line_jitter_x'].get(), p_line['line_jitter_x'].get())
line_dy = random.uniform(-p_line['line_jitter_y'].get(), p_line['line_jitter_y'].get())
line_angle_deg = random.uniform(-p_line['line_tilt'].get(), p_line['line_tilt'].get())

Character-level: jitter_pos, jitter_size, jitter_rot.

char_jitter_x = random.uniform(-p['jitter_pos'].get(), p['jitter_pos'].get())
char_jitter_y = random.uniform(-p['jitter_pos'].get(), p['jitter_pos'].get())
char_jitter_rot = random.uniform(-p['jitter_rot'].get(), p['jitter_rot'].get())
char_jitter_fs = f_size + random.uniform(-p['jitter_size'].get(), p['jitter_size'].get())

Preview vs. final: random.seed(42) for stable preview; random.seed() for export variety.

if is_preview: random.seed(42)
else: random.seed()

Summary

Saves time on handwritten assignments; highly customizable; beginner-friendly UI; lightweight Python stack. Possible extensions: ligatures, stroke width, batch PDFs.

GitHub: https://github.com/ChenAI-TGF/PDF_HandWrite