Print from an Apple II to any modern printer

Some retro-computing enthusiasts may already have heard of, or used, one or more of the programs I wrote for my Apple //c. It started with a Mastodon client helped by a serial-to-network proxy, surl-server. Then things got a bit out of hand, and after a while I could stream video to my Apple //c, with Woztubes. This meant that my surl-server proxy now had two serial cables attached to the Apple II:

one on its modem port, for general proxy things like network requests and streaming audio, and one on its printer port, for streaming video.

But as long as no video is streamed, the printer cable was sitting there unused. I decided to make it useful and make it possible to print from any Apple II program, modern or vintage, to a modern printer.

To be precise, from any program supporting the ImageWriter II.

At that time, printers already worked by receiving bytes and printing something out of them, but in a much simpler way than now. Either those would be ASCII characters to print as-is, or “control” characters, meaning “turn bold on”, “set line height to X”, etc. Most (all?) modern printers support much more evolved languages describing what to put on a sheet of paper, like PostScript for example. Even PostScript is old (1982!) but it was too complicated for computers like the Apple II and printers like the ImageWriter.

So this is what surl-server does now at startup: open the printer serial port, and wait for data to arrive on it. Capture this data. Somehow convert the data from ImageWriter “language” to PostScript. Send the result to a modern printer (or a file).

This is the result:

The Print Shop, still working!

Here is a short write-up of how this works. If just want to install it, please read the dedicated surl-server page.

Waiting for data from the printer port

surl-server is still, primarily, the proxy I use to read Mastodon or listen to music on my Apple II, and as such, surl-server’s first order of business is to handle I/O from the modem port. To handle eventual data from the printer port without disturbing the normal function of the proxy, I needed to add a thread, open the printer port’s serial adapter there, and wait for bytes. And also, provide a way to stop that thread before streaming video, and start it again afterwards. That was the first commit of the process.

Somehow convert the data to PostScript

My first conversion implementation relied on IWEM, a little PostScript thing released by Apple itself as part of GS/OS, which does exactly that, in a very simple manner: Just send the IWEM script to your printer, followed straight by the raw ImageWriter II data (examples here)!

At the time, Apple provided this piece of code to allow Apple II users to print to LaserWriters. It is very refreshing, in 2024, to learn that at some point, even Apple did not deprecate everything everywhere to force customers to upgrade, is it not?

But IWEM has two big drawbacks: first, it is PostScript, very hard for me to understand and modify if needed. Second, it is not free software. It is very clearly Copyright Apple, so I could not ship it with my GPL3+ work, and even if Apple wouldn’t care, even if it would be morally OK: legally it’s not, so…

So I ditched that and instead, went with imagewriter.cpp, initially written by the DosBox folks, and now surviving in at least two IIgs emulators, under GPLv2 or later. So I did exactly what the GPL is made for: use other people’s work to further one’s work. Sometimes someone may do the same with my own code, and I’ll be glad it’s useful.

imagewriter.cpp works by opening a virtual sheet of paper (a large bitmap), and, following the ImageWriter II protocol, putting characters and dots where they should be. With that parser already written, from my perspective, this was really easy:

if (a byte arrived) {
  imagewriter_init(some parameters like filename, paper size etc);
  imagewriter_loop(first byte);
  while (another byte arrives before a timeout) {
    imagewriter_loop(byte);
  }
  imagewriter_feed();
  imagewriter_close();
}

In other words, as we get a byte, we start an imaginary ImageWriter II, give it this byte, and then as long as we get data, we give it this data. When we stop receiving data for a long enough amount of time, we consider that the print job is over, and we close the printer – there is no “I’m done printing” control character that the Apple II would send the printer, that would be too easy.

At the time, you checked with your eyes if a print was done, and then you got the paper out of the printer with the “Form Feed” button. Now, I’ve had to search for a good timeout value – as much as AppleWorks, Dazzle Draw or other programs I tried send data to the printer at 9600bps from start to end, other programs, like The Print Shop, make pauses in the middle of the printing process (cutely displaying “THINKING” on the screen) before starting again. In order to avoid cutting Print Shop jobs in half, I have set the timeout to 20 seconds.

Send the result to a printer

At that point (imagewriter_close), the Postscript file is written. If we don’t want to send the result to a real printer, we’re done. That is what will happen on a standard installation of surl-server from the image I provide, if you, the user, configure nothing: instead of being physically printed, the .ps file will be made available on ftp://surl-server.local/prints/.

But if the user has a printer configured, we want to do a physical print. For this, we need to use the CUPS system, ask it for a printer, and give it the PostScript data. CUPS comes with a C library that is quite easy to use as long as you do basic stuff:

cups_dest_t *dest;
cups_info_t *info;
int job_id;
char *filename = "the file where imagewriter.cpp printed";
FILE *fp;
char buffer[BUFSIZE];

dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, NULL, NULL);
info = cupsCopyDestInfo(CUPS_HTTP_DEFAULT, dest);
cupsCreateDestJob(CUPS_HTTP_DEFAULT, dest, info,
                  &job_id, filename, 0, NULL);
cupsStartDestDocument(CUPS_HTTP_DEFAULT, dest, info,
                      job_id, filename, CUPS_FORMAT_POSTSCRIPT,
                      0, NULL, 1);
fp = fopen(filename, "rb");
while ((size = fread(buffer, 1, BUFSIZE, fp)) > 0) {
  cupsWriteRequestData(CUPS_HTTP_DEFAULT, buffer, size);
}
fclose(fp);
cupsFinishDestDocument(CUPS_HTTP_DEFAULT, dest, info);

This code has absolutely zero error checking, return value control, or resource freeing, so absolutely don’t use it as is. It’s just there to show the global sequence required to get a print out of libcups.

What could still be done

I am quite happy with that functionality as it is, but it would be nice to have different printers implementations, and not only the ImageWriter II. That could be configured in surl-server’s printer.conf file, and could allow wider usage – although I think the ImageWriter II was widely supported at the time, not only from Apple computers. But I think this is not necessary for the Apple II use-case, as about all its software handles the ImageWriter II. Plus, that would ruin RetroPrinter‘s business model.

The first two successful prints