I am trying to add a IBlockElement at the end of the last page in the Itext 7 version
my approach is
After writing all the elements to the pdf document get the vertical
position from the writer with writer.getVerticalPosition()
Calculate the available space on the current page by using the
bottomMargin as a reference.
If space is less than the space required then add another blank page
Create and insert a container of fixed height with text vertical alignment to
bottom Add IBlockElement content to the container
However when I am using it
var PdfWriter= new PdfWriter(memoryStream, writerProperties)
PdfWriter.getverticalposition()
I am getting error:
PdfWriter writer does not contain a definition of
getverticalposition(). No method getverticalposition() accept first
argument of type PdfWriter are you missing assembly reference?
Do I have to change the assembly reference or something?
EDIT DATE: 10-Nov-2018
private class BottomBlockElement : Div
{
public BottomBlockElement(IBlockElement wrapping)
{
base.SetKeepTogether(true);
base.Add(wrapping);
//add(wrapping);
//setKeepTogether(true);
}
override protected IRenderer MakeNewRenderer()
{
return new BottomBlockRenderer(this);
}
}
private class BottomBlockRenderer : DivRenderer
{
public BottomBlockRenderer(BottomBlockElement modelElement) : base(modelElement)
{
}
override public LayoutResult Layout(LayoutContext layoutContext)
{
LayoutResult result = base.Layout(layoutContext);
if (result.GetStatus() == LayoutResult.FULL)
{
float leftoverHeight = result.GetOccupiedArea().GetBBox().GetBottom() - layoutContext.GetArea().GetBBox().GetBottom();
Move(0, -leftoverHeight);
return new LayoutResult(result.GetStatus(), layoutContext.GetArea(), result.GetSplitRenderer(), result.GetOverflowRenderer());
}
else
{
return result;
}
}
public override IRenderer GetNextRenderer()
{
return new BottomBlockRenderer((BottomBlockElement)modelElement);
}
}
But still the text is overlapping
iText7 still allows you to get analogue of current vertical position, but as #mkl noted in his comment, there are are lot of nuances with this concept because iText7 support much more complex layout strategies.
In general you should try to think out of the box sometimes when migrating from iText5 to iText7 - there are a ton of things you can do much easier with iText7, including your use case of adding content to the bottom of last page.
You can use iText7 analogue of CSS absolute positioning to add content to the bottom. To do that, you have to specify position property as well as bottom offset. There is still no fancy public API to do that because the functionality is still under constant improvement, but you can do that with setting necessary properties manually. All you need to do is add these lines:
glueToBottom.setProperty(Property.POSITION, LayoutPosition.ABSOLUTE);
glueToBottom.setProperty(Property.BOTTOM, 0);
To demonstrate the result, let's add some content first and then the block element to the end of the last page.
Document document = new Document(pdfDocument);
for (int i = 0; i < 40; i++) {
document.add(new Paragraph("Hello " + i));
}
IBlockElement glueToBottom = new Paragraph("Hi, I am the bottom content")
.setFontSize(25)
.setWidth(UnitValue.createPercentValue(100))
.setBackgroundColor(ColorConstants.RED)
.setTextAlignment(TextAlignment.CENTER);
glueToBottom.setProperty(Property.POSITION, LayoutPosition.ABSOLUTE);
glueToBottom.setProperty(Property.BOTTOM, 0);
document.add(glueToBottom);
document.close();
You will get something like this at page 2 (last page), which matches your description exactly:
UPD: The algorithm in question was updated and now contains logic of inserting a new page for the content at the bottom of the page if the content at the bottom would overlap with existing content had the page not been inserted. To achieve same behavior you would need a slight modification of the code above: instead of setting positioning properties via setProperty let's implement our own element and corresponding renderer and wrap your block element into this implementation. We will add our element now as follows:
document.add(new BottomBlockElement(glueToBottom));
The implementations are straightforward - just move the element to the bottom between laying it out and drawing. This is a bit verbose in code but still quite clear:
private static class BottomBlockElement extends Div {
public BottomBlockElement(IBlockElement wrapping) {
super();
add(wrapping);
setKeepTogether(true);
}
#Override
protected IRenderer makeNewRenderer() {
return new BottomBlockRenderer(this);
}
}
private static class BottomBlockRenderer extends DivRenderer {
public BottomBlockRenderer(BottomBlockElement modelElement) {
super(modelElement);
}
#Override
public LayoutResult layout(LayoutContext layoutContext) {
LayoutResult result = super.layout(layoutContext);
if (result.getStatus() == LayoutResult.FULL) {
float leftoverHeight = result.getOccupiedArea().getBBox().getBottom() - layoutContext.getArea().getBBox().getBottom();
move(0, -leftoverHeight);
return new LayoutResult(result.getStatus(), layoutContext.getArea(), result.getSplitRenderer(), result.getOverflowRenderer());
} else {
return result;
}
}
#Override
public IRenderer getNextRenderer() {
return new BottomBlockRenderer((BottomBlockElement) modelElement);
}
}
Now our main part looks like this:
Document document = new Document(pdfDocument);
for (int i = 0; i < 58; i++) {
document.add(new Paragraph("Hello " + i));
}
IBlockElement glueToBottom = new Paragraph("Hi, I am the bottom content")
.setFontSize(25)
.setWidth(UnitValue.createPercentValue(100))
.setBorder(new SolidBorder(ColorConstants.RED, 1))
.setTextAlignment(TextAlignment.CENTER);
document.add(new BottomBlockElement(glueToBottom));
document.close();
And the result is the last page containing only the bottom block if the previous one does not have enough space:
Related
I am trying to create a PDF with a mixture of Portrait and Landscape/Seascape pages.
I have rotated the pages to be Seascape where required but the content is still being output as portrait. I am reading the data in from an HTML file using iText's pdfHTML solution.
Below is some sample code I've used to do this.
var elements = HtmlConverter.ConvertToElements(htmlSource, props);
var pdf = new PdfDocument(new PdfWriter(pdfDest));
var pageOrientationHandler = new PageOrientationsEventHandler();
pdf.AddEventHandler(PdfDocumentEvent.INSERT_PAGE, pageOrientationHandler);
var document = new Document(pdf, PageSize.A4);
document.SetFontProvider(new DefaultFontProvider());
document.Add(new Paragraph(LOREM_IPSUM));
pageOrientationHandler.SetSeascape();
document.Add(new AreaBreak());
foreach (IElement element in elements)
{
document.Add((Div)element);
}
pageOrientationHandler.SetSeascape();
document.Add(new AreaBreak());
foreach (IElement element in elements)
{
document.Add((Div)element);
}
pageOrientationHandler.SetPortrait();
document.Add(new AreaBreak());
document.Add(new Paragraph(LOREM_IPSUM));
document.Close();
The PageOrientationsEventHandler class looks like this:
public class PageOrientationsEventHandler : IEventHandler
{
private PdfNumber _orientation = new(0);
public void SetPortrait()
{
_orientation = new PdfNumber(0);
}
public void SetLandscape()
{
_orientation = new PdfNumber(90);
}
public void SetSeascape()
{
_orientation = new PdfNumber(270);
}
public void HandleEvent(Event currentEvent)
{
PdfDocumentEvent pdfDocumentEvent = (PdfDocumentEvent)currentEvent;
var page = pdfDocumentEvent.GetPage();
page.Put(PdfName.Rotate, _orientation);
}
}
What I would like is for pages 1 and 4 to be portrait orientation with portrait content (i.e. a "normal" looking page with text) and pages 2 and 3 to be Seascape with Seascape content.
What I am seeing in the generated PDF is:
This current incorrect layout
But what I want to see is:
Something like this
Can somebody tell me what I am doing wrong?
NB: These images are just showing pages 1 and 2.
EDIT:
I think I've managed to work something out, but it doesn't feel like the correct way to go about it.
I've changed the code that adds the table from the HTML to this:
foreach (IElement element in elements)
{
document.Add((Div)element);
Div div = (Div)element;
div.SetRotationAngle(_radiansConverter.ToRadians(270));
div.SetWidth(PAGE_HEIGHT);
div.SetHorizontalAlignment(iText.Layout.Properties.HorizontalAlignment.RIGHT);
}
I am still using the page orientation handler to rotate the actual page, but also setting the table to rotate by 270 degrees, and then setting the width of the table to be slightly less than the height of the page.
Which is better than what I was after from the second image I posted (I messed up what I thought the orientation of the table should be).
Also the print preview for 2 x 2 looks like what I am after with the top of the table being aligned along the left side rather than the right:
I am trying to write a code analysis extension for visual studio using MEF. I have implemented the ITagger interface for an IErrorTag along with the required ITaggerProvider. As a result, i get the expected squiggles in the editor window for the issues my code analysis finds. However, when hovering above the squiggles with the mouse, the respective tooltip content is never displayed.
Here is a minimalistic example which has the same problem:
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using System;
using System.Collections.Generic;
namespace CodeAnalyzer
{
struct DummyIssue
{
public int Line; // one based line
public string ToolTip;
public DummyIssue(int line, string toolTip)
{
Line = line;
ToolTip = toolTip;
}
}
internal class DummyCodeCheckTagger : ITagger<IErrorTag>
{
readonly List<DummyIssue> mIssues;
readonly ITextView TextView;
public DummyCodeCheckTagger(ITextView textView)
{
TextView = textView;
mIssues = new List<DummyIssue>
{
new DummyIssue(1, "asldfjoqwet"),
new DummyIssue(7, "ASASDAER")
};
textView.LayoutChanged += Update;
}
public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
private void Update(object sender, TextViewLayoutChangedEventArgs args)
{
TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(args.NewSnapshot, 0, args.NewSnapshot.Length)));
}
IEnumerable<ITagSpan<IErrorTag>> ITagger<IErrorTag>.GetTags(NormalizedSnapshotSpanCollection spans)
{
var issues = mIssues;
foreach (var span in spans)
{
foreach (var issue in issues)
{
int zeroBasedLine = issue.Line - 1;
ITextSnapshotLine snapshotLine = TextView.TextSnapshot.GetLineFromLineNumber(zeroBasedLine);
SnapshotSpan snapshotSpan = snapshotLine.Extent;
if (spans.IntersectsWith(snapshotSpan))
{
yield return new TagSpan<IErrorTag>(snapshotSpan, new ErrorTag(PredefinedErrorTypeNames.SyntaxError, issue.ToolTip));
}
}
}
}
}
}
The result looks like this:
tooltip not displaying
What am i missing to get the tooltip displayed?
Fater's comment above led me to think about the problem again. Since i already tried the suggestions in the document posted by fater without success, i started thinking if the problem could be somewhere else.
It turns out that the ITagger implementation was not the problem, but the ITaggerProvider implementation caused the strange behavior. For that,
I pretty much followed the VSIX ErrorList example implementing a SpellChecker, which contains the following code
/// <summary>
/// Create a tagger that does spell checking on the view/buffer combination.
/// </summary>
public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
{
ITagger<T> tagger = null;
// Only attempt to spell check on the view's edit buffer (and multiple views could have that buffer open simultaneously so
// only create one instance of the spell checker.
if ((buffer == textView.TextBuffer) && (typeof(T) == typeof(IErrorTag)))
{
var spellChecker = buffer.Properties.GetOrCreateSingletonProperty(typeof(SpellChecker), () => new SpellChecker(this, textView, buffer));
// This is a thin wrapper around the SpellChecker that can be disposed of without shutting down the SpellChecker
// (unless it was the last tagger on the spell checker).
tagger = new SpellCheckerTagger(spellChecker) as ITagger<T>;
}
return tagger;
}
The point is, that the code above only creates an ITagger for a certain view. In that case the created tagger is used only for providing the squiggles in the editor window view. Visual Studio uses a different tagger instance for providing the tooltips for the squiggles and another tagger instance for coloring the scroll bar in the editor window. I had assumed that this would be done by one single tagger instance.
I'm generating a .pdf file using the iText 7 library. And I would like to basically duplicate every page, preferably using a PdfDocumentEvent.END_PAGE EventHandler. Because I would like to have each duplicate page, alternating after each other with a different header/footer.
The following image will illustrate the desired result:
I realise that this desired result can probably be achieved by generating a single instance first and then using another reader and writer to duplicate each page and add the header/footer with some kind of stamper.
But because of different requirements I would like to solve this while generating. But it might just not be possible the way I want to solve this.
I've tried a couple of things already but I get some strange behaviours and I can't seem to figure this out by myself. I'll add some of this code, which hopefully also helps explaining the issue further.
This is the example code to generate the document:
protected void ManipulatePdf(string dest)
{
var writer = new PdfWriter(dest);
var pdfDoc = new PdfDocument(writer);
var doc = new Document(pdfDoc, PageSize.A4);
//Custom EventHandler that adds a page footer
pdfDoc.AddEventHandler(PdfDocumentEvent.END_PAGE, new CustomEndPageEventHandler(pdfDoc));
//The actual generating of the document
for (int i = 0; i < 3; i++)
{
doc.Add(new Paragraph("Test " + (i + 1)));
if (i != 2)
{
doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
/* I would like to add that in reality it is not known exactly when page-breaks will occur */
doc.Close();
}
And below is the code I have so far for adding the extra pages. Please note that this code is not correct and gives some strange behaviour.
There is no code for alternating the header/footer yet, this code was simply supposed to insert duplicates between each page.
I'll also add another image below with the generated result, but this is not what is expected.
protected class CustomEndPageEventHandler : IEventHandler
{
protected PdfFormXObject placeholder;
protected float side = 20;
protected float x = 300;
protected float y = 25;
protected float space = 4.5f;
protected float descent = 3;
public CustomEndPageEventHandler(PdfDocument pdf) {
placeholder =
new PdfFormXObject(new Rectangle(0, 0, side, side));
}
public void HandleEvent(Event currentEvent)
{
if(!(currentEvent is PdfDocumentEvent docEvent)) return;
var pdf = docEvent.GetDocument();
var page = docEvent.GetPage();
var pageNumber = pdf.GetPageNumber(page);
var pageSize = page.GetPageSize();
var pdfCanvas = new PdfCanvas(
page.GetLastContentStream(), page.GetResources(), pdf);
var canvas = new Canvas(pdfCanvas, pdf, pageSize);
Paragraph p = new Paragraph()
.Add($"{pageNumber}");
canvas.ShowTextAligned(p, x, y, TextAlignment.RIGHT);
pdfCanvas.AddXObject(placeholder, x + space, y - descent);
pdfCanvas.Release();
//Here starts the custom code to insert a duplicate
if ((pageNumber % 2) != 0 && pageNumber < 6)
{
pdf.AddPage(pageNumber + 1, page);
pdf.AddNewPage(pageNumber+2);
}
}
public void WriteTotal(PdfDocument pdf) {
var canvas = new Canvas(placeholder, pdf);
canvas.ShowTextAligned($"/{pdf.GetNumberOfPages()}",0, descent, TextAlignment.LEFT);
}
}
The above code results into something like this. I've tried a couple of different variations of this, when you for example when you remove the line pdf.AddNewPage(pageNumber+2); then the page with Test 2 and Test 3 are overlapping each other.
I'm actually stuck on this and would really like to know if this can be done in the PdfDocumentEvent.END_PAGE EventHandler, so I'm looking forward to seeing what you can come up with.
I have a Panel filled with a lot of controls for users to fill. These include textboxes, checkboxes, radiobuttons etc. It is a long form to fill so the controls are in a scrollable panel. What I need is to save the whole panel as pdf. I think PDFsharp is a good library to be able to save any text or image as a pdf file but I don't want to write code for every single control inside the panel. I once wrote a class to create a pdf file from a Control object. It was iterating all inner controls (and their inner controls until no inner control is left) of the given control and write their Text property (yes/no for chekable controls) to pdf using their Location and Size properties. I could not find it now but I remember it was having issues with some of the DevExpress controls I use so I didn't bother writing it again. (Edit: I had to, you can find it below.) I think taking a screenshot and save that image as pdf would be nice but I couldn't find out how to achieve it. This question seems like it but there is no satisfying answer to that.
So, screenshot or not I'm open for any advice. There should be many occasions where users must fill long forms and be able to keep it as pdf. Again, any advice or workaround would be appreciated. (I think about creating the form using html, displaying it in a WebBrowser control and using an html to pdf library but I really prefer using my existent form)
Many Thanks.
Edit:
I had to write something iterates inner controls of a container control (like a panel) and writes every inner control to a pdf using their Location, Size and Font properties though, I don't recommend to use it (at least as it is) because of these:
It sets the page's size to given control's size and use only one (usually huge) pdf page. You can add a logic to split it to pages if you need to. (I didn't, but I guess you'll probably need your pdf more printer friendly).
Cheeso's method (using a FlowDocument) is a much more "legitimate" way for a task like this. I prefer using that over this but I didn't have a choice in this instance.
I used PDFsharp in this code. You can find it in it's hompage or it's CodePlex page.
PdfReport class:
private PdfDocument Document;
public Control Control { get; private set; }
public PdfReport(Control control) { Control = control; }
public PdfDocument CreatePdf(PdfDocument document = null)
{
Document = document != null ? document : new PdfDocument();
PdfPage page = Document.AddPage();
page.Height = Control.Height;
page.Width = Control.Width;
XGraphics gfx = XGraphics.FromPdfPage(page);
foreach (PdfItem item in CreatePdf(new Point(0, 0), Control.Controls))
{
XStringFormat format = item.IsContainer ? XStringFormats.TopLeft : item.TextAlign == ContentAlignment.BottomCenter ? XStringFormats.BottomCenter : item.TextAlign == ContentAlignment.TopLeft ? XStringFormats.TopLeft : item.TextAlign == ContentAlignment.TopCenter ? XStringFormats.TopCenter : XStringFormats.Center;
gfx.DrawString(item.Text, item.Font, item.Brush, new XRect(item.Location, item.Size), format);
}
return Document;
}
private IEnumerable<PdfItem> CreatePdf(Point location, Control.ControlCollection controls)
{
List<PdfItem> items = new List<PdfItem>();
foreach (Control control in controls)
{
if (control.Controls.Count > 0)
items.AddRange(CreatePdf(control.Location, control.Controls));
items.Add(new PdfItem(control, location));
}
return items;
}
public void SaveAsPdf(string path, bool open = false)
{
CreatePdf().Save(path);
if (open)
Process.Start(path);
}
PdfItem class:
public string Text { get; set; }
public Point Location { get; set; }
public Size Size { get; set; }
public Font Font { get; set; }
public bool IsContainer { get; set; }
public ContentAlignment TextAlign { get; set; }
public Color ForeColor { get; set; }
public XBrush Brush { get { return new SolidBrush(ForeColor); } }
public PdfItem() { }
public PdfItem(string text, Point location, Font font, Color foreColor, Size size, bool isContainer = false, ContentAlignment alignment = ContentAlignment.MiddleCenter)
{
Text = text;
Location = location;
Size = size;
Font = new Font(font.FontFamily, font.Size, font.Style, GraphicsUnit.World);
TextAlign = alignment;
ForeColor = foreColor;
IsContainer = isContainer;
}
public PdfItem(string text, Point location, Size size)
: this(text, location, new Font("Calibri", 12), Color.Black, size) { }
public PdfItem(Control control, Point parentLocation)
: this(control.Text, control.Location, control.Font, control.ForeColor, control.Size, control.Controls.Count > 0)
{
Location = new Point(Location.X + parentLocation.X, Location.Y + parentLocation.Y);
IEnumerable<PropertyInfo> properties = control.GetType().GetProperties();
if (properties.FirstOrDefault(p => p.Name == "TextAlign" && p.PropertyType == typeof(ContentAlignment)) != null)
TextAlign = (control as dynamic).TextAlign;
if (properties.FirstOrDefault(p => p.Name == "Checked" && p.PropertyType == typeof(bool)) != null)
{
string title = control.Text != null && control.Text.Length > 0 ? string.Format("{0}: ", control.Text) : string.Empty;
Text = string.Format("{0}{1}", title, (control as dynamic).Checked ? "Yes" : "No");
}
}
Regarding
. I think taking a screenshot and save that image as pdf would be nice but I couldn't find out how to achieve it.
There is a tool called "cropper" available on codeplex.com. It is designed to be used as a user tool that can take screenshots. It is managed code, open source.
I can imagine embedding some of the cropper magic into your app so that you could take that screenshot. I can also imagine this would be useful for collecting a diagnostic image of the screen at the time of a problem.
On the other hand... if you are interested in producing a printed form that reproduces the content on the screen, then I think you should be using WPF, in which case doing what you want is pretty easy. For example, this question describes how to do a print-preview for a FlowDocument. From that point your user can print to PDF (if he has a PDF printer installed) or print to XPS, or print to a physical output device, and so on.
I don't know if this would help you or not, but DocRaptor.com's pdf api could be built in so it would do it for you, no matter what the user inputs. It uses basic html.
As you can use the below :)
YourPanel.AutoSize = true;
int width = YourPanel.Size.Width;
int height = YourPanel.Size.Height;
Bitmap bm = new Bitmap(width, height);
YourPanel.DrawToBitmap(bm, new Rectangle(0, 0, width, height));
string outputFileName = #"C:\YourDirectory/myimage.bmp";
using (MemoryStream memory = new MemoryStream())
{
using (FileStream fs = new FileStream(outputFileName, FileMode.Create, FileAccess.ReadWrite))
{
bm.Save(memory, ImageFormat.Bmp);
Clipboard.SetImage(bm);
byte[] bytes = memory.ToArray();
fs.Write(bytes, 0, bytes.Length);
}
}
YourPanel.AutoSize = false;
The Clipboard.SetImage will send you bm to the clipboard so you can paste them to your pdf form or whatever document
This also has an example built in that saves it as a image for you if you want.
The trick here is Autosize for your panel. It needs to be set to true so the panel resizes itself as a whole area visible, then right after you do your work you can resize it to false so it uses scrollbars again for the users screen (you may see it flash for half a second, but this code does work.
Saving it in a PDF I personally just prefer to write it there as my clipboard or you can write byte. But ITextSharp is a great library for the extension to work with!
I Really hope this helps.
Hi
I am using itextsharp to generate a pdf file.I am placing a backgound image on it and want that image on all the pages .But when the first page is completed the text move to next page automatically so the image is not appearing on the new page.
Is there a way to identify the end of page so that we can add a new page and then set the image first so will appear in background and then can add the remaining text.
All is i want a image in background on all the pages of pdf file.
I suggest you use a page event:
myWriter.setPageEvent(new BackgroundPageEvent(backgroundImage));
class BackgroundPageEvent extends PdfPageEventHelper {
Image backgroundImage = null;
public BackgroundPageEvent( Image img ) {
backgroundImage = img;
}
public void onStartPage(PdfWriter writer, Document doc) {
PdfContentByte underContent = writer.getDirectContentUnder();
underContent.addImage(backgroundImage);
}
}
With the above code, backgroundImage will be added to the "under content" as each page is created. No need to worry about when to add it yourself... iText will figure that out for you, and the first thing in the underContent of each page will be your image. You might need to play around with the various overrides of addImage to get the size you want.
I believe you can also query doc for the current page size if it varies in your program. If not, you should be able to create the image you pass in with an absolute position/scale (which may be what you're doing already).
PdfPageEvent has a number of other events you can override. PdfPageEventHelper covers all the bases with "no ops" so you can just override the event[s] you want:
OnStartPage
OnEndPage
OnCloseDocument
OnParagraph
OnParagraphEnd
OnChapter
OnChapterEnd
OnSection
OnSectionEnd
OnGenericTag
Generic tag is actually Really Handy. You can give a generic tag (a string) to just about anything within your document, and your OnGenericTag override will be called with the rect that was used to draw whatever it was you tagged. All kinds of spiffy possibilities.
Just check PdfWriter.PageNumber property like this:
using (FileStream fs = File.Create("test.pdf"))
{
Document document = new Document(PageSize.A4, 72, 72, 72, 72);
PdfWriter writer = PdfWriter.GetInstance(document, fs);
document.Open();
int pageNumber = -1;
for (int i = 0; i < 20; i++)
{
if (pageNumber != writer.PageNumber)
{
// Add image
pageNumber = writer.PageNumber;
}
// Add something else
}
document.Close();
}