reamkit 1.15.4 → 1.17.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/dist/esm/core/bidi/segments.js +1 -2
- package/dist/esm/core/converter/facade.d.ts +5 -0
- package/dist/esm/core/converter/project.js +5 -1
- package/dist/esm/core/converter/ream.d.ts +6 -0
- package/dist/esm/core/converter/ream.js +34 -8
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +659 -7
- package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
- package/dist/esm/core/drawingml/chart-geometry.js +424 -92
- package/dist/esm/core/drawingml/chart-parser.js +301 -11
- package/dist/esm/core/drawingml/colors.d.ts +7 -7
- package/dist/esm/core/drawingml/colors.js +84 -18
- package/dist/esm/core/drawingml/preset-geometry.js +240 -6
- package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
- package/dist/esm/core/drawingml/shape-render.js +116 -3
- package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
- package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
- package/dist/esm/core/drawingml/theme-parser.js +92 -1
- package/dist/esm/core/font/measure.d.ts +5 -0
- package/dist/esm/core/font/measure.js +21 -1
- package/dist/esm/core/font/ttf-subset.js +26 -1
- package/dist/esm/core/images.d.ts +2 -2
- package/dist/esm/core/images.js +353 -43
- package/dist/esm/core/indexed-colors.d.ts +11 -0
- package/dist/esm/core/indexed-colors.js +81 -0
- package/dist/esm/core/ir/flow.d.ts +23 -1
- package/dist/esm/core/ir/sheet.d.ts +61 -0
- package/dist/esm/{excel → core}/number-format.d.ts +37 -0
- package/dist/esm/core/number-format.js +800 -0
- package/dist/esm/core/numbering/apply.d.ts +5 -3
- package/dist/esm/core/numbering/apply.js +42 -11
- package/dist/esm/core/numbering/index.js +2 -0
- package/dist/esm/core/numbering/state.d.ts +13 -1
- package/dist/esm/core/numbering/state.js +256 -15
- package/dist/esm/core/opc/alternate-content.d.ts +13 -0
- package/dist/esm/core/opc/alternate-content.js +72 -0
- package/dist/esm/core/opc/core-properties.js +1 -0
- package/dist/esm/core/opc/package.d.ts +14 -1
- package/dist/esm/core/opc/package.js +43 -4
- package/dist/esm/core/opc/relationships.js +1 -0
- package/dist/esm/core/opc/xml-entities.d.ts +12 -0
- package/dist/esm/core/opc/xml-entities.js +91 -0
- package/dist/esm/core/po-helpers.js +16 -1
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
- package/dist/esm/core/style-cascade/resolver.js +66 -11
- package/dist/esm/core/style-cascade/table.js +35 -11
- package/dist/esm/core/style-cascade/types.d.ts +42 -1
- package/dist/esm/core/style-cascade/types.js +4 -0
- package/dist/esm/core/vector.d.ts +30 -0
- package/dist/esm/excel/activex-parser.js +1 -0
- package/dist/esm/excel/column-bands.d.ts +26 -1
- package/dist/esm/excel/column-bands.js +156 -20
- package/dist/esm/excel/comments-parser.js +5 -3
- package/dist/esm/excel/conditional-format.d.ts +25 -2
- package/dist/esm/excel/conditional-format.js +63 -19
- package/dist/esm/excel/escaped-text.d.ts +9 -0
- package/dist/esm/excel/escaped-text.js +20 -0
- package/dist/esm/excel/form-control-parser.js +1 -0
- package/dist/esm/excel/formula/dates.js +1 -1
- package/dist/esm/excel/header-footer.d.ts +9 -1
- package/dist/esm/excel/header-footer.js +162 -14
- package/dist/esm/excel/index.d.ts +1 -1
- package/dist/esm/excel/index.js +2 -2
- package/dist/esm/excel/pivot-table-parser.js +1 -0
- package/dist/esm/excel/print-model.d.ts +75 -0
- package/dist/esm/excel/print-model.js +899 -77
- package/dist/esm/excel/printer-settings.d.ts +16 -0
- package/dist/esm/excel/printer-settings.js +36 -0
- package/dist/esm/excel/rich-value.d.ts +13 -0
- package/dist/esm/excel/rich-value.js +120 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -7
- package/dist/esm/excel/sheet-drawing.d.ts +11 -0
- package/dist/esm/excel/sheet-drawing.js +26 -3
- package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
- package/dist/esm/excel/sheet-shape-parser.js +461 -32
- package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
- package/dist/esm/excel/sheet-to-flow.js +553 -36
- package/dist/esm/excel/slicer-parser.js +1 -0
- package/dist/esm/excel/styles-parser.d.ts +30 -2
- package/dist/esm/excel/styles-parser.js +177 -40
- package/dist/esm/excel/table-parser.js +1 -0
- package/dist/esm/excel/vml-drawing.d.ts +68 -0
- package/dist/esm/excel/vml-drawing.js +158 -0
- package/dist/esm/excel/workbook-parser.d.ts +5 -0
- package/dist/esm/excel/workbook-parser.js +5 -1
- package/dist/esm/excel/worksheet-parser.d.ts +2 -1
- package/dist/esm/excel/worksheet-parser.js +150 -61
- package/dist/esm/excel/xlsx-reader.js +242 -11
- package/dist/esm/excel/xlsx-to-pdf.js +12 -1
- package/dist/esm/html/html-writer.js +29 -6
- package/dist/esm/layout/page-doc.d.ts +87 -1
- package/dist/esm/layout/styled-layout.d.ts +46 -2
- package/dist/esm/layout/styled-layout.js +2345 -274
- package/dist/esm/pdf/cid-font.js +20 -9
- package/dist/esm/pdf/objects.d.ts +13 -0
- package/dist/esm/pdf/objects.js +18 -1
- package/dist/esm/pdf/shading.js +76 -6
- package/dist/esm/pdf/styled-page-emitter.js +355 -49
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf/vector-graphics.d.ts +31 -2
- package/dist/esm/pdf/vector-graphics.js +103 -3
- package/dist/esm/pptx/pptx-reader.js +2 -0
- package/dist/esm/pptx/slide-parser.d.ts +21 -4
- package/dist/esm/pptx/slide-parser.js +98 -24
- package/dist/esm/svg/svg-writer.js +23 -4
- package/dist/esm/word/document-parser.d.ts +96 -7
- package/dist/esm/word/document-parser.js +538 -116
- package/dist/esm/word/docx-reader.js +135 -43
- package/dist/esm/word/docx-writer.js +6 -1
- package/dist/esm/word/drawing-parser.d.ts +86 -5
- package/dist/esm/word/drawing-parser.js +1590 -54
- package/dist/esm/word/index.d.ts +4 -4
- package/dist/esm/word/numbering-parser.d.ts +2 -1
- package/dist/esm/word/numbering-parser.js +108 -21
- package/dist/esm/word/paragraph-properties.d.ts +11 -1
- package/dist/esm/word/paragraph-properties.js +219 -10
- package/dist/esm/word/run-properties.js +45 -1
- package/dist/esm/word/settings-parser.d.ts +24 -0
- package/dist/esm/word/settings-parser.js +32 -5
- package/dist/esm/word/shading.d.ts +11 -0
- package/dist/esm/word/shading.js +64 -0
- package/dist/esm/word/styles-parser.js +9 -2
- package/dist/esm/word/table-parser.d.ts +9 -1
- package/dist/esm/word/table-parser.js +182 -30
- package/package.json +7 -3
- package/dist/esm/excel/number-format.js +0 -476
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Alignment, FontFamilyMap, NumberingReference, UnderlineStyle, VerticalAlign } from '../document-model/index.js';
|
|
1
|
+
import { Alignment, CellBorders, CellShading, FontFamilyMap, FrameProperties, NumberingReference, RunProperties, TabStop, UnderlineStyle, VerticalAlign } from '../document-model/index.js';
|
|
2
2
|
import { Pt } from '../ir/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* A run's fully-resolved properties: every field required because the cascade
|
|
@@ -9,7 +9,13 @@ export interface ResolvedRunProperties {
|
|
|
9
9
|
readonly bold: boolean;
|
|
10
10
|
readonly italic: boolean;
|
|
11
11
|
readonly underline: UnderlineStyle;
|
|
12
|
+
/** §17.3.2.40 `w:u @w:color` — the underline's own colour; absent means the text's. */
|
|
13
|
+
readonly underlineColorHex?: string;
|
|
12
14
|
readonly strike: boolean;
|
|
15
|
+
/** §17.3.2.5 `w:caps` — displayed in capitals. */
|
|
16
|
+
readonly caps: boolean;
|
|
17
|
+
/** §17.3.2.33 `w:smallCaps` — displayed in capitals, the lower case smaller. */
|
|
18
|
+
readonly smallCaps: boolean;
|
|
13
19
|
readonly fontSizePt: Pt;
|
|
14
20
|
readonly colorHex: string;
|
|
15
21
|
readonly fontFamily: FontFamilyMap;
|
|
@@ -20,6 +26,10 @@ export interface ResolvedRunProperties {
|
|
|
20
26
|
* specifies one; consumed by the tagged-PDF per-element `/Lang`, never layout.
|
|
21
27
|
*/
|
|
22
28
|
readonly lang?: string;
|
|
29
|
+
/** §17.3.2.32 — the background painted behind the run's glyphs. */
|
|
30
|
+
readonly shadingColorHex?: string;
|
|
31
|
+
/** §17.3.2.35 — extra space between the run's characters, in points. */
|
|
32
|
+
readonly letterSpacingPt?: Pt;
|
|
23
33
|
}
|
|
24
34
|
/**
|
|
25
35
|
* A paragraph's fully-resolved properties: every field required, the cascade
|
|
@@ -35,6 +45,14 @@ export interface ResolvedParagraphProperties {
|
|
|
35
45
|
readonly indentRight: Pt;
|
|
36
46
|
readonly indentFirstLine: Pt;
|
|
37
47
|
readonly pageBreakBefore: boolean;
|
|
48
|
+
/** §17.3.1.9 — drop the space between this paragraph and a same-styled neighbour. */
|
|
49
|
+
readonly contextualSpacing: boolean;
|
|
50
|
+
/** §17.3.1.37 — the paragraph's tab stops, in ascending position order. */
|
|
51
|
+
readonly tabs: ReadonlyArray<TabStop>;
|
|
52
|
+
/** §17.3.1.24 `w:pBdr` — rules drawn around the paragraph. */
|
|
53
|
+
readonly borders?: CellBorders;
|
|
54
|
+
/** §17.3.1.31 `w:pPr/w:shd` — the paragraph's own background fill. */
|
|
55
|
+
readonly shading?: CellShading;
|
|
38
56
|
readonly bidi: boolean;
|
|
39
57
|
/**
|
|
40
58
|
* ECMA-376 §17.3.1.20 — resolved outline level (0–8). Undefined = body text.
|
|
@@ -46,11 +64,34 @@ export interface ResolvedParagraphProperties {
|
|
|
46
64
|
* detection when a style lacks an explicit outline level.
|
|
47
65
|
*/
|
|
48
66
|
readonly styleId?: string;
|
|
67
|
+
/**
|
|
68
|
+
* §17.3.1.29 `w:pPr/w:rPr` — the formatting of the paragraph MARK. An empty
|
|
69
|
+
* paragraph is as tall as its mark, so dropping this on resolution collapsed
|
|
70
|
+
* a 36pt empty cell to a line of body text (conditionalstyles-tbllook.docx).
|
|
71
|
+
*/
|
|
72
|
+
readonly runProperties?: RunProperties;
|
|
73
|
+
/** §17.3.1.11 — the paragraph floats as a text frame. */
|
|
74
|
+
readonly frame?: FrameProperties;
|
|
75
|
+
/** §17.3.1.41 — which way the paragraph's lines run (`lrTb` is not recorded). */
|
|
76
|
+
readonly textDirection?: 'btLr' | 'tbRl';
|
|
77
|
+
/** §17.3.1.32 — whether these lines stand on the section's grid. Absent ⇒ they do. */
|
|
78
|
+
readonly snapToGrid?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* §17.6.17 — the paragraph mark carries the section break, so a paragraph
|
|
81
|
+
* with no content of its own prints nothing at all.
|
|
82
|
+
*/
|
|
83
|
+
readonly sectionBreak?: boolean;
|
|
49
84
|
/**
|
|
50
85
|
* §17.9 list reference, carried through for tagged-PDF list structure
|
|
51
86
|
* (L/LI nesting) — markers themselves are materialized by `applyNumbering`.
|
|
52
87
|
*/
|
|
53
88
|
readonly numbering?: NumberingReference;
|
|
89
|
+
/**
|
|
90
|
+
* §17.6.5 — the line pitch of the section's document grid, in points. Not a
|
|
91
|
+
* property of the paragraph at all: the section it falls in owns it, and the
|
|
92
|
+
* layout stamps it here so a line knows the grid it stands on.
|
|
93
|
+
*/
|
|
94
|
+
readonly gridLinePitchPt?: Pt;
|
|
54
95
|
}
|
|
55
96
|
/** Word's empty-document run defaults (used when `docDefaults` is absent). */
|
|
56
97
|
export declare const DEFAULT_RESOLVED_RUN: ResolvedRunProperties;
|
|
@@ -6,6 +6,8 @@ var DEFAULT_RESOLVED_RUN = {
|
|
|
6
6
|
italic: false,
|
|
7
7
|
underline: "none",
|
|
8
8
|
strike: false,
|
|
9
|
+
caps: false,
|
|
10
|
+
smallCaps: false,
|
|
9
11
|
fontSizePt: halfPtToPt(22),
|
|
10
12
|
colorHex: "000000",
|
|
11
13
|
fontFamily: {},
|
|
@@ -23,6 +25,8 @@ var DEFAULT_RESOLVED_PARAGRAPH = {
|
|
|
23
25
|
indentRight: twipsToPt(0),
|
|
24
26
|
indentFirstLine: twipsToPt(0),
|
|
25
27
|
pageBreakBefore: false,
|
|
28
|
+
contextualSpacing: false,
|
|
29
|
+
tabs: [],
|
|
26
30
|
bidi: false
|
|
27
31
|
};
|
|
28
32
|
//#endregion
|
|
@@ -60,6 +60,23 @@ export interface ShapeGradient {
|
|
|
60
60
|
readonly kind: 'linear' | 'radial';
|
|
61
61
|
/** Linear only: gradient direction in degrees clockwise (0 = left→right). */
|
|
62
62
|
readonly angle?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Radial only: where the first stop sits inside the box, as fractions of it
|
|
65
|
+
* (`{x: 0, y: 0}` is the top-left corner). Absent means the middle, which is
|
|
66
|
+
* where DrawingML's own `a:path` puts it; VML aims it at a corner instead
|
|
67
|
+
* (`@focus` with an `@angle`), and fill.docx's page sweep starts in one.
|
|
68
|
+
*/
|
|
69
|
+
readonly center?: {
|
|
70
|
+
readonly x: number;
|
|
71
|
+
readonly y: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Radial only: the shape of the sweep's contours. Circles by default, which
|
|
75
|
+
* is what DrawingML's `a:path path="circle"` draws; VML's `gradientRadial`
|
|
76
|
+
* grows RECTANGLES out to the box instead, and so does the page background
|
|
77
|
+
* of fill.docx.
|
|
78
|
+
*/
|
|
79
|
+
readonly sweep?: 'circle' | 'rect';
|
|
63
80
|
readonly stops: ReadonlyArray<GradientStop>;
|
|
64
81
|
}
|
|
65
82
|
/**
|
|
@@ -82,6 +99,19 @@ export interface VectorShape {
|
|
|
82
99
|
readonly fillGradient?: ShapeGradient;
|
|
83
100
|
/** Stroke description. Omitted = no stroke. */
|
|
84
101
|
readonly stroke?: StrokeStyle;
|
|
102
|
+
/**
|
|
103
|
+
* §20.1.8.40 — a drop shadow drawn UNDER this shape: the same paths, offset
|
|
104
|
+
* by `(dxPt, dyPt)` in the page's own frame (y down), filled in `colorHex` at
|
|
105
|
+
* `alpha`. `blurPt` is the softness the source asked for; writers that cannot
|
|
106
|
+
* blur draw a hard edge.
|
|
107
|
+
*/
|
|
108
|
+
readonly shadow?: {
|
|
109
|
+
readonly dxPt: number;
|
|
110
|
+
readonly dyPt: number;
|
|
111
|
+
readonly blurPt: number;
|
|
112
|
+
readonly colorHex: string;
|
|
113
|
+
readonly alpha: number;
|
|
114
|
+
};
|
|
85
115
|
/**
|
|
86
116
|
* CTM applied via `cm` (§8.3.4): maps the local frame onto the page. The
|
|
87
117
|
* 6-tuple is `[a b c d e f]` of the matrix `[[a b 0][c d 0][e f 1]]`.
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { BodyElement, TableProperties, TableRow } from '../core/document-model/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap a grid in its printed row and column headings (§18.3.1.70 `headings`):
|
|
4
|
+
* the column letters across the top, the row numbers down the left, each in a
|
|
5
|
+
* thin box, the way the sheet looks on screen. A banded sheet gets its own
|
|
6
|
+
* letters per band, which is what both references print.
|
|
7
|
+
*
|
|
8
|
+
* @param rows The band's rows.
|
|
9
|
+
* @param widths The band's column widths in twips.
|
|
10
|
+
* @param colStart The absolute index of the band's first column.
|
|
11
|
+
* @param rowNumbers The absolute 1-based row number of each row.
|
|
12
|
+
* @returns The rows and widths with the heading band added.
|
|
13
|
+
*/
|
|
14
|
+
export declare function withHeadingBand(rows: ReadonlyArray<TableRow>, widths: ReadonlyArray<number>, colStart: number, rowNumbers: ReadonlyArray<number>): {
|
|
15
|
+
rows: Array<TableRow>;
|
|
16
|
+
widths: Array<number>;
|
|
17
|
+
};
|
|
2
18
|
/** A contiguous run of columns that prints together as one band (E-SHEET SE1). */
|
|
3
19
|
export interface ColumnBand {
|
|
4
20
|
/** First local column index (inclusive). */
|
|
@@ -31,6 +47,15 @@ export declare function computeColumnBands(columnWidths: ReadonlyArray<number>,
|
|
|
31
47
|
* @param columnWidths Per-column widths in twips, by local index.
|
|
32
48
|
* @param bands The bands from {@link computeColumnBands}.
|
|
33
49
|
* @param properties The shared table properties applied to every band.
|
|
50
|
+
* @param titleRowIndex The print-title row, or -1.
|
|
51
|
+
* @param drawingReachTwips How far right the sheet's drawings reach (0 if none).
|
|
52
|
+
* @param headings §18.3.1.70 — the printed row/column headings, when the sheet
|
|
53
|
+
* asks for them: the absolute index of the grid's first column and the sheet
|
|
54
|
+
* row number of each emitted row. Each band gets ITS OWN letters, which is
|
|
55
|
+
* what both references print across the top of a continuation page.
|
|
34
56
|
* @returns One table body element per band, in band order.
|
|
35
57
|
*/
|
|
36
|
-
export declare function bandedTables(rows: ReadonlyArray<TableRow>, columnWidths: ReadonlyArray<number>, bands: ReadonlyArray<ColumnBand>, properties: TableProperties
|
|
58
|
+
export declare function bandedTables(rows: ReadonlyArray<TableRow>, columnWidths: ReadonlyArray<number>, bands: ReadonlyArray<ColumnBand>, properties: TableProperties, titleRowIndex?: number, drawingReachTwips?: number, headings?: {
|
|
59
|
+
readonly colStart: number;
|
|
60
|
+
readonly rowNumbers: ReadonlyArray<number>;
|
|
61
|
+
}): Array<BodyElement>;
|
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
import { twipsToPt } from "../core/ir/units.js";
|
|
2
2
|
//#region src/excel/column-bands.ts
|
|
3
|
+
/** §18.3.1.70 — the width of the row-number column, in twips. */
|
|
4
|
+
var HEADING_COL_TWIPS = 460;
|
|
5
|
+
/** The A1-style letters of an absolute column index (0 → A, 26 → AA). */
|
|
6
|
+
function columnLetters(index) {
|
|
7
|
+
let n = index;
|
|
8
|
+
let out = "";
|
|
9
|
+
do {
|
|
10
|
+
out = String.fromCharCode(65 + n % 26) + out;
|
|
11
|
+
n = Math.floor(n / 26) - 1;
|
|
12
|
+
} while (n >= 0);
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Wrap a grid in its printed row and column headings (§18.3.1.70 `headings`):
|
|
17
|
+
* the column letters across the top, the row numbers down the left, each in a
|
|
18
|
+
* thin box, the way the sheet looks on screen. A banded sheet gets its own
|
|
19
|
+
* letters per band, which is what both references print.
|
|
20
|
+
*
|
|
21
|
+
* @param rows The band's rows.
|
|
22
|
+
* @param widths The band's column widths in twips.
|
|
23
|
+
* @param colStart The absolute index of the band's first column.
|
|
24
|
+
* @param rowNumbers The absolute 1-based row number of each row.
|
|
25
|
+
* @returns The rows and widths with the heading band added.
|
|
26
|
+
*/
|
|
27
|
+
function withHeadingBand(rows, widths, colStart, rowNumbers) {
|
|
28
|
+
const border = {
|
|
29
|
+
style: "single",
|
|
30
|
+
width: .5,
|
|
31
|
+
colorHex: "808080"
|
|
32
|
+
};
|
|
33
|
+
const borders = {
|
|
34
|
+
top: border,
|
|
35
|
+
bottom: border,
|
|
36
|
+
left: border,
|
|
37
|
+
right: border
|
|
38
|
+
};
|
|
39
|
+
const head = (text) => ({
|
|
40
|
+
properties: {
|
|
41
|
+
borders,
|
|
42
|
+
verticalAlign: "center"
|
|
43
|
+
},
|
|
44
|
+
content: [{
|
|
45
|
+
kind: "paragraph",
|
|
46
|
+
paragraph: {
|
|
47
|
+
properties: { alignment: "center" },
|
|
48
|
+
runs: [{
|
|
49
|
+
text,
|
|
50
|
+
properties: {}
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
}]
|
|
54
|
+
});
|
|
55
|
+
const letters = [head("")];
|
|
56
|
+
for (let i = 0; i < widths.length; i++) letters.push(head(columnLetters(colStart + i)));
|
|
57
|
+
const out = [{
|
|
58
|
+
properties: { isHeader: true },
|
|
59
|
+
cells: letters
|
|
60
|
+
}];
|
|
61
|
+
rows.forEach((row, i) => {
|
|
62
|
+
out.push({
|
|
63
|
+
properties: row.properties,
|
|
64
|
+
cells: [head(String(rowNumbers[i] ?? i + 1)), ...row.cells]
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
rows: out,
|
|
69
|
+
widths: [HEADING_COL_TWIPS, ...widths]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
3
72
|
/**
|
|
4
73
|
* Greedy fill: accumulate column widths until the next column would overflow the
|
|
5
74
|
* content width, then start a new band. A manual column break (`colBreaks` holds
|
|
@@ -50,27 +119,63 @@ function computeColumnBands(columnWidths, contentWidthTwips, colBreaks) {
|
|
|
50
119
|
* @param columnWidths Per-column widths in twips, by local index.
|
|
51
120
|
* @param bands The bands from {@link computeColumnBands}.
|
|
52
121
|
* @param properties The shared table properties applied to every band.
|
|
122
|
+
* @param titleRowIndex The print-title row, or -1.
|
|
123
|
+
* @param drawingReachTwips How far right the sheet's drawings reach (0 if none).
|
|
124
|
+
* @param headings §18.3.1.70 — the printed row/column headings, when the sheet
|
|
125
|
+
* asks for them: the absolute index of the grid's first column and the sheet
|
|
126
|
+
* row number of each emitted row. Each band gets ITS OWN letters, which is
|
|
127
|
+
* what both references print across the top of a continuation page.
|
|
53
128
|
* @returns One table body element per band, in band order.
|
|
54
129
|
*/
|
|
55
|
-
function bandedTables(rows, columnWidths, bands, properties) {
|
|
56
|
-
return bands.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
130
|
+
function bandedTables(rows, columnWidths, bands, properties, titleRowIndex = -1, drawingReachTwips = 0, headings) {
|
|
131
|
+
return bands.flatMap((band, bandIndex) => {
|
|
132
|
+
const bandLeft = columnWidths.slice(0, band.start).reduce((sum, w) => sum + w, 0);
|
|
133
|
+
const bandTwips = columnWidths.slice(band.start, band.end + 1);
|
|
134
|
+
const grid = bandTwips.map((w) => twipsToPt(w));
|
|
135
|
+
const bandRowNumbers = [...headings?.rowNumbers ?? []];
|
|
136
|
+
const bandRows = rows.map((row, rowIndex) => {
|
|
137
|
+
const cells = sliceRowCells(row.cells, band);
|
|
138
|
+
return {
|
|
139
|
+
properties: bandIndex > 0 && rowIndex === 0 ? {
|
|
140
|
+
...row.properties,
|
|
141
|
+
pageBreakBefore: true
|
|
142
|
+
} : row.properties,
|
|
143
|
+
cells
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
if (bandIndex > 0 && !(bandLeft < drawingReachTwips) && !bandRows.some(rowDrawsSomething)) return [];
|
|
147
|
+
while (bandRows.length > 1 && !rowDrawsSomething(bandRows[bandRows.length - 1])) {
|
|
148
|
+
bandRows.pop();
|
|
149
|
+
bandRowNumbers.pop();
|
|
150
|
+
}
|
|
151
|
+
return (titleRowIndex > 0 && titleRowIndex < bandRows.length ? [{
|
|
152
|
+
rows: bandRows.slice(0, titleRowIndex),
|
|
153
|
+
numbers: bandRowNumbers.slice(0, titleRowIndex)
|
|
154
|
+
}, {
|
|
155
|
+
rows: bandRows.slice(titleRowIndex),
|
|
156
|
+
numbers: bandRowNumbers.slice(titleRowIndex)
|
|
157
|
+
}] : [{
|
|
158
|
+
rows: bandRows,
|
|
159
|
+
numbers: bandRowNumbers
|
|
160
|
+
}]).map((part) => {
|
|
161
|
+
const headed = headings ? withHeadingBand(part.rows, bandTwips, headings.colStart + band.start, part.numbers) : void 0;
|
|
162
|
+
return {
|
|
163
|
+
kind: "table",
|
|
164
|
+
table: {
|
|
165
|
+
properties,
|
|
166
|
+
grid: headed ? headed.widths.map((w) => twipsToPt(w)) : grid,
|
|
167
|
+
rows: headed ? leadWithBandBreak(headed.rows) : part.rows
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/** Whether a row would put anything on the page — content, fill, border or mark. */
|
|
174
|
+
function rowDrawsSomething(row) {
|
|
175
|
+
return row.cells.some((cell) => {
|
|
176
|
+
if (cell.content.length > 0) return true;
|
|
177
|
+
const p = cell.properties;
|
|
178
|
+
return p.shading !== void 0 || p.borders !== void 0 || p.dataBar !== void 0 || p.icon !== void 0 || p.sparkline !== void 0 || p.dropdown === true;
|
|
74
179
|
});
|
|
75
180
|
}
|
|
76
181
|
function sliceRowCells(cells, band) {
|
|
@@ -103,5 +208,36 @@ function blankCell(span) {
|
|
|
103
208
|
content: []
|
|
104
209
|
};
|
|
105
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Move a `pageBreakBefore` that landed on the second row up to the first.
|
|
213
|
+
*
|
|
214
|
+
* The band's page break is set on the band's own leading row, before the
|
|
215
|
+
* heading band prepends the column-letter row in front of it. Left there, the
|
|
216
|
+
* letters print on one page and the band they label starts on the next.
|
|
217
|
+
*
|
|
218
|
+
* @param rows The band's rows, letters row first.
|
|
219
|
+
* @returns The same rows with the break carried by whichever leads them.
|
|
220
|
+
*/
|
|
221
|
+
function leadWithBandBreak(rows) {
|
|
222
|
+
const second = rows[1];
|
|
223
|
+
if (!second?.properties.pageBreakBefore) return rows;
|
|
224
|
+
const [first, ...rest] = rows;
|
|
225
|
+
if (!first) return rows;
|
|
226
|
+
const { pageBreakBefore: _dropped, ...keep } = second.properties;
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
...first,
|
|
230
|
+
properties: {
|
|
231
|
+
...first.properties,
|
|
232
|
+
pageBreakBefore: true
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
...second,
|
|
237
|
+
properties: keep
|
|
238
|
+
},
|
|
239
|
+
...rest.slice(1)
|
|
240
|
+
];
|
|
241
|
+
}
|
|
106
242
|
//#endregion
|
|
107
|
-
export { bandedTables, computeColumnBands };
|
|
243
|
+
export { bandedTables, computeColumnBands, withHeadingBand };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { resolveInternalEntities } from "../core/opc/xml-entities.js";
|
|
1
2
|
import { XMLParser } from "fast-xml-parser";
|
|
2
3
|
//#region src/excel/comments-parser.ts
|
|
3
4
|
var decoder = new TextDecoder("utf-8");
|
|
4
5
|
var parser = new XMLParser({
|
|
6
|
+
htmlEntities: true,
|
|
5
7
|
ignoreAttributes: false,
|
|
6
8
|
attributeNamePrefix: "@_",
|
|
7
9
|
textNodeName: "#text",
|
|
@@ -17,7 +19,7 @@ var parser = new XMLParser({
|
|
|
17
19
|
* of the text — stripped here so the resolved author is not shown twice.
|
|
18
20
|
*/
|
|
19
21
|
function parseLegacyComments(data) {
|
|
20
|
-
const root = asObject(parser.parse(decoder.decode(data))["comments"]);
|
|
22
|
+
const root = asObject(parser.parse(resolveInternalEntities(decoder.decode(data)))["comments"]);
|
|
21
23
|
if (!root) return [];
|
|
22
24
|
const authors = toArray(asObject(root["authors"])?.["author"]).map((a) => textOf(a));
|
|
23
25
|
const out = [];
|
|
@@ -44,7 +46,7 @@ function parseLegacyComments(data) {
|
|
|
44
46
|
* the authors of threaded comments.
|
|
45
47
|
*/
|
|
46
48
|
function parsePersons(data) {
|
|
47
|
-
const root = asObject(parser.parse(decoder.decode(data))["personList"]);
|
|
49
|
+
const root = asObject(parser.parse(resolveInternalEntities(decoder.decode(data)))["personList"]);
|
|
48
50
|
const map = /* @__PURE__ */ new Map();
|
|
49
51
|
if (!root) return map;
|
|
50
52
|
for (const p of toArray(root["person"])) {
|
|
@@ -65,7 +67,7 @@ function parsePersons(data) {
|
|
|
65
67
|
* @param persons The `id → displayName` map from {@link parsePersons}.
|
|
66
68
|
*/
|
|
67
69
|
function parseThreadedComments(data, persons) {
|
|
68
|
-
const root = asObject(parser.parse(decoder.decode(data))["ThreadedComments"]);
|
|
70
|
+
const root = asObject(parser.parse(resolveInternalEntities(decoder.decode(data)))["ThreadedComments"]);
|
|
69
71
|
if (!root) return [];
|
|
70
72
|
const out = [];
|
|
71
73
|
for (const c of toArray(root["threadedComment"])) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CellIcon } from '../core/document-model/index.js';
|
|
2
|
-
import { ConditionalFormat, DefinedName, WorksheetCell, XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
2
|
+
import { ConditionalFormat, DefinedName, WorksheetCell, XlsxBorder, XlsxStyles } from '../core/spreadsheet-model/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* A resolved per-cell override the print model layers over a cell's base format: a
|
|
5
5
|
* solid highlight fill, font tweaks, an in-cell data bar (fraction of the cell
|
|
@@ -7,16 +7,32 @@ import { ConditionalFormat, DefinedName, WorksheetCell, XlsxStyles } from '../co
|
|
|
7
7
|
*/
|
|
8
8
|
export interface CfOverride {
|
|
9
9
|
readonly fillHex?: string;
|
|
10
|
+
/**
|
|
11
|
+
* §18.8.9 — the edges the rule's dxf declares. A rule may format with nothing
|
|
12
|
+
* else, and dropping them made such a rule invisible.
|
|
13
|
+
*/
|
|
14
|
+
readonly border?: XlsxBorder;
|
|
15
|
+
/** §18.8.9 — the format the rule renders the cell's value in. */
|
|
16
|
+
readonly numberFormat?: string;
|
|
10
17
|
readonly fontColorHex?: string;
|
|
11
18
|
readonly bold?: boolean;
|
|
12
19
|
readonly italic?: boolean;
|
|
20
|
+
/** §18.8.37 — the rule strikes the cell's text through. */
|
|
21
|
+
readonly strike?: boolean;
|
|
13
22
|
/** An in-cell data bar: its fraction of the cell width, colour, and optional left offset. */
|
|
14
23
|
readonly dataBar?: {
|
|
15
24
|
readonly fraction: number;
|
|
16
25
|
readonly colorHex: string;
|
|
17
26
|
readonly startFraction?: number;
|
|
27
|
+
readonly negative?: boolean;
|
|
18
28
|
};
|
|
19
29
|
readonly icon?: CellIcon;
|
|
30
|
+
/**
|
|
31
|
+
* §18.3.1.28 `<dataBar showValue="0">` — the cell shows its BAR and not its
|
|
32
|
+
* number. Excel's "Show Bar Only": the figure would otherwise sit on top of
|
|
33
|
+
* its own gauge.
|
|
34
|
+
*/
|
|
35
|
+
readonly hideValue?: boolean;
|
|
20
36
|
}
|
|
21
37
|
/**
|
|
22
38
|
* A per-cell lookup returning the {@link CfOverride} for the cell at `(row, col)`,
|
|
@@ -25,7 +41,14 @@ export interface CfOverride {
|
|
|
25
41
|
* text tests and to key duplicate / unique comparisons for non-numeric cells
|
|
26
42
|
* (empty/undefined for a blank cell).
|
|
27
43
|
*/
|
|
28
|
-
export type CellConditionalFormatter = (row: number, col: number, numericValue: number | undefined, text: string | undefined
|
|
44
|
+
export type CellConditionalFormatter = (row: number, col: number, numericValue: number | undefined, text: string | undefined,
|
|
45
|
+
/**
|
|
46
|
+
* The cell holds nothing. It still COMPARES as zero — Excel and Calc both
|
|
47
|
+
* colour an empty cell for a rule written `cellIs equal 0`, which is how
|
|
48
|
+
* 48539.xlsx paints its whole Pass/Fail column red — but it is not a value
|
|
49
|
+
* that can repeat, so duplicate/uniqueValues must pass it over.
|
|
50
|
+
*/
|
|
51
|
+
blank?: boolean) => CfOverride | undefined;
|
|
29
52
|
/**
|
|
30
53
|
* Compile a sheet's `<conditionalFormatting>` rules + the workbook's `<dxfs>` into
|
|
31
54
|
* a per-cell {@link CellConditionalFormatter}. Returns undefined when the sheet has
|
|
@@ -89,22 +89,24 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
|
|
|
89
89
|
definedNames: definedNames ?? []
|
|
90
90
|
} : void 0;
|
|
91
91
|
const ctx = hasExpr ? buildEvalContext(cells, resolveText, nowSerial, date1904, cross) : void 0;
|
|
92
|
-
return (row, col, value, text) => {
|
|
92
|
+
return (row, col, value, text, blank) => {
|
|
93
93
|
if (!hasExpr && value === void 0 && (text === void 0 || text.length === 0)) return;
|
|
94
|
-
const dupKey = dupKeyOf(value, text);
|
|
94
|
+
const dupKey = blank === true ? void 0 : dupKeyOf(value, text);
|
|
95
95
|
let textFmt;
|
|
96
96
|
let textClaimed = false;
|
|
97
97
|
let bar;
|
|
98
|
+
let hideValue = false;
|
|
98
99
|
let icon;
|
|
99
|
-
const claim = (
|
|
100
|
-
textFmt = dxfToOverride(dxfs[dxfId]);
|
|
100
|
+
const claim = (r) => {
|
|
101
|
+
textFmt = dxfToOverride(r.dxf ?? dxfs[r.dxfId]);
|
|
101
102
|
textClaimed = true;
|
|
102
103
|
};
|
|
103
104
|
for (const { ranges, rule, scale, bar: resolved, iconThresholds, threshold, dupKeys, compiled, origin } of flat) {
|
|
104
105
|
if (!coversCell(ranges, row, col)) continue;
|
|
105
106
|
switch (rule.type) {
|
|
106
107
|
case "cellIs":
|
|
107
|
-
if (
|
|
108
|
+
if (textClaimed) break;
|
|
109
|
+
if (value !== void 0 && Number.isFinite(Number(operandText(rule.formulas[0]))) ? cellIsMatches(rule.operator, value, rule.formulas) : text !== void 0 && cellIsMatchesText(rule.operator, text, rule.formulas)) claim(rule);
|
|
108
110
|
break;
|
|
109
111
|
case "colorScale":
|
|
110
112
|
if (!textClaimed && value !== void 0 && scale) {
|
|
@@ -113,7 +115,10 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
|
|
|
113
115
|
}
|
|
114
116
|
break;
|
|
115
117
|
case "dataBar":
|
|
116
|
-
if (!bar && value !== void 0 && resolved)
|
|
118
|
+
if (!bar && value !== void 0 && resolved) {
|
|
119
|
+
bar = dataBarBar(resolved, value);
|
|
120
|
+
if (rule.showValue === false) hideValue = true;
|
|
121
|
+
}
|
|
117
122
|
break;
|
|
118
123
|
case "iconSet":
|
|
119
124
|
if (!icon && value !== void 0 && iconThresholds) {
|
|
@@ -123,17 +128,17 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
|
|
|
123
128
|
break;
|
|
124
129
|
case "top10":
|
|
125
130
|
case "aboveAverage":
|
|
126
|
-
if (!textClaimed && value !== void 0 && threshold && thresholdMatches(threshold, value)) claim(rule
|
|
131
|
+
if (!textClaimed && value !== void 0 && threshold && thresholdMatches(threshold, value)) claim(rule);
|
|
127
132
|
break;
|
|
128
133
|
case "duplicateValues":
|
|
129
134
|
case "uniqueValues":
|
|
130
|
-
if (!textClaimed && dupKey !== void 0 && dupKeys?.has(dupKey)) claim(rule
|
|
135
|
+
if (!textClaimed && dupKey !== void 0 && dupKeys?.has(dupKey)) claim(rule);
|
|
131
136
|
break;
|
|
132
137
|
case "containsText":
|
|
133
138
|
case "notContainsText":
|
|
134
139
|
case "beginsWith":
|
|
135
140
|
case "endsWith":
|
|
136
|
-
if (!textClaimed && text !== void 0 && textRuleMatches(rule, text)) claim(rule
|
|
141
|
+
if (!textClaimed && text !== void 0 && textRuleMatches(rule, text)) claim(rule);
|
|
137
142
|
break;
|
|
138
143
|
case "expression":
|
|
139
144
|
if (!textClaimed && compiled && ctx && origin) {
|
|
@@ -142,11 +147,11 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
|
|
|
142
147
|
dCol: col - origin.col,
|
|
143
148
|
curRow: row,
|
|
144
149
|
curCol: col
|
|
145
|
-
})) claim(rule
|
|
150
|
+
})) claim(rule);
|
|
146
151
|
}
|
|
147
152
|
break;
|
|
148
153
|
case "timePeriod":
|
|
149
|
-
if (!textClaimed && value !== void 0 && nowSerial !== void 0 && timePeriodMatches(rule.timePeriod, value, nowSerial, date1904)) claim(rule
|
|
154
|
+
if (!textClaimed && value !== void 0 && nowSerial !== void 0 && timePeriodMatches(rule.timePeriod, value, nowSerial, date1904)) claim(rule);
|
|
150
155
|
break;
|
|
151
156
|
}
|
|
152
157
|
}
|
|
@@ -154,7 +159,8 @@ function buildConditionalFormatter(conditionalFormats, styles, cells, resolveTex
|
|
|
154
159
|
return {
|
|
155
160
|
...textFmt ?? {},
|
|
156
161
|
...bar ? { dataBar: bar } : {},
|
|
157
|
-
...icon ? { icon } : {}
|
|
162
|
+
...icon ? { icon } : {},
|
|
163
|
+
...hideValue ? { hideValue: true } : {}
|
|
158
164
|
};
|
|
159
165
|
};
|
|
160
166
|
}
|
|
@@ -277,6 +283,39 @@ function coversCell(ranges, row, col) {
|
|
|
277
283
|
for (const r of ranges) if (row >= r.startRow && row <= r.endRow && col >= r.startColumn && col <= r.endColumn) return true;
|
|
278
284
|
return false;
|
|
279
285
|
}
|
|
286
|
+
/** A `cellIs` operand: a bare number, or a quoted string literal. */
|
|
287
|
+
function operandText(raw) {
|
|
288
|
+
const t = (raw ?? "").trim();
|
|
289
|
+
const quoted = /^"(.*)"$/su.exec(t);
|
|
290
|
+
return quoted ? quoted[1] ?? "" : t;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* §18.3.1.11 — a `cellIs` rule compares the cell against one or two operands,
|
|
294
|
+
* and the comparison is not always arithmetic: `notEqual ""` over a sheet of
|
|
295
|
+
* words is how a document asks to format every cell that HOLDS something.
|
|
296
|
+
* Reading only numbers, that rule matched nothing, and
|
|
297
|
+
* tdf152581_bordercolorNotExportedToXLSX.xlsx lost the border and fill its two
|
|
298
|
+
* cells carry. Both sides numeric ⇒ compare numbers, as before; otherwise
|
|
299
|
+
* compare text, case-insensitively, the way Excel collates it.
|
|
300
|
+
*/
|
|
301
|
+
function cellIsMatchesText(operator, text, formulas) {
|
|
302
|
+
const a = operandText(formulas[0]).toLowerCase();
|
|
303
|
+
const v = text.toLowerCase();
|
|
304
|
+
switch (operator) {
|
|
305
|
+
case "lessThan": return v < a;
|
|
306
|
+
case "lessThanOrEqual": return v <= a;
|
|
307
|
+
case "equal": return v === a;
|
|
308
|
+
case "notEqual": return v !== a;
|
|
309
|
+
case "greaterThanOrEqual": return v >= a;
|
|
310
|
+
case "greaterThan": return v > a;
|
|
311
|
+
case "between":
|
|
312
|
+
case "notBetween": {
|
|
313
|
+
const b = operandText(formulas[1]).toLowerCase();
|
|
314
|
+
const inside = v >= (a <= b ? a : b) && v <= (a <= b ? b : a);
|
|
315
|
+
return operator === "between" ? inside : !inside;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
280
319
|
function cellIsMatches(operator, value, formulas) {
|
|
281
320
|
const a = Number(formulas[0]);
|
|
282
321
|
if (!Number.isFinite(a)) return false;
|
|
@@ -304,6 +343,9 @@ function dxfToOverride(dxf) {
|
|
|
304
343
|
if (dxf.font?.colorHex) out.fontColorHex = dxf.font.colorHex;
|
|
305
344
|
if (dxf.font?.bold !== void 0) out.bold = dxf.font.bold;
|
|
306
345
|
if (dxf.font?.italic !== void 0) out.italic = dxf.font.italic;
|
|
346
|
+
if (dxf.font?.strike !== void 0) out.strike = dxf.font.strike;
|
|
347
|
+
if (dxf.border) out.border = dxf.border;
|
|
348
|
+
if (dxf.numberFormat) out.numberFormat = dxf.numberFormat;
|
|
307
349
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
308
350
|
}
|
|
309
351
|
function collectRangeValues(cells, ranges) {
|
|
@@ -323,8 +365,8 @@ function resolveColorScale(rule, ranges, cells) {
|
|
|
323
365
|
const vmin = sorted[0];
|
|
324
366
|
const vmax = sorted[sorted.length - 1];
|
|
325
367
|
const thresholds = [];
|
|
326
|
-
for (const cfvo of rule.cfvos) {
|
|
327
|
-
const t = resolveCfvo(cfvo, vmin, vmax, sorted);
|
|
368
|
+
for (const [i, cfvo] of rule.cfvos.entries()) {
|
|
369
|
+
const t = resolveCfvo(cfvo, vmin, vmax, sorted, i === rule.cfvos.length - 1);
|
|
328
370
|
if (t === void 0) return void 0;
|
|
329
371
|
thresholds.push(t);
|
|
330
372
|
}
|
|
@@ -341,16 +383,16 @@ function resolveColorScale(rule, ranges, cells) {
|
|
|
341
383
|
colors
|
|
342
384
|
};
|
|
343
385
|
}
|
|
344
|
-
function resolveCfvo(cfvo, vmin, vmax, sorted) {
|
|
386
|
+
function resolveCfvo(cfvo, vmin, vmax, sorted, isLast = false) {
|
|
345
387
|
switch (cfvo.type) {
|
|
346
388
|
case "min":
|
|
347
389
|
case "autoMin": return vmin;
|
|
348
390
|
case "max":
|
|
349
391
|
case "autoMax": return vmax;
|
|
350
|
-
case "num":
|
|
392
|
+
case "num": return Number.isFinite(Number(cfvo.val)) ? Number(cfvo.val) : void 0;
|
|
351
393
|
case "formula": {
|
|
352
394
|
const n = Number(cfvo.val);
|
|
353
|
-
return Number.isFinite(n) ? n :
|
|
395
|
+
return Number.isFinite(n) ? n : isLast ? vmax : vmin;
|
|
354
396
|
}
|
|
355
397
|
case "percent": {
|
|
356
398
|
const p = Number(cfvo.val);
|
|
@@ -410,7 +452,7 @@ function resolveDataBar(rule, ranges, cells) {
|
|
|
410
452
|
const vmin = sorted[0];
|
|
411
453
|
const vmax = sorted[sorted.length - 1];
|
|
412
454
|
const lower = resolveCfvo(rule.cfvos[0], vmin, vmax, sorted);
|
|
413
|
-
const upper = resolveCfvo(rule.cfvos[1], vmin, vmax, sorted);
|
|
455
|
+
const upper = resolveCfvo(rule.cfvos[1], vmin, vmax, sorted, true);
|
|
414
456
|
if (lower === void 0 || upper === void 0) return void 0;
|
|
415
457
|
return {
|
|
416
458
|
lower,
|
|
@@ -433,7 +475,8 @@ function dataBarBar(bar, value) {
|
|
|
433
475
|
return {
|
|
434
476
|
fraction: w,
|
|
435
477
|
colorHex: bar.negativeColorHex,
|
|
436
|
-
startFraction: axis - w
|
|
478
|
+
startFraction: axis - w,
|
|
479
|
+
negative: true
|
|
437
480
|
};
|
|
438
481
|
}
|
|
439
482
|
return {
|
|
@@ -577,6 +620,7 @@ function resolveDupUnique(rule, ranges, cells, resolveText) {
|
|
|
577
620
|
}
|
|
578
621
|
function cellDupKey(cell, resolveText) {
|
|
579
622
|
if (cell.type === "n") {
|
|
623
|
+
if (cell.rawValue.length === 0) return void 0;
|
|
580
624
|
const n = Number(cell.rawValue);
|
|
581
625
|
return Number.isFinite(n) ? `n:${n}` : void 0;
|
|
582
626
|
}
|