officeparser 7.1.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +93 -18
  2. package/dist/OfficeGenerator.d.ts +1 -1
  3. package/dist/OfficeGenerator.js +16 -7
  4. package/dist/cli.d.ts +4 -0
  5. package/dist/cli.js +12 -3
  6. package/dist/defaults.js +11 -0
  7. package/dist/generators/BaseGenerator.d.ts +3 -3
  8. package/dist/generators/ChunkingGenerator.js +8 -1
  9. package/dist/generators/CsvGenerator.d.ts +1 -1
  10. package/dist/generators/HtmlGenerator.d.ts +2 -1
  11. package/dist/generators/HtmlGenerator.js +462 -40
  12. package/dist/generators/MarkdownGenerator.d.ts +1 -1
  13. package/dist/generators/MarkdownGenerator.js +3 -1
  14. package/dist/generators/PdfGenerator.d.ts +1 -1
  15. package/dist/generators/PdfGenerator.js +0 -6
  16. package/dist/generators/RtfGenerator.d.ts +2 -1
  17. package/dist/generators/RtfGenerator.js +43 -6
  18. package/dist/generators/TextGenerator.d.ts +1 -1
  19. package/dist/officeparser.browser.d.ts +259 -52
  20. package/dist/officeparser.browser.iife.js +380 -93
  21. package/dist/officeparser.browser.mjs +380 -93
  22. package/dist/parsers/CsvParser.js +1 -1
  23. package/dist/parsers/ExcelParser.js +63 -19
  24. package/dist/parsers/HtmlParser.js +10 -1
  25. package/dist/parsers/MarkdownParser.js +13 -10
  26. package/dist/parsers/OpenOfficeParser.js +57 -34
  27. package/dist/parsers/PdfParser.js +23 -1
  28. package/dist/parsers/PowerPointParser.js +164 -40
  29. package/dist/parsers/RtfParser.js +28 -24
  30. package/dist/parsers/WordParser.js +154 -11
  31. package/dist/sbom.cdx.json +100 -100
  32. package/dist/types.d.ts +265 -52
  33. package/dist/types.js +2 -0
  34. package/dist/utils/astUtils.d.ts +2 -2
  35. package/dist/utils/astUtils.js +2 -1
  36. package/dist/utils/configUtils.d.ts +5 -0
  37. package/dist/utils/configUtils.js +55 -1
  38. package/dist/utils/errorUtils.js +2 -1
  39. package/dist/utils/xmlUtils.d.ts +9 -0
  40. package/dist/utils/xmlUtils.js +53 -1
  41. package/package.json +1 -1
@@ -50,6 +50,120 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
50
50
  spreadsheetTabs = `<div class="spreadsheet-tabs">${tabs}</div>`;
51
51
  spreadsheetScript = `
52
52
  <script>
53
+ function initSpreadsheetResizing() {
54
+ document.querySelectorAll('.excel-grid').forEach(table => {
55
+ if (table.dataset.resizingInitialized) return;
56
+ if (table.offsetWidth === 0) return; // skip hidden tables
57
+ table.dataset.resizingInitialized = 'true';
58
+
59
+ const sheetId = table.parentElement.id || 'sheet';
60
+ const docId = window.location.pathname;
61
+
62
+ // Freeze initial auto-layout widths of columns and set table layout to fixed
63
+ const colHeaders = table.querySelectorAll('.excel-col-header');
64
+ colHeaders.forEach((header, index) => {
65
+ let currentWidth = header.offsetWidth;
66
+ const savedWidth = localStorage.getItem(docId + '_' + sheetId + '_col_' + index);
67
+ if (savedWidth) {
68
+ currentWidth = parseInt(savedWidth, 10);
69
+ }
70
+ header.style.width = currentWidth + 'px';
71
+ header.style.minWidth = currentWidth + 'px';
72
+ });
73
+ table.style.width = table.offsetWidth + 'px';
74
+ table.style.tableLayout = 'fixed';
75
+
76
+ // Freeze initial auto-layout heights of rows
77
+ table.querySelectorAll('tr').forEach((row, index) => {
78
+ let currentHeight = row.offsetHeight;
79
+ const savedHeight = localStorage.getItem(docId + '_' + sheetId + '_row_' + index);
80
+ if (savedHeight) {
81
+ currentHeight = parseInt(savedHeight, 10);
82
+ }
83
+ row.style.height = currentHeight + 'px';
84
+ });
85
+
86
+ colHeaders.forEach((header, index) => {
87
+ if (header.querySelector('.col-resizer')) return;
88
+ const resizer = document.createElement('div');
89
+ resizer.className = 'col-resizer';
90
+ header.appendChild(resizer);
91
+
92
+ let startX = 0;
93
+ let startWidth = 0;
94
+ let startTableWidth = 0;
95
+
96
+ const onMouseMove = (e) => {
97
+ const width = startWidth + (e.clientX - startX);
98
+ if (width > 40) {
99
+ header.style.width = width + 'px';
100
+ header.style.minWidth = width + 'px';
101
+ table.style.width = (startTableWidth + (width - startWidth)) + 'px';
102
+ }
103
+ };
104
+
105
+ const onMouseUp = (e) => {
106
+ resizer.classList.remove('resizing');
107
+ document.removeEventListener('mousemove', onMouseMove);
108
+ document.removeEventListener('mouseup', onMouseUp);
109
+ const finalWidth = startWidth + (e.clientX - startX);
110
+ if (finalWidth > 40) {
111
+ localStorage.setItem(docId + '_' + sheetId + '_col_' + index, finalWidth);
112
+ }
113
+ };
114
+
115
+ resizer.addEventListener('mousedown', (e) => {
116
+ e.preventDefault();
117
+ e.stopPropagation();
118
+ startX = e.clientX;
119
+ startWidth = header.offsetWidth;
120
+ startTableWidth = table.offsetWidth;
121
+ resizer.classList.add('resizing');
122
+ document.addEventListener('mousemove', onMouseMove);
123
+ document.addEventListener('mouseup', onMouseUp);
124
+ });
125
+ });
126
+
127
+ table.querySelectorAll('.excel-row-num').forEach((rowHeader, index) => {
128
+ if (rowHeader.querySelector('.row-resizer')) return;
129
+ const resizer = document.createElement('div');
130
+ resizer.className = 'row-resizer';
131
+ rowHeader.appendChild(resizer);
132
+
133
+ const row = rowHeader.parentElement;
134
+ let startY = 0;
135
+ let startHeight = 0;
136
+
137
+ const onMouseMove = (e) => {
138
+ const height = startHeight + (e.clientY - startY);
139
+ if (height > 20) {
140
+ row.style.height = height + 'px';
141
+ }
142
+ };
143
+
144
+ const onMouseUp = (e) => {
145
+ resizer.classList.remove('resizing');
146
+ document.removeEventListener('mousemove', onMouseMove);
147
+ document.removeEventListener('mouseup', onMouseUp);
148
+ const finalHeight = startHeight + (e.clientY - startY);
149
+ if (finalHeight > 20) {
150
+ localStorage.setItem(docId + '_' + sheetId + '_row_' + index, finalHeight);
151
+ }
152
+ };
153
+
154
+ resizer.addEventListener('mousedown', (e) => {
155
+ e.preventDefault();
156
+ e.stopPropagation();
157
+ startY = e.clientY;
158
+ startHeight = row.offsetHeight;
159
+ resizer.classList.add('resizing');
160
+ document.addEventListener('mousemove', onMouseMove);
161
+ document.addEventListener('mouseup', onMouseUp);
162
+ });
163
+ });
164
+ });
165
+ }
166
+
53
167
  function switchSheet() {
54
168
  try {
55
169
  let hash = window.location.hash;
@@ -79,6 +193,8 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
79
193
  }
80
194
  });
81
195
  }
196
+
197
+ initSpreadsheetResizing();
82
198
  } catch (e) {
83
199
  console.error('Sheet switch failed:', e);
84
200
  const firstSheet = document.querySelector('.spreadsheet-sheet');
@@ -96,9 +212,11 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
96
212
  metaTags = this.renderMetaTags();
97
213
  }
98
214
  const styles = this.config.htmlConfig.standalone ? '' : `<style>${this.getPremiumStyles(this.isSpreadsheetMode, isPresentation, isPdf)}</style>`;
215
+ const inj = this.config.htmlConfig.injections;
99
216
  const value = this.config.htmlConfig.standalone ? `<!DOCTYPE html>
100
217
  <html lang="en">
101
218
  <head>
219
+ ${inj.headStart}
102
220
  <meta charset="UTF-8">
103
221
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
104
222
  <title>${this.escape(title)}</title>
@@ -107,8 +225,10 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
107
225
  <style>
108
226
  ${this.getPremiumStyles(this.isSpreadsheetMode, isPresentation, isPdf)}
109
227
  </style>
228
+ ${inj.headEnd}
110
229
  </head>
111
230
  <body>
231
+ ${inj.bodyStart}
112
232
  <div class="${containerClass}">
113
233
  <article>
114
234
  ${metadataBlock}
@@ -117,6 +237,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
117
237
  ${spreadsheetTabs}
118
238
  </div>
119
239
  ${spreadsheetScript}
240
+ ${inj.bodyEnd}
120
241
  </body>
121
242
  </html>` : `${styles}<div class="${containerClass}">${metadataBlock}${bodyContent}${spreadsheetTabs}</div>${spreadsheetScript}`;
122
243
  return {
@@ -490,24 +611,128 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
490
611
  return `<td${rowSpan}${colSpan}${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</td>`;
491
612
  }
492
613
  case 'sheet': {
493
- // For sheet-based files, wrap rows in a table
494
- let finalChildren = childrenOutput;
495
614
  const rows = node.children || [];
496
- // Apply same header detection as 'table' nodes
497
- if (rows.length > 0 && rows[0].type === 'row') {
498
- const firstRow = rows[0];
499
- const firstRowCells = firstRow.children || [];
500
- const isHeaderStyle = firstRow.metadata?.style?.toLowerCase().includes('header');
501
- const allBold = firstRowCells.length > 0 && firstRowCells.every(c => c.children?.every(child => child.formatting?.bold === true));
502
- if (isHeaderStyle || allBold) {
503
- const headOutput = await this.processNodeRecursive(firstRow, async (n, children) => {
504
- return children.replace(/<td/g, '<th').replace(/<\/td>/g, '</th>');
505
- });
506
- const bodyRows = rows.slice(1);
507
- const bodyOutput = await this.processNodeArray(bodyRows);
508
- finalChildren = `<thead>${headOutput}</thead><tbody>${bodyOutput}</tbody>`;
615
+ // Find grid bounds
616
+ let maxRow = -1;
617
+ let maxCol = -1;
618
+ for (const child of rows) {
619
+ if (child.type === 'row') {
620
+ for (const cell of child.children || []) {
621
+ if (cell.type === 'cell') {
622
+ const meta = cell.metadata;
623
+ if (meta) {
624
+ const r = meta.row;
625
+ const c = meta.col;
626
+ const rSpan = meta.rowSpan || 1;
627
+ const cSpan = meta.colSpan || 1;
628
+ if (r + rSpan - 1 > maxRow)
629
+ maxRow = r + rSpan - 1;
630
+ if (c + cSpan - 1 > maxCol)
631
+ maxCol = c + cSpan - 1;
632
+ }
633
+ }
634
+ }
509
635
  }
510
636
  }
637
+ let tableHtml = '';
638
+ if (maxRow >= 0 && maxCol >= 0) {
639
+ // Populate cell grid and track merged cells
640
+ const grid = Array.from({ length: maxRow + 1 }, () => Array(maxCol + 1).fill(null));
641
+ const mergedCovered = Array.from({ length: maxRow + 1 }, () => Array(maxCol + 1).fill(false));
642
+ const rowNodeMap = new Map();
643
+ for (const child of rows) {
644
+ if (child.type === 'row') {
645
+ const cellsInRow = child.children?.filter(c => c.type === 'cell') || [];
646
+ if (cellsInRow.length > 0) {
647
+ const r = cellsInRow[0].metadata.row;
648
+ rowNodeMap.set(r, child);
649
+ for (const cell of cellsInRow) {
650
+ const meta = cell.metadata;
651
+ if (meta) {
652
+ const c = meta.col;
653
+ if (r >= 0 && r <= maxRow && c >= 0 && c <= maxCol) {
654
+ grid[r][c] = cell;
655
+ const rSpan = meta.rowSpan || 1;
656
+ const cSpan = meta.colSpan || 1;
657
+ if (rSpan > 1 || cSpan > 1) {
658
+ for (let rOffset = 0; rOffset < rSpan; rOffset++) {
659
+ for (let cOffset = 0; cOffset < cSpan; cOffset++) {
660
+ if (rOffset === 0 && cOffset === 0)
661
+ continue;
662
+ const targetR = r + rOffset;
663
+ const targetC = c + cOffset;
664
+ if (targetR <= maxRow && targetC <= maxCol) {
665
+ mergedCovered[targetR][targetC] = true;
666
+ }
667
+ }
668
+ }
669
+ }
670
+ }
671
+ }
672
+ }
673
+ }
674
+ }
675
+ }
676
+ // Build column headers (A, B, C...)
677
+ let ths = '<th class="excel-row-num-header"></th>';
678
+ for (let c = 0; c <= maxCol; c++) {
679
+ ths += `<th class="excel-col-header">${this.getColumnLetter(c)}</th>`;
680
+ }
681
+ const thead = `<thead><tr>${ths}</tr></thead>`;
682
+ // Build rows
683
+ let tbodyRows = '';
684
+ for (let r = 0; r <= maxRow; r++) {
685
+ const rowNode = rowNodeMap.get(r);
686
+ let trAttrs = '';
687
+ if (rowNode) {
688
+ const mapping = this.getSemanticMapping(rowNode);
689
+ const rClasses = ['excel-row'];
690
+ if (mapping?.classes)
691
+ rClasses.push(...mapping.classes);
692
+ trAttrs += ` class="${rClasses.join(' ')}"`;
693
+ if (mapping?.attributes) {
694
+ for (const [key, val] of Object.entries(mapping.attributes)) {
695
+ trAttrs += ` ${key}="${this.escape(val)}"`;
696
+ }
697
+ }
698
+ const rAnchorIds = this.config.ignoreInternalLinks ? [] : [...(rowNode.metadata?.anchorIds || [])];
699
+ if (rAnchorIds.length > 0) {
700
+ trAttrs += ` id="${rAnchorIds[0]}"`;
701
+ }
702
+ if (this.config.includeFormatting) {
703
+ const styles = this.getInlineStyles(rowNode);
704
+ if (styles)
705
+ trAttrs += ` style="${styles}"`;
706
+ }
707
+ }
708
+ else {
709
+ trAttrs = ' class="excel-row"';
710
+ }
711
+ let rowCellsHtml = `<td class="excel-row-num">${r + 1}</td>`;
712
+ for (let c = 0; c <= maxCol; c++) {
713
+ if (mergedCovered[r][c]) {
714
+ continue;
715
+ }
716
+ const cell = grid[r][c];
717
+ if (cell) {
718
+ const cellHtml = await this.processNodeRecursive(cell, this.nodeProcessor.bind(this));
719
+ rowCellsHtml += cellHtml;
720
+ }
721
+ else {
722
+ rowCellsHtml += '<td class="excel-cell-empty"></td>';
723
+ }
724
+ }
725
+ tbodyRows += `<tr${trAttrs}>${rowCellsHtml}</tr>\n`;
726
+ }
727
+ const tbody = `<tbody>${tbodyRows}</tbody>`;
728
+ tableHtml = `<table class="spreadsheet-table excel-grid">${thead}${tbody}</table>`;
729
+ }
730
+ // Process non-row elements (images, charts, etc.)
731
+ const nonRowNodes = rows.filter(c => c.type !== 'row');
732
+ let nonRowHtml = '';
733
+ if (nonRowNodes.length > 0) {
734
+ nonRowHtml = await this.processNodeArray(nonRowNodes);
735
+ }
511
736
  const isFirstSheet = this.ast.content.filter(n => n.type === 'sheet')[0] === node;
512
737
  const isActive = isFirstSheet;
513
738
  const sheetIndex = this.ast.content.filter(n => n.type === 'sheet').indexOf(node);
@@ -521,11 +746,18 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
521
746
  const classAttr = ` class="${mergedClasses.join(' ')}"`;
522
747
  // Ensure we don't have duplicate IDs
523
748
  const finalIdAttr = ` id="${sheetId}"`;
524
- return `${extraAnchors}<div${finalIdAttr}${classAttr}${mappedAttrs}${styleAttr}><table class="spreadsheet-table">${finalChildren}</table></div>`;
749
+ return `${extraAnchors}<div${finalIdAttr}${classAttr}${mappedAttrs}${styleAttr}>${tableHtml}${nonRowHtml}</div>`;
525
750
  }
526
751
  case 'paragraph':
527
752
  case 'heading': {
528
753
  const tag = node.type === 'heading' ? `h${node.metadata?.level || 1}` : 'p';
754
+ // Normalize empty paragraphs so DOCX and PPTX empty cells render with consistent height
755
+ // Strip tags to check if it's purely empty or just contains non-breaking spaces (like PPTX)
756
+ const textOnly = childrenOutput.replace(/<[^>]+>/g, '').trim();
757
+ if (!textOnly && !node.children?.some(c => c.type === 'image' || c.type === 'chart')) {
758
+ const extraClass = className ? ` class="${className.replace('class="', '').replace('"', '')} empty-paragraph"` : ' class="empty-paragraph"';
759
+ return `${extraAnchors}<${tag}${idAttr}${extraClass}${mappedAttrs}${styleAttr}><br></${tag}>`;
760
+ }
529
761
  return `${extraAnchors}<${tag}${idAttr}${className}${mappedAttrs}${styleAttr}>${childrenOutput}</${tag}>`;
530
762
  }
531
763
  case 'slide': {
@@ -622,6 +854,21 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
622
854
  return styles.join('; ');
623
855
  }
624
856
  getPremiumStyles(isSpreadsheet = false, isPresentation = false, isPdf = false) {
857
+ let resolvedWidth = this.config.htmlConfig.containerWidth;
858
+ if (!resolvedWidth || resolvedWidth === 'auto') {
859
+ if (isSpreadsheet) {
860
+ resolvedWidth = '100%';
861
+ }
862
+ else if (isPresentation) {
863
+ resolvedWidth = '297mm';
864
+ }
865
+ else {
866
+ resolvedWidth = '900px';
867
+ }
868
+ }
869
+ else if (typeof resolvedWidth === 'number') {
870
+ resolvedWidth = `${resolvedWidth}px`;
871
+ }
625
872
  return `
626
873
  :root {
627
874
  --primary-color: #2c3e50;
@@ -631,6 +878,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
631
878
  --border-color: #e9ecef;
632
879
  --accent-color: #3498db;
633
880
  --shadow: 0 10px 25px rgba(0,0,0,0.05);
881
+ --container-width: ${resolvedWidth};
634
882
  }
635
883
  * {
636
884
  box-sizing: border-box;
@@ -644,7 +892,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
644
892
  padding: ${isSpreadsheet ? '0' : '50px 20px'};
645
893
  }
646
894
  .container {
647
- max-width: 850px;
895
+ max-width: var(--container-width);
648
896
  margin: 0 auto;
649
897
  background: var(--container-bg);
650
898
  padding: 60px 80px;
@@ -652,7 +900,7 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
652
900
  box-shadow: var(--shadow);
653
901
  }
654
902
  .presentation-container, .pdf-container {
655
- max-width: 1200px;
903
+ max-width: var(--container-width);
656
904
  margin: 0 auto;
657
905
  }
658
906
  .presentation-container .metadata-summary, .pdf-container .metadata-summary {
@@ -700,34 +948,51 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
700
948
  h3 { font-size: 1.5em; }
701
949
 
702
950
  p { margin-bottom: 1.3em; }
951
+ p.empty-paragraph { margin: 0; min-height: 1em; }
703
952
 
704
- ul, ol { margin: 1.5em 0; padding-left: 2em; }
705
- li { margin-bottom: 0.6em; }
953
+ ul, ol { margin: 1em 0; padding-left: 2em; }
954
+ li { margin-bottom: 0.25em; }
955
+ li > p { margin-bottom: 0.25em; }
706
956
 
707
957
  .table-container {
708
- width: 100%;
958
+ width: fit-content;
959
+ max-width: 100%;
709
960
  overflow-x: auto;
710
961
  -webkit-overflow-scrolling: touch;
711
- margin: 25px 0;
962
+ margin: 25px auto;
963
+ border: 1px solid var(--border-color);
964
+ border-radius: 8px;
965
+ background: white;
712
966
  }
713
967
  table {
714
968
  width: auto;
715
969
  max-width: 100%;
716
970
  min-width: ${isSpreadsheet ? '100%' : '300px'};
717
- border-collapse: collapse;
718
- margin: 0 auto;
719
- border: 1px solid var(--border-color);
720
- border-radius: 8px;
721
- overflow: hidden;
971
+ border-collapse: separate;
972
+ border-spacing: 0;
973
+ margin: 0;
974
+ border: none;
722
975
  }
723
976
  th, td {
724
- padding: 12px 16px;
977
+ padding: 8px 12px;
725
978
  border-bottom: 1px solid var(--border-color);
726
979
  border-right: 1px solid var(--border-color);
727
980
  text-align: left;
728
981
  vertical-align: top;
729
982
  overflow-wrap: break-word;
730
983
  }
984
+ th:last-child, td:last-child {
985
+ border-right: none;
986
+ }
987
+ tr:last-child th, tr:last-child td {
988
+ border-bottom: none;
989
+ }
990
+ th, td > p {
991
+ margin: 0;
992
+ }
993
+ td > *:last-child, th > *:last-child {
994
+ margin-bottom: 0;
995
+ }
731
996
  th {
732
997
  background-color: #f8f9fa;
733
998
  color: var(--primary-color);
@@ -739,7 +1004,6 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
739
1004
  top: 0;
740
1005
  z-index: 10;
741
1006
  }
742
- tr:last-child td { border-bottom: none; }
743
1007
  tr:nth-child(even) { background-color: #fdfdfd; }
744
1008
  tr:hover { background-color: #f1f4f9; }
745
1009
 
@@ -757,8 +1021,99 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
757
1021
  padding: 8px 12px;
758
1022
  font-size: 13px;
759
1023
  white-space: nowrap;
1024
+ }
1025
+ /* Spreadsheet Grid styling */
1026
+ .excel-grid {
1027
+ border-collapse: collapse;
1028
+ border-spacing: 0;
1029
+ background: var(--container-bg);
1030
+ border: 1px solid var(--border-color);
1031
+ width: max-content;
1032
+ max-width: none;
1033
+ min-width: 0;
1034
+ }
1035
+ .excel-grid th, .excel-grid td {
1036
+ border: 1px solid var(--border-color);
1037
+ padding: 4px 8px;
1038
+ font-size: 13px;
1039
+ line-height: 1.2;
1040
+ overflow: hidden;
1041
+ text-overflow: ellipsis;
1042
+ white-space: nowrap;
1043
+ }
1044
+ .excel-col-header {
1045
+ background: #f8f9fa;
1046
+ color: #5f6368;
1047
+ font-weight: 500;
1048
+ text-align: center;
1049
+ user-select: none;
1050
+ border-bottom: 2px solid var(--border-color);
1051
+ position: sticky;
1052
+ top: 0;
1053
+ z-index: 10;
760
1054
  min-width: 100px;
761
1055
  }
1056
+ .col-resizer {
1057
+ position: absolute;
1058
+ top: 0;
1059
+ right: 0;
1060
+ width: 4px;
1061
+ height: 100%;
1062
+ cursor: col-resize;
1063
+ user-select: none;
1064
+ z-index: 20;
1065
+ }
1066
+ .col-resizer:hover, .col-resizer.resizing {
1067
+ background: var(--accent-color, #3498db);
1068
+ }
1069
+ .excel-row-num {
1070
+ background: #f8f9fa;
1071
+ color: #5f6368;
1072
+ text-align: center;
1073
+ font-weight: 500;
1074
+ width: 45px;
1075
+ min-width: 45px !important;
1076
+ max-width: 45px;
1077
+ user-select: none;
1078
+ border-right: 2px solid var(--border-color);
1079
+ position: sticky;
1080
+ left: 0;
1081
+ z-index: 5;
1082
+ }
1083
+ .row-resizer {
1084
+ position: absolute;
1085
+ bottom: 0;
1086
+ left: 0;
1087
+ width: 100%;
1088
+ height: 4px;
1089
+ cursor: row-resize;
1090
+ user-select: none;
1091
+ z-index: 20;
1092
+ }
1093
+ .row-resizer:hover, .row-resizer.resizing {
1094
+ background: var(--accent-color, #3498db);
1095
+ }
1096
+ .excel-row-num-header {
1097
+ background: #f1f3f4;
1098
+ width: 45px;
1099
+ min-width: 45px !important;
1100
+ max-width: 45px;
1101
+ position: sticky;
1102
+ top: 0;
1103
+ left: 0;
1104
+ z-index: 15;
1105
+ border-right: 2px solid var(--border-color);
1106
+ border-bottom: 2px solid var(--border-color);
1107
+ }
1108
+ .excel-cell-empty {
1109
+ background: var(--container-bg);
1110
+ }
1111
+ .excel-grid tr:hover td {
1112
+ background-color: #f1f4f9;
1113
+ }
1114
+ .excel-grid tr:hover td.excel-row-num {
1115
+ background-color: #f8f9fa; /* Keep header color */
1116
+ }
762
1117
 
763
1118
  /* Tab Bar */
764
1119
  .spreadsheet-tabs {
@@ -792,9 +1147,9 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
792
1147
  /* Slide & Page Separation */
793
1148
  .slide {
794
1149
  background: white;
795
- min-height: 600px;
1150
+ aspect-ratio: 297 / 210;
796
1151
  margin: 0 auto 40px auto;
797
- padding: 60px 80px;
1152
+ padding: 4% 6%;
798
1153
  border-radius: 12px;
799
1154
  box-shadow: 0 10px 30px rgba(0,0,0,0.1);
800
1155
  position: relative;
@@ -804,21 +1159,74 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
804
1159
  border: 1px solid var(--border-color);
805
1160
  box-sizing: border-box;
806
1161
  width: 100%;
1162
+ overflow: hidden;
1163
+ overflow-y: auto;
807
1164
  overflow-wrap: break-word;
808
1165
  page-break-after: always;
809
1166
  }
1167
+ .slide:has(+ .slide-note) {
1168
+ margin-bottom: 0;
1169
+ border-bottom-left-radius: 0;
1170
+ border-bottom-right-radius: 0;
1171
+ border-bottom: none;
1172
+ }
1173
+ .slide table {
1174
+ width: auto;
1175
+ max-width: 100%;
1176
+ font-size: 0.85em;
1177
+ margin: 10px 0;
1178
+ }
1179
+ .slide th, .slide td {
1180
+ padding: 6px 10px;
1181
+ }
1182
+ .slide > * {
1183
+ flex-shrink: 0;
1184
+ }
1185
+ .slide > .table-container {
1186
+ flex-shrink: 1;
1187
+ min-height: 0;
1188
+ }
1189
+ .slide .table-container {
1190
+ margin: 10px auto;
1191
+ overflow-y: auto;
1192
+ }
1193
+ .slide .chart-container {
1194
+ margin: 15px auto;
1195
+ max-width: 100%;
1196
+ padding: 10px;
1197
+ height: 240px;
1198
+ box-shadow: none;
1199
+ border-radius: 8px;
1200
+ }
1201
+ .slide img {
1202
+ max-height: 200px;
1203
+ width: auto;
1204
+ margin: 10px auto;
1205
+ box-shadow: none;
1206
+ }
1207
+ .slide .image-container {
1208
+ margin: 15px 0;
1209
+ }
1210
+ .slide h1 { font-size: 1.8em; margin-top: 0.4em; margin-bottom: 0.3em; padding-bottom: 5px; }
1211
+ .slide h2 { font-size: 1.4em; margin-top: 0.4em; }
1212
+ .slide h3 { font-size: 1.25em; }
1213
+ .slide p { margin-bottom: 0.6em; font-size: 0.95em; }
1214
+ .slide li > p { margin-bottom: 0.25em; }
1215
+ .slide ul, .slide ol { margin: 0.6em 0; }
1216
+ .slide li { margin-bottom: 0.25em; }
810
1217
  .slide-note {
811
1218
  background: #fdfdfd;
812
1219
  border: 1px solid var(--border-color);
813
- border-left: 4px solid var(--accent-color);
814
- border-radius: 12px;
1220
+ border-top: 1px dashed var(--border-color);
1221
+ border-radius: 0 0 12px 12px;
815
1222
  padding: 30px 50px;
816
- margin: 20px auto 60px auto;
817
- max-width: 1000px;
1223
+ margin: 0 auto 40px auto;
1224
+ width: 100%;
1225
+ box-sizing: border-box;
818
1226
  font-size: 0.95em;
819
1227
  color: var(--text-color);
820
1228
  position: relative;
821
- box-shadow: 0 4px 15px rgba(0,0,0,0.05);
1229
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
822
1230
  }
823
1231
  .note-footnote, .note-endnote {
824
1232
  margin: 30px auto;
@@ -848,9 +1256,9 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
848
1256
  }
849
1257
  .page {
850
1258
  background: white;
851
- min-height: 1000px;
1259
+ aspect-ratio: 1 / 1.4142;
852
1260
  margin: 0 auto 40px auto;
853
- padding: 70px 90px;
1261
+ padding: 8% 10%;
854
1262
  box-shadow: 0 5px 20px rgba(0,0,0,0.08);
855
1263
  position: relative;
856
1264
  border: 1px solid var(--border-color);
@@ -870,8 +1278,8 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
870
1278
 
871
1279
  /* High Fidelity Presentation Mode */
872
1280
  @media screen and (max-width: 800px) {
873
- .slide { min-height: auto; padding: 40px 30px; }
874
- .page { padding: 40px; }
1281
+ .slide { min-height: auto; aspect-ratio: auto; padding: 40px 30px; }
1282
+ .page { aspect-ratio: auto; padding: 40px; }
875
1283
  }
876
1284
 
877
1285
  /* Nested Table Styles */
@@ -880,6 +1288,8 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
880
1288
  border-radius: 6px;
881
1289
  box-shadow: 0 2px 8px rgba(0,0,0,0.03);
882
1290
  background-color: #ffffff;
1291
+ border: 1px solid var(--border-color);
1292
+ overflow: hidden;
883
1293
  }
884
1294
  td td {
885
1295
  padding: 10px 14px;
@@ -999,11 +1409,23 @@ class HtmlGenerator extends BaseGenerator_js_1.BaseGenerator {
999
1409
  height: auto !important;
1000
1410
  }
1001
1411
  }
1412
+
1413
+ /* --- Custom User CSS --- */
1414
+ ${this.config.htmlConfig.customCss}
1002
1415
  `;
1003
1416
  }
1004
1417
  slugify(text) {
1005
1418
  return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
1006
1419
  }
1420
+ getColumnLetter(colIndex) {
1421
+ let temp = colIndex;
1422
+ let letter = '';
1423
+ while (temp >= 0) {
1424
+ letter = String.fromCharCode((temp % 26) + 65) + letter;
1425
+ temp = Math.floor(temp / 26) - 1;
1426
+ }
1427
+ return letter;
1428
+ }
1007
1429
  escape(text) {
1008
1430
  if (typeof text !== 'string')
1009
1431
  return text;