[UPDATE] рефакторинг: handlers, fontspec, Docker optimization

This commit is contained in:
Arseny
2026-07-15 14:09:25 +03:00
parent 9e1d5c3c7e
commit f1cc6f0fa2
9 changed files with 154 additions and 51 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
texlive-xetex \ texlive-xetex \
texlive-lang-cyrillic \ texlive-lang-cyrillic \
texlive-latex-extra \ texlive-latex-extra \
texlive-fonts-extra \ fonts-liberation \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

+15 -7
View File
@@ -16,6 +16,12 @@ UNNUMBERED = {
"СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ", "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ",
} }
NEWPAGE_BEFORE = {
"ВВЕДЕНИЕ",
"ЗАКЛЮЧЕНИЕ",
"СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ",
}
def heading(r: Renderer) -> str: def heading(r: Renderer) -> str:
tok = r.tokens[r.pos] tok = r.tokens[r.pos]
@@ -27,10 +33,15 @@ def heading(r: Renderer) -> str:
r.pos += 2 r.pos += 2
if text.upper() in UNNUMBERED: if text.upper() in UNNUMBERED:
cmd = {1: "section*", 2: "subsection*", 3: "subsubsection*"}.get( prefix = ""
level, "paragraph*" if text.upper() in NEWPAGE_BEFORE:
prefix = "\\clearpage\n"
return (
f"{prefix}"
f"\\begin{{center}}\n"
f"\\fontsize{{14pt}}{{18pt}}\\selectfont \\bfseries {text}\n"
f"\\end{{center}}"
) )
return f"\\{cmd}{{{text}}}"
if level == 1: if level == 1:
r.section += 1 r.section += 1
@@ -47,7 +58,4 @@ def heading(r: Renderer) -> str:
else: else:
num = "" num = ""
cmd = {1: "section", 2: "subsection", 3: "subsubsection"}.get( return f"\\noindent\\textbf{{{num}{text}}}"
level, "paragraph"
)
return f"\\{cmd}{{{num}{text}}}"
+1 -1
View File
@@ -38,7 +38,7 @@ def abbreviations(text: str) -> str:
tabular_rows = [] tabular_rows = []
for abbr, desc in rows: for abbr, desc in rows:
pad = max_abbr - len(abbr) + 2 pad = max_abbr - len(abbr) + 2
tabular_rows.append(f"{abbr} {' ' * pad}& {desc} \\\\") tabular_rows.append(f"{abbr} {' ' * pad}& --- {desc} \\\\")
col_spec = f"@{{\\hspace{{0pt}}}}p{{{max_abbr + 2}em}}@{{\\hspace{{0pt}}}}l" col_spec = f"@{{\\hspace{{0pt}}}}p{{{max_abbr + 2}em}}@{{\\hspace{{0pt}}}}l"
body = "\n".join(tabular_rows) body = "\n".join(tabular_rows)
+9 -5
View File
@@ -1,25 +1,29 @@
PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle} PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle}
\usepackage{geometry} \usepackage{geometry}
\geometry{a4paper,tmargin=2cm,bmargin=2cm,lmargin=3cm,rmargin=1cm} \geometry{a4paper,tmargin=2cm,bmargin=2cm,lmargin=3cm,rmargin=1cm}
\usepackage[T2A]{fontenc} \usepackage{fontspec}
\usepackage[utf8]{inputenc} \setmainfont{Liberation Serif}
\setsansfont{Liberation Sans}
\setmonofont{Liberation Mono}
\usepackage[english, main=russian]{babel} \usepackage[english, main=russian]{babel}
\usepackage{textcomp} \usepackage{textcomp}
\usepackage{indentfirst} \usepackage{indentfirst}
\setlength{\parindent}{1.25cm}
\usepackage{setspace}
\onehalfspacing
\usepackage{graphicx} \usepackage{graphicx}
\graphicspath{ {./images/} } \graphicspath{ {./} {./images/} }
\usepackage{wrapfig} \usepackage{wrapfig}
\linespread{1.3}
\sloppy \sloppy
\clubpenalty=10000 \clubpenalty=10000
\widowpenalty=10000 \widowpenalty=10000
\usepackage{enumitem} \usepackage{enumitem}
\setlist[enumerate,1]{label=\arabic*., leftmargin=1.25cm, itemsep=0pt, parsep=0pt}
\usepackage{caption} \usepackage{caption}
\usepackage{hyperref} \usepackage{hyperref}
\hypersetup {unicode=true} \hypersetup {unicode=true}
\DeclareCaptionLabelSeparator*{emdash}{~--- } \DeclareCaptionLabelSeparator*{emdash}{~--- }
\captionsetup[figure]{labelsep=emdash,font=onehalfspacing,position=bottom} \captionsetup[figure]{labelsep=emdash,font=onehalfspacing,position=bottom}
\usepackage{mathptmx}
\usepackage{listings} \usepackage{listings}
\lstset{ \lstset{
basicstyle=\ttfamily\footnotesize, basicstyle=\ttfamily\footnotesize,
+14 -10
View File
@@ -8,12 +8,13 @@ def render_titlepage(fm: dict[str, str]) -> str:
student = fm.get("студент", "") student = fm.get("студент", "")
advisor = fm.get("преподаватель", "") advisor = fm.get("преподаватель", "")
group = fm.get("группа", "") group = fm.get("группа", "")
year = fm.get("год", "2026")
header_lines = [ header_lines = [
r"\vspace*{-0.7ex}", r"\vspace*{-0.7ex}",
r"\hspace*{-6pt}\centerline{Министерство науки и высшего образования Российской Федерации}", r"\hspace*{-6pt}\centerline{Министерство науки и высшего образования Российской Федерации}",
r"\vspace*{-0.7ex}", r"\vspace*{-0.7ex}",
r"\centerline{Федеральное государственное бюджетное образовательное учреждение }", r"\centerline{Федеральное государственное автономное образовательное учреждение}",
r"\vspace*{-0.7ex}", r"\vspace*{-0.7ex}",
r"\centerline{высшего образования}", r"\centerline{высшего образования}",
r"\vspace*{-0.7ex}", r"\vspace*{-0.7ex}",
@@ -21,6 +22,8 @@ def render_titlepage(fm: dict[str, str]) -> str:
r"\vspace*{-0.7ex}", r"\vspace*{-0.7ex}",
r"\centerline{имени Н.Э. Баумана>>}", r"\centerline{имени Н.Э. Баумана>>}",
r"\vspace*{-0.7ex}", r"\vspace*{-0.7ex}",
r"\centerline{(национальный исследовательский университет)>>}",
r"\vspace*{-0.7ex}",
r"\centerline{(МГТУ им. Н.Э. Баумана)}", r"\centerline{(МГТУ им. Н.Э. Баумана)}",
] ]
header = "\n".join(header_lines) header = "\n".join(header_lines)
@@ -62,23 +65,24 @@ def render_titlepage(fm: dict[str, str]) -> str:
"\\vspace{11em}\n" "\\vspace{11em}\n"
"\n" "\n"
"\\begin{center}\n" "\\begin{center}\n"
f"\\Large {{\\bf {title}}} \\\\\n" f"\\fontsize{{22pt}}{{26pt}}\\selectfont \\bfseries {title} \\\\\n"
"\\end{center}\\normalsize\n" "\\end{center}\n"
"\n" "\n"
"\\vspace{8em}\n" "\\vspace{8em}\n"
"\n" "\n"
"\\begin{flushright}\n" "\\noindent Студент группы " + group + "\n"
f" {{Студент группы {group} {student} \\hspace*{{15pt}}\\\\\n" "\\hfill \\underline{\\hspace{3cm}} \\hfill \\underline{\\hspace{4cm}} \\\\\n"
" \\vspace{2ex}\n" "\\hfill (Группа) \\hfill (Подпись, дата) \\hfill (И.О. Фамилия) \\\\\n"
f" Преподаватель {advisor}\\hspace*{{15pt}}}}\n" "\\vspace{2ex}\n"
"\\end{flushright}\n"
"\n" "\n"
"\\bigskip\n" "\\noindent Преподаватель " + advisor + "\n"
"\\hfill \\underline{\\hspace{3cm}} \\hfill \\underline{\\hspace{4cm}} \\\\\n"
"\\hfill \\hfill (Подпись, дата) \\hfill (И.О. Фамилия)\n"
"\n" "\n"
"\\vfill\n" "\\vfill\n"
"\n" "\n"
"\\begin{center}\n" "\\begin{center}\n"
"\\textsl{Москва 2025}\n" f"\\textsl{{{year} г.}}\n"
"\\end{center}\n" "\\end{center}\n"
"\\end{titlepage}" "\\end{titlepage}"
) )
+23 -8
View File
@@ -7,6 +7,16 @@ from md2gost.parser import Parser
from md2gost.render import Renderer from md2gost.render import Renderer
def run_xelatex(tex_path: Path, output_dir: Path) -> bool:
result = subprocess.run(
["xelatex", "-interaction=nonstopmode",
"-output-directory", str(output_dir), str(tex_path)],
capture_output=True, text=True,
)
pdf_path = output_dir / tex_path.with_suffix(".pdf").name
return pdf_path.exists()
def main(): def main():
cli = argparse.ArgumentParser( cli = argparse.ArgumentParser(
prog="md2gost", prog="md2gost",
@@ -40,16 +50,21 @@ def main():
if not args.no_pdf: if not args.no_pdf:
pdf_path = args.output / args.input.with_suffix(".pdf").name pdf_path = args.output / args.input.with_suffix(".pdf").name
result = subprocess.run( log_path = args.output / "report.log"
["xelatex", "-interaction=nonstopmode", "-output-directory", str(args.output), str(tex_path)],
capture_output=True, text=True, if not run_xelatex(tex_path, args.output):
) print("ошибка: xelatex не собрал PDF", file=sys.stderr)
if not pdf_path.exists(): if log_path.exists():
print(result.stdout[-2000:], file=sys.stderr) for line in log_path.read_text(errors="replace").splitlines():
if line.startswith("!"):
print(f" {line}", file=sys.stderr)
sys.exit(1) sys.exit(1)
if not run_xelatex(tex_path, args.output):
print("ошибка: xelatex (2-й проход) не собрал PDF", file=sys.stderr)
sys.exit(1)
print(f"{pdf_path}") print(f"{pdf_path}")
if result.returncode != 0:
print(" (предупреждения xelatex, PDF собран)", file=sys.stderr)
if __name__ == "__main__": if __name__ == "__main__":
+36 -19
View File
@@ -16,6 +16,34 @@ from md2gost.latex import END_DOCUMENT, PREAMBLE, render_titlepage
from markdown_it.token import Token from markdown_it.token import Token
def _fence(r, tok):
return fence(r, tok)
def _image(r, tok):
return image(r, tok)
def _inline(r, tok):
return render_inline(tok)
_BLOCK = {
"heading_open": heading,
"paragraph_open": paragraph,
"bullet_list_open": bullet_list,
"ordered_list_open": ordered_list,
"table_open": table,
"hr": hr,
}
_INLINE = {
"fence": _fence,
"image": _image,
"inline": _inline,
}
class Renderer: class Renderer:
def __init__(self, tokens: list[Token], front_matter: dict[str, str]) -> None: def __init__(self, tokens: list[Token], front_matter: dict[str, str]) -> None:
self.tokens = tokens self.tokens = tokens
@@ -29,29 +57,18 @@ class Renderer:
def render(self) -> str: def render(self) -> str:
parts: list[str] = [PREAMBLE, render_titlepage(self.fm)] parts: list[str] = [PREAMBLE, render_titlepage(self.fm)]
dispatch = {
"heading_open": lambda: heading(self),
"paragraph_open": lambda: paragraph(self),
"bullet_list_open": lambda: bullet_list(self),
"ordered_list_open": lambda: ordered_list(self),
"table_open": lambda: table(self),
"hr": lambda: hr(self),
"front_matter": None,
}
while self.pos < len(self.tokens): while self.pos < len(self.tokens):
tok = self.tokens[self.pos] tok = self.tokens[self.pos]
prev_pos = self.pos prev_pos = self.pos
tt = tok.type
handler = dispatch.get(tok.type) if tt in _BLOCK:
if handler is not None: result = _BLOCK[tt](self)
parts.append(handler()) parts.append(result)
elif tok.type == "fence": if self.current_heading == "СОДЕРЖАНИЕ":
parts.append(fence(self, tok)) parts.append("\\newpage\n\\tableofcontents\n\\newpage")
elif tok.type == "image": elif tt in _INLINE:
parts.append(image(self, tok)) parts.append(_INLINE[tt](self, tok))
elif tok.type == "inline":
parts.append(render_inline(tok))
else: else:
self.pos += 1 self.pos += 1
continue continue
+55
View File
@@ -0,0 +1,55 @@
---
факультет: «Информатика и системы управления»
кафедра: «Теоретическая информатика и компьютерные технологии»
название: Отчёт о летней практике
студент: А. С. Булдаков
преподаватель: Д. П. Посевин
группа: ИУ9-22Б
---
# АННОТАЦИЯ
В настоящем отчёте описывается разработка серверной части программно-аппаратного комплекса мониторинга радиоэфира, предназначенного для обнаружения источников радиосигнала и вычисления их координат методом триангуляции.
Актуальность разработки обусловлена возрастающей потребностью в контроле радиоэлектронной обстановки для обнаружения неавторизованных передающих устройств.
Разработано серверное приложение на языке C++, включающее TCP-сервер для приёма данных от трёх программно-определяемых радиосистем, модуль вычисления координат источника на основе модели логарифмического затухания сигнала и метода роевой оптимизации, а также графический интерфейс на базе фреймворка Qt. Обмен данными между модулями реализован через потокобезопасную очередь с использованием условной переменной.
# СОДЕРЖАНИЕ
# ПЕРЕЧЕНЬ ПРИНЯТЫХ СОКРАЩЕНИЙ
SDR — Software-Defined Radio — программно-определяемая радиосистема
PSO — Particle Swarm Optimization — метод роевой оптимизации
TCP — Transmission Control Protocol — протокол управления передачей
GUI — Graphical User Interface — графический интерфейс пользователя
# ВВЕДЕНИЕ
Количество радиоэлектронных устройств в повседневной жизни неуклонно растёт. Помимо легитимных передатчиков в радиоэфире появляются устройства, эксплуатация которых несанкционирована или представляет потенциальную угрозу [1]. Для принятия решения о дальнейших действиях относительно обнаруженного источника радиосигнала необходимо не только зафиксировать сам факт его наличия, но и определить координаты передающего устройства [2].
Существующие решения для мониторинга радиоэфира, как правило, представляют собой дорогостоящие комплексные системы, требующие специализированного оборудования и инфраструктуры. Альтернативным подходом является использование компактных программно-определяемых радиосистем, способных передавать данные на обычные вычислительные модули для последующей обработки.
Целью работы является разработка серверной части программно-аппаратного комплекса мониторинга радиоэфира, обеспечивающего приём данных от программно-определяемых радиосистем и вычисление координат источников радиосигнала.
Для достижения поставленной цели необходимо решить следующие задачи:
1. Разработать TCP-сервер для приёма и обработки данных от трёх SDR-приёмников
2. Реализовать алгоритм триангуляции на основе модели логарифмического затухания сигнала и метода роевой оптимизации [3]
3. Разработать графический интерфейс для визуализации результатов пеленгации
4. Обеспечить взаимодействие модулей через потокобезопасную очередь
5. Настроить контейнеризацию приложения с помощью Docker
# Общее описание системы
# Выполненные работы
# ЗАКЛЮЧЕНИЕ
# СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Гилев, И. В. Использование технологии SDR для анализа электромагнитной обстановки в системах связи специального назначения / И. В. Гилев, А. Р. Бушуева // Актуальные проблемы деятельности подразделений уголовно-исполнительной системы : сборник материалов Всероссийской научно-практической конференции. В 3 т., Воронеж, 23 октября 2025 года. – Воронеж: Федеральное казенное образовательное учреждение высшего профессионального образования "Воронежский институт Федеральной службы исполнения наказаний России", Издательско-полиграфический центр «Научная книга», 2025. С. 324-328.
2. Шалаев, А. А. Оценка фактических значений отношения сигнал/шум в радионавигационных системах на основе экспериментальных данных / А. А. Шалаев // Crede Experto: транспорт, общество, образование, язык. – 2025. № 4. С. 61-76.
3. Семенюк, С. С. Определение местоположения источников радиоизлучений разностно-дальномерным методом на основе роевого интеллекта / С. С. Семенюк, А. В. Каблуков, А. С. Григоров // Динамика сложных систем - XXI век. 2024. Т. 18, № 1. С. 51-57.