Spaces:
Sleeping
Sleeping
Download document/formatters/table_fit.py from gui-sparim/teste_docs: direct link, hf CLI and curl.
- Browser
- Download file 5.2 kB
-
https://huggingface.co/spaces/gui-sparim/teste_docs/resolve/main/document/formatters/table_fit.py
- Command line
-
hf download hf://spaces/gui-sparim/teste_docs/document/formatters/table_fit.py
-
curl -L -o table_fit.py https://huggingface.co/spaces/gui-sparim/teste_docs/resolve/main/document/formatters/table_fit.py
5.2 kB
| """Fit annual tables after their values and footnote markers have been filled.""" | |
| from functools import lru_cache | |
| from math import ceil | |
| from pathlib import Path | |
| import re | |
| from PIL import ImageFont | |
| from docx.oxml import OxmlElement | |
| from docx.oxml.ns import qn | |
| from .table import set_table_font_size | |
| def _font(bold=False): | |
| # The same bundled metrics are available locally and in Spaces. DejaVu Sans | |
| # is slightly wider than Arial, leaving a margin for Word font substitution. | |
| from matplotlib import get_data_path | |
| name = 'DejaVuSans-Bold.ttf' if bold else 'DejaVuSans.ttf' | |
| return ImageFont.truetype(str(Path(get_data_path()) / 'fonts' / 'ttf' / name), 1000) | |
| def _width(text, bold=False): | |
| return _font(bold).getlength(text) / 1000 | |
| def _lines(text, width, size, bold=False): | |
| """Estimate wrapping using glyph widths, preserving explicit line breaks.""" | |
| count = 0 | |
| for line in text.split('\n'): | |
| used = 0 | |
| count += 1 | |
| for word in re.findall(r'\S+', line): | |
| length = _width(word, bold) * size | |
| gap = _width(' ', bold) * size if used else 0 | |
| if used and used + gap + length > width: | |
| count += 1 | |
| used = 0 | |
| gap = 0 | |
| if length > width: | |
| extra = max(0, ceil(length / width) - 1) | |
| count += extra | |
| used = length - extra * width | |
| else: | |
| used += gap + length | |
| return count | |
| def _margin(cell, side, default): | |
| margins = cell._tc.get_or_add_tcPr().find(qn('w:tcMar')) | |
| node = margins.find(qn('w:' + side)) if margins is not None else None | |
| return int(node.get(qn('w:w'), default)) / 20 if node is not None else default / 20 | |
| def fit_table_to_page(doc, table, notes=()): | |
| """Select the largest half-point size <= 8 that fits the populated table. | |
| Currency, numbers and year labels must fit on one line. Other text can wrap. | |
| Height includes cell padding, the caption allowance and 7 pt footnotes. This | |
| is a conservative estimate: Word remains responsible for final pagination. | |
| The years always remain columns of the same table, even for longer tables. | |
| """ | |
| section = doc.sections[-1] | |
| page_width = (section.page_width - section.left_margin - section.right_margin) / 12700 | |
| page_height = (section.page_height - section.top_margin - section.bottom_margin) / 12700 | |
| note_height = sum(_lines(str(note), page_width, 7) * 8 + 4 for note in notes) | |
| available_height = max(1, page_height - 30 - note_height) | |
| rows = [] | |
| for row in table.rows: | |
| row_props = row._tr.get_or_add_trPr() | |
| if row_props.find(qn('w:cantSplit')) is None: | |
| row_props.append(OxmlElement('w:cantSplit')) | |
| cells = [] | |
| for cell in row.cells: | |
| props = cell._tc.get_or_add_tcPr() | |
| for node in list(props.findall(qn('w:noWrap'))): | |
| props.remove(node) | |
| width = max(1, cell.width / 12700 - _margin(cell, 'left', 80) - _margin(cell, 'right', 80) - 2) | |
| paragraphs = [] | |
| for paragraph in cell.paragraphs: | |
| marker_count = len(paragraph._p.findall('.//' + qn('w:footnoteReference'))) | |
| text = paragraph.text + '1' * marker_count | |
| bold = any(run.bold for run in paragraph.runs) | |
| paragraphs.append((text, bold, paragraph)) | |
| cells.append((width, _margin(cell, 'top', 65) + _margin(cell, 'bottom', 65), paragraphs)) | |
| rows.append(cells) | |
| def fits(size): | |
| height = 0 | |
| for cells in rows: | |
| row_height = 0 | |
| for width, padding, paragraphs in cells: | |
| cell_height = padding | |
| for text, bold, paragraph in paragraphs: | |
| # Do not split an amount, a date, a coefficient or IPTU year | |
| # into fragments to make the width calculation succeed. | |
| single_line = bool(re.fullmatch(r'(?:R\$\s*)?[−+\-]?\d[\d.,%/\s]*|IPTU\s+\d{4}', text.strip())) | |
| if single_line and _width(text, bold) * size > width: | |
| return False | |
| cell_height += _lines(text, width, size, bold) * size * 1.15 | |
| cell_height += (paragraph.paragraph_format.space_before or 0) / 12700 | |
| cell_height += (paragraph.paragraph_format.space_after or 0) / 12700 | |
| row_height = max(row_height, cell_height) | |
| height += row_height | |
| return height <= available_height | |
| # w:sz stores integer half-points: smaller steps would be rounded away. | |
| size = 8 | |
| while size > 0.5 and not fits(size): | |
| size -= 0.5 | |
| set_table_font_size(table, size) | |
| keep_together = fits(size) | |
| for row_index, row in enumerate(table.rows): | |
| for cell in row.cells: | |
| for paragraph in cell.paragraphs: | |
| paragraph.paragraph_format.line_spacing = 1.05 | |
| paragraph.paragraph_format.keep_with_next = keep_together and row_index < len(table.rows) - 1 | |
| paragraph.paragraph_format.keep_together = True | |
| return size | |