diff --git a/docs/modules/program-certificates.md b/docs/modules/program-certificates.md
index 2b3db324..aeac0ada 100644
--- a/docs/modules/program-certificates.md
+++ b/docs/modules/program-certificates.md
@@ -75,3 +75,14 @@ PDF не изменяются, миграция БД не требуется. П
6. Фильтры Все/Отправлено/Ошибки с серверным поиском и пагинацией.
7. Повтор только ошибок, тот же PDF, отправленные письма не дублируются.
8. Свой PDF в программе и ссылка в сервисном письме; защита чужих документов.
+
+## Ориентация А4
+
+`draft.orientation` принимает `landscape` (297 × 210 мм) или `portrait`
+(210 × 297 мм). Новые и старые шаблоны без этого поля используют `landscape`.
+При сохранении старым клиентом без поля API сохраняет предыдущую ориентацию.
+Неизвестные значения отклоняются без изменения ревизии. Ориентация входит в
+снимок выдачи; preview и генерация PDF используют его физические размеры,
+включая подгонку текста. Фон вписывается целиком без обрезки; вертикальные
+изображения сохраняют разрешение до 2480 × 3508. Ранее сформированные PDF
+не пересоздаются. Миграция базы не требуется.
diff --git a/partner_programs/services/certificates.py b/partner_programs/services/certificates.py
index 907c1461..ea743391 100644
--- a/partner_programs/services/certificates.py
+++ b/partner_programs/services/certificates.py
@@ -53,6 +53,7 @@ def default_draft():
(60, 70, 25, 8, 16),
]
return {
+ "orientation": "landscape",
"background_id": None,
"signature_id": None,
"director": "",
@@ -84,6 +85,7 @@ def hidden_elements(draft):
def normalized_draft(draft):
return {
**draft,
+ "orientation": draft.get("orientation", "landscape"),
"hidden_elements": [key for key in ELEMENTS if key in hidden_elements(draft)],
}
@@ -193,7 +195,10 @@ def upload_asset(program, kind, upload):
from PIL import ImageOps
image = ImageOps.exif_transpose(image)
- image.thumbnail((3508, 2480) if kind == "background" else (2000, 2000))
+ limit = (2000, 2000)
+ if kind == "background":
+ limit = (2480, 3508) if image.height > image.width else (3508, 2480)
+ image.thumbnail(limit)
buffer = io.BytesIO()
image.save(buffer, format="PNG")
if buffer.tell() > 20 * 1024 * 1024:
@@ -229,6 +234,9 @@ class Element(serializers.Serializer):
font_size = serializers.FloatField(min_value=8, max_value=72)
class Draft(serializers.Serializer):
+ orientation = serializers.ChoiceField(
+ choices=["landscape", "portrait"], required=False
+ )
background_id = serializers.IntegerField(allow_null=True)
signature_id = serializers.IntegerField(allow_null=True)
director = serializers.CharField(max_length=250, allow_blank=True)
@@ -251,6 +259,9 @@ class Draft(serializers.Serializer):
serializer = Draft(data=draft)
serializer.is_valid(raise_exception=True)
data = serializer.data
+ if "orientation" not in data:
+ # Older clients must not silently rotate a portrait template when saving.
+ data["orientation"] = (previous or {}).get("orientation", "landscape")
if "hidden_elements" not in data:
# Старый клиент не должен восстанавливать удаленные новым редактором элементы.
hidden = hidden_elements(previous or data)
@@ -298,11 +309,18 @@ def certificate_font(bold):
)
-def fit_certificate_text(value, box, bold=False):
+def page_dimensions(orientation="landscape"):
+ return (210, 297) if orientation == "portrait" else (297, 210)
+
+
+def fit_certificate_text(value, box, bold=False, orientation="landscape"):
"""Подгоняем реальные строки Mont по обеим сторонам бокса, без обрезки ФИО."""
font = certificate_font(bold)
- width = max(1, box["width"] * 11.22 - 2)
- height = max(1, box["height"] * 7.937 - 2)
+ page_width, page_height = (
+ (793.7, 1122) if orientation == "portrait" else (1122, 793.7)
+ )
+ width = max(1, box["width"] * page_width / 100 - 2)
+ height = max(1, box["height"] * page_height / 100 - 2)
def lines_at(size):
def fits(text):
@@ -344,6 +362,8 @@ def render_pdf(snapshot, assets, name, program_name, number, issued_at):
from weasyprint.text.fonts import FontConfiguration
fonts = Path(settings.BASE_DIR) / "assets/users/user_CV"
+ orientation = snapshot.get("orientation", "landscape")
+ page_width, page_height = page_dimensions(orientation)
def fetch(url):
if url == "certificate-font:regular":
@@ -391,7 +411,9 @@ def src(asset):
)
else:
value = values[key]
- size, lines = fit_certificate_text(value, box, key in ("name", "title"))
+ size, lines = fit_certificate_text(
+ value, box, key in ("name", "title"), orientation
+ )
content = "
".join(escape(line) for line in lines)
elements.append(
f'