diff --git a/Dockerfile b/Dockerfile index 359bc90..34a11d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ texlive-xetex \ texlive-lang-cyrillic \ texlive-latex-extra \ - texlive-fonts-extra \ + fonts-liberation \ && rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/emblem.png b/emblem.png new file mode 100644 index 0000000..34db84d Binary files /dev/null and b/emblem.png differ diff --git a/md2gost/handlers/heading.py b/md2gost/handlers/heading.py index f48835f..c2f71d2 100644 --- a/md2gost/handlers/heading.py +++ b/md2gost/handlers/heading.py @@ -16,6 +16,12 @@ UNNUMBERED = { "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ", } +NEWPAGE_BEFORE = { + "ВВЕДЕНИЕ", + "ЗАКЛЮЧЕНИЕ", + "СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ", +} + def heading(r: Renderer) -> str: tok = r.tokens[r.pos] @@ -27,10 +33,15 @@ def heading(r: Renderer) -> str: r.pos += 2 if text.upper() in UNNUMBERED: - cmd = {1: "section*", 2: "subsection*", 3: "subsubsection*"}.get( - level, "paragraph*" + prefix = "" + 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: r.section += 1 @@ -47,7 +58,4 @@ def heading(r: Renderer) -> str: else: num = "" - cmd = {1: "section", 2: "subsection", 3: "subsubsection"}.get( - level, "paragraph" - ) - return f"\\{cmd}{{{num}{text}}}" + return f"\\noindent\\textbf{{{num}{text}}}" diff --git a/md2gost/handlers/text.py b/md2gost/handlers/text.py index 76088b4..5abc3c1 100644 --- a/md2gost/handlers/text.py +++ b/md2gost/handlers/text.py @@ -38,7 +38,7 @@ def abbreviations(text: str) -> str: tabular_rows = [] for abbr, desc in rows: 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" body = "\n".join(tabular_rows) diff --git a/md2gost/latex/preamble.py b/md2gost/latex/preamble.py index 598302c..a02f6ac 100644 --- a/md2gost/latex/preamble.py +++ b/md2gost/latex/preamble.py @@ -1,25 +1,29 @@ PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle} \usepackage{geometry} \geometry{a4paper,tmargin=2cm,bmargin=2cm,lmargin=3cm,rmargin=1cm} -\usepackage[T2A]{fontenc} -\usepackage[utf8]{inputenc} +\usepackage{fontspec} +\setmainfont{Liberation Serif} +\setsansfont{Liberation Sans} +\setmonofont{Liberation Mono} \usepackage[english, main=russian]{babel} \usepackage{textcomp} \usepackage{indentfirst} +\setlength{\parindent}{1.25cm} +\usepackage{setspace} +\onehalfspacing \usepackage{graphicx} -\graphicspath{ {./images/} } +\graphicspath{ {./} {./images/} } \usepackage{wrapfig} -\linespread{1.3} \sloppy \clubpenalty=10000 \widowpenalty=10000 \usepackage{enumitem} +\setlist[enumerate,1]{label=\arabic*., leftmargin=1.25cm, itemsep=0pt, parsep=0pt} \usepackage{caption} \usepackage{hyperref} \hypersetup {unicode=true} \DeclareCaptionLabelSeparator*{emdash}{~--- } \captionsetup[figure]{labelsep=emdash,font=onehalfspacing,position=bottom} -\usepackage{mathptmx} \usepackage{listings} \lstset{ basicstyle=\ttfamily\footnotesize, diff --git a/md2gost/latex/titlepage.py b/md2gost/latex/titlepage.py index a591864..7c02619 100644 --- a/md2gost/latex/titlepage.py +++ b/md2gost/latex/titlepage.py @@ -8,12 +8,13 @@ def render_titlepage(fm: dict[str, str]) -> str: student = fm.get("студент", "") advisor = fm.get("преподаватель", "") group = fm.get("группа", "") + year = fm.get("год", "2026") header_lines = [ r"\vspace*{-0.7ex}", r"\hspace*{-6pt}\centerline{Министерство науки и высшего образования Российской Федерации}", r"\vspace*{-0.7ex}", - r"\centerline{Федеральное государственное бюджетное образовательное учреждение }", + r"\centerline{Федеральное государственное автономное образовательное учреждение}", r"\vspace*{-0.7ex}", r"\centerline{высшего образования}", r"\vspace*{-0.7ex}", @@ -21,6 +22,8 @@ def render_titlepage(fm: dict[str, str]) -> str: r"\vspace*{-0.7ex}", r"\centerline{имени Н.Э. Баумана>>}", r"\vspace*{-0.7ex}", + r"\centerline{(национальный исследовательский университет)>>}", + r"\vspace*{-0.7ex}", r"\centerline{(МГТУ им. Н.Э. Баумана)}", ] header = "\n".join(header_lines) @@ -62,23 +65,24 @@ def render_titlepage(fm: dict[str, str]) -> str: "\\vspace{11em}\n" "\n" "\\begin{center}\n" - f"\\Large {{\\bf {title}}} \\\\\n" - "\\end{center}\\normalsize\n" + f"\\fontsize{{22pt}}{{26pt}}\\selectfont \\bfseries {title} \\\\\n" + "\\end{center}\n" "\n" "\\vspace{8em}\n" "\n" - "\\begin{flushright}\n" - f" {{Студент группы {group} {student} \\hspace*{{15pt}}\\\\\n" - " \\vspace{2ex}\n" - f" Преподаватель {advisor}\\hspace*{{15pt}}}}\n" - "\\end{flushright}\n" + "\\noindent Студент группы " + group + "\n" + "\\hfill \\underline{\\hspace{3cm}} \\hfill \\underline{\\hspace{4cm}} \\\\\n" + "\\hfill (Группа) \\hfill (Подпись, дата) \\hfill (И.О. Фамилия) \\\\\n" + "\\vspace{2ex}\n" "\n" - "\\bigskip\n" + "\\noindent Преподаватель " + advisor + "\n" + "\\hfill \\underline{\\hspace{3cm}} \\hfill \\underline{\\hspace{4cm}} \\\\\n" + "\\hfill \\hfill (Подпись, дата) \\hfill (И.О. Фамилия)\n" "\n" "\\vfill\n" "\n" "\\begin{center}\n" - "\\textsl{Москва 2025}\n" + f"\\textsl{{{year} г.}}\n" "\\end{center}\n" "\\end{titlepage}" ) diff --git a/md2gost/main.py b/md2gost/main.py index ec2f1d3..79cb35c 100644 --- a/md2gost/main.py +++ b/md2gost/main.py @@ -7,6 +7,16 @@ from md2gost.parser import Parser 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(): cli = argparse.ArgumentParser( prog="md2gost", @@ -40,16 +50,21 @@ def main(): if not args.no_pdf: pdf_path = args.output / args.input.with_suffix(".pdf").name - result = subprocess.run( - ["xelatex", "-interaction=nonstopmode", "-output-directory", str(args.output), str(tex_path)], - capture_output=True, text=True, - ) - if not pdf_path.exists(): - print(result.stdout[-2000:], file=sys.stderr) + log_path = args.output / "report.log" + + if not run_xelatex(tex_path, args.output): + print("ошибка: xelatex не собрал PDF", file=sys.stderr) + if log_path.exists(): + for line in log_path.read_text(errors="replace").splitlines(): + if line.startswith("!"): + print(f" {line}", file=sys.stderr) 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}") - if result.returncode != 0: - print(" (предупреждения xelatex, PDF собран)", file=sys.stderr) if __name__ == "__main__": diff --git a/md2gost/render.py b/md2gost/render.py index ea09e3f..e92c06f 100644 --- a/md2gost/render.py +++ b/md2gost/render.py @@ -16,6 +16,34 @@ from md2gost.latex import END_DOCUMENT, PREAMBLE, render_titlepage 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: def __init__(self, tokens: list[Token], front_matter: dict[str, str]) -> None: self.tokens = tokens @@ -29,29 +57,18 @@ class Renderer: def render(self) -> str: 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): tok = self.tokens[self.pos] prev_pos = self.pos + tt = tok.type - handler = dispatch.get(tok.type) - if handler is not None: - parts.append(handler()) - elif tok.type == "fence": - parts.append(fence(self, tok)) - elif tok.type == "image": - parts.append(image(self, tok)) - elif tok.type == "inline": - parts.append(render_inline(tok)) + if tt in _BLOCK: + result = _BLOCK[tt](self) + parts.append(result) + if self.current_heading == "СОДЕРЖАНИЕ": + parts.append("\\newpage\n\\tableofcontents\n\\newpage") + elif tt in _INLINE: + parts.append(_INLINE[tt](self, tok)) else: self.pos += 1 continue diff --git a/report.md b/report.md new file mode 100644 index 0000000..f1527ec --- /dev/null +++ b/report.md @@ -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.