gui-sparim's picture
Upload 44 files
8d6c767 verified
"""
Formatadores de tabelas.
"""
from docx import Document
from docx.shared import Pt, Inches, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from typing import List, Optional, Union, Dict, Any
from .paragraph import aplicar_cor_run
WD_ALIGN_VERTICAL = WD_CELL_VERTICAL_ALIGNMENT
def set_cell_shading(cell, color: str) -> None:
"""
Define a cor de fundo de uma célula.
Args:
cell: Célula da tabela
color: Cor em hex (ex: "E6E6E6")
"""
shading = OxmlElement('w:shd')
shading.set(qn('w:fill'), color)
cell._tc.get_or_add_tcPr().append(shading)
def set_cell_text_rotation(cell, direcao: str = 'btLr') -> None:
"""
Define a direção/rotação do texto em uma célula.
Args:
cell: Célula da tabela
direcao: Direção do texto:
- 'btLr': bottom-to-top, left-to-right (90° - vertical)
- 'tbRl': top-to-bottom, right-to-left (270°)
"""
tcPr = cell._tc.get_or_add_tcPr()
textDirection = OxmlElement('w:textDirection')
textDirection.set(qn('w:val'), direcao)
tcPr.append(textDirection)
def formatar_celula_tabela(cell, texto: str, negrito: bool = False, cor=None,
shading_color: Optional[str] = None, rotacao: bool = False):
"""
Formata uma célula de tabela com configurações padrão.
Args:
cell: Célula da tabela
texto: Texto a inserir
negrito: Se o texto deve ser negrito
cor: Cor do texto (tuple ou RGBColor)
shading_color: Cor de fundo da célula (hex string)
rotacao: Se True, rotaciona o texto 90° (vertical)
Returns:
Run criado
"""
p = cell.paragraphs[0]
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
p.paragraph_format.left_indent = Cm(0)
p.paragraph_format.right_indent = Cm(0)
p.paragraph_format.first_line_indent = Cm(0)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
run = p.add_run(texto)
run.font.size = Pt(9)
run.font.name = 'Arial'
run.bold = negrito
if cor:
aplicar_cor_run(run, cor)
if shading_color:
set_cell_shading(cell, shading_color)
if rotacao:
set_cell_text_rotation(cell)
return run
def add_simple_table(
doc: Document,
dados_tabela: List[List[Union[str, Dict[str, Any]]]],
header_row: bool = True,
largura_colunas: Optional[List[int]] = None,
rotacao_cabecalho: bool = False
):
"""
Adiciona uma tabela simples ao documento.
Args:
doc: Documento
dados_tabela: Lista de listas. Cada célula pode ser string ou dict {'texto', 'cor'}
header_row: Se True, primeira linha é cabeçalho (negrito com fundo cinza)
largura_colunas: Lista de inteiros representando proporção da largura de cada coluna.
rotacao_cabecalho: Se True, rotaciona o texto dos cabeçalhos 90° (vertical)
Returns:
Tabela criada ou None se dados vazios
"""
if not dados_tabela or not dados_tabela[0]:
return None
num_colunas = len(dados_tabela[0])
if largura_colunas is None:
largura_colunas = [1] * num_colunas
elif len(largura_colunas) != num_colunas:
raise ValueError("largura_colunas deve ter o mesmo número de colunas de dados_tabela")
total = sum(largura_colunas)
proporcoes = [x / total for x in largura_colunas]
table = doc.add_table(rows=len(dados_tabela), cols=num_colunas)
table.style = 'Table Grid'
table.alignment = WD_TABLE_ALIGNMENT.CENTER
largura_total = Inches(6.5)
larguras_em_colunas = [largura_total * p for p in proporcoes]
for i, row_data in enumerate(dados_tabela):
row = table.rows[i]
for j, cell_data in enumerate(row_data):
if j >= len(row.cells):
continue
cell = row.cells[j]
if isinstance(cell_data, dict):
texto = cell_data.get('texto', '')
cor = cell_data.get('cor')
else:
texto = str(cell_data) if cell_data else ""
cor = None
is_header = header_row and i == 0
formatar_celula_tabela(
cell, texto,
negrito=is_header,
cor=cor,
shading_color="E6E6E6" if is_header else None,
rotacao=is_header and rotacao_cabecalho
)
cell.width = larguras_em_colunas[j]
# Ajustar altura do cabeçalho para texto rotacionado
if header_row and rotacao_cabecalho and dados_tabela:
# Calcular o maior texto do cabeçalho
header_texts = []
for cell_data in dados_tabela[0]:
if isinstance(cell_data, dict):
header_texts.append(cell_data.get('texto', ''))
else:
header_texts.append(str(cell_data) if cell_data else "")
if header_texts:
max_len = max(len(t) for t in header_texts)
# Aproximadamente 0.18cm por caractere em Arial 9pt + margem
altura_cm = max(1.0, max_len * 0.18 + 0.3)
configurar_linha_tabela_altura(table.rows[0], altura_cm)
doc.add_paragraph()
return table
def configurar_linha_tabela_altura(row, altura_cm: float = 0.6) -> None:
"""
Configura altura mínima de uma linha de tabela.
Args:
row: Linha da tabela
altura_cm: Altura em centímetros
"""
tr = row._tr
trPr = tr.get_or_add_trPr()
trHeight = OxmlElement('w:trHeight')
trHeight.set(qn('w:val'), str(int(Cm(altura_cm).twips)))
trHeight.set(qn('w:hRule'), 'atLeast')
trPr.append(trHeight)
def criar_celula_cabecalho_tabela(cell, texto: str, recuo_primeira_linha_cm: float = 1.0) -> None:
"""
Formata uma célula de cabeçalho de seção na tabela principal.
Args:
cell: Célula da tabela
texto: Texto do cabeçalho
recuo_primeira_linha_cm: Recuo da primeira linha
"""
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
p = cell.paragraphs[0]
p.paragraph_format.left_indent = Cm(0)
p.paragraph_format.first_line_indent = Cm(recuo_primeira_linha_cm)
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
run = p.add_run(texto)
run.bold = True
run.underline = True
run.font.size = Pt(11)
run.font.name = 'Arial'
set_cell_shading(cell, "E6E6E6")
def criar_celula_dados_tabela(cell, texto: str, is_label: bool = False) -> None:
"""
Formata uma célula de dados na tabela principal.
Args:
cell: Célula da tabela
texto: Texto da célula
is_label: Se True, formata como rótulo (negrito)
"""
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
p = cell.paragraphs[0]
p.paragraph_format.left_indent = Cm(0)
p.paragraph_format.first_line_indent = Cm(0)
p.paragraph_format.space_before = Pt(0)
p.paragraph_format.space_after = Pt(0)
run = p.add_run(texto)
run.bold = is_label
run.font.size = Pt(9)
run.font.name = 'Arial'