@@ -49,15 +49,24 @@ export class IgxPdfExporterService extends IgxBaseExporter {
4949
5050 protected exportDataImplementation ( data : IExportRecord [ ] , options : IgxPdfExporterOptions , done : ( ) => void ) : void {
5151 const defaultOwner = this . _ownersMap . get ( DEFAULT_OWNER ) ;
52- const columns = defaultOwner ?. columns . filter ( col => col . field && ! col . skip && col . headerType === ExportHeaderType . ColumnHeader ) || [ ] ;
52+
53+ // Get all columns (including multi-column headers)
54+ const allColumns = defaultOwner ?. columns . filter ( col => ! col . skip ) || [ ] ;
55+
56+ // Get leaf columns (actual data columns)
57+ const leafColumns = allColumns . filter ( col => col . field && col . headerType === ExportHeaderType . ColumnHeader ) ;
58+
59+ // Check if we have multi-level headers
60+ const maxLevel = defaultOwner ?. maxLevel || 0 ;
61+ const hasMultiColumnHeaders = maxLevel > 0 && allColumns . some ( col => col . headerType === ExportHeaderType . MultiColumnHeader ) ;
5362
54- if ( columns . length === 0 && data . length > 0 ) {
63+ if ( leafColumns . length === 0 && data . length > 0 ) {
5564 // If no columns are defined, use the keys from the first data record
5665 const firstDataElement = data [ 0 ] ;
5766 const keys = Object . keys ( firstDataElement . data ) ;
5867
5968 keys . forEach ( ( key ) => {
60- columns . push ( {
69+ leafColumns . push ( {
6170 header : key ,
6271 field : key ,
6372 skip : false ,
@@ -80,8 +89,8 @@ export class IgxPdfExporterService extends IgxBaseExporter {
8089 const margin = 40 ;
8190 const usableWidth = pageWidth - ( 2 * margin ) ;
8291
83- // Calculate column widths
84- const columnWidth = usableWidth / columns . length ;
92+ // Calculate column widths based on leaf columns
93+ const columnWidth = usableWidth / leafColumns . length ;
8594 const rowHeight = 20 ;
8695 const headerHeight = 25 ;
8796 const indentSize = 15 ; // Indentation per level for hierarchical data
@@ -92,9 +101,14 @@ export class IgxPdfExporterService extends IgxBaseExporter {
92101 // Set font
93102 pdf . setFontSize ( options . fontSize ) ;
94103
95- // Draw main table headers
96- this . drawTableHeaders ( pdf , columns , margin , yPosition , columnWidth , headerHeight , usableWidth , options ) ;
97- yPosition += headerHeight ;
104+ // Draw multi-level headers if present
105+ if ( hasMultiColumnHeaders ) {
106+ yPosition = this . drawMultiLevelHeaders ( pdf , allColumns , maxLevel , margin , yPosition , columnWidth , headerHeight , usableWidth , options ) ;
107+ } else {
108+ // Draw simple single-level headers
109+ this . drawTableHeaders ( pdf , leafColumns , margin , yPosition , columnWidth , headerHeight , usableWidth , options ) ;
110+ yPosition += headerHeight ;
111+ }
98112
99113 // Draw data rows
100114 pdf . setFont ( 'helvetica' , 'normal' ) ;
@@ -113,6 +127,14 @@ export class IgxPdfExporterService extends IgxBaseExporter {
113127 if ( yPosition + rowHeight > pageHeight - margin ) {
114128 pdf . addPage ( ) ;
115129 yPosition = margin ;
130+
131+ // Redraw headers on new page
132+ if ( hasMultiColumnHeaders ) {
133+ yPosition = this . drawMultiLevelHeaders ( pdf , allColumns , maxLevel , margin , yPosition , columnWidth , headerHeight , usableWidth , options ) ;
134+ } else {
135+ this . drawTableHeaders ( pdf , leafColumns , margin , yPosition , columnWidth , headerHeight , usableWidth , options ) ;
136+ yPosition += headerHeight ;
137+ }
116138 }
117139
118140 // Calculate indentation for hierarchical records
@@ -124,7 +146,7 @@ export class IgxPdfExporterService extends IgxBaseExporter {
124146 const indent = indentLevel * indentSize ;
125147
126148 // Draw parent row
127- this . drawDataRow ( pdf , record , columns , margin , yPosition , columnWidth , rowHeight , indent , options ) ;
149+ this . drawDataRow ( pdf , record , leafColumns , margin , yPosition , columnWidth , rowHeight , indent , options ) ;
128150 yPosition += rowHeight ;
129151
130152 // For hierarchical grids, check if this record has child records
@@ -203,6 +225,72 @@ export class IgxPdfExporterService extends IgxBaseExporter {
203225 done ( ) ;
204226 }
205227
228+ private drawMultiLevelHeaders (
229+ pdf : jsPDF ,
230+ columns : any [ ] ,
231+ maxLevel : number ,
232+ xStart : number ,
233+ yStart : number ,
234+ baseColumnWidth : number ,
235+ headerHeight : number ,
236+ tableWidth : number ,
237+ options : IgxPdfExporterOptions
238+ ) : number {
239+ let yPosition = yStart ;
240+ pdf . setFont ( 'helvetica' , 'bold' ) ;
241+
242+ // Draw headers level by level (from top/parent to bottom/children)
243+ for ( let level = 0 ; level <= maxLevel ; level ++ ) {
244+ // Get headers for this level
245+ const headersForLevel = columns . filter ( col => {
246+ if ( level === 0 ) {
247+ // For level 0, include multi-column headers at level 0 and leaf columns that span multiple levels
248+ return ( col . level === 0 && col . headerType === ExportHeaderType . MultiColumnHeader ) ||
249+ ( col . level === 0 && col . headerType === ExportHeaderType . ColumnHeader ) ;
250+ } else {
251+ // For other levels, include headers at this level or leaf columns that need to span down
252+ return ( col . level === level && col . headerType === ExportHeaderType . MultiColumnHeader ) ||
253+ ( col . level < level && col . headerType === ExportHeaderType . ColumnHeader ) ;
254+ }
255+ } ) . filter ( col => col . columnSpan > 0 ) ;
256+
257+ if ( headersForLevel . length === 0 ) continue ;
258+
259+ // Sort by startIndex to maintain order
260+ headersForLevel . sort ( ( a , b ) => a . startIndex - b . startIndex ) ;
261+
262+ // Draw background
263+ pdf . setFillColor ( 240 , 240 , 240 ) ;
264+ if ( options . showTableBorders ) {
265+ pdf . rect ( xStart , yPosition , tableWidth , headerHeight , 'F' ) ;
266+ }
267+
268+ // Draw each header in this level
269+ headersForLevel . forEach ( ( col ) => {
270+ const colSpan = col . columnSpan || 1 ;
271+ const width = baseColumnWidth * colSpan ;
272+ const xPosition = xStart + ( col . startIndex * baseColumnWidth ) ;
273+
274+ if ( options . showTableBorders ) {
275+ pdf . rect ( xPosition , yPosition , width , headerHeight ) ;
276+ }
277+
278+ // Center text in cell
279+ const headerText = col . header || col . field || '' ;
280+ const textWidth = pdf . getTextWidth ( headerText ) ;
281+ const textX = xPosition + ( width - textWidth ) / 2 ;
282+ const textY = yPosition + headerHeight / 2 + options . fontSize / 3 ;
283+
284+ pdf . text ( headerText , textX , textY ) ;
285+ } ) ;
286+
287+ yPosition += headerHeight ;
288+ }
289+
290+ pdf . setFont ( 'helvetica' , 'normal' ) ;
291+ return yPosition ;
292+ }
293+
206294 private drawTableHeaders (
207295 pdf : jsPDF ,
208296 columns : any [ ] ,
0 commit comments