% \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} % % \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_:|. 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} % % \end{macrocode} % % \end{implementation} % % \PrintIndex