Main Menu

News:

SyntaxBoom, now with pwetty syntax highlighted code boxes! \o/ 

https://www.syntaxboom.com/forum/index.php?topic=96

Shoutbox

Jackdaw

Today at 20:00:40
Going to have to try bourbon and beans. That should be an explosive combination.

Baggey

Today at 13:15:42
I sometimes mix a chicken vindaloo and a Tika Masala together. Awesome  :P

Dabzy

Today at 05:49:34
And doing the act was the realisation I went for an indian when out... 20mins I was in the thunderbox waiting for me back chaff to cool down!

Dabzy

Today at 05:48:11
When I was on my "Year On The Blur", aka drinking after getting divorced, I was minging one night, couldnt remember getting home. Anyway, next day, went to work, and needed a poo...

GfK

2025-10-15, 21:39:05
I overdosed on jelly babies once and my arse was like the shooty gun bit at the start of James Bond

Amon

2025-10-15, 20:16:38
lol

Jackdaw

2025-10-15, 19:40:48
Never had a Phall or a tinderloo. But I have heard that your backside feels like that map at the start of every episode of Bonanza.

GfK

2025-10-15, 19:22:25
Never confuse phall with phallus

Baggey

2025-10-15, 18:30:58
My mate ate a phall one night. Even that was to hot for me. I bet he suffered in the morning!  :-[

Dabzy

2025-10-15, 18:02:52
I like nice and toasty bit of fire in my bait as well, I used to eat really red hot gear, but nowadays if I do... Heartburn happens! :(

Members
Stats
  • Total Posts: 1,811
  • Total Topics: 224
  • Online today: 26
  • Online ever: 232 (Oct 08, 2025, 09:18 AM)
Users Online
  • Users: 2
  • Guests: 5
  • Total: 7
  • ando
Welcome to SyntaxBoom. Please login or sign up.

Recent

BlitzMax-NG AddPrinterJob()

Started by Dabzy, Oct 12, 2025, 09:23 AM

Previous topic - Next topic

Dabzy

Regarding _PJ_'s printer troubles, I thought it would be nice to have printing in one of my projects, and one was a bit bored, so I came up with this (Note: Windows only, but if anyone wants to expand on it for Linux/MacOS, then... Canny):-

Import MaxGui.Drivers

Import "AddPrinterJob.c"

Extern
	Function AddPrinterJob:Int(filePath:Byte Ptr, docTitle:Byte Ptr, fontName_:Byte Ptr, fontSize_:Double) 
End Extern

Local font:TGUIFont
Local printResult:Int 

Local sourceFile:String = CurrentDir$()+"/printtestANSI.txt"
'Local sourceFile:String = CurrentDir$()+"/printtestUNI-8.txt"
'Local sourceFile:String = CurrentDir$()+"/printtestUNI-16BE.txt"
'Local sourceFile:String = CurrentDir$()+"/printtestUNI-16LE.txt"
'Local sourceFile:String = CurrentDir$()+"/printtestUNI-BOM.txt"

font = RequestFont(font)

If font <> Null
	printResult = AddPrinterJob(sourceFile,"Dabzy's Print Jobbie",FontName(font), FontSize(font))

	Select printResult
		Case -1
			Print "The file could not be found or is corrupt."
		Case 0
			Print "Print job cancelled"
		Case 1
			Print "Print job complete... Most success, me like! :)"
	End Select
EndIf

Which requires the following C file below:

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

int IsValidUTF8(const unsigned char *data, size_t len);
int DetectFileEncoding(const char *filePath);
int ReadUTF16BELine(FILE *fp, wchar_t *buffer, int bufferSize);

int AddPrinterJob(const char *filePath, const char *docTitle, const char *fontName, double pointSize) {
    PRINTDLG pd = {0};
    DOCINFO di = {0};
    HDC hdcPrinter;
    FILE *fp = NULL;
    wchar_t buffer[4096];
    int y = 100;
    int leftMargin = 100;
    int rightMargin = 100;
    int topMargin = 100;
    int encoding = DetectFileEncoding(filePath);
    int isUTF16BE = (encoding == 2);
    
    wchar_t wFilePath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, 0, filePath, -1, wFilePath, MAX_PATH);
    
    if (isUTF16BE) {
        fp = _wfopen(wFilePath, L"rb");
        unsigned char bom[2];
        if (fread(bom, 1, 2, fp) == 2) {
            if (!(bom[0] == 0xFE && bom[1] == 0xFF)) {
                fseek(fp, 0, SEEK_SET);
            }
        }
    } else {
        switch (encoding) {
            case 1:
                fp = _wfopen(wFilePath, L"r, ccs=UTF-16LE");
                break;
            case 3:
                fp = _wfopen(wFilePath, L"r, ccs=UTF-8");
                break;
            default:
                fp = _wfopen(wFilePath, L"r, ccs=UTF-8");
                if (!fp) {
                    fp = _wfopen(wFilePath, L"r");
                }
                break;
        }
    }
    
    if (!fp) {
        return -1;
    }
    
    pd.lStructSize = sizeof(PRINTDLG);
    pd.Flags = PD_RETURNDC;
    pd.nCopies = 1;
    
    if (!PrintDlg(&pd)) {
        fclose(fp);
        return 0;
    }
    
    hdcPrinter = pd.hDC;
    
    int pageWidth = GetDeviceCaps(hdcPrinter, HORZRES);
    int pageHeight = GetDeviceCaps(hdcPrinter, VERTRES);
    int printableWidth = pageWidth - leftMargin - rightMargin;
    int bottomMargin = pageHeight - 100;
    
    int logicalSize = -MulDiv(pointSize, GetDeviceCaps(hdcPrinter, LOGPIXELSY), 72);
       
    di.cbSize = sizeof(DOCINFO);
    di.lpszDocName = docTitle;
    StartDoc(hdcPrinter, &di);
    StartPage(hdcPrinter);
    
    wchar_t wFontName[LF_FACESIZE];
    MultiByteToWideChar(CP_ACP, 0, fontName, -1, wFontName, LF_FACESIZE);
    
    HFONT hFont = CreateFontW(
        logicalSize, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, wFontName
    );
    HFONT hOldFont = (HFONT)SelectObject(hdcPrinter, hFont);
    
    TEXTMETRIC tm;
    GetTextMetrics(hdcPrinter, &tm);
    int lineHeight = tm.tmHeight + tm.tmExternalLeading;
    
    int hasMoreLines = 1;
    while (hasMoreLines) {
        if (isUTF16BE) {
            hasMoreLines = ReadUTF16BELine(fp, buffer, sizeof(buffer)/sizeof(wchar_t));
            if (!hasMoreLines) break;
        } else {
            if (!fgetws(buffer, sizeof(buffer)/sizeof(wchar_t), fp)) break;
            
            size_t len = wcslen(buffer);
            if (len > 0 && buffer[len-1] == L'\n') {
                buffer[len-1] = L'\0';
            }
        }
        
        if (wcslen(buffer) == 0) {
            if (y + lineHeight > bottomMargin) {
                EndPage(hdcPrinter);
                StartPage(hdcPrinter);
                y = topMargin;
            }
            y += lineHeight;
            continue;
        }
        
        wchar_t *remainingText = buffer;
        
        while (*remainingText != L'\0') {
            while (*remainingText == L' ') remainingText++;
            if (*remainingText == L'\0') break;
            
            int availableHeight = bottomMargin - y - lineHeight;
            
            if (availableHeight < lineHeight) {
                EndPage(hdcPrinter);
                StartPage(hdcPrinter);
                y = topMargin;
                availableHeight = bottomMargin - y - lineHeight;
            }
            
            RECT rect;
            rect.left = leftMargin;
            rect.top = y;
            rect.right = leftMargin + printableWidth;
            rect.bottom = bottomMargin;
            
            RECT calcRect = rect;
            calcRect.bottom = pageHeight + 10000;
            int totalHeight = DrawTextW(hdcPrinter, remainingText, -1, &calcRect, 
                                      DT_WORDBREAK | DT_CALCRECT);
            
            if (totalHeight <= availableHeight) {
                rect.bottom = y + totalHeight;
                DrawTextW(hdcPrinter, remainingText, -1, &rect, DT_WORDBREAK);
                y += totalHeight;
                break;
            }
            
            int textLen = wcslen(remainingText);
            int left = 1;
            int right = textLen;
            int bestSplit = 0;
            
            while (left <= right) {
                int mid = (left + right) / 2;
                
                wchar_t testText[4096];
                wcsncpy(testText, remainingText, mid);
                testText[mid] = L'\0';
                
                RECT testRect;
                testRect.left = leftMargin;
                testRect.top = y;
                testRect.right = leftMargin + printableWidth;
                testRect.bottom = pageHeight + 10000;
                
                int testHeight = DrawTextW(hdcPrinter, testText, -1, &testRect, 
                                         DT_WORDBREAK | DT_CALCRECT);
                
                if (testHeight <= availableHeight) {
                    bestSplit = mid;
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
            
            int splitPoint = bestSplit;
            while (splitPoint > 0 && remainingText[splitPoint] != L' ' && 
                   remainingText[splitPoint] != L'\0') {
                splitPoint--;
            }
            
            if (splitPoint == 0) {
                splitPoint = bestSplit;
            }
            
            if (splitPoint == 0) {
                splitPoint = 1;
            }
            
            wchar_t pageText[4096];
            wcsncpy(pageText, remainingText, splitPoint);
            pageText[splitPoint] = L'\0';
            
            RECT finalCalc;
            finalCalc.left = leftMargin;
            finalCalc.top = y;
            finalCalc.right = leftMargin + printableWidth;
            finalCalc.bottom = pageHeight + 10000;
            int actualHeight = DrawTextW(hdcPrinter, pageText, -1, &finalCalc, 
                                       DT_WORDBREAK | DT_CALCRECT);
            
            rect.bottom = y + actualHeight;
            DrawTextW(hdcPrinter, pageText, -1, &rect, DT_WORDBREAK);
            
            remainingText += splitPoint;
            
            if (*remainingText != L'\0') {
                EndPage(hdcPrinter);
                StartPage(hdcPrinter);
                y = topMargin;
            } else {
                y += actualHeight;
            }
        }
    }
    
    SelectObject(hdcPrinter, hOldFont);
    EndPage(hdcPrinter);
    EndDoc(hdcPrinter);
    DeleteDC(hdcPrinter);
    DeleteObject(hFont);
    fclose(fp);
    
    return 1;
}

int IsValidUTF8(const unsigned char *data, size_t len) {
    size_t i = 0;
    while (i < len) {
        if (data[i] <= 0x7F) {
            i++;
        } else if ((data[i] & 0xE0) == 0xC0) {
            if (i + 1 >= len || (data[i+1] & 0xC0) != 0x80) return 0;
            i += 2;
        } else if ((data[i] & 0xF0) == 0xE0) {
            if (i + 2 >= len || (data[i+1] & 0xC0) != 0x80 || (data[i+2] & 0xC0) != 0x80) return 0;
            i += 3;
        } else if ((data[i] & 0xF8) == 0xF0) {
            if (i + 3 >= len || (data[i+1] & 0xC0) != 0x80 || (data[i+2] & 0xC0) != 0x80 || (data[i+3] & 0xC0) != 0x80) return 0;
            i += 4;
        } else {
            return 0;
        }
    }
    return 1;
}

int DetectFileEncoding(const char *filePath) {
    FILE *fp = fopen(filePath, "rb");
    if (!fp) return 0;
    
    unsigned char buffer[4096];
    size_t read = fread(buffer, 1, sizeof(buffer), fp);
    fclose(fp);
    
    if (read < 2) return 0;
    
    if (read >= 2) {
        if (buffer[0] == 0xFF && buffer[1] == 0xFE) return 1;
        if (buffer[0] == 0xFE && buffer[1] == 0xFF) return 2;
    }
    if (read >= 3) {
        if (buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF) return 3;
    }

    int nullEven = 0, nullOdd = 0;
    for (size_t i = 0; i < read - 1; i += 2) {
        if (buffer[i] == 0) nullEven++;
        if (buffer[i+1] == 0) nullOdd++;
    }
    if (nullOdd > read / 4) return 1; 
    if (nullEven > read / 4) return 2; 
    
    if (IsValidUTF8(buffer, read)) {
        int hasNonASCII = 0;
        for (size_t i = 0; i < read; i++) {
            if (buffer[i] > 0x7F) {
                hasNonASCII = 1;
                break;
            }
        }
        if (hasNonASCII) return 3; 
    }
    
    return 0;
}

int ReadUTF16BELine(FILE *fp, wchar_t *buffer, int bufferSize) {
    unsigned char byte1, byte2;
    int pos = 0;
    
    while (pos < bufferSize - 1) {
        if (fread(&byte1, 1, 1, fp) != 1) {
            if (pos == 0) return 0; // EOF
            break;
        }
        if (fread(&byte2, 1, 1, fp) != 1) {
            break;
        }
        
        wchar_t wc = (byte1 << 8) | byte2;
        
        if (wc == L'\n') {
            buffer[pos] = L'\0';
            return 1;
        }
        if (wc == L'\r') {
            long currentPos = ftell(fp);
            if (fread(&byte1, 1, 1, fp) == 1 && fread(&byte2, 1, 1, fp) == 1) {
                wchar_t next = (byte1 << 8) | byte2;
                if (next != L'\n') {
                    fseek(fp, currentPos, SEEK_SET);
                }
            }
            buffer[pos] = L'\0';
            return 1;
        }
        
        buffer[pos++] = wc;
    }
    
    buffer[pos] = L'\0';
    return pos > 0;
}

This handles encoding automatically, ANSI, UTF-8, UTF-8 BOM, UTF-16LE and UTF-16BE, with UTF-16BE being a bit of a pig, but, I mangled it.

Works both for x86 and x64 builds.

Slight caveat is that even though there is word wrapping, and the the size of the characters to the page size are worked out, if you do a stupid font size and the word is too big along the horizontal to fit the page, it will over run and thus, be clipped.

All in a all, a cheap and cheerful was of sending a file to the printer if your not bothered about formatting. If you require more complicated print jobs, then as @Midimaster suggested, take a look at the PDF module.

A zip file is attached to this post to save you mucking about setting it up.

Dabz
Intel i7-13620H, nVidia GerForce RTX 4060 Laptop GPU (8GB GDDR6), 16GB LPDDR5X, 1TB SSD, Windows 11 x64 piss flap of an OS!

_PJ_

QuoteI was bored so I literally solved a bunch of PJ*s problems in an afternoon

 :D  ;D  :D

Nice one, Dabzy - thank you - This is awesome!

Dabzy

No worries, it was fun doing it actually, though, with testing, my ink in the printer has taken quite a hit! :D

Dabz
Intel i7-13620H, nVidia GerForce RTX 4060 Laptop GPU (8GB GDDR6), 16GB LPDDR5X, 1TB SSD, Windows 11 x64 piss flap of an OS!