I'm trying to render an HTML document as PDF using wkhtmltopdf.exe, which I call from a C# web application.
The HTML document needs to have both a footer and header that recur on every page, which is possible with wkhtmltopdf by specifying --header-html <a path> as an argument.
However, the footer is rendered dynamically from a Razor view and I would rather not have to store it in a temporary file on disk and use that path, but I want to use the rendered HTML that's already in memory. That's possible for the document itself, by writing to the StandardInput stream, like so:
var wkhtml = ConfigurationManager.AppSettings["WkHtmlToPdfPath"];
var p = new Process();
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = wkhtml;
p.StartInfo.Arguments = "-q -n --disable-smart-shrinking - -";
p.Start();
var stdin = p.StandardInput;
stdin.AutoFlush = true;
stdin.Write(template);
stdin.Dispose();
Is it possible to do the same thing for the header and footer HTML, namely to pass it in inline without having to resort to temporary files?
I've tried:
stdin.Write(string.Format("--footer-html {0} ", footer));
But of course, it just treats that as part of the document, not a footer.
The main reason I wanna render the footer and header dynamically as well is (mostly) caused by another issue. While it would be nice to have a dynamic header and footer, it's mostly to solve the problem that I have to link to images with an absolute path (ie: C:\templates\images\logo.png) because relative paths (ie: images/logo.png) don't work when you use stdin and just pass in a string blob of HTML, so I need to insert the absolute path through Razor at runtime.
For this issue, I've tried setting the working directory of the process to match the relative paths, but to no avail:
p.StartInfo.WorkingDirectory = #"C:\templates";
If I could solve that problem, that would resolve 90% of the issue as well.
Note sure if you solved this JulianR, and I will also assume you are in MVC(?)
If not you can ignore some of the initial code below, but I had a similar situation whereby I needed to stream output directly to wkhtmltopdf due to secure and logged in sections of a site.
Firstly in a controller you can pull in the View you need for the display with any applicable master page (that itself may use headers and footers):
var view = ViewEngines.Engines.FindView(ControllerContext, myViewName, myMasterPageLayout);
you then get a current of this view with any necessary ViewData, Tempdata, etc. and store this in a string (content below):
string content;
ViewData.Model = model;
using (var writer = new System.IO.StringWriter())
{
var context = new ViewContext(ControllerContext, view.View, ViewData, TempData, writer);
view.View.Render(context, writer);
writer.Flush();
content = writer.ToString();
writer.Close();
}
at this stage you could actually modify your output html in the string if needed - e.g. to change any local paths to full paths
With you output HTML you then just pass into wkhtmltopdf:
var p = new Process();
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.UseShellExecute = false;
//Other parameters as required
byte[] file;
try
{
p.Start();
byte[] buffer = new byte[32768];
using (System.IO.StreamWriter stdin = p.StandardInput)
{
stdin.AutoFlush = true;
stdin.Write(content);
}
using (MemoryStream ms = new MemoryStream())
{
ms.Position = 0;
p.StandardOutput.BaseStream.CopyTo(ms);
file = ms.ToArray();
}
p.StandardOutput.Close();
// wait or exit
p.WaitForExit(60000);
// read the exit code, close process
int returnCode = p.ExitCode;
}
you then have a byte array that contains your PDF content of the whole page.
Related
I am attempting to auto-fill/sign/print the federal i-9 form using Spire.PDF and C#. The i9 file is an XFA form and is protected and doesn't allow for signing. However, if I fill the i9 and print to PDF, then I can sign that new file.
The step I'm getting stuck on is printing the filled i9 to a PDF file without actually opening Acrobat or having direct interaction from the end-user to specify a file name. I say 'printing' because if I just save it as a PDF file it never flattens the XFA form and remains locked against signing.
So far I have automated printing of the file using this code:
Process proc = new Process();
proc.StartInfo.Verb = "PrintTo";
proc.StartInfo.FileName = filename;
proc.StartInfo.Arguments = "\"" + printername + "\"";
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.CreateNoWindow = true;
proc.Start();
and I think I might be able to force use of the Microsoft Print to PDF 'printer' here, but I don't know if there's a way to specify the file name to use so that the user isn't prompted?
If I try printing using the Spire.PDF control, I am only able to get a file with the "Please wait...
If this message is not eventually replaced by the proper contents of the document, your PDF
viewer may not be able to display this type of document.." message as a result.
When the form is opened to print via Acrobat I get a popup of "This form contains incomplete or invalid information. Are you sure you want to print?" If I click Yes then I can successfully print to PDF and then I can sign that file.
So, I believe whatever data-checking is happening is causing the failure to print via code and I'm hoping those wiser than I might have some ideas of ways around this issue.
Thank you in advance for your help! If you just search for Federal i9 you should find the file I'm working with. I didn't see a space to attach a file here.
This is the code that I'm using to try to accomplish my task via the Spire.PDF control.
PdfDocument doc = new PdfDocument();
string i9path = "locationofi9file"
string newi9path = "locationoffilledi9file"
doc.LoadFromFile(i9path);
/*fill form here*/
doc.Form.IsFlatten = true;
doc.SaveToFile(newi9path, FileFormat.PDF);
doc.Close();
doc.Dispose();
doc.LoadFromFile(newi9path);
string file = "printi9";
if (System.IO.File.Exists(file))
System.IO.File.Delete(file);
string directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
PrinterSettings settings = new PrinterSettings();
PageSettings pages = new PageSettings();
string printername = "Microsoft Print to PDF";
settings.PrinterName = printername;
settings.PrintToFile = true;
settings.PrintFileName = Path.Combine(directory, file + ".pdf");
PrintDocument printDoc = doc.PrintDocument;
printDoc.PrinterSettings = settings;
printDoc.Print();
doc.Close();
doc.Dispose();
I'm trying to make a program that send pdf file to a thermal printer. The problem is that after the file has been sent to printer, the default PDF reader (foxit reader in this case) is always open up and unmanageable. And yes, I already spent days to search but nothing completely works for me.
Also, I have tried to turn off "View PDF Result" property on Foxit Reader PDF Printer but it seems does not work as well.
Here is my simple code:
System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo(#"D:\test.pdf");
info.Arguments = "\"XP-58\"";
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
info.UseShellExecute = true;
info.Verb = "PrintTo";
PrintProcess p = new PrintProcess();
p.StartInfo = info;
p.EnableRaisingEvents = true;
p.Start();
p.WaitForInputIdle(1500);
p.Stop();
Now I can completely close the pdf reader (foxit reader) or whatever using this way. But, still looking for another can be do this task silently without open then close it.
private static bool KillAdobe(string name)
{
foreach (Process clsProcess in Process.GetProcesses().Where(
clsProcess => clsProcess.ProcessName.StartsWith(name)))
{
clsProcess.Kill();
return true;
}
return false;
}
I have been trying to get my MVC application te create pdf files based on MVC Views. I got this working with plain html. But i would also like to iclude my css files that i use for the browser. Now some of them work but with one i get the following error:
An exception of type 'System.FormatException' occurred in mscorlib.dll but was not handled in user code
Additional information: Input string was not in a correct format.
I am using the following code:
var data = GetHtml(new IndexModel(Context), "~\\Views\\Home\\Index.cshtml", "");
using (var document = new iTextSharp.text.Document())
{
//define output control HTML
var memStream = new MemoryStream();
TextReader xmlString = new StringReader(data);
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream("c:\\tmp\\my.pdf", FileMode.OpenOrCreate));
//open doc
document.Open();
// register all fonts in current computer
FontFactory.RegisterDirectories();
// Set factories
var htmlContext = new HtmlPipelineContext(null);
htmlContext.SetTagFactory(Tags.GetHtmlTagProcessorFactory());
// Set css
ICSSResolver cssResolver = XMLWorkerHelper.GetInstance().GetDefaultCssResolver(false);
cssResolver.AddCssFile(HttpContext.Server.MapPath("~/Content/elements.css"), true);
cssResolver.AddCssFile(HttpContext.Server.MapPath("~/Content/style.css"), true);
cssResolver.AddCssFile(HttpContext.Server.MapPath("~/Content/jquery-ui.css"), true);
// Export
IPipeline pipeline = new CssResolverPipeline(cssResolver, new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer)));
var worker = new XMLWorker(pipeline, true);
var xmlParse = new XMLParser(true, worker);
xmlParse.Parse(xmlString);
xmlParse.Flush();
document.Close();
}
the string "data" is correct and has no issues, the problem lies with the AddCssFile().
If i create the pdf without and css files everything works, but including the css files triggers the error.
Help will be very much appreciated.
I don't know the exact answer, but by looking at the error you are getting back, I would try two different approaches.
Move the
cssResolver.AddCssFile(HttpContext.Server.MapPath("~/Content/elements.css"), true);
To something like
var cssPath = HttpContext.Server.MapPath("~/Content/elements.css"), true);
cssResolver.AddCssFile(cssPath);
Then set a breakpoint and look at the values being returned for cssPath. Make sure they are accurate and do not contain any odd characters.
Second approach... If all else fails, try giving an absolute URL to the CSS resource such as http://yourdomain.com/cssPath instead of a file system path.
If either of those two appraoches help you, then you can use it to determine the actual problem and then refactor it to your hearts content after that.
UPDATE ------------------------------------------------------------------>
According to the documentation, you need an absolute URL for the file, so Server.MapPath won't work.
addCssFile
void addCssFile(String href,
boolean isPersistent)
throws CssResolverException
Add a
Parameters:
href - the link to the css file ( an absolute uri )
isPersistent - true if the added css should not be deleted on a call to clear
Throws:
CssResolverException - thrown if something goes wrong
In that case, I would try using something like :
public string AbsoluteContent(string contentPath)
{
var path = Url.Content(contentPath);
var url = new Uri(HttpContext.Current.Request.Url, path);
return url.AbsoluteUri;
}
and use it like such :
var cssPath = AbsoluteContent("~/Content/embeddedCss/yourcssfile.css");
Is it possible to print a PDF document using 'System.Printing' Namespace? How does it differ from System.Drawing.Printing'? I want to print a document and check whether it is printed or not! Presently i use a process to print pdf as
var fileName = filepath;
ProcessStartInfo psInfo = new ProcessStartInfo();
psInfo.Arguments = "HP LaserJet P1505n";
psInfo.FileName = fileName;
psInfo.WindowStyle = ProcessWindowStyle.Hidden;
psInfo.Verb = "print";
psInfo.CreateNoWindow = false;
psInfo.UseShellExecute = true;
process = Process.Start(psInfo);
Here i cannot determine page printed or not!
How can i accomplish this? It is possible to check status of printer using Win32_Printer with System.Management namespace but not "printing" status
I have a DocumentViewer with a fixedDocument (constructed in XAML) I then add content to the fixedDocument in code and it displays perfecty on screen.
My problem is when I try to create an XPS file from the fixedDocument, im getting an 'its already a child of another element' error.
I cant find a DocumentViewer.Children.Clear method, How can I remove/detach the fixedDocument so I can use it to create the file?
for completeness, here's the code where im getting the error:
public void CreateXPSFile()
{
// 1 - create xps_file
string OutputPath = baseDir + pathAdjust + "test.xps";
using (FileStream fs = File.Create(OutputPath))
{
ConvertToXps(fixedDocument, fs);
}
// open the document using the system default xps viewer
Process.Start(OutputPath);
}
public static void ConvertToXps(FixedDocument fd, Stream outputStream)
{
var package = Package.Open(outputStream, FileMode.Create);
var xpsDoc = new XpsDocument(package, CompressionOption.Normal);
XpsDocumentWriter xpsWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc);
// xps documents are built using fixed document sequences
var fixedDocSeq = new FixedDocumentSequence();
var docRef = new DocumentReference();
docRef.BeginInit();
docRef.SetDocument(fd);
docRef.EndInit();
((IAddChild)fixedDocSeq).AddChild(docRef); <<<<<----- Error occurs here
// write out our fixed document to xps
xpsWriter.Write(fixedDocSeq.DocumentPaginator);
xpsDoc.Close();
package.Close();
}
Thanks
You should just be able to set the Document to null.
DocumentViewer dv;
dv.Document = null;
Since you are not loading the XPS into dv then setting the dv.Document = null; might not do the trick. Rather than Process.Start(OutputPath); load the xps into dv. Or you can assign the process to a name so you can close it. But I would explicitly load into dv.
System.Diagnostics.Process myProcess = new System.Diagnostics.Process();
myProcess.StartInfo.FileName = "C:\\HelloWorld.exe";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.Start();
// ...
myProcess.Close();