- 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>
130 lines
5.0 KiB
Python
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) # 머리 행 쪽마다
|