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,10 +1,10 @@
|
|
|
1
1
|
import { dict, name, ref, stream, unicodeString } from "./objects.js";
|
|
2
2
|
import { embedTtfFont } from "./cid-font.js";
|
|
3
3
|
import { preparePdfEncryption } from "./encryption.js";
|
|
4
|
-
import {
|
|
4
|
+
import { pageLayers, paintPlan } from "../layout/page-doc.js";
|
|
5
5
|
import { reverseByCodePoint } from "../core/bidi/index.js";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import "../layout/styled-layout.js";
|
|
7
|
+
import { BORDER_DASHES, FAUX_ITALIC_SHEAR, computeJustifyExtra, lineVisualOrder, settableInOneMatrix, washVeil } from "../layout/line-paint.js";
|
|
8
8
|
import { emitClipPath, emitVectorShape, shadowBlurLayers } from "./vector-graphics.js";
|
|
9
9
|
import { buildGradientAlphaMask, buildGradientPattern, shapeBbox } from "./shading.js";
|
|
10
10
|
import { warpGlyphMatrix } from "../core/drawingml/text-warp.js";
|
|
@@ -16,16 +16,6 @@ import { addSignaturePlaceholder } from "./signature.js";
|
|
|
16
16
|
import { buildXmpPacket } from "./xmp.js";
|
|
17
17
|
//#region src/pdf/styled-page-emitter.ts
|
|
18
18
|
var encoder = new TextEncoder();
|
|
19
|
-
function washVeil(wash) {
|
|
20
|
-
if (!wash) return void 0;
|
|
21
|
-
const alpha = 1 - wash.gain;
|
|
22
|
-
if (!(alpha > .004)) return void 0;
|
|
23
|
-
const offset = .5 * (1 - wash.gain) + wash.black;
|
|
24
|
-
return {
|
|
25
|
-
alpha: Math.min(1, alpha),
|
|
26
|
-
grey: Math.max(0, Math.min(1, offset / alpha))
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
19
|
/**
|
|
30
20
|
* The veil operators for a washed picture, to follow its own `/Im Do`: the
|
|
31
21
|
* placement matrix maps the unit square onto the picture as drawn, so the wash
|
|
@@ -268,7 +258,7 @@ function assembleStyledPdf(laid, options, doc) {
|
|
|
268
258
|
tagFor: (structId) => builder.node(structId).type
|
|
269
259
|
};
|
|
270
260
|
}
|
|
271
|
-
const { content: contentBytes, links } =
|
|
261
|
+
const { content: contentBytes, links } = emitPageLayers(page, pageTagging, gradientNames, alphaStateNames, wantDuotone, gradientMaskNames);
|
|
272
262
|
const contentsRef = doc.add(stream({}, contentBytes));
|
|
273
263
|
const pageEntries = {
|
|
274
264
|
Type: name("Page"),
|
|
@@ -611,31 +601,56 @@ function emitTaggedRuns(out, cmds, tagging, body) {
|
|
|
611
601
|
* 24. Scaling them by the width instead drew page-borders-export-case-2.docx's
|
|
612
602
|
* half-point frame as a row of specks a quarter the length it should be.
|
|
613
603
|
*/
|
|
614
|
-
var BORDER_DASHES = new Map([
|
|
615
|
-
["dashed", [8, 2.5]],
|
|
616
|
-
["dashSmallGap", [3, 1]],
|
|
617
|
-
["dotted", [.5, 1]],
|
|
618
|
-
["dashDot", [
|
|
619
|
-
8,
|
|
620
|
-
2.5,
|
|
621
|
-
2.5,
|
|
622
|
-
2.5
|
|
623
|
-
]],
|
|
624
|
-
["dashDotDot", [
|
|
625
|
-
8,
|
|
626
|
-
2.5,
|
|
627
|
-
2.5,
|
|
628
|
-
2.5,
|
|
629
|
-
2.5,
|
|
630
|
-
2.5
|
|
631
|
-
]]
|
|
632
|
-
]);
|
|
633
|
-
var FAUX_ITALIC_SHEAR = .2126;
|
|
634
604
|
/** The PDF dash array for a border style; an empty string means a solid rule. */
|
|
635
605
|
function dashPatternFor(style) {
|
|
636
606
|
const pattern = style !== void 0 ? BORDER_DASHES.get(style) : void 0;
|
|
637
607
|
return pattern ? `[${pattern.map(formatNumber).join(" ")}]` : "";
|
|
638
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* A page's content stream in its layers (pageLayers): its own items, then —
|
|
611
|
+
* when some of them stand IN FRONT of its text (PageItemBase.over) — those,
|
|
612
|
+
* painted again pass by pass over what the first layer left, their own text
|
|
613
|
+
* last; and each group seen through a window of its own (PageItemBase.window)
|
|
614
|
+
* inside a clip to it. Each layer is a balanced stream of its own, so they
|
|
615
|
+
* simply follow one another.
|
|
616
|
+
*/
|
|
617
|
+
function emitPageLayers(page, ...rest) {
|
|
618
|
+
const layers = pageLayers(page.commands);
|
|
619
|
+
if (layers.length === 1 && !layers[0].window) return emitPageContent(page, ...rest);
|
|
620
|
+
const parts = [];
|
|
621
|
+
const links = [];
|
|
622
|
+
for (const layer of layers) {
|
|
623
|
+
const emitted = emitPageContent({
|
|
624
|
+
...page,
|
|
625
|
+
commands: layer.items
|
|
626
|
+
}, ...rest);
|
|
627
|
+
links.push(...emitted.links);
|
|
628
|
+
const w = layer.window;
|
|
629
|
+
if (!w) {
|
|
630
|
+
parts.push(emitted.content);
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (emitted.content.length === 0) continue;
|
|
634
|
+
const rect = [
|
|
635
|
+
w.x,
|
|
636
|
+
page.height - w.y - w.height,
|
|
637
|
+
w.width,
|
|
638
|
+
w.height
|
|
639
|
+
].map(formatNumber);
|
|
640
|
+
parts.push(encoder.encode(`q ${rect.join(" ")} re W n`), emitted.content, encoder.encode("Q"));
|
|
641
|
+
}
|
|
642
|
+
const content = new Uint8Array(parts.reduce((sum, p) => sum + p.length, 0) + Math.max(0, parts.length - 1));
|
|
643
|
+
let at = 0;
|
|
644
|
+
parts.forEach((p, i) => {
|
|
645
|
+
if (i > 0) content[at++] = 10;
|
|
646
|
+
content.set(p, at);
|
|
647
|
+
at += p.length;
|
|
648
|
+
});
|
|
649
|
+
return {
|
|
650
|
+
content,
|
|
651
|
+
links
|
|
652
|
+
};
|
|
653
|
+
}
|
|
639
654
|
function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneStateFor, gradientMaskNames) {
|
|
640
655
|
const commands = page.commands;
|
|
641
656
|
const links = [];
|
|
@@ -1414,9 +1429,6 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
|
|
|
1414
1429
|
links
|
|
1415
1430
|
};
|
|
1416
1431
|
}
|
|
1417
|
-
function lineVisualOrder(line) {
|
|
1418
|
-
return reorderVisual(line.tokens.map((t) => t.bidiLevel));
|
|
1419
|
-
}
|
|
1420
1432
|
/**
|
|
1421
1433
|
* The operators that put an image XObject in a box — ISO 32000-1 §8.9.5.1: the
|
|
1422
1434
|
* unit square scaled to the box and moved to `(x, y)`, its bottom-left corner.
|
|
@@ -1460,40 +1472,6 @@ function rotateAboutCm(cx, cy, deg) {
|
|
|
1460
1472
|
const s = Math.sin(rad);
|
|
1461
1473
|
return `${formatNumber(c)} ${formatNumber(s)} ${formatNumber(-s)} ${formatNumber(c)} ${formatNumber(cx - cx * c + cy * s)} ${formatNumber(cy - cx * s - cy * c)} cm`;
|
|
1462
1474
|
}
|
|
1463
|
-
/**
|
|
1464
|
-
* Whether a line is ONE text matrix and a run of glyphs: nothing in it placed
|
|
1465
|
-
* on its own — a tab, a stretched space, a picture, a formula, a right-to-left
|
|
1466
|
-
* run, a rise, a faked italic — and nothing drawn beside its glyphs, a run's
|
|
1467
|
-
* shading, a highlight or a rule. Only such a line can be turned by its matrix
|
|
1468
|
-
* alone; any other is placed token by token in page space.
|
|
1469
|
-
*
|
|
1470
|
-
* @param line The laid-out line.
|
|
1471
|
-
* @returns True when a single `Tm` sets the whole of it.
|
|
1472
|
-
*/
|
|
1473
|
-
function settableInOneMatrix(line) {
|
|
1474
|
-
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);
|
|
1475
|
-
}
|
|
1476
|
-
function computeJustifyExtra(line) {
|
|
1477
|
-
const alignment = line.resolved.alignment;
|
|
1478
|
-
if (alignment !== "both" && alignment !== "distribute") return 0;
|
|
1479
|
-
if (line.noJustify === true) return 0;
|
|
1480
|
-
if (line.isLastInParagraph && alignment !== "distribute") return 0;
|
|
1481
|
-
let last = line.tokens.length - 1;
|
|
1482
|
-
while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
|
|
1483
|
-
let spaces = 0;
|
|
1484
|
-
let narrowest = Infinity;
|
|
1485
|
-
let contentWidthPt = 0;
|
|
1486
|
-
for (let i = 0; i <= last; i++) {
|
|
1487
|
-
const tok = line.tokens[i];
|
|
1488
|
-
contentWidthPt += tok.widthPt;
|
|
1489
|
-
if (!tok.isSpace) continue;
|
|
1490
|
-
spaces++;
|
|
1491
|
-
if (tok.widthPt < narrowest) narrowest = tok.widthPt;
|
|
1492
|
-
}
|
|
1493
|
-
if (spaces === 0) return 0;
|
|
1494
|
-
const extra = (line.availableWidthPt - contentWidthPt) / spaces;
|
|
1495
|
-
return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
|
|
1496
|
-
}
|
|
1497
1475
|
function hexToRgb01(hex) {
|
|
1498
1476
|
return [
|
|
1499
1477
|
parseInt(hex.slice(0, 2), 16) / 255,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PdfDocument } from "./writer.js";
|
|
2
|
-
import { layoutStyledDocument } from "../layout/styled-layout.js";
|
|
3
2
|
import "../layout/page-doc.js";
|
|
3
|
+
import { layoutStyledDocument } from "../layout/styled-layout.js";
|
|
4
4
|
import { emitStyledPdf, emitStyledPdfEncrypted } from "./styled-page-emitter.js";
|
|
5
5
|
//#region src/pdf/styled-page-renderer.ts
|
|
6
6
|
/**
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { knowsFamily, resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
|
|
2
2
|
import { parseTtf } from "../core/font/ttf-parser.js";
|
|
3
3
|
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
4
|
+
import { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable, winAnsiLatinName } from "./encodings.js";
|
|
5
|
+
import { cffCidToGid, cffFsType, cffNameToGid, cffOutlineSource, cffSpaceAdvance, openTypeCff } from "./cff-outline.js";
|
|
6
|
+
import { cmapSubtables, outlineSource, postGlyphNames, sfntFsType, sfntSpaceAdvance } from "./glyf-outline.js";
|
|
4
7
|
import { parseToUnicodeCMap } from "./cmap.js";
|
|
5
8
|
import { decodePredefined, predefinedCMap, splitPredefined } from "./predefined-cmap.js";
|
|
6
9
|
import { isZapfDingbats, zapfDingbatsChar } from "./dingbats.js";
|
|
7
10
|
import { textForGlyphName } from "./glyph-names.js";
|
|
8
|
-
import { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable, winAnsiLatinName } from "./encodings.js";
|
|
9
|
-
import { cffCidToGid, cffFsType, cffNameToGid, cffOutlineSource, cffSpaceAdvance, openTypeCff } from "./cff-outline.js";
|
|
10
11
|
import { type1Font } from "./type1-outline.js";
|
|
11
|
-
import { cmapSubtables, outlineSource, postGlyphNames, sfntFsType, sfntSpaceAdvance } from "./glyf-outline.js";
|
|
12
12
|
import { standardFace, standardWidth } from "./standard-widths.js";
|
|
13
13
|
import { eachPageFont, embeddedFontName, hasLiftableProgram, programStyle } from "./embedded-fonts.js";
|
|
14
14
|
//#region src/pdf-reader/font.ts
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { FontResource, LaidOutDocument, TextLineItem } from '../layout/page-doc.js';
|
|
2
|
+
import { ImageCrop } from '../core/document-model/index.js';
|
|
3
|
+
import { VectorShape } from '../core/vector.js';
|
|
4
|
+
/** The glyph outlines of a page's faces, each defined once and used by id. */
|
|
5
|
+
export declare class SvgGlyphs {
|
|
6
|
+
private readonly sources;
|
|
7
|
+
private readonly ids;
|
|
8
|
+
private readonly defs;
|
|
9
|
+
private readonly faceIds;
|
|
10
|
+
/**
|
|
11
|
+
* The id of the path that draws glyph `gid` of `font`, defining it on first
|
|
12
|
+
* use: `null` for a glyph with no ink (a space), `undefined` where the face
|
|
13
|
+
* carries no outlines this reads — the caller then sets the token as text.
|
|
14
|
+
*
|
|
15
|
+
* @param font The face.
|
|
16
|
+
* @param gid The glyph.
|
|
17
|
+
* @returns The `<path>` id, `null`, or `undefined`.
|
|
18
|
+
*/
|
|
19
|
+
use(font: FontResource, gid: number): string | null | undefined;
|
|
20
|
+
/** The `<defs>` holding every glyph used, or nothing when none was. */
|
|
21
|
+
defsXml(): string;
|
|
22
|
+
private sourceOf;
|
|
23
|
+
}
|
|
24
|
+
/** What drawing a line needs from the writer around it. */
|
|
25
|
+
export interface SvgLineCtx {
|
|
26
|
+
readonly glyphs: SvgGlyphs;
|
|
27
|
+
/** The document-wide counter generated ids are numbered from. */
|
|
28
|
+
readonly ids: {
|
|
29
|
+
n: number;
|
|
30
|
+
};
|
|
31
|
+
readonly laid: LaidOutDocument;
|
|
32
|
+
/** A picture in a box, as the writer draws one. */
|
|
33
|
+
readonly image: (out: Array<string>, placed: PlacedImage) => void;
|
|
34
|
+
/** A vector shape, as the writer draws one. */
|
|
35
|
+
readonly shape: (out: Array<string>, shape: VectorShape) => void;
|
|
36
|
+
}
|
|
37
|
+
/** A picture placed in a box on the page — its top-left corner and size, in points. */
|
|
38
|
+
export interface PlacedImage {
|
|
39
|
+
readonly resourceName: string;
|
|
40
|
+
readonly x: number;
|
|
41
|
+
readonly y: number;
|
|
42
|
+
readonly width: number;
|
|
43
|
+
readonly height: number;
|
|
44
|
+
readonly crop?: ImageCrop;
|
|
45
|
+
readonly rotationDeg?: number;
|
|
46
|
+
readonly flipH?: boolean;
|
|
47
|
+
readonly flipV?: boolean;
|
|
48
|
+
readonly alpha?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Draw one laid-out line: what stands behind its glyphs, then the glyphs, a
|
|
52
|
+
* picture or formula wherever one stands in it.
|
|
53
|
+
*
|
|
54
|
+
* @param out The SVG fragment sink.
|
|
55
|
+
* @param item The line, as the layout placed it.
|
|
56
|
+
* @param ctx The glyph definitions and the writer's own drawing.
|
|
57
|
+
*/
|
|
58
|
+
export declare function emitSvgLine(out: Array<string>, item: TextLineItem, ctx: SvgLineCtx): void;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { svgPathData } from "../core/vector.js";
|
|
2
|
+
import { reverseByCodePoint } from "../core/bidi/index.js";
|
|
3
|
+
import { FAUX_ITALIC_SHEAR, computeJustifyExtra, lineVisualOrder } from "../layout/line-paint.js";
|
|
4
|
+
import { warpGlyphMatrix } from "../core/drawingml/text-warp.js";
|
|
5
|
+
import { sanitizeHref } from "../core/links.js";
|
|
6
|
+
import { cffOutlineSource, openTypeCff } from "../pdf-reader/cff-outline.js";
|
|
7
|
+
import { outlineSource } from "../pdf-reader/glyf-outline.js";
|
|
8
|
+
//#region src/svg/svg-text.ts
|
|
9
|
+
/** The colour of a comment range's highlight — the PDF emitter's. */
|
|
10
|
+
var HIGHLIGHT_HEX = "fff3a3";
|
|
11
|
+
/** The glyph outlines of a page's faces, each defined once and used by id. */
|
|
12
|
+
var SvgGlyphs = class {
|
|
13
|
+
sources = /* @__PURE__ */ new Map();
|
|
14
|
+
ids = /* @__PURE__ */ new Map();
|
|
15
|
+
defs = [];
|
|
16
|
+
faceIds = /* @__PURE__ */ new Map();
|
|
17
|
+
/**
|
|
18
|
+
* The id of the path that draws glyph `gid` of `font`, defining it on first
|
|
19
|
+
* use: `null` for a glyph with no ink (a space), `undefined` where the face
|
|
20
|
+
* carries no outlines this reads — the caller then sets the token as text.
|
|
21
|
+
*
|
|
22
|
+
* @param font The face.
|
|
23
|
+
* @param gid The glyph.
|
|
24
|
+
* @returns The `<path>` id, `null`, or `undefined`.
|
|
25
|
+
*/
|
|
26
|
+
use(font, gid) {
|
|
27
|
+
const source = this.sourceOf(font);
|
|
28
|
+
if (!source) return void 0;
|
|
29
|
+
let face = this.faceIds.get(font);
|
|
30
|
+
if (face === void 0) {
|
|
31
|
+
face = this.faceIds.size;
|
|
32
|
+
this.faceIds.set(font, face);
|
|
33
|
+
}
|
|
34
|
+
const key = `g${String(face)}-${String(gid)}`;
|
|
35
|
+
const known = this.ids.get(key);
|
|
36
|
+
if (known !== void 0) return known;
|
|
37
|
+
const path = source.path(gid);
|
|
38
|
+
if (!path || path.length === 0) {
|
|
39
|
+
this.ids.set(key, null);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
this.defs.push(`<path id="${key}" d="${svgPathData(path, fmtEm)}"/>`);
|
|
43
|
+
this.ids.set(key, key);
|
|
44
|
+
return key;
|
|
45
|
+
}
|
|
46
|
+
/** The `<defs>` holding every glyph used, or nothing when none was. */
|
|
47
|
+
defsXml() {
|
|
48
|
+
return this.defs.length > 0 ? `<defs>${this.defs.join("")}</defs>` : "";
|
|
49
|
+
}
|
|
50
|
+
sourceOf(font) {
|
|
51
|
+
let source = this.sources.get(font);
|
|
52
|
+
if (source === void 0) {
|
|
53
|
+
const raw = font.parsed.raw;
|
|
54
|
+
const cff = font.parsed.tables.has("CFF ") ? openTypeCff(raw) : void 0;
|
|
55
|
+
source = (cff ? cffOutlineSource(cff) : outlineSource(raw)) ?? null;
|
|
56
|
+
this.sources.set(font, source);
|
|
57
|
+
}
|
|
58
|
+
return source ?? void 0;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Draw one laid-out line: what stands behind its glyphs, then the glyphs, a
|
|
63
|
+
* picture or formula wherever one stands in it.
|
|
64
|
+
*
|
|
65
|
+
* @param out The SVG fragment sink.
|
|
66
|
+
* @param item The line, as the layout placed it.
|
|
67
|
+
* @param ctx The glyph definitions and the writer's own drawing.
|
|
68
|
+
*/
|
|
69
|
+
function emitSvgLine(out, item, ctx) {
|
|
70
|
+
const clip = item.clip;
|
|
71
|
+
if (clip) {
|
|
72
|
+
const id = `clip${String(ctx.ids.n++)}`;
|
|
73
|
+
out.push(`<clipPath id="${id}"><rect x="${fmt(clip.x)}" y="${fmt(clip.y)}" width="${fmt(clip.width)}" height="${fmt(clip.height)}"/></clipPath>`, `<g clip-path="url(#${id})">`);
|
|
74
|
+
}
|
|
75
|
+
if (item.warp) emitWarped(out, item, ctx);
|
|
76
|
+
else if (item.rotationDeg) {
|
|
77
|
+
out.push(`<g transform="rotate(${fmt(-item.rotationDeg)} ${fmt(item.originX)} ${fmt(item.baselineY)})">`);
|
|
78
|
+
emitFlat(out, item, ctx);
|
|
79
|
+
out.push("</g>");
|
|
80
|
+
} else emitFlat(out, item, ctx);
|
|
81
|
+
if (clip) out.push("</g>");
|
|
82
|
+
}
|
|
83
|
+
/** A line set along its baseline, token by token. */
|
|
84
|
+
function emitFlat(out, item, ctx) {
|
|
85
|
+
const line = item.line;
|
|
86
|
+
const originX = item.originX;
|
|
87
|
+
const baseline = item.baselineY;
|
|
88
|
+
const extraPerSpace = computeJustifyExtra(line);
|
|
89
|
+
const advance = (tok) => tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
|
|
90
|
+
const lineFs = line.maxFontSizePt || 12;
|
|
91
|
+
const boxAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
|
|
92
|
+
const boxDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
|
|
93
|
+
let x = originX;
|
|
94
|
+
for (const tok of line.tokens) {
|
|
95
|
+
const w = advance(tok);
|
|
96
|
+
if (tok.kind === "text") {
|
|
97
|
+
const hex = tok.resolvedRun.shadingColorHex ?? (tok.highlight === true ? HIGHLIGHT_HEX : void 0);
|
|
98
|
+
if (hex !== void 0) out.push(`<rect x="${fmt(x)}" y="${fmt(baseline - boxAscent)}" width="${fmt(w)}" height="${fmt(boxAscent + boxDescent)}" fill="#${hex}"/>`);
|
|
99
|
+
}
|
|
100
|
+
x += w;
|
|
101
|
+
}
|
|
102
|
+
x = originX;
|
|
103
|
+
for (const tok of line.tokens) {
|
|
104
|
+
const w = advance(tok);
|
|
105
|
+
if (tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike)) emitDecoration(out, tok, x, w, baseline);
|
|
106
|
+
x += w;
|
|
107
|
+
}
|
|
108
|
+
const order = line.tokens.some((t) => t.bidiLevel % 2 === 1) ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
|
|
109
|
+
x = originX;
|
|
110
|
+
for (const i of order) {
|
|
111
|
+
const tok = line.tokens[i];
|
|
112
|
+
if (tok.kind === "image") emitImageToken(out, tok, x, baseline, ctx);
|
|
113
|
+
else if (tok.kind === "math") emitMathToken(out, tok, x, baseline, ctx);
|
|
114
|
+
else emitTextToken(out, tok, x, baseline, ctx);
|
|
115
|
+
x += advance(tok);
|
|
116
|
+
}
|
|
117
|
+
const words = line.tokens.map((t) => t.kind === "text" ? t.text : "").join("");
|
|
118
|
+
if (words.trim().length > 0) out.push(`<text x="${fmt(originX)}" y="${fmt(baseline)}" font-size="${fmt(lineFs)}" textLength="${fmt(x - originX)}" lengthAdjust="spacingAndGlyphs" fill-opacity="0" xml:space="preserve">${escapeXml(words)}</text>`);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* One run's glyphs from `x` along the baseline: the shaped glyphs of its text
|
|
122
|
+
* (reversed for a right-to-left run, as the PDF shows it), each at the advance
|
|
123
|
+
* the shaper gave it plus the run's letter spacing.
|
|
124
|
+
*/
|
|
125
|
+
function emitTextToken(out, tok, x, baseline, ctx) {
|
|
126
|
+
if (tok.text.length === 0) return;
|
|
127
|
+
const size = tok.fontSizePt;
|
|
128
|
+
const scale = (tok.synthetic?.widthScale ?? 1) * (tok.resolvedRun.widthScale ?? 1);
|
|
129
|
+
const slant = tok.synthetic?.italic === true ? FAUX_ITALIC_SHEAR : 0;
|
|
130
|
+
const rise = tok.risePt ?? 0;
|
|
131
|
+
const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
|
|
132
|
+
const run = tok.font.measure.glyphRun(text);
|
|
133
|
+
const upem = tok.font.parsed.unitsPerEm;
|
|
134
|
+
const spacingEm = (tok.resolvedRun.letterSpacingPt ?? 0) / (size * scale);
|
|
135
|
+
const uses = [];
|
|
136
|
+
let pen = 0;
|
|
137
|
+
for (let g = 0; g < run.gids.length; g++) {
|
|
138
|
+
const id = ctx.glyphs.use(tok.font, run.gids[g]);
|
|
139
|
+
if (id === void 0) {
|
|
140
|
+
emitAsText(out, tok, x, baseline);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (id !== null) uses.push(`<use href="#${id}"${pen !== 0 ? ` x="${fmtEm(pen)}"` : ""}/>`);
|
|
144
|
+
pen += (run.advances[g] ?? 0) / upem + spacingEm;
|
|
145
|
+
}
|
|
146
|
+
if (uses.length === 0) return;
|
|
147
|
+
const outline = tok.resolvedRun.textOutline;
|
|
148
|
+
const strokeWidth = outline !== void 0 ? outline.widthPt : tok.synthetic?.bold === true ? size * .03 : 0;
|
|
149
|
+
const stroke = strokeWidth > 0 ? ` stroke="#${outline?.colorHex ?? tok.resolvedRun.colorHex}" stroke-width="${fmtEm(strokeWidth / size)}"` : "";
|
|
150
|
+
const group = `<g transform="matrix(${fmt(size * scale)} 0 ${fmt(slant * size)} ${fmt(-size)} ${fmt(x)} ${fmt(baseline - rise)})" fill="#${tok.resolvedRun.colorHex}"${stroke}>${uses.join("")}</g>`;
|
|
151
|
+
const href = tok.href !== void 0 ? sanitizeHref(tok.href) : void 0;
|
|
152
|
+
out.push(href !== void 0 ? `<a href="${escapeXml(href)}">${group}</a>` : group);
|
|
153
|
+
}
|
|
154
|
+
/** A run of a face whose outlines cannot be read: its characters, for the viewer's fonts to set. */
|
|
155
|
+
function emitAsText(out, tok, x, baseline) {
|
|
156
|
+
if (tok.text.trim().length === 0) return;
|
|
157
|
+
out.push(`<text x="${fmt(x)}" y="${fmt(baseline - (tok.risePt ?? 0))}" font-family="sans-serif" font-size="${fmt(tok.fontSizePt)}" fill="#${tok.resolvedRun.colorHex}">${escapeXml(tok.text)}</text>`);
|
|
158
|
+
}
|
|
159
|
+
/** A run's underline and strikethrough, where the PDF emitter draws them. */
|
|
160
|
+
function emitDecoration(out, tok, x, w, baseline) {
|
|
161
|
+
const size = tok.fontSizePt;
|
|
162
|
+
const thickness = Math.max(.4, size * .06);
|
|
163
|
+
const style = tok.resolvedRun.underline;
|
|
164
|
+
if (style !== "none") {
|
|
165
|
+
const hex = tok.resolvedRun.underlineColorHex ?? tok.resolvedRun.colorHex;
|
|
166
|
+
const t = style === "thick" || style === "dottedHeavy" || style === "dashHeavy" ? thickness * 2 : thickness;
|
|
167
|
+
const top = baseline + size * .12;
|
|
168
|
+
const dashed = style === "dotted" || style === "dottedHeavy" ? [t, t * 2] : style === "dash" || style === "dashHeavy" ? [t * 4, t * 3] : void 0;
|
|
169
|
+
if (dashed) out.push(`<line x1="${fmt(x)}" y1="${fmt(top + t / 2)}" x2="${fmt(x + w)}" y2="${fmt(top + t / 2)}" stroke="#${hex}" stroke-width="${fmt(t)}" stroke-dasharray="${dashed.map(fmt).join(" ")}"/>`);
|
|
170
|
+
else {
|
|
171
|
+
out.push(`<rect x="${fmt(x)}" y="${fmt(top)}" width="${fmt(w)}" height="${fmt(t)}" fill="#${hex}"/>`);
|
|
172
|
+
if (style === "double") out.push(`<rect x="${fmt(x)}" y="${fmt(top + t * 2)}" width="${fmt(w)}" height="${fmt(t)}" fill="#${hex}"/>`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (tok.resolvedRun.strike) out.push(`<rect x="${fmt(x)}" y="${fmt(baseline - size * .26 - thickness)}" width="${fmt(w)}" height="${fmt(thickness)}" fill="#${tok.resolvedRun.colorHex}"/>`);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* An inline picture standing on the baseline, its bottom edge on it — or a
|
|
179
|
+
* metafile, which draws itself from that corner in a frame of its own (y up).
|
|
180
|
+
*/
|
|
181
|
+
function emitImageToken(out, tok, x, baseline, ctx) {
|
|
182
|
+
const b = tok.drawBox;
|
|
183
|
+
const left = x + (b?.dxPt ?? 0);
|
|
184
|
+
const bottom = baseline - (b?.dyPt ?? 0);
|
|
185
|
+
const width = b?.widthPt ?? tok.widthPt;
|
|
186
|
+
const height = b?.heightPt ?? tok.heightPt;
|
|
187
|
+
const meta = tok.metafile;
|
|
188
|
+
if (meta) {
|
|
189
|
+
for (const img of meta.images ?? []) {
|
|
190
|
+
if (img.resourceName === "") continue;
|
|
191
|
+
ctx.image(out, {
|
|
192
|
+
resourceName: img.resourceName,
|
|
193
|
+
x: left + img.x,
|
|
194
|
+
y: bottom - img.y - img.height,
|
|
195
|
+
width: img.width,
|
|
196
|
+
height: img.height,
|
|
197
|
+
...img.rotationDeg !== void 0 ? { rotationDeg: img.rotationDeg } : {}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
for (const sh of meta.shapes) ctx.shape(out, {
|
|
201
|
+
...sh,
|
|
202
|
+
transform: [
|
|
203
|
+
1,
|
|
204
|
+
0,
|
|
205
|
+
0,
|
|
206
|
+
-1,
|
|
207
|
+
left,
|
|
208
|
+
bottom
|
|
209
|
+
]
|
|
210
|
+
});
|
|
211
|
+
for (const t of meta.texts) {
|
|
212
|
+
const first = t.line.tokens.find((k) => k.kind === "text");
|
|
213
|
+
if (first?.kind !== "text") continue;
|
|
214
|
+
emitTextToken(out, first, left + t.x, bottom - t.y, ctx);
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!tok.imageResourceName) return;
|
|
219
|
+
const shadow = tok.shadow;
|
|
220
|
+
if (shadow) {
|
|
221
|
+
const id = `shadow${String(ctx.ids.n++)}`;
|
|
222
|
+
const blur = shadow.blurPt > 0;
|
|
223
|
+
if (blur) out.push(`<filter id="${id}" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="${fmt(shadow.blurPt / 2)}"/></filter>`);
|
|
224
|
+
out.push(`<rect x="${fmt(left + shadow.dxPt)}" y="${fmt(bottom - height + shadow.dyPt)}" width="${fmt(width)}" height="${fmt(height)}" fill="#${shadow.colorHex}" opacity="${fmt(shadow.alpha)}"${blur ? ` filter="url(#${id})"` : ""}/>`);
|
|
225
|
+
}
|
|
226
|
+
ctx.image(out, {
|
|
227
|
+
resourceName: tok.imageResourceName,
|
|
228
|
+
x: left,
|
|
229
|
+
y: bottom - height,
|
|
230
|
+
width,
|
|
231
|
+
height,
|
|
232
|
+
...tok.crop ? { crop: tok.crop } : {},
|
|
233
|
+
...tok.rotationDeg !== void 0 ? { rotationDeg: tok.rotationDeg } : {},
|
|
234
|
+
...tok.flipH === true ? { flipH: true } : {},
|
|
235
|
+
...tok.flipV === true ? { flipV: true } : {}
|
|
236
|
+
});
|
|
237
|
+
const frame = tok.outline;
|
|
238
|
+
if (frame) out.push(`<rect x="${fmt(left)}" y="${fmt(bottom - height)}" width="${fmt(width)}" height="${fmt(height)}" fill="none" stroke="#${frame.colorHex}" stroke-width="${fmt(frame.widthPt)}"/>`);
|
|
239
|
+
}
|
|
240
|
+
/** An inline formula: its glyphs, rules and paths, placed from its origin on the baseline (y up). */
|
|
241
|
+
function emitMathToken(out, tok, x, baseline, ctx) {
|
|
242
|
+
for (const it of tok.items) if (it.kind === "glyph") {
|
|
243
|
+
const run = it.font.measure.glyphRun(it.text);
|
|
244
|
+
const upem = it.font.parsed.unitsPerEm;
|
|
245
|
+
const uses = [];
|
|
246
|
+
let pen = 0;
|
|
247
|
+
for (let g = 0; g < run.gids.length; g++) {
|
|
248
|
+
const id = ctx.glyphs.use(it.font, run.gids[g]);
|
|
249
|
+
if (id) uses.push(`<use href="#${id}"${pen !== 0 ? ` x="${fmtEm(pen)}"` : ""}/>`);
|
|
250
|
+
pen += (run.advances[g] ?? 0) / upem;
|
|
251
|
+
}
|
|
252
|
+
if (uses.length > 0) out.push(`<g transform="matrix(${fmt(it.sizePt)} 0 0 ${fmt(-it.sizePt)} ${fmt(x + it.x)} ${fmt(baseline - it.y)})" fill="#000000">${uses.join("")}</g>`);
|
|
253
|
+
} else if (it.kind === "rule") out.push(`<rect x="${fmt(x + it.x)}" y="${fmt(baseline - it.y - it.h)}" width="${fmt(it.w)}" height="${fmt(it.h)}" fill="#000000"/>`);
|
|
254
|
+
else ctx.shape(out, {
|
|
255
|
+
paths: [{ segments: it.segments }],
|
|
256
|
+
...it.fill === true ? { fillColorHex: "000000" } : {},
|
|
257
|
+
...it.strokeWidthPt !== void 0 ? { stroke: {
|
|
258
|
+
colorHex: "000000",
|
|
259
|
+
widthPt: it.strokeWidthPt
|
|
260
|
+
} } : {},
|
|
261
|
+
transform: [
|
|
262
|
+
1,
|
|
263
|
+
0,
|
|
264
|
+
0,
|
|
265
|
+
-1,
|
|
266
|
+
x,
|
|
267
|
+
baseline
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* §20.1.9.10 — WordArt: every glyph at its own point on the warp's curve, each
|
|
273
|
+
* with its own matrix. The token's glyphs are rescaled to the width it was
|
|
274
|
+
* measured at, as the PDF does, so the block still ends where the layout said.
|
|
275
|
+
*/
|
|
276
|
+
function emitWarped(out, item, ctx) {
|
|
277
|
+
const warp = item.warp;
|
|
278
|
+
let cursor = item.originX;
|
|
279
|
+
for (const tok of item.line.tokens) {
|
|
280
|
+
if (tok.kind !== "text" || tok.isSpace) {
|
|
281
|
+
cursor += tok.widthPt;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const size = tok.fontSizePt;
|
|
285
|
+
const chars = [...tok.text];
|
|
286
|
+
const raw = chars.map((c) => tok.font.measure.textWidthPt(c, size));
|
|
287
|
+
const total = raw.reduce((a, b) => a + b, 0);
|
|
288
|
+
const k = total > 0 ? tok.widthPt / total : 1;
|
|
289
|
+
for (let i = 0; i < chars.length; i++) {
|
|
290
|
+
const adv = raw[i] * k;
|
|
291
|
+
const m = warpGlyphMatrix(warp, cursor, adv, item.baselineY);
|
|
292
|
+
cursor += adv;
|
|
293
|
+
if (!m) continue;
|
|
294
|
+
const run = tok.font.measure.glyphRun(chars[i]);
|
|
295
|
+
const upem = tok.font.parsed.unitsPerEm;
|
|
296
|
+
const uses = [];
|
|
297
|
+
let pen = 0;
|
|
298
|
+
for (let g = 0; g < run.gids.length; g++) {
|
|
299
|
+
const id = ctx.glyphs.use(tok.font, run.gids[g]);
|
|
300
|
+
if (id) uses.push(`<use href="#${id}"${pen !== 0 ? ` x="${fmtEm(pen)}"` : ""}/>`);
|
|
301
|
+
pen += (run.advances[g] ?? 0) / upem;
|
|
302
|
+
}
|
|
303
|
+
if (uses.length === 0) continue;
|
|
304
|
+
out.push(`<g transform="matrix(${fmt(m[0] * size)} ${fmt(m[1] * size)} ${fmt(m[2] * size)} ${fmt(m[3] * size)} ${fmt(m[4])} ${fmt(m[5])})" fill="#${tok.resolvedRun.colorHex}">${uses.join("")}</g>`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function escapeXml(s) {
|
|
309
|
+
return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
310
|
+
}
|
|
311
|
+
/** A page coordinate, to the hundredth of a point. */
|
|
312
|
+
function fmt(n) {
|
|
313
|
+
const r = Math.round(n * 100) / 100;
|
|
314
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
315
|
+
}
|
|
316
|
+
/** A coordinate in a one-unit em, to a ten-thousandth of it. */
|
|
317
|
+
function fmtEm(n) {
|
|
318
|
+
const r = Math.round(n * 1e4) / 1e4;
|
|
319
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
export { SvgGlyphs, emitSvgLine };
|