A little more LaTeX

While I’m on the topic of LaTeX, I might as well throw in another post on how I’m writing my reports for work nowadays. It’s based strongly on TextExpander, because that’s the best way I know to make automations that work on both macOS and iOS.

Although my writing workflow does change occasionally, it tends to stay stable for years at a time. In the mid-90s, after learning HTML, I began using a system based on SGML and troff. That lasted until 2000 or so, when I started writing in LaTeX. After discovering Markdown (and MultiMarkdown), I began using a Markdown-to-LaTeX-to-paper workflow that changed to Markdown-to-LaTeX-to-PDF when my client base got a little more computer savvy. This system served me well for 10–12 years, starting in 2005. I wrote about it here, including a fun diagram.

A few years ago, though, I found myself writing more reports that needed extra care in formatting. I would start by writing in Markdown, as before, but too often had to edit the intermediate LaTeX to get the pages of the PDF looking the way I wanted. I decided it was best to just switch back to writing LaTeX directly. Coincidentally, this happened at about the time I bought my 9.7″ iPad Pro and wanted to start writing on it.

For the bulk of a report, writing in LaTeX isn’t much different from writing in Markdown. You’re basically just writing in plain text, with blank lines separating the paragraphs. The big differences come at the beginning of the text, where LaTeX requires a long header, and in the inclusion of figures, where LaTeX requires a lot of formatting to get the figures where you want them.1

Luckily, this extra writing needed by LaTeX is mostly boilerplate, and there’s a good system for automating the insertion of boilerplate on both the Mac and the iPad: TextExpander. I went through some of my LaTeX source files, pulled out the text fragments that I use frequently or are verbose, and built a set of TextExpander snippets with them. If you’re a LaTeX user, you can download them.

Fair warning: the longest snippet, the one that generates the LaTeX header, won’t work as-is for you because it relies on a style file (not included) that’s heavily customized for my company. But you can edit it to fit your needs.

LaTeX TextExpander snippets

One thing you’ll notice right away is that the abbreviations start with ll. That’s how the whole set is defined.

LaTeX snippet prefix definition

All of my snippets use a double letter prefix on the home row, and ll is a natural for LaTeX.

Many of the snippets are self-explanatory, but some are worth discussing, especially those for figures.

The llfig snippet is sort of the baseline snippet for figures that aren’t special in any particular way. The snippet definition is

\begin{figure}[htbp]
\begin{center}
\includegraphics[width=5.5in]{%clipboard%fillpopup:name=extension:default=.pdf:.jpg%}
\caption{%|}
\label{%clipboard}
\end{center}
\end{figure}

and it looks like this when invoked:

Figure fill-in snippet

It assumes the name of the figure file (sans extension) is on the clipboard and uses that in both the \includegraphics command and as the figure’s label for reference in the text. It provides a popup button to select whether the figure is a PDF or a JPEG, and it puts the cursor between the braces of the \caption command. I used to have a fill-in field for the caption, but I find it generally more convenient to type in a real text editor than in a field in a dialog box.

The [width=5.5in] specifier is something I often change after the fact to make for a better looking page. I don’t define it as a fill-in field because I don’t know the best width until I’ve seen the rendered page.

The llland snippet is specifically for figures that are photographs taken in landscape orientation. It’s similar to the llfig snippet, except the image will always be a JPEG and the size of the graphic is set to a height of 3.25 inches. This height works well for fitting two photos on a page.

\begin{figure}[htbp]
\begin{center}
\includegraphics[height=3.25in]{%clipboard.jpg}
\caption{%| (%clipboard).}
\label{%clipboard}
\end{center}
\end{figure}

Note also that the clipboard, which is supposed to be the filename sans extension when the snippet is invoked, is put in parentheses after the caption. I do this because I usually have dozens of photos associated with a project, and this makes it easier to identify the which photos are in the report.

The llport snippet is for photographs taken in landscape orientation. It’s more complicated than the llland, because I want the caption to show up next to the photo, not below it. It accomplishes this by creating two minipage environments, one on the left for the photo and one on the right for the caption.

\begin{figure}[htbp]
\begin{center}
\begin{minipage}[c]{0.55\linewidth}
\includegraphics[height=4.25in]{%clipboard.jpg}
\end{minipage}\hfill
\begin{minipage}[c]{0.40\linewidth}
\caption{%| (%clipboard).}
\label{%clipboard}
\end{minipage}
\end{center}
\end{figure}

Like the llland snippet, this allows two photos to fit together on the same page. Both can be landscape, both portrait, or one of each.

Example of landscape and portrait photographs

Finally, there is the llsfig snippet, which is just like llfig except that it uses the sidewaysfigure float defined in the rotating package. As you can guess from the name, this meant for large, wide figures that are best displayed rotated 90° on a page of their own.

\begin{sidewaysfigure}
\begin{center}
\includegraphics[width=8in]{%clipboard%fillpopup:name=extension:default=.pdf:.jpg%}
\end{center}
\hspace{1.5in}\parbox{6in}{\caption{%|}\label{%clipboard}}
\end{sidewaysfigure}

I wrote about using sidewaysfigure over a decade ago. Time flies.

All these snippets assume that the clipboard contains the filename without the extension because it’s easier on both platforms to copy the filename without the extension than with it. On a Mac, two slow clicks (not a double click) on a file in the Finder selects the filename without the extension, so a quick ⌘C puts that onto the clipboard. On an iPad, choosing Rename from a file’s popup menu in the Dropbox app puts the cursor just before the dot that starts the extension, so it’s easy to type ⇧⌘← to select and ⌘C to copy. Using the Files app is similar.

There are some compromises in these snippets that I wouldn’t have made back when I did all my writing on Macs. In those days, I would have avoided contaminating the clipboard and used AppleScript instead to get the name of the selected file in the Finder. But I want the portability of the iPad and consistency across platforms, so the small loss in productivity is worth it.


  1. This formatting was also required in my Markdown-LaTeX-PDF workflow, but it was handled by the XSLT that converted from Markdown to LaTeX. 


LaTeX via Drafts

As I said in my last post, I often write reports on both Mac and iPad, switching between the two according to whichever is more convenient to work on. As the writing progresses, the text is kept in both Drafts and a file in Dropbox, which are kept in sync by the Drafts actions described in that post. My reports are written in LaTeX, which have to be compiled into a PDF. Until recently, I did the compilation either directly on a Mac via commands entered in a Terminal window or indirectly on a Mac via commands entered into Prompt, the iOS app that lets me connect to my Macs via SSH. But this week, I created a Drafts Action/Shortcut hybrid that allows me to bypass Prompt and compile LaTeX files from Drafts.

I’ve never set up a similar workflow on my Mac to allow me to compile LaTeX from within BBEdit, because I’ve never seen much advantage in doing so. On a Mac, I can always have a Terminal window ready to run the compilation command. On an iPad, doing the equivalent with Prompt isn’t as easy. Unless Prompt is an active app, it will time out after a few minutes—this is an iOS thing, something the folks at Panic can’t get around. Which means if I’m writing in Drafts (or any editor, for that matter), running the LaTeX compilation command in Prompt is a juggling act. I either have to keep Prompt open as the other app in Split View, which means I have to periodically replace Drafts in Split View with Safari, Dropbox, Books, or whatever apps I’m refering to as I write, or I have to relaunch Prompt, log in, and cd to the right directory every time I want to compile to see how the report looks.

But no more. With the PDFLaTeX action, I can keep Drafts in one pane, the research app in the other, and compile with a menu selection or keystroke combination.

The action has two steps: a Script step that figures out the directory and filename and a Run Shortcut step that sends that information to a Shortcut that connects to my computer and compiles the LaTeX into a PDF.

Here’s the script:

javascript:
 1:  // Get the folder and filename from the dbox line.
 2:  var d = draft.content;
 3:  
 4:  // Regex for fileline.
 5:  var fileRE = /dbox:(.+)$/m;
 6:  
 7:  if (fileRE.test(d)) {
 8:    // Get the path. Add a slash to the front if I forgot.
 9:    var fileline = d.match(fileRE);
10:    var flStart = d.search(fileRE);
11:    var path = fileline[1];
12:    if (path[0] != '/') {
13:      path = '/' + path;
14:      editor.setTextInRange(flStart + 5, 0, '/');
15:    }
16:    // Split the path into directory and filename and make a dictionary.
17:     parts = path.split("/")
18:     var fileinfo = {"directory":"~/Dropbox" + parts.slice(0, -1).join("/"),
19:                 "filename":parts.slice(-1)[0]};
20:    
21:    // Create a template tag from the dictionary.
22:    draft.setTemplateTag("fileinfo", JSON.stringify(fileinfo))
23:   
24:  } else {
25:    alert("No dbox line");
26:  }

Recall that I put a dbox line in my drafts that gives the path to the Dropbox file where the draft is also saved. In a LaTeX file, the dbox line is a comment that looks like this:

% dbox:/projects/test/report/report.tex

Most of the script has been recycled from my earlier script that saves a draft to Dropbox. You can go to that post for the explanation of how the dbox line is parsed. I’ll concentrate here on the new stuff.

Line 17 splits the path into a list of its components: subdirectories and the filename. Lines 18–19 then creates a JSON dictionary. The directory item is the reconstructed path, starting with the Dropbox folder in my home folder. The filename item is exactly what you think it is.

Line 22 creates a template tag named fileinfo for this draft. The value of the tag is the string form of the JSON dictionary created in Lines 18–19. We’ll use this tag in the next step to pass information to a Shortcut.

The Run Shortcut step looks like this:

Run Shortcut step

There’s not much to it. The Shortcut it runs is also named PDFLaTeX, and the text passed to it is the value of the fileinfo tag created in Line 22 of the script above.

Here’s how the PDFLaTeX shortcut is defined:

PDFLaTeX shortcut

The first step parses the text passed to the shortcut into a dictionary. The second step logs into my computer via SSH and runs the following three commands

export PATH=$PATH:/Library/TeX/texbin
cd <Dictionary:directory>
latexmk -pdf -interaction=nonstopmode <Dictionary:filename>

where the things inside the angled bracket are magic variables that extract the given items from the dictionary created in the first step.

For reasons I don’t quite understand, SSHing into my computer through a Shortcut isn’t exactly like doing it via Prompt.1 Most significantly, the PATH environment variable doesn’t include the directory where the various TeX/LaTeX commands are kept. So the purpose of the first line is to add that directory to the PATH.

The second line changes to the directory where the LaTeX file is saved, and the third line does the compilation via latexmk.

The latexmk command is a Perl script that gets installed along with MacTeX. It’s job is to automate the compilation of LaTex files into the final output. If you’re a LaTeX user, you know that generating output often requires two or more steps as bibliographies get built and floating figures move from one page to another; latexmk figures out which commands need to be run and runs them as often as necessary until everything is in its final state. The -pdf option tells latexmk to generate a PDF and the -interaction=nonstopmode option tells it to keep going even if there’s an error in the source files.

I have the action bound to the ⇧⌘P keystroke combination. When I run it, Shortcuts appears on the screen while the shorcut runs. If the compilation is successful, Shortcuts disappears and the screen goes back to Drafts and the research app. If there’s a LaTeX error—a misspelled command or an unclosed brace—Shortcuts stays on the screen displaying the error message.

PDFLaTeX error message

As it stands, the error message displayed is pretty worthless. The full error message from latexmk, which has what we need, is simply too long to fit in the dialog box and the good part gets lost in the truncation. I may try to extract the actual error from the log file and display that instead.

Even with a crummy error message, this is better than juggling apps or relaunching Prompt.


  1. I suspect it has something to do with whether or not I’m logging in with an interactive shell. I could probably avoid this line if I had a better understanding of how and when bash’s various dotfiles are invoked. But the PATH of least resistance (I slay me) was to update the environment variable. 


Drafts and Dropbox redux

About a year ago, I wrote a post about using something I called filelines (akin to Vim’s modelines) and Drafts actions to save drafts to Dropbox. The little two-step action I wrote back then served me well for a long time, but I’ve recently updated it and created a reverse action for pulling files from Dropbox and putting their text into drafts.

The motivation for this is simple: although I do a lot of writing entirely in Drafts, text often needs to be saved in files before it can be used. And with longer pieces, like the reports I do for work, I often switch between working on my iPad and my Macs.1

The key to going back and forth between drafts and files is the fileline, a comment line that tells Drafts where that draft is saved as a file in Dropbox. In LaTeX, a fileline takes this form:

% dbox:/projects/ritchie.condo/report/report.tex

Similarly in Python:

# dbox:/projects/thompson.bridge/analysis/stringer.py

Basically, a fileline is dbox: followed by the POSIX-style path to the file, starting at the root of the Dropbox folder.

The action that saves a draft to the file specified in the fileline has just one step, this script:

javascript:
 1:  // Get Dropbox path from dbox: file line in draft
 2:  // and save the draft to that file.
 3:  
 4:  var d = draft.content;
 5:  
 6:  // Regex for fileline.
 7:  var fileRE = /dbox:(.+)$/m;
 8:  
 9:  if (fileRE.test(d)) {
10:    // Set the path. Add a slash to the front if I forgot.
11:    var fileline = d.match(fileRE);
12:    var flStart = d.search(fileRE);
13:    var path = fileline[1];
14:    if (path[0] != '/') {
15:      path = '/' + path;
16:      editor.setTextInRange(flStart + 5, 0, '/');
17:    }
18:    // Save the draft to the specified file.
19:    var db = Dropbox.create();
20:    var check = db.write(path, draft.content, "overwrite");
21:    if (!check) {
22:      alert("Couldn't save to " + path);
23:    }
24:  } else {
25:    alert("No dbox line");
26:  }

The basics of the script are simple; most of the code is there to handle mistakes I might make in creating the fileline.

Line 4 puts the content of the current draft into the variable d. Line 7 defines the regular expression used to find the fileline and extract the path from it.

The if/else block that starts on Line 9 tests whether there is a fileline in the draft. If there is, it does what we’ll talk about in the next few paragraphs; if not, it puts up an alert box (Line 25) warning me.

Assuming a fileline is present, Lines 11 and 12 get the matching text and its starting location in the draft. The starting location is the character position of the “d” in “dbox.” Line 13 then pulls out the part of the matching text corresponding to the parentheses in the regex and saves it to the path variable.

Lines 14–17 handle the case where I forget that the Dropbox path has to start with a slash. I have trouble remembering this because the path is relative to the Dropbox folder, and relative paths in Unix don’t start with a slash. But the Drafts scripting commands for communicating with Dropbox (which we’ll get to soon) require the leading slash. So if I forget, these lines add the leading slash to the path (Line 15) and add the slash to the fileline (Line 16).

Now we’re in the endgame. Line 19 creates the connection to Dropbox, and Line 20 saves the content of the draft (which may have been edited in Line 16) to the file defined by path. If something bad happens during the save, Line 22 tells me.

This action is called Save to Dropbox Fileline and I’ve given it the keyboard shortcut ⇧⌘S. I suppose I could have gone with just ⌘S, but that seemed presumptuous when I first wrote this action (too important a shortcut for my little script), and I haven’t changed it.

I use the reverse action when I started writing something on my iPad, wrote some more on the Mac, and then want to continue writing on the iPad. When I open the draft on my iPad, it won’t have the additions made on the Mac. But the fileline is there, so I can read in the text of the updated file from Dropbox and replace the draft with it. The action that does it, Reload from Dropbox Fileline (⇧⌘R), has a single step consisting of this script:

javascript:
 1:  // Get Dropbox path from dbox: file line in draft
 2:  // and replace draft with that file.
 3:  
 4:  var d = draft.content;
 5:  
 6:  // Regex for fileline.
 7:  var fileRE = /dbox:(.+)$/m;
 8:  
 9:  if (fileRE.test(d)) {
10:    // Set the path. Add a slash to the front if I forgot.
11:    var fileline = d.match(fileRE);
12:    var path = fileline[1];
13:    if (path[0] != '/') {
14:      path = '/' + path;
15:    }
16:    // Download file and replace the current draft.
17:    var db = Dropbox.create();
18:    var txt = db.read(path);
19:    editor.setText(txt);
20:  } else {
21:    alert("No dbox line");
22:  }

Most of the reloading script is the same as the saving script. The part that handles the possibility of my forgetting to include the leading slash in the path is simpler, because editing the draft to add it is of no value—the draft is going to be overwritten by the contents of the Dropbox file.2

Lines 18 and 19 are new. Line 18 reads the content of the file defined by path into the variable txt. Line 19 then replaces the content of the current draft with txt.

There are, of course, text editors for iOS that handle the syncing of files to Dropbox (or iCloud) automatically. If I could get as much functionality out of them as I can from Drafts, I would switch. But where can I go? I’ve had trouble with persistent scripting bugs in Editorial and it seems like abandonware now. 1Writer sounds like it has good scripting support, but when I tried it a year or two ago it just kept crashing (I probably should give it another look).

At the moment, Drafts is the only text editor I can bend to meet my needs, including reading and writing to files.


  1. The recent release of Drafts for the Mac has certainly been helpful in switching between platforms, but until the Mac Drafts feature set is on par with that of iOS Drafts, I’ll continue to do most of my writing on the Mac in BBEdit. 

  2. Given that I’ve already saved the draft to Dropbox—and my saving script adds the leading slash—the condition tested in Line 13 should never occur. But I’ve kept the test in anyway, because things that should never occur sometimes do. 


Signature features

When apps get reviewed, there’s often a signature feature that gets all the attention and skews the public perception of them. It’s not that the attention paid to the signature feature isn’t warranted, but I suspect that many people think of these apps as having only the signature feature and therefore either don’t use the app to its fullest extent or don’t use it at all.

Drafts, for example, is commonly thought of as an app for writing small bits of text that then get shunted off some other app—Mail, Messages, Reminders, Evernote, another text editor—where they “belong.” And there’s no question Drafts is spectacular at this because it launches quickly to a blank note and it has a big library of actions for moving text into other apps. Its developer, Greg Pierce, even markets Drafts as “where text starts.”

But for me, Drafts has become the place “where text is.” The power that comes from its library of actions—especially the Script action—allows me to treat Drafts as my iOS version of BBEdit, the place where I do all of my iOS writing, no matter what the text is for or how long it is.1

Blog posts, for example, are written using a group of Blogging actions that I’ve organized to be available from the sidebar

Blogging actions in Drafts sidebar

and from a set of custom keys

Blogging actions as custom keys

Many of these actions also have keyboard shortcuts.

These actions allow me to stay in Drafts from the time I first get an idea for a post until it’s published (and then corrected for typos and other mistakes pointed out by readers). There’s no need to switch to another editor just because the post is getting longer.

I keep lots of permanent notes in Drafts, too. For example, the local train schedule to and from Chicago:

Train schedule in Drafts

I could, of course, get the schedule online, but keeping it as a series of drafts—eastbound, westbound, weekdays, weekends—makes it much faster to access, and I can see a whole range of options (including the starred express runs) at once. I don’t have any actions for the train schedules, but I do use tagging and workspaces to get at them quickly.

One person who agrees with me that Drafts is not just where text starts is Tim Nahumck. He’s probably at the leading edge of using Drafts for everything, and you can get a taste of how he works in the most recent episode of the Automators podcast.

Speaking of podcasts, Castro is another app for which the signature feature often overwhelms other discussion. Castro’s inbox, where you perform triage—send this one to the top of the playlist, send that one to the bottom, just delete this other one—is what set Castro apart from other podcast players and is the reason I switched to it from Overcast a couple of years ago.

But I suspect there are many people who haven’t tried Castro because they think it forces you to make decisions about every episode of every podcast you subscribe to. If that’s the case, emphasis on the feature that made me a customer is mistakenly keeping other customers away.

Because you can set up Castro to handle new episodes differently for each podcast you subscribe to.

Options for new episodes in Castro

I certainly don’t use triage for all of the podcasts I subscribe to. Some, like Liftoff and 99% Invisible, I have set to go directly to the top of my playlist. I want to hear them as soon as they’re available. Others, like In Our Time, I almost always want to listen to but don’t feel as much urgency about. They go to the bottom of the playlist. Triage is reserved for podcasts that occasionally offer an episode in which I’m interested—a couple of Python programming podcasts and the unedited Incomparable bootleg episodes, for example. These are the “Add to New” episodes that go into the limbo of the inbox, waiting for me to look at the title and description before deciding their fate.

There’s no question it’s convenient to think of apps in terms of their signature features. After all, most of us have lives to lead and can’t keep all the features of all the apps in our heads at once. Boiling them down to their signature features is a way of dealing with app overload. But sometimes, when the rest of your life gives you a breather, it’s a good idea to look at the capabilities of an app that fall under the radar. You may find some handy capabilities.

For my part, I need to give Overcast a relook. It’s categorized in my head as Voice Boost and Smart Speed,2 features I didn’t use much. But I bet it’s refined its episode organization scheme in the time I’ve been away from it and might now edge out Castro on my home screen.


  1. OK, I sometimes slip over to Textastic for its syntax highlighting. I could swear Greg once told me that was going to be added to Drafts. 

  2. And, in the past couple of weeks, Clip Sharing