@@ -14,6 +14,15 @@ namespace DocSharp.Renderer;
1414
1515internal class DocxRenderer : DocxEnumerator < QuestPdfModel > , IDocumentRenderer < QuestPDF . Fluent . Document >
1616{
17+ private QuestPdfPageSet ? currentPageSet ; // Current section
18+ private Stack < QuestPdfContainer > currentContainer = new ( ) ; // Container can be the main document body, header, footer, table cell, ...
19+ private Stack < IQuestPdfRunContainer > currentRunContainer = new ( ) ; // Spans can only be added to a paragraph or hyperlink
20+ private Stack < QuestPdfParagraph > currentParagraph = new ( ) ; // Hyperlinks can only be added to a paragraph
21+ private Stack < QuestPdfTable > currentTable = new ( ) ; // Rows can only be added to a table
22+ private Stack < QuestPdfTableRow > currentRow = new ( ) ; // Cells can only be added to a table row
23+ private Stack < QuestPdfSpan > currentSpan = new ( ) ; // Text can only be added to a span
24+ private QuestPDF . Infrastructure . Color ? pageColor ; // Page color is the same for all sections in DOCX
25+
1726 /// <summary>
1827 /// Render a DOCX document to a QuestPDF document.
1928 /// </summary>
@@ -65,10 +74,18 @@ internal override void ProcessDocument(W.Document document, QuestPdfModel output
6574 base . ProcessDocument ( document , output ) ;
6675 }
6776
77+ internal override void ProcessDocumentBackground ( DocumentBackground background , QuestPdfModel output )
78+ {
79+ if ( ColorHelpers . EnsureHexColor ( background . Color ? . Value ) is string color && ! string . IsNullOrWhiteSpace ( color ) )
80+ {
81+ pageColor = QuestPDF . Infrastructure . Color . FromHex ( color ) ;
82+ // Page background color is the same for all sections in DOCX, save the value.
83+ }
84+ }
85+
6886 internal override void ProcessBody ( W . Body body , QuestPdfModel output )
69- {
87+ {
7088 Sections = body . GetSections ( ) ; // Split content in sections (implemented in the base class)
71-
7289 foreach ( var sect in Sections )
7390 {
7491 ProcessSection ( sect , body . GetMainDocumentPart ( ) , output ) ;
@@ -77,14 +94,17 @@ internal override void ProcessBody(W.Body body, QuestPdfModel output)
7794
7895 internal override void ProcessSection ( ( List < OpenXmlElement > content , SectionProperties properties ) section , MainDocumentPart ? mainPart , QuestPdfModel output )
7996 {
97+ if ( mainPart == null )
98+ return ;
99+
80100 // Process section properties here and add them to a new QuestPdfPageSet object
81101 var sectionProperties = section . properties ;
82- float w = ( float ) DocSharp . Primitives . PageSize . Default . WidthMm ;
83- float h = ( float ) DocSharp . Primitives . PageSize . Default . HeightMm ;
84- float l = ( float ) DocSharp . Primitives . PageMargins . Default . LeftMm ;
85- float t = ( float ) DocSharp . Primitives . PageMargins . Default . TopMm ;
86- float r = ( float ) DocSharp . Primitives . PageMargins . Default . RightMm ;
87- float b = ( float ) DocSharp . Primitives . PageMargins . Default . BottomMm ;
102+ float w = Primitives . PageSize . Default . WidthTwips ( ) ;
103+ float h = Primitives . PageSize . Default . HeightTwips ( ) ;
104+ float l = Primitives . PageMargins . Default . LeftTwips ( ) ;
105+ float t = Primitives . PageMargins . Default . TopTwips ( ) ;
106+ float r = Primitives . PageMargins . Default . RightTwips ( ) ;
107+ float b = Primitives . PageMargins . Default . BottomTwips ( ) ;
88108
89109 if ( sectionProperties . GetFirstChild < PageSize > ( ) is PageSize size )
90110 {
@@ -112,65 +132,212 @@ internal override void ProcessSection((List<OpenXmlElement> content, SectionProp
112132 }
113133 }
114134 var pageSet = new QuestPdfPageSet ( w , h , l , t , r , b , QuestPDF . Infrastructure . Unit . Millimetre ) ;
135+ if ( pageColor . HasValue )
136+ pageSet . BackgroundColor = pageColor . Value ;
137+
138+ // Add page set to PageSets collection
115139 output . PageSets . Add ( pageSet ) ;
116140
117- // Then, enumerate elements in the section (paragraphs, tables, ...)
141+ // Process headers for this section
142+ var headerRefs = sectionProperties . Elements < HeaderReference > ( ) ;
143+ // QuestPDF can't produce different header and footer on odd/even pages.
144+ // For now, handle the default header and footer only.
145+ var headerRef = headerRefs . FirstOrDefault ( h => h . Type == null || ! h . Type . HasValue || h . Type . Value == HeaderFooterValues . Default ) ;
146+ if ( headerRef ? . Id ? . Value is string headerId && mainPart . GetPartById ( headerId ) is HeaderPart headerPart )
147+ {
148+ currentContainer . Push ( pageSet . Header ) ;
149+ base . ProcessHeader ( headerPart . Header , output ) ;
150+ if ( currentContainer . Count > 0 )
151+ currentContainer . Pop ( ) ;
152+ }
153+
154+ // Process footers for this section
155+ var footerRefs = sectionProperties . Elements < FooterReference > ( ) ;
156+ // QuestPDF can't produce different header and footer on odd/even pages.
157+ // For now, handle the default header and footer only.
158+ var footerRef = footerRefs . FirstOrDefault ( h => h . Type == null || ! h . Type . HasValue || h . Type . Value == HeaderFooterValues . Default ) ;
159+ if ( footerRef ? . Id ? . Value is string footerId && mainPart . GetPartById ( footerId ) is FooterPart footerPart )
160+ {
161+ currentContainer . Push ( pageSet . Footer ) ;
162+ base . ProcessFooter ( footerPart . Footer , output ) ;
163+ if ( currentContainer . Count > 0 )
164+ currentContainer . Pop ( ) ;
165+ }
166+
167+ // Process elements in the section body itself (paragraphs, tables, ...)
168+ currentContainer . Push ( pageSet . Content ) ;
118169 base . ProcessSection ( section , mainPart , output ) ;
170+ if ( currentContainer . Count > 0 )
171+ currentContainer . Pop ( ) ;
119172 }
120173
121174 internal override void ProcessParagraph ( Paragraph paragraph , QuestPdfModel output )
122175 {
123- // Paragraph properties can be processed here.
124- var alignment = paragraph . GetEffectiveProperty < TextAlignment > ( ) ;
125-
126- // Then, enumerate elements in the paragraph (runs, hyperlinks, math formulas).
176+ // Process paragraph properties here and add them to a new QuestPdfParagraph object
177+ var p = new QuestPdfParagraph ( ) ;
178+ if ( paragraph . GetEffectiveProperty < Justification > ( ) is Justification jc && jc . Val != null )
179+ {
180+ if ( jc . Val == JustificationValues . Center )
181+ p . Alignment = ParagraphAlignment . Center ;
182+ else if ( jc . Val == JustificationValues . Right )
183+ p . Alignment = ParagraphAlignment . Right ;
184+ else if ( jc . Val == JustificationValues . Both || jc . Val == JustificationValues . Distribute || jc . Val == JustificationValues . ThaiDistribute )
185+ p . Alignment = ParagraphAlignment . Justify ;
186+ else if ( jc . Val == JustificationValues . Start )
187+ p . Alignment = ParagraphAlignment . Start ;
188+ else if ( jc . Val == JustificationValues . End )
189+ p . Alignment = ParagraphAlignment . End ;
190+ else
191+ p . Alignment = ParagraphAlignment . Left ;
192+ }
193+
194+ // Add paragraph to the current container (body, header, footer, table cell, ...)
195+ if ( currentContainer . Count > 0 )
196+ currentContainer . Peek ( ) . Content . Add ( p ) ;
197+
198+ // Enumerate and process paragraph elements (runs, hyperlinks, math formulas, ...)
199+ currentRunContainer . Push ( p ) ;
200+ currentParagraph . Push ( p ) ;
127201 base . ProcessParagraph ( paragraph , output ) ;
202+ if ( currentRunContainer . Count > 0 )
203+ currentRunContainer . Pop ( ) ;
204+ if ( currentParagraph . Count > 0 )
205+ currentParagraph . Pop ( ) ;
128206 }
129207
130208 internal override void ProcessHyperlink ( Hyperlink hyperlink , QuestPdfModel output )
131209 {
132- // The hyperlink URL/anchor can be processed here.
210+ // Retrieve the URL or anchor for this hyperlink and add it to a new QuestPdfHyperlink object
211+ var h = new QuestPdfHyperlink ( ) ;
133212
134- // Then, enumerate runs in the hyperlink
213+ // Add hyperlink to the paragraph model.
214+ if ( currentParagraph . Count > 0 )
215+ currentParagraph . Peek ( ) . Elements . Add ( h ) ;
216+
217+ // Enumerate and process runs in this hyperlink
218+ currentRunContainer . Push ( h ) ;
135219 base . ProcessHyperlink ( hyperlink , output ) ;
220+ if ( currentRunContainer . Count > 0 )
221+ currentRunContainer . Pop ( ) ;
136222 }
137223
138224 internal override void ProcessRun ( Run run , QuestPdfModel output )
139225 {
140- // Run properties can be processed here.
226+ // Process run properties and add them to a new QuestPdfSpan object
141227 bool bold = run . GetEffectiveProperty < Bold > ( ) is Bold b && ( b . Val == null || b . Val ) ;
142228 bool italic = run . GetEffectiveProperty < Italic > ( ) is Italic i && ( i . Val == null || i . Val ) ;
143-
229+ UnderlineStyle underline = UnderlineStyle . None ;
230+ StrikethroughStyle strikethrough = StrikethroughStyle . None ;
231+ SubSuperscript supSuperscript = SubSuperscript . Normal ;
232+ CapsType caps = CapsType . Normal ;
233+ string ? fontFamily = null ;
234+ int ? fontSize = null ;
235+ QuestPDF . Infrastructure . Color ? fontColor = null ;
236+ QuestPDF . Infrastructure . Color ? bgColor = null ;
237+ QuestPDF . Infrastructure . Color ? underlineColor = null ;
238+ float ? letterSpacing = null ;
239+ var span = new QuestPdfSpan ( null , bold , italic , underline , strikethrough , supSuperscript , caps , fontFamily , fontSize , fontColor , bgColor , underlineColor , letterSpacing ) ;
240+
241+ // Add span to the paragraph/hyperlink.
242+ if ( currentRunContainer . Count > 0 )
243+ currentRunContainer . Peek ( ) . AddSpan ( span ) ;
244+
144245 // Then, enumerate run elements (text, picture, break, page number, footnote reference...)
246+ currentSpan . Push ( span ) ;
145247 base . ProcessRun ( run , output ) ;
248+ if ( currentSpan . Count > 0 )
249+ currentSpan . Pop ( ) ;
146250 }
147251
148252 internal override void ProcessText ( Text text , QuestPdfModel output )
149253 {
150- var textString = text . Text ;
254+ if ( currentSpan . Count > 0 && ! string . IsNullOrEmpty ( text . Text ) )
255+ currentSpan . Peek ( ) . Text += Environment . NewLine ;
256+ }
257+
258+ internal override void ProcessBreak ( Break @break , QuestPdfModel output )
259+ {
260+ if ( @break . Type == null || ! @break . Type . HasValue || @break . Type . Value == BreakValues . TextWrapping )
261+ {
262+ if ( currentSpan . Count > 0 )
263+ currentSpan . Peek ( ) . Text += Environment . NewLine ;
264+ }
265+ // TODO: page/column break
151266 }
152267
153268 internal override void ProcessTable ( Table table , QuestPdfModel output )
154269 {
155- // Enumerate rows and cells
156- base . ProcessTable ( table , output ) ;
270+ // Process table properties and create a new QuestPdfTable object
271+ var t = new QuestPdfTable ( )
272+ {
273+ ColumnsCount = table . Elements < TableRow > ( ) . Max ( c => c . Elements < TableCell > ( ) . Count ( ) )
274+ // TODO: check SdtRow/CustomXmlRow and SdtCell/CustomXmlCell too.
275+ } ;
276+ // Add table to the current container.
277+ if ( currentContainer . Count > 0 )
278+ currentContainer . Peek ( ) . Content . Add ( t ) ;
279+
280+ // Enumerate rows and cells
281+ currentTable . Push ( t ) ;
282+ base . ProcessTable ( table , output ) ;
283+ if ( currentTable . Count > 0 )
284+ currentTable . Pop ( ) ;
157285 }
158286
159287 internal override void ProcessTableRow ( TableRow tableRow , QuestPdfModel output )
160288 {
161- // Enumerate cells
289+ // Create a new QuestPdfTableRow object
290+ var row = new QuestPdfTableRow ( ) ;
291+
292+ // Add row to the table model.
293+ if ( currentTable . Count > 0 )
294+ currentTable . Peek ( ) . Rows . Add ( row ) ;
295+
296+ // Enumerate cells
297+ currentRow . Push ( row ) ;
162298 base . ProcessTableRow ( tableRow , output ) ;
299+ if ( currentRow . Count > 0 )
300+ currentRow . Pop ( ) ;
163301 }
164302
165303 internal override void ProcessTableCell ( TableCell tableCell , QuestPdfModel output )
166304 {
305+ // Create a new QuestPdfTableCell object
306+ var cell = new QuestPdfTableCell ( ) ;
307+
308+ // Process cell properties
309+ if ( tableCell . TableCellProperties ? . GridSpan ? . Val != null )
310+ {
311+ if ( tableCell . TableCellProperties . GridSpan . Val . Value > 1 )
312+ cell . ColumnSpan = ( uint ) tableCell . TableCellProperties . GridSpan . Val . Value ;
313+ }
314+ if ( tableCell . GetEffectiveProperty < Shading > ( ) is Shading shading )
315+ {
316+ if ( ( shading . Val == null || ( shading . Val . Value != ShadingPatternValues . Nil && shading . Val . Value != ShadingPatternValues . Solid ) ) &&
317+ ColorHelpers . EnsureHexColor ( shading . Color ? . Value ) is string color && ! string . IsNullOrWhiteSpace ( color ) )
318+ {
319+ cell . BackgroundColor = QuestPDF . Infrastructure . Color . FromHex ( color ) ;
320+ // TODO: recognize other patterns. The pure primary color is displayed for ShadingPatternValues.Clear,
321+ // pure secondary color is displayed for ShadingPatternValues.Solid.
322+ // For now, we use the primary color for all patterns except Solid and Nil, and the secondary color for Solid.
323+ }
324+ else if ( ( shading . Val == null || shading . Val . Value == ShadingPatternValues . Solid ) &&
325+ ColorHelpers . EnsureHexColor ( shading . Fill ? . Value ) is string bgColor && ! string . IsNullOrWhiteSpace ( bgColor ) )
326+ {
327+ cell . BackgroundColor = QuestPDF . Infrastructure . Color . FromHex ( bgColor ) ;
328+ }
329+ }
330+ // TODO: vertical merge (set Cell.RowSpan); borders
331+
332+ // Add cell to the row model.
333+ if ( currentRow . Count > 0 )
334+ currentRow . Peek ( ) . Cells . Add ( cell ) ;
335+
167336 // Enumerate paragraphs (or nested tables) in the cell
337+ currentContainer . Push ( cell ) ;
168338 base . ProcessTableCell ( tableCell , output ) ;
169- }
170-
171- internal override void ProcessBreak ( Break @break , QuestPdfModel output )
172- {
173- // Process line/page/column break
339+ if ( currentContainer . Count > 0 )
340+ currentContainer . Pop ( ) ;
174341 }
175342
176343 internal override void ProcessBookmarkStart ( BookmarkStart bookmarkStart , QuestPdfModel output )
@@ -197,10 +364,6 @@ internal override void ProcessCommentEnd(CommentRangeEnd commentEnd, QuestPdfMod
197364 {
198365 }
199366
200- internal override void ProcessDocumentBackground ( DocumentBackground background , QuestPdfModel output )
201- {
202- }
203-
204367 internal override void ProcessDrawing ( Drawing picture , QuestPdfModel output )
205368 {
206369 }
0 commit comments