diff --git a/app/tools/page_numbers.py b/app/tools/page_numbers.py index 8a9216c..c80cb5b 100644 --- a/app/tools/page_numbers.py +++ b/app/tools/page_numbers.py @@ -122,29 +122,35 @@ def _run(self): out_path = self._resolve_output_file(self.drop_out, pdf_path) if not out_path: return + fmt_template = _FORMATS[self.cmb_format.currentIndex()][1] + pos_code = _POSITIONS[self.cmb_position.currentIndex()][1] + font_size = self.spin_size.value() + start_page = self.spin_start_page.value() - 1 # 0-indexed + start_num = self.spin_start_number.value() + margin = max(18, font_size + 8) + txt = self.edit_pages.text().strip() + + # ── Phase 1 (main thread): scan for existing numbers and prompt. + # The scan reads only a thin band at the chosen edge of each + # target page, so it stays fast enough not to need a worker. + # The user-visible Yes/No/Cancel decision must run on the main + # thread anyway, and re-entering the worker for a second phase + # would add complexity without a perceived speedup. try: import fitz, re with self._open_fitz(pdf_path) as doc: total = doc.page_count - - txt = self.edit_pages.text().strip() targets = set(parse_pages(txt, total)) if txt else set(range(total)) - - fmt_template = _FORMATS[self.cmb_format.currentIndex()][1] - pos_code = _POSITIONS[self.cmb_position.currentIndex()][1] - font_size = self.spin_size.value() - start_page = self.spin_start_page.value() - 1 # 0-indexed - start_num = self.spin_start_number.value() - margin = max(18, font_size + 8) - - # ── detect existing page numbers in the chosen margin band ── band_h = max(50, font_size * 4) num_re = re.compile( r"^\s*(?:\d+\s*(?:/\s*\d+)?|" r"(?:page|página|pagina|seite|stránka)\s+\d+(?:\s+(?:of|de|sur|von|di|van)\s+\d+)?)\s*$", re.IGNORECASE, ) - existing = [] # list of (page_idx, [fitz.Rect]) + # Plain (x0, y0, x1, y1) tuples — no fitz.Rect objects + # leak past the `with` block; the worker reconstructs + # them after re-opening the doc. + existing: list = [] for i in range(total): if i not in targets or i < start_page: continue @@ -162,45 +168,68 @@ def _run(self): for span in line.get("spans", []): stxt = span.get("text", "").strip() if stxt and num_re.match(stxt): - hits.append(fitz.Rect(span["bbox"])) + hits.append(tuple(span["bbox"])) if hits: existing.append((i, hits)) - - replace = False - if existing: - ans = QMessageBox.question( - self, t("msg.warning"), - t("tool.page_numbers.existing_found", n=len(existing)), - QMessageBox.StandardButton.Yes - | QMessageBox.StandardButton.No - | QMessageBox.StandardButton.Cancel, - ) - if ans == QMessageBox.StandardButton.Cancel: - return - replace = (ans == QMessageBox.StandardButton.Yes) - + except Exception as e: + QMessageBox.critical(self, t("msg.error"), str(e)) + return + + # numbered_total = how many pages will actually receive a number + numbered_total = sum(1 for i in range(total) + if i in targets and i >= start_page) + if numbered_total == 0: + QMessageBox.warning(self, t("msg.warning"), + t("tool.page_numbers.no_targets")) + return + + replace = False + if existing: + ans = QMessageBox.question( + self, t("msg.warning"), + t("tool.page_numbers.existing_found", n=len(existing)), + QMessageBox.StandardButton.Yes + | QMessageBox.StandardButton.No + | QMessageBox.StandardButton.Cancel, + ) + if ans == QMessageBox.StandardButton.Cancel: + return + replace = (ans == QMessageBox.StandardButton.Yes) + + pwd = self._pdf_password + + # ── Phase 2 (worker thread): apply redactions + insert numbers. + # This is the slow part — apply_redactions rasterises the + # affected regions and insert_text touches every target page. + def do_work(worker): + import fitz + doc = fitz.open(pdf_path) + if doc.needs_pass and pwd: + doc.authenticate(pwd) + try: if replace: for pg_idx, rects in existing: + if worker.is_cancelled(): + return None pg = doc[pg_idx] - for r in rects: - pg.add_redact_annot(r, fill=(1, 1, 1)) + for bbox in rects: + pg.add_redact_annot(fitz.Rect(*bbox), fill=(1, 1, 1)) pg.apply_redactions() - # numbered_total = how many pages will actually be numbered - numbered_total = sum(1 for i in range(total) - if i in targets and i >= start_page) - counter = 0 for i in range(total): if i not in targets or i < start_page: continue + if worker.is_cancelled(): + return None counter += 1 n_display = start_num + counter - 1 - label = fmt_template.format(n=n_display, total=numbered_total + start_num - 1) + label = fmt_template.format( + n=n_display, total=numbered_total + start_num - 1) page = doc[i] rect = page.rect - # Estimate text width (rough: 0.5 * font_size per char for Helvetica) + # Estimate text width (rough: 0.5 * font_size per char) tw = len(label) * font_size * 0.5 if pos_code[0] == "t": y = margin @@ -214,14 +243,28 @@ def _run(self): x = rect.width - margin - tw page.insert_text(fitz.Point(x, y), label, - fontsize=font_size, fontname="helv", color=(0, 0, 0)) - + fontsize=font_size, fontname="helv", + color=(0, 0, 0)) + worker.progress.emit(counter, + t("progress.page_numbers.page", + current=counter, + total=numbered_total)) + + if worker.is_cancelled(): + return None doc.save(out_path, garbage=4, deflate=True) - self._status(f"✔ → {os.path.basename(out_path)}") - msg = t("tool.page_numbers.done", path=out_path) + finally: + doc.close() + return out_path + + def on_done(saved): + self._status(f"✔ → {os.path.basename(saved)}") + msg = t("tool.page_numbers.done", path=saved) if self._pipeline_active: - self._pipeline_success(msg, out_path) + self._pipeline_success(msg, saved) else: QMessageBox.information(self, t("msg.done"), msg) - except Exception as e: - QMessageBox.critical(self, t("msg.error"), str(e)) + + self._run_background(do_work, total=numbered_total, + label=t("progress.page_numbers.applying"), + on_done=on_done) diff --git a/app/translations.json b/app/translations.json index da1d3c9..de72a84 100644 --- a/app/translations.json +++ b/app/translations.json @@ -379,6 +379,8 @@ "progress.watermark.page": "Watermarking page {current} of {total}…", "progress.nup.placing": "Building N-up sheets…", "progress.nup.page": "Placing page {current} of {total}…", + "progress.page_numbers.applying": "Numbering pages…", + "progress.page_numbers.page": "Numbering page {current} of {total}…", "sidebar.collapse_expand": "Collapse / Expand", "btn.select_pdfs": "Select PDFs", "btn.select_folder": "Select folder", @@ -455,6 +457,7 @@ "tool.page_numbers.pos.bottom_center": "Bottom center", "tool.page_numbers.pos.bottom_right": "Bottom right", "tool.page_numbers.existing_found": "Found {n} page(s) with existing page numbers in the chosen margin.\n\nReplace them with the new numbers?\n\nYes = remove old and add new\nNo = keep old and add new on top", + "tool.page_numbers.no_targets": "No pages match the selected range.", "nav.nup": "N-up", "tool.nup.name": "N-up layout", "tool.nup.desc": "Combine multiple PDF pages onto a single sheet (2, 4, 6, 9 or 16 per page).", @@ -913,6 +916,8 @@ "progress.watermark.page": "A marcar página {current} de {total}…", "progress.nup.placing": "A construir folhas N-up…", "progress.nup.page": "A colocar página {current} de {total}…", + "progress.page_numbers.applying": "A numerar páginas…", + "progress.page_numbers.page": "A numerar página {current} de {total}…", "sidebar.collapse_expand": "Recolher / Expandir", "btn.select_pdfs": "Selecionar PDFs", "btn.select_folder": "Selecionar pasta", @@ -989,6 +994,7 @@ "tool.page_numbers.pos.bottom_center": "Rodapé centro", "tool.page_numbers.pos.bottom_right": "Rodapé direita", "tool.page_numbers.existing_found": "Foram encontradas {n} página(s) com numeração existente na margem escolhida.\n\nSubstituir pelos novos números?\n\nSim = remover os antigos e adicionar os novos\nNão = manter os antigos e adicionar os novos por cima", + "tool.page_numbers.no_targets": "Nenhuma página corresponde ao intervalo escolhido.", "nav.nup": "N-up", "tool.nup.name": "Múltiplas páginas por folha", "tool.nup.desc": "Junta várias páginas do PDF numa só folha (2, 4, 6, 9 ou 16 por folha).", @@ -1447,6 +1453,8 @@ "progress.watermark.page": "Marcando página {current} de {total}…", "progress.nup.placing": "Construyendo hojas N-up…", "progress.nup.page": "Colocando página {current} de {total}…", + "progress.page_numbers.applying": "Numerando páginas…", + "progress.page_numbers.page": "Numerando página {current} de {total}…", "sidebar.collapse_expand": "Contraer / Expandir", "btn.select_pdfs": "Seleccionar PDFs", "btn.select_folder": "Seleccionar carpeta", @@ -1523,6 +1531,7 @@ "tool.page_numbers.pos.bottom_center": "Abajo centro", "tool.page_numbers.pos.bottom_right": "Abajo derecha", "tool.page_numbers.existing_found": "Se encontraron {n} página(s) con números existentes en el margen elegido.\n\n¿Sustituir por los nuevos?\n\nSí = quitar los viejos y añadir los nuevos\nNo = mantener los viejos y añadir los nuevos encima", + "tool.page_numbers.no_targets": "Ninguna página coincide con el intervalo seleccionado.", "nav.nup": "N-up", "tool.nup.name": "Varias páginas por hoja", "tool.nup.desc": "Combina varias páginas del PDF en una sola hoja (2, 4, 6, 9 o 16 por hoja).", @@ -1981,6 +1990,8 @@ "progress.watermark.page": "Filigrane page {current} sur {total}…", "progress.nup.placing": "Construction des feuilles N-up…", "progress.nup.page": "Placement page {current} sur {total}…", + "progress.page_numbers.applying": "Numérotation des pages…", + "progress.page_numbers.page": "Numérotation page {current} sur {total}…", "sidebar.collapse_expand": "Réduire / Développer", "btn.select_pdfs": "Sélectionner des PDF", "btn.select_folder": "Sélectionner un dossier", @@ -2057,6 +2068,7 @@ "tool.page_numbers.pos.bottom_center": "En bas au centre", "tool.page_numbers.pos.bottom_right": "En bas à droite", "tool.page_numbers.existing_found": "{n} page(s) contiennent déjà des numéros dans la marge choisie.\n\nLes remplacer par les nouveaux ?\n\nOui = supprimer les anciens et ajouter les nouveaux\nNon = conserver les anciens et ajouter les nouveaux par-dessus", + "tool.page_numbers.no_targets": "Aucune page ne correspond à la plage sélectionnée.", "nav.nup": "N-up", "tool.nup.name": "Plusieurs pages par feuille", "tool.nup.desc": "Combine plusieurs pages du PDF sur une seule feuille (2, 4, 6, 9 ou 16 par feuille).", @@ -2515,6 +2527,8 @@ "progress.watermark.page": "Seite {current} von {total} wird mit Wasserzeichen versehen…", "progress.nup.placing": "N-up-Bögen werden erstellt…", "progress.nup.page": "Seite {current} von {total} wird platziert…", + "progress.page_numbers.applying": "Seiten werden nummeriert…", + "progress.page_numbers.page": "Seite {current} von {total} wird nummeriert…", "sidebar.collapse_expand": "Einklappen / Ausklappen", "btn.select_pdfs": "PDFs auswählen", "btn.select_folder": "Ordner auswählen", @@ -2591,6 +2605,7 @@ "tool.page_numbers.pos.bottom_center": "Unten mittig", "tool.page_numbers.pos.bottom_right": "Unten rechts", "tool.page_numbers.existing_found": "{n} Seite(n) enthalten bereits Seitenzahlen im gewählten Rand.\n\nDurch neue ersetzen?\n\nJa = alte entfernen und neue hinzufügen\nNein = alte behalten und neue darüber legen", + "tool.page_numbers.no_targets": "Keine Seite passt zum ausgewählten Bereich.", "nav.nup": "N-up", "tool.nup.name": "Mehrere Seiten pro Blatt", "tool.nup.desc": "Mehrere PDF-Seiten auf einem Blatt zusammenführen (2, 4, 6, 9 oder 16 pro Blatt).", @@ -3049,6 +3064,8 @@ "progress.watermark.page": "正在为第 {current}/{total} 页加水印…", "progress.nup.placing": "正在生成多合一页面…", "progress.nup.page": "正在放置第 {current}/{total} 页…", + "progress.page_numbers.applying": "正在编页码…", + "progress.page_numbers.page": "正在为第 {current}/{total} 页编号…", "sidebar.collapse_expand": "折叠 / 展开", "btn.select_pdfs": "选择PDF文件", "btn.select_folder": "选择文件夹", @@ -3125,6 +3142,7 @@ "tool.page_numbers.pos.bottom_center": "下中", "tool.page_numbers.pos.bottom_right": "右下", "tool.page_numbers.existing_found": "在所选边距中检测到 {n} 页已存在页码。\n\n是否替换为新页码?\n\n是 = 删除旧的并添加新的\n否 = 保留旧的并在上方添加新的", + "tool.page_numbers.no_targets": "所选范围内没有匹配的页面。", "nav.nup": "N合一", "tool.nup.name": "多页合并", "tool.nup.desc": "将多个PDF页面合并到一张纸上(每张2、4、6、9或16页)。", @@ -3583,6 +3601,8 @@ "progress.watermark.page": "Filigrana pagina {current} di {total}…", "progress.nup.placing": "Costruzione fogli N-up…", "progress.nup.page": "Posizionamento pagina {current} di {total}…", + "progress.page_numbers.applying": "Numerazione pagine…", + "progress.page_numbers.page": "Numerazione pagina {current} di {total}…", "sidebar.collapse_expand": "Comprimi / Espandi", "btn.select_pdfs": "Seleziona PDF", "btn.select_folder": "Seleziona cartella", @@ -3659,6 +3679,7 @@ "tool.page_numbers.pos.bottom_center": "In basso al centro", "tool.page_numbers.pos.bottom_right": "In basso a destra", "tool.page_numbers.existing_found": "{n} pagina/e contengono già numeri nel margine scelto.\n\nSostituirli con i nuovi?\n\nSì = rimuovi i vecchi e aggiungi i nuovi\nNo = mantieni i vecchi e aggiungi i nuovi sopra", + "tool.page_numbers.no_targets": "Nessuna pagina corrisponde all'intervallo selezionato.", "nav.nup": "N-up", "tool.nup.name": "Più pagine per foglio", "tool.nup.desc": "Combina più pagine PDF su un singolo foglio (2, 4, 6, 9 o 16 per foglio).", @@ -4117,6 +4138,8 @@ "progress.watermark.page": "Watermerk op pagina {current} van {total}…", "progress.nup.placing": "N-up-vellen samenstellen…", "progress.nup.page": "Pagina {current} van {total} plaatsen…", + "progress.page_numbers.applying": "Pagina's nummeren…", + "progress.page_numbers.page": "Pagina {current} van {total} nummeren…", "sidebar.collapse_expand": "Inklappen / Uitklappen", "btn.select_pdfs": "PDF's selecteren", "btn.select_folder": "Map selecteren", @@ -4193,6 +4216,7 @@ "tool.page_numbers.pos.bottom_center": "Onderaan midden", "tool.page_numbers.pos.bottom_right": "Rechtsonder", "tool.page_numbers.existing_found": "{n} pagina(s) bevatten al paginanummers in de gekozen marge.\n\nVervangen door de nieuwe?\n\nJa = oude verwijderen en nieuwe toevoegen\nNee = oude behouden en nieuwe erboven plaatsen", + "tool.page_numbers.no_targets": "Geen pagina komt overeen met het geselecteerde bereik.", "nav.nup": "N-up", "tool.nup.name": "Meerdere pagina's per vel", "tool.nup.desc": "Combineer meerdere PDF-pagina's op één vel (2, 4, 6, 9 of 16 per vel).",