% \iffalse meta-comment
%
% nycbullets: New York City Subway route bullets for LuaLaTeX
% Copyright (C) 2026 Matthew Leingang
%
% 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.txt
%
% The accompanying fonts are dedicated to the public domain under CC0 1.0,
% except for the bullets credited in the documentation.
%
% Not affiliated with or endorsed by the MTA; the bullets may be trademarks
% of the MTA.
%
% \fi
%
% \iffalse
%<*driver>
\DocumentMetadata{}
\documentclass[full]{l3doc}
\usepackage{nycbullets}
\usepackage{longtable,booktabs}
% Helvetica, like the MTA's signs; TeX Gyre Heros is a free clone of it.
\IfFontExistsTF{Helvetica}
  {\setmainfont{Helvetica}\setsansfont{Helvetica}}
  {\setmainfont{TeX Gyre Heros}\setsansfont{TeX Gyre Heros}}
% Menlo, Apple's companion monospace to Helvetica; DejaVu Sans Mono is the
% free font it is based on.
\IfFontExistsTF{Menlo}
  {\setmonofont{Menlo}[Scale=MatchLowercase]}
  {\setmonofont{DejaVu Sans Mono}[Scale=MatchLowercase]}
% The key tables are as wide as their contents, so that descriptions don't
% wrap, and centered on the page (not the text block, which l3doc sets off
% to the right), so that wide ones stick out evenly into both margins.
% Unequal glue -\nycbulletsshift and +\nycbulletsshift on either side moves
% the table's center from the text block's to the page's.
% The All table, with its long tagged keys, is a size smaller to fit.
\newdimen\nycbulletsshift
\def\nycbulletsallid{all}
\NewDocumentEnvironment{nycbulletstable}{mm}
  {%
    \subsection{#2 (\texttt{set=#1})}
    \nycbulletsetup{set=#1}%
    \def\nycbulletsthisid{#1}%
    \ifx\nycbulletsthisid\nycbulletsallid \footnotesize \else \small \fi
    \setlength\tabcolsep{4pt}%
    \renewcommand\arraystretch{1.6}%
    \setlength\nycbulletsshift
      {\dimexpr 1in + \hoffset + \oddsidemargin + \textwidth/2
        - \paperwidth/2\relax}%
    \LTleft=-\nycbulletsshift plus 1fill minus 1fill\relax
    \LTright=\nycbulletsshift plus 1fill minus 1fill\relax
    \begin{longtable}{@{}c>{\ttfamily}lrll@{}}
      \toprule
      Bullet & \normalfont Key & Code point & Description & Service \\
      \midrule
      \endhead
      \bottomrule
      \endfoot
  }
  {\end{longtable}}
\NewDocumentCommand\nycbulletsinput{m}
  {\begingroup\catcode`\%=14 \input{#1}\endgroup}
\NewDocumentCommand\nycbulletsinputtable{m}
  {\nycbulletsinput{nycbullets-keytable-#1.tex}}
% The letter in a bullet sits 0.1em per unit of scale above the baseline:
% lower the bullets so that it lines up with the text.
\NewDocumentCommand\nycbulletsrow{mmmm}
  {\nycbullet[scale=2,raise=-0.2em]{#1} & \detokenize{#1} & \texttt{U+#2} & #3 & #4 \\}
\begin{document}
  \DocInput{nycbullets.dtx}
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{nycbullets} package\\
%   New York City Subway route bullets^^A
% }
% \author{Matthew Leingang\thanks{\href{mailto:mpl5@nyu.edu}{\texttt{mpl5@nyu.edu}}}}
% \date{Released 2026-10-04}
%
% \maketitle
% 
% \begin{quotation}\noindent 
% You must take the \nycbullet[scale=1.3,raise=-0.1em]{A} train \\
% To go to Sugar Hill way up in Harlem \\
% \hfill --- Ella Fitzgerald 
% \end{quotation}
%
% \begin{quotation}\noindent 
%  Same faces every day, but you don't know their names \\
%  Party people going places on the \nycbullet[scale=1.3,raise=-0.1em]{D} train \\
% \hfill --- Beastie Boys
% \end{quotation}
%
% \begin{documentation}
%
% \section{Introduction}
%
% This package typesets the route ``bullets'' of the New York City Subway:
% \nycbullet{A}\,\nycbullet{C}\,\nycbullet{E},
% \nycbullet{F}\,\nycbullet{Fd}, \nycbullet{7}\,\nycbullet{7d}, and so on.
% The artwork comes from the
% \href{https://commons.wikimedia.org/wiki/Category:New_York_City_Subway_bullets}
%   {New York City Subway bullets category on Wikimedia Commons},
% where nearly all of it is in the public domain (see
% section~\ref{sec:credits} for the exceptions).  It has been compiled into
% a family of
% OpenType color fonts, one per set of bullets on Commons, plus one font
% containing all of them.
%
% \section{Usage}
%
% Load the package with Lua\LaTeX{}:
% \begin{verbatim}
% \usepackage{nycbullets}
% \end{verbatim}
%
% \begin{function}{\nycbullet}
%   \begin{syntax}
%     \cs{nycbullet}\oarg{options}\marg{key}
%   \end{syntax}
%   Typesets the bullet named \meta{key} from the current set.  The
%   \meta{options} are any of the options of \cs{nycbulletsetup}, applied to
%   this bullet only; as a shortcut, an option that is just the name of a
%   set selects that set, so \verb|\nycbullet[helvetica]{F}| gives the
%   Helvetica-set \nycbullet[helvetica]{F}.
% \end{function}
%
% A \meta{key} names the route, followed by modifiers and variants:
% \begin{itemize}
%   \item The route itself: \texttt{F}, \texttt{7}, \texttt{SIR},
%     \texttt{JFK}, \texttt{10}, or \texttt{-} for the dash bullet.
%   \item A lower-case \texttt{d} for the diamond (express) form:
%     \texttt{Fd} \nycbullet{Fd}, \texttt{6d} \nycbullet{6d}.  A few R32
%     ``special'' forms add an \texttt{s}: \texttt{Bs}, \texttt{Bsd}.
%   \item Variants, introduced by a dot: colors such as \texttt{M.brown}
%     \nycbullet{M.brown}, historical routings such as
%     \texttt{B.broadway}, years such as \texttt{AA.1967-1979}, and so on.
% \end{itemize}
% The complete list of keys in each set is in section~\ref{sec:tables}.
% An unknown key is an error.
%
% \begin{function}{\nycbulletsetup}
%   \begin{syntax}
%     \cs{nycbulletsetup}\Arg{key=value list}
%   \end{syntax}
%   Sets options for all following bullets in the current group.  The same
%   options can be given when loading the package.
% \end{function}
%
% \begin{description}
%   \item[\texttt{set=\meta{name}}] The bullet set to use.  The sets are
%     \texttt{std} (or \texttt{standard}), \texttt{helv}
%     (\texttt{helvetica}), \texttt{nycta}, \texttt{r62a}, \texttt{r32},
%     \texttt{retired}, \texttt{legacy}, and \texttt{all}.  The default is
%     \texttt{std}.
%   \item[\texttt{scale=\meta{number}}] Size of the bullets relative to the
%     current font size.  The default, |1|, makes a circular bullet
%     $0.8$\,em across, a little taller than a capital letter.
%   \item[\texttt{raise=\meta{dimension}}] Shift the bullets up (or down,
%     if negative).  The default is |0pt|: circular bullets then extend
%     $0.1$\,em below the baseline.
%   \item[\texttt{color=true\textbar false}] Whether to use the colored
%     glyphs (the default) or the monochrome outlines.
%   \item[\texttt{renderer=node\textbar harfbuzz}] Which \pkg{luaotfload}
%     shaper to use.  Both render the bullets in color.
% \end{description}
% The \texttt{color} and \texttt{renderer} options take effect when a set's
% font is first used, so set them in the preamble.
%
% \begin{function}{\nycbulletfont}
%   \begin{syntax}
%     \cs{nycbulletfont}\oarg{set}
%   \end{syntax}
%   Switches to the bullet font of a set, for typing keys directly.  The
%   keys are ligatures in the font, so |{\nycbulletfont F Fd SIR}| gives
%   {\nycbulletfont F Fd SIR}.  Characters that are not part of a key
%   come out as empty boxes.  Separate adjacent bullets with spaces:
%   ``|AC|'' is not the same as ``|A C|''.
% \end{function}
%
% \subsection{Engines}
%
% The bullets are in color only with Lua\LaTeX{}, which renders the fonts'
% \texttt{COLR} table.  With Xe\LaTeX{} the package works, but the bullets
% come out in a single color (the current text color), because
% Xe\TeX{} does not support color fonts.  pdf\LaTeX{} cannot use the fonts
% at all.
%
% \subsection{Using the fonts elsewhere}
%
% The fonts \texttt{NYCSubwayBullets-*.ttf} can be installed and used in any
% application.  Besides the \texttt{COLR} table they have an \texttt{SVG}
% color table (for Safari, macOS and Adobe applications) and monochrome
% outlines (as a fallback).  Each bullet can be typed as its key, provided
% the application applies standard ligatures, or as its Private Use Area
% code point, which is listed in the tables below.
%
% \section{Credits}
% \label{sec:credits}
%
% The bullet artwork comes from Wikimedia Commons.  Nearly all of it is
% the work of the Metropolitan Transportation Authority and is in the
% public domain, and the fonts are dedicated to the public domain under
% CC0~1.0.  These bullets are used under licenses that require
% attribution:
% \nycbulletsinput{nycbullets-credits.tex}
%
% Not affiliated with or endorsed by the MTA; the bullets may be
% trademarks of the MTA.
%
% \section{The bullets}
% \label{sec:tables}
%
% \nycbulletsinputtable{std}
% \nycbulletsinputtable{helv}
% \nycbulletsinputtable{nycta}
% \nycbulletsinputtable{r62a}
% \nycbulletsinputtable{r32}
% \nycbulletsinputtable{retired}
% \nycbulletsinputtable{legacy}
% \nycbulletsinputtable{all}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{nycbullets} implementation}
%
%    \begin{macrocode}
%<*package>
%<@@=nycbullets>
%    \end{macrocode}
%
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[2022-06-01]
\ProvidesExplPackage{nycbullets}{2026-10-04}{1.0}
  {New York City Subway route bullets}
%    \end{macrocode}
%
% \subsection{Messages}
%
%    \begin{macrocode}
\msg_new:nnn { nycbullets } { pdftex }
  {
    The~nycbullets~package~needs~OpenType~fonts:~
    use~LuaLaTeX~(or~XeLaTeX~for~monochrome~bullets).
  }
\msg_new:nnn { nycbullets } { xetex }
  { XeTeX~cannot~render~color~fonts:~the~bullets~will~be~monochrome. }
\msg_new:nnnn { nycbullets } { unknown-key }
  { Unknown~bullet~key~`#1'~in~set~`#2'. }
  { See~the~nycbullets~documentation~for~the~keys~in~each~set. }
\msg_new:nnn { nycbullets } { unknown-option }
  { Unknown~option~`#1'. }
\msg_new:nnnn { nycbullets } { unknown-set }
  { Unknown~bullet~set~`#1'. }
  { The~known~sets~are:~#2. }
%    \end{macrocode}
%
% \subsection{Engine check}
%
%    \begin{macrocode}
\sys_if_engine_pdftex:T
  {
    \msg_critical:nn { nycbullets } { pdftex }
  }
\sys_if_engine_xetex:T
  { \msg_warning:nn { nycbullets } { xetex } }
\RequirePackage { fontspec }
%    \end{macrocode}
%
% \subsection{Variables}
%
%    \begin{macrocode}
\tl_new:N \l_@@_set_tl
\fp_new:N \l_@@_scale_fp
\dim_new:N \l_@@_raise_dim
\bool_new:N \l_@@_color_bool
\tl_new:N \l_@@_renderer_tl
\tl_new:N \l_@@_id_tl
\tl_new:N \l_@@_cp_tl
\box_new:N \l_@@_box
%    \end{macrocode}
%
% \begin{variable}{\g_@@_sets_prop,\g_@@_alias_prop,\g_@@_names_seq}
%   Set id $\to$ font file; any accepted set name $\to$ set id; the list of
%   set ids (for messages).
%    \begin{macrocode}
\prop_new:N \g_@@_sets_prop
\prop_new:N \g_@@_alias_prop
\seq_new:N \g_@@_names_seq
%    \end{macrocode}
% \end{variable}
%
% \subsection{Declaring sets and keys}
%
% These are used by the generated files \texttt{nycbullets-sets.def} and
% \texttt{nycbullets-\meta{set}.def}.
%
% \begin{macro}{\nycbullets_declare_set:nnn}
%   \begin{syntax}
%     \cs{nycbullets_declare_set:nnn} \Arg{id} \Arg{name} \Arg{font file}
%   \end{syntax}
%    \begin{macrocode}
\cs_new_protected:Npn \nycbullets_declare_set:nnn #1#2#3
  {
    \prop_gput:Nnn \g_@@_sets_prop {#1} {#3}
    \prop_gput:Nnn \g_@@_alias_prop {#1} {#1}
    \prop_gput:Nen \g_@@_alias_prop { \str_lowercase:n {#2} } {#1}
    \seq_gput_right:Nn \g_@@_names_seq {#1}
    \prop_new:c { g_@@_keys_#1_prop }
    \bool_new:c { g_@@_loaded_#1_bool }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\nycbullets_declare_keys:nn}
%   \begin{syntax}
%     \cs{nycbullets_declare_keys:nn} \Arg{id} \{ \Arg{key} \Arg{hex code point} \ldots{} \}
%   \end{syntax}
%    \begin{macrocode}
\cs_new_protected:Npn \nycbullets_declare_keys:nn #1#2
  {
    \bool_set_false:N \l_@@_pending_bool
    \tl_map_tokens:nn {#2} { \@@_declare_key:nn {#1} }
  }
%    \end{macrocode}
%   \cs{tl_map_tokens:nn} hands over one braced item at a time, so keys and
%   code points alternate: we remember each key until its code point
%   arrives.
%    \begin{macrocode}
\tl_new:N \l_@@_pending_key_tl
\bool_new:N \l_@@_pending_bool
\cs_new_protected:Npn \@@_declare_key:nn #1#2
  {
    \bool_if:NTF \l_@@_pending_bool
      {
        \prop_gput:cVn { g_@@_keys_#1_prop } \l_@@_pending_key_tl {#2}
        \bool_set_false:N \l_@@_pending_bool
      }
      {
        \tl_set:Ne \l_@@_pending_key_tl { \tl_to_str:n {#2} }
        \bool_set_true:N \l_@@_pending_bool
      }
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
\file_input:n { nycbullets-sets.def }
%    \end{macrocode}
%
% \subsection{Loading a set}
%
% \begin{macro}{\@@_resolve_set:n}
%   Set \cs{l_@@_id_tl} to the id of the set named |#1|, or raise an error
%   and fall back to the first set.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_resolve_set:n #1
  {
    \prop_get:NeNF \g_@@_alias_prop { \str_lowercase:n {#1} } \l_@@_id_tl
      {
        \msg_error:nnne { nycbullets } { unknown-set } {#1}
          { \seq_use:Nn \g_@@_names_seq { ,~ } }
        \tl_set:Ne \l_@@_id_tl { \seq_item:Nn \g_@@_names_seq { 1 } }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_load:V}
%   On first use of a set, read its key table.  This may happen inside a
%   group, so everything it defines is global.  We make sure |%| is a
%   comment character, which it isn't in (for instance) \cs{DocInput},
%   and that line ends are ignored, so the file can't end the paragraph.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_load:n #1
  {
    \bool_if:cF { g_@@_loaded_#1_bool }
      {
        \group_begin:
          \char_set_catcode_comment:N \%
          \char_set_catcode_ignore:n { 13 }
          \file_input:n { nycbullets-#1.def }
        \group_end:
        \bool_gset_true:c { g_@@_loaded_#1_bool }
      }
  }
\cs_generate_variant:Nn \@@_load:n { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_declare_font:nn}
%   Declare the font family |\@@_font_<id>:|.  With Lua\TeX{} the
%   |colr| feature turns on the color layers.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_declare_font:nn #1#2
  {
    \clist_clear:N \l_@@_features_clist
    \sys_if_engine_luatex:T
      {
        \str_if_eq:VnT \l_@@_renderer_tl { harfbuzz }
          { \clist_put_right:Nn \l_@@_features_clist { Renderer = HarfBuzz } }
        \bool_if:NTF \l_@@_color_bool
          { \clist_put_right:Nn \l_@@_features_clist { RawFeature = {+colr} } }
          { \clist_put_right:Nn \l_@@_features_clist { RawFeature = {-colr} } }
      }
    \use:e
      {
        \exp_not:N \newfontfamily
        \exp_not:c { @@_font_#1: }
        { \exp_not:n {#2} }
        [ \exp_not:V \l_@@_features_clist ]
      }
  }
\clist_new:N \l_@@_features_clist
%    \end{macrocode}
% \end{macro}
%
%   The font families are declared at the start of the document, after any
%   \cs{nycbulletsetup} in the preamble.  (\cs{newfontfamily} defines its
%   command locally, so it cannot wait for the first bullet, which is
%   typeset in a group.)  Declaring a family does not load the font.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_declare_fonts:
  {
    \prop_map_inline:Nn \g_@@_sets_prop
      { \@@_declare_font:nn {##1} {##2} }
  }
\hook_gput_code:nnn { begindocument } { nycbullets } { \@@_declare_fonts: }
%    \end{macrocode}
%
% \subsection{Options}
%
%    \begin{macrocode}
\keys_define:nn { nycbullets }
  {
    set      .tl_set:N   = \l_@@_set_tl ,
    set      .initial:n  = std ,
    scale    .fp_set:N   = \l_@@_scale_fp ,
    scale    .initial:n  = 1 ,
    raise    .dim_set:N  = \l_@@_raise_dim ,
    raise    .initial:n  = 0pt ,
    color    .bool_set:N = \l_@@_color_bool ,
    color    .initial:n  = true ,
    color    .default:n  = true ,
    renderer .choices:nn = { node , harfbuzz }
      { \tl_set:Ne \l_@@_renderer_tl { \l_keys_choice_tl } } ,
    renderer .initial:n  = node ,
    unknown  .code:n     =
      {
        \tl_if_blank:nTF {#1}
          { \tl_set:Ne \l_@@_set_tl { \l_keys_key_str } }
          {
            \msg_error:nne { nycbullets } { unknown-option }
              { \l_keys_key_str }
          }
      } ,
  }
\ProcessKeyOptions [ nycbullets ]
%    \end{macrocode}
%
% \subsection{User commands}
%
% \begin{macro}{\nycbullets_codepoint:nn}
%   The (hexadecimal) code point of key |#2| in set |#1| (an id or alias),
%   or nothing.  Expandable; the set must have been used already.
%    \begin{macrocode}
\cs_new:Npn \nycbullets_codepoint:nn #1#2
  {
    \prop_item:cn
      {
        g_@@_keys_
        \exp_args:NNe \prop_item:Nn \g_@@_alias_prop { \str_lowercase:n {#1} }
        _prop
      }
      {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\nycbullets_bullet:n}
%   Typeset one bullet with the current options.
%    \begin{macrocode}
\cs_new_protected:Npn \nycbullets_bullet:n #1
  {
    \@@_resolve_set:V \l_@@_set_tl
    \@@_load:V \l_@@_id_tl
    \@@_bullet:Vn \l_@@_id_tl {#1}
  }
%    \end{macrocode}
%   The set id is passed as an argument here so that it can be part of
%   control sequence names: in \cs{ExplSyntaxOn}, |\l_@@_id_tl _prop|
%   would be read as a single name.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_bullet:nn #1#2
  {
    \prop_get:cnNTF { g_@@_keys_#1_prop } {#2} \l_@@_cp_tl
      {
        \mode_leave_vertical:
        \hbox_set:Nn \l_@@_box
          {
            \@@_select_font:n {#1}
            \tex_char:D " \l_@@_cp_tl \scan_stop:
          }
        \box_move_up:nn { \l_@@_raise_dim } { \box_use_drop:N \l_@@_box }
      }
      { \msg_error:nnnn { nycbullets } { unknown-key } {#2} {#1} }
  }
\cs_generate_variant:Nn \@@_bullet:nn { V }
\cs_generate_variant:Nn \@@_resolve_set:n { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_select_font:n}
%   Switch to the font of set |#1| at the requested scale.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_select_font:n #1
  {
    \use:c { @@_font_#1: }
    \fontsize
      { \fp_eval:n { \l_@@_scale_fp * \use:c { f@size } } pt }
      { \use:c { f@baselineskip } }
    \selectfont
  }
\cs_generate_variant:Nn \@@_select_font:n { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\nycbullet,\nycbulletsetup,\nycbulletfont}
%    \begin{macrocode}
\NewDocumentCommand \nycbullet { o m }
  {
    \group_begin:
      \IfValueT {#1} { \keys_set:nn { nycbullets } {#1} }
      \nycbullets_bullet:n {#2}
    \group_end:
  }
\NewDocumentCommand \nycbulletsetup { m }
  { \keys_set:nn { nycbullets } {#1} }
\NewDocumentCommand \nycbulletfont { o }
  {
    \IfValueT {#1} { \tl_set:Nn \l_@@_set_tl {#1} }
    \@@_resolve_set:V \l_@@_set_tl
    \@@_load:V \l_@@_id_tl
    \@@_select_font:V \l_@@_id_tl
  }
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
