I need to put some dynamic text onto a pdf. I need to verify that the text does not overflow the boundary box I am allowed to use to place the text in.
Is there a way to detect if this is happening?
Are there any copy-fit rules that I can use to handle it when it does happen?
Thanks
iText5 is in maintenance mode and I recommend that you start your project using iText7. iText7 currently does not provide out of the box mechanisms for copy-fitting, but it can be done manually with little effort because layout engine is very flexible in iText7. Technically it can be done in iText5 as well, but I will provide an answer for iText7 for Java, and converting to C# shouldn't be a problem for you.
The basic idea is to make use of the Renderers concept and try yo layout your paragraph in the given area with different font sizes until you find the size which is OK for you. A binary search approach fits perfectly here.
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outFileName));
Document doc = new Document(pdfDoc);
String text = "...";
Rectangle area = new Rectangle(100, 100, 200, 200);
// Just draw a black box around to verify the result visually
new PdfCanvas(pdfDoc.addNewPage()).rectangle(area).setStrokeColor(Color.BLACK).stroke();
Paragraph p = new Paragraph(text);
IRenderer renderer = p.createRendererSubTree().setParent(doc.getRenderer());
LayoutArea layoutArea = new LayoutArea(1, area);
// lFontSize means the font size which will be definitely small enough to fit all the text into the box.
// rFontSize is the maximum bound for the font size you want to use. The only constraint is that it should be greater than lFontSize
// Set rFontSize to smaller value if you don't want the font size to scale upwards
float lFontSize = 0.0001f, rFontSize = 10000;
// Binary search. Can be replaced with while (Math.abs(lFontSize - rFontSize) < eps). It is a matter of implementation/taste
for (int i = 0; i < 100; i++) {
float mFontSize = (lFontSize + rFontSize) / 2;
p.setFontSize(mFontSize);
LayoutResult result = renderer.layout(new LayoutContext(layoutArea));
if (result.getStatus() == LayoutResult.FULL) {
lFontSize = mFontSize;
} else {
rFontSize = mFontSize;
}
}
// lFontSize is guaranteed to be small enough to fit all the text. Using it.
float finalFontSize = lFontSize;
System.out.println("Final font size: " + finalFontSize);
p.setFontSize(finalFontSize);
// We need to layout the final time with the final font size set.
renderer.layout(new LayoutContext(layoutArea));
renderer.draw(new DrawContext(pdfDoc, new PdfCanvas(pdfDoc.getPage(1))));
doc.close();
The output:
Final font size: 5.7393746
Visual result:
Related
I want to print a DHL label using a label printer.
The DHL label consists of 2 parts: The left half of the image is just info that I can keep to myself. The right part of the PDF is what should actually be printed.
I would therefore like to cut away the left part of the PDF.
I do not want to make it blank, but I really I want to cut if off.
How could I do this?
You essentially want to cut away one half of the PDF page; looking at you screenshot most likely the lower half.
Using iTextSharp 5.5.13.3 you can do that like this:
var testFile = #"new pdf1.pdf";
var resultFile = #"new pdf1-Cut.pdf";
using (PdfReader pdfReader = new PdfReader(testFile))
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, File.Create(resultFile)))
{
for (int i = 1; i <= pdfReader.NumberOfPages; i++)
{
Rectangle cropBox = pdfReader.GetCropBox(i);
PdfArray newCropBox = new PdfArray(new float[] {
cropBox.Left, (cropBox.Bottom + cropBox.Top) / 2,
cropBox.Right, cropBox.Top });
PdfDictionary pageDictionary = pdfReader.GetPageN(i);
pageDictionary.Put(PdfName.CROPBOX, newCropBox);
pageDictionary.Put(PdfName.MEDIABOX, newCropBox);
}
}
(CutPages test CutInHalfForTmighty)
Before
After
I want to display UI logo images in their aspect ratio but in proper size that fit within its container. Already all logo images as per aspect ratio but few are too big or small compare to requirements.
At present this kind of thing happening:
Here is the code that I am using:
private void ShowGamePlayLogoViewed ()
{
for (int i = 0; i < DataCollection.companyDetailsList.Count; i++) {
Company company = DataCollection.companyDetailsList [i];
if (company.ViewedCounter > 0) {
GameObject companyItemObj = Instantiate (companyItemPref, gridViewContainer) as GameObject;
CompanyItem companyItem = companyItemObj.GetComponent<CompanyItem> ();
companyItem.companyId = company.CompanyId;
companyItem.UpdateCompanyLogo (company.CompanyLogo);
companyItem.UpdateCompanyName (company.CompanyName);
}
}
}
public void UpdateCompanyLogo (Sprite logoSprite)
{
logoImage.sprite = logoSprite;
logoImage.SetNativeSize ();
}
As you are seeing, logos overlapping the container. I want to display properly them in their respective containers also in aspect ratio too.
Let me clarify one more thing: all logos loaded from web server and they all are dynamic at a time, based on server data it will appear in mobile screen.
I have found solution through Unity Forum and I want to say thanks to #Hosnkobf for reply.
Here is the exact reply that worked for me properly:
adjust the image game object inside unity so that it has the height you are looking for. also adjust the anchors so that it scales with different resolutions properly.
Add an "AspectRatioFitter" component to the object. Make it "Height controls width".
Instead of logoImage.SetNativeSize (); do the following:
float aspectRatio = logoSprite.rect.width / logoSprite.rect.height;
var fitter = logoImage.GetComponent<UnityEngine.UI.AspectRatioFitter>();
fitter.aspectRatio = aspectRatio;
I hope this become useful to other members :)
I have a pdf which comprises of some data, followed by some whitespace. I don't know how large the data is, but I'd like to trim off the whitespace following the data
PdfReader reader = new PdfReader(PDFLOCATION);
Rectangle rect = new Rectangle(700, 2000);
Document document = new Document(rect);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(SAVELCATION));
document.open();
int n = reader.getNumberOfPages();
PdfImportedPage page;
for (int i = 1; i <= n; i++) {
document.newPage();
page = writer.getImportedPage(reader, i);
Image instance = Image.getInstance(page);
document.add(instance);
}
document.close();
Is there a way to clip/trim the whitespace for each page in the new document?
This PDF contains vector graphics.
I'm usung iTextPDF, but can switch to any Java library (mavenized, Apache license preferred)
As no actual solution has been posted, here some pointers from the accompanying itext-questions mailing list thread:
As you want to merely trim pages, this is not a case of PdfWriter + getImportedPage usage but instead of PdfStamper usage. Your main code using a PdfStamper might look like this:
PdfReader reader = new PdfReader(resourceStream);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("target/test-outputs/test-trimmed-stamper.pdf"));
// Go through all pages
int n = reader.getNumberOfPages();
for (int i = 1; i <= n; i++)
{
Rectangle pageSize = reader.getPageSize(i);
Rectangle rect = getOutputPageSize(pageSize, reader, i);
PdfDictionary page = reader.getPageN(i);
page.put(PdfName.CROPBOX, new PdfArray(new float[]{rect.getLeft(), rect.getBottom(), rect.getRight(), rect.getTop()}));
stamper.markUsed(page);
}
stamper.close();
As you see I also added another argument to your getOutputPageSize method to-be. It is the page number. The amount of white space to trim might differ on different pages after all.
If the source document did not contain vector graphics, you could simply use the iText parser package classes. There even already is a TextMarginFinder based on them. In this case the getOutputPageSize method (with the additional page parameter) could look like this:
private Rectangle getOutputPageSize(Rectangle pageSize, PdfReader reader, int page) throws IOException
{
PdfReaderContentParser parser = new PdfReaderContentParser(reader);
TextMarginFinder finder = parser.processContent(page, new TextMarginFinder());
Rectangle result = new Rectangle(finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
System.out.printf("Text/bitmap boundary: %f,%f to %f, %f\n", finder.getLlx(), finder.getLly(), finder.getUrx(), finder.getUry());
return result;
}
Using this method with your file test.pdf results in:
As you see the code trims according to text (and bitmap image) content on the page.
To find the bounding box respecting vector graphics, too, you essentially have to do the same but you have to extend the parser framework used here to inform its listeners (the TextMarginFinder essentially is a listener to drawing events sent from the parser framework) about vector graphics operations, too. This is non-trivial, especially if you don't know PDF syntax by heart yet.
If your PDFs to trim are not too generic but can be forced to include some text or bitmap graphics in relevant positions, though, you could use the sample code above (probably with minor changes) anyways.
E.g. if your PDFs always start with text on top and end with text at the bottom, you could change getOutputPageSize to create the result rectangle like this:
Rectangle result = new Rectangle(pageSize.getLeft(), finder.getLly(), pageSize.getRight(), finder.getUry());
This only trims top and bottom empty space:
Depending on your input data pool and requirements this might suffice.
Or you can use some other heuristics depending on your knowledge on the input data. If you know something about the positioning of text (e.g. the heading to always be centered and some other text to always start at the left), you can easily extend the TextMarginFinder to take advantage of this knowledge.
Recent (April 2015, iText 5.5.6-SNAPSHOT) improvements
The current development version, 5.5.6-SNAPSHOT, extends the parser package to also include vector graphics parsing. This allows for an extension of iText's original TextMarginFinder class implementing the new ExtRenderListener methods like this:
#Override
public void modifyPath(PathConstructionRenderInfo renderInfo)
{
List<Vector> points = new ArrayList<Vector>();
if (renderInfo.getOperation() == PathConstructionRenderInfo.RECT)
{
float x = renderInfo.getSegmentData().get(0);
float y = renderInfo.getSegmentData().get(1);
float w = renderInfo.getSegmentData().get(2);
float h = renderInfo.getSegmentData().get(3);
points.add(new Vector(x, y, 1));
points.add(new Vector(x+w, y, 1));
points.add(new Vector(x, y+h, 1));
points.add(new Vector(x+w, y+h, 1));
}
else if (renderInfo.getSegmentData() != null)
{
for (int i = 0; i < renderInfo.getSegmentData().size()-1; i+=2)
{
points.add(new Vector(renderInfo.getSegmentData().get(i), renderInfo.getSegmentData().get(i+1), 1));
}
}
for (Vector point: points)
{
point = point.cross(renderInfo.getCtm());
Rectangle2D.Float pointRectangle = new Rectangle2D.Float(point.get(Vector.I1), point.get(Vector.I2), 0, 0);
if (currentPathRectangle == null)
currentPathRectangle = pointRectangle;
else
currentPathRectangle.add(pointRectangle);
}
}
#Override
public Path renderPath(PathPaintingRenderInfo renderInfo)
{
if (renderInfo.getOperation() != PathPaintingRenderInfo.NO_OP)
{
if (textRectangle == null)
textRectangle = currentPathRectangle;
else
textRectangle.add(currentPathRectangle);
}
currentPathRectangle = null;
return null;
}
#Override
public void clipPath(int rule)
{
}
(Full source: MarginFinder.java)
Using this class to trim the white space results in
which is pretty much what one would hope for.
Beware: The implementation above is far from optimal. It is not even correct as it includes all curve control points which is too much. Furthermore it ignores stuff like line width or wedge types. It actually merely is a proof-of-concept.
All test code is in TestTrimPdfPage.java.
Adding a time series bar chart for a large time span in PDF results in large file size like 50 MB or more depending on the data points. Here are the code samples:
Adding chart to PDF
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(RESULT));
document.open();
PdfContentByte cb = writer.getDirectContent();
float width = PageSize.A4.getWidth();
float height = PageSize.A4.getHeight() / 2;
PdfTemplate bar = cb.createTemplate(width, height);
Graphics2D g2d2 = new PdfGraphics2D(bar, width, height);
Rectangle2D r2d2 = new Rectangle2D.Double(0, 0, width, height);
getBarChart().draw(g2d2, r2d2);
g2d2.dispose();
cb.addTemplate(bar, 0, 0);
document.close();
Creating chart
JFreeChart getBarChart() {
TimeSeries series = new TimeSeries("Data");
GregorianCalendar cal = new GregorianCalendar();
for (int i=0; i<365*24; i++) {
cal.add(Calendar.HOUR, 1);
series.addOrUpdate(new Millisecond(cal.getTime()), Math.random());
}
XYPlot plot = new XYPlot();
plot.setDataset(new XYBarDataset(new TimeSeriesCollection(series), 10));
plot.setRenderer(new XYBarRenderer());
plot.setRangeAxis(new NumberAxis());
plot.setDomainAxis(new DateAxis());
return new JFreeChart(plot);
}
How can I reduce the file size?
Using itextpdf-5.4.4 and jfreechart-1.0.15.
While inspecting the PDF provided by the OP, it quickly becomes apparent that it is full of Pattern definitions and the like used for drawing pretty bars. To reduce the PDF size, therefore, simplifying the bar design is the way to go.
In the case at hand this can be done by setting a different default bar painter (using XYBarRenderer.setDefaultBarPainter()). The initial value of that attribute is a GradientXYBarPainter, but using gradients for so small bars makes the number of required drawing operations and operators explode while only making a difference at a gigantic zoom level, if at all.
As already worked out in the comments to the question, using the StandardXYBarPainter instead solves the size issues.
You can try to set full compression and compare the difference:
PdfReader reader = new PdfReader(new FileInputStream("in.pdf"));
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("out.pdf"));
int total = reader.getNumberOfPages() + 1;
for ( int i=1; i<total; i++) {
reader.setPageContent(i + 1, reader.getPageContent(i + 1));
}
stamper.setFullCompression();
stamper.close();
I'm using itextSharp to add anotations in a pdf document.
I have a pdf document that already contains an image saved in it, it's a stamp.
So I draw some stroke on this pdf in the stamp and everything is fine when I draw them in my WPF but when I send the pdf by email using iTextSharp for the conversion the line I drawed is now below the stamp.
How I can solve this problem ?
Thank you
The explanation you posted as an answer (BTW, more apropos would have been to edit your question to contain that data) explains the issue.
There are two principal types of objects visible on a PDF page:
the PDF page content;
annotations associated with the page.
The annotations are always displayed above the page content if they are displayed at all.
In your case you add the image to the PDF page content (using OverContent or UnderContent only changes where in relation to other PDF page content material your additions appear). The stamp, on the other hand, most likely is realized by means of an annotation. Thus, the stamp annotation always is above your additions.
If you want to have your additions appear above the stamp, you either have to add your additions as some kind of annotation, too, or you have to flatten the stamp annotation into the page content before adding your stuff.
Which of these varients is better, depends on the requirements you have. Are there any requirements forcing the stamp to remain a stamp annotation? Are there any requirements forcing your additions to remain part of the content? Please elaborate your requirements. As content and annotations have some different properties when displayed or printed, please state all requirements.
And furthermore, please supply sample documents.
So like I said the original pdf have a stamp saved inside it, if I open the pdf with acrobat reader I can move the stamp.
So here my code to write some strokes :
using (var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read))
using (var intputStream = new FileStream(pathPdf, FileMode.Open, FileAccess.Read, FileShare.Read))
{
PdfReader reader = new PdfReader(intputStream);
using (var pdfStamper = new PdfStamper(reader, outputStream))
{
foreach (var page in pages)
{
if (page != null && page.ExportedImages.HasItems())
{
PdfContentByte pdfContent = pdfStamper.GetOverContent(page.PageIndex);
Rectangle pageSize = reader.GetPageSizeWithRotation(page.PageIndex);
PdfLayer pdfLayer = new PdfLayer(string.Format(ANNOTATIONNAMEWITHPAGENAME, page.PageIndex), pdfContent.PdfWriter);
foreach (ExporterEditPageInfoImage exportedInfo in page.ExportedImages)
{
Image image = PngImage.GetImage(exportedInfo.Path);
image.Layer = pdfLayer;
if (quality == PublishQuality.Normal || quality == PublishQuality.Medium || quality == PublishQuality.High)
{
float width = (float)Math.Ceiling((image.Width / image.DpiX) * 72);
float height = (float)Math.Ceiling((image.Height / image.DpiY) * 72);
image.ScaleAbsolute(width, height);
float x = (float)(exportedInfo.HorizontalTile * (page.TileSize * (72 / 96d)));
float y = (float)Math.Max(0, (pageSize.Height - ((exportedInfo.VerticalTile + 1) * (page.TileSize * (72 / 96d)))));
image.SetAbsolutePosition(x, y);
}
else
throw new NotSupportedException();
pdfContent.AddImage(image);
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
pdfStamper.Close();
}
}
So my strokes are saved good in the pdf the problem the stamp is always on top of everything and I think is normal so can I do a workaround for this ?