static void Main(string[] args)
{
Telerik.Windows.Documents.Flow.Model.RadFlowDocument document = CreateDocument();
Telerik.Windows.Documents.Flow.FormatProviders.Docx.DocxFormatProvider provider = new Telerik.Windows.Documents.Flow.FormatProviders.Docx.DocxFormatProvider();
string outputFilePath = "output.docx";
File.Delete(outputFilePath);
using (Stream output = File.OpenWrite(outputFilePath))
{
provider.Export(document, output, TimeSpan.FromSeconds(10));
}
Process.Start(new ProcessStartInfo() { FileName = outputFilePath, UseShellExecute = true });
}
//Not working example
private static RadFlowDocument CreateDocument()
{
int FullPercentWidth = 100;
var document = new RadFlowDocument();
var editor = new RadFlowDocumentEditor(document);
editor.InsertSection();
var header = document.Sections.First().Headers.Add().Blocks.AddParagraph();
header.TextAlignment = Alignment.Center;
editor.MoveToParagraphStart(header);
editor.InsertLine("Dissemination Label");
var br = new Break(document);
header.Inlines.Add(br);
editor.MoveToParagraphEnd(header);
editor.InsertText("Test");
editor.MoveToParagraphStart(document.Sections.First().Blocks.AddParagraph());
editor.InsertParagraph();
editor.InsertLine("First Header");
editor.InsertLine("Second Header");
editor.TableFormatting.StyleId = BuiltInStyleNames.TableGridStyleId;
document.StyleRepository.AddBuiltInStyle(BuiltInStyleNames.TableGridStyleId);
Table table = editor.InsertTable();
table.PreferredWidth = new TableWidthUnit(TableWidthUnitType.Percent, FullPercentWidth);
table.LayoutType = TableLayoutType.AutoFit;
TableRow headerRow = table.Rows.AddTableRow();
var cell = headerRow.Cells.AddTableCell();
var cellParagraph = cell.Blocks.AddParagraph();
_ = cellParagraph.Inlines.AddRun("ID");
var cell2 = headerRow.Cells.AddTableCell();
var cellParagraph2 = cell2.Blocks.AddParagraph();
_ = cellParagraph2.Inlines.AddRun("Title");
var cell3 = headerRow.Cells.AddTableCell();
var cellParagraph3 = cell3.Blocks.AddParagraph();
_ = cellParagraph3.Inlines.AddRun("Page Number");
for (var i = 0; i < 3; i++)
{
var dataRow = table.Rows.AddTableRow();
string id = "ID-" + i;
var cell4 = dataRow.Cells.AddTableCell();
var cellParagraph4 = cell4.Blocks.AddParagraph();
_ = cellParagraph4.Inlines.AddRun(id);
var cell5 = dataRow.Cells.AddTableCell();
var cellParagraph5 = cell5.Blocks.AddParagraph();
_ = cellParagraph5.Inlines.AddRun($"Fake Title {i}");
var cell6 = dataRow.Cells.AddTableCell();
var cellParagraph6 = cell6.Blocks.AddParagraph();
editor.MoveToParagraphStart(cellParagraph6);
editor.InsertField($"PAGEREF bookmark-{id}", string.Empty);
}
for (var i = 0; i < 3; i++)
{
var id = "ID-" + i;
var section = document.Sections.AddSection();
section.SectionType = SectionType.NextPage;
var header2 = document.Sections.Count == 1
? document.Sections.AddSection().Headers.Add().Blocks.AddParagraph()
: document.Sections.Last().Headers.Add().Blocks.AddParagraph();
editor.MoveToParagraphStart(header2);
editor.InsertLine("Dissemination Label");
editor.InsertBreak(BreakType.LineBreak);
editor.InsertLine("Fake Header");
editor.InsertText("Display name");
var table2 = new Table(document)
{
PreferredWidth = new TableWidthUnit(TableWidthUnitType.Percent, FullPercentWidth),
LayoutType = TableLayoutType.AutoFit
};
var headerRow2 = table2.Rows.AddTableRow();
headerRow2.CanSplit = false;
var headerCell = headerRow2.Cells.AddTableCell();
var headerParagraph = headerCell.Blocks.AddParagraph();
headerParagraph.Inlines.AddRun("Title").FontWeight = FontWeights.Bold;
headerParagraph.Spacing.SpacingAfter = 0;
headerCell.ColumnSpan = 3;
table2.LayoutType = TableLayoutType.FixedWidth;
var row = InsertRow(table2);
var cell7 = row.Cells.AddTableCell();
var cellParagraph7 = cell7.Blocks.AddParagraph();
_ = cellParagraph7.Inlines.AddRun("Stuff and things");
var cell8 = row.Cells.AddTableCell();
var cellParagraph8 = cell8.Blocks.AddParagraph();
_ = cellParagraph8.Inlines.AddRun($"Stuff and things-{id}");
editor.InsertBookmark($"bookmark-{id}");
_ = InsertRow(table2);
_ = InsertRow(table2);
document.Sections.Last().Blocks.Add(table2);
var table3 = new Table(document)
{
PreferredWidth = new TableWidthUnit(TableWidthUnitType.Percent, FullPercentWidth),
LayoutType = TableLayoutType.AutoFit
};
var fakeText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed laoreet finibus nulla sit amet consectetur. Fusce dignissim sapien congue augue hendrerit, eu rutrum orci lacinia. Maecenas sit amet augue ut arcu consequat molestie ac pretium nulla. Donec venenatis rhoncus pulvinar. Aliquam vel est vitae lacus porta aliquam. Morbi aliquet vulputate turpis, ut vulputate elit accumsan at. Vivamus interdum dictum arcu vel euismod. Curabitur commodo eu nisi ut ultrices. Duis at auctor eros. Vivamus et metus ligula. Vestibulum feugiat velit a feugiat sodales. Sed vitae urna sodales, faucibus felis non, sagittis diam.\r\n\r\nPraesent turpis est, aliquet consectetur felis et, pharetra placerat ipsum. Sed at consectetur metus. Integer dictum iaculis libero, interdum vehicula ipsum convallis a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed pretium ac quam id finibus. Maecenas bibendum magna vel rhoncus eleifend. Etiam nec ante nulla. Etiam lacinia vulputate quam, et ullamcorper magna fermentum quis. Suspendisse potenti. Quisque quis nulla non velit lacinia laoreet. Fusce et lacinia enim, sit amet rhoncus eros. Etiam placerat fringilla nibh ac commodo.\r\n\r\nMorbi ac commodo elit. Sed a leo quis sem convallis volutpat eget et nunc. In laoreet eleifend ullamcorper. Phasellus pharetra molestie eleifend. Cras consequat risus ac est accumsan sagittis. Suspendisse facilisis ultrices ipsum, vitae porttitor augue tincidunt ac. Ut sagittis nisl tristique efficitur aliquam. Pellentesque molestie mauris id ipsum lacinia, a vehicula eros molestie. Aliquam quis sagittis tellus.";
for (var j = 0; j < 2; j++)
{
var row2 = InsertRow(table3);
var cell9 = row2.Cells.AddTableCell();
var cellParagraph9 = cell9.Blocks.AddParagraph();
_ = cellParagraph9.Inlines.AddRun(fakeText);
}
document.Sections.Last().Blocks.Add(table3);
}
FlowExtensibilityManager.NumberingFieldsProvider = new NumberingFieldsProvider();
foreach (var s in document.Sections)
{
s.Footers.Add();
Footer f = s.Footers.Default;
Paragraph paragraph = f.Blocks.AddParagraph();
paragraph.TextAlignment = Alignment.Right;
editor.MoveToParagraphStart(paragraph);
editor.InsertText("Page ");
editor.InsertField("PAGE", string.Empty);
editor.InsertText(" of ");
editor.InsertField("NUMPAGES", string.Empty);
var paragrpah2 = s.Blocks.AddParagraph();
editor.MoveToParagraphStart(paragrpah2);
}
document.UpdateFields();
return document;
}
private static TableRow InsertRow(Table table)
{
TableRow row = new TableRow(table.Document);
table.Rows.Add(row);
return row;
}
It would be great if the SkiaImageExportSettings offer a DocumentUnhandledException event allowing the developer to handle specific errors when exporting to image formats.
This would be an essential improvement in the existing Exception handling mechanism and would enable exporting PDF documents to images even though some parts of the image may not be completely supported: https://docs.telerik.com/devtools/document-processing/libraries/radpdfprocessing/features/handling-document-exceptions
When exporting a PDF page to an image with the SkiaImageFormatProvider the following error occurs:
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'
Import the following HTML content and export it to DOCX format:
<p>Here is my list</p>
<ol start="108" style="list-style-type: lower-latin;">
<li>Item 1</li>
<li>Item 2</li>
</ol>
Expected result:
Actual result:
The PDF/A-1 standard uses the PDF Reference 1.4 and specifies two levels of compliance:
- PDF/A-1b - Its goal is to ensure reliable reproduction of the visual appearance of the document.
- PDF/A-1a - Its objective is to ensure that documents content can be searched and re-purposed. This compliance level has some additional requirements:
Since the PdfProcessing and its PdfFormatProvider is compliant with the PDF Reference 1.7. , the produced documents are created with this version as well:
Provide the ability to work with images in headers/footers of a worksheet. Note that large images put in headers are visualized as watermarks on printing/exporting to PDF.
Watermark in Excel: https://support.office.com/en-us/article/add-a-watermark-in-excel-a372182a-d733-484e-825c-18ddf3edf009
When converting HTML to DOCX, margins set on an HTML element are ignored. These styles are exported correctly when the HTML passed to the converter is formatted with indents. The following XUnit test demonstrates this behavior with a simplified example.
using Telerik.Windows.Documents.Flow.FormatProviders.Docx;
using Telerik.Windows.Documents.Flow.FormatProviders.Html;
namespace MSPI.Tests.Unit;
public class WordExportTest
{
[Fact]
public async Task TextExport()
{
const string formattedDocumentSavePath = @"C:\Testing\export-test-formatted.docx";
const string formattedContent = """"
<p>Test paragraph</p>
<ol style="margin-left: 100px;">
<li>Item 1</li>
<li>Item 2</li>
</ol>
"""";
const string minifiedDocumentSavePath = @"C:\Testing\export-test-minified.docx";
const string minifiedContent = """"<p>Test paragraph</p><ol style="margin-left: 100px;"><li>Item 1</li><li>Item 2</li></ol>"""";
var htmlFormatProvider = new HtmlFormatProvider();
var docxFormatProvider = new DocxFormatProvider();
await using var minifiedDocumentMemoryStream = new MemoryStream();
var minifiedRadFlowDocument = htmlFormatProvider.Import(minifiedContent, TimeSpan.FromSeconds(30));
docxFormatProvider.Export(minifiedRadFlowDocument, minifiedDocumentMemoryStream, TimeSpan.FromSeconds(30));
var minifiedBytes = minifiedDocumentMemoryStream.ToArray();
await File.WriteAllBytesAsync(minifiedDocumentSavePath, minifiedBytes);
await using var formattedDocumentMemoryStream = new MemoryStream();
var formattedRadFlowDocument = htmlFormatProvider.Import(formattedContent, TimeSpan.FromSeconds(30));
docxFormatProvider.Export(formattedRadFlowDocument, formattedDocumentMemoryStream, TimeSpan.FromSeconds(30));
var formattedBytes = formattedDocumentMemoryStream.ToArray();
await File.WriteAllBytesAsync(formattedDocumentSavePath, formattedBytes);
}
}
The minified HTML produces the following document:
The formatted HTML produces the following document:
Read the documentation for CancelationTokenSource.CancelAfter:
this method will throw an ArgumentOutOfRangeException when: delay.TotalMilliseconds is less than -1 or greater than Int32.MaxValue (or UInt32.MaxValue - 1 on some versions of .NET). Note that this upper bound is more restrictive than TimeSpan.MaxValue.
----------------------------------------------------
your code in CancelationTokenSourceFactory.CreateTokenSource does this check:
if (timeSpan.HasValue && timeSpan.Value != TimeSpan.MaxValue)
this check for TimeSpan.MaxValue seems totally pointless here, if timeSpan is anything between ~2147483647 and 922337203685476 milliseconds long this will still just throw a ArgumentOutOfRangeException.
I suspect that this check was intended as a way to prevent creating a cancellation timer that never triggers in the CancellationTokenSource, which should look like this:
if (timeSpan.HasValue && timeSpan != Timeout.InfiniteTimeSpan) //Timeout.InfiniteTimeSpan is -1 milliseconds
In the 2025 version of the Documents packages, "TimeSpan? timeout" were added to a number of interfaces, with the old versions obsoleted, for example: IWorkbookFormatProvider.Import & IWorkbookFormatProvider.Export.
This is a very strange choice, because this limits the flexibility of the interfaces for no reason at all. By only providing the TimeSpan parameter and not a CancellationToken is currently impossible to cancel the operation because e.g. an API request was canceled.
Internally these methods are implemented by first creating a cancellation token using
using CancellationTokenSource cancellationTokenSource = CancelationTokenSourceFactory.CreateTokenSource(timeout);
the token from this CancellationTokenSource is then passed to a protected method. Because this internal method uses a CancellationToken anyway, there is practically 0 development cost to exposing this in the interface, which makes the choice not to do so even more confusing.
The interfaces should expose methods that take a CancellationToken instead of a TimeSpan. This would allow for the same functionality as the TimeSpan parameter, by simply passing a cancellation token with a CancelAfter set with a TimeSpan, and an extension method could be provided for the interface which does exactly that, so users can still call these methods with a TimeSpan parameter if they wish to do so for convenience.
Please, in the next version, make these interfaces methods like this:
Workbook Import(Stream input, CancellationToken cancellationToken = default);
void Export(Workbook workbook, Stream output, CancellationToken cancellationToken = default);
and, for convenience, add extension methods for these like this:
public static class WorkbookFormatProviderExtensions
{
public static Workbook Import(this IWorkbookFormatProvider workbookFormatProvider, Stream input, TimeSpan? timeout)
{
using CancellationTokenSource cancellationTokenSource = CancelationTokenSourceFactory.CreateTokenSource(timeout);
return workbookFormatProvider.Import(input, cancellationTokenSource.Token);
}
}
Affected interfaces I've run into so far:
There may be more with this same pattern, I haven't checked.
NullReferenceException when inserting a document containing a table with a document variable having a line break (\n) in its value.
Add support for pivot tables.
at System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) at System.Collections.Generic.Dictionary`2.FindValue(TKey key) at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key) at Telerik.Windows.Documents.Spreadsheet.FormatProviders.OpenXml.Xlsx.Model.Elements.Worksheets.ConditionalFormattingRuleElementX14.OnAfterRead(IXlsxWorksheetImportContext context) in C:\Work\document-processing\Documents\Spreadsheet\FormatProviders\OpenXml\Xlsx\Model\Elements\Worksheets\ConditionalFormatting\x14\ConditionalFormattingRuleElementX14.cs:line 62
Purpose: Long-term archiving of electronic documents with full semantic structure.
Level "1a" ensures:
Tagged PDF (with proper logical structure and reading order)
Unicode text for proper text extraction and searchability
Embedded fonts (for consistent rendering)
Restrictions:
No audio/video
No encryption
No JavaScript
No external content (everything must be self-contained)
Based on: PDF 1.4 (Acrobat 5)