reamkit 1.31.0 → 1.32.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 +10 -0
- package/dist/esm/core/bidi/index.d.ts +10 -0
- package/dist/esm/core/bidi/index.js +18 -2
- package/dist/esm/core/converter/facade.js +10 -5
- package/dist/esm/core/converter/project.js +1 -0
- package/dist/esm/core/converter/ream.js +17 -6
- package/dist/esm/core/converter/sheet-images.d.ts +17 -0
- package/dist/esm/core/converter/sheet-images.js +33 -0
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +112 -0
- package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
- package/dist/esm/core/drawingml/chart-geometry.js +198 -108
- package/dist/esm/core/drawingml/chart-parser.js +73 -2
- package/dist/esm/core/drawingml/colors.d.ts +6 -0
- package/dist/esm/core/drawingml/colors.js +23 -3
- package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
- package/dist/esm/core/drawingml/shape-render.js +4 -2
- package/dist/esm/core/font/measure.d.ts +9 -0
- package/dist/esm/core/font/measure.js +9 -1
- package/dist/esm/core/fonts/families.js +4 -2
- package/dist/esm/core/fonts/scripts.js +4 -2
- package/dist/esm/core/ir/flow.d.ts +6 -0
- package/dist/esm/core/ir/sheet.d.ts +43 -0
- package/dist/esm/core/number-format.js +16 -11
- package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
- package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
- package/dist/esm/core/style-cascade/resolver.js +4 -2
- package/dist/esm/excel/column-bands.d.ts +39 -6
- package/dist/esm/excel/column-bands.js +41 -17
- package/dist/esm/excel/comments-parser.js +19 -0
- package/dist/esm/excel/conditional-format.d.ts +7 -3
- package/dist/esm/excel/conditional-format.js +77 -39
- package/dist/esm/excel/formula/context.d.ts +18 -0
- package/dist/esm/excel/formula/eval.js +51 -0
- package/dist/esm/excel/formula/index.d.ts +2 -2
- package/dist/esm/excel/formula/lexer.d.ts +1 -1
- package/dist/esm/excel/formula/lexer.js +41 -0
- package/dist/esm/excel/formula/parser.d.ts +25 -0
- package/dist/esm/excel/formula/parser.js +95 -0
- package/dist/esm/excel/print-model.d.ts +38 -1
- package/dist/esm/excel/print-model.js +522 -80
- package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
- package/dist/esm/excel/shared-strings-parser.js +11 -1
- package/dist/esm/excel/sheet-drawing.d.ts +38 -2
- package/dist/esm/excel/sheet-drawing.js +47 -13
- package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
- package/dist/esm/excel/sheet-shape-parser.js +18 -6
- package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
- package/dist/esm/excel/sheet-to-flow.js +434 -47
- package/dist/esm/excel/styles-parser.d.ts +9 -0
- package/dist/esm/excel/styles-parser.js +80 -26
- package/dist/esm/excel/table-parser.js +5 -0
- package/dist/esm/excel/table-style-presets.d.ts +26 -0
- package/dist/esm/excel/table-style-presets.js +1555 -0
- package/dist/esm/excel/table-style.d.ts +13 -0
- package/dist/esm/excel/table-style.js +75 -0
- package/dist/esm/excel/tint.d.ts +10 -0
- package/dist/esm/excel/tint.js +75 -0
- package/dist/esm/excel/vml-drawing.d.ts +41 -3
- package/dist/esm/excel/vml-drawing.js +143 -10
- package/dist/esm/excel/worksheet-parser.js +89 -15
- package/dist/esm/excel/xls/biff-reader.js +10 -2
- package/dist/esm/excel/xlsx-reader.js +195 -14
- package/dist/esm/excel/xlsx-writer.js +8 -7
- package/dist/esm/html/html-writer.js +202 -20
- package/dist/esm/layout/fit-pages.d.ts +11 -0
- package/dist/esm/layout/fit-pages.js +86 -0
- package/dist/esm/layout/line-paint.d.ts +58 -0
- package/dist/esm/layout/line-paint.js +107 -0
- package/dist/esm/layout/page-doc.d.ts +51 -0
- package/dist/esm/layout/page-doc.js +141 -1
- package/dist/esm/layout/styled-layout.d.ts +7 -0
- package/dist/esm/layout/styled-layout.js +274 -51
- package/dist/esm/layout/turned-section.js +14 -2
- package/dist/esm/markdown/markdown-writer.js +17 -6
- package/dist/esm/pdf/styled-page-emitter.js +49 -71
- package/dist/esm/pdf/styled-page-renderer.js +1 -1
- package/dist/esm/pdf-reader/font.js +3 -3
- package/dist/esm/svg/svg-text.d.ts +58 -0
- package/dist/esm/svg/svg-text.js +322 -0
- package/dist/esm/svg/svg-writer.js +135 -71
- package/dist/esm/word/docx-writer.js +11 -2
- package/package.json +5 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
|
|
1
2
|
import { FEATURES } from "../core/ir/features.js";
|
|
2
3
|
import { headingLevelOf } from "../core/outline.js";
|
|
3
4
|
import { detectImageFormat } from "../core/images.js";
|
|
@@ -7,7 +8,7 @@ import "../core/style-cascade/index.js";
|
|
|
7
8
|
import { buildSparkline } from "../core/drawingml/sparkline-geometry.js";
|
|
8
9
|
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
9
10
|
import { buildChartScene } from "../core/drawingml/chart-geometry.js";
|
|
10
|
-
import { buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef } from "../core/drawingml/shape-render.js";
|
|
11
|
+
import { buildShapePaths, buildShapeTransform, buildStroke, gradientSvgDef, lineEndPaths } from "../core/drawingml/shape-render.js";
|
|
11
12
|
import { sanitizeHref } from "../core/links.js";
|
|
12
13
|
import { toBase64 } from "../core/bytes.js";
|
|
13
14
|
//#region src/html/html-writer.ts
|
|
@@ -47,13 +48,15 @@ function writeHtml(flow) {
|
|
|
47
48
|
out.push(`<style>${BASE_CSS}</style>`);
|
|
48
49
|
out.push("</head>");
|
|
49
50
|
out.push("<body>");
|
|
50
|
-
|
|
51
|
+
const sheets = flow.sections.some((s) => s.sheet !== void 0);
|
|
52
|
+
out.push(sheets ? "<article>" : `<article style="max-width: ${fmt(contentWidthPt(flow))}pt">`);
|
|
51
53
|
if (flow.headersFooters && flow.headersFooters.size > 0) losses.push({
|
|
52
54
|
severity: "dropped",
|
|
53
55
|
feature: FEATURES.headersFooters,
|
|
54
56
|
detail: "headers/footers have no fixed pages to band onto in flowed HTML"
|
|
55
57
|
});
|
|
56
|
-
|
|
58
|
+
if (sheets) emitSheets(out, flow, ctx);
|
|
59
|
+
else for (const el of flow.body) emitBlock(out, el, ctx);
|
|
57
60
|
emitNotesSection(out, flow.footnotes, ctx.notes.footnotes, "fn", ctx);
|
|
58
61
|
emitNotesSection(out, flow.endnotes, ctx.notes.endnotes, "en", ctx);
|
|
59
62
|
emitCommentsSection(out, flow.comments, ctx.notes.comments, ctx);
|
|
@@ -95,12 +98,73 @@ var BASE_CSS = [
|
|
|
95
98
|
"th{text-align:inherit}",
|
|
96
99
|
".tab{display:inline-block;min-width:18pt}",
|
|
97
100
|
"img{vertical-align:baseline}",
|
|
101
|
+
".sheet{margin:0 0 24pt}",
|
|
102
|
+
".sheet-name{font:600 11pt/1.4 sans-serif;margin:0 0 6pt;color:#333}",
|
|
103
|
+
".surface{position:relative}",
|
|
104
|
+
".drawing{z-index:1}",
|
|
98
105
|
".notes{margin-top:18pt;font-size:smaller}",
|
|
99
106
|
".notes hr{margin:0 0 6pt;border:none;border-top:0.75pt solid #000;width:144pt;margin-left:0}",
|
|
100
107
|
".comment-range{background:#fff3a3}",
|
|
101
108
|
".comment-replies{margin-left:18pt}",
|
|
102
109
|
".comments .resolved>.comment-meta::after{content:\" \\2713\";color:#3a7d3a}"
|
|
103
110
|
].join("");
|
|
111
|
+
/**
|
|
112
|
+
* A workbook laid out for the screen: each sheet a section of its own, headed
|
|
113
|
+
* by its tab's name, its grid and drawings on one surface. The surface is the
|
|
114
|
+
* positioned box the drawings are placed in, and it is made as large as the
|
|
115
|
+
* furthest of them — a chart below the last row is outside the grid, and a box
|
|
116
|
+
* only as tall as the grid let the next sheet run under it.
|
|
117
|
+
*/
|
|
118
|
+
function emitSheets(out, flow, ctx) {
|
|
119
|
+
let start = 0;
|
|
120
|
+
for (const section of flow.sections) {
|
|
121
|
+
const els = flow.body.slice(start, section.endIndex);
|
|
122
|
+
start = section.endIndex;
|
|
123
|
+
if (!section.sheet) {
|
|
124
|
+
for (const el of els) emitBlock(out, el, ctx);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const name = section.sheet.name;
|
|
128
|
+
out.push(`<section class="sheet" data-sheet="${escapeAttr(name)}">`);
|
|
129
|
+
out.push(`<h2 class="sheet-name">${escapeText(name)}</h2>`);
|
|
130
|
+
let right = 0;
|
|
131
|
+
let bottom = 0;
|
|
132
|
+
for (const el of els) {
|
|
133
|
+
const at = surfaceBox(el);
|
|
134
|
+
if (!at) continue;
|
|
135
|
+
right = Math.max(right, at.x + at.width);
|
|
136
|
+
bottom = Math.max(bottom, at.y + at.height);
|
|
137
|
+
}
|
|
138
|
+
const reach = right > 0 || bottom > 0 ? `min-width:${fmt(right)}pt;min-height:${fmt(bottom)}pt` : "";
|
|
139
|
+
out.push(`<div class="surface"${reach ? ` style="${reach}"` : ""}>`);
|
|
140
|
+
const onSurface = {
|
|
141
|
+
...ctx,
|
|
142
|
+
surface: true
|
|
143
|
+
};
|
|
144
|
+
for (const el of els) emitBlock(out, el, onSurface);
|
|
145
|
+
out.push("</div>");
|
|
146
|
+
out.push("</section>");
|
|
147
|
+
}
|
|
148
|
+
for (const el of flow.body.slice(start)) emitBlock(out, el, ctx);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Where a floating drawing sits on a worksheet's surface, in points from its
|
|
152
|
+
* top-left corner — or `undefined` for a block that is not a float placed by
|
|
153
|
+
* offsets (a paragraph, a table, an inline picture).
|
|
154
|
+
*/
|
|
155
|
+
function surfaceBox(el) {
|
|
156
|
+
if (el.kind === "paragraph" || el.kind === "table") return void 0;
|
|
157
|
+
const block = el.kind === "image" ? el.image : el.kind === "chart" ? el.chart : el.shape;
|
|
158
|
+
const x = block.float?.posH?.offsetPt;
|
|
159
|
+
const y = block.float?.posV?.offsetPt;
|
|
160
|
+
if (x === void 0 || y === void 0) return void 0;
|
|
161
|
+
return {
|
|
162
|
+
x,
|
|
163
|
+
y,
|
|
164
|
+
width: block.width,
|
|
165
|
+
height: block.height
|
|
166
|
+
};
|
|
167
|
+
}
|
|
104
168
|
function collectReferencedAnchors(body) {
|
|
105
169
|
const out = /* @__PURE__ */ new Set();
|
|
106
170
|
const visit = (els) => {
|
|
@@ -190,6 +254,16 @@ function emitCommentsSection(out, comments, numbers, ctx) {
|
|
|
190
254
|
out.push("</ol></section>");
|
|
191
255
|
}
|
|
192
256
|
function emitBlock(out, el, ctx) {
|
|
257
|
+
const at = ctx.surface ? surfaceBox(el) : void 0;
|
|
258
|
+
if (at) {
|
|
259
|
+
out.push(`<div class="drawing" style="position:absolute;left:${fmt(at.x)}pt;top:${fmt(at.y)}pt;width:${fmt(at.width)}pt;height:${fmt(at.height)}pt">`);
|
|
260
|
+
emitBlock(out, el, {
|
|
261
|
+
...ctx,
|
|
262
|
+
surface: false
|
|
263
|
+
});
|
|
264
|
+
out.push("</div>");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
193
267
|
if (el.kind === "paragraph") emitParagraph(out, el.paragraph, ctx);
|
|
194
268
|
else if (el.kind === "table") emitTable(out, el.table, ctx);
|
|
195
269
|
else if (el.kind === "image") emitImageBlock(out, el.image, ctx);
|
|
@@ -205,7 +279,13 @@ var blockFigureCss = (pp) => {
|
|
|
205
279
|
if (pp.spacingAfter !== void 0 && pp.spacingAfter > 0) css.push(`margin-bottom:${fmt(pp.spacingAfter)}pt`);
|
|
206
280
|
return css.length > 0 ? ` style="${css.join(";")}"` : "";
|
|
207
281
|
};
|
|
208
|
-
var
|
|
282
|
+
var LINE_PAD_PT = .5;
|
|
283
|
+
var svgOpen = (w, h, label) => {
|
|
284
|
+
const padX = w < 2 * LINE_PAD_PT ? LINE_PAD_PT : 0;
|
|
285
|
+
const padY = h < 2 * LINE_PAD_PT ? LINE_PAD_PT : 0;
|
|
286
|
+
const style = padX > 0 || padY > 0 ? `margin:${fmt(-padY)}pt 0 0 ${fmt(-padX)}pt;vertical-align:top` : "max-width:100%;height:auto";
|
|
287
|
+
return `<svg viewBox="${fmt(-padX)} ${fmt(-padY)} ${fmt(w + 2 * padX)} ${fmt(h + 2 * padY)}" width="${fmt(w + 2 * padX)}pt" height="${fmt(h + 2 * padY)}pt" style="${style}" overflow="visible" xmlns="http://www.w3.org/2000/svg" role="img"${label ? ` aria-label="${escapeAttr(label)}"` : ""}>`;
|
|
288
|
+
};
|
|
209
289
|
var strokeAttrs = (stroke) => {
|
|
210
290
|
if (!stroke) return "";
|
|
211
291
|
let out = ` stroke="#${stroke.colorHex}" stroke-width="${fmt(stroke.widthPt)}"`;
|
|
@@ -293,17 +373,20 @@ function emitOneShape(out, shape, ctx) {
|
|
|
293
373
|
const paths = buildShapePaths(shape.geometry, w, h);
|
|
294
374
|
const gradDef = shape.fill.kind === "gradient" && shape.fill.gradient ? gradientSvgDef("grad0", shape.fill.gradient) : void 0;
|
|
295
375
|
const fill = gradDef ? "url(#grad0)" : shape.fill.kind === "solid" && shape.fill.colorHex ? `#${shape.fill.colorHex}` : "none";
|
|
296
|
-
const
|
|
376
|
+
const strokeStyle = buildStroke(shape.line);
|
|
377
|
+
const stroke = strokeAttrs(strokeStyle);
|
|
297
378
|
const t = shape.transform;
|
|
298
379
|
const transform = ` transform="matrix(${flipTransform(buildShapeTransform(0, 0, w, h, t?.rotation60k ?? 0, t?.flipH ?? false, t?.flipV ?? false), h).map(fmt).join(" ")})"`;
|
|
299
380
|
const svg = [svgOpen(w, h, shape.altText)];
|
|
300
381
|
if (gradDef) svg.push(`<defs>${gradDef}</defs>`);
|
|
301
382
|
const shadow = shape.shadow;
|
|
302
|
-
|
|
383
|
+
if (shadow) svg.push(`<g filter="drop-shadow(${fmt(shadow.dxPt)}px ${fmt(shadow.dyPt)}px ${fmt(shadow.blurPt / 2)}px rgba(${hexToRgbCss(shadow.colorHex)},${fmt(shadow.alpha)}))">`);
|
|
303
384
|
for (const path of paths) {
|
|
304
385
|
const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
|
|
305
|
-
svg.push(`<path d="${svgPathData(path.segments, fmt)}" fill="${fill}"${rule}${stroke}${transform}
|
|
386
|
+
svg.push(`<path d="${svgPathData(path.segments, fmt)}" fill="${fill}"${rule}${stroke}${transform}/>`);
|
|
306
387
|
}
|
|
388
|
+
if (shadow) svg.push("</g>");
|
|
389
|
+
if (strokeStyle) for (const end of lineEndPaths(paths, shape.line, strokeStyle.widthPt)) svg.push(`<path d="${svgPathData(end.segments, fmt)}" fill="#${strokeStyle.colorHex}"${transform}/>`);
|
|
307
390
|
svg.push("</svg>");
|
|
308
391
|
const figureCss = blockFigureCss(shape.paragraphProperties);
|
|
309
392
|
const text = shape.text;
|
|
@@ -336,12 +419,16 @@ function emitParagraph(out, p, ctx) {
|
|
|
336
419
|
}
|
|
337
420
|
function paragraphCss(r) {
|
|
338
421
|
const css = [];
|
|
339
|
-
|
|
340
|
-
|
|
422
|
+
const bidi = r.bidi === true;
|
|
423
|
+
const align = bidi && r.alignment === "left" ? "right" : bidi && r.alignment === "right" ? "left" : r.alignment;
|
|
424
|
+
if (align === "center" || align === "right" || bidi && align === "left") css.push(`text-align:${align}`);
|
|
425
|
+
else if (align === "both" || align === "distribute") css.push("text-align:justify");
|
|
341
426
|
if (r.spacingBefore > 0) css.push(`margin-top:${fmt(r.spacingBefore)}pt`);
|
|
342
427
|
if (r.spacingAfter > 0) css.push(`margin-bottom:${fmt(r.spacingAfter)}pt`);
|
|
343
|
-
|
|
344
|
-
|
|
428
|
+
const indentLeft = bidi ? r.indentRight : r.indentLeft;
|
|
429
|
+
const indentRight = bidi ? r.indentLeft : r.indentRight;
|
|
430
|
+
if (indentLeft !== 0) css.push(`margin-left:${fmt(indentLeft)}pt`);
|
|
431
|
+
if (indentRight !== 0) css.push(`margin-right:${fmt(indentRight)}pt`);
|
|
345
432
|
if (r.indentFirstLine !== 0) css.push(`text-indent:${fmt(r.indentFirstLine)}pt`);
|
|
346
433
|
if (r.spacingLine > 0) if (r.spacingLineRule === "auto") css.push(`line-height:${fmt(r.spacingLine / 12)}`);
|
|
347
434
|
else css.push(`line-height:${fmt(r.spacingLine)}pt`);
|
|
@@ -379,7 +466,7 @@ function runHtml(run, p, ctx) {
|
|
|
379
466
|
if (run.text.length === 0) return "";
|
|
380
467
|
const resolved = resolveRunProperties(run.properties, p.properties, EMPTY_STYLE_SHEET);
|
|
381
468
|
const style = runCss(resolved);
|
|
382
|
-
let html = `<span${resolved.rtl ? " dir=\"rtl\"" : ""}${style ? ` style="${style}"` : ""}>${textHtml(run.text)}</span>`;
|
|
469
|
+
let html = `<span${resolved.rtl ? " dir=\"rtl\"" : ""}${style ? ` style="${escapeAttr(style)}"` : ""}>${textHtml(run.text)}</span>`;
|
|
383
470
|
if (resolved.verticalAlign === "superscript") html = `<sup>${html}</sup>`;
|
|
384
471
|
else if (resolved.verticalAlign === "subscript") html = `<sub>${html}</sub>`;
|
|
385
472
|
if (run.commentRangeRefs && run.commentRangeRefs.length > 0) html = `<span class="comment-range">${html}</span>`;
|
|
@@ -398,7 +485,7 @@ function runHtml(run, p, ctx) {
|
|
|
398
485
|
function runCss(r) {
|
|
399
486
|
const css = [];
|
|
400
487
|
const family = r.fontFamily.ascii;
|
|
401
|
-
if (family) css.push(`font-family:${
|
|
488
|
+
if (family) css.push(`font-family:${fontStack(family)}`);
|
|
402
489
|
css.push(`font-size:${fmt(r.fontSizePt)}pt`);
|
|
403
490
|
if (r.bold) css.push("font-weight:700");
|
|
404
491
|
if (r.italic) css.push("font-style:italic");
|
|
@@ -414,6 +501,28 @@ function runCss(r) {
|
|
|
414
501
|
if (r.colorHex !== "000000") css.push(`color:#${r.colorHex}`);
|
|
415
502
|
return css.join(";");
|
|
416
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* A family as CSS asks for it: the document's own, then — where the page is
|
|
506
|
+
* read without that font, which for Calibri is anywhere Office is not — its
|
|
507
|
+
* open metric twin, then the class of face it is. Without the class a browser
|
|
508
|
+
* that lacks the font falls to its default, a serif: a Calibri sheet came out
|
|
509
|
+
* in Times.
|
|
510
|
+
*
|
|
511
|
+
* The name is a CSS string in SINGLE quotes. The declaration goes into a
|
|
512
|
+
* double-quoted `style` attribute, where a double quote ended the attribute
|
|
513
|
+
* and took the size, weight and colour after the family with it.
|
|
514
|
+
*/
|
|
515
|
+
function fontStack(family) {
|
|
516
|
+
return [`'${family.replace(/\s+/gu, " ").replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`, ...FALLBACKS[resolveFamilyStyle(family).key]].join(",");
|
|
517
|
+
}
|
|
518
|
+
/** What follows a family in its stack, by the open substitute it maps to. */
|
|
519
|
+
var FALLBACKS = {
|
|
520
|
+
carlito: ["Carlito", "sans-serif"],
|
|
521
|
+
caladea: ["Caladea", "serif"],
|
|
522
|
+
arimo: ["sans-serif"],
|
|
523
|
+
tinos: ["serif"],
|
|
524
|
+
cousine: ["monospace"]
|
|
525
|
+
};
|
|
417
526
|
function decorationStyle(u) {
|
|
418
527
|
switch (u) {
|
|
419
528
|
case "double": return "double";
|
|
@@ -430,8 +539,11 @@ function textHtml(text) {
|
|
|
430
539
|
}
|
|
431
540
|
function emitTable(out, table, ctx) {
|
|
432
541
|
const css = [];
|
|
542
|
+
const fixed = table.properties.layout === "fixed";
|
|
433
543
|
if (table.properties.widthPt !== void 0) css.push(`width:${fmt(table.properties.widthPt)}pt`);
|
|
434
544
|
else if (table.properties.widthFraction !== void 0) css.push(`width:${fmt(table.properties.widthFraction * 100)}%`);
|
|
545
|
+
else if (fixed && table.grid.length > 0) css.push(`width:${fmt(table.grid.reduce((sum, w) => sum + w, 0))}pt`);
|
|
546
|
+
if (fixed) css.push("table-layout:fixed");
|
|
435
547
|
if (table.properties.alignment === "center") css.push("margin-left:auto;margin-right:auto");
|
|
436
548
|
else if (table.properties.alignment === "right") css.push("margin-left:auto");
|
|
437
549
|
out.push(`<table${css.length > 0 ? ` style="${css.join(";")}"` : ""}>`);
|
|
@@ -448,6 +560,7 @@ function emitTable(out, table, ctx) {
|
|
|
448
560
|
return start;
|
|
449
561
|
});
|
|
450
562
|
});
|
|
563
|
+
const cellGrid = table.properties.gridlines ? gridOfCells(table, colStarts) : void 0;
|
|
451
564
|
const frozen = table.properties.frozen;
|
|
452
565
|
const leftOffsets = [];
|
|
453
566
|
const topOffsets = [];
|
|
@@ -465,14 +578,19 @@ function emitTable(out, table, ctx) {
|
|
|
465
578
|
}
|
|
466
579
|
for (let ri = 0; ri < table.rows.length; ri++) {
|
|
467
580
|
const row = table.rows[ri];
|
|
468
|
-
|
|
581
|
+
const { height, heightRule } = row.properties;
|
|
582
|
+
out.push(height !== void 0 && heightRule !== "auto" ? `<tr style="height:${fmt(height)}pt">` : "<tr>");
|
|
469
583
|
for (let ci = 0; ci < row.cells.length; ci++) {
|
|
470
584
|
const cell = row.cells[ci];
|
|
471
585
|
const merge = cell.properties.merge;
|
|
472
586
|
if (merge === "middle" || merge === "end") continue;
|
|
473
587
|
const cs = colStarts[ri][ci];
|
|
474
588
|
const rowSpan = merge === "start" ? mergeRowSpan(table, colStarts, ri, cs) : 1;
|
|
589
|
+
const claimed = cellGrid ? claimedEdges(cellGrid, ri, cs, cs + (cell.properties.colSpan ?? 1) - 1, rowSpan) : void 0;
|
|
590
|
+
const flagInsetPt = noteFlagInset(cell, cs, table.grid);
|
|
475
591
|
emitCell(out, cell, table, ctx, {
|
|
592
|
+
...flagInsetPt > 0 ? { flagInsetPt } : {},
|
|
593
|
+
...claimed && claimed.size > 0 ? { claimed } : {},
|
|
476
594
|
isHeader: row.properties.isHeader === true,
|
|
477
595
|
firstRow: ri === 0,
|
|
478
596
|
lastRow: ri === table.rows.length - 1,
|
|
@@ -487,6 +605,56 @@ function emitTable(out, table, ctx) {
|
|
|
487
605
|
}
|
|
488
606
|
out.push("</table>");
|
|
489
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* How far a note's flag stands in from the cell's side: the width of the
|
|
610
|
+
* columns its text only borrowed (CellProperties.paintColumns) — after its own
|
|
611
|
+
* column, or before it where the span runs leftwards (paintAtEnd).
|
|
612
|
+
*/
|
|
613
|
+
function noteFlagInset(cell, colStart, grid) {
|
|
614
|
+
const p = cell.properties;
|
|
615
|
+
const span = p.colSpan ?? 1;
|
|
616
|
+
if (!p.noteFlag || p.paintColumns === void 0 || p.paintColumns >= span) return 0;
|
|
617
|
+
return (p.paintAtEnd ? grid.slice(colStart, colStart + span - p.paintColumns) : grid.slice(colStart + p.paintColumns, colStart + span)).reduce((sum, w) => sum + w, 0);
|
|
618
|
+
}
|
|
619
|
+
/** The cell at each row × grid column (a spanning cell at every column it covers). */
|
|
620
|
+
function gridOfCells(table, colStarts) {
|
|
621
|
+
return table.rows.map((row, ri) => {
|
|
622
|
+
const line = new Array(table.grid.length).fill(void 0);
|
|
623
|
+
row.cells.forEach((cell, ci) => {
|
|
624
|
+
const start = colStarts[ri][ci];
|
|
625
|
+
for (let k = 0; k < (cell.properties.colSpan ?? 1); k++) line[start + k] = cell;
|
|
626
|
+
});
|
|
627
|
+
return line;
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* The edges of a cell (rows `ri`…`ri + rowSpan − 1`, columns `c0`…`c1`) that a
|
|
632
|
+
* neighbour across them CLAIMS: one that rules its facing side, or is filled.
|
|
633
|
+
* A window's gridline gives way there. Collapsed borders are settled edge by
|
|
634
|
+
* edge, wider first and then by position, and a gridline is as wide as a
|
|
635
|
+
* thin rule once the browser rounds both to a pixel — so the cell above or to
|
|
636
|
+
* the left won, and a merged range ruled all round showed only two of its four
|
|
637
|
+
* sides. Excel draws no gridline along a filled cell either. Where only some of
|
|
638
|
+
* the neighbours claim the edge, the others draw their own gridline on it.
|
|
639
|
+
*/
|
|
640
|
+
function claimedEdges(grid, ri, c0, c1, rowSpan) {
|
|
641
|
+
const claims = (cell, facing) => {
|
|
642
|
+
if (!cell) return false;
|
|
643
|
+
if (cell.properties.shading) return true;
|
|
644
|
+
const b = cell.properties.borders?.[facing];
|
|
645
|
+
return b !== void 0 && b.style !== "none";
|
|
646
|
+
};
|
|
647
|
+
const out = /* @__PURE__ */ new Set();
|
|
648
|
+
for (let c = c0; c <= c1; c++) {
|
|
649
|
+
if (claims(grid[ri - 1]?.[c], "bottom")) out.add("top");
|
|
650
|
+
if (claims(grid[ri + rowSpan]?.[c], "top")) out.add("bottom");
|
|
651
|
+
}
|
|
652
|
+
for (let r = ri; r < ri + rowSpan; r++) {
|
|
653
|
+
if (claims(grid[r]?.[c0 - 1], "right")) out.add("left");
|
|
654
|
+
if (claims(grid[r]?.[c1 + 1], "left")) out.add("right");
|
|
655
|
+
}
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
490
658
|
function mergeRowSpan(table, colStarts, startRow, colStart) {
|
|
491
659
|
let span = 1;
|
|
492
660
|
for (let r = startRow + 1; r < table.rows.length; r++) {
|
|
@@ -508,11 +676,15 @@ function emitCell(out, cell, table, ctx, pos) {
|
|
|
508
676
|
const css = [];
|
|
509
677
|
const t = table.properties.borders;
|
|
510
678
|
const c = cell.properties.borders;
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
pushBorder(css, "
|
|
514
|
-
pushBorder(css, "
|
|
679
|
+
const g = cell.properties.shading ? void 0 : table.properties.gridlines;
|
|
680
|
+
const gridOn = (side) => pos.claimed?.has(side) ? void 0 : g;
|
|
681
|
+
pushBorder(css, "top", c?.top ?? (pos.firstRow ? t?.top : t?.insideH) ?? gridOn("top"));
|
|
682
|
+
pushBorder(css, "bottom", c?.bottom ?? (pos.lastRow ? t?.bottom : t?.insideH) ?? gridOn("bottom"));
|
|
683
|
+
pushBorder(css, "left", c?.left ?? (pos.firstCol ? t?.left : t?.insideV) ?? gridOn("left"));
|
|
684
|
+
pushBorder(css, "right", c?.right ?? (pos.lastCol ? t?.right : t?.insideV) ?? gridOn("right"));
|
|
515
685
|
if (cell.properties.shading) css.push(`background-color:#${cell.properties.shading.colorHex}`);
|
|
686
|
+
if (cell.properties.verticalAlign === "center") css.push("vertical-align:middle");
|
|
687
|
+
else if (cell.properties.verticalAlign === "bottom") css.push("vertical-align:bottom");
|
|
516
688
|
if (cell.properties.noWrap) css.push("white-space:nowrap", "overflow:hidden");
|
|
517
689
|
if (cell.properties.dataBar) {
|
|
518
690
|
const db = cell.properties.dataBar;
|
|
@@ -520,7 +692,8 @@ function emitCell(out, cell, table, ctx, pos) {
|
|
|
520
692
|
const start = (clamp(db.startFraction ?? 0) * 100).toFixed(2);
|
|
521
693
|
const end = (clamp((db.startFraction ?? 0) + db.fraction) * 100).toFixed(2);
|
|
522
694
|
const c = db.colorHex;
|
|
523
|
-
|
|
695
|
+
const [from, to] = db.solid ? [c, c] : db.negative ? ["FFFFFF", c] : [c, "FFFFFF"];
|
|
696
|
+
css.push(`background-image:linear-gradient(to right,transparent ${start}%,#${from} ${start}%,#${to} ${end}%,transparent ${end}%)`, "background-repeat:no-repeat", "background-size:calc(100% - 3pt) calc(100% - 3pt)", "background-position:1.5pt 1.5pt");
|
|
524
697
|
}
|
|
525
698
|
const margins = cell.properties.margins ?? table.properties.defaultCellMargins;
|
|
526
699
|
const padTop = margins?.top ?? 0;
|
|
@@ -536,13 +709,22 @@ function emitCell(out, cell, table, ctx, pos) {
|
|
|
536
709
|
css.push(`z-index:${z}`);
|
|
537
710
|
if (!cell.properties.shading) css.push("background-color:#fff");
|
|
538
711
|
}
|
|
712
|
+
const note = cell.properties.noteFlag;
|
|
713
|
+
if (note) {
|
|
714
|
+
attrs.push(`title="${escapeAttr(note.text)}"`);
|
|
715
|
+
if (pos.stickyTop === void 0 && pos.stickyLeft === void 0) css.push("position:relative");
|
|
716
|
+
}
|
|
539
717
|
out.push(`<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${css.join(";")}">`);
|
|
718
|
+
if (note) out.push(cellNoteFlag(note, pos.flagInsetPt ?? 0));
|
|
540
719
|
if (cell.properties.dropdown) out.push(cellDropdownSvg());
|
|
541
720
|
if (cell.properties.icon) out.push(cellIconSvg(cell.properties.icon));
|
|
542
721
|
if (cell.properties.sparkline) out.push(cellSparklineSvg(cell.properties.sparkline));
|
|
543
722
|
for (const child of cell.content) emitBlock(out, child, ctx);
|
|
544
723
|
out.push(`</${tag}>`);
|
|
545
724
|
}
|
|
725
|
+
function cellNoteFlag(note, insetPt) {
|
|
726
|
+
return `<span aria-hidden="true" style="position:absolute;top:0;${note.atLeft ? "left" : "right"}:${fmt(insetPt)}pt;width:0;height:0;border-top:4.5pt solid #${note.kind === "thread" ? "7030A0" : "FF0000"};border-${note.atLeft ? "right" : "left"}:4.5pt solid transparent"></span>`;
|
|
727
|
+
}
|
|
546
728
|
function cellSparklineSvg(sp) {
|
|
547
729
|
const W = 56;
|
|
548
730
|
const H = 16;
|
|
@@ -598,7 +780,7 @@ function cellIconSvg(icon) {
|
|
|
598
780
|
body = `<circle cx="5" cy="5" r="4" fill="${fill}"/>`;
|
|
599
781
|
break;
|
|
600
782
|
}
|
|
601
|
-
return `<svg width="10" height="10" viewBox="0 0 10 10" style="
|
|
783
|
+
return `<svg width="10" height="10" viewBox="0 0 10 10" style="float:left;margin:2px 3px 0 0">${body}</svg>`;
|
|
602
784
|
}
|
|
603
785
|
var round2 = (n) => (Math.round(n * 100) / 100).toString();
|
|
604
786
|
function barsIconSvg(fill, color, empty) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { LaidOutDocument } from './page-doc.js';
|
|
2
|
+
/**
|
|
3
|
+
* Each page cut down to the right and bottom edges of what is drawn on it,
|
|
4
|
+
* plus `pad`. The top-left corner stays where it is — the content's origin is
|
|
5
|
+
* the page's, and the layout already kept its margin there.
|
|
6
|
+
*
|
|
7
|
+
* @param laid The laid-out document, its pages larger than their content.
|
|
8
|
+
* @param pad The room kept past the content on the right and at the bottom.
|
|
9
|
+
* @returns The same document on pages no larger than what they hold.
|
|
10
|
+
*/
|
|
11
|
+
export declare function fitPagesToContent(laid: LaidOutDocument, pad: number): LaidOutDocument;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { pt } from "../core/ir/units.js";
|
|
2
|
+
//#region src/layout/fit-pages.ts
|
|
3
|
+
/**
|
|
4
|
+
* Each page cut down to the right and bottom edges of what is drawn on it,
|
|
5
|
+
* plus `pad`. The top-left corner stays where it is — the content's origin is
|
|
6
|
+
* the page's, and the layout already kept its margin there.
|
|
7
|
+
*
|
|
8
|
+
* @param laid The laid-out document, its pages larger than their content.
|
|
9
|
+
* @param pad The room kept past the content on the right and at the bottom.
|
|
10
|
+
* @returns The same document on pages no larger than what they hold.
|
|
11
|
+
*/
|
|
12
|
+
function fitPagesToContent(laid, pad) {
|
|
13
|
+
return {
|
|
14
|
+
...laid,
|
|
15
|
+
pages: laid.pages.map((page) => fitPage(page, pad))
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function fitPage(page, pad) {
|
|
19
|
+
let right = 0;
|
|
20
|
+
let bottom = 0;
|
|
21
|
+
for (const item of page.commands) {
|
|
22
|
+
const reach = reachOf(item);
|
|
23
|
+
const w = item.window;
|
|
24
|
+
right = Math.max(right, w ? Math.min(reach.right, w.x + w.width) : reach.right);
|
|
25
|
+
bottom = Math.max(bottom, w ? Math.min(reach.bottom, w.y + w.height) : reach.bottom);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...page,
|
|
29
|
+
width: pt(Math.min(page.width, Math.ceil(right + pad))),
|
|
30
|
+
height: pt(Math.min(page.height, Math.ceil(bottom + pad)))
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** How far right and down one item paints. */
|
|
34
|
+
function reachOf(item) {
|
|
35
|
+
switch (item.type) {
|
|
36
|
+
case "fill":
|
|
37
|
+
case "image": return {
|
|
38
|
+
right: item.x + item.width,
|
|
39
|
+
bottom: item.y + item.height
|
|
40
|
+
};
|
|
41
|
+
case "border": {
|
|
42
|
+
const half = item.borderSizePt / 2;
|
|
43
|
+
return {
|
|
44
|
+
right: item.x + item.width + half,
|
|
45
|
+
bottom: item.y + item.height + half
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
case "line": {
|
|
49
|
+
if (item.clip) return {
|
|
50
|
+
right: item.clip.x + item.clip.width,
|
|
51
|
+
bottom: item.clip.y + item.clip.height
|
|
52
|
+
};
|
|
53
|
+
const descent = item.line.metricDescentPt ?? item.line.maxFontSizePt * .25;
|
|
54
|
+
return {
|
|
55
|
+
right: item.originX + item.line.contentWidthPt,
|
|
56
|
+
bottom: item.baselineY + descent
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
case "shape": return shapeReach(item.shape);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** The far corner of a shape's paths on the page, its stroke included (control points bound a curve). */
|
|
63
|
+
function shapeReach(shape) {
|
|
64
|
+
const [a, b, c, d, e, f] = shape.transform;
|
|
65
|
+
const half = (shape.stroke?.widthPt ?? 0) / 2;
|
|
66
|
+
let right = 0;
|
|
67
|
+
let bottom = 0;
|
|
68
|
+
const take = (x, y) => {
|
|
69
|
+
right = Math.max(right, a * x + c * y + e + half);
|
|
70
|
+
bottom = Math.max(bottom, b * x + d * y + f + half);
|
|
71
|
+
};
|
|
72
|
+
for (const path of shape.paths) for (const s of path.segments) {
|
|
73
|
+
if (s.op === "close") continue;
|
|
74
|
+
if (s.op === "cubic") {
|
|
75
|
+
take(s.x1, s.y1);
|
|
76
|
+
take(s.x2, s.y2);
|
|
77
|
+
}
|
|
78
|
+
take(s.x, s.y);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
right,
|
|
82
|
+
bottom
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { fitPagesToContent };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BorderStyle } from '../core/document-model/index.js';
|
|
2
|
+
import { Line } from './page-doc.js';
|
|
3
|
+
/** The slant of a faked italic — tan(12°), the angle a text italic leans at. */
|
|
4
|
+
export declare const FAUX_ITALIC_SHEAR = 0.2126;
|
|
5
|
+
/**
|
|
6
|
+
* §18.18.3 — the dash pattern of a rule that is not solid, in points: dashes
|
|
7
|
+
* and gaps alternating, from a dash.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BORDER_DASHES: ReadonlyMap<BorderStyle, ReadonlyArray<number>>;
|
|
10
|
+
/**
|
|
11
|
+
* UAX #9 rule L2 over a line's tokens: the token indices in visual order. Each
|
|
12
|
+
* token carries a single embedding level (the tokenizer split runs at level
|
|
13
|
+
* boundaries), so reordering tokens is equivalent to reordering their
|
|
14
|
+
* characters.
|
|
15
|
+
*
|
|
16
|
+
* @param line The laid-out line.
|
|
17
|
+
* @returns Its token indices, left to right as they are drawn.
|
|
18
|
+
*/
|
|
19
|
+
export declare function lineVisualOrder(line: Line): Array<number>;
|
|
20
|
+
/**
|
|
21
|
+
* The extra width each space of a justified line takes — 0 for a line that is
|
|
22
|
+
* not justified, or the last line of a paragraph (which stays flush by
|
|
23
|
+
* convention).
|
|
24
|
+
*
|
|
25
|
+
* @param line The laid-out line.
|
|
26
|
+
* @returns Points added to (or, squeezed, taken from) each space token.
|
|
27
|
+
*/
|
|
28
|
+
export declare function computeJustifyExtra(line: Line): number;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a line is ONE text matrix and a run of glyphs: nothing in it placed
|
|
31
|
+
* on its own — a tab, a stretched space, a picture, a formula, a right-to-left
|
|
32
|
+
* run, a rise, a faked italic — and nothing drawn beside its glyphs, a run's
|
|
33
|
+
* shading, a highlight or a rule. Only such a line can be turned by its matrix
|
|
34
|
+
* alone; any other is placed token by token in page space.
|
|
35
|
+
*
|
|
36
|
+
* @param line The laid-out line.
|
|
37
|
+
* @returns True when a single text matrix sets the whole of it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function settableInOneMatrix(line: Line): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* §14.1.2.10 — the wash a picture is drawn through, as the flat veil it is.
|
|
42
|
+
* `gain`/`blacklevel` are contrast and brightness about mid grey, so a source
|
|
43
|
+
* value `in` prints as `(in - 0.5) * gain + 0.5 + black` — and painting a
|
|
44
|
+
* colour `c` at opacity `a` gives `in * (1 - a) + c * a`, the same line with
|
|
45
|
+
* `a = 1 - gain`. Exact for every wash a document can state, and it needs no
|
|
46
|
+
* decoder: the picture's own pixels are not rewritten.
|
|
47
|
+
*
|
|
48
|
+
* @param wash The picture's wash, if it has one.
|
|
49
|
+
* @returns The grey (0…1) and opacity of the veil laid over the picture, or
|
|
50
|
+
* nothing where the wash changes nothing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function washVeil(wash: {
|
|
53
|
+
readonly gain: number;
|
|
54
|
+
readonly black: number;
|
|
55
|
+
} | undefined): {
|
|
56
|
+
alpha: number;
|
|
57
|
+
grey: number;
|
|
58
|
+
} | undefined;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { reorderVisual } from "../core/bidi/algorithm.js";
|
|
2
|
+
import "../core/bidi/index.js";
|
|
3
|
+
import { GLUE_SHRINK_RATIO } from "./styled-layout.js";
|
|
4
|
+
//#region src/layout/line-paint.ts
|
|
5
|
+
/** The slant of a faked italic — tan(12°), the angle a text italic leans at. */
|
|
6
|
+
var FAUX_ITALIC_SHEAR = .2126;
|
|
7
|
+
/**
|
|
8
|
+
* §18.18.3 — the dash pattern of a rule that is not solid, in points: dashes
|
|
9
|
+
* and gaps alternating, from a dash.
|
|
10
|
+
*/
|
|
11
|
+
var BORDER_DASHES = new Map([
|
|
12
|
+
["dashed", [8, 2.5]],
|
|
13
|
+
["dashSmallGap", [3, 1]],
|
|
14
|
+
["dotted", [.5, 1]],
|
|
15
|
+
["dashDot", [
|
|
16
|
+
8,
|
|
17
|
+
2.5,
|
|
18
|
+
2.5,
|
|
19
|
+
2.5
|
|
20
|
+
]],
|
|
21
|
+
["dashDotDot", [
|
|
22
|
+
8,
|
|
23
|
+
2.5,
|
|
24
|
+
2.5,
|
|
25
|
+
2.5,
|
|
26
|
+
2.5,
|
|
27
|
+
2.5
|
|
28
|
+
]]
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* UAX #9 rule L2 over a line's tokens: the token indices in visual order. Each
|
|
32
|
+
* token carries a single embedding level (the tokenizer split runs at level
|
|
33
|
+
* boundaries), so reordering tokens is equivalent to reordering their
|
|
34
|
+
* characters.
|
|
35
|
+
*
|
|
36
|
+
* @param line The laid-out line.
|
|
37
|
+
* @returns Its token indices, left to right as they are drawn.
|
|
38
|
+
*/
|
|
39
|
+
function lineVisualOrder(line) {
|
|
40
|
+
return reorderVisual(line.tokens.map((t) => t.bidiLevel));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The extra width each space of a justified line takes — 0 for a line that is
|
|
44
|
+
* not justified, or the last line of a paragraph (which stays flush by
|
|
45
|
+
* convention).
|
|
46
|
+
*
|
|
47
|
+
* @param line The laid-out line.
|
|
48
|
+
* @returns Points added to (or, squeezed, taken from) each space token.
|
|
49
|
+
*/
|
|
50
|
+
function computeJustifyExtra(line) {
|
|
51
|
+
const alignment = line.resolved.alignment;
|
|
52
|
+
if (alignment !== "both" && alignment !== "distribute") return 0;
|
|
53
|
+
if (line.noJustify === true) return 0;
|
|
54
|
+
if (line.isLastInParagraph && alignment !== "distribute") return 0;
|
|
55
|
+
let last = line.tokens.length - 1;
|
|
56
|
+
while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
|
|
57
|
+
let spaces = 0;
|
|
58
|
+
let narrowest = Infinity;
|
|
59
|
+
let contentWidthPt = 0;
|
|
60
|
+
for (let i = 0; i <= last; i++) {
|
|
61
|
+
const tok = line.tokens[i];
|
|
62
|
+
contentWidthPt += tok.widthPt;
|
|
63
|
+
if (!tok.isSpace) continue;
|
|
64
|
+
spaces++;
|
|
65
|
+
if (tok.widthPt < narrowest) narrowest = tok.widthPt;
|
|
66
|
+
}
|
|
67
|
+
if (spaces === 0) return 0;
|
|
68
|
+
const extra = (line.availableWidthPt - contentWidthPt) / spaces;
|
|
69
|
+
return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a line is ONE text matrix and a run of glyphs: nothing in it placed
|
|
73
|
+
* on its own — a tab, a stretched space, a picture, a formula, a right-to-left
|
|
74
|
+
* run, a rise, a faked italic — and nothing drawn beside its glyphs, a run's
|
|
75
|
+
* shading, a highlight or a rule. Only such a line can be turned by its matrix
|
|
76
|
+
* alone; any other is placed token by token in page space.
|
|
77
|
+
*
|
|
78
|
+
* @param line The laid-out line.
|
|
79
|
+
* @returns True when a single text matrix sets the whole of it.
|
|
80
|
+
*/
|
|
81
|
+
function settableInOneMatrix(line) {
|
|
82
|
+
return computeJustifyExtra(line) === 0 && line.tokens.every((t) => t.kind === "text" && t.tab !== true && t.bidiLevel % 2 === 0 && t.risePt === void 0 && t.synthetic?.italic !== true && t.highlight !== true && t.resolvedRun.shadingColorHex === void 0 && t.resolvedRun.underline === "none" && !t.resolvedRun.strike);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* §14.1.2.10 — the wash a picture is drawn through, as the flat veil it is.
|
|
86
|
+
* `gain`/`blacklevel` are contrast and brightness about mid grey, so a source
|
|
87
|
+
* value `in` prints as `(in - 0.5) * gain + 0.5 + black` — and painting a
|
|
88
|
+
* colour `c` at opacity `a` gives `in * (1 - a) + c * a`, the same line with
|
|
89
|
+
* `a = 1 - gain`. Exact for every wash a document can state, and it needs no
|
|
90
|
+
* decoder: the picture's own pixels are not rewritten.
|
|
91
|
+
*
|
|
92
|
+
* @param wash The picture's wash, if it has one.
|
|
93
|
+
* @returns The grey (0…1) and opacity of the veil laid over the picture, or
|
|
94
|
+
* nothing where the wash changes nothing.
|
|
95
|
+
*/
|
|
96
|
+
function washVeil(wash) {
|
|
97
|
+
if (!wash) return void 0;
|
|
98
|
+
const alpha = 1 - wash.gain;
|
|
99
|
+
if (!(alpha > .004)) return void 0;
|
|
100
|
+
const offset = .5 * (1 - wash.gain) + wash.black;
|
|
101
|
+
return {
|
|
102
|
+
alpha: Math.min(1, alpha),
|
|
103
|
+
grey: Math.max(0, Math.min(1, offset / alpha))
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { BORDER_DASHES, FAUX_ITALIC_SHEAR, computeJustifyExtra, lineVisualOrder, settableInOneMatrix, washVeil };
|