%% %% magicthegathering.sty -- LaTeX support for Magic: The Gathering %% %% Copyright (C) 2024--2026 Hypergeomancer %% %% This work may be distributed and/or modified under the conditions %% of the LaTeX Project Public License, either version 1.3c of this %% license or (at your option) any later version. %% The latest version of this license is in %% %% https://www.latex-project.org/lppl/lppl-1-3c/ %% %% and version 1.3c or later is part of all distributions of LaTeX %% version 2008 or later. %% %% This work has the LPPL maintenance status `maintained'. %% The Current Maintainer of this work is Hypergeomancer. %% %% Magic: The Gathering, the mana symbols, the tap symbol, the set %% symbols, and all associated card names and game elements are %% trademarks and/or copyrighted materials of Wizards of the Coast LLC %% and/or its affiliates. All rights in such materials are reserved %% by Wizards of the Coast. This package is an unofficial fan project %% and is not produced, endorsed, supported, or affiliated with %% Wizards of the Coast. %% %% Mana and set symbol SVG artwork is from the Mana and Keyrune %% projects by Andrew Gioia, released under the SIL Open Font %% Licence 1.1. %% %% Symbol artwork last updated: December 2025. %% Newer sets may not yet be included. %% \NeedsTeXFormat{LaTeX2e}[2020/10/01] \ProvidesPackage{magicthegathering} [2026/03/15 v1.0 Magic: The Gathering typesetting (Hypergeomancer)] %% ============================================================ %% 1. PACKAGE OPTIONS %% ============================================================ \RequirePackage{pgfopts} \newif\if@mtg@hyperref \@mtg@hyperreftrue \newif\if@mtg@color \@mtg@colortrue \newif\if@mtg@links \@mtg@linkstrue \pgfkeys{ /magicthegathering/.cd, hyperref/.is if = @mtg@hyperref, colorlinks/.is if = @mtg@color, activelinks/.is if = @mtg@links, symbolpath/.store in = \mtg@symbolpath, setpath/.store in = \mtg@setpath, hyperref = true, colorlinks = true, activelinks = true, symbolpath = {symbols}, setpath = {sets}, } \ProcessPgfOptions{/magicthegathering} %% ============================================================ %% 2. REQUIRED PACKAGES %% ============================================================ \RequirePackage{graphicx} \RequirePackage{svg} \RequirePackage{xstring} \RequirePackage{booktabs} \RequirePackage{array} \RequirePackage{multicol} \RequirePackage{enumitem} \RequirePackage{xcolor} \RequirePackage{expl3} \RequirePackage{xparse} \if@mtg@hyperref \RequirePackage{hyperref} \if@mtg@color \hypersetup{ colorlinks = true, linkcolor = black, urlcolor = blue!70!black, pdfborder = {0 0 0}, } \fi \fi %% ============================================================ %% 3. LENGTHS %% ============================================================ \newlength{\mtg@symht} \setlength{\mtg@symht}{0.8em} \newlength{\mtg@setsymht} \setlength{\mtg@setsymht}{1em} %% ============================================================ %% 4. IMPLEMENTATION (single ExplSyntaxOn block) %% ============================================================ \ExplSyntaxOn %% --- Internal helpers --- %% Graphic loader: prefers .pdf, falls back to .svg. \cs_new_protected:Nn \mtg__graphic:nnn { \file_if_exist:nTF { #2 / #3 .pdf } { \includegraphics [ height = #1 ] { #2 / #3 .pdf } } { \includesvg [ height = #1 ] { #2 / #3 } } } %% ----------------------------------------------------------------- %% 4a. MANA SYMBOLS %% ----------------------------------------------------------------- \NewDocumentCommand \setmanasymbolsize { m } { \setlength { \mtg@symht } {#1} } \NewDocumentCommand \mana { m } { \raisebox { -0.2ex } { \mtg__graphic:nnn { \the\mtg@symht } { \mtg@symbolpath } {#1} } } \NewDocumentCommand \manacost { m } { \seq_set_split:Nnn \l_tmpa_seq { ~ } {#1} \seq_map_inline:Nn \l_tmpa_seq { \tl_if_empty:nF {##1} { \mana {##1} } } } \NewDocumentCommand \manaW {} { \mana{W} } \NewDocumentCommand \manaU {} { \mana{U} } \NewDocumentCommand \manaB {} { \mana{B} } \NewDocumentCommand \manaR {} { \mana{R} } \NewDocumentCommand \manaG {} { \mana{G} } \NewDocumentCommand \manaC {} { \mana{C} } %% ----------------------------------------------------------------- %% 4b. SET SYMBOLS %% ----------------------------------------------------------------- \NewDocumentCommand \setsetsymbolsize { m } { \setlength { \mtg@setsymht } {#1} } \NewDocumentCommand \setsymbol { m } { \raisebox { -0.4ex } { \mtg__graphic:nnn { \the\mtg@setsymht } { \mtg@setpath } {#1} } } %% ----------------------------------------------------------------- %% 4c. CARD LINKS -- defined after \ExplSyntaxOff; see below. %% ----------------------------------------------------------------- %% ----------------------------------------------------------------- %% 4d. MATCHUP ENVIRONMENT (sideboard guide) %% ----------------------------------------------------------------- \seq_new:N \l__mtg_in_seq \seq_new:N \l__mtg_out_seq \int_new:N \l__mtg_matchup_len_int \tl_new:N \g__mtg_matchup_body_tl \NewDocumentCommand \initem { m } { \seq_put_right:Nn \l__mtg_in_seq {#1} } \NewDocumentCommand \outitem { m } { \seq_put_right:Nn \l__mtg_out_seq {#1} } \cs_new_protected:Nn \__mtg_sort_seq:N { \seq_sort:Nn #1 { \str_compare:nNnTF {##1} > {##2} { \sort_return_swapped: } { \sort_return_same: } } } \NewDocumentEnvironment { matchup } {} { \seq_clear:N \l__mtg_in_seq \seq_clear:N \l__mtg_out_seq } { \__mtg_sort_seq:N \l__mtg_in_seq \__mtg_sort_seq:N \l__mtg_out_seq \int_set:Nn \l__mtg_matchup_len_int { \int_max:nn { \seq_count:N \l__mtg_in_seq } { \seq_count:N \l__mtg_out_seq } } \tl_gclear:N \g__mtg_matchup_body_tl \int_step_inline:nn { \l__mtg_matchup_len_int } { \tl_gput_right:Nx \g__mtg_matchup_body_tl { \seq_item:Nn \l__mtg_in_seq {##1} & \seq_item:Nn \l__mtg_out_seq {##1} \\ } } \begin{center} \begin{tabular}{ @{} l @{\hspace{2em}} l @{} } \toprule \multicolumn{1}{c}{\textbf{IN}} & \multicolumn{1}{c}{\textbf{OUT}} \\ \midrule \tl_use:N \g__mtg_matchup_body_tl \bottomrule \end{tabular} \end{center} } %% ----------------------------------------------------------------- %% 4e. DECK-LIST IMPORT %% ----------------------------------------------------------------- \seq_new:N \l__mtg_main_seq \seq_new:N \l__mtg_side_seq \bool_new:N \l__mtg_in_side_bool \int_new:N \l__mtg_main_total_int \int_new:N \l__mtg_side_total_int \cs_new_protected:Nn \__mtg_deck_reset: { \seq_clear:N \l__mtg_main_seq \seq_clear:N \l__mtg_side_seq \int_zero:N \l__mtg_main_total_int \int_zero:N \l__mtg_side_total_int \bool_set_false:N \l__mtg_in_side_bool } \cs_new_protected:Nn \__mtg_deck_parse_line:n { \tl_set:Nn \l_tmpa_tl {#1} \tl_trim_spaces:N \l_tmpa_tl \str_if_eq:VnTF \l_tmpa_tl { SIDEBOARD } { \bool_set_true:N \l__mtg_in_side_bool } { \tl_if_empty:NF \l_tmpa_tl { \seq_set_split:NnV \l_tmpb_seq { ~ } \l_tmpa_tl \tl_set:Nx \l_tmpc_tl { \seq_item:Nn \l_tmpb_seq {1} } \seq_pop_left:NN \l_tmpb_seq \l_tmpd_tl \tl_set:Nx \l_tmpe_tl { \seq_use:Nn \l_tmpb_seq { ~ } } \bool_if:NTF \l__mtg_in_side_bool { \int_add:Nn \l__mtg_side_total_int { \l_tmpc_tl } \seq_put_right:Nx \l__mtg_side_seq { \tl_use:N \l_tmpc_tl \c_space_tl \tl_use:N \l_tmpe_tl } } { \int_add:Nn \l__mtg_main_total_int { \l_tmpc_tl } \seq_put_right:Nx \l__mtg_main_seq { \tl_use:N \l_tmpc_tl \c_space_tl \tl_use:N \l_tmpe_tl } } } } } \cs_new_protected:Nn \__mtg_deck_format_entry:n { \seq_set_split:Nnn \l_tmpa_seq { ~ } {#1} \tl_set:Nx \l_tmpc_tl { \seq_item:Nn \l_tmpa_seq {1} } \seq_pop_left:NN \l_tmpa_seq \l_tmpd_tl \tl_set:Nx \l_tmpe_tl { \seq_use:Nn \l_tmpa_seq { ~ } } \item[] \tl_use:N \l_tmpc_tl \c_space_tl \card { \tl_use:N \l_tmpe_tl } } \msg_new:nnn { mtg } { deck-not-found } { Deck-list~file~'#1'~not~found.~Check~the~file~path. } \NewDocumentCommand \insertdeck { m } { \__mtg_deck_reset: \file_if_exist:nTF {#1} { \ior_open:Nn \g_tmpa_ior {#1} \ior_map_inline:Nn \g_tmpa_ior { \__mtg_deck_parse_line:n {##1} } \ior_close:N \g_tmpa_ior } { \msg_error:nnn { mtg } { deck-not-found } {#1} } \noindent\rule{\linewidth}{0.4pt}\par\nobreak \begin{multicols}{2} \begin{itemize}[leftmargin=*,itemsep=0pt,parsep=1pt,topsep=2pt] \item[] \textbf{Maindeck}~(\int_use:N \l__mtg_main_total_int) \seq_map_function:NN \l__mtg_main_seq \__mtg_deck_format_entry:n \end{itemize} \int_compare:nNnT { \l__mtg_side_total_int } > { 0 } { \vfill\columnbreak \begin{itemize}[leftmargin=*,itemsep=0pt,parsep=1pt,topsep=2pt] \item[] \textbf{Sideboard}~(\int_use:N \l__mtg_side_total_int) \seq_map_function:NN \l__mtg_side_seq \__mtg_deck_format_entry:n \end{itemize} } \end{multicols} \noindent\rule{\linewidth}{0.4pt}\par } %% ----------------------------------------------------------------- %% 4f. MATCH RESULTS TABLE %% ----------------------------------------------------------------- \seq_new:N \g__mtg_results_seq \int_new:N \g__mtg_wins_int \int_new:N \g__mtg_losses_int \int_new:N \g__mtg_draws_int \tl_new:N \g__mtg_results_body_tl \NewDocumentCommand \matchresult { m m m m m } { \seq_gput_right:Nn \g__mtg_results_seq { #1 & #2 & #3 & #4 & #5 } \str_case:nn {#5} { { Win } { \int_gincr:N \g__mtg_wins_int } { Loss } { \int_gincr:N \g__mtg_losses_int } { Draw } { \int_gincr:N \g__mtg_draws_int } } } \NewDocumentEnvironment { matchresults } {} { \seq_gclear:N \g__mtg_results_seq \tl_gclear:N \g__mtg_results_body_tl \int_gzero:N \g__mtg_wins_int \int_gzero:N \g__mtg_losses_int \int_gzero:N \g__mtg_draws_int } { \seq_map_inline:Nn \g__mtg_results_seq { \tl_gput_right:Nn \g__mtg_results_body_tl { ##1 \\ } } \begin{center} \begin{tabular}{ @{} c l l c c @{} } \toprule \textbf{Round} & \textbf{Opponent} & \textbf{Deck} & \textbf{Score} & \textbf{Result} \\ \midrule \tl_use:N \g__mtg_results_body_tl \bottomrule \end{tabular} \end{center} \medskip \noindent \textbf{Record:}~% \int_use:N \g__mtg_wins_int --\int_use:N \g__mtg_losses_int --\int_use:N \g__mtg_draws_int \quad (\int_eval:n { \g__mtg_wins_int + \g__mtg_losses_int + \g__mtg_draws_int } ~{$\ $}matches) } \ExplSyntaxOff %% ----------------------------------------------------------------- %% 5. CARD LINKS (outside ExplSyntaxOn to avoid catcode conflicts %% between expl3 and hyperref) %% %% When activelinks=false, \card produces plain \textit and %% \cardlink produces plain text with no hyperlink. %% ----------------------------------------------------------------- \makeatletter \newcommand*{\card}[1]{% \if@mtg@links \StrSubstitute{#1}{ }{+}[\mtg@cardurl]% \href{https://scryfall.com/search?q=\mtg@cardurl}{\textit{#1}}% \else \textit{#1}% \fi } \newcommand*{\cardlink}[2]{% \if@mtg@links \StrSubstitute{#1}{ }{+}[\mtg@cardurl]% \href{https://scryfall.com/search?q=\mtg@cardurl}{#2}% \else #2% \fi } \makeatother %% ============================================================ \endinput %% %% End of file `magicthegathering.sty'.