One day a few years ago, I saw an article about a vending machine that prints short stories based on the number of minutes needed to read the story. It’s print medium is thermal receipt paper. I thought, genius! It gives the reader a short break from screen time and the used story can be thrown in recycling.
Recently I stumbled across my trusty (not much used) label printer, which uses thermal labels. Wanting to find a new use for it I decided to try and emulate the short story dispenser. I quickly ran through the specs finding that, yes it can take receipt paper 2.5" in width. The closest I could find was DYMO 30270 which was 2.25". Surfing Amazon I found that Kenco produces 2 7/16" x 3,360" receipt paper. I took a chance at $4.99 and lo, it fit like a glove! It uses the full width of the printer without issue.
I hit a bit of a road block because most modern printer drivers (Windows, Cups etc) aren’t really setup to print on continuous paper. In the good old days I had a dot matrix printer and could print reams of continuous text, banners etc without issue. Now in the laser printer era those times are forgotten.
Time to write some code. Cudos to DYMO for publishing the complete technical reference for their printers. This document outlines the complete printer language of the printer. In a nutshell the printer is a 1 bit thick line printer. There are no extra functions to print characters, graphics or anything. It is the quinnesential dumb printer, but that’s a good thing!
When you don’t have anything to print there’s always random data. The printer can take data in 1 byte blocks so to print the full width you need 84 bytes. This will print off a cube of random bits 672x672. It also showcases how to format each function call type to the printer from C.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc,char **argv)
{
// Graphics/Barcode Mode
printf("\ei");
// Continuous paper
int length= -10;
printf("\eL%c%c",length >> 8, length);
// Bytes per line
printf("\eD%c",84);
// Init random number generator
srand((unsigned int) time (NULL));
// Send the data
for (int y=0; y<672; y++)
{
printf("\x16");
for (int x=0; x<84; x++)
putc(rand(),stdout);
}
// Eject
printf("\eE");
return EXIT_SUCCESS;
}
Ok smart guy, great program (that outputs to stdout), now how do you get it to the printer over USB. Ummmm…
Fortunally I’ve written up a guide on how to send the output of the program directly to the printer for both Linux & Windows!. No drivers needed, remember the program IS the driver.
Now is where we can do something meaningful. To keep the code below simple, I needed a graphics file format that only allowed black and white and was extremely easy to parse. The pragmatist rises to meet the challenge! The Portable Bitmap Format (PBM) fits the bill perfectly. It has a simple validation header and dimensions of the image followed by a pure binary stream of data that can be fed directly to the printer. For our example we will be using the binary formatted version of a PBM to save extra code for conversion.
How to I convert to such an old format? I’ve used GIMP and ImageMagick with great success. Below are a couple of useful commands from ImageMagick I’ve found helpful:
convert -resize 672 source.jpg dest.pbm
convert -rotate 90 -resize 672 source.jpg dest.pbm
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFSIZE 1024
int main()
{
char buf[BUFSIZE];
memset(buf,0x00,BUFSIZE);
// Validate Magic Number
fgets(buf,BUFSIZE,stdin);
if (strncasecmp(buf,"P4",2)!=0)
{
fprintf(stderr,"Binary formatted PBM expected\n\n");
return EXIT_FAILURE;
}
// Skip Comments
do
fgets(buf,BUFSIZE,stdin);
while (buf[0]=='#');
int cols=0,rows=0;
sscanf(buf,"%d %d",&cols,&rows);
if (cols > BUFSIZE)
{
fprintf(stderr,"Image Width %d > buffsize %d\n\n",cols,BUFSIZE);
return EXIT_FAILURE;
}
// Graphics/Barcode Mode
printf("\ei");
// Continuous paper
int length= -10;
printf("\eL%c%c",length >> 8, length);
// Bytes per row (In the img vs printer max)
int imgbytes=cols/8 + (cols%8 > 0);
int rowbytes=imgbytes > 84 ? 84 : imgbytes;
printf("\eD%c",rowbytes);
for (int x=0; x<rows; x++)
{
if (fread(buf,sizeof(char),imgbytes,stdin)!=imgbytes)
return EXIT_FAILURE;
fputc(0x16,stdout);
fwrite(buf,sizeof(char),rowbytes,stdout);
}
// Eject
printf("\eE");
return EXIT_SUCCESS;
}
Now the “fun” part, rendering text (fonts) as bitmaps. First we need a font that’s easy to work with and has a very lightweight footprint. The first thought would be TrueType/FreeType, but we need something a bit more basic (think old).
Dating back to 1988, the Glyph Bitmap Distribution Format (BDF) was released. In a nutshell this is a human readable font file that stored in bitmap format. As luck would have it, Giuseppe Gatta wrote an awesome BDF font rendering library (nvBDFlib) intended to be used in LCD type displays. One important note is the library only renders printable ASCII characters (32-127).
It’s even a bit more complicated than that, you need to find a 300dpi BDF font that is the size and style you are looking for. Open source to the rescue. otf2bdf is an awesome program that converts TTF files to BDF! You can customize your font to any point size and any resolution!
otf2bdf -p 8 -r 300 -l '32_127' -o fixed-8.bdf LiberationMono-Regular.ttf
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <nvbdflib.h>
#define SET_BIT(value, pos) (value |= (1U<< pos))
unsigned char dataline[50][84];
void bdfbanner_drawing_function(int x, int y, int c)
{
if (c && x<(84*8) && y<50)
{
// Reverse the bit order!
int bit=abs(8-(x%8))-1;
dataline[y][x/8]=SET_BIT(dataline[y][x/8],bit);
}
}
void print_string(BDF_FONT *bdfFont, char *buf)
{
memset(dataline,0x00,50*84);
bdfPrintString(bdfFont, 0, 0, buf);
for (int i=0; i<50; i++)
{
fputc(0x16,stdout);
fwrite(dataline[i],sizeof(char),84,stdout);
}
}
int main()
{
char buf[16535];
BDF_FONT *bdfFont= bdfReadPath("fixed-8.bdf");
bdfSetDrawingFunction(bdfbanner_drawing_function);
bdfSetDrawingAreaSize(84*8,1);
bdfSetDrawingWrap(0);
// Text Mode
printf("\eh");
// Continuous paper
int length=-10;
printf("\eL%c%c",length >> 8, length);
// Bytes per line
printf("\eD%c",84);
while (fgets(buf,16535,stdin)!=NULL)
print_string(bdfFont,buf);
// Eject
printf("\eE");
return EXIT_SUCCESS;
}
We have all the tools and the knowledge, lets print a short story!
First a word about word wrap. The code was purposly written to be short and clear, adding wordwrap would have made things a bit more complicated so you’ll need to set your word wrap to 34 columns using the above font. Here is our example story.txt printed below:
Created: 2020-02-01 | Modified: 2024-10-15 |