diff --git a/md2gost/latex/preamble.py b/md2gost/latex/preamble.py index 9d15686..090d717 100644 --- a/md2gost/latex/preamble.py +++ b/md2gost/latex/preamble.py @@ -96,6 +96,37 @@ PREAMBLE = r"""\documentclass[a4paper, 14pt]{extarticle} \end{tabular}% } +\newcommand{\ptpfieldw}[4][4.5cm]{% + \par\noindent + \makebox[#1][l]{#2}% + \begin{tabular}[t]{@{}c@{}}% + \makebox[#4][c]{#3} \\[-1.05em]% + \rule{#4}{0.4pt}% + \end{tabular}\par} + +\newcommand{\ptpfield}[3][4.5cm]{% + \ptpfieldw[#1]{#2}{#3}{\dimexpr\textwidth-#1\relax}} + +\newcommand{\ptpfieldnote}[4][4.5cm]{% + \ptpfield[#1]{#2}{#3}% + \par\vspace{-0.7em}% + \hspace*{#1}\makebox[\dimexpr\textwidth-#1\relax][c]{\footnotesize\itshape #4}\par} + +\newcommand{\ptpfieldpar}[3][4.5cm]{% + \par\noindent + \makebox[#1][l]{#2}% + \begin{tabular}[t]{@{}c@{}}% + \parbox{\dimexpr\textwidth-#1\relax}{\centering #3} \\[-0.55em]% + \rule{\dimexpr\textwidth-#1\relax}{0.4pt}% + \end{tabular}\par} + +\newcommand{\ptpsig}[3]{% + \par\noindent + \begin{tabularx}{\textwidth}{@{}l X c c@{}}% + #1 & & \sigline[4.2cm]{}{\itshape подпись, дата} + & \sigline[4.2cm]{#2}{\itshape #3} \\% + \end{tabularx}\par} + \begin{document}""" END_DOCUMENT = r"\end{document}" + "\n" diff --git a/md2gost/latex/titlepages/_common.py b/md2gost/latex/titlepages/_common.py index 3f50533..52fb1c1 100644 --- a/md2gost/latex/titlepages/_common.py +++ b/md2gost/latex/titlepages/_common.py @@ -36,4 +36,10 @@ def ministry_lines(fm: dict[str, str]) -> str: f"({get('вуз_статус')})>>", f"({get('вуз_аббревиатура')})", ] - return "\n".join(f"\\vspace*{{-0.7ex}}\n\\centerline{{{line}}}" for line in lines) + # Первая строка без отрицательного отступа: иначе она вылезает за верхнюю + # границу рамки, в которую шапку заключает бланк отчёта. + head = f"\\centerline{{{lines[0]}}}" + rest = "\n".join( + f"\\vspace*{{-0.7ex}}\n\\centerline{{{line}}}" for line in lines[1:] + ) + return f"{head}\n{rest}" diff --git a/md2gost/latex/titlepages/ptp_report.py b/md2gost/latex/titlepages/ptp_report.py index be92f23..d321d7d 100644 --- a/md2gost/latex/titlepages/ptp_report.py +++ b/md2gost/latex/titlepages/ptp_report.py @@ -1,5 +1,97 @@ +"""Титульный лист по бланку «ОТЧЁТ ПО УЧЕБНОЙ ПРАКТИКЕ» кафедры ИУ9.""" + from __future__ import annotations +from md2gost.latex.titlepages._common import field, ministry_lines + def render(fm: dict[str, str]) -> str: - raise NotImplementedError + faculty = field(fm, "факультет") + department = field(fm, "кафедра") + student_short = field(fm, "студент") + student_full = field(fm, "студент_полностью") or student_short + group = field(fm, "группа") + practice = field(fm, "тип_практики") + company = field(fm, "предприятие") + team = field(fm, "команда") + repo = field(fm, "хранилище") + advisor = field(fm, "руководитель") + year = field(fm, "год", "2026") + + team_line = f"{team} (хранилище {repo})" if repo else team + + return ( + "\\begin{titlepage}\n" + "\\thispagestyle{empty}\n" + "\n" + "% --- Шапка в рамке с разделителем, как в бланке ---\n" + "\\vspace*{-45pt}\n" + "\\noindent\\begin{tabular}" + "{|@{\\hspace{4pt}}c@{\\hspace{4pt}}|@{\\hspace{4pt}}c@{\\hspace{4pt}}|}\n" + "\\hline\n" + "\\begin{minipage}{0.16\\textwidth}\\centering\\vspace{3pt}\n" + "\\includegraphics[width=0.92\\textwidth]{emblem}\n" + "\\vspace{3pt}\\end{minipage}\n" + "&\n" + "\\begin{minipage}{0.775\\textwidth}\\small\\bfseries\n" + "\\vspace*{2pt}\n" + f"{ministry_lines(fm)}\n" + "\\vspace*{0.5ex}\n" + "\\end{minipage} \\\\\n" + "\\hline\n" + "\\end{tabular}\n" + "\n" + "% --- Полосы ---\n" + "\\vspace{6pt}\n" + "\\noindent\\rule{\\textwidth}{2.3pt}\n" + "\n" + "\\vspace*{-18pt}\n" + "\\noindent\\rule{\\textwidth}{0.4pt}\n" + "\n" + "% --- Факультет и кафедра ---\n" + "\\vspace{2.5ex}\n" + f"\\ptpfield[2.8cm]{{ФАКУЛЬТЕТ}}{{{faculty}}}\n" + "\n" + "\\vspace{1.5ex}\n" + f"\\ptpfield[2.8cm]{{КАФЕДРА}}{{{department}}}\n" + "\n" + "% --- Заголовок ---\n" + "\\vspace{3.5em}\n" + "\\begin{center}\n" + "{\\Large\\bfseries\\underline{ОТЧЁТ ПО УЧЕБНОЙ ПРАКТИКЕ}}\n" + "\\end{center}\n" + "\n" + "% --- Поля ---\n" + "\\vspace{2.5em}\n" + f"\\ptpfieldnote[2.4cm]{{Студент}}{{{student_full}}}{{фамилия, имя, отчество}}\n" + "\n" + "\\vspace{1.8ex}\n" + f"\\ptpfieldw[2.4cm]{{Группа}}{{{group}}}{{5cm}}\n" + "\n" + "\\vspace{1.8ex}\n" + f"\\ptpfield[4.6cm]{{Тип практики}}{{{practice}}}\n" + "\n" + "\\vspace{1.8ex}\n" + f"\\ptpfield[4.6cm]{{Название предприятия}}{{{company}}}\n" + "\n" + "\\vspace{1.8ex}\n" + f"\\ptpfieldpar[2.4cm]{{Команда}}{{{team_line}}}\n" + "\n" + "% --- Подписи ---\n" + "\\vfill\n" + f"\\ptpsig{{Студент}}{{{student_short}}}{{фамилия, и.о.}}\n" + "\n" + "\\vspace{2em}\n" + f"\\ptpsig{{Руководитель практики}}{{{advisor}}}{{фамилия, и.о.}}\n" + "\n" + "% --- Оценка ---\n" + "\\vspace{2em}\n" + "\\noindent Оценка \\rule{0.5\\textwidth}{0.4pt}\n" + "\n" + "\\vfill\n" + "\\begin{center}\n" + f"\\textsl{{{year} г.}}\n" + "\\end{center}\n" + "\n" + "\\end{titlepage}" + ) diff --git a/tests/test_render.py b/tests/test_render.py index 139ef85..5f441f5 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -163,3 +163,71 @@ def test_titlepage_registry_lists_three_layouts() -> None: def test_titlepage_unknown_name_raises() -> None: with pytest.raises(ValueError, match="ptp-report"): render_fm("титульник: несуществующий\n") + + +PTP_REPORT_FM = ( + "титульник: ptp-report\n" + "факультет: Информатика и системы управления (ИУ)\n" + "кафедра: Теоретическая информатика и компьютерные технологии (ИУ9)\n" + "студент: И. И. Камалетдинов\n" + "студент_полностью: Камалетдинов Ильяс Илнарович\n" + "группа: ИУ9-22Б\n" + "тип_практики: проектно-технологическая практика\n" + "предприятие: МГТУ имени Н. Э. Баумана\n" + "команда: Система мониторинга радиоэфира\n" + "хранилище: https://monitor.yss.su/IU9/radio-serv\n" + "руководитель: Д. П. Посевин\n" + "год: 2026\n" +) + + +def test_ptp_report_has_blank_headings() -> None: + tex = render_fm(PTP_REPORT_FM) + for heading in ( + "ОТЧЁТ ПО УЧЕБНОЙ ПРАКТИКЕ", + "Тип практики", + "Название предприятия", + "Команда", + "Оценка", + "Руководитель практики", + ): + assert heading in tex + + +def test_ptp_report_fills_all_fields() -> None: + tex = render_fm(PTP_REPORT_FM) + for value in ( + "Камалетдинов Ильяс Илнарович", + "И. И. Камалетдинов", + "ИУ9-22Б", + "проектно-технологическая практика", + "МГТУ имени Н. Э. Баумана", + "Д. П. Посевин", + "2026", + ): + assert value in tex + + +def test_ptp_report_team_line_includes_repository() -> None: + tex = render_fm(PTP_REPORT_FM) + assert "Система мониторинга радиоэфира (хранилище" in tex + assert "monitor.yss.su/IU9/radio-serv" in tex + + +def test_ptp_report_team_line_without_repository() -> None: + fm = PTP_REPORT_FM.replace( + "хранилище: https://monitor.yss.su/IU9/radio-serv\n", "" + ) + tex = render_fm(fm) + assert "хранилище" not in tex + + +def test_ptp_report_has_no_hardcoded_names() -> None: + """Имена, группы и предприятия приходят только из front matter.""" + import inspect + + from md2gost.latex.titlepages import ptp_report + + source = inspect.getsource(ptp_report) + for forbidden in ("Посевин", "Коновалов", "Камалетдинов", "ИУ9-2"): + assert forbidden not in source