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