How can I add disparate chunks to a PdfPCell using iTextSharp? - pdf-generation

How can I concatenate disparate chunks and add them to a paragraph, the paragraph to a cell, then the cell to a table using iTextSharp (in generating a PDF file)?
I am able to get to a certain "place" in my PDF file generation, so that it looks like so (the right side of the page is blank, as it should be):
This is the code I'm using for that:
using (var ms = new MemoryStream())
{
using (var doc = new Document(PageSize.A4, 50, 50, 25, 25))
{
//Create a writer that's bound to our PDF abstraction and our stream
using (var writer = PdfWriter.GetInstance(doc, ms))
{
//Open the document for writing
doc.Open();
var courierBold11Font = FontFactory.GetFont(FontFactory.COURIER_BOLD, 11, BaseColor.BLACK);
var docTitle = new Paragraph("Mark Twain", courierBold11Font);
doc.Add(docTitle);
var timesRoman9Font = FontFactory.GetFont("Times Roman", 9, BaseColor.BLACK);
var subTitle = new Paragraph("Roughing It", timesRoman9Font);
doc.Add(subTitle);
var courier9RedFont = FontFactory.GetFont("Courier", 9, BaseColor.RED);
var importantNotice = new Paragraph("'All down but nine; set 'em up on the other alley, pard' - Scotty Briggs", courier9RedFont);
importantNotice.Leading = 0;
importantNotice.MultipliedLeading = 0.9F; // reduce the width between lines in the paragraph with these two settings
PdfPTable table = new PdfPTable(1);
PdfPCell cellImportantNote = new PdfPCell(importantNotice);
cellImportantNote.BorderWidth = PdfPCell.NO_BORDER;
table.WidthPercentage = 50;
table.HorizontalAlignment = Element.ALIGN_LEFT;
table.AddCell(cellImportantNote);
doc.Add(table);
doc.Close();
}
var bytes = ms.ToArray();
String PDFTestOutputFileName = String.Format("iTextSharp_{0}.pdf", DateTime.Now.ToShortTimeString());
PDFTestOutputFileName = PDFTestOutputFileName.Replace(":", "_");
var testFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), PDFTestOutputFileName);
File.WriteAllBytes(testFile, bytes);
MessageBox.Show(String.Format("{0} written", PDFTestOutputFileName));
}
}
However, I need to break up the red text so that part of it is bolded, parts of it are anchor tags/hrefs, etc.
I thought I could do it this way:
var courier9RedBoldFont = FontFactory.GetFont(FontFactory.COURIER_BOLD, 9, BaseColor.RED);
// Build up chunkified version of "important notice"
Chunk boldpart = new Chunk("All down but nine - set 'em up on the other alley, pard", courier9RedBoldFont);
Chunk attribution = new Chunk("Scotty Briggs", courier9RedFont);
PdfPTable tbl = new PdfPTable(1);
tbl.WidthPercentage = 50;
tbl.HorizontalAlignment = Element.ALIGN_LEFT;
var par = new Paragraph();
par.Chunks.Add(boldpart);
par.Chunks.Add(attribution );
PdfPCell chunky = new PdfPCell(par);
chunky.BorderWidth = PdfPCell.NO_BORDER;
tbl.AddCell(chunky);
doc.Add(tbl);
...but that's not adding anything at all to the PDF file, but why not? Doesn't a cell take a paragraph, and cannot a paragraph be comprised of Chunks?

Instead of para.Chunks.Add() just use par.Add(); The Chunks that are returned from Paragraph actually come from the base class Phrase. If you look at the code for that property you'll see that the collection returned is actually a temporary collection created on the fly so it is effectively read-only.

Related

Dynamic report columns in BIRT

I have a BIRT report, which simply selects all the columns of a table. Every time a new column is added to the table I have to modify the report to visualize the new column. Is it possible somehow to show the result of a "select * from table" query in the report, so I shouldn't modify the report template anymore? The order of columns is not important.
Thank You.
This is not directly possible.
However, using the DE API, you can create and save a new rptdesign file based on the query, then in a second step execute that report.
The example at https://www.eclipse.org/birt/documentation/integrating/deapi.php only shows how to create layout items, but you can as well create Data Sources and Data Sets.
This is a bit tricky however, so unless you need this quite often, it is not worth the effort.
Here is an excerpt from a script I am using in a file sql2rptdesign.rptdesign to create a new report design. This should give you an idea:
importPackage(Packages.org.eclipse.birt.data.engine.api);
importPackage(Packages.org.eclipse.birt.data.engine.core);
importPackage(Packages.org.eclipse.birt.report.model.api);
importPackage(Packages.org.eclipse.birt.report.model.api.elements.structures);
importPackage(Packages.org.eclipse.birt.report.model.api.util);
importPackage(Packages.org.eclipse.birt.data.engine.api.querydefn);
var engine = reportContext.getReportRunnable().getReportEngine();
var myconfig = reportContext.getReportRunnable().getReportEngine().getConfig();
var designFactory = designHandle.getElementFactory();
// Ein neues DataSet anlegen
var de = DataEngine.newDataEngine( myconfig, null );
var dsrc = reportContext.getDesignHandle().findDataSource("lisa");
var dsHandle = designFactory.newOdaDataSet("Main", "org.eclipse.birt.report.data.oda.jdbc.JdbcSelectDataSet" );
dsHandle.setDataSource(dsrc.getName());
dsHandle.setQueryText(sql);
designHandle.getDataSets( ).add( dsHandle );
Then I add the output columns (I parsed the SQL statement beforehand to get the necessary information). I don't know if this is strictly needed.
var numColumns = 0;
for (var position=0; position<rows.length; position++) {
var row = rows[position];
var colName = row[0];
if (colName == "__Template") {
continue;
}
var colTyp = row[1];
var colWidth = row[2];
var resultCol = new OdaResultSetColumn();
resultCol.setColumnName(colName);
resultCol.setNativeName(colName);
resultCol.setDataType(colTyp);
resultCol.setPosition(position+1);
numColumns = position + 1;
dsHandle.getPropertyHandle(DataSetHandle.RESULT_SET_PROP).addItem(resultCol);
var resultHint = new ColumnHint();
resultHint.setProperty(ColumnHint.COLUMN_NAME_MEMBER, colName);
dsHandle.getPropertyHandle(DataSetHandle.COLUMN_HINTS_PROP).addItem(resultHint);
}
And then add a table, its column bindings, columns, detail rows and cell contents to the layout. This is a bit lenghty.
// Tabellarisches Layout
var elementFactory = designHandle.getElementFactory();
var body = designHandle.getBody();
var tab = elementFactory.newTableItem("Main", numColumns);
tab.setDataSet(dsHandle);
tab.pageBreakInterval = 9999;
for (var colNum=0; colNum<numColumns; colNum++) {
var r = rows[colNum];
var colName = r[0];
var colNameSlashes = addSlashes(colName);
var colType = r[1];
var width = r[2];
var pattern = r[3];
var align = r[4];
// Binding
var colBinding = StructureFactory.createComputedColumn( );
colBinding.setName(colName);
colBinding.setExpression( 'dataSetRow[' + colNameSlashes + ']');
colBinding.setDataType(colType);
tab.addColumnBinding(colBinding, true);
// Überschrift
var textdata = elementFactory.newTextData(null);
textdata.setContentType("plain");
textdata.setValueExpr(colNameSlashes);
textdata.setStringProperty("whiteSpace", "normal");
tab.getLayoutModel().getCell(1,colNum+1).getContent().add(textdata);
// Wert
var textdata = elementFactory.newTextData(null);
textdata.setContentType("plain");
textdata.setHasExpression(true);
textdata.setStringProperty("whiteSpace", "normal");
var expr = 'row[' + colNameSlashes + ']';
textdata.setValueExpr(expr);
tab.getLayoutModel().getCell(2,colNum+1).getContent().add(textdata);
// Spaltenbreite und Ausrichtung
var columnHandle = tab.getColumns().get(colNum); // 0-basiert?
if (width) {
columnHandle.setProperty("width", StringUtil.parse(width));
}
}
body.add(tab, 0);
And finally save the new file:
designHandle.saveAs(designFilename);
However, this doesn't use a template as you would like to do.

itext7 barcodes in footer

I'm working on a rather complex solution that takes an html-like input and converts it to a pdf. One of the many items that I'm trying to solve for is adding barcodes (all types, 3 of 9, PDF417, and qr code) to the footer of documents.
A couple details that give me pause on how to implement:
Bar code will contain current page number
Bar code will contain total page count
Bar code will be inside other itext elements (like a table cell or paragraph) and (in the final solution) needs to be parsed out ahead of time
Knowing those details, I'm struggling a bit on how to combine barcodes with something like the page x of y strategy of using a template to replace page count after rendering all the content.
I assume that each bar code will need it's own template because of the page count, and keep track of the templates until all the content is rendered and then update each individual template with the appropriate bar code. But because the footer is parsed out ahead of time, I need a template that represents a bar code so that the footer will have the correct height and content can be adjusted appropriately.
I believe that each of these pieces need to be handled in the event handler for end of page, is that a correct assessment?
UPD Edited to include code sample. I pulled out quite a bit of the other stuff I was trying to accomplish from this example. As for the parsed ahead of time, instead of going over a loop from 1 to 20 and creating random elements, some other process creates all the elements that need to be present on the document and will pass in that list of elements to the renderer. That does include the footer content as well. In this case I'm creating the footer table in the constructor of the HeaderHandler as that is close to the same concept. The reason I bring this up is that I won't be able to create the table in the HandleEvent of the handler like in most examples I have seen about tables in footers. Hope that makes sense.
void Main()
{
PdfDocument pdf = new PdfDocument(new PdfWriter(Dest));
PageSize pageSize = PageSize.A4;
Document doc = new Document(pdf, pageSize, true);
HeaderHandler hh = new HeaderHandler(doc);
...
some other object generation
...
// create random paragraphs to fill up multiple pages in the final solution this would have already happened.
for (var i = 0; i < 20; i++)
AddItemToList(elementList, i, objects);
// add random elements back to the document
foreach (var e in elementList)
{
... add each item just added to elementList to the document ...
}
renderer.Flush();
hh.UpdateTotal(pdf);
// I think I need to update all the barcodes and print them out here so that page count part of the barcode can be written
doc.Close();
}
class HeaderHandler : IEventHandler
{
Table Footer;
Document Doc;
public Margin First;
public Margin Middle;
public Margin Last;
public Dictionary<int, Margin> PageMargins { get; set; }
public float HeaderHeight { get; }
public float FooterHeight { get; }
PdfFormXObject PgCount;
Text PageNumber;
Dictionary<string, PdfFormXObject> BarcodeImages;
public HeaderHandler(Document doc)
{
Doc = doc;
Footer = new Table(new float[] { 4, 2, 4}).SetAutoLayout();
PageMargins = new Dictionary<int, Margin>();
BarcodeImages = new Dictionary<string, PdfFormXObject>();
var pageSize = Doc.GetPdfDocument().GetDefaultPageSize();
var width = pageSize.GetRight() - pageSize.GetLeft() - Doc.GetLeftMargin() - Doc.GetRightMargin();
// page total
PgCount = new PdfFormXObject(new Rectangle(0,0, 13, 13));
Footer.AddCell(new Cell().Add(new Paragraph("info 1")));
PageNumber = new Text("{page}");
var cell = new Cell().Add(new Paragraph().Add(PageNumber).Add(" of ").Add(new Image(PgCount)).Add(" pages").SetTextAlignment(TextAlignment.CENTER));
Footer.AddCell(cell);
Footer.AddCell(new Cell().Add(new Paragraph("info 2")));
Footer.AddCell("footer 1");
Footer.AddCell("footer 2");
// I think I need to add a template here for the barcode as a placeholder so that when the renderersubtree is ran it provides space for the barcode
Footer.AddCell(new Cell().Add(new Paragraph("{barcode} {qr code - {page} | {pagect} | doc name}")));
TableRenderer fRenderer = (TableRenderer)Footer.CreateRendererSubTree();
using (var s = new MemoryStream())
{
fRenderer.SetParent(new Document(new PdfDocument(new PdfWriter(s))).GetRenderer());
FooterHeight = fRenderer.Layout(new LayoutContext(new LayoutArea(0, PageSize.A4))).GetOccupiedArea().GetBBox().GetHeight();
}
}
public void UpdateTotal(PdfDocument pdf) {
Canvas canvas = new Canvas(PgCount, pdf);
canvas.ShowTextAligned(pdf.GetNumberOfPages().ToString(), 0, -3, TextAlignment.LEFT);
}
//draw footer and header tables
public void HandleEvent(Event e)
{
PdfDocumentEvent docEvent = e as PdfDocumentEvent;
if (docEvent == null)
return;
PdfDocument pdf = docEvent.GetDocument();
PdfPage page = docEvent.GetPage();
PdfCanvas pdfCanvas = new PdfCanvas(page.GetLastContentStream(), page.GetResources(), pdf);
int pageNum = pdf.GetPageNumber(page);
var pageSize = Doc.GetPdfDocument().GetDefaultPageSize();
Margin activeMargin = new Margin();
if (PageMargins.ContainsKey(pageNum))
activeMargin = PageMargins[pageNum];
var width = pageSize.GetRight() - pageSize.GetLeft() - activeMargin.Left - activeMargin.Right;
Header.SetWidth(width);
Footer.SetWidth(width);
var pageReferences = new List<TextRenderer>();
// update page number text so it can be written to in the footer
PageNumber.SetText(pageNum.ToString());
// draw the footer
rect = new Rectangle(pdf.GetDefaultPageSize().GetX() + activeMargin.Left, activeMargin.Bottom - GetFooterHeight(), 100, GetFooterHeight());
canvas = new Canvas(pdfCanvas, pdf, rect);
// I think it's here that I need to be able to add a barcode placeholder to something that can be called
canvas.Add(Footer);
}
public float GetFooterHeight()
{
return FooterHeight;
}
}

How to compare datasets with different minPeriod values

My main dataset has a minPeriod of one day, "DD".
I then add a new dataset to compare to the main dataset. This new dataset has a minPeriod of one min, "mm".
However, even after changing chart.categoryAxesSettings.minPeriod to equal mm, the new dataset does not appear.
But when I use a main dataset that also has a minPeriod='mm', the new compared dataset does appear.
So, I'm wondering how I can use a main dataset with a minPeriod='DD' and compare to it a new dataset with minPeriod='mm'.
Please let me know if you need more info.
Thanks! Find code below.
chart = new AmCharts.AmStockChart();
//this is my main dataset
//the data in it has a minPeriod of one day
dataSet = new AmCharts.DataSet();
dataSet.title = 'Price';
dataSet.dataProvider = chartData;
dataSet.fieldMappings = [{fromField:"Open", toField:"open"}, {fromField:"High", toField:"high"}, {fromField:"Low", toField:"low"}, {fromField:"Close", toField:"close"}, {fromField:"Volume", toField:"volume"}, {fromField:"orderPrice", toField:"orderPrice"}];
dataSet.categoryField = "date";
chart.dataSets = [dataSet];
categoryAxesSettings = new AmCharts.CategoryAxesSettings();
categoryAxesSettings.minPeriod = "DD";
categoryAxesSettings.maxSeries = 750;
chart.categoryAxesSettings = categoryAxesSettings;
//main panel
pricePanel = new AmCharts.StockPanel();
pricePanel.recalculateToPercents = "never";
//create priceGraph
var priceGraph = new AmCharts.StockGraph();
priceGraph.valueField = "closeField";
priceGraph.type = "ohlc";
priceGraph.title = "Price";
priceGraph.openField = 'open';
priceGraph.highField = 'high';
priceGraph.lowField = 'low';
priceGraph.closeField = 'close';
pricePanel.addStockGraph(priceGraph);
//display chart
chart.write("chartdiv");
//create new dataset where data has minPeriod = 'mm'
var orderDataset = new AmCharts.DataSet();
orderDataset.title = 'orderSet';
orderDataset.dataProvider = orderGraphData;
orderDataset.fieldMappings = [{fromField:"orderPrice", toField:"orderPrice"}];
orderDataset.categoryField = "date";
orderDataset.compared = true;
chart.dataSets.push(orderDataset);
//create priceGraph
var orderGraph = new AmCharts.StockGraph();
orderGraph.comparable = true;
orderGraph.compareField = 'orderPrice';
orderGraph.compareGraph = {
"type": "step",
"bullet": "round",
"lineThickness": 2,
"bulletBorderColor": "#FFFFFF",
"bulletBorderAlpha": 1,
"bulletBorderThickness": 3
};
pricePanel.addStockGraph(orderGraph);
//change minPeriod to mm
chart.categoryAxesSettings.minPeriod = "mm";
chart.validateData();

Displaying a polyline of geocoordinate objects

I have a bunch of GeoCoordinate objects that I wish to display as a polyline (to show a path a person has taken).
My unsuccessful attempt (Does not display any line):
var map = new Map(); // Nokia Maps
var layer = new MapLayer();
var overlay = new MapOverlay();
var polyline = new MapPolyline();
var gc = new GeoCoordinateCollection();
foreach(var geo in MyGeoCoordinateList) {
gc.Add(geo);
}
polyline.Path = gc;
polyline.StrokeColors = Colors.Red;
polyline.StrokeThickness = 3;
overlay.Content = polyline;
layer.Add(overlay);
map.Layers.Add(layer);
LayoutRoot.Children.Add(map);
It turns out that to display a path (which simply connects points and does not follow roads like in Mike Brown's example) you need to add the polyline to a MapElements object, i.e:
var map = new Map(); // Nokia Maps
var polyline = new MapPolyline();
var gc = new GeoCoordinateCollection();
foreach(var geo in MyGeoCoordinateList) {
gc.Add(geo);
}
polyline.Path = gc;
polyline.StrokeColors = Colors.Red;
polyline.StrokeThickness = 3;
map.MapElements.Add(polyline);
LayoutRoot.Children.Add(map);
As a bonus here's how to set the map so it contains the points in the view port (with nice animation):
map.SetView(LocationRectangle.CreateBoundingRectangle(MyGeoCoordinateList, MapAnimationKind.Parabolic);
I wanted to do the same thing, I used a GeoQuery passing in all of the waypoints. The query generated a route that I then displayed on the UI.
var query = new RouteQuery();
query.Waypoints = new[]
{
new GeoCoordinate(40.7840553533410,-73.9764425910787),
new GeoCoordinate(40.7833068308611,-73.9745997113487),
new GeoCoordinate(40.7826229881351,-73.9730985576614),
new GeoCoordinate(40.7821147220884,-73.9719513345183),
new GeoCoordinate(40.7809503161196,-73.9724639235822),
new GeoCoordinate(40.7803311395532,-73.9721954245488),
new GeoCoordinate(40.7795640919224,-73.9729398991417),
};
query.TravelMode = TravelMode.Walking;
var result = await query.GetRouteAsync();
var mapRoute = new MapRoute(result);
mapRoute.Color = Colors.Magenta;
mainMap.AddRoute(mapRoute);
mainMap is the Map control in my UI. The query creates a route that follows the available path (since I marked it as a walking path, it used the foot paths in central park).

Google Maps V.3 custom icons

How can I make a similar script for custom marker with Google Maps 3? Just like this with v.2 :
var iconBlue = new GIcon();
iconBlue.image = 'images/cell1.png';
iconBlue.shadow = '';
iconBlue.iconSize = new GSize(32, 32);
iconBlue.shadowSize = new GSize(22, 20);
iconBlue.iconAnchor = new GPoint(3, 16);
iconBlue.infoWindowAnchor = new GPoint(15, 15);
var iconRed = new GIcon();
iconRed.image = 'images/cell3.png';
iconRed.shadow = '';
iconRed.iconSize = new GSize(32, 32);
iconRed.shadowSize = new GSize(22, 20);
iconRed.iconAnchor = new GPoint(15, 2);
iconRed.infoWindowAnchor = new GPoint(15, 15);
var iconGreen = new GIcon();
iconGreen.image = 'images/cell2.png';
iconGreen.shadow = '';
iconGreen.iconSize = new GSize(32, 32);
iconGreen.shadowSize = new GSize(22, 20);
iconGreen.iconAnchor = new GPoint(-9, 2);
iconGreen.infoWindowAnchor = new GPoint(20, 15);
var customIcons = [];
customIcons["60"] = iconBlue;
customIcons["240"] = iconRed;
customIcons["350"] = iconGreen;
I wrote the following in Version 3 and the marker is not shown.
var cellone = new google.maps.MarkerImage("images/cell1.png",
new google.maps.Size(20, 34), new google.maps.Point(0,0),
new google.maps.Point(3, 16));
var celltwo = new google.maps.MarkerImage("images/cell2.png",
new google.maps.Size(20, 34), new google.maps.Point(0,0),
new google.maps.Point(-9, 2));
var cellthree = new google.maps.MarkerImage("images/cell3.png",
new google.maps.Size(20, 34), new google.maps.Point(0,0),
new google.maps.Point(15, 2));
var customIcons = [];
customIcons["60"] = cellone;
customIcons["240"] = celltwo;
customIcons["350"] = cellthree;
I've recently just did this icon stuff myself. And I initially used the same type of code you used, but that didn't work. Apparently new GIcon() was not functioning properly.
There is a very simple way to change icon though and it doesn't use variables.
This is what I did.
Declare markerVariable as a global variable.
When you create new markers, one of the lines should say icon : ...
Here is a sample of my code. Of course, you will have to change certain lines to have it working for your program (and you can't use marker with labels unless you change certain functions).
Lastly, icon : ... can be a link or location on your computer.
var iconArray = [
["http://www.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png"],
["http://www.google.com/intl/en_us/mapfiles/ms/micons/yellow-dot.png"],
["http://www.google.com/intl/en_us/mapfiles/ms/micons/green-dot.png"],
["http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png"],
["http://www.google.com/intl/en_us/mapfiles/ms/micons/purple-dot.png"],
["http://www.google.com/intl/en_us/mapfiles/ms/micons/pink-dot.png"],
["http://www.google.com/intl/en_us/mapfiles/ms/micons/ltblue-dot.png"]
];
function getMarkers() {
for (i = 0; i < locations.length; i++) {
marker[i] = new MarkerWithLabel({
position: new google.maps.LatLng(locations[i][1], locations[i][2]),
draggable: false,
map: map,
icon: new google.maps.MarkerImage(iconArray[i]),
//icon: new google.maps.MarkerImage("http://www.google.com/intl/en_us/mapfiles/ms/micons/pink-dot.png"),
//labelContent: locations[i][3],
labelAnchor: new google.maps.Point(30, 0),
labelClass: "labels", // the CSS class for the label
labelStyle: {opacity: 0.75}
});
}
}
function initialize() {
getMarkers();
}
Translated version of Mike Williams' v2 "category" example

Resources