Here is some sample printing code for you that should do the job properly. It is a minimal, self-contained example that you should be able to just compile and run. Based on that, you should then be able to adapt it to your specific needs. First, here's the code, and then some explanatory notes.
Please note that I have written this as a Console app, but that's probably not what you actually want. Also, the function that does all the work (print_file()
) is now callable from C as requested. OK, here's the code:
#include <windows.h>
#include <conio.h>
#include <string>
#include <fstream>
#include <iostream>
#define SCALE_FACTOR 100 // percent
inline static int MM_TO_PIXELS (int mm, int dpi)
{
return MulDiv (mm * 100, dpi, 2540);
}
// Calculate the wrapped height of a string
static int calculate_wrapped_string_height (HDC hDC, int width, const std::string& s)
{
RECT r = { 0, 0, width, 16384 };
DrawText (hDC, s.c_str (), (int) s.length (), &r, DT_CALCRECT | DT_NOPREFIX | DT_WORDBREAK);
return (r.bottom == 16384) ? calculate_wrapped_string_height (hDC, width, " ") : r.bottom;
}
// Print a string in the width provided.
static void print_string (HDC hDC, int x, int y, int width, const std::string& s)
{
RECT r = { x, y, x + width, 16384 };
DrawText (hDC, s.c_str (), (int) s.length (), &r, DT_NOPREFIX | DT_WORDBREAK);
}
// Print page number. Returns (y + vertical space consumed)
static int print_pagenum (HDC hDC, int x, int y, int width, int& pagenum)
{
std::string hdr = "Page: " + std::to_string (++pagenum) + "\n";
int space_needed = calculate_wrapped_string_height (hDC, width, hdr);
print_string (hDC, x, y, width, hdr);
std::cout << "Printing page: " << pagenum << "\n";
return space_needed;
}
extern "C" bool print_file (const char *filename)
{
std::ifstream f;
f.open ("g:\\temp\\print_me.txt", std::ios_base::in);
if (!f)
{
std::cout << "Cannot open input file, error " << GetLastError () << "\n";
return false;
}
// Display print dialog
PRINTDLGEX pdex = { sizeof (pdex) };
PRINTPAGERANGE pr [10] = { };
HDC hDC;
pdex.hwndOwner = GetDesktopWindow ();
pdex.Flags = PD_ALLPAGES | PD_RETURNDC | PD_NOCURRENTPAGE | PD_NOSELECTION;
pdex.nMaxPageRanges = _countof (pr);
pdex.nPageRanges = 1;
pr [0].nFromPage = pr [0].nToPage = 1;
pdex.lpPageRanges = pr;
pdex.nMinPage = 1;
pdex.nMaxPage = 999999;
pdex.nCopies = 1;
pdex.nStartPage = START_PAGE_GENERAL;
HRESULT hr = PrintDlgEx (&pdex);
if (hr != S_OK)
{
std::cout << "PrintDlgEx failed, error " << GetLastError () << "\n";
return false;
}
if (pdex.dwResultAction == PD_RESULT_CANCEL)
return false;
hDC = pdex.hDC;
if (pdex.dwResultAction != PD_RESULT_PRINT)
{
DeleteDC (hDC);
return false;
}
// Only print what we need to
int max_page = 0x7fffffff;
if (pdex.Flags & PD_PAGENUMS)
{
max_page = 0;
for (int i = 0; i < (int) pdex.nPageRanges; ++i)
{
if ((int) pdex.lpPageRanges [i].nToPage > max_page)
max_page = pdex.lpPageRanges [i].nToPage;
}
}
constexpr int dpi = 96 * 100 / SCALE_FACTOR;
int lpx = GetDeviceCaps (hDC, LOGPIXELSX);
int lpy = GetDeviceCaps (hDC, LOGPIXELSX);
int res_x = GetDeviceCaps (hDC, HORZRES);
int res_y = GetDeviceCaps (hDC, VERTRES);
// margins
int left_margin = MM_TO_PIXELS (10, dpi);
int top_margin = MM_TO_PIXELS (20, dpi);
int right_margin = MM_TO_PIXELS (20, dpi);
int bottom_margin = MM_TO_PIXELS (20, dpi);
int width = MulDiv (res_x, dpi, lpx) - (left_margin + right_margin);
int y_max = MulDiv (res_y, dpi, lpy) - bottom_margin;
// Set up for SCALE_FACTOR
SetMapMode (hDC, MM_ANISOTROPIC);
SetWindowExtEx (hDC, dpi, dpi, NULL);
SetViewportExtEx (hDC, lpx, lpy, NULL);
SetStretchBltMode (hDC, HALFTONE);
DOCINFO di = { 0 };
di.cbSize = sizeof (di);
di.lpszDocName = "Stack Overflow";
int job_id = StartDoc (hDC, &di);
if (job_id <= 0)
{
std::cout << "StartDoc failed, error " << GetLastError () << "\n";
DeleteDC (hDC);
return false;
}
SetBkMode (hDC, TRANSPARENT);
LOGFONT lf = { 0 };
lf.lfWeight = FW_NORMAL;
lf.lfHeight = -12;
HFONT hTextFont = CreateFontIndirect (&lf);
HFONT hOldFont = (HFONT) GetCurrentObject (hDC, OBJ_FONT);
SelectObject (hDC, hTextFont);
int x = left_margin;
int y = top_margin;
int pagenum = 0;
int err = StartPage (hDC);
if (err <= 0)
{
std::cout << "StartPage failed, error " << GetLastError () << "\n";
DeleteDC (hDC);
return false;
}
y += print_pagenum (hDC, x, y, width, pagenum);
// Printing loop, per line
for ( ; ; )
{
if (_kbhit ())
{
AbortDoc (hDC);
break;
}
std::string line;
std::getline (f, line);
if (!f)
break;
int space_needed = calculate_wrapped_string_height (hDC, width, line);
if (space_needed > y_max - y)
{
if (pagenum >= max_page)
break;
if (EndPage (hDC) < 0 || StartPage (hDC) < 0)
break;
y = top_margin;
y += print_pagenum (hDC, x, y, width, pagenum);
}
print_string (hDC, x, y, width, line);
y += space_needed;
}
EndPage (hDC);
EndDoc (hDC);
SelectObject (hDC, hOldFont);
DeleteObject (hTextFont);
DeleteDC (hDC);
return true;
}
// main
int main ()
{
bool ok = print_file ("g:\\temp\\print_me.txt");
return !ok;
}
Notes:
The code shows how to paginate the output properly. I included page numbers in he printed output, just for fun.
Input filename is hard-coded. Please adapt this to your needs.
As I say, this is written as a Console app. If you were including this in a Windows app you would use a different parent window handle and a different mechanism (a modeless dialog, typically) to report progress and to allow the user to cancel the print job.
The code as written expects to be compiled as ANSI (purely for convenience). I'm sure you can fix that.
This code doesn't handle page ranges entered by the user in the Windows standard Print dialog properly. I leave that as an exercise for the reader.
To make this code callable from C, compile it as a separate .cpp file (excluding main
of course) and then prototype print_file
(in a
separate .h file) as:
extern "C" bool print_file (const char *filename);
Then #include this file in both your .cpp file and your .c file (s).
Please note that bool
is a predefined type - sort of - in C99 and later, see:
https://stackoverflow.com/a/1608350/5743288