How to force with iText7 a new blank page only when necessary in order to have each text exactly on two pages? - itext7

I'm using itext 7.2.1 and I've this situation: I have a list of letters with variable content that normally fits in one page, but occasionally can span over two pages.
My goal is to force a new blank page after the "short" letters so they start alway on odd pages. The length of text is not know in advance.
Basically I have the following code:
PdfWriter writer = new PdfWriter("letters.pdf");
Document document = new Document(new PdfDocument(writer));
List<String> letters = . . .code to retrieve letters text;
int nLetter = 0;
for (String text : letters) {
Paragraph p = new Paragraph().add(text);
doc.add(p);
nLetter++;
//now after layout of last paragraph I must ensure that
//the next paragraph starts on page (nLetter*2 + 1): how can I do this?
}
. . .
doc.close();
What is the best way to do so in iText7? I tried with custom DocumentRenderer but I haven't found a clean and working solution.

The best and simpliest way to know if your next paragraph starts on the next page of the document is to get the root renderer of your document and get current area. And it will return the area(rectangle and page) where the end of the text is located.
doc.getRenderer().getCurrentArea();

Related

MigraDoc Formatting

I am completely new to PDF creation including MigraDoc. I have gotten this far, which is really close to what I want for now. My question is that the text string (myMessage) that I pass to the "bodyParagraph" is up to 100 lines long, which causes three pages to be created, which is good. However the first page's Top margin is slightly greater than the second and third pages. I have no idea of why...
Basically, I am trying to create every page the same. Same header, footer and the body to take the same space regardless of the number of lines in the "bodyParagraph" content. If I have taken the completely wrong approach I would be open to suggestions.
Also, if there is a good tutorial to point me to that would be great. I can't really find anything but samples. I have learned everything from the samples, but sections, paragraph, etc is all new to me and I would like to get a better understanding of what I've done.
public static Document CreateWorkOrderPDF2(Document document, string filename, string WorkOrderHeader, string myMessage)
{
Section section = document.AddSection();
section.PageSetup.PageFormat = PageFormat.Letter;
section.PageSetup.StartingNumber = 1;
section.PageSetup.LeftMargin = 40;
//Sets the height of the top margin
section.PageSetup.TopMargin = 100;
section.PageSetup.RightMargin = 40;
section.PageSetup.BottomMargin = 40;
//MARGIN
HeaderFooter header = section.Headers.Primary;
header.Format.Font.Size = 16;
header.Format.Font.Color = Colors.DarkBlue;
MigraDoc.DocumentObjectModel.Shapes.Image headerImage = header.AddImage("../../Fonts/castorgate.regular.png");
headerImage.Width = "2cm";
Paragraph headerParagraph = section.AddParagraph();
headerParagraph = header.AddParagraph(WorkOrderHeader);
//BODY PARAGRAPH
Paragraph bodyParagraph = section.AddParagraph();
bodyParagraph = section.AddParagraph(myMessage);
bodyParagraph.Format.Font.Size = 10;
bodyParagraph.Format.Font.Color = Colors.DarkRed;
//paragraph.Format.Distancne = "3cm";
Paragraph renderDate = section.AddParagraph();
renderDate = section.AddParagraph("Work Order Generated: ");
renderDate.AddDateField();
return document;
}
The line Paragraph bodyParagraph = section.AddParagraph(); adds an empty paragraph. I assume that is the extra space on the first page.
Same issue with renderDate in the following code block.
Just remove the calls section.AddParagraph() to remove the empty paragraphs if you don't want them.
MigraDoc is much like Word and understanding sections, paragraphs, &c. in Word will also help you with MigraDoc. That knowledge along with the samples and IntelliSense should get you going.
You can use MigraDoc to create an RTF file, open the RTF in Word, and click the pilcrow to show formatting characters in Word.

kendo ui editor how to modify user selection with range object

Kendo UI 2015.2.805 Kendo UI Editor for Jacascript
I want to extend the kendo ui editor by adding a custom tool that will convert a user selected block that spans two or more paragraphs into block of single spaced text. This can be done by locating all interior p tags and converting them into br tags, taking care not to change the first or last tag.
My problem is working with the range object.
Getting the range is easy:
var range = editor.getRange();
The range object has a start and end container, and a start and end offset (within that container). I can access the text (without markup)
console.log(range.toString());
Oddly, other examples I have seen, including working examples, show that
console.log(range);
will dump the text, however that does not work in my project, I just get the word 'Range', which is the type of the object. This concerns me.
However, all I really need however is a start and end offset in the editor's markup (editor.value()) then I can locate and change the p's to br's.
I've read the telerik documentation and the referenced quirksmode site's explanation of html ranges, and while informative nothing shows how to locate the range withing the text (which seems pretty basic to me).
I suspect I'm overlooking something simple.
Given a range object how can I locate the start and end offset within the editor's content?
EDIT: After additional research it appears much more complex than I anticipated. It seems I must deal with the range and/or selection objects rather than directly with the editor content. Smarter minds than I came up with the range object for reasons I cannot fathom.
Here is what I have so far:
var range = letterEditor.editor.getRange();
var divSelection;
divSelection = range.cloneRange();
//cloning may be needless extra work...
//here manipulate the divSelection to how I want it.
//divSeletion is a range, not sure how to manipulate it
var sel = letterEditor.editor.getSelection()
sel.removeAllRanges();
sel.addRange(divSelection);
EDIT 2:
Based on Tim Down's Solution I came up with this simple test:
var html;
var sel = letterEditor.editor.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
html = html.replace("</p><p>", "<br/>")
var range = letterEditor.editor.getRange();
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = html;
var frag = document.createDocumentFragment(), child;
while ((child = div.firstChild)) {
frag.appendChild(child);
}
range.insertNode(frag);
The first part, getting the html selection works fine, the second part also works however the editor inserts tags around all lines so the result is incorrect; extra lines including fragments of the selection.
The editor supports a view html popup which shows the editor content as html and it allows for editing the html. If I change the targeted p tags to br's I get the desired result. (The editor does support br as a default line feed vs p, but I want p's most of the time). That I can edit the html with the html viewer tool lets me know this is possible, I just need identify the selection start and end in the editor content, then a simple textual replacement via regex on the editor value would do the trick.
Edit 3:
Poking around kendo.all.max.js I discovered that pressing shift+enter creates a br instead of a p tag for the line feed. I was going to extend it to do just that as a workaround for the single-space tool. I would still like a solution to this if anyone knows, but for now I will instruct users to shift-enter for single spaced blocks of text.
This will accomplish it. Uses Tim Down's code to get html. RegEx could probably be made more efficient. 'Trick' is using split = false in insertHtml.
var sel = letterEditor.editor.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
var block = container.innerHTML;
var rgx = new RegExp(/<br class="k-br">/gi);
block = block.replace(rgx, "");
rgx = new RegExp(/<\/p><p>/gi);
block = block.replace(rgx, "<br/>");
rgx = new RegExp(/<\/p>|<p>/gi);
block = block.replace(rgx, "");
letterEditor.editor.exec("insertHtml", { html: block, split: false });
}

How can I stamp text on generated PDF pages?

From your help I have managed to get a very nice PDF generation tool built. It builds a PDF based off of a 5 page template. On the 3rd and 5th page there is a possibility of needing additional pages added and moving the next pages down. The 5th page is landscape even. Everything works perfect except one little additional functionality that I am looking for.
The template that I have built has form fields on the fifth page. Therefore, I use the following code to fill the field:
var pdfReader = new PdfReader(existingFileStream);
var stamper = new PdfStamper(pdfReader, newFileStream);
var form = stamper.AcroFields;
form.SetField("fkClientName", clientName);
The field gets filled just fine, but not on the additional pages. Which is weird because I do call this line:
PdfImportedPage templatePage = stamper.GetImportedPage(pdfReader, 5);
I feel like it should see that there is form fields on that fifth page. However, I read that stamper.GetImportedPage does not retrieve form fields. I don't really care if it's a form field or text. I just need the client name at the top of each generated additional page. Here is what my columntext code looks like that builds the additional pages:
while (true)
{
ct.SetSimpleColumn(-75, 75, PageSize.A4.Height + 25, PageSize.A4.Width - 200);
if (!ColumnText.HasMoreText(ct.Go()))
break;
pageNum++;
stamper.InsertPage(pageNum, new Rectangle(792f, 612f));
stamper.GetOverContent(pageNum).AddTemplate(templatePage, 0, -1f, 1f, 0, 0, PageSize.A4.Width);
ct.Canvas = stamper.GetOverContent(pageNum);
}
If you had company stationery with some kind of background and you wanted to create a document that has flowing text (a column that can flow over to the next page) that also has a repeating header, then I would prefer using PdfWriter.
I'd use PdfWriter to add the content (without using ColumnText, just use the page size and the margins to define the column) and I would add the background and the header using page events. See for instance the Stationery example from my book.
I'd create a subclass for PdfPageEventHelper and I'd load the page you want to see repeated into a PdfImportedPage instance named page:
PdfReader reader = new PdfReader(STATIONERY);
page = writer.getImportedPage(reader, 1);
You may also want to initialize a Phrase with the name of your customer:
header = new Phrase(customerName);
Then you override the onEndPage() method like this:
public void onEndPage(PdfWriter writer, Document document) {
writer.getDirectContentUnder().addTemplate(page, 0, 0);
ColumnText.showTextAligned(writer.getDirectContent(),
Element.ALIGN_RIGHT, header, 36, 806, 0);
}
Now you don't have to worry about ColumnText and new pages. Every time a new page is created, the background and the header will be added automatically.
However, you are using PdfStamper because your original document isn't company stationery: it's a 5 page document. If this document doesn't contain any interactive elements (you've created it using iTextSharp, so you know if it's a flat document or not), I'd still try the PdfWriter approach and change the page instance in the event whenever a new page is needed.
If you want to keep on using PdfStamper, you'll have to add the header in a different way. For instance using a different ColumnText instance, or, if it's a single line, using ColumnText.showTextAligned(). If you don't know the coordinates for the header, you can retrieve the position of the field using the getFieldPositions() method.

iText - Adding external image using Chunk

I am new to iText and faced with a real interesting case about adding external images to a paragraph. Here is the thing:
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream("out2.pdf"));
document.open();
Paragraph p = new Paragraph();
Image img = Image.getInstance("blablabla.jpg");
img.setAlignment(Image.LEFT| Image.TEXTWRAP);
// Notice the image added to the Paragraph through a Chunk
p.add(new Chunk(img2, 0, 0, true));
document.add(p);
Paragraph p2 = new Paragraph("Hello Worlddd!");
document.add(p2);
gives me the picture and "Hello Worlddd!" string below. However,
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream("out2.pdf"));
document.open();
Paragraph p = new Paragraph();
Image img = Image.getInstance("blablabla.jpg");
img.setAlignment(Image.LEFT| Image.TEXTWRAP);
// Notice the image added directly to the Paragraph
p.add(img);
document.add(p);
Paragraph p2 = new Paragraph("Hello Worlddd!");
document.add(p2);
gives me the picture and string "Hello worlddd!" located on the right hand side of the picture and one line above it.
What is the logic behind that difference?
The behaviour you described is because in the second code snippet the Paragraph doesn't adjust its leading, but adjust its width. If in the second snippet you add the line
p.add("Hello world 1")
just before
p.add(img)
you'll see the string "Hello world 1" on the left and a little bit above the string "Hello Worlddd!". If you output the leading of p (System.out.println(p.getLeading()) you can see it's a low number (typically 16) and not the height of the image.
In the first example you use the chunk constructor with 4 arguments
new Chunk(img, 0, 0, true)
with the last (true) saying to adjust the leading, so it print as you expected.
If you add an image directly, its alignment properties (set with
setAlignment()) are taken into account. So the image is on the left (Image.LEFT) and the text is wrapped around (Image.TEXTWRAP).
If you wrap the image in a Chunk it is handled as if it were a chunk of
text. So the alignment properties, specific to images, are lost. This results in the text being below the image.
If you try Image.RIGHT, this becomes more apparent. Nothing changes in the first example: the image is still on the left. In the second example, the image is aligned to the right and the text is wrapped left of it.

append image to EXISTING pdf using itextsharp

The following code is very good at putting a single page into a pdf.
It does not work for subsequent pages.
If the stream is an existing pdf file the image is replaced. How do I get NewPage() to actually create a new page and add the image at the end.
using (Stream ms = GetStream()) {
Document doc = new Document(PageSize.A4);
var writer = PdfWriter.GetInstance(doc, ms);
doc.Open();
if (!doc.NewPage())
throw new InvalidOperationException("NewPage failed.");
PDFImage jpg = PDFImage.GetInstance(image, ImageFormat.Jpeg);
jpg.Alignment = Element.ALIGN_CENTER;
jpg.ScaleToFit(PageSize.A4.Width, PageSize.A4.Height);
doc.Add(jpg);
doc.Close();
}
Calling doc.NewPage() doesn't do anything when there's nothing on the current page. There are at least 3 options:
1) Add something invisible to the current page. An empty paragraph, some white space to the PdfContentByte, whatever.
2) Tell your PDF document "no, its really not empty, take my word": PdfDocument.PageEmpty =false;
3) Don't throw when NewPage returns false. That's perfectly acceptable under the circumstances.
I'd go with #3 personally.

Resources