Files
Aislo/resources/tester/spreadsheet/test_spreadsheet_print.py
T
eomsangdonandClaude Opus 5.5 f20aea412a feat(534-2): 내장 엑셀 내보내기 — 인쇄 HTML(Node print) · xlsx · SVG→PNG · PDF
- spreadsheet_print_html: 통합문서 → 인쇄 HTML(DOM 없음 · 화면 엔진 값 · formatValue · 병합 · colgroup 폭 · 쪽 나눔 덩이 · thead 머리행 반복 · 그림 img · 시트마다 @page)
- 서식 → CSS 를 클립보드에서 떼어 공용(medium · thick · double 굵기 반영)
- 서버 Node 진입 prints · 파이썬 print_htmls · workbook_pdf(공용 PDF 엔진 page_size=None)
- common_util_workbook_xlsx: openpyxl 로 값 · 식 · 서식 · 병합 · 폭 · 높이 · 틀 고정 · 인쇄 설정 · 쪽 나눔 · 그림(두 칸 닻)
- common_util_svg_png: Playwright 로 SVG → PNG(PyMuPDF 안 씀)
- 시험 test_spreadsheet_print(인쇄 HTML · xlsx 되읽기 · PNG 크기 · PDF 쪽 수)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:33:57 +09:00

130 lines
5.0 KiB
Python

"""534-2 — 내장 엑셀 내보내기: 인쇄 HTML(Node 번들 `printHtml`) · xlsx 되읽기 · SVG → PNG · PDF 쪽 수.
책 하나(시트 둘 · 병합 · 열 폭 · 숨긴 열 · 머리 행 · 쪽 나눔 둘 · 그림 · 식)로 셋을 함께 봄.
"""
import io
import re
import shutil
import pytest
from openpyxl import load_workbook
from common_util import common_util_spreadsheet as spreadsheet
from common_util.common_util_print_pdf import PrintError
from common_util.common_util_svg_png import svgs_to_pngs
from common_util.common_util_workbook_xlsx import workbook_to_xlsx
pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
SVG = (
'<svg xmlns="http://www.w3.org/2000/svg" width="120" height="60">'
'<rect width="120" height="60" fill="#0a0"/></svg>'
)
BREAKS = [6, 12]
def _book() -> dict:
cells = {"A1": {"값": "제목", "서식": 1}, "A2": {"값": "측점", "서식": 2}, "B2": {"값": "Fs"}}
for r in range(3, 31):
cells[f"A{r}"] = {"값": f"No.{r}"}
cells[f"B{r}"] = {"값": r / 10, "서식": 3}
cells["C3"] = {"식": "SUM(B3:B30)", "서식": 3}
cells["D3"] = {"값": "숨김"}
return {
"종류": "통합문서",
"판": 1,
"열": "stab_report",
"서식": [
{},
{"크기": 14, "굵게": True},
{"채움": "#EEEEEE", "테두리": {"아래": {"선": "medium"}}},
{"형식": "0.00"},
],
"시트": [
{
"id": "s1",
"이름": "측점표",
"칸": cells,
"병합": ["A1:C1", "E5:E8"],
"열": {"A": {"폭": 12}, "B": {"폭": 6}, "D": {"숨김": True}},
"행": {"1": {"높이": 24}},
"틀고정": {"행": 2},
"인쇄": {
"방향": "landscape",
"맞춤": {"폭": 1, "높이": 0},
"머리행": [2, 2],
"쪽나눔": BREAKS,
},
"그림": [{"열쇠": "fig/a.png", "닻": "F3:H10", "이름": "단면"}],
},
{"id": "s2", "이름": "숨김", "칸": {"A1": {"값": 1}}, "숨김": True},
],
"활성": "s1",
}
@pytest.fixture(scope="module")
def png() -> bytes:
try:
return svgs_to_pngs([SVG])[0]
except PrintError as error:
pytest.skip(f"브라우저 없음 — {error}")
def test_svg_png_크기(png):
from PIL import Image
assert Image.open(io.BytesIO(png)).size == (240, 120) # scale 2
def test_인쇄_html(png):
html = spreadsheet.print_htmls([{"book": _book(), "figures": {"fig/a.png": png}}])[0]
sheets = re.findall(r'<section class="sheet" data-sheet="(\w+)"', html)
assert sheets == ["s1"] # 숨긴 시트 안 찍음
chunks = html.split('<div class="chunk">')[1:]
assert len(chunks) == len(BREAKS) + 1
for chunk in chunks: # 덩이마다 머리 행 한 줄
thead = re.search(r"<thead>(.*?)</thead>", chunk).group(1)
assert thead.count("<tr") == 1 and "측점" in thead
# 열 폭 = 화면 px 환산(글자 × 7 + 5) · 숨긴 D 빠짐(A B C E F G H = 7)
widths = re.findall(r'<col style="width:(\d+)px">', chunks[0])
assert widths[:3] == ["89", "47", "64"] and len(widths) == 7 * 2 # 앞 표(제목) + 본 표
# 병합 A1:C1 · E5:E8 = 쪽 나눔(6 아래)에서 잘림 — 첫 덩이 5~6 · 둘째 덩이 7~8
assert 'colspan="3"' in chunks[0] and 'rowspan="2"' in chunks[0] and 'rowspan="2"' in chunks[1]
assert "rowspan" not in chunks[2]
assert html.count("<img ") == 1 and "data:image/png;base64," in html
assert "@page p0{size:A4 landscape" in html
assert ">46.20<" in html # 식 값 = 화면 엔진 · 형식 0.00(SUM 0.3..3.0)
def test_xlsx_되읽기(png):
data = workbook_to_xlsx(_book(), {"fig/a.png": png})
wb = load_workbook(io.BytesIO(data))
assert wb.sheetnames == ["측점표", "숨김"]
ws = wb["측점표"]
assert sorted(str(m) for m in ws.merged_cells.ranges) == ["A1:C1", "E5:E8"]
assert [b.id for b in ws.row_breaks.brk] == BREAKS
assert len(ws._images) == 1
assert ws.print_title_rows == "$2:$2"
assert ws["C3"].value == "=SUM(B3:B30)"
assert ws["B3"].number_format == "0.00" and ws["A1"].font.b
assert ws.column_dimensions["A"].width == 12 and ws.column_dimensions["D"].hidden
assert ws.row_dimensions[1].height == 24 and ws.freeze_panes == "A3"
assert ws.page_setup.orientation == "landscape" and ws.page_setup.fitToWidth == 1
assert wb["숨김"].sheet_state == "hidden"
def test_pdf_쪽수(png):
book = _book()
try:
pdf = spreadsheet.workbook_pdf(book, {"fig/a.png": png})
except PrintError as error:
pytest.skip(f"브라우저 없음 — {error}")
from pypdf import PdfReader
pages = PdfReader(io.BytesIO(pdf)).pages
assert len(pages) == len(BREAKS) + 1
assert all(float(p.mediabox.width) > float(p.mediabox.height) for p in pages) # 가로
assert all("측점" in p.extract_text() for p in pages) # 머리 행 쪽마다