@@ -85,45 +85,28 @@ export class IgxPdfExporterService extends IgxBaseExporter {
8585 const rowHeight = 20 ;
8686 const headerHeight = 25 ;
8787 const indentSize = 15 ; // Indentation per level for hierarchical data
88+ const childTableIndent = 30 ; // Indent for child tables
8889
8990 let yPosition = margin ;
9091
9192 // Set font
9293 pdf . setFontSize ( options . fontSize ) ;
9394
94- // Draw table headers
95- pdf . setFont ( 'helvetica' , 'bold' ) ;
96- pdf . setFillColor ( 240 , 240 , 240 ) ;
97-
98- if ( options . showTableBorders ) {
99- pdf . rect ( margin , yPosition , usableWidth , headerHeight , 'F' ) ;
100- }
101-
102- columns . forEach ( ( col , index ) => {
103- const xPosition = margin + ( index * columnWidth ) ;
104- const headerText = col . header || col . field ;
105-
106- if ( options . showTableBorders ) {
107- pdf . rect ( xPosition , yPosition , columnWidth , headerHeight ) ;
108- }
109-
110- // Center text in cell
111- const textWidth = pdf . getTextWidth ( headerText ) ;
112- const textX = xPosition + ( columnWidth - textWidth ) / 2 ;
113- const textY = yPosition + headerHeight / 2 + options . fontSize / 3 ;
114-
115- pdf . text ( headerText , textX , textY ) ;
116- } ) ;
117-
95+ // Draw main table headers
96+ this . drawTableHeaders ( pdf , columns , margin , yPosition , columnWidth , headerHeight , usableWidth , options ) ;
11897 yPosition += headerHeight ;
11998
12099 // Draw data rows
121100 pdf . setFont ( 'helvetica' , 'normal' ) ;
122101
123- data . forEach ( ( record ) => {
102+ let i = 0 ;
103+ while ( i < data . length ) {
104+ const record = data [ i ] ;
105+
124106 // Skip hidden records (collapsed hierarchy)
125107 if ( record . hidden ) {
126- return ;
108+ i ++ ;
109+ continue ;
127110 }
128111
129112 // Check if we need a new page
@@ -133,54 +116,174 @@ export class IgxPdfExporterService extends IgxBaseExporter {
133116 }
134117
135118 // Calculate indentation for hierarchical records
136- const isHierarchical = record . type === 'TreeGridRecord' || record . type === 'HierarchicalGridRecord' ;
137- const indentLevel = isHierarchical ? ( record . level || 0 ) : 0 ;
119+ const isTreeGrid = record . type === 'TreeGridRecord' ;
120+ const isHierarchicalGrid = record . type === 'HierarchicalGridRecord' ;
121+ const indentLevel = ( isTreeGrid || isHierarchicalGrid ) ? ( record . level || 0 ) : 0 ;
138122 const indent = indentLevel * indentSize ;
139123
140- columns . forEach ( ( col , index ) => {
141- const xPosition = margin + ( index * columnWidth ) ;
142- let cellValue = record . data [ col . field ] ;
124+ // Draw parent row
125+ this . drawDataRow ( pdf , record , columns , margin , yPosition , columnWidth , rowHeight , indent , options ) ;
126+ yPosition += rowHeight ;
127+
128+ // For hierarchical grids, check if this record has child records
129+ if ( isHierarchicalGrid ) {
130+ const childRecords = [ ] ;
131+ let childOwner = null ;
143132
144- // Convert value to string
145- if ( cellValue === null || cellValue === undefined ) {
146- cellValue = '' ;
147- } else if ( cellValue instanceof Date ) {
148- cellValue = cellValue . toLocaleDateString ( ) ;
149- } else {
150- cellValue = String ( cellValue ) ;
133+ // Collect all child records that belong to this parent
134+ let j = i + 1 ;
135+ while ( j < data . length && data [ j ] . owner !== DEFAULT_OWNER && data [ j ] . level > record . level ) {
136+ if ( ! data [ j ] . hidden ) {
137+ childRecords . push ( data [ j ] ) ;
138+ if ( ! childOwner ) {
139+ childOwner = data [ j ] . owner ;
140+ }
141+ }
142+ j ++ ;
151143 }
152144
153- if ( options . showTableBorders ) {
154- pdf . rect ( xPosition , yPosition , columnWidth , rowHeight ) ;
155- }
145+ // If there are child records, draw a child table
146+ if ( childRecords . length > 0 && childOwner ) {
147+ const childColumns = this . _ownersMap . get ( childOwner ) ?. columns . filter (
148+ col => col . field && ! col . skip && col . headerType === ExportHeaderType . ColumnHeader
149+ ) || [ ] ;
156150
157- // Apply indentation to the first column for hierarchical data
158- const textIndent = ( index === 0 && isHierarchical ) ? indent : 0 ;
159-
160- // Truncate text if it's too long, accounting for indentation
161- const maxTextWidth = columnWidth - 10 - textIndent ;
162- let displayText = cellValue ;
163-
164- if ( pdf . getTextWidth ( displayText ) > maxTextWidth ) {
165- while ( pdf . getTextWidth ( displayText + '...' ) > maxTextWidth && displayText . length > 0 ) {
166- displayText = displayText . substring ( 0 , displayText . length - 1 ) ;
151+ if ( childColumns . length > 0 ) {
152+ // Add some spacing before child table
153+ yPosition += 5 ;
154+
155+ // Check if child table fits on current page
156+ const childTableHeight = headerHeight + ( childRecords . length * rowHeight ) + 10 ;
157+ if ( yPosition + childTableHeight > pageHeight - margin ) {
158+ pdf . addPage ( ) ;
159+ yPosition = margin ;
160+ }
161+
162+ // Draw child table with indentation
163+ const childTableWidth = usableWidth - childTableIndent ;
164+ const childColumnWidth = childTableWidth / childColumns . length ;
165+ const childTableX = margin + childTableIndent ;
166+
167+ // Draw child table headers
168+ this . drawTableHeaders ( pdf , childColumns , childTableX , yPosition , childColumnWidth , headerHeight , childTableWidth , options ) ;
169+ yPosition += headerHeight ;
170+
171+ // Draw child table rows
172+ childRecords . forEach ( ( childRecord ) => {
173+ // Check if we need a new page
174+ if ( yPosition + rowHeight > pageHeight - margin ) {
175+ pdf . addPage ( ) ;
176+ yPosition = margin ;
177+ // Redraw headers on new page
178+ this . drawTableHeaders ( pdf , childColumns , childTableX , yPosition , childColumnWidth , headerHeight , childTableWidth , options ) ;
179+ yPosition += headerHeight ;
180+ }
181+
182+ this . drawDataRow ( pdf , childRecord , childColumns , childTableX , yPosition , childColumnWidth , rowHeight , 0 , options ) ;
183+ yPosition += rowHeight ;
184+ } ) ;
185+
186+ // Add spacing after child table
187+ yPosition += 5 ;
167188 }
168- displayText += '...' ;
169- }
170189
171- const textY = yPosition + rowHeight / 2 + options . fontSize / 3 ;
172- pdf . text ( displayText , xPosition + 5 + textIndent , textY ) ;
173- } ) ;
190+ // Skip the child records we just processed
191+ i = j - 1 ;
192+ }
193+ }
174194
175- yPosition += rowHeight ;
176- } ) ;
195+ i ++ ;
196+ }
177197
178198 // Save the PDF
179199 this . saveFile ( pdf , options . fileName ) ;
180200 this . exportEnded . emit ( { pdf } ) ;
181201 done ( ) ;
182202 }
183203
204+ private drawTableHeaders (
205+ pdf : jsPDF ,
206+ columns : any [ ] ,
207+ xStart : number ,
208+ yPosition : number ,
209+ columnWidth : number ,
210+ headerHeight : number ,
211+ tableWidth : number ,
212+ options : IgxPdfExporterOptions
213+ ) : void {
214+ pdf . setFont ( 'helvetica' , 'bold' ) ;
215+ pdf . setFillColor ( 240 , 240 , 240 ) ;
216+
217+ if ( options . showTableBorders ) {
218+ pdf . rect ( xStart , yPosition , tableWidth , headerHeight , 'F' ) ;
219+ }
220+
221+ columns . forEach ( ( col , index ) => {
222+ const xPosition = xStart + ( index * columnWidth ) ;
223+ const headerText = col . header || col . field ;
224+
225+ if ( options . showTableBorders ) {
226+ pdf . rect ( xPosition , yPosition , columnWidth , headerHeight ) ;
227+ }
228+
229+ // Center text in cell
230+ const textWidth = pdf . getTextWidth ( headerText ) ;
231+ const textX = xPosition + ( columnWidth - textWidth ) / 2 ;
232+ const textY = yPosition + headerHeight / 2 + options . fontSize / 3 ;
233+
234+ pdf . text ( headerText , textX , textY ) ;
235+ } ) ;
236+
237+ pdf . setFont ( 'helvetica' , 'normal' ) ;
238+ }
239+
240+ private drawDataRow (
241+ pdf : jsPDF ,
242+ record : IExportRecord ,
243+ columns : any [ ] ,
244+ xStart : number ,
245+ yPosition : number ,
246+ columnWidth : number ,
247+ rowHeight : number ,
248+ indent : number ,
249+ options : IgxPdfExporterOptions
250+ ) : void {
251+ columns . forEach ( ( col , index ) => {
252+ const xPosition = xStart + ( index * columnWidth ) ;
253+ let cellValue = record . data [ col . field ] ;
254+
255+ // Convert value to string
256+ if ( cellValue === null || cellValue === undefined ) {
257+ cellValue = '' ;
258+ } else if ( cellValue instanceof Date ) {
259+ cellValue = cellValue . toLocaleDateString ( ) ;
260+ } else {
261+ cellValue = String ( cellValue ) ;
262+ }
263+
264+ if ( options . showTableBorders ) {
265+ pdf . rect ( xPosition , yPosition , columnWidth , rowHeight ) ;
266+ }
267+
268+ // Apply indentation to the first column for hierarchical data
269+ const textIndent = ( index === 0 ) ? indent : 0 ;
270+
271+ // Truncate text if it's too long, accounting for indentation
272+ const maxTextWidth = columnWidth - 10 - textIndent ;
273+ let displayText = cellValue ;
274+
275+ if ( pdf . getTextWidth ( displayText ) > maxTextWidth ) {
276+ while ( pdf . getTextWidth ( displayText + '...' ) > maxTextWidth && displayText . length > 0 ) {
277+ displayText = displayText . substring ( 0 , displayText . length - 1 ) ;
278+ }
279+ displayText += '...' ;
280+ }
281+
282+ const textY = yPosition + rowHeight / 2 + options . fontSize / 3 ;
283+ pdf . text ( displayText , xPosition + 5 + textIndent , textY ) ;
284+ } ) ;
285+ }
286+
184287 private saveFile ( pdf : jsPDF , fileName : string ) : void {
185288 const blob = pdf . output ( 'blob' ) ;
186289 ExportUtilities . saveBlobToFile ( blob , fileName ) ;
0 commit comments