Cannot get SpacingAfter to work on image with iTextSharp - c#

Hi I would like to add some extra space after a image in my iTextSharp generated pdf document. But for some reason whatever I try to do it will not prevent my text from wrapping
Example image
As you can see the "to read everyth..." does not indent like the rest
This is the codesnip that should do this:
var brevityBox = iTextSharp.text.Image.GetInstance("http://" + domain + "/ImageGen.ashx?Text=" + brevityScore + "&FontSize=120&&FontStyle=Bold&Font=Calibri&Align=Center&image=/media/images/PDF/BrevityBox.jpg");
brevityBox.ScaleToFit(80f, 220f);
brevityBox.Alignment = Image.TEXTWRAP;
brevityBox.SpacingAfter = 460f;
doc.Add(brevityBox);
Chunk c3 = new Chunk(brevityText, FontFactory.GetFont("Verdana", 12, Font.NORMAL)); ;
Paragraph p3 = new Paragraph();
p3.IndentationLeft = 20;
p3.IndentationRight = 20;
p3.Alignment = Element.ALIGN_LEFT;
p3.Add(c3);
doc.Add(p3);
Just to prove the point the SpacingAfter is 460 points.
IndentationRight
works fine
Any ideas?

I believe that iTextSharp is not even using the SpacingAfter property. I modified the method
iTextSharp.text.pdf.PdfDocument.Add(iTextSharp.text.Image image){}
to this:
if (imageEnd < 0 || imageEnd < currentHeight + image.ScaledHeight + diff + image.SpacingAfter)
{
imageEnd = currentHeight + image.ScaledHeight + diff + image.SpacingAfter;
}
From v5.2.1 it was line 2244 in PdfDocument.cs

Related

How to switch different iText document renderers in the same page

The case is that at the beginning of Page, Elements should be drawn in one column, and after that, elements in the same page should be drawn in two columns.
So far, according to the iText example "c02e10_jekyllhydev6", I just can switch different renderers between pages, which means first applying DocumentRenderer, then add AreaBreak of Next Page, and applying ColumnDocumentRenderer in the new page.
The code:
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
Document doc = new Document(pdfDoc, PageSize.A4);
doc.SetMargins(36, 36, 36, 36);
Paragraph p = new Paragraph();
p.SetBorder(new SolidBorder(0.5f));
for (int i = 1; i <= 500; i++)
{
p.Add(new Text(i + " "));
}
doc.Add(p);
**doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));**
PageSize ps = PageSize.A4;
var effectiveArea = doc.GetPageEffectiveArea(PageSize.A4);
float columnHeight = effectiveArea.GetHeight();
//Define column areas
Rectangle[] columns = new Rectangle[] {
new Rectangle(36, 36, 200, columnHeight),
new Rectangle(36 + 200 + 20, 36, effectiveArea.GetWidth()- 200 - 20, columnHeight)
};
ColumnDocumentRenderer renderer1 = new ColumnDocumentRenderer(doc, new Rectangle[] { columns[0] });
doc.SetRenderer(renderer1);
**doc.Add(new AreaBreak(AreaBreakType.LAST_PAGE));**
Paragraph p1 = new Paragraph();
p1.SetBorder(new SolidBorder(0.5f));
for (int i = 1; i <= 500; i++)
{
p1.Add(new Text(i + " "));
}
doc.Add(p1);
ColumnDocumentRenderer renderer2 = new ColumnDocumentRenderer(doc, new Rectangle[] { columns[1] });
doc.SetRenderer(renderer2);
Paragraph p2 = new Paragraph();
for (int i = 1; i <= 1000; i++)
{
p2.Add(new Text(i + " "));
}
doc.Add(p2);
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
DocumentRenderer renderer3 = new DocumentRenderer(doc);
doc.SetRenderer(renderer3);
doc.Add(new AreaBreak(AreaBreakType.LAST_PAGE));
Paragraph p3 = new Paragraph();
for (int i = 1; i <= 1000; i++)
{
p3.Add(new Text(i + " "));
}
doc.Add(p3);
doc.Close();
If no AreaBreak added to the document, the contents with different renderers will be overlapped.
From Alexey's comment in this case, It seems possible that switching different renderers in the same page without content overlapping.
To handle this appropriately, you would have to update currentArea of the renderer you are going to switch to with the currentArea of the previous renderer you have just finished working with. You can do that by extending the standard provided renderers, or calling renderer.getCurrentArea() and modifying the bBox.
But I don't know how to achieve it according to above guides.
There is no need in ColumnDocumentRenderer in your case - that renderer was created for cases when you want to lay out your content in columns in scope of one page and then move on to the next page etc. In your case you are laying out the content in columns that span many pages and that is equivalent to just setting proper margins to the DocumentRenderer instead of passing the columns to ColumnDocumentRenderer.
To switch the renderers ad-hoc you indeed need to tweak their currentArea field and currentPageNumber as well. Please note that the solution below is not guaranteed to work in all complex cases and in all iText versions. This is merely a guideline on how to implement what you need and not a complete solution to all cases.
The helper renderer class we need is very simple - it allows customizing current area:
private class ExtendedDocumentRenderer : DocumentRenderer {
public ExtendedDocumentRenderer(Document document, RootLayoutArea currentArea) : base(document) {
this.currentArea = new RootLayoutArea(currentArea.GetPageNumber(), currentArea.GetBBox().Clone());
this.currentPageNumber = this.currentArea.GetPageNumber();
}
}
Now I've adapted your code to get rid of the ColumnDocumentRenderer and use plain DocumentRenderer instead. You just need to tweak document margins and recalculate current area (the space that is left on the page) properly:
Document doc = new Document(pdfDocument);
Paragraph p = new Paragraph();
p.SetBorder(new SolidBorder(0.5f));
for (int i = 1; i <= 500; i++)
{
p.Add(new Text(i + " "));
}
doc.Add(p);
RootLayoutArea endOfFullWidthContentArea = (RootLayoutArea) doc.GetRenderer().GetCurrentArea();
ExtendedDocumentRenderer renderer1 = new ExtendedDocumentRenderer(doc,
new RootLayoutArea(endOfFullWidthContentArea.GetPageNumber(), endOfFullWidthContentArea.GetBBox().Clone().SetWidth(200)));
doc.SetRightMargin(doc.GetRightMargin() + doc.GetPageEffectiveArea(PageSize.A4).GetWidth() - 200);
doc.SetRenderer(renderer1);
Paragraph p1 = new Paragraph();
p1.SetBorder(new SolidBorder(0.5f));
for (int i = 1; i <= 500; i++)
{
p1.Add(new Text(i + " "));
}
doc.Add(p1);
ExtendedDocumentRenderer renderer2 = new ExtendedDocumentRenderer(doc,
new RootLayoutArea(endOfFullWidthContentArea.GetPageNumber(),
endOfFullWidthContentArea.GetBBox().Clone().MoveRight(200)
.SetWidth(endOfFullWidthContentArea.GetBBox().GetWidth() - 200)));
doc.SetRightMargin(36);
doc.SetLeftMargin(200 + 36);
doc.SetRenderer(renderer2);
Paragraph p2 = new Paragraph();
for (int i = 1; i <= 1000; i++)
{
p2.Add(new Text(i + " "));
}
doc.Add(p2);
// Compute which free area is lower in the document
RootLayoutArea areaColumn1 = (RootLayoutArea) renderer1.GetCurrentArea();
RootLayoutArea areaColumn2 = (RootLayoutArea) renderer2.GetCurrentArea();
RootLayoutArea downArea = areaColumn1.GetPageNumber() > areaColumn2.GetPageNumber() ? areaColumn1 :
(areaColumn1.GetPageNumber() < areaColumn2.GetPageNumber() ? areaColumn2 :
(areaColumn1.GetBBox().GetTop() < areaColumn2.GetBBox().GetTop() ? areaColumn1 : areaColumn2));
doc.SetMargins(36, 36, 36, 36);
DocumentRenderer renderer3 = new ExtendedDocumentRenderer(doc,
new RootLayoutArea(downArea.GetPageNumber(), downArea.GetBBox().Clone().SetX(36).SetWidth(doc.GetPageEffectiveArea(PageSize.A4).GetWidth())));
doc.SetRenderer(renderer3);
Paragraph p3 = new Paragraph();
for (int i = 1; i <= 1000; i++)
{
p3.Add(new Text(i + " "));
}
doc.Add(p3);
doc.Close();
Result looks as follows:

How to put multiple lines in NetTelegramBot KeybrardMarkup?

I'm making a telegram bot with c#.
I want to read a number of names (not more than 20 tough thy could be less) and give them to the user as keyboardMarkup. With a one dimensional array they go all in one line and it's unreadable. I wanted to make 4 line with 5 names so i tried a 4x5 array. But i get this error
Error CS0029 Cannot implicitly convert type 'NetTelegramBotApi.Types.KeyboardButton[,]' to 'NetTelegramBotApi.Types.KeyboardButton[][]'
if (text == "/lista")
{
// Read a text file line by line.
listapz = System.IO.File.ReadAllLines("listapz.txt");
string selezione = "Seleziona il paziente:";
int i = 0;
int x = 0;
int y = 0;
var arrays = new KeyboardButton[4,5];
for (i = 0; i < listapz.Length; i++)
{
y = i / 5;
x = i - (y * 5);
arrays[y,x] = new KeyboardButton("$" + (i + 1) + " " + listapz[i]);
selezione += "\n$" + (i + 1) + " " + listapz[i] + "";
}
var keyb = new ReplyKeyboardMarkup()
{
Keyboard = arrays,
OneTimeKeyboard = true,
ResizeKeyboard = true
};
var reqAction = new SendMessage(update.Message.Chat.Id, selezione) { ReplyMarkup = keyb };
bot.MakeRequestAsync(reqAction).Wait();
Console.WriteLine(reqAction);
continue;
}
any solution?
You can create new one dimensional array for every line
Then
keyb.row(first_array) //you need get only values in python it will like keyb.row(*array)
keyb.row(second_array) //and so on
and add all keyboards in one reply_markup=keyb

Visual Studio Pie Chart - Generate Percentages

I am generating pie charts by pulling data from SQL tables. The data is an accumulation of hours for different projects.
The charts are building fine but I would like the pie graph to display the respective percentage of each slice of the pie that is being used when the graphs are generated.
I am creating the charts in the method shown below.
// Fill Chart with usable data
for (int i = 0; i <= index - 1; i++)
{
Chart6.Series["Series1"].Points.AddXY(project[i], projTime[i]);
}
Chart6.Series[0]["PieLabelStyle"] = "Disabled";
I was hoping there would be a simple command I could pass in the code behind to create this but scouring the web has provided no usable results. The best I have found is this method but these are not options in Visual Studio Express 2013.
Prepare like this:
Series S = Chart6.Series["Series1"];
S.ChartType = SeriesChartType.Pie;
S.IsValueShownAsLabel = true;
S["PieLabelStyle"] = "Outside";
Create DataPoints like this if you know the total:
DataPoint p = new DataPoint(project[i], projTime[i]);
// check your data type for the calculation!
p.Label = p.YValues[0] + "h =\n" +
(100d * p.YValues[0] / total).ToString("00.00") + "%\n"; // my format
If you don't know the total, first set the points, then calculate the total and finally set the label:
for (int i = 0; i <= index - 1; i++)
{
S.Points.AddXY(project[i], projTime[i]);
}
// calculate the total:
double total = S.Points.Sum(dp => dp.YValues[0]);
// now we can set the percentages
foreach (DataPoint p in S.Points)
{
p.Label = p.YValues[0] + "h =\n" +
(100d * p.YValues[0] / total).ToString("00.00") + "%\n"; // my format
}
chart1.Titles.Add(S.Points.Count + " projects, total " +
total.ToString("###,##0") + " hours");
chart1.Titles[0].Font = new System.Drawing.Font("Arial", 14f);

Weird behavior when setting a row's height on EPPlus

I am building an Excel file with EEPlus under MVC-5 C# application. Everything goes as planned until I set a height on a row (so an image can fit).
I load de images and set the height on column 20, like so:
Image cfPhoto = null;
Bitmap cfBm = null;
ExcelPicture pictureCf = null;
var photoInitialColumn = 0;
i++;
completedFormPhotos.ForEach(delegate(CompletedFormPhoto cfP)
{
cfPhoto = Image.FromFile(HostingEnvironment.MapPath("~/Content/Images/FormPhotos/" + cfP.Id + ".jpg"));
cfBm = new Bitmap(cfPhoto, new Size(215, 170));
pictureCf = worksheet.Drawings.AddPicture(cfP.Id.ToString(), cfBm);
pictureCf.SetPosition(i+1, 0, photoInitialColumn, 10);
worksheet.Cells[_alpha[photoInitialColumn] + (i + 3) + ':' + _alpha[photoInitialColumn + 1] + (i + 3)].Merge = true;
worksheet.Cells[_alpha[photoInitialColumn] + (i + 3) + ':' + _alpha[photoInitialColumn + 1] + (i + 3)].Value = cfP.comment;
worksheet.Cells[_alpha[photoInitialColumn] + (i + 3) + ':' + _alpha[photoInitialColumn + 1] + (i + 3)].Style.WrapText = true;
photoInitialColumn += 2;
//HERE I SET THE HEIGHT. At this point, i == 18
worksheet.Row(i+2).Height = 180;
});
But, I have a company logo at the top of the Excel file (A1 cell) which gets resized as well (on height). That is defined like this:
Image _keyLogo = Image.FromFile(HostingEnvironment.MapPath("~/Content/Images/key_logo.png"));
var pictureLogo = worksheet.Drawings.AddPicture("Logo Key Quimica", _keyLogo);
pictureLogo.SetPosition(0, 0, 0, 0);
Resulting on this:
Any help would be really appreciated.
Here is the excel file in question.
It comes down to the EditAs setting of the picture logo. By default it will be set to OneCell but setting it to TwoCell I believe will solve your problem. The documentation on it (looking at EPP 4.0.1) is rather cryptic but it basically says this setting will tell the drawing to maintain its original row/column position and size. The names seem a bit counter intuitive though.
I had to guess what your code looks like (let me know if I got it wrong) and I was able to reproduce the problem you were having and then solve with the EditAs setting:
[TestMethod]
public void Image_Stretch_Test()
{
//http://stackoverflow.com/questions/27873762/weird-behavior-when-setting-a-rows-height-on-epplus
var existingFile = new FileInfo(#"c:\temp\temp.xlsx");
if (existingFile.Exists)
existingFile.Delete();
using (var package = new ExcelPackage(existingFile))
{
var workbook = package.Workbook;
var worksheet = workbook.Worksheets.Add("newsheet");
var _keyLogo = Image.FromFile("C:/Users/Ernie/Desktop/key_logo.png");
var pictureLogo = worksheet.Drawings.AddPicture("Logo Key Quimica", _keyLogo);
pictureLogo.SetPosition(0, 0, 0, 0);
pictureLogo.EditAs = eEditAs.TwoCell; //REMOVE THIS TO SHOW THE STRETCH PROBLEM
var cfPhoto = Image.FromFile("C:/Users/Ernie/Desktop/Main_Pic.png");
var cfBm = new Bitmap(cfPhoto, new Size(215, 170));
var pictureCf = worksheet.Drawings.AddPicture("Main_Pic", cfBm);
pictureCf.SetPosition(10, 0, 0, 0);
worksheet.Row(11).Height = 280;
package.Save();
}
}
The other thing that fix it was add the logo AFTER the row resize but not sure if that is practical to what you are trying to do.

ABCPDF Calculation of header size and position.

The problem is a header file, which I have to include on each page of the pdf file generated by abcpdf.
The header file contains more than one image file and several lines of text, which varies from case to case.
The problem is that I do not know how to calculate the size of the header. I need to have its size to allocate the rectangle positions to put the rest of html file on each page together with header. I am using C#.
First off you need to create your document with enough space at the top to allow a header to be added. The settings below are for a normal A4 document with a header of about 1/5 of the page. Remember the coordinates on a PDF are from the bottom right not the top left..
//Setting to create the document using ABCPdf 8
var theDoc = new Doc();
theDoc.MediaBox.String = "A4";
theDoc.HtmlOptions.PageCacheEnabled = false;
theDoc.HtmlOptions.ImageQuality = 101;
theDoc.Rect.Width = 719;
theDoc.Rect.Height = 590;
theDoc.Rect.Position(2, 70);
theDoc.HtmlOptions.Engine = EngineType.Gecko;
The code below out puts a header across each page on the document, with a header image then a colored box under the image with some custom text in.
The header image in this case is 1710 x 381 to keep the resolution of the image as high as possible to stop it looking fuzzy when printed.
private static Doc AddHeader(Doc theDoc)
{
int theCount = theDoc.PageCount;
int i = 0;
//Image header
for (i = 1; i <= theCount; i++)
{
theDoc.Rect.Width = 590;
theDoc.Rect.Height = 140;
theDoc.Rect.Position(0, 706);
theDoc.PageNumber = i;
string imagefilePath = HttpContext.Current.Server.MapPath("/images/pdf/pdf-header.png");
Bitmap myBmp = (Bitmap)Bitmap.FromFile(imagefilePath);
theDoc.AddImage(myBmp);
}
//Blue header box
for (i = 2; i <= theCount; i++)
{
theDoc.Rect.String = "20 15 590 50";
theDoc.Rect.Position(13, 672);
System.Drawing.Color c = System.Drawing.ColorTranslator.FromHtml("#468DCB");
theDoc.Color.Color = c;
theDoc.PageNumber = i;
theDoc.FillRect();
}
//Blue header text
for (i = 2; i <= theCount; i++)
{
theDoc.Rect.String = "20 15 586 50";
theDoc.Rect.Position(25, 660);
System.Drawing.Color cText = System.Drawing.ColorTranslator.FromHtml("#ffffff");
theDoc.Color.Color = cText;
string theFont = "Century Gothic";
theDoc.Font = theDoc.AddFont(theFont);
theDoc.FontSize = 14;
theDoc.PageNumber = i;
theDoc.AddText("Your Text Here");
}
return theDoc;
}

Categories