Untitled
Anonymous
plain_text
03/10/2026 6:35 PM
29.0 KB
9
Indexable
import os
import sys
import json
import random
import threading
from datetime import datetime
import tkinter as tk
from tkinter import filedialog, messagebox
import customtkinter as ctk
# Библиотеки генерации
import qrcode
import qrcode.image.svg
import barcode
from barcode.writer import SVGWriter
from pdfrw import PdfReader, PdfWriter, IndirectPdfDict
from pdfrw.buildxobj import pagexobj
from pdfrw.toreportlab import makerl
from reportlab.graphics import renderPDF
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen.canvas import Canvas
from svglib.svglib import svg2rlg
# --- Настройки внешнего вида ---
ctk.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light"
ctk.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue"
class App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Генератор Бланков ЕГЭ 2026")
self.geometry("900x750")
# Переменные для путей
self.template_path = "template.pdf"
self.font_main = "ttcommons.ttf"
self.font_mono = "monospaced.ttf"
self.output_filename = "result"
# Настройка сетки
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
# --- Левая панель (Сайдбар) ---
self.sidebar_frame = ctk.CTkFrame(self, width=200, corner_radius=0)
self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsew")
self.sidebar_frame.grid_rowconfigure(4, weight=1)
self.logo_label = ctk.CTkLabel(self.sidebar_frame, text="EGE GEN\nPRO", font=ctk.CTkFont(size=20, weight="bold"))
self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10))
self.sidebar_button_load = ctk.CTkButton(self.sidebar_frame, text="Выбрать шаблон PDF", command=self.select_template)
self.sidebar_button_load.grid(row=1, column=0, padx=20, pady=10)
self.appearance_mode_label = ctk.CTkLabel(self.sidebar_frame, text="Тема оформления:", anchor="w")
self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0))
self.appearance_mode_optionemenu = ctk.CTkOptionMenu(self.sidebar_frame, values=["System", "Light", "Dark"],
command=self.change_appearance_mode_event)
self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 20))
# --- Основная область ---
self.main_frame = ctk.CTkScrollableFrame(self, label_text="Параметры генерации")
self.main_frame.grid(row=0, column=1, padx=20, pady=20, sticky="nsew")
# Группа: Данные экзамена
self.create_input_group("Данные экзамена", 0)
self.entry_title = self.add_entry("Заголовок бланка", "ЕДИНЫЙ ГОСУДАРСТВЕННЫЙ ЭКЗАМЕН")
self.entry_region = self.add_entry("Регион (код)", "77")
self.entry_code_oo = self.add_entry("Код ОО (Школы)", "14049")
self.entry_grade = self.add_entry("Класс", "11А")
# Группа: Предмет
self.create_input_group("Предмет", 5)
self.entry_subj_code = self.add_entry("Код предмета", "01")
self.entry_subj_name = self.add_entry("Название предмета (сокр.)", "мат")
# Группа: Настройки вывода
self.create_input_group("Настройки генерации", 10)
self.entry_additional_count = self.add_entry("Кол-во доп. бланков", "0")
self.entry_filename = self.add_entry("Имя выходного файла", "result")
# Лог и Кнопка
self.generate_btn = ctk.CTkButton(self.main_frame, text="ГЕНЕРИРОВАТЬ", height=50,
font=ctk.CTkFont(size=16, weight="bold"),
fg_color="green", hover_color="darkgreen",
command=self.start_generation)
self.generate_btn.grid(row=15, column=0, columnspan=2, padx=20, pady=30, sticky="ew")
self.progressbar = ctk.CTkProgressBar(self.main_frame)
self.progressbar.grid(row=16, column=0, columnspan=2, padx=20, pady=10, sticky="ew")
self.progressbar.set(0)
self.log_box = ctk.CTkTextbox(self.main_frame, height=100)
self.log_box.grid(row=17, column=0, columnspan=2, padx=20, pady=10, sticky="ew")
self.log_box.insert("0.0", "Готов к работе...\n")
# Загрузка конфига
self.load_config()
self.current_row = 0 # Вспомогательная
def create_input_group(self, text, start_row):
lbl = ctk.CTkLabel(self.main_frame, text=text, font=ctk.CTkFont(size=14, weight="bold"))
lbl.grid(row=start_row, column=0, sticky="w", padx=10, pady=(15, 5))
self.current_row = start_row + 1
def add_entry(self, label_text, default_val):
lbl = ctk.CTkLabel(self.main_frame, text=label_text)
lbl.grid(row=self.current_row, column=0, sticky="w", padx=20, pady=2)
entry = ctk.CTkEntry(self.main_frame, width=300)
entry.grid(row=self.current_row, column=1, sticky="e", padx=20, pady=2)
entry.insert(0, default_val)
self.current_row += 1
return entry
def change_appearance_mode_event(self, new_appearance_mode: str):
ctk.set_appearance_mode(new_appearance_mode)
def select_template(self):
filename = filedialog.askopenfilename(filetypes=[("PDF Files", "*.pdf")])
if filename:
self.template_path = filename
self.log_message(f"Выбран шаблон: {os.path.basename(filename)}")
def log_message(self, message):
self.log_box.insert("end", message + "\n")
self.log_box.see("end")
def save_config(self):
config = {
"region": self.entry_region.get(),
"code_oo": self.entry_code_oo.get(),
"grade": self.entry_grade.get(),
"subj_code": self.entry_subj_code.get(),
"subj_name": self.entry_subj_name.get(),
"title": self.entry_title.get()
}
try:
with open("config.json", "w", encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=4)
except Exception as e:
print(f"Не удалось сохранить конфиг: {e}")
def load_config(self):
if os.path.exists("config.json"):
try:
with open("config.json", "r", encoding='utf-8') as f:
config = json.load(f)
self.entry_region.delete(0, "end"); self.entry_region.insert(0, config.get("region", ""))
self.entry_code_oo.delete(0, "end"); self.entry_code_oo.insert(0, config.get("code_oo", ""))
self.entry_grade.delete(0, "end"); self.entry_grade.insert(0, config.get("grade", ""))
self.entry_subj_code.delete(0, "end"); self.entry_subj_code.insert(0, config.get("subj_code", ""))
self.entry_subj_name.delete(0, "end"); self.entry_subj_name.insert(0, config.get("subj_name", ""))
self.entry_title.delete(0, "end"); self.entry_title.insert(0, config.get("title", ""))
except:
pass
def start_generation(self):
# Валидация
if not os.path.exists(self.template_path):
messagebox.showerror("Ошибка", f"Файл шаблона не найден: {self.template_path}")
return
if not os.path.exists(self.font_main) or not os.path.exists(self.font_mono):
messagebox.showerror("Ошибка", "Не найдены файлы шрифтов (ttcommons.ttf, monospaced.ttf)")
return
self.save_config()
self.generate_btn.configure(state="disabled", text="ГЕНЕРАЦИЯ...")
self.progressbar.set(0)
self.log_box.delete("0.0", "end")
# Запуск в отдельном потоке
threading.Thread(target=self.process_pdf, daemon=True).start()
# --- ЛОГИКА ГЕНЕРАЦИИ (Адаптированная) ---
def process_pdf(self):
try:
data = {
"region": self.entry_region.get(),
"code_oo": self.entry_code_oo.get(),
"grade": self.entry_grade.get(),
"title": self.entry_title.get(),
"subj_code": self.entry_subj_code.get(),
"subj_name": self.entry_subj_name.get(),
"blank_footer": "При нехватке места используйте",
"blank_footer_2": "на оборотной стороне листа",
"blank_register": "Бланк регистрации",
"blank_answ1": "Бланк ответов №1",
"blank_answ2": "Бланк ответов №2",
"blank_answ2_sub1": "Лист 1",
"blank_answ2_sub2": "Лист 2",
"blank_answ3": "Дополнительный бланк ответов №2",
"qrcode": "БЛАНКИ ДЛЯ ПРОБНЫХ ЭКЗАМЕНОВ ЕДИНОГО ГОСУДАРСТВЕННОГО ЭКЗАМЕНА 2026"
}
try:
additional_forms = int(self.entry_additional_count.get())
except ValueError:
additional_forms = 0
outfile_name = self.entry_filename.get()
base_height = 297 * mm
base_char_space = 5
self.log_message("Регистрация шрифтов...")
pdfmetrics.registerFont(TTFont('GlobalFont', self.font_main))
pdfmetrics.registerFont(TTFont('GlobalMonoFont', self.font_mono))
# QR Code
self.log_message("Создание QR-кода...")
qr = qrcode.QRCode(box_size=18, border=0, error_correction=qrcode.constants.ERROR_CORRECT_Q)
qr.add_data(data["qrcode"])
qr.make_image(image_factory=qrcode.image.svg.SvgPathImage).save("qr.svg")
# Main Barcode
main_barcode_val = str(int(datetime.now().timestamp() * 1000) + random.randint(1000, 9999))
self.create_barcode_svg(main_barcode_val, "barcode_main")
self.log_message(f"Основной штрих-код: {main_barcode_val}")
# Additional Barcodes
additional_barcodes = []
for i in range(additional_forms):
val = str(int(datetime.now().timestamp() * 1000) + random.randint(1000, 9999))
fname = f"barcode_additional_{i + 1}"
self.create_barcode_svg(val, fname)
additional_barcodes.append((fname, val))
# PDF Logic
pages = PdfReader(self.template_path, decompress=False).pages
pdfWriter = PdfWriter()
total_pages = min(4, len(pages)) + additional_forms
current_progress = 0
# Internal helper to draw page
def draw_page_content(c, page_idx, barcode_file, is_additional=False):
# Загрузка и обработка баркода
barcode_svg = svg2rlg(barcode_file)
if barcode_svg.height != 14 * mm:
coef = (14 * mm) / barcode_svg.height
barcode_svg.height = 14 * mm
barcode_svg.width *= coef
barcode_svg.scale(coef, coef)
# QR
qrcode_svg = svg2rlg("qr.svg")
if qrcode_svg.height != 28.5 * mm:
coef = (28.5 * mm) / qrcode_svg.height
qrcode_svg.height = 28.5 * mm
qrcode_svg.width *= coef
qrcode_svg.scale(coef, coef)
c.setFont("GlobalFont", 20)
# Заголовок
c.drawString(
54 * mm + ((173 - 54) * mm - stringWidth(data["title"], "GlobalFont", 20)) / 2.0,
base_height - 10.75 * mm,
data["title"]
)
# Горизонтальный баркод
renderPDF.draw(barcode_svg, c, 20 * mm + ((70.5 - 20) * mm - barcode_svg.width) / 2.0, base_height - 57 * mm)
# Вертикальный баркод
barcode_vertical = svg2rlg(barcode_file)
if barcode_vertical.height != 13 * mm:
coef = (13 * mm) / barcode_vertical.height
barcode_vertical.height = 13 * mm
barcode_vertical.width *= coef
barcode_vertical.scale(coef, coef)
barcode_vertical.rotate(90)
renderPDF.draw(barcode_vertical, c, 203.25 * mm, base_height - 57 * mm + ((57 - 9) * mm - barcode_vertical.width) / 2.0)
# QR
renderPDF.draw(qrcode_svg, c, 8.5 * mm, base_height - 38 * mm)
# Контент страниц (упрощенная копия логики из оригинала для краткости, но функциональная)
if not is_additional:
if page_idx == 0:
c.drawString(54 * mm + ((173 - 54) * mm - stringWidth(data["blank_register"], "GlobalFont", 20)) / 2.0, base_height - 19.5 * mm, data["blank_register"])
c.setFont("GlobalMonoFont", 20)
c.drawString(46.5 * mm, base_height - 35.5 * mm, data["region"], charSpace=base_char_space)
c.drawString(64.5 * mm, base_height - 35.5 * mm, data["code_oo"], charSpace=base_char_space)
c.drawString(105.5 * mm, base_height - 35.5 * mm, data["grade"], charSpace=base_char_space)
c.drawString(96.2 * mm, base_height - 54.5 * mm, data["subj_name"], charSpace=base_char_space)
c.drawString(80 * mm, base_height - 54.5 * mm, data["subj_code"], charSpace=base_char_space)
elif page_idx == 1:
c.drawString(52 * mm + ((173 - 54) * mm - stringWidth(data["blank_answ1"], "GlobalFont", 20)) / 2.0, base_height - 18.5 * mm, data["blank_answ1"])
c.setFont("GlobalMonoFont", 20)
c.drawString(49.5 * mm, base_height - 31.5 * mm, data["region"], charSpace=base_char_space)
c.drawString(66.3475 * mm, base_height - 31.5 * mm, data["subj_code"], charSpace=base_char_space)
c.drawString(83.75 * mm, base_height - 31.5 * mm, data["subj_name"], charSpace=base_char_space)
elif page_idx == 2:
c.drawString(53 * mm + ((129 - 53) * mm - stringWidth(data["blank_answ2"], "GlobalFont", 20)) / 2.0, base_height - 18.5 * mm, data["blank_answ2"])
c.drawString(133 * mm, base_height - 18.5 * mm, data["blank_answ2_sub1"])
c.setFont("GlobalMonoFont", 20)
c.drawString(55.85 * mm, base_height - 30 * mm, data["region"], charSpace=base_char_space)
c.drawString(79 * mm, base_height - 30 * mm, data["subj_code"], charSpace=base_char_space)
c.drawString(103.25 * mm, base_height - 30 * mm, data["subj_name"], charSpace=base_char_space)
c.drawString(168 * mm, base_height - 39.5 * mm, "1", charSpace=base_char_space)
c.setFont("GlobalFont", 10)
footer = (data["blank_footer"] + " " + data["blank_answ2"] + " (" + data["blank_answ2_sub2"] + ") " + data["blank_footer_2"])
c.drawString(15 * mm + ((177 - 15) * mm - stringWidth(footer, "GlobalFont", 10)) / 2.0, base_height - 289 * mm, footer)
elif page_idx == 3:
c.drawString(53 * mm + ((129 - 53) * mm - stringWidth(data["blank_answ2"], "GlobalFont", 20)) / 2.0, base_height - 18.5 * mm, data["blank_answ2"])
c.drawString(133 * mm, base_height - 18.5 * mm, data["blank_answ2_sub2"])
c.setFont("GlobalMonoFont", 20)
c.drawString(55.85 * mm, base_height - 30 * mm, data["region"], charSpace=base_char_space)
c.drawString(79 * mm, base_height - 30 * mm, data["subj_code"], charSpace=base_char_space)
c.drawString(103.25 * mm, base_height - 30 * mm, data["subj_name"], charSpace=base_char_space)
c.setFont("GlobalFont", 10)
footer = data["blank_footer"] + " " + data["blank_answ3"]
c.drawString(44 * mm + ((197 - 44) * mm - stringWidth(footer, "GlobalFont", 10)) / 2.0, base_height - 289 * mm, footer)
else:
# Доп бланк (похож на 4-ю страницу, но другие надписи)
c.drawString(52 * mm + ((173 - 54) * mm - stringWidth(data["blank_answ3"], "GlobalFont", 20)) / 2.0, base_height - 18 * mm, data["blank_answ3"])
c.setFont("GlobalMonoFont", 20)
c.drawString(55.85 * mm, base_height - 30 * mm, data["region"], charSpace=base_char_space)
c.drawString(79 * mm, base_height - 30 * mm, data["subj_code"], charSpace=base_char_space)
c.drawString(103.25 * mm, base_height - 30 * mm, data["subj_name"], charSpace=base_char_space)
c.setFont("GlobalFont", 10)
footer = data["blank_footer"] + " " + data["blank_answ3"] + " " + data["blank_footer_2"]
c.drawString(28 * mm + ((197 - 28) * mm - stringWidth(footer, "GlobalFont", 10)) / 2.0, base_height - 289 * mm, footer)
# --- Генерация основных страниц ---
for page_num in range(min(4, len(pages))):
self.log_message(f"Обработка страницы {page_num + 1}...")
template_obj = pagexobj(pages[page_num])
temp_filename = f"temp_{page_num}.pdf"
c = Canvas(temp_filename)
xobj_name = makerl(c, template_obj)
c.doForm(xobj_name)
draw_page_content(c, page_num, "barcode_main.svg", False)
c.save()
pdfWriter.addpages(PdfReader(temp_filename).pages)
os.remove(temp_filename)
current_progress += 1
self.progressbar.set(current_progress / total_pages)
# --- Генерация доп бланков ---
if len(pages) > 4:
template_extra = pages[4]
template_obj_extra = pagexobj(template_extra)
for i in range(additional_forms):
self.log_message(f"Обработка доп. бланка {i + 1}...")
barcode_fname, _ = additional_barcodes[i]
temp_filename = f"temp_extra_{i}.pdf"
c = Canvas(temp_filename)
xobj_name = makerl(c, template_obj_extra)
c.doForm(xobj_name)
draw_page_content(c, 4, f"{barcode_fname}.svg", True)
c.save()
pdfWriter.addpages(PdfReader(temp_filename).pages)
os.remove(temp_filename)
current_progress += 1
self.progressbar.set(current_progress / total_pages)
# Метаданные и сохранение
pdfWriter.trailer.Info = IndirectPdfDict(
Title=data["title"],
Creator="EGE Gen Pro"
)
final_name = f"{outfile_name}.pdf"
pdfWriter.write(final_name)
self.cleanup_files(additional_barcodes)
self.progressbar.set(1)
self.log_message(f"ГОТОВО! Файл сохранен: {final_name}")
self.generate_btn.configure(state="normal", text="ГЕНЕРИРОВАТЬ")
messagebox.showinfo("Успех", f"Файл успешно создан:\n{os.path.abspath(final_name)}")
except Exception as e:
self.log_message(f"КРИТИЧЕСКАЯ ОШИБКА: {str(e)}")
self.generate_btn.configure(state="normal", text="ГЕНЕРИРОВАТЬ")
messagebox.showerror("Ошибка", str(e))
def create_barcode_svg(self, val, filename):
code128 = barcode.get_barcode_class('code128')
inst = code128(val, writer=SVGWriter())
inst.save(filename, {
"module_width": 0.4, "module_height": 10.0, "font_size": 5.0,
"text_distance": 2.25, "quiet_zone": 0.0, "center_text": True
})
def cleanup_files(self, additional_barcodes):
files_to_remove = ["barcode_main.svg", "qr.svg"]
for fname, _ in additional_barcodes:
files_to_remove.append(f"{fname}.svg")
for f in files_to_remove:
if os.path.exists(f):
os.remove(f)
if __name__ == "__main__":
app = App()
app.mainloop()Editor is loading...
Leave a Comment