0

I am generating some reports with iTextSharp 5.

Opening the generated .PDF-file, everything looks and actually is fine with most PDF-readers.

When I open the PDF using AdobeReader(DC) however, it asks me if i want to save the changes on close. Although I didn't change anything.

Hitting 'Cancel' sure makes the message window go away, but hitting save causes the file to actually shrinks in size.

before and after saving

Now what is happening there? And why? How can I disable it?

The users of the application are most likely gonna use AdobeReader as well.

I don't want them to see the save dialog anytime they open a report.

Here is my BaseReport class

public abstract class BaseReport : PdfPageEventHelper
{
    protected const string SPACE = " ";
    protected const string COLON = ":";
    protected static string NEWLINE = Environment.NewLine;
    protected Document document;
    protected PdfTemplate footerTemplate;
    protected PdfContentByte contentByte;
    protected PdfWriter writer;

    private PdfTemplate totalPageNoTemplate;
    private int lastPageNumber;

    // properties for header
    private bool done;
    // needs to be overriden in subclass order to use header feature
    protected string kundeHeader { get; set; }


    // font definitions
    protected BaseFont baseFont;
    protected Font fontFooter;
    protected Font fontGeneralText;
    protected Font fontLabelText;
    protected Font fontBoldLabelText;
    protected Font fontBoldText;
    protected Font fontSpace;
    protected Font fontLargeBoldText;

    protected int language;
    protected bool useLogo = false;
    protected bool usePageNumbers = false;
    protected bool usePrintDate = false;
    protected const string PRINT_FULLDATE_FORMAT = "dd.MM.yyyy HH:mm";
    protected const string PRINT_DATE_ONLY_FORMAT = "dd.MM.yyyy";

    protected Rectangle pagesize = PageSize.A4;
    protected float marginLeft = 80;
    protected float marginRight = 35;
    protected float marginTop = 40;
    protected float marginBottom = 40;

    private MemoryStream PDFStream { get; set; } = new MemoryStream();
    private DateTime printDate;

    public BaseReport(int language = Languages.DE, bool landscape = false)
    {
        this.language = language;
        if (landscape)
        {
            pagesize = pagesize.Rotate();
        }
    }


    public byte[] GenerateReport()
    {
        CultureInfo cultureBefore = Resources.Culture;
        try
        {
            Resources.Culture = SelectCultureForLangauge();
            PrepareReport();
            document = new Document(pagesize, marginLeft, marginRight, marginTop, marginBottom);
            BuildFonts();
            OpenDocument();
            PrepareDocument();
            GenerateContent();
            document.Close();
            return PDFStream.GetBuffer();
        } finally
        {
            Resources.Culture = cultureBefore;
        }
    }

    public void GenerateReport(string filename)
    {
        byte[] report = GenerateReport();
        using (FileStream f = new FileStream(filename, FileMode.Create))
        {
            f.Write(report, 0, report.Length);
        }
    }

    protected CultureInfo SelectCultureForLangauge()
    {
        string languageCode = GetLanguageCode();
        return CultureInfo.GetCultureInfo(languageCode);
    }

    protected string GetLanguageCode()
    {
        string languageCode = string.Empty;
        switch (language)
        {
            case Languages.FR: languageCode = "FR"; break;
            case Languages.IT: languageCode = "IT"; break;
            case Languages.EN: languageCode = "EN"; break;
            default: languageCode = "DE"; break;
        }

        return languageCode;
    }

    protected virtual void PrepareReport() { }

    protected virtual void PrepareDocument() { }

    protected abstract void GenerateContent();

    private void BuildFonts()
    {
        baseFont = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
        fontFooter = FontFactory.GetFont(FontFactory.HELVETICA, 11, Font.ITALIC, BaseColor.DARK_GRAY);
        fontGeneralText = FontFactory.GetFont(FontFactory.HELVETICA, 11, Font.NORMAL, BaseColor.BLACK);
        fontLabelText = FontFactory.GetFont(FontFactory.HELVETICA, 8.5f, Font.NORMAL, BaseColor.BLACK);
        fontBoldLabelText = FontFactory.GetFont(FontFactory.HELVETICA, 8.5f, Font.BOLD, BaseColor.BLACK);
        fontBoldText = FontFactory.GetFont(FontFactory.HELVETICA, 11, Font.BOLD, BaseColor.BLACK);
        fontSpace = FontFactory.GetFont(FontFactory.HELVETICA, 3.5f, Font.NORMAL, BaseColor.BLACK);
        fontLargeBoldText = FontFactory.GetFont(FontFactory.HELVETICA, 17, Font.BOLD, BaseColor.BLACK);
        GetFontIfAvailable();
    }

    private void GetFontIfAvailable()
    {
        string fileName = "IF_Rg";
        try
        {
            baseFont = LoadFontFromFile(fileName, true);
            fontFooter = new Font(baseFont, 11, Font.ITALIC, BaseColor.DARK_GRAY);
            fontGeneralText = new Font(baseFont, 11, Font.NORMAL, BaseColor.BLACK);
            fontLabelText = new Font(baseFont, 8.5f, Font.NORMAL, BaseColor.BLACK);
            fontBoldLabelText = new Font(baseFont, 8.5f, Font.BOLD, BaseColor.BLACK);
            fontBoldText = new Font(baseFont, 11, Font.BOLD, BaseColor.BLACK);
            fontSpace = new Font(baseFont, 3.5f, Font.NORMAL, BaseColor.BLACK);
            fontLargeBoldText = new Font(baseFont, 17, Font.BOLD, BaseColor.BLACK);
        } catch (FileNotFoundException)
        {
            LogWrapper.Warn("Font not found - using default.");
        }

    }

    protected BaseFont LoadFontFromFile(string fileName, bool embedded)
    {
        string fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\" + fileName + ".ttf";
        if (File.Exists(fontPath))
        {
            return BaseFont.CreateFont(fontPath, BaseFont.WINANSI, embedded);
        }
        else
        {
            throw new FileNotFoundException($"Fontfile {fileName} was not found!");
        }
    }

    protected Image HeaderLogo()
    {
        Image logo = Image.GetInstance(Resources.logo, BaseColor.BLACK);
        // TODO msc pick logo from debitor
        logo.ScaleToFit(100f, 100f);
        return logo;
    }

    protected void OpenDocument()
    {
        writer = PdfWriter.GetInstance(document, PDFStream);
        writer.PageEvent = this;
        writer.SetFullCompression();
        document.Open();
        contentByte = writer.DirectContent;
    }

    protected void AddLabelAt(string label, float posX, float posY)
    {
        PdfContentByte cb = writer.DirectContent;
        ColumnText column = new ColumnText(cb);
        column.SetText(new Paragraph(label + NEWLINE, fontLabelText));
        column.SetSimpleColumn(posX, 20, posX + 200, posY);
        column.Go();
    }

    protected void AddLabelOnMargin(string label)
    {
        AddLabelAt(label, document.LeftMargin - 40, writer.GetVerticalPosition(false));
    }

    protected Phrase ParaLine(string Text, Font textfont)
    {
        return new Phrase(Text, textfont);
    }

    public override void OnOpenDocument(PdfWriter writer, Document document)
    {
        if (usePageNumbers)
        {
            totalPageNoTemplate = writer.DirectContentUnder.CreateTemplate(50, 50);
        }
        if (usePrintDate)
        {
            printDate = DateTime.Now;
        }
    }

    public override void OnStartPage(PdfWriter writer, Document document)
    {
        if (useLogo || (document.PageNumber > 1 && !string.IsNullOrEmpty(kundeHeader)))
        {
            PdfContentByte canvas = writer.DirectContentUnder;
            canvas.SaveState();
            if (document.PageNumber > 1 && !string.IsNullOrEmpty(kundeHeader))
            {
                //showtextaligned only shows a single line
                //therefor the header needs to be split and its parts need to be added seperately
                string[] headerParts = kundeHeader.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                Phrase header = new Phrase(kundeHeader, fontLabelText);
                ColumnText.ShowTextAligned(canvas, Element.ALIGN_LEFT,
                        ParaLine(headerParts[0], fontLabelText),
                        document.LeftMargin,
                        document.Top + 30, 0);
                ColumnText.ShowTextAligned(canvas, Element.ALIGN_LEFT,
                        ParaLine(headerParts[1], fontLabelText),
                        document.LeftMargin,
                        document.Top + 20, 0);
            }
            if (useLogo)
            {                     
                Image logo = HeaderLogo();
                logo.SetAbsolutePosition(marginLeft - 17.5f, document.Top + document.TopMargin - 50);
                document.Add(logo);
            }
            canvas.RestoreState();
        }
    }

    public override void OnEndPage(PdfWriter writer, Document document)
    {
        if (usePageNumbers || usePrintDate)
        {
            PdfContentByte canvas = writer.DirectContentUnder;
            canvas.SaveState();
            if (usePageNumbers)
            {
                // adds current page number to the footer section of the document
                int pageN = writer.PageNumber;
                string text = Resources.LabelSeite + SPACE + pageN + "/";
                float len = fontLabelText.BaseFont.GetWidthPoint(text, fontLabelText.Size);
                ColumnText.ShowTextAligned(canvas, Element.ALIGN_LEFT,
                        ParaLine(text, fontLabelText),
                        document.LeftMargin,
                        document.Bottom - 10, 0);
                // adds template to fill in total page number (see OnCloseDocument method)
                canvas.AddTemplate(totalPageNoTemplate, document.LeftMargin + len, document.Bottom - 10);
                lastPageNumber = pageN;
            }
            if (usePrintDate)
            {
                // adds the printdate to the footer secdtion of the document
                string dateFormatted = printDate.ToString(PRINT_FULLDATE_FORMAT);
                ColumnText.ShowTextAligned(canvas, Element.ALIGN_RIGHT,
                        ParaLine(dateFormatted, fontLabelText),
                        document.Right,
                        document.Bottom - 10, 0);
            }
            canvas.RestoreState();
        }
    }

    public override void OnCloseDocument(PdfWriter writer, Document document)
    {
        if (usePageNumbers)
        {
            // fills in the total page number to the prepared template in the footer section of the document
            string text = lastPageNumber + "";
            float widthPoint = fontLabelText.BaseFont.GetWidthPoint(text, fontLabelText.Size);
            totalPageNoTemplate.Width = widthPoint;
            ColumnText.ShowTextAligned(totalPageNoTemplate, Element.ALIGN_LEFT, ParaLine(text, fontLabelText), 0, 0, 0);
        }
    }
Master Azazel
  • 612
  • 1
  • 12
  • 32
  • Most of the PDF viewers are tolerant towards minor PDF syntax issues. Adobe is tolerant too, but wants to fix these issues. That's why you get a dialog asking you if you want to save the document. Adobe wants to fix the issue so that it doesn't occur the next time you open the document. If you are using iText 5.2.x, you should upgrade, because there are known issues with that version. We are not aware of problems with other versions, although it is always possible to create a faulty PDF with iText if your code is wrong. Without seeing your code or your PDF, no one can answer your question. – Bruno Lowagie May 10 '17 at 08:09
  • Note that the problem also occurs when filling out quirky forms with iText. You can avoid the problem by fixing the forms, or by filling them out in a different way (**show us your code if this applies to you**). As for the "shrinking": I bet you are creating the PDF the way PDFs were created *before* PDF 1.5. Starting with PDF 1.5, it is possible to compress the cross-reference table and specific objects (in iText, we call this *full compression*). When Adobe Reader saves the fixed version of your PDF, it probably compresses the `xref` table and selected objects. – Bruno Lowagie May 10 '17 at 08:11
  • Thanks for the quick response! I am using iTextSharp 5.5.10.0, so that should be ok. So if i implement `full compression` for my reports, AdobeReader should be fine with it? – Master Azazel May 10 '17 at 08:14
  • No, that is not what I said. Using *full compression* should result in a reduced file size, but it doesn't fix syntax errors you may be introducing into the PDF. – Bruno Lowagie May 10 '17 at 08:15
  • Okay ill try to find a way to check the syntax using itextsharp then in addition to implementing `full compression`. Thank you! – Master Azazel May 10 '17 at 08:19
  • 1
    If you want help, share your PDF. We can check if it's a form (to solve a known problem). If it's not a form, we can verify it using Preflight to see which syntax error Adobe Reader is complaining about. You might also share your code so that we can see if you do any low-level operations that are in conflict with ISO 32000. – Bruno Lowagie May 10 '17 at 08:45
  • I updated the question to include my code. Enabling full compression results in AdobeReader (as the ONLY pdf-reader) not being able to open the file anymore at all – Master Azazel May 10 '17 at 09:01
  • 1
    One obvious error that jumps to the eye is that you are adding content in the `OnStartPage()` method. That is forbidden (as documented). Also, you are using `document.Add()` in this method. It is forbidden to use the `document` object in a page event implementation because that `document` object isn't the `document` object you think it is. It's actually an internal `PdfDocument` instance that should only be used for read-only purposes. All of these design flaws in those old iText versions are fixed in iText 7. – Bruno Lowagie May 10 '17 at 09:10
  • im specifically bound to this version. how would i go about headers from second page without the onstartpage event? and do you have any idea why adobereader will read uncompressed files but the same file, compressed, it cant ? thanks :) – Master Azazel May 10 '17 at 09:16
  • It is a common misconception that headers need to be added in the `OnStartPage()` method. There is nothing wrong with adding the headers in the `OnEndPage()` method (see the multiple examples on the official web site). As for your second comment: your assumption is wrong. If a PDF contains invalid syntax, Adobe Reader will complain *no matter which compression level you use*. – Bruno Lowagie May 10 '17 at 09:19

1 Answers1

0

iTextSharp-generated PDFs now cause Save dialog in Adobe Reader X

I had to switch PDFStream.GetBuffer() with PDFStream.ToArray(); problem solved.

Community
  • 1
  • 1
Master Azazel
  • 612
  • 1
  • 12
  • 32
  • Ah. So this effectively is a duplicate to that question and should be marked as such... – mkl May 11 '17 at 13:58