1

Say I have an ASCII encoded text file mytextfile.txt and I want to print it.

I have found various sources on MSDN, but none of it seems useful:

From here:

Defines a reusable object that sends output to a printer, when printing from a Windows Forms application. public ref class PrintDocument : Component

However this is meant for C++ (not C)

I also found this, which defines several functions, none of which actually seem to have the ability to print:

IPrintDocumentPackageStatusEvent: Represents the progress of the print job.

IPrintDocumentPackageTarget: Allows users to enumerate the supported package target types and to create one with a given type ID. IPrintDocumentPackageTarget also supports the tracking of the package printing progress and cancelling.

IPrintDocumentPackageTargetFactory: Used with IPrintDocumentPackageTarget for starting a print job.

Ah! That looks like it! Well it turns out that this is also for C++:

IPrintDocumentPackageTargetFactory::CreateDocumentPackageTargetForPrintJob

I think it is impracticle (and difficult) to try and imitate classes in C.

I could use the print command:

system("print mytextfile.txt");

But this does seem to be a bit of a hack, is there a better way to print a file using functions?

To clarify: I want this printing on paper not in terminal.

Community
  • 1
  • 1
Xantium
  • 11,201
  • 10
  • 62
  • 89
  • Try `ShellExecute(0, L"print", L"test.txt", 0, 0, SW_SHOW)` to print through the default text editor. Or [Print Spooler API Functions](https://msdn.microsoft.com/en-us/library/windows/desktop/hh448420(v=vs.85).aspx) to render everything yourself. Otherwise you are looking to use COM in C, which is rather difficult. – Barmak Shemirani Jul 28 '18 at 22:05
  • maybe using win32 api (quite old document though): https://support.microsoft.com/de-de/help/138594/howto-send-raw-data-to-a-printer-by-using-the-win32-api – Stephan Schlecht Jul 28 '18 at 22:11
  • 2
    Open `lpt1` using `fopen` and `fprintf` to it? – Jonathan Potter Jul 28 '18 at 22:11
  • @JonathanPotter Sorry not that easy, I need it on paper (unless it is, in which case super) :) – Xantium Jul 28 '18 at 22:14
  • Assuming your printer is `lpt1` and you have paper in your printer it should work :) – Jonathan Potter Jul 28 '18 at 22:14
  • 4
    Windows doesn't provide such a high level function. You would have to write code to talk to the printer as a GDI device. – David Heffernan Jul 29 '18 at 04:37
  • 1
    [IPrintDocumentPackageTargetFactory](https://learn.microsoft.com/en-us/windows/desktop/api/documenttarget/nn-documenttarget-iprintdocumentpackagetargetfactory) isn't meant for C++. It's a COM interface, and as such language-agnostic. You can consume COM from C just fine. It may be a bit verbose, but no more difficult than using it from C++. Just make sure to `#define COBJMACROS` before including *DocumentTarget.h*, to get the C helper macros defined. – IInspectable Jul 29 '18 at 07:07
  • 1
    To print an ASCII file, you could probably just open the printer ([CreateFile](https://learn.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilew) passing the printer name, e.g. `LPT1`), and write to the handle ([WriteFile](https://learn.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-writefile)). – IInspectable Jul 29 '18 at 07:15
  • @IInspectable Your comments looks like an answer. Maybe leave one? Do you have any tutorials/guids that explain how to use the COM inteface in C, So far I've found [this](https://www.codeproject.com/Articles/13601/COM-in-plain-C)? Thank you for your help. – Xantium Jul 29 '18 at 08:18
  • 1
    It may or not may not do what you need. I cannot verify that. Presumably, you can, so it would make more sense for you to verify applicability, as well as [answer your own question](https://stackoverflow.com/help/self-answer). You can either use the Windows API calls, or the CRT calls as outlined in Jonathan Potter's [comment](https://stackoverflow.com/questions/51575459/printing-a-file-in-windows?noredirect=1#comment90116977_51575459). – IInspectable Jul 29 '18 at 12:10

2 Answers2

4

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:

  1. The code shows how to paginate the output properly. I included page numbers in he printed output, just for fun.

  2. Input filename is hard-coded. Please adapt this to your needs.

  3. 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.

  4. The code as written expects to be compiled as ANSI (purely for convenience). I'm sure you can fix that.

  5. 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.

  6. 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

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • I will accept your answer (although it is not a pure C solution) if you mention that it can be called from C by making it into a runtime. That way it will still be possible to call it from C – Xantium Aug 02 '18 at 14:15
1

You could read a line, print a line, in a loop, until EOF

Following EDITED to output to printer

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

#define MAX_BUF_SIZE 1024

int main( void )
{
    char buffer[ MAX_BUF_SIZE ];

    FILE *fin = fopen( "mytextfile.txt", "f" );
    if( ! fin )
    {
        perror( "fopen failed for reading file" );
        exit( EXIT_FAILURE );
    }

    // implied else, fopen successful

    FILE *printer = fopen("LPT1", "w");
    if( !printer )
    {
        perror( "fopen failed for printing" );
        exit( EXIT_FAILURE );
    }

    // implied else, fopen successful

    while( fgets( buffer, sizeof buffer, fin ) )
    {
        fprintf( printer, "%s", buffer );
    }

    fprintf( printer, "%s",  "\n" );

    fclose( fin );
    fclose( printer );
    return 0;
}
user3629249
  • 16,402
  • 1
  • 16
  • 17
  • But how would that print anything? I have tagged it `printers` before you say it's unclear, and all the functions mention printing. – Xantium Jul 28 '18 at 22:08
  • @Simon, the `while()` loop is calling `fgets()` to read text into the `buffer[]` and if successful in reading any text, then the call to `printf()` outputs that text to the terminal – user3629249 Jul 28 '18 at 22:10
  • 1
    But I need it on **paper**! I'm sorry if you misunderstood I'll edit – Xantium Jul 28 '18 at 22:10
  • You need it on paper, OK, the command line to run the program (assuming the executable name is 'printffile' would be: `printfile > lpr` or `printfile | pr – user3629249 Jul 28 '18 at 22:13
  • But this would still be using something like `system()` is there something that uses functions without passing through the command line functions. – Xantium Jul 28 '18 at 22:21
  • so add a couple lines to open (for write) the printer then use `fprintf()` rather than `printf()` – user3629249 Jul 28 '18 at 22:35
  • the command line can be easily written to redirect the output to the printer with something like: `printfile > lpt1` Which is NOT using any function, like `system()` – user3629249 Jul 28 '18 at 22:40
  • If you want to use the command interpreter, you'll have to create a command interpreter process. Using `CreateProcess` is the moral equivalent of using `system()`. The OP does not want to spawn an additional process. – IInspectable Jul 29 '18 at 08:40
  • 1
    @IInspectable, My posted answer does NOT create an additional process. It DOES do the job the OP ask for, using only C library functions – user3629249 Jul 30 '18 at 14:20