From f51b60a839061577fc925c3fb6a87dc45d13434d Mon Sep 17 00:00:00 2001 From: grigo Date: Wed, 2 Sep 2026 15:53:52 +0300 Subject: [PATCH] fix table --- web/backend/app/services/pdf_export.py | 7 ++- web/backend/app/services/pdf_gost_frame.py | 66 ++++++++++++++++------ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/web/backend/app/services/pdf_export.py b/web/backend/app/services/pdf_export.py index c5f0eae..5409ae5 100644 --- a/web/backend/app/services/pdf_export.py +++ b/web/backend/app/services/pdf_export.py @@ -14,7 +14,7 @@ from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen import canvas from app.core.config import get_settings -from app.services.pdf_gost_frame import draw_gost_frame +from app.services.pdf_gost_frame import TABLE_LEFT, draw_gost_frame _font_registered = False FONT_NAME = "GOST_A" @@ -231,7 +231,8 @@ def _draw_rotated_header( line = lines[0] if lines else text line = _elide(c, line, font, size, h - mm) tw = c.stringWidth(line, font, size) - c.drawString((h - tw) / 2, (w - size * 0.35) / 2, line) + # Текст растёт внутрь таблицы (+x после поворота), не влево в поле подшивки + c.drawString(max(0, (h - tw) / 2), (w - size * 0.35) / 2, line) c.restoreState() @@ -344,7 +345,7 @@ def _draw_table_page( font = _ensure_font() c.setLineWidth(LINE_WIDTH) - table_x = layout.margin_left_mm * mm + table_x = TABLE_LEFT table_top = page_h - layout.margin_top_mm * mm row_h = layout.row_height_mm * mm header_h = layout.header_height_mm * mm diff --git a/web/backend/app/services/pdf_gost_frame.py b/web/backend/app/services/pdf_gost_frame.py index 4f2df71..9f64bdc 100644 --- a/web/backend/app/services/pdf_gost_frame.py +++ b/web/backend/app/services/pdf_gost_frame.py @@ -10,6 +10,10 @@ M_LEFT = 20 * mm M_TOP = 5 * mm M_RIGHT = 5 * mm M_BOTTOM = 5 * mm +BINDING_WIDTH = 12 * mm +# Поле подшивки: 8–20 мм от левого края листа; таблица начинается ровно на inner.left (20 мм). +TABLE_LEFT = M_LEFT +BINDING_LEFT = TABLE_LEFT - BINDING_WIDTH DOC_TYPES = { "perechen": 1, @@ -45,6 +49,22 @@ def _set_frame_pen(c: canvas.Canvas) -> None: c.setStrokeColorRGB(0, 0, 0) +def _elide(c: canvas.Canvas, text: str, font: str, size: float, max_w: float) -> str: + if not text: + return text + if c.stringWidth(text, font, size) <= max_w: + return text + ell = "…" + lo, hi = 0, len(text) + while lo < hi: + mid = (lo + hi + 1) // 2 + if c.stringWidth(text[:mid] + ell, font, size) <= max_w: + lo = mid + else: + hi = mid - 1 + return text[:lo] + ell if lo else ell + + def _txt( c: canvas.Canvas, x: float, @@ -60,6 +80,7 @@ def _txt( if not text: return c.setFont(font, size) + text = _elide(c, text, font, size, w - mm) tw = c.stringWidth(text, font, size) if align == "center": tx = x + (w - tw) / 2 @@ -116,26 +137,38 @@ def _draw_inner_border(c: canvas.Canvas, inner: Inner) -> None: c.rect(inner.left, inner.bottom, inner.right - inner.left, inner.top - inner.bottom) +def _clip_binding_zone(c: canvas.Canvas, inner: Inner) -> None: + """Ограничить рисование полосы подшивки: 8–20 мм, не заходить в таблицу.""" + p = c.beginPath() + p.rect(BINDING_LEFT, inner.bottom, BINDING_WIDTH, inner.top - inner.bottom) + c.clipPath(p, stroke=0, fill=0) + + 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 + """Left vertical strip (12×145 mm) — strictly left of the table (8–20 mm).""" + w = BINDING_WIDTH h = 145 * mm - x = inner.left - w + x = BINDING_LEFT y = inner.bottom + c.saveState() + _set_frame_pen(c) + _clip_binding_zone(c, inner) 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) + c.line(x, yy, TABLE_LEFT, yy) + c.restoreState() 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: +def _draw_left_binding_text( + c: canvas.Canvas, inner: Inner, 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) + _clip_binding_zone(c, inner) + c.translate(strip_x + BINDING_WIDTH, strip_y + 145 * mm) c.rotate(-90) labels = [ (0, 25, "Инв. № подл"), @@ -153,16 +186,17 @@ def _draw_left_binding_text(c: canvas.Canvas, strip_x: float, strip_y: float, in def _draw_top_left_strip(c: canvas.Canvas, inner: Inner, inscriptions: dict[int, str], doc_type: int, font: str) -> None: - w = 12 * mm + w = BINDING_WIDTH h = 120 * mm - x = inner.left - w + x = BINDING_LEFT 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) + _set_frame_pen(c) + _clip_binding_zone(c, inner) + c.rect(x, y, w, h) + c.line(x, y + 60 * mm, TABLE_LEFT, y + 60 * mm) + + c.translate(TABLE_LEFT, 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) @@ -333,7 +367,7 @@ def draw_gost_frame( 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) + _draw_left_binding_text(c, inner, sx, sy, inscriptions, font) if page == 1: _draw_top_left_strip(c, inner, inscriptions, doc_type, font)