I'm using PdfFormXObject instead of PdfCanvas to apply background, border, and/or background color to a certain area of a page (mainly because PdfCanvas needs Page to construct, PdfFormXObject doesn't. As my content may last several pages). The problem is that the image is not positioned as expected if coordination (x = 0, y = 0) means bottom-left corner. I also want to position the Canvas to an fixed position but canvas.SetFixedPosition() seems not working. See attached original image and the image to be positioned at {x, y, width, height} = {100f, 100f, 200f, 200f} which should be at the bottom of the page(which is not) and it's also truncated somehow?
code
public void CreatePDF(string path)
{
var writer = new PdfWriter(path);
var pdf = new PdfDocument(writer);
var doc = new Document(pdf, PageSize.LETTER);
doc.SetMargins(18, 18, 18, 18);
var rect = new Rectangle(100f, 100f, 200f, 200f);
var temp = new PdfFormXObject(new Rectangle(rect.GetWidth(), rect.GetHeight()));
var ca = new Canvas(temp, pdf);
// ca.SetFixedPosition(rect.GetLeft(), rect.GetBottom(), rect.GetWidth());
var img = new Image(ImageDataFactory.Create(path));
img.SetFixedPosition(rect.GetLeft(), rect.GetBottom());
img.ScaleAbsolute(rect.GetWidth(), rect.GetHeight());
ca.Add(img);
ca.SetBackgroundColor(ColorConstants.BLUE); // not shown blue bg
ca.Close();
doc.Add(new Image(temp));
doc.Close();
pdf.Close();
}
original image
generated one(wrongly placed at the top)
update
Here is the working code after mkl's direction. But canvas cannot set border/background color:
public void CreatePDF(string path)
{
var writer = new PdfWriter(path);
var pdf = new PdfDocument(writer);
var doc = new Document(pdf, PageSize.LETTER);
doc.SetMargins(LETTER_MARGIN, LETTER_MARGIN, LETTER_MARGIN, LETTER_MARGIN);
var rect = new Rectangle(100f, 300f, 200f, 200f);
var w = Doc.GetPageEffectiveArea(PageSize.LETTER).GetWidth(); //576f
var h = Doc.GetPageEffectiveArea(PageSize.LETTER).GetHeight();//756f
var temp = new PdfFormXObject(new Rectangle(w, h));
var ca = new Canvas(temp, pdf);
ca.SetFixedPosition(0, 0, 576f);
ca.SetBorder(new SolidBorder(1f));//not work
ca.SetBackgroundColor(ColorConstants.BLUE);//not work
var img = new Image(ImageDataFactory.Create(path));
img.SetFixedPosition(rect.GetLeft(), rect.GetBottom());
img.ScaleAbsolute(rect.GetWidth(), rect.GetHeight());
ca.Add(img);
ca.Close();
doc.Add(new Image(temp));
doc.Close();
pdf.Close();
}
Update
I added a Div to the ca and set border and background color to the Div. Works perfectly.
TIA
First, you create a PdfFormXObject using a rectangle you only define width and height for:
var temp = new PdfFormXObject(new Rectangle(rect.GetWidth(), rect.GetHeight()));
Thus, the rectangle has the lower left corner at the origin of the form xobject coordinate system, i.e. lower left is (0,0) and upper right is (200,200).
On this area you position a 200×200 image at (100,100):
img.SetFixedPosition(rect.GetLeft(), rect.GetBottom());
img.ScaleAbsolute(rect.GetWidth(), rect.GetHeight());
ca.Add(img);
So most of the image is outside the xobject, only the lower left quadrant is inside, the one you see in your screenshot.
Then you add this xobject to the document without positioning:
doc.Add(new Image(temp));
Thus, you ask itext to put your 200×200 xobject where it fits given the existing content. So it will be positioned further up than you want.
To fix your issue, therefore, arrange the xobject area and the image position so that the image is in the area, and position the xobject as desired.
Related
I am using C# and iText 7.2.1.
I want to draw texts right inside rectangles. I see in document that the positioning anchor of rectangles and paragraphs are both 'left-bottom corner'. But when I use the following code, they are not at the same location. Seems they have different understanding of the Y coordinate.
My code:
using iText.IO.Font;
using iText.IO.Font.Constants;
using iText.Kernel.Colors;
using iText.Kernel.Font;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;
using iText.Layout;
using iText.Layout.Element;
namespace iTextTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var str = "ABCD1234";
var fontSize = 32;
var x = 100;
var y = 700;
var writer = new PdfWriter("test.pdf");
var pdfdoc = new PdfDocument(writer);
var doc = new Document(pdfdoc);
var font = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);
var width = font.GetWidth(str, fontSize);
var height = fontSize;
// Draw rectangle
var pdfPage = pdfdoc.AddNewPage();
var pdfCanvas = new PdfCanvas(pdfPage);
pdfCanvas.SetFillColor(ColorConstants.YELLOW);
pdfCanvas.Rectangle(x, y, width, height);
pdfCanvas.Fill();
// Draw text
var p = new Paragraph().Add(str).SetFont(font);
p.SetFontSize(fontSize).SetFontColor(ColorConstants.BLACK);
p.SetFixedPosition(x, y, width);
doc.Add(p);
doc.Close();
pdfdoc.Close();
writer.Close();
}
}
}
A line of a paragraph does not require the height of the font size but instead of the leading. For tightly set text the leading may equal the font size (or sometimes even be smaller than it) but for easy-to-read text the leading usually is larger than the font size.
When drawing a paragraph with iText, the extra space is located at the bottom of the line. This causes the empty space under the line you see in your output.
For your code to work, therefore, you have to set the leading to match your font size, i.e.
// Draw text
var p = new Paragraph().Add(str).SetFont(font);
p.SetFontSize(fontSize).SetFontColor(ColorConstants.BLACK);
p.SetFixedPosition(x, y, width);
p.SetFixedLeading(fontSize); // <-- added
doc.Add(p);
(TextPosition test TextAndPositionLikeLandings)
The result:
I am capturing InkStrokes and have a need to create a scaled bitmap image of the strokes in the background. The captured images need to be of uniform size regardless of how big the bounding box of the ink.
For example, if original ink stroke is drawn and the bounding box top/left is 100,100 and size is 200,200 on the ink canvas, I want the ink to start at 0,0 of the new rendered bitmap that is 50,50 size (ignore impact of stroke width right now).
I have figured out how to scale the ink strokes (thanks StackOverflow) but not how to move the strokes. Right now, it seems I have to create a bitmap the size of the InkCanvas, render the scaled ink, then crop bigger image to the correct size.
I've tried using the InkStroke.PointTranslate via
var scaleMatrix = Matrix3x2.CreateScale(scale);
scaleMatrix.Translation = -offset; // top/left of ink stroke bounding box
stroke.PointTransform = scaleMatrix;
But the coordinates do not come out correct.
Any help much appreciated.
You can combine transformations by multiplying matrices. This works for me
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
var boundingBox = inkCanvas.InkPresenter.StrokeContainer.BoundingRect;
var matrix1 = Matrix3x2.CreateTranslation((float)-boundingBox.X, (float)-boundingBox.Y);
var matrix2 = Matrix3x2.CreateScale(0.5f);
var builder = new InkStrokeBuilder();
var newStrokeList = new List<InkStroke>();
foreach (var stroke in strokes)
{
newStrokeList.Add(builder.CreateStrokeFromInkPoints
(stroke.GetInkPoints(), matrix1 * matrix2));
}
//Add the translated and scaled strokes to the inkcanvas
inkCanvas.InkPresenter.StrokeContainer.AddStrokes(newStrokeList);
Maybe I was still doing something wrong, but it appears you cannot use InkStrokeBuilder.CreateStrokeFromInkPoints with more than one kind of transform. I tried all kinds of combinations/approaches, and just could not get it to work.
Here is my solution...
private static IList<InkStroke> GetScaledAndTransformedStrokes(IList<InkStroke> strokeList, float scale)
{
var builder = new InkStrokeBuilder();
var newStrokeList = new List<InkStroke>();
var boundingBox = strokeList.GetBoundingBox();
foreach (var singleStroke in strokeList)
{
var translateMatrix = new Matrix(1, 0, 0, 1, -boundingBox.X, -boundingBox.Y);
var newInkPoints = new List<InkPoint>();
var originalInkPoints = singleStroke.GetInkPoints();
foreach (var point in originalInkPoints)
{
var newPosition = translateMatrix.Transform(point.Position);
var newInkPoint = new InkPoint(newPosition, point.Pressure, point.TiltX, point.TiltY, point.Timestamp);
newInkPoints.Add(newInkPoint);
}
var newStroke = builder.CreateStrokeFromInkPoints(newInkPoints, new Matrix3x2(scale, 0, 0, scale, 0, 0));
newStrokeList.Add(newStroke);
}
return newStrokeList;
}
I ended up having to apply my own translate transform then use the builder.CreateStrokeFromInkPoints with a scale matrix applied to get the results I wanted. GetBoundingBox is my own extension:
public static class RectExtensions
{
public static Rect CombineWith(this Rect r, Rect rect)
{
var top = (r.Top < rect.Top) ? r.Top : rect.Top;
var left = (r.Left < rect.Left) ? r.Left : rect.Left;
var bottom = (r.Bottom < rect.Bottom) ? rect.Bottom : r.Bottom;
var right = (r.Right < rect.Right) ? rect.Right : r.Right;
var newRect = new Rect(new Point(left, top), new Point(right, bottom));
return newRect;
}
}
At the moment I'm trying to change the fonts used in a PDF document.
I therefore declared rectangles and extract the text using LocationTextExtractionStrategy:
System.util.RectangleJ rect = new System.util.RectangleJ(fX, fY, fWidth, fHeight);
RenderFilter[] rfArea = { new RegionTextRenderFilter(rect) };
ITextExtractionStrategy str = new FilteredTextRenderListener(new LocationTextExtractionStrategy(), rfArea);
string s = PdfTextExtractor.GetTextFromPage(reader, 1, str);
This works as expected, although it's quite complicated to find the right coordinates.
But how do I place the text in a new PDF document at the same starting location fX, fY so I don't break the layout?
I tried it using ColumnText but a multiline text is put one above the other:
Paragraph para = new Paragraph(fLineSpacing, s, font);
para.IndentationLeft = fLineIndentionLeft;
para.SpacingAfter = fSpacingAfter;
para.SpacingBefore = fSpacingBefore;
para.Alignment = iFontAlignment;
PdfContentByte cb = writer.DirectContent;
ColumnText ct = new ColumnText(cb);
ct.SetSimpleColumn(para, fX, fY + fHeight, fWidth + fX, fY, 0f, Element.ALIGN_LEFT);
ct.Go();
What am I missing? Or is there even something simpler? Using my approach I will have to cover the entire page defining lots of rectangles.
Here is my code. I expect the viewbox to render according to size of the pdf canvas size (which is something like 750 by 600). However, when the image is saved to pdf, the image is severely cropped. Only the top/left details are saved. I know this is the case becase if I set page.Canvas.DrawImage's size parameter to various values, the image changes size, but it is always the same cropped image, so either RenderTargetBitmap is not rending the viewbox properly or the encoder is not encoding properly or the pdfimage.fromstream is not converting properly.
//.. set up the doc
var doc = new PdfDocument();
var page = doc.Pages.Add(PdfPageSize.Letter, new PdfMargins(30f), PdfPageRotateAngle.RotateAngle0, PdfPageOrientation.Landscape);
//.. render the viewbox (must be done or its elements don't scale)
vb.Measure(new System.Windows.Size(page.Canvas.ClientSize.Width, page.Canvas.ClientSize.Height));
vb.Arrange(new Rect(0, 0, page.Canvas.ClientSize.Width, page.Canvas.ClientSize.Height));
vb.UpdateLayout();
RenderTargetBitmap render = new RenderTargetBitmap((int)vb.ActualWidth, (int)vb.ActualHeight, 150, 150, PixelFormats.Pbgra32);
render.Render(vb);
//.. save the render to memory
var stream = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
var frame = BitmapFrame.Create(render);
encoder.Frames.Add(frame);
encoder.Save(stream);
//.. use spire pdf to convert the stream to an image
var img = PdfImage.FromStream(stream);
float widthFitRate = img.PhysicalDimension.Width / page.Canvas.ClientSize.Width;
float heightFitRate = img.PhysicalDimension.Height / page.Canvas.ClientSize.Height;
float fitRate = Math.Max(widthFitRate, heightFitRate);
float fitWidth = img.PhysicalDimension.Width / fitRate;
float fitHeight = img.PhysicalDimension.Height / fitRate;
page.Canvas.DrawImage(img, new PointF(0,0), new SizeF(img.PhysicalDimension.Width, img.PhysicalDimension.Height ));
doc.SaveToFile(#"C:\test.pdf");
doc.Close();
I have an existing PDF (not with form fields - more of a scanned document), and am using PdfReader to load the PDF "template" so that I write text on it.
For position simple fields I am using:
PdfReader reader = new PdfReader(templatePath);
Chunk chunk = new Chunk(text, fontToUse);
Phrase phrase = new Phrase();
phrase.Add(chunk);
PdfContentByte canvas = this.PdfWriter.DirectContent;
ColumnText.ShowTextAligned(this.PdfContentByte, alignment, phrase, left, top, 0);
I also need to write some text to a specific area which is a 400 x 200 rectangle. Since the size of the text varies, it may or may not fit into the rectangle.
Is there a way to write text to the rectangle, and if the text is too large for it to simply not appear (like overflow hidden would work in HTML)?
Got it!
Phrase myText = new Phrase(text);
PdfPTable table = new PdfPTable(1);
table.TotalWidth = 300;
table.LockedWidth = true;
PdfPCell cell = new PdfPCell(myText);
cell.Border = 0;
cell.FixedHeight = 40;
table.AddCell(cell);
table.WriteSelectedRows
(
0,
-1,
300,
700,
writer.DirectContent
);