Using Angus J Clipper library to outline GraphicsPath text in vb.net - vb.net

I am creating text in a GDI+ GraphicsPath, using DrawString, and then outputting this to PDF as a path.
This is all working perfectly at the moment.
The time I have an issue is when the chosen font causes the outlines to overlap each other. I have an image example though being a new user i can't upload it... (seems pointless..?..)
I found a library and also someone who has achieved the same as what I am looking for in this blog
I have converted the code snippet to vb.net though I keep getting an empty solution from the library.
Has anybody else managed to pass in a graphicsPath containing a string and retrieve outlined text using this or a similar library?

Here's some C# code that works ...
using ClipperLib;
static public void PathToPolygon(GraphicsPath path, Polygons polys, Single scale)
{
GraphicsPathIterator pathIterator = new GraphicsPathIterator(path);
pathIterator.Rewind();
polys.Clear();
PointF[] points = new PointF[pathIterator.Count];
byte[] types = new byte[pathIterator.Count];
pathIterator.Enumerate(ref points, ref types);
int i = 0;
while (i < pathIterator.Count)
{
Polygon pg = new Polygon();
polys.Add(pg);
do {
IntPoint pt = new IntPoint((int)(points[i].X * scale), (int)(points[i].Y * scale));
pg.Add(pt);
i++;
}
while (i < pathIterator.Count && types[i] != 0);
}
}
static private PointF[] PolygonToPointFArray(Polygon pg, float scale)
{
PointF[] result = new PointF[pg.Count];
for (int i = 0; i < pg.Count; ++i)
{
result[i].X = (float)pg[i].X / scale;
result[i].Y = (float)pg[i].Y / scale;
}
return result;
}
private void DrawBitmap()
{
Font f = new Font("Arial", 90);
Pen myPen = new Pen(Color.FromArgb(196, 0xC3, 0xC9, 0xCF), (float)0.6);
SolidBrush myBrush = new SolidBrush(Color.FromArgb(127, 0xDD, 0xDD, 0xF0));
path.Reset();
Polygons polys;
path.AddString("ABC", f.FontFamily, (int)f.Style, f.Size, new Point(100, 100), null);
path.Flatten();
//scale all points up by 100 because Clipper uses integer coordinates
PathToPolygon(path, polys, 100);
path.Reset();
//offset polys remembering to multiply delta by scaling amount ...
polys = Clipper.OffsetPolygons(polys, 7 * 100, JoinType.jtRound);
for (int i = 0; i < polys.Count(); i++)
{
//reverses scaling ...
PointF[] pts2 = PolygonToPointFArray(polys[i], 100);
path.AddPolygon(pts2);
}
newgraphic.FillPath(myBrush, path);
newgraphic.DrawPath(myPen, path);
}

Related

Using Drawing to create a graphic verification code does not display

I am developing an API tool for generating verification codes. I use the Drawing package. I have found some methods, but why can't I generate the graphic verification codes I need? Where am I doing wrong?
public void Output(HttpResponse objHttpResponse)
{
using (Bitmap bitmap = this.GetImage())
{
if (bitmap != null)
{
using (MemoryStream ms= new MemoryStream())
{
bitmap .Save(ms, ImageFormat.Jpeg);
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.ContentType = "image/Jpeg";
HttpContext.Current.Response.BinaryWrite(ms.ToArray());
HttpContext.Current.Response.Flush();
HttpContext.Current.Response.End();
}
}
}
}
I have a complete code for generating verification code using Bitmap, you can refer to the following:
Controller:
[ApiController]
public class CaptchaController : Controller
{
[Route("get_captcha")]
public Object VerifyCode()
{
string code = "";
Bitmap bitmap = Captcha.CreateCaptcha(out code);
MemoryStream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Gif);
return File(stream.ToArray(), "image/gif");
}
}
API to generate verification code:
public class Captcha
{
public static Bitmap CreateCaptcha(out string code)
{
//Create a Bitmap object and draw
Bitmap bitmap = new Bitmap(200, 60);
Graphics graph = Graphics.FromImage(bitmap);
graph.FillRectangle(new SolidBrush(Color.White), 0, 0, 200, 60);
Font font = new Font(FontFamily.GenericSerif, 48, FontStyle.Bold, GraphicsUnit.Pixel);
Random r = new Random();
string letters = "0123456789";
StringBuilder sb = new StringBuilder();
//Add random 4 numbers
for (int x = 0; x < 4; x++)
{
string letter = letters.Substring(r.Next(0, letters.Length - 1), 1);
sb.Append(letter);
graph.DrawString(letter, font, new SolidBrush(Color.Black), x * 38, r.Next(0, 15));
}
code = sb.ToString();
//Confuse the background
Pen linePen = new Pen(new SolidBrush(Color.Black), 2);
for (int x = 0; x < 6; x++)
graph.DrawLine(linePen, new Point(r.Next(0, 199), r.Next(0, 59)), new Point(r.Next(0, 199), r.Next(0, 59)));
return bitmap;
}
}
Result:

A better solution for Itext 7 Text fitting

I have a project that used Itext 5 and worked as intended.
Program had to put userInput in certain 'Chunks' inside paragraphs. Paragraphs have unmovable (chunks)words per line, and the userInput should scale in the space reserved for the userInput inside paragraph.
Old Project had the following code(made as example)
public class Oldway {
static final transient Font bold2 = FontFactory.getFont("Times-Roman", 10.0f, 1);
public static void main(String[] args) {
Document document = new Document();
document.setPageSize(PageSize.A4);
try {
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(new File("itext5.pdf")));
document.open();
Paragraph title = new Paragraph("Title of doc");
title.setAlignment(1);
document.add(title);
Paragraph dec= new Paragraph();
Chunk ch01 = new Chunk("Prev text ");
dec.add(ch01);
Chunk ch02 = new Chunk(getEmptySpace(42));
dec.add(ch02);
Chunk ch03 = new Chunk(" next Text");
dec.add(ch03);
document.add(dec);
float y = writer.getVerticalPosition(false);
float x2 = document.left() + ch01.getWidthPoint();
float x3 = x2 + ch02.getWidthPoint();
getPlainFillTest("Text to insert", document, y, x3, x2, writer, false);
document.close();
writer.flush();
} catch (FileNotFoundException | DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Chunk getEmptySpace(int size) {
Chunk ch = new Chunk();
for(int i = 0;i<=size;i++) {
ch.append("\u00a0");
}
return new Chunk(ch);
}
public static void getPlainFillTest(String str,Document document,float y, float x1pos,
float x2pos, PdfWriter writer,boolean withTab) {
if(str.isEmpty() || str.isBlank()) {
str = "________";
}
Rectangle rec2 = null;
if(!withTab)
rec2 = new Rectangle(x2pos, y, x1pos-2,y+10);
else {
rec2 = new Rectangle(x2pos+35, y, x1pos+33,y+10);
}
BaseFont bf = bold2.getBaseFont();
PdfContentByte cb = writer.getDirectContent();
float fontSize = getMaxFontSize(bf, str,(int)rec2.getWidth(), (int)rec2.getHeight());
Phrase phrase = new Phrase(str, new Font(bf, fontSize));
ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, phrase,
// center horizontally
(rec2.getLeft() + rec2.getRight()) / 2,
// shift baseline based on descent
rec2.getBottom() - bf.getDescentPoint(str, fontSize),0);
cb.saveState();//patrulaterul albastru
cb.setColorStroke(Color.BLUE);
cb.rectangle(rec2.getLeft(), rec2.getBottom(), rec2.getWidth(), rec2.getHeight());
cb.stroke();
cb.restoreState();
}
//stackoverflow solution
private static float getMaxFontSize(BaseFont bf, String text, int width, int height){
// avoid infinite loop when text is empty
if(text.isEmpty()){
return 0.0f;
}
float fontSize = 0.1f;
while(bf.getWidthPoint(text, fontSize) < width){
fontSize += 0.1f;
}
float maxHeight = measureHeight(bf, text, fontSize);
while(maxHeight > height){
fontSize -= 0.1f;
maxHeight = measureHeight(bf, text, fontSize);
};
return fontSize;
}
public static float measureHeight(BaseFont baseFont, String text, float fontSize)
{
float ascend = baseFont.getAscentPoint(text, fontSize);
float descend = baseFont.getDescentPoint(text, fontSize);
return ascend - descend;
}}
Now I'm trying to do the same thing in IText 7 and ...is not that easy!
I manage to create a working code, but its messy, and some things don't get the right coordinates. The Itext7 code(made as example):
public class Newway {
public static void main(String[] args) {
PdfWriter writer;
try {
writer = new PdfWriter(new File("test2.pdf"));
PdfDocument document = new PdfDocument(writer);
document.getDocumentInfo().addCreationDate();
document.getDocumentInfo().setTitle("Title");
document.setDefaultPageSize(PageSize.A4);
Document doc = new Document(document);
doc.setFontSize(12);
Paragraph par = new Paragraph();
Text ch01 = new Text("Prev Text ");
par.add(ch01);
Paragraph space = new Paragraph();
space.setMaxWidth(40);
for(int i=0;i<40;i++) {
par.add("\u00a0");
space.add("\u00a0");
}
Text ch02 = new Text(" next text");
par.add(ch02);
doc.add(par);
Paragraph linePara = new Paragraph().add("Test from UserInput")
.setTextAlignment(TextAlignment.CENTER).setBorder(new DottedBorder(1));
float width = doc.getPageEffectiveArea(PageSize.A4).getWidth();
float height = doc.getPageEffectiveArea(PageSize.A4).getHeight();
IRenderer primul = ch01.createRendererSubTree().setParent(doc.getRenderer());
IRenderer spaceR = space.createRendererSubTree().setParent(doc.getRenderer());
LayoutResult primulResult = primul.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
LayoutResult layoutResult = spaceR.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
Rectangle primulBox = ((TextRenderer) primul).getInnerAreaBBox();
Rectangle rect = ((ParagraphRenderer) spaceR).getInnerAreaBBox();
float rwidth = rect.getWidth();
float rheight = rect.getHeight();
float x = primulBox.getWidth()+ doc.getLeftMargin();
float y = rect.getY()+(rheight*2.05f);//rect.getY() is never accurate, is always below the paragraph. WHY ??
Rectangle towr = new Rectangle(x, y, rwidth, rheight*1.12f);//rheight on default is way too small
PdfCanvas pdfcanvas = new PdfCanvas(document.getFirstPage());
Canvas canvas = new Canvas(pdfcanvas, towr);
//from theinternet
float fontSizeL = 1;
float fontSizeR = 14;
while (Math.abs(fontSizeL - fontSizeR) > 1e-1) {
float curFontSize = (fontSizeL + fontSizeR) / 2;
linePara.setFontSize(curFontSize);
// It is important to set parent for the current element renderer to a root renderer
IRenderer renderer = linePara.createRendererSubTree().setParent(canvas.getRenderer());
LayoutContext context = new LayoutContext(new LayoutArea(1, towr));
if (renderer.layout(context).getStatus() == LayoutResult.FULL) {
// we can fit all the text with curFontSize
fontSizeL = curFontSize;
} else {
fontSizeR = curFontSize;
}
}
canvas.add(linePara);
new PdfCanvas(document.getFirstPage()).rectangle(towr).setStrokeColor(ColorConstants.BLACK).stroke();
canvas.close();
doc.close();
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}
The questions are
Is there a better, more elegant way to do this ?
Why rect.getY() is always below the paragraph? and how do I get Y to match the Paragraph real Y coorinate ?
Why the default 'rheight' is always too small? but (rheight*1.1f) works ?
(Optional) How do I set tab() space size in IText 7 ?
This way is quite good because it's taking into account all the possible model element settings and implications of the layout process. Your iText 5 alternative was good enough for basic case of Latin-based text without any modifications on the visual side. The iText 7 code you have is much more flexible and will still work if you use more complex layout settings, complex scripts etc. Also I see the iText 5 code is 105 lines in your example while the iText 7 code is 80 lines.
You are adding some magic +(rheight*2.05f); here while in reality what you are missing here is that when you draw via Canvas you don't have your margins defined anymore, so what you really need instead of rect.getY()+(rheight*2.05f); is rect.getY() + doc.getBottomMargin()
The issue comes from the fact that you are calculating rheight as renderer.getInnerAreaBBox() while this calculation does not take into account default margins that are applied to a paragraph. Margins are included into the occupied area but not the inner area bbox. To fix that, use renderer.getOccupiedArea().getBBox() instead. In this case there is not need to multiply rheight by a coefficient anymore.
The visual result is slightly different now but there is no magic constants anymore. Depending on what you are trying to really achieve you can tune the code further (add some margins here and there etc). But the code adapts well to the change in the user text.
Visual result before:
Visual result after:
Resultant code:
PdfWriter writer;
try {
writer = new PdfWriter(new File("test2.pdf"));
PdfDocument document = new PdfDocument(writer);
document.getDocumentInfo().addCreationDate();
document.getDocumentInfo().setTitle("Title");
document.setDefaultPageSize(PageSize.A4);
Document doc = new Document(document);
doc.setFontSize(12);
Paragraph par = new Paragraph();
Text ch01 = new Text("Prev Text ");
par.add(ch01);
Paragraph space = new Paragraph();
space.setMaxWidth(40);
for(int i=0;i<40;i++) {
par.add("\u00a0");
space.add("\u00a0");
}
Text ch02 = new Text(" next text");
par.add(ch02);
doc.add(par);
Paragraph linePara = new Paragraph().add("Test from UserInput")
.setTextAlignment(TextAlignment.CENTER).setBorder(new DottedBorder(1));
float width = doc.getPageEffectiveArea(PageSize.A4).getWidth();
float height = doc.getPageEffectiveArea(PageSize.A4).getHeight();
IRenderer primul = ch01.createRendererSubTree().setParent(doc.getRenderer());
IRenderer spaceR = space.createRendererSubTree().setParent(doc.getRenderer());
LayoutResult primulResult = primul.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
LayoutResult layoutResult = spaceR.layout(new LayoutContext(new LayoutArea(1, new Rectangle(width,height))));
Rectangle primulBox = ((TextRenderer) primul).getInnerAreaBBox();
Rectangle rect = ((ParagraphRenderer) spaceR).getOccupiedArea().getBBox();
float rwidth = rect.getWidth();
float rheight = rect.getHeight();
float x = primulBox.getWidth()+ doc.getLeftMargin();
float y = rect.getY() + doc.getBottomMargin();
Rectangle towr = new Rectangle(x, y, rwidth, rheight);
PdfCanvas pdfcanvas = new PdfCanvas(document.getFirstPage());
Canvas canvas = new Canvas(pdfcanvas, towr);
//from theinternet
float fontSizeL = 1;
float fontSizeR = 14;
while (Math.abs(fontSizeL - fontSizeR) > 1e-1) {
float curFontSize = (fontSizeL + fontSizeR) / 2;
linePara.setFontSize(curFontSize);
// It is important to set parent for the current element renderer to a root renderer
IRenderer renderer = linePara.createRendererSubTree().setParent(canvas.getRenderer());
LayoutContext context = new LayoutContext(new LayoutArea(1, towr));
if (renderer.layout(context).getStatus() == LayoutResult.FULL) {
// we can fit all the text with curFontSize
fontSizeL = curFontSize;
} else {
fontSizeR = curFontSize;
}
}
canvas.add(linePara);
new PdfCanvas(document.getFirstPage()).rectangle(towr).setStrokeColor(ColorConstants.BLACK).stroke();
canvas.close();
doc.close();
writer.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

Mobius strip has a seam! Java3D

I'm creating a mobius strip in Java3D. I've got a seam and I can't seem to get rid of it! I'm assuming it's got to do with normals and the fact that the difference in angle between the conjoined edges is technically 180. Can anyone help me remove this seam?
Here's my code:
public class MobiusStrip extends Applet {
public static void main(String[] args){
new MainFrame(new MobiusStrip(), 800, 600);
}
#Override
public void init(){
GraphicsConfiguration gc = SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas = new Canvas3D(gc);
this.setLayout(new BorderLayout());
this.add(canvas, BorderLayout.CENTER);
SimpleUniverse su = new SimpleUniverse(canvas);
su.getViewingPlatform().setNominalViewingTransform();
BranchGroup bg = createSceneGraph();
bg.compile();
su.addBranchGraph(bg);
}
private BranchGroup createSceneGraph(){
BranchGroup root = new BranchGroup();
Shape3D shape = new Shape3D();
shape.setGeometry(mobius().getIndexedGeometryArray());
//Scaling transform
Transform3D tr = new Transform3D();
tr.setScale(0.5);
//Spin transform group
TransformGroup spin = new TransformGroup();
spin.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
root.addChild(spin);
//Set appearance
Appearance ap = new Appearance();
PointAttributes pa = new PointAttributes(10, true);
ap.setPointAttributes(pa);
ap.setPolygonAttributes(new PolygonAttributes
(PolygonAttributes.POLYGON_FILL,
PolygonAttributes.CULL_NONE, 0));
//Set materials
Material mat = new Material();
mat.setLightingEnable(true);
mat.setShininess(30);
ap.setMaterial(mat);
//Overarching Transform group
TransformGroup tg = new TransformGroup(tr);
tg.addChild(shape);
spin.addChild(tg);
shape.setAppearance(ap);
//Set rotation
Alpha alpha = new Alpha(-1, 6000);
RotationInterpolator rotate = new RotationInterpolator(alpha, spin);
BoundingSphere bounds = new BoundingSphere();
rotate.setSchedulingBounds(bounds);
spin.addChild(rotate);
//Set background
Background background = new Background(1.0f, 1.0f, 1.0f);
background.setApplicationBounds(bounds);
root.addChild(background);
//Set lighting
AmbientLight light = new AmbientLight(true, new Color3f(Color.BLACK));
light.setInfluencingBounds(bounds);
root.addChild(light);
PointLight ptlight = new PointLight(new Color3f(Color.white),
new Point3f(0.5f,0.5f,1f),
new Point3f(1f,0.2f,0f));
ptlight.setInfluencingBounds(bounds);
root.addChild(ptlight);
return root;
}//Close branchgroup method
//Create the Mobius shape
private GeometryInfo mobius()
{
int m = 100; //number of row points
int n = 100; //number of col points
int p = 4*((m-1)*(n-1)); //faces * points per face
IndexedQuadArray iqa = new IndexedQuadArray(m*n,
GeometryArray.COORDINATES, p);
Point3d[] vertices = new Point3d[m*n];
int index = 0;
//Create vertices
for(int i = 0; i < m; i++)
{
for(int j = 0; j < n; j++)
{
double u = i * (2*(Math.PI))/(m - 1);
double v = -0.3 + (j * (0.6/(n-1)));
double x=(1+v*Math.cos(u/2))*Math.cos(u);
double y=(1+v*Math.cos(u/2))*Math.sin(u);
double z=v*Math.sin(u/2);
vertices[index]=new Point3d(x,y,z);
index++;
}//close nested for loop
}//close for loop
iqa.setCoordinates(0, vertices);
index = 0;
//set index for coordinates
for(int i = 0; i < m-1; i++){
for(int j = 0; j < n-1; j++){
iqa.setCoordinateIndex(index, i*m+j);
index++;
iqa.setCoordinateIndex(index, i*m+j+1);
index++;
iqa.setCoordinateIndex(index, (i+1)*m+j+1);
index++;
iqa.setCoordinateIndex(index, (i+1)*m+j);
index++;
}//close nested for loop
}//close for loop
//create geometry info and generate normals for shape
GeometryInfo gi = new GeometryInfo(iqa);
NormalGenerator ng = new NormalGenerator();
ng.generateNormals(gi);
return gi;
}
}
See this question for more explanation. You'll need two changes:
ap.setPolygonAttributes(new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_BACK, 0));
double u = i * (4 * (Math.PI)) / (m - 1);

How can I copy annotations/highlighting from one PDF to an updated version of that PDF?

I've recently moved to almost exclusively electronic books. I prefer to mark up documents with highlighting or annotations as I read them.
However, when I get an updated version of a PDF - O'Reilly, for example, will give access to corrected versions of the books you've purchased - I'm then stuck with a marked up older copy and a newer copy, without my notes.
My preferred language being C# I realize that iTextSharp is probably what I'd need to use if I wanted to programmatically do this (see for example Copy pdf annotations via C#), but is there an easier way to handle this?
I can't believe I'm the only one with this issue, so is there perhaps already a solution that will handle this for me?
You can use this example for iTextSharp to approach your problem:
var output = new MemoryStream();
using (var document = new Document(PageSize.A4, 70f, 70f, 20f, 20f))
{
var readers = new List<PdfReader>();
var writer = PdfWriter.GetInstance(document, output);
writer.CloseStream = false;
document.Open();
const Int32 requiredWidth = 500;
const Int32 zeroBottom = 647;
const Int32 left = 50;
Action<String, Action> inlcudePdfInDocument = (filename, e) =>
{
var reader = new PdfReader(filename);
readers.Add(reader);
var pageCount = reader.NumberOfPages;
for (var i = 0; i < pageCount; i++)
{
e?.Invoke();
var imp = writer.GetImportedPage(reader, (i + 1));
var scale = requiredWidth / imp.Width;
var height = imp.Height * scale;
writer.DirectContent.AddTemplate(imp, scale, 0, 0, scale, left, zeroBottom - height);
var annots = reader.GetPageN(i + 1).GetAsArray(PdfName.ANNOTS);
if (annots != null && annots.Size != 0)
{
foreach (var a in annots)
{
var newannot = new PdfAnnotation(writer, new Rectangle(0, 0));
var annotObj = (PdfDictionary) PdfReader.GetPdfObject(a);
newannot.PutAll(annotObj);
var rect = newannot.GetAsArray(PdfName.RECT);
rect[0] = new PdfNumber(((PdfNumber)rect[0]).DoubleValue * scale + left); // Left
rect[1] = new PdfNumber(((PdfNumber)rect[1]).DoubleValue * scale); // top
rect[2] = new PdfNumber(((PdfNumber)rect[2]).DoubleValue * scale + left); // right
rect[3] = new PdfNumber(((PdfNumber)rect[3]).DoubleValue * scale); // bottom
writer.AddAnnotation(newannot);
}
}
document.NewPage();
}
}
foreach (var apprPdf in pdfs)
{
document.NewPage();
inlcudePdfInDocument(apprPdf.Pdf, null);
}
document.Close();
readers.ForEach(x => x.Close());
}
output.Position = 0;
return output;
This example copies a list of pdf files with annotations into a new pdf file.
Obtain data from two PdfReaders simultaneously - one for copying new pdf and another for copying annotations from old pdf.

itextsharp converting 1 column to 3 columns per page

I was wondering if i could get some help converting this into a 3 column (going down to left) per page report.
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("-> Creates a PDF file with a block of Text.");
Document document = new Document(PageSize.LETTER);
try
{
PdfWriter writer = PdfWriter.GetInstance(
document,
new FileStream(#"c:\\temp\\column_example.pdf", FileMode.Create));
document.Open();
PdfContentByte cb = writer.DirectContent;
float pos;
PdfPTable table;
PdfPCell cell = new PdfPCell(new Phrase(string.Empty));
Phrase phrase;
float columnWidth = (PageSize.LETTER.Width - 36);
ColumnText ct = GetColumn(cb, columnWidth);
int status = 0;
string line = "Line{0}";
for(int i=0; i<50; i++)
{
table = new PdfPTable(1);
table.SpacingAfter = 9F;
cell = new PdfPCell(new Phrase("Header for table " + i));
table.AddCell(cell);
for (int j = 0; j < (i%2 == 0 ? 5 : 7); j++)
{
phrase = new Phrase(string.Format(line, i));
cell = new PdfPCell(phrase);
table.AddCell(cell);
}
ct.AddElement(table);
pos = ct.YLine;
status = ct.Go(true);
Console.WriteLine("Lines written:" + ct.LinesWritten + " Y-position: " + pos + " - " + ct.YLine);
if (!ColumnText.HasMoreText(status))
{
ct.AddElement(table);
ct.YLine = pos;
ct.Go(false);
}
else
{
document.NewPage();
ct.SetText(null);
ct.AddElement(table);
ct.YLine = PageSize.LETTER.Height - 36;
ct.Go();
}
}
}
catch (DocumentException de)
{
Console.Error.WriteLine(de.Message);
}
catch (IOException ioe)
{
Console.Error.WriteLine(ioe.Message);
}
finally
{
document.Close();
}
Console.ReadLine();
}
private static ColumnText GetColumn(PdfContentByte cb, float columnWidth)
{
var ct = new ColumnText(cb);
ct.SetSimpleColumn(36, 36, columnWidth, PageSize.LETTER.Height - 36, 18, Element.ALIGN_JUSTIFIED);
return ct;
}
}
}
I'm really new with itextsharp and can't find any good examples on how to do this.
Thanks for any help
The easiest way to do that is to put your individual tables into a master 3-column table. Below is code that does that. You'll probably want to adjust margins, widths and borders but this should get you started at least.
Also, since you said you were new to iTextSharp I'm going to assume that you don't have a specific need for using DirectContent. DC is very powerful but most of what you need to do with iTextSharp you can do through specific objects instead. The code below has all DC stuff removed.
//(iText 5.1.1.0)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "column_example.pdf");
Console.WriteLine("-> Creates a PDF file with a block of Text.");
Document document = new Document(PageSize.LETTER);
try
{
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(filePath, FileMode.Create));
document.Open();
//Create a master table with 3 columns
PdfPTable masterTable = new PdfPTable(3);
//Set the column widths, this should probably be adjusted
masterTable.SetWidths(new float[] { 200, 200, 200 });
PdfPTable table;
PdfPCell cell;
Phrase phrase;
string line = "Line{0}";
for (int i = 0; i < 50; i++)
{
table = new PdfPTable(1);
table.SpacingAfter = 9F;
cell = new PdfPCell(new Phrase("Header for table " + i));
table.AddCell(cell);
for (int j = 0; j < (i % 2 == 0 ? 5 : 7); j++)
{
phrase = new Phrase(string.Format(line, i));
cell = new PdfPCell(phrase);
table.AddCell(cell);
}
//Add the sub-table to our master table instead of the writer
masterTable.AddCell(table);
}
//Add the master table to our document
document.Add(masterTable);
}
catch (DocumentException de)
{
Console.Error.WriteLine(de.Message);
}
catch (IOException ioe)
{
Console.Error.WriteLine(ioe.Message);
}
finally
{
document.Close();
}
Console.ReadLine();
}
}
}
EDIT
Sorry, I didn't understand from your original post what you were looking for but do now. Unfortunately you are entering the realm of Math and Mod. I don't have to time (or the brain power this morning) to go through this completely but hopefully I can give you a start.
The entire programming world is based on left-to-right and then top-to-bottom, when you switch it around you tend to have to jump through giant hoops to do what you want (like making an HTML list into 3 columns alphabetized with A's in column 1, B's in column 2, etc.)
In order to do what you want you need to know the heights of the tables so that you can calculate how many vertically that you can get on the page. Unfortunately table height isn't known until render time. The solution (at least for me) is to draw each table to a temporary document which allows us to know the height, then we store the table in an array and throw away the document. Now we've got an array of tables with known heights that we can walk through.
The snippet below does all of this. I changed your row count rule to a random number from 2 to 9 just to get more variety in the sample data. Also, starting with iTextSharp 5.1 (I think that's the right version) many of the "big" objects support IDisposable so I'm using. If you are using an older version you'll need to drop the using and switch to normal variable declaration. Hopefully the comments make sense. You'll see that I pulled out some magic numbers into variables, too.
//Our array of tables
List<PdfPTable> Tables = new List<PdfPTable>();
//Create a random number of rows to get better sample data
int rowCount;
Random r = new Random();
string line = "Line {0}";
PdfPTable table;
//This is the horizontal padding between tables
float hSpace = 5;
//Total number of columns that we want
int columnCount = 3;
//Create a temporary document to write our table to so that their sizes can be calculated
using (Document tempDoc = new Document(PageSize.LETTER))
{
using (MemoryStream tempMS = new MemoryStream())
{
using (PdfWriter tempW = PdfWriter.GetInstance(tempDoc, tempMS))
{
tempDoc.Open();
//Calculate the table width which is the usable space minus the padding between tables divided by the column count
float documentUseableWidth = tempDoc.PageSize.Width - tempDoc.LeftMargin - tempDoc.RightMargin;
float totalTableHPadding = (hSpace * (columnCount - 1));
float tableWidth = (documentUseableWidth - totalTableHPadding) / columnCount;
for (int i = 0; i < 50; i++)
{
table = new PdfPTable(1);
table.AddCell(new PdfPCell(new Phrase("Header for table " + i)));
rowCount = r.Next(2, 10);
for (int j = 0; j < rowCount; j++)
{
table.AddCell(new PdfPCell(new Phrase(string.Format(line, i))));
}
//In order to use WriteSelectedRows you need to set the width of the table
table.SetTotalWidth(new float[] { tableWidth });
//Write the table to our temporary document in order to calculate the height
table.WriteSelectedRows(1, table.Rows.Count, 0, 0, tempW.DirectContent);
//Add the table to our array
Tables.Add(table);
}
tempDoc.Close();
}
}
}
Once you've got your array of tables you can loop through those and draw them using:
Tables[i].WriteSelectedRows(1, Tables[i].Rows.Count, curX, curY, writer.DirectContent);
Where i is your current table index and curX and curY are your current coordinates.
Hopefully this gets you going in the right direction. WriteSelectedRows does a great job of putting a table exactly where you want it.
One last thing to remember, the Y coordinate that it takes starts at the bottom of the document, not the top, so 0 is the bottom and 720 is "above" it and not below.