@pagent-libs/core 0.1.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.
- package/README.md +32 -0
- package/dist/canvas/cell-renderer.d.ts +45 -0
- package/dist/canvas/cell-renderer.d.ts.map +1 -0
- package/dist/canvas/grid-renderer.d.ts +29 -0
- package/dist/canvas/grid-renderer.d.ts.map +1 -0
- package/dist/canvas/header-renderer.d.ts +58 -0
- package/dist/canvas/header-renderer.d.ts.map +1 -0
- package/dist/canvas/hit-testing.d.ts +81 -0
- package/dist/canvas/hit-testing.d.ts.map +1 -0
- package/dist/canvas/index.d.ts +9 -0
- package/dist/canvas/index.d.ts.map +1 -0
- package/dist/canvas/renderer.d.ts +140 -0
- package/dist/canvas/renderer.d.ts.map +1 -0
- package/dist/canvas/selection-renderer.d.ts +55 -0
- package/dist/canvas/selection-renderer.d.ts.map +1 -0
- package/dist/canvas/text-renderer.d.ts +49 -0
- package/dist/canvas/text-renderer.d.ts.map +1 -0
- package/dist/canvas/types.d.ts +200 -0
- package/dist/canvas/types.d.ts.map +1 -0
- package/dist/collaboration/firebase-provider.d.ts +13 -0
- package/dist/collaboration/firebase-provider.d.ts.map +1 -0
- package/dist/collaboration/index.d.ts +3 -0
- package/dist/collaboration/index.d.ts.map +1 -0
- package/dist/collaboration/types.d.ts +34 -0
- package/dist/collaboration/types.d.ts.map +1 -0
- package/dist/event-emitter.d.ts +13 -0
- package/dist/event-emitter.d.ts.map +1 -0
- package/dist/export/csv.d.ts +5 -0
- package/dist/export/csv.d.ts.map +1 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/features/filter.d.ts +58 -0
- package/dist/features/filter.d.ts.map +1 -0
- package/dist/features/freeze.d.ts +86 -0
- package/dist/features/freeze.d.ts.map +1 -0
- package/dist/features/index.d.ts +4 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/sort.d.ts +15 -0
- package/dist/features/sort.d.ts.map +1 -0
- package/dist/format-pool.d.ts +17 -0
- package/dist/format-pool.d.ts.map +1 -0
- package/dist/formula-graph.d.ts +12 -0
- package/dist/formula-graph.d.ts.map +1 -0
- package/dist/formula-parser/cell-reference.d.ts +7 -0
- package/dist/formula-parser/cell-reference.d.ts.map +1 -0
- package/dist/formula-parser/formula-adjust.d.ts +13 -0
- package/dist/formula-parser/formula-adjust.d.ts.map +1 -0
- package/dist/formula-parser/formula-ranges.d.ts +22 -0
- package/dist/formula-parser/formula-ranges.d.ts.map +1 -0
- package/dist/formula-parser/index.d.ts +6 -0
- package/dist/formula-parser/index.d.ts.map +1 -0
- package/dist/formula-parser/parser.d.ts +18 -0
- package/dist/formula-parser/parser.d.ts.map +1 -0
- package/dist/formula-parser/types.d.ts +33 -0
- package/dist/formula-parser/types.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +5823 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +5885 -0
- package/dist/index.js.map +1 -0
- package/dist/sheet.d.ts +119 -0
- package/dist/sheet.d.ts.map +1 -0
- package/dist/style-pool.d.ts +17 -0
- package/dist/style-pool.d.ts.map +1 -0
- package/dist/types.d.ts +260 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/cell-key.d.ts +7 -0
- package/dist/utils/cell-key.d.ts.map +1 -0
- package/dist/utils/format-utils.d.ts +75 -0
- package/dist/utils/format-utils.d.ts.map +1 -0
- package/dist/utils/range.d.ts +13 -0
- package/dist/utils/range.d.ts.map +1 -0
- package/dist/workbook.d.ts +155 -0
- package/dist/workbook.d.ts.map +1 -0
- package/package.json +46 -0
- package/src/canvas/cell-renderer.ts +181 -0
- package/src/canvas/grid-renderer.ts +238 -0
- package/src/canvas/header-renderer.ts +402 -0
- package/src/canvas/hit-testing.ts +537 -0
- package/src/canvas/index.ts +16 -0
- package/src/canvas/renderer.ts +1056 -0
- package/src/canvas/selection-renderer.ts +604 -0
- package/src/canvas/text-renderer.ts +321 -0
- package/src/canvas/types.ts +289 -0
- package/src/collaboration/firebase-provider.ts +48 -0
- package/src/collaboration/index.ts +5 -0
- package/src/collaboration/types.ts +38 -0
- package/src/event-emitter.ts +73 -0
- package/src/export/csv.ts +101 -0
- package/src/export/index.ts +4 -0
- package/src/features/filter.ts +231 -0
- package/src/features/freeze.ts +271 -0
- package/src/features/index.ts +5 -0
- package/src/features/sort.ts +282 -0
- package/src/format-pool.ts +61 -0
- package/src/formula-graph.ts +84 -0
- package/src/formula-parser/cell-reference.ts +99 -0
- package/src/formula-parser/formula-adjust.ts +129 -0
- package/src/formula-parser/formula-ranges.ts +159 -0
- package/src/formula-parser/index.ts +8 -0
- package/src/formula-parser/parser.ts +438 -0
- package/src/formula-parser/types.ts +39 -0
- package/src/index.ts +25 -0
- package/src/sheet.ts +502 -0
- package/src/style-pool.ts +62 -0
- package/src/types.ts +291 -0
- package/src/utils/cell-key.ts +19 -0
- package/src/utils/format-utils.ts +515 -0
- package/src/utils/range.ts +53 -0
- package/src/workbook.ts +1031 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// Cell Renderer - Handles cell content rendering
|
|
2
|
+
|
|
3
|
+
import type { CanvasTheme, Rect, TextStyle, CellBorders } from './types';
|
|
4
|
+
import type { Cell, CellStyle, CellFormat } from '../types';
|
|
5
|
+
import { TextRenderer } from './text-renderer';
|
|
6
|
+
import { GridRenderer } from './grid-renderer';
|
|
7
|
+
import { formatNumber } from '../utils/format-utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders cell content including backgrounds, text, and borders
|
|
11
|
+
*/
|
|
12
|
+
export class CellRenderer {
|
|
13
|
+
private theme: CanvasTheme;
|
|
14
|
+
private textRenderer: TextRenderer;
|
|
15
|
+
private gridRenderer: GridRenderer;
|
|
16
|
+
|
|
17
|
+
constructor(theme: CanvasTheme, textRenderer: TextRenderer) {
|
|
18
|
+
this.theme = theme;
|
|
19
|
+
this.textRenderer = textRenderer;
|
|
20
|
+
this.gridRenderer = new GridRenderer(theme);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Render a cell with all its content
|
|
25
|
+
*/
|
|
26
|
+
renderCell(
|
|
27
|
+
ctx: CanvasRenderingContext2D,
|
|
28
|
+
cell: Cell | undefined,
|
|
29
|
+
bounds: Rect,
|
|
30
|
+
style: CellStyle | undefined,
|
|
31
|
+
format: CellFormat | undefined
|
|
32
|
+
): void {
|
|
33
|
+
// 1. Render background
|
|
34
|
+
this.renderBackground(ctx, bounds, style);
|
|
35
|
+
|
|
36
|
+
// 2. Render cell content (text/value)
|
|
37
|
+
if (cell) {
|
|
38
|
+
const displayValue = this.formatCellValue(cell, format);
|
|
39
|
+
if (displayValue) {
|
|
40
|
+
const textStyle = this.cellStyleToTextStyle(style);
|
|
41
|
+
|
|
42
|
+
// Check if text wrapping is enabled
|
|
43
|
+
if (style?.textWrap) {
|
|
44
|
+
this.textRenderer.renderWrappedText(ctx, displayValue, bounds, textStyle);
|
|
45
|
+
} else {
|
|
46
|
+
this.textRenderer.renderText(ctx, displayValue, bounds, textStyle);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Render borders (if any)
|
|
52
|
+
if (style) {
|
|
53
|
+
const borders = this.cellStyleToBorders(style);
|
|
54
|
+
if (borders.top || borders.right || borders.bottom || borders.left) {
|
|
55
|
+
this.gridRenderer.renderCellBorders(ctx, bounds, borders);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Render an empty cell (just background)
|
|
62
|
+
*/
|
|
63
|
+
renderEmptyCell(ctx: CanvasRenderingContext2D, bounds: Rect): void {
|
|
64
|
+
this.renderBackground(ctx, bounds, undefined);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render cell background
|
|
69
|
+
*/
|
|
70
|
+
private renderBackground(
|
|
71
|
+
ctx: CanvasRenderingContext2D,
|
|
72
|
+
bounds: Rect,
|
|
73
|
+
style: CellStyle | undefined
|
|
74
|
+
): void {
|
|
75
|
+
const bgColor = style?.backgroundColor ?? this.theme.cellBackgroundColor;
|
|
76
|
+
|
|
77
|
+
ctx.fillStyle = bgColor;
|
|
78
|
+
ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format cell value for display
|
|
83
|
+
*/
|
|
84
|
+
private formatCellValue(cell: Cell, format: CellFormat | undefined): string {
|
|
85
|
+
const value = cell.value;
|
|
86
|
+
|
|
87
|
+
if (value === null || value === undefined) {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If there's a formula, show the computed value (not the formula)
|
|
92
|
+
// The formula is shown in the formula bar, not in the cell
|
|
93
|
+
|
|
94
|
+
if (typeof value === 'number') {
|
|
95
|
+
return this.formatNumber(value, format);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof value === 'boolean') {
|
|
99
|
+
return value ? 'TRUE' : 'FALSE';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return String(value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Format a number according to the cell format
|
|
107
|
+
*/
|
|
108
|
+
private formatNumber(value: number, format: CellFormat | undefined): string {
|
|
109
|
+
// Use the comprehensive formatNumber function from format-utils
|
|
110
|
+
// Pass an empty format object if none is provided
|
|
111
|
+
return formatNumber(value, format || {});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Convert CellStyle to TextStyle for rendering
|
|
117
|
+
*/
|
|
118
|
+
private cellStyleToTextStyle(style: CellStyle | undefined): Partial<TextStyle> {
|
|
119
|
+
if (!style) {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
fontFamily: style.fontFamily,
|
|
125
|
+
fontSize: style.fontSize,
|
|
126
|
+
fontWeight: style.bold ? 'bold' : 'normal',
|
|
127
|
+
fontStyle: style.italic ? 'italic' : 'normal',
|
|
128
|
+
color: style.fontColor,
|
|
129
|
+
textAlign: style.textAlign,
|
|
130
|
+
verticalAlign: style.verticalAlign,
|
|
131
|
+
textDecoration: style.textDecoration,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Convert CellStyle borders to CellBorders
|
|
137
|
+
*/
|
|
138
|
+
private cellStyleToBorders(style: CellStyle): CellBorders {
|
|
139
|
+
return {
|
|
140
|
+
top: this.gridRenderer.parseBorderString(style.borderTop),
|
|
141
|
+
right: this.gridRenderer.parseBorderString(style.borderRight),
|
|
142
|
+
bottom: this.gridRenderer.parseBorderString(style.borderBottom),
|
|
143
|
+
left: this.gridRenderer.parseBorderString(style.borderLeft),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Render a hyperlink cell (with special styling)
|
|
149
|
+
*/
|
|
150
|
+
renderHyperlinkCell(
|
|
151
|
+
ctx: CanvasRenderingContext2D,
|
|
152
|
+
cell: Cell,
|
|
153
|
+
bounds: Rect,
|
|
154
|
+
style: CellStyle | undefined,
|
|
155
|
+
format: CellFormat | undefined
|
|
156
|
+
): void {
|
|
157
|
+
// Render background
|
|
158
|
+
this.renderBackground(ctx, bounds, style);
|
|
159
|
+
|
|
160
|
+
// Render text with hyperlink styling
|
|
161
|
+
const displayValue = this.formatCellValue(cell, format);
|
|
162
|
+
if (displayValue) {
|
|
163
|
+
const textStyle: Partial<TextStyle> = {
|
|
164
|
+
...this.cellStyleToTextStyle(style),
|
|
165
|
+
color: '#1a73e8', // Blue color for hyperlinks
|
|
166
|
+
textDecoration: 'underline',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
this.textRenderer.renderText(ctx, displayValue, bounds, textStyle);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Render borders
|
|
173
|
+
if (style) {
|
|
174
|
+
const borders = this.cellStyleToBorders(style);
|
|
175
|
+
if (borders.top || borders.right || borders.bottom || borders.left) {
|
|
176
|
+
this.gridRenderer.renderCellBorders(ctx, bounds, borders);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// Grid Renderer - Handles grid lines and cell borders
|
|
2
|
+
|
|
3
|
+
import type { CanvasTheme, Viewport, BorderStyle, CellBorders, Rect } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renders grid lines and cell borders
|
|
7
|
+
*/
|
|
8
|
+
export class GridRenderer {
|
|
9
|
+
private theme: CanvasTheme;
|
|
10
|
+
|
|
11
|
+
constructor(theme: CanvasTheme) {
|
|
12
|
+
this.theme = theme;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Render grid lines for the visible area
|
|
17
|
+
*/
|
|
18
|
+
renderGridLines(
|
|
19
|
+
ctx: CanvasRenderingContext2D,
|
|
20
|
+
viewport: Viewport,
|
|
21
|
+
rowHeights: Map<number, number>,
|
|
22
|
+
colWidths: Map<number, number>,
|
|
23
|
+
defaultRowHeight: number,
|
|
24
|
+
defaultColWidth: number,
|
|
25
|
+
headerWidth: number,
|
|
26
|
+
headerHeight: number,
|
|
27
|
+
startRow: number,
|
|
28
|
+
endRow: number,
|
|
29
|
+
startCol: number,
|
|
30
|
+
endCol: number,
|
|
31
|
+
hiddenRows?: Set<number>,
|
|
32
|
+
hiddenCols?: Set<number>
|
|
33
|
+
): void {
|
|
34
|
+
const { scrollTop, scrollLeft, width, height } = viewport;
|
|
35
|
+
const hidRows = hiddenRows ?? new Set<number>();
|
|
36
|
+
const hidCols = hiddenCols ?? new Set<number>();
|
|
37
|
+
|
|
38
|
+
ctx.strokeStyle = this.theme.gridLineColor;
|
|
39
|
+
ctx.lineWidth = this.theme.gridLineWidth;
|
|
40
|
+
|
|
41
|
+
// Calculate starting positions (skip hidden rows/cols)
|
|
42
|
+
let startY = headerHeight;
|
|
43
|
+
for (let r = 0; r < startRow; r++) {
|
|
44
|
+
if (!hidRows.has(r)) {
|
|
45
|
+
startY += rowHeights.get(r) ?? defaultRowHeight;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
startY -= scrollTop;
|
|
49
|
+
|
|
50
|
+
let startX = headerWidth;
|
|
51
|
+
for (let c = 0; c < startCol; c++) {
|
|
52
|
+
if (!hidCols.has(c)) {
|
|
53
|
+
startX += colWidths.get(c) ?? defaultColWidth;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
startX -= scrollLeft;
|
|
57
|
+
|
|
58
|
+
ctx.beginPath();
|
|
59
|
+
|
|
60
|
+
// Draw horizontal lines (row separators) - skip hidden rows
|
|
61
|
+
let y = startY;
|
|
62
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
63
|
+
if (hidRows.has(row)) continue;
|
|
64
|
+
// Snap to pixel grid for crisp lines
|
|
65
|
+
const lineY = Math.round(y) + 0.5;
|
|
66
|
+
ctx.moveTo(headerWidth, lineY);
|
|
67
|
+
ctx.lineTo(width, lineY);
|
|
68
|
+
y += rowHeights.get(row) ?? defaultRowHeight;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Draw vertical lines (column separators) - skip hidden columns
|
|
72
|
+
let x = startX;
|
|
73
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
74
|
+
if (hidCols.has(col)) continue;
|
|
75
|
+
// Snap to pixel grid for crisp lines
|
|
76
|
+
const lineX = Math.round(x) + 0.5;
|
|
77
|
+
ctx.moveTo(lineX, headerHeight);
|
|
78
|
+
ctx.lineTo(lineX, height);
|
|
79
|
+
x += colWidths.get(col) ?? defaultColWidth;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ctx.stroke();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Parse a CSS border string into a BorderStyle object
|
|
87
|
+
*/
|
|
88
|
+
parseBorderString(border: string | undefined): BorderStyle | undefined {
|
|
89
|
+
if (!border || border === 'none') {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Parse formats like "1px solid #000000" or "2px dashed red"
|
|
94
|
+
const parts = border.split(' ');
|
|
95
|
+
if (parts.length < 2) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const widthStr = parts[0];
|
|
100
|
+
const style = parts[1] as 'solid' | 'dashed' | 'dotted';
|
|
101
|
+
const color = parts[2] ?? '#000000';
|
|
102
|
+
|
|
103
|
+
const width = parseInt(widthStr, 10);
|
|
104
|
+
if (isNaN(width)) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { width, style, color };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Render borders for a single cell
|
|
113
|
+
*/
|
|
114
|
+
renderCellBorders(
|
|
115
|
+
ctx: CanvasRenderingContext2D,
|
|
116
|
+
bounds: Rect,
|
|
117
|
+
borders: CellBorders
|
|
118
|
+
): void {
|
|
119
|
+
const { x, y, width, height } = bounds;
|
|
120
|
+
|
|
121
|
+
// Render each border if specified
|
|
122
|
+
if (borders.top) {
|
|
123
|
+
this.renderBorderLine(ctx, x, y, x + width, y, borders.top);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (borders.right) {
|
|
127
|
+
this.renderBorderLine(ctx, x + width, y, x + width, y + height, borders.right);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (borders.bottom) {
|
|
131
|
+
this.renderBorderLine(ctx, x, y + height, x + width, y + height, borders.bottom);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (borders.left) {
|
|
135
|
+
this.renderBorderLine(ctx, x, y, x, y + height, borders.left);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Render a single border line with the specified style
|
|
141
|
+
*/
|
|
142
|
+
private renderBorderLine(
|
|
143
|
+
ctx: CanvasRenderingContext2D,
|
|
144
|
+
x1: number,
|
|
145
|
+
y1: number,
|
|
146
|
+
x2: number,
|
|
147
|
+
y2: number,
|
|
148
|
+
style: BorderStyle
|
|
149
|
+
): void {
|
|
150
|
+
ctx.strokeStyle = style.color;
|
|
151
|
+
ctx.lineWidth = style.width;
|
|
152
|
+
|
|
153
|
+
// Set line dash pattern based on style
|
|
154
|
+
switch (style.style) {
|
|
155
|
+
case 'dashed':
|
|
156
|
+
ctx.setLineDash([4, 2]);
|
|
157
|
+
break;
|
|
158
|
+
case 'dotted':
|
|
159
|
+
ctx.setLineDash([1, 1]);
|
|
160
|
+
break;
|
|
161
|
+
case 'solid':
|
|
162
|
+
default:
|
|
163
|
+
ctx.setLineDash([]);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
ctx.beginPath();
|
|
168
|
+
|
|
169
|
+
// Snap to pixel grid for crisp lines
|
|
170
|
+
const snapX1 = Math.round(x1) + 0.5;
|
|
171
|
+
const snapY1 = Math.round(y1) + 0.5;
|
|
172
|
+
const snapX2 = Math.round(x2) + 0.5;
|
|
173
|
+
const snapY2 = Math.round(y2) + 0.5;
|
|
174
|
+
|
|
175
|
+
ctx.moveTo(snapX1, snapY1);
|
|
176
|
+
ctx.lineTo(snapX2, snapY2);
|
|
177
|
+
ctx.stroke();
|
|
178
|
+
|
|
179
|
+
// Reset line dash
|
|
180
|
+
ctx.setLineDash([]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Render borders for all visible cells that have custom borders
|
|
185
|
+
*/
|
|
186
|
+
renderAllCellBorders(
|
|
187
|
+
ctx: CanvasRenderingContext2D,
|
|
188
|
+
cellBorders: Map<string, CellBorders>,
|
|
189
|
+
viewport: Viewport,
|
|
190
|
+
rowHeights: Map<number, number>,
|
|
191
|
+
colWidths: Map<number, number>,
|
|
192
|
+
defaultRowHeight: number,
|
|
193
|
+
defaultColWidth: number,
|
|
194
|
+
headerWidth: number,
|
|
195
|
+
headerHeight: number,
|
|
196
|
+
startRow: number,
|
|
197
|
+
endRow: number,
|
|
198
|
+
startCol: number,
|
|
199
|
+
endCol: number
|
|
200
|
+
): void {
|
|
201
|
+
const { scrollTop, scrollLeft } = viewport;
|
|
202
|
+
|
|
203
|
+
// Calculate starting positions
|
|
204
|
+
let startY = headerHeight;
|
|
205
|
+
for (let r = 0; r < startRow; r++) {
|
|
206
|
+
startY += rowHeights.get(r) ?? defaultRowHeight;
|
|
207
|
+
}
|
|
208
|
+
startY -= scrollTop;
|
|
209
|
+
|
|
210
|
+
let startX = headerWidth;
|
|
211
|
+
for (let c = 0; c < startCol; c++) {
|
|
212
|
+
startX += colWidths.get(c) ?? defaultColWidth;
|
|
213
|
+
}
|
|
214
|
+
startX -= scrollLeft;
|
|
215
|
+
|
|
216
|
+
// Iterate through visible cells and render borders
|
|
217
|
+
let y = startY;
|
|
218
|
+
for (let row = startRow; row < endRow; row++) {
|
|
219
|
+
const rowHeight = rowHeights.get(row) ?? defaultRowHeight;
|
|
220
|
+
let x = startX;
|
|
221
|
+
|
|
222
|
+
for (let col = startCol; col < endCol; col++) {
|
|
223
|
+
const colWidth = colWidths.get(col) ?? defaultColWidth;
|
|
224
|
+
const cellKey = `${row}:${col}`;
|
|
225
|
+
const borders = cellBorders.get(cellKey);
|
|
226
|
+
|
|
227
|
+
if (borders) {
|
|
228
|
+
this.renderCellBorders(ctx, { x, y, width: colWidth, height: rowHeight }, borders);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
x += colWidth;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
y += rowHeight;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|