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
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!
No worries, it was fun doing it actually, though, with testing, my ink in the printer has taken quite a hit! :D
Dabz