Skip to content

Instantly share code, notes, and snippets.

@dinizime
Created July 24, 2025 12:37
Show Gist options
  • Select an option

  • Save dinizime/41dfbd84b8d62fd89dd8d99734f2cb70 to your computer and use it in GitHub Desktop.

Select an option

Save dinizime/41dfbd84b8d62fd89dd8d99734f2cb70 to your computer and use it in GitHub Desktop.
Script em python para combinar multiplos PDF
# -*- 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