@pptx-studio/render-dom 0.1.1 → 0.3.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/CHANGELOG.md +53 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @pptx-studio/render-dom
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 33a6f9d: Line ends are drawn, and strokes round to the display's own pixels.
|
|
8
|
+
|
|
9
|
+
`render-svg` draws the six `a:headEnd`/`a:tailEnd` heads as markers on the open ends of a stroked
|
|
10
|
+
path, sized from the pen PowerPoint's export uses at every width: two points at or under a
|
|
11
|
+
two-point line, the whole-pixel stroke above it, never under a device pixel (`markerPen` in `paint`,
|
|
12
|
+
49 of 49 F2 cases). No renderer had drawn a line end before.
|
|
13
|
+
|
|
14
|
+
`RenderOptions.devicePixelRatio` multiplies the named width for the stroke rule alone, so a slide at
|
|
15
|
+
100 % on a 2x display carries the strokes of 200 % at the same CSS size; `render-dom` passes it
|
|
16
|
+
through. The studio page passes the display's ratio and re-mounts when the window moves to another
|
|
17
|
+
display.
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Updated dependencies [33a6f9d]
|
|
22
|
+
- @pptx-studio/paint@0.3.0
|
|
23
|
+
- @pptx-studio/render-svg@0.4.0
|
|
24
|
+
- @pptx-studio/model@0.1.3
|
|
25
|
+
|
|
26
|
+
## 0.2.0
|
|
27
|
+
|
|
28
|
+
### Minor Changes
|
|
29
|
+
|
|
30
|
+
- 2f84efc: Strokes are drawn at the width the slide is drawn at, so a zoom is a fresh mount and never a resize.
|
|
31
|
+
|
|
32
|
+
PowerPoint's export, asked at seven widths from 120 to 3840 pixels, draws a stroke at its width
|
|
33
|
+
rounded to whole device pixels and never under one, and a zero width as one pixel — 42/42 hairlines
|
|
34
|
+
and 56/56 thin strokes, against every hand-written alternative (F2, `zoom.json`). `paint` gains
|
|
35
|
+
`deviceStrokeWidth(widthEmu, pxPerPt)` and the floor it holds, `MIN_PX_PER_PT`. When
|
|
36
|
+
`RenderOptions.width` names a device, `render-svg` rounds `stroke-width` and `stroke-dasharray` to
|
|
37
|
+
its pixels and draws a dashed hairline solid; without one, the true width, and a hairline as
|
|
38
|
+
`stroke-width="1" vector-effect="non-scaling-stroke"`. `MountedSlide.resize()` is gone from
|
|
39
|
+
`render-dom`: mount again at the new width. The text layout is the same at every width.
|
|
40
|
+
|
|
41
|
+
Bullets are drawn. `render-svg` lays out `a:buChar`, `a:buAutoNum` and `a:buBlip` through
|
|
42
|
+
`@pptx-studio/text` and emits them in both renderers; an autonumber scheme the measurement could not
|
|
43
|
+
read (`arabic1Minus`, `arabic2Minus`) throws `TEXT_AUTONUMBER_UNMEASURED` rather than drawing a
|
|
44
|
+
guess. A bordered picture keeps its picture: the clipped outline band is its own path. An effect
|
|
45
|
+
filter is written in points inside a `scale(12700)` pair — a rotated glow in an EMU-unit filter
|
|
46
|
+
cost Chromium nine seconds a shape, and thirty milliseconds in points. `mediaFromStore(store)` is
|
|
47
|
+
the one media resolver, shared by the CLI, the harness and the page. ADR 0054.
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Updated dependencies [2f84efc]
|
|
52
|
+
- @pptx-studio/paint@0.2.0
|
|
53
|
+
- @pptx-studio/render-svg@0.3.0
|
|
54
|
+
- @pptx-studio/model@0.1.2
|
|
55
|
+
|
|
3
56
|
## 0.1.1
|
|
4
57
|
|
|
5
58
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -50,8 +50,6 @@ interface MountedSlide {
|
|
|
50
50
|
readonly placed: readonly Placed[];
|
|
51
51
|
/** The element drawing a given `p:cNvPr/@id`, for hit-testing and selection. */
|
|
52
52
|
element(cNvPrId: number): SVGElement | null;
|
|
53
|
-
/** Resize without re-laying anything out: the viewBox does the work. */
|
|
54
|
-
resize(width: number, height: number): void;
|
|
55
53
|
/** Detach from the host. The nodes stay valid, so a caller may re-attach. */
|
|
56
54
|
unmount(): void;
|
|
57
55
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/mount.ts","../src/overlay.ts","../src/text/layer.ts"],"mappings":";;;;;;;;;;;;KASY;;;;;cAMC,iCAAiC;cAKjC,uBAAuB;WACzB,MAAM;EAEH,YAAA,MAAM,oBAAoB;;iBAOxB,iBAAiB,iBAAiB,SAAS;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/mount.ts","../src/overlay.ts","../src/text/layer.ts"],"mappings":";;;;;;;;;;;;KASY;;;;;cAMC,iCAAiC;cAKjC,uBAAuB;WACzB,MAAM;EAEH,YAAA,MAAM,oBAAoB;;iBAOxB,iBAAiB,iBAAiB,SAAS;;;;;;;;;;;;;;iBC4B3C,WAAW,UAAU,UAAU,MAAM,UAAU;UAW9C,qBAAqB;;;;;;;;WAQ3B,WAAW;;UAGL;WACN,MAAM;WACN,iBAAiB;;EAE1B,QAAQ,kBAAkB;;EAE1B;;;;;;;;;iBAmBc,WACd,MAAM,SACN,OAAO,OACP,MAAM,WACN,UAAS,eACR;;;;;;;;iBAuCa,kBACd,OAAO,OACP,MAAM,WACN,UAAS;;;;UC7GM;;WAEN;;WAEA,QAAQ,SAAS;;WAEjB,gBAAgB;;UAGV,4BAA4B;WAClC,WAAW;;EAEpB,YAAY,MAAM;;EAElB,YAAY,MAAM;;UAGH;WACN,MAAM;WACN,SAAS;;EAElB,OAAO,QAAQ;;EAEf;EACA;;;;;;;;;;iBAWc,WAAW,MAAM,eAAe,iBAAiB,kBAAkB;;;;;;;;;;iBAmBnE,YACd,gBAAgB,eAChB,QAAQ,SAAS,mCACP;;;;;;;;iBAsBI,aACd,MAAM,eACN,QAAQ,QACR,UAAS,sBACR;;;;UC9Fc;WACN;WACA;;;UAIM;WACN,OAAO;;WAEP;WACA;WACA;WACA;;WAEA;WACA;;UAGM;WACN,WAAW;;WAEX;;WAEA;;UAGM;WACN,MAAM;;EAEf,QAAQ,kBAAkB;;EAE1B,OAAO;EACP;;;;;;;;;;iBA0Jc,eACd,MAAM,SACN,iBAAiB,cACjB,MAAM,WACN,UAAS,mBACR"}
|
package/dist/index.js
CHANGED
|
@@ -40,12 +40,11 @@ function isRenderDomError(value) {
|
|
|
40
40
|
* image: they belong to **no** namespace and must go through `setAttribute`,
|
|
41
41
|
* with the single exception of `xlink:href`, which this package never emits.
|
|
42
42
|
*
|
|
43
|
-
* ## Zoom is a
|
|
43
|
+
* ## Zoom is a mount at that width, never a re-layout
|
|
44
44
|
*
|
|
45
|
-
* The
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* zoom level changes the line breaks as the user zooms.
|
|
45
|
+
* The `<svg>` carries a `viewBox` in EMU and is sized in CSS pixels. Text is laid out
|
|
46
|
+
* once at one point size, so the line breaks are the same at every zoom; strokes are
|
|
47
|
+
* rounded to the device pixels of the mounted width (F2), so a zoom is another mount.
|
|
49
48
|
*/
|
|
50
49
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
51
50
|
/**
|
|
@@ -100,10 +99,6 @@ function mountSlide(host, sheet, size, options = {}) {
|
|
|
100
99
|
element(cNvPrId) {
|
|
101
100
|
return byId.get(cNvPrId) ?? null;
|
|
102
101
|
},
|
|
103
|
-
resize(width, height) {
|
|
104
|
-
root.setAttribute("width", String(width));
|
|
105
|
-
root.setAttribute("height", String(height));
|
|
106
|
-
},
|
|
107
102
|
unmount() {
|
|
108
103
|
root.remove();
|
|
109
104
|
}
|
|
@@ -398,6 +393,13 @@ function lineElement(document, line) {
|
|
|
398
393
|
style.lineHeight = px(line.strutPt);
|
|
399
394
|
style.whiteSpace = "pre";
|
|
400
395
|
if (line.wordSpacingPt !== 0) style.wordSpacing = px(line.wordSpacingPt);
|
|
396
|
+
if (line.bullet !== null) {
|
|
397
|
+
const bullet = pieceElement(document, line.bullet);
|
|
398
|
+
bullet.style.position = "absolute";
|
|
399
|
+
bullet.style.left = px(line.bullet.leftPt);
|
|
400
|
+
bullet.style.top = "0";
|
|
401
|
+
el.appendChild(bullet);
|
|
402
|
+
}
|
|
401
403
|
for (const piece of line.pieces) if (piece.upright.length > 0) for (const glyph of piece.upright) el.appendChild(uprightElement(document, piece, glyph));
|
|
402
404
|
else el.appendChild(pieceElement(document, piece));
|
|
403
405
|
const baseline = line.baselinePt - line.topPt;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/mount.ts","../src/overlay.ts","../src/text/layer.ts"],"sourcesContent":["/**\n * Every failure in this package is a `RenderDomError` with a code.\n *\n * There are only two, and both mean the *caller* asked for something\n * impossible: a host that is not an element, or an element with no document to\n * build in. Everything about the slide itself is `render-svg`'s to refuse, and\n * this package never re-decides it.\n */\n\nexport type RenderDomErrorCode =\n /** `mountSlide` was handed something that is not an element. */\n | 'RENDER_DOM_NO_HOST'\n /** The host is detached and no `document` was passed. */\n | 'RENDER_DOM_NO_DOCUMENT';\n\nexport const RENDER_DOM_ERROR_CODES: readonly RenderDomErrorCode[] = [\n 'RENDER_DOM_NO_HOST',\n 'RENDER_DOM_NO_DOCUMENT',\n];\n\nexport class RenderDomError extends Error {\n readonly code: RenderDomErrorCode;\n\n constructor(code: RenderDomErrorCode, message: string) {\n super(message);\n this.name = 'RenderDomError';\n this.code = code;\n }\n}\n\nexport function isRenderDomError(value: unknown): value is RenderDomError {\n return value instanceof RenderDomError;\n}\n","/**\n * The live renderer: the same node tree as `render-svg`, as real elements.\n *\n * ## Why this is thirty lines of DOM and not a second renderer\n *\n * The plan asks for two renderers \"over the same layout engine\", so that a\n * thumbnail and the editor cannot disagree about what is on the screen. Sharing\n * a *layout* is not enough for that on its own: emitting a `<linearGradient>`\n * twice, once as text and once as `createElementNS`, is two chances to write the\n * same gradient wrong in two different ways.\n *\n * So the shared surface goes a level deeper. `render-svg` builds a node tree and\n * serialises it; this builds the identical tree and instantiates it. Everything\n * either of them knows about DrawingML is in that tree before either touches it,\n * and the only thing this file decides is which DOM call makes a node.\n *\n * ## The namespace, and the four attributes that need their own\n *\n * `document.createElement` makes an HTML element that happens to be spelled\n * `path`, and it draws nothing at all - silently. Every element here goes\n * through `createElementNS` with the SVG namespace. Attributes are the mirror\n * image: they belong to **no** namespace and must go through `setAttribute`,\n * with the single exception of `xlink:href`, which this package never emits.\n *\n * ## Zoom is a transform, never a re-layout\n *\n * The mounted `<svg>` carries a `viewBox` in EMU and is sized in CSS pixels, so\n * scaling it is one attribute and no arithmetic. The plan's rule is the reason:\n * font metrics are not linear in point size, so a renderer that re-lays out per\n * zoom level changes the line breaks as the user zooms.\n */\n\nimport {\n num,\n serializeSvg,\n slideNode,\n type Placed,\n type RenderOptions,\n type SlideSize,\n type SvgElement,\n type SvgNode,\n} from '@pptx-studio/render-svg';\nimport type { Sheet } from '@pptx-studio/model';\n\nimport { RenderDomError } from './errors.js';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * One node into one element. Exported because the debug overlay wants it too.\n *\n * A number goes through `render-svg`'s own `num`, not `String`. That is the\n * whole \"one tree, two renderers\" claim in one line: the string emitter rounds\n * to three decimals and normalises `-0`, so a DOM path using `String` would\n * write `117158.34312` where a thumbnail wrote `117158.343`. Every coordinate\n * in a slide render happens to be a whole number of EMU, which is why this went\n * unnoticed until the overlay's text rectangle - the first fractional one -\n * disagreed at the fourth decimal.\n */\nexport function createNode(document: Document, node: SvgNode): Node {\n if (node.kind === 'text') return document.createTextNode(node.text);\n const el = document.createElementNS(SVG_NS, node.tag);\n for (const [name, value] of Object.entries(node.attrs)) {\n if (value === null || value === undefined) continue;\n el.setAttribute(name, typeof value === 'number' ? num(value) : value);\n }\n for (const child of node.children) el.appendChild(createNode(document, child));\n return el;\n}\n\nexport interface MountOptions extends RenderOptions {\n /**\n * The `document` to build in.\n *\n * Passed rather than reached for, because this package must work in a Web\n * Worker with an `OffscreenCanvas` and no global `document` - and because a\n * test that has to stub a global is a test that can pass for the wrong reason.\n */\n readonly document?: Document;\n}\n\nexport interface MountedSlide {\n readonly root: SVGSVGElement;\n readonly placed: readonly Placed[];\n /** The element drawing a given `p:cNvPr/@id`, for hit-testing and selection. */\n element(cNvPrId: number): SVGElement | null;\n /** Resize without re-laying anything out: the viewBox does the work. */\n resize(width: number, height: number): void;\n /** Detach from the host. The nodes stay valid, so a caller may re-attach. */\n unmount(): void;\n}\n\nfunction ownerDocument(host: Element, options: MountOptions): Document {\n const document = options.document ?? host.ownerDocument;\n\n if (document === null || document === undefined) {\n throw new RenderDomError('RENDER_DOM_NO_DOCUMENT', 'the host element has no ownerDocument');\n }\n return document;\n}\n\n/**\n * Render a sheet into a host element.\n *\n * The host is emptied first. Anything a caller wants to keep beside the slide -\n * the overlay canvas of 2.11, the text layer of 3.8 - is a sibling of the host,\n * not a child of it.\n */\nexport function mountSlide(\n host: Element,\n sheet: Sheet,\n size: SlideSize,\n options: MountOptions = {},\n): MountedSlide {\n // `typeof null` is 'object', so the null check has to come first or the\n // guard reads a property off it and throws the wrong error.\n if (host === null || typeof host !== 'object' || typeof host.appendChild !== 'function') {\n throw new RenderDomError('RENDER_DOM_NO_HOST', 'mountSlide needs an element to build into');\n }\n const document = ownerDocument(host, options);\n const rendered = slideNode(sheet, size, options);\n const root = createNode(document, rendered.node) as SVGSVGElement;\n\n host.replaceChildren(root);\n\n const byId = new Map<number, SVGElement>();\n const walk = (node: Element): void => {\n const id = node.getAttribute('data-shape');\n if (id !== null && !byId.has(Number(id))) byId.set(Number(id), node as SVGElement);\n for (const child of Array.from(node.children)) walk(child);\n };\n walk(root);\n\n return {\n root,\n placed: rendered.placed,\n element(cNvPrId: number): SVGElement | null {\n return byId.get(cNvPrId) ?? null;\n },\n resize(width: number, height: number): void {\n root.setAttribute('width', String(width));\n root.setAttribute('height', String(height));\n },\n unmount(): void {\n root.remove();\n },\n };\n}\n\n/**\n * The same slide as a string, from this package.\n *\n * A convenience rather than a second implementation: it calls `render-svg`'s\n * serializer on the same tree `mountSlide` instantiates, which is the property\n * the tests check.\n */\nexport function renderSlideMarkup(\n sheet: Sheet,\n size: SlideSize,\n options: RenderOptions = {},\n): string {\n return serializeSvg(slideNode(sheet, size, options).node);\n}\n\nexport type { SvgElement };\n","/**\n * The debug overlay, live: the same nodes `render-svg` builds, plus a pointer.\n *\n * Sub-phase 2.11. Everything drawn here comes from `shapeOverlay`, so the\n * handle a user grabs is at the position a thumbnail would have drawn it - the\n * overlay is not a second opinion about where the geometry went. What this file\n * adds is the four things a pointer needs and a node tree cannot have: an\n * element to mount into, hit-testing, the drag, and the inverse transform that\n * gets a pointer from the slide into the shape's own space.\n *\n * ## Slide space, shape space, and why the inverse has to be exact\n *\n * A shape's adjust handles live in *its* coordinates: `(0, 0)` at the top left\n * of `a:ext`, before any rotation or mirroring. A pointer arrives in the\n * slide's. `inverseFramePoint` undoes exactly what `frameTransform` did, in the\n * reverse order - so the mirror is undone *after* the rotation, because it was\n * applied *before* it. Get that backwards and handles work perfectly on every\n * unrotated shape and drift on precisely the flipped, turned ones where a user\n * would never guess the renderer was at fault.\n *\n * ## The drag writes adjust values, not positions\n *\n * `dragHandle` inverts the handle's own formula and returns `{ guideName:\n * value }` for the guides that handle controls and nothing else, already\n * rounded to the integers the file will hold. So a drag produces an `a:avLst`\n * edit, the shape is re-evaluated from it, and the handle lands wherever the\n * geometry says it lands - which is why a handle confined to a curve does not\n * follow the pointer off it. That is the correct behaviour and PowerPoint's.\n */\n\nimport { dragHandle, type Point, type PresetGuide } from '@pptx-studio/geometry';\nimport {\n inverseFramePoint,\n shapeOverlay,\n type OverlayOptions,\n type Placed,\n type ShapeOverlay,\n} from '@pptx-studio/render-svg';\n\nimport { RenderDomError } from './errors.js';\nimport { createNode } from './mount.js';\n\n/** What a completed drag produced. */\nexport interface HandleEdit {\n /** Which `a:ahLst` entry was dragged. */\n readonly handle: number;\n /** The adjust values this handle controls, rounded as the file will hold them. */\n readonly adjust: Readonly<Record<string, number>>;\n /** The shape's whole `a:avLst` after the edit, ready to write back. */\n readonly avLst: readonly PresetGuide[];\n}\n\nexport interface OverlayMountOptions extends OverlayOptions {\n readonly document?: Document;\n /** Called on every pointer move during a drag, and once more on release. */\n onChange?: (edit: HandleEdit) => void;\n /** Called once on release. */\n onCommit?: (edit: HandleEdit) => void;\n}\n\nexport interface MountedOverlay {\n readonly root: SVGGElement;\n readonly overlay: ShapeOverlay;\n /** Redraw against a new placement - after a drag, or a resize. */\n update(placed: Placed): void;\n /** True while a handle is being dragged. */\n dragging(): boolean;\n unmount(): void;\n}\n\n/**\n * A point in the host `<svg>`'s user space - EMU - from a pointer event.\n *\n * Through the SVG CTM rather than by arithmetic on `getBoundingClientRect`,\n * because the slide may be inside any number of CSS transforms and the browser\n * already knows the composed matrix. `getScreenCTM` returns null for an element\n * that is not being rendered, which a drag cannot reach but a stray event can.\n */\nexport function slidePoint(root: SVGSVGElement, clientX: number, clientY: number): Point | null {\n const ctm = root.getScreenCTM();\n if (ctm === null) return null;\n const point = root.createSVGPoint();\n point.x = clientX;\n point.y = clientY;\n const local = point.matrixTransform(ctm.inverse());\n return { x: local.x, y: local.y };\n}\n\n/**\n * The `a:avLst` a drag leaves behind.\n *\n * The dragged guides replace their namesakes and everything else is kept in\n * place, because an `a:avLst` is an ordered list and a shape with three adjusts\n * whose second one moves must still write three, in order. A guide the shape did\n * not previously declare is appended: the preset's default was in force and the\n * drag has just overridden it.\n */\nexport function mergeAdjust(\n avLst: readonly PresetGuide[],\n adjust: Readonly<Record<string, number>>,\n): readonly PresetGuide[] {\n const seen = new Set<string>();\n const merged: PresetGuide[] = avLst.map((guide) => {\n const value = adjust[guide.name];\n if (value === undefined) return guide;\n seen.add(guide.name);\n return { name: guide.name, fmla: ['val', String(Math.round(value))] };\n });\n for (const [name, value] of Object.entries(adjust)) {\n if (seen.has(name)) continue;\n merged.push({ name, fmla: ['val', String(Math.round(value))] });\n }\n return merged;\n}\n\n/**\n * Draw the overlay for one shape into a host `<svg>`, and make its handles drag.\n *\n * The host must be the same `<svg>` the slide is in, because the overlay is\n * emitted in slide coordinates and carries the shape's own transform - the same\n * arrangement `mountSlide` produces, so an editor appends this to that root.\n */\nexport function mountOverlay(\n root: SVGSVGElement,\n placed: Placed,\n options: OverlayMountOptions = {},\n): MountedOverlay {\n if (root === null || typeof root !== 'object' || typeof root.appendChild !== 'function') {\n throw new RenderDomError(\n 'RENDER_DOM_NO_HOST',\n 'mountOverlay needs the slide <svg> to build in',\n );\n }\n const document = options.document ?? root.ownerDocument;\n if (document === null || document === undefined) {\n throw new RenderDomError('RENDER_DOM_NO_DOCUMENT', 'the host element has no ownerDocument');\n }\n\n let current = placed;\n let overlay = shapeOverlay(current, options);\n let group = createNode(document, overlay.node) as SVGGElement;\n root.appendChild(group);\n\n let active: number | null = null;\n let pointer: number | null = null;\n\n function edit(at: Point, index: number): HandleEdit | null {\n const source = current.geometrySource;\n const handle = overlay.handles.find((h) => h.index === index);\n if (source === null || handle === undefined) return null;\n const local = inverseFramePoint(current.frame, at);\n const adjust = dragHandle(\n source.geometry,\n { w: current.frame.cx, h: current.frame.cy },\n handle.handle,\n local,\n { adjust: source.adjust },\n );\n if (Object.keys(adjust).length === 0) return null;\n return { handle: index, adjust, avLst: mergeAdjust(source.adjust, adjust) };\n }\n\n /**\n * Hold the pointer for the duration of the drag.\n *\n * On the root rather than on the handle, because the handle element is\n * replaced on every update and a captured element that leaves the tree takes\n * the capture with it - the drag would stop the moment the shape redrew.\n *\n * `setPointerCapture` throws `NotFoundError` for a pointer that is no longer\n * active, which is reachable whenever the button is released between the\n * event being queued and this running. Losing the capture degrades the drag\n * to one that stops at the window edge; throwing would abandon it entirely.\n */\n function capture(pointerId: number): void {\n try {\n root.setPointerCapture(pointerId);\n } catch {\n // No capture, and the drag continues without it.\n }\n }\n\n function onPointerDown(event: PointerEvent): void {\n const target = event.target as Element | null;\n const owner = target?.closest?.('[data-role=\"handle\"]');\n const index = owner?.getAttribute('data-handle');\n // A handle with no `gdRef` at all controls nothing and must not swallow\n // the gesture. No preset has one - all 120 with an `a:ahLst` declare at\n // least one axis - but an `a:custGeom` in a real file may.\n if (index === null || index === undefined || owner?.getAttribute('data-axes') === '') return;\n active = Number(index);\n pointer = event.pointerId;\n capture(event.pointerId);\n event.preventDefault();\n }\n\n function onPointerMove(event: PointerEvent): void {\n if (active === null || event.pointerId !== pointer) return;\n const at = slidePoint(root, event.clientX, event.clientY);\n if (at === null) return;\n const result = edit(at, active);\n if (result !== null) options.onChange?.(result);\n }\n\n function onPointerUp(event: PointerEvent): void {\n if (active === null || event.pointerId !== pointer) return;\n const at = slidePoint(root, event.clientX, event.clientY);\n const result = at === null ? null : edit(at, active);\n active = null;\n pointer = null;\n if (root.hasPointerCapture(event.pointerId)) root.releasePointerCapture(event.pointerId);\n if (result !== null) options.onCommit?.(result);\n }\n\n // One signal for all four, so `unmount` cannot detach three of them and\n // leave the fourth listening on a root that outlives this overlay. Four\n // paired `removeEventListener` calls are four chances to forget one, and a\n // half-removed listener is invisible until a second overlay is mounted on the\n // same slide and the dead one starts editing a shape that is no longer there.\n const listening = new AbortController();\n const on = { signal: listening.signal };\n root.addEventListener('pointerdown', onPointerDown, on);\n root.addEventListener('pointermove', onPointerMove, on);\n root.addEventListener('pointerup', onPointerUp, on);\n root.addEventListener('pointercancel', onPointerUp, on);\n\n return {\n get root(): SVGGElement {\n return group;\n },\n get overlay(): ShapeOverlay {\n return overlay;\n },\n update(next: Placed): void {\n current = next;\n overlay = shapeOverlay(current, options);\n const replacement = createNode(document, overlay.node) as SVGGElement;\n group.replaceWith(replacement);\n group = replacement;\n },\n dragging(): boolean {\n return active !== null;\n },\n unmount(): void {\n listening.abort();\n group.remove();\n },\n };\n}\n","/**\n * The HTML text layer: the same lines as the SVG, as real text nodes.\n *\n * The plan's bet 4 asks for HTML text beside the SVG geometry, and the reason is\n * everything a `<text>` element cannot do: shaping, bidi, an IME, a selection a\n * user can drag, a screen reader. This draws from the identical `TextBlock`\n * `render-svg` emits, so the two cannot disagree about a line box - which is\n * sub-phase 3.8's own verification.\n *\n * ## Why the line carries an odd `line-height`\n *\n * A browser puts a baseline a half-leading plus an ascent below the line box and\n * PowerPoint puts it at the face's own share of it. `TextLine` carries the\n * `line-height` that reconciles the two, computed once in the layout pass. It\n * lands within one CSS pixel and no closer, because Chromium rounds a font's\n * ascent to a whole pixel before it lays a line out - which is why the SVG\n * emitter, where the baseline is a coordinate, is the one that thumbnails and\n * exports use.\n */\n\nimport type {\n PieceRule,\n TextBlock,\n TextLine,\n TextPiece,\n UprightGlyph,\n} from '@pptx-studio/render-svg';\nimport { toCss, type Rgba } from '@pptx-studio/paint';\n\nimport { RenderDomError } from '../errors.js';\n\n/** A slide's size in points, which is what a block's coordinates are in. */\nexport interface LayerSize {\n readonly widthPt: number;\n readonly heightPt: number;\n}\n\n/** One shape's text, with the frame it is drawn in. */\nexport interface LayerBlock {\n readonly block: TextBlock;\n /** The shape's frame, in points on the slide. */\n readonly leftPt: number;\n readonly topPt: number;\n readonly widthPt: number;\n readonly heightPt: number;\n /** `p:cNvPr/@id`, so a caller can find the element for a shape. */\n readonly cNvPrId: number;\n readonly name?: string | undefined;\n}\n\nexport interface TextLayerOptions {\n readonly document?: Document | undefined;\n /** CSS pixels per point. One transform, never a re-layout. */\n readonly scale?: number | undefined;\n /** Let the layer take pointer events. Off by default: the SVG hit-tests. */\n readonly interactive?: boolean | undefined;\n}\n\nexport interface MountedTextLayer {\n readonly root: HTMLElement;\n /** The element holding one shape's text. */\n element(cNvPrId: number): HTMLElement | null;\n /** Rescale without re-laying anything out. */\n resize(scale: number): void;\n unmount(): void;\n}\n\nfunction px(value: number): string {\n // Three decimals of a point is about a nanometre, and matches `render-svg`'s\n // own rounding, so the two layers round identically.\n return `${String(Math.round(value * 1000) / 1000)}px`;\n}\n\n/** Through `toCss`, because `Rgba` channels are 0..1 and not bytes. */\nfunction css(color: Rgba | null): string {\n return color === null ? 'inherit' : toCss(color);\n}\n\n/** The dash pattern a rule is painted with, in multiples of its thickness. */\nconst DASHES: Readonly<Record<string, readonly number[]>> = {\n dotted: [1, 2],\n dashed: [4, 3],\n longDashed: [8, 3],\n dotDash: [4, 3, 1, 3],\n dotDotDash: [1, 3, 1, 3, 4, 3],\n};\n\nfunction ruleElement(\n document: Document,\n rule: PieceRule,\n x: number,\n baseline: number,\n color: string,\n): HTMLElement {\n const el = document.createElement('div');\n const style = el.style;\n style.position = 'absolute';\n style.left = px(x + rule.leftPt);\n style.top = px(baseline + rule.topPt);\n style.width = px(rule.widthPt);\n style.height = px(rule.thickness);\n const dash = DASHES[rule.pattern];\n if (dash === undefined) {\n style.background = color;\n return el;\n }\n // A dashed rule as a repeating gradient, so it stays one element and scales\n // with the layer instead of being redrawn.\n const on = (dash[0] ?? 1) * rule.thickness;\n const off = (dash[1] ?? 1) * rule.thickness;\n style.background = `repeating-linear-gradient(to right, ${color} 0 ${px(on)}, transparent ${px(on)} ${px(on + off)})`;\n return el;\n}\n\nfunction pieceElement(document: Document, piece: TextPiece): HTMLElement {\n const el = document.createElement('span');\n const style = el.style;\n style.fontFamily = piece.cssFamily;\n style.fontSize = px(piece.font.sz / 100);\n if (piece.font.bold === true) style.fontWeight = 'bold';\n if (piece.font.italic === true) style.fontStyle = 'italic';\n if (piece.font.spc !== undefined && piece.font.spc !== 0) {\n style.letterSpacing = px(piece.font.spc / 100);\n }\n style.color = css(piece.color);\n if (piece.highlight !== null) style.background = css(piece.highlight);\n if (piece.risePt !== 0) {\n style.position = 'relative';\n style.top = px(-piece.risePt);\n }\n el.textContent = piece.text;\n return el;\n}\n\n/**\n * One glyph stood upright inside a turned line, placed from its own pen.\n *\n * The span is laid out unrotated with its baseline `sizePt` below its top - what\n * `UprightGlyph.strutPt` solves for - and then turned a quarter back about that\n * point, so the pen lands where the SVG emitter puts the same glyph.\n */\nfunction uprightElement(document: Document, piece: TextPiece, glyph: UprightGlyph): HTMLElement {\n const el = pieceElement(document, piece);\n const style = el.style;\n const sizePt = piece.font.sz / 100;\n style.position = 'absolute';\n style.left = px(glyph.alongPt - piece.risePt);\n style.top = px(glyph.acrossPt - sizePt);\n style.lineHeight = px(glyph.strutPt);\n style.transformOrigin = `0 ${px(sizePt)}`;\n style.transform = 'rotate(-90deg)';\n el.textContent = glyph.text;\n return el;\n}\n\nfunction lineElement(document: Document, line: TextLine): HTMLElement {\n const el = document.createElement('div');\n const style = el.style;\n style.position = 'absolute';\n style.left = px(line.leftPt);\n style.top = px(line.topPt);\n style.width = px(line.widthPt);\n style.height = px(line.heightPt);\n style.lineHeight = px(line.strutPt);\n style.whiteSpace = 'pre';\n if (line.wordSpacingPt !== 0) style.wordSpacing = px(line.wordSpacingPt);\n\n for (const piece of line.pieces) {\n if (piece.upright.length > 0) {\n for (const glyph of piece.upright) el.appendChild(uprightElement(document, piece, glyph));\n } else el.appendChild(pieceElement(document, piece));\n }\n\n const baseline = line.baselinePt - line.topPt;\n for (const piece of line.pieces) {\n const color = css(piece.color);\n for (const rule of piece.rules) {\n el.appendChild(ruleElement(document, rule, piece.leftPt, baseline - piece.risePt, color));\n }\n }\n return el;\n}\n\nfunction blockElement(document: Document, entry: LayerBlock): HTMLElement {\n const el = document.createElement('div');\n const style = el.style;\n style.position = 'absolute';\n style.left = px(entry.leftPt);\n style.top = px(entry.topPt);\n style.width = px(entry.widthPt);\n style.height = px(entry.heightPt);\n if (entry.block.turnDeg !== 0) {\n style.transformOrigin = '50% 50%';\n style.transform = `rotate(${String(entry.block.turnDeg)}deg)`;\n }\n el.setAttribute('data-text', String(entry.cNvPrId));\n if (entry.name !== undefined && entry.name !== '') el.setAttribute('data-name', entry.name);\n for (const line of entry.block.lines) el.appendChild(lineElement(document, line));\n return el;\n}\n\n/**\n * Mount a text layer into a host, over a slide of `size` points.\n *\n * The host is emptied first. The layer is sized in points and scaled with one\n * transform, which is the plan's rule: font metrics are not linear in point\n * size, so a renderer that re-laid out per zoom would change its line breaks as\n * the user zoomed.\n */\nexport function mountTextLayer(\n host: Element,\n blocks: readonly LayerBlock[],\n size: LayerSize,\n options: TextLayerOptions = {},\n): MountedTextLayer {\n if (host === null || typeof host !== 'object' || typeof host.appendChild !== 'function') {\n throw new RenderDomError('RENDER_DOM_NO_HOST', 'mountTextLayer needs an element to build into');\n }\n const document = options.document ?? host.ownerDocument;\n if (document === null || document === undefined) {\n throw new RenderDomError('RENDER_DOM_NO_DOCUMENT', 'the host element has no ownerDocument');\n }\n\n const root = document.createElement('div');\n const style = root.style;\n style.position = 'absolute';\n style.left = '0';\n style.top = '0';\n style.width = px(size.widthPt);\n style.height = px(size.heightPt);\n style.transformOrigin = '0 0';\n style.pointerEvents = options.interactive === true ? 'auto' : 'none';\n root.setAttribute('data-role', 'text-layer');\n\n const byId = new Map<number, HTMLElement>();\n for (const entry of blocks) {\n if (entry.block.lines.length === 0) continue;\n const el = blockElement(document, entry);\n byId.set(entry.cNvPrId, el);\n root.appendChild(el);\n }\n\n const apply = (scale: number): void => {\n style.transform = scale === 1 ? '' : `scale(${String(scale)})`;\n };\n apply(options.scale ?? 1);\n host.replaceChildren(root);\n\n return {\n root,\n element(cNvPrId: number): HTMLElement | null {\n return byId.get(cNvPrId) ?? null;\n },\n resize(scale: number): void {\n apply(scale);\n },\n unmount(): void {\n root.remove();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/mount.ts","../src/overlay.ts","../src/text/layer.ts"],"sourcesContent":["/**\n * Every failure in this package is a `RenderDomError` with a code.\n *\n * There are only two, and both mean the *caller* asked for something\n * impossible: a host that is not an element, or an element with no document to\n * build in. Everything about the slide itself is `render-svg`'s to refuse, and\n * this package never re-decides it.\n */\n\nexport type RenderDomErrorCode =\n /** `mountSlide` was handed something that is not an element. */\n | 'RENDER_DOM_NO_HOST'\n /** The host is detached and no `document` was passed. */\n | 'RENDER_DOM_NO_DOCUMENT';\n\nexport const RENDER_DOM_ERROR_CODES: readonly RenderDomErrorCode[] = [\n 'RENDER_DOM_NO_HOST',\n 'RENDER_DOM_NO_DOCUMENT',\n];\n\nexport class RenderDomError extends Error {\n readonly code: RenderDomErrorCode;\n\n constructor(code: RenderDomErrorCode, message: string) {\n super(message);\n this.name = 'RenderDomError';\n this.code = code;\n }\n}\n\nexport function isRenderDomError(value: unknown): value is RenderDomError {\n return value instanceof RenderDomError;\n}\n","/**\n * The live renderer: the same node tree as `render-svg`, as real elements.\n *\n * ## Why this is thirty lines of DOM and not a second renderer\n *\n * The plan asks for two renderers \"over the same layout engine\", so that a\n * thumbnail and the editor cannot disagree about what is on the screen. Sharing\n * a *layout* is not enough for that on its own: emitting a `<linearGradient>`\n * twice, once as text and once as `createElementNS`, is two chances to write the\n * same gradient wrong in two different ways.\n *\n * So the shared surface goes a level deeper. `render-svg` builds a node tree and\n * serialises it; this builds the identical tree and instantiates it. Everything\n * either of them knows about DrawingML is in that tree before either touches it,\n * and the only thing this file decides is which DOM call makes a node.\n *\n * ## The namespace, and the four attributes that need their own\n *\n * `document.createElement` makes an HTML element that happens to be spelled\n * `path`, and it draws nothing at all - silently. Every element here goes\n * through `createElementNS` with the SVG namespace. Attributes are the mirror\n * image: they belong to **no** namespace and must go through `setAttribute`,\n * with the single exception of `xlink:href`, which this package never emits.\n *\n * ## Zoom is a mount at that width, never a re-layout\n *\n * The `<svg>` carries a `viewBox` in EMU and is sized in CSS pixels. Text is laid out\n * once at one point size, so the line breaks are the same at every zoom; strokes are\n * rounded to the device pixels of the mounted width (F2), so a zoom is another mount.\n */\n\nimport {\n num,\n serializeSvg,\n slideNode,\n type Placed,\n type RenderOptions,\n type SlideSize,\n type SvgElement,\n type SvgNode,\n} from '@pptx-studio/render-svg';\nimport type { Sheet } from '@pptx-studio/model';\n\nimport { RenderDomError } from './errors.js';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * One node into one element. Exported because the debug overlay wants it too.\n *\n * A number goes through `render-svg`'s own `num`, not `String`. That is the\n * whole \"one tree, two renderers\" claim in one line: the string emitter rounds\n * to three decimals and normalises `-0`, so a DOM path using `String` would\n * write `117158.34312` where a thumbnail wrote `117158.343`. Every coordinate\n * in a slide render happens to be a whole number of EMU, which is why this went\n * unnoticed until the overlay's text rectangle - the first fractional one -\n * disagreed at the fourth decimal.\n */\nexport function createNode(document: Document, node: SvgNode): Node {\n if (node.kind === 'text') return document.createTextNode(node.text);\n const el = document.createElementNS(SVG_NS, node.tag);\n for (const [name, value] of Object.entries(node.attrs)) {\n if (value === null || value === undefined) continue;\n el.setAttribute(name, typeof value === 'number' ? num(value) : value);\n }\n for (const child of node.children) el.appendChild(createNode(document, child));\n return el;\n}\n\nexport interface MountOptions extends RenderOptions {\n /**\n * The `document` to build in.\n *\n * Passed rather than reached for, because this package must work in a Web\n * Worker with an `OffscreenCanvas` and no global `document` - and because a\n * test that has to stub a global is a test that can pass for the wrong reason.\n */\n readonly document?: Document;\n}\n\nexport interface MountedSlide {\n readonly root: SVGSVGElement;\n readonly placed: readonly Placed[];\n /** The element drawing a given `p:cNvPr/@id`, for hit-testing and selection. */\n element(cNvPrId: number): SVGElement | null;\n /** Detach from the host. The nodes stay valid, so a caller may re-attach. */\n unmount(): void;\n}\n\nfunction ownerDocument(host: Element, options: MountOptions): Document {\n const document = options.document ?? host.ownerDocument;\n\n if (document === null || document === undefined) {\n throw new RenderDomError('RENDER_DOM_NO_DOCUMENT', 'the host element has no ownerDocument');\n }\n return document;\n}\n\n/**\n * Render a sheet into a host element.\n *\n * The host is emptied first. Anything a caller wants to keep beside the slide -\n * the overlay canvas of 2.11, the text layer of 3.8 - is a sibling of the host,\n * not a child of it.\n */\nexport function mountSlide(\n host: Element,\n sheet: Sheet,\n size: SlideSize,\n options: MountOptions = {},\n): MountedSlide {\n // `typeof null` is 'object', so the null check has to come first or the\n // guard reads a property off it and throws the wrong error.\n if (host === null || typeof host !== 'object' || typeof host.appendChild !== 'function') {\n throw new RenderDomError('RENDER_DOM_NO_HOST', 'mountSlide needs an element to build into');\n }\n const document = ownerDocument(host, options);\n const rendered = slideNode(sheet, size, options);\n const root = createNode(document, rendered.node) as SVGSVGElement;\n\n host.replaceChildren(root);\n\n const byId = new Map<number, SVGElement>();\n const walk = (node: Element): void => {\n const id = node.getAttribute('data-shape');\n if (id !== null && !byId.has(Number(id))) byId.set(Number(id), node as SVGElement);\n for (const child of Array.from(node.children)) walk(child);\n };\n walk(root);\n\n return {\n root,\n placed: rendered.placed,\n element(cNvPrId: number): SVGElement | null {\n return byId.get(cNvPrId) ?? null;\n },\n unmount(): void {\n root.remove();\n },\n };\n}\n\n/**\n * The same slide as a string, from this package.\n *\n * A convenience rather than a second implementation: it calls `render-svg`'s\n * serializer on the same tree `mountSlide` instantiates, which is the property\n * the tests check.\n */\nexport function renderSlideMarkup(\n sheet: Sheet,\n size: SlideSize,\n options: RenderOptions = {},\n): string {\n return serializeSvg(slideNode(sheet, size, options).node);\n}\n\nexport type { SvgElement };\n","/**\n * The debug overlay, live: the same nodes `render-svg` builds, plus a pointer.\n *\n * Sub-phase 2.11. Everything drawn here comes from `shapeOverlay`, so the\n * handle a user grabs is at the position a thumbnail would have drawn it - the\n * overlay is not a second opinion about where the geometry went. What this file\n * adds is the four things a pointer needs and a node tree cannot have: an\n * element to mount into, hit-testing, the drag, and the inverse transform that\n * gets a pointer from the slide into the shape's own space.\n *\n * ## Slide space, shape space, and why the inverse has to be exact\n *\n * A shape's adjust handles live in *its* coordinates: `(0, 0)` at the top left\n * of `a:ext`, before any rotation or mirroring. A pointer arrives in the\n * slide's. `inverseFramePoint` undoes exactly what `frameTransform` did, in the\n * reverse order - so the mirror is undone *after* the rotation, because it was\n * applied *before* it. Get that backwards and handles work perfectly on every\n * unrotated shape and drift on precisely the flipped, turned ones where a user\n * would never guess the renderer was at fault.\n *\n * ## The drag writes adjust values, not positions\n *\n * `dragHandle` inverts the handle's own formula and returns `{ guideName:\n * value }` for the guides that handle controls and nothing else, already\n * rounded to the integers the file will hold. So a drag produces an `a:avLst`\n * edit, the shape is re-evaluated from it, and the handle lands wherever the\n * geometry says it lands - which is why a handle confined to a curve does not\n * follow the pointer off it. That is the correct behaviour and PowerPoint's.\n */\n\nimport { dragHandle, type Point, type PresetGuide } from '@pptx-studio/geometry';\nimport {\n inverseFramePoint,\n shapeOverlay,\n type OverlayOptions,\n type Placed,\n type ShapeOverlay,\n} from '@pptx-studio/render-svg';\n\nimport { RenderDomError } from './errors.js';\nimport { createNode } from './mount.js';\n\n/** What a completed drag produced. */\nexport interface HandleEdit {\n /** Which `a:ahLst` entry was dragged. */\n readonly handle: number;\n /** The adjust values this handle controls, rounded as the file will hold them. */\n readonly adjust: Readonly<Record<string, number>>;\n /** The shape's whole `a:avLst` after the edit, ready to write back. */\n readonly avLst: readonly PresetGuide[];\n}\n\nexport interface OverlayMountOptions extends OverlayOptions {\n readonly document?: Document;\n /** Called on every pointer move during a drag, and once more on release. */\n onChange?: (edit: HandleEdit) => void;\n /** Called once on release. */\n onCommit?: (edit: HandleEdit) => void;\n}\n\nexport interface MountedOverlay {\n readonly root: SVGGElement;\n readonly overlay: ShapeOverlay;\n /** Redraw against a new placement - after a drag, or a resize. */\n update(placed: Placed): void;\n /** True while a handle is being dragged. */\n dragging(): boolean;\n unmount(): void;\n}\n\n/**\n * A point in the host `<svg>`'s user space - EMU - from a pointer event.\n *\n * Through the SVG CTM rather than by arithmetic on `getBoundingClientRect`,\n * because the slide may be inside any number of CSS transforms and the browser\n * already knows the composed matrix. `getScreenCTM` returns null for an element\n * that is not being rendered, which a drag cannot reach but a stray event can.\n */\nexport function slidePoint(root: SVGSVGElement, clientX: number, clientY: number): Point | null {\n const ctm = root.getScreenCTM();\n if (ctm === null) return null;\n const point = root.createSVGPoint();\n point.x = clientX;\n point.y = clientY;\n const local = point.matrixTransform(ctm.inverse());\n return { x: local.x, y: local.y };\n}\n\n/**\n * The `a:avLst` a drag leaves behind.\n *\n * The dragged guides replace their namesakes and everything else is kept in\n * place, because an `a:avLst` is an ordered list and a shape with three adjusts\n * whose second one moves must still write three, in order. A guide the shape did\n * not previously declare is appended: the preset's default was in force and the\n * drag has just overridden it.\n */\nexport function mergeAdjust(\n avLst: readonly PresetGuide[],\n adjust: Readonly<Record<string, number>>,\n): readonly PresetGuide[] {\n const seen = new Set<string>();\n const merged: PresetGuide[] = avLst.map((guide) => {\n const value = adjust[guide.name];\n if (value === undefined) return guide;\n seen.add(guide.name);\n return { name: guide.name, fmla: ['val', String(Math.round(value))] };\n });\n for (const [name, value] of Object.entries(adjust)) {\n if (seen.has(name)) continue;\n merged.push({ name, fmla: ['val', String(Math.round(value))] });\n }\n return merged;\n}\n\n/**\n * Draw the overlay for one shape into a host `<svg>`, and make its handles drag.\n *\n * The host must be the same `<svg>` the slide is in, because the overlay is\n * emitted in slide coordinates and carries the shape's own transform - the same\n * arrangement `mountSlide` produces, so an editor appends this to that root.\n */\nexport function mountOverlay(\n root: SVGSVGElement,\n placed: Placed,\n options: OverlayMountOptions = {},\n): MountedOverlay {\n if (root === null || typeof root !== 'object' || typeof root.appendChild !== 'function') {\n throw new RenderDomError(\n 'RENDER_DOM_NO_HOST',\n 'mountOverlay needs the slide <svg> to build in',\n );\n }\n const document = options.document ?? root.ownerDocument;\n if (document === null || document === undefined) {\n throw new RenderDomError('RENDER_DOM_NO_DOCUMENT', 'the host element has no ownerDocument');\n }\n\n let current = placed;\n let overlay = shapeOverlay(current, options);\n let group = createNode(document, overlay.node) as SVGGElement;\n root.appendChild(group);\n\n let active: number | null = null;\n let pointer: number | null = null;\n\n function edit(at: Point, index: number): HandleEdit | null {\n const source = current.geometrySource;\n const handle = overlay.handles.find((h) => h.index === index);\n if (source === null || handle === undefined) return null;\n const local = inverseFramePoint(current.frame, at);\n const adjust = dragHandle(\n source.geometry,\n { w: current.frame.cx, h: current.frame.cy },\n handle.handle,\n local,\n { adjust: source.adjust },\n );\n if (Object.keys(adjust).length === 0) return null;\n return { handle: index, adjust, avLst: mergeAdjust(source.adjust, adjust) };\n }\n\n /**\n * Hold the pointer for the duration of the drag.\n *\n * On the root rather than on the handle, because the handle element is\n * replaced on every update and a captured element that leaves the tree takes\n * the capture with it - the drag would stop the moment the shape redrew.\n *\n * `setPointerCapture` throws `NotFoundError` for a pointer that is no longer\n * active, which is reachable whenever the button is released between the\n * event being queued and this running. Losing the capture degrades the drag\n * to one that stops at the window edge; throwing would abandon it entirely.\n */\n function capture(pointerId: number): void {\n try {\n root.setPointerCapture(pointerId);\n } catch {\n // No capture, and the drag continues without it.\n }\n }\n\n function onPointerDown(event: PointerEvent): void {\n const target = event.target as Element | null;\n const owner = target?.closest?.('[data-role=\"handle\"]');\n const index = owner?.getAttribute('data-handle');\n // A handle with no `gdRef` at all controls nothing and must not swallow\n // the gesture. No preset has one - all 120 with an `a:ahLst` declare at\n // least one axis - but an `a:custGeom` in a real file may.\n if (index === null || index === undefined || owner?.getAttribute('data-axes') === '') return;\n active = Number(index);\n pointer = event.pointerId;\n capture(event.pointerId);\n event.preventDefault();\n }\n\n function onPointerMove(event: PointerEvent): void {\n if (active === null || event.pointerId !== pointer) return;\n const at = slidePoint(root, event.clientX, event.clientY);\n if (at === null) return;\n const result = edit(at, active);\n if (result !== null) options.onChange?.(result);\n }\n\n function onPointerUp(event: PointerEvent): void {\n if (active === null || event.pointerId !== pointer) return;\n const at = slidePoint(root, event.clientX, event.clientY);\n const result = at === null ? null : edit(at, active);\n active = null;\n pointer = null;\n if (root.hasPointerCapture(event.pointerId)) root.releasePointerCapture(event.pointerId);\n if (result !== null) options.onCommit?.(result);\n }\n\n // One signal for all four, so `unmount` cannot detach three of them and\n // leave the fourth listening on a root that outlives this overlay. Four\n // paired `removeEventListener` calls are four chances to forget one, and a\n // half-removed listener is invisible until a second overlay is mounted on the\n // same slide and the dead one starts editing a shape that is no longer there.\n const listening = new AbortController();\n const on = { signal: listening.signal };\n root.addEventListener('pointerdown', onPointerDown, on);\n root.addEventListener('pointermove', onPointerMove, on);\n root.addEventListener('pointerup', onPointerUp, on);\n root.addEventListener('pointercancel', onPointerUp, on);\n\n return {\n get root(): SVGGElement {\n return group;\n },\n get overlay(): ShapeOverlay {\n return overlay;\n },\n update(next: Placed): void {\n current = next;\n overlay = shapeOverlay(current, options);\n const replacement = createNode(document, overlay.node) as SVGGElement;\n group.replaceWith(replacement);\n group = replacement;\n },\n dragging(): boolean {\n return active !== null;\n },\n unmount(): void {\n listening.abort();\n group.remove();\n },\n };\n}\n","/**\n * The HTML text layer: the same lines as the SVG, as real text nodes.\n *\n * The plan's bet 4 asks for HTML text beside the SVG geometry, and the reason is\n * everything a `<text>` element cannot do: shaping, bidi, an IME, a selection a\n * user can drag, a screen reader. This draws from the identical `TextBlock`\n * `render-svg` emits, so the two cannot disagree about a line box - which is\n * sub-phase 3.8's own verification.\n *\n * ## Why the line carries an odd `line-height`\n *\n * A browser puts a baseline a half-leading plus an ascent below the line box and\n * PowerPoint puts it at the face's own share of it. `TextLine` carries the\n * `line-height` that reconciles the two, computed once in the layout pass. It\n * lands within one CSS pixel and no closer, because Chromium rounds a font's\n * ascent to a whole pixel before it lays a line out - which is why the SVG\n * emitter, where the baseline is a coordinate, is the one that thumbnails and\n * exports use.\n */\n\nimport type {\n PieceRule,\n TextBlock,\n TextLine,\n TextPiece,\n UprightGlyph,\n} from '@pptx-studio/render-svg';\nimport { toCss, type Rgba } from '@pptx-studio/paint';\n\nimport { RenderDomError } from '../errors.js';\n\n/** A slide's size in points, which is what a block's coordinates are in. */\nexport interface LayerSize {\n readonly widthPt: number;\n readonly heightPt: number;\n}\n\n/** One shape's text, with the frame it is drawn in. */\nexport interface LayerBlock {\n readonly block: TextBlock;\n /** The shape's frame, in points on the slide. */\n readonly leftPt: number;\n readonly topPt: number;\n readonly widthPt: number;\n readonly heightPt: number;\n /** `p:cNvPr/@id`, so a caller can find the element for a shape. */\n readonly cNvPrId: number;\n readonly name?: string | undefined;\n}\n\nexport interface TextLayerOptions {\n readonly document?: Document | undefined;\n /** CSS pixels per point. One transform, never a re-layout. */\n readonly scale?: number | undefined;\n /** Let the layer take pointer events. Off by default: the SVG hit-tests. */\n readonly interactive?: boolean | undefined;\n}\n\nexport interface MountedTextLayer {\n readonly root: HTMLElement;\n /** The element holding one shape's text. */\n element(cNvPrId: number): HTMLElement | null;\n /** Rescale without re-laying anything out. */\n resize(scale: number): void;\n unmount(): void;\n}\n\nfunction px(value: number): string {\n // Three decimals of a point is about a nanometre, and matches `render-svg`'s\n // own rounding, so the two layers round identically.\n return `${String(Math.round(value * 1000) / 1000)}px`;\n}\n\n/** Through `toCss`, because `Rgba` channels are 0..1 and not bytes. */\nfunction css(color: Rgba | null): string {\n return color === null ? 'inherit' : toCss(color);\n}\n\n/** The dash pattern a rule is painted with, in multiples of its thickness. */\nconst DASHES: Readonly<Record<string, readonly number[]>> = {\n dotted: [1, 2],\n dashed: [4, 3],\n longDashed: [8, 3],\n dotDash: [4, 3, 1, 3],\n dotDotDash: [1, 3, 1, 3, 4, 3],\n};\n\nfunction ruleElement(\n document: Document,\n rule: PieceRule,\n x: number,\n baseline: number,\n color: string,\n): HTMLElement {\n const el = document.createElement('div');\n const style = el.style;\n style.position = 'absolute';\n style.left = px(x + rule.leftPt);\n style.top = px(baseline + rule.topPt);\n style.width = px(rule.widthPt);\n style.height = px(rule.thickness);\n const dash = DASHES[rule.pattern];\n if (dash === undefined) {\n style.background = color;\n return el;\n }\n // A dashed rule as a repeating gradient, so it stays one element and scales\n // with the layer instead of being redrawn.\n const on = (dash[0] ?? 1) * rule.thickness;\n const off = (dash[1] ?? 1) * rule.thickness;\n style.background = `repeating-linear-gradient(to right, ${color} 0 ${px(on)}, transparent ${px(on)} ${px(on + off)})`;\n return el;\n}\n\nfunction pieceElement(document: Document, piece: TextPiece): HTMLElement {\n const el = document.createElement('span');\n const style = el.style;\n style.fontFamily = piece.cssFamily;\n style.fontSize = px(piece.font.sz / 100);\n if (piece.font.bold === true) style.fontWeight = 'bold';\n if (piece.font.italic === true) style.fontStyle = 'italic';\n if (piece.font.spc !== undefined && piece.font.spc !== 0) {\n style.letterSpacing = px(piece.font.spc / 100);\n }\n style.color = css(piece.color);\n if (piece.highlight !== null) style.background = css(piece.highlight);\n if (piece.risePt !== 0) {\n style.position = 'relative';\n style.top = px(-piece.risePt);\n }\n el.textContent = piece.text;\n return el;\n}\n\n/**\n * One glyph stood upright inside a turned line, placed from its own pen.\n *\n * The span is laid out unrotated with its baseline `sizePt` below its top - what\n * `UprightGlyph.strutPt` solves for - and then turned a quarter back about that\n * point, so the pen lands where the SVG emitter puts the same glyph.\n */\nfunction uprightElement(document: Document, piece: TextPiece, glyph: UprightGlyph): HTMLElement {\n const el = pieceElement(document, piece);\n const style = el.style;\n const sizePt = piece.font.sz / 100;\n style.position = 'absolute';\n style.left = px(glyph.alongPt - piece.risePt);\n style.top = px(glyph.acrossPt - sizePt);\n style.lineHeight = px(glyph.strutPt);\n style.transformOrigin = `0 ${px(sizePt)}`;\n style.transform = 'rotate(-90deg)';\n el.textContent = glyph.text;\n return el;\n}\n\nfunction lineElement(document: Document, line: TextLine): HTMLElement {\n const el = document.createElement('div');\n const style = el.style;\n style.position = 'absolute';\n style.left = px(line.leftPt);\n style.top = px(line.topPt);\n style.width = px(line.widthPt);\n style.height = px(line.heightPt);\n style.lineHeight = px(line.strutPt);\n style.whiteSpace = 'pre';\n if (line.wordSpacingPt !== 0) style.wordSpacing = px(line.wordSpacingPt);\n\n // The bullet is placed from the line's left edge, outside the flow the pieces share.\n if (line.bullet !== null) {\n const bullet = pieceElement(document, line.bullet);\n bullet.style.position = 'absolute';\n bullet.style.left = px(line.bullet.leftPt);\n bullet.style.top = '0';\n el.appendChild(bullet);\n }\n\n for (const piece of line.pieces) {\n if (piece.upright.length > 0) {\n for (const glyph of piece.upright) el.appendChild(uprightElement(document, piece, glyph));\n } else el.appendChild(pieceElement(document, piece));\n }\n\n const baseline = line.baselinePt - line.topPt;\n for (const piece of line.pieces) {\n const color = css(piece.color);\n for (const rule of piece.rules) {\n el.appendChild(ruleElement(document, rule, piece.leftPt, baseline - piece.risePt, color));\n }\n }\n return el;\n}\n\nfunction blockElement(document: Document, entry: LayerBlock): HTMLElement {\n const el = document.createElement('div');\n const style = el.style;\n style.position = 'absolute';\n style.left = px(entry.leftPt);\n style.top = px(entry.topPt);\n style.width = px(entry.widthPt);\n style.height = px(entry.heightPt);\n if (entry.block.turnDeg !== 0) {\n style.transformOrigin = '50% 50%';\n style.transform = `rotate(${String(entry.block.turnDeg)}deg)`;\n }\n el.setAttribute('data-text', String(entry.cNvPrId));\n if (entry.name !== undefined && entry.name !== '') el.setAttribute('data-name', entry.name);\n for (const line of entry.block.lines) el.appendChild(lineElement(document, line));\n return el;\n}\n\n/**\n * Mount a text layer into a host, over a slide of `size` points.\n *\n * The host is emptied first. The layer is sized in points and scaled with one\n * transform, which is the plan's rule: font metrics are not linear in point\n * size, so a renderer that re-laid out per zoom would change its line breaks as\n * the user zoomed.\n */\nexport function mountTextLayer(\n host: Element,\n blocks: readonly LayerBlock[],\n size: LayerSize,\n options: TextLayerOptions = {},\n): MountedTextLayer {\n if (host === null || typeof host !== 'object' || typeof host.appendChild !== 'function') {\n throw new RenderDomError('RENDER_DOM_NO_HOST', 'mountTextLayer needs an element to build into');\n }\n const document = options.document ?? host.ownerDocument;\n if (document === null || document === undefined) {\n throw new RenderDomError('RENDER_DOM_NO_DOCUMENT', 'the host element has no ownerDocument');\n }\n\n const root = document.createElement('div');\n const style = root.style;\n style.position = 'absolute';\n style.left = '0';\n style.top = '0';\n style.width = px(size.widthPt);\n style.height = px(size.heightPt);\n style.transformOrigin = '0 0';\n style.pointerEvents = options.interactive === true ? 'auto' : 'none';\n root.setAttribute('data-role', 'text-layer');\n\n const byId = new Map<number, HTMLElement>();\n for (const entry of blocks) {\n if (entry.block.lines.length === 0) continue;\n const el = blockElement(document, entry);\n byId.set(entry.cNvPrId, el);\n root.appendChild(el);\n }\n\n const apply = (scale: number): void => {\n style.transform = scale === 1 ? '' : `scale(${String(scale)})`;\n };\n apply(options.scale ?? 1);\n host.replaceChildren(root);\n\n return {\n root,\n element(cNvPrId: number): HTMLElement | null {\n return byId.get(cNvPrId) ?? null;\n },\n resize(scale: number): void {\n apply(scale);\n },\n unmount(): void {\n root.remove();\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pptx-studio/render-dom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The live DOM renderer: one element per shape, over the same layout engine as render-svg",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@pptx-studio/geometry": "^0.1.1",
|
|
45
|
-
"@pptx-studio/
|
|
46
|
-
"@pptx-studio/
|
|
47
|
-
"@pptx-studio/render-svg": "^0.
|
|
45
|
+
"@pptx-studio/paint": "^0.3.0",
|
|
46
|
+
"@pptx-studio/model": "^0.1.3",
|
|
47
|
+
"@pptx-studio/render-svg": "^0.4.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@arethetypeswrong/cli": "0.18.5",
|