Header in a XWPFDocument is only on the last page when adding section breaks - kotlin

I am trying to create a word document using Apache POI. This document includes images, and I need to flip the page with the image to be landscape oriented, while keeping the rest of the document portrait oriented. However, I also need to use a header (and a footer, but I assume it is gonna work the same way as headers do).
The code I am using:
val document = XWPFDocument()
// the header I need to be on every page
document.createHeader(HeaderFooterType.DEFAULT).createParagraph().createRun().setText("Header")
// some text before the image
var par = document.createParagraph()
var run = par.createRun()
run.setText("I am text")
// add the image, flip the page with the image onto landscape
par = document.createParagraph()
val pageSize = par.ctp.addNewPPr().addNewSectPr().addNewPgSz() // this creates new section, so I can get the pageSize
pageSize.orient = STPageOrientation.LANDSCAPE
pageSize.h = BigInteger.valueOf(595 * 20) // unit used TWIPS (Twentieth of an Imperial Point)
pageSize.w = BigInteger.valueOf(842 * 20)
run = par.createRun()
val decodedByteArray = Base64.getDecoder().decode(PLACEHOLDER_IMAGE.substringAfter(','))
run.addPicture(
ByteArrayInputStream(decodedByteArray),
XWPFDocument.PICTURE_TYPE_PNG,
"",
pixelToEMU(400),
pixelToEMU(150)
)
// some text after the image
par = document.createParagraph()
run = par.createRun()
run.setText("Me too")
val out = FileOutputStream("Output.docx")
document.write(out)
out.close()
document.close()
The resulting document however has the header only after the last section break. I am aware, that every section has its' own header/footer, and that you can set the header to be the same as the previous, but the header I need seems to be always the last header, and I have no idea how to make it stick to the front of the document, where it can be used by the following sections.
Image of the resulting document
So my question is: How can I make the header appear on every page, while keeping the page formatting as is? If there is a way to make the individual pages, with the images, flip orientation without inserting section breaks, that would be a solution aswell.
In my attempts to solve this, I've been able to put data only into the header in the last section, but never have I managed to actually get my hands on the headers in the previous sections, or on the default header. Notice, that the header in the code I'm creating is set as default, but the real default header is empty and I haven't been able to access it.
NOTE: This is not the actual project, here I've only isolated the problem into a much simpler code without giving any real thought to coding style, etc.

The problem is that sections not only have separate page settings but have separate header/footer settings too.
XWPFDocumnet.createHeader creates a header which reference gets stored in document body section properties. If you create a paragraph having own section properties - a section break paragraph - then the section properties in this section break paragraph also needs a reference to a header, else the section above that section break paragraph has no header.
Best solution I can think of is to always take a copy of the document body section properties to be the section properties of additional section break paragraphs. Then only change what needs to be changed. So the copy contains needed references to headers and/or footeres already.
The following complete example shows that. The method createSectionbreak always takes a copy of the document body section properties, except there are none. Then it creates new section properties for the section break paragraph. The method setPageSizeLandscape then changes the page size.
The code is Java code. Me not using Kotlin. But the principle shold be clear also from that.
import java.io.FileOutputStream;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.wp.usermodel.HeaderFooterType;
public class CreateWordSectionsPageSizeHeader {
static org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr getDocumentBodySectPr(XWPFDocument document) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDocument1 ctDocument = document.getDocument();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody ctBody = ctDocument.getBody();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr ctSectPrDocumentBody = ctBody.getSectPr();
return ctSectPrDocumentBody;
}
static XWPFParagraph createSectionbreak(XWPFDocument document) {
XWPFParagraph sectionBreakParagraph = document.createParagraph();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr ctSectPrDocumentBody = getDocumentBodySectPr(document);
if (ctSectPrDocumentBody != null) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr ctSectPrNewSect = (org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr)ctSectPrDocumentBody.copy();
sectionBreakParagraph.getCTP().addNewPPr().setSectPr(ctSectPrNewSect);
} else {
sectionBreakParagraph.getCTP().addNewPPr().addNewSectPr();
}
return sectionBreakParagraph;
}
static void setPageSizeLandscape(XWPFParagraph sectionBreakParagraph) {
if (sectionBreakParagraph.getCTP().getPPr() == null) return;
if (sectionBreakParagraph.getCTP().getPPr().getSectPr() == null) return;
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr ctSectPr = sectionBreakParagraph.getCTP().getPPr().getSectPr();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz ctPageSz = ctSectPr.getPgSz();
if (ctPageSz == null) ctPageSz = ctSectPr.addNewPgSz();
ctPageSz.setOrient​(org.openxmlformats.schemas.wordprocessingml.x2006.main.STPageOrientation.LANDSCAPE);
// paper format letter
ctPageSz.setH(java.math.BigInteger.valueOf(12240)); //12240 Twips = 12240/20 = 612 pt = 612/72 = 8.5"
ctPageSz.setW(java.math.BigInteger.valueOf(15840)); //15840 Twips = 15840/20 = 792 pt = 792/72 = 11"
}
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument();
XWPFParagraph paragraph;
XWPFRun run;
// create header start
XWPFHeader header = document.createHeader(HeaderFooterType.DEFAULT);
paragraph = header.getParagraphArray(0);
if (paragraph == null) paragraph = header.createParagraph();
paragraph.setAlignment(ParagraphAlignment.LEFT);
run = paragraph.createRun();
run.setText("Header");
// create header end
// create body content
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setText("Text in section 1");
// create section break
paragraph = createSectionbreak(document);
setPageSizeLandscape(paragraph);
// create section break end
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setText("Text in section 2");
// create body content end
FileOutputStream out = new FileOutputStream("./CreateWordSectionsPageSizeHeader.docx");
document.write(out);
out.close();
document.close();
}
}

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.

XWPF POI footer different in last page

How to set footer diferrent only in last page. I mean, in last page no footer or different. in first page and other are same.
enter link description here
this is my docx
There is not a header/footer only for the last page in Microsoft Word.
There are different header/footer settings possible for first page, even pages and default pages. There the default header/footer gets used for each pages if not else is defined. So if there is a header/footer for even pages and a setting that odd and even pages shall be different, then default header/footer gets used as header/footer for odd pages only. If there is no such setting, then the default header/footer gets used for each page. This is except first page, if there is a special header/footer for first page set.
But a Word document can be separated into different sections. And each section may have own header/footer settings. So if, and only if, the last page is in its own section, then there can be a different header/footer for the last page (last section) only.
Unfortunately apache poi do not provide methods to set sections in XWPF. And it only provides methods to set the three different header/footer types in document. So to get a XWPFDocument separated into sections and to set different header/footer settings for those sections, a little bit cheating is necessary.
One could create one of the three possible header/footer types only to set it to be the default header/footer for an own section later. The following example shows this. It creates a footer for even pages in document, which never gets used but later gets referenced as the default footer for the last section. Note the further comments in the code.
Of course that only can work if one at least knows where the last page starts. That must be known because the section break needs to be set before the content of the lase page.
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.wp.usermodel.HeaderFooterType;
public class CreateWordDifferentFooters {
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument();
XWPFParagraph paragraph;
XWPFRun run;
XWPFFooter footer;
//create footers
//footer for first page
footer = document.createFooter(HeaderFooterType.FIRST);
paragraph = footer.createParagraph();
run = paragraph.createRun();
run.setText("Footer FIRST");
//default footer = footer for each what is not else defined
footer = document.createFooter(HeaderFooterType.DEFAULT);
paragraph = footer.createParagraph();
run = paragraph.createRun();
run.setText("Footer DEFAULT");
//footer for even pages - gets default footer for page in last section later
footer = document.createFooter(HeaderFooterType.EVEN);
paragraph = footer.createParagraph();
run = paragraph.createRun();
run.setText("Footer EVEN = DEFAULT in last section");
//the body content
//section 1
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setText("First page in first section ...");
paragraph = document.createParagraph();
run = paragraph.createRun();
run.addBreak(BreakType.PAGE);
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setText("Second page in first section ...");
paragraph = document.createParagraph();
run = paragraph.createRun();
run.addBreak(BreakType.PAGE);
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setText("Third page in first section ...");
paragraph = document.createParagraph();
//paragraph with section setting for section above and section break
paragraph = document.createParagraph();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr ctSectPrSect1 = paragraph.getCTP().addNewPPr().addNewSectPr(); //we need ctSectPrSect1 later
//section 2
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setText("Fourth and last page. Only page in last section ...");
//section setting for section above = last section in document
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDocument1 ctDocument = document.getDocument();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody ctBody = ctDocument.getBody();
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr ctSectPrLastSect = ctBody.getSectPr(); //there must be a SectPr already because of the footer settings above
//move first and default footer to section 1
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef ctHdrFtrRef0 = ctSectPrLastSect.getFooterReferenceArray(0); // first footer
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef ctHdrFtrRef1 = ctSectPrLastSect.getFooterReferenceArray(1); // default footer
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef[] ctHdrFtrRefs = new org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef[]{ctHdrFtrRef0, ctHdrFtrRef1};
ctSectPrSect1.setFooterReferenceArray(ctHdrFtrRefs);
//set "there is a title page" for section 1 to make first footer work
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTOnOff ctOnOff = org.openxmlformats.schemas.wordprocessingml.x2006.main.CTOnOff.Factory.newInstance();
ctOnOff.setVal(true);
ctSectPrSect1.setTitlePg(ctOnOff);
//set footer reference of even footer to be default footer reference for last section
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHdrFtrRef ctHdrFtrRef = ctSectPrLastSect.getFooterReferenceArray(2);
ctHdrFtrRef.setType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STHdrFtr.DEFAULT); //change this from STHdrFtr.EVEN to STHdrFtr.DEFAULT
//unset "there is a title page" for the last section
ctSectPrLastSect.unsetTitlePg();
//remove first and old default footer references from last section
ctSectPrLastSect.removeFooterReference(1);
ctSectPrLastSect.removeFooterReference(0);
FileOutputStream out = new FileOutputStream("./CreateWordDifferentFooters.docx");
document.write(out);
out.close();
document.close();
}
}
This is tested and works using current apache poi 5.2.0.

Splitting at a specific point in PDFBox

I would like to split to generate a new pdf by concatenating certain individual pages, but the last page has to be split at a certain point (i.e. all content above a limit to be included and everything below to be excluded - I only care about the ones having their upper left corner above a line). Is that possible using PDFbox?
One way to achieve the task, i.e. to split a page at a certain point (i.e. all content above a limit to be included and everything below to be excluded) would be to prepend a clip path.
You can use this method:
void clipPage(PDDocument document, PDPage page, BoundingBox clipBox) throws IOException
{
PDPageContentStream pageContentStream = new PDPageContentStream(document, page, true, false);
pageContentStream.addRect(clipBox.getLowerLeftX(), clipBox.getLowerLeftY(), clipBox.getWidth(), clipBox.getHeight());
pageContentStream.clipPath(PathIterator.WIND_NON_ZERO);
pageContentStream.close();
COSArray newContents = new COSArray();
COSStreamArray contents = (COSStreamArray) page.getContents().getStream();
newContents.add(contents.get(contents.getStreamCount()-1));
for (int i = 0; i < contents.getStreamCount()-1; i++)
{
newContents.add(contents.get(i));
}
page.setContents(new PDStream(new COSStreamArray(newContents)));
}
to clip the given page along the given clipBox. (It first creates a new content stream defining the clip path and then arranges this stream to be the first one of the page.)
E.g. to clip the content of a page along the horizontal line 650 units above the bottom, do this:
PDPage page = ...
PDRectangle cropBox = page.findCropBox();
clipPage(document, page, new BoundingBox(
cropBox.getLowerLeftX(),
cropBox.getLowerLeftY() + 650,
cropBox.getUpperRightX(),
cropBox.getUpperRightY()));
For a running example look here: ClipPage.java.

Need alternative to local or remote goto/destinations with merged documents

BACKGROUND
I have a java program that analyzes data and creates a pdf report using itext 5.
I recently had to add a summary of major problems at the start of the document so a user would not have to read over a hundred pages to find problems. Problems are only discovered when serially looking through the data.
I solved the problem by creating 3 pdf documents and then merging them, a start/title pdf, the summary of problems pdf, and the body or analysis pdf. (Basically splitting the original document at the point I wanted to insert the summary)
I use PdfReader and PdfCopy to combine the documents. I am able to keep the chapter bookmarks OK.
THE PROBLEM
As I encounter a significant problem I add it to the 'summary' document. I want to add a link in the summary to point to the problem in the body.
I tried to use Chunk.setLocalDestination and setLocalGoto but realized why that did not work, so I tried using setLocalDestination and setRemoteGoto (with and without 'file://'), but that did not work either. (Also, I used the final pdf document name in the RemoteGoto, not the temporary pdf document name.)
I do not want to use bookmarks because that seems wrong and would not look right.
I am hoping someone could suggest an alternate method or make a suggestion.
To recap, in my current code a create a Chunk with setLocalDestination and that chunk goes into the 'body' document. At the same time I create a setRemoteGoto which is put into the summary document. I was hoping when they were combined the link would work, but when the link is clicked, you go to the first page of the combined document.
Thanks.....
PS I have both iText in action books
CLARIFICATION 3/5/2014
What I was calling 'bookmarks' are really Chapter class entities that are inserted into sections of the 3 documents as they are being created.
After saving the 3 documents, PdfReader is used to open each and PdfCopy is used to put them into a new, final document.
I get the data from the Chapters, which creates the 'bookmarks' on the left side of the Pdf reader used by the user, e.g. Acrobat Reader.
int thisPdfPages = reader.getNumberOfPages();
reader.consolidateNamedDestinations();
java.util.List<HashMap<String, Object>> bookmarks = SimpleBookmark.getBookmark(reader);
if (bookmarks != null) {
if (pageOffset != 0) {
if (debug3) auditLogger.log("Shifting pages by " + pageOffset );
SimpleBookmark.shiftPageNumbers(bookmarks, pageOffset, null);
}
masterBookmarks.addAll(bookmarks);
}
for (int i = 0; i < thisPdfPages;) {
page = copy.getImportedPage(reader, ++i);
stamp = copy.createPageStamp(page);
// add page numbers
ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_CENTER, new Phrase(String.format("page %d of %d", start + i, totalPages)), 297.5f, 28, 0);
stamp.alterContents();
copy.addPage(page);
}
PRAcroForm form = reader.getAcroForm();
if (form != null) {
copy.copyAcroForm(reader);
}
When analyzing the data I have 2 documents open, a base document which contains all the details and a summary document which contains notable events over some thresholds.
//NOTE section is part of the 'body' document
//NOTE summaryPhrase is a part of the 'summary' document
String linkName = "summaryPf_" + networkid ;
//create Link target
section.add(new Chunk("CHANGE TO EMPTY STRING WHEN WORKING").setLocalDestination( linkName ));
//create Link
Chunk linkChunk = new Chunk( "[Link] " );
Font linkFont = new Font( regularFont );
linkFont.setColor(BaseColor.BLUE);
linkFont.setStyle( Font.UNDERLINE );
linkChunk.setFont( linkFont );
boolean useLocal = true;
// both local and remote goto's fail
if (useLocal) {
linkChunk.setLocalGoto( linkName);
} else {
// all permutations of setting filename fail,
// but it does bring up a permissions dialog when the link is clicked.
//String remotePdfName = "file://./" + pdfReportName ;
//String remotePdfName = "file://" + pdfReportName ;
//String remotePdfName = "file:" + pdfReportName ;
String remotePdfName = pdfReportName ;
linkChunk.setRemoteGoto( remotePdfName, linkName);
}
// add link to summary document
summaryPhrase.add( linkChunk );
summaryPhrase.add( String.format("There were %d devices with ping failures", summaryCount));
summaryPhrase.add( Chunk.NEWLINE );
}
If I use setLocalGoto, when you click the link in the final document you goto the first page.
If I use setRemoteGoto, a dialog ask permission to go to a document, but the document fails to open, tried several permutations on filename.

RichTextBlock overflow, keep paragraphs together on one page (in one overflow control)

I am printing RichTextBlock contents, using RichTextOverflow for pagination, it's working great but I would like to keep paragraphs together on one page, and now sometimes I have page breaks in middle of paragraph.
Here is the example :
Paragraph 3 should be on the next page, it seems that ITextParagraphFormat.KeepTogether is just what I need but don't know how to get that for RichTextBlock.
If you put your paragraph inside TextBlock/RTB inside InlineUIContainer it will be treated as a single element and will carry over to the next page unbroken if needed. This approach may have some side effects, but I believe it should work for printing.
After all I made a method that will "push" paragraphs that are broken on two pages on to a next page, I am not very happy about this solution but I didn't found another way
public static void BreakBeforeLastParagraph(RichTextBlockOverflow rto)
{
if (!rto.HasOverflowContent)
return;
var pageBreak = rto.ContentEnd.Offset;
var brokenPar = rto.ContentSource.Blocks.FirstOrDefault(pr => pr.ElementStart.Offset <= pageBreak && pr.ElementEnd.Offset >= pageBreak);
if (brokenPar != null)
{
double spacerSize = pageBreak - brokenPar.ElementStart.Offset;
var spacer = new Paragraph();
spacer.Margin = new Windows.UI.Xaml.Thickness(0,spacerSize, 0,0);
rto.ContentSource.Blocks.Insert(rto.ContentSource.Blocks.IndexOf(brokenPar), spacer);
}
rto.UpdateLayout();
}