From ca316bece1dc7c8073d016398e463962d61c3be8 Mon Sep 17 00:00:00 2001 From: Maarten Sijm <9739541+mpsijm@users.noreply.github.com> Date: Sun, 20 Nov 2022 18:11:42 +0100 Subject: [PATCH] [latex] Add command problem_slides --- bin/export.py | 1 + bin/latex.py | 41 ++++++++++--- bin/tools.py | 55 +++++++++++++++-- latex/contest-problem-slide.tex | 17 ++++++ latex/contest-solution.tex | 3 + latex/problem-slides.tex | 101 ++++++++++++++++++++++++++++++++ 6 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 latex/contest-problem-slide.tex create mode 100644 latex/problem-slides.tex diff --git a/bin/export.py b/bin/export.py index 76814b13..82174d22 100644 --- a/bin/export.py +++ b/bin/export.py @@ -258,6 +258,7 @@ def build_contest_zip(problems, zipfiles, outfile, statement_language): ] + list(Path('.').glob(f'contest*.{statement_language}.pdf')) + list(Path('.').glob(f'solutions*.{statement_language}.pdf')) + + list(Path('.').glob(f'problem-slides*.{statement_language}.pdf')) ): if Path(fname).is_file(): zf.write( diff --git a/bin/latex.py b/bin/latex.py index a448dc64..1666b662 100644 --- a/bin/latex.py +++ b/bin/latex.py @@ -4,13 +4,14 @@ import re import shutil import sys +from enum import Enum from pathlib import Path from typing import Optional from colorama import Fore, Style import config -from contest import contest_yaml +from contest import contest_yaml, problems_yaml import problem from util import ( copy_and_substitute, @@ -387,19 +388,27 @@ def find_logo() -> Path: return config.tools_root / 'latex' / 'images' / 'logo-not-found.pdf' +class PdfType(str, Enum): + PROBLEM = 'problem' + PROBLEM_SLIDE = 'problem-slide' + SOLUTION = 'solution' + + def build_contest_pdf( contest: str, problems: list["problem.Problem"], tmpdir: Path, language: str, - solutions=False, + build_type=PdfType.PROBLEM, web=False, ) -> bool: builddir = tmpdir / contest / 'latex' / language builddir.mkdir(parents=True, exist_ok=True) - build_type = 'solution' if solutions else 'problem' - main_file = 'solutions' if solutions else 'contest' + problem_slides = build_type == PdfType.PROBLEM_SLIDE + solutions = build_type == PdfType.SOLUTION + + main_file = 'problem-slides' if problem_slides else 'solutions' if solutions else 'contest' main_file += '-web.tex' if web else '.tex' bar = PrintBar(f'{main_file[:-3]}{language}.pdf') @@ -448,6 +457,8 @@ def build_contest_pdf( else config.tools_root / 'latex' / f'contest-{build_type}.tex' ).read_text() + probyaml = problems_yaml() + for problem in problems: if build_type == 'problem': prepare_problem(problem, language) @@ -465,12 +476,28 @@ def build_contest_pdf( bar.warn(f'solution.{language}.tex not found', problem.name) continue + background = next( + (p['rgb'] for p in probyaml if p['id'] == str(problem.path) and 'rgb' in p), '#ffffff' + )[1:] + # Source: https://github.com/DOMjudge/domjudge/blob/095854650facda41dbb40966e70199840b887e33/webapp/src/Twig/TwigExtension.php#L1056 + foreground = ( + '000000' + if sum(int(background[i : i + 2], 16) for i in range(0, 6, 2)) > 450 + else 'ffffff' + ) + border = "".join( + ("00" + hex(max(0, int(background[i : i + 2], 16) - 64))[2:])[-2:] + for i in range(0, 6, 2) + ) problems_data += substitute( per_problem_data, { 'problemlabel': problem.label, 'problemyamlname': problem.settings.name[language].replace('_', ' '), 'problemauthor': problem.settings.author, + 'problembackground': background, + 'problemforeground': foreground, + 'problemborder': border, 'timelimit': get_tl(problem), 'problemdir': problem.path.absolute().as_posix(), 'problemdirname': problem.name, @@ -492,9 +519,9 @@ def build_contest_pdf( return build_latex_pdf(builddir, Path(main_file), language, bar) -def build_contest_pdfs(contest, problems, tmpdir, lang=None, solutions=False, web=False): +def build_contest_pdfs(contest, problems, tmpdir, lang=None, build_type=PdfType.PROBLEM, web=False): if lang: - return build_contest_pdf(contest, problems, tmpdir, lang, solutions, web) + return build_contest_pdf(contest, problems, tmpdir, lang, build_type, web) """Build contest PDFs for all available languages""" statement_languages = set.intersection(*(set(p.statement_languages) for p in problems)) @@ -519,7 +546,7 @@ def build_contest_pdfs(contest, problems, tmpdir, lang=None, solutions=False, we color_type=MessageType.FATAL, ) return all( - [build_contest_pdf(contest, problems, tmpdir, lang, solutions, web) for lang in languages] + build_contest_pdf(contest, problems, tmpdir, lang, build_type, web) for lang in languages ) diff --git a/bin/tools.py b/bin/tools.py index 0a5c0659..c2fb946b 100755 --- a/bin/tools.py +++ b/bin/tools.py @@ -420,6 +420,26 @@ def build_parser(): pdfparser.add_argument('--web', action='store_true', help='Create a web version of the pdf.') pdfparser.add_argument('-1', action='store_true', help='Only run the LaTeX compiler once.') + # Problem slides + pdfparser = subparsers.add_parser( + 'problem_slides', parents=[global_parser], help='Build the problem slides pdf.' + ) + # pdfparser.add_argument( + # '--all', + # '-a', + # action='store_true', + # help='Create problem statements for individual problems as well.', + # ) + pdfparser.add_argument('--no-timelimit', action='store_true', help='Do not print timelimits.') + pdfparser.add_argument( + '--watch', + '-w', + action='store_true', + help='Continuously compile the pdf whenever a `problem_statement.tex` changes. Note that this does not pick up changes to `*.yaml` configuration files.', + ) + # pdfparser.add_argument('--web', action='store_true', help='Create a web version of the pdf.') + pdfparser.add_argument('-1', action='store_true', help='Only run the LaTeX compiler once.') + # Solution slides solparser = subparsers.add_parser( 'solutions', parents=[global_parser], help='Build the solution slides pdf.' @@ -1059,7 +1079,16 @@ def run_parsed_arguments(args): if action in ['solutions']: success &= latex.build_contest_pdfs( - contest, problems, tmpdir, solutions=True, web=config.args.web + contest, problems, tmpdir, build_type=latex.PdfType.SOLUTION, web=config.args.web + ) + + if action in ['problem_slides']: + success &= latex.build_contest_pdfs( + contest, + problems, + tmpdir, + build_type=latex.PdfType.PROBLEM_SLIDE, + web=config.args.web, ) if action in ['zip']: @@ -1073,11 +1102,27 @@ def run_parsed_arguments(args): contest, problems, tmpdir, statement_language, web=True ) if not config.args.no_solutions: - success &= latex.build_contest_pdfs( - contest, problems, tmpdir, statement_language, solutions=True + success &= latex.build_contest_pdf( + contest, + problems, + tmpdir, + statement_language, + build_type=latex.PdfType.SOLUTION, + ) + success &= latex.build_contest_pdf( + contest, + problems, + tmpdir, + statement_language, + build_type=latex.PdfType.SOLUTION, + web=True, ) - success &= latex.build_contest_pdfs( - contest, problems, tmpdir, statement_language, solutions=True, web=True + success &= latex.build_contest_pdf( + contest, + problems, + tmpdir, + statement_language, + build_type=latex.PdfType.PROBLEM_SLIDE, ) outfile = contest + '.zip' diff --git a/latex/contest-problem-slide.tex b/latex/contest-problem-slide.tex new file mode 100644 index 00000000..235bb078 --- /dev/null +++ b/latex/contest-problem-slide.tex @@ -0,0 +1,17 @@ +\begingroup\graphicspath{{{%problemdir%}/problem_statement/}} + \renewcommand{\problemlabel}{{%problemlabel%}} + \renewcommand{\problemyamlname}{{%problemyamlname%}} + \renewcommand{\problemauthor}{{%problemauthor%}} + \renewcommand{\problembackground}{{%problembackground%}} + \renewcommand{\problemforeground}{{%problemforeground%}} + \renewcommand{\problemborder}{{%problemborder%}} + \renewcommand{\timelimit}{{%timelimit%}} + \input{{%problemdir%}/problem_statement/problem-slide.\lang.tex} + \renewcommand{\problemlabel}{} + \renewcommand{\problemyamlname}{} + \renewcommand{\problemauthor}{} + \renewcommand{\problembackground}{} + \renewcommand{\problemforeground}{} + \renewcommand{\problemborder}{} + \renewcommand{\timelimit}{} +\endgroup diff --git a/latex/contest-solution.tex b/latex/contest-solution.tex index af12fc9c..fd11f9a8 100644 --- a/latex/contest-solution.tex +++ b/latex/contest-solution.tex @@ -5,4 +5,7 @@ \renewcommand{\timelimit}{{%timelimit%}} \input{{%problemdir%}/problem_statement/solution.\lang.tex} \renewcommand{\problemlabel}{} + \renewcommand{\problemyamlname}{} + \renewcommand{\problemauthor}{} + \renewcommand{\timelimit}{} \endgroup diff --git a/latex/problem-slides.tex b/latex/problem-slides.tex new file mode 100644 index 00000000..0f1abc8b --- /dev/null +++ b/latex/problem-slides.tex @@ -0,0 +1,101 @@ +\documentclass[rgb,dvipsnames,aspectratio=169,9pt,t]{beamer} + +\usepackage[T1, OT1]{fontenc} +\DeclareTextSymbolDefault{\dh}{T1} +\usepackage[english]{babel} +\usepackage{lmodern} + +%------------------------------------------------------------------------------- +% The following are required for most problems: +%------------------------------------------------------------------------------- +\usepackage{amsmath,amssymb} +\usepackage{pgf,tikz} +\usepackage{mathrsfs} +\usetikzlibrary{arrows} +\usetikzlibrary{shapes} +\usetikzlibrary{backgrounds} +\usetikzlibrary{patterns} +\usetikzlibrary{positioning} +\usepackage{pgfplots} +\usepackage{pgfplotstable} +\pgfplotsset{compat=1.15} +\usepackage{graphicx} +\usepackage{listings} +%\usepackage{subcaption} +\usepackage{algorithm} +\usepackage[makeroom]{cancel} +\usepackage[noend]{algpseudocode} +\usepackage{standalone} +\usepackage{ifthen} +\usepackage{tcolorbox} +\usepackage[autoplay,controls,loop,poster=last]{animate} + +\lstset{ + backgroundcolor=\color{white}, + tabsize=4, + language=python, + basicstyle=\footnotesize\ttfamily, + breaklines=true, + keywordstyle=\color[rgb]{0, 0, 1}, + commentstyle=\color[rgb]{0, 0.5, 0}, + stringstyle=\color{red} +} + +\newcommand{\timelimit}{0.0s} +\newcommand{\problemlabel}{} % Empty to hide activity chart +\newcommand{\problemauthor}{Problem author} +\newcommand{\problembackground}{} +\newcommand{\problemforeground}{} +\newcommand{\problemborder}{} +% TODO: Clean these up +\newcommand{\problemyamlname}{Problem name} +\newcommand{\fullproblemtitle}{\problemlabel: \problemyamlname} +\newcommand{\problemtitle}{\problemyamlname} + +\usetheme[numbering=none,block=fill]{metropolis} + +\newcommand{\illustration}[3]{ + \begin{column}[T]{#1\textwidth} + \includegraphics[width=\textwidth]{#2} + \ifstrempty{#3}{ + \vspace{-5pt} + }{ + \begin{flushright} + \vspace{-5pt} + \tiny #3 + \end{flushright} + } + \end{column} +} + +\setbeamertemplate{frametitle}{% + \nointerlineskip% + \vspace{1em}% + \begin{minipage}{0.06\paperwidth}% + \begin{tikzpicture} + \definecolor{problembackground}{HTML}{\problembackground} + \definecolor{problemforeground}{HTML}{\problemforeground} + \definecolor{problemborder}{HTML}{\problemborder} + \node[rectangle,rounded corners,thick,minimum size=2em,draw=problemborder,fill=problembackground,text=problemforeground] (0, 0) {\Huge\problemlabel}; + \end{tikzpicture} + \end{minipage}% + \begin{minipage}{0.8\paperwidth}% + \color{black}% + \ifdefempty{\problemlabel}{% + \insertframetitle\strut% + }{% + \problemtitle% + \\[0.3em]% + \tiny% + Time limit: \timelimit{}s% + \quad\quad% + Problem Author: \problemauthor% + \strut% + }% + \end{minipage}% + \hfill% +} + +\begin{document} +\input{./contest-problem-slides.tex} +\end{document}