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 used for merging:
private void MergeDifferentDocumentsPagesWithFixed()
{
string[] documentsToMerge =
{
"14301-STOCK_Proforma.pdf",
"14302-STOCK_Proforma.pdf",
"14303-STOCK_Proforma.pdf",
"14304-STOCK_Proforma.pdf",
"14305-STOCK_Proforma.pdf",
"14330-STOCK_Proforma.pdf"
};
RadFixedDocument doc = new RadFixedDocument();
PdfFormatProvider provider = new PdfFormatProvider();
foreach (string current_item in documentsToMerge)
{
string path = @"..\..\Samples\" + current_item;
RadFixedDocument docToMerge;
if (!File.Exists(path))
{
continue;
}
using (Stream stream = File.OpenRead(path))
{
docToMerge = provider.Import(stream);
}
doc.Merge(docToMerge);
}
string outputFilePath = @"..\..\mergedFixed.pdf";
File.Delete(outputFilePath);
using (Stream output = File.OpenWrite(outputFilePath))
{
provider.Export(doc, output);
}
Process.Start(new ProcessStartInfo() { FileName = outputFilePath, UseShellExecute = true });
}
Workaround:
private void MergeDifferentDocumentsPages(string resultFileName)
{
string[] documentsToMerge =
{
"14301-STOCK_Proforma.pdf",
"14302-STOCK_Proforma.pdf",
"14303-STOCK_Proforma.pdf",
"14304-STOCK_Proforma.pdf",
"14305-STOCK_Proforma.pdf",
"14330-STOCK_Proforma.pdf"
};
File.Delete(resultFileName);
using (PdfStreamWriter fileWriter = new PdfStreamWriter(File.OpenWrite(resultFileName)))
{
foreach (string documentName in documentsToMerge)
{
string path = @"..\..\Samples\" + documentName;
using (PdfFileSource fileToMerge = new PdfFileSource(File.OpenRead(path)))
{
foreach (PdfPageSource pageToMerge in fileToMerge.Pages)
{
fileWriter.WritePage(pageToMerge);
}
}
}
}
Process.Start(new ProcessStartInfo() { FileName = resultFileName, UseShellExecute = true });
}
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 });
}
When exporting a document with the .NET Framework implementation, only subsets of the used fonts are embedded in the document. This is currently not available in the Silverlight implementation, and it most probably won't be available in the .NET Standard implementation, as the current implementation is dependent on the WPF font classes. Provide API to plug similar logic when using different platforms as well.
This item wil handle the TrueType OpenType Font format. For the Compact Font Format (CFF) please follow: Export subset of Compact Font Format (CFF) fonts for platforms different than .NET Framework
Currently, when registering *.otf font file with FontsRepository.RegisterFont method an exception is thrown during the font creation. WORKAROUND: The font file may be converted to TTF format (*.ttf) which is successfully registered. This conversion may be achieved with FontForge by opening the font file and then choosing File -> Generate Fonts -> TrueType -> Generate. More information on FontForge program may be found here: https://fontforge.github.io/en-US/