343 lines
13 KiB
Python
343 lines
13 KiB
Python
"""GOST title block / frame drawing (ported from controller/pdfcontroller.cpp)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from reportlab.lib.units import mm
|
||
from reportlab.pdfgen import canvas
|
||
|
||
FRAME_LINE = 0.5 * mm
|
||
M_LEFT = 20 * mm
|
||
M_TOP = 5 * mm
|
||
M_RIGHT = 5 * mm
|
||
M_BOTTOM = 5 * mm
|
||
|
||
DOC_TYPES = {
|
||
"perechen": 1,
|
||
"specification_pcb": 2,
|
||
"specification": 3,
|
||
"vedomost": 4,
|
||
}
|
||
|
||
PRIMARY_APP_FIELD = {1: 251, 2: 252, 3: 253, 4: 254}
|
||
|
||
|
||
def _ins(inscriptions: dict[int, str], num: int) -> str:
|
||
return str(inscriptions.get(num, "") or "")
|
||
|
||
|
||
class Inner:
|
||
"""Inner document area in ReportLab coordinates (origin bottom-left)."""
|
||
|
||
def __init__(self, page_w: float, page_h: float):
|
||
self.page_w = page_w
|
||
self.page_h = page_h
|
||
self.left = M_LEFT
|
||
self.bottom = M_BOTTOM
|
||
self.right = page_w - M_RIGHT
|
||
self.top = page_h - M_TOP
|
||
|
||
def rect(self, x: float, y: float, w: float, h: float) -> None:
|
||
pass
|
||
|
||
|
||
def _set_frame_pen(c: canvas.Canvas) -> None:
|
||
c.setLineWidth(FRAME_LINE)
|
||
c.setStrokeColorRGB(0, 0, 0)
|
||
|
||
|
||
def _txt(
|
||
c: canvas.Canvas,
|
||
x: float,
|
||
y: float,
|
||
w: float,
|
||
h: float,
|
||
text: str,
|
||
font: str,
|
||
size: float,
|
||
align: str = "center",
|
||
valign: str = "center",
|
||
) -> None:
|
||
if not text:
|
||
return
|
||
c.setFont(font, size)
|
||
tw = c.stringWidth(text, font, size)
|
||
if align == "center":
|
||
tx = x + (w - tw) / 2
|
||
elif align == "right":
|
||
tx = x + w - tw - 0.5 * mm
|
||
else:
|
||
tx = x + 0.5 * mm
|
||
if valign == "bottom":
|
||
ty = y + 1 * mm
|
||
elif valign == "top":
|
||
ty = y + h - size * 0.35 - 0.5 * mm
|
||
else:
|
||
ty = y + (h - size * 0.35) / 2
|
||
c.drawString(tx, ty, text)
|
||
|
||
|
||
def _txt_multiline(
|
||
c: canvas.Canvas,
|
||
x: float,
|
||
y: float,
|
||
w: float,
|
||
h: float,
|
||
text: str,
|
||
font: str,
|
||
size: float,
|
||
max_lines: int = 3,
|
||
) -> None:
|
||
if not text:
|
||
return
|
||
c.setFont(font, size)
|
||
words = text.split()
|
||
lines: list[str] = []
|
||
current = ""
|
||
for word in words:
|
||
test = f"{current} {word}".strip()
|
||
if c.stringWidth(test, font, size) <= w - mm:
|
||
current = test
|
||
else:
|
||
if current:
|
||
lines.append(current)
|
||
current = word
|
||
if current:
|
||
lines.append(current)
|
||
lines = lines[:max_lines]
|
||
step = size * 1.2
|
||
start_y = y + h - size * 0.5 - mm
|
||
for i, line in enumerate(lines):
|
||
tw = c.stringWidth(line, font, size)
|
||
c.drawString(x + (w - tw) / 2, start_y - i * step, line)
|
||
|
||
|
||
def _draw_inner_border(c: canvas.Canvas, inner: Inner) -> None:
|
||
_set_frame_pen(c)
|
||
c.rect(inner.left, inner.bottom, inner.right - inner.left, inner.top - inner.bottom)
|
||
|
||
|
||
def _draw_left_binding_strip(c: canvas.Canvas, inner: Inner) -> tuple[float, float, float, float]:
|
||
"""Left vertical strip (12×145 mm) at bottom-left of inner area."""
|
||
w = 12 * mm
|
||
h = 145 * mm
|
||
x = inner.left - w
|
||
y = inner.bottom
|
||
c.rect(x, y, w, h)
|
||
# horizontal dividers (from bottom): 25, 35, 25, 25, 35 mm sections
|
||
offsets = [25, 60, 85, 110, 145]
|
||
for off in offsets:
|
||
yy = y + off * mm
|
||
c.line(x, yy, inner.left, yy)
|
||
c.line(x + w + 5 * mm, y, x + w + 5 * mm, y + h)
|
||
return x, y, w, h
|
||
|
||
|
||
def _draw_left_binding_text(c: canvas.Canvas, strip_x: float, strip_y: float, inscriptions: dict[int, str], font: str) -> None:
|
||
size = 12
|
||
c.saveState()
|
||
c.translate(strip_x + 12 * mm, strip_y + 145 * mm)
|
||
c.rotate(-90)
|
||
labels = [
|
||
(0, 25, "Инв. № подл"),
|
||
(25, 35, "Подп. и дата"),
|
||
(60, 25, "Взам. инв. №"),
|
||
(85, 25, "Инв. № дубл"),
|
||
(110, 35, "Подп. и дата"),
|
||
]
|
||
for ox, ow, label in labels:
|
||
_txt(c, ox * mm, 0, ow * mm, 5 * mm, label, font, size)
|
||
vals = [(0, 25, 19), (25, 35, 20), (60, 25, 21), (85, 25, 22), (110, 35, 23)]
|
||
for ox, ow, field in vals:
|
||
_txt(c, ox * mm, 5 * mm, ow * mm, 7 * mm, _ins(inscriptions, field), font, size)
|
||
c.restoreState()
|
||
|
||
|
||
def _draw_top_left_strip(c: canvas.Canvas, inner: Inner, inscriptions: dict[int, str], doc_type: int, font: str) -> None:
|
||
w = 12 * mm
|
||
h = 120 * mm
|
||
x = inner.left - w
|
||
y = inner.top - h
|
||
c.rect(x, y, w, h)
|
||
c.line(x + w + 5 * mm, y, x + w + 5 * mm, y + h)
|
||
c.line(x, y + 60 * mm, inner.left, y + 60 * mm)
|
||
|
||
c.saveState()
|
||
c.translate(x + w, y)
|
||
c.rotate(-90)
|
||
_txt(c, 0, 0, 60 * mm, 5 * mm, "Справ. №", font, 12)
|
||
_txt(c, 60 * mm, 0, 60 * mm, 5 * mm, "Перв. примен.", font, 12)
|
||
_txt(c, 0, 5 * mm, 60 * mm, 7 * mm, _ins(inscriptions, 24), font, 12)
|
||
_txt(
|
||
c,
|
||
60 * mm,
|
||
5 * mm,
|
||
60 * mm,
|
||
7 * mm,
|
||
_ins(inscriptions, PRIMARY_APP_FIELD.get(doc_type, 25)),
|
||
font,
|
||
12,
|
||
)
|
||
c.restoreState()
|
||
|
||
|
||
def _draw_stamp_first(c: canvas.Canvas, inner: Inner, inscriptions: dict[int, str], doc_type: int, page: int, total: int, font: str) -> None:
|
||
mw, mh = 185 * mm, 40 * mm
|
||
x = inner.right - mw
|
||
y = inner.bottom
|
||
c.rect(x, y, mw, mh)
|
||
|
||
# horizontal lines
|
||
for dy in (5, 10, 15, 20, 25, 30, 35):
|
||
if dy <= 10 or dy >= 20:
|
||
c.line(x, y + dy * mm, x + (7 + 10 + 23 + 15 + 10) * mm, y + dy * mm)
|
||
else:
|
||
c.line(x, y + dy * mm, x + mw, y + dy * mm)
|
||
c.line(x + 65 * mm, y, x + 65 * mm, y + mh)
|
||
for dx in (7, 17, 40, 55):
|
||
c.line(x + dx * mm, y, x + dx * mm, y + (15 * mm if dx == 7 else mh))
|
||
c.line(x + mw - 50 * mm, y + 15 * mm, x + mw - 50 * mm, y + mh)
|
||
c.line(x + mw - 45 * mm, y + 20 * mm, x + mw - 45 * mm, y + 25 * mm)
|
||
c.line(x + mw - 40 * mm, y + 20 * mm, x + mw - 40 * mm, y + 25 * mm)
|
||
c.line(x + mw - 35 * mm, y + 15 * mm, x + mw - 35 * mm, y + 25 * mm)
|
||
c.line(x + mw - 20 * mm, y + 15 * mm, x + mw - 20 * mm, y + 25 * mm)
|
||
c.line(x + mw - 50 * mm, y + 20 * mm, x + mw, y + 20 * mm)
|
||
c.line(x + mw - 50 * mm, y + 25 * mm, x + mw, y + 25 * mm)
|
||
|
||
# idk block — 22 mm directly above main stamp top
|
||
ix = x + mw - 120 * mm
|
||
iy = y + mh
|
||
iw, ih = 120 * mm, 22 * mm
|
||
c.line(ix, iy, ix, iy + ih)
|
||
c.line(ix + iw, iy, ix + iw, iy + ih)
|
||
c.line(ix, iy + ih, ix + iw, iy + ih)
|
||
c.line(ix, iy + 14 * mm, ix + iw, iy + 14 * mm)
|
||
c.line(ix + 14 * mm, iy, ix + 14 * mm, iy + 14 * mm)
|
||
c.line(ix + 67 * mm, iy, ix + 67 * mm, iy + 14 * mm)
|
||
_txt(c, ix, iy, 14 * mm, 14 * mm, _ins(inscriptions, 27), font, 12)
|
||
_txt(c, ix + 14 * mm, iy, 53 * mm, 14 * mm, _ins(inscriptions, 28), font, 12)
|
||
_txt(c, ix + 67 * mm, iy, 53 * mm, 14 * mm, _ins(inscriptions, 29), font, 12)
|
||
_txt(c, ix, iy + 14 * mm, iw, 8 * mm, _ins(inscriptions, 30), font, 12)
|
||
|
||
# change block headers
|
||
_txt(c, x, y + mh - 5 * mm, 7 * mm, 5 * mm, _ins(inscriptions, 14), font, 12)
|
||
_txt(c, x + 7 * mm, y + mh - 5 * mm, 10 * mm, 5 * mm, _ins(inscriptions, 15), font, 12)
|
||
_txt(c, x + 17 * mm, y + mh - 5 * mm, 23 * mm, 5 * mm, _ins(inscriptions, 16), font, 12)
|
||
_txt(c, x + 40 * mm, y + mh - 5 * mm, 15 * mm, 5 * mm, _ins(inscriptions, 17), font, 12)
|
||
_txt(c, x + 55 * mm, y + mh - 5 * mm, 10 * mm, 5 * mm, _ins(inscriptions, 18), font, 12)
|
||
_txt(c, x, y + mh - 10 * mm, 7 * mm, 5 * mm, "Изм.", font, 12)
|
||
_txt(c, x + 7 * mm, y + mh - 10 * mm, 10 * mm, 5 * mm, "Лист", font, 12)
|
||
_txt(c, x + 17 * mm, y + mh - 10 * mm, 23 * mm, 5 * mm, "№ докум.", font, 12)
|
||
_txt(c, x + 40 * mm, y + mh - 10 * mm, 15 * mm, 5 * mm, "Подп.", font, 12)
|
||
_txt(c, x + 55 * mm, y + mh - 10 * mm, 10 * mm, 5 * mm, "Дата", font, 12)
|
||
|
||
_txt(c, x + 1 * mm, y + mh - 15 * mm, 16 * mm, 5 * mm, "Разраб.", font, 12, "left")
|
||
_txt(c, x + 1 * mm, y + mh - 20 * mm, 16 * mm, 5 * mm, "Пров.", font, 12, "left")
|
||
_txt(c, x + 1 * mm, y + mh - 25 * mm, 16 * mm, 5 * mm, _ins(inscriptions, 10), font, 12, "left")
|
||
_txt(c, x + 18 * mm, y + mh - 25 * mm, 22 * mm, 5 * mm, _ins(inscriptions, 11), font, 12, "left")
|
||
_txt(c, x + 18 * mm, y + mh - 15 * mm, 22 * mm, 5 * mm, _ins(inscriptions, 111), font, 12, "left")
|
||
_txt(c, x + 18 * mm, y + mh - 20 * mm, 22 * mm, 5 * mm, _ins(inscriptions, 112), font, 12, "left")
|
||
_txt(c, x + 18 * mm, y + mh - 30 * mm, 22 * mm, 5 * mm, _ins(inscriptions, 113), font, 12, "left")
|
||
_txt(c, x + 18 * mm, y + mh - 35 * mm, 22 * mm, 5 * mm, _ins(inscriptions, 114), font, 12, "left")
|
||
_txt(c, x + 41 * mm, y + mh - 25 * mm, 14 * mm, 5 * mm, _ins(inscriptions, 12), font, 12, "left")
|
||
_txt(c, x + 56 * mm, y + mh - 25 * mm, 9 * mm, 5 * mm, _ins(inscriptions, 13), font, 12, "left")
|
||
_txt(c, x + 1 * mm, y + mh - 30 * mm, 16 * mm, 5 * mm, "Н. контр.", font, 12, "left")
|
||
_txt(c, x + 1 * mm, y + mh - 35 * mm, 16 * mm, 5 * mm, "Утв.", font, 12, "left")
|
||
|
||
# product name / doc title area
|
||
if doc_type != 4:
|
||
name_text = _ins(inscriptions, 1002 if doc_type == 3 else 1)
|
||
_txt_multiline(c, x + 65 * mm, y + mh - 40 * mm, 70 * mm, 25 * mm, name_text, font, 18)
|
||
if doc_type == 1:
|
||
_txt(c, x + 65 * mm, y + mh - 40 * mm, 70 * mm, 25 * mm, "Перечень элементов", font, 12, valign="bottom")
|
||
else:
|
||
_txt_multiline(c, x + 65 * mm, y + mh - 35 * mm, 70 * mm, 20 * mm, _ins(inscriptions, 1), font, 18)
|
||
_txt(c, x + 65 * mm, y + mh - 35 * mm, 70 * mm, 5 * mm, _ins(inscriptions, 301), font, 12)
|
||
_txt(c, x + 65 * mm, y, 70 * mm, 20 * mm, "Ведомость покупных изделий", font, 12, valign="bottom")
|
||
|
||
_txt(c, x + 65 * mm, y, 70 * mm, 5 * mm, "Копировал", font, 12, "left")
|
||
|
||
title = _ins(inscriptions, 2)
|
||
if doc_type == 1:
|
||
title = f"{title} ПЭ3".strip()
|
||
elif doc_type == 4:
|
||
title = f"{title} ВП".strip()
|
||
elif doc_type == 3:
|
||
title = _ins(inscriptions, 1001)
|
||
_txt(c, x + 65 * mm, y + mh - 15 * mm, 120 * mm, 15 * mm, title, font, 18)
|
||
|
||
_txt(c, x + 135 * mm, y + mh - 15 * mm, 15 * mm, 5 * mm, "Лит.", font, 12)
|
||
_txt(c, x + 150 * mm, y + mh - 15 * mm, 15 * mm, 5 * mm, "Лист", font, 12)
|
||
_txt(c, x + 165 * mm, y + mh - 15 * mm, 20 * mm, 5 * mm, "Листов", font, 12)
|
||
littera = _ins(inscriptions, 4)
|
||
for i in range(min(3, len(littera))):
|
||
_txt(c, x + (135 + 5 * i) * mm, y + mh - 20 * mm, 5 * mm, 5 * mm, littera[i], font, 12)
|
||
if total > 1:
|
||
_txt(c, x + 150 * mm, y + mh - 20 * mm, 15 * mm, 5 * mm, str(page), font, 12)
|
||
_txt(c, x + 165 * mm, y + mh - 20 * mm, 20 * mm, 5 * mm, str(total), font, 12)
|
||
_txt(c, x + 135 * mm, y + mh - 35 * mm, 50 * mm, 15 * mm, _ins(inscriptions, 9), font, 12)
|
||
|
||
fmt = "Формат А3" if doc_type == 4 else "Формат А4"
|
||
_txt(c, x + 135 * mm, y, 50 * mm, 5 * mm, fmt, font, 12, "left")
|
||
|
||
|
||
def _draw_stamp_other(c: canvas.Canvas, inner: Inner, inscriptions: dict[int, str], doc_type: int, page: int, font: str) -> None:
|
||
mw, mh = 185 * mm, 15 * mm
|
||
x = inner.right - mw
|
||
y = inner.bottom
|
||
c.rect(x, y, mw, mh)
|
||
for dx in (7, 17, 40, 55, 65, 175):
|
||
c.line(x + dx * mm, y, x + dx * mm, y + mh)
|
||
c.line(x, y + 5 * mm, x + 65 * mm, y + 5 * mm)
|
||
c.line(x, y + 10 * mm, x + 65 * mm, y + 10 * mm)
|
||
c.line(x + mw - 10 * mm, y + 7 * mm, x + mw, y + 7 * mm)
|
||
|
||
_txt(c, x, y, 7 * mm, 5 * mm, "Изм.", font, 12)
|
||
_txt(c, x + 7 * mm, y, 10 * mm, 5 * mm, "Лист", font, 12)
|
||
_txt(c, x + 17 * mm, y, 23 * mm, 5 * mm, "№ докум", font, 12)
|
||
_txt(c, x + 40 * mm, y, 15 * mm, 5 * mm, "Подп.", font, 12)
|
||
_txt(c, x + 55 * mm, y, 10 * mm, 5 * mm, "Дата", font, 12)
|
||
_txt(c, x, y + 5 * mm, 7 * mm, 5 * mm, _ins(inscriptions, 14), font, 12)
|
||
_txt(c, x + 7 * mm, y + 5 * mm, 10 * mm, 5 * mm, _ins(inscriptions, 15), font, 12)
|
||
_txt(c, x + 17 * mm, y + 5 * mm, 23 * mm, 5 * mm, _ins(inscriptions, 16), font, 12)
|
||
_txt(c, x + 40 * mm, y + 5 * mm, 15 * mm, 5 * mm, _ins(inscriptions, 17), font, 12)
|
||
_txt(c, x + 55 * mm, y + 5 * mm, 10 * mm, 5 * mm, _ins(inscriptions, 18), font, 12)
|
||
|
||
title = _ins(inscriptions, 2)
|
||
if doc_type == 1:
|
||
title = f"{title} ПЭ3".strip()
|
||
elif doc_type == 4:
|
||
title = f"{title} ВП".strip()
|
||
elif doc_type == 3:
|
||
title = _ins(inscriptions, 1001)
|
||
_txt(c, x + 65 * mm, y + mh - 15 * mm, 110 * mm, 15 * mm, title, font, 24)
|
||
_txt(c, x + 65 * mm, y, 110 * mm, 5 * mm, "Копировал", font, 12, "left")
|
||
fmt = "Формат А3" if doc_type == 4 else "Формат А4"
|
||
_txt(c, x + 65 * mm, y, 110 * mm, 5 * mm, fmt, font, 12, "right")
|
||
_txt(c, x + mw - 10 * mm, y + mh - 7 * mm, 10 * mm, 7 * mm, "Лист", font, 12)
|
||
_txt(c, x + mw - 10 * mm, y, 10 * mm, 8 * mm, str(page), font, 12)
|
||
|
||
|
||
def draw_gost_frame(
|
||
c: canvas.Canvas,
|
||
page_w: float,
|
||
page_h: float,
|
||
inscriptions: dict[int, str],
|
||
table_type: str,
|
||
page: int,
|
||
total: int,
|
||
font: str,
|
||
) -> None:
|
||
"""Draw GOST A4/A3 frame matching desktop layout."""
|
||
doc_type = DOC_TYPES.get(table_type, 1)
|
||
display_total = total + 1 # desktop increments for registration sheet
|
||
inner = Inner(page_w, page_h)
|
||
_draw_inner_border(c, inner)
|
||
sx, sy, sw, sh = _draw_left_binding_strip(c, inner)
|
||
_draw_left_binding_text(c, sx, sy, inscriptions, font)
|
||
|
||
if page == 1:
|
||
_draw_top_left_strip(c, inner, inscriptions, doc_type, font)
|
||
_draw_stamp_first(c, inner, inscriptions, doc_type, page, display_total, font)
|
||
else:
|
||
_draw_stamp_other(c, inner, inscriptions, doc_type, page, font)
|