iText7: How to use table.flush() and set a very wide page - itext7

I'm using iText7 (7.2.3) to generate PDF documents with wide tables. I need the table to completely appear on the page. These documents also contains tens of thousands of rows so, ideally, I'd like to be able to use a low memory footprint using table.flush() (see https://kb.itextpdf.com/home/it7kb/examples/large-tables).
Here's my actual code that works:
// At this point table contains all the table data
Text Title = new Text("Report Title")
.SetFont(PdfFontFactory.CreateFont(StandardFonts.HELVETICA_BOLD));
Paragraph p1 = new Paragraph(Title).SetMargin(0);
table.SetBorderCollapse(BorderCollapsePropertyValue.SEPARATE);
table.SetNextRenderer(new CustomBorderTableRenderer(table));
PdfDocument pdfDoc = new(new PdfWriter(Filename));
Document doc = new(pdfDoc);
// calculate maximum width of document based on table width
float necessaryWidth = 523f;
IRenderer tableRenderer = table.CreateRendererSubTree().SetParent(doc.GetRenderer());
LayoutResult tableLayoutResult = tableRenderer.Layout(new LayoutContext(new LayoutArea(0, new Rectangle(necessaryWidth, 1000))));
float tableHeightTotal = tableLayoutResult.GetOccupiedArea().GetBBox().GetHeight();
float tableWidthTotal = tableLayoutResult.GetOccupiedArea().GetBBox().GetWidth();
// regenerate document with updated width
doc = new(pdfDoc, new iText.Kernel.Geom.PageSize(tableWidthTotal, 1024));
doc.Add(p1);
doc.Add(table);
doc.Close();
It's a bit dirty because I first get all the table data (which can take a lot of memory) then check the table width inside the document and regenerate a new page with updated width. Only after that can I insert the table data in the page.
I feel it's kind of the chicken or the egg problem: if I know the page width in advance no problem I can use table.flush() but in order to get the table width I need to fill all the table first.
I think my best bet is to update the page width after all rows are inserted but I could not find a way to do this.
Anyone has a suggestion?

Related

How to create a itext table having cells at user specified positions

I have a requirement to add images in an iText PDF table, but the position of cells (consisting of images) will depend on indexes (row and column number) given by the user. This table could also have empty cells in between if no index for any image is given.
How to add a cell in itext pdf at random position?
I looked out for this at various forums, not successful. I would really appriciate the help.
There is no API in iText's Table class to add a cell to an arbitrary position. The reasoning behind such a decision lies in iText's Table architecture: if a table has not been constructed yet (i.e. it's unknown whether there will be some cells with a rowpan and/or a colpan greater than 1), it's not feasible for iText to know whether it could place a cell at some custom position.
However, the good news is that all the layout-related code is triggered only after the table is added to the document. So, one can always do the following:
create a table
fill it with some presaved empty Cell objects
alter some Cell object as requested
add the table to the document
In the snippet above I will show, hot to add some diagonal content to the table after the cells have been added to it. The same approach could be followed for images.
int numberOfRows = 3;
int numberOfColumns = 3;
Table table = new Table(numberOfColumns);
List<List<Cell>> cells = new ArrayList<>();
for (int i = 0; i < numberOfRows; i++) {
List<Cell> row = new ArrayList<>();
for (int j = 0; j < numberOfColumns; j++) {
Cell cell = new Cell();
row.add(cell);
table.addCell(cell);
}
cells.add(row);
}
// Add some text diagonally
cells.get(0).get(0).add(new Paragraph("Hello"));
cells.get(1).get(1).add(new Paragraph("diagonal"));
cells.get(2).get(2).add(new Paragraph("world!"));
The resultant PDF looks as follows:

Auto Import Image in sheet?

I run my own fantasy baseball yearly projections and I'm looking to automatically add player images to a sheet I created.
Test Page
I use a formula to create a link to the player's image page on ESPN.
https://a.espncdn.com/combiner/i?img=/i/headshots/mlb/players/full/33859.png
Is there a way to automatically add the player image to the page?
=image("Link") is the formula, but i cant use the cell location as the link.
Any advise or guidance will be helpful and appreciated
I created an Apps Script method to achieve your target as it is not possible to do it using formula.
Here's the script I did with comments:
function SpecialOnEdit(e) {
//Get the edited cell
var range = e.range;
//Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
//Download the image. (e.value is the URL in Google sheet)
var response = UrlFetchApp.fetch(e.value);
//Get the binary data of the image and create a blob object
var binaryData = response.getContent();
var blob = Utilities.newBlob(binaryData, 'image/png', 'MyImageName');
//Insert the image into the same row that the user place the image URL link
var image = sheet.insertImage(blob, range.getHeight(), range.getRow());
//Set the height of the row, show that the image can fit the row
sheet.setRowHeight(range.getRow(), image.getHeight() + 10);
}
You should add triggers next after pasting the script. Go to your Script Editor and click Edit->Current project's triggers->Add Trigger->under Select event type, select On edit->click Save
Next, to test the trigger and script, paste a URL of a PNG image format in the spreadsheet to validate the result.
Here's a sample output of the script and trigger:

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.

Find&Replace script in Google Docs SpreadSheets

I have google spreadsheet with direct links to images (jpg and png):
https://docs.google.com/spreadsheet/ccc?key=0AoPGWppcjtzhdDh6MW1QNVJhSHlwVTlfRnRtd0pvNGc&usp=sharing
I want to increase rows heights starting from "2nd row" to 100px and render images there.
It's possible to do via Find&Replace:
Find jpg and Replace to jpg", 1)
Find http://img and Replace to =image("http://img)
Select rows and Scale them
and the same for png image-urls.
Watch this screencast http://www.screenr.com/S0RH
Is it possible to automate it via script? I think - YES! It have to be pretty simple but I googled a lot but haven't found the solution. I can't do it myself as don't know coding. Will anyone help and make this script?
A function to do what you ask is simple, if you have a basic understanding of the language (Javascript), know how to use the development environment, and read the API documentation.
For example, see this script. It's been added to your shared spreadsheet, so you can also view it (and run it) in the script editor there.
/**
* Scan column A, looking for images that have been inserted using
* =image() function. For any row with an image, set the row height
* to 100 pixels.
*/
function resizeImageRows() {
var sheet = SpreadsheetApp.getActiveSheet(); // Get a handle on the sheet
var HEADERS = 1; // Number of header rows at top
var firstRow = HEADERS + 1; // First row with data
var lastRow = sheet.getLastRow(); // Last row with data
var imageRange = sheet.getRange(1, 1, lastRow, 1); // Column A
// Get all formulas from Column A, without Headers
var formulas = imageRange.getFormulas().slice(HEADERS);
// Look for image() formulas, and set the row height.
for (var i = 0; i< formulas.length; i++) {
if (formulas[i][0].indexOf('image') !== -1) {
sheet.setRowHeight(i+firstRow, 100); // Set height to 100 pixels
}
}
}
You can absolutely do this with the find and replace function under the edit menu, just make sure you click "search in formulas" and it will find and replace in the formula.

ActiveReports as a convert to pdf machine

The company I'm with is likely to obtain an ActiveReports 7 license. There's a new project requirement that several webgrids (not actually webgrids, but more like html rendered with zurb) need to be converted into pdfs. At one point in the code behind they're effectively datasets or can be created into such. Is there a way to shuttle the data from the datasets into active reports, then render it out as a PDF. I'd like to keep the report as generic as possible, and thus have one active report for all the datatables, so doing using active reports as its usually done is kind of out of the question.
The only thing I can think of at the moment is a single textbox in the group header into which I could concatenate all the headers, and a single textbox in the details into which I could throw all the data for each row. The problem here is that I'd run into many formatting issues as nothing would line up properly - as tab delimiting would solve nothing here. I could have multiple textboxes with various spacing, but then it would eventually devolve into a different report for each dataset. Is it possible to apply some sort of markup so that I could keep the spacing of columns as I feed the data in. Do active reports richtextboxes honor html markup? Or is there another solution altogether?
I'd use Itextsharp, but its not free for commercial products.
Thanks,
Sam
You can dynamically build a report that will output a simple table based on a specified DataSet, well actually a System.Data.DataTable. Basically for each column in the DataTable, add a textbox to the header to hold the name of the column and add another textbox to the Detail section to hold the value.
For the textbox in the detail section set its DataField property to the name of the column. With the binding in place, you can set the report's DataSource property to the DataTable and then run the report and export it to PDF.
The following code is a basic example:
var left = 0f;
var width = 1f;
var height = .25f;
var space = .25f;
var rpt = new SectionReport();
rpt.Sections.Add(SectionType.ReportHeader, "rh").Height = height;
rpt.Sections.Add(SectionType.Detail, "detail").Height = height;
rpt.Sections.Add(SectionType.ReportFooter, "rf").Height = height;
foreach (System.Data.DataColumn col in dataTable.Columns)
{
var txt = new TextBox { Location = new PointF(left, 0), Size = new SizeF(width, height) };
txt.Text = col.ColumnName;
rpt.Sections["rh"].Controls.Add(txt);
txt = new TextBox { Location = new PointF(left, 0), Size = new SizeF(width, height) };
txt.DataField = col.ColumnName;
rpt.Sections["detail"].Controls.Add(txt);
left += width + space;
}
rpt.DataSource = dataTable;
rpt.Run();
var pdf = new PdfExport();
pdf.Export(rpt.Document, #"c:\Users\scott\downloads\test.pdf");

Resources