Replace multiple different images on one PDF template page with itext (itextsharp) - asp.net

We have an ASP.NET application that users use to generate certain reports. So far we had one PDF template that had one image on it, and we would just replace that image with our programatically generated one (graph).
We have used code from this site for that:http://blog.rubypdf.com/2007/12/12/how-to-replace-images-in-a-pdf/
Problem now is that we have two different images on one PDF page, and the code from link above selects both images on one page and replaces them all at once with our generated image.
Does anyone have any idea how to replace multiple different images on one page with itext?
Thanks

Ugh. First, let me rewrite some of that source.
PdfReader pdf = new PdfReader("in.pdf");
PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("c:\\out.pdf"));
PdfWriter writer = stp.getWriter();
Image img = Image.getInstance("image.png");
PdfDictionary pg = pdf.getPageN(1);
PdfDictionary res = pg.getAsDict.get(PdfName.RESOURCES);
PdfDictionary xobj = res.getAsDict(PdfName.XOBJECT);
if (xobj != null) {
for (Iterator<PdfName> it = xobj.getKeys().iterator(); it.hasNext(); ) {
PdfObject obj = xobj.get(it.next());
if (obj.isIndirect()) {
PdfDictionary tg = (PdfDictionary)PdfReader.getPdfObject(obj);
PdfName type = tg.getAsName(PdfName.SUBTYPE));
if (PdfName.IMAGE.equals(type)) {
PdfReader.killIndirect(obj);
Image maskImage = img.getImageMask();
if (maskImage != null)
writer.addDirectImageSimple(maskImage);
writer.addDirectImageSimple(img, (PRIndirectReference)obj);
break;
}
}
}
}
Whew. the getAs functions can save you quite a bit of knuckle-grease and make your code much clearer.
Now. You need to be able to differentiate between the various images. If you're willing to hard-code things, you could find out what the resource names are and go that route:
String imageResName[] = {"Img1", "Img2" ... };
Image img[] = {Image.getInstance("foo.png"), Image.getInstance("bar.png"), ... };
for (int i = 0; i < imageResName.length; ++i) {
PdfName curKey = new PdfName(imageResName[i]);
PdfIndirectReference ref = xobj.getAsIndirect(curKey);
PdfReader.killIndirect( ref );
Image maskImage = img[i].getImageMask();
if (maskImage != null) {
writer.addDirectImageSimple(maskImage);
}
writer.addDirectImageSimple(img[i], (PRIndirectReference)ref);
}
If you're not willing to go with hardcoded resource names (and no one would fault you, quite the opposite, particularly when the order they appear (and thus the number on the end) depends on their order in a hash map... [shudder]), you may be able to differentiate based on image width and height.
//keep the original for loop, stepping through resource names
if (PdfName.IMAGE.equals(type)) {
float width = tg.getAsNumber(PdfName.WIDTH).floatValue();
float height = tg.getAsNumber(PdfName.HEIGHT).floatValue();
Image img = getImageFromDimensions(width, height);
Image maskImage = img.getImageMask();
...
}

Just a note that sometimes the image will be nested in a form, so it is wise to make a function that will be called recursively.
Something like this:
public void StartHere()
{
PdfReader pdf = new PdfReader("in.pdf");
PdfStamper stp = new PdfStamper(pdf, new FileOutputStream("c:\\out.pdf"));
PdfWriter writer = stp.getWriter();
Image img = Image.getInstance("image.png");
PdfDictionary pg = pdf.getPageN(1);
replaceImage(pg, writer,img);
}
private void replaceImage(PdfDictionary pg, PdfWriter writer,Image img)
{
PdfDictionary res = pg.getAsDict.get(PdfName.RESOURCES);
PdfDictionary xobj = res.getAsDict(PdfName.XOBJECT);
if (xobj != null) {
for (Iterator<PdfName> it = xobj.getKeys().iterator(); it.hasNext(); ) {
PdfObject obj = xobj.get(it.next());
if (obj.isIndirect()) {
PdfDictionary tg = (PdfDictionary)PdfReader.getPdfObject(obj);
PdfName type = tg.getAsName(PdfName.SUBTYPE));
if (PdfName.IMAGE.equals(type))
{
PdfReader.killIndirect(obj);
Image maskImage = img.getImageMask();
if (maskImage != null)
writer.addDirectImageSimple(maskImage);
writer.addDirectImageSimple(img, (PRIndirectReference)obj);
break;
}
else if(PdfName.FORM.equals(type))
{
replaceImage(tg, writer,img);
}
}
}
}

Related

Multiple pages in the same PDF reporting with PDFsharp

I am getting values from database and collection of all values are larger than a single page. Number of pages are unknown and it might differ report by report. Currently, only 1 page of multiple pages is being printed but same information in every pages. I want continuous information in the pages not same information in every page. How can I do that with PDFsharp?
Here I am giving my codes to understand...
Document doc = new Document();
//Create table
var sec = doc.AddSection();
var table = sec.AddTable();
table.Format.Font.Size = 6;
table.Borders.Distance = 0;
table.Borders.Color = MigraDoc.DocumentObjectModel.Colors.DarkGray;
var renderer = new DocumentRenderer(doc);
renderer.PrepareDocument();
int totalPage = renderer.FormattedDocument.PageCount;
for (int i = 1; i <= totalPage; ++i)
{
PdfPage pdfPage = pdf.AddPage();
XGraphics gfx = XGraphics.FromPdfPage(pdfPage);
renderer.RenderObject(gfx, XUnit.FromMillimeter(10), XUnit.FromMillimeter(10), XUnit.FromMillimeter(double.MaxValue), table);
gfx.Dispose();
}
You can have MigraDoc create the whole document without a for loop:
PdfDocumentRenderer renderer = new PdfDocumentRenderer(true, PdfSharp.Pdf.PdfFontEmbedding.Always);
renderer.Document = document;
renderer.RenderDocument();
Your for loop has a variable i that is not used in your code. If you want to do it the complicated way, use RenderPage instead of RenderObject.
See also:
http://www.pdfsharp.net/wiki/MixMigraDocAndPdfSharp-sample.ashx
I'm not sure if this may be of help to anyone but if you want to break your table into multiple pages you can use this method:
public Document MultiplePagesPerDocument(Document document, DocumentForm documentForm, Section section, DocumentPage page, DataTable dt, int maxTableRows)
{
_document = document;
if (dt.Rows.Count > maxTableRows)
{
for (int i = 1; i <= dt.Rows.Count; i++)
{
// Break the table onto next page for maxTableRows
if (i % maxTableRows == 0)
{
section = _document.LastSection;
section.AddPageBreak();
Paragraph paragraph = section.AddParagraph();
paragraph.Format.SpaceAfter = "3cm";
_position = new PageItemPosition()
{
Height = "3.0cm",
Width = "8.0cm",
Top = "3.0cm",
LeftPosition = ShapePosition.Left,
TopPosition = ShapePosition.Top
};
object tableSection = page.AddPageTextFrame(section, _position, string.Empty);
documentForm.GenerateSection(section, tableSection, 170, "Table", dt);
}
}
}
return this._document;
}

Get size of Wordpress RSS feed?

The scenario:
I have a flash website (here) that imports some of its content from a wordpress site (here) via the Wordpress's RSS (I added a plugin to include pages in the RSS). This isn't a brilliant idea I know, but for a long list of reasons that I won't go into it's the setup I'm stuck with :/
The Problem:
When importing the RSS feed (it's downloaded into the SWF at runtime and read like an XML), I can't get the filesize. This means the preloader isn't accurate (I just manually guessed a number for it to work with for now, which obviously will grow over time). How do I get this file size?
I know there may be certain settings on the server that compresses the feed/files (GZip or such), but I'm very unfamiliar with wordpress architecture and despite my best efforts I couldn't find where this setting would be applied.
This is the preloader update code. The 113*1024 is where I want to put rss_loader.bytesTotal (the loader of the WordPress content), but when I do rss_loader.bytesTotal returns as 0:
function update_loading(e:Event):void
{
var total:Number = stage.loaderInfo.bytesTotal + (113 * 1024) + css_loader.bytesTotal;
var loaded:Number = stage.loaderInfo.bytesLoaded + rss_loader.bytesLoaded + css_loader.bytesLoaded;
if (total == loaded)
{
updatePicture(360);
loading_mc.perc.text = "100%";
removeEventListener(Event.ENTER_FRAME,update_loading);
nextFrame();
}
else
{
var deg:Number = (loaded / total) * 360;
updatePicture(deg);
loading_mc.perc.text = String(int((loaded / total) * 100))+"%";
}
}
Here's the RSS import code, though I don't believe it's very relevant:
var rss_xml:XML = new XML();
var post_xml:XML = new XML();
var page_xml:XML = new XML();
var rss_loader:URLLoader;
rss_loader = new URLLoader(new URLRequest("http://news.sulsc.org/feed"));
function rss_loaded(e:Event):void
{
rss_xml = XML(e.target.data);
var rss_raw:String = String(rss_xml);
rss_raw = rss_raw.split(":encoded").join("");
rss_raw = rss_raw.split("\n").join("");
rss_raw = rss_raw.split('<p style="text-align: right;">').join('<p class="right">');
rss_raw = rss_raw.split('<p style="text-align: center;">').join('<p class="centre">');
rss_raw = rss_raw.split('<p style="text-align: left;">').join('<p class="left">');
rss_raw = rss_raw.split('<p style="text-align: justify;">').join('<p class="just">');
var heading_replace:RegExp = new RegExp("(</h[0-9]>)(<h[0-9]>)","g9");
var underline_replace:RegExp = new RegExp('<span style="text-decoration: underline;">(.*?)</span>',"gi");
var bold_replace:RegExp = new RegExp('<strong>(.*?)</strong>',"gi");
var italic_replace:RegExp = new RegExp('<em>(.*?)</em>',"gi");
rss_raw = rss_raw.replace(heading_replace,'$1<p class="space"></p>$2');
rss_raw = rss_raw.replace(underline_replace,'<u>$1</u>');
rss_raw = rss_raw.replace(bold_replace,'<b>$1</b>');
rss_raw = rss_raw.replace(italic_replace,'<i>$1</i>');
var page_test:RegExp = new RegExp("http://(news.|www.|)sulsc\\.org/(|wordpress/)\\?page_id=[0-9]+","gi");
page_xml = XML(rss_raw);
post_xml = XML(rss_raw);
rss_xml = null;
for (var i:uint; i<post_xml.channel.item.length(); i++)
{
if (page_test.test(post_xml.channel.item[i]) == true)
{
post_xml.channel.item[i] = "";
i--;
}
if (i == post_xml.channel.item.length()-1)
{
rss_loader.bytesLoaded = 113 * 1024;
}
}
}
I ran into a similar issue with a project last year. Turns out the server wasn't returning the correct header for Flash to pick up on. I can't remember which header it needs to populate bytesTotal, unfortunately, but I did come up with a quick work around.
this.urlLoader.addEventListenever( HTTPStatusEvent.HTTP_RESPONSE_STATUS, this.statusResponse );
private function statusResponse(e:HTTPStatusEvent = null):void{
for ( var i:Number = 0; i < e.responseHeaders.length; i++ ) {
var current:Object = e.responseHeaders[i];
if ( current.name.toLowerCase() == "content-length" ) {
this.bytesTotal = current.value;
break;
}
}
}
This worked for me, although it obviously relies on the server sending a Content-Length header.

how to get the image dimesion without using bitmap or graphics object in .net

i want to create SQL CLR integrated function from Visual C#, now my requirement is user will pass a folder path as a paramter, and the function should get all the image file from the the folder, and get its basic property like FileSize, dimension etc.. but it seems SQL project does not supports System.Drawing Namespace... as i created the same function in normal project it worked fine, as i was able to use System.Drawing Namespace, but here i cannot use, System.Drawing Namespace.. so is there any other way to get the image dimension...
below is the code i have used in my normal project.
public DataTable InsertFile(string FolderPath)
{
DataTable dt = new DataTable();
DataColumn[] col = new DataColumn[] { new DataColumn("FileName", typeof(System.String)), new DataColumn("FileSize", typeof(System.Int32)), new DataColumn("FilePath", typeof(System.String)), new DataColumn("Width", typeof(System.Int32)), new DataColumn("Height", typeof(System.Int32)) };
dt.Columns.AddRange(col);
FileInfo info= null;
Bitmap bmp = null;
foreach (String s in Directory.GetFiles(FolderPath, "*.jpg"))
{
info = new FileInfo(s);
bmp = new Bitmap(s);
DataRow dr = dt.NewRow();
dr["FileName"] = Path.GetFileName(s);
dr["FileSize"] = info.Length / 1024;
dr["FilePath"] = s;
dr["Width"] = bmp.Width;
dr["Height"] = bmp.Height;
dt.Rows.Add(dr);
}
return dt;
}
does anyone have any idea how to get image dimension without using System.Drawing Namespace.
wow never seen anyone try this before, but if using Drawing in a SQL project isn't allowed try reading the header info like this http://www.codeproject.com/KB/cs/ReadingImageHeaders.aspx
Edit included the code, with the change to remove the dependency on Size.
while (binaryReader.ReadByte() == 0xff)
{
byte marker = binaryReader.ReadByte();
ushort chunkLength = binaryReader.ReadLittleEndianInt16();
if (marker == 0xc0)
{
binaryReader.ReadByte();
int height = binaryReader.ReadLittleEndianInt16();
int width = binaryReader.ReadLittleEndianInt16();
return new int[] { width, height };
}
binaryReader.ReadBytes(chunkLength - 2);
}
Is the Image object any better for you?
System.Drawing.Image forSize = System.Drawing.Image.FromFile(s);
dr["Width"] = forSize.Width;
and so forth.
That any better or same problem?

please help me with image.GetThumbnailImage (it create very low quality image)

i use this code to create thumbnails
System.Drawing.Image.GetThumbnailImageAbort abort = new System.Drawing.Image.GetThumbnailImageAbort(this.ThumbnailCallback);
System.Drawing.Image image2 = image.GetThumbnailImage((int)Math.Round((double)wid / difference), (int)Math.Round((double)hei / difference), abort, IntPtr.Zero);
image2.Save(str2, System.Drawing.Imaging.ImageFormat.Jpeg);
image2.Dispose();
but i get this very low quality image
but it is suposed to be like this one
what i am making wrong
or how can achieve this
Your problem is not really with the GetThumbnailImage() method, but instead in how you are saving the file. You need to specify the quality level of the JPEG you are saving, or it seems it always defaults to a very low value.
Consider this code as a guide (it's from an old .NET 2.0 project; the code still works fine compiled against 4.0, but there may be a more direct method in 4.0; I've never had reason to check)
ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo jpegEncoder = null;
for (int x = 0; x < encoders.Length; x++) {
if (string.Compare(encoders[x].MimeType, "image/jpeg", true) == 0) {
jpegEncoder = encoders[x];
break;
}
}
if (jpegEncoder == null) throw new ApplicationException("Could not find JPEG encoder!");
EncoderParameters prms = new EncoderParameters(1);
prms.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 80L);
bitmap.Save(fileName, jpegEncoder, prms);
Here is another solution that should always work without fetching out the encoder. It resizes keeping relation between width & heigh ... modify for your needs.
/// <summary>
/// Resize an image with high quality
/// </summary>
public static Image ResizeImage(Image srcImage, int width)
{
var b = new Bitmap(width, srcImage.Height * width / srcImage.Width);
using (var g = Graphics.FromImage((Image)b))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(srcImage, 0, 0, b.Width, b.Height);
}
return b;
}

Create HTML table out of object array in Javascript

I am calling a web Method from javascript. The web method returns an array of customers from the northwind database. The example I am working from is here: Calling Web Services with ASP.NET AJAX
I dont know how to write this javascript method: CreateCustomersTable
This would create the html table to display the data being returned. Any help would be appreciated.
My javascript
function GetCustomerByCountry() {
var country = $get("txtCountry").value;
AjaxWebService.GetCustomersByCountry(country, OnWSRequestComplete, OnWSRequestFailed);
}
function OnWSRequestComplete(results) {
if (results != null) {
CreateCustomersTable(results);
//GetMap(results);
}
}
function CreateCustomersTable(result) {
alert(result);
if (document.all) //Filter for IE DOM since other browsers are limited
{
// How do I do this?
}
}
else {
$get("divOutput").innerHTML = "RSS only available in IE5+"; }
}
My web Method
[WebMethod]
public Customer[] GetCustomersByCountry(string country)
{
NorthwindDALTableAdapters.CustomersTableAdapter adap =
new NorthwindDALTableAdapters.CustomersTableAdapter();
NorthwindDAL.CustomersDataTable dt = adap.GetCustomersByCountry(country);
if (dt.Rows.Count <= 0)
{
return null;
}
Customer[] customers = new Customer[dt.Rows.Count];
for (int i = 0; i < dt.Rows.Count; i++)
{
NorthwindDAL.CustomersRow row = (NorthwindDAL.CustomersRow)dt.Rows[i];
customers[i] = new Customer();
customers[i].CustomerId = row.CustomerID;
customers[i].Name = row.ContactName;
}
return customers;
}
Try to look what is the result variable value in debug mode. If the structure seems the structure that i'm imagining, something like this could work:
function CreateCustomersTable(result) {
var str = '<table>';
str += '<tr><th>Id</th><th>Name</th></tr>';
for ( var i=0; i< result.length; i++){
str += '<tr><td>' + result[i].CustomerId + '</td><td>' + result[i].Name + '</td></tr>';
}
str += '</table>';
return str;
}
And then You can do somethig like this:
var existingDiv = document.getElementById('Id of an existing Div');
existingDiv.innerHTML = CreateCustomersTable(result);
I wish this help you.
Something like this, assuming you have JSON returned in the "result" value. The "container" is a div with id of "container". I'm cloning nodes to save memory, but also if you wanted to assign some base classes to the "base" elements.
var table = document.createElement('table');
var baseRow = document.createElement('tr');
var baseCell = document.createElement('td');
var container = document.getElementById('container');
for(var i = 0; i < results.length; i++){
//Create a new row
var myRow = baseRow.cloneNode(false);
//Create a new cell, you could loop this for multiple cells
var myCell = baseCell.cloneNode(false);
myCell.innerHTML = result.value;
//Append new cell
myRow.appendChild(myCell);
//Append new row
table.appendChild(myRow);
}
container.appendChild(table);
You should pass the array as JSON or XML instead of just the toString() value of it (unless that offcourse is returns either JSON oR XML). Note that JSOn is better for javascript since it is a javascript native format.
Also the person who told you that browser other then IE can not do DOM manipulation should propably have done horrible things to him/her.
If your format is JSON you can just for-loop them and create the elements and print them. (once you figured out what format your service returns we can help you better.)

Resources