% \iffalse meta-comment
%
% Copyright (c) 2021 Daniel Schmitz
%
% Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
% associated documentation files (the "Software"), to deal in the Software without restriction,
% including without limitation the rights to use, copy, modify, merge, publish, distribute,
% sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
% furnished to do so, subject to the following conditions:
%
% The above copyright notice and this permission notice shall be included in all copies or substantial
% portions of the Software.
%
% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
% NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
% DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
% OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
%
% \fi
% \iffalse
%<*driver>
\documentclass[full]{l3doc}
\usepackage[height=1.5\baselineskip]{mahjong}
\usepackage{tabularx}
\usepackage{array}
\usepackage{booktabs}
\usepackage{multicol}
\usepackage{cleveref}
\usepackage{gensymb}
\renewcommand{\arraystretch}{1.8}
\parskip 6pt
\parindent 0pt
\EnableCrossrefs
\CodelineIndex
\RecordChanges
\begin{document}
\DocInput{mahjong.dtx} \end{document}
%</driver>
% \fi
% \CharacterTable
% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z
% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z
% Digits \0\1\2\3\4\5\6\7\8\9
% Exclamation \! Double quote \" Hash (number) \#
% Dollar \$ Percent \% Ampersand \&
% Acute accent \' Left paren \( Right paren \)
% Asterisk \* Plus \+ Comma \,
% Minus \- Point \. Solidus \/
% Colon \: Semicolon \; Less than \<
% Equals \= Greater than \> Question mark \?
% Commercial at \@ Left bracket \[ Backslash \\
% Right bracket \] Circumflex \^ Underscore \_
% Grave accent \` Left brace \{ Vertical bar \|
% Right brace \} Tilde \~}
% \changes{v0.5}{2021/04/07}{First working version, minimal error handling}
% \changes{v0.9}{2021/04/11}{Fully functional}
% \changes{v1.0}{2021/04/14}{First complete release}
% \changes{v1.0.1}{2021/04/16}{Added package prefix to filenames}
% \changes{v1.1}{2025/01/06}{Added feature to control size of symbols. Adjusted vertical alignment.}
%
% \GetFileInfo{mahjong.sty}
% \DoNotIndex{\#,\$,\%,\&,\@,\\,\{,\},\^,\_,\~,\ }
% \DoNotIndex{\advance,\begingroup,\catcode,\closein}
% \DoNotIndex{\closeout,\day,\def,\edef,\else,\empty,\endgroup}
% \title{The \textsf{mahjong} package\thanks{This document corresponds to \textsf{mahjong}~\fileversion, dated~\filedate}}
% \author{Daniel Schmitz \\ \texttt{
[email protected]}}
%
% \maketitle
% \tableofcontents
% \begin{abstract}
% The \textsf{mahjong} package provides a \LaTeXe{} and \LaTeX~3 interface for typesetting mahjong tiles using an extended version of MPSZ algebraic notation.
% Features include spaces, rotated, blank, and concealed tiles, as well as red fives.
% The size of the mahjong tiles and their symbols can be controlled using package options and optional arguments.
% It is primarily aimed at Riichi (aka. Japanese) Mahjong but can be used to typeset any style of mahjong, save for flower tiles.
% \end{abstract}
%
% \begin{documentation}
% \section{Introduction}
% Mahjong is a tile-based game originating from China which is popular in East and South-East Asia and has since spread throughout the world.
% The \textsf{mahjong} package provides an interface for typesetting mahjong tiles and hands using MPSZ algebraic notation.
% This documentation assumes familiarity with the game in general but none of its many styles.
% Nonetheless, some basic terms will be defined because of differing vocabulary among players.
%
% \newcommand{\rmahjong}[1]{\raisebox{-.15\height}{\mahjong{#1}}}
% \section{Mahjong Tiles}
% \subsection{Suited Tiles}
% The suited tiles are referred to as follows:
%
% \begin{tabular}{@{} l l @{}}
% \toprule
% \textbf{Suit} & \textbf{Tiles} \\
% \midrule
% Bamboo & \rmahjong{1234506789s} \\
% Dots & \rmahjong{1234506789p} \\
% Character & \rmahjong{1234506789m} \\
% \bottomrule
% \end{tabular}
%
% Suited tiles are referred to using the pattern \meta{value} \meta{suit}.
% For instance, \mahjong{4s} is called \textit{4~Bamboo}.
%
% \subsection{Honor Tiles}
% This documentation refers to the seven honor tiles as follows:
%
% \begin{tabular}{@{}c c c c@{}}
% \toprule
% \multicolumn{4}{c}{\bfseries Winds} \\
% \mahjong{1z} & \mahjong{2z} & \mahjong{3z} & \mahjong{4z} \\
% East Wind (E) & South Wind (S) & West Wind (W) & North Wind (N) \\
% \midrule
% \multicolumn{4}{c}{\bfseries Dragons} \\
% \mahjong{5z} & \mahjong{6z} & \mahjong{7z} & \\
% White Dragon & Green Dragon & Red Dragon & \\
% \bottomrule
% \end{tabular}
%
% \section{MPSZ Algebraic Notation}
% \subsection{Standard Notation}
% \begin{table}
% \centering
% \caption[MPSZ reference]{MPSZ notation reference. Each tile is identified by its column's number and its row's letter.}
% \label{tab:mpszref}
% \begin{tabular}{ccccccccccc}
% & \textbf{0} & \textbf{1} & \textbf{2} & \textbf{3} & \textbf{4} & \textbf{5} & \textbf{6} & \textbf{7} & \textbf{8} & \textbf{9} \\
% \textbf{s} & \raisebox{-.75\baselineskip}{\rmahjong{0s}} & \rmahjong{1s} & \rmahjong{2s} & \rmahjong{3s} & \rmahjong{4s} & \rmahjong{5s} & \rmahjong{6s} & \rmahjong{7s} & \rmahjong{8s} & \rmahjong{9s} \\
% \textbf{p} & \rmahjong{0p} & \rmahjong{1p} & \rmahjong{2p} & \rmahjong{3p} & \rmahjong{4p} & \rmahjong{5p} & \rmahjong{6p} & \rmahjong{7p} & \rmahjong{8p} & \rmahjong{9p} \\
% \textbf{m} & \rmahjong{0m} & \rmahjong{1m} & \rmahjong{2m} & \rmahjong{3m} & \rmahjong{4m} & \rmahjong{5m} & \rmahjong{6m} & \rmahjong{7m} & \rmahjong{8m} & \rmahjong{9m} \\
% \textbf{z} & & \rmahjong{1z} & \rmahjong{2z} & \rmahjong{3z} & \rmahjong{4z} & \rmahjong{5z} & \rmahjong{6z} & \rmahjong{7z} & &
% \end{tabular}
% \end{table}
%
% % \DeleteShortVerb{\"}
% MPSZ notation assigns each tile an identifier consisting of a digit and a letter (\cref{tab:mpszref}).
% For suited tiles, the digit corresponds to the tile's value and the letter to its suit, Bamboo (|s|), Dots (|p|) or Character (|m|).
% For instance, |2m| identifies \mahjong{2m} (2~Character).
% The only exception ot this rule are red fives, whose numeric value is 0.
% Red 5 Bamboo, for example, has identifier |0s|.
% Honor tiles are assigned the "suit" |z|, with |1z| -- |4z| corresponding to E, S, W and N, and |5z| -- |7z| to the white, green and red dragon, respectively.
%
% Collections of tiles, such as melds or hands, are represented by concatenating the identifiers of the tiles they comprise.
% For instance, |3s4s5s| corresponds to \mahjong{3s4s5s}.
% Groups of tiles sharing the same suit can be abbreviated by omitting all but the last suit identifier.
% The above example can also be expressed as |345s|.
% Spaces are ignored and the notation is case-insensitive.
%
% \subsection{Extensions}
% \paragraph{Spaces.} Spaces can be inserted using |-|:
% |444s-567s| produces \mahjong{444s-567s}.
%
% \paragraph{Concealed Tiles.} \mahjong{X} Concealed (or face-down) tiles are represented by |X|.
% They don't need a suit identifier and don't act as one.
% |123s X 456s| and |123 X 456s| are therefore equivalent.
%
% \paragraph{Blank Tiles.} \mahjong{?} Blank or unknown tiles are represented by |?|.
% Just like concealed tiles, they don't change the current suit.
% |123s ? 456s| and |123 ? 456s| are equivalent, for instance.
%
% \paragraph{Rotation.} Inserting an apostrophe (|'|) rotates the \textit{preceeding} tile counter-clockwise.
% For instance, |6'66m| is rendered as \mahjong{6'66m}.
% This can only be done once per tile, i.e. it is not possible to rotate them 180\degree{} or 270\degree{}.
% When you want to rotate the last tile of a group, it doesn't matter whether the apostrophe appears before or after the suit, so |77'm| and |77m'| are equivalent.
%
%
% \paragraph{Rotation and Stacking.} Quotes (|"|) cause the \textit{preceeding} tile to be rendered as two rotated and stacked tiles.
% For instance, |77"7z| produces \mahjong{77"7z}.
%
% \section{Typesetting Mahjong Tiles in Your Document}
% \DescribeMacro{\mahjong}
% The main interface is |\mahjong| \oarg{height} \oarg{scale} \marg{hand}.
% \meta{hand} refers to a tile sequence in MPSZ notation as discussed above.
% \meta{height} specifies the height of the rendered mahjong tiles.
% \meta{scale} specifies the fraction of vertical space that the tiles' symbols should occupy.
% The value should be between 0 and 1.
% If an optional argument is not given, the default (which can be set through a package argument) will be used.
%
% \DescribeMacro{\mahjong_typeset_hand:n} \DescribeMacro{\mahjong_typeset_hand:x}
% The \LaTeX~3 interface for rendering mahjong tiles are |\mahjong_typeset_hand:n| and its variants.
% This macro accepts the hand to be rendered in MPSZ notation.
% The height can be specified by setting |\l_mahjong_tile_height| \DescribeMacro{\l_mahjong_tile_height} and the default height is saved in |\g_mahjong_default_height|. \DescribeMacro{\g_mahjong_default_height}
% The scale of the tiles' symbols can be changed by setting |\l_mahjong_tile_scale| \DescribeMacro{\l_mahjong_tile_scale} and the default scale is saved in |\g_mahjong_default_scale|.\DescribeMacro{\g_mahjong_default_scale}
%
% \subsection{Package Options}
% \begin{description}
% \item[height]
% The default height can be set using the package's |height| parameter.
% For instance, |\usepackage[height=2\baselineskip]{mahjong}| sets the default size of mahjong tiles to double the value of |\baselineskip| in the context they are rendered in.
% \item[scale] The default scale can be set using the package's |scale| parameter.
% It should ideally be set to a constant to ensure consistent typesetting.
% The default is 0.75, i.e. the symbols take up 85\% of the tiles' vertical space.
% \end{description}
%
% \section{Acknowledgments}
% The mahjong tiles used in this package were created by GitHub user \href{
https://github.com/FluffyStuff}{FluffyStuff}.
% The original repository is \href{
https://github.com/FluffyStuff/riichi-mahjong-tiles}{FluffyStuff/riichi-mahjong-tiles}, used under CC-BY Version 4.0.
% \end{documentation}
% \begin{implementation}
%
% \begin{macrocode}
%<*pkg>
%<@@=mahjong>
\NeedsTeXFormat{LaTeX2e}[2019/10/01]
\RequirePackage{expl3}
\ProvidesExplPackage{mahjong}{2025/01/06}{1.1}{Typeset Mahjong Hands}
\RequirePackage{xparse}
\RequirePackage{l3keys2e}
\RequirePackage{graphicx}
\RequirePackage{stackengine}
% \end{macrocode}
% \begin{macrocode}
\msg_new:nnnn {mahjong} {invalid_token}
{Token ~ #1 ~ is ~ not ~ valid ~ in ~ MPSZ ~ notation}
{Valid ~ tokens ~ are ~ digits ~ 0-9, ~ m, ~ p, ~ s, ~ z, ~ x, ~ -, ~ ?, ~ ', ~ and ~ " }
\msg_new:nnnn {mahjong} {unknown_tile}
{I~don't~know~tile~#1.}
{Please~check~the~documentation~for~recognized~tiles.}
\msg_new:nnnn {mahjong} {unknown_orientation}
{Orientation ~ #1 ~ is ~ unknown}
{This ~ should ~ not ~ happen.~ Please ~ create ~ a ~ bug ~ report.}
\keys_define:nn {mahjong} {
height .dim_gset:N = \g_mahjong_default_height,
scale .int_gset:N = \g_mahjong_default_scale
}
% Identifiers for all suits
\cs_new:Npn \c_@@_suits_tl {mpsz}
% Allowed tokens
\cs_new:Npn \c_@@_allowed_tokens_tl {0123456789mpsz-?x'"}
% Variables have to be declared globally
\tl_new:N \l_@@_suit_tl
\tl_new:N \l_@@_tiles_tl
\tl_new:N \l_@@_reversed_tl
\tl_new:N \l_@@_hand_tl
\tl_new:N \l_@@_current_suit_tl
\tl_new:N \l_@@_current_group_tl
\tl_new:N \l_@@_current_char
\dim_set:Nn \g_mahjong_default_height {\baselineskip}
\dim_new:N \l_mahjong_tile_height
\fp_set:Nn \g_mahjong_default_scale {0.75}
\fp_new:N \l_mahjong_tile_scale
\dim_new:N \l_@@_symbol_height
\dim_new:N \l_@@_baseline_offset
\int_new:N \l_@@_tile_orientation_int
\seq_new:N \l_@@_tile_maps_seq
\str_new:N \l_@@_file_path_str
\ProcessKeysPackageOptions{mahjong}
% \end{macrocode}
% \begin{macro}[aux]{\@@_make_tile:nn, \@@_make_tile:VV, \@@_make_tile:xV, \@@_make_tile:nV}
% Inserts a mahjong tile into the input stream.
% This functions only handles tiles that use the front background and have a forground,
% i.e. regular and blank tiles.
% \begin{macrocode}
\cs_set:Npn \@@_make_tile:nn #1#2 {
\file_if_exist:nTF {#1} {
\int_case:nnF {#2} {
{0} {
\stackinset{c}{0pt}{c}{0pt}{
\includegraphics[
angle=0,
height=\l_@@_symbol_height]
{#1}
}{
\includegraphics[
angle=0,
height=\l_mahjong_tile_height]
{tiles/mahjong-Front.pdf}
}
} {1} {
\stackinset{c}{0pt}{c}{0pt}{
\includegraphics[
angle=90,
width=\l_@@_symbol_height]
{#1}
}{
\includegraphics[
angle=90,
width=\l_mahjong_tile_height]
{tiles/mahjong-Front.pdf}
}
} {2} {
% Stack 2 rotated tiles on top of each other.
\stackon [0pt] {
\@@_make_tile:nn {#1} {1}
} {
\@@_make_tile:nn {#1} {1}
}
}
} {
\msg_fatal:nnx {mahjong} {unknown_orientation} {#2}
}
} {
\msg_error:nnx {mahjong} {unknown_tile} {#1}
}
}
\cs_generate_variant:Nn \@@_make_tile:nn {VV, xV, nV}
% \end{macrocode}
% \end{macro}
% \begin{macro}{\mahjong_typeset_hand:n, \mahjong_typeset_hand:x}
% Parses and typesets a mahjong hand in MPSZ notation.
% Set |\l_mahjong_tile_height| to control the tiles' size and |\l_mahjong_tile_scale| to control the size of the symbol relative to the tile.
% \begin{macrocode}
% Parses a full hand
\cs_set:Npn \mahjong_typeset_hand:n #1 {
% Set computed dimensions for symbol height and baseline offset
\dim_set:Nn \l_@@_symbol_height {\fp_to_decimal:n {\l_mahjong_tile_scale}\l_mahjong_tile_height}
\dim_set:Nn \l_@@_baseline_offset {(\l_mahjong_tile_height - \l_@@_symbol_height) / 2}
% Start sequence processing
\tl_set:Nx \l_@@_hand_tl {\text_lowercase:n {#1}}
% MPSZ notation is easier to parse right-to-left, so reverse string
% There is no string reversal function but we can reverse token lists
% Token lists and strings are freely convertible between each other
\tl_set:Nx \l_@@_reversed_tl {\tl_reverse:V \l_@@_hand_tl}
\tl_map_variable:NNn \l_@@_reversed_tl \l_@@_current_char {
% Check if we recognize the current token
\exp_args:NVV \tl_if_in:nnF \c_@@_allowed_tokens_tl \l_@@_current_char {
\msg_error:nnx {mahjong} {invalid_token} {\l_@@_current_char}
}
\exp_args:NVV \tl_if_in:nnTF \c_@@_suits_tl \l_@@_current_char {
% If we've found a suit identifier, change the current suit
\tl_set:NV \l_@@_current_suit_tl \l_@@_current_char
} {
\str_case:VnF \l_@@_current_char {
{'} {
% An apostrophe indicates that the next tile is rotated
\int_set:Nn \l_@@_tile_orientation_int {1}
}
{"} {
% Quotes mean the next tile is actually 2 rotated tiles stacked on top of each other
\int_set:Nn \l_@@_tile_orientation_int {2}
}
} {
% Default case: We've got a complete tile identifier
% Turn it into a property list
\prop_clear:N \l_tmpa_prop
\prop_put:NnV \l_tmpa_prop {suit} \l_@@_current_suit_tl
\prop_put:NnV \l_tmpa_prop {face} \l_@@_current_char
\prop_put:NnV \l_tmpa_prop {orientation} \l_@@_tile_orientation_int
% Add it to the beginning of the sequence (we are parsing in reverse)
\seq_put_left:NV \l_@@_tile_maps_seq \l_tmpa_prop
\int_set:Nn \l_@@_tile_orientation_int {0}
}
}
}
% Typesetting begins here. Sequence is in correct order
\raisebox{-\l_@@_baseline_offset}{
\seq_map_variable:NNn \l_@@_tile_maps_seq \l_tmpa_prop {
\prop_get:NnN \l_tmpa_prop {face} \l_tmpa_tl
\prop_get:NnN \l_tmpa_prop {orientation} \l_tmpa_int
\str_case:VnF \l_tmpa_tl {
{-} {
% If the current face is a dash, insert a space
\includegraphics[height=\l_mahjong_tile_height]{tiles/mahjong-Space.pdf}
} {x} {
% Insert a flipped tile
\int_case:nn {\l_tmpa_int} {
{0} { % Upright
\includegraphics[
angle=0,
height=\l_mahjong_tile_height]
{tiles/mahjong-Back.pdf}
} {1} { % Rotated
\includegraphics[
angle=90,
width=\l_mahjong_tile_height]
{tiles/mahjong-Back.pdf}
} {2} { % Rotated and stacked
\stackon [0pt] {
\includegraphics[
angle=90,
width=\l_mahjong_tile_height]
{tiles/mahjong-Back.pdf}
} {
\includegraphics[angle=90,
width=\l_mahjong_tile_height]
{tiles/mahjong-Back.pdf}
}
}
}
} {?} { % Blank tile
\@@_make_tile:nV {tiles/mahjong-Blank.pdf} \l_tmpa_int
}
} { % Any other tile identified by a code.
\@@_make_tile:xV {tiles/mahjong-\l_tmpa_tl\prop_item:Nn \l_tmpa_prop {suit}.pdf} \l_tmpa_int
}
}
}
% Clear the list for the next invocation
\seq_clear:N \l_@@_tile_maps_seq
}
% Have TeX automatically expand the argument for us
\cs_generate_variant:Nn \@@_typeset_hand:n {x}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\mahjong}
% This is the only \LaTeXe{} macro in this package.
% It typesets a mahjong hand.
% \begin{macrocode}
\NewDocumentCommand{\mahjong}{O{\g_mahjong_default_height} O{\g_mahjong_default_scale} m}{
\dim_set:Nn \l_mahjong_tile_height {#1}
\fp_set:Nn \l_mahjong_tile_scale {#2}
\mahjong_typeset_hand:n {#3}
}
%</pkg>
% \end{macrocode}
% \end{macro}
%
% \end{implementation}
%
% \PrintChanges
\endinput