PdfProcessing: Handle merge of documents containing fields with the same invalid name.
When calling a PDF export in a high-concurrency environment the internal state can get corrupted because it uses a non-thread safe collection internally.
Sample code:
void WriteToPdf(RadFlowDocument document, Stream outputStream) {
PdfFormatProvider pdfWriter = new() {};
pdfWriter.Export(document, outputStream);
}
When calling it like this:
Parallel.ForEachAsync(listOfDocuments, (document, _) => { WriteToPdf(document, Stream.Null); });
An exception may occur:
System.InvalidOperationException: Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
at System.Collections.Generic.Dictionary`2.FindValue(TKey key)
at Telerik.Windows.Documents.Fixed.Model.Fonts.FontsRepository.TryCreateFont(FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontBase& font)
at Telerik.Windows.Documents.Flow.FormatProviders.Pdf.Utils.Extensions.CopyPropertiesFrom(CharacterProperties fixedProperties, PdfExportContext context, CharacterProperties properties)
at Telerik.Windows.Documents.Flow.FormatProviders.Pdf.Export.PdfExporter.CreateListLevel(ListLevel flowLevel)
at Telerik.Windows.Documents.Flow.FormatProviders.Pdf.Export.PdfExporter.CreateList(List flowList)
at Telerik.Windows.Documents.Flow.FormatProviders.Pdf.Export.PdfExporter.ExportDocument(RadFlowDocument document, RadFixedDocumentEditor editor)
at Telerik.Windows.Documents.Flow.FormatProviders.Pdf.Export.PdfExporter.ExportInternal()
at Telerik.Windows.Documents.Common.FormatProviders.FormatProviderBase`1.Export(T document, Stream output)
at xxxxx.Application.Common.PdfGeneration.PdfWriter.WriteToPdf(RadFlowDocument document, Stream outputStream)
The state is then corrupted forever, until the application is restarted.
Realistic scenario where this is also reproduced: Web application that generates PDFs and is called concurrently.
This is the code snippet for reproducing the error message:
static void Main(string[] args)
{
string filePath = "Lorem ipsum dolor sit amet.pdf";
//load a random document
PdfFormatProvider provider = new PdfFormatProvider();
RadFixedDocument originalDocument;
using (Stream stream = File.OpenRead(filePath))
{
originalDocument = provider.Import(stream);
}
//draw something on the first page
FixedContentEditor editor = new FixedContentEditor(originalDocument.Pages[0]);
editor.GraphicProperties.IsFilled = true;
editor.GraphicProperties.FillColor = RgbColors.Black;
Telerik.Documents.Primitives.Rect Rect = new Telerik.Documents.Primitives.Rect(10, 10, 200, 100);
editor.DrawRectangle(Rect);
//export the pages as images and build a brand new document from the images
SkiaImageFormatProvider imageProvider = new SkiaImageFormatProvider();
imageProvider.ExportSettings.ImageFormat = SkiaImageFormat.Jpeg;
imageProvider.ExportSettings.ScaleFactor = 0.8;
imageProvider.ExportSettings.Quality = 80;
RadFixedDocument doc = new RadFixedDocument();
foreach (RadFixedPage page in originalDocument.Pages)
{
byte[] resultImage = imageProvider.Export(page);
RadFixedPage pdfpage = doc.Pages.AddPage();
editor = new FixedContentEditor(pdfpage);
Stream imageStream = new MemoryStream(resultImage);
editor.DrawImage(imageStream);
}
//export the pdf built from the images
PdfFormatProvider pdfFormatProvider = new PdfFormatProvider();
string outputPdf = @"output.pdf";
File.Delete(outputPdf);
using (Stream output = File.OpenWrite(outputPdf))
{
pdfFormatProvider.Export(doc, output);
}
Process.Start(new ProcessStartInfo() { FileName = outputPdf, UseShellExecute = true });
}
Workaround:
static void Main(string[] args)
{
string filePath = "Lorem ipsum dolor sit amet.pdf";
//load a random document
PdfFormatProvider provider = new PdfFormatProvider();
RadFixedDocument originalDocument;
using (Stream stream = File.OpenRead(filePath))
{
originalDocument = provider.Import(stream);
}
//draw something on the first page
FixedContentEditor editor = new FixedContentEditor(originalDocument.Pages[0]);
editor.GraphicProperties.IsFilled = true;
editor.GraphicProperties.FillColor = RgbColors.Black;
Telerik.Documents.Primitives.Rect Rect = new Telerik.Documents.Primitives.Rect(10, 10, 200, 100);
editor.DrawRectangle(Rect);
using (Stream output = File.OpenWrite(filePath))
{
provider.Export(originalDocument, output);
}
using (Stream stream = File.OpenRead(filePath))
{
originalDocument = provider.Import(stream);
}
//export the pages as images and build a brand new document from the images
SkiaImageFormatProvider imageProvider = new SkiaImageFormatProvider();
imageProvider.ExportSettings.ImageFormat = SkiaImageFormat.Jpeg;
imageProvider.ExportSettings.ScaleFactor = 0.8;
imageProvider.ExportSettings.Quality = 80;
RadFixedDocument doc = new RadFixedDocument();
foreach (RadFixedPage page in originalDocument.Pages)
{
byte[] resultImage = imageProvider.Export(page);
RadFixedPage pdfpage = doc.Pages.AddPage();
editor = new FixedContentEditor(pdfpage);
Stream imageStream = new MemoryStream(resultImage);
editor.DrawImage(imageStream);
}
//export the pdf built from the images
PdfFormatProvider pdfFormatProvider = new PdfFormatProvider();
string outputPdf = @"output.pdf";
File.Delete(outputPdf);
using (Stream output = File.OpenWrite(outputPdf))
{
pdfFormatProvider.Export(doc, output);
}
Process.Start(new ProcessStartInfo() { FileName = outputPdf, UseShellExecute = true });
}
NumberedHierarchical list type has inconsistent indentation after bullets.
The visual fill element is not exported to pdf:
Only the text part is present.
Import the document with RadSpreadProcessing and export it PDF format. You will notice that the cell value is displayed in the exported PDF document while in the Excel file it is hidden due to the custom format:
Merge two documents (use the attached ones) that already have embedded files with the same names.
Observed:
at Telerik.Windows.Documents.Core.Fonts.Type1.Type1Format.Type1FontSource.GetFontFamily()
This exception was originally thrown at this call stack: Telerik.Windows.Documents.Fixed.FormatProviders.Pdf.Model.Elements.DocumentStructure.DocumentCatalog.CopyEmbeddedFilesTo(Telerik.Windows.Documents.Fixed.FormatProviders.Pdf.Import.Parser.PostScriptReader, Telerik.Windows.Documents.Fixed.FormatProviders.Pdf.Import.IRadFixedDocumentImportContext) in DocumentCatalog.cs
A strange symbol is added as a name when embedding a file with an empty string as its name argument.
For example exporting the text "\uD83D\uDE0A" with "Segoe UI Symbol" font family should export a single smiling face. Instead the characters are skipped during the export as PdfProcessing is trying to export them as separate char values ("\uD83D" and "\uDE0A") and the font does not contain glyphs corresponding to these char codes.
When importing specific documents and signing a predefined SignatureField leads to not successfully validated signatures in Adobe, but successfully validated in other pdf viewers (eg. Telerik WPF PdfViewer, Foxit PDF Reader).
Solution:
When signing an existing document (after the import) we must be sure the AcroForm's ViewersShouldRecalculateWidgetAppearances property is set to false, otherwise, the exported and signed PDF document could not be shown as a signed.
if (document.AcroForm.ViewersShouldRecalculateWidgetAppearances)
{
document.AcroForm.ViewersShouldRecalculateWidgetAppearances = false;
}