Created
July 24, 2025 12:37
-
-
Save dinizime/41dfbd84b8d62fd89dd8d99734f2cb70 to your computer and use it in GitHub Desktop.
Script em python para combinar multiplos PDF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| # ========================= | |
| # š Exemplos de Uso | |
| # ========================= | |
| """ | |
| Combinador de PDFs para mapas | |
| - Combina múltiplos PDFs de pÔgina única em uma única pÔgina | |
| - Posicionamento preciso sem redimensionamento | |
| - Orientação horizontal e vertical (0° e 90°) | |
| Necessidade dos seguintes pacotes: | |
| pypdf | |
| Sugestão: pip install pypdf | |
| # Criar template de configuração (tamanho obrigatório): | |
| py combinar_pdf.py template mapa1.pdf mapa2.pdf --largura 841 --altura 1189 -o config_mapas.json | |
| # Combinar PDFs usando configuração (usa tamanho definido na configuração): | |
| py combinar_pdf.py combinar config_mapas.json -o mapas_combinados.pdf | |
| A coordenada (0,0) se refere ao canto inferior esquerdo. | |
| As coordenadas x, y no JSON sĆ£o sempre em MILĆMETROS. | |
| # Exemplo de arquivo de configuração: | |
| { | |
| "papel_saida": { | |
| "largura_mm": 900.0, | |
| "altura_mm": 1485.0 | |
| }, | |
| "pdfs": [ | |
| { | |
| "caminho": "mapa1.pdf", | |
| "x": 0, | |
| "y": 0, | |
| "orientacao": "horizontal", | |
| "dimensoes_originais": { | |
| "largura_mm": 841.0, | |
| "altura_mm": 594.1 | |
| } | |
| }, | |
| { | |
| "caminho": "mapa2.pdf", | |
| "x": 0, | |
| "y": 600, | |
| "orientacao": "vertical", | |
| "dimensoes_originais": { | |
| "largura_mm": 881.9, | |
| "altura_mm": 670.3 | |
| } | |
| } | |
| ] | |
| } | |
| """ | |
| import json | |
| import copy | |
| from pathlib import Path | |
| from pypdf import PdfReader, PdfWriter, Transformation | |
| import argparse | |
| # ========================= | |
| # š§ UtilitĆ”rios | |
| # ========================= | |
| def mm_to_pt(mm): | |
| """Converte milĆmetros para pontos""" | |
| return mm * 2.834645 | |
| def pt_to_mm(pt): | |
| """Converte pontos para milĆmetros""" | |
| return pt / 2.834645 | |
| class CombinadorPDF: | |
| def __init__(self, largura_saida, altura_saida): | |
| """ | |
| Inicializa o combinador de PDFs. | |
| Args: | |
| largura_saida: Largura da pĆ”gina de saĆda em pontos (obrigatório) | |
| altura_saida: Altura da pĆ”gina de saĆda em pontos (obrigatório) | |
| """ | |
| self.largura_saida = largura_saida | |
| self.altura_saida = altura_saida | |
| def combinar_pdfs(self, configuracoes, arquivo_saida): | |
| """ | |
| Combina múltiplos PDFs em uma única pÔgina. | |
| Args: | |
| configuracoes: Lista de dicionƔrios com: | |
| - 'caminho': Caminho para o arquivo PDF | |
| - 'x': Posição X na pĆ”gina de saĆda (em mm) | |
| - 'y': Posição Y na pĆ”gina de saĆda (em mm) | |
| - 'orientacao': 'horizontal' ou 'vertical' | |
| arquivo_saida: Caminho para o arquivo PDF de saĆda | |
| """ | |
| # Criar novo documento com pÔgina única | |
| writer = PdfWriter() | |
| pagina_saida = writer.add_blank_page(width=self.largura_saida, height=self.altura_saida) | |
| for config in configuracoes: | |
| try: | |
| self._adicionar_pdf_na_pagina(pagina_saida, config) | |
| print(f"ā Adicionado {config['caminho']} na posição ({config['x']}, {config['y']})") | |
| except Exception as e: | |
| print(f"ā Erro ao adicionar {config['caminho']}: {e}") | |
| continue | |
| # Salvar PDF combinado | |
| with open(arquivo_saida, 'wb') as f_saida: | |
| writer.write(f_saida) | |
| print(f"\nš PDF combinado salvo em: {arquivo_saida}") | |
| def _adicionar_pdf_na_pagina(self, pagina_saida, config): | |
| """Adiciona um PDF individual na pĆ”gina de saĆda.""" | |
| caminho_pdf = config['caminho'] | |
| x_pos_mm = config['x'] | |
| y_pos_mm = config['y'] | |
| orientacao = config.get('orientacao', 'horizontal') | |
| # Converter posiƧƵes de mm para pontos | |
| x_pos_pt = mm_to_pt(x_pos_mm) | |
| y_pos_pt = mm_to_pt(y_pos_mm) | |
| # Abrir PDF fonte | |
| reader = PdfReader(caminho_pdf) | |
| if len(reader.pages) == 0: | |
| raise ValueError(f"PDF {caminho_pdf} não possui pÔginas") | |
| pagina_fonte = copy.deepcopy(reader.pages[0]) | |
| # Aplicar orientação (rotação) | |
| if orientacao == 'vertical': | |
| # Obter dimensões e posição do mediabox | |
| mediabox = pagina_fonte.mediabox | |
| largura_original_pt = float(mediabox.width) | |
| altura_original_pt = float(mediabox.height) | |
| # Converter para mm | |
| largura_original_mm = pt_to_mm(largura_original_pt) | |
| altura_original_mm = pt_to_mm(altura_original_pt) | |
| centro_x = largura_original_pt / 2 | |
| centro_y = altura_original_pt / 2 | |
| # 4. Criar transformação: | |
| # - Translada para origem | |
| # - Rotaciona -90° | |
| # - Translada para posição final | |
| transformation = (Transformation() | |
| .translate(-centro_x, -centro_y) # Move centro para origem | |
| .rotate(-90) # Rotaciona 90° horÔrio | |
| .translate(centro_y, centro_x) | |
| .translate(x_pos_pt, y_pos_pt) | |
| ) | |
| pagina_saida.merge_transformed_page(pagina_fonte, transformation) | |
| print(f" š Rotacionado 90° HORĆRIO - Original: {largura_original_mm:.1f}Ć{altura_original_mm:.1f}mm") | |
| else: | |
| # Orientação horizontal - usar merge_translated_page | |
| pagina_saida.merge_translated_page(pagina_fonte, x_pos_pt, y_pos_pt) | |
| print(f" š Posicionado em ({x_pos_mm}, {y_pos_mm}) mm - horizontal") | |
| # ========================= | |
| # š Gerenciamento de Configuração | |
| # ========================= | |
| def criar_template_configuracao(arquivos_pdf, largura_papel_mm, altura_papel_mm, arquivo_saida="configuracao.json"): | |
| """Cria template de configuração com base nos arquivos PDF fornecidos.""" | |
| print(f"\nš Criando template de configuração...") | |
| print(f"š Papel de saĆda: {largura_papel_mm} Ć {altura_papel_mm} mm") | |
| configuracao = [] | |
| for arquivo_pdf in arquivos_pdf: | |
| try: | |
| reader = PdfReader(arquivo_pdf) | |
| if len(reader.pages) == 0: | |
| raise ValueError(f"PDF {arquivo_pdf} não possui pÔginas") | |
| pagina = reader.pages[0] | |
| mediabox = pagina.mediabox | |
| largura = float(mediabox.width) | |
| altura = float(mediabox.height) | |
| config_item = { | |
| "caminho": arquivo_pdf, | |
| "x": 0, | |
| "y": 0, | |
| "orientacao": "horizontal", | |
| "dimensoes_originais": { | |
| "largura_mm": round(pt_to_mm(largura), 1), | |
| "altura_mm": round(pt_to_mm(altura), 1) | |
| } | |
| } | |
| configuracao.append(config_item) | |
| print(f"š {arquivo_pdf}: {pt_to_mm(largura):.1f} Ć {pt_to_mm(altura):.1f} mm") | |
| except Exception as e: | |
| print(f"ā Erro ao processar {arquivo_pdf}: {e}") | |
| # Adicionar informaƧƵes do papel de saĆda na configuração | |
| config_completa = { | |
| "papel_saida": { | |
| "largura_mm": largura_papel_mm, | |
| "altura_mm": altura_papel_mm | |
| }, | |
| "pdfs": configuracao | |
| } | |
| # Salvar configuração | |
| with open(arquivo_saida, 'w', encoding='utf-8') as f: | |
| json.dump(config_completa, f, indent=2, ensure_ascii=False) | |
| print(f"\nā Template salvo em: {arquivo_saida}") | |
| print("š Edite as posiƧƵes x, y (em milĆmetros) e orientaƧƵes conforme necessĆ”rio.") | |
| print("š OrientaƧƵes disponĆveis: 'horizontal', 'vertical'") | |
| print("š IMPORTANTE: Coordenadas x, y sĆ£o em milĆmetros, (0,0) = canto inferior esquerdo") | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Combina múltiplos PDFs de pÔgina única") | |
| subparsers = parser.add_subparsers(dest='comando', help='Comandos disponĆveis') | |
| # Comando template | |
| template_parser = subparsers.add_parser('template', help='Criar template de configuração') | |
| template_parser.add_argument('arquivos_pdf', nargs='+', help='Arquivos PDF para incluir no template') | |
| template_parser.add_argument('-o', '--saida', default='configuracao.json', help='Arquivo de configuração de saĆda') | |
| template_parser.add_argument('--largura', type=float, required=True, help='Largura da pÔgina em mm (obrigatório)') | |
| template_parser.add_argument('--altura', type=float, required=True, help='Altura da pÔgina em mm (obrigatório)') | |
| # Comando combinar | |
| combinar_parser = subparsers.add_parser('combinar', help='Combinar PDFs usando configuração') | |
| combinar_parser.add_argument('configuracao', help='Arquivo JSON de configuração') | |
| combinar_parser.add_argument('-o', '--saida', default='combinado.pdf', help='Arquivo PDF de saĆda') | |
| args = parser.parse_args() | |
| if args.comando == 'template': | |
| if args.largura <= 0 or args.altura <= 0: | |
| print("ā Largura e altura devem ser valores positivos.") | |
| return | |
| criar_template_configuracao(args.arquivos_pdf, args.largura, args.altura, args.saida) | |
| elif args.comando == 'combinar': | |
| # Carregar configuração | |
| try: | |
| with open(args.configuracao, 'r', encoding='utf-8') as f: | |
| config_data = json.load(f) | |
| except Exception as e: | |
| print(f"ā Erro ao carregar configuração: {e}") | |
| return | |
| # Verificar se configuração tem formato correto | |
| if not isinstance(config_data, dict) or "papel_saida" not in config_data or "pdfs" not in config_data: | |
| print("ā Formato de configuração invĆ”lido.") | |
| print(" A configuração deve conter 'papel_saida' e 'pdfs'") | |
| return | |
| # Obter tamanho do papel da configuração | |
| papel_config = config_data["papel_saida"] | |
| largura_mm = papel_config["largura_mm"] | |
| altura_mm = papel_config["altura_mm"] | |
| configuracao = config_data["pdfs"] | |
| print(f"š Papel de saĆda: {largura_mm} Ć {altura_mm} mm") | |
| # Converter dimensƵes para pontos | |
| largura_pt = mm_to_pt(largura_mm) | |
| altura_pt = mm_to_pt(altura_mm) | |
| print(f"š§ Combinando PDFs...") | |
| try: | |
| combiner = CombinadorPDF(largura_pt, altura_pt) | |
| combiner.combinar_pdfs(configuracao, args.saida) | |
| except Exception as e: | |
| print(f"ā Erro na combinação: {e}") | |
| else: | |
| parser.print_help() | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment