Syncfusion SFDT Format
What is SFDT?
SFDT (Syncfusion Document Format) is a JSON-based document representation format created by Syncfusion for their Document Editor component.
Why SFDT Exists
- Web-friendly: JSON is native to JavaScript
- Easy manipulation: Modify documents in browser
- Lightweight: Smaller than full OOXML XML
- Editor optimized: Built for Syncfusion Document Editor
In AppraiSys
SFDT is used as an intermediate format:
- Templates stored as DOCX
- Convert to SFDT for mail merge
- Manipulate in Syncfusion
- Convert back to DOCX for delivery
SFDT Structure Overview
Complete SFDT Document
{
"sections": [
{
"sectionFormat": {
"pageWidth": 612,
"pageHeight": 792,
"leftMargin": 72,
"rightMargin": 72,
"topMargin": 72,
"bottomMargin": 72
},
"blocks": [
{
"paragraphFormat": {},
"characterFormat": {},
"inlines": []
}
],
"headersFooters": {
"header": {},
"footer": {}
}
}
],
"characterFormat": {},
"paragraphFormat": {},
"defaultTabWidth": 36,
"trackChanges": false,
"enforcement": false,
"hashValue": "",
"saltValue": "",
"formatting": false,
"protectionType": "NoProtection",
"dontUseHTMLParagraphAutoSpacing": false,
"formFieldShading": true,
"styles": [],
"lists": [],
"abstractLists": [],
"comments": [],
"revisions": [],
"customXml": []
}
Sections
Section Structure
{
"sections": [
{
"sectionFormat": {
"pageWidth": 612, // 8.5 inches * 72 points
"pageHeight": 792, // 11 inches * 72 points
"leftMargin": 72, // 1 inch
"rightMargin": 72,
"topMargin": 72,
"bottomMargin": 72,
"differentFirstPage": false,
"different OddAndEvenPages": false,
"headerDistance": 36, // 0.5 inch
"footerDistance": 36,
"bidi": false
},
"blocks": [...],
"headersFooters": {}
}
]
}
Key properties:
- All measurements in points (72 per inch)
- Multiple sections supported
- Headers/footers per section
Blocks (Paragraphs and Tables)
Paragraph Block
{
"paragraphFormat": {
"leftIndent": 0,
"rightIndent": 0,
"firstLineIndent": 0,
"textAlignment": "Left", // Left, Center, Right, Justify
"beforeSpacing": 0,
"afterSpacing": 8,
"lineSpacing": 1.15,
"lineSpacingType": "Multiple", // Multiple, AtLeast, Exactly
"styleName": "Normal",
"listFormat": {},
"bidi": false
},
"characterFormat": {
"bold": false,
"italic": false,
"fontSize": 11,
"fontFamily": "Calibri",
"fontColor": "#000000FF", // RGBA format
"fontFamilyBidi": "Calibri"
},
"inlines": [
{
"text": "Hello World",
"characterFormat": {}
}
]
}
Table Block
{
"table": {
"rows": [
{
"cells": [
{
"blocks": [
{
"inlines": [
{ "text": "Cell content" }
]
}
],
"cellFormat": {
"cellWidth": 150,
"columnSpan": 1,
"rowSpan": 1,
"preferredWidth": 150,
"preferredWidthType": "Point",
"verticalAlignment": "Top",
"borders": {},
"shading": {}
}
}
],
"rowFormat": {
"height": 0,
"heightType": "AtLeast",
"isHeader": false,
"allowBreakAcrossPages": true
}
}
],
"grid": [150, 150], // Column widths
"tableFormat": {
"borders": {},
"cellSpacing": 0,
"leftIndent": 0,
"preferredWidth": 100,
"preferredWidthType": "Percent",
"bidi": false
}
}
}
Inlines (Content)
Text Inline
{
"text": "Sample text",
"characterFormat": {
"bold": true,
"italic": false,
"fontSize": 12,
"fontFamily": "Times New Roman",
"fontColor": "#000000FF",
"underline": "Single", // None, Single, Double, etc.
"strikethrough": "None",
"baselineAlignment": "Normal", // Superscript, Subscript
"highlightColor": "NoColor"
}
}
Line Break Inline
{
"breakType": "LineBreak",
"breakClearType": "None"
}
Image Inline
{
"imageString": "data:image/png;base64,iVBORw0KGgo...",
"width": 72, // Points
"height": 72,
"name": "image1.png",
"alternateText": "",
"title": "",
"visible": true,
"widthScale": 100,
"heightScale": 100,
"verticalPosition": 0,
"verticalOrigin": "Paragraph",
"verticalAlignment": "None",
"horizontalPosition": 0,
"horizontalOrigin": "Column",
"horizontalAlignment": "None",
"allowOverlap": true,
"textWrappingStyle": "Inline",
"textWrappingType": "Both",
"distanceBottom": 0,
"distanceLeft": 9,
"distanceRight": 9,
"distanceTop": 0,
"layoutInCell": true,
"lockAnchor": false,
"zOrderPosition": 0,
"isInlineImage": true
}
Image formats:
- Base64 encoded:
data:image/png;base64,... - URL:
https://example.com/image.png
From AppraiSys (DocumentToPdfConverter.cs):
// Download images and convert to base64
var imagesToDownload = sfdtWrapper?.Images
.Where(x => x.Value.FirstOrDefault()?.StartsWith("http") ?? false)
.ToList();
foreach (var downloadedImage in downloadedImages)
{
sfdtWrapper.Images[key].Insert(0,
$"data:{contentType};base64,{Convert.ToBase64String(bytes)}"
);
}
Styles
Paragraph Style
{
"name": "Heading 1",
"type": "Paragraph",
"paragraphFormat": {
"leftIndent": 0,
"rightIndent": 0,
"firstLineIndent": 0,
"textAlignment": "Left",
"beforeSpacing": 12,
"afterSpacing": 6,
"lineSpacing": 1.15,
"lineSpacingType": "Multiple",
"outlineLevel": "Level1"
},
"characterFormat": {
"fontSize": 16,
"fontFamily": "Calibri Light",
"fontColor": "#2F5496FF",
"bold": false,
"italic": false
},
"basedOn": "Normal",
"link": "Heading 1 Char",
"next": "Normal"
}
Character Style
{
"name": "Emphasis",
"type": "Character",
"characterFormat": {
"italic": true,
"fontColor": "#000000FF"
}
}
Lists and Numbering
List Format
{
"listFormat": {
"listId": 0,
"listLevelNumber": 0
}
}
List Definition
{
"abstractListId": 0,
"levels": [
{
"characterFormat": {},
"paragraphFormat": {
"leftIndent": 36,
"firstLineIndent": -18
},
"followCharacter": "Tab",
"listLevelPattern": "Arabic", // Arabic, UpperRoman, LowerLetter, etc.
"numberFormat": "%1.",
"restartLevel": 0,
"startAt": 1
}
]
}
AppraiSys Usage Patterns
Converting DOCX to SFDT
From DocumentToSfdtFormat.cs:
public static string Convert(MemoryStream stream)
{
// Load DOCX using Syncfusion DocIO
var doc = new Syncfusion.DocIO.DLS.WordDocument(
stream,
Syncfusion.DocIO.FormatType.Docx
);
// Convert to SFDT using EJ2 DocumentEditor
var sfdtDocument = Syncfusion.EJ2.DocumentEditor.WordDocument.Load(doc);
// Optimize output
sfdtDocument.OptimizeSfdt = true;
// Serialize to JSON
var sfdt = Newtonsoft.Json.JsonConvert.SerializeObject(sfdtDocument);
// Cleanup
sfdtDocument.Dispose();
doc.Dispose();
return sfdt;
}
Converting SFDT to DOCX
// From DocumentToPdfConverter.cs
var sfdtDocument = Syncfusion.EJ2.DocumentEditor.WordDocument.Save(
mergedSfdt,
Syncfusion.EJ2.DocumentEditor.FormatType.Docx
);
using var wordDocument = new Syncfusion.DocIO.DLS.WordDocument(
sfdtDocument,
Syncfusion.DocIO.FormatType.Docx
);
Mail Merge with SFDT
From SyncfusionFieldsPipelineStep.cs:
// Document is in DOCX format
DestinationDocument = new WordDocument(input.DocumentData, FormatType.Docx);
// Configure mail merge
DestinationDocument.MailMerge.ClearFields = false;
DestinationDocument.MailMerge.BeforeClearField += BeforeClearFieldWrapper;
// Execute merge (internal conversion to SFDT happens)
DestinationDocument.MailMerge.Execute(new List<string> { "" });
SFDT Limitations and Bugs
Known Issues (from AppraiSys experience)
1. Blank Paragraph Artifacts
Problem: Extra blank paragraphs appear after conversion
Cause: Syncfusion adds spacing paragraphs
Workaround: Clean empty blocks post-conversion
2. Section Break Oddities
Problem: Section breaks don't convert cleanly
Cause: SFDT section model differs from OOXML
Workaround: Test section-heavy documents carefully
3. Image Embedding Issues
Problem: External image URLs don't always work
Cause: Syncfusion requires base64 for reliable rendering
Solution: Download and encode images (as in AppraiSys)
4. Header/Footer Preservation
Problem: Complex headers/footers may lose formatting
Cause: SFDT header model simplified
Workaround: Keep headers/footers simple
5. Content Control Flattening
Problem: SDTs sometimes removed after conversion
Cause: Syncfusion mail merge behavior
Solution: Set ClearFields = false
6. TOC Refresh
Problem: Table of contents doesn't update
Cause: SFDT doesn't support automatic TOC update
Workaround: Regenerate TOC in final DOCX
AppraiSys Patches
From AppraiSys.Atlas.SyncfusionDep:
public static void PatchSyncfusion()
{
var harmony = new Harmony("appraisys.syncfusion.patches");
// Apply null guards for internal Syncfusion access
ApplyNullGuardsPatch(harmony);
// Remove embedded image versions
ApplyRemoveEmbeddedVersionsOfImagesPatch(harmony);
}
Purpose: Work around Syncfusion bugs using Harmony runtime patching
SFDT vs OOXML Comparison
| Feature | OOXML | SFDT |
|---|---|---|
| Format | XML in ZIP | JSON |
| Size | Larger | Smaller |
| Human readable | Somewhat | More readable |
| Web friendly | No | Yes |
| Images | Separate files | Base64 inline |
| Relationships | .rels files | Embedded |
| Validation | XML schema | JSON schema |
| Editing | Complex | Easier |
| Browser support | No | Yes (with editor) |
Working with SFDT Programmatically
Parse SFDT JSON
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
var sfdtObject = JObject.Parse(sfdtJson);
// Access sections
var sections = sfdtObject["sections"] as JArray;
// Access first block
var firstBlock = sections[0]["blocks"][0];
// Check if it's a paragraph
if (firstBlock["inlines"] != null)
{
// It's a paragraph
var inlines = firstBlock["inlines"] as JArray;
}
else if (firstBlock["table"] != null)
{
// It's a table
var table = firstBlock["table"];
}
Modify SFDT
// Add text to first paragraph
var sections = sfdtObject["sections"] as JArray;
var firstBlock = sections[0]["blocks"][0];
var inlines = firstBlock["inlines"] as JArray;
inlines.Add(new JObject
{
["text"] = " Additional text",
["characterFormat"] = new JObject()
});
// Serialize back
var modifiedSfdt = sfdtObject.ToString();
Find Text in SFDT
public List<string> FindAllText(JObject sfdt)
{
var texts = new List<string>();
foreach (var section in sfdt["sections"])
{
foreach (var block in section["blocks"])
{
if (block["inlines"] != null)
{
foreach (var inline in block["inlines"])
{
if (inline["text"] != null)
{
texts.Add(inline["text"].ToString());
}
}
}
}
}
return texts;
}
SFDT Best Practices
✅ DO:
- Optimize SFDT: Always set
OptimizeSfdt = true - Validate JSON: Parse to verify valid JSON
- Handle images: Convert to base64 for reliability
- Test conversions: Always round-trip test DOCX → SFDT → DOCX
- Clean empty blocks: Remove artifact paragraphs
❌ DON'T:
- Don't edit SFDT manually: Use Syncfusion APIs
- Don't trust external image URLs: Download and encode
- Don't ignore validation errors: Fix structure issues
- Don't skip round-trip testing: Conversions can lose data
- Don't assume OOXML equivalence: Some features differ
Debugging SFDT
Visualize Structure
// In browser console
const sfdt = JSON.parse(sfdtString);
console.table(sfdt.sections[0].blocks.map((b, i) => ({
index: i,
type: b.inlines ? 'paragraph' : 'table',
content: b.inlines ? b.inlines.length + ' inlines' : b.table.rows.length + ' rows'
})));
Compare DOCX vs SFDT
// 1. Convert DOCX to SFDT
var sfdt = DocumentToSfdtFormat.Convert(docxStream);
// 2. Parse and inspect
var sfdtObj = JObject.Parse(sfdt);
var blockCount = sfdtObj["sections"][0]["blocks"].Count();
Console.WriteLine($"SFDT has {blockCount} blocks");
// 3. Convert back and compare
var docx2Stream = ConvertSfdtToDocx(sfdt);
// Visual comparison in Word
SFDT Schema Reference
Root Level
{
"sections": [], // Required: Document sections
"characterFormat": {}, // Optional: Default character format
"paragraphFormat": {}, // Optional: Default paragraph format
"defaultTabWidth": 36, // Optional: Default tab width in points
"trackChanges": false, // Optional: Track changes enabled
"styles": [], // Optional: Style definitions
"lists": [], // Optional: List instances
"abstractLists": [], // Optional: List templates
"comments": [], // Optional: Comments
"revisions": [], // Optional: Tracked changes
"customXml": [] // Optional: Custom XML data
}
Quick Reference
Common SFDT Modifications
Change text color:
"characterFormat": {
"fontColor": "#FF0000FF" // Red (RGBA)
}
Center paragraph:
"paragraphFormat": {
"textAlignment": "Center"
}
Bold text:
"characterFormat": {
"bold": true
}
Add spacing:
"paragraphFormat": {
"beforeSpacing": 12,
"afterSpacing": 12
}
Next Steps
➡️ Next: QA Testing Workflow
Related:
- OOXML ↔ SFDT Mapping - Complete transformation guide
- Advanced Syncfusion - Deep dive
- [Syncfusion Patterns](../05-Code examples/syncfusion-patterns.md) - Code examples
💡 Remember: SFDT is JSON-based and web-friendly, but has limitations compared to full OOXML. Test conversions thoroughly!