1

I'm trying to print various types of documents (PDF, TIF, Multi-TIF, jpeg, png...) from a C# application.

I tried various approaches, each time resulting in failure.

  1. print command: I get an error message saying that the 'PRN device is not initialized'. Apparently the print command is only valid for LPT devices, altough there exists a way (mapping) this won't work since I'd have to set this up on each computer seperately and I'd have to do it 'on the fly', since a user needs to select the printer he wants to print on.

  2. using the WPF PrintDialog: Apparently I need to either work with the System.Windows.Documents namespace that, from previous research, involves building a class for each and every type of document myself. I'd have to do research and then implement that stuff - not enough time and there has to be an easier way

  3. using the WPF PrintDialog (pt.2): Via the AddJob(string, string, bool) method. Apparently this needs an XPS document. Which means I'd have to find a converter for every documenttype to XPS and make sure nothing goes ever wrong during conversion. Nope.

  4. using the WPF PrintDialog (pt.3): Some people were, apparently, able to use the PrintSystemJobInfo.JobStream to sent their stuff over. This did in fact something on my end. But that something being a bunch of ASCII signs being spread out over ~10 pages when I tried printing a .jpg file. Nope.

  5. using System.Drawing.Printing: Again, I'd have to essentially build a converter method for every type of document. Nope.

I tried finding some kind of library that'd do but I couldn't find anything suitable.

By the ol' rule that if you use a huge amount of time trying to solve a problem the solution is rather simple in reality, I ask if anyone knows where I either went wrong (maybe something in the previous 5 points isn't as complex as I make it out to be) or if there is another way to do this.

My code for point 4:

PrintDialog prntDialog = new PrintDialog();
prntDialog.CurrentPageEnabled = false;
prntDialog.SelectedPagesEnabled = false;
prntDialog.UserPageRangeEnabled = false;
if (prntDialog.ShowDialog().Value)
{
    using (var job = prntDialog.PrintQueue.AddJob())
    {
        job.Pause();
        using (var jobStream = job.JobStream)
        {
            using (var sourceStream = File.Open(Path.Combine(printdirName, doc.GetPlainFilename()), FileMode.Open))
            {
                while (sourceStream.Position < sourceStream.Length)
                {
                    byte[] buffer = new byte[1024];
                    int readBytes = sourceStream.Read(buffer, 0, 1024);
                    jobStream.Write(buffer, 0, readBytes);
                }

                jobStream.Flush();
            }
        }

        job.Resume();
    }
}
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Steffen Winkler
  • 2,805
  • 2
  • 35
  • 58
  • http://stackoverflow.com/a/27429435/43846 ? – stuartd Nov 24 '15 at 16:44
  • @stuartd that's my point 4 and I guess it depends on the printer model/driver, since in my case I only get ASCII sings when I try to send a jpeg over. I also added my code for that point to the question. Since I don't want to provoke OOM exceptions, I changed some things. (writing via a buffer and pausing/resuming the printjob) – Steffen Winkler Nov 24 '15 at 16:45
  • 1
    The article states that it's for PDF files, specifically. That may be why your JPEG is giving you ASCII strings. This is basically just using the printer's built-in PDF rendering. – Mike U Nov 25 '15 at 21:29

1 Answers1

1

I believe your first problem is that the scope of the solution is too broad. You're not likely to find a single "print all the things" library, since printing something requires a rendering engine in and of itself. If you're dealing with specific types of files that you want to print, you should be able to categorize them into groups: PDF, Image, Text, etc.

You will then have to provide a rendering engine (either .NET, a downloaded library, or something custom) for each category.

You could use the built-in .NET Drawing namespace types to handle image printing pretty easily. Using the PrintDocument class isn't much different that using the System.Drawing.Graphics class, and would only require code to make sure that the printed image was scaled to fit the printing bounds.

Same with printing straight text. Using the System.Drawing.StringFormat class to define how the text is rendered, and define the bounds as the printable area of the page and then it's just a call to DrawText(). Below is some code from an old project to print text from a RichTextBox WinForms control, while doing some custom alteration to add line numbers to the printed output.

    public void Print()
    {
        this.InitPrintDocument();
        using (System.Windows.Forms.PrintDialog dlg = new System.Windows.Forms.PrintDialog())
        {
            dlg.AllowSomePages = false;
            dlg.AllowSelection = false;
            dlg.AllowPrintToFile = false;
            dlg.AllowCurrentPage = false;
            if (dlg.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
            {
                this._printDoc.PrinterSettings = dlg.PrinterSettings;
                this._printDoc.Print();
            }
        }
    }
    private void InitPrintDocument()
    {
        if (this._printDoc != null)
            this._printDoc.Dispose();
        this._printDoc = new System.Drawing.Printing.PrintDocument();
        this._printDoc.BeginPrint += new System.Drawing.Printing.PrintEventHandler(this.printDocument_onBeginPrint);
        this._printDoc.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(this.printDocument_onPrintPage);
    }
    private void printDocument_onBeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
    {
        this._lastPrintLine = 0;
    }
    private void printDocument_onPrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
    {
        string pgTxt = "";
        this.SuspendRefresh();
        this.SuspendScroll();
        int stChar = this.GetFirstCharIndexFromLine(this._lastPrintLine),
            stLn = this._lastPrintLine,
            selStart = this.SelectionStart,
            selLength = this.SelectionLength;
        try
        {
            using (StringFormat fmt = new StringFormat(StringFormatFlags.LineLimit))
            {
                int lnCnt = this.Lines.Length;
                SizeF lnNumSz = e.Graphics.MeasureString("0000", this.Font);
                float numMarginWidth = lnNumSz.Width + 2.0f;

                while (e.Graphics.MeasureString(pgTxt, this.Font, new Size(e.MarginBounds.Size.Width - (int)numMarginWidth, e.MarginBounds.Size.Height), fmt).Height < e.MarginBounds.Height - (e.MarginBounds.Top / 2) && this._lastPrintLine < this.Lines.Length)
                    pgTxt += ((pgTxt.Length > 0) ? "\n" : "") + this.Lines[this._lastPrintLine++];

                bool newLine = true;
                PointF nextCharPos = new PointF((float)e.MarginBounds.Left + numMarginWidth, (float)e.MarginBounds.Top);
                RectangleF numMarginBounds = new RectangleF((float)e.MarginBounds.Left, (float)e.MarginBounds.Top, numMarginWidth, (float)e.MarginBounds.Height);
                using (SolidBrush numBrush = new SolidBrush(Color.FromArgb(200, 200, 200)))
                    e.Graphics.FillRectangle(numBrush, numMarginBounds);
                for (int i = 0; i < pgTxt.Length; i++)
                {
                    this.SelectionStart = stChar + i;
                    if (newLine)
                    {
                        RectangleF numRect = new RectangleF(e.MarginBounds.Left, nextCharPos.Y, numMarginWidth, lnNumSz.Height);
                        using (StringFormat format = new StringFormat(StringFormatFlags.NoWrap))
                        {
                            format.Alignment = StringAlignment.Far;
                            e.Graphics.DrawString(Convert.ToString(this.GetLineFromCharIndex(stChar + i) + 1), this.Font, Brushes.DarkGreen, numRect, format);
                        }
                    }
                    string nextChar = pgTxt.Substring(i, 1);
                    if (nextChar != "\n")
                    {
                        SelectionLength = 1;
                        Color curCol = this.SelectionColor;
                        SizeF charSz = e.Graphics.MeasureString(nextChar, this.Font);
                        if (nextCharPos.X + (charSz.Width / 1.5) > (e.MarginBounds.Width / 0.9))
                            nextCharPos = new PointF((float)e.MarginBounds.Left + numMarginWidth, nextCharPos.Y + (charSz.Height / 1.105f));
                        using (SolidBrush brush = new SolidBrush(curCol))
                            e.Graphics.DrawString(nextChar, this.Font, brush, nextCharPos);
                        nextCharPos = new PointF(nextCharPos.X + charSz.Width / ((nextChar != " ") ? 1.5f : 0.9f), nextCharPos.Y);
                        newLine = false;
                    }
                    else
                    {
                        newLine = true;
                        SizeF charSz = e.Graphics.MeasureString("W", this.Font);
                        nextCharPos = new PointF((float)e.MarginBounds.Left + numMarginWidth, nextCharPos.Y + (charSz.Height / 1.105f));
                    }
                }
                e.HasMorePages = (this._lastPrintLine < this.Lines.Length);

                if (!e.HasMorePages)
                {
                    e.Graphics.FillRectangle(Brushes.White, new RectangleF(0.0f, nextCharPos.Y + lnNumSz.Height, e.PageBounds.Width, e.MarginBounds.Bottom - (nextCharPos.Y + lnNumSz.Height)));
                }
            }
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(this, "Unable to generate page for printing:\n\n" + ex.Message, "Unexpected Error");
        }
        finally
        {
            this.SelectionStart = selStart;
            this.SelectionLength = selLength;
            this.ResumeScroll();
            this.ResumeRefresh();
        }
    }

PDF's are more difficult to render, but there are plenty of pre-built libraries out there to handle that. ITextSharp, of course, is one of the most powerful and flexible, but also tends to be complicated to use. If all you're looking to do is print, I recommend checking out PDFSharp, since the library follows the typical .NET method naming guidelines and is more intuitive to code against.

http://pdfsharp.com/PDFsharp/

Amadeus Sanchez
  • 2,375
  • 2
  • 25
  • 31
Mike U
  • 709
  • 6
  • 11
  • `You will then have to provide a rendering engine` oh. That explains that. With that in mind, if I embedded the image files into PDF documents, would PDFSharp be able to deal with that? Because I do have a 'everything to PDF' method. – Steffen Winkler Nov 24 '15 at 19:57
  • Errh, just took a look at PDFSharp. Their print method just calls the executable of Acrobat Reader. This is getting ridiculous... – Steffen Winkler Nov 25 '15 at 08:49
  • Hmm... it's been a while since I've used PDFSharp to print anything directly. Usually, it's concatenating pages or inserting page numbers, etc. Seems odd that it would require an external executable, though. Maybe if you explained more what you are trying to accomplish, we could give you better recommendations. I think, ultimately, the problem you're going to have is that you are underestimating the amount of work required to convert data from the screen to a printed page. – Mike U Nov 25 '15 at 21:26
  • I'm marking your answer as 'the' answer, since you explained why what I wanted doesn't really exist (at least not for free). However, I was able to find a tool that'd at least let me print TIF and PDF documents. I used that tool already but didn't previously know about the print functionality, only your text about the need of a renderer made me realize that the tool should have that ability, and it did. The tool is XtremeDocumentStudio by Gnostice and it's a commerical application. – Steffen Winkler Nov 26 '15 at 13:55