[PS CC2025] Печать на 200 стр

izrukvruki

Топикстартер
15 лет на форуме
Сообщения
2 004
Реакции
365
Есть бухгалтерский документ pdf с большим кол-вом страниц - 100-300 стр.
Нужно на каждую страницу в нижним левом углу вставить печать организации и печать "Копия верна".
Как это можно автоматизировать?
 
Quite Imposing
Indesign
Preps


Уж и не знаю, продолжать ли...
 
Последнее редактирование:
Штатными средствами Акробата
1779953252734.png
 
  • Спасибо
Реакции: George и zollinger
watermark в Acrobat, кажется, нужен именно для таких задач
 
Фотошоп тут причем, раздел перепутали?
 
Quite Imposing
 

Вложения

  • 2026-05-28 10-32-57 Stick on PDF pages.jpg
    2026-05-28 10-32-57 Stick on PDF pages.jpg
    133.5 КБ · Просм.: 26
И я, наверняка, ещё что-то забыл.
Так и есть! :D
Ну, тогда и так можно:

 
Последнее редактирование модератором:
  • Спасибо
Реакции: izrukvruki
Ну, можно и чуть менее универсально
Python:
import io
import os
import sys
import threading
import tkinter as tk
from tkinter import filedialog, messagebox, ttk

# Стабильные библиотеки для препресса
from pypdf import PdfReader, PdfWriter
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas


def get_resource_path(relative_path):
    """Определяет правильный путь к ресурсам при упаковке в .exe."""
    if hasattr(sys, "_MEIPASS"):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)


def create_stamp_pdf(img_path, x_mm, y_mm, w_mm, h_mm, page_width, page_height):
    """Создает в памяти PDF-страницу со штампом в режиме Multiply."""
    packet = io.BytesIO()
    can = canvas.Canvas(packet, pagesize=(page_width, page_height))

    can.saveState()
    can.setBlendMode("Multiply")

    # Пересчет координат (в ReportLab отсчет идет снизу вверх)
    y_points = page_height - (y_mm * mm) - (h_mm * mm)
    x_points = x_mm * mm

    can.drawImage(
        img_path,
        x_points,
        y_points,
        width=w_mm * mm,
        height=h_mm * mm,
        mask="auto",
    )

    can.restoreState()
    can.save()

    packet.seek(0)
    # ИСПРАВЛЕНО: Явно берем первую страницу [0] из созданного штампа
    return PdfReader(packet).pages[0]


def process_pdf_worker(
    pdf_path,
    img_path,
    x_mm,
    y_mm,
    w_mm,
    h_mm,
    progress_window,
    progress_bar,
    status_label,
):
    """Функция обработки, выполняемая в фоновом потоке."""
    try:
        reader = PdfReader(pdf_path)
        writer = PdfWriter()

        total_pages = len(reader.pages)

        # Конфигурируем прогресс-бар в отдельном окне
        progress_bar.config(maximum=total_pages)
        progress_window.update_idletasks()

        # ИСПРАВЛЕНО: Корректно берем размеры первой страницы через индекс [0]
        first_page = reader.pages[0]
        page_width = float(first_page.mediabox.width)
        page_height = float(first_page.mediabox.height)

        # Создаем прозрачный оверлей (теперь возвращает чистый объект страницы)
        stamp_page = create_stamp_pdf(
            img_path, x_mm, y_mm, w_mm, h_mm, page_width, page_height
        )

        # Пакетное наложение слоев
        for i in range(total_pages):
            page = reader.pages[i]
            # Метод merge_page просто соединяет два слоя. Текст оригинала не меняется.
            page.merge_page(stamp_page)
            writer.add_page(page)

            # Обновляем прогресс-бар и текст в отдельном окне
            progress_bar.config(value=i + 1)
            status_label.config(
                text=f"Обработано страниц: {i + 1} из {total_pages}"
            )
            progress_window.update_idletasks()

        # Формируем имя для сохранения
        dir_name, file_name = os.path.split(pdf_path)
        output_path = os.path.join(dir_name, "ready_" + file_name)

        with open(output_path, "wb") as f:
            writer.write(f)

        # Закрываем окно прогресса перед выводом финального сообщения
        progress_window.destroy()

        # Успешное завершение: показываем окно и закрываем всю программу
        messagebox.showinfo(
            "Успех", f"Файл успешно сохранен как:\n{output_path}"
        )
        root.quit()  # Полное закрытие программы

    except Exception as e:
        if progress_window.winfo_exists():
            progress_window.destroy()  # Закрываем окно прогресса при ошибке
        messagebox.showerror(
            "Ошибка обработки", f"Произошла ошибка при изменении PDF:\n{str(e)}"
        )
        root.deiconify()  # Возвращаем главное окно, если что-то пошло не так


def start_processing():
    """Собирает данные, открывает окно прогресса и запускает поток."""
    # 1. Выбор исходного PDF-файла
    pdf_path = filedialog.askopenfilename(
        title="Выберите исходный PDF-документ",
        filetypes=[("PDF файлы", "*.pdf")],
    )
    if not pdf_path:
        return

    # 2. Выбор изображения
    img_path = filedialog.askopenfilename(
        title="Выберите изображение для вставки",
        filetypes=[
            ("Изображения", "*.png *.jpg *.jpeg *.bmp"),
            ("Все файлы", "*.*"),
        ],
    )
    if not img_path:
        return

    # 3. Считывание координат
    try:
        x_mm = float(entry_x.get())
        y_mm = float(entry_y.get())
        w_mm = float(entry_w.get())
        h_mm = float(entry_h.get())
    except ValueError:
        messagebox.showerror(
            "Ошибка", "Пожалуйста, введите корректные числа в поля координат."
        )
        return

    # Скрываем главное окно настроек на время работы
    root.withdraw()

    # --- Создание ОТДЕЛЬНОГО окна для прогресс-бара ---
    progress_window = tk.Toplevel(root)
    progress_window.title("Обработка...")
    progress_window.geometry("320x100")
    progress_window.resizable(False, False)

    # Блокируем взаимодействие с другими окнами, пока это открыто
    progress_window.grab_set()

    # Чтобы пользователь случайно не закрыл окно прогресса крестиком и не сломал поток
    progress_window.protocol("WM_DELETE_WINDOW", lambda: None)

    status_label = tk.Label(
        progress_window, text="Подготовка к обработке...", font=("Arial", 10)
    )
    status_label.pack(pady=10)

    progress_bar = ttk.Progressbar(
        progress_window, orient="horizontal", mode="determinate", length=280
    )
    progress_bar.pack(padx=20, pady=5)

    # Запуск обработки в отдельном фоновом потоке
    threading.Thread(
        target=process_pdf_worker,
        args=(
            pdf_path,
            img_path,
            x_mm,
            y_mm,
            w_mm,
            h_mm,
            progress_window,
            progress_bar,
            status_label,
        ),
        daemon=True,
    ).start()


# --- Создание графического интерфейса ---
root = tk.Tk()
root.title("Вставка изображения в PDF (Multiply)")
root.geometry("380x280")
root.resizable(False, False)

frame = tk.LabelFrame(root, text=" Настройки положения (в мм) ")
frame.pack(padx=15, pady=15, fill="x", ipady=5)

# Поля ввода
tk.Label(frame, text="Отступ слева (X):").grid(
    row=0, column=0, sticky="w", pady=5, padx=10
)
entry_x = tk.Entry(frame, width=10)
entry_x.insert(0, "20")
entry_x.grid(row=0, column=1, pady=5)

tk.Label(frame, text="Отступ сверху (Y):").grid(
    row=1, column=0, sticky="w", pady=5, padx=10
)
entry_y = tk.Entry(frame, width=10)
entry_y.insert(0, "20")
entry_y.grid(row=1, column=1, pady=5)

tk.Label(frame, text="Ширина картинки:").grid(
    row=2, column=0, sticky="w", pady=5, padx=10
)
entry_w = tk.Entry(frame, width=10)
entry_w.insert(0, "50")
entry_w.grid(row=2, column=1, pady=5)

tk.Label(frame, text="Высота картинки:").grid(
    row=3, column=0, sticky="w", pady=5, padx=10
)
entry_h = tk.Entry(frame, width=10)
entry_h.insert(0, "50")
entry_h.grid(row=3, column=1, pady=5)

# Кнопка запуска процесса
btn_start = tk.Button(
    root,
    text="Выбрать файлы и запустить",
    command=start_processing,
    font=("Segoe UI", 10),
)
btn_start.pack(fill="x", padx=15, pady=5, ipady=5)

root.mainloop()

Ну, и теперь есть exe.
 
Последнее редактирование модератором:
  • Спасибо
Реакции: ~RA~
можно и штаны через голову надевать. Как забавно LLM меняют мир