Photos and links in Markdown

I write my reports for work in Markdown and convert them into PDFs through a combination of MultiMarkdown, LaTeX, and some homemade scripts. I described the conversion process in a post last year. Today I’ll describe a new script that helps me do the writing itself in BBEdit.

The format of a typical report looks something like this:

Title: Sample Report
Format: complete
Author: Dr. Drang
Date: December 25, 2011
Client: Jane P. Client
        That Company
        1234 Wacker Drive
        Chicago IL 60606

# Introduction #

Kind or another is to me so great that nothing but the earth
rotates in the long run. If the light be sent through its
sides one half as long as he thinks prudent, the sum say it
may be prolonged in any direction or in other words, such a
scheme is quite impossible, for the same reason. that its
density is inappropriately applied to the nature of what is
important to note is, that the most obvious and mechanical
actions, also into ether forms. For instance, the mechanical
energy. The meteorite that falls.


# Description #

And again by the ether. In the ether. An exchange between
different conditions of the same rate as when they are
separated. The ether in this particular. There is no
resistance; that a mass of matter is, then, proportional to
the amount of transfer motion instead of the whole series
without assuming imponderables, or fluids or forces.
Mechanical motion only, by pressure, from any known in
physical science. would have been discovered by weighing
([this photo](#p20111225-001)).

Of them. It implies a body may be ever so gently. If they do
not thus with other velocities observed in masses of matter.
is its significance has not been shortened. Some years ago.
The inertia of the bodies possess inertia. That it should
swing ten times ([this photo](#p20111225-003)). From the
visible ones at the most remote part as at the rate of
186,000 miles long, if 186,000 times faster than such comet,
and 900,000 times a second, and the pitch of the vibrating
body will be heard in the ether. Molecular cohesion exists
between very wide ranges. When strong, so if one is
concerned only.

Make all the observed movements of double stars testify to
its activity among the thousands of them, but the records
of. Newton'quoted at the head of this intervening space must
therefore be impact and freedom alternating with each other
at near distances, and cohere if contiguous, and electric
bodies operate at greater distances, as well ([this
photo](#p20111225-010)). In which case the assumption was
that the ether is selective and it is possible to produce
rays of circularly polarized light.


# Conclusions #

Into heat and radiated away. One may say. Electrical action
is not heat-transmission. There is only one fifth that of a
mass of matter that can be asserted concerning such events
is, that the magnetic field is to be reckoned as millions of
miles from the earth, and moon, or sun, the quantity of
matter involved is not changed, so much as the whole solar
system is the mechanism each one of the same element as to
retard the rotation and the atom or say the
hundred-thousandth part.


![p20111225-001][]
![p20111225-003][]
![p20111225-010][]


[p20111225-001]: 20111225-001.jpg "A caption of a photo." plate=yes portrait=no
[p20111225-003]: 20111225-001.jpg "Another caption of a photo." plate=yes portrait=yes
[p20111225-010]: 20111225-001.jpg "Yet another caption of a photo." plate=yes portrait=no

Markdown users will recognize the section headers set off my hash marks. MultiMarkdown users will recognize the metadata lines at the top of the file. What may be unusual is the way I handle the inclusion of photographs.

A typical report of mine is less than five pages long but includes at least a dozen photographs of the equipment or structure I’m writing about.1 The photos have to be fairly large, both to show the details to my clients and to put the details in the context of the overall device. Interspersing so many large photos within the body of the report would break up the text too much and make it hard to read, so I put the photos at the end of the report and reference them in the body. I often include drawings or diagrams in my reports, too, but I usually mix them within the body because there are seldom more than one or two.

In the final processed document, the photos are referred to as “plates,” a term common in book publication, especially when they’re printed on special sheets of paper, distinct from the rest of the book. Back when my reports were always printed before shipping to the client, I used special paper to for the color photos (another reason to keep the photos together at the end of the report), which made “plates” an appropriate term. Now that my reports are almost always emailed to the client, the term is less appropriate but I still use it.

As you can see, there are three parts to a photo/plate:

  1. There’s the reference to the photo in the body of the report. This takes the form of an inline Markdown link, e.g., [this photo](#p20111225-001). During the processing of the file into a PDF, the “this photo” will be turned into something like “Plate 1,” and it will link to the photo at the end of the report. The reference starts with a hash mark because it’s an internal link. The p is a reminder to me that this is a photo, not a regular figure, and the remainder is the photo’s file name, sans the .jpg extension.
  2. There’s the photo itself, e.g., ![p20111225-001][]. This is Markdown reference-style image syntax, where the reference is the same as the alt text.
  3. Finally, there’s the reference itself at the bottom of the file. This includes the file name, the caption, and a couple of attributes I made up: plate, which distinguishes plates from regular figures; and portrait, which distinguishes portrait from landscape orientation. These get added to the <img> tag when MultiMarkdown processes the document. In the final PDF, landscape photos have their captions below the image, portrait photos have their captions to the side of the image. This saves vertical space and allows me to use larger photos while still being able to fit two per page.

As you might expect, when I was using TextMate, I had a macro/command/snippet combination to help automate this process. But because TextMate wasn’t fully scriptable, I never figured out a way to get all three parts added to the document in one step. I could get Parts 1 and 3 added automatically, but I always added Part 2 by copying and pasting (via rectangular selections) from the beginning of Part 3.

BBEdit offers a full AppleScript dictionary, which makes it easy to move the cursor around and add text anywhere in the document. Recently I wrote a script to add all three parts of a photo/plate. I start by putting the photo’s file name on the clipboard by selecting the photo in the Finder, pressing Return to select its name (sans extension), copying, and pressing Return again to deselect the name. Then I run the script, which asks me for the caption and orientation.

Combined caption and orientation dialog

All the parts are added, and the cursor placed just after Part 1 so I can continue typing. It works very smoothly and, unlike my TextMate system, does the complete job.

Here’s the script:

applescript:
 1:  -- Add a plate and a reference to it to a report.
 2:  -- The name of the JPEG file (sans extension) must be on the clipboard
 3:  -- when the script is run.
 4:  
 5:  -- Get the name of the JPEG file.
 6:  set pName to the clipboard
 7:  
 8:  -- Get the caption and orientation.
 9:  set answer to (display dialog "Caption:" with title "Insert plate" default answer "" buttons {"Cancel", "Portrait", "Landscape"} default button 3)
10:  set caption to text returned of answer
11:  if last character of caption is not "." then
12:   set caption to caption & "."
13:  end if
14:  if button returned of answer is "Portrait" then
15:   set portrait to "yes"
16:  else
17:   set portrait to "no"
18:  end if
19:  
20:  tell application "BBEdit"
21:   -- Add the link.
22:   set curPt to characterOffset of selection
23:   set link to "[this photo](#p" & pName & ")"
24:   select insertion point before character curPt of front document
25:   set selection to link
26:   
27:   -- Save the location we want to return to.
28:   select insertion point before character (curPt + (length of link)) of front document
29:   set savePt to selection
30:   
31:   -- Find the plates section.
32:   set plates to find "^!\\[p([^]]+)\\]\\[\\]$" searching in front document ¬
33:     options {search mode:grep, backwards:true, showing results:false, returning results:true, starting at top:true}
34:   
35:   -- Add this plate to the end of the section, making a new plates section if necessary.
36:   if found of plates then
37:     set lastPlate to end_offset of last item of found matches of plates
38:     select insertion point after character lastPlate of front document
39:   else
40:     select insertion point after last character of front document
41:     set selection to linefeed & linefeed
42:     select insertion point before line -2 of front document
43:   end if
44:   set selection to "![p" & pName & "][]" & linefeed
45:   
46:   -- Add the reference to the bottom of the document.
47:   select insertion point after last character of front document
48:   set selection to "[p" & pName & "]: " & pName & ".jpg " & " " & ¬
49:     quote & caption & quote & " plate=yes  portrait=" & portrait & linefeed
50:   
51:   -- Return the cursor to where we started.
52:   select savePt
53:  end tell

The regular expression in Line 35 looks more complicated than it really is because the literal square brackets have to be escaped with backslashes, and the backslashes themselves have to be escaped to protect them from AppleScript. The rest of the script is, I think, pretty straightforward.

If I knew how to use Xcode, I’d try to make up a custom dialog box in which both the caption and the orientation could be chosen at once the orientation would be chosen via radio buttons. Someday, maybe. [Update 9/27/12: I originally had the caption and orientation set in two separate dialogs. Thanks to tofias in the comments for pointing out that I could get both pieces of information in one dialog by using three buttons.]

Because I’m the only person in the world who uses my system for writing reports, this script can’t be directly used by anyone else, but I’m posting it with the hope that others will see something in it that they could use in their own workflows.


  1. My reports are usually a description of the state of a machine or structure (failed, commonly) and an analysis of how it got that way.