How to align content in columns using spacing, tabs or padding? - c#

i'm trying to create a pdf looks like this
but when i try string padding, it looks like this in pdf file
here is the part of the c# code i tried. myExcelData is filled from excel file.
for (int i = 0; i < 15; i++)
{
Chunk cSira = new Chunk((i + 1).ToString().PadRight(10), icerikFont);
Chunk cHizmet = new Chunk(myExcelData.Tables[0].Rows[i][6].ToString().PadRight(80), icerikFont);
Chunk cAdet = new Chunk(myExcelData.Tables[0].Rows[i][1].ToString().PadRight(10), icerikFont);
Chunk cBirimFiyat = new Chunk(myExcelData.Tables[0].Rows[i][2].ToString().PadRight(20), icerikFont);
Chunk cTutar = new Chunk(myExcelData.Tables[0].Rows[i][3].ToString().PadRight(20), icerikFont);
d.Add(cSira);
d.Add(cHizmet);
d.Add(cAdet);
d.Add(cBirimFiyat);
d.Add(cTutar);
d.Add(Chunk.NEWLINE);
}

There are two ways to do this:
use a monospaced font (e.g. Courier). Currently you're using a font of which the width of the different characters is different.
download chapter 2 of my book and learn how to use TAB CHUNKS (look for that title on page 48).
Use a PdfPTable as mkl indicates in his comment (maybe you were overlooking the obvious)
If you need a code snippet for option 2, take a look at the DirectorOverview3 example. The result looks like this. If you don't understand Java, read the C# example.

Related

Write text to fixed position in paragraph with iText7

I try to write a pdf file with a header, logo and table using iText7 in c#.
I never used iText7 before and therefore I don't know how to write text in a paragraph to a fixed position.
Right now I am just using tabstops as anchors for my text. But the problem here is, when the string is too long everything following in the line will be shifted by a tabstop and the "columns" in the header aren't aligned anymore.
The following picture is what I want too achieve:
This picture shows what happens if a string gets too long (in this example I used a long username):
Here is a code snippet I use to write one line of the header:
// generate 8 tabstops to split pdf in equal sections
List<TabStop> tabStops = new List<TabStop>();
for (uint i = 0; i < 8; i++)
{
float tabSize = pageSize.GetWidth() / 8;
tabStops.Add(new TabStop(tabSize, TabAlignment.LEFT));
}
Paragraph p = new Paragraph();
p.SetFontSize(10);
// add tabstops to paragraph for text alignment
p.AddTabStops(tabStops);
// add title of header
p.Add(title1).Add("\n");
// write line one of header
p.Add("Serie: ").Add(new Tab()).Add(info.serial.ToString())
.Add(new Tab()).Add(new Tab())
.Add("Input CSV: ").Add(new Tab()).Add(info.inputFileName)
.Add(new Tab()).Add(new Tab()).Add("Out-Series: ")
.Add(info.serial.ToString()).Add("\n");
// line 2...
p.Add("User: ").Add(new Tab()).Add(info.username)
.Add(new Tab()).Add(new Tab()).Add(new Tab())
.Add("qPCR-Datei: ").Add(new Tab()).Add(info.qpcr1FileName)
.Add(new Tab()).Add(new Tab()).Add(new Tab())
.Add("STR-Out: ").Add(strFileName).Add("\n");
I hope someone can help me show me a better way of text alignment or has information where to look at.
Another nice tip would be how I can keep linebreaks in the same tab stop section. for example if a file name gets too long (s. "STR-Out: " in picture) the linebreak will be executed but the part of the filename in the new line should stay at the tab stop behind "STR-OUT: "
Instead of Tab/Tabspace use Tables and Cells so that alignment will be proper.
Create table of column 8 size (Label, Value, space , Label, Value, Space, Label, Value)
Use this sample Code.
PdfPTable table = new PdfPTable(8);
PdfPCell cell;
cell = new PdfPCell();
cell.setRowspan(2); //only if spanning needed
table.addCell(cell);
for(int aw=0;aw<8;aw++){
table.addCell("hi");
}
Thanks #shihabudheenk for pointing me in the right direction with the idea of using a table.
Just had to adjust some code to iText7.
First thing is that
Table headerTable = new Table().SetBorder(Border.NO_BORDER);
has no effect in iText7, you have to set the option for each cell individually like:
Cell cell = new Cell().SetBorder(Border.NO_BORDER);
but here is the problem that
cell.Add()
in iText7 only accepts IBlockElement as parameter so i have too use it like this:
cell.Add(new Paragraph("text");
which is pretty annoying doing that for every cell over and over again. Therefore i used a removeBorder function as suggested here
So the final code I use to build the header looks like this:
// initialize table with fixed column sizes
Table headerTable = new Table(UnitValue.CreatePercentArray(
new[] { 1f, 1.2f, 1f, 1.8f, 0.7f, 2.5f })).SetFixedLayout();
// write headerline 1
headerTable.AddCell("Serie: ").AddCell(info.serial.ToString())
.AddCell("Input CSV: ")
.AddCell(info.inputFileName)
// write remaining lines...
....
// remove boarder from all cells
removeBorder(headerTable);
private static void removeBorder(Table table)
{
foreach (IElement iElement in table.GetChildren())
{
((Cell)iElement).SetBorder(Border.NO_BORDER);
}
}

How to fill remaining space of a line in iText7

I'm trying to fill the remaining space of the last line of a paragraph using iText7 with C#:
var par = new Paragraph(text);
par.Add(c);
document.Add(par);
How can i add - char to fill the space left by the line? Something like LineSeparator(new DashedLine() but from the beginning on the last character of my paragraph instead of new line.
You can use the concept of tabs and tab stops for it. This concept is not iText-specific.
Roughly speaking you can define points (tab stops) and adding a tab would "jump" to the next point. In your case the tab stop is the end of the line and you only need one tab.
Here is a complete example that uses small dashes on the baseline as the filling. You can implement ILineDrawer yourself to customize the behavior or subclass/configure an existing implementation. The code is in Java, but to convert it to C# you basically need to do some capitalization and that's it.
Document doc = ....;
Paragraph p = new Paragraph("Hello world").add(new Tab());
ILineDrawer filling = new DashedLine();
PageSize pageSize = doc.getPdfDocument().getDefaultPageSize();
Rectangle effectivePageSize = doc.getPageEffectiveArea(pageSize);
float rightTabStopPoint = effectivePageSize.getWidth();
TabStop tabStop = new TabStop(rightTabStopPoint, TabAlignment.LEFT, filling);
p.addTabStops(tabStop);
doc.add(p);
Result looks as follows:

Excel: Losing decimal separator when converting from strings to number

I am trying to read some values from several files and save them in a new .xlsx file with different grouping. I devised a very simple setup to test different formatting and behavior with null values. I always open just-created file in Excel to see outcome. So far no problem.
However in my test-case I can achieve either: A) save the test values as they are (strings) or B) force Excel to regard them as numbers with given format (good), but lose decimal separator (very bad & strange).
I had traced problem to the last line in a code snippet below. The idea of self-assign is from another post somewhere here at SO but right now I am unable to find it.
If the line is commented-out the results are as in a string[,] contents only they are formatted as text (and Excel complains about this with "number formatted as text" message). If I uncomment it, the numbers are regarded as numbers but lose decimal separators. Also the problem might be a fact that I am in Czech Republic and decimal separator is , which might trouble Excel. Moreover, reading the values from start into a double[,] contents is out, since I need to indicate whether value is absent (with empty cell). And double?[,] contents crashes Excel...
Please, havenĀ“t you met this behavior before? I would like to 1) be able to indicate missing value and 2) have contents of cells formatted as a number, not text. Can you help me how to achieve this?
excelApp = new Excel.Application();
excelWorkBooks = excelApp.Workbooks;
excelWorkBook = excelWorkBooks.Add();
excelSheets = excelWorkBook.Sheets;
excelWorkSheet = excelSheets[1]; //Beware! Excel is one-based as opposed to a zero-based C#
string[,] contents = new string[,] { { "1,23", "2,123123123", "3,1415926535" }, { "2,15", null, "" } };
int contentsHeight = contents.GetLength(0);
int contentsWidth = contents.GetLength(1);
System.Globalization.CultureInfo currentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
string numberFormat = string.Format("0" + currentCulture.NumberFormat.NumberDecimalSeparator + "00E+00");
for (int column = 0; column < contentsWidth; column++) {
excelWorkSheet.Columns[column + 1].NumberFormat = numberFormat;
}
Excel.Range range = excelWorkSheet.Range[excelWorkSheet.Cells[1, 1], excelWorkSheet.Cells[contentsHeight, contentsWidth]];
range.Value = contents;
// range.Value = range.Value; //Problematic place
EDIT: I tryed to change NumberFormat from 0,00E+00 to something like 0,0, 0.0, #,# for the sake of test, but with no success. Either crash (decimal dot) or remains as a text.
There's no need to convert numbers to text before writing them to a cell. Excel understands numbers. A further problem is that the code is trying to set the array as the value of an entire range, as if pasting into Excel.
It's possible to set numbers, even nulls, directly using a simple loop, eg
double?[,] contents = new double?[,] { { 1.23, 2.123123123, 3.1415926535 },
{ 2.15, null, null } };
int contentsHeight = contents.GetLength(0);
int contentsWidth = contents.GetLength(1);
...
for(int i=0;i<= contentsHeight; i++)
for (int j = 0; j <= contentsWidth; j++)
excelWorkSheet.Cells[i+1,j+1].Value = contents[i,j];
Instead of using Excel through Interop though, it's better to use a package like EPPlus to generate xlsx files directly without having Excel installed. This allows generating real Excel files even on web servers, where installing Excel is impossible.
The code for this particular problem would be similar:
var file = new FileInfo("test.xlsx");
using (var pck = new ExcelPackage(file))
{
var ws = pck.Workbook.Worksheets.Add("Rules");
for(int i=0;i<= contentsHeight; i++)
for (int j = 0; j <= contentsWidth; j++)
ws.Cells[i+1,j+1].Value = contents[i,j];
pck.Save();
}
EPPlus has some convenience methods that make loading a sheet easy, eg LoadFromDataTable or LoadFromCollection. If the data came from a DataTable, creating the sheet would be as simple as:
var file = new FileInfo("test.xlsx");
using (var pck = new ExcelPackage(file))
{
var ws = pck.Workbook.Worksheets.Add("Rules");
ws.LoadFromDataTable(myTable);
pck.Save();
}
LoadFromDataTable returns an ExcelRange which allows cell formatting just like Excel Interop.

Excel Interop Open/Repair HResult exception

What I do: populate & format an Excel file using a mix of Interop and ClosedXML.
First, the file is populated via Interop, then saved, closed, then I format the cells' RichText using ClosedXML.
Unfortunately, this formatting causes Excel to view my file as "corrupt" and needs to repair it.
This is the relevant part:
var workbook = new XLWorkbook(xlsPath);
var sheet = workbook.Worksheet("Error Log");
for (var rownum = 2; rownum <= 10000; rownum++)
{
var oldcell = sheet.Cell("C" + rownum);
var newcell = sheet.Cell("D" + rownum);
var oldtext = oldcell.GetFormattedString();
if(string.IsNullOrEmpty(oldtext.Trim()))
break;
XlHelper.ColorCellText(oldcell, "del", System.Drawing.Color.Red);
XlHelper.ColorCellText(newcell, "add", System.Drawing.Color.Green);
}
workbook.Save();
And the colouring method:
public static void ColorCellText(IXLCell cel, string tagName, System.Drawing.Color col)
{
var rex = new Regex("\\<g\\sid\\=[\\sa-z0-9\\.\\:\\=\\\"]+?\\>");
var txt = cel.GetFormattedString();
var mc = rex.Matches(txt);
var xlcol = XLColor.FromColor(col);
foreach (Match m in mc)
{
txt = txt.Replace(m.Value, "");
txt = txt.Replace("</g>", "");
}
var startTag = string.Format("[{0}]", tagName);
var endTag = string.Format("[/{0}]", tagName);
var crt = cel.RichText;
crt.ClearText();
while (txt.Contains(startTag) || txt.Contains(endTag))
{
var pos1 = txt.IndexOf(startTag);
if (pos1 == -1)
pos1 = 0;
var pos2 = txt.IndexOf(endTag);
if (pos2 == -1)
pos2 = txt.Length - 1;
var txtLen = pos2 - pos1 - 5;
crt.AddText(txt.Substring(0, pos1));
crt.AddText(txt.Substring(pos1 + 5, txtLen)).SetFontColor(xlcol);
txt = txt.Substring(pos2 + 6);
}
if (!string.IsNullOrEmpty(txt))
crt.AddText(txt);
}
Error in file myfile.xlsx
The following repairs were performed: _x000d__x000a__x000d__x000a_
Repaired records:
string properties of /xl/sharedStrings.xml-Part (strings)
I've been through all the xmls looking for clues. In the affected sheet, in comparison view of Productivity Tool, some blocks appear as inserted in the repaired file and deleted in the corrupt one, although nothing significant seemed changed - except for one thing: the style attribute of that cell. Here an example:
<x:c r="AA2" s="59">
<x:f>
(IFERROR(VLOOKUP(G2,Legende!$A$42:$B$45,2,FALSE),0))
</x:f>
</x:c>
I have checked the styles.xml for style 59, but there is none. In the repaired file, this style has been changed to 14, which in my styles.xml is listed as a number format.
Unfortunately, a global search/replace of these invalid style indexes did not resolve the issue.
Seeing the things going on here with corrupt indexes, renamed xmls, invalid named ranges etc., I took a different route: not to use interop at all, maybe the corruption was caused by Excel in the first place and the coloring was only the last straw.
Using ClosedXml only:
Wow. Just wow. This makes it even worse. I commented out the colouring part since without that, Interop produced a readable file without errors, so that's what I expect of ClosedXml too.
This is how I open the file and address the worksheet with ClosedXml:
var wb= new XLWorkbook(xlsPath);
var errors = wb.Worksheet("Error Log");
This is how I write the values into the file:
errors.Cell(zeile, 1).SetValue(fname);
With zeile being a simple int counter.
I then dare to set a column width:
errors.Column(2).Width = 50;
errors.Column(3).Width = 50;
errors.Column(4).Width = 50;
As well as setting some values in another sheet in exactly the same fashion before saving with validation.
wb.Save(true);
wb.Dispose();
Lo and behold: The validation throws errors:
Attribute 'name' should have unique value. Its current value 'Legende duplicates with others.
Attribute 'sheetId' should have unique value. Its current value '4' duplicates with others.
A couple more errors like attribute 'top' having invalid value '11.425781'.
Excel cannot open the file directly, must repair it. My Sheet "Legende" is now empty and the first sheet instead of third, and I get an additional fourth sheet "Restored_Table1" which contains my original "Legende" contents.
What the hell is going on with this file??
New attempt: re-create the Excel template from scratch - in LibreOffice.
I now think that the issue is entirely misleading. If I use the newly created file from LibreOffice, the validation causes a System.OutOfMemory exception due to too many validation errors. Opening in Excel requires repair, gives additional sheet and so forth.
Creating in LibreOffice, then opening in Excel, saving, then using that file as template produces a much better result albeit not perfect yet.
Since I copied parts over from the old Excel file into LO while creating the new file, I assume some corrupt remnant got copied over.
I cannot shake the feeling that this is the file itself after all and has nothing to do with how I edit it!
Will post updaate tomorrow.
OK. Stuff this.
I created a completely fresh file with LibreOffice, making sure not to copy over anything at all from the original file, and I ditched Interop in favour of ClosedXml.
=> This produced a corrupt file in which my first sheet was cleared and its contents move to a "Restored_Table1".
After I opened my fresh new template with Excel via Open/Repair and saved it, the resulting, uncoloured file was NOT corrupt.
=> Colouring it produces the "original" corruption, all sheets intact.
ClosedXml seems to be marginally slower than Interop but at this point I couldn't care less. I guess we will have to live with the "corrupt" message and just get on with it.
I hate xlsx.

Bold a single word within a sentence with iTextSharp

Is it possible to bold a single word within a sentence with iTextSharp? I am trying to bold several individual words without having to break the string into individual phrases.
I want to this type of out put
Eg:REASON(S) FOR CANCELLATION: See Statutory reason(s) designated by Code No(s) 1 on the reverse side hereof.
My actual output is below
Eg:REASON(S) FOR CANCELLATION: See Statutory reason(s) designated by Code No(s) 1 on the reverse side hereof.
Code
pdftb4 = new PdfPTable(1);
pdftb4.WidthPercentage = 100;
width = new float[1];
width[0] = 0.7F;
pdftb4.SetWidths(width);
pdfcel4 = new PdfPCell(new Phrase("\n REASON(S) FOR CANCELLATION: See Statutoryreason(s) designated by Code No(s) 1 on the reverse side hereof", docBlackFont10));
pdfcel4.Border = 0;
pdfcel4.HorizontalAlignment = Element.ALIGN_LEFT;
pdftb4.AddCell(pdfcel4);
objDocument.Add(pdftb4);
somebody please help me
The way to accomplish what you are trying is with Chunks. A simple example is:
var normalFont = FontFactory.GetFont(FontFactory.HELVETICA, 12);
var boldFont = FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 12);
var phrase = new Phrase();
phrase.Add(new Chunk("REASON(S) FOR CANCELLATION:", boldFont));
phrase.Add(new Chunk(" See Statutoryreason(s) designated by Code No(s) 1 on the reverse side hereof", normalFont));
Can also create font like
Font verdanaBold = FontFactory.GetFont("Verdana", 7f, Font.BOLD);
Maybe this link Bolding with Rich Text Values in iTextSharp will help?
Not sure if it fits your scenario completely but might get you where you need to go.

Categories