前情提要
【纯小白,勿喷】刚刚发错区了,重发
由于工作需要,在word中插入大量的手写签名,开始我在网上找在线的网站一个一个转换,但是这样非常麻烦,后来想干脆自己设计一个,现在ai这么强大,你只管提需求,剩下的交ai。
于是我基于自己的需求,通过ai写了一个程序,基于python开发的,我自己一句代码都没写,也不会写,完全是一个小白。有用的上的源码拿走直接用,不保证兼容系统,我自己的windows11正常运行。(当然,手写字体自行下载放入对应的文件夹内)
目录结构如下
/SignatureGenerator
│── /fonts # 存放手写体.ttf文件(用户自备)
│── /output # 生成的签名图片
│── app.py # 主程序(PyQt5界面+核心逻辑)
│── config.py # 配置文件(调整DPI/旋转角度等)
│── requirements.txt # 依赖库清单
源码
app.py
复制代码 隐藏代码
import os
import random
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import Qt
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import numpy as np
class SignatureGenerator(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("智能签名生成器 v2.1")
self.setGeometry(300, 300, 1200, 800)
self.setStyleSheet("""
QMainWindow { background: #f8f9fa; font-family: 'Microsoft YaHei'; }
QPushButton {
background: #4e73df; color: white; border-radius: 6px;
padding: 10px 20px; font-size: 14px; min-width: 120px;
}
QTextEdit, QComboBox {
border: 1px solid #d1d9e6; border-radius: 6px; padding: 12px;
font-size: 14px; background: white;
}
""")
self.init_ui()
self.load_fonts()
self.generated_images = []
def init_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QHBoxLayout(central_widget)
# 左侧控制面板
control_panel = QVBoxLayout()
control_panel.setSpacing(20)
input_group = QGroupBox("批量输入设置")
input_layout = QVBoxLayout()
self.input_field = QTextEdit()
self.input_field.setPlaceholderText("请输入姓名,用逗号分隔(例:张三,李四·王五)")
input_layout.addWidget(QLabel("待生成姓名列表:"))
input_layout.addWidget(self.input_field)
input_group.setLayout(input_layout)
self.generate_btn = QPushButton("🎨 随机生成签名")
self.generate_btn.clicked.connect(self.generate_signatures)
self.download_btn = QPushButton("💾 下载全部签名")
self.download_btn.setEnabled(False)
self.download_btn.clicked.connect(self.download_signatures)
# 右侧预览区域
self.preview_area = QScrollArea()
self.preview_container = QWidget()
self.preview_layout = QVBoxLayout(self.preview_container)
self.preview_area.setWidget(self.preview_container)
self.preview_area.setWidgetResizable(True)
# 组装布局
control_panel.addWidget(input_group)
control_panel.addWidget(self.generate_btn)
control_panel.addWidget(self.download_btn)
control_panel.addStretch()
layout.addLayout(control_panel, 1)
layout.addWidget(self.preview_area, 2)
def load_fonts(self):
"""加载字体文件"""
self.font_files = []
fonts_dir = os.path.join(os.path.dirname(__file__), "fonts")
if os.path.exists(fonts_dir):
for f in os.listdir(fonts_dir):
if f.lower().endswith(('.ttf', '.otf')):
self.font_files.append(os.path.join(fonts_dir, f))
if not self.font_files:
QMessageBox.warning(self, "警告", "未检测到字体文件,请将.ttf文件放入/fonts目录")
def generate_signatures(self):
"""核心生成逻辑"""
names_text = self.input_field.toPlainText().strip()
if not names_text:
QMessageBox.warning(self, "提示", "请输入至少一个姓名")
return
names = [n.strip() for n in names_text.replace(",", ",").split(",") if n.strip()]
if not names:
QMessageBox.warning(self, "提示", "请输入有效的姓名(用逗号分隔)")
return
# 清空预览区
self.clear_preview()
self.generated_images = []
used_fonts = set()
for name in names:
if len(name) < 2 or len(name) > 4:
QMessageBox.warning(self, "格式错误", f"跳过不支持的姓名长度: {name} (需2-4个字符)")
continue
# 选择未使用的字体
font_path = self.select_unique_font(used_fonts)
if not font_path:
QMessageBox.critical(self, "错误", "字体资源不足,请添加更多字体文件")
break
# 生成签名图片
img_path = self.create_signature_image(name, font_path)
if img_path:
self.generated_images.append((name, img_path))
self.add_preview_item(name, img_path)
self.download_btn.setEnabled(bool(self.generated_images))
def create_signature_image(self, text, font_path, img_size=(800, 300)):
"""生成高清签名图片(修复重叠问题)"""
try:
# 创建透明画布(扩大尺寸防裁剪)
img = Image.new('RGBA', img_size, (255, 255, 255, 0))
draw = ImageDraw.Draw(img)
# 动态计算字体大小
font_size = self.calculate_font_size(len(text))
font = ImageFont.truetype(font_path, font_size)
# 计算文本位置(居中)
text_width = font.getlength(text)
text_height = font.size
x = (img_size[0] - text_width) / 2
y = (img_size[1] - text_height) / 2
# 添加自然手写效果(单次绘制+后期处理)
draw.text((x, y), text, font=font, fill=(0, 0, 0, 220))
# 模拟手写抖动(高斯模糊替代多次绘制)
img = img.filter(ImageFilter.GaussianBlur(radius=0.8))
# 随机旋转(中心旋转防裁剪)
angle = random.uniform(-10, 10)
rotated_img = img.rotate(
angle,
center=(img_size[0]//2, img_size[1]//2),
expand=True,
resample=Image.BICUBIC
)
# 裁剪透明边缘
bbox = rotated_img.getbbox()
cropped_img = rotated_img.crop(bbox) if bbox else rotated_img
# 保存高清PNG
os.makedirs("output", exist_ok=True)
output_path = os.path.join("output", f"sign_{text}_{random.randint(1000,9999)}.png")
cropped_img.save(output_path, dpi=(300, 300), quality=100)
return output_path
except Exception as e:
print(f"生成失败: {str(e)}")
return None
def select_unique_font(self, used_fonts):
"""选择未使用的字体"""
available_fonts = [f for f in self.font_files if f not in used_fonts]
if not available_fonts:
available_fonts = self.font_files # 字体不足时复用
return random.choice(available_fonts) if available_fonts else None
def calculate_font_size(self, name_length):
"""根据姓名长度动态计算字体大小"""
base_size = 80
size_step = -10
return max(50, base_size + (name_length - 2) * size_step)
def clear_preview(self):
"""清空预览区域"""
for i in reversed(range(self.preview_layout.count())):
self.preview_layout.itemAt(i).widget().deleteLater()
def add_preview_item(self, name, img_path):
"""添加预览项"""
pixmap = QPixmap(img_path)
label = QLabel()
label.setPixmap(pixmap.scaled(300, 100, Qt.KeepAspectRatio))
name_label = QLabel(f"签名:{name}")
name_label.setStyleSheet("font-weight: bold; margin-top: 10px;")
self.preview_layout.addWidget(name_label)
self.preview_layout.addWidget(label)
def download_signatures(self):
"""下载所有生成的签名图片"""
if not hasattr(self, 'generated_images') or not self.generated_images:
QMessageBox.warning(self, "提示", "没有可下载的签名")
return
download_dir = QFileDialog.getExistingDirectory(
self,
"选择保存目录",
options=QFileDialog.ShowDirsOnly
)
if not download_dir:
return
success_count = 0
for name, img_path in self.generated_images:
try:
target_path = os.path.join(download_dir, f"{name}_签名.png")
with open(img_path, 'rb') as src, open(target_path, 'wb') as dst:
dst.write(src.read())
success_count += 1
except Exception as e:
print(f"保存 {name} 的签名失败: {e}")
QMessageBox.information(
self,
"完成",
f"成功保存 {success_count}/{len(self.generated_images)} 个签名到:\n{download_dir}"
)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SignatureGenerator()
window.show()
sys.exit(app.exec_())
config.py
复制代码 隐藏代码
# 图像生成设置
IMAGE_CONFIG = {
"default_size": (800, 300), # 默认画布尺寸
"dpi": 300, # 输出分辨率
"blur_radius": 0.8, # 手写效果模糊度
"max_rotate_angle": 10, # 最大旋转角度
"opacity": 220, # 文字透明度(0-255)
"min_font_size": 50, # 最小字体大小
"base_font_size": 80 # 基准字体大小
}
# 字体设置
FONT_CONFIG = {
"supported_length": [2, 3, 4] # 支持的姓名长度
}
requirements.txt
复制代码 隐藏代码
PyQt5>=5.15.7 # 测试支持 Python 3.13 的版本
numpy>=2.0.0 # 明确使用 NumPy 2.x
requests==2.31.0
很多人要成品,我不知道发成品是否属违规,既然有需求,我发出来。
下载:https://alinwei.lanzouu.com/iXVjE30q7w6f 密码:8zu1
2025.7.14更新优化
前端界面增加字体大小滑块,增加字符间距滑块、增加噪点程度滑块、增加凌乱度滑块。
核心源码文件
app.py
复制代码 隐藏代码
import os
import random
import sys
from datetime import datetime
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import Qt, QSize, QPoint, QRect
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import numpy as np
import math
import io
class SignatureGenerator(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("智能随机签名生成器优化增强版 v2.4")
self.setGeometry(300, 200, 1200, 800)
app_font = QFont("Microsoft YaHei", 14)
QApplication.setFont(app_font)
self.setStyleSheet("""
QMainWindow { background: #f0f2f5; font-family: 'Microsoft YaHei'; }
QPushButton { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #5e87db, stop:1 #3a66cc); color: white; border-radius: 6px; padding: 12px 24px; font-size: 14px; min-width: 140px; font-weight: bold; border: none; }
QPushButton:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #6b93e1, stop:1 #4470d2); }
QPushButton:pressed { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3a66cc, stop:1 #5e87db); }
QPushButton:disabled { background: #d0d0d0; color: #a0a0a0; }
QTextEdit { border: 1px solid #c0cfed; border-radius: 8px; padding: 12px; font-size: 14px; background: white; selection-background-color: #c0cfed; }
QTextEdit:focus { border: 2px solid #5e87db; }
QGroupBox { font-weight: bold; font-size: 15px; padding-top: 16px; border: 2px solid #c0cfed; border-radius: 10px; margin-top: 15px; color: #2e4374; background: white; }
QGroupBox::title { subcontrol-origin: margin; left: 15px; padding: 0 10px 0 10px; background: white; }
QLabel { font-size: 14px; color: #333333; padding: 3px; }
QScrollArea { border: 1px solid #c0cfed; border-radius: 10px; background: white; }
QSlider { height: 30px; }
QSlider::groove:horizontal { border: 1px solid #c0cfed; height: 8px; background: #e6ecf7; margin: 2px 0; border-radius: 4px; }
QSlider::handle:horizontal { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #5e87db, stop:1 #3a66cc); border: 1px solid #5e87db; width: 18px; margin: -8px 0; border-radius: 9px; }
QSlider::handle:horizontal:hover { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #6b93e1, stop:1 #4470d2); }
QSlider::add-page:horizontal { background: #e6ecf7; border: 1px solid #c0cfed; border-radius: 4px; }
QSlider::sub-page:horizontal { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #add5fc, stop:1 #5e87db); border: 1px solid #c0cfed; border-radius: 4px; }
""")
self.init_ui()
self.load_fonts()
self.generated_signatures = []
def init_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QHBoxLayout(central_widget)
layout.setSpacing(25)
layout.setContentsMargins(20, 20, 20, 20)
control_panel = QVBoxLayout()
control_panel.setSpacing(25)
title_label = QLabel("智能签名生成器")
title_label.setStyleSheet("""
font-size: 22px;
font-weight: bold;
color: #2e4374;
padding: 10px 0;
""")
title_label.setAlignment(Qt.AlignCenter)
desc_label = QLabel("生成专业、自然的手写签名效果")
desc_label.setStyleSheet("font-size: 14px; color: #666666; padding-bottom: 10px;")
desc_label.setAlignment(Qt.AlignCenter)
input_group = QGroupBox("批量输入设置")
input_layout = QVBoxLayout()
input_layout.setContentsMargins(15, 25, 15, 15)
input_layout.setSpacing(12)
name_label = QLabel("待生成姓名列表:")
name_label.setStyleSheet("font-weight: bold;")
self.input_field = QTextEdit()
self.input_field.setPlaceholderText("请输入姓名,用逗号分隔(例:张三,李四,王五)")
self.input_field.setMaximumHeight(100)
input_layout.addWidget(name_label)
input_layout.addWidget(self.input_field)
input_group.setLayout(input_layout)
style_group = QGroupBox("样式调整设置")
style_layout = QVBoxLayout()
style_layout.setContentsMargins(15, 25, 15, 15)
style_layout.setSpacing(15)
font_size_layout = QVBoxLayout()
font_size_label = QLabel("字体大小:")
font_size_label.setStyleSheet("font-weight: bold;")
font_size_slider_layout = QHBoxLayout()
self.font_size_slider = QSlider(Qt.Horizontal)
self.font_size_slider.setRange(30, 150)
self.font_size_slider.setValue(80)
self.font_size_slider.valueChanged.connect(self.update_font_size_label)
self.font_size_label = QLabel("80")
self.font_size_label.setFixedWidth(35)
self.font_size_label.setAlignment(Qt.AlignCenter)
self.font_size_label.setStyleSheet("""
background: #5e87db;
color: white;
border-radius: 4px;
padding: 4px;
font-weight: bold;
""")
font_size_slider_layout.addWidget(self.font_size_slider)
font_size_slider_layout.addWidget(self.font_size_label)
font_size_layout.addWidget(font_size_label)
font_size_layout.addLayout(font_size_slider_layout)
spacing_layout = QVBoxLayout()
spacing_label = QLabel("字符间距:")
spacing_label.setStyleSheet("font-weight: bold;")
spacing_slider_layout = QHBoxLayout()
self.spacing_slider = QSlider(Qt.Horizontal)
self.spacing_slider.setRange(-20, 50)
self.spacing_slider.setValue(0)
self.spacing_slider.valueChanged.connect(self.update_spacing_label)
self.spacing_label = QLabel("0")
self.spacing_label.setFixedWidth(35)
self.spacing_label.setAlignment(Qt.AlignCenter)
self.spacing_label.setStyleSheet("""
background: #5e87db;
color: white;
border-radius: 4px;
padding: 4px;
font-weight: bold;
""")
spacing_slider_layout.addWidget(self.spacing_slider)
spacing_slider_layout.addWidget(self.spacing_label)
spacing_layout.addWidget(spacing_label)
spacing_layout.addLayout(spacing_slider_layout)
noise_layout = QVBoxLayout()
noise_label = QLabel("噪点程度:")
noise_label.setStyleSheet("font-weight: bold;")
noise_slider_layout = QHBoxLayout()
self.noise_slider = QSlider(Qt.Horizontal)
self.noise_slider.setRange(0, 100)
self.noise_slider.setValue(20)
self.noise_slider.valueChanged.connect(self.update_noise_label)
self.noise_label = QLabel("20")
self.noise_label.setFixedWidth(35)
self.noise_label.setAlignment(Qt.AlignCenter)
self.noise_label.setStyleSheet("""
background: #5e87db;
color: white;
border-radius: 4px;
padding: 4px;
font-weight: bold;
""")
noise_slider_layout.addWidget(self.noise_slider)
noise_slider_layout.addWidget(self.noise_label)
noise_layout.addWidget(noise_label)
noise_layout.addLayout(noise_slider_layout)
mess_layout = QVBoxLayout()
mess_label = QLabel("凌乱程度:")
mess_label.setStyleSheet("font-weight: bold;")
mess_slider_layout = QHBoxLayout()
self.mess_slider = QSlider(Qt.Horizontal)
self.mess_slider.setRange(0, 100)
self.mess_slider.setValue(50)
self.mess_slider.valueChanged.connect(self.update_mess_label)
self.mess_label = QLabel("50")
self.mess_label.setFixedWidth(35)
self.mess_label.setAlignment(Qt.AlignCenter)
self.mess_label.setStyleSheet("""
background: #5e87db;
color: white;
border-radius: 4px;
padding: 4px;
font-weight: bold;
""")
mess_slider_layout.addWidget(self.mess_slider)
mess_slider_layout.addWidget(self.mess_label)
mess_layout.addWidget(mess_label)
mess_layout.addLayout(mess_slider_layout)
style_layout.addLayout(font_size_layout)
style_layout.addLayout(spacing_layout)
style_layout.addLayout(noise_layout)
style_layout.addLayout(mess_layout)
reset_btn = QPushButton("🔄 重置默认设置")
reset_btn.clicked.connect(self.reset_style_settings)
style_layout.addWidget(reset_btn)
style_group.setLayout(style_layout)
button_layout = QHBoxLayout()
button_layout.setSpacing(15)
self.generate_btn = QPushButton("🎨 随机生成签名")
self.generate_btn.clicked.connect(self.generate_signatures)
self.download_btn = QPushButton("💾 下载全部签名")
self.download_btn.setEnabled(False)
self.download_btn.clicked.connect(self.download_signatures)
button_layout.addWidget(self.generate_btn)
button_layout.addWidget(self.download_btn)
preview_title = QLabel("签名预览区")
preview_title.setStyleSheet("""
font-size: 16px;
font-weight: bold;
color: #2e4374;
padding: 10px;
background: white;
border-radius: 8px;
border: 1px solid #c0cfed;
margin-bottom: 10px;
""")
preview_title.setAlignment(Qt.AlignCenter)
control_panel.addWidget(title_label)
control_panel.addWidget(desc_label)
control_panel.addWidget(input_group)
control_panel.addWidget(style_group)
control_panel.addLayout(button_layout)
control_panel.addStretch(1)
preview_panel = QVBoxLayout()
preview_panel.addWidget(preview_title)
self.preview_scroll = QScrollArea()
self.preview_scroll.setWidgetResizable(True)
self.preview_content = QWidget()
self.preview_layout = QVBoxLayout(self.preview_content)
self.preview_layout.setAlignment(Qt.AlignTop)
self.preview_layout.setSpacing(20)
self.preview_layout.setContentsMargins(20, 20, 20, 20)
self.preview_scroll.setWidget(self.preview_content)
preview_panel.addWidget(self.preview_scroll)
layout.addLayout(control_panel, 1)
layout.addLayout(preview_panel, 2)
def load_fonts(self):
self.fonts = []
fonts_dir = "./fonts/"
if not os.path.exists(fonts_dir):
os.makedirs(fonts_dir)
print("创建了fonts目录,请添加字体文件")
QMessageBox.warning(self, "字体缺失", "字体目录为空,请在fonts目录添加.ttf或.otf字体文件")
return
for file in os.listdir(fonts_dir):
if file.lower().endswith(('.ttf', '.otf')):
font_path = os.path.join(fonts_dir, file)
try:
ImageFont.truetype(font_path, 40)
self.fonts.append(font_path)
except Exception as e:
print(f"加载字体 {font_path} 失败: {e}")
if not self.fonts:
QMessageBox.warning(self, "字体缺失", "没有找到可用字体,请在fonts目录添加.ttf或.otf字体文件")
def update_font_size_label(self, value):
self.font_size_label.setText(str(value))
def update_spacing_label(self, value):
self.spacing_label.setText(str(value))
def update_noise_label(self, value):
self.noise_label.setText(str(value))
def update_mess_label(self, value):
self.mess_label.setText(str(value))
def reset_style_settings(self):
self.font_size_slider.setValue(80)
self.spacing_slider.setValue(0)
self.noise_slider.setValue(20)
self.mess_slider.setValue(50)
def generate_signatures(self):
if not self.fonts:
QMessageBox.warning(self, "字体缺失", "没有找到可用字体,请在fonts目录添加.ttf或.otf字体文件")
return
for i in reversed(range(self.preview_layout.count())):
self.preview_layout.itemAt(i).widget().deleteLater()
self.generated_signatures = []
input_text = self.input_field.toPlainText().strip()
if not input_text:
QMessageBox.warning(self, "输入缺失", "请输入至少一个姓名")
return
names = [name.strip() for name in input_text.split(',') if name.strip()]
if not names:
QMessageBox.warning(self, "输入缺失", "请输入有效的姓名列表,用逗号分隔")
return
font_size = self.font_size_slider.value()
spacing = self.spacing_slider.value()
noise_level = self.noise_slider.value() / 100.0
mess_level = self.mess_slider.value() / 100.0
for name in names:
font_path = random.choice(self.fonts)
try:
signature = self.create_signature(name, font_path, font_size, spacing, noise_level, mess_level)
if signature:
self.generated_signatures.append((name, signature))
preview_item = self.create_preview_item(name, signature)
self.preview_layout.addWidget(preview_item)
except Exception as e:
print(f"生成签名失败: {e}")
if self.generated_signatures:
self.download_btn.setEnabled(True)
else:
QMessageBox.warning(self, "生成失败", "没有成功生成签名,请重试")
def create_signature(self, name, font_path, font_size, spacing, noise_level, mess_level):
try:
font = ImageFont.truetype(font_path, font_size)
text_width, text_height = self.get_text_dimensions(name, font, spacing)
padding = 50
img_width = text_width + padding * 2
img_height = text_height + padding * 2
image = Image.new('RGBA', (img_width, img_height), (255, 255, 255, 0))
draw = ImageDraw.Draw(image)
x = padding
y = padding
if mess_level > 0:
x += random.randint(-int(10 * mess_level), int(10 * mess_level))
y += random.randint(-int(10 * mess_level), int(10 * mess_level))
rotation = random.uniform(-5 * mess_level, 5 * mess_level)
image = image.rotate(rotation, resample=Image.BICUBIC, expand=False, fillcolor=(255, 255, 255, 0))
draw = ImageDraw.Draw(image)
for char in name:
char_x = x
char_y = y
if mess_level > 0:
char_x += random.randint(-int(5 * mess_level), int(5 * mess_level))
char_y += random.randint(-int(5 * mess_level), int(5 * mess_level))
draw.text((char_x, char_y), char, fill=(0, 0, 0, 255), font=font)
bbox = font.getbbox(char)
char_width = bbox[2] - bbox[0]
x += char_width + spacing
if noise_level > 0:
image = self.apply_noise(image, noise_level)
return image
except Exception as e:
print(f"创建签名失败: {e}")
return None
def get_text_dimensions(self, text, font, spacing):
total_width = 0
max_height = 0
for char in text:
bbox = font.getbbox(char)
width = bbox[2] - bbox[0]
height = bbox[3] - bbox[1]
total_width += width + spacing
max_height = max(max_height, height)
if text:
total_width -= spacing
return total_width, max_height
def apply_noise(self, image, level):
noisy_image = image.copy()
width, height = image.size
pixels = noisy_image.load()
for y in range(height):
for x in range(width):
r, g, b, a = pixels[x, y]
if a > 0:
if random.random() < level * 0.05:
noise = random.randint(-30, 30)
r = max(0, min(255, r + noise))
g = max(0, min(255, g + noise))
b = max(0, min(255, b + noise))
pixels[x, y] = (r, g, b, a)
if random.random() < level * 0.01:
pixels[x, y] = (r, g, b, max(0, a - random.randint(50, 200)))
if level > 0.5:
noisy_image = noisy_image.filter(ImageFilter.GaussianBlur(radius=0.3))
return noisy_image
def create_preview_item(self, name, signature):
container = QWidget()
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
container.setStyleSheet("""
QWidget {
background-color: white;
border: 1px solid #c0cfed;
border-radius: 10px;
}
""")
preview_label = QLabel()
preview_label.setAlignment(Qt.AlignCenter)
preview_label.setStyleSheet("border: none; padding: 10px;")
img_byte_arr = io.BytesIO()
signature.save(img_byte_arr, format='PNG')
img_byte_arr.seek(0)
img_data = img_byte_arr.read()
pixmap = QPixmap()
pixmap.loadFromData(img_data)
preview_label.setPixmap(pixmap)
name_label = QLabel(f"姓名: {name}")
name_label.setStyleSheet("""
font-size: 14px;
font-weight: bold;
color: #2e4374;
padding: 10px;
border-top: 1px solid #c0cfed;
""")
name_label.setAlignment(Qt.AlignCenter)
layout.addWidget(preview_label)
layout.addWidget(name_label)
return container
def download_signatures(self):
if not self.generated_signatures:
return
save_dir = QFileDialog.getExistingDirectory(self, "选择保存目录")
if not save_dir:
return
date_str = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = os.path.join(save_dir, f"签名_{date_str}")
try:
if not os.path.exists(save_path):
os.makedirs(save_path)
for i, (name, signature) in enumerate(self.generated_signatures):
file_name = f"{name}_{i+1}.png"
file_path = os.path.join(save_path, file_name)
signature.save(file_path, "PNG")
QMessageBox.information(self, "下载成功", f"所有签名已保存至: {save_path}")
except Exception as e:
QMessageBox.critical(self, "保存失败", f"保存签名时出错: {str(e)}")
if __name__ == "__main__":
fonts_dir = os.path.abspath("fonts")
if not os.path.exists(fonts_dir):
os.makedirs(fonts_dir)
app = QApplication(sys.argv)
QMessageBox.information(None, "首次使用提示", "已自动创建 fonts 文件夹,请将手写体字体(.ttf/.otf)文件放入该文件夹后重新启动程序。")
sys.exit(0)
font_files = [f for f in os.listdir(fonts_dir) if f.lower().endswith(('.ttf', '.otf'))]
if not font_files:
app = QApplication(sys.argv)
QMessageBox.warning(None, "字体缺失", "fonts 文件夹为空,请将手写体字体(.ttf/.otf)文件放入该文件夹后重新启动程序。")
sys.exit(0)
app = QApplication(sys.argv)
window = SignatureGenerator()
window.show()
sys.exit(app.exec_())
扫码免费获取资源:
