The Pragmatic Addict

Turn a Dymo LabelWriter 400 Turbo into a story teller

Dymo

Background

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.

My first program

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.

random2dymo.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;
}

Talk to the printer

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.

Results

Results

Print an image

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.

Image conversion tips

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 and Resize (keep aspect ratio) to fit printer

convert -resize 672 source.jpg dest.pbm

Do the above but rotate 90 degrees

convert -rotate 90 -resize 672 source.jpg dest.pbm

pbm2dymo.c

#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;
}

Results

Results

Print Text!

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).

But wait, where do I find a BDF font?

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!

fixed-8.bdf

otf2bdf -p 8 -r 300 -l '32_127' -o fixed-8.bdf LiberationMono-Regular.ttf

txt2dymo.c

#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;
}

Print a short story!

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:

Results


Created: 2020-02-01 Modified: 2024-10-15