Render XHTML on one page to PDF - kotlin

I have XHTML containing shop receipt. I am trying to generate PDF out of it. Generation is not problem at all. But I would like to have "break-less" page (whole content fits to one page).
I have Koltin Spring project and using flying-saucer.
org.xhtmlrenderer:flying-saucer-pdf-itext5:9.1.22
Code is simple as this:
fun generatePDF(templateString: String): ByteArrayOutputStream {
val renderer = ITextRenderer()
renderer.sharedContext.also {
it.isPrint = true
it.isInteractive = false
it.textRenderer.setSmoothingThreshold(0F)
}
renderer.setDocumentFromString(templateString, baseUrl)
renderer.layout()
val baos = ByteArrayOutputStream()
renderer.createPDF(baos)
renderer.finishPDF()
return baos
}
Is it somehow possible to do it?
Note: I've found some information about page size in the documentation, but I am not sure how to use it if I don't know the exact size (items in receipt are calculated).

Related

Adding an Annotation to a PdfFormXObject so the Annotation is reusable

I'm using iText 7 to construct reusable PDF components that I reuse across multiple pages within a document. I'm using iText-dotnet for this task (v7), using F# as the language. (This shouldn't be hard to follow for non-F# people as it's just iText calls :D)
I know how to add annotations to a Page, that isn't the issue. Adding the annotation to the page is as simple as page.AddAnnotation(newAnnotation).
Where I'm having difficulty, is that there is no "Page" associated with a Canvas when you are using a PdfFormXObject() to render a Pdf fragment.
let template = new PdfFormXObject(rect)
let templateCanvas = PdfCanvas(template, pageContext.Canvas.GetPdfDocument())
let newCanvas = new Canvas(templateCanvas, rect)
Once I have the new Canvas, I try to write to the Canvas and add the Annotation via Page.AddAnnotation(). The problem is that there is no Page attached to the PdfFormXObject!
// Create the destination and annotation (destPage is the pageNumber)
let dest = PdfExplicitDestination.CreateFitB(destPage)
let action = PdfAction.CreateGoTo(dest)
let annotation = PdfLinkAnnotation(rect)
let border = iText.Kernel.Pdf.PdfAnnotationBorder(0f, 0f, 0f)
// set up the Annotation with action and display information
annotation
.SetHighlightMode(PdfAnnotation.HIGHLIGHT_PUSH)
.SetAction(action)
.SetBorder(border)
|> ignore
// Try adding the annotation to the page BOOM! (There is *NO* page (null) associated with newCanvas)
newCanvas.GetPage().AddAnnotation(annotation) |> ignore // HELP HERE: Is there another way to do this?
The issue is that I do not know of a different way to set the Annotation on the canvas. Is there a way to render the annotation and just add the annotation directly to the canvas as raw PDF instructions?
Alternatively, is there a way create a different reusable PDF fragment in iText so I can also reuse the GoTo annotation.
N.B. I could split off the annotations and then apply them every time I use the PdfFormXObject() on a new page, but that sort of defeats the purpose of reusing Pdf fragments (template) in my final PDF to reduce it's size.
If you can point me in the right direction, that would be great.
Again, this is not how to add an annotation to a Page(), that's easy. It's how to add an annotation to a PdfFormXObject (or similar mechanism that I'm unaware of for constructing rusable Pdf fragments).
-- As per John's comments below:
I cannot seem to find any reference to single use annotations.
I'm aware of the following example link, so I modified it to look like this:
private static void Main(string[] args)
{
try
{
PdfDocument pdfDocument = new PdfDocument(new PdfWriter("TestMultiLink.pdf"));
Document document = new Document(pdfDocument);
string destinationName = "MyForwardDestination";
// Create a PdfStringDestination to use more than once.
var stringDestination = new PdfStringDestination(destinationName);
for (int page = 1; page <= 50; page++)
{
document.Add(new Paragraph().SetFontSize(100).Add($"{page}"));
switch (page)
{
case 1: // First use of PdfStringDestination
document.Add(new Paragraph(new Link("Click here for a forward jump", stringDestination).SetFontSize(20)));
break;
case 3: // Re-use the stringDestination
document.Add(new Paragraph(new Link("Click here for a forward jump", stringDestination).SetFontSize(10)));
break;
case 42:
pdfDocument.AddNamedDestination(destinationName, PdfExplicitDestination.CreateFit(pdfDocument.GetLastPage()).GetPdfObject());
break;
}
if (page < 50)
document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
document.Close();
}
catch (Exception e)
{
Console.WriteLine($"Ouch: {e.Message}");
}
}
If you dig into the iText source for iText.Layout.Link, you'll see that the String Destination is added as an Annotation. Therefore, I'm not sure if John's answer is true anymore.
Does anyone know how I can convert the Annotation to a Dictionary and how I would go about adding the PdfDictionary (raw) info into the PftFormXObject?
Thanks
#johnwhitington is correct.
Per PDF specification, annotations can only be added to a page, they cannot be added to a form XObject. It is not a limitation of iText or any other PDF library.
Annotations cannot be reused, each annotation is a distinct object.

Get report from Acumatica as single page PDF

I am trying to get Acumatica report as single page PDF.
(Multiple pages works fine regardless is it from report screen or PDF generated by custom code)
All reports and groups got "True" in Keep Together setting.
I tried this as report screen, or tried to generate PDF with code:
public PXAction<PX.Objects.AR.ARInvoice> CreatePDF;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Create PDF")]
protected void createPDF()
{
//Report Paramenters
Dictionary<String, String> parameters = new Dictionary<String, String>();
parameters["ARInvoice.DocType"] = Base.Document.Current.DocType;
parameters["ARInvoice.RefNbr"] = Base.Document.Current.RefNbr;
parameters["DocType"] = Base.Document.Current.DocType;
parameters["RefNbr"] = Base.Document.Current.RefNbr;
//Report Processing
PX.Reports.Controls.Report _report = PX.Data.Reports.PXReportTools.LoadReport("EP301009", null);
PX.Data.Reports.PXReportTools.InitReportParameters(_report, parameters,
PX.Reports.SettingsProvider.Instance.Default);
ReportNode reportNode = ReportProcessor.ProcessReport(_report);
//Generation PDF
byte[] data = PX.Reports.Mail.Message.GenerateReport(reportNode, ReportProcessor.FilterHtml).First();
FileInfo file = new FileInfo("report.html", null, data);
//Saving report
UploadFileMaintenance graph = new UploadFileMaintenance();
graph.SaveFile(file);
PXNoteAttribute.AttachFile(Base.Document.Cache, Base.Document.Current, file);
throw new PXRedirectToFileException(file, true);
}
The only result with single page, that i achieved - Acumatica is showing only first page, when clicking "PDF" button. Others are lost.
Is it possible to override "PDF" button behavior in Acumatica?
The report engine is page based so you must use pages. There's only a finite amount of data that can fit on a single page of a finite size.
I would recommend to increase the page size to fit more data on a single page. This can be done in the PageSettings properties of the report in Acumatica report designer.

Remove acroform in pdf

Im using removeField to remove field of a document but how can I remove completly acroform in pdf?
Im aware of
acroform.flatten()
But i wonder if this is the correct method to remove all acroform ? Is there better way of doing this making maybe pdf smaller in size? Or remove acroform faster?
Call PDDocument.getDocumentCatalog().setAcroForm(null) and also remove all widget annotations from each page:
List<PDAnnotation> annotations = page.getAnnotations();
List<PDAnnotation> newList = new ArrayList<>();
for (PDAnnotation ann : annotations)
{
if (!(ann instanceof PDAnnotationWidget))
{
newList.add(ann);
}
}
if (newList.isEmpty())
{
page.setAnnotations(null);
}
else
{
page.setAnnotations(newList);
}

Extracting a page from PDF document (using PDFBox)

I'm trying to break a PDF down into individual pages. Although it
functionally works, the pdf for each page ends up being almost the size of the original PDF (250MB). I've seen some references in deleting
annotations which might include links to other pages/resources. I've tried the below, but no luck. Can someone let me know what I'm doing wrong?
(Below code is in Kotlin). I've also tried using addPage vs. importPage,
since the later creates a deep copy. Same result.
doc.pages.forEachIndexed { idx: Int, p: PDPage ->
val newDoc = PDDocument()
val newPage = newDoc.importPage(p)
newPage.annotations = null
newPage.resources = null
newDoc.save("/tmp/$idx.pdf")
newDoc.close()
}

How to add watermark to a landscape file using pdfbox

I'm using pdfbox 1.8.11 and FOP to add water mark to pdf:s. It works nicely to most input pdf files.
However I get a problem when the file is in landscape, the watermarking will be 90 degree right rotated.
I had similar problem with visible signature, it is fixed. thanks to the solution in sign landscape file . Any idea how to make water mark rotation works? Thanks in advance!
The original picture for watermark is:
Up arrow
After FOP watermark the image is rotated:
image rotated
apologize for answer late.
The idea for 'water mark' here to add add some transforms into the original pdf using fop apache fop. You can fine java code example and fo template example from apache fop website.
In any case i will illustrate the example here too:
1. the java code of how to use fop
import org.apache.fop.apps.*;
import org.xml.sax.*;
import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.sax.*;
import javax.xml.transform.stream.*;
class rendtest {
private static FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
private static TransformerFactory tFactory = TransformerFactory.newInstance();
public static void main(String args[]) {
OutputStream out;
try {
//Load the stylesheet
Templates templates = tFactory.newTemplates(
new StreamSource(new File(args[1])));
//First run (to /dev/null)
out = new org.apache.commons.io.output.NullOutputStream();
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
Transformer transformer = templates.newTransformer();
transformer.setParameter("page-count", "#");
transformer.transform(new StreamSource(new File(args[0])),
new SAXResult(fop.getDefaultHandler()));
//Get total page count
String pageCount = Integer.toString(driver.getResults().getPageCount());
//Second run (the real thing)
out = new java.io.FileOutputStream(args[2]);
out = new java.io.BufferedOutputStream(out);
try {
foUserAgent = fopFactory.newFOUserAgent();
fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
transformer = templates.newTransformer();
transformer.setParameter("page-count", pageCount);
transformer.transform(new StreamSource(new File(args[0])),
new SAXResult(fop.getDefaultHandler()));
} finally {
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
for the problem i had for rendering landscape pdf:s, in fop template you only need to add one more attribute to tell this file is in landscape layout.
The attribute is to set reference-orientation="90". Then your other definitions in the fop template will be applied properly.