""" 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'