XPS Print Quality C# vs. XPS viewer - c#

I'm having a somewhat odd print quality problem in my C# application. I have an XPS file (it's basically just a 1 page image, that was originally a scanned black and white image) that I'm trying to print to an IBM InfoPrint Mainframe driver via a C# application. I've printed to numerous other print drivers and never had a problem, but this driver gives me terrible quality with the AFP file it creates. If I open the same file in the Microsoft XPS viewer application and print to the same driver, the quality looks fine.
Trying to work though the problem I've tried 3 or 4 different approaches to printing in the C# app. The original code did something like this (trimmed for brevity):
System.Windows.Xps.XpsDocumentWriter writer = PrintQueue.CreateXpsDocumentWriter(mPrintQueue);
mCollator = writer.CreateVisualsCollator();
mCollator.BeginBatchWrite();
ContainerVisual v = getContainerVisual(xpsFilePath);
//tried all sorts of different options on the print ticket, no effect
mCollator.Write(v,mDefaultTicket);
That code (which I've truncated) certainly could have had some weird issues in it, so I tried something much simpler:
LocalPrintServer localPrintServer = new LocalPrintServer();
PrintQueue defaultPrintQueue = LocalPrintServer.GetDefaultPrintQueue();
PrintSystemJobInfo xpsPrintJob = defaultPrintQueue.AddJob("title", xpsDocPath, false);
Same results.
I even tried using the WCF print dialog, same poor quality (http://msdn.microsoft.com/en-us/library/ms742418.aspx).
One area I haven't tried yet, is using the old-school underlying print API's, but I'm not sure why that would behave differently. One other option I have, is my original document is a PDF, and I have a good 3rd party library that can make me an EMF file instead. However, every time I try to stream that EMF file to my printer, I get garbled text.
Any ideas on why this quality is lost, how to fix, or how to stream an EMF file to a print driver, would be much appreciated!
UPDATE:
One other note. This nice sample app: http://wrb.home.xs4all.nl/Articles_2010/Article_XPSViewer_01.htm experiences the same quality loss. I've also now performed tests where I open the PDF directly and render the Bitmaps to a Print Document, same fuzziness of the resulting images. If I open the PDFs in Acrobat and print they look fine.

So to close this issue, it seems that the IBM Infoprint driver (at least the way it's being used here) has quite different quality depending on how you print in C#.
In this question I was using:
System.Windows.Documents.Serialization.Write(Visual, PrintTicket);
I completely changed my approach, removing XPS entirely, and obtained an emf (windows metafile) rendition of my document, then sent that emf file to the Windows printer using the windows print event handler:
using (PrintDocument pd = new PrintDocument())
{
pd.DocumentName = this.mJobName;
pd.PrinterSettings.PrinterName = this.mPrinterName;
pd.PrintController = new StandardPrintController();
pd.PrintPage += new PrintPageEventHandler(DoPrintPage);
pd.Print();
}
(I've obviously omitted a lot of code here, but you can find examples of how to use this approach relatively easily)
In my testing, most print drivers were equally happy with either printing approach, but the IBM Infoprint driver was EXTREMELY sensitive to the quality. One possible explanation is that the Infoprint printer was required to be configured with a weird fixed DPI and it may be doing a relatively poor job converting.
EDIT: More detailed sample code was requested, so here ya go. Note that getting an EMF file is a pre-req for this approach. In this case I'm using ABC PDF, which lets you generate an EMF file from your PDF with a relatively simple call.
class AbcPrintEmf
{
private Doc mDoc;
private string mJobName;
private string mPrinterName;
private string mTempFilePath;
private bool mRenderTextAsPolygon;
public AbcPdfPrinterApproach(Doc printMe, string jobName, string printerName, bool debug, string tempFilePath, bool renderTextAsPolygon)
{
mDoc = printMe;
mDoc.PageNumber = 1;
mJobName = jobName;
mPrinterName = printerName;
mRenderTextAsPolygon = renderTextAsPolygon;
if (debug)
mTempFilePath = tempFilePath;
}
public void print()
{
using (PrintDocument pd = new PrintDocument())
{
pd.DocumentName = this.mJobName;
pd.PrinterSettings.PrinterName = this.mPrinterName;
pd.PrintController = new StandardPrintController();
pd.PrintPage += new PrintPageEventHandler(DoPrintPage);
pd.Print();
}
}
private void DoPrintPage(object sender, PrintPageEventArgs e)
{
using (Graphics g = e.Graphics)
{
if (mDoc.PageCount == 0) return;
if (mDoc.Page == 0) return;
XRect cropBox = mDoc.CropBox;
double srcWidth = (cropBox.Width / 72) * 100;
double srcHeight = (cropBox.Height / 72) * 100;
double pageWidth = e.PageBounds.Width;
double pageHeight = e.PageBounds.Height;
double marginX = e.PageSettings.HardMarginX;
double marginY = e.PageSettings.HardMarginY;
double dstWidth = pageWidth - (marginX * 2);
double dstHeight = pageHeight - (marginY * 2);
// if source bigger than destination then scale
if ((srcWidth > dstWidth) || (srcHeight > dstHeight))
{
double sx = dstWidth / srcWidth;
double sy = dstHeight / srcHeight;
double s = Math.Min(sx, sy);
srcWidth *= s;
srcHeight *= s;
}
// now center
double x = (pageWidth - srcWidth) / 2;
double y = (pageHeight - srcHeight) / 2;
// save state
RectangleF theRect = new RectangleF((float)x, (float)y, (float)srcWidth, (float)srcHeight);
int theRez = e.PageSettings.PrinterResolution.X;
// draw content
mDoc.Rect.SetRect(cropBox);
mDoc.Rendering.DotsPerInch = theRez;
mDoc.Rendering.ColorSpace = "RGB";
mDoc.Rendering.BitsPerChannel = 8;
if (mRenderTextAsPolygon)
{
//i.e. render text as polygon (non default)
mDoc.SetInfo(0, "RenderTextAsText", "0");
}
byte[] theData = mDoc.Rendering.GetData(".emf");
if (mTempFilePath != null)
{
File.WriteAllBytes(mTempFilePath + #"\" + mDoc.PageNumber + ".emf", theData);
}
using (MemoryStream theStream = new MemoryStream(theData))
{
using (Metafile theEMF = new Metafile(theStream))
{
g.DrawImage(theEMF, theRect);
}
}
e.HasMorePages = mDoc.PageNumber < mDoc.PageCount;
if (!e.HasMorePages) return;
//increment to next page, corrupted PDF's have occasionally failed to increment
//which would otherwise put us in a spooling infinite loop, which is bad, so this check avoids it
int oldPageNumber = mDoc.PageNumber;
++mDoc.PageNumber;
int newPageNumber = mDoc.PageNumber;
if ((oldPageNumber + 1) != newPageNumber)
{
throw new Exception("PDF cannot be printed as it is corrupt, pageNumbers will not increment properly.");
}
}
}
}

Related

C# interop word, cut and past works on Office 2016 but not on Office 2019

I found similar questions but not exactly the same.
I have a word template which I fill by texts entered by users.
On the user interface, there is a text field and two signature fields (a 3rd party component which produces an image file).
If the text is not very long, it passes on two versions of word. But if the text is long and there is some enters, it doesn't work on Office 2019 and Office 365. On Office 2016, it works always.
To better explain,
I open the document :
Microsoft.Office.Interop.Word.Application app = null;
Microsoft.Office.Interop.Word.Document doc = null;
...
app = new Microsoft.Office.Interop.Word.Application();
doc = app.Documents.Open(tempPath);
app.Visible = false;
doc.Bookmarks["comment"].Select();
app.Selection.TypeText(orderComment); //Order comment is typed by the user
...
//this code saves the signature as a png image and it works in any case. The image exists in the folder before calling the rest of the code.
string clientSignaturePath = System.Configuration.ConfigurationManager.AppSettings["TempPath"] + Guid.NewGuid().ToString().Substring(0, 6) + ".png";
using (FileStream fs = new FileStream(clientSignaturePath, FileMode.Create))
{
using (BinaryWriter bw = new BinaryWriter(fs))
{
byte[] data = Convert.FromBase64String(model.ClientSignature);
bw.Write(data);
bw.Close();
}
fs.Close();
}
//If the orderComment is too long, it gives this error in this method when I call the line rng.Paste(); on Office 2019 and 365 but not on 2016.
error : this method or property is not available because the clipboard is empty or invalid
UserMethods.InsertImage(doc, clientSignaturePath, "client", 79, 175);
In the class UserMethods:
public static void InsertImage(Microsoft.Office.Interop.Word.Document doc, string imagePath, string type, float? imageHeight = null, float? imageWidth = null)
{
Range rng = null;
if (type == "tech")
rng = doc.Tables[7].Cell(1, 1).Range;
else if (type == "client")
rng = doc.Tables[7].Cell(1, 2).Range;
else
rng = doc.Tables[7].Cell(1, 3).Range;
Microsoft.Office.Interop.Word.InlineShape autoScaledInlineShape = rng.InlineShapes.AddPicture(imagePath);
float scaledWidth = imageWidth ?? autoScaledInlineShape.Width;
float scaledHeight = imageHeight ?? autoScaledInlineShape.Height;
autoScaledInlineShape.Delete();
// Create a new Shape and fill it with the picture
Microsoft.Office.Interop.Word.Shape newShape = doc.Shapes.AddShape(1, 0, 0, scaledWidth, scaledHeight);
newShape.Fill.UserPicture(imagePath);
// Convert the Shape to an InlineShape and optional disable Border
Microsoft.Office.Interop.Word.InlineShape finalInlineShape = newShape.ConvertToInlineShape();
//finalInlineShape.Line.Visible = Microsoft.Office.Core.MsoTriState.msoFalse;
// Cut the range of the InlineShape to clipboard
finalInlineShape.Range.Cut();
// And paste it to the target Range
rng.Paste();
}
My Version of Office which works in any case:
And the server's (Windows Server 2016) offic version which doesn't work in case of large text :
Thanks in advance.
The Cut method may cause security issues related to Clipboard access
try
rng.FormattedText = finalInlineShape.Range.FormattedText;
finalInlineShape.Delete();
and comment;
//finalInlineShape.Range.Cut();

Ghostscript.NET image text quality issue

I am attempting to convert a pdf document to images using ghostscript. The desired dpi is set to 72px which should be high enough for text to display clear but most of the text is illegible.
I can raise the dpi but that will cause very large image files which I would prefer not to have.
I know there are arguments for ghostscript to add anti aliasing etc (e.g. -dDOINTERPOLATE). How do I add them to the following piece of code, or is there a better way to do this?
int desired_x_dpi = 72;
int desired_y_dpi = 72;
GhostscriptRasterizer _rasterizer = new GhostscriptRasterizer();
_rasterizer.Open(inputPdfPath, localDllInfo, false);
for (int pageNumber = 1; pageNumber <= _rasterizer.PageCount; pageNumber++)
{
string pageFilePath = Path.Combine(outputPath, "Page-" + pageNumber.ToString() + ".png");
Image img = _rasterizer.GetPage(desired_x_dpi, desired_y_dpi, pageNumber);
img.Save(pageFilePath, ImageFormat.Png);
}
In 1.1.9 the GhostscriptRasterizer has -dDOINTERPOLATE set by default. The only parameters you can control via GhostscriptRasterizer class are TextAlphaBits and GraphicsAlphaBits.
I would recommend you to try to use other classes from the Ghostscript.NET if you want more control over the parameters.
Take a look at this samples: Image devices usage samples
You can add custom parameters (switches) this way:
GhostscriptPngDevice dev = new GhostscriptPngDevice(GhostscriptPngDeviceType.Png16m);
dev.GraphicsAlphaBits = GhostscriptImageDeviceAlphaBits.V_4;
dev.TextAlphaBits = GhostscriptImageDeviceAlphaBits.V_4;
dev.ResolutionXY = new GhostscriptImageDeviceResolution(96, 96);
dev.InputFiles.Add(#"E:\gss_test\indispensable.pdf");
dev.Pdf.FirstPage = 2;
dev.Pdf.LastPage = 4;
dev.CustomSwitches.Add("-dDOINTERPOLATE"); // custom parameter
dev.OutputPath = #"E:\gss_test\output\indispensable_color_page_%03d.png";
dev.Process();
When I catch some time, I will extend GhostscriptRasterizer to accept custom parameters in the Open method for the Ghostscript.NET v.1.2.0 release.
Got same problem. Fixed by adding CustomSwitches with resolution to GhostscriptRasterizer:
using (var rasterizer = new GhostscriptRasterizer())
{
rasterizer.CustomSwitches.Add("-r500x500");
...other code here
}

Blurry image when converting DOC to PNG

Ok I have a problem that just baffles me yet again. I have some code that turns a DOC file to a PNG file. When I do it on a localhost, the image is fine. When I take the same code and put it on live server, the image is extremely small (same size as the DOT file what I got the DOC file from, basically DOT gets filled out and turned into a DOC). Now... here's the crazy part. If I log into the hosting server as an admin and THEN go to the live website, the image is large and crisp, even if I go to the site from an iPhone. As soon as I log out of the hosting server and refresh the live page, image is tiny again. Here's the code I am using to convert DOC to PNG. On a side note, if I use method 2, I can make the image bigger and higher resolution, but fonts are out of place.
private void ConvertDocToPNG(string startupPath, string filename1)
{
var docPath = Path.Combine(startupPath, filename1);
Application app = new Application();
Microsoft.Office.Interop.Word.Document doc = new Microsoft.Office.Interop.Word.Document();
app.Visible = false;
doc = app.Documents.Open(docPath);
app.WindowState = Microsoft.Office.Interop.Word.WdWindowState.wdWindowStateMaximize;
app.ActiveWindow.ActivePane.View.Zoom.Percentage = 100;
doc.ShowGrammaticalErrors = false;
doc.ShowRevisions = false;
doc.ShowSpellingErrors = false;
//Opens the word document and fetch each page and converts to image
foreach (Microsoft.Office.Interop.Word.Window window in doc.Windows)
{
foreach (Microsoft.Office.Interop.Word.Pane pane in window.Panes)
{
for (var i = 1; i <= pane.Pages.Count; i++)
{
Microsoft.Office.Interop.Word.Page page = null;
bool populated = false;
while (!populated)
{
try
{
// This !##$ variable won't always be ready to spill its pages. If you step through
// the code, it will always work. If you just execute it, it will crash. So what
// I am doing is letting the code catch up a little by letting the thread sleep
// for a microsecond. The second time around, this variable should populate ok.
page = pane.Pages[i];
populated = true;
}
catch (COMException ex)
{
Thread.Sleep(1);
}
}
var bits = page.EnhMetaFileBits;
var target = Path.Combine(startupPath + "\\", string.Format("{1}_page_{0}", i, filename1.Split('.')[0]));
try
{
using (var ms = new MemoryStream((byte[])(bits)))
{
var image = System.Drawing.Image.FromStream(ms);
var pngTarget = Path.ChangeExtension(target, "png");
// Method 2
image.Save(pngTarget, System.Drawing.Imaging.ImageFormat.Png);
// Another way to save it using custom size
//float width = Convert.ToInt32(hfIdCardMaxWidth.Value);
//float height = Convert.ToInt32(hfIdCardMaxHeight.Value);
//float scale = Math.Min(width / image.Width, height / image.Height);
//int resizedWidth = (int)Math.Round(image.Width * scale);
//int resizedHeight = (int)Math.Round(image.Height * scale);
//Bitmap myBitmap = new Bitmap(image, new Size(resizedWidth, resizedHeight));
//myBitmap.Save(pngTarget, System.Drawing.Imaging.ImageFormat.Png);
}
}
catch (System.Exception ex)
{
doc.Close(true, Type.Missing, Type.Missing);
Marshal.ReleaseComObject(doc);
doc = null;
app.Quit(true, Type.Missing, Type.Missing);
Marshal.ReleaseComObject(app);
app = null;
throw ex;
}
}
}
}
doc.Close(true, Type.Missing, Type.Missing);
Marshal.ReleaseComObject(doc);
doc = null;
app.Quit(true, Type.Missing, Type.Missing);
Marshal.ReleaseComObject(app);
app = null;
}
Given that you are using the interop in an unattended fashion, all sorts of weird/unexpected things can happen. I will admit, I don't know why you are experiencing the symptoms you are given your test cases in different environments. I have a very strong feeling being unattended is the culprit. The interop runs in the user's login context, and if there is no user... well... yeah. So, how to get around this and still be unattended? The first that comes to mind is using the OpenXML SDK. This is the safe way of manipulating office documents in an unattended fashion. I use it for unattended report generation myself.
Assumptions:
Standard DOCX format
The doc contains words/pictures/styles/whatever. It's not just a sack of images (if it is, there are much easier ways to accomplish what you need)
The API:
http://www.microsoft.com/en-us/download/details.aspx?id=30425
But, you can't convert a doc to an image with OpenXML! I thought of a workaround, but this is NOT tested. The idea is to convert the doc to html, then render out the html and stuff it into an image.
Here is a way to convert your word doc to HTML using OpenXML:
The big set of power tools that can do all sorts of handy things:
http://powertools.codeplex.com/
The specific module that you will need: http://openxmldeveloper.org/blog/b/openxmldeveloper/archive/2014/01/30/transform-docx-to-html-css-with-high-fidelity-using-powertools-for-open-xml.aspx
Here is a handy library to render out the HTML and dump it into an image:
http://htmlrenderer.codeplex.com/
using (var ms = new MemoryStream((byte[])(bits)))
{
var emf = new Metafile(ms);
var scale = 400 / emf.HorizontalResolution;
var Width= emf.Width * scale;
var Height = emf.Height * scale;
System.Drawing.Bitmap b = new System.Drawing.Bitmap((Int32)Width, (Int32)Height);
var G = System.Drawing.Graphics.FromImage(b);
G.Clear(System.Drawing.Color.White);
G.DrawImage(emf, 0, 0, (float)Width, (float)Height);
b.Save(pngTarget, System.Drawing.Imaging.ImageFormat.Png);
}

C# PrintDocument creates invalid xps file depending on default printer?

My C# application prints some pages to a xps file, however i have discovered that if the default printer is a networked printer then the created xps file is invalid "The XPS viewer cannot open this document".
This confuses me since i'm not even writing to a networked printer.. but to a file.
If i don't have the default printer set to a networked printer (default printer is "send to OneNote" or "Microsoft XPS Document Writer"), then the bellow code correctly creates a XPS file with 2 pages when executed:
pageCounter = 0;
PrintDocument p = new PrintDocument();
p.PrintPage += delegate(object sender1, PrintPageEventArgs e1)
{
// 8.5 x 11 paper:
float x0 = 25;
float xEnd = 850 - x0;
float y0 = 25;
float yEnd = 1100 * 2 - y0; // bottom of 2ed page
Font TitleFont = new Font("Times New Roman", 30);
if (pageCounter == 0) // for the first page
{
e1.Graphics.DrawString("My Title", TitleFont, new SolidBrush(Color.Black), new RectangleF(300, 15, xEnd, yEnd));
e1.HasMorePages = true; // more pages
pageCounter++;// next page counter
}
else // the second page
{
e1.Graphics.DrawString("Page 2", TitleFont, new SolidBrush(Color.Black), new RectangleF(300, 15, xEnd, yEnd));
}
};
// now try to print
try
{
p.PrinterSettings.PrintFileName = fileName; // the file name set earlier
p.PrinterSettings.PrintToFile = true; // print to a file (i thought this would ignore the default printer)
p.Print();
}
catch (Exception ex)
{
// for the Bug I have described, this Exception doesn't happen.
// it creates an XPS file, but the file is invalid in the cases mentioned
MessageBox.Show("Error", "Printing Error", MessageBoxButton.OK);
}
so my question is... why does this happen, what am i doing wrong?
Well, there's no specific question here, but I'll tell you what I know. You are using the default printer's driver to generate the output document that is being saved to a file. Some drivers output xps content that is then consumed by the printer to put ink/toner on the page. Other drivers output postscript, PCL, PDF, or some other data format. So, depending on the default printer, you could be saving data in any one of these formats.
To ensure that you actually produce XPS content, you would need to specify the "Microsoft XPS Document Writer" as the printer to use in p.PrinterSettings.PrinterName. Of course, this could fail if that print queue has been renamed or removed. You could jump through some hoops with PrinterSettings.InstalledPrinters to try and determine which queue is the XPS Document Writer, but again, this will fail if the printer has been removed. A more robust solution would be to generate the XPS content directly with an XpsDocumentWriter, however that would require some substantial changes.

Excel, Word, PDFs AND PowerPoint print dialogues

Anybody know how to get all of these in a C# app that leads the user to a print dialogue screen?
What I mean is this:
In the gui the user selects a document, right clicks on the doc, chooses Print. Instead of immediately printing out the doc, the print dialogue comes up.
Anybody know the syntax for all of this? I tried using all of the interops for the MS Office apps and trying the printdialogue method...but no such luck.
Can anybody provide me with some code? I've seriously been at this for HOURS and can't come up with a thing.
Maybe see this previous Q&A:
send-document-to-printer-with-c#
Or, for a WPF app that prints a FlowDocument :
private void DoThePrint(System.Windows.Documents.FlowDocument document)
{
// Clone the source document's content into a new FlowDocument.
// This is because the pagination for the printer needs to be
// done differently than the pagination for the displayed page.
// We print the copy, rather that the original FlowDocument.
System.IO.MemoryStream s = new System.IO.MemoryStream();
TextRange source = new TextRange(document.ContentStart, document.ContentEnd);
source.Save(s, DataFormats.Xaml);
FlowDocument copy = new FlowDocument();
TextRange dest = new TextRange(copy.ContentStart, copy.ContentEnd);
dest.Load(s, DataFormats.Xaml);
// Create a XpsDocumentWriter object, implicitly opening a Windows common print dialog,
// and allowing the user to select a printer.
// get information about the dimensions of the seleted printer+media.
System.Printing.PrintDocumentImageableArea ia = null;
System.Windows.Xps.XpsDocumentWriter docWriter = System.Printing.PrintQueue.CreateXpsDocumentWriter(ref ia);
if (docWriter != null && ia != null)
{
DocumentPaginator paginator = ((IDocumentPaginatorSource)copy).DocumentPaginator;
// Change the PageSize and PagePadding for the document to match the CanvasSize for the printer device.
paginator.PageSize = new Size(ia.MediaSizeWidth, ia.MediaSizeHeight);
Thickness t = new Thickness(72); // copy.PagePadding;
copy.PagePadding = new Thickness(
Math.Max(ia.OriginWidth, t.Left),
Math.Max(ia.OriginHeight, t.Top),
Math.Max(ia.MediaSizeWidth - (ia.OriginWidth + ia.ExtentWidth), t.Right),
Math.Max(ia.MediaSizeHeight - (ia.OriginHeight + ia.ExtentHeight), t.Bottom));
copy.ColumnWidth = double.PositiveInfinity;
//copy.PageWidth = 528; // allow the page to be the natural with of the output device
// Send content to the printer.
docWriter.Write(paginator);
}
}

Categories

Resources