I've been attempting to find an easy solution to exporting a Canvas in my WPF Application to a PDF Document.
So far, the best solution has been to use the PrintDialog and set it up to automatically use the Microsoft Print the PDF 'printer'. The only problem I have had with this is that although the PrintDialog is skipped, there is a FileDialog to choose where the file should be saved.
Sadly, this is a deal-breaker because I would like to run this over a large number of canvases with automatically generated PDF names (well, programitically provided anyway).
Other solutions I have looked at include:
Using PrintDocument, but from my experimentation I would have to manually iterate through all my Canveses children and manually invoke the correct Draw method (of which a lot of my custom elements with transformation would be rather time consuming to do)
Exporting as a PNG image and then embedding that in a PDF. Although this works, TextBlocks within my canvas are no longer text. So this isn't an ideal situation.
Using the 3rd party library PDFSharp has the same downfall as the PrintDocument. A lot of custom logic for each element.
With PDFSharp. I did find a method fir generating the XGraphics from a Canvas but no way of then consuming that object to make a PDF Page
So does anybody know how I can skip or automate the PDF PrintDialog, or consume PDFSharp XGraphics to make
A page. Or any other ideas for directions to take this besides writing a whole library to convert each of my Canvas elements to PDF elements.
If you look at the output port of a recent windows installation of Microsoft Print To PDF
You may note it is set to PORTPROMP: and that is exactly what causes the request for a filename.
You might note lower down, I have several ports set to a filename, and the fourth one down is called "My Print to PDF"
So very last century methodology; when I print with a duplicate printer but give it a different name I can use different page ratios etc., without altering the built in standard one. The output for a file will naturally be built:-
A) Exactly in one repeatable location, that I can file monitor and rename it, based on the source calling the print sequence, such that if it is my current default printer I can right click files to print to a known \folder\file.pdf
B) The same port can be used via certain /pt (printto) command combinations to output, not just to that default port location, but to a given folder\name such as
"%ProgramFiles%\Windows NT\Accessories\WORDPAD.EXE" /pt listIN.doc "My Print to PDF" "My Print to PDF" "listOUT.pdf"
Other drivers usually charge for the convenience of WPF programmable renaming, but I will leave you that PrintVisual challenge for another of your three wishes.
MS suggest XPS is best But then they would be promoting it as a PDF competitor.
It does not need to be Doc[X]2PDF it could be [O]XPS2PDF or aPNG2PDF or many pages TIFF2PDF etc. etc. Any of those are Native to Win 10 also other 3rd party apps such as [Free]Office with a PrintTo verb will do XLS[X]2PDF. Imagination becomes pagination.
I had a great success in generating PDFs using PDFSharp in combination with SkiaSharp (for more advanced graphics).
Let me begin from the very end:
you save the PdfDocument object in the following way:
PdfDocument yourDocument = ...;
string filename = #"your\file\path\document.pdf"
yourDocument.Save(filename);
creating the PdfDocument with a page can be achieved the following way (adjust the parameters to fit your needs):
PdfDocument yourDocument = new PdfDocument();
yourDocument.PageLayout = PdfPageLayout.SinglePage;
yourDocument.Info.Title = "Your document title";
PdfPage yourPage = yourDocument.AddPage();
yourDocument.Orientation = PageOrientation.Landscape;
yourDocument.Size = PageSize.A4;
the PdfPage object's content (as an example I'm putting a string and an image) is filled in the following way:
using (XGraphics gfx = XGraphics.FromPdfPage(yourPage))
{
XFont yourFont = new XFont("Helvetica", 20, XFontStyle.Bold);
gfx.DrawString(
"Your string in the page",
yourFont,
XBrushes.Black,
new XRect(0, XUnit.FromMillimeter(10), page.Width, yourFont.GetHeight()),
XStringFormats.Center);
using (Stream s = new FileStream(#"path\to\your\image.png", FileMode.Open))
{
XImage image = XImage.FromStream(s);
var imageRect = new XRect()
{
Location = new XPoint() { X = XUnit.FromMillimeter(42), Y = XUnit.FromMillimeter(42) },
Size = new XSize() { Width = XUnit.FromMillimeter(42), Height = XUnit.FromMillimeter(42.0 * image.PixelHeight / image.PixelWidth) }
};
gfx.DrawImage(image, imageRect);
}
}
Of course, the font objects can be created as static members of your class.
And this is, in short to answer your question, how you consume the XGraphics object to create a PDF page.
Let me know if you need more assistance.
Related
I am using iTextSharp (5.5.5.90) to generate PDF files. I am using paragraphs and importing pages from readers and such. Here is how I create my document, from there I just append what I need:
FileStream fs = new FileStream("filename.pdf", FileMode.Create, FileAccess.Write, FileShare.None);
Document doc = new Document(new Rectangle(PageSize.LETTER), 58, 58, 100, 50);
PdfWriter writer = PdfWriter.GetInstance(doc, fs);
Once the file is created, I add paragraphs like this:
doc.Add(new Paragraph("Paragraph text"));
And import pages from readers like this:
writer.DirectContent.AddTemplate(writer.GetImportedPage(reader, page), 0, 0);
My question is how would I go back to page one after generating the entire document and add an element to page one? I will be adding a barcode (I know how to add barcodes, tables and such where I want them on the current page), but I don't know how to "go back" to page one to add an element.
Here is the full code, but you won't be able to compile it because of dependencies. Also, don't get caught up in the details of the full code, as this is a large project to create dynamically generated documents. https://pastebin.com/kABi7fzW
I can't attest as to the exactly calls to iTextSharp's as we approached our documentation quite differently; open a Word template, at the data as DataTables, etc., do a MailMerge, close and reopen and save as PDF. Sounds more involved but doesn't require the granular level of detail you're doing of creating the document paragraph by paragraph but it does allow the document generator to worry about content and not style placement (handled via Word, manually and external to the application).
From experience with iTextSharp, you'll have a lot of trouble trying to float an element on top of a section to insert the barcode. The document generation tool has an annoying tendency to not quite work in this scenario. We endured many weeks of back and forth with iTextSharp support and a version upgrade and still couldn't get it to behave properly in all scenarios.
As discussed in the comments and given how you've already written your code (I doubt you'll scrap all that code and start with a MailMerge unless you really, really have to), you'll need to insert a placeholder block that you can locate via iTextSharp's PdfBuilder api. I'd imagine that setting a bookmark location would likely be the easiest way.
If it's possible (preferable?) to have the barcode on a page of its own, then you already have the code needed to do this (circa line 324 in your pastebin link) with;
// create doc...
// reopen doc and get page count
doc.NewPage();
// add barcode with page count + 1
// save
After struggling whole day, I identified the issue but this didn't solve my problem.
On short:
I need to open a PDF, convert to BW (grayscale), search some words and insert some notes nearby found words. At a first look it seems easy but I discovered how hard PDF files are processed (having no "words" concepts and so on).
Now the first task, converting to grayscale just drove me crazy. I didn't find a working solution either commercial or free. I came up with this solution:
open the PDF
print with windows drivers, some free PDF printers
This is quite ugly since I will force the C# users to install such 3'rd party SW but.. that is fpr the moment. I tested FreePDF, CutePDF and PDFCreator. All of them are working "stand alone" as expected.
Now when I tried to print from C#, obviously, I don't want the print dialog, just select BW option and print (aka. convert)
The following code just uses a PDF library, shown for clarity only.
Aspose.Pdf.Facades.PdfViewer viewer = new Aspose.Pdf.Facades.PdfViewer();
viewer.BindPdf(txtPDF.Text);
viewer.PrintAsGrayscale = true;
//viewer.RenderingOptions = new RenderingOptions { UseNewImagingEngine = true };
//Set attributes for printing
//viewer.AutoResize = true; //Print the file with adjusted size
//viewer.AutoRotate = true; //Print the file with adjusted rotation
viewer.PrintPageDialog = true; //Do not produce the page number dialog when printing
////PrinterJob printJob = PrinterJob.getPrinterJob();
//Create objects for printer and page settings and PrintDocument
System.Drawing.Printing.PrinterSettings ps = new System.Drawing.Printing.PrinterSettings();
System.Drawing.Printing.PageSettings pgs = new System.Drawing.Printing.PageSettings();
//System.Drawing.Printing.PrintDocument prtdoc = new System.Drawing.Printing.PrintDocument();
//prtdoc.PrinterSettings = ps;
//Set printer name
//ps.PrinterName = prtdoc.PrinterSettings.PrinterName;
ps.PrinterName = "CutePDF Writer";
ps.PrintToFile = true;
ps.PrintFileName = #"test.pdf";
//
//ps.
//Set PageSize (if required)
//pgs.PaperSize = new System.Drawing.Printing.PaperSize("A4", 827, 1169);
//Set PageMargins (if required)
//pgs.Margins = new System.Drawing.Printing.Margins(0, 0, 0, 0);
//Print document using printer and page settings
viewer.PrintDocumentWithSettings(ps);
//viewer.PrintDocument();
//Close the PDF file after priting
What I discovered and seems to be little explained, is that if you select
ps.PrintToFile = true;
no matter C# PDF library or PDF printer driver, Windows will just skip the PDF drivers and instead of PDF files will output PS (postscript) ones which obviously, will not be recognized by Adobe Reader.
Now the question (and I am positive that others who may want to print PDFs from C# may be encountered) is how to print to CutePDF for example and still suppress any filename dialog?
In other words, just print silently with programmatically selected filename from C# application. Or somehow convince "print to file" to go through PDF driver, not Windows default PS driver.
Thanks very much for any hints.
I solved conversion to grayscale with a commercial component with this post and I also posted there my complete solution, in care anyone will struggle like me.
Converting PDF to Grayscale pdf using ABC PDF
I have a problem when I'm working with image PDF files (PDF file with image only, no text) There are two PDF files img1, img2 and I want to combine two of them into one A4 page PDF file.
I have tried below code.
string Img1 = "C:/temp/image1.pdf";
string Img2 = "C:/temp/image2.pdf";
string MergedFile = "C:/temp/Combo.pdf";
//Create our PDF readers
PdfReader r1 = new PdfReader(Img1);
PdfReader r2 = new PdfReader(Img2);
//Our new page size, an A3 in landscape mode
iTextSharp.text.Rectangle NewPageSize = PageSize.A3.Rotate();
using (FileStream fs = new FileStream(MergedFile, FileMode.Create,
FileAccess.Write, FileShare.None))
{
//Create our document without margins
using (Document doc = new Document(NewPageSize, 0, 0, 0, 0))
{
using (PdfWriter w = PdfWriter.GetInstance(doc, fs))
{
doc.Open();
//Get our imported pages
PdfImportedPage imp1 = w.GetImportedPage(r1, 1);
PdfImportedPage imp2 = w.GetImportedPage(r2, 1);
//Add them to our merged document at specific X/Y coords
**w.DirectContent.AddTemplate(imp1, 0, 0);
w.DirectContent.AddTemplate(imp2, 0, -350);**
doc.Close();
}
}
}
r1.Close();
r2.Close();
So when i execute above code, because i have mentioned the y coord , it will combine pdf and two images will be on one page only.
BUt i don't want to do that
Here i am just giving example of two images,but in actual there are more than 20 images (converted into PDFs).
So depending on the image size, it should combine files. i can not give fix y coord for each n every file
Can anyone please help me to combine multiple PDF into single with no blank space..?
Structurally, here is what you want to do:
Allocate a new page of the "right" size
Merge the content streams of the pages
Merge the resources of the pages
Adjust all the annotations (if any)
The first step is easy, the rest, the second is easy, the third not so much (and will have the side effect of complicating step 2). I'll let you know ahead of time that I lied to you about the order.
Merging the content streams will be straight forward. What you will want to do is a four step process (I'll inject here that I know PDF very well, but iTextSharp not too well):
Insert a gsave operator (q)
Insert a transform operator (cm) to transform to the location where you want content to appear. In you case it will be 1 0 0 1 X Y cm
Copy the content streams from the current page
Insert a grestore operator (Q)
To merge the resources, you have to look at your newly created page's resources and for the current page do one of three things for each resource in each class of resource in a PDF page (XObject, Font, ColorSpace, ExtGState, Pattern, Shading, ProcSet - although for procset, you could set each procset to be the entire suite and do no harm):
If the resource exists in the newly created page, but under a different name, mark it as renamed.
If the resource does not exist in the newly created page and there is no resource with the same name, copy it in.
If the resource does not exist in the newly created page and there is a name conflict, rename the resource to a synthetic name not in the newly create page and copy it in.
Now to get back to my lie. In the resource merging, you will likely need a map built for the current page that maps old resource name to new resource name. When in the process of copying the content stream from one to the next, you will need to map all resource names referenced in the content stream to the new names built in the resource merge step.
To Adjust annotations, you will have to move them to their new location by adjusting the Rect property in each. You will also need to reset the /Parent property. For any of the text markup annotations, you will need to adjust the Quads.
Now, here is where the works will get gummed up in all of that. If a page is rotated, this will not work. If a page has a crop box, you will have to look at it and adjust the clipping region to simulate the crop. If the page is rotated and has Text annotations, this will need to attention to annotation flags to ensure that the aspect ratio is correct. If the document has link annotations on any of the pages with GoTo actions/destinations, you will need to adjust these.
I have a complex application producing PDFs via PDFSharp. I'm running into a problem which is proving very difficult to solve.
When rendering images (text is an image as well) rotated, the PDF produced looks fine, but when printed it has jagged edges and generally messed up -- see attachment.
Here is the relevant code:
// determine how big the image should be
double destinationWidth = Math.Round(pageWidth * imageInfo.WidthFactor);
double destinationHeight = destinationWidth;
// rescale the image to needed size
imageInfo.Image = ImageHelper.ResizeImage(imageInfo.Image, (int)(destinationWidth * 3), (int)(destinationHeight * 3));
// get image
XImage xImage = XImage.FromGdiPlusImage(imageInfo.Image);
// define fill area
XRect destination = new XRect();
destination.X = imageInfo.XFactor * pageWidth;
destination.Y = imageInfo.YFactor * pageHeight;
destination.Width = destinationWidth; //pageWidth * imageInfo.WidthFactor;
destination.Height = destinationHeight; //destination.Width; // shouldn't this use the page height and height factor?
// save state before rotate
XGraphicsState previousState = gfx.Save();
// rotate canvas
gfx.RotateAtTransform(imageInfo.RotationAngle, new XPoint(destination.X + destination.Width / 2, destination.Y + destination.Height / 2));
// render image
gfx.DrawImage(xImage, destination);
// undo transforms
gfx.Restore(previousState);
Please, please, help. It prints fine from Chrome's PDF viewer, for what it's worth.
I attempted converting the images to SVG (pixel by pixel) and rendering, which worked fine, but performance made it not feasible. I need to find a more elegant solution.
Thanks so much!
PDF:
https://dl.dropbox.com/u/49564994/PDF.pdf
Print-out:
https://dl.dropbox.com/u/49564994/Print.jpg
Almost two years ago I had a similar problem. A generated PDF was all garbled up when I printed it. It was just a report, did not contain any images, but several sentences or words were missing.
I used a Word template, replaced some placeholders to generate a report and then saved the Word document to PDF using the Office Save As PDF add-in.
There is a difference when you print the PDF with a PCL printer driver or a PostScript one. Check out if you get any difference between those. Might be a font problem. Check that the font encoding is set correctly.
At the time I did not find a solution. Finally resorted to converting the PDF to an image and sending that to the printer. Worked fine.
This should also be possible using PDFSharp by invoking GhostScript to create images from PDF pages.
I have a ready made PDF, and I would need to modify the trimbox, bleedbox with SetBoxSize and use the setPDFXConformance. Is there a way to do this?
I've tried with stamper.Writer, but it doesn't care about what I set there
2011.02.01.
We've tested it with Acrobat Pro, and it said that the trimbox was not defined. It seems the the stamper's writer's methods/properties don't effect the resulting pdf. Here are the source and result files: http://stemaweb.hu/pdfs.zip
my code:
PdfReader reader = new PdfReader(#"c:\source.pdf");
PdfStamper stamper = new PdfStamper(reader, new FileStream(#"c:\result.pdf", FileMode.Create));
stamper.Writer.SetPageSize(PageSize.A4);
stamper.Writer.PDFXConformance = PdfWriter.PDFX32002;
stamper.Writer.SetBoxSize("trim", new iTextSharp.text.Rectangle(20, 20, 100, 100));
PdfContentByte cb = stamper.GetOverContent(1);
/*drawing*/
stamper.Close();
Because the boxes are not visible, I tried to modify the pagesize with the writer but that didn't do anything either.
SetPDFXConformance won't turn a "normal" PDF into a PDF/X pdf. SetPDFXConformance is really just for document generation, causing iText to throw an exception if you do something blatantly off spec.
"it doesn't care about what I set there". Trim and bleed boxes are not something you can see visually in Reader. How are you testing for them?
Could you post some code, and a link to your output PDF?
Ah. You're using stamper.Writer. In this case, that doesn't work out so well. All the page level, Well Supported Actions via PdfStamper will take a page number or page's PdfDictionary as an argument. SetBoxSize just takes a string & a rectangle, so that's youre clue.
Going "under the hood" as you are is actually defaulting back to PdfWriter.setBoxSize... which is only for creating PDFs, not modifying an existing page.
So: You need to use the low-level PDF objects make the changes you want. No Problemo:
for (int i = 1; i <= myReader.getNumberOfPages(); ++i) {
PdfDictionary pageDict = myREADER_YES_READER.getPageN(i);
PdfRectangle newBox = new PdfRectangle( 20, 20, 100, 100 );
pageDict.put(PdfName.TRIMBOX, newBox);
newBox = new PdfRectangle( PageSize.A4 );
pageDict.put(PdfName.MEDIABOX, newBox );
}
/* drawing */
stamper.close();
As to the PDFX32002 conformance, I think you're going to have to go code diving to figure out exactly what is needed. Writer.PDFXConformance is another aspect of Writer that only works when generating a PDF, not modifying an existing one.
The good news is that PdfXConformanceImp is a public class. The bad news is that its only used internally by PdfWriter and PdfContentByte... hey. You are getting some changes in behavior with your present code (just not enough). Specifically, if you try something that isn't allowed within that PdfContentByte, you'll get a PdfXConformanceException with message describing the restriction you've violated. Trying to add an optional content group (layer) would throw for example.
Ah. That's not so bad. MAYBE. Try this:
PDFXConformanceImp pdfx = new PDFXConformanceImp();
pdfx.setConformance(PdfWriter.PDFX32002);
pdfx.commpleteInfoDictionary(stamper.Writer.getInfo());
pdfx.completeExtraCatalog(stamper.Writer.getExtraCatalog());
stamper.close();
If you drop stamper.Writer.PDFXConformance = PdfWriter.PDFX32002;, you won't get exceptions when you do something Forbidden in your contentByte. Other than that, I don't think it'll matter.
Hmm.. That's not the whole solution. The OutputIntents from the extraCatalog are merged into the main catalog as well. Perhaps this will work:
//replace the completeExtraCatalog call above with this
pdfx.completeExtraCatalog(myReader.getCatalog());
I wish you luck.