Трек «Метод Дегтярева»: концепция, презентация и рабочие листы
This commit is contained in:
commit
2c79f59064
28 files changed
+2999
No files matched your search
+351
@@ -0,0 +1,351 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from docx import Document
|
||||
from docx.shared import Pt, Cm, RGBColor
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.enum.section import WD_ORIENT
|
||||
from docx.oxml.ns import qn
|
||||
from docx.oxml import OxmlElement
|
||||
|
||||
SRC = 'Метод_Дегтярева_концепция_трека.md'
|
||||
OUT = 'Метод_Дегтярева_концепция_трека.docx'
|
||||
|
||||
GREEN = RGBColor(0x2E, 0x7D, 0x5B)
|
||||
DARK = RGBColor(0x1A, 0x1A, 0x1A)
|
||||
GRAY = RGBColor(0x5F, 0x66, 0x6B)
|
||||
LIGHT_GRAY = RGBColor(0x8A, 0x90, 0x95)
|
||||
ROSE = RGBColor(0xB0, 0x3A, 0x55)
|
||||
|
||||
|
||||
def set_run(run, name='Arial', size=10.5, bold=False, italic=False, color=DARK):
|
||||
run.font.name = name
|
||||
run.font.size = Pt(size)
|
||||
run.font.bold = bold
|
||||
run.font.italic = italic
|
||||
run.font.color.rgb = color
|
||||
rPr = run._element.get_or_add_rPr()
|
||||
rFonts = rPr.find(qn('w:rFonts'))
|
||||
if rFonts is None:
|
||||
rFonts = OxmlElement('w:rFonts')
|
||||
rPr.append(rFonts)
|
||||
rFonts.set(qn('w:ascii'), name)
|
||||
rFonts.set(qn('w:hAnsi'), name)
|
||||
rFonts.set(qn('w:eastAsia'), name)
|
||||
rFonts.set(qn('w:cs'), name)
|
||||
|
||||
|
||||
def shade(cell, hex_color):
|
||||
tcPr = cell._tc.get_or_add_tcPr()
|
||||
shd = OxmlElement('w:shd')
|
||||
shd.set(qn('w:val'), 'clear')
|
||||
shd.set(qn('w:color'), 'auto')
|
||||
shd.set(qn('w:fill'), hex_color)
|
||||
tcPr.append(shd)
|
||||
|
||||
|
||||
def cell_margins(table, top=45, bottom=45, left=80, right=80):
|
||||
tblPr = table._tbl.tblPr
|
||||
mar = OxmlElement('w:tblCellMar')
|
||||
for tag, val in (('top', top), ('left', left), ('bottom', bottom), ('right', right)):
|
||||
el = OxmlElement('w:' + tag)
|
||||
el.set(qn('w:w'), str(val))
|
||||
el.set(qn('w:type'), 'dxa')
|
||||
mar.append(el)
|
||||
tblPr.append(mar)
|
||||
|
||||
|
||||
def table_borders(table, color='D9E2DD', size=4):
|
||||
tblPr = table._tbl.tblPr
|
||||
borders = OxmlElement('w:tblBorders')
|
||||
for edge in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
|
||||
el = OxmlElement('w:' + edge)
|
||||
el.set(qn('w:val'), 'single')
|
||||
el.set(qn('w:sz'), str(size))
|
||||
el.set(qn('w:space'), '0')
|
||||
el.set(qn('w:color'), color)
|
||||
borders.append(el)
|
||||
tblPr.append(borders)
|
||||
|
||||
|
||||
def para_bottom_border(paragraph, color='2E7D5B', size=8):
|
||||
pPr = paragraph._p.get_or_add_pPr()
|
||||
pBdr = OxmlElement('w:pBdr')
|
||||
bottom = OxmlElement('w:bottom')
|
||||
bottom.set(qn('w:val'), 'single')
|
||||
bottom.set(qn('w:sz'), str(size))
|
||||
bottom.set(qn('w:space'), '4')
|
||||
bottom.set(qn('w:color'), color)
|
||||
pBdr.append(bottom)
|
||||
pPr.append(pBdr)
|
||||
|
||||
|
||||
def para_left_border(paragraph, color='B03A55', size=18):
|
||||
pPr = paragraph._p.get_or_add_pPr()
|
||||
pBdr = OxmlElement('w:pBdr')
|
||||
left = OxmlElement('w:left')
|
||||
left.set(qn('w:val'), 'single')
|
||||
left.set(qn('w:sz'), str(size))
|
||||
left.set(qn('w:space'), '8')
|
||||
left.set(qn('w:color'), color)
|
||||
pBdr.append(left)
|
||||
pPr.append(pBdr)
|
||||
|
||||
|
||||
def add_page_number(paragraph):
|
||||
run = paragraph.add_run()
|
||||
fld1 = OxmlElement('w:fldChar')
|
||||
fld1.set(qn('w:fldCharType'), 'begin')
|
||||
instr = OxmlElement('w:instrText')
|
||||
instr.set(qn('xml:space'), 'preserve')
|
||||
instr.text = 'PAGE'
|
||||
fld2 = OxmlElement('w:fldChar')
|
||||
fld2.set(qn('w:fldCharType'), 'end')
|
||||
run._r.append(fld1)
|
||||
run._r.append(instr)
|
||||
run._r.append(fld2)
|
||||
set_run(run, size=8, color=LIGHT_GRAY)
|
||||
|
||||
|
||||
def parse_inline(text):
|
||||
parts = re.split(r'(\*\*.*?\*\*|_.*?_)', text)
|
||||
out = []
|
||||
for p in parts:
|
||||
if not p:
|
||||
continue
|
||||
if p.startswith('**') and p.endswith('**') and len(p) > 4:
|
||||
out.append((p[2:-2], True, False))
|
||||
elif p.startswith('_') and p.endswith('_') and len(p) > 2:
|
||||
out.append((p[1:-1], False, True))
|
||||
else:
|
||||
out.append((p, False, False))
|
||||
return out
|
||||
|
||||
|
||||
def add_rich_paragraph(doc, text, style=None, size=10.5, color=DARK, bold=False,
|
||||
italic=False, align=None, space_before=0, space_after=4,
|
||||
left_indent=None, first_line_indent=None, line_spacing=1.15):
|
||||
p = doc.add_paragraph(style=style) if style else doc.add_paragraph()
|
||||
pf = p.paragraph_format
|
||||
pf.space_before = Pt(space_before)
|
||||
pf.space_after = Pt(space_after)
|
||||
pf.line_spacing = line_spacing
|
||||
if align is not None:
|
||||
p.alignment = align
|
||||
if left_indent is not None:
|
||||
pf.left_indent = Cm(left_indent)
|
||||
if first_line_indent is not None:
|
||||
pf.first_line_indent = Cm(first_line_indent)
|
||||
for chunk, b, i in parse_inline(text):
|
||||
r = p.add_run(chunk)
|
||||
set_run(r, size=size, bold=(bold or b), italic=(italic or i), color=color)
|
||||
return p
|
||||
|
||||
|
||||
def add_heading(doc, text, level):
|
||||
p = doc.add_paragraph()
|
||||
pf = p.paragraph_format
|
||||
pf.keep_with_next = True
|
||||
if level == 1:
|
||||
pf.space_before = Pt(14)
|
||||
pf.space_after = Pt(5)
|
||||
r = p.add_run(text)
|
||||
set_run(r, name='Georgia', size=15, bold=True, color=GREEN)
|
||||
para_bottom_border(p, color='CFE3D9', size=6)
|
||||
else:
|
||||
pf.space_before = Pt(10)
|
||||
pf.space_after = Pt(3)
|
||||
r = p.add_run(text)
|
||||
set_run(r, name='Arial', size=11.5, bold=True, color=DARK)
|
||||
return p
|
||||
|
||||
|
||||
def add_table(doc, rows, keep_together=True):
|
||||
ncols = len(rows[0])
|
||||
table = doc.add_table(rows=len(rows), cols=ncols)
|
||||
table.alignment = 1
|
||||
table.autofit = True
|
||||
table_borders(table)
|
||||
cell_margins(table)
|
||||
for ri, row in enumerate(rows):
|
||||
tr = table.rows[ri]._tr
|
||||
trPr = tr.get_or_add_trPr()
|
||||
cantSplit = OxmlElement('w:cantSplit')
|
||||
trPr.append(cantSplit)
|
||||
if ri == 0:
|
||||
tblHeader = OxmlElement('w:tblHeader')
|
||||
trPr.append(tblHeader)
|
||||
for ci, cell_text in enumerate(row):
|
||||
cell = table.cell(ri, ci)
|
||||
cell.text = ''
|
||||
p = cell.paragraphs[0]
|
||||
p.paragraph_format.space_before = Pt(0)
|
||||
p.paragraph_format.space_after = Pt(0)
|
||||
p.paragraph_format.line_spacing = 1.0
|
||||
if keep_together and ri < len(rows) - 1:
|
||||
p.paragraph_format.keep_with_next = True
|
||||
for chunk, b, i in parse_inline(cell_text):
|
||||
r = p.add_run(chunk)
|
||||
if ri == 0:
|
||||
set_run(r, size=8.8, bold=True, color=GREEN)
|
||||
else:
|
||||
set_run(r, size=8.8, bold=b, italic=i, color=DARK)
|
||||
if ri == 0:
|
||||
shade(cell, 'EAF4EF')
|
||||
elif ri % 2 == 0:
|
||||
shade(cell, 'F7FAF8')
|
||||
doc.add_paragraph().paragraph_format.space_after = Pt(2)
|
||||
return table
|
||||
|
||||
|
||||
def build():
|
||||
doc = Document()
|
||||
|
||||
# Page setup
|
||||
section = doc.sections[0]
|
||||
section.page_width = Cm(21.0)
|
||||
section.page_height = Cm(29.7)
|
||||
section.top_margin = Cm(2.0)
|
||||
section.bottom_margin = Cm(2.0)
|
||||
section.left_margin = Cm(2.2)
|
||||
section.right_margin = Cm(2.2)
|
||||
|
||||
# Base styles
|
||||
normal = doc.styles['Normal']
|
||||
normal.font.name = 'Arial'
|
||||
normal.font.size = Pt(10.5)
|
||||
normal.font.color.rgb = DARK
|
||||
normal.paragraph_format.line_spacing = 1.15
|
||||
normal.paragraph_format.space_after = Pt(4)
|
||||
|
||||
# Footer
|
||||
footer_p = section.footer.paragraphs[0]
|
||||
footer_p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
r = footer_p.add_run('Метод Дегтярева • Форум молодёжи Донбасса • ')
|
||||
set_run(r, size=8, color=LIGHT_GRAY)
|
||||
add_page_number(footer_p)
|
||||
|
||||
# Cover block
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(2)
|
||||
r = p.add_run('ФОРУМ МОЛОДЁЖИ ДОНБАССА «ФМД: У НАС ТАК ПРИНЯТО»')
|
||||
set_run(r, size=8.5, bold=True, color=GRAY)
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(10)
|
||||
p.paragraph_format.space_after = Pt(2)
|
||||
r = p.add_run('ТРЕК «МЕТОД ДЕГТЯРЕВА»')
|
||||
set_run(r, name='Georgia', size=26, bold=True, color=DARK)
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(8)
|
||||
r = p.add_run('Цифра, у которой есть хозяин')
|
||||
set_run(r, name='Georgia', size=15, italic=True, color=GREEN)
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(1)
|
||||
r = p.add_run('Смена 1 «ПСБ Патриот» • 10–13 ноября 2026')
|
||||
set_run(r, size=9.5, color=GRAY)
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(10)
|
||||
r = p.add_run('Эксперт и наставник трека: Бойко К.С.')
|
||||
set_run(r, size=9.5, color=GRAY)
|
||||
para_bottom_border(p, color='2E7D5B', size=10)
|
||||
|
||||
# Parse markdown from the first content heading
|
||||
with open(SRC, 'r', encoding='utf-8') as f:
|
||||
lines = f.read().splitlines()
|
||||
|
||||
start = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith('## Короткая версия'):
|
||||
start = i
|
||||
break
|
||||
|
||||
i = start
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
stripped = line.strip()
|
||||
|
||||
if not stripped:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped == '---':
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(2)
|
||||
p.paragraph_format.space_after = Pt(2)
|
||||
para_bottom_border(p, color='E3E8E5', size=4)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped.startswith('### '):
|
||||
add_heading(doc, stripped[4:].strip(), 2)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped.startswith('## '):
|
||||
add_heading(doc, stripped[3:].strip(), 1)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped.startswith('# '):
|
||||
add_heading(doc, stripped[2:].strip(), 1)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Table
|
||||
if stripped.startswith('|') and stripped.endswith('|'):
|
||||
rows = []
|
||||
while i < len(lines):
|
||||
s = lines[i].strip()
|
||||
if not (s.startswith('|') and s.endswith('|')):
|
||||
break
|
||||
cells = [c.strip() for c in s.strip('|').split('|')]
|
||||
if all(re.fullmatch(r':?-{2,}:?', c) for c in cells):
|
||||
i += 1
|
||||
continue
|
||||
rows.append(cells)
|
||||
i += 1
|
||||
if rows:
|
||||
add_table(doc, rows, keep_together=(len(rows) <= 7))
|
||||
continue
|
||||
|
||||
# Blockquote
|
||||
if stripped.startswith('> '):
|
||||
text = stripped[2:].strip()
|
||||
p = add_rich_paragraph(doc, text, size=10.5, italic=True, color=GRAY,
|
||||
left_indent=0.8, space_before=2, space_after=6)
|
||||
para_left_border(p, color='B03A55', size=14)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Bullet
|
||||
if stripped.startswith('- '):
|
||||
text = stripped[2:].strip()
|
||||
p = add_rich_paragraph(doc, '• ' + text, size=10.5,
|
||||
left_indent=0.55, first_line_indent=-0.55,
|
||||
space_after=2)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Numbered list
|
||||
m = re.match(r'^(\d+)\.\s+(.*)$', stripped)
|
||||
if m:
|
||||
num, text = m.group(1), m.group(2)
|
||||
p = add_rich_paragraph(doc, f'{num}. ' + text, size=10.5,
|
||||
left_indent=0.6, first_line_indent=-0.6,
|
||||
space_after=2)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Regular paragraph
|
||||
add_rich_paragraph(doc, stripped, size=10.5, space_after=4)
|
||||
i += 1
|
||||
|
||||
doc.save(OUT)
|
||||
print('saved', OUT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
build()
|
||||
Reference in new issue
Block a user