feat(535-2): B09 도면 PDF PyMuPDF(AGPL) 제거 — 장마다 SVG 쪽 → 공용 PDF 엔진(Chromium) · pypdf 합치기

- 점선 = 미리 자른 실선 조각(Chromium 이 점선을 채운 윤곽으로 풀어 PDF 3 배 커지는 것 막음)
- 글 안 빈칸 그대로(white-space: pre) · 세운 글 방향 = 화면(openwebcad canvas)과 같게
- 못 넣은 그림 = data: 아님 · base64 깨짐
- test_302_2: PDF 읽기 pymupdf → pypdf · add_drawing_page(pymupdf 문서) 시험 → drawing_page(SVG) 로 고침 · 점선 조각 · 빈칸 시험 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 19:42:54 +09:00
co-authored by Claude Opus 5.5
parent 9add13ccbc
commit b8ef5d033d
4 changed files with 193 additions and 122 deletions
+49 -19
View File
@@ -1,18 +1,20 @@
"""PLAN 302-2 인쇄 미리보기 도면 PDF — 목록 차례 · 쪽 수 · 쪽 크기 · 한글 · 숨긴 층 · 권한.
"""PLAN 302-2 · 535-2 인쇄 미리보기 도면 PDF — 목록 차례 · 쪽 수 · 쪽 크기 · 한글 · 숨긴 층 · 권한 ·
점선 조각 · 그림 · 빈칸 그대로(PDF 읽기 = pypdf).
임시 폴더 견본만 씀(공용 storage 안 씀) — B07 목록 · 단건 · 표제란 값은 바꿔 끼움.
"""
from __future__ import annotations
import io
import json
from pathlib import Path
from uuid import uuid4
import pymupdf
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pypdf import PdfReader
from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
frame_entities,
@@ -24,7 +26,8 @@ from B09_Estimation.draft import B09_Draft_Print_Drawings_Router as router_modul
from B09_Estimation.draft import B09_Draft_Print_Drawings_Source as source
from B09_Estimation.draft.B09_Draft_Print_Drawings_Pdf import (
PT_PER_MM,
add_drawing_page,
dash_pieces,
drawing_page,
paint_color,
)
from common_util import common_util_auth as auth
@@ -170,18 +173,21 @@ def test_PDF_쪽수_크기_한글_숨긴층(sample):
assert response.headers["content-type"] == "application/pdf"
assert response.headers["x-aislo-pages"] == "5"
assert json.loads(response.headers["x-aislo-skipped-images"]) == {}
doc = pymupdf.open(stream=response.content, filetype="pdf")
assert doc.page_count == 5
for page in doc:
assert abs(page.rect.width / PT_PER_MM - 840) < 0.5
assert abs(page.rect.height / PT_PER_MM - 594) < 0.5
first = doc[0].get_text()
if response.status_code == 503:
pytest.skip(response.json()["message"]) # 서버에 Edge · Chrome 없음
pages = PdfReader(io.BytesIO(response.content)).pages
assert len(pages) == 5
for page in pages:
assert abs(float(page.mediabox.width) / PT_PER_MM - 840) < 0.5
assert abs(float(page.mediabox.height) / PT_PER_MM - 594) < 0.5
texts = [page.extract_text() for page in pages]
first = texts[0]
assert "가나다임도개설공사" in first and "라마바설계" in first # 표제란 한글 그대로
assert "보이는글" in first
assert "숨은글" not in first and "얼린글" not in first
# 구조물 장 = 얹은 도각 + 장 도번 · 표제란 = 프로젝트 값
assert "구조물그림" in doc[2].get_text() and "SD-000001" in doc[2].get_text()
assert "SD-000002-2" in doc[4].get_text() and "가나다임도개설공사" in doc[4].get_text()
assert "구조물그림" in texts[2] and "SD-000001" in texts[2]
assert "SD-000002-2" in texts[4] and "가나다임도개설공사" in texts[4]
def test_PDF_고른_차례_모르는_id(sample):
@@ -189,8 +195,10 @@ def test_PDF_고른_차례_모르는_id(sample):
client = _client(SESSION)
picked = ["structure:SD-000001", "b07:cover"]
response = client.post(f"/api/projects/{pid}/print/drawings-pdf", json={"ids": picked})
doc = pymupdf.open(stream=response.content, filetype="pdf")
assert doc.page_count == 2 and "구조물그림" in doc[0].get_text()
if response.status_code == 503:
pytest.skip(response.json()["message"])
pages = PdfReader(io.BytesIO(response.content)).pages
assert len(pages) == 2 and "구조물그림" in pages[0].extract_text()
bad = client.post(f"/api/projects/{pid}/print/drawings-pdf", json={"ids": ["b07:없음"]})
assert bad.status_code == 400
@@ -244,9 +252,14 @@ def test_로그인_회사_권한(sample, monkeypatch):
assert _client(SESSION, project_company=1).get(path).status_code == 200
#: 1×1 PNG
PNG = (
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAE"
"hQGAhKmMIQAAAABJRU5ErkJggg=="
)
def test_그림_넣기_못_넣은_수():
png = pymupdf.Pixmap(pymupdf.csRGB, pymupdf.IRect(0, 0, 4, 4), 0).tobytes("png")
import base64
def image(data: str) -> dict:
points = [{"x": 10, "y": 10}, {"x": 50, "y": 10}, {"x": 50, "y": 50}, {"x": 10, "y": 50}]
@@ -254,14 +267,31 @@ def test_그림_넣기_못_넣은_수():
drawing = {
"entities": [
image("data:image/png;base64," + base64.b64encode(png).decode()),
image("data:image/png;base64," + PNG),
image("/api/assets/없는그림.png"),
image("data:image/png;base64,@@깨진@@"),
],
"layers": [],
}
doc = pymupdf.open()
paper, skipped = add_drawing_page(doc, drawing)
assert skipped == 1 and len(doc[0].get_images()) == 1
page, size, paper, skipped = drawing_page(drawing)
assert skipped == 2 and page.count("<image ") == 1
def test_점선_조각_빈칸_그대로():
"""점선 = 미리 자른 실선 조각(Chromium 이 윤곽으로 안 풂) · 무늬 = 꺾은선 처음부터 이어 셈."""
pieces = dash_pieces([(0, 0), (10, 0), (10, 10)], (6, 3))
assert pieces == [
[(0, 0), (6.0, 0.0)],
[(9.0, 0.0), (10, 0), (10.0, 5.0)],
[(10.0, 8.0), (10, 10)],
]
assert dash_pieces([(0, 0), (1, 0)], (0, 0)) == [[(0, 0), (1, 0)]] # 무늬 없음 = 실선
line = {"type": "Line", "layerId": "a", "lineDash": [6, 3]}
line["shapeData"] = {"startPoint": {"x": 0, "y": 0}, "endPoint": {"x": 100, "y": 0}}
text = _text("축 척", 50, 50, "a")
page, size, paper, _ = drawing_page({"entities": [line, text], "layers": []})
assert "stroke-dasharray" not in page and page.count("M") > 5
assert "축 척" in page and "white-space: pre" in page
def test_미확정_횡단_구조물_Node_번들이_돎():