Dieses ist die Dingfabrik Doku des freien (GPLv3) Epilog Laser Treibers für Linux.

Gefunden auf http://www.as220.org/labs/wiki/index.php/Laser_Cutter_Technical_Info_Links und entsprechend angepasst.
Unten angehängte Datei als cups-epilog.c speichern und dann entsprechend hier dem Ubuntu Beispiel einrichten

 sudo apt-get install libcups2-dev
 gcc -o epilog `cups-config --cflags` cups-epilog.c `cups-config --libs`
 sudo mv epilog /usr/lib/cups/backend/
 sudo chown root:root /usr/lib/cups/backend/epilog
 sudo /etc/init.d/cups restart

Dann http://localhost:631/admin aufrufen und einen neuen Drucker pro Material einrichten mit dieser Drucker URI:

epilog://172.168.0.5/Legend/vs=7/vp=100/r=600/

Parameter:

af	Auto focus (0=no, 1=yes)
r	Resolution 75-1200
rs	Raster speed 1-100
rp	Raster power 0-100
vs	Vector speed 1-100
vp	Vector power 1-100
vf	Vector frequency 10-5000
sc	Photograph screen size in pizels, 0=threshold, +ve=line, -ve=spot, used in mono mode, default 8.
rm	Raster mode mono/grey/colour

Weitere Einstellungen für den Druckertreiber:

make/manufacturer raw
model/driver raw

Dann kann man diesen Drucker mit einer Arbeitsfläche von A2 ansteuern. Alternativ auch .ps Dateien folgend aufrufen:

lpr -P laser-cardboard-vector drawing.ps
ROT (ff0000)) mit 0.25px Vektoren schneiden.

/** @file cups-epilog.c - Epilog cups driver */

/* @file cups-epilog.c @verbatim
 *========================================================================
 * E&OE Copyright © 2002-2008 Andrews & Arnold Ltd <info@aaisp.net.uk>
 * Copyright 2008 AS220 Labs <brandon@as220.org>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *========================================================================
 *
 *
 * Author: Andrew & Arnold Ltd and Brandon Edens
 *
 *
 * Description:
 * Epilog laser engraver
 *
 * The Epilog laser engraver comes with a windows printer driver. This works
 * well with Corel Draw, and that is about it. There are other windows
 * applications, like inkscape, but these rasterise the image before sending to
 * the windows printer driver, so there is no way to use them to vector cut!
 *
 * The cups-epilog app is a cups backend, so build and link/copy to
 * /usr/lib/cups/backend/epilog. It allows you to print postscript to the laser
 * and both raster and cut. It works well with inkscape.
 *
 * With this linux driver, vector cutting is recognised by any line or curve in
 * 100% red (1.0 0.0 0.0 setrgbcolor).
 *
 * Create printers using epilog://host/Legend/options where host is the
 * hostname or IP of the epilog engraver. The options are as follows. This
 * allows you to make a printer for each different type of material.
 * af	Auto focus (0=no, 1=yes)
 * r	Resolution 75-1200
 * rs	Raster speed 1-100
 * rp	Raster power 0-100
 * vs	Vector speed 1-100
 * vp	Vector power 1-100
 * vf	Vector frequency 10-5000
 * sc	Photograph screen size in pizels, 0=threshold, +ve=line, -ve=spot, used
 *      in mono mode, default 8.
 * rm	Raster mode mono/grey/colour
 *
 * The mono raster mode uses a line or dot screen on any grey levels or
 * colours. This can be controlled with the sc parameter. The default is 8,
 * which makes a nice fine line screen on 600dpi engraving. At 600/1200 dpi,
 * the image is also lightened to allow for the size of the laser point.
 *
 * The grey raster mode maps the grey level to power level. The power level is
 * scaled to the raster power setting (unlike the windows driver which is
 * always 100% in 3D mode).
 *
 * In colour mode, the primary and secondary colours are processed as separate
 * passes, using the grey level of the colour as a power level. The power level
 * is scaled to the raster power setting. Note that red is 100% red, and non
 * 100% green and blue, etc, so 50% red, 0% green/blue is not counted as red,
 * but counts as "grey". 100% red, and 50% green/blue counts as red, half
 * power. This means you can make distinct raster areas of the page so that you
 * do not waste time moving the head over blank space between them.
 *
 * Epolog cups driver
 * Uses gs to rasterise the postscript input.
 * URI is epilog://host/Legend/options
 * E.g. epilog://host/Legend/rp=100/rs=100/vp=100/vs=10/vf=5000/rm=mono/flip/af
 * Options are as follows, use / to separate :-
 * rp   Raster power
 * rs   Raster speed
 * vp   Vector power
 * vs   Vector speed
 * vf   Vector frequency
 * w    Default image width (pt)
 * h    Default image height (pt)
 * sc   Screen (lpi = res/screen, 0=simple threshold)
 * r    Resolution (dpi)
 * af   Auto focus
 * rm   Raster mode (mono/grey/colour)
 * flip X flip (for reverse cut)
 * Raster modes:-
 * mono Screen applied to grey levels
 * grey Grey levels are power (scaled to raster power setting)
 * colour       Each colour grey/red/green/blue/cyan/magenta/yellow plotted
 * separately, lightness=power
 *
 *
 * Installation:
 * gcc -o epilog `cups-config --cflags` cups-epilog.c `cups-config --libs`
 * http://www.cups.org/documentation.php/api-overview.html
 *
 * Manual testing can be accomplished through execution akin to:
 * $ export DEVICE_URI="epilog://epilog-mini/Legend/rp=100/rs=100/vp=100/vs=10/vf=5000/rm=grey"
 * # ./epilog job user title copies options
 * $ ./epilog 123 jdoe test 1 options < hello-world.ps
 *
 */

/*************************************************************************
 * includes
 */
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/*************************************************************************
 * local defines
 */

/** Default on whether or not auto-focus is enabled. */
#define AUTO_FOCUS (1)

/** Default bed height (y-axis) in pts. */
#define BED_HEIGHT (1296)

/** Default bed width (x-axis) in pts. */
#define BED_WIDTH (1728)

/** Number of bytes in the bitmap header. */
#define BITMAP_HEADER_NBYTES (54)

/** Default for debug mode. */
#define DEBUG (0)

/** Basename for files generated by the program. */
#define FILE_BASENAME "epilog"

/** Number of characters allowable for a filename. */
#define FILENAME_NCHARS (128)

/** Default on whether or not the result is supposed to be flipped along the X
 * axis.
 */
#define FLIP (0)

/** Maximum allowable hostname characters. */
#define HOSTNAME_NCHARS (1024)

/** Additional offset for the X axis. */
#define HPGLX (0)

/** Additional offset for the Y axis. */
#define HPGLY (0)

/** Accepted number of points per an inch. */
#define POINTS_PER_INCH (72)

/** Maximum wait before timing out on connecting to the printer (in seconds). */
#define PRINTER_MAX_WAIT (300)

/** Default mode for processing raster engraving (varying power depending upon
 * image characteristics).
 * Possible values are:
 * 'c' = color determines power level
 * 'g' = grey-scale levels determine power level
 * 'm' = mono mode
 * 'n' = no rasterization
 */
#define RASTER_MODE_DEFAULT 'm'

/** Default power level for raster engraving */
#define RASTER_POWER_DEFAULT (40)

/** Whether or not the raster printing is to be repeated. */
#define RASTER_REPEAT (1)

/** Default speed for raster engraving */
#define RASTER_SPEED_DEFAULT (100)

/** Default resolution is 600 DPI */
#define RESOLUTION_DEFAULT (600)

/** Pixel size of screen (0 is threshold).
 * FIXME - add more details
 */
#define SCREEN_DEFAULT (8)

/** Number of seconds per a minute. */
#define SECONDS_PER_MIN (60)

/** Temporary directory to store files. */
#define TMP_DIRECTORY "/tmp"

/** FIXME */
#define VECTOR_FREQUENCY_DEFAULT (1500)

/** Default power level for vector cutting. */
#define VECTOR_POWER_DEFAULT (50)

/** Default speed level for vector cutting. */
#define VECTOR_SPEED_DEFAULT (30)

/*************************************************************************
 * local types
 */

/*************************************************************************
 * local variables
 */

/** Temporary buffer for building out strings. */
static char buf[102400];

/** Determines whether or not debug is enabled. */
static char debug = DEBUG;

/** Variable to track auto-focus. */
static int focus = AUTO_FOCUS;

/** Variable to track whether or not the X axis should be flipped. */
static char flip = FLIP;

/** Height of the image (y-axis). By default this is the bed's height. */
static int height = BED_HEIGHT;

/** Job name for the print. */
static char *job_name = "";

/** User name that submitted the print job. */
static char *job_user = "";

/** Title for the job print. */
static char *job_title = "";

/** Number of copies to produce of the print. */
static char *job_copies = "";

/** Printer queue specified options. */
static char *job_options = "";

/** Variable to track the resolution of the print. */
static int resolution = RESOLUTION_DEFAULT;

/** Variable to track the mode for rasterization. One of color 'c', or
 * grey-scale 'g', mono 'm', or none 'n'
 */
static char raster_mode = RASTER_MODE_DEFAULT;

/** Variable to track the raster speed. */
static int raster_speed = RASTER_SPEED_DEFAULT;

/** Variable to track the raster power. */
static int raster_power = RASTER_POWER_DEFAULT;

/** Variable to track whether or not a rasterization should be repeated. */
static int raster_repeat = RASTER_REPEAT;

/** FIXME -- pixel size of screen, 0= threshold */
static int screen = SCREEN_DEFAULT;

/** The DEVICE_URI for the printer. */
static char *device_uri = "";

/** Options for the printer. */
static char *queue = "";

/** Variable to track the vector speed. */
static int vector_speed = VECTOR_SPEED_DEFAULT;

/** Variable to track the vector power. */
static int vector_power = VECTOR_POWER_DEFAULT;

/** Variable to track the vector frequency. FIXME */
static int vector_freq = VECTOR_FREQUENCY_DEFAULT;

/** Width of the image (x-axis). By default this is the bed's width. */
static int width = BED_WIDTH;            // default bed

/** X re-center (0 = not). */
static int x_center;

/** Track whether or not to repeat X. */
static int x_repeat = 1;

/** Y re-center (0 = not). */
static int y_center;

/** Track whether or not to repeat X. */
static int y_repeat = 1;

/*************************************************************************
 * local functions
 */
static int big_to_little_endian(uint8_t *position, int bytes);
static bool execute_ghostscript(char *filename_bitmap, char *filename_eps,
                                char *filename_vector,
                                char *bmp_mode, int resolution,
                                int height, int width);
static bool generate_raster(FILE *pjl_file, FILE *bitmap_file);
static bool generate_vector(FILE *pjl_file, FILE *vector_file);
static bool generate_pjl(FILE *bitmap_file, FILE *pjl_file, FILE *vector_file);
static bool ps_to_eps(FILE *ps_file, FILE *eps_file);
static bool process_job_title_commands(char *title);
static bool process_queue_options(char *queue);
static void range_checks(void);
static int printer_connect(const char *host, const int timeout);
static bool printer_disconnect(int socket_descriptor);
static bool printer_send(const char *host, FILE *pjl_file);

/*************************************************************************/

/**
 * Convert a big endian value stored in the array starting at the given pointer
 * position to its little endian value.
 *
 * @param position the starting location for the conversion. Each successive
 * unsigned byte is upto nbytes is considered part of the value.
 * @param nbytes the number of successive bytes to convert.
 *
 * @return An integer containing the little endian value of the successive
 * bytes.
 */
static int
big_to_little_endian(uint8_t *position, int nbytes)
{
    int i;
    int result = 0;

    for (i = 0; i < nbytes; i++) {
        result += *(position + i) << (8 * i);
    }
    return result;
}

/**
 * Execute ghostscript feeding it an ecapsulated postscript file which is then
 * converted into a bitmap image. As a byproduct output of the ghostscript
 * process is redirected to a .vector file which will contain instructions on
 * how to perform a vector cut of lines within the postscript.
 *
 * @param filename_bitmap the filename to use for the resulting bitmap file.
 * @param filename_eps the filename to read in encapsulated postscript from.
 * @param filename_vector the filename that will contain the vector
 * information.
 * @param bmp_mode a string which is one of bmp16m, bmpgray, or bmpmono.
 * @param resolution the encapsulated postscript resolution.
 * @param height the postscript height in points per inch.
 * @param width the postscript width in points per inch.
 *
 * @return Return true if the execution of ghostscript succeeds, false
 * otherwise.
 */
static bool
execute_ghostscript(char *filename_bitmap,
                    char *filename_eps,
                    char *filename_vector,
                    char *bmp_mode, int resolution,
                    int height, int width)
{
    char buf[8192];
    sprintf(buf,
            "/usr/bin/gs -q -dBATCH -dNOPAUSE -r%d -g%dx%d -sDEVICE=%s \
-sOutputFile=%s %s > %s",
            resolution,
            (width * resolution) / POINTS_PER_INCH,
            (height * resolution) / POINTS_PER_INCH,
            bmp_mode,
            filename_bitmap,
            filename_eps,
            filename_vector);
    if (debug) {
        fprintf(stderr, "%s\n", buf);
    }
    if (system(buf)) {
        return false;
    }
    return true;
}

/**
 *
 */
static bool
generate_raster(FILE *pjl_file, FILE *bitmap_file)
{
    int h;
    int d;
    int offx;
    int offy;
    int basex = 0;
    int basey = 0;
    int repeat;

    uint8_t bitmap_header[BITMAP_HEADER_NBYTES];

    if (x_center) {
        basex = x_center - width / 2;
    }
    if (y_center) {
        basey = y_center - height / 2;
    }
    if (basex < 0) {
        basex = 0;
    }
    if (basey < 0) {
        basey = 0;
    }
    // rasterises
    basex = basex * resolution / POINTS_PER_INCH;
    basey = basey * resolution / POINTS_PER_INCH;

    repeat = raster_repeat;
    while (repeat--) {
        /* repeated (over printed) */
        int pass;
        int passes;
        long base_offset;
        if (raster_mode == 'c') {
            passes = 7;
        } else {
            passes = 1;
        }

        /* Read in the bitmap header. */
        fread(bitmap_header, 1, BITMAP_HEADER_NBYTES, bitmap_file);

        /* Re-load width/height from bmp as it is possible that someone used
         * setpagedevice or some such
         */
        /* Bytes 18 - 21 are the bitmap width (little endian format). */
        width = big_to_little_endian(bitmap_header + 18, 4);

        /* Bytes 22 - 25 are the bitmap height (little endian format). */
        height = big_to_little_endian(bitmap_header + 22, 4);

        /* Bytes 10 - 13 base offset for the beginning of the bitmap data. */
        base_offset = big_to_little_endian(bitmap_header + 10, 4);

        if (raster_mode == 'c' || raster_mode == 'g') {
            /* colour/grey are byte per pixel power levels */
            h = width;
            /* BMP padded to 4 bytes per scan line */
            d = (h * 3 + 3) / 4 * 4;
        } else {
            /* mono */
            h = (width + 7) / 8;
            /* BMP padded to 4 bytes per scan line */
            d = (h + 3) / 4 * 4;
        }
        if (debug) {
            fprintf(stderr, "Width %d Height %d Bytes %d Line %d\n",
                    width, height, h, d);
        }

        /* Raster Orientation */
        fprintf(pjl_file, "\e*r0F");
        /* Raster power */
        fprintf(pjl_file, "\e&y%dP",
                (raster_mode == 'c' ||
                 raster_mode == 'g') ? 100 : raster_power);
        /* Raster speed */
        fprintf(pjl_file, "\e&z%dS", raster_speed);
        fprintf(pjl_file, "\e*r%dT", height * y_repeat);
        fprintf(pjl_file, "\e*r%dS", width * x_repeat);
        /* Raster compression */
        fprintf(pjl_file, "\e*b%dM", (raster_mode == 'c' || raster_mode == 'g')
                ? 7 : 2);
        /* Raster direction (1 = up) */
        fprintf(pjl_file, "\e&y1O");
        /* start at current position */
        fprintf(pjl_file, "\e*r1A");
        for (offx = width * (x_repeat - 1); offx >= 0; offx -= width) {
            for (offy = height * (y_repeat - 1); offy >= 0; offy -= height) {
                for (pass = 0; pass < passes; pass++) {
                    // raster (basic)
                    int y;
                    char dir = 0;

                    fseek(bitmap_file, base_offset, SEEK_SET);
                    for (y = height - 1; y >= 0; y--) {
                        int l;

                        switch (raster_mode) {
                        case 'c':      // colour (passes)
                        {
                            char *f = buf;
                            char *t = buf;
                            if (d > sizeof (buf)) {
                                perror("Too wide");
                                return false;
                            }
                            l = fread ((char *)buf, 1, d, bitmap_file);
                            if (l != d) {
                                fprintf(stderr, "Bad bit data from gs %d/%d (y=%d)\n", l, d, y);
                                return false;
                            }
                            while (l--) {
                                // pack and pass check RGB
                                int n = 0;
                                int v = 0;
                                int p = 0;
                                int c = 0;
                                for (c = 0; c < 3; c++) {
                                    if (*f > 240) {
                                        p |= (1 << c);
                                    } else {
                                        n++;
                                        v += *f;
                                    }
                                    f++;
                                }
                                if (n) {
                                    v /= n;
                                } else {
                                    p = 0;
                                    v = 255;
                                }
                                if (p != pass) {
                                    v = 255;
                                }
                                *t++ = 255 - v;
                            }
                        }
                        break;
                        case 'g':      // grey level
                        {
                            /* BMP padded to 4 bytes per scan line */
                            int d = (h + 3) / 4 * 4;
                            if (d > sizeof (buf)) {
                                fprintf(stderr, "Too wide\n");
                                return false;
                            }
                            l = fread((char *)buf, 1, d, bitmap_file);
                            if (l != d) {
                                fprintf (stderr, "Bad bit data from gs %d/%d (y=%d)\n", l, d, y);
                                return false;
                            }
                            for (l = 0; l < h; l++) {
                                buf[l] = (255 - (uint8_t)buf[l]);
                            }
                        }
                        break;
                        default:       // mono
                        {
                            int d = (h + 3) / 4 * 4;  // BMP padded to 4 bytes per scan line
                            if (d > sizeof (buf))
                            {
                                perror("Too wide");
                                return false;
                            }
                            l = fread((char *) buf, 1, d, bitmap_file);
                            if (l != d)
                            {
                                fprintf(stderr, "Bad bit data from gs %d/%d (y=%d)\n", l, d, y);
                                return false;
                            }
                        }
                        }

                        if (raster_mode == 'c' || raster_mode == 'g') {
                            for (l = 0; l < h; l++) {
                                /* Raster value is multiplied by the
                                 * power scale.
                                 */
                                buf[l] = (uint8_t)buf[l] * raster_power / 255;
                            }
                        }

                        /* find left/right of data */
                        for (l = 0; l < h && !buf[l]; l++) {
                            ;
                        }

                        if (l < h) {
                            /* a line to print */
                            int r;
                            int n;
                            unsigned char pack[sizeof (buf) * 5 / 4 + 1];
                            for (r = h - 1; r > l && !buf[r]; r--) {
                                ;
                            }
                            r++;
                            fprintf(pjl_file, "\e*p%dY", basey + offy + y);
                            fprintf(pjl_file, "\e*p%dX", basex + offx +
                                    ((raster_mode == 'c' || raster_mode == 'g') ? l : l * 8));
                            if (dir) {
                                fprintf(pjl_file, "\e*b%dA", -(r - l));
                                // reverse bytes!
                                for (n = 0; n < (r - l) / 2; n++){
                                    unsigned char t = buf[l + n];
                                    buf[l + n] = buf[r - n - 1];
                                    buf[r - n - 1] = t;
                                }
                            } else {
                                fprintf(pjl_file, "\e*b%dA", (r - l));
                            }
                            dir = 1 - dir;
                            // pack
                            n = 0;
                            while (l < r) {
                                int p;
                                for (p = l; p < r && p < l + 128 && buf[p]
                                         == buf[l]; p++) {
                                    ;
                                }
                                if (p - l >= 2) {
                                    // run length
                                    pack[n++] = 257 - (p - l);
                                    pack[n++] = buf[l];
                                    l = p;
                                } else {
                                    for (p = l;
                                         p < r && p < l + 127 &&
                                             (p + 1 == r || buf[p] !=
                                              buf[p + 1]);
                                         p++) {
                                        ;
                                    }

                                    pack[n++] = p - l - 1;
                                    while (l < p) {
                                        pack[n++] = buf[l++];
                                    }
                                }
                            }
                            fprintf(pjl_file, "\e*b%dW", (n + 7) / 8 * 8);
                            r = 0;
                            while (r < n)
                                fputc(pack[r++], pjl_file);
                            while (r & 7)
                            {
                                r++;
                                fputc(0x80, pjl_file);
                            }
                        }
                    }
                }
            }
        }
        fprintf(pjl_file, "\e*rC");       // end raster
        fputc(26, pjl_file);      // some end of file markers
        fputc(4, pjl_file);
    }
    return true;
}

/**
 *
 */
static bool
generate_vector(FILE *pjl_file, FILE *vector_file)
{
    char up = 1;           // output status
    char newline = 1;      // input status (last was M)
    char started = 0;
    int sx = 0;
    int sy = 0;
    int lx = 0;
    int ly = 0;
    int power = 100;
    int offx;
    int offy;
    int basex = 0;
    int basey = 0;

    if (x_center) {
        basex = x_center - width / 2;
    }
    if (y_center) {
        basey = y_center - height / 2;
    }
    if (basex < 0) {
        basex = 0;
    }
    if (basey < 0) {
        basey = 0;
    }
    // rasterises
    basex = basex * resolution / POINTS_PER_INCH;
    basey = basey * resolution / POINTS_PER_INCH;

    for (offy = height * (y_repeat - 1); offy >= 0; offy -= height) {
        for (offx = width * (x_repeat - 1); offx >= 0; offx -= width) {
            char passstart = 0;
            rewind(vector_file);
            while (fgets((char *) buf, sizeof (buf), vector_file)) {
                if (isalpha(*buf)) {
                    int x,
                        y;
                    if (!passstart) {
                        passstart = 1;
                        fprintf(pjl_file, "IN;");
                        fprintf(pjl_file, "XR%04d;", vector_freq);
                        fprintf(pjl_file, "YP%03d;", vector_power);
                        fprintf(pjl_file, "ZS%03d;", vector_speed);

                    }
                    switch (*buf) {
                    case 'M': // move
                        if (sscanf((char *) buf + 1, "%d,%d", &y, &x)
                            == 2) {
                            sx = x;
                            sy = y;
                            newline = 1;
                        }
                        break;
                    case 'C': // close - only makes sense after an "L"
                        if (newline == 0 && up == 0 && (lx != sx || ly
                                                        != sy)) {
                            fprintf(pjl_file, ",%d,%d", basex + offx + sx +
                                    HPGLX, basey + offy + sy + HPGLY);
                        }
                        break;
                    case 'P': // power
                        if (sscanf((char *)buf + 1, "%d", &x) == 1
                            && x != power) {
                            int epower;
                            power = x;
                            if (!started) {
                                started = 1;
                                /* XXX disabled as current code path inserts
                                 *  this statement AFTER the IN; statement.
                                 */
                                /* start HPGL */
/*                                 fprintf(pjl_file, "\e%%1B");     */
                            }
                            if (!up) {
                                fprintf(pjl_file, ";PU");
                            }
                            up = 1;
                            epower = (power * vector_power + 50) / 100;
                            if (vector_speed && vector_speed < 100) {
                                int espeed = vector_speed;
                                int efreq = vector_freq;
                                if (epower && x < 100) {
                                    int r;
                                    int q;
                                    r = 10000 / x; // power, up to set power level (i.e. x=100)
                                    q = 10000 / espeed;
                                    if (q < r)
                                        r = q;
                                    q = 500000 / efreq;
                                    if (q < r)
                                        r = q;
                                    epower = (50 + epower * r) / 100;
                                    espeed = (50 + espeed * r) / 100;
                                    efreq = (50 + espeed * r) / 100;
                                }
                                fprintf(pjl_file, ";ZS%03d;XR%03d;", espeed, efreq);
                            }
                            fprintf(pjl_file, ";YP%03d;", epower);
                        }
                        break;
                    case 'L': // line
                        if (!started) {
                            started = 1;
                            //fprintf(pjl_file, "\e%%1B;");      // start HPGL
                        }
                        if (newline) {
                            if (!up)
                                fprintf(pjl_file, ";");
                            fprintf(pjl_file, "PU%d,%d",
                                    basex + offx + sx + HPGLX,
                                    basey + offy + sy + HPGLY);
                            up = 1;
                            newline = 0;
                        }
                        if (up) {
                            fprintf(pjl_file, ";PD");
                        } else {
                            fprintf(pjl_file, ",");
                        }
                        up = 0;
                        if (sscanf ((char *) buf + 1, "%d,%d", &y, &x)
                            == 2) {
                            fprintf (pjl_file, "%d,%d", basex + offx + x +
                                     HPGLX, basey + offy + y + HPGLY);
                        }
                        lx = x;
                        ly = y;
                        break;
                    }
                    if (*buf == 'X')
                        break;
                }
            }
        }
    }
    if (started) {
        if (up == 0)
            fprintf(pjl_file, ";");
        fprintf(pjl_file, "\e%%0B");      // end HLGL
    }
    fprintf(pjl_file, "\e%%1BPU");  // start HLGL, and pen up, end
    return true;
}

/**
 *
 */
static bool
generate_pjl(FILE *bitmap_file, FILE *pjl_file,
                              FILE *vector_file)
{
    int i;

    /* Print the printer job language header. */
    fprintf(pjl_file, "\e%%-12345X@PJL JOB NAME=%s\r\n", job_title);
    fprintf(pjl_file, "\eE@PJL ENTER LANGUAGE=PCL\r\n");
    /* Set autofocus on or off. */
    fprintf(pjl_file, "\e&y%dA", focus);
    /* Left (long-edge) offset registration.  Adjusts the position of the
     * logical page across the width of the page.
     */
    fprintf(pjl_file, "\e&l0U");
    /* Top (short-edge) offset registration.  Adjusts the position of the
     * logical page across the length of the page.
     */
    fprintf(pjl_file, "\e&l0Z");

    /* Resolution of the print. */
    fprintf(pjl_file, "\e&u%dD", resolution);
    /* X position = 0 */
    fprintf(pjl_file, "\e*p0X");
    /* Y position = 0 */
    fprintf(pjl_file, "\e*p0Y");
    /* PCL resolution. */
    fprintf(pjl_file, "\e*t%dR", resolution);

    /* If raster power is enabled and raster mode is not 'n' then add that
     * information to the print job.
     */
    if (raster_power && raster_mode != 'n') {

        /* FIXME unknown purpose. */
        fprintf(pjl_file, "\e&y0C");

        /* We're going to perform a raster print. */
        generate_raster(pjl_file, bitmap_file);
    }

    /* If vector power is > 0 then add vector information to the print job. */
    if (vector_power) {
        fprintf(pjl_file, "\eE@PJL ENTER LANGUAGE=PCL\r\n");
        /* Page Orientation */
        fprintf(pjl_file, "\e*r0F");
        fprintf(pjl_file, "\e&y50P");
        fprintf(pjl_file, "\e&z50S");
        fprintf(pjl_file, "\e*r%dT", height * y_repeat);
        fprintf(pjl_file, "\e*r%dS", width * x_repeat);
        fprintf(pjl_file, "\e*r1A");
        fprintf(pjl_file, "\e*rC");
        fprintf(pjl_file, "\e%%1B");

        /* We're going to perform a vector print. */
        generate_vector(pjl_file, vector_file);
    }

    /* Footer for printer job language. */
    /* Reset */
    fprintf(pjl_file, "\eE");
    /* Exit language. */
    fprintf(pjl_file, "\e%%-12345X");
    /* End job. */
    fprintf(pjl_file, "@PJL EOJ \r\n");
    /* Pad out the remainder of the file with 0 characters. */
    for(i = 0; i < 4096; i++) {
        fputc(0, pjl_file);
    }
    return true;
}

/**
 * Convert the given postscript file (ps) converting it to an encapsulated
 * postscript file (eps).
 *
 * @param ps_file a file handle pointing to an opened postscript file that
 * is to be converted.
 * @param eps_file a file handle pointing to the opened encapsulated
 * postscript file to store the result.
 *
 * @return Return true if the function completes its task, false otherwise.
 */
static bool
ps_to_eps(FILE *ps_file, FILE *eps_file)
{
    int xoffset = 0;
    int yoffset = 0;

    int l;
    while (fgets((char *)buf, sizeof (buf), ps_file)) {
        fprintf(eps_file, "%s", (char *)buf);
        if (*buf != '%') {
            break;
        }
        if (!strncasecmp((char *) buf, "%%PageBoundingBox:", 18)) {
            int lower_left_x;
            int lower_left_y;
            int upper_right_x;
            int upper_right_y;
            if (sscanf((char *)buf + 14, "%d %d %d %d",
                       &lower_left_x,
                       &lower_left_y,
                       &upper_right_x,
                       &upper_right_y) == 4) {
                xoffset = lower_left_x;
                yoffset = lower_left_y;
                width = (upper_right_x - lower_left_x);
                height = (upper_right_y - lower_left_y);
                fprintf(eps_file, "/setpagedevice{pop}def\n"); // use bbox
                if (xoffset || yoffset) {
                    fprintf(eps_file, "%d %d translate\n", -xoffset, -yoffset);
                }
                if (flip) {
                    fprintf(eps_file, "%d 0 translate -1 1 scale\n", width);
                }
            }
        }
        if (!strncasecmp((char *) buf, "%!", 2)) {
            fprintf
                (eps_file,
                 "/==={(        )cvs print}def/stroke{currentrgbcolor 0.0 \
eq exch 0.0 eq and exch 0.0 ne and{(P)=== currentrgbcolor pop pop 100 mul \
round  cvi = flattenpath{transform(M)=== round cvi ===(,)=== round cvi \
=}{transform(L)=== round cvi ===(,)=== round cvi =}{}{(C)=}pathforall \
newpath}{stroke}ifelse}bind def/showpage{(X)= showpage}bind def\n");
            if (raster_mode != 'c' && raster_mode != 'g') {
                if (screen == 0) {
                    fprintf(eps_file, "{0.5 ge{1}{0}ifelse}settransfer\n");
                } else {
                    int s = screen;
                    if (s < 0) {
                        s = 0 - s;
                    }
                    if (resolution >= 600) {
                        // adjust for overprint
                        fprintf(eps_file,
                                "{dup 0 ne{%d %d div add}if}settransfer\n",
                                resolution / 600, s);
                    }
                    fprintf(eps_file, "%d 30{%s}setscreen\n", resolution / s,
                            (screen > 0) ? "pop abs 1 exch sub" :
                            "180 mul cos exch 180 mul cos add 2 div");
                }
            }
        }
    }
    while ((l = fread ((char *) buf, 1, sizeof (buf), ps_file)) > 0) {
        fwrite ((char *) buf, 1, l, eps_file);
    }
    return true;
}

/**
 * The print job title can affect the functioning of the software. Job titles
 * can take three forms:
 * xRXxRYx specify that the execution of the job should repeat.
 * cCXcCYc specify the center location for the print (in inches).
 * nofocus if autofocus is to be disabled.
 *
 * @param the print job title.
 * @return True if the system is able to process the job title, false otherwise.
 */
static bool
process_job_title_commands(char *title)
{
    // xRXxRYx for repeat, cCXcCYc is centre (mm)
    char *p = title;
    char *d;
    if (*p++ == 'x') {
        if (isdigit(*p)) {
            d = p;
            while (isdigit(*p)) {
                p++;
            }
            if (*p++ == 'x') {
                x_repeat = atoi(d);
                title = p;
                if (isdigit(*p)) {
                    d = p;
                    while (isdigit(*p)) {
                        p++;
                    }
                    if (*p++ == 'x') {
                        y_repeat = atoi(d);
                        title = p;
                    }
                }
            }
        }
    }
    p = title;
    if (*p++ == 'c') {
        if (isdigit(*p)) {
            d = p;
            while (isdigit(*p)) {
                p++;
            }
            if (*p++ == 'c') {
                x_center = atoi(d) * POINTS_PER_INCH;
                title = p;
                if (isdigit(*p)) {
                    d = p;
                    while (isdigit(*p)) {
                        p++;
                    }
                    if (*p++ == 'c') {
                        y_center = atoi(d) * POINTS_PER_INCH;
                        title = p;
                    }
                }
            }
        }
    }
    if (!strncmp(job_title, "nofocus", 7)) {
        title += 7;
        focus = 0;
    }
    return true;
}

/**
 * Process the queue options which take a form akin to:
 * @verbatim
 * /rp=100/rs=100/vp=100/vs=10/vf=5000/rm=mono/flip/af
 * @endverbatim
 * Each option is separated by the '/' character and can be specified as
 * /name=value or just /name (for boolean values).
 *
 * This function has the side effect of modifying the global variables
 * specifying major settings for the device.
 *
 * @param queue a string containing the options that are to be interpreted and
 * assigned to the global variables controlling printer characteristics.
 *
 * @return True if the function is able to complete its task, false otherwise.
 */
static bool
process_queue_options(char *queue_options)
{
    char *o = strchr(queue_options, '/');

    if (!queue_options) {
        fprintf(stderr, "URI syntax epilog://host/queue_optionsname\n");
        return false;
    }
    *queue_options++ = 0;

    if (o) {
        *o++ = 0;
        while (*o) {
            char *t = o,
                *v = "1";
            while (isalpha(*o)) {
                o++;
            }
            if (*o == '=') {
                *o++ = 0;
                v = o;
                while (*o && (isalnum(*o) || *o == '-')) {
                    o++;
                }
            }
            while (*o && !isalpha(*o)) {
                *o++ = 0;
            }
            if (!strcasecmp(t, "af")) {
                focus = atoi(v);
            }
            if (!strcasecmp(t, "r")) {
                resolution = atoi(v);
            }
            if (!strcasecmp(t, "rs")) {
                raster_speed = atoi(v);
            }
            if (!strcasecmp(t, "rp")) {
                raster_power = atoi(v);
            }
            if (!strcasecmp(t, "rm")) {
                raster_mode = tolower(*v);
            }
            if (!strcasecmp(t, "rr")) {
                raster_repeat = atoi(v);
            }
            if (!strcasecmp(t, "vs")) {
                vector_speed = atoi(v);
            }
            if (!strcasecmp(t, "vp")) {
                vector_power = atoi(v);
            }
            if (!strcasecmp(t, "vf")) {
                vector_freq = atoi(v);
            }
            if (!strcasecmp(t, "sc")) {
                screen = atoi(v);
            }
            if (!strcasecmp(t, "w")) {
                width = atoi(v);
            }
            if (!strcasecmp(t, "h")) {
                height = atoi(v);
            }
            if (!strcasecmp(t, "flip")) {
                flip = 1;
            }
            if (!strcasecmp(t, "debug")) {
                debug = 1;
            }
        }
    }
    return true;
}

/**
 * Perform range validation checks on the major global variables to ensure
 * their values are sane. If values are outside accepted tolerances then modify
 * them to be the correct value.
 *
 * @return Nothing
 */
static void
range_checks(void)
{
    if (raster_power > 100) {
        raster_power = 100;
    }
    if (raster_power < 0) {
        raster_power = 0;
    }

    if (raster_speed > 100) {
        raster_speed = 100;
    }
    if (raster_speed < 1) {
        raster_speed = 1;
    }

    if (resolution > 1200) {
        resolution = 1200;
    }
    if (resolution < 75) {
        resolution = 75;
    }

    if (screen < 1) {
        screen = 1;
    }

    if (vector_freq < 10) {
        vector_freq = 10;
    }
    if (vector_freq > 5000) {
        vector_freq = 5000;
    }

    if (vector_power > 100) {
        vector_power = 100;
    }
    if (vector_power < 0) {
        vector_power = 0;
    }

    if (vector_speed > 100) {
        vector_speed = 100;
    }
    if (vector_speed < 1) {
        vector_speed = 1;
    }
}

/**
 * Connect to a printer.
 *
 * @param host The hostname or IP address of the printer to connect to.
 * @param timeout The number of seconds to wait before timing out on the
 * connect operation.
 * @return A socket descriptor to the printer.
 */
static int
printer_connect(const char *host, const int timeout)
{
    int socket_descriptor = -1;
    int i;

    for (i = 0; i < timeout; i++) {
        struct addrinfo *res;
        struct addrinfo *addr;
        struct addrinfo base = { 0, PF_UNSPEC, SOCK_STREAM };
        int error_code = getaddrinfo(host, "printer", &base, &res);

        /* Set an alarm to go off if the program has gone out to lunch. */
        alarm(SECONDS_PER_MIN);

        /* If getaddrinfo did not return an error code then we attempt to
         * connect to the printer and establish a socket.
         */
        if (!error_code) {
            for (addr = res; addr; addr = addr->ai_next) {
                socket_descriptor = socket(addr->ai_family, addr->ai_socktype,
                                           addr->ai_protocol);
                if (socket_descriptor >= 0) {
                    if (!connect(socket_descriptor, addr->ai_addr,
                                 addr->ai_addrlen)) {
                        break;
                    } else {
                        close(socket_descriptor);
                        socket_descriptor = -1;
                    }
                }
            }
            freeaddrinfo(res);
        }
        if (socket_descriptor >= 0) {
            break;
        }

        /* Sleep for a second then try again. */
        sleep(1);
    }
    if (i >= timeout) {
        fprintf(stderr, "Cannot connect to %s\n", host);
        return -1;
    }
    /* Disable the timeout alarm. */
    alarm(0);
    /* Return the newly opened socket descriptor */
    return socket_descriptor;
}

/**
 * Disconnect from a printer.
 *
 * @param socket_descriptor the descriptor of the printer that is to be
 * disconnected from.
 * @return True if the printer connection was successfully closed, false otherwise.
 */
static bool
printer_disconnect(int socket_descriptor)
{
    int error_code;
    error_code = close(socket_descriptor);
    /* Report on possible errors to standard error. */
    if (error_code == -1) {
        switch (errno) {
        case EBADF:
            perror("Socket descriptor given was not valid.");
            break;
        case EINTR:
            perror("Closing socket descriptor was interrupted by a signal.");
            break;
        case EIO:
            perror("I/O error occurred during closing of socket descriptor.");
            break;
        }
    }

    /* Return status of disconnect operation to the calling function. */
    if (error_code) {
        return false;
    } else {
        return true;
    }
}

/**
 *
 */
static bool
printer_send(const char *host, FILE *pjl_file)
{
    char localhost[HOSTNAME_NCHARS] = "";
    unsigned char lpdres;
    int socket_descriptor = -1;

    gethostname(localhost, sizeof(localhost));
    {
        char *d = strchr(localhost, '.');
        if (d) {
            *d = 0;
        }
    }

    /* Connect to the printer. */
    socket_descriptor = printer_connect(host, PRINTER_MAX_WAIT);

    // talk to printer
    sprintf(buf, "\002%s\n", queue);
    write(socket_descriptor, (char *)buf, strlen(buf));
    read(socket_descriptor, &lpdres, 1);
    if (lpdres) {
        fprintf (stderr, "Bad response from %s, %u\n", host, lpdres);
        return false;
    }
    sprintf(buf, "H%s\n", localhost);
    sprintf(buf + strlen(buf) + 1, "P%s\n", job_user);
    sprintf(buf + strlen(buf) + 1, "J%s\n", job_title);
    sprintf(buf + strlen(buf) + 1, "ldfA%s%s\n", job_name, localhost);
    sprintf(buf + strlen(buf) + 1, "UdfA%s%s\n", job_name, localhost);
    sprintf(buf + strlen(buf) + 1, "N%s\n", job_title);
    sprintf(buf + strlen(buf) + 1, "\002%d cfA%s%s\n", (int)strlen(buf), job_name, localhost);
    write(socket_descriptor, buf + strlen(buf) + 1, strlen(buf + strlen(buf) + 1));
    read(socket_descriptor, &lpdres, 1);
    if (lpdres) {
        fprintf(stderr, "Bad response from %s, %u\n", host, lpdres);
        return false;
    }
    write(socket_descriptor, (char *)buf, strlen(buf) + 1);
    read(socket_descriptor, &lpdres, 1);
    if (lpdres) {
        fprintf(stderr, "Bad response from %s, %u\n", host, lpdres);
        return false;
    }
    {
        {
            struct stat file_stat;
            if (fstat(fileno(pjl_file), &file_stat)) {
                perror(buf);
                return false;
            }
            sprintf((char *) buf, "\003%u dfA%s%s\n", (int) file_stat.st_size, job_name, localhost);
        }
        write(socket_descriptor, (char *)buf, strlen(buf));
        read(socket_descriptor, &lpdres, 1);
        if (lpdres) {
            fprintf(stderr, "Bad response from %s, %u\n", host, lpdres);
            return false;
        }
        {
            int l;
            while ((l = fread((char *)buf, 1, sizeof (buf), pjl_file)) > 0) {
                write(socket_descriptor, (char *)buf, l);
            }
        }
    }
    // dont wait for a response...
    printer_disconnect(socket_descriptor);
    return true;
}

/**
 * Main entry point for the program.
 *
 * @param argc The number of command line options passed to the program.
 * @param argv An array of strings where each string represents a command line
 * argument.
 * @return An integer where 0 represents successful termination, any other
 * value represents an error code.
 */
int
main(int argc, char *argv[])
{

    /** The printer hostname. */
    static char *host = "";

    /* Strings designating filenames. */
    char file_basename[FILENAME_NCHARS];
    char filename_bitmap[FILENAME_NCHARS];
    char filename_cups_debug[FILENAME_NCHARS];
    char filename_eps[FILENAME_NCHARS];
    char filename_pdf[FILENAME_NCHARS];
    char filename_pjl[FILENAME_NCHARS];
    char filename_ps[FILENAME_NCHARS];
    char filename_vector[FILENAME_NCHARS];

    /* File handles. */
    FILE *file_debug;
    FILE *file_bitmap;
    FILE *file_eps;
    FILE *file_cups;
    FILE *file_pdf;
    FILE *file_ps;
    FILE *file_ps_output;
    FILE *file_pjl;
    FILE *file_vector;

    /* Temporary variables. */
    int l;

    /*
     * Process the command line arguments specified by the user. Proper command
     * line arguments are fed to this program from cups and will be of a form akin
     * to:
     * @verbatim
     * job_number user job_title num_copies queue_options
     * 123        jdoe myjob     1          asdf
     * @endverbatim
     */
    if (argc == 1) {
        printf("direct epilog \"Unknown\" \"Epilog laser (thin red lines vector cut)\"\n");
        return 1;
    }
    if (argc > 1) {
        job_name = argv[1];
    }
    if (argc > 2) {
        job_user = argv[2];
    }
    if (argc > 3) {
        job_title = argv[3];
    }
    if (argc > 4) {
        job_copies = argv[4];
    }
    if (argc > 5) {
        job_options = argv[5];
    }

    /* Gather the site information from the user's environment variable. */
    device_uri = getenv("DEVICE_URI");
    if (!device_uri) {
        fprintf(stderr, "No $DEVICE_URI set\n");
        return 0;
    }
    host = strstr(device_uri, "//");
    if (!host) {
        fprintf(stderr, "URI syntax epilog://host/queuename\n");
        return 0;
    }
    host += 2;

    /* Process the queue arguments. */
    queue = strchr(host, '/');
    if (!process_queue_options(queue)) {
        fprintf(stderr, "Error processing epilog queue options.");
        return 0;
    }

    /* Perform a check over the global values to ensure that they have values
     * that are within a tolerated range.
     */
    range_checks();

    /* Determine and set the names of all files that will be manipulated by the
     * program.
     */
    sprintf(file_basename, "%s/%s-%d", TMP_DIRECTORY, FILE_BASENAME, getpid());
    sprintf(filename_bitmap, "%s.bmp", file_basename);
    sprintf(filename_eps, "%s.eps", file_basename);
    sprintf(filename_pjl, "%s.pjl", file_basename);
    sprintf(filename_vector, "%s.vector", file_basename);

    /* Gather the postscript file from either standard input or a filename
     * specified as a command line argument.
     */
    if (argc > 6) {
        file_cups = fopen(argv[6], "r");
    } else {
        file_cups = stdin;
    }
    if (!file_cups) {
        perror((argc > 6) ? argv[6] : "stdin");
        return 1;
    }

    /* Write out the incoming cups data if debug is enabled. */
    if (debug) {
        /* We save the incoming cups data to the filesystem. */
        sprintf(filename_cups_debug, "%s.cups", file_basename);
        file_debug = fopen(filename_cups_debug, "w");

        /* Check that file handle opened. */
        if (!file_debug) {
            perror(filename_cups_debug);
            return 1;
        }

        /* Write cups data to the filesystem. */
        while ((l = fread((char *)buf, 1, sizeof(buf), file_cups)) > 0) {
            fwrite((char *)buf, 1, l, file_debug);
        }
        fclose(file_debug);
        /* In case file_cups pointed to stdin we close the existing file handle
         * and switch over to using the debug file handle.
         */
        fclose(file_cups);
        file_cups = fopen(filename_cups_debug, "r");
    }

    /* Check whether the incoming data is ps or pdf data. */
    fread((char *)buf, 1, 4, file_cups);
    rewind(file_cups);
    if (strncasecmp((char *)buf, "%PDF", 4) == 0) {
        /* We have a pdf file. Convert it to postscript. */
        sprintf(filename_pdf, "%s.pdf", file_basename);
        sprintf(filename_ps, "%s.ps", file_basename);

        file_pdf = fopen(filename_pdf, "w");
        if (!file_pdf) {
            perror(filename_pdf);
            return 1;
        }

        /* Write the cups data out to the file_pdf. */
        while ((l = fread((char *)buf, 1, sizeof(buf), file_cups)) > 0) {
            fwrite((char *)buf, 1, l, file_pdf);
        }

        fclose(file_cups);
        fclose(file_pdf);

        /* Execute the command pdf2ps to convert the pdf file to ps. */
        sprintf(buf, "/usr/bin/pdf2ps %s %s", filename_pdf, filename_ps);
        if (debug) {
            fprintf(stderr, "%s\n", buf);
        }
        if (system(buf)) {
            fprintf(stderr, "Failure to execute pdf2ps. Quitting...");
            return 1;
        }

        if (!debug) {
            /* Debug is disabled so remove generated pdf file. */
            if (unlink(filename_pdf)) {
                perror(filename_pdf);
            }
        }

        /* Set file_ps to the generated ps file. */
        file_ps  = fopen(filename_ps, "r");
    } else {
        /* Input file is postscript. Set the file_ps handle to file_cups. */
        file_ps = file_cups;
    }

    /* Open the encapsulated postscript file for writing. */
    file_eps = fopen(filename_eps, "w");
    if (!file_eps) {
        perror(filename_eps);
        return 1;
    }
    /* Convert postscript to encapsulated postscript. */
    if (!ps_to_eps(file_ps, file_eps)) {
        perror("Error converting postscript to encapsulated postscript.");
        fclose(file_eps);
        return 1;
    }
    /* Cleanup after encapsulated postscript creation. */
    fclose(file_eps);
    if (file_ps != stdin) {
        fclose(file_ps);
        if (unlink(filename_ps)) {
            perror(filename_ps);
        }
    }

    if(!execute_ghostscript(filename_bitmap,
                            filename_eps,
                            filename_vector,
                            (raster_mode == 'c') ? "bmp16m" :
                            (raster_mode == 'g') ? "bmpgray" : "bmpmono",
                            resolution, height, width)) {
        perror("Failure to execute ghostscript command.\n");
        return 1;
    }

    /* The print job title has the capability to modify characteristics of the
     * print. Check for embedded information and possibly update the job title
     * before sending the title to the printer.
     */
    if (!*job_title || !strcasecmp(job_title, "_stdin_") ||
        !strcasecmp(job_title, "(stdin)") || !strncasecmp (job_title, "print ", 6)) {
        sprintf(job_title, "%dx%din", width / POINTS_PER_INCH,
                height / POINTS_PER_INCH);
    } else {
        process_job_title_commands(job_title);
    }

    /* Open file handles needed by generation of the printer job language
     * file.
     */
    file_bitmap = fopen(filename_bitmap, "r");
    file_vector = fopen(filename_vector, "r");
    file_pjl = fopen(filename_pjl, "w");
    if (!file_pjl) {
        perror(filename_pjl);
        return 1;
    }
    /* Execute the generation of the printer job language (pjl) file. */
    if (!generate_pjl(file_bitmap, file_pjl, file_vector)) {
        perror("Generation of pjl file failed.\n");
        fclose(file_pjl);
        return 1;
    }
    /* Close open file handles. */
    fclose(file_bitmap);
    fclose(file_pjl);
    fclose(file_vector);

    /* Cleanup unneeded files provided that debug mode is disabled. */
    if (!debug) {
        if (unlink(filename_bitmap)) {
            perror(filename_bitmap);
        }
        if (unlink(filename_eps)) {
            perror(filename_eps);
        }
        if (unlink(filename_vector)) {
            perror(filename_vector);
        }
    }

    /* Open printer job language file. */
    file_pjl = fopen(filename_pjl, "r");
    if (!file_pjl) {
        perror(filename_pjl);
        return 1;
    }
    /* Send print job to printer. */
    if (!printer_send(host, file_pjl)) {
        perror("Could not send pjl file to printer.\n");
        return 1;
    }
    fclose(file_pjl);
    if (!debug) {
        if (unlink(filename_pjl)) {
            perror(filename_pjl);
        }
    }

    return 0;
}