/*
 *  Copyright (C) 2002-2004  The DOSBox Team
 *  Copyright (C) 2020       Eluan Costa Miranda
 *
 *  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 2 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 Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Most of this code was taken from an old (as of 2020) unfinished DOSBox patch.
 * I've fixed numerous issues with the interface, drawing and printer logic and
 * this should print most things correctly and with the right positioning.
 *
 * Tested under DOS (Print Screen key, Edit.exe, Word Perfect 5.1), Win 3.1
 * (Write, Paintbrush) and Win 95 (Word 6, Paint). I've adapted it (ESC/P
 * commands resolution, mainly) to emulate the Epson LX-810, which is the
 * printer that I have.
 *
 * Dependencies: Freetype (until I have a dump of the character ROM - or maybe
 *               I keep it for an inaccurate "high-res" output?)
 * Output: Postscript, BMP, PNG, TIFF, JPG.
 *         (Haven't tested the Windows printer forwarding yet.)
 *
 * These are the print modes for the Epson LX-810:
 * -Bitmap printing emulates the pins but can optionally keep the old behaviour
 *  from the DOSBox patch and output continuously. Pin emulation is naive for
 *  now.
 * -Draft printing uses freely redistributable fonts that imitate the Epson FX
 *  Printers, they look almost the same as the LX-810. Not all extended
 *  characters are available and sometimes a combination of styles and effects
 *  isn't available, so it will fallback to a similar one (in this case the dots
 *  can potentially have slightly different dimensions.) High-speed draft
 *  characters are not available. Should be OK for english ASCII printing.
 * -Provide your own monospaced Roman and Sans Serif truetype or opentype fonts.
 *  Most fonts are pretty complete unicode-wise, so these will look nice.
 *  There is also code to handle non-monospaced fonts automatically that should
 *  work OK.
 *
 * INSTRUCTIONS:
 * Currently, a "printer" directory on the pcem main path is used. On Linux this
 * would be "~/.pcem/printer" by default. There, the fonts roman.ttf,
 * roman_italics.ttf, sansserif.ttf and sansserif_italics.ttf should be present.
 * Also a subdirectory "fonts" with all the Epson FX-80 opentype fonts from
 * http://const-iterator.de/fxmatrix/ should exist. This same directory will be
 * used for all the output files.
 *
 * There is also a menu for pressing some of the printer's control panel
 * buttons. On Linux, access with a right-click while the mouse is ungrabbed.
 *
 * TODO/Notes:
 * -Dump/Recreate the character ROM. This should make it possible to use the
 *  bitgraph functions to print the characters too.
 * -Better TTF font handling. Use STB TTF?
 * -Bind the printer panel buttons to key combinations in addition to the menu?
 * -Menu commands should only work when the printer is not accepting data. (Use
 *  DC1 / DC3 for this? Or the select bit in the parallel port?)
 * -Make the Windows print redirection better (currently is a hack that shows
 *  the printing dialog too soon).
 * -Dump to PDF with GhostScript.
 * -Printer redirection on linux (GhostScript, CUPS)
 * -Update the Mingw Makefiles with FreeType.
 * -Search for TODO in this file. :-)
 */

#define MAX_PATH_STRING 1024
#define false 0
#define true (!false)
#define bool int

#include <stdlib.h>
#include <string.h>

#include <ft2build.h>
#include FT_FREETYPE_H

#if defined(WIN32)
#include <windows.h>
#include <winspool.h>
#endif

#include <math.h>
#include "lpt_epsonlx810.h"

#include <pcem/devices.h>
#include <pcem/unsafe/config.h>
#include <pcem/logging.h>
#include <SDL.h>

#define STYLE_CONDENSED 0x02
#define STYLE_BOLD 0x04
#define STYLE_DOUBLESTRIKE 0x08
#define STYLE_DOUBLEWIDTH 0x10
#define STYLE_ITALICS 0x20
#define STYLE_UNDERLINE 0x40
#define STYLE_SUPERSCRIPT 0x80
#define STYLE_SUBSCRIPT 0x100
#define STYLE_DOUBLEWIDTHONELINE 0x800

#define QUALITY_DRAFT 0x01
#define QUALITY_NLQ 0x02

#define OUTPUT_TYPE_POSTSCRIPT 0
#define OUTPUT_TYPE_BMP 1
#define OUTPUT_TYPE_PNG 2
#define OUTPUT_TYPE_TIFF 3
#define OUTPUT_TYPE_JPG 4
#define OUTPUT_TYPE_FORWARD_TO_REAL_PRINTER 5

typedef enum Typeface {
        roman = 0,
        sansserif,
} Typeface;

typedef struct lpt_epsonprinter_t {
        FT_Library FTlib; // FreeType2 library used to render the characters

        SDL_Surface *page; // Surface representing the current page
        FT_Face curFont;   // The font currently used to render characters

        // TODO: where exactly in relation to the pins are curX and curY?
        double curX, curY; // Position of the print head (in inch)

        uint16_t dpi;    // dpi of the page
        uint16_t ESCCmd; // ESC-command that is currently processed
        bool ESCSeen;    // True if last read character was an ESC (0x1B)

        uint8_t numParam, neededParam; // Numbers of parameters already read/needed to process command

        uint8_t params[20];             // Buffer for the read params
        uint16_t defaultStyle, style;   // Style of font (see STYLE_* constants)
        double defaultCPI, cpi, actcpi; // CPI value set by DIP switch, program and the actual one (taking in account font types)

        double topMargin, bottomMargin, rightMargin, leftMargin; // Margins of the page (in inch)
        double pageWidth, pageHeight;                            // Size of page (in inch)
        double defaultPageWidth, defaultPageHeight;              // Default size of page (in inch)
        double lineSpacing;                                      // Size of one line (in inch)

        double horiztabs[32]; // Stores the set horizontal tabs (in inch)
        uint8_t numHorizTabs; // Number of configured tabs

        double verttabs[16]; // Stores the set vertical tabs (in inch)
        uint8_t numVertTabs; // Number of configured tabs

        uint8_t defaultCharTable, curCharTable; // Default and currently used char table und charset
        uint8_t defaultI18NCharset, curI18NCharset;
        uint16_t defaultCodePage;
        uint8_t defaultQuality, printQuality; // Print quality (see QUALITY_* constants)

        Typeface defaultNLQtypeFace, NLQtypeFace; // Typeface used in NLQ printing mode

        bool charRead;        // True if a character was read since the printer was last initialized
        bool autoFeed;        // True if a LF should automatically added after a CR
        bool printUpperContr; // True if the upper command characters should be printed

        struct bitGraphicParams // Holds information about printing bit images
        {
                uint16_t horizDens, vertDens; // Density of image to print (in dpi)
                bool adjacent;                // Print adjacent pixels? (ignored)
                uint8_t bytesColumn;          // Bytes per column
                uint16_t remBytes;            // Bytes left to read before image is done
                uint8_t column[6];            // Bytes of the current and last column
                uint8_t readBytesColumn;      // Bytes read so far for the current column
        } bitGraph;

        uint8_t densk, densl, densy, densz; // Image density modes used in ESC K/L/Y/Z commands

        uint16_t curMap[256];   // Currently used ASCII => Unicode mapping
        uint16_t charTables[2]; // Charactertables

#if defined(WIN32)
        HDC printerDC; // Win32 printer device
#endif

        int output_type;           // Output method selected by user
        void *outputHandle;        // If not null, additional pages will be appended to the given handle
        bool multipageOutput;      // If true, all pages are combined to one file/print job etc. until the "eject page" button is
                                   // pressed
        uint16_t multiPageCounter; // Current page (when printing multipages)

        uint8_t ASCII85Buffer[4]; // Buffer used in ASCII85 encoding
        uint8_t ASCII85BufferPos; // Position in ASCII85 encode buffer
        uint8_t ASCII85CurCol;    // Columns printed so far in the current lines

        uint8_t last_data;
        uint8_t controlreg;
        char fontpath[MAX_PATH_STRING];
        char outputpath[MAX_PATH_STRING];

        int emulatePinsForBitmaps;
} lpt_epsonprinter_t;

// Process one character sent to virtual printer
void printChar(lpt_epsonprinter_t *printer);

// Hard Reset (like switching printer off and on)
void resetPrinterHard(lpt_epsonprinter_t *printer);

// Set Autofeed value
void setAutofeed(bool feed, lpt_epsonprinter_t *printer);

// Get Autofeed value
bool getAutofeed(lpt_epsonprinter_t *printer);

// True if printer is unable to process more data right now (do not use printChar)
bool isBusy(lpt_epsonprinter_t *printer);

// True if the last sent character was received
bool ack(lpt_epsonprinter_t *printer);

// Manual formfeed
void formFeed(lpt_epsonprinter_t *printer);

// Returns true if the current page is blank
bool isBlank(lpt_epsonprinter_t *printer);

// Checks if given char belongs to a command and process it. If false, the character
// should be printed
bool processCommandChar(uint8_t ch, lpt_epsonprinter_t *printer);

// Resets the printer to the factory settings
void resetPrinter(lpt_epsonprinter_t *printer, int software_command);

// Reload font. Must be called after changing dpi, style or cpi
void updateFont(lpt_epsonprinter_t *printer);

// Clears page. If save is true, saves the current page to a bitmap
void newPage(bool save, lpt_epsonprinter_t *printer);

// Blits the given glyph on the page surface. If add is true, the values of bitmap are
// added to the values of the pixels in the page
void blitGlyph(FT_Bitmap bitmap, uint16_t destx, uint16_t desty, bool add, lpt_epsonprinter_t *printer);

// Draws an anti-aliased line from (fromx, y) to (tox, y).
void drawLine(uint16_t fromx, uint16_t tox, uint16_t y, lpt_epsonprinter_t *printer);

// Setup the bitGraph structure
void setupBitImage(uint8_t dens, uint16_t numCols, lpt_epsonprinter_t *printer);

// Process a character that is part of bit image. Must be called iff bitGraph.remBytes > 0.
void printBitGraph(uint8_t ch, lpt_epsonprinter_t *printer);

// Copies the codepage mapping from the constant array to CurMap, along with international character sets for character < 0x80
void updateCharset(lpt_epsonprinter_t *printer);

// Output current page
void outputPage(lpt_epsonprinter_t *printer);

// Prints out a byte using ASCII85 encoding (only outputs something every four bytes). When b>255, closes the ASCII85 string
void fprintASCII85(FILE *f, uint16_t b, lpt_epsonprinter_t *printer);

// Closes a multipage document
void finishMultipage(lpt_epsonprinter_t *printer);

// Returns value of the num-th pixel (couting left-right, top-down) in a safe way
uint8_t getPixel(uint32_t num, lpt_epsonprinter_t *printer);

#define FREETYPE_FONT_MULTIPLIER 64

#define PARAM16(I) (printer->params[(I) + 1] * 256 + printer->params[(I)])
#define CURX_INCH_TO_PIXEL ((unsigned int)floor(printer->curX * printer->dpi + 0.5))
#define CURY_INCH_TO_PIXEL ((unsigned int)floor(printer->curY * printer->dpi + 0.5))
// Be careful to do this as the last step before outputting anything
#define CURX_INCH_TO_PIXEL_ADD(v) ((unsigned int)floor(printer->curX * printer->dpi + 0.5 + (v)))
#define CURY_INCH_TO_PIXEL_ADD(v) ((unsigned int)floor(printer->curY * printer->dpi + 0.5 + (v)))

// these are the bsd safe string handling functions
#ifndef HAVE_STRLCAT
size_t strlcat(char *dst, const char *src, size_t size) {
        size_t srclen;
        size_t dstlen;

        dstlen = strlen(dst);
        size -= dstlen + 1;

        if (!size)
                return (dstlen);

        srclen = strlen(src);

        if (srclen > size)
                srclen = size;

        memcpy(dst + dstlen, src, srclen);
        dst[dstlen + srclen] = '\0';

        return (dstlen + srclen);
}
#endif /* !HAVE_STRLCAT */

#ifndef HAVE_STRLCPY
size_t strlcpy(char *dst, const char *src, size_t size) {
        size_t srclen;

        size--;
        srclen = strlen(src);

        if (srclen > size)
                srclen = size;

        memcpy(dst, src, srclen);
        dst[srclen] = '\0';

        return (srclen);
}
#endif /* !HAVE_STRLCPY */

// Various ASCII codepage to unicode maps

static const uint16_t italicsMap[256] = {
        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e,
        0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d,
        0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
        0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b,
        0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a,
        0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
        0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068,
        0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
        // the rest of these should be rendered in italics
        0x00e0, 0x00e8, 0x00f9, 0x00f2, 0x00ec, 0x00b0, 0x00a3, 0x00a1, 0x00bf, 0x00d1, 0x00f1, 0x00a4, 0x20a7, 0x00c5, 0x00e5,
        0x00e7, // TODO: 0x00b0 may be 0x00ba? Also 0x20a7 is annother abbreviation (Pts) instead of the Pt from epson.
        0x00a7, 0x00df, 0x00c6, 0x00e6, 0x00d8, 0x00f8, 0x00a8, 0x00c4, 0x00d6, 0x00dc, 0x00e4, 0x00f6, 0x00fc, 0x00c9, 0x00e9,
        0x00a5, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d,
        0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c,
        0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b,
        0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a,
        0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069,
        0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
        0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e,
        0x0030, // 0xff should be the slashed zero in italics? some fonts have that in codepoint 0xe00f
};

static const uint16_t cp437Map[256] = {
        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e,
        0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d,
        0x001e, 0x001f, // TODO: 0x0015 should be 0x00a7 in some revisions of the LX-810?
        0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e,
        0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d,
        0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c,
        0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b,
        0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a,
        0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079,
        0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea,
        0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
        0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa,
        0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561,
        0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500,
        0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559,
        0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393,
        0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1,
        0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0};

static const uint16_t cp850Map[256] = {
        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e,
        0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d,
        0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
        0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b,
        0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a,
        0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
        0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068,
        0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5,
        0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2,
        0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x00d7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1,
        0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502,
        0x2524, 0x00c1, 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, 0x255d, 0x00a2, 0x00a5, 0x2510, 0x2514, 0x2534, 0x252c,
        0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x00a4, 0x00f0, 0x00d0,
        0x00ca, 0x00cb, 0x00c8, 0x0131, 0x00cd, 0x00ce, 0x00cf, 0x2518, 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, 0x00d3,
        0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, 0x00b5, 0x00fe, 0x00de, 0x00da, 0x00db, 0x00d9, 0x00fd, 0x00dd, 0x00af, 0x00b4,
        0x00ad, 0x00b1, 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, 0x25a0,
        0x00a0};

static const uint16_t cp860Map[256] = {
        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e,
        0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d,
        0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
        0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b,
        0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a,
        0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
        0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068,
        0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e3, 0x00e0, 0x00c1,
        0x00e7, 0x00ea, 0x00ca, 0x00e8, 0x00cd, 0x00d4, 0x00ec, 0x00c3, 0x00c2, 0x00c9, 0x00c0, 0x00c8, 0x00f4, 0x00f5, 0x00f2,
        0x00da, 0x00f9, 0x00cc, 0x00d5, 0x00dc, 0x00a2, 0x00a3, 0x00d9, 0x20a7, 0x00d3, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1,
        0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00d2, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502,
        0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c,
        0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564,
        0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1,
        0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
        0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0,
        0x00a0};

static const uint16_t cp863Map[256] = {
        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e,
        0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d,
        0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
        0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b,
        0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a,
        0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
        0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068,
        0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00c2, 0x00e0, 0x00b6,
        0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x2017, 0x00c0, 0x00a7, 0x00c9, 0x00c8, 0x00ca, 0x00f4, 0x00cb, 0x00cf,
        0x00fb, 0x00f9, 0x00a4, 0x00d4, 0x00dc, 0x00a2, 0x00a3, 0x00d9, 0x00db, 0x0192, 0x00a6, 0x00b4, 0x00f3, 0x00fa, 0x00a8,
        0x00b8, 0x00b3, 0x00af, 0x00ce, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00be, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502,
        0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c,
        0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564,
        0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1,
        0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
        0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0,
        0x00a0};

static const uint16_t cp865Map[256] = {
        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e,
        0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d,
        0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
        0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b,
        0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a,
        0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059,
        0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068,
        0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5,
        0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2,
        0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1,
        0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00a4, 0x2591, 0x2592, 0x2593, 0x2502,
        0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c,
        0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564,
        0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1,
        0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
        0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0,
        0x00a0};

static const uint16_t intCharSets[13][12] = {
        {0x0023, 0x0024, 0x0040, 0x005b, 0x005c, 0x005d, 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e}, // USA
        {0x0023, 0x0024, 0x00e0, 0x00b0, 0x00e7, 0x00a7, 0x005e, 0x0060, 0x00e9, 0x00f9, 0x00e8,
         0x00a8}, // France TODO: 0x00b0 may be 0x00ba?
        {0x0023, 0x0024, 0x00a7, 0x00c4, 0x00d6, 0x00dc, 0x005e, 0x0060, 0x00e4, 0x00f6, 0x00fc, 0x00df}, // Germany
        {0x00a3, 0x0024, 0x0040, 0x005b, 0x005c, 0x005d, 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e}, // UK
        {0x0023, 0x0024, 0x0040, 0x00c6, 0x00d8, 0x00c5, 0x005e, 0x0060, 0x00e6, 0x00f8, 0x00e5, 0x007e}, // Denmark I
        {0x0023, 0x00a4, 0x00c9, 0x00c4, 0x00d6, 0x00c5, 0x00dc, 0x00e9, 0x00e4, 0x00f6, 0x00e5, 0x00fc}, // Sweden
        {0x0023, 0x0024, 0x0040, 0x00b0, 0x005c, 0x00e9, 0x005e, 0x00f9, 0x00e0, 0x00f2, 0x00e8,
         0x00ec}, // Italy TODO: 0x00b0 may be 0x00ba?
        {0x20a7, 0x0024, 0x0040, 0x00a1, 0x00d1, 0x00bf, 0x005e, 0x0060, 0x00a8, 0x00f1, 0x007d,
         0x007e}, // Spain I TODO: 0x20a7 is annother abbreviation (Pts) instead of the Pt from epson.
        {0x0023, 0x0024, 0x0040, 0x005b, 0x00a5, 0x005d, 0x005e, 0x0060, 0x007b, 0x007c, 0x007d, 0x007e}, // Japan
        {0x0023, 0x00a4, 0x00c9, 0x00c6, 0x00d8, 0x00c5, 0x00dc, 0x00e9, 0x00e6, 0x00f8, 0x00e5, 0x00fc}, // Norway
        {0x0023, 0x0024, 0x00c9, 0x00c6, 0x00d8, 0x00c5, 0x00dc, 0x00e9, 0x00e6, 0x00f8, 0x00e5, 0x00fc}, // Denmark II
        {0x0023, 0x0024, 0x00e1, 0x00a1, 0x00d1, 0x00bf, 0x00e9, 0x0060, 0x00ed, 0x00f1, 0x00f3, 0x00fa}, // Spain II
        {0x0023, 0x0024, 0x00e1, 0x00a1, 0x00d1, 0x00bf, 0x00e9, 0x00fc, 0x00ed, 0x00f1, 0x00f3, 0x00fa}, // Latin America
};

void resetPrinterHard(lpt_epsonprinter_t *printer) {
        printer->charRead = false;
        resetPrinter(printer, false);
}

// TODO: see exaclty what is reset with ESC @
void resetPrinter(lpt_epsonprinter_t *printer, int software_command) {
        if (!software_command) {
                printer->curX = printer->curY = 0.0;
                printer->ESCSeen = false;
                printer->ESCCmd = 0;
                printer->numParam = printer->neededParam = 0;
                // TODO: PRINTABLE AREAS ARE BEING IGNORED! Ok because we "may" print to real paper and then use the areas in the
                // real one? Have two options: 1) Have printable areas 2) Reduce the paper width accordingly.
        }
        printer->topMargin = 0.0;
        printer->leftMargin = 0.0;
        printer->rightMargin = printer->pageWidth = printer->defaultPageWidth;
        printer->bottomMargin = printer->pageHeight = printer->defaultPageHeight;
        printer->lineSpacing = (double)1 / 6;
        printer->cpi = printer->defaultCPI;
        printer->curCharTable = printer->defaultCharTable;
        printer->curI18NCharset = printer->defaultI18NCharset;
        printer->style = printer->defaultStyle;
        if (printer->defaultCharTable == 0) // Italics
                printer->printUpperContr = false;
        else
                printer->printUpperContr = true;
        printer->bitGraph.remBytes = 0;
        printer->densk = 0;
        printer->densl = 1;
        printer->densy = 2;
        printer->densz = 3;
        printer->charTables[0] = 0; // Italics
        printer->charTables[1] = printer->defaultCodePage;
        printer->NLQtypeFace = printer->defaultNLQtypeFace;
        printer->printQuality = printer->defaultQuality;

        updateCharset(printer);

        updateFont(printer);

        if (!software_command)
                newPage(false, printer);

        // Default tabs => Each eight characters
        for (unsigned int i = 0; i < 32; i++)
                printer->horiztabs[i] = i * 8 * (1 / (double)printer->cpi);
        printer->numHorizTabs = 32;

        printer->numVertTabs = 255;

        printer->last_data = 0;
}

void updateCharset(lpt_epsonprinter_t *printer) {
        uint16_t *mapToUse = NULL;
        uint16_t cp = printer->charTables[printer->curCharTable];

        switch (cp) {
        case 0:
                mapToUse = (uint16_t *)&italicsMap;
                break;
        case 437:
                mapToUse = (uint16_t *)&cp437Map;
                break;
        case 850:
                mapToUse = (uint16_t *)&cp850Map;
                break;
        case 860:
                mapToUse = (uint16_t *)&cp860Map;
                break;
        case 863:
                mapToUse = (uint16_t *)&cp863Map;
                break;
        case 865:
                mapToUse = (uint16_t *)&cp865Map;
                break;
        default:
                pclog("Unsupported codepage %i. Using CP437 instead.\n", cp);
                mapToUse = (uint16_t *)&cp437Map;
        }

        for (int i = 0; i < 256; i++)
                printer->curMap[i] = mapToUse[i];

        // TODO: See if it works like this
        printer->curMap[0x23] = intCharSets[printer->curI18NCharset][0];
        printer->curMap[0x24] = intCharSets[printer->curI18NCharset][1];
        printer->curMap[0x40] = intCharSets[printer->curI18NCharset][2];
        printer->curMap[0x5b] = intCharSets[printer->curI18NCharset][3];
        printer->curMap[0x5c] = intCharSets[printer->curI18NCharset][4];
        printer->curMap[0x5d] = intCharSets[printer->curI18NCharset][5];
        printer->curMap[0x5e] = intCharSets[printer->curI18NCharset][6];
        printer->curMap[0x60] = intCharSets[printer->curI18NCharset][7];
        printer->curMap[0x7b] = intCharSets[printer->curI18NCharset][8];
        printer->curMap[0x7c] = intCharSets[printer->curI18NCharset][9];
        printer->curMap[0x7d] = intCharSets[printer->curI18NCharset][10];
        printer->curMap[0x7e] = intCharSets[printer->curI18NCharset][11];
}

// TODO: all these fonts are approximations! Even the FX one is not exactly equal to the LX-810 and it is 7-bit ascii only.
// TODO: maybe we are calling this unnecessarily (some parameters only affect drawing)
void updateFont(lpt_epsonprinter_t *printer) {
        if (printer->curFont != NULL)
                FT_Done_Face(printer->curFont);

        if (printer->printQuality == QUALITY_NLQ) {
                const char *fontName;

                switch (printer->NLQtypeFace) {
                case roman:
                        if (printer->style & STYLE_ITALICS)
                                fontName = "roman_italics.ttf";
                        else
                                fontName = "roman.ttf";
                        break;
                case sansserif:
                        if (printer->style & STYLE_ITALICS)
                                fontName = "sansserif_italics.ttf";
                        else
                                fontName = "sansserif.ttf";
                        break;
                default:
                        fatal("Unknown font: %d\n", printer->NLQtypeFace);
                }

                char fullpath[MAX_PATH_STRING];
                strlcpy(fullpath, printer->fontpath, MAX_PATH_STRING);
                strlcat(fullpath, fontName, MAX_PATH_STRING);

                if (FT_New_Face(printer->FTlib, fullpath, 0, &printer->curFont)) {
                        fatal("Unable to load font %s\n", fullpath);
                        printer->curFont = NULL;
                }

                double horizPoints = 10.5;
                double vertPoints = 10.5;

                printer->actcpi = printer->cpi;

                // TODO: condensed only for draft fonts?
                if (printer->cpi != 10 && !(printer->style & STYLE_CONDENSED)) {
                        horizPoints *= (double)10 / (double)printer->cpi;
                        // vertPoints *= (double)10 / (double)printer->cpi;
                }

                if (printer->cpi == 10 && (printer->style & STYLE_CONDENSED)) {
                        // TODO: 17.125, 17.16 or 17.14?
                        printer->actcpi = 17.125;
                        horizPoints *= (double)10 / (double)17.125;
                        // vertPoints *= (double)10 / (double)17.125;
                }

                if (printer->cpi == 12 && (printer->style & STYLE_CONDENSED)) {
                        printer->actcpi = 20.0;
                        horizPoints *= (double)10 / (double)20.0;
                        // vertPoints *= (double)10 / (double)20.0;
                }

                if ((printer->style & STYLE_DOUBLEWIDTH) || (printer->style & STYLE_DOUBLEWIDTHONELINE)) {
                        printer->actcpi /= 2;
                        horizPoints *= (double)2.0;
                }

                if ((printer->style & STYLE_SUPERSCRIPT) || (printer->style & STYLE_SUBSCRIPT)) {
                        // TODO: aparently the horizontal size is the same?
                        // horizPoints *= (double)2 / (double)3;
                        vertPoints *= (double)2 / (double)3;
                        // actcpi /= (double)2 / (double)3;
                }

                /* TODO: emphasized and double-strike also change width? maybe not */

                FT_Set_Char_Size(printer->curFont, (uint16_t)horizPoints * FREETYPE_FONT_MULTIPLIER,
                                 (uint16_t)vertPoints * FREETYPE_FONT_MULTIPLIER, printer->dpi, printer->dpi);
        } else if (printer->printQuality == QUALITY_DRAFT) // TODO: high-speed draft
        {
                const char *fontName_Base;
                // TODO: these fonts are ALL incomplete! Need one with all codepages and all 13 international characters for each.
                // Can I dump this from my LX-810 and do something?
                if (printer->charTables[printer->curCharTable] == 0)
                        fontName_Base = "FXMatrix105Mono";
                else
                        fontName_Base = "FXMatrix105Mono";

                const char *fontName_cpi;
                if (!(printer->style & STYLE_CONDENSED)) {
                        if (printer->cpi == 10)
                                fontName_cpi = "Pica";
                        else if (printer->cpi == 12)
                                fontName_cpi = "Elite";
                } else {
                        if (printer->cpi == 10)
                                fontName_cpi = "Compr";
                        else if (printer->cpi == 12)
                                fontName_cpi = "Compr"; // TODO: missing
                }

                const char *fontName_doublewidth;
                if ((printer->style & STYLE_DOUBLEWIDTH) || (printer->style & STYLE_DOUBLEWIDTHONELINE))
                        fontName_doublewidth = "Exp";
                else
                        fontName_doublewidth = "";

                const char *fontName_doublestrike;
                if (printer->style & STYLE_DOUBLESTRIKE)
                        fontName_doublestrike = "Dbl";
                else
                        fontName_doublestrike = "";

                const char *fontName_underline;
                if (printer->style & STYLE_UNDERLINE)
                        fontName_underline = "UL";
                else
                        fontName_underline = "";

                const char *fontName_subsup;
                if (printer->style & STYLE_SUPERSCRIPT)
                        fontName_subsup = "Sup";
                else if (printer->style & STYLE_SUBSCRIPT)
                        fontName_subsup = "Sub";
                else
                        fontName_subsup = "";

                const char *fontName_itemph;
                if (printer->style & STYLE_ITALICS) {
                        if (printer->style & STYLE_BOLD)
                                fontName_itemph = "BoldItalic";
                        else
                                fontName_itemph = "Italic";
                } else if (printer->style & STYLE_BOLD)
                        fontName_itemph = "Bold";
                else
                        fontName_itemph = "Regular";

                double horizPoints = 10.5;
                double vertPoints = 10.5;

                printer->actcpi = printer->cpi;

                if (printer->cpi != 10 && !(printer->style & STYLE_CONDENSED)) {
                        horizPoints *= (double)10 / (double)printer->cpi;
                        // vertPoints *= (double)10/ ( double)printer->cpi;
                }

                if (printer->cpi == 10 && (printer->style & STYLE_CONDENSED)) {
                        // TODO: 17.125, 17.16 or 17.14?
                        printer->actcpi = 17.125;
                        horizPoints *= (double)10 / (double)17.125;
                        // vertPoints *= (double)10 / (double)17.125;
                }

                if (printer->cpi == 12 && (printer->style & STYLE_CONDENSED)) {
                        printer->actcpi = 20.0;
                        horizPoints *= (double)10 / (double)20.0;
                        // vertPoints *= (double)10 / (double)20.0;
                }

                if ((printer->style & STYLE_DOUBLEWIDTH) || (printer->style & STYLE_DOUBLEWIDTHONELINE)) {
                        printer->actcpi /= 2;
                        //                        horizPoints *= (double)2.0; //  TODO: commenting this because of the font. See
                        //                        also where we adjusted cpi/points because the font already accounted for that in
                        //                        its sizes
                }

                if ((printer->style & STYLE_SUPERSCRIPT) || (printer->style & STYLE_SUBSCRIPT)) {
                        // TODO: aparently the horizontal size is the same?
                        // horizPoints *= (double)2/(double)3;
                        vertPoints *= (double)2 / (double)3;
                        // printer->actcpi /= (double)2/(double)3;
                }

                /* TODO: emphasized and double-strike also change width? maybe not */

                char fullpath[MAX_PATH_STRING];
                strlcpy(fullpath, printer->fontpath, MAX_PATH_STRING);
                strlcat(fullpath, "fonts/", MAX_PATH_STRING);
                strlcat(fullpath, fontName_Base, MAX_PATH_STRING);
                strlcat(fullpath, fontName_cpi, MAX_PATH_STRING);
                strlcat(fullpath, fontName_doublewidth, MAX_PATH_STRING);
                strlcat(fullpath, fontName_doublestrike, MAX_PATH_STRING);
                strlcat(fullpath, fontName_underline, MAX_PATH_STRING);
                strlcat(fullpath, fontName_subsup, MAX_PATH_STRING);
                strlcat(fullpath, fontName_itemph, MAX_PATH_STRING);
                strlcat(fullpath, ".otf", MAX_PATH_STRING);

                if (FT_New_Face(printer->FTlib, fullpath, 0, &printer->curFont)) {
                        pclog("Unable to load font %s, trying default\n", fullpath);
                        strlcpy(fullpath, printer->fontpath, MAX_PATH_STRING);
                        strlcat(fullpath, "fonts/", MAX_PATH_STRING);
                        strlcat(fullpath, fontName_Base, MAX_PATH_STRING);
                        strlcat(fullpath, fontName_cpi, MAX_PATH_STRING);
                        strlcat(fullpath, "Regular.otf", MAX_PATH_STRING);

                        if (FT_New_Face(printer->FTlib, fullpath, 0, &printer->curFont)) {
                                fatal("Unable to load font %s\n", fullpath);
                                printer->curFont = NULL;
                        }
                }

                FT_Set_Char_Size(printer->curFont, (uint16_t)horizPoints * FREETYPE_FONT_MULTIPLIER,
                                 (uint16_t)vertPoints * FREETYPE_FONT_MULTIPLIER, printer->dpi, printer->dpi);
        } else
                fatal("Unknown print quality: %d\n", printer->printQuality);
}

bool processCommandChar(uint8_t ch, lpt_epsonprinter_t *printer) {
        if (printer->ESCSeen) {
                printer->ESCCmd = ch;
                printer->ESCSeen = false;
                printer->numParam = 0;

                switch (printer->ESCCmd) {
                case 0x02: // Undocumented
                case 0x0e: // Select double-width printing (one line) (ESC SO)
                case 0x0f: // Select condensed printing (ESC SI)
                case 0x1b: // Load/Eject (effect: rewind page) THIS IS NOT AN EPSON COMMAND! (ESC ESC)
                case 0x30: // Select 1/8-inch line spacing (ESC 0)
                case 0x31: // Select 7/72-inch line spacing (ESC 1)
                case 0x32: // Select 1/6-inch line spacing (ESC 2)
                case 0x34: // Select italic font (ESC 4)
                case 0x35: // Cancel italic font (ESC 5)
                case 0x36: // Enable printing of upper control codes (ESC 6)
                case 0x37: // Enable upper control codes (ESC 7)
                case 0x38: // Disable paper out detection (ESC 8)
                case 0x39: // Enable paper out detection (ESC 9)
                case 0x3c: // Unidirectional mode (one line) (ESC <)
                case 0x40: // Initialize printer (ESC @)
                case 0x45: // Select bold font (ESC E)
                case 0x46: // Cancel bold font (ESC F)
                case 0x47: // Select double-strike printing (ESC G)
                case 0x48: // Cancel double-strike printing (ESC H)
                case 0x4d: // Select 10.5-point, 12-cpi (ESC M)
                case 0x4f: // Cancel bottom margin
                case 0x50: // Select 10.5-point, 10-cpi (ESC P)
                case 0x54: // Cancel superscript/subscript printing (ESC T)
                case 0x73: // Select low-speed mode (ESC s)
                        printer->neededParam = 0;
                        break;
                case 0x19: // Control paper loading/ejecting (ESC EM)
                case 0x21: // Master select (ESC !)
                case 0x2d: // Turn underline on/off (ESC -)
                case 0x2f: // Select vertical tab channel (ESC /)
                case 0x33: // Set n/216-inch line spacing (ESC 3)
                case 0x41: // Set n/72-inch line spacing
                case 0x43: // Set page length in lines (ESC C)
                case 0x49: // Enable printing of control codes (ESC I n)
                case 0x4a: // Advance print position vertically (ESC J n)
                case 0x4e: // Set bottom margin (ESC N)
                case 0x51: // Set right margin (ESC Q)
                case 0x52: // Select an international character set (ESC R)
                case 0x53: // Select superscript/subscript printing (ESC S)
                case 0x55: // Turn unidirectional mode on/off (ESC U)
                case 0x57: // Turn double-width printing on/off (ESC W)
                case 0x61: // Select justification (ESC a)
                case 0x6b: // Select typeface (ESC k)
                case 0x6c: // Set left margin (ESC 1)
                case 0x70: // Turn proportional mode on/off (ESC p)
                case 0x74: // Select character table (ESC t)
                case 0x78: // Select NLQ or draft (ESC x)
                        printer->neededParam = 1;
                        break;
                case 0x3f: // Reassign bit-image mode (ESC ?)
                case 0x4b: // Select 60-dpi graphics (ESC K)
                case 0x4c: // Select 120-dpi graphics (ESC L)
                case 0x59: // Select 120-dpi, double-speed graphics (ESC Y)
                case 0x5a: // Select 240-dpi graphics (ESC Z)
                        printer->neededParam = 2;
                        break;
                case 0x2a: // Select bit image (ESC *)
                        printer->neededParam = 3;
                        break;
                case 0x62: // Set vertical tabs in VFU channels (ESC b)
                case 0x42: // Set vertical tabs (ESC B)
                        printer->numVertTabs = 0;
                        return true;
                case 0x44: // Set horizontal tabs (ESC D)
                        printer->numHorizTabs = 0;
                        return true;
                case 0x25: // Select user-defined set (ESC %)
                case 0x26: // Define user-defined characters (ESC &)
                case 0x3a: // Copy ROM to RAM (ESC :)
                        fatal("User-defined characters not supported!\n");
                        return true;
                case 0x28: // Two bytes sequence
                        return true;
                default:
                        fatal("PRINTER: Unknown command ESC %c (%02X). Unable to skip parameters.\n", printer->ESCCmd,
                              printer->ESCCmd);
                        printer->neededParam = 0;
                        printer->ESCCmd = 0;
                        return true;
                }

                if (printer->neededParam > 0)
                        return true;
        }

        // Ignore VFU channel setting
        if (printer->ESCCmd == 0x62) {
                // TODO: do not ignore this and ESC /?
                printer->ESCCmd = 0x42;
                return true;
        }

        // Collect vertical tabs
        if (printer->ESCCmd == 0x42) {
                if (ch == 0 || (printer->numVertTabs > 0 &&
                                printer->verttabs[printer->numVertTabs - 1] > (double)ch * printer->lineSpacing)) // Done
                        printer->ESCCmd = 0;
                else if (printer->numVertTabs < 16)
                        printer->verttabs[printer->numVertTabs++] = (double)ch * printer->lineSpacing;
        }

        // Collect horizontal tabs
        if (printer->ESCCmd == 0x44) {
                if (ch == 0 || (printer->numHorizTabs > 0 &&
                                printer->horiztabs[printer->numHorizTabs - 1] > (double)ch * (1 / (double)printer->cpi))) // Done
                        printer->ESCCmd = 0;
                else if (printer->numHorizTabs < 32)
                        printer->horiztabs[printer->numHorizTabs++] = (double)ch * (1 / (double)printer->cpi);
        }

        if (printer->numParam < printer->neededParam) {
                printer->params[printer->numParam++] = ch;

                if (printer->numParam < printer->neededParam)
                        return true;
        }

        if (printer->ESCCmd != 0) {
                switch (printer->ESCCmd) {
                case 0x02: // Undocumented
                        // Ignore
                        break;
                case 0x0e: // Select double-width printing (one line) (ESC SO)
                        printer->style |= STYLE_DOUBLEWIDTHONELINE;
                        updateFont(printer);
                        break;
                case 0x0f: // Select condensed printing (ESC SI)
                        printer->style |= STYLE_CONDENSED;
                        updateFont(printer);
                        break;
                case 0x19: // Control paper loading/ejecting (ESC EM)
                        // We are not really loading paper, so most commands can be ignored
                        if (printer->params[0] == 'R')
                                newPage(true, printer);
                        break;
                case 0x1b: // Load/Eject (effect: rewind page) THIS IS NOT AN EPSON COMMAND! (ESC ESC)
                        // TODO: check if this is the true behaviour when having continuous paper.
                        if (printer->style & STYLE_DOUBLEWIDTHONELINE) {
                                printer->style &= 0xFFFF - STYLE_DOUBLEWIDTHONELINE;
                                updateFont(printer);
                        }
                        printer->curX = printer->leftMargin;
                        printer->curY = printer->topMargin;
                        break;
                case 0x21: // Master select (ESC !)
                        printer->cpi = printer->params[0] & 0x01 ? 12 : 10;

                        // Reset first seven bits
                        printer->style &= 0xFF80;
                        if (printer->params[0] & 0x04)
                                printer->style |= STYLE_CONDENSED;
                        if (printer->params[0] & 0x08)
                                printer->style |= STYLE_BOLD;
                        if (printer->params[0] & 0x10)
                                printer->style |= STYLE_DOUBLESTRIKE;
                        if (printer->params[0] & 0x20)
                                printer->style |= STYLE_DOUBLEWIDTH;
                        if (printer->params[0] & 0x40)
                                printer->style |= STYLE_ITALICS;
                        if (printer->params[0] & 0x80)
                                printer->style |= STYLE_UNDERLINE;

                        updateFont(printer);
                        break;
                case 0x2a: // Select bit image (ESC *)
                        setupBitImage(printer->params[0], PARAM16(1), printer);
                        break;
                case 0x2d: // Turn underline on/off (ESC -)
                        if (printer->params[0] == 0 || printer->params[0] == 48)
                                printer->style &= 0xFFFF - STYLE_UNDERLINE;
                        if (printer->params[0] == 1 || printer->params[0] == 49)
                                printer->style |= STYLE_UNDERLINE;
                        updateFont(printer);
                        break;
                case 0x2f: // Select vertical tab channel (ESC /)
                        // Ignore
                        break;
                case 0x30: // Select 1/8-inch line spacing (ESC 0)
                        printer->lineSpacing = (double)1 / 8;
                        break;
                case 0x31: // Select 7/72-inch line spacing (ESC 1)
                        printer->lineSpacing = (double)7 / 72;
                        break;
                case 0x32: // Select 1/6-inch line spacing (ESC 2)
                        printer->lineSpacing = (double)1 / 6;
                        break;
                case 0x33: // Set n/216-inch line spacing (ESC 3)
                        printer->lineSpacing = (double)printer->params[0] / 216;
                        break;
                case 0x34: // Select italic font (ESC 4)
                        printer->style |= STYLE_ITALICS;
                        updateFont(printer);
                        break;
                case 0x35: // Cancel italic font (ESC 5)
                        printer->style &= 0xFFFF - STYLE_ITALICS;
                        updateFont(printer);
                        break;
                case 0x36: // Enable printing of upper control codes (ESC 6)
                        printer->printUpperContr = true;
                        break;
                case 0x37: // Enable upper control codes (ESC 7)
                        printer->printUpperContr = false;
                        break;
                case 0x38: // Disable paper out detection (ESC 8)
                        // We have infinite paper, so just ignore this
                        break;
                case 0x39: // Enable paper out detection (ESC 9)
                        // We have infinite paper, so just ignore this
                        break;
                case 0x3c: // Unidirectional mode (one line) (ESC <)
                        // We don't have a print head, so just ignore this
                        break;
                case 0x3f: // Reassign bit-image mode (ESC ?)
                        if (printer->params[0] == 75)
                                printer->densk = printer->params[1];
                        if (printer->params[0] == 76)
                                printer->densl = printer->params[1];
                        if (printer->params[0] == 89) // TODO: this option here is not present on the LX-810? (Even though the
                                                      // default mode for Y exists)
                                printer->densy = printer->params[1];
                        if (printer->params[0] == 90)
                                printer->densz = printer->params[1];
                        break;
                case 0x40: // Initialize printer (ESC @) TODO: one more command that should clear the current line buffer
                        resetPrinter(printer, true);
                        break;
                case 0x41: // Set n/72-inch line spacing
                        printer->lineSpacing = (double)printer->params[0] / 72;
                        break;
                case 0x43: // Set page length in lines (ESC C)
                        if (printer->params[0] != 0)
                                printer->pageHeight = printer->bottomMargin =
                                        (double)printer->params[0] *
                                        printer->lineSpacing; // TODO: is this right? Also, we have ALREADY allocated the SDL
                                                              // surface! This can result in memory corruption
                        else                                  // == 0 => Set page length in inches
                        {
                                printer->neededParam = 1;
                                printer->numParam = 0;
                                printer->ESCCmd = 0x100;
                                return true;
                        }
                        break;
                case 0x45: // Select bold font (ESC E)
                        printer->style |= STYLE_BOLD;
                        updateFont(printer);
                        break;
                case 0x46: // Cancel bold font (ESC F)
                        printer->style &= 0xFFFF - STYLE_BOLD;
                        updateFont(printer);
                        break;
                case 0x47: // Select dobule-strike printing (ESC G)
                        printer->style |= STYLE_DOUBLESTRIKE;
                        updateFont(printer);
                        break;
                case 0x48: // Cancel double-strike printing (ESC H)
                        printer->style &= 0xFFFF - STYLE_DOUBLESTRIKE;
                        updateFont(printer);
                        break;
                case 0x49: // Enable printing of control codes (ESC I n)
                        // TODO: which program/driver did this? Is it undocumented but SHOULD WORK on the LX-810?
                        pclog("EPSON ESC I: printing of control codes not implemented on the LX-810!\n");
                        break;
                case 0x4a: // Advance print position vertically (ESC J n)
                        printer->curY +=
                                (double)((double)printer->params[0] /
                                         216); // TODO: this command produces an immediate line feed without carriage return?
                        if (printer->curY > printer->bottomMargin)
                                newPage(true, printer);
                        break;
                case 0x4b: // Select 60-dpi graphics (ESC K)
                        setupBitImage(printer->densk, PARAM16(0), printer);
                        break;
                case 0x4c: // Select 120-dpi graphics (ESC L)
                        setupBitImage(printer->densl, PARAM16(0), printer);
                        break;
                case 0x4d: // Select 10.5-point, 12-cpi (ESC M)
                        printer->cpi = 12;
                        updateFont(printer);
                        break;
                case 0x4e: // Set bottom margin (ESC N) // TODO: does this work like this in the LX-810?
                        printer->topMargin = 0.0;
                        printer->bottomMargin = (double)printer->params[0] * printer->lineSpacing;
                        break;
                case 0x4f: // Cancel bottom (and top) margin // TODO: does this work like this in the LX-810?
                        printer->topMargin = 0.0;
                        printer->bottomMargin = printer->pageHeight;
                        break;
                case 0x50: // Select 10.5-point, 10-cpi (ESC P)
                        printer->cpi = 10;
                        updateFont(printer);
                        break;
                case 0x51: // Set right margin
                        // TODO: clear previous tab settings and all characters in the current line? Minimum space between the
                        // margins?
                        printer->rightMargin = (double)(printer->params[0] - 1.0) / (double)printer->cpi;
                        break;
                case 0x52: // Select an international character set (ESC R)
                        if (printer->params[0] <= 12) {
                                printer->curI18NCharset = printer->params[0];
                                updateCharset(printer);
                        }
                        break;
                case 0x53: // Select superscript/subscript printing (ESC S)
                        printer->style &= 0xFFFF - STYLE_SUPERSCRIPT - STYLE_SUBSCRIPT;
                        if (printer->params[0] == 0 || printer->params[0] == 48)
                                printer->style |= STYLE_SUPERSCRIPT;
                        if (printer->params[0] == 1 || printer->params[1] == 49)
                                printer->style |= STYLE_SUBSCRIPT;
                        updateFont(printer);
                        break;
                case 0x54: // Cancel superscript/subscript printing (ESC T)
                        printer->style &= 0xFFFF - STYLE_SUPERSCRIPT - STYLE_SUBSCRIPT;
                        updateFont(printer);
                        break;
                case 0x55: // Turn unidirectional mode on/off (ESC U)
                        // We don't have a print head, so just ignore this
                        break;
                case 0x57: // Turn double-width printing on/off (ESC W)
                        // TODO: see if this is right
                        if (printer->params[0] == 0 || printer->params[0] == 48)
                                printer->style &= 0xFFFF - STYLE_DOUBLEWIDTH;
                        if (printer->params[0] == 1 || printer->params[0] == 49)
                                printer->style |= STYLE_DOUBLEWIDTH;
                        updateFont(printer);
                        break;
                case 0x59: // Select 120-dpi, double-speed graphics (ESC Y)
                        setupBitImage(printer->densy, PARAM16(0), printer);
                        break;
                case 0x5a: // Select 240-dpi graphics (ESC Z)
                        setupBitImage(printer->densz, PARAM16(0), printer);
                        break;
                case 0x61: // Select justification (ESC a)
                        // TODO: do this?
                        break;
                case 0x6b: // Select typeface (ESC k)
                        if (printer->params[0] <= 11 || printer->params[0] == 30 || printer->params[0] == 31)
                                printer->NLQtypeFace = (Typeface)printer->params[0];
                        updateFont(printer);
                        break;
                case 0x6c: // Set left margin (ESC l)
                        // TODO: clear previous tab settings and all characters in the current line? Minimum space between the
                        // margins?
                        printer->leftMargin = (double)(printer->params[0] - 1.0) / (double)printer->cpi;
                        if (printer->curX < printer->leftMargin)
                                printer->curX = printer->leftMargin;
                        break;
                case 0x70: // Turn proportional mode on/off (ESC p)
                        // TODO: Win3.1 driver did this. Is it undocumented but SHOULD WORK on the LX-810?
                        pclog("EPSON ESC p: proportional mode not supported by the LX-810!\n");
                        break;
                case 0x73: // Select low-speed mode (ESC s)
                        // Ignore
                        break;
                case 0x74: // Select character table (ESC t)
                        if (printer->params[0] < 2)
                                printer->curCharTable = printer->params[0];
                        updateCharset(printer);
                        updateFont(printer);
                        break;
                case 0x78: // Select NLQ or draft (ESC x)
                        if (printer->params[0] == 0 || printer->params[0] == 48)
                                printer->printQuality = QUALITY_DRAFT;
                        if (printer->params[0] == 1 || printer->params[0] == 49)
                                printer->printQuality = QUALITY_NLQ;
                        break;
                case 0x100: // Set page length in inches (ESC C NUL)
                        printer->pageHeight = (double)printer->params[0];
                        printer->bottomMargin = printer->pageHeight; // TODO: is this right?
                        printer->topMargin = 0.0;
                        break;
                default:
                        pclog("PRINTER: Skipped unsupported command ESC %c (%02X)\n", printer->ESCCmd, printer->ESCCmd);
                }

                printer->ESCCmd = 0;
                return true;
        }

        // TODO: Set Tab Increments (ESC e)
        // TODO: Skip (ESC f)
        // TODO: Select 9-Pin Graphics Mode (ESC ^) (has two bytes per column)

        int control_code_shifted = false;
        if (!printer->printUpperContr) {
                if ((ch >= 0x80 && ch <= 0x9f) || ch == 0xff)
                        ch -= 0x80;
                control_code_shifted = true;
        }

        switch (ch) {
        case 0x07: // Beeper (BEL)
                // BEEEP!
                pclog("PRINTER: BEEP!\n");
                return true;
        case 0x08: // Backspace (BS)
        {
                // TODO: seems more complex than this
                double newX = printer->curX - (1 / (double)printer->actcpi);
                if (newX >= printer->leftMargin)
                        printer->curX = newX;
        }
                return true;
        case 0x09: // Tab horizontally (HT)
        {
                // Find tab right to current pos
                double moveTo = -1;
                for (uint8_t i = 0; i < printer->numHorizTabs; i++)
                        if (printer->horiztabs[i] > printer->curX)
                                moveTo = printer->horiztabs[i];
                // Nothing found => Ignore
                if (moveTo > 0 && moveTo < printer->rightMargin)
                        printer->curX = moveTo;
        }
                return true;
        case 0x0b:                             // Tab vertically (VT)
                if (printer->numVertTabs == 0) // All tabs cancelled => Act like CR
                        printer->curX = printer->leftMargin;
                else if (printer->numVertTabs == 255) // No tabs set since reset => Act like LF
                {
                        printer->curX = printer->leftMargin;
                        printer->curY += printer->lineSpacing;
                        if (printer->curY > printer->bottomMargin)
                                newPage(true, printer);
                } else {
                        // Find tab below current pos
                        double moveTo = -1;
                        for (uint8_t i = 0; i < printer->numVertTabs; i++)
                                if (printer->verttabs[i] > printer->curY)
                                        moveTo = printer->verttabs[i];

                        // Nothing found => Act like FF
                        if (moveTo > printer->bottomMargin || moveTo < 0)
                                newPage(true, printer);
                        else
                                printer->curY = moveTo;
                }
                if (printer->style & STYLE_DOUBLEWIDTHONELINE) {
                        printer->style &= 0xFFFF - STYLE_DOUBLEWIDTHONELINE;
                        updateFont(printer);
                }
                return true;
        case 0x0c: // Form feed (FF)
                if (printer->style & STYLE_DOUBLEWIDTHONELINE) {
                        printer->style &= 0xFFFF - STYLE_DOUBLEWIDTHONELINE;
                        updateFont(printer);
                }
                newPage(true, printer);
                return true;
        case 0x0d: // Carriage Return (CR)
                printer->curX = printer->leftMargin;
                if (!printer->autoFeed)
                        return true;
        case 0x0a: // Line feed
                if (printer->style & STYLE_DOUBLEWIDTHONELINE) {
                        printer->style &= 0xFFFF - STYLE_DOUBLEWIDTHONELINE;
                        updateFont(printer);
                }
                printer->curX = printer->leftMargin; // TODO: \n does this too or just \r?
                printer->curY += printer->lineSpacing;

                if (printer->curY > printer->bottomMargin)
                        newPage(true, printer);
                return true;
        case 0x0e: // Select double-width printing (one line) (SO)
                printer->style |= STYLE_DOUBLEWIDTHONELINE;
                updateFont(printer);
                return true;
        case 0x0f: // Select condensed printing (SI)
                printer->style |= STYLE_CONDENSED;
                updateFont(printer);
                return true;
        case 0x11: // Select printer (DC1)
                // Ignore
                return true;
        case 0x12: // Cancel condensed printing (DC2)
                printer->style &= 0xFFFF - STYLE_CONDENSED;
                updateFont(printer);
                return true;
        case 0x13: // Deselect printer (DC3)
                // Ignore
                return true;
        case 0x14: // Cancel double-width printing (one line) (DC4)
                printer->style &= 0xFFFF - STYLE_DOUBLEWIDTHONELINE;
                updateFont(printer);
                return true;
        case 0x18:           // Cancel line (CAN)
                return true; // TODO: is this used? should we cancel the line being printed?
        case 0x1b:           // ESC
                printer->ESCSeen = true;
                return true;
        case 0x7f:           // Delete character (DEL)
                return true; // TODO: is this used? should we cancel the character being printed?
        default:
                if (control_code_shifted) {
                        // TODO: see how this should really happen when the control code isn't processed (there are codepages with
                        // printable symbols < 0x20)
                        ch += 0x80;
                }
                return false;
        }

        return false;
}

void newPage(bool save, lpt_epsonprinter_t *printer) {
        if (save)
                outputPage(printer);

        printer->curY = printer->topMargin;

        SDL_Rect rect;
        rect.x = 0;
        rect.y = 0;
        rect.w = printer->page->w;
        rect.h = printer->page->h;
        SDL_FillRect(printer->page, &rect, SDL_MapRGB(printer->page->format, 255, 255, 255));
}

void printChar(lpt_epsonprinter_t *printer) {
        uint8_t ch = printer->last_data;
        printer->charRead = true;
        if (printer->page == NULL)
                return;

        // Are we currently printing a bit graphic?
        if (printer->bitGraph.remBytes > 0) {
                printBitGraph(ch, printer);
                return;
        }

        // See if it was a command
        if (processCommandChar(ch, printer))
                return;

        // Do not print if no font is available
        if (!printer->curFont)
                return;
        if (ch == 0)
                ch = 0x20;

        // TODO: real italics for the italics charset, where the upper half is (mostly) the lower half italicized.
        int italicize = false;
        if (!(printer->style & STYLE_ITALICS) && printer->charTables[printer->curCharTable] == 0 &&
            ch > 0x7f) // prevent double italics (they are present on the font)
        {
                italicize = true;
        }

        if (italicize) {
                FT_Matrix matrix;
                matrix.xx = 0x10000L;
                matrix.xy = (FT_Fixed)(0.20 * 0x10000L);
                matrix.yx = 0;
                matrix.yy = 0x10000L;
                FT_Set_Transform(printer->curFont, &matrix, NULL);
        } else
                FT_Set_Transform(printer->curFont, NULL, NULL);

        // Find the glyph for the char to render
        FT_UInt index = FT_Get_Char_Index(printer->curFont, printer->curMap[ch]);

        // Load the glyph
        FT_Load_Glyph(printer->curFont, index, FT_LOAD_DEFAULT);

        // Render a high-quality bitmap
        FT_Render_Glyph(printer->curFont->glyph, FT_RENDER_MODE_NORMAL);

        // TODO: some fonts have negative left and/or top and as of this writing we don't do printable areas! So do this hack on
        // unsigned ints...
        uint16_t penX = CURX_INCH_TO_PIXEL_ADD(fmax(0.0, printer->curFont->glyph->bitmap_left));
        uint16_t penY = CURY_INCH_TO_PIXEL_ADD(fmax(
                0.0, -printer->curFont->glyph->bitmap_top + printer->curFont->size->metrics.ascender / FREETYPE_FONT_MULTIPLIER));
        // handle proportional fonts
        penX += (((double)printer->dpi / printer->actcpi) - printer->curFont->glyph->bitmap.width) / 2;

        if (printer->style & STYLE_SUBSCRIPT) // TODO: this and other placementes are guesswork
                penY += printer->curFont->glyph->metrics.vertAdvance / FREETYPE_FONT_MULTIPLIER * (double)1 / (double)3;

        // Copy bitmap into page
        SDL_LockSurface(printer->page);

        blitGlyph(printer->curFont->glyph->bitmap, penX, penY, false, printer);

        // Doublestrike => Print the glyph a second time one pixel below
        if (printer->printQuality != QUALITY_DRAFT) // TODO: have the font variations for the NLQ fonts too
                if (printer->style & STYLE_DOUBLESTRIKE)
                        blitGlyph(printer->curFont->glyph->bitmap, penX, penY + 1, true, printer);

        // Bold => Print the glyph a second time one pixel to the right
        if (printer->printQuality != QUALITY_DRAFT) // TODO: have the font variations for the NLQ fonts too
                if (printer->style & STYLE_BOLD)
                        blitGlyph(printer->curFont->glyph->bitmap, penX + 1, penY, true, printer);

        SDL_UnlockSurface(printer->page);

        // For line printing
        uint16_t lineStart = CURX_INCH_TO_PIXEL;

        printer->curX += 1 / (double)printer->actcpi;

        // Draw lines if desired
        if (printer->printQuality != QUALITY_DRAFT) // TODO: have the font variations for the NLQ fonts too
                if (printer->style & STYLE_UNDERLINE) {
                        // Find out where to put the line
                        uint16_t lineY =
                                CURY_INCH_TO_PIXEL_ADD(printer->curFont->size->metrics.height / FREETYPE_FONT_MULTIPLIER);

                        // TODO: use previous CURX_INCH_TO_PIXEL or penX here?
                        drawLine(lineStart, CURX_INCH_TO_PIXEL, lineY, printer);
                }
}

void blitGlyph(FT_Bitmap bitmap, uint16_t destx, uint16_t desty, bool add, lpt_epsonprinter_t *printer) {
        for (unsigned int y = 0; y < bitmap.rows; y++) {
                for (unsigned int x = 0; x < bitmap.width; x++) {
                        // Read pixel from glyph bitmap
                        uint8_t *source = bitmap.buffer + x + y * bitmap.pitch;

                        // Ignore background and don't go over the border
                        // TODO: This should not be linear!
                        if (*source != 0 && (destx + x < (unsigned int)printer->page->w) &&
                            (desty + y < (unsigned int)printer->page->h)) {
                                uint8_t *target =
                                        (uint8_t *)printer->page->pixels + (x + destx) + (y + desty) * printer->page->pitch;
                                if (add) {
                                        if (*target + *source > 255)
                                                *target = 255;
                                        else
                                                *target += *source;
                                } else
                                        *target = *source;
                        }
                }
        }
}

void drawLine(uint16_t fromx, uint16_t tox, uint16_t y, lpt_epsonprinter_t *printer) {
        SDL_LockSurface(printer->page);

        // Draw anti-aliased line
        for (unsigned int x = fromx; x <= tox; x++) {
                // Skip parts if going over the border
                if (x < (unsigned int)printer->page->w) {
                        if (y > 0 && (y - 1) < printer->page->h)
                                *((uint8_t *)printer->page->pixels + x + (y - 1) * printer->page->pitch) = 120;
                        if (y < printer->page->h)
                                *((uint8_t *)printer->page->pixels + x + y * printer->page->pitch) = 255;
                        if (y + 1 < printer->page->h)
                                *((uint8_t *)printer->page->pixels + x + (y + 1) * printer->page->pitch) = 120;
                }
        }

        SDL_UnlockSurface(printer->page);
}

void setAutofeed(bool feed, lpt_epsonprinter_t *printer) { printer->autoFeed = feed; }

bool getAutofeed(lpt_epsonprinter_t *printer) { return printer->autoFeed; }

bool isBusy(lpt_epsonprinter_t *printer) {
        // We're never busy
        return false;
}

bool ack(lpt_epsonprinter_t *printer) {
        // Acknowledge last char read
        return printer->charRead;
}

// TODO: see if densities are OK
// TODO: 240x144 setting under windows 95 looks bad when printing bitmap fonts because of the "no adjancent dots" rule? Shouldn't
// it look a little better?
void setupBitImage(uint8_t dens, uint16_t numCols, lpt_epsonprinter_t *printer) {
        switch (dens) {
        case 0: // Single-density
                printer->bitGraph.horizDens = 60;
                printer->bitGraph.vertDens = 72;
                printer->bitGraph.adjacent = true;
                printer->bitGraph.bytesColumn = 1;
                break;
        case 1: // Double-density
                printer->bitGraph.horizDens = 120;
                printer->bitGraph.vertDens = 72;
                printer->bitGraph.adjacent = true;
                printer->bitGraph.bytesColumn = 1;
                break;
        case 2: // High-speed double-density
                printer->bitGraph.horizDens = 120;
                printer->bitGraph.vertDens = 72;
                printer->bitGraph.adjacent = false;
                printer->bitGraph.bytesColumn = 1;
                break;
        case 3: // Quadruple-density
                printer->bitGraph.horizDens = 240;
                printer->bitGraph.vertDens = 72;
                printer->bitGraph.adjacent = false;
                printer->bitGraph.bytesColumn = 1;
                break;
        case 4: // CRT I
                printer->bitGraph.horizDens = 80;
                printer->bitGraph.vertDens = 72;
                printer->bitGraph.adjacent = true;
                printer->bitGraph.bytesColumn = 1;
                break;
        case 5: // Plotter (1:1
                printer->bitGraph.horizDens = 72;
                printer->bitGraph.vertDens = 72;
                printer->bitGraph.adjacent = true;
                printer->bitGraph.bytesColumn = 1;
                break;
        case 6: // CRT II
                printer->bitGraph.horizDens = 90;
                printer->bitGraph.vertDens = 72;
                printer->bitGraph.adjacent = true;
                printer->bitGraph.bytesColumn = 1;
                break;
        default:
                fatal("PRINTER: Unsupported bit image density %i\n", dens);
        }

        printer->bitGraph.remBytes = numCols * printer->bitGraph.bytesColumn;
        printer->bitGraph.readBytesColumn = 0;
}

// TODO: anti-alias because of high dpi, also be mindful that additions when superimposing pin strikes are not linear
// TODO: use this for printing when we have the character roms
void printBitGraph(uint8_t ch, lpt_epsonprinter_t *printer) {
        printer->bitGraph.column[printer->bitGraph.readBytesColumn++] = ch;
        printer->bitGraph.remBytes--;

        // Only print after reading a full column
        if (printer->bitGraph.readBytesColumn < printer->bitGraph.bytesColumn)
                return;

        double oldY = printer->curY;

        SDL_LockSurface(printer->page);

        // TODO: other rasterizations where we should do start->end instead of start + steps
        double pixsizeX;
        double pixsizeY;
        if (printer->emulatePinsForBitmaps) {
                pixsizeX = 0.01141732283464566929 * printer->dpi;
                pixsizeY = 0.01141732283464566929 * printer->dpi;
        } else {
                pixsizeX = printer->dpi / printer->bitGraph.horizDens > 0 ? printer->dpi / printer->bitGraph.horizDens : 1;
                pixsizeY = printer->dpi / printer->bitGraph.vertDens > 0 ? printer->dpi / printer->bitGraph.vertDens : 1;
        }

        for (unsigned int i = 0; i < printer->bitGraph.bytesColumn; i++) {
                for (int j = 7; j >= 0; j--) {
                        uint8_t pixel = (printer->bitGraph.column[i] >> j) & 0x01;

                        if (pixel != 0) {
                                int startx;
                                int endx;
                                int starty;
                                int endy;

                                if (printer->emulatePinsForBitmaps) {
                                        startx = CURX_INCH_TO_PIXEL_ADD(-pixsizeX);
                                        endx = CURX_INCH_TO_PIXEL_ADD(pixsizeX * 2);
                                        starty = CURY_INCH_TO_PIXEL_ADD(-pixsizeY);
                                        endy = CURY_INCH_TO_PIXEL_ADD(pixsizeY * 2);
                                } else {
                                        startx = CURX_INCH_TO_PIXEL;
                                        endx = CURX_INCH_TO_PIXEL_ADD(pixsizeX);
                                        starty = CURY_INCH_TO_PIXEL;
                                        endy = CURY_INCH_TO_PIXEL_ADD(pixsizeY);
                                }

                                for (int xx = startx; xx < endx; xx++)
                                        for (int yy = starty; yy < endy; yy++)
                                                if ((xx < printer->page->w) && (yy < printer->page->h)) {
                                                        if (printer->emulatePinsForBitmaps) {
                                                                double middlex = CURX_INCH_TO_PIXEL_ADD(pixsizeX / 2);
                                                                double middley = CURY_INCH_TO_PIXEL_ADD(pixsizeY / 2);
                                                                double dist = sqrt((xx - middlex) * (xx - middlex) +
                                                                                   (yy - middley) * (yy - middley));
                                                                double radius = pixsizeY / 2;
                                                                double ratio = dist / radius;
                                                                // pin diameter == 0.29mm == 0.01141732283464566929 inches. 0.35mm
                                                                // or 1/72 inch between pins.
                                                                uint8_t source = (ratio <= 1.0) ? (255 * (1 - ratio * ratio)) : 0;
                                                                // uint8_t source = (ratio <= 1.0) ? (255 * (1 - ratio)) : 0;
                                                                // uint8_t source = (ratio <= 1.0) ? 255 : 0;
                                                                uint8_t *target = (uint8_t *)printer->page->pixels + xx +
                                                                                  yy * printer->page->pitch;
                                                                if ((int)*target + (int)source > 255)
                                                                        *target = 255;
                                                                else
                                                                        *target += source;
                                                        } else
                                                                *((uint8_t *)printer->page->pixels + xx +
                                                                  yy * printer->page->pitch) = 255;
                                                }
                        }

                        printer->curY += (double)1 / (double)printer->bitGraph.vertDens;
                }
        }

        SDL_UnlockSurface(printer->page);

        printer->curY = oldY;

        printer->bitGraph.readBytesColumn = 0;

        // Advance the cursor
        printer->curX += (double)1 / (double)printer->bitGraph.horizDens;
}

void formFeed(lpt_epsonprinter_t *printer) {
        // Don't output blank pages
        newPage(!isBlank(printer), printer);

        finishMultipage(printer);
}

static void findNextName(const char *front, const char *ext, char *filename, size_t filename_size, const char *base_path) {
        char buffer[10];
        uint16_t num = 1;
        FILE *test = NULL;
        do {
                snprintf(&buffer[0], 10, "%d", num++);
                strlcpy(filename, base_path, filename_size);
                strlcat(filename, front, filename_size);
                strlcat(filename, buffer, filename_size);
                strlcat(filename, ext, filename_size);

                test = fopen(filename, "rb");
                if (test != NULL)
                        fclose(test);
                if (num == 999999999)
                        fatal("Too many printed pages! Please make space!\n");
        } while (test != NULL);
}

void outputPage(lpt_epsonprinter_t *printer) {
        char filename[MAX_PATH_STRING];

        if (printer->output_type == OUTPUT_TYPE_FORWARD_TO_REAL_PRINTER) {
#if defined(WIN32)
                // You'll need the mouse for the print dialog
                if (mouselocked)
                        CaptureMouse();

                uint16_t physW = GetDeviceCaps(printer->printerDC, PHYSICALWIDTH);
                uint16_t physH = GetDeviceCaps(printer->printerDC, PHYSICALHEIGHT);

                double scaleW, scaleH;

                if (printer->page->w > physW)
                        scaleW = (double)printer->page->w / (double)physW;
                else
                        scaleW = (double)physW / (double)printer->page->w;

                if (printer->page->h > physH)
                        scaleH = (double)printer->page->h / (double)physH;
                else
                        scaleH = (double)physH / (double)printer->page->h;

                HDC memHDC = CreateCompatibleDC(printer->printerDC);
                HBITMAP bitmap = CreateCompatibleBitmap(memHDC, printer->page->w, printer->page->h);
                SelectObject(memHDC, bitmap);

                // Start new printer job?
                if (printer->outputHandle == NULL) {
                        DOCINFO docinfo;
                        docinfo.cbSize = sizeof(docinfo);
                        docinfo.lpszDocName = "PCem Printer";
                        docinfo.lpszOutput = NULL;
                        docinfo.lpszDatatype = NULL;
                        docinfo.fwType = 0;

                        StartDoc(printer->printerDC, &docinfo);
                        printer->multiPageCounter = 1;
                }

                StartPage(printer->printerDC);
                SDL_LockSurface(printer->page);

                SDL_Palette *sdlpal = printer->page->format->palette;

                for (uint16_t y = 0; y < printer->page->h; y++) {
                        for (uint16_t x = 0; x < printer->page->w; x++) {
                                uint8_t pixel = *((uint8_t *)printer->page->pixels + x + (y * printer->page->pitch));
                                uint32_t color = 0;
                                color |= sdlpal->colors[pixel].r;
                                color |= ((uint32_t)sdlpal->colors[pixel].g) << 8;
                                color |= ((uint32_t)sdlpal->colors[pixel].b) << 16;
                                SetPixel(memHDC, x, y, color);
                        }
                }

                SDL_UnlockSurface(printer->page);

                StretchBlt(printer->printerDC, 0, 0, physW, physH, memHDC, 0, 0, printer->page->w, printer->page->h, SRCCOPY);

                EndPage(printer->printerDC);

                if (printer->multipageOutput) {
                        printer->multiPageCounter++;
                        printer->outputHandle = printer->printerDC;
                } else {
                        EndDoc(printer->printerDC);
                        printer->outputHandle = NULL;
                }

                DeleteDC(memHDC);
#else
                fatal("PRINTER: Direct printing not supported under this OS\n");
#endif
        } else if (printer->output_type == OUTPUT_TYPE_POSTSCRIPT) {
                // TODO: this was working even when the last entry on the palette was undefined - see if there is any workaround
                // in this code
                FILE *psfile = NULL;

                // Continue postscript file?
                if (printer->outputHandle != NULL)
                        psfile = (FILE *)printer->outputHandle;

                // Create new file?
                if (psfile == NULL) {
                        if (!printer->multipageOutput)
                                findNextName("page", ".ps", filename, MAX_PATH_STRING, printer->outputpath);
                        else
                                findNextName("doc", ".ps", filename, MAX_PATH_STRING, printer->outputpath);

                        psfile = fopen(filename, "wb");
                        if (!psfile) {
                                fatal("PRINTER: Can't open file %s for printer output\n", filename);
                                return;
                        }

                        // Print header
                        fprintf(psfile, "%%!PS-Adobe-3.0\n");
                        fprintf(psfile, "%%%%Pages: (atend)\n");
                        fprintf(psfile, "%%%%BoundingBox: 0 0 %i %i\n", (uint16_t)(printer->defaultPageWidth * printer->dpi),
                                (uint16_t)(printer->defaultPageHeight * printer->dpi)); // TODO: use current height here?
                        fprintf(psfile, "%%%%Creator: PCem Virtual Printer\n");
                        fprintf(psfile, "%%%%DocumentData: Clean7Bit\n");
                        fprintf(psfile, "%%%%LanguageLevel: 2\n");
                        fprintf(psfile, "%%%%EndComments\n");
                        printer->multiPageCounter = 1;
                }

                fprintf(psfile, "%%%%Page: %i %i\n", printer->multiPageCounter, printer->multiPageCounter);
                fprintf(psfile, "%i %i scale\n", (uint16_t)(printer->defaultPageWidth * printer->dpi),
                        (uint16_t)(printer->defaultPageHeight * printer->dpi)); // TODO: use current height here?
                fprintf(psfile, "%i %i 8 [%i 0 0 -%i 0 %i]\n", printer->page->w, printer->page->h, printer->page->w,
                        printer->page->h, printer->page->h);
                fprintf(psfile, "currentfile\n");
                fprintf(psfile, "/ASCII85Decode filter\n");
                fprintf(psfile, "/RunLengthDecode filter\n");
                fprintf(psfile, "image\n");

                SDL_LockSurface(printer->page);

                uint32_t pix = 0;
                uint32_t numpix = printer->page->h * printer->page->w;
                printer->ASCII85BufferPos = printer->ASCII85CurCol = 0;

                while (pix < numpix) {
                        // Compress data using RLE
                        if ((pix < numpix - 2) && (getPixel(pix, printer) == getPixel(pix + 1, printer)) &&
                            (getPixel(pix, printer) == getPixel(pix + 2, printer))) {
                                // Found three or more pixels with the same color
                                uint8_t sameCount = 3;
                                uint8_t col = getPixel(pix, printer);
                                while (sameCount < 128 && sameCount + pix < numpix && col == getPixel(pix + sameCount, printer))
                                        sameCount++;

                                fprintASCII85(psfile, 257 - sameCount, printer);
                                fprintASCII85(psfile, 255 - col, printer);

                                // Skip ahead
                                pix += sameCount;
                        } else {
                                // Find end of heterogenous area
                                uint8_t diffCount = 1;
                                while (diffCount < 128 && diffCount + pix < numpix &&
                                       ((diffCount + pix < numpix - 2) ||
                                        (getPixel(pix + diffCount, printer) != getPixel(pix + diffCount + 1, printer)) ||
                                        (getPixel(pix + diffCount, printer) != getPixel(pix + diffCount + 2, printer))))
                                        diffCount++;

                                fprintASCII85(psfile, diffCount - 1, printer);
                                for (uint8_t i = 0; i < diffCount; i++)
                                        fprintASCII85(psfile, 255 - getPixel(pix++, printer), printer);
                        }
                }

                // Write EOD for RLE and ASCII85
                fprintASCII85(psfile, 128, printer);
                fprintASCII85(psfile, 256, printer);

                SDL_UnlockSurface(printer->page);

                fprintf(psfile, "showpage\n");

                if (printer->multipageOutput) {
                        printer->multiPageCounter++;
                        printer->outputHandle = psfile;
                } else {
                        fprintf(psfile, "%%%%Pages: 1\n");
                        fprintf(psfile, "%%%%EOF\n");
                        fclose(psfile);
                        printer->outputHandle = NULL;
                }
        } else {
                const char *ext;
                const char *output_format;
                switch (printer->output_type) {
                case OUTPUT_TYPE_BMP:
                        ext = ".bmp";
                        output_format = IMAGE_BMP;
                        break;
                case OUTPUT_TYPE_PNG:
                        ext = ".png";
                        output_format = IMAGE_PNG;
                        break;
                case OUTPUT_TYPE_TIFF:
                        ext = ".tiff";
                        output_format = IMAGE_TIFF;
                        break;
                case OUTPUT_TYPE_JPG:
                        ext = ".jpg";
                        output_format = IMAGE_JPG;
                        break;
                default:
                        fatal("PRINTER: Unknown output type: %d\n", printer->output_type);
                }

                // Find a page that does not exists
                findNextName("page", ext, filename, MAX_PATH_STRING, printer->outputpath);

                char *rgb = malloc(printer->page->w * printer->page->h * 3);
                char *rgbptr = rgb;

                SDL_LockSurface(printer->page);

                SDL_Palette *sdlpal = printer->page->format->palette;

                for (int y = 0; y < printer->page->h; y++) {
                        for (int x = 0; x < printer->page->w; x++) {
                                uint8_t pixel = *((uint8_t *)printer->page->pixels + x + (y * printer->page->pitch));

                                *rgbptr++ = sdlpal->colors[pixel].r;
                                *rgbptr++ = sdlpal->colors[pixel].g;
                                *rgbptr++ = sdlpal->colors[pixel].b;
                        }
                }

                SDL_UnlockSurface(printer->page);

                // TODO: save as 8bpp instead of converting to rgb
                // TODO: image will be copied twice (here and in the function below)
                wx_image_save_fullpath(filename, output_format, rgb, printer->page->w, printer->page->h, false);
                free(rgb);
        }
}

void fprintASCII85(FILE *f, uint16_t b, lpt_epsonprinter_t *printer) {
        if (b != 256) {
                if (b < 256)
                        printer->ASCII85Buffer[printer->ASCII85BufferPos++] = (uint8_t)b;

                if (printer->ASCII85BufferPos == 4 || b == 257) {
                        uint32_t num = (uint32_t)printer->ASCII85Buffer[0] << 24 | (uint32_t)printer->ASCII85Buffer[1] << 16 |
                                       (uint32_t)printer->ASCII85Buffer[2] << 8 | (uint32_t)printer->ASCII85Buffer[3];

                        // Deal with special case
                        if (num == 0 && b != 257) {
                                fprintf(f, "z");
                                if (++printer->ASCII85CurCol >= 79) {
                                        printer->ASCII85CurCol = 0;
                                        fprintf(f, "\n");
                                }
                        } else {
                                char buffer[5];
                                for (int8_t i = 4; i >= 0; i--) {
                                        buffer[i] = (uint8_t)((uint32_t)num % (uint32_t)85);
                                        buffer[i] += 33;
                                        num /= (uint32_t)85;
                                }

                                // Make sure a line never starts with a % (which may be mistaken as start of a comment)
                                if (printer->ASCII85CurCol == 0 && buffer[0] == '%')
                                        fprintf(f, " ");

                                for (int i = 0; i < ((b != 257) ? 5 : printer->ASCII85BufferPos + 1); i++) {
                                        fprintf(f, "%c", buffer[i]);
                                        if (++printer->ASCII85CurCol >= 79) {
                                                printer->ASCII85CurCol = 0;
                                                fprintf(f, "\n");
                                        }
                                }
                        }

                        printer->ASCII85BufferPos = 0;
                }

        } else // Close string
        {
                // Partial tupel if there are still bytes in the buffer
                if (printer->ASCII85BufferPos > 0) {
                        for (uint8_t i = printer->ASCII85BufferPos; i < 4; i++)
                                printer->ASCII85Buffer[i] = 0;

                        fprintASCII85(f, 257, printer);
                }

                fprintf(f, "~");
                fprintf(f, ">\n");
        }
}

void finishMultipage(lpt_epsonprinter_t *printer) {
        if (printer->outputHandle != NULL) {
                if (printer->output_type == OUTPUT_TYPE_POSTSCRIPT) {
                        FILE *psfile = (FILE *)printer->outputHandle;
                        fprintf(psfile, "%%%%Pages: %i\n", printer->multiPageCounter);
                        fprintf(psfile, "%%%%EOF\n");
                        fclose(psfile);
                } else if (printer->output_type == OUTPUT_TYPE_FORWARD_TO_REAL_PRINTER) {
#if defined(WIN32)
                        EndDoc(printer->printerDC);
#endif
                }
                printer->outputHandle = NULL;
        }
}

bool isBlank(lpt_epsonprinter_t *printer) {
        bool blank = true;

        SDL_LockSurface(printer->page);

        for (uint16_t y = 0; y < printer->page->h; y++)
                for (uint16_t x = 0; x < printer->page->w; x++)
                        if (*((uint8_t *)printer->page->pixels + x + (y * printer->page->pitch)) != 0)
                                blank = false;

        SDL_UnlockSurface(printer->page);

        return blank;
}

uint8_t getPixel(uint32_t num, lpt_epsonprinter_t *printer) {
        // Respect the pitch
        return *((uint8_t *)printer->page->pixels + (num % printer->page->w) + ((num / printer->page->w) * printer->page->pitch));
}

static void epsonprinter_write_data(uint8_t val, void *p) {
        lpt_epsonprinter_t *lpt_epsonprinter = (lpt_epsonprinter_t *)p;
#ifdef PRINTER_DEBUG
        pclog("lx-810 write data %02X\n", val);
#endif
        lpt_epsonprinter->last_data = val;
}

static void epsonprinter_write_ctrl(uint8_t val, void *p) {
        lpt_epsonprinter_t *lpt_epsonprinter = (lpt_epsonprinter_t *)p;
#ifdef PRINTER_DEBUG
        pclog("lx-810 write control %02X\n", val);
#endif

        setAutofeed((val & 0x02) != 0, lpt_epsonprinter);
        if ((val & 0x01) && (!(lpt_epsonprinter->controlreg & 0x01)))
                printChar(lpt_epsonprinter);
        if ((val & 0x04) && (!(lpt_epsonprinter->controlreg & 0x04)))
                resetPrinterHard(lpt_epsonprinter);

        lpt_epsonprinter->controlreg = val;
}

static uint8_t epsonprinter_read_status(void *p) {
        lpt_epsonprinter_t *lpt_epsonprinter = (lpt_epsonprinter_t *)p;
        uint8_t temp;

        // Printer is always online and never reports an error
        temp = 0x1f; // 0x18;

        //        if (lpt_epsonprinter->controlreg & 0x08 == 0)
        //                temp |= 0x10;

        if (!isBusy(lpt_epsonprinter))
                temp |= 0x80;

        if (!ack(lpt_epsonprinter))
                temp |= 0x40;
#ifdef PRINTER_DEBUG
        pclog("lx-810 read status %02X\n", temp);
#endif
        return temp;
}

static uint8_t epsonprinter_read_ctrl(void *p) {
        lpt_epsonprinter_t *lpt_epsonprinter = (lpt_epsonprinter_t *)p;
        uint8_t temp = 0xe0 | (getAutofeed(lpt_epsonprinter) ? 0x02 : 0x00) | (lpt_epsonprinter->controlreg & 0xfd);
#ifdef PRINTER_DEBUG
        pclog("lx-810 read control %02X\n", temp);
#endif

        return temp;
}

static void *epsonprinter_init() {
        lpt_epsonprinter_t *lpt_epsonprinter = (lpt_epsonprinter_t *)malloc(sizeof(lpt_epsonprinter_t));
        memset(lpt_epsonprinter, 0, sizeof(lpt_epsonprinter_t));

        lpt_epsonprinter->controlreg = 0x04;

        // TODO: printable area? (make it an option because the user may want to REALLY print the results and we don't want to add
        // two forced margins?)
        // TODO: also follow all mode overrides (relation of dip switches, front panel, software cmds) to the letter from the user
        // manual
        // TODO: see if defaults are right everywhere
        // TODO: possible overflows in 8-bit and 16-bit ints when doing high dpis
        // TODO: auto-form feed after inactivity option
        // TODO: ink intensity (high speed draft, dry ribbon, etc)
        // TODO: simulate errors and out of paper just for fun?
        // TODO: I've seen a postscript non-multipage in win95/word6 come out blank once. Couldn't repeat
        // width and height unit is 1/10 inch, multipageoutput == keep appending on postscript or print job until manual formfeed
        int dpi = device_get_config_int("dpi");
        int output_type = device_get_config_int("outputtype");
        int multipage = device_get_config_int("multipage");
        int selected_papersize = device_get_config_int("papersize");
        int width;
        int height;
        switch (selected_papersize) {
        case 0:
                width = 85;
                height = 110;
                break;
        case 1:
                // TODO: should we care about this precision loss?
                width = 210 / 254;
                height = 297 / 254;
                break;
        default:
                fatal("PRINTER: Invalid paper size\n");
        }
        int cpi = device_get_config_int("cpi");
        int selected_fontstyle = device_get_config_int("font");
        uint16_t style;
        uint8_t quality;
        Typeface font;
        // TODO: we are just setting the default NLQ font to Roman when starting with draft, see if this is right.
        switch (selected_fontstyle) {
        case 0:
                style = 0;
                quality = QUALITY_DRAFT;
                font = roman;
                break;
        case 1:
                style = STYLE_CONDENSED;
                quality = QUALITY_DRAFT;
                font = roman;
                break;
        case 2:
                style = 0;
                quality = QUALITY_NLQ;
                font = roman;
                break;
        case 3:
                style = 0;
                quality = QUALITY_NLQ;
                font = sansserif;
                break;
        default:
                fatal("PRINTER: Invalid initial font setting: %d\n", selected_fontstyle);
        }
        int selected_charset = device_get_config_int("characterset");
        uint8_t chartable, i18ncharset;
        uint16_t codepage;
        switch (selected_charset) {
        case 0:
                chartable = 0;
                i18ncharset = 0;
                codepage = 437;
                break;
        case 1:
                chartable = 1;
                i18ncharset = 0;
                codepage = 437;
                break;
        case 2:
                chartable = 1;
                i18ncharset = 1;
                codepage = 437;
                break;
        case 3:
                chartable = 1;
                i18ncharset = 2;
                codepage = 437;
                break;
        case 4:
                chartable = 1;
                i18ncharset = 3;
                codepage = 437;
                break;
        case 5:
                chartable = 1;
                i18ncharset = 4;
                codepage = 437;
                break;
        case 6:
                chartable = 1;
                i18ncharset = 5;
                codepage = 437;
                break;
        case 7:
                chartable = 1;
                i18ncharset = 6;
                codepage = 437;
                break;
        case 8:
                chartable = 1;
                i18ncharset = 7;
                codepage = 437;
                break;
        case 9:
                chartable = 1;
                i18ncharset = 8;
                codepage = 437;
                break;
        case 10:
                chartable = 1;
                i18ncharset = 9;
                codepage = 437;
                break;
        case 11:
                chartable = 1;
                i18ncharset = 10;
                codepage = 437;
                break;
        case 12:
                chartable = 1;
                i18ncharset = 11;
                codepage = 437;
                break;
        case 13:
                chartable = 1;
                i18ncharset = 12;
                codepage = 437;
                break;
        case 14:
                chartable = 1;
                i18ncharset = 0;
                codepage = 850;
                break;
        case 15:
                chartable = 1;
                i18ncharset = 0;
                codepage = 860;
                break;
        case 16:
                chartable = 1;
                i18ncharset = 0;
                codepage = 863;
                break;
        case 17:
                chartable = 1;
                i18ncharset = 0;
                codepage = 865;
                break;
        default:
                fatal("PRINTER: Invalid initial charset setting: %d\n", selected_charset);
        }
        int emulatepins = device_get_config_int("bitmaps_emulate_pins");

        char s[512];
        strlcpy(lpt_epsonprinter->fontpath, printer_path, MAX_PATH_STRING - 2);
        put_backslash(lpt_epsonprinter->fontpath);

        strlcpy(lpt_epsonprinter->outputpath, printer_path, MAX_PATH_STRING - 2);
        put_backslash(lpt_epsonprinter->outputpath);

        if (FT_Init_FreeType(&lpt_epsonprinter->FTlib)) {
                fatal("PRINTER: Unable to init Freetype2. Printing disabled\n");
                lpt_epsonprinter->page = NULL;
        } else {
                lpt_epsonprinter->dpi = dpi;
                lpt_epsonprinter->output_type = output_type;
                lpt_epsonprinter->multipageOutput = multipage;

                lpt_epsonprinter->defaultPageWidth = (double)width / (double)10;
                lpt_epsonprinter->defaultPageHeight = (double)height / (double)10;

                lpt_epsonprinter->defaultCPI = cpi;
                lpt_epsonprinter->defaultStyle = style;
                lpt_epsonprinter->defaultQuality = quality;
                lpt_epsonprinter->defaultNLQtypeFace = font;
                lpt_epsonprinter->defaultCharTable = chartable;
                lpt_epsonprinter->defaultI18NCharset = i18ncharset;
                lpt_epsonprinter->defaultCodePage = codepage;
                lpt_epsonprinter->emulatePinsForBitmaps = emulatepins;

                // Create page
                lpt_epsonprinter->page = SDL_CreateRGBSurface(
                        0, (unsigned int)(lpt_epsonprinter->defaultPageWidth * lpt_epsonprinter->dpi),
                        (unsigned int)(lpt_epsonprinter->defaultPageHeight * lpt_epsonprinter->dpi), 8, 0, 0, 0, 0);

                // Set a grey palette
                SDL_Palette *palette = lpt_epsonprinter->page->format->palette;

                for (unsigned int i = 0; i < 256; i++)
                        palette->colors[i].r = palette->colors[i].g = palette->colors[i].b = 255 - i;

                lpt_epsonprinter->curFont = NULL;
                lpt_epsonprinter->autoFeed = false;
                lpt_epsonprinter->outputHandle = NULL;

                resetPrinterHard(lpt_epsonprinter);

                if (lpt_epsonprinter->output_type == OUTPUT_TYPE_FORWARD_TO_REAL_PRINTER) {
#if defined(WIN32)
                        // TODO: this looks like a hack
                        // Show Print dialog to obtain a printer device context

                        PRINTDLG pd;
                        pd.lStructSize = sizeof(PRINTDLG);
                        pd.hDevMode = (HANDLE)NULL;
                        pd.hDevNames = (HANDLE)NULL;
                        pd.Flags = PD_RETURNDC;
                        pd.hwndOwner = NULL;
                        pd.hDC = (HDC)NULL;
                        pd.nFromPage = 1;
                        pd.nToPage = 1;
                        pd.nMinPage = 0;
                        pd.nMaxPage = 0;
                        pd.nCopies = 1;
                        pd.hInstance = NULL;
                        pd.lCustData = 0L;
                        pd.lpfnPrintHook = (LPPRINTHOOKPROC)NULL;
                        pd.lpfnSetupHook = (LPSETUPHOOKPROC)NULL;
                        pd.lpPrintTemplateName = (LPSTR)NULL;
                        pd.lpSetupTemplateName = (LPSTR)NULL;
                        pd.hPrintTemplate = (HANDLE)NULL;
                        pd.hSetupTemplate = (HANDLE)NULL;
                        PrintDlg(&pd);
                        // TODO: what if user presses cancel?
                        lpt_epsonprinter->printerDC = pd.hDC;
#endif
                }
                pclog("PRINTER: Enabled\n");
        }

        // TODO: MAPPER_AddHandler(FormFeed,MK_f2,MMOD1,"ejectpage","formfeed");

        return lpt_epsonprinter;
}

static void epsonprinter_close(void *p) {
        lpt_epsonprinter_t *lpt_epsonprinter = (lpt_epsonprinter_t *)p;

        formFeed(lpt_epsonprinter); // eject any pending page
        if (lpt_epsonprinter->page != NULL) {
                SDL_FreeSurface(lpt_epsonprinter->page);
                lpt_epsonprinter->page = NULL;
                FT_Done_FreeType(lpt_epsonprinter->FTlib);
        }
#if defined(WIN32)
        DeleteDC(lpt_epsonprinter->printerDC);
#endif

        free(lpt_epsonprinter);
}

void *epsonprinter_init_lpt1() {
        current_device = (device_t *)&lpt_epsonprinter_device;
        void *p = epsonprinter_init();
        current_device = NULL;
        // lpt1_device_attach(&lpt_epsonprinter_device, p);

        return p;
}
void epsonprinter_close_lpt1(void *p) {
        epsonprinter_close(p);
        // lpt1_device_detach();
}

static device_config_t epsonprinter_config[] = {
        {.name = "dpi",
         .description = "Output DPI",
         .type = CONFIG_SELECTION,
         .selection = {{.description = "240 DPI", .value = 240},
                       {.description = "360 DPI", .value = 360},
                       {.description = "480 DPI", .value = 480},
                       {.description = "600 DPI", .value = 600},
                       {.description = "720 DPI", .value = 720},
                       {.description = "840 DPI", .value = 840},
                       {.description = "960 DPI", .value = 960},
                       {.description = "1080 DPI", .value = 1080},
                       {.description = ""}},
         .default_int = 360},
        {.name = "outputtype",
         .description = "Output type",
         .type = CONFIG_SELECTION,
         .selection = {{.description = "Postscript (.ps)", .value = OUTPUT_TYPE_POSTSCRIPT},
                       {.description = "Bitmap (.bmp)", .value = OUTPUT_TYPE_BMP},
                       {.description = "Portable Network Graphics (.png)", .value = OUTPUT_TYPE_PNG},
                       {.description = "Tagged Image File Format (.tiff)", .value = OUTPUT_TYPE_TIFF},
                       {.description = "JPEG (.jpg)", .value = OUTPUT_TYPE_JPG},
#if defined(WIN32)
                       {.description = "Redirect to real printer", .value = OUTPUT_TYPE_FORWARD_TO_REAL_PRINTER},
#endif
                       {.description = ""}},
         .default_int = OUTPUT_TYPE_PNG},
        {.name = "multipage",
         .description = "Multipage for Postscript and real printer",
         .type = CONFIG_BINARY,
         .default_int = 1},
        {.name = "papersize",
         .description = "Default paper size",
         .type = CONFIG_SELECTION,
         .selection =
                 {// TODO: more default lengths are supported, see DIP switch settings on the manual and see to with paper types
                  // they map
                  {.description = "Letter (8 1/2\" x 11\")", .value = 0},
                  {.description = "A4 (210mm x 297mm)", .value = 1},
                  {.description = ""}},
         .default_int = 0},
        {.name = "cpi",
         .description = "Default CPI (Characters per inch)",
         .type = CONFIG_SELECTION,
         .selection = {{.description = "10 CPI", .value = 10}, {.description = "12 CPI", .value = 12}, {.description = ""}},
         .default_int = 10},
        /*{ TODO
                .name = "shapeofzero",
                .description = "Shape of zero",
                .type = CONFIG_SELECTION,
                .selection =
                {
                        {
                                .description = "Not Slashed",
                                .value = 0
                        },
                        {
                                .description = "Slashed",
                                .value = 1
                        },
                        {
                                .description = ""
                        }
                },
                .default_int = 0
        },*/
        {.name = "font",
         .description = "Default font",
         .type = CONFIG_SELECTION,
         .selection = {{.description = "Draft", .value = 0},
                       {.description = "Draft condensed", .value = 1},
                       {.description = "NLQ Roman", .value = 2},
                       {.description = "NLQ Sans Serif", .value = 3},
                       {.description = ""}},
         .default_int = 0},
        /*{ TODO
                .name = "draftmode",
                .description = "Draft mode",
                .type = CONFIG_SELECTION,
                .selection =
                {
                        {
                                .description = "Normal",
                                .value = 0
                        },
                        {
                                .description = "High-speed", // TODO: different font, only works at 10 CPI! Reverts do "Draft"
        otherwise (what about styles?) .value = 1
                        },
                        {
                                .description = ""
                        }
                },
                .default_int = 0
        },*/
        {.name = "characterset",
         .description = "Default character set",
         .type = CONFIG_SELECTION,
         .selection = {{.description = "Italics", .value = 0},
                       {.description = "CP437 + U.S.A.", .value = 1},
                       {.description = "CP437 + France", .value = 2},
                       {.description = "CP437 + Germany", .value = 3},
                       {.description = "CP437 + U.K.", .value = 4},
                       {.description = "CP437 + Denmark I", .value = 5},
                       {.description = "CP437 + Sweden", .value = 6},
                       {.description = "CP437 + Italy", .value = 7},
                       {.description = "CP437 + Spain I", .value = 8},
                       {.description = "CP437 + Japan (Only available as a printer command on real hardware)", .value = 9},
                       {.description = "CP437 + Norway (Only available as a printer command on real hardware)", .value = 10},
                       {.description = "CP437 + Denmark II (Only available as a printer command on real hardware)", .value = 11},
                       {.description = "CP437 + Spain II (Only available as a printer command on real hardware)", .value = 12},
                       {.description = "CP437 + Latin America (Only available as a printer command on real hardware)",
                        .value = 13},
                       // The international character sets above can be selected (via software) with the codepages below, but I
                       // don't know if it makes sense.
                       {.description = "CP850 (Multilingual)", .value = 14},
                       {.description = "CP860 (Portugal)", .value = 15},
                       {.description = "CP863 (Canada-French)", .value = 16},
                       {.description = "CP865 (Norway)", .value = 17},
                       {.description = ""}},
         .default_int = 1},
        /*{ TODO: Didn't implement because I didn't test if this is FORCE or just START ON
                .name = "autolinefeed",
                .description = "Force auto linefeed",
                .type = CONFIG_BINARY,
                .default_int = 0
        },*/
        /*{ TODO: this forces an upper and lower margin by default - half and inch at each. Can be overridden by software - check
        if it goes back to these values after a software/command reset. .name = "skipperforation", .description = "1-inch skip
        over perforation", .type = CONFIG_BINARY, .default_int = 0
        },*/
        {.name = "bitmaps_emulate_pins",
         .description = "Emulate pin spacing when printing graphics",
         .type = CONFIG_BINARY,
         .default_int = 1},
        {.type = -1}};

lpt_device_t lpt_epsonprinter_device = {"Epson LX-810 Printer",
                                        0,
                                        epsonprinter_init_lpt1,
                                        epsonprinter_close_lpt1,
                                        NULL,
                                        NULL,
                                        NULL,
                                        NULL,
                                        epsonprinter_config,
                                        epsonprinter_write_data,
                                        epsonprinter_write_ctrl,
                                        epsonprinter_read_status};
