#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Календарь ЭТИКА ПРИНТ 2027 — три новых направления (F, G, H).
F — «Год-плакат»: год в две строки во всю высоту шапки, крупная типографика.
G — «Паттерн»: плотная брендовая заливка (полосы + сетка знаков «Э»).
H — «Этикетка»: стопка наклеек с вырубкой, тема материала.
Запуск: python "_сборка/календарь_новые.py"
"""
import importlib.util
import pathlib
import re
ROOT = pathlib.Path('/home/user')
SB = ROOT / '_сборка'
CAL = ROOT / 'Календарь'
_spec = importlib.util.spec_from_file_location('kc', str(SB / 'календарь_квартальный.py'))
kc = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(kc)
ЛОГО_Ч = ROOT / 'Бренд/Логотипы/лого горизонтальное (черное).svg'
ЛОГО_Б = ROOT / 'Бренд/Логотипы/лого горизонтальное (белое).svg'
ЗНАК_Ч = ROOT / 'Бренд/Логотипы/знак (черный).svg'
ЗНАК_Б = ROOT / 'Бренд/Логотипы/знак (белый).svg'
ЗНАК_О = ROOT / 'Бренд/Логотипы/знак (оранжевый).svg'
ГОД = kc.ГОД
ACC = '#EA5709'
INK = '#141414'
УГОЛЬ = '#1E1E1E'
КРЕМ = '#F2EFE9'
БЕЛ = '#FFFFFF'
def b64(p):
return kc.b64(p)
def год_стр(цифры, класс='yr'):
циф = ''.join(f'
{с}' for с, ц in zip(str(ГОД), цифры))
return f'
{циф}
'
# ────────────────────────── F: год-плакат ──────────────────────────
def шапка_F(b):
return f"""
{kc.СЛОГАН}
год
Типография с 2001 года
Полный цикл печати
etika-print.ru
+7 (351) 200-71-30
"""
# ────────────────────────── G: паттерн ──────────────────────────
def знаки_сеткой():
out = []
for row, y in enumerate((6, 56, 106, 156)):
for col, x in enumerate((8, 58, 108, 158, 208, 258)):
out.append(f'
')
return '\n '.join(out)
def смещение(row):
return 0 if row % 2 == 0 else 25
def шапка_G(b):
return f"""
{знаки_сеткой()}
{kc.СЛОГАН}
Типография с 2001 года
Полный цикл печати
{год_стр([БЕЛ, ACC, БЕЛ, ACC])}
год
"""
# ────────────────────────── H: этикетка ──────────────────────────
def этикетка(л, т, ш, в, уг, фон, рамка, z, доп=''):
return (f'
')
def шапка_H(b):
return f"""
{год_стр([INK, БЕЛ, INK, БЕЛ])}
год
{kc.СЛОГАН}
Типография с 2001 года
Полный цикл печати
"""
ШАПКИ = {
'F — год-плакат': (шапка_F, 'dark'),
'G — паттерн': (шапка_G, 'dark'),
'H — этикетка': (шапка_H, 'light'),
}
def стили(L, b, pw, ph):
return f"""
@page {{ size: {pw}mm {ph}mm; margin: 0; }}
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
html {{ -webkit-print-color-adjust: exact; print-color-adjust: exact; }}
body {{ font-family: 'Mont', Arial, sans-serif; }}
.page {{ width: {pw}mm; height: {ph}mm; position: relative; overflow: hidden; background: #fff; }}
.head {{ position: absolute; left: {L - b}mm; top: {L - b}mm; width: {297 + 2 * b}mm;
height: {220 + b}mm; overflow: hidden; }}
.hF {{ background: {INK}; }}
.hG {{ background: {УГОЛЬ}; }}
.hH {{ background: {КРЕМ}; }}
.head .logo {{ position: absolute; z-index: 9; }}
.head .logo img {{ height: 13mm; display: block; }}
.head .slo {{ position: absolute; z-index: 9; font-size: 9pt; font-weight: 700;
letter-spacing: .2em; text-transform: uppercase; color: #fff; }}
.head .tag {{ position: absolute; z-index: 9; font-size: 8.4pt; font-weight: 700;
letter-spacing: .16em; text-transform: uppercase; color: rgba(255,255,255,.8); line-height: 1.6; }}
.head .hair {{ position: absolute; left: 0; right: 0; height: .3mm; background: rgba(255,255,255,.22); z-index: 3; }}
/* F */
.hF .wm {{ position: absolute; right: -70mm; bottom: -70mm; width: 240mm; opacity: .08; z-index: 1; }}
.hF .wm img {{ width: 100%; display: block; }}
.hF .vbar {{ position: absolute; right: 108mm; top: 0; width: 1.2mm; height: 220mm; background: {ACC}; z-index: 3; }}
.hF .yr2 {{ position: absolute; left: 16mm; bottom: -6mm; z-index: 6; font-size: 372pt; font-weight: 800;
letter-spacing: -12px; line-height: .78; }}
.hF .yr2 span {{ display: inline-block; }}
.hF .yrlab {{ position: absolute; z-index: 6; font-size: 30pt;
font-weight: 700; letter-spacing: .1em; color: #fff; text-transform: uppercase; }}
.hF .sq {{ position: absolute; background: {ACC}; z-index: 6; }}
/* G */
.hG .pattern {{ position: absolute; inset: 0; z-index: 1; background-image:
repeating-linear-gradient(-45deg, rgba(234,87,9,.90) 0 7mm, rgba(0,0,0,0) 7mm 30mm),
repeating-linear-gradient(-45deg, rgba(255,255,255,.42) 0 .35mm, rgba(0,0,0,0) .35mm 11mm),
repeating-linear-gradient(45deg, rgba(255,255,255,.14) 0 .3mm, rgba(0,0,0,0) .3mm 8mm); }}
.hG .zm-grid {{ position: absolute; inset: 0; z-index: 2; }}
.hG .zm {{ position: absolute; width: 30mm; height: 30mm; z-index: 2; opacity: .10;
background: url(data:image/svg+xml;base64,{b64(ЗНАК_Б)}) center/contain no-repeat; }}
.hG .plate {{ position: absolute; background: rgba(16,16,16,.88); z-index: 3; }}
.hG .logo-plate {{ position: absolute; left: -10mm; top: -10mm; width: 148mm; height: 62mm;
background: {ACC}; z-index: 4; transform: rotate(-2deg); }}
.hG .yr {{ position: absolute; left: 16mm; bottom: 16mm; z-index: 6; font-size: 300pt;
font-weight: 800; letter-spacing: -6px; line-height: .82; white-space: nowrap; }}
.hG .yr span {{ display: inline-block; }}
.hG .yrlab {{ position: absolute; right: 22mm; z-index: 6; font-size: 26pt; font-weight: 700;
color: #fff; letter-spacing: .1em; }}
/* H */
.hH .cutline {{ position: absolute; inset: 7mm; border: .4mm dashed rgba(20,20,20,.30);
border-radius: 2mm; z-index: 1; }}
.hH .label {{ position: absolute; border-radius: 3mm; }}
.hH .label-sign {{ position: absolute; width: 38mm; z-index: 4; }}
.hH .label-sign img {{ width: 100%; display: block; }}
.hH .label-logo {{ position: absolute; width: 86mm; z-index: 5; }}
.hH .label-logo img {{ width: 100%; display: block; }}
.hH .yr {{ position: absolute; left: 40mm; top: 106mm; z-index: 7; font-size: 250pt;
font-weight: 800; letter-spacing: -6px; line-height: .82; white-space: nowrap; }}
.hH .yr span {{ display: inline-block; }}
.hH .yrlab {{ position: absolute; right: 22mm; z-index: 7; font-size: 24pt; font-weight: 700;
letter-spacing: .1em; }}
.hH .tick {{ position: absolute; width: 14mm; height: 14mm; z-index: 5; }}
/* ── сетки месяцев ── */
.grids {{ position: absolute; top: {L + 220}mm; left: {L + 15}mm; width: 267mm; }}
.month {{ height: 111.25mm; padding: 4.5mm 0 3mm; display: flex; flex-direction: column; }}
.month + .month {{ border-top: 1px solid {kc.LINE}; }}
.mhead {{ display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 3mm; }}
.mru {{ font-size: 19pt; font-weight: 800; letter-spacing: -.4px; color: {kc.INK}; }}
.men {{ font-size: 10pt; font-weight: 600; color: {kc.MUTED}; letter-spacing: .08em; text-transform: uppercase; }}
.mclock, .wrow {{ display: grid; grid-template-columns: 10mm repeat(7, 1fr); }}
.wrow {{ flex: 1; }}
.wrow > div {{ display: flex; align-items: center; justify-content: center; }}
.mclock {{ border-bottom: 1px solid {kc.LINE}; padding-bottom: 1.5mm; margin-bottom: 1.5mm; }}
.wd {{ text-align: center; line-height: 1.15; }}
.wd b {{ display: block; font-size: 8.6pt; font-weight: 700; color: {kc.MUTED}; }}
.wd span {{ font-size: 6.8pt; color: #A9A9A9; }}
.wrow > div {{ font-size: 21pt; font-weight: 600; color: {kc.INK}; }}
.wrow .wk {{ font-size: 7.6pt; font-weight: 600; color: #B4B4B4; }}
.wrow .we {{ color: {ACC}; }}
.wrow .out {{ color: #C9C9C9; font-weight: 400; }}
.wrow .we.out {{ color: #E7C0AA; }}
.imprint {{ position: absolute; left: {L - b}mm; bottom: 0; width: {297 + 2 * b}mm; height: {16 + b}mm;
background: {INK}; color: #fff; display: flex; align-items: center; justify-content: space-between;
padding: 0 20mm {b}mm; font-size: 8.6pt; }}
.imprint b {{ font-weight: 700; }}
.imprint .c {{ text-align: center; }}
.imprint .r {{ text-align: right; }}
.mk {{ position: absolute; background: #000; }}
"""
def файл(имя, полный=True, bleed=False, marks=False):
b = kc.BL if bleed else 0.0
m = kc.MM if marks else 0.0
L = b + m
pw, ph = 297 + 2 * L, 700 + 2 * L
фн, _ = ШАПКИ[имя]
тело = фн(b)
if полный:
тело += f"""
{''.join(kc.сетка_месяца(*м) for м in kc.МЕСЯЦЫ)}
454010, Челябинск, Копейское шоссе, 48/7
etika-print.ru
+7 (351) 200-71-30
"""
метки = '\n ' + kc.marca(L, b, pw, ph) if marks else ''
return f"""
Календарь ЭТИКА ПРИНТ {ГОД} — {имя}
{тело}{метки}
"""
def только_шапка(имя):
h = файл(имя, полный=False)
h = re.sub(r'@page \{ size: [\d.]+mm [\d.]+mm;', '@page { size: 297mm 220mm;', h)
h = re.sub(r'\.page \{ width: [\d.]+mm; height: [\d.]+mm;',
'.page { width: 297mm; height: 220mm;', h)
return h
def main():
from playwright.sync_api import sync_playwright
CAL.mkdir(exist_ok=True)
задачи = []
for имя in ШАПКИ:
задачи.append((f'Календарь ЭТИКА ПРИНТ 2027 — {имя}', файл(имя), 1123, True))
задачи.append((f'{имя} (шапка)', только_шапка(имя), 1123, True))
with sync_playwright() as p:
br = p.chromium.launch()
for имя, html, шир, pdf in задачи:
(CAL / (имя + '.html')).write_text(html, encoding='utf-8')
pg = br.new_page(viewport={'width': шир, 'height': 900}, device_scale_factor=2)
pg.goto((CAL / (имя + '.html')).as_uri())
pg.wait_for_timeout(400)
pg.screenshot(path=str(CAL / (имя + '.png')), full_page=True)
if pdf:
pg.pdf(path=str(CAL / (имя + '.pdf')), print_background=True,
prefer_css_page_size=True)
print('OK :', имя)
pg.close()
br.close()
if __name__ == '__main__':
main()