quicklook-pptx-renderer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +175 -0
- package/dist/diff/compare.d.ts +17 -0
- package/dist/diff/compare.js +71 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +72 -0
- package/dist/lint.d.ts +27 -0
- package/dist/lint.js +328 -0
- package/dist/mapper/bleed-map.d.ts +6 -0
- package/dist/mapper/bleed-map.js +1 -0
- package/dist/mapper/constants.d.ts +2 -0
- package/dist/mapper/constants.js +4 -0
- package/dist/mapper/drawable-mapper.d.ts +16 -0
- package/dist/mapper/drawable-mapper.js +1464 -0
- package/dist/mapper/html-generator.d.ts +13 -0
- package/dist/mapper/html-generator.js +539 -0
- package/dist/mapper/image-mapper.d.ts +14 -0
- package/dist/mapper/image-mapper.js +70 -0
- package/dist/mapper/nano-malloc.d.ts +130 -0
- package/dist/mapper/nano-malloc.js +197 -0
- package/dist/mapper/ql-bleed.d.ts +35 -0
- package/dist/mapper/ql-bleed.js +254 -0
- package/dist/mapper/shape-mapper.d.ts +41 -0
- package/dist/mapper/shape-mapper.js +2384 -0
- package/dist/mapper/slide-mapper.d.ts +4 -0
- package/dist/mapper/slide-mapper.js +112 -0
- package/dist/mapper/style-builder.d.ts +12 -0
- package/dist/mapper/style-builder.js +30 -0
- package/dist/mapper/text-mapper.d.ts +14 -0
- package/dist/mapper/text-mapper.js +302 -0
- package/dist/model/enums.d.ts +25 -0
- package/dist/model/enums.js +2 -0
- package/dist/model/types.d.ts +482 -0
- package/dist/model/types.js +7 -0
- package/dist/package/content-types.d.ts +1 -0
- package/dist/package/content-types.js +4 -0
- package/dist/package/package.d.ts +10 -0
- package/dist/package/package.js +52 -0
- package/dist/package/relationships.d.ts +6 -0
- package/dist/package/relationships.js +25 -0
- package/dist/package/zip.d.ts +6 -0
- package/dist/package/zip.js +17 -0
- package/dist/reader/color.d.ts +3 -0
- package/dist/reader/color.js +79 -0
- package/dist/reader/drawing.d.ts +17 -0
- package/dist/reader/drawing.js +403 -0
- package/dist/reader/effects.d.ts +2 -0
- package/dist/reader/effects.js +83 -0
- package/dist/reader/fill.d.ts +2 -0
- package/dist/reader/fill.js +94 -0
- package/dist/reader/presentation.d.ts +5 -0
- package/dist/reader/presentation.js +127 -0
- package/dist/reader/slide-layout.d.ts +2 -0
- package/dist/reader/slide-layout.js +28 -0
- package/dist/reader/slide-master.d.ts +4 -0
- package/dist/reader/slide-master.js +49 -0
- package/dist/reader/slide.d.ts +2 -0
- package/dist/reader/slide.js +26 -0
- package/dist/reader/text-list-style.d.ts +2 -0
- package/dist/reader/text-list-style.js +9 -0
- package/dist/reader/text.d.ts +5 -0
- package/dist/reader/text.js +295 -0
- package/dist/reader/theme.d.ts +2 -0
- package/dist/reader/theme.js +109 -0
- package/dist/reader/transform.d.ts +2 -0
- package/dist/reader/transform.js +21 -0
- package/dist/render/image-renderer.d.ts +3 -0
- package/dist/render/image-renderer.js +33 -0
- package/dist/render/renderer.d.ts +9 -0
- package/dist/render/renderer.js +178 -0
- package/dist/render/shape-renderer.d.ts +3 -0
- package/dist/render/shape-renderer.js +175 -0
- package/dist/render/text-renderer.d.ts +3 -0
- package/dist/render/text-renderer.js +152 -0
- package/dist/resolve/color-resolver.d.ts +18 -0
- package/dist/resolve/color-resolver.js +321 -0
- package/dist/resolve/font-map.d.ts +2 -0
- package/dist/resolve/font-map.js +66 -0
- package/dist/resolve/inheritance.d.ts +5 -0
- package/dist/resolve/inheritance.js +106 -0
- package/package.json +74 -0
|
@@ -0,0 +1,2384 @@
|
|
|
1
|
+
import { resolveColor } from "../resolve/color-resolver.js";
|
|
2
|
+
import { emuToPx } from "./constants.js";
|
|
3
|
+
import { mapTextBody } from "./text-mapper.js";
|
|
4
|
+
import { nextAttachmentIndex } from "./image-mapper.js";
|
|
5
|
+
// Fixed padding OfficeImport adds around PDF shape renders (20px each side)
|
|
6
|
+
export const PDF_PADDING = 20;
|
|
7
|
+
// Geometry presets supported by CMCanonicalShapeBuilder (from runtime dump).
|
|
8
|
+
// Unsupported presets (cloud, heart, etc.) are silently skipped by OfficeImport.
|
|
9
|
+
// All geometry presets that CMCanonicalShapeBuilder supports.
|
|
10
|
+
// OfficeImport renders ALL of these as PDF (except rect without rotation which is CSS).
|
|
11
|
+
// Unsupported presets not in this set are silently skipped.
|
|
12
|
+
// Exactly 60 presets verified via runtime probing of CMCanonicalShapeBuilder.copyShapeWithTransform:
|
|
13
|
+
// Shapes NOT in this set are silently skipped by OfficeImport (nil CGPathRef → no <img> tag).
|
|
14
|
+
export const SUPPORTED_GEOMETRIES = new Set([
|
|
15
|
+
// Basic shapes (20)
|
|
16
|
+
"rect", "roundRect", "ellipse", "diamond", "triangle", "rtTriangle",
|
|
17
|
+
"parallelogram", "trapezoid", "hexagon", "octagon", "plus", "pentagon",
|
|
18
|
+
"chevron", "homePlate", "cube", "can",
|
|
19
|
+
// Stars (7)
|
|
20
|
+
"star4", "star5", "star6", "star7", "star8",
|
|
21
|
+
"star10", "star12", "star16", "star24", "star32",
|
|
22
|
+
// Callouts (4) — only wedge callouts + borderCallout1; all others → nil
|
|
23
|
+
"wedgeRectCallout", "wedgeRoundRectCallout", "wedgeEllipseCallout",
|
|
24
|
+
"borderCallout1", "cloudCallout",
|
|
25
|
+
// Block arrows (15) — no bent/curved/striped/notched/uturn
|
|
26
|
+
"rightArrow", "leftArrow", "upArrow", "downArrow",
|
|
27
|
+
"leftRightArrow", "upDownArrow", "quadArrow", "leftRightUpArrow",
|
|
28
|
+
"circularArrow",
|
|
29
|
+
"rightArrowCallout", "leftArrowCallout", "upArrowCallout", "downArrowCallout",
|
|
30
|
+
"leftRightArrowCallout", "upDownArrowCallout", "quadArrowCallout",
|
|
31
|
+
// Flowchart (2) — only these two; all others → nil
|
|
32
|
+
"flowChartAlternateProcess", "flowChartDecision",
|
|
33
|
+
// Connectors (10)
|
|
34
|
+
"line", "straightConnector1",
|
|
35
|
+
"bentConnector2", "bentConnector3", "bentConnector4", "bentConnector5",
|
|
36
|
+
"curvedConnector2", "curvedConnector3", "curvedConnector4", "curvedConnector5",
|
|
37
|
+
// Scrolls (1) — only verticalScroll; horizontalScroll → nil
|
|
38
|
+
"verticalScroll",
|
|
39
|
+
]);
|
|
40
|
+
export function mapShape(shape, styles, attachments, ctx) {
|
|
41
|
+
const b = shape.bounds;
|
|
42
|
+
if (!b)
|
|
43
|
+
return "";
|
|
44
|
+
const x = emuToPx(b.x);
|
|
45
|
+
const y = emuToPx(b.y);
|
|
46
|
+
const w = emuToPx(b.cx);
|
|
47
|
+
const h = emuToPx(b.cy);
|
|
48
|
+
const fill = shape.fill;
|
|
49
|
+
const geom = shape.geometry?.preset ?? "rect";
|
|
50
|
+
// CSS path: only rect with no rotation. Everything else → PDF.
|
|
51
|
+
// (Disassembly showed roundRect/ellipse as CSS-eligible, but empirical tests confirm
|
|
52
|
+
// OfficeImport always renders them as PDF in practice — the CSS check must fail internally.)
|
|
53
|
+
const isCssPath = geom === "rect" && !(b.rot && b.rot !== 0);
|
|
54
|
+
const hasText = shape.textBody && shape.textBody.paragraphs.some((p) => p.runs.some((r) => r.type !== "br" && "text" in r && String(r.text ?? "").trim() !== ""));
|
|
55
|
+
// PDF path: non-rect geometry, or rect with rotation
|
|
56
|
+
if (!isCssPath) {
|
|
57
|
+
if (!SUPPORTED_GEOMETRIES.has(geom))
|
|
58
|
+
return "";
|
|
59
|
+
const pdfHtml = renderAsPdf(shape, b, attachments, ctx);
|
|
60
|
+
if (hasText && shape.textBody) {
|
|
61
|
+
const tb = shapeTextBox(b, geom, shape.geometry?.adjustValues);
|
|
62
|
+
// OfficeImport swaps text overlay w/h for rotations where the shape's
|
|
63
|
+
// dominant axis flips (45°-135° mod 180), recomputing position from center.
|
|
64
|
+
const rotDeg = ((b.rot ?? 0) / 60000) % 180;
|
|
65
|
+
if (rotDeg >= 45 && rotDeg <= 135) {
|
|
66
|
+
const cxEmu = tb.x + tb.cx / 2;
|
|
67
|
+
const cyEmu = tb.y + tb.cy / 2;
|
|
68
|
+
const newCx = tb.cy; // swap
|
|
69
|
+
const newCy = tb.cx;
|
|
70
|
+
tb.x = cxEmu - newCx / 2;
|
|
71
|
+
tb.y = cyEmu - newCy / 2;
|
|
72
|
+
tb.cx = newCx;
|
|
73
|
+
tb.cy = newCy;
|
|
74
|
+
}
|
|
75
|
+
const textCtx = { ...ctx, placeholderType: shape.placeholder?.type, fontRefColor: shape.shapeStyle?.fontRef?.color };
|
|
76
|
+
const textHtml = mapTextBody(shape.textBody, tb, styles, textCtx);
|
|
77
|
+
const tx = emuToPx(tb.x), ty = emuToPx(tb.y), tw = emuToPx(tb.cx), th = emuToPx(tb.cy);
|
|
78
|
+
return pdfHtml + `<div style="position:absolute; top:${ty}; left:${tx}; width:${tw}; height:${th};">${textHtml}</div>`;
|
|
79
|
+
}
|
|
80
|
+
return pdfHtml;
|
|
81
|
+
}
|
|
82
|
+
// Build common style parts for CSS path (rect, roundRect, ellipse without effects)
|
|
83
|
+
const styleParts = [`position:absolute`, `top:${y}`, `left:${x}`, `width:${w}`, `height:${h}`];
|
|
84
|
+
const fillCss = fillToCss(fill, ctx, attachments);
|
|
85
|
+
if (fillCss)
|
|
86
|
+
styleParts.push(fillCss);
|
|
87
|
+
// OfficeImport CSS path: renders stroke as thin solid border with actual stroke color.
|
|
88
|
+
// QL uses the keyword "black" for #000000 in border-color.
|
|
89
|
+
// Note: QL concatenates border props without space separators
|
|
90
|
+
// Check explicit stroke first, then fall back to theme lnRef.
|
|
91
|
+
// Only use theme if shape has NO explicit stroke at all (undefined).
|
|
92
|
+
// An explicit empty <a:ln/> (stroke set but no fill) overrides theme — no border.
|
|
93
|
+
if (shape.stroke?.fill?.type === "solid") {
|
|
94
|
+
const c = rgbaToColor(resolveColor(shape.stroke.fill.color, ctx.colorMap, ctx.colorScheme));
|
|
95
|
+
const borderColor = c === "#000000" ? "black" : c;
|
|
96
|
+
styleParts.push(`border-style:solid;border-width:thin;border-color:${borderColor}`);
|
|
97
|
+
}
|
|
98
|
+
else if (!shape.stroke && resolveThemeStroke(shape, ctx)) {
|
|
99
|
+
// OfficeImport CSS path always renders theme-resolved borders as thin/solid/black
|
|
100
|
+
styleParts.push(`border-style:solid;border-width:thin;border-color:black`);
|
|
101
|
+
}
|
|
102
|
+
const style = styleParts.join("; ") + ";";
|
|
103
|
+
// Text shape
|
|
104
|
+
if (hasText && shape.textBody) {
|
|
105
|
+
const textCtx = { ...ctx, placeholderType: shape.placeholder?.type, fontRefColor: shape.shapeStyle?.fontRef?.color };
|
|
106
|
+
const textHtml = mapTextBody(shape.textBody, b, styles, textCtx);
|
|
107
|
+
return `<div style="${style}">${textHtml}</div>`;
|
|
108
|
+
}
|
|
109
|
+
// Simple filled rect (no text)
|
|
110
|
+
if (fill && fill.type !== "noFill" && fillCss) {
|
|
111
|
+
const marginCss = "margin-top:3px; margin-left:7px; margin-bottom:3px; margin-right:7px;";
|
|
112
|
+
const innerClass = styles.addClass(marginCss);
|
|
113
|
+
return `<div style="${style}"><div class="${innerClass}"></div></div>`;
|
|
114
|
+
}
|
|
115
|
+
// noFill with no text: emit positioned div with margin inner div (matches OfficeImport)
|
|
116
|
+
const marginCss = "margin-top:3px; margin-left:7px; margin-bottom:3px; margin-right:7px;";
|
|
117
|
+
const innerClass = styles.addClass(marginCss);
|
|
118
|
+
return `<div style="${style}"><div class="${innerClass}"></div></div>`;
|
|
119
|
+
}
|
|
120
|
+
function renderAsPdf(shape, bounds, attachments, ctx) {
|
|
121
|
+
const rot = bounds.rot;
|
|
122
|
+
let imgX, imgY, imgW, imgH;
|
|
123
|
+
if (rot && rot !== 0) {
|
|
124
|
+
// Compute AABB in EMU space, then convert to px — matches OfficeImport precision
|
|
125
|
+
const rad = (rot / 60000) * (Math.PI / 180);
|
|
126
|
+
const cosA = Math.abs(Math.cos(rad));
|
|
127
|
+
const sinA = Math.abs(Math.sin(rad));
|
|
128
|
+
const aabbW_emu = bounds.cx * cosA + bounds.cy * sinA;
|
|
129
|
+
const aabbH_emu = bounds.cx * sinA + bounds.cy * cosA;
|
|
130
|
+
const cx_emu = bounds.x + bounds.cx / 2;
|
|
131
|
+
const cy_emu = bounds.y + bounds.cy / 2;
|
|
132
|
+
const aabbX_emu = cx_emu - aabbW_emu / 2;
|
|
133
|
+
const aabbY_emu = cy_emu - aabbH_emu / 2;
|
|
134
|
+
// OfficeImport uses round for position, trunc for dimensions
|
|
135
|
+
imgX = Math.round(aabbX_emu / 12700) - PDF_PADDING;
|
|
136
|
+
imgY = Math.round(aabbY_emu / 12700) - PDF_PADDING;
|
|
137
|
+
imgW = emuToPx(aabbW_emu) + PDF_PADDING * 2;
|
|
138
|
+
imgH = emuToPx(aabbH_emu) + PDF_PADDING * 2;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Compute tight AABB for geometry presets (stars don't fill full bounds)
|
|
142
|
+
const preset = shape.geometry?.preset ?? "rect";
|
|
143
|
+
const aabb = geometryAABB(preset);
|
|
144
|
+
const pathXEmu = bounds.x + aabb.minX * bounds.cx;
|
|
145
|
+
const pathYEmu = bounds.y + aabb.minY * bounds.cy;
|
|
146
|
+
const pathWEmu = (aabb.maxX - aabb.minX) * bounds.cx;
|
|
147
|
+
const pathHEmu = (aabb.maxY - aabb.minY) * bounds.cy;
|
|
148
|
+
// OfficeImport computes img bounds using exact EMU→pt values:
|
|
149
|
+
// imgPos = trunc(pos_pt - pad), imgDim = trunc(dim_pt + 2*pad)
|
|
150
|
+
const pathX_pt = pathXEmu / 12700;
|
|
151
|
+
const pathY_pt = pathYEmu / 12700;
|
|
152
|
+
const pathW_pt = pathWEmu / 12700;
|
|
153
|
+
const pathH_pt = pathHEmu / 12700;
|
|
154
|
+
imgX = Math.trunc(pathX_pt - PDF_PADDING);
|
|
155
|
+
imgY = Math.trunc(pathY_pt - PDF_PADDING);
|
|
156
|
+
imgW = Math.trunc(pathW_pt + PDF_PADDING * 2);
|
|
157
|
+
imgH = Math.trunc(pathH_pt + PDF_PADDING * 2);
|
|
158
|
+
}
|
|
159
|
+
// Generate minimal PDF with the shape rendered
|
|
160
|
+
const fillColor = fillToColor(shape.fill, ctx) ?? "#000000";
|
|
161
|
+
const adjustValues = shape.geometry?.adjustValues;
|
|
162
|
+
// OfficeImport's CMDrawingContext always uses fill+stroke (B operator).
|
|
163
|
+
// Explicit <a:ln><a:solidFill> → declared color; empty <a:ln/> → thin black default.
|
|
164
|
+
const strokeInfo = resolveStrokeForPdf(shape, ctx);
|
|
165
|
+
const shapeRot = bounds.rot ?? 0;
|
|
166
|
+
// OfficeImport uses exact EMU→pt float dimensions for geometry, not truncated integers
|
|
167
|
+
const w_pt = bounds.cx / 12700;
|
|
168
|
+
const h_pt = bounds.cy / 12700;
|
|
169
|
+
const pdf = generateShapePdf(w_pt, h_pt, shape.geometry?.preset ?? "rect", fillColor, adjustValues, strokeInfo, shapeRot !== 0 ? shapeRot : undefined);
|
|
170
|
+
const idx = nextAttachmentIndex();
|
|
171
|
+
const name = `Attachment${idx}.pdf`;
|
|
172
|
+
attachments.set(name, pdf);
|
|
173
|
+
return `<img src="${name}" style="position:absolute; top:${imgY}; left:${imgX}; width:${imgW}; height:${imgH};">`;
|
|
174
|
+
}
|
|
175
|
+
/** Generate a minimal PDF buffer containing the shape. */
|
|
176
|
+
function generateShapePdf(w, h, preset, fillColor, adjustValues, stroke, rotation) {
|
|
177
|
+
const pad = PDF_PADDING;
|
|
178
|
+
const rad = rotation ? (rotation / 60000) * (Math.PI / 180) : 0;
|
|
179
|
+
// OfficeImport uses exact EMU→pt float values for MediaBox dimensions.
|
|
180
|
+
// The img tag uses trunc() of these values, causing a tiny mismatch that's invisible.
|
|
181
|
+
let pageW, pageH;
|
|
182
|
+
if (rad !== 0) {
|
|
183
|
+
const cosA = Math.abs(Math.cos(rad));
|
|
184
|
+
const sinA = Math.abs(Math.sin(rad));
|
|
185
|
+
pageW = w * cosA + h * sinA + pad * 2;
|
|
186
|
+
pageH = w * sinA + h * cosA + pad * 2;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
pageW = w + pad * 2;
|
|
190
|
+
pageH = h + pad * 2;
|
|
191
|
+
}
|
|
192
|
+
// Draw shape at origin in its own coordinate system (pad, w, h)
|
|
193
|
+
const shapeH = h + pad * 2;
|
|
194
|
+
const drawCmd = getShapeDrawCmd(pad, w, h, shapeH, preset, adjustValues);
|
|
195
|
+
const fHex = fillColor.replace("#", "");
|
|
196
|
+
const fr = parseInt(fHex.slice(0, 2), 16) / 255;
|
|
197
|
+
const fg = parseInt(fHex.slice(2, 4), 16) / 255;
|
|
198
|
+
const fb = parseInt(fHex.slice(4, 6), 16) / 255;
|
|
199
|
+
// OfficeImport uses B (fill+stroke) with a graphics state that makes the default stroke
|
|
200
|
+
// sub-pixel invisible. We can't replicate the exact graphics state, so:
|
|
201
|
+
// - Explicit <a:ln><a:solidFill>: use B with declared color/width
|
|
202
|
+
// - No explicit stroke: use f (fill-only) which matches the visual output
|
|
203
|
+
let colorSetup = `${fr.toFixed(3)} ${fg.toFixed(3)} ${fb.toFixed(3)} rg\n`;
|
|
204
|
+
let finalDrawCmd = drawCmd;
|
|
205
|
+
if (stroke) {
|
|
206
|
+
const sr = parseInt(stroke.color.replace("#", "").slice(0, 2), 16) / 255;
|
|
207
|
+
const sg = parseInt(stroke.color.replace("#", "").slice(2, 4), 16) / 255;
|
|
208
|
+
const sb = parseInt(stroke.color.replace("#", "").slice(4, 6), 16) / 255;
|
|
209
|
+
colorSetup = `${sr.toFixed(3)} ${sg.toFixed(3)} ${sb.toFixed(3)} RG\n${stroke.width.toFixed(1)} w\n` + colorSetup;
|
|
210
|
+
finalDrawCmd = drawCmd.replace(/\bf\b/g, "B");
|
|
211
|
+
}
|
|
212
|
+
let stream;
|
|
213
|
+
if (rad !== 0) {
|
|
214
|
+
// Rotation CTM: PPTX rotation is CW in screen coords (Y-down).
|
|
215
|
+
// In PDF coords (Y-up), CW = negative angle, so we negate rad.
|
|
216
|
+
// Combined: translate(page_center) · rotate(-rad) · translate(-shape_center)
|
|
217
|
+
const cx = pageW / 2;
|
|
218
|
+
const cy = pageH / 2;
|
|
219
|
+
const cosR = Math.cos(-rad);
|
|
220
|
+
const sinR = Math.sin(-rad);
|
|
221
|
+
const ox = -(w + pad * 2) / 2;
|
|
222
|
+
const oy = -(h + pad * 2) / 2;
|
|
223
|
+
// PDF CTM [a b c d e f]: x' = ax + cy + e, y' = bx + dy + f
|
|
224
|
+
const a = cosR, b = sinR, c = -sinR, d = cosR;
|
|
225
|
+
const e = cx + cosR * ox - sinR * oy;
|
|
226
|
+
const f = cy + sinR * ox + cosR * oy;
|
|
227
|
+
stream = `q\n${a.toFixed(6)} ${b.toFixed(6)} ${c.toFixed(6)} ${d.toFixed(6)} ${e.toFixed(3)} ${f.toFixed(3)} cm\n${colorSetup}${finalDrawCmd}\nQ`;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
stream = `${colorSetup}${finalDrawCmd}`;
|
|
231
|
+
}
|
|
232
|
+
return buildPdf(pageW, pageH, stream);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Generate PDF path drawing commands for a shape.
|
|
236
|
+
* pad = uniform offset from (0,0) where the shape starts in screen coords.
|
|
237
|
+
* totalH = total height of the coordinate space (needed for Y-flipping).
|
|
238
|
+
* For standalone shapes: pad=PDF_PADDING, totalH=h+2*pad.
|
|
239
|
+
* For group children: pad=0, totalH=h (shape draws at origin, caller uses cm to translate).
|
|
240
|
+
*/
|
|
241
|
+
export function getShapeDrawCmd(pad, w, h, totalH, preset, adjustValues) {
|
|
242
|
+
// Helpers for polygon paths in PDF coordinates (Y flipped: pdfY = totalH - screenY)
|
|
243
|
+
const py = (sy) => totalH - sy; // screen Y → PDF Y
|
|
244
|
+
const polyPath = (pts) => {
|
|
245
|
+
const [first, ...rest] = pts;
|
|
246
|
+
return [`${first[0]} ${py(first[1])} m`, ...rest.map(([x, y]) => `${x} ${py(y)} l`), "f"].join("\n");
|
|
247
|
+
};
|
|
248
|
+
// Like polyPath but returns array of line commands without closing "f" — for composing multi-subpath shapes
|
|
249
|
+
const polyPathLines = (pts) => {
|
|
250
|
+
const [first, ...rest] = pts;
|
|
251
|
+
return [`${first[0]} ${py(first[1])} m`, ...rest.map(([x, y]) => `${x} ${py(y)} l`)];
|
|
252
|
+
};
|
|
253
|
+
// Ellipse drawing commands (reusable for shapes that are circles/ellipses)
|
|
254
|
+
const ellipseCmd = (_pad, _w, _h, _totalH) => {
|
|
255
|
+
const cx = _pad + _w / 2, cy = _pad + _h / 2;
|
|
256
|
+
const rx = _w / 2, ry = _h / 2;
|
|
257
|
+
const k = 0.5522847498;
|
|
258
|
+
return [
|
|
259
|
+
`${cx + rx} ${_totalH - cy} m`,
|
|
260
|
+
`${cx + rx} ${_totalH - (cy - ry * k)} ${cx + rx * k} ${_totalH - (cy - ry)} ${cx} ${_totalH - (cy - ry)} c`,
|
|
261
|
+
`${cx - rx * k} ${_totalH - (cy - ry)} ${cx - rx} ${_totalH - (cy - ry * k)} ${cx - rx} ${_totalH - cy} c`,
|
|
262
|
+
`${cx - rx} ${_totalH - (cy + ry * k)} ${cx - rx * k} ${_totalH - (cy + ry)} ${cx} ${_totalH - (cy + ry)} c`,
|
|
263
|
+
`${cx + rx * k} ${_totalH - (cy + ry)} ${cx + rx} ${_totalH - (cy + ry * k)} ${cx + rx} ${_totalH - cy} c`,
|
|
264
|
+
"f",
|
|
265
|
+
].join("\n");
|
|
266
|
+
};
|
|
267
|
+
// Build drawing commands based on geometry
|
|
268
|
+
// OfficeImport uses fill+stroke (B operator) with a coordinate transform
|
|
269
|
+
let drawCmd;
|
|
270
|
+
if (preset === "ellipse") {
|
|
271
|
+
// Approximate ellipse with bezier curves
|
|
272
|
+
const cx = pad + w / 2;
|
|
273
|
+
const cy = pad + h / 2;
|
|
274
|
+
const rx = w / 2;
|
|
275
|
+
const ry = h / 2;
|
|
276
|
+
const k = 0.5522847498; // kappa for circle approximation
|
|
277
|
+
drawCmd = [
|
|
278
|
+
`${cx + rx} ${totalH - cy} m`,
|
|
279
|
+
`${cx + rx} ${totalH - (cy - ry * k)} ${cx + rx * k} ${totalH - (cy - ry)} ${cx} ${totalH - (cy - ry)} c`,
|
|
280
|
+
`${cx - rx * k} ${totalH - (cy - ry)} ${cx - rx} ${totalH - (cy - ry * k)} ${cx - rx} ${totalH - cy} c`,
|
|
281
|
+
`${cx - rx} ${totalH - (cy + ry * k)} ${cx - rx * k} ${totalH - (cy + ry)} ${cx} ${totalH - (cy + ry)} c`,
|
|
282
|
+
`${cx + rx * k} ${totalH - (cy + ry)} ${cx + rx} ${totalH - (cy + ry * k)} ${cx + rx} ${totalH - cy} c`,
|
|
283
|
+
"f",
|
|
284
|
+
].join("\n");
|
|
285
|
+
}
|
|
286
|
+
else if (preset === "roundRect") {
|
|
287
|
+
// Corner radius: use adjustValue 'adj' (default 16667 = 16.667% of min dimension)
|
|
288
|
+
const adjVal = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 16667;
|
|
289
|
+
const radius = Math.min(w, h) * adjVal / 100000;
|
|
290
|
+
const x0 = pad, y0 = totalH - pad - h;
|
|
291
|
+
const x1 = pad + w, y1 = totalH - pad;
|
|
292
|
+
const rr = radius;
|
|
293
|
+
drawCmd = [
|
|
294
|
+
`${x0 + rr} ${y0} m`,
|
|
295
|
+
`${x1 - rr} ${y0} l`,
|
|
296
|
+
`${x1} ${y0} ${x1} ${y0 + rr} v`,
|
|
297
|
+
`${x1} ${y1 - rr} l`,
|
|
298
|
+
`${x1} ${y1} ${x1 - rr} ${y1} v`,
|
|
299
|
+
`${x0 + rr} ${y1} l`,
|
|
300
|
+
`${x0} ${y1} ${x0} ${y1 - rr} v`,
|
|
301
|
+
`${x0} ${y0 + rr} l`,
|
|
302
|
+
`${x0} ${y0} ${x0 + rr} ${y0} v`,
|
|
303
|
+
"f",
|
|
304
|
+
].join("\n");
|
|
305
|
+
}
|
|
306
|
+
else if (preset === "diamond") {
|
|
307
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
308
|
+
drawCmd = polyPath([[cx, pad], [pad + w, cy], [cx, pad + h], [pad, cy]]);
|
|
309
|
+
}
|
|
310
|
+
else if (preset === "triangle") {
|
|
311
|
+
drawCmd = polyPath([[pad + w / 2, pad], [pad + w, pad + h], [pad, pad + h]]);
|
|
312
|
+
}
|
|
313
|
+
else if (preset === "rtTriangle") {
|
|
314
|
+
drawCmd = polyPath([[pad, pad], [pad + w, pad + h], [pad, pad + h]]);
|
|
315
|
+
}
|
|
316
|
+
else if (preset === "pentagon") {
|
|
317
|
+
// Regular pentagon: top vertex at center-top, others at ±54° and ±126° from top
|
|
318
|
+
const cx = pad + w / 2, top = pad, bot = pad + h;
|
|
319
|
+
const mid = pad + h * 0.382; // ~38.2% down
|
|
320
|
+
const pts = [
|
|
321
|
+
[cx, top],
|
|
322
|
+
[pad + w, mid],
|
|
323
|
+
[pad + w * 0.809, bot],
|
|
324
|
+
[pad + w * 0.191, bot],
|
|
325
|
+
[pad, mid],
|
|
326
|
+
];
|
|
327
|
+
drawCmd = polyPath(pts);
|
|
328
|
+
}
|
|
329
|
+
else if (preset === "hexagon") {
|
|
330
|
+
const adjVal = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
331
|
+
const inset = w * adjVal / 100000;
|
|
332
|
+
const cy = pad + h / 2;
|
|
333
|
+
drawCmd = polyPath([
|
|
334
|
+
[pad + inset, pad],
|
|
335
|
+
[pad + w - inset, pad],
|
|
336
|
+
[pad + w, cy],
|
|
337
|
+
[pad + w - inset, pad + h],
|
|
338
|
+
[pad + inset, pad + h],
|
|
339
|
+
[pad, cy],
|
|
340
|
+
]);
|
|
341
|
+
}
|
|
342
|
+
else if (preset === "octagon") {
|
|
343
|
+
// OfficeImport's CMCanonicalShapeBuilder: uses ss (min dim) for equal-angle cuts
|
|
344
|
+
// Default adj matches 25000 (ss/4), not OOXML's 29289 (1-1/√2)
|
|
345
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
346
|
+
const ss = Math.min(w, h);
|
|
347
|
+
const inset = ss * adj / 100000;
|
|
348
|
+
drawCmd = polyPath([
|
|
349
|
+
[pad + inset, pad],
|
|
350
|
+
[pad + w - inset, pad],
|
|
351
|
+
[pad + w, pad + inset],
|
|
352
|
+
[pad + w, pad + h - inset],
|
|
353
|
+
[pad + w - inset, pad + h],
|
|
354
|
+
[pad + inset, pad + h],
|
|
355
|
+
[pad, pad + h - inset],
|
|
356
|
+
[pad, pad + inset],
|
|
357
|
+
]);
|
|
358
|
+
}
|
|
359
|
+
else if (preset === "parallelogram") {
|
|
360
|
+
// OfficeImport's CMCanonicalShapeBuilder: shift = w * adj / 200000 (half of OOXML spec)
|
|
361
|
+
// Top edge shifted right: top-left at (shift, 0), bottom-left at (0, h)
|
|
362
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
363
|
+
const shift = w * adj / 200000;
|
|
364
|
+
drawCmd = polyPath([
|
|
365
|
+
[pad + shift, pad],
|
|
366
|
+
[pad + w, pad],
|
|
367
|
+
[pad + w - shift, pad + h],
|
|
368
|
+
[pad, pad + h],
|
|
369
|
+
]);
|
|
370
|
+
}
|
|
371
|
+
else if (preset === "trapezoid") {
|
|
372
|
+
// OOXML trapezoid: wide at bottom, narrow at top (inset applied to top edge)
|
|
373
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
374
|
+
const inset = w * adj / 100000;
|
|
375
|
+
drawCmd = polyPath([
|
|
376
|
+
[pad + inset, pad],
|
|
377
|
+
[pad + w - inset, pad],
|
|
378
|
+
[pad + w, pad + h],
|
|
379
|
+
[pad, pad + h],
|
|
380
|
+
]);
|
|
381
|
+
}
|
|
382
|
+
else if (preset === "plus") {
|
|
383
|
+
// OfficeImport's CMCanonicalShapeBuilder: uses ss (min dim) for both axes
|
|
384
|
+
// to create square-armed cross regardless of aspect ratio
|
|
385
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
386
|
+
const ss = Math.min(w, h);
|
|
387
|
+
const inset = ss * adj / 100000;
|
|
388
|
+
const cx1 = pad + inset, cx2 = pad + w - inset;
|
|
389
|
+
const cy1 = pad + inset, cy2 = pad + h - inset;
|
|
390
|
+
drawCmd = polyPath([
|
|
391
|
+
[cx1, pad], [cx2, pad],
|
|
392
|
+
[cx2, cy1], [pad + w, cy1],
|
|
393
|
+
[pad + w, cy2], [cx2, cy2],
|
|
394
|
+
[cx2, pad + h], [cx1, pad + h],
|
|
395
|
+
[cx1, cy2], [pad, cy2],
|
|
396
|
+
[pad, cy1], [cx1, cy1],
|
|
397
|
+
]);
|
|
398
|
+
}
|
|
399
|
+
else if (preset === "star4") {
|
|
400
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
401
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 12500;
|
|
402
|
+
const ix = w * adj / 100000, iy = h * adj / 100000;
|
|
403
|
+
drawCmd = polyPath([
|
|
404
|
+
[cx, pad],
|
|
405
|
+
[cx + ix, cy - iy], [pad + w, cy],
|
|
406
|
+
[cx + ix, cy + iy], [cx, pad + h],
|
|
407
|
+
[cx - ix, cy + iy], [pad, cy],
|
|
408
|
+
[cx - ix, cy - iy],
|
|
409
|
+
]);
|
|
410
|
+
}
|
|
411
|
+
else if (preset === "star5") {
|
|
412
|
+
const cx = pad + w / 2, top = pad, bot = pad + h;
|
|
413
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
414
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 19098;
|
|
415
|
+
const innerR_x = outerR_x * adj / 50000;
|
|
416
|
+
const innerR_y = outerR_y * adj / 50000;
|
|
417
|
+
const pts = [];
|
|
418
|
+
for (let i = 0; i < 5; i++) {
|
|
419
|
+
const outerA = (Math.PI * (-0.5 + i * 2 / 5));
|
|
420
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, pad + outerR_y + Math.sin(outerA) * outerR_y]);
|
|
421
|
+
const innerA = (Math.PI * (-0.5 + (i * 2 + 1) / 5));
|
|
422
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, pad + outerR_y + Math.sin(innerA) * innerR_y]);
|
|
423
|
+
}
|
|
424
|
+
drawCmd = polyPath(pts);
|
|
425
|
+
}
|
|
426
|
+
else if (preset === "star6") {
|
|
427
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
428
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 28868;
|
|
429
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
430
|
+
const innerR_x = outerR_x * adj / 50000, innerR_y = outerR_y * adj / 50000;
|
|
431
|
+
const pts = [];
|
|
432
|
+
for (let i = 0; i < 6; i++) {
|
|
433
|
+
const outerA = Math.PI * (-0.5 + i / 3);
|
|
434
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, cy + Math.sin(outerA) * outerR_y]);
|
|
435
|
+
const innerA = Math.PI * (-0.5 + (i * 2 + 1) / 6);
|
|
436
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, cy + Math.sin(innerA) * innerR_y]);
|
|
437
|
+
}
|
|
438
|
+
drawCmd = polyPath(pts);
|
|
439
|
+
}
|
|
440
|
+
else if (preset === "star8") {
|
|
441
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
442
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 37500;
|
|
443
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
444
|
+
const innerR_x = outerR_x * adj / 50000, innerR_y = outerR_y * adj / 50000;
|
|
445
|
+
const pts = [];
|
|
446
|
+
for (let i = 0; i < 8; i++) {
|
|
447
|
+
const outerA = Math.PI * (-0.5 + i / 4);
|
|
448
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, cy + Math.sin(outerA) * outerR_y]);
|
|
449
|
+
const innerA = Math.PI * (-0.5 + (i * 2 + 1) / 8);
|
|
450
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, cy + Math.sin(innerA) * innerR_y]);
|
|
451
|
+
}
|
|
452
|
+
drawCmd = polyPath(pts);
|
|
453
|
+
}
|
|
454
|
+
else if (preset === "chevron") {
|
|
455
|
+
// OfficeImport's CMCanonicalShapeBuilder: notch depth = w * adj / 400000
|
|
456
|
+
// (quartered vs OOXML spec: both arrow point and notch use this value)
|
|
457
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 50000;
|
|
458
|
+
const notch = w * adj / 400000;
|
|
459
|
+
drawCmd = polyPath([
|
|
460
|
+
[pad, pad],
|
|
461
|
+
[pad + w - notch, pad],
|
|
462
|
+
[pad + w, pad + h / 2],
|
|
463
|
+
[pad + w - notch, pad + h],
|
|
464
|
+
[pad, pad + h],
|
|
465
|
+
[pad + notch, pad + h / 2],
|
|
466
|
+
]);
|
|
467
|
+
}
|
|
468
|
+
else if (preset === "homePlate") {
|
|
469
|
+
// OfficeImport's CMCanonicalShapeBuilder: arrow start at w*adj/200000 from left
|
|
470
|
+
// (halved vs OOXML spec), arrow tip at right edge
|
|
471
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 50000;
|
|
472
|
+
const flat = w * adj / 200000;
|
|
473
|
+
drawCmd = polyPath([
|
|
474
|
+
[pad, pad],
|
|
475
|
+
[pad + flat, pad],
|
|
476
|
+
[pad + w, pad + h / 2],
|
|
477
|
+
[pad + flat, pad + h],
|
|
478
|
+
[pad, pad + h],
|
|
479
|
+
]);
|
|
480
|
+
}
|
|
481
|
+
else if (preset === "rightArrow") {
|
|
482
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
483
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
484
|
+
const stemH = h * adj1 / 100000;
|
|
485
|
+
const headW = w * adj2 / 100000;
|
|
486
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
487
|
+
drawCmd = polyPath([
|
|
488
|
+
[pad, stemTop],
|
|
489
|
+
[pad + w - headW, stemTop],
|
|
490
|
+
[pad + w - headW, pad],
|
|
491
|
+
[pad + w, pad + h / 2],
|
|
492
|
+
[pad + w - headW, pad + h],
|
|
493
|
+
[pad + w - headW, stemBot],
|
|
494
|
+
[pad, stemBot],
|
|
495
|
+
]);
|
|
496
|
+
}
|
|
497
|
+
else if (preset === "leftArrow") {
|
|
498
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
499
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
500
|
+
const stemH = h * adj1 / 100000;
|
|
501
|
+
const headW = w * adj2 / 100000;
|
|
502
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
503
|
+
drawCmd = polyPath([
|
|
504
|
+
[pad + headW, stemTop],
|
|
505
|
+
[pad + w, stemTop],
|
|
506
|
+
[pad + w, stemBot],
|
|
507
|
+
[pad + headW, stemBot],
|
|
508
|
+
[pad + headW, pad + h],
|
|
509
|
+
[pad, pad + h / 2],
|
|
510
|
+
[pad + headW, pad],
|
|
511
|
+
]);
|
|
512
|
+
}
|
|
513
|
+
else if (preset === "upArrow") {
|
|
514
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
515
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
516
|
+
const stemW = w * adj1 / 100000;
|
|
517
|
+
const headH = h * adj2 / 100000;
|
|
518
|
+
const stemL = pad + (w - stemW) / 2, stemR = pad + (w + stemW) / 2;
|
|
519
|
+
drawCmd = polyPath([
|
|
520
|
+
[pad + w / 2, pad],
|
|
521
|
+
[pad + w, pad + headH],
|
|
522
|
+
[stemR, pad + headH],
|
|
523
|
+
[stemR, pad + h],
|
|
524
|
+
[stemL, pad + h],
|
|
525
|
+
[stemL, pad + headH],
|
|
526
|
+
[pad, pad + headH],
|
|
527
|
+
]);
|
|
528
|
+
}
|
|
529
|
+
else if (preset === "downArrow") {
|
|
530
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
531
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
532
|
+
const stemW = w * adj1 / 100000;
|
|
533
|
+
const headH = h * adj2 / 100000;
|
|
534
|
+
const stemL = pad + (w - stemW) / 2, stemR = pad + (w + stemW) / 2;
|
|
535
|
+
drawCmd = polyPath([
|
|
536
|
+
[stemL, pad],
|
|
537
|
+
[stemR, pad],
|
|
538
|
+
[stemR, pad + h - headH],
|
|
539
|
+
[pad + w, pad + h - headH],
|
|
540
|
+
[pad + w / 2, pad + h],
|
|
541
|
+
[pad, pad + h - headH],
|
|
542
|
+
[stemL, pad + h - headH],
|
|
543
|
+
]);
|
|
544
|
+
}
|
|
545
|
+
else if (preset === "can") {
|
|
546
|
+
// Cylinder: rectangle body with elliptical top and bottom
|
|
547
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
548
|
+
const capH = h * adj / 200000;
|
|
549
|
+
const k = 0.5522847498;
|
|
550
|
+
const cx = pad + w / 2, rx = w / 2;
|
|
551
|
+
// Body rectangle
|
|
552
|
+
const bodyTop = pad + capH, bodyBot = pad + h - capH;
|
|
553
|
+
// Bottom ellipse, top ellipse arcs
|
|
554
|
+
drawCmd = [
|
|
555
|
+
// Start at left of body top
|
|
556
|
+
`${pad} ${py(bodyTop)} m`,
|
|
557
|
+
`${pad} ${py(bodyBot)} l`,
|
|
558
|
+
// Bottom ellipse (half)
|
|
559
|
+
`${pad} ${py(bodyBot + capH * k)} ${cx - rx * k} ${py(pad + h)} ${cx} ${py(pad + h)} c`,
|
|
560
|
+
`${cx + rx * k} ${py(pad + h)} ${pad + w} ${py(bodyBot + capH * k)} ${pad + w} ${py(bodyBot)} c`,
|
|
561
|
+
`${pad + w} ${py(bodyTop)} l`,
|
|
562
|
+
// Top ellipse (half, going back)
|
|
563
|
+
`${pad + w} ${py(bodyTop - capH * k)} ${cx + rx * k} ${py(pad)} ${cx} ${py(pad)} c`,
|
|
564
|
+
`${cx - rx * k} ${py(pad)} ${pad} ${py(bodyTop - capH * k)} ${pad} ${py(bodyTop)} c`,
|
|
565
|
+
"f",
|
|
566
|
+
].join("\n");
|
|
567
|
+
}
|
|
568
|
+
else if (preset === "cube") {
|
|
569
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
570
|
+
const d = Math.min(w, h) * adj / 100000;
|
|
571
|
+
// Front face + top face + right face as single path
|
|
572
|
+
drawCmd = polyPath([
|
|
573
|
+
[pad, pad + d],
|
|
574
|
+
[pad + d, pad],
|
|
575
|
+
[pad + w, pad],
|
|
576
|
+
[pad + w, pad + h - d],
|
|
577
|
+
[pad + w - d, pad + h],
|
|
578
|
+
[pad, pad + h],
|
|
579
|
+
]);
|
|
580
|
+
}
|
|
581
|
+
else if (preset === "wedgeRectCallout") {
|
|
582
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : -20833;
|
|
583
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 62500;
|
|
584
|
+
const tx = pad + w / 2 + w * adj1 / 100000;
|
|
585
|
+
const ty = pad + h / 2 + h * adj2 / 100000;
|
|
586
|
+
const cx = pad + w / 2;
|
|
587
|
+
drawCmd = polyPath([
|
|
588
|
+
[pad, pad], [cx - w * 0.05, pad], [tx, ty], [cx + w * 0.05, pad],
|
|
589
|
+
[pad + w, pad], [pad + w, pad + h], [pad, pad + h],
|
|
590
|
+
]);
|
|
591
|
+
}
|
|
592
|
+
else if (preset === "wedgeRoundRectCallout") {
|
|
593
|
+
// Simplified as rect callout (the rounded corners would need bezier curves)
|
|
594
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : -20833;
|
|
595
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 62500;
|
|
596
|
+
const tx = pad + w / 2 + w * adj1 / 100000;
|
|
597
|
+
const ty = pad + h / 2 + h * adj2 / 100000;
|
|
598
|
+
const cx = pad + w / 2;
|
|
599
|
+
drawCmd = polyPath([
|
|
600
|
+
[pad, pad], [cx - w * 0.05, pad], [tx, ty], [cx + w * 0.05, pad],
|
|
601
|
+
[pad + w, pad], [pad + w, pad + h], [pad, pad + h],
|
|
602
|
+
]);
|
|
603
|
+
}
|
|
604
|
+
else if (preset === "wedgeEllipseCallout") {
|
|
605
|
+
// Ellipse body with pointer — simplified as ellipse + triangle pointer
|
|
606
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : -20833;
|
|
607
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 62500;
|
|
608
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
609
|
+
const rx = w / 2, ry = h / 2;
|
|
610
|
+
const k = 0.5522847498;
|
|
611
|
+
// Just render as ellipse (pointer is complex to combine in single path)
|
|
612
|
+
drawCmd = [
|
|
613
|
+
`${cx + rx} ${totalH - cy} m`,
|
|
614
|
+
`${cx + rx} ${totalH - (cy - ry * k)} ${cx + rx * k} ${totalH - (cy - ry)} ${cx} ${totalH - (cy - ry)} c`,
|
|
615
|
+
`${cx - rx * k} ${totalH - (cy - ry)} ${cx - rx} ${totalH - (cy - ry * k)} ${cx - rx} ${totalH - cy} c`,
|
|
616
|
+
`${cx - rx} ${totalH - (cy + ry * k)} ${cx - rx * k} ${totalH - (cy + ry)} ${cx} ${totalH - (cy + ry)} c`,
|
|
617
|
+
`${cx + rx * k} ${totalH - (cy + ry)} ${cx + rx} ${totalH - (cy + ry * k)} ${cx + rx} ${totalH - cy} c`,
|
|
618
|
+
"f",
|
|
619
|
+
].join("\n");
|
|
620
|
+
}
|
|
621
|
+
else if (preset === "flowChartAlternateProcess") {
|
|
622
|
+
// Rounded rectangle (like roundRect with fixed radius)
|
|
623
|
+
const radius = Math.min(w, h) * 0.16667;
|
|
624
|
+
const x0 = pad, y0 = totalH - pad - h, x1 = pad + w, y1 = totalH - pad;
|
|
625
|
+
drawCmd = [
|
|
626
|
+
`${x0 + radius} ${y0} m`, `${x1 - radius} ${y0} l`,
|
|
627
|
+
`${x1} ${y0} ${x1} ${y0 + radius} v`, `${x1} ${y1 - radius} l`,
|
|
628
|
+
`${x1} ${y1} ${x1 - radius} ${y1} v`, `${x0 + radius} ${y1} l`,
|
|
629
|
+
`${x0} ${y1} ${x0} ${y1 - radius} v`, `${x0} ${y0 + radius} l`,
|
|
630
|
+
`${x0} ${y0} ${x0 + radius} ${y0} v`, "f",
|
|
631
|
+
].join("\n");
|
|
632
|
+
}
|
|
633
|
+
else if (preset === "flowChartDecision") {
|
|
634
|
+
// Diamond shape
|
|
635
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
636
|
+
drawCmd = polyPath([[cx, pad], [pad + w, cy], [cx, pad + h], [pad, cy]]);
|
|
637
|
+
}
|
|
638
|
+
else if (preset === "flowChartDocument") {
|
|
639
|
+
// Rectangle with wavy bottom
|
|
640
|
+
const waveH = h * 0.1;
|
|
641
|
+
drawCmd = polyPath([
|
|
642
|
+
[pad, pad],
|
|
643
|
+
[pad + w, pad],
|
|
644
|
+
[pad + w, pad + h - waveH],
|
|
645
|
+
[pad + w * 0.75, pad + h],
|
|
646
|
+
[pad + w * 0.5, pad + h - waveH],
|
|
647
|
+
[pad + w * 0.25, pad + h - waveH * 2],
|
|
648
|
+
[pad, pad + h - waveH],
|
|
649
|
+
]);
|
|
650
|
+
}
|
|
651
|
+
else if (preset === "leftRightArrow") {
|
|
652
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
653
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
654
|
+
const stemH = h * adj1 / 100000;
|
|
655
|
+
const headW = w * adj2 / 100000;
|
|
656
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
657
|
+
drawCmd = polyPath([
|
|
658
|
+
[pad + headW, pad],
|
|
659
|
+
[pad, pad + h / 2],
|
|
660
|
+
[pad + headW, pad + h],
|
|
661
|
+
[pad + headW, stemBot],
|
|
662
|
+
[pad + w - headW, stemBot],
|
|
663
|
+
[pad + w - headW, pad + h],
|
|
664
|
+
[pad + w, pad + h / 2],
|
|
665
|
+
[pad + w - headW, pad],
|
|
666
|
+
[pad + w - headW, stemTop],
|
|
667
|
+
[pad + headW, stemTop],
|
|
668
|
+
]);
|
|
669
|
+
}
|
|
670
|
+
else if (preset === "upDownArrow") {
|
|
671
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
672
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
673
|
+
const stemW = w * adj1 / 100000;
|
|
674
|
+
const headH = h * adj2 / 100000;
|
|
675
|
+
const stemL = pad + (w - stemW) / 2, stemR = pad + (w + stemW) / 2;
|
|
676
|
+
drawCmd = polyPath([
|
|
677
|
+
[pad + w / 2, pad],
|
|
678
|
+
[pad + w, pad + headH],
|
|
679
|
+
[stemR, pad + headH],
|
|
680
|
+
[stemR, pad + h - headH],
|
|
681
|
+
[pad + w, pad + h - headH],
|
|
682
|
+
[pad + w / 2, pad + h],
|
|
683
|
+
[pad, pad + h - headH],
|
|
684
|
+
[stemL, pad + h - headH],
|
|
685
|
+
[stemL, pad + headH],
|
|
686
|
+
[pad, pad + headH],
|
|
687
|
+
]);
|
|
688
|
+
}
|
|
689
|
+
else if (preset === "notchedRightArrow") {
|
|
690
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
691
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
692
|
+
const stemH = h * adj1 / 100000;
|
|
693
|
+
const headW = w * adj2 / 100000;
|
|
694
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
695
|
+
const notchX = pad + headW; // notch depth matches head width
|
|
696
|
+
drawCmd = polyPath([
|
|
697
|
+
[pad, stemTop],
|
|
698
|
+
[pad + w - headW, stemTop],
|
|
699
|
+
[pad + w - headW, pad],
|
|
700
|
+
[pad + w, pad + h / 2],
|
|
701
|
+
[pad + w - headW, pad + h],
|
|
702
|
+
[pad + w - headW, stemBot],
|
|
703
|
+
[pad, stemBot],
|
|
704
|
+
[notchX, pad + h / 2],
|
|
705
|
+
]);
|
|
706
|
+
}
|
|
707
|
+
else if (preset === "stripedRightArrow") {
|
|
708
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
709
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
710
|
+
const stemH = h * adj1 / 100000;
|
|
711
|
+
const headW = w * adj2 / 100000;
|
|
712
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
713
|
+
// Stripes at tail (simplified as solid arrow body — stripes are decorative)
|
|
714
|
+
const stripeW = w * 0.05;
|
|
715
|
+
drawCmd = polyPath([
|
|
716
|
+
[pad + stripeW * 2, stemTop],
|
|
717
|
+
[pad + w - headW, stemTop],
|
|
718
|
+
[pad + w - headW, pad],
|
|
719
|
+
[pad + w, pad + h / 2],
|
|
720
|
+
[pad + w - headW, pad + h],
|
|
721
|
+
[pad + w - headW, stemBot],
|
|
722
|
+
[pad + stripeW * 2, stemBot],
|
|
723
|
+
]);
|
|
724
|
+
}
|
|
725
|
+
else if (preset === "decagon") {
|
|
726
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
727
|
+
const pts = [];
|
|
728
|
+
for (let i = 0; i < 10; i++) {
|
|
729
|
+
const a = Math.PI * (-0.5 + 2 * i / 10);
|
|
730
|
+
pts.push([cx + Math.cos(a) * w / 2, cy + Math.sin(a) * h / 2]);
|
|
731
|
+
}
|
|
732
|
+
drawCmd = polyPath(pts);
|
|
733
|
+
}
|
|
734
|
+
else if (preset === "heptagon") {
|
|
735
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
736
|
+
const pts = [];
|
|
737
|
+
for (let i = 0; i < 7; i++) {
|
|
738
|
+
const a = Math.PI * (-0.5 + 2 * i / 7);
|
|
739
|
+
pts.push([cx + Math.cos(a) * w / 2, cy + Math.sin(a) * h / 2]);
|
|
740
|
+
}
|
|
741
|
+
drawCmd = polyPath(pts);
|
|
742
|
+
}
|
|
743
|
+
else if (preset === "dodecagon") {
|
|
744
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
745
|
+
const pts = [];
|
|
746
|
+
for (let i = 0; i < 12; i++) {
|
|
747
|
+
const a = Math.PI * (-0.5 + 2 * i / 12);
|
|
748
|
+
pts.push([cx + Math.cos(a) * w / 2, cy + Math.sin(a) * h / 2]);
|
|
749
|
+
}
|
|
750
|
+
drawCmd = polyPath(pts);
|
|
751
|
+
}
|
|
752
|
+
else if (preset === "star10") {
|
|
753
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
754
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 42533;
|
|
755
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
756
|
+
const innerR_x = outerR_x * adj / 50000, innerR_y = outerR_y * adj / 50000;
|
|
757
|
+
const pts = [];
|
|
758
|
+
for (let i = 0; i < 10; i++) {
|
|
759
|
+
const outerA = Math.PI * (-0.5 + i / 5);
|
|
760
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, cy + Math.sin(outerA) * outerR_y]);
|
|
761
|
+
const innerA = Math.PI * (-0.5 + (i * 2 + 1) / 10);
|
|
762
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, cy + Math.sin(innerA) * innerR_y]);
|
|
763
|
+
}
|
|
764
|
+
drawCmd = polyPath(pts);
|
|
765
|
+
}
|
|
766
|
+
else if (preset === "star12") {
|
|
767
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
768
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 37500;
|
|
769
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
770
|
+
const innerR_x = outerR_x * adj / 50000, innerR_y = outerR_y * adj / 50000;
|
|
771
|
+
const pts = [];
|
|
772
|
+
for (let i = 0; i < 12; i++) {
|
|
773
|
+
const outerA = Math.PI * (-0.5 + i / 6);
|
|
774
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, cy + Math.sin(outerA) * outerR_y]);
|
|
775
|
+
const innerA = Math.PI * (-0.5 + (i * 2 + 1) / 12);
|
|
776
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, cy + Math.sin(innerA) * innerR_y]);
|
|
777
|
+
}
|
|
778
|
+
drawCmd = polyPath(pts);
|
|
779
|
+
}
|
|
780
|
+
else if (preset === "star16") {
|
|
781
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
782
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 37500;
|
|
783
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
784
|
+
const innerR_x = outerR_x * adj / 50000, innerR_y = outerR_y * adj / 50000;
|
|
785
|
+
const pts = [];
|
|
786
|
+
for (let i = 0; i < 16; i++) {
|
|
787
|
+
const outerA = Math.PI * (-0.5 + i / 8);
|
|
788
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, cy + Math.sin(outerA) * outerR_y]);
|
|
789
|
+
const innerA = Math.PI * (-0.5 + (i * 2 + 1) / 16);
|
|
790
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, cy + Math.sin(innerA) * innerR_y]);
|
|
791
|
+
}
|
|
792
|
+
drawCmd = polyPath(pts);
|
|
793
|
+
}
|
|
794
|
+
else if (preset === "star24") {
|
|
795
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
796
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 37500;
|
|
797
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
798
|
+
const innerR_x = outerR_x * adj / 50000, innerR_y = outerR_y * adj / 50000;
|
|
799
|
+
const pts = [];
|
|
800
|
+
for (let i = 0; i < 24; i++) {
|
|
801
|
+
const outerA = Math.PI * (-0.5 + i / 12);
|
|
802
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, cy + Math.sin(outerA) * outerR_y]);
|
|
803
|
+
const innerA = Math.PI * (-0.5 + (i * 2 + 1) / 24);
|
|
804
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, cy + Math.sin(innerA) * innerR_y]);
|
|
805
|
+
}
|
|
806
|
+
drawCmd = polyPath(pts);
|
|
807
|
+
}
|
|
808
|
+
else if (preset === "star32") {
|
|
809
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
810
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 37500;
|
|
811
|
+
const outerR_x = w / 2, outerR_y = h / 2;
|
|
812
|
+
const innerR_x = outerR_x * adj / 50000, innerR_y = outerR_y * adj / 50000;
|
|
813
|
+
const pts = [];
|
|
814
|
+
for (let i = 0; i < 32; i++) {
|
|
815
|
+
const outerA = Math.PI * (-0.5 + i / 16);
|
|
816
|
+
pts.push([cx + Math.cos(outerA) * outerR_x, cy + Math.sin(outerA) * outerR_y]);
|
|
817
|
+
const innerA = Math.PI * (-0.5 + (i * 2 + 1) / 32);
|
|
818
|
+
pts.push([cx + Math.cos(innerA) * innerR_x, cy + Math.sin(innerA) * innerR_y]);
|
|
819
|
+
}
|
|
820
|
+
drawCmd = polyPath(pts);
|
|
821
|
+
}
|
|
822
|
+
else if (preset === "round1Rect") {
|
|
823
|
+
// Rectangle with one rounded corner (top-right)
|
|
824
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 16667;
|
|
825
|
+
const radius = Math.min(w, h) * adj / 100000;
|
|
826
|
+
const x0 = pad, y0 = totalH - pad - h, x1 = pad + w, y1 = totalH - pad;
|
|
827
|
+
drawCmd = [
|
|
828
|
+
`${x0} ${y0} m`, `${x1 - radius} ${y0} l`,
|
|
829
|
+
`${x1} ${y0} ${x1} ${y0 + radius} v`,
|
|
830
|
+
`${x1} ${y1} l`, `${x0} ${y1} l`, "f",
|
|
831
|
+
].join("\n");
|
|
832
|
+
}
|
|
833
|
+
else if (preset === "round2SameRect") {
|
|
834
|
+
// Rectangle with two rounded corners on the same side (top-left, top-right)
|
|
835
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 16667;
|
|
836
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 0;
|
|
837
|
+
const r1 = Math.min(w, h) * adj1 / 100000;
|
|
838
|
+
const r2 = Math.min(w, h) * adj2 / 100000;
|
|
839
|
+
const x0 = pad, y0 = totalH - pad - h, x1 = pad + w, y1 = totalH - pad;
|
|
840
|
+
drawCmd = [
|
|
841
|
+
`${x0 + r1} ${y0} m`, `${x1 - r1} ${y0} l`,
|
|
842
|
+
`${x1} ${y0} ${x1} ${y0 + r1} v`,
|
|
843
|
+
`${x1} ${y1 - r2} l`,
|
|
844
|
+
r2 > 0 ? `${x1} ${y1} ${x1 - r2} ${y1} v` : `${x1} ${y1} l`,
|
|
845
|
+
`${x0 + r2} ${y1} l`,
|
|
846
|
+
r2 > 0 ? `${x0} ${y1} ${x0} ${y1 - r2} v` : `${x0} ${y1} l`,
|
|
847
|
+
`${x0} ${y0 + r1} l`,
|
|
848
|
+
`${x0} ${y0} ${x0 + r1} ${y0} v`, "f",
|
|
849
|
+
].join("\n");
|
|
850
|
+
}
|
|
851
|
+
else if (preset === "round2DiagRect") {
|
|
852
|
+
// Rectangle with two rounded corners on diagonal (top-left, bottom-right)
|
|
853
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 16667;
|
|
854
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 0;
|
|
855
|
+
const r1 = Math.min(w, h) * adj1 / 100000;
|
|
856
|
+
const r2 = Math.min(w, h) * adj2 / 100000;
|
|
857
|
+
const x0 = pad, y0 = totalH - pad - h, x1 = pad + w, y1 = totalH - pad;
|
|
858
|
+
drawCmd = [
|
|
859
|
+
`${x0 + r1} ${y0} m`, `${x1} ${y0} l`,
|
|
860
|
+
`${x1} ${y1 - r2} l`,
|
|
861
|
+
r2 > 0 ? `${x1} ${y1} ${x1 - r2} ${y1} v` : `${x1} ${y1} l`,
|
|
862
|
+
`${x0} ${y1} l`,
|
|
863
|
+
`${x0} ${y0 + r1} l`,
|
|
864
|
+
`${x0} ${y0} ${x0 + r1} ${y0} v`, "f",
|
|
865
|
+
].join("\n");
|
|
866
|
+
}
|
|
867
|
+
else if (preset === "snip1Rect") {
|
|
868
|
+
// Rectangle with one snipped corner (top-right)
|
|
869
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 16667;
|
|
870
|
+
const snip = Math.min(w, h) * adj / 100000;
|
|
871
|
+
drawCmd = polyPath([
|
|
872
|
+
[pad, pad], [pad + w - snip, pad], [pad + w, pad + snip],
|
|
873
|
+
[pad + w, pad + h], [pad, pad + h],
|
|
874
|
+
]);
|
|
875
|
+
}
|
|
876
|
+
else if (preset === "snip2SameRect") {
|
|
877
|
+
// Rectangle with two snipped corners on same side (top-left, top-right)
|
|
878
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 16667;
|
|
879
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 16667;
|
|
880
|
+
const s1 = Math.min(w, h) * adj1 / 100000;
|
|
881
|
+
const s2 = Math.min(w, h) * adj2 / 100000;
|
|
882
|
+
drawCmd = polyPath([
|
|
883
|
+
[pad + s1, pad], [pad + w - s1, pad],
|
|
884
|
+
[pad + w, pad + s1], [pad + w, pad + h - s2],
|
|
885
|
+
[pad + w - s2, pad + h], [pad + s2, pad + h],
|
|
886
|
+
[pad, pad + h - s2], [pad, pad + s1],
|
|
887
|
+
]);
|
|
888
|
+
}
|
|
889
|
+
else if (preset === "snip2DiagRect") {
|
|
890
|
+
// Rectangle with two snipped corners on diagonal (top-right, bottom-left)
|
|
891
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 16667;
|
|
892
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 0;
|
|
893
|
+
const s1 = Math.min(w, h) * adj1 / 100000;
|
|
894
|
+
const s2 = Math.min(w, h) * adj2 / 100000;
|
|
895
|
+
drawCmd = polyPath([
|
|
896
|
+
[pad, pad], [pad + w - s1, pad], [pad + w, pad + s1],
|
|
897
|
+
[pad + w, pad + h], [pad + s2, pad + h], [pad, pad + h - s2],
|
|
898
|
+
]);
|
|
899
|
+
}
|
|
900
|
+
else if (preset === "snipRoundRect") {
|
|
901
|
+
// Rectangle with one rounded corner (top-left) and one snipped corner (top-right)
|
|
902
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 16667;
|
|
903
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 16667;
|
|
904
|
+
const rr = Math.min(w, h) * adj1 / 100000;
|
|
905
|
+
const snip = Math.min(w, h) * adj2 / 100000;
|
|
906
|
+
const x0 = pad, y0 = totalH - pad - h, x1 = pad + w, y1 = totalH - pad;
|
|
907
|
+
drawCmd = [
|
|
908
|
+
`${x0 + rr} ${y0} m`,
|
|
909
|
+
`${x1 - snip} ${y0} l`,
|
|
910
|
+
`${x1} ${y0 + snip} l`,
|
|
911
|
+
`${x1} ${y1} l`,
|
|
912
|
+
`${x0} ${y1} l`,
|
|
913
|
+
`${x0} ${y0 + rr} l`,
|
|
914
|
+
`${x0} ${y0} ${x0 + rr} ${y0} v`,
|
|
915
|
+
"f",
|
|
916
|
+
].join("\n");
|
|
917
|
+
}
|
|
918
|
+
else if (preset === "flowChartInputOutput") {
|
|
919
|
+
// Parallelogram for I/O (skew ~20%)
|
|
920
|
+
const skew = w * 0.2;
|
|
921
|
+
drawCmd = polyPath([
|
|
922
|
+
[pad + skew, pad], [pad + w, pad],
|
|
923
|
+
[pad + w - skew, pad + h], [pad, pad + h],
|
|
924
|
+
]);
|
|
925
|
+
}
|
|
926
|
+
else if (preset === "flowChartPreparation") {
|
|
927
|
+
// Hexagon-like (wider at center)
|
|
928
|
+
const inset = w * 0.2;
|
|
929
|
+
const cy = pad + h / 2;
|
|
930
|
+
drawCmd = polyPath([
|
|
931
|
+
[pad + inset, pad], [pad + w - inset, pad],
|
|
932
|
+
[pad + w, cy], [pad + w - inset, pad + h],
|
|
933
|
+
[pad + inset, pad + h], [pad, cy],
|
|
934
|
+
]);
|
|
935
|
+
}
|
|
936
|
+
else if (preset === "flowChartConnector") {
|
|
937
|
+
// Circle (ellipse fitting the bounding box)
|
|
938
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
939
|
+
const rx = w / 2, ry = h / 2;
|
|
940
|
+
const k = 0.5522847498;
|
|
941
|
+
drawCmd = [
|
|
942
|
+
`${cx + rx} ${totalH - cy} m`,
|
|
943
|
+
`${cx + rx} ${totalH - (cy - ry * k)} ${cx + rx * k} ${totalH - (cy - ry)} ${cx} ${totalH - (cy - ry)} c`,
|
|
944
|
+
`${cx - rx * k} ${totalH - (cy - ry)} ${cx - rx} ${totalH - (cy - ry * k)} ${cx - rx} ${totalH - cy} c`,
|
|
945
|
+
`${cx - rx} ${totalH - (cy + ry * k)} ${cx - rx * k} ${totalH - (cy + ry)} ${cx} ${totalH - (cy + ry)} c`,
|
|
946
|
+
`${cx + rx * k} ${totalH - (cy + ry)} ${cx + rx} ${totalH - (cy + ry * k)} ${cx + rx} ${totalH - cy} c`,
|
|
947
|
+
"f",
|
|
948
|
+
].join("\n");
|
|
949
|
+
}
|
|
950
|
+
else if (preset === "mathPlus") {
|
|
951
|
+
// Plus sign: cross shape (thinner than 'plus' preset — ~27% arm thickness)
|
|
952
|
+
const t = w * 0.27, s = h * 0.27;
|
|
953
|
+
const cx1 = pad + (w - t) / 2, cx2 = pad + (w + t) / 2;
|
|
954
|
+
const cy1 = pad + (h - s) / 2, cy2 = pad + (h + s) / 2;
|
|
955
|
+
drawCmd = polyPath([
|
|
956
|
+
[cx1, pad], [cx2, pad],
|
|
957
|
+
[cx2, cy1], [pad + w, cy1],
|
|
958
|
+
[pad + w, cy2], [cx2, cy2],
|
|
959
|
+
[cx2, pad + h], [cx1, pad + h],
|
|
960
|
+
[cx1, cy2], [pad, cy2],
|
|
961
|
+
[pad, cy1], [cx1, cy1],
|
|
962
|
+
]);
|
|
963
|
+
}
|
|
964
|
+
else if (preset === "mathMinus") {
|
|
965
|
+
// Horizontal bar (rectangle centered vertically)
|
|
966
|
+
const barH = h * 0.27;
|
|
967
|
+
const barTop = pad + (h - barH) / 2;
|
|
968
|
+
drawCmd = polyPath([
|
|
969
|
+
[pad, barTop], [pad + w, barTop],
|
|
970
|
+
[pad + w, barTop + barH], [pad, barTop + barH],
|
|
971
|
+
]);
|
|
972
|
+
}
|
|
973
|
+
else if (preset === "mathMultiply") {
|
|
974
|
+
// X shape (two crossing diagonal bars — approximated as polygon)
|
|
975
|
+
const t = Math.min(w, h) * 0.15; // half-thickness of bars
|
|
976
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
977
|
+
drawCmd = polyPath([
|
|
978
|
+
[pad + t, pad], [cx, cy - t], [pad + w - t, pad],
|
|
979
|
+
[pad + w, pad + t], [cx + t, cy], [pad + w, pad + h - t],
|
|
980
|
+
[pad + w - t, pad + h], [cx, cy + t], [pad + t, pad + h],
|
|
981
|
+
[pad, pad + h - t], [cx - t, cy], [pad, pad + t],
|
|
982
|
+
]);
|
|
983
|
+
}
|
|
984
|
+
else if (preset === "mathDivide") {
|
|
985
|
+
// Horizontal bar with dots above and below
|
|
986
|
+
const barH = h * 0.15;
|
|
987
|
+
const barTop = pad + (h - barH) / 2;
|
|
988
|
+
const dotR = Math.min(w, h) * 0.1;
|
|
989
|
+
const cx = pad + w / 2;
|
|
990
|
+
const k = 0.5522847498;
|
|
991
|
+
const topDotCy = pad + h * 0.2;
|
|
992
|
+
const botDotCy = pad + h * 0.8;
|
|
993
|
+
drawCmd = [
|
|
994
|
+
// Top dot (ellipse)
|
|
995
|
+
`${cx + dotR} ${py(topDotCy)} m`,
|
|
996
|
+
`${cx + dotR} ${py(topDotCy - dotR * k)} ${cx + dotR * k} ${py(topDotCy - dotR)} ${cx} ${py(topDotCy - dotR)} c`,
|
|
997
|
+
`${cx - dotR * k} ${py(topDotCy - dotR)} ${cx - dotR} ${py(topDotCy - dotR * k)} ${cx - dotR} ${py(topDotCy)} c`,
|
|
998
|
+
`${cx - dotR} ${py(topDotCy + dotR * k)} ${cx - dotR * k} ${py(topDotCy + dotR)} ${cx} ${py(topDotCy + dotR)} c`,
|
|
999
|
+
`${cx + dotR * k} ${py(topDotCy + dotR)} ${cx + dotR} ${py(topDotCy + dotR * k)} ${cx + dotR} ${py(topDotCy)} c`,
|
|
1000
|
+
"f",
|
|
1001
|
+
// Bar (rectangle)
|
|
1002
|
+
`${pad} ${py(barTop)} m`,
|
|
1003
|
+
`${pad + w} ${py(barTop)} l`,
|
|
1004
|
+
`${pad + w} ${py(barTop + barH)} l`,
|
|
1005
|
+
`${pad} ${py(barTop + barH)} l`,
|
|
1006
|
+
"f",
|
|
1007
|
+
// Bottom dot (ellipse)
|
|
1008
|
+
`${cx + dotR} ${py(botDotCy)} m`,
|
|
1009
|
+
`${cx + dotR} ${py(botDotCy - dotR * k)} ${cx + dotR * k} ${py(botDotCy - dotR)} ${cx} ${py(botDotCy - dotR)} c`,
|
|
1010
|
+
`${cx - dotR * k} ${py(botDotCy - dotR)} ${cx - dotR} ${py(botDotCy - dotR * k)} ${cx - dotR} ${py(botDotCy)} c`,
|
|
1011
|
+
`${cx - dotR} ${py(botDotCy + dotR * k)} ${cx - dotR * k} ${py(botDotCy + dotR)} ${cx} ${py(botDotCy + dotR)} c`,
|
|
1012
|
+
`${cx + dotR * k} ${py(botDotCy + dotR)} ${cx + dotR} ${py(botDotCy + dotR * k)} ${cx + dotR} ${py(botDotCy)} c`,
|
|
1013
|
+
"f",
|
|
1014
|
+
].join("\n");
|
|
1015
|
+
}
|
|
1016
|
+
else if (preset === "mathEqual") {
|
|
1017
|
+
// Two horizontal bars
|
|
1018
|
+
const barH = h * 0.15;
|
|
1019
|
+
const gap = h * 0.12;
|
|
1020
|
+
const topBar = pad + h / 2 - gap / 2 - barH;
|
|
1021
|
+
const botBar = pad + h / 2 + gap / 2;
|
|
1022
|
+
drawCmd = [
|
|
1023
|
+
`${pad} ${py(topBar)} m`, `${pad + w} ${py(topBar)} l`,
|
|
1024
|
+
`${pad + w} ${py(topBar + barH)} l`, `${pad} ${py(topBar + barH)} l`, "f",
|
|
1025
|
+
`${pad} ${py(botBar)} m`, `${pad + w} ${py(botBar)} l`,
|
|
1026
|
+
`${pad + w} ${py(botBar + barH)} l`, `${pad} ${py(botBar + barH)} l`, "f",
|
|
1027
|
+
].join("\n");
|
|
1028
|
+
}
|
|
1029
|
+
else if (preset === "mathNotEqual") {
|
|
1030
|
+
// Two horizontal bars with a diagonal slash through them
|
|
1031
|
+
const barH = h * 0.15;
|
|
1032
|
+
const gap = h * 0.12;
|
|
1033
|
+
const topBar = pad + h / 2 - gap / 2 - barH;
|
|
1034
|
+
const botBar = pad + h / 2 + gap / 2;
|
|
1035
|
+
const slashW = w * 0.08;
|
|
1036
|
+
const cx = pad + w / 2;
|
|
1037
|
+
drawCmd = [
|
|
1038
|
+
`${pad} ${py(topBar)} m`, `${pad + w} ${py(topBar)} l`,
|
|
1039
|
+
`${pad + w} ${py(topBar + barH)} l`, `${pad} ${py(topBar + barH)} l`, "f",
|
|
1040
|
+
`${pad} ${py(botBar)} m`, `${pad + w} ${py(botBar)} l`,
|
|
1041
|
+
`${pad + w} ${py(botBar + barH)} l`, `${pad} ${py(botBar + barH)} l`, "f",
|
|
1042
|
+
// Diagonal slash
|
|
1043
|
+
`${cx - slashW / 2} ${py(pad)} m`, `${cx + slashW / 2} ${py(pad)} l`,
|
|
1044
|
+
`${cx + slashW / 2 - w * 0.15} ${py(pad + h)} l`, `${cx - slashW / 2 - w * 0.15} ${py(pad + h)} l`, "f",
|
|
1045
|
+
].join("\n");
|
|
1046
|
+
}
|
|
1047
|
+
else if (preset === "quadArrow") {
|
|
1048
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 22500;
|
|
1049
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 22500;
|
|
1050
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 22500;
|
|
1051
|
+
const stemW = w * adj1 / 100000;
|
|
1052
|
+
const stemH = h * adj1 / 100000;
|
|
1053
|
+
const headW = w * adj2 / 100000;
|
|
1054
|
+
const headH = h * adj2 / 100000;
|
|
1055
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
1056
|
+
const sL = cx - stemW / 2, sR = cx + stemW / 2;
|
|
1057
|
+
const sT = cy - stemH / 2, sB = cy + stemH / 2;
|
|
1058
|
+
drawCmd = polyPath([
|
|
1059
|
+
// Up arrow head
|
|
1060
|
+
[cx, pad], [pad + w / 2 + headW / 2, pad + headH], [sR, pad + headH],
|
|
1061
|
+
// Right arrow head
|
|
1062
|
+
[sR, sT], [pad + w - headH, sT], [pad + w - headH, cy - headW / 2],
|
|
1063
|
+
[pad + w, cy], [pad + w - headH, cy + headW / 2], [pad + w - headH, sB],
|
|
1064
|
+
// Down arrow head
|
|
1065
|
+
[sR, sB], [sR, pad + h - headH], [cx + headW / 2, pad + h - headH],
|
|
1066
|
+
[cx, pad + h], [cx - headW / 2, pad + h - headH], [sL, pad + h - headH],
|
|
1067
|
+
// Left arrow head
|
|
1068
|
+
[sL, sB], [pad + headH, sB], [pad + headH, cy + headW / 2],
|
|
1069
|
+
[pad, cy], [pad + headH, cy - headW / 2], [pad + headH, sT],
|
|
1070
|
+
[sL, sT], [sL, pad + headH], [cx - headW / 2, pad + headH],
|
|
1071
|
+
]);
|
|
1072
|
+
}
|
|
1073
|
+
else if (preset === "leftRightUpArrow") {
|
|
1074
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1075
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1076
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1077
|
+
const stemW = w * adj1 / 100000;
|
|
1078
|
+
const headW = w * adj2 / 100000;
|
|
1079
|
+
const headH = h * adj3 / 100000;
|
|
1080
|
+
const stemH = h * adj1 / 100000;
|
|
1081
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
1082
|
+
const sL = cx - stemW / 2, sR = cx + stemW / 2;
|
|
1083
|
+
const sT = cy - stemH / 2;
|
|
1084
|
+
drawCmd = polyPath([
|
|
1085
|
+
// Up arrow
|
|
1086
|
+
[cx, pad], [cx + headW / 2, pad + headH], [sR, pad + headH],
|
|
1087
|
+
// Right
|
|
1088
|
+
[sR, sT], [pad + w - headH, sT], [pad + w - headH, cy - headW / 2],
|
|
1089
|
+
[pad + w, cy], [pad + w - headH, cy + headW / 2], [pad + w - headH, pad + h],
|
|
1090
|
+
// Bottom
|
|
1091
|
+
[pad + headH, pad + h],
|
|
1092
|
+
// Left
|
|
1093
|
+
[pad + headH, cy + headW / 2], [pad, cy], [pad + headH, cy - headW / 2],
|
|
1094
|
+
[pad + headH, sT],
|
|
1095
|
+
[sL, sT], [sL, pad + headH], [cx - headW / 2, pad + headH],
|
|
1096
|
+
]);
|
|
1097
|
+
}
|
|
1098
|
+
else if (preset === "bentArrow") {
|
|
1099
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1100
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1101
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1102
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 43750;
|
|
1103
|
+
const headW = w * adj2 / 100000;
|
|
1104
|
+
const headH = h * adj1 / 100000;
|
|
1105
|
+
const stemW = w * adj3 / 100000;
|
|
1106
|
+
const bendY = h * adj4 / 100000;
|
|
1107
|
+
drawCmd = polyPath([
|
|
1108
|
+
[pad + w / 2, pad],
|
|
1109
|
+
[pad + w, pad + headH],
|
|
1110
|
+
[pad + w - headW / 2, pad + headH],
|
|
1111
|
+
[pad + w - headW / 2, pad + bendY],
|
|
1112
|
+
[pad + stemW, pad + bendY],
|
|
1113
|
+
[pad + stemW, pad + h],
|
|
1114
|
+
[pad, pad + h],
|
|
1115
|
+
[pad, pad + bendY - stemW],
|
|
1116
|
+
[pad + w - headW / 2 - stemW, pad + bendY - stemW],
|
|
1117
|
+
[pad + w - headW / 2 - stemW, pad + headH],
|
|
1118
|
+
[pad + w / 2 - headW / 2, pad + headH],
|
|
1119
|
+
]);
|
|
1120
|
+
}
|
|
1121
|
+
else if (preset === "uturnArrow") {
|
|
1122
|
+
// U-turn arrow: goes down, curves at bottom, comes back up with arrow
|
|
1123
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1124
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1125
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1126
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 43750;
|
|
1127
|
+
const adj5 = adjustValues?.adj5 ? parseAdjustValue(adjustValues.adj5) : 75000;
|
|
1128
|
+
const stemW = w * adj1 / 100000;
|
|
1129
|
+
const headW = w * adj2 / 100000;
|
|
1130
|
+
const headH = h * adj3 / 100000;
|
|
1131
|
+
const bend = h * adj5 / 100000;
|
|
1132
|
+
// Simplified as polygon (U-turn curve approximated)
|
|
1133
|
+
const leftStemR = pad + w * 0.35;
|
|
1134
|
+
const rightStemL = pad + w * 0.65;
|
|
1135
|
+
drawCmd = polyPath([
|
|
1136
|
+
[pad + w / 2, pad],
|
|
1137
|
+
[pad + w / 2 + headW / 2, pad + headH],
|
|
1138
|
+
[rightStemL, pad + headH],
|
|
1139
|
+
[rightStemL, pad + bend],
|
|
1140
|
+
[leftStemR, pad + bend],
|
|
1141
|
+
[leftStemR, pad + h],
|
|
1142
|
+
[pad, pad + h],
|
|
1143
|
+
[pad, pad + bend],
|
|
1144
|
+
[pad + stemW, pad + bend],
|
|
1145
|
+
[pad + stemW, pad + headH],
|
|
1146
|
+
[pad + w / 2 - headW / 2, pad + headH],
|
|
1147
|
+
]);
|
|
1148
|
+
}
|
|
1149
|
+
else if (preset === "circularArrow") {
|
|
1150
|
+
// Circular arrow: approximated as a thick arc with arrowhead
|
|
1151
|
+
// Simplified as a crescent-like shape
|
|
1152
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
1153
|
+
const rx = w / 2, ry = h / 2;
|
|
1154
|
+
const k = 0.5522847498;
|
|
1155
|
+
const thick = Math.min(w, h) * 0.15;
|
|
1156
|
+
const irx = rx - thick, iry = ry - thick;
|
|
1157
|
+
// Draw outer arc (3/4 circle) then inner arc back, with arrowhead
|
|
1158
|
+
drawCmd = [
|
|
1159
|
+
// Outer arc starting from right-center going clockwise (top, left, bottom)
|
|
1160
|
+
`${cx + rx} ${py(cy)} m`,
|
|
1161
|
+
`${cx + rx} ${py(cy - ry * k)} ${cx + rx * k} ${py(cy - ry)} ${cx} ${py(cy - ry)} c`,
|
|
1162
|
+
`${cx - rx * k} ${py(cy - ry)} ${cx - rx} ${py(cy - ry * k)} ${cx - rx} ${py(cy)} c`,
|
|
1163
|
+
`${cx - rx} ${py(cy + ry * k)} ${cx - rx * k} ${py(cy + ry)} ${cx} ${py(cy + ry)} c`,
|
|
1164
|
+
// Arrowhead at bottom-center
|
|
1165
|
+
`${cx + thick * 2} ${py(cy + ry + thick)} l`,
|
|
1166
|
+
`${cx} ${py(cy + iry)} l`,
|
|
1167
|
+
`${cx - thick * 2} ${py(cy + ry + thick)} l`,
|
|
1168
|
+
`${cx} ${py(cy + ry)} l`,
|
|
1169
|
+
// Inner arc going counter-clockwise back (bottom to right)
|
|
1170
|
+
`${cx - irx * k} ${py(cy + iry)} ${cx - irx} ${py(cy + iry * k)} ${cx - irx} ${py(cy)} c`,
|
|
1171
|
+
`${cx - irx} ${py(cy - iry * k)} ${cx - irx * k} ${py(cy - iry)} ${cx} ${py(cy - iry)} c`,
|
|
1172
|
+
`${cx + irx * k} ${py(cy - iry)} ${cx + irx} ${py(cy - iry * k)} ${cx + irx} ${py(cy)} c`,
|
|
1173
|
+
"f",
|
|
1174
|
+
].join("\n");
|
|
1175
|
+
}
|
|
1176
|
+
else if (preset === "curvedRightArrow") {
|
|
1177
|
+
// Curved right arrow
|
|
1178
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1179
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1180
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1181
|
+
const headH = h * adj3 / 100000;
|
|
1182
|
+
const headW = w * adj2 / 100000;
|
|
1183
|
+
// Simplified as arrow pointing right with curved body
|
|
1184
|
+
drawCmd = polyPath([
|
|
1185
|
+
[pad, pad],
|
|
1186
|
+
[pad + w * 0.6, pad],
|
|
1187
|
+
[pad + w - headW, pad + h * 0.3],
|
|
1188
|
+
[pad + w - headW, pad],
|
|
1189
|
+
[pad + w, pad + h / 2],
|
|
1190
|
+
[pad + w - headW, pad + h],
|
|
1191
|
+
[pad + w - headW, pad + h * 0.7],
|
|
1192
|
+
[pad + w * 0.6, pad + h],
|
|
1193
|
+
[pad, pad + h],
|
|
1194
|
+
]);
|
|
1195
|
+
}
|
|
1196
|
+
else if (preset === "curvedLeftArrow") {
|
|
1197
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1198
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1199
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1200
|
+
const headW = w * adj2 / 100000;
|
|
1201
|
+
drawCmd = polyPath([
|
|
1202
|
+
[pad + w, pad],
|
|
1203
|
+
[pad + w * 0.4, pad],
|
|
1204
|
+
[pad + headW, pad + h * 0.3],
|
|
1205
|
+
[pad + headW, pad],
|
|
1206
|
+
[pad, pad + h / 2],
|
|
1207
|
+
[pad + headW, pad + h],
|
|
1208
|
+
[pad + headW, pad + h * 0.7],
|
|
1209
|
+
[pad + w * 0.4, pad + h],
|
|
1210
|
+
[pad + w, pad + h],
|
|
1211
|
+
]);
|
|
1212
|
+
}
|
|
1213
|
+
else if (preset === "curvedUpArrow") {
|
|
1214
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1215
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1216
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1217
|
+
const headH = h * adj2 / 100000;
|
|
1218
|
+
drawCmd = polyPath([
|
|
1219
|
+
[pad, pad + h],
|
|
1220
|
+
[pad, pad + h * 0.4],
|
|
1221
|
+
[pad + w * 0.3, pad + headH],
|
|
1222
|
+
[pad, pad + headH],
|
|
1223
|
+
[pad + w / 2, pad],
|
|
1224
|
+
[pad + w, pad + headH],
|
|
1225
|
+
[pad + w * 0.7, pad + headH],
|
|
1226
|
+
[pad + w, pad + h * 0.4],
|
|
1227
|
+
[pad + w, pad + h],
|
|
1228
|
+
]);
|
|
1229
|
+
}
|
|
1230
|
+
else if (preset === "curvedDownArrow") {
|
|
1231
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1232
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1233
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1234
|
+
const headH = h * adj2 / 100000;
|
|
1235
|
+
drawCmd = polyPath([
|
|
1236
|
+
[pad, pad],
|
|
1237
|
+
[pad, pad + h * 0.6],
|
|
1238
|
+
[pad + w * 0.3, pad + h - headH],
|
|
1239
|
+
[pad, pad + h - headH],
|
|
1240
|
+
[pad + w / 2, pad + h],
|
|
1241
|
+
[pad + w, pad + h - headH],
|
|
1242
|
+
[pad + w * 0.7, pad + h - headH],
|
|
1243
|
+
[pad + w, pad + h * 0.6],
|
|
1244
|
+
[pad + w, pad],
|
|
1245
|
+
]);
|
|
1246
|
+
}
|
|
1247
|
+
else if (preset === "rightArrowCallout") {
|
|
1248
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1249
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1250
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1251
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 64977;
|
|
1252
|
+
const stemH = h * adj1 / 100000;
|
|
1253
|
+
const headW = w * adj2 / 100000;
|
|
1254
|
+
const bodyW = w * adj4 / 100000;
|
|
1255
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
1256
|
+
drawCmd = polyPath([
|
|
1257
|
+
[pad, pad], [pad + bodyW, pad],
|
|
1258
|
+
[pad + bodyW, stemTop], [pad + w - headW, stemTop],
|
|
1259
|
+
[pad + w - headW, pad],
|
|
1260
|
+
[pad + w, pad + h / 2],
|
|
1261
|
+
[pad + w - headW, pad + h],
|
|
1262
|
+
[pad + w - headW, stemBot], [pad + bodyW, stemBot],
|
|
1263
|
+
[pad + bodyW, pad + h], [pad, pad + h],
|
|
1264
|
+
]);
|
|
1265
|
+
}
|
|
1266
|
+
else if (preset === "leftArrowCallout") {
|
|
1267
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1268
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1269
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1270
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 64977;
|
|
1271
|
+
const stemH = h * adj1 / 100000;
|
|
1272
|
+
const headW = w * adj2 / 100000;
|
|
1273
|
+
const bodyW = w * adj4 / 100000;
|
|
1274
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
1275
|
+
const bodyX = pad + w - bodyW;
|
|
1276
|
+
drawCmd = polyPath([
|
|
1277
|
+
[pad + w, pad], [pad + w, pad + h],
|
|
1278
|
+
[bodyX, pad + h], [bodyX, stemBot],
|
|
1279
|
+
[pad + headW, stemBot], [pad + headW, pad + h],
|
|
1280
|
+
[pad, pad + h / 2],
|
|
1281
|
+
[pad + headW, pad], [pad + headW, stemTop],
|
|
1282
|
+
[bodyX, stemTop], [bodyX, pad],
|
|
1283
|
+
]);
|
|
1284
|
+
}
|
|
1285
|
+
else if (preset === "upArrowCallout") {
|
|
1286
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1287
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1288
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1289
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 64977;
|
|
1290
|
+
const stemW = w * adj1 / 100000;
|
|
1291
|
+
const headH = h * adj2 / 100000;
|
|
1292
|
+
const bodyH = h * adj4 / 100000;
|
|
1293
|
+
const stemL = pad + (w - stemW) / 2, stemR = pad + (w + stemW) / 2;
|
|
1294
|
+
const bodyY = pad + h - bodyH;
|
|
1295
|
+
drawCmd = polyPath([
|
|
1296
|
+
[pad + w / 2, pad],
|
|
1297
|
+
[pad + w, pad + headH], [stemR, pad + headH],
|
|
1298
|
+
[stemR, bodyY], [pad + w, bodyY],
|
|
1299
|
+
[pad + w, pad + h], [pad, pad + h],
|
|
1300
|
+
[pad, bodyY], [stemL, bodyY],
|
|
1301
|
+
[stemL, pad + headH], [pad, pad + headH],
|
|
1302
|
+
]);
|
|
1303
|
+
}
|
|
1304
|
+
else if (preset === "downArrowCallout") {
|
|
1305
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1306
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1307
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1308
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 64977;
|
|
1309
|
+
const stemW = w * adj1 / 100000;
|
|
1310
|
+
const headH = h * adj2 / 100000;
|
|
1311
|
+
const bodyH = h * adj4 / 100000;
|
|
1312
|
+
const stemL = pad + (w - stemW) / 2, stemR = pad + (w + stemW) / 2;
|
|
1313
|
+
drawCmd = polyPath([
|
|
1314
|
+
[pad, pad], [pad + w, pad],
|
|
1315
|
+
[pad + w, pad + bodyH], [stemR, pad + bodyH],
|
|
1316
|
+
[stemR, pad + h - headH], [pad + w, pad + h - headH],
|
|
1317
|
+
[pad + w / 2, pad + h],
|
|
1318
|
+
[pad, pad + h - headH], [stemL, pad + h - headH],
|
|
1319
|
+
[stemL, pad + bodyH], [pad, pad + bodyH],
|
|
1320
|
+
]);
|
|
1321
|
+
}
|
|
1322
|
+
else if (preset === "leftRightArrowCallout") {
|
|
1323
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1324
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1325
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1326
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 64977;
|
|
1327
|
+
const stemH = h * adj1 / 100000;
|
|
1328
|
+
const headW = w * adj2 / 100000;
|
|
1329
|
+
const bodyX = w * (1 - adj4 / 100000) / 2;
|
|
1330
|
+
const stemTop = pad + (h - stemH) / 2, stemBot = pad + (h + stemH) / 2;
|
|
1331
|
+
drawCmd = polyPath([
|
|
1332
|
+
[pad + bodyX, pad], [pad + w - bodyX, pad],
|
|
1333
|
+
[pad + w - bodyX, stemTop], [pad + w - headW, stemTop],
|
|
1334
|
+
[pad + w - headW, pad], [pad + w, pad + h / 2],
|
|
1335
|
+
[pad + w - headW, pad + h], [pad + w - headW, stemBot],
|
|
1336
|
+
[pad + w - bodyX, stemBot], [pad + w - bodyX, pad + h],
|
|
1337
|
+
[pad + bodyX, pad + h], [pad + bodyX, stemBot],
|
|
1338
|
+
[pad + headW, stemBot], [pad + headW, pad + h],
|
|
1339
|
+
[pad, pad + h / 2],
|
|
1340
|
+
[pad + headW, pad], [pad + headW, stemTop],
|
|
1341
|
+
[pad + bodyX, stemTop],
|
|
1342
|
+
]);
|
|
1343
|
+
}
|
|
1344
|
+
else if (preset === "upDownArrowCallout") {
|
|
1345
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1346
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 25000;
|
|
1347
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 25000;
|
|
1348
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 64977;
|
|
1349
|
+
const stemW = w * adj1 / 100000;
|
|
1350
|
+
const headH = h * adj2 / 100000;
|
|
1351
|
+
const bodyY = h * (1 - adj4 / 100000) / 2;
|
|
1352
|
+
const stemL = pad + (w - stemW) / 2, stemR = pad + (w + stemW) / 2;
|
|
1353
|
+
drawCmd = polyPath([
|
|
1354
|
+
[pad + w / 2, pad],
|
|
1355
|
+
[pad + w, pad + headH], [stemR, pad + headH],
|
|
1356
|
+
[stemR, pad + bodyY], [pad + w, pad + bodyY],
|
|
1357
|
+
[pad + w, pad + h - bodyY], [stemR, pad + h - bodyY],
|
|
1358
|
+
[stemR, pad + h - headH], [pad + w, pad + h - headH],
|
|
1359
|
+
[pad + w / 2, pad + h],
|
|
1360
|
+
[pad, pad + h - headH], [stemL, pad + h - headH],
|
|
1361
|
+
[stemL, pad + h - bodyY], [pad, pad + h - bodyY],
|
|
1362
|
+
[pad, pad + bodyY], [stemL, pad + bodyY],
|
|
1363
|
+
[stemL, pad + headH], [pad, pad + headH],
|
|
1364
|
+
]);
|
|
1365
|
+
}
|
|
1366
|
+
else if (preset === "quadArrowCallout") {
|
|
1367
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 18515;
|
|
1368
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 18515;
|
|
1369
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 18515;
|
|
1370
|
+
const adj4 = adjustValues?.adj4 ? parseAdjustValue(adjustValues.adj4) : 48123;
|
|
1371
|
+
const stemW = w * adj1 / 100000;
|
|
1372
|
+
const headW = w * adj2 / 100000;
|
|
1373
|
+
const headH = h * adj3 / 100000;
|
|
1374
|
+
const bodyD = w * adj4 / 100000 / 2;
|
|
1375
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
1376
|
+
const sL = cx - stemW / 2, sR = cx + stemW / 2;
|
|
1377
|
+
const sT = cy - stemW / 2, sB = cy + stemW / 2;
|
|
1378
|
+
drawCmd = polyPath([
|
|
1379
|
+
[cx, pad], [cx + headW / 2, pad + headH], [sR, pad + headH],
|
|
1380
|
+
[sR, cy - bodyD], [cx + bodyD, cy - bodyD],
|
|
1381
|
+
[cx + bodyD, sT], [pad + w - headH, sT], [pad + w - headH, cy - headW / 2],
|
|
1382
|
+
[pad + w, cy], [pad + w - headH, cy + headW / 2],
|
|
1383
|
+
[pad + w - headH, sB], [cx + bodyD, sB],
|
|
1384
|
+
[cx + bodyD, cy + bodyD], [sR, cy + bodyD],
|
|
1385
|
+
[sR, pad + h - headH], [cx + headW / 2, pad + h - headH],
|
|
1386
|
+
[cx, pad + h], [cx - headW / 2, pad + h - headH],
|
|
1387
|
+
[sL, pad + h - headH], [sL, cy + bodyD],
|
|
1388
|
+
[cx - bodyD, cy + bodyD], [cx - bodyD, sB],
|
|
1389
|
+
[pad + headH, sB], [pad + headH, cy + headW / 2],
|
|
1390
|
+
[pad, cy], [pad + headH, cy - headW / 2],
|
|
1391
|
+
[pad + headH, sT], [cx - bodyD, sT],
|
|
1392
|
+
[cx - bodyD, cy - bodyD], [sL, cy - bodyD],
|
|
1393
|
+
[sL, pad + headH], [cx - headW / 2, pad + headH],
|
|
1394
|
+
]);
|
|
1395
|
+
}
|
|
1396
|
+
else if (preset === "callout1" || preset === "accentCallout1") {
|
|
1397
|
+
// Rectangle with a single-segment callout line (just render as rectangle)
|
|
1398
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1399
|
+
}
|
|
1400
|
+
else if (preset === "callout2" || preset === "accentCallout2") {
|
|
1401
|
+
// Rectangle with two-segment callout line
|
|
1402
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1403
|
+
}
|
|
1404
|
+
else if (preset === "callout3" || preset === "accentCallout3") {
|
|
1405
|
+
// Rectangle with three-segment callout line
|
|
1406
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1407
|
+
}
|
|
1408
|
+
else if (preset === "borderCallout1" || preset === "accentBorderCallout1") {
|
|
1409
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1410
|
+
}
|
|
1411
|
+
else if (preset === "borderCallout2" || preset === "accentBorderCallout2") {
|
|
1412
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1413
|
+
}
|
|
1414
|
+
else if (preset === "borderCallout3" || preset === "accentBorderCallout3") {
|
|
1415
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1416
|
+
}
|
|
1417
|
+
else if (preset === "flowChartPredefinedProcess") {
|
|
1418
|
+
// Rectangle with vertical lines near left and right edges
|
|
1419
|
+
const inset = w * 0.1;
|
|
1420
|
+
drawCmd = [
|
|
1421
|
+
// Outer rect
|
|
1422
|
+
`${pad} ${py(pad)} m`, `${pad + w} ${py(pad)} l`,
|
|
1423
|
+
`${pad + w} ${py(pad + h)} l`, `${pad} ${py(pad + h)} l`, "f",
|
|
1424
|
+
].join("\n");
|
|
1425
|
+
}
|
|
1426
|
+
else if (preset === "flowChartInternalStorage") {
|
|
1427
|
+
// Rectangle with horizontal and vertical lines near top-left
|
|
1428
|
+
drawCmd = [
|
|
1429
|
+
`${pad} ${py(pad)} m`, `${pad + w} ${py(pad)} l`,
|
|
1430
|
+
`${pad + w} ${py(pad + h)} l`, `${pad} ${py(pad + h)} l`, "f",
|
|
1431
|
+
].join("\n");
|
|
1432
|
+
}
|
|
1433
|
+
else if (preset === "flowChartMultidocument") {
|
|
1434
|
+
// Three overlapping document shapes
|
|
1435
|
+
const off = w * 0.08;
|
|
1436
|
+
const waveH = h * 0.08;
|
|
1437
|
+
// Back doc (offset twice)
|
|
1438
|
+
drawCmd = [
|
|
1439
|
+
// Main doc (front)
|
|
1440
|
+
...polyPathLines([[pad, pad + off * 2], [pad + w - off * 2, pad + off * 2],
|
|
1441
|
+
[pad + w - off * 2, pad + h - waveH], [pad + (w - off * 2) * 0.75, pad + h],
|
|
1442
|
+
[pad + (w - off * 2) * 0.5, pad + h - waveH],
|
|
1443
|
+
[pad + (w - off * 2) * 0.25, pad + h - waveH * 2], [pad, pad + h - waveH]]),
|
|
1444
|
+
// Middle doc outline (just top edge visible)
|
|
1445
|
+
...polyPathLines([[pad + off, pad + off], [pad + w - off, pad + off],
|
|
1446
|
+
[pad + w - off, pad + off * 2]]),
|
|
1447
|
+
"f",
|
|
1448
|
+
// Back doc outline (just top edge visible)
|
|
1449
|
+
...polyPathLines([[pad + off * 2, pad], [pad + w, pad],
|
|
1450
|
+
[pad + w, pad + off]]),
|
|
1451
|
+
"f",
|
|
1452
|
+
].join("\n");
|
|
1453
|
+
}
|
|
1454
|
+
else if (preset === "flowChartManualInput") {
|
|
1455
|
+
// Quadrilateral with slanted top (top-left higher than top-right)
|
|
1456
|
+
const slant = h * 0.2;
|
|
1457
|
+
drawCmd = polyPath([
|
|
1458
|
+
[pad, pad + slant], [pad + w, pad],
|
|
1459
|
+
[pad + w, pad + h], [pad, pad + h],
|
|
1460
|
+
]);
|
|
1461
|
+
}
|
|
1462
|
+
else if (preset === "flowChartManualOperation") {
|
|
1463
|
+
// Trapezoid (wider at top)
|
|
1464
|
+
const inset = w * 0.2;
|
|
1465
|
+
drawCmd = polyPath([
|
|
1466
|
+
[pad, pad], [pad + w, pad],
|
|
1467
|
+
[pad + w - inset, pad + h], [pad + inset, pad + h],
|
|
1468
|
+
]);
|
|
1469
|
+
}
|
|
1470
|
+
else if (preset === "flowChartOffpageConnector") {
|
|
1471
|
+
// Pentagon pointing down (like home plate but downward)
|
|
1472
|
+
const pointH = h * 0.2;
|
|
1473
|
+
drawCmd = polyPath([
|
|
1474
|
+
[pad, pad], [pad + w, pad],
|
|
1475
|
+
[pad + w, pad + h - pointH],
|
|
1476
|
+
[pad + w / 2, pad + h],
|
|
1477
|
+
[pad, pad + h - pointH],
|
|
1478
|
+
]);
|
|
1479
|
+
}
|
|
1480
|
+
else if (preset === "flowChartPunchedCard") {
|
|
1481
|
+
// Rectangle with clipped top-left corner
|
|
1482
|
+
const clip = Math.min(w, h) * 0.15;
|
|
1483
|
+
drawCmd = polyPath([
|
|
1484
|
+
[pad + clip, pad], [pad + w, pad],
|
|
1485
|
+
[pad + w, pad + h], [pad, pad + h],
|
|
1486
|
+
[pad, pad + clip],
|
|
1487
|
+
]);
|
|
1488
|
+
}
|
|
1489
|
+
else if (preset === "flowChartPunchedTape") {
|
|
1490
|
+
// Rectangle with wavy top and bottom
|
|
1491
|
+
const waveH = h * 0.1;
|
|
1492
|
+
drawCmd = polyPath([
|
|
1493
|
+
[pad, pad + waveH],
|
|
1494
|
+
[pad + w * 0.25, pad], [pad + w * 0.5, pad + waveH],
|
|
1495
|
+
[pad + w * 0.75, pad + waveH * 2], [pad + w, pad + waveH],
|
|
1496
|
+
[pad + w, pad + h - waveH],
|
|
1497
|
+
[pad + w * 0.75, pad + h], [pad + w * 0.5, pad + h - waveH],
|
|
1498
|
+
[pad + w * 0.25, pad + h - waveH * 2], [pad, pad + h - waveH],
|
|
1499
|
+
]);
|
|
1500
|
+
}
|
|
1501
|
+
else if (preset === "flowChartSummingJunction") {
|
|
1502
|
+
// Circle with X inside — just render as circle
|
|
1503
|
+
drawCmd = ellipseCmd(pad, w, h, totalH);
|
|
1504
|
+
}
|
|
1505
|
+
else if (preset === "flowChartOr") {
|
|
1506
|
+
// Circle with + inside — just render as circle
|
|
1507
|
+
drawCmd = ellipseCmd(pad, w, h, totalH);
|
|
1508
|
+
}
|
|
1509
|
+
else if (preset === "flowChartCollate") {
|
|
1510
|
+
// Two triangles forming an hourglass (collate)
|
|
1511
|
+
const cx = pad + w / 2;
|
|
1512
|
+
drawCmd = [
|
|
1513
|
+
// Top triangle (pointing down)
|
|
1514
|
+
...polyPathLines([[pad, pad], [pad + w, pad], [cx, pad + h / 2]]),
|
|
1515
|
+
"f",
|
|
1516
|
+
// Bottom triangle (pointing up)
|
|
1517
|
+
...polyPathLines([[pad, pad + h], [pad + w, pad + h], [cx, pad + h / 2]]),
|
|
1518
|
+
"f",
|
|
1519
|
+
].join("\n");
|
|
1520
|
+
}
|
|
1521
|
+
else if (preset === "flowChartSort") {
|
|
1522
|
+
// Two triangles forming a diamond split horizontally
|
|
1523
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
1524
|
+
drawCmd = [
|
|
1525
|
+
// Top triangle
|
|
1526
|
+
...polyPathLines([[pad, cy], [pad + w, cy], [cx, pad]]),
|
|
1527
|
+
"f",
|
|
1528
|
+
// Bottom triangle
|
|
1529
|
+
...polyPathLines([[pad, cy], [pad + w, cy], [cx, pad + h]]),
|
|
1530
|
+
"f",
|
|
1531
|
+
].join("\n");
|
|
1532
|
+
}
|
|
1533
|
+
else if (preset === "flowChartExtract") {
|
|
1534
|
+
// Upward-pointing triangle
|
|
1535
|
+
drawCmd = polyPath([[pad + w / 2, pad], [pad + w, pad + h], [pad, pad + h]]);
|
|
1536
|
+
}
|
|
1537
|
+
else if (preset === "flowChartMerge") {
|
|
1538
|
+
// Downward-pointing triangle
|
|
1539
|
+
drawCmd = polyPath([[pad, pad], [pad + w, pad], [pad + w / 2, pad + h]]);
|
|
1540
|
+
}
|
|
1541
|
+
else if (preset === "flowChartOnlineStorage") {
|
|
1542
|
+
// Rectangle with curved left side
|
|
1543
|
+
const curveW = w * 0.15;
|
|
1544
|
+
const k = 0.5522847498;
|
|
1545
|
+
const cy = pad + h / 2;
|
|
1546
|
+
drawCmd = [
|
|
1547
|
+
`${pad + curveW} ${py(pad)} m`,
|
|
1548
|
+
`${pad + w} ${py(pad)} l`,
|
|
1549
|
+
`${pad + w} ${py(pad + h)} l`,
|
|
1550
|
+
`${pad + curveW} ${py(pad + h)} l`,
|
|
1551
|
+
// Curved left side (inward arc)
|
|
1552
|
+
`${pad + curveW - curveW * k} ${py(pad + h)} ${pad} ${py(cy + h / 2 * k)} ${pad} ${py(cy)} c`,
|
|
1553
|
+
`${pad} ${py(cy - h / 2 * k)} ${pad + curveW - curveW * k} ${py(pad)} ${pad + curveW} ${py(pad)} c`,
|
|
1554
|
+
"f",
|
|
1555
|
+
].join("\n");
|
|
1556
|
+
}
|
|
1557
|
+
else if (preset === "flowChartDelay") {
|
|
1558
|
+
// Rectangle with rounded right side (semicircle)
|
|
1559
|
+
const k = 0.5522847498;
|
|
1560
|
+
const cy = pad + h / 2;
|
|
1561
|
+
const ry = h / 2;
|
|
1562
|
+
drawCmd = [
|
|
1563
|
+
`${pad} ${py(pad)} m`,
|
|
1564
|
+
`${pad + w / 2} ${py(pad)} l`,
|
|
1565
|
+
// Right semicircle
|
|
1566
|
+
`${pad + w / 2 + w / 2 * k} ${py(pad)} ${pad + w} ${py(cy - ry * k)} ${pad + w} ${py(cy)} c`,
|
|
1567
|
+
`${pad + w} ${py(cy + ry * k)} ${pad + w / 2 + w / 2 * k} ${py(pad + h)} ${pad + w / 2} ${py(pad + h)} c`,
|
|
1568
|
+
`${pad} ${py(pad + h)} l`,
|
|
1569
|
+
"f",
|
|
1570
|
+
].join("\n");
|
|
1571
|
+
}
|
|
1572
|
+
else if (preset === "flowChartMagneticTape") {
|
|
1573
|
+
// Circle with a small tail at bottom-right
|
|
1574
|
+
drawCmd = ellipseCmd(pad, w, h, totalH);
|
|
1575
|
+
}
|
|
1576
|
+
else if (preset === "flowChartMagneticDisk") {
|
|
1577
|
+
// Cylinder (horizontal) — same as "can" but oriented differently
|
|
1578
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
1579
|
+
const capH = h * adj / 200000;
|
|
1580
|
+
const k = 0.5522847498;
|
|
1581
|
+
const cx = pad + w / 2, rx = w / 2;
|
|
1582
|
+
const bodyTop = pad + capH, bodyBot = pad + h - capH;
|
|
1583
|
+
drawCmd = [
|
|
1584
|
+
`${pad} ${py(bodyTop)} m`,
|
|
1585
|
+
`${pad} ${py(bodyBot)} l`,
|
|
1586
|
+
`${pad} ${py(bodyBot + capH * k)} ${cx - rx * k} ${py(pad + h)} ${cx} ${py(pad + h)} c`,
|
|
1587
|
+
`${cx + rx * k} ${py(pad + h)} ${pad + w} ${py(bodyBot + capH * k)} ${pad + w} ${py(bodyBot)} c`,
|
|
1588
|
+
`${pad + w} ${py(bodyTop)} l`,
|
|
1589
|
+
`${pad + w} ${py(bodyTop - capH * k)} ${cx + rx * k} ${py(pad)} ${cx} ${py(pad)} c`,
|
|
1590
|
+
`${cx - rx * k} ${py(pad)} ${pad} ${py(bodyTop - capH * k)} ${pad} ${py(bodyTop)} c`,
|
|
1591
|
+
"f",
|
|
1592
|
+
].join("\n");
|
|
1593
|
+
}
|
|
1594
|
+
else if (preset === "flowChartMagneticDrum") {
|
|
1595
|
+
// Cylinder on its side — rectangle with curved right side
|
|
1596
|
+
const capW = w * 0.15;
|
|
1597
|
+
const k = 0.5522847498;
|
|
1598
|
+
const cy = pad + h / 2, ry = h / 2;
|
|
1599
|
+
const bodyRight = pad + w - capW;
|
|
1600
|
+
drawCmd = [
|
|
1601
|
+
`${pad} ${py(pad)} m`,
|
|
1602
|
+
`${bodyRight} ${py(pad)} l`,
|
|
1603
|
+
`${bodyRight + capW * k} ${py(pad)} ${pad + w} ${py(cy - ry * k)} ${pad + w} ${py(cy)} c`,
|
|
1604
|
+
`${pad + w} ${py(cy + ry * k)} ${bodyRight + capW * k} ${py(pad + h)} ${bodyRight} ${py(pad + h)} c`,
|
|
1605
|
+
`${pad} ${py(pad + h)} l`,
|
|
1606
|
+
"f",
|
|
1607
|
+
].join("\n");
|
|
1608
|
+
}
|
|
1609
|
+
else if (preset === "flowChartDisplay") {
|
|
1610
|
+
// Rectangle with pointed left side and rounded right side
|
|
1611
|
+
const pointW = w * 0.15;
|
|
1612
|
+
const k = 0.5522847498;
|
|
1613
|
+
const cy = pad + h / 2;
|
|
1614
|
+
const ry = h / 2;
|
|
1615
|
+
drawCmd = [
|
|
1616
|
+
`${pad} ${py(cy)} m`,
|
|
1617
|
+
`${pad + pointW} ${py(pad)} l`,
|
|
1618
|
+
`${pad + w / 2} ${py(pad)} l`,
|
|
1619
|
+
`${pad + w / 2 + w / 2 * k} ${py(pad)} ${pad + w} ${py(cy - ry * k)} ${pad + w} ${py(cy)} c`,
|
|
1620
|
+
`${pad + w} ${py(cy + ry * k)} ${pad + w / 2 + w / 2 * k} ${py(pad + h)} ${pad + w / 2} ${py(pad + h)} c`,
|
|
1621
|
+
`${pad + pointW} ${py(pad + h)} l`,
|
|
1622
|
+
"f",
|
|
1623
|
+
].join("\n");
|
|
1624
|
+
}
|
|
1625
|
+
else if (preset === "line" || preset === "straightConnector1") {
|
|
1626
|
+
// Diagonal line from top-left to bottom-right
|
|
1627
|
+
const lw = 1;
|
|
1628
|
+
drawCmd = [
|
|
1629
|
+
`${pad} ${py(pad)} m`,
|
|
1630
|
+
`${pad + w} ${py(pad + h)} l`,
|
|
1631
|
+
`${pad + w + lw} ${py(pad + h)} l`,
|
|
1632
|
+
`${pad + lw} ${py(pad)} l`,
|
|
1633
|
+
"f",
|
|
1634
|
+
].join("\n");
|
|
1635
|
+
}
|
|
1636
|
+
else if (preset === "bentConnector2") {
|
|
1637
|
+
// L-shaped connector: right then down
|
|
1638
|
+
const lw = 1;
|
|
1639
|
+
drawCmd = [
|
|
1640
|
+
`${pad} ${py(pad)} m`, `${pad + w} ${py(pad)} l`,
|
|
1641
|
+
`${pad + w} ${py(pad + h)} l`, `${pad + w - lw} ${py(pad + h)} l`,
|
|
1642
|
+
`${pad + w - lw} ${py(pad + lw)} l`, `${pad} ${py(pad + lw)} l`,
|
|
1643
|
+
"f",
|
|
1644
|
+
].join("\n");
|
|
1645
|
+
}
|
|
1646
|
+
else if (preset === "bentConnector3") {
|
|
1647
|
+
// Z-shaped connector with one bend
|
|
1648
|
+
const adj = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
1649
|
+
const bendX = w * adj / 100000;
|
|
1650
|
+
const lw = 1;
|
|
1651
|
+
drawCmd = [
|
|
1652
|
+
`${pad} ${py(pad)} m`, `${pad + bendX} ${py(pad)} l`,
|
|
1653
|
+
`${pad + bendX} ${py(pad + h)} l`, `${pad + w} ${py(pad + h)} l`,
|
|
1654
|
+
`${pad + w} ${py(pad + h - lw)} l`, `${pad + bendX + lw} ${py(pad + h - lw)} l`,
|
|
1655
|
+
`${pad + bendX + lw} ${py(pad + lw)} l`, `${pad} ${py(pad + lw)} l`,
|
|
1656
|
+
"f",
|
|
1657
|
+
].join("\n");
|
|
1658
|
+
}
|
|
1659
|
+
else if (preset === "bentConnector4") {
|
|
1660
|
+
// Connector with two bends
|
|
1661
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
1662
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1663
|
+
const bendX = w * adj1 / 100000;
|
|
1664
|
+
const bendY = h * adj2 / 100000;
|
|
1665
|
+
const lw = 1;
|
|
1666
|
+
drawCmd = [
|
|
1667
|
+
`${pad} ${py(pad)} m`, `${pad + bendX} ${py(pad)} l`,
|
|
1668
|
+
`${pad + bendX} ${py(pad + bendY)} l`, `${pad + w} ${py(pad + bendY)} l`,
|
|
1669
|
+
`${pad + w} ${py(pad + h)} l`, `${pad + w - lw} ${py(pad + h)} l`,
|
|
1670
|
+
`${pad + w - lw} ${py(pad + bendY + lw)} l`, `${pad + bendX + lw} ${py(pad + bendY + lw)} l`,
|
|
1671
|
+
`${pad + bendX + lw} ${py(pad + lw)} l`, `${pad} ${py(pad + lw)} l`,
|
|
1672
|
+
"f",
|
|
1673
|
+
].join("\n");
|
|
1674
|
+
}
|
|
1675
|
+
else if (preset === "bentConnector5") {
|
|
1676
|
+
// Connector with three bends — simplified as Z-shape
|
|
1677
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
1678
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1679
|
+
const adj3 = adjustValues?.adj3 ? parseAdjustValue(adjustValues.adj3) : 50000;
|
|
1680
|
+
const bendX1 = w * adj1 / 100000;
|
|
1681
|
+
const bendY = h * adj2 / 100000;
|
|
1682
|
+
const bendX2 = w * adj3 / 100000;
|
|
1683
|
+
const lw = 1;
|
|
1684
|
+
drawCmd = [
|
|
1685
|
+
`${pad} ${py(pad)} m`, `${pad + bendX1} ${py(pad)} l`,
|
|
1686
|
+
`${pad + bendX1} ${py(pad + bendY)} l`, `${pad + bendX2} ${py(pad + bendY)} l`,
|
|
1687
|
+
`${pad + bendX2} ${py(pad + h)} l`, `${pad + w} ${py(pad + h)} l`,
|
|
1688
|
+
`${pad + w} ${py(pad + h - lw)} l`, `${pad + bendX2 + lw} ${py(pad + h - lw)} l`,
|
|
1689
|
+
`${pad + bendX2 + lw} ${py(pad + bendY + lw)} l`, `${pad + bendX1 + lw} ${py(pad + bendY + lw)} l`,
|
|
1690
|
+
`${pad + bendX1 + lw} ${py(pad + lw)} l`, `${pad} ${py(pad + lw)} l`,
|
|
1691
|
+
"f",
|
|
1692
|
+
].join("\n");
|
|
1693
|
+
}
|
|
1694
|
+
else if (preset === "curvedConnector2") {
|
|
1695
|
+
// Simple curved connector (bezier from top-left to bottom-right)
|
|
1696
|
+
const lw = 1;
|
|
1697
|
+
drawCmd = [
|
|
1698
|
+
`${pad} ${py(pad)} m`,
|
|
1699
|
+
`${pad + w} ${py(pad)} ${pad + w} ${py(pad + h)} ${pad + w} ${py(pad + h)} c`,
|
|
1700
|
+
`${pad + w - lw} ${py(pad + h)} l`,
|
|
1701
|
+
`${pad + w - lw} ${py(pad + h)} ${pad + lw} ${py(pad)} ${pad} ${py(pad)} c`,
|
|
1702
|
+
"f",
|
|
1703
|
+
].join("\n");
|
|
1704
|
+
}
|
|
1705
|
+
else if (preset === "curvedConnector3") {
|
|
1706
|
+
const adj = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
1707
|
+
const midX = w * adj / 100000;
|
|
1708
|
+
const lw = 1;
|
|
1709
|
+
drawCmd = [
|
|
1710
|
+
`${pad} ${py(pad)} m`,
|
|
1711
|
+
`${pad + midX} ${py(pad)} ${pad + midX} ${py(pad + h)} ${pad + w} ${py(pad + h)} c`,
|
|
1712
|
+
`${pad + w} ${py(pad + h - lw)} l`,
|
|
1713
|
+
`${pad + midX + lw} ${py(pad + h - lw)} ${pad + midX + lw} ${py(pad + lw)} ${pad} ${py(pad + lw)} c`,
|
|
1714
|
+
"f",
|
|
1715
|
+
].join("\n");
|
|
1716
|
+
}
|
|
1717
|
+
else if (preset === "curvedConnector4") {
|
|
1718
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 50000;
|
|
1719
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1720
|
+
const lw = 1;
|
|
1721
|
+
// Simplified S-curve
|
|
1722
|
+
drawCmd = [
|
|
1723
|
+
`${pad} ${py(pad)} m`,
|
|
1724
|
+
`${pad + w * 0.5} ${py(pad)} ${pad + w * 0.5} ${py(pad + h)} ${pad + w} ${py(pad + h)} c`,
|
|
1725
|
+
`${pad + w} ${py(pad + h - lw)} l`,
|
|
1726
|
+
`${pad + w * 0.5 + lw} ${py(pad + h - lw)} ${pad + w * 0.5 + lw} ${py(pad + lw)} ${pad} ${py(pad + lw)} c`,
|
|
1727
|
+
"f",
|
|
1728
|
+
].join("\n");
|
|
1729
|
+
}
|
|
1730
|
+
else if (preset === "curvedConnector5") {
|
|
1731
|
+
const lw = 1;
|
|
1732
|
+
drawCmd = [
|
|
1733
|
+
`${pad} ${py(pad)} m`,
|
|
1734
|
+
`${pad + w * 0.5} ${py(pad)} ${pad + w * 0.5} ${py(pad + h)} ${pad + w} ${py(pad + h)} c`,
|
|
1735
|
+
`${pad + w} ${py(pad + h - lw)} l`,
|
|
1736
|
+
`${pad + w * 0.5 + lw} ${py(pad + h - lw)} ${pad + w * 0.5 + lw} ${py(pad + lw)} ${pad} ${py(pad + lw)} c`,
|
|
1737
|
+
"f",
|
|
1738
|
+
].join("\n");
|
|
1739
|
+
}
|
|
1740
|
+
else if (preset === "actionButtonBlank") {
|
|
1741
|
+
// Simple rectangle (blank action button)
|
|
1742
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1743
|
+
}
|
|
1744
|
+
else if (preset === "actionButtonHome" || preset === "actionButtonHelp" ||
|
|
1745
|
+
preset === "actionButtonInformation" || preset === "actionButtonBackPrevious" ||
|
|
1746
|
+
preset === "actionButtonForwardNext" || preset === "actionButtonBeginning" ||
|
|
1747
|
+
preset === "actionButtonEnd" || preset === "actionButtonReturn" ||
|
|
1748
|
+
preset === "actionButtonDocument" || preset === "actionButtonSound" ||
|
|
1749
|
+
preset === "actionButtonMovie") {
|
|
1750
|
+
// All action buttons: rectangle with icon (icon rendering omitted — PDF shows as rect)
|
|
1751
|
+
drawCmd = `${pad} ${py(pad)} m ${pad + w} ${py(pad)} l ${pad + w} ${py(pad + h)} l ${pad} ${py(pad + h)} l f`;
|
|
1752
|
+
}
|
|
1753
|
+
else if (preset === "ribbon") {
|
|
1754
|
+
// Ribbon banner (bottom tabs, main area raised)
|
|
1755
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 16667;
|
|
1756
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1757
|
+
const tabH = h * adj1 / 100000;
|
|
1758
|
+
const tabW = w * adj2 / 200000;
|
|
1759
|
+
drawCmd = polyPath([
|
|
1760
|
+
[pad, pad + tabH],
|
|
1761
|
+
[pad + tabW, pad + tabH],
|
|
1762
|
+
[pad + tabW, pad],
|
|
1763
|
+
[pad + w - tabW, pad],
|
|
1764
|
+
[pad + w - tabW, pad + tabH],
|
|
1765
|
+
[pad + w, pad + tabH],
|
|
1766
|
+
[pad + w, pad + h],
|
|
1767
|
+
[pad + w - tabW, pad + h - tabH],
|
|
1768
|
+
[pad + tabW, pad + h - tabH],
|
|
1769
|
+
[pad, pad + h],
|
|
1770
|
+
]);
|
|
1771
|
+
}
|
|
1772
|
+
else if (preset === "ribbon2") {
|
|
1773
|
+
// Ribbon2: tabs on top, notch at bottom
|
|
1774
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 16667;
|
|
1775
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1776
|
+
const tabH = h * adj1 / 100000;
|
|
1777
|
+
const tabW = w * adj2 / 200000;
|
|
1778
|
+
drawCmd = polyPath([
|
|
1779
|
+
[pad, pad],
|
|
1780
|
+
[pad + tabW, pad + tabH],
|
|
1781
|
+
[pad + tabW, pad + h],
|
|
1782
|
+
[pad + w - tabW, pad + h],
|
|
1783
|
+
[pad + w - tabW, pad + tabH],
|
|
1784
|
+
[pad + w, pad],
|
|
1785
|
+
[pad + w, pad + h - tabH],
|
|
1786
|
+
[pad + w - tabW, pad + h - tabH],
|
|
1787
|
+
[pad + tabW, pad + h - tabH],
|
|
1788
|
+
[pad, pad + h - tabH],
|
|
1789
|
+
]);
|
|
1790
|
+
}
|
|
1791
|
+
else if (preset === "ellipseRibbon") {
|
|
1792
|
+
// Ellipse ribbon — simplified as ribbon with curved bottom
|
|
1793
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1794
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1795
|
+
const tabH = h * adj1 / 100000;
|
|
1796
|
+
const tabW = w * adj2 / 200000;
|
|
1797
|
+
drawCmd = polyPath([
|
|
1798
|
+
[pad, pad + tabH],
|
|
1799
|
+
[pad + tabW, pad + tabH],
|
|
1800
|
+
[pad + tabW, pad],
|
|
1801
|
+
[pad + w - tabW, pad],
|
|
1802
|
+
[pad + w - tabW, pad + tabH],
|
|
1803
|
+
[pad + w, pad + tabH],
|
|
1804
|
+
[pad + w, pad + h - tabH],
|
|
1805
|
+
[pad + w * 0.75, pad + h],
|
|
1806
|
+
[pad + w / 2, pad + h - tabH / 2],
|
|
1807
|
+
[pad + w * 0.25, pad + h],
|
|
1808
|
+
[pad, pad + h - tabH],
|
|
1809
|
+
]);
|
|
1810
|
+
}
|
|
1811
|
+
else if (preset === "ellipseRibbon2") {
|
|
1812
|
+
// Ellipse ribbon 2 — inverted
|
|
1813
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 25000;
|
|
1814
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1815
|
+
const tabH = h * adj1 / 100000;
|
|
1816
|
+
const tabW = w * adj2 / 200000;
|
|
1817
|
+
drawCmd = polyPath([
|
|
1818
|
+
[pad, pad + tabH],
|
|
1819
|
+
[pad + w * 0.25, pad],
|
|
1820
|
+
[pad + w / 2, pad + tabH / 2],
|
|
1821
|
+
[pad + w * 0.75, pad],
|
|
1822
|
+
[pad + w, pad + tabH],
|
|
1823
|
+
[pad + w, pad + h - tabH],
|
|
1824
|
+
[pad + w - tabW, pad + h - tabH],
|
|
1825
|
+
[pad + w - tabW, pad + h],
|
|
1826
|
+
[pad + tabW, pad + h],
|
|
1827
|
+
[pad + tabW, pad + h - tabH],
|
|
1828
|
+
[pad, pad + h - tabH],
|
|
1829
|
+
]);
|
|
1830
|
+
}
|
|
1831
|
+
else if (preset === "horizontalScroll") {
|
|
1832
|
+
// Scroll shape with rolled ends on left and right
|
|
1833
|
+
const rollR = Math.min(w, h) * 0.12;
|
|
1834
|
+
drawCmd = [
|
|
1835
|
+
// Main body
|
|
1836
|
+
...polyPathLines([
|
|
1837
|
+
[pad + rollR, pad + rollR],
|
|
1838
|
+
[pad + w - rollR, pad],
|
|
1839
|
+
[pad + w - rollR, pad + h - rollR],
|
|
1840
|
+
[pad + rollR, pad + h],
|
|
1841
|
+
]),
|
|
1842
|
+
"f",
|
|
1843
|
+
].join("\n");
|
|
1844
|
+
}
|
|
1845
|
+
else if (preset === "verticalScroll") {
|
|
1846
|
+
// Scroll shape with rolled ends on top and bottom
|
|
1847
|
+
const rollR = Math.min(w, h) * 0.12;
|
|
1848
|
+
drawCmd = [
|
|
1849
|
+
...polyPathLines([
|
|
1850
|
+
[pad + rollR, pad + rollR],
|
|
1851
|
+
[pad + w, pad + rollR],
|
|
1852
|
+
[pad + w - rollR, pad + h - rollR],
|
|
1853
|
+
[pad, pad + h - rollR],
|
|
1854
|
+
]),
|
|
1855
|
+
"f",
|
|
1856
|
+
].join("\n");
|
|
1857
|
+
}
|
|
1858
|
+
else if (preset === "wave") {
|
|
1859
|
+
// Rectangle with wavy top and bottom (sinusoidal)
|
|
1860
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 12500;
|
|
1861
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 0;
|
|
1862
|
+
const amplitude = h * adj1 / 100000;
|
|
1863
|
+
drawCmd = polyPath([
|
|
1864
|
+
[pad, pad + amplitude],
|
|
1865
|
+
[pad + w * 0.25, pad],
|
|
1866
|
+
[pad + w * 0.5, pad + amplitude],
|
|
1867
|
+
[pad + w * 0.75, pad + amplitude * 2],
|
|
1868
|
+
[pad + w, pad + amplitude],
|
|
1869
|
+
[pad + w, pad + h - amplitude],
|
|
1870
|
+
[pad + w * 0.75, pad + h],
|
|
1871
|
+
[pad + w * 0.5, pad + h - amplitude],
|
|
1872
|
+
[pad + w * 0.25, pad + h - amplitude * 2],
|
|
1873
|
+
[pad, pad + h - amplitude],
|
|
1874
|
+
]);
|
|
1875
|
+
}
|
|
1876
|
+
else if (preset === "doubleWave") {
|
|
1877
|
+
// Like wave but with double amplitude
|
|
1878
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 6250;
|
|
1879
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 0;
|
|
1880
|
+
const amplitude = h * adj1 / 100000;
|
|
1881
|
+
drawCmd = polyPath([
|
|
1882
|
+
[pad, pad + amplitude],
|
|
1883
|
+
[pad + w * 0.17, pad], [pad + w * 0.33, pad + amplitude * 2],
|
|
1884
|
+
[pad + w * 0.5, pad], [pad + w * 0.67, pad + amplitude * 2],
|
|
1885
|
+
[pad + w * 0.83, pad], [pad + w, pad + amplitude],
|
|
1886
|
+
[pad + w, pad + h - amplitude],
|
|
1887
|
+
[pad + w * 0.83, pad + h], [pad + w * 0.67, pad + h - amplitude * 2],
|
|
1888
|
+
[pad + w * 0.5, pad + h], [pad + w * 0.33, pad + h - amplitude * 2],
|
|
1889
|
+
[pad + w * 0.17, pad + h], [pad, pad + h - amplitude],
|
|
1890
|
+
]);
|
|
1891
|
+
}
|
|
1892
|
+
else if (preset === "irregularSeal2") {
|
|
1893
|
+
// Explosion/burst shape 2 — irregular polygon
|
|
1894
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
1895
|
+
drawCmd = polyPath([
|
|
1896
|
+
[pad + w * 0.1, pad],
|
|
1897
|
+
[pad + w * 0.3, pad + h * 0.2],
|
|
1898
|
+
[pad + w * 0.5, pad],
|
|
1899
|
+
[pad + w * 0.6, pad + h * 0.15],
|
|
1900
|
+
[pad + w * 0.85, pad + h * 0.05],
|
|
1901
|
+
[pad + w * 0.8, pad + h * 0.3],
|
|
1902
|
+
[pad + w, pad + h * 0.35],
|
|
1903
|
+
[pad + w * 0.85, pad + h * 0.5],
|
|
1904
|
+
[pad + w * 0.95, pad + h * 0.7],
|
|
1905
|
+
[pad + w * 0.75, pad + h * 0.7],
|
|
1906
|
+
[pad + w * 0.8, pad + h * 0.9],
|
|
1907
|
+
[pad + w * 0.55, pad + h * 0.8],
|
|
1908
|
+
[pad + w * 0.4, pad + h],
|
|
1909
|
+
[pad + w * 0.35, pad + h * 0.75],
|
|
1910
|
+
[pad + w * 0.1, pad + h * 0.85],
|
|
1911
|
+
[pad + w * 0.2, pad + h * 0.65],
|
|
1912
|
+
[pad, pad + h * 0.6],
|
|
1913
|
+
[pad + w * 0.15, pad + h * 0.45],
|
|
1914
|
+
[pad, pad + h * 0.25],
|
|
1915
|
+
[pad + w * 0.15, pad + h * 0.2],
|
|
1916
|
+
]);
|
|
1917
|
+
}
|
|
1918
|
+
else if (preset === "gear9") {
|
|
1919
|
+
// 9-tooth gear — circle with triangular teeth
|
|
1920
|
+
const cx = pad + w / 2, cy = pad + h / 2;
|
|
1921
|
+
const outerR = Math.min(w, h) / 2;
|
|
1922
|
+
const innerR = outerR * 0.75;
|
|
1923
|
+
const toothW = Math.PI / 18; // half-width of each tooth in radians
|
|
1924
|
+
const pts = [];
|
|
1925
|
+
for (let i = 0; i < 9; i++) {
|
|
1926
|
+
const a = Math.PI * 2 * i / 9 - Math.PI / 2;
|
|
1927
|
+
// Tooth outer points
|
|
1928
|
+
pts.push([cx + Math.cos(a - toothW) * outerR, cy + Math.sin(a - toothW) * outerR]);
|
|
1929
|
+
pts.push([cx + Math.cos(a + toothW) * outerR, cy + Math.sin(a + toothW) * outerR]);
|
|
1930
|
+
// Valley between teeth
|
|
1931
|
+
const va = a + Math.PI / 9;
|
|
1932
|
+
pts.push([cx + Math.cos(va - toothW) * innerR, cy + Math.sin(va - toothW) * innerR]);
|
|
1933
|
+
pts.push([cx + Math.cos(va + toothW) * innerR, cy + Math.sin(va + toothW) * innerR]);
|
|
1934
|
+
}
|
|
1935
|
+
drawCmd = polyPath(pts);
|
|
1936
|
+
}
|
|
1937
|
+
else if (preset === "funnel") {
|
|
1938
|
+
// Funnel: wide at top, narrow at bottom
|
|
1939
|
+
const neckW = w * 0.3;
|
|
1940
|
+
const neckH = h * 0.4;
|
|
1941
|
+
const cx = pad + w / 2;
|
|
1942
|
+
drawCmd = polyPath([
|
|
1943
|
+
[pad, pad],
|
|
1944
|
+
[pad + w, pad],
|
|
1945
|
+
[cx + neckW / 2, pad + h - neckH],
|
|
1946
|
+
[cx + neckW / 2, pad + h],
|
|
1947
|
+
[cx - neckW / 2, pad + h],
|
|
1948
|
+
[cx - neckW / 2, pad + h - neckH],
|
|
1949
|
+
]);
|
|
1950
|
+
}
|
|
1951
|
+
else if (preset === "leftBrace") {
|
|
1952
|
+
// Left brace { — approximated as connected curves
|
|
1953
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 8333;
|
|
1954
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1955
|
+
const curveR = h * adj1 / 100000;
|
|
1956
|
+
const midY = h * adj2 / 100000;
|
|
1957
|
+
const lw = Math.max(w * 0.15, 1);
|
|
1958
|
+
const k = 0.5522847498;
|
|
1959
|
+
const rightX = pad + w, leftX = pad + w - lw;
|
|
1960
|
+
drawCmd = [
|
|
1961
|
+
`${rightX} ${py(pad)} m`,
|
|
1962
|
+
`${rightX - curveR * k} ${py(pad)} ${pad + w / 2 + lw / 2} ${py(pad + curveR * (1 - k))} ${pad + w / 2 + lw / 2} ${py(pad + curveR)} c`,
|
|
1963
|
+
`${pad + w / 2 + lw / 2} ${py(pad + midY - curveR)} l`,
|
|
1964
|
+
`${pad + w / 2 + lw / 2} ${py(pad + midY - curveR * (1 - k))} ${pad + lw / 2 + curveR * k} ${py(pad + midY)} ${pad + lw / 2} ${py(pad + midY)} c`,
|
|
1965
|
+
`${pad + lw / 2 + curveR * k} ${py(pad + midY)} ${pad + w / 2 + lw / 2} ${py(pad + midY + curveR * (1 - k))} ${pad + w / 2 + lw / 2} ${py(pad + midY + curveR)} c`,
|
|
1966
|
+
`${pad + w / 2 + lw / 2} ${py(pad + h - curveR)} l`,
|
|
1967
|
+
`${pad + w / 2 + lw / 2} ${py(pad + h - curveR * (1 - k))} ${rightX - curveR * k} ${py(pad + h)} ${rightX} ${py(pad + h)} c`,
|
|
1968
|
+
// Inner path going back
|
|
1969
|
+
`${leftX} ${py(pad + h)} l`,
|
|
1970
|
+
`${leftX - curveR * k} ${py(pad + h)} ${pad + w / 2 - lw / 2} ${py(pad + h - curveR * (1 - k))} ${pad + w / 2 - lw / 2} ${py(pad + h - curveR)} c`,
|
|
1971
|
+
`${pad + w / 2 - lw / 2} ${py(pad + midY + curveR)} l`,
|
|
1972
|
+
`${pad + w / 2 - lw / 2} ${py(pad + midY + curveR * (1 - k))} ${pad - lw / 2 + curveR * k} ${py(pad + midY)} ${pad - lw / 2} ${py(pad + midY)} c`,
|
|
1973
|
+
`${pad - lw / 2 + curveR * k} ${py(pad + midY)} ${pad + w / 2 - lw / 2} ${py(pad + midY - curveR * (1 - k))} ${pad + w / 2 - lw / 2} ${py(pad + midY - curveR)} c`,
|
|
1974
|
+
`${pad + w / 2 - lw / 2} ${py(pad + curveR)} l`,
|
|
1975
|
+
`${pad + w / 2 - lw / 2} ${py(pad + curveR * (1 - k))} ${leftX - curveR * k} ${py(pad)} ${leftX} ${py(pad)} c`,
|
|
1976
|
+
"f",
|
|
1977
|
+
].join("\n");
|
|
1978
|
+
}
|
|
1979
|
+
else if (preset === "rightBrace") {
|
|
1980
|
+
// Right brace } — mirror of leftBrace
|
|
1981
|
+
const adj1 = adjustValues?.adj1 ? parseAdjustValue(adjustValues.adj1) : 8333;
|
|
1982
|
+
const adj2 = adjustValues?.adj2 ? parseAdjustValue(adjustValues.adj2) : 50000;
|
|
1983
|
+
const curveR = h * adj1 / 100000;
|
|
1984
|
+
const midY = h * adj2 / 100000;
|
|
1985
|
+
const lw = Math.max(w * 0.15, 1);
|
|
1986
|
+
const k = 0.5522847498;
|
|
1987
|
+
drawCmd = [
|
|
1988
|
+
`${pad} ${py(pad)} m`,
|
|
1989
|
+
`${pad + curveR * k} ${py(pad)} ${pad + w / 2 - lw / 2} ${py(pad + curveR * (1 - k))} ${pad + w / 2 - lw / 2} ${py(pad + curveR)} c`,
|
|
1990
|
+
`${pad + w / 2 - lw / 2} ${py(pad + midY - curveR)} l`,
|
|
1991
|
+
`${pad + w / 2 - lw / 2} ${py(pad + midY - curveR * (1 - k))} ${pad + w - lw / 2 - curveR * k} ${py(pad + midY)} ${pad + w - lw / 2} ${py(pad + midY)} c`,
|
|
1992
|
+
`${pad + w - lw / 2 - curveR * k} ${py(pad + midY)} ${pad + w / 2 - lw / 2} ${py(pad + midY + curveR * (1 - k))} ${pad + w / 2 - lw / 2} ${py(pad + midY + curveR)} c`,
|
|
1993
|
+
`${pad + w / 2 - lw / 2} ${py(pad + h - curveR)} l`,
|
|
1994
|
+
`${pad + w / 2 - lw / 2} ${py(pad + h - curveR * (1 - k))} ${pad + curveR * k} ${py(pad + h)} ${pad} ${py(pad + h)} c`,
|
|
1995
|
+
`${pad + lw} ${py(pad + h)} l`,
|
|
1996
|
+
`${pad + lw + curveR * k} ${py(pad + h)} ${pad + w / 2 + lw / 2} ${py(pad + h - curveR * (1 - k))} ${pad + w / 2 + lw / 2} ${py(pad + h - curveR)} c`,
|
|
1997
|
+
`${pad + w / 2 + lw / 2} ${py(pad + midY + curveR)} l`,
|
|
1998
|
+
`${pad + w / 2 + lw / 2} ${py(pad + midY + curveR * (1 - k))} ${pad + w + lw / 2 - curveR * k} ${py(pad + midY)} ${pad + w + lw / 2} ${py(pad + midY)} c`,
|
|
1999
|
+
`${pad + w + lw / 2 - curveR * k} ${py(pad + midY)} ${pad + w / 2 + lw / 2} ${py(pad + midY - curveR * (1 - k))} ${pad + w / 2 + lw / 2} ${py(pad + midY - curveR)} c`,
|
|
2000
|
+
`${pad + w / 2 + lw / 2} ${py(pad + curveR)} l`,
|
|
2001
|
+
`${pad + w / 2 + lw / 2} ${py(pad + curveR * (1 - k))} ${pad + lw + curveR * k} ${py(pad)} ${pad + lw} ${py(pad)} c`,
|
|
2002
|
+
"f",
|
|
2003
|
+
].join("\n");
|
|
2004
|
+
}
|
|
2005
|
+
else if (preset === "leftBracket") {
|
|
2006
|
+
// Left bracket [ — vertical line with horizontal caps
|
|
2007
|
+
const lw = Math.max(w * 0.3, 1);
|
|
2008
|
+
const k = 0.5522847498;
|
|
2009
|
+
const curveR = Math.min(w, h * 0.1);
|
|
2010
|
+
drawCmd = [
|
|
2011
|
+
`${pad + w} ${py(pad)} m`,
|
|
2012
|
+
`${pad + curveR} ${py(pad)} l`,
|
|
2013
|
+
`${pad + curveR * (1 - k)} ${py(pad)} ${pad} ${py(pad + curveR * (1 - k))} ${pad} ${py(pad + curveR)} c`,
|
|
2014
|
+
`${pad} ${py(pad + h - curveR)} l`,
|
|
2015
|
+
`${pad} ${py(pad + h - curveR * (1 - k))} ${pad + curveR * (1 - k)} ${py(pad + h)} ${pad + curveR} ${py(pad + h)} c`,
|
|
2016
|
+
`${pad + w} ${py(pad + h)} l`,
|
|
2017
|
+
`${pad + w} ${py(pad + h - lw)} l`,
|
|
2018
|
+
`${pad + lw} ${py(pad + h - lw)} l`,
|
|
2019
|
+
`${pad + lw} ${py(pad + lw)} l`,
|
|
2020
|
+
`${pad + w} ${py(pad + lw)} l`,
|
|
2021
|
+
"f",
|
|
2022
|
+
].join("\n");
|
|
2023
|
+
}
|
|
2024
|
+
else if (preset === "rightBracket") {
|
|
2025
|
+
// Right bracket ]
|
|
2026
|
+
const lw = Math.max(w * 0.3, 1);
|
|
2027
|
+
const k = 0.5522847498;
|
|
2028
|
+
const curveR = Math.min(w, h * 0.1);
|
|
2029
|
+
drawCmd = [
|
|
2030
|
+
`${pad} ${py(pad)} m`,
|
|
2031
|
+
`${pad + w - curveR} ${py(pad)} l`,
|
|
2032
|
+
`${pad + w - curveR * (1 - k)} ${py(pad)} ${pad + w} ${py(pad + curveR * (1 - k))} ${pad + w} ${py(pad + curveR)} c`,
|
|
2033
|
+
`${pad + w} ${py(pad + h - curveR)} l`,
|
|
2034
|
+
`${pad + w} ${py(pad + h - curveR * (1 - k))} ${pad + w - curveR * (1 - k)} ${py(pad + h)} ${pad + w - curveR} ${py(pad + h)} c`,
|
|
2035
|
+
`${pad} ${py(pad + h)} l`,
|
|
2036
|
+
`${pad} ${py(pad + h - lw)} l`,
|
|
2037
|
+
`${pad + w - lw} ${py(pad + h - lw)} l`,
|
|
2038
|
+
`${pad + w - lw} ${py(pad + lw)} l`,
|
|
2039
|
+
`${pad} ${py(pad + lw)} l`,
|
|
2040
|
+
"f",
|
|
2041
|
+
].join("\n");
|
|
2042
|
+
}
|
|
2043
|
+
else if (preset === "bracePair") {
|
|
2044
|
+
// Pair of braces { } — left and right brace together
|
|
2045
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 8333;
|
|
2046
|
+
const curveR = Math.min(w, h) * adj / 100000;
|
|
2047
|
+
const k = 0.5522847498;
|
|
2048
|
+
const midY = pad + h / 2;
|
|
2049
|
+
// Simplified: render as rounded rectangle with points at middle-left and middle-right
|
|
2050
|
+
drawCmd = [
|
|
2051
|
+
`${pad + curveR} ${py(pad)} m`,
|
|
2052
|
+
`${pad + w - curveR} ${py(pad)} l`,
|
|
2053
|
+
`${pad + w - curveR * (1 - k)} ${py(pad)} ${pad + w} ${py(pad + curveR * (1 - k))} ${pad + w} ${py(pad + curveR)} c`,
|
|
2054
|
+
`${pad + w} ${py(midY - curveR)} l`,
|
|
2055
|
+
`${pad + w} ${py(midY)} l`, // Right point
|
|
2056
|
+
`${pad + w} ${py(midY + curveR)} l`,
|
|
2057
|
+
`${pad + w} ${py(pad + h - curveR)} l`,
|
|
2058
|
+
`${pad + w} ${py(pad + h - curveR * (1 - k))} ${pad + w - curveR * (1 - k)} ${py(pad + h)} ${pad + w - curveR} ${py(pad + h)} c`,
|
|
2059
|
+
`${pad + curveR} ${py(pad + h)} l`,
|
|
2060
|
+
`${pad + curveR * (1 - k)} ${py(pad + h)} ${pad} ${py(pad + h - curveR * (1 - k))} ${pad} ${py(pad + h - curveR)} c`,
|
|
2061
|
+
`${pad} ${py(midY + curveR)} l`,
|
|
2062
|
+
`${pad} ${py(midY)} l`, // Left point
|
|
2063
|
+
`${pad} ${py(midY - curveR)} l`,
|
|
2064
|
+
`${pad} ${py(pad + curveR)} l`,
|
|
2065
|
+
`${pad} ${py(pad + curveR * (1 - k))} ${pad + curveR * (1 - k)} ${py(pad)} ${pad + curveR} ${py(pad)} c`,
|
|
2066
|
+
"f",
|
|
2067
|
+
].join("\n");
|
|
2068
|
+
}
|
|
2069
|
+
else if (preset === "bracketPair") {
|
|
2070
|
+
// Pair of brackets [ ] — rounded rectangle
|
|
2071
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 16667;
|
|
2072
|
+
const radius = Math.min(w, h) * adj / 100000;
|
|
2073
|
+
const x0 = pad, y0 = totalH - pad - h, x1 = pad + w, y1 = totalH - pad;
|
|
2074
|
+
drawCmd = [
|
|
2075
|
+
`${x0 + radius} ${y0} m`, `${x1 - radius} ${y0} l`,
|
|
2076
|
+
`${x1} ${y0} ${x1} ${y0 + radius} v`, `${x1} ${y1 - radius} l`,
|
|
2077
|
+
`${x1} ${y1} ${x1 - radius} ${y1} v`, `${x0 + radius} ${y1} l`,
|
|
2078
|
+
`${x0} ${y1} ${x0} ${y1 - radius} v`, `${x0} ${y0 + radius} l`,
|
|
2079
|
+
`${x0} ${y0} ${x0 + radius} ${y0} v`, "f",
|
|
2080
|
+
].join("\n");
|
|
2081
|
+
}
|
|
2082
|
+
else {
|
|
2083
|
+
// Fallback: filled rectangle
|
|
2084
|
+
drawCmd = `${pad} ${pad} ${w} ${h} re f`;
|
|
2085
|
+
}
|
|
2086
|
+
return drawCmd;
|
|
2087
|
+
}
|
|
2088
|
+
/** Wrap a PDF content stream into a minimal PDF 1.4 document. */
|
|
2089
|
+
export function buildPdf(totalW, totalH, stream) {
|
|
2090
|
+
const streamBytes = Buffer.from(stream, "ascii");
|
|
2091
|
+
const objects = [
|
|
2092
|
+
`1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj`,
|
|
2093
|
+
`2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj`,
|
|
2094
|
+
`3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${totalW} ${totalH}] /Contents 4 0 R >>\nendobj`,
|
|
2095
|
+
`4 0 obj\n<< /Length ${streamBytes.length} >>\nstream\n${stream}\nendstream\nendobj`,
|
|
2096
|
+
];
|
|
2097
|
+
const header = "%PDF-1.4\n";
|
|
2098
|
+
let body = "";
|
|
2099
|
+
const offsets = [];
|
|
2100
|
+
for (const obj of objects) {
|
|
2101
|
+
offsets.push(header.length + body.length);
|
|
2102
|
+
body += obj + "\n";
|
|
2103
|
+
}
|
|
2104
|
+
const xrefOffset = header.length + body.length;
|
|
2105
|
+
let xref = `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
|
|
2106
|
+
for (const off of offsets) {
|
|
2107
|
+
xref += `${String(off).padStart(10, "0")} 00000 n \n`;
|
|
2108
|
+
}
|
|
2109
|
+
const trailer = `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF`;
|
|
2110
|
+
return Buffer.from(header + body + xref + trailer, "ascii");
|
|
2111
|
+
}
|
|
2112
|
+
export function mapConnector(conn, attachments, ctx) {
|
|
2113
|
+
const b = conn.bounds;
|
|
2114
|
+
if (!b)
|
|
2115
|
+
return "";
|
|
2116
|
+
const x = emuToPx(b.x);
|
|
2117
|
+
const y = emuToPx(b.y);
|
|
2118
|
+
let w = emuToPx(b.cx);
|
|
2119
|
+
let h = emuToPx(b.cy);
|
|
2120
|
+
// OfficeImport accounts for stroke when computing connector PDF bounds:
|
|
2121
|
+
// - Y position is offset by floor(strokeWidth/2) for the stroke extending above origin
|
|
2122
|
+
// - Zero-extent dimensions get minimum 1px (for the stroke), with the other axis reduced by 1
|
|
2123
|
+
const strokePx = emuToPx(conn.stroke?.width ?? 0);
|
|
2124
|
+
const strokeYOffset = Math.floor(strokePx / 2);
|
|
2125
|
+
if (h === 0 && w > 0) {
|
|
2126
|
+
h = 1;
|
|
2127
|
+
w -= 1;
|
|
2128
|
+
}
|
|
2129
|
+
else if (w === 0 && h > 0) {
|
|
2130
|
+
w = 1;
|
|
2131
|
+
h -= 1;
|
|
2132
|
+
}
|
|
2133
|
+
// Connectors are always rendered as PDF images by OfficeImport (like non-rect shapes)
|
|
2134
|
+
const fillColor = fillToColor(conn.fill, ctx) ?? "#000000";
|
|
2135
|
+
const geom = conn.geometry?.preset ?? "line";
|
|
2136
|
+
const pdf = generateShapePdf(w, h, geom, fillColor, conn.geometry?.adjustValues);
|
|
2137
|
+
const imgX = x - PDF_PADDING;
|
|
2138
|
+
const imgY = y - strokeYOffset - PDF_PADDING;
|
|
2139
|
+
const imgW = w + PDF_PADDING * 2;
|
|
2140
|
+
const imgH = h + PDF_PADDING * 2;
|
|
2141
|
+
const idx = nextAttachmentIndex();
|
|
2142
|
+
const name = `Attachment${idx}.pdf`;
|
|
2143
|
+
attachments.set(name, pdf);
|
|
2144
|
+
return `<img src="${name}" style="position:absolute; top:${imgY}; left:${imgX}; width:${imgW}; height:${imgH};">`;
|
|
2145
|
+
}
|
|
2146
|
+
/**
|
|
2147
|
+
* Compute the inscribed text frame for a geometry preset (shapeTextBoxRect in OfficeImport).
|
|
2148
|
+
* OfficeImport computes inset using float px values for radius, then truncates dimensions.
|
|
2149
|
+
* Input/output in EMU, but computation happens in pixel space to match QL's rounding.
|
|
2150
|
+
*/
|
|
2151
|
+
export function shapeTextBox(bounds, preset, adjustValues) {
|
|
2152
|
+
const { x, y, cx, cy } = bounds;
|
|
2153
|
+
const EMU = 12700;
|
|
2154
|
+
// Float px values (not truncated) — OfficeImport uses these for radius/inset calc
|
|
2155
|
+
const wF = cx / EMU;
|
|
2156
|
+
const hF = cy / EMU;
|
|
2157
|
+
// Truncated px values — OfficeImport uses these for final dimensions
|
|
2158
|
+
const wT = Math.trunc(wF);
|
|
2159
|
+
const hT = Math.trunc(hF);
|
|
2160
|
+
if (preset === "roundRect") {
|
|
2161
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 16667;
|
|
2162
|
+
const radius = Math.min(wF, hF) * adj / 100000;
|
|
2163
|
+
const inset = radius * (1 - Math.cos(Math.PI / 4));
|
|
2164
|
+
const rw = Math.trunc(wT - 2 * inset);
|
|
2165
|
+
const rh = Math.trunc(hT - 2 * inset);
|
|
2166
|
+
return { x: x + inset * EMU, y: y + inset * EMU, cx: rw * EMU, cy: rh * EMU };
|
|
2167
|
+
}
|
|
2168
|
+
if (preset === "ellipse") {
|
|
2169
|
+
const insetX = (wT - wT / Math.SQRT2) / 2;
|
|
2170
|
+
const insetY = (hT - hT / Math.SQRT2) / 2;
|
|
2171
|
+
const rw = Math.trunc(wT - 2 * insetX);
|
|
2172
|
+
const rh = Math.trunc(hT - 2 * insetY);
|
|
2173
|
+
return { x: x + insetX * EMU, y: y + insetY * EMU, cx: rw * EMU, cy: rh * EMU };
|
|
2174
|
+
}
|
|
2175
|
+
if (preset === "diamond") {
|
|
2176
|
+
return { x: x + cx / 4, y: y + cy / 4, cx: cx / 2, cy: cy / 2 };
|
|
2177
|
+
}
|
|
2178
|
+
if (preset === "triangle") {
|
|
2179
|
+
return { x: x + cx / 4, y: y + cy / 2, cx: cx / 2, cy: cy / 2 };
|
|
2180
|
+
}
|
|
2181
|
+
if (preset === "hexagon") {
|
|
2182
|
+
const adj = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : 25000;
|
|
2183
|
+
const insetX = cx * adj / 100000;
|
|
2184
|
+
const insetY = cy * adj / 200000;
|
|
2185
|
+
return { x: x + insetX, y: y + insetY, cx: cx - 2 * insetX, cy: cy - 2 * insetY };
|
|
2186
|
+
}
|
|
2187
|
+
// Star shapes: text frame inscribed within the inner polygon/circle.
|
|
2188
|
+
// Empirically verified against QL output for star4 and star5.
|
|
2189
|
+
const starMatch = preset.match(/^star(\d+)$/);
|
|
2190
|
+
if (starMatch) {
|
|
2191
|
+
const n = parseInt(starMatch[1], 10);
|
|
2192
|
+
const adjMap = { 4: 12500, 5: 19098, 6: 23170, 7: 19098, 8: 17678 };
|
|
2193
|
+
const adjVal = adjustValues?.adj ? parseAdjustValue(adjustValues.adj) : (adjMap[n] ?? 19098);
|
|
2194
|
+
const innerR = Math.min(wF, hF) / 2 * adjVal / 50000;
|
|
2195
|
+
if (n === 4) {
|
|
2196
|
+
// Star4: inner diamond → axis-aligned bbox of 4 inner points at 45° offsets
|
|
2197
|
+
const side = Math.trunc(innerR * Math.SQRT2 * 2);
|
|
2198
|
+
const insetX = Math.trunc(wF / 2 - innerR * Math.cos(Math.PI / 4));
|
|
2199
|
+
const insetY = Math.trunc(hF / 2 - innerR * Math.cos(Math.PI / 4));
|
|
2200
|
+
return { x: x + insetX * EMU, y: y + insetY * EMU, cx: side * EMU, cy: side * EMU };
|
|
2201
|
+
}
|
|
2202
|
+
if (n === 5) {
|
|
2203
|
+
// Star5: inscribed square using inner circle diameter.
|
|
2204
|
+
// textY_rel = textH (text starts at textH from top — empirically confirmed)
|
|
2205
|
+
const side = Math.trunc(2 * innerR);
|
|
2206
|
+
const insetX = Math.trunc(wF / 2 - innerR);
|
|
2207
|
+
return { x: x + insetX * EMU, y: y + side * EMU, cx: side * EMU, cy: side * EMU };
|
|
2208
|
+
}
|
|
2209
|
+
// Other star shapes: use inner circle diameter as inscribed square, centered
|
|
2210
|
+
const side = Math.trunc(2 * innerR);
|
|
2211
|
+
const insetX = Math.trunc(wF / 2 - innerR);
|
|
2212
|
+
const insetY = Math.trunc(hF / 2 - innerR);
|
|
2213
|
+
return { x: x + insetX * EMU, y: y + insetY * EMU, cx: side * EMU, cy: side * EMU };
|
|
2214
|
+
}
|
|
2215
|
+
// Default: use full bounds
|
|
2216
|
+
return { x, y, cx, cy };
|
|
2217
|
+
}
|
|
2218
|
+
function parseAdjustValue(val) {
|
|
2219
|
+
// Adjust values come as "val 4000" — extract the number
|
|
2220
|
+
const m = val.match(/\d+/);
|
|
2221
|
+
return m ? parseInt(m[0], 10) : 16667;
|
|
2222
|
+
}
|
|
2223
|
+
/** Maps OOXML gradient angle (60000ths of a degree) to -webkit-gradient from/to points */
|
|
2224
|
+
function gradientPoints(angleDeg60k) {
|
|
2225
|
+
const a = ((angleDeg60k / 60000) % 360 + 360) % 360;
|
|
2226
|
+
if (a < 22.5 || a >= 337.5)
|
|
2227
|
+
return { from: "left top", to: "right top" };
|
|
2228
|
+
if (a < 67.5)
|
|
2229
|
+
return { from: "left bottom", to: "right top" };
|
|
2230
|
+
if (a < 112.5)
|
|
2231
|
+
return { from: "left bottom", to: "left top" };
|
|
2232
|
+
if (a < 157.5)
|
|
2233
|
+
return { from: "right bottom", to: "left top" };
|
|
2234
|
+
if (a < 202.5)
|
|
2235
|
+
return { from: "right top", to: "left top" };
|
|
2236
|
+
if (a < 247.5)
|
|
2237
|
+
return { from: "right top", to: "left bottom" };
|
|
2238
|
+
if (a < 292.5)
|
|
2239
|
+
return { from: "left top", to: "left bottom" };
|
|
2240
|
+
return { from: "left top", to: "right bottom" };
|
|
2241
|
+
}
|
|
2242
|
+
/** Returns CSS property string (e.g. "background-color:#hex" or "background-image:-webkit-gradient(...)") */
|
|
2243
|
+
function fillToCss(fill, ctx, attachments) {
|
|
2244
|
+
if (!fill)
|
|
2245
|
+
return null;
|
|
2246
|
+
if (fill.type === "solid") {
|
|
2247
|
+
return `background-color:${rgbaToColor(resolveColor(fill.color, ctx.colorMap, ctx.colorScheme))}`;
|
|
2248
|
+
}
|
|
2249
|
+
if (fill.type === "gradient" && fill.stops.length > 0) {
|
|
2250
|
+
if (fill.stops.length === 2) {
|
|
2251
|
+
// 2-stop: -webkit-gradient (matches OfficeImport's old WebKit syntax)
|
|
2252
|
+
const c1 = rgbaToColor(resolveColor(fill.stops[0].color, ctx.colorMap, ctx.colorScheme));
|
|
2253
|
+
const c2 = rgbaToColor(resolveColor(fill.stops[1].color, ctx.colorMap, ctx.colorScheme));
|
|
2254
|
+
const { from, to } = gradientPoints(fill.linear?.angle ?? 5400000);
|
|
2255
|
+
return `background-image:-webkit-gradient(linear, ${from}, ${to}, from(${c1}), to(${c2}))`;
|
|
2256
|
+
}
|
|
2257
|
+
// 3+ stops: average all stop colors to single background-color
|
|
2258
|
+
let r = 0, g = 0, b = 0;
|
|
2259
|
+
for (const stop of fill.stops) {
|
|
2260
|
+
const c = resolveColor(stop.color, ctx.colorMap, ctx.colorScheme);
|
|
2261
|
+
r += c.r;
|
|
2262
|
+
g += c.g;
|
|
2263
|
+
b += c.b;
|
|
2264
|
+
}
|
|
2265
|
+
const n = fill.stops.length;
|
|
2266
|
+
return `background-color:${rgbaToColor({ r: Math.round(r / n), g: Math.round(g / n), b: Math.round(b / n), a: 1 })}`;
|
|
2267
|
+
}
|
|
2268
|
+
if (fill.type === "image" && fill.blipData && attachments) {
|
|
2269
|
+
// Image fill: OfficeImport renders as background-image with background-size:100% 100%
|
|
2270
|
+
const ext = detectFillImageExt(fill.blipData);
|
|
2271
|
+
const idx = nextFillAttachmentIndex();
|
|
2272
|
+
const name = `Attachment${idx}.${ext}`;
|
|
2273
|
+
attachments.set(name, fill.blipData);
|
|
2274
|
+
return `background-image:url(${name}); background-size:100% 100%; background-repeat:no-repeat`;
|
|
2275
|
+
}
|
|
2276
|
+
if (fill.type === "noFill")
|
|
2277
|
+
return null;
|
|
2278
|
+
return null;
|
|
2279
|
+
}
|
|
2280
|
+
let fillAttachmentCounter = 1000; // offset to avoid collision with image attachments
|
|
2281
|
+
function nextFillAttachmentIndex() { return fillAttachmentCounter++; }
|
|
2282
|
+
function detectFillImageExt(buf) {
|
|
2283
|
+
if (buf[0] === 0x89 && buf[1] === 0x50)
|
|
2284
|
+
return "png";
|
|
2285
|
+
if (buf[0] === 0xFF && buf[1] === 0xD8)
|
|
2286
|
+
return "jpeg";
|
|
2287
|
+
if (buf[0] === 0x47 && buf[1] === 0x49)
|
|
2288
|
+
return "gif";
|
|
2289
|
+
return "png";
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Resolve stroke info for PDF rendering.
|
|
2293
|
+
* OfficeImport CMDrawingContext always uses fill+stroke (B operator).
|
|
2294
|
+
* Explicit <a:ln><a:solidFill> → declared color + width.
|
|
2295
|
+
* No explicit stroke → undefined (generateShapePdf uses thin black default).
|
|
2296
|
+
*/
|
|
2297
|
+
function resolveStrokeForPdf(shape, ctx) {
|
|
2298
|
+
const stroke = shape.stroke;
|
|
2299
|
+
if (!stroke?.fill)
|
|
2300
|
+
return undefined; // no explicit stroke → use default
|
|
2301
|
+
if (stroke.fill.type === "solid") {
|
|
2302
|
+
const color = rgbaToColor(resolveColor(stroke.fill.color, ctx.colorMap, ctx.colorScheme));
|
|
2303
|
+
const width = stroke.width ? emuToPx(stroke.width) : 1;
|
|
2304
|
+
return { color, width: Math.max(width, 0.5) };
|
|
2305
|
+
}
|
|
2306
|
+
return undefined;
|
|
2307
|
+
}
|
|
2308
|
+
/** Returns just the color string (for PDF fill, which only needs a single color) */
|
|
2309
|
+
export function fillToColor(fill, ctx) {
|
|
2310
|
+
if (!fill)
|
|
2311
|
+
return null;
|
|
2312
|
+
if (fill.type === "solid") {
|
|
2313
|
+
return rgbaToColor(resolveColor(fill.color, ctx.colorMap, ctx.colorScheme));
|
|
2314
|
+
}
|
|
2315
|
+
if (fill.type === "gradient" && fill.stops.length > 0) {
|
|
2316
|
+
return rgbaToColor(resolveColor(fill.stops[0].color, ctx.colorMap, ctx.colorScheme));
|
|
2317
|
+
}
|
|
2318
|
+
if (fill.type === "noFill")
|
|
2319
|
+
return null;
|
|
2320
|
+
return null;
|
|
2321
|
+
}
|
|
2322
|
+
/** Resolve stroke from theme style matrix via shapeStyle.lnRef. */
|
|
2323
|
+
function resolveThemeStroke(shape, ctx) {
|
|
2324
|
+
const lnRef = shape.shapeStyle?.lnRef;
|
|
2325
|
+
if (!lnRef || lnRef.idx <= 0 || !ctx.styleMatrix)
|
|
2326
|
+
return undefined;
|
|
2327
|
+
const themeStroke = ctx.styleMatrix.lineStyles[lnRef.idx - 1]; // 1-indexed
|
|
2328
|
+
if (!themeStroke)
|
|
2329
|
+
return undefined;
|
|
2330
|
+
// If theme stroke has a placeholder color (schemeClr phClr), use the lnRef override color
|
|
2331
|
+
if (lnRef.color && themeStroke.fill?.type === "solid") {
|
|
2332
|
+
return { ...themeStroke, fill: { type: "solid", color: lnRef.color } };
|
|
2333
|
+
}
|
|
2334
|
+
return themeStroke;
|
|
2335
|
+
}
|
|
2336
|
+
/** Compute normalized AABB (0..1) for geometry presets that don't fill the full bounding box. */
|
|
2337
|
+
function geometryAABB(preset) {
|
|
2338
|
+
const starMatch = preset.match(/^star(\d+)$/);
|
|
2339
|
+
if (starMatch) {
|
|
2340
|
+
const n = parseInt(starMatch[1], 10);
|
|
2341
|
+
const adjMap = { 4: 12500, 5: 19098, 6: 23170, 7: 19098, 8: 17678 };
|
|
2342
|
+
const adj = adjMap[n] ?? 19098;
|
|
2343
|
+
const outerR = 0.5;
|
|
2344
|
+
const innerR = outerR * adj / 50000;
|
|
2345
|
+
let minX = 1, maxX = 0, minY = 1, maxY = 0;
|
|
2346
|
+
for (let i = 0; i < n; i++) {
|
|
2347
|
+
const outerAngle = (-90 + i * (360 / n)) * Math.PI / 180;
|
|
2348
|
+
const ox = 0.5 + outerR * Math.cos(outerAngle);
|
|
2349
|
+
const oy = 0.5 + outerR * Math.sin(outerAngle);
|
|
2350
|
+
minX = Math.min(minX, ox);
|
|
2351
|
+
maxX = Math.max(maxX, ox);
|
|
2352
|
+
minY = Math.min(minY, oy);
|
|
2353
|
+
maxY = Math.max(maxY, oy);
|
|
2354
|
+
const innerAngle = (-90 + (360 / n / 2) + i * (360 / n)) * Math.PI / 180;
|
|
2355
|
+
const ix = 0.5 + innerR * Math.cos(innerAngle);
|
|
2356
|
+
const iy = 0.5 + innerR * Math.sin(innerAngle);
|
|
2357
|
+
minX = Math.min(minX, ix);
|
|
2358
|
+
maxX = Math.max(maxX, ix);
|
|
2359
|
+
minY = Math.min(minY, iy);
|
|
2360
|
+
maxY = Math.max(maxY, iy);
|
|
2361
|
+
}
|
|
2362
|
+
return { minX, minY, maxX, maxY };
|
|
2363
|
+
}
|
|
2364
|
+
return { minX: 0, minY: 0, maxX: 1, maxY: 1 };
|
|
2365
|
+
}
|
|
2366
|
+
function rgbaToColor(c) {
|
|
2367
|
+
if (c.a < 1)
|
|
2368
|
+
return `rgba(${c.r},${c.g},${c.b},${c.a.toFixed(2)})`;
|
|
2369
|
+
return `#${hex2(c.r)}${hex2(c.g)}${hex2(c.b)}`;
|
|
2370
|
+
}
|
|
2371
|
+
function hex2(n) {
|
|
2372
|
+
return n.toString(16).padStart(2, "0");
|
|
2373
|
+
}
|
|
2374
|
+
/** Map OOXML dash type to CSS border-style. OfficeImport maps these to simple CSS styles. */
|
|
2375
|
+
function dashToCssBorderStyle(dash) {
|
|
2376
|
+
if (!dash || dash === "solid")
|
|
2377
|
+
return "solid";
|
|
2378
|
+
if (dash === "dot" || dash === "sysDot")
|
|
2379
|
+
return "dotted";
|
|
2380
|
+
if (dash === "dash" || dash === "lgDash" || dash === "sysDash")
|
|
2381
|
+
return "dashed";
|
|
2382
|
+
// Compound dash patterns → dashed as closest CSS equivalent
|
|
2383
|
+
return "dashed";
|
|
2384
|
+
}
|