"""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)