@mce/bigesj 0.18.4 → 0.19.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.
@@ -1 +1,2 @@
1
+ export declare function cachedFetchImageBitmap(url: string): Promise<ImageBitmap>;
1
2
  export declare function convertImageElementToUrl(el: Record<string, any>): Promise<string>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { definePlugin, useEditor } from "mce";
2
2
  import { onBeforeMount, onScopeDispose, ref } from "vue";
3
- import { clearUndef, idGenerator, isGradient, isGradientFill, normalizeCRLF, normalizeGradientFill } from "modern-idoc";
3
+ import { clearUndef, idGenerator, isGradient, isGradientFill, normalizeCRLF, normalizeGradientFill, normalizeNumber } from "modern-idoc";
4
4
  import { OoxmlNode, parsePresetShapeDefinition } from "modern-openxml";
5
5
  import { assets } from "modern-canvas";
6
6
  import { gunzipSync } from "fflate";
@@ -16,6 +16,17 @@ function levenshteinDistance(a, b) {
16
16
  }
17
17
  return matrix[b.length][a.length];
18
18
  }
19
+ var indexedFonts;
20
+ var idIndexMap = /* @__PURE__ */ new Map();
21
+ var nameIndexMap = /* @__PURE__ */ new Map();
22
+ var searchCache = /* @__PURE__ */ new Map();
23
+ function ensureFontIndex(fonts) {
24
+ if (indexedFonts === fonts) return;
25
+ indexedFonts = fonts;
26
+ idIndexMap = new Map(fonts.map((item, index) => [item.id, index]));
27
+ nameIndexMap = new Map(fonts.flatMap((item, index) => [...item.en_name.split(","), ...item.name.split(",")].map((name) => [name, index])));
28
+ searchCache.clear();
29
+ }
19
30
  function useFonts(editor) {
20
31
  const { http, loadFont: baseLoadFont } = editor ?? useEditor();
21
32
  async function loadBigeFonts(url, init = false) {
@@ -30,12 +41,8 @@ function useFonts(editor) {
30
41
  return result;
31
42
  }
32
43
  function searchBigeFont(keyword, fonts = bigeFonts.value) {
33
- const idIndexMap = new Map(fonts.map((item, index) => [item.id, index]));
34
- const nameIndexMap = new Map(fonts.flatMap((item, index) => {
35
- return [...item.en_name.split(","), ...item.name.split(",")].map((name) => {
36
- return [name, index];
37
- });
38
- }));
44
+ ensureFontIndex(fonts);
45
+ if (searchCache.has(keyword)) return searchCache.get(keyword);
39
46
  const fontFamilies = keyword.replace(/"/g, "").split(",");
40
47
  let index;
41
48
  fontFamilies.forEach((fontFamily) => {
@@ -50,6 +57,7 @@ function useFonts(editor) {
50
57
  else if (a.endsWith(" B")) a = `${a.substring(0, a.length - 2)}粗体`;
51
58
  const aLen = a.length;
52
59
  nameIndexMap.forEach((i, b) => {
60
+ if (Math.abs(aLen - b.length) >= aLen) return;
53
61
  const dist = levenshteinDistance(a, b);
54
62
  if (aLen <= dist) return;
55
63
  const weight = -(dist * .9 + (b.endsWith("常规") ? 0 : 1) * .1);
@@ -60,7 +68,9 @@ function useFonts(editor) {
60
68
  });
61
69
  });
62
70
  }
63
- return index !== void 0 ? fonts[index] : void 0;
71
+ const result = index !== void 0 ? fonts[index] : void 0;
72
+ searchCache.set(keyword, result);
73
+ return result;
64
74
  }
65
75
  async function loadFont(name) {
66
76
  const names = typeof name === "string" ? [name] : name;
@@ -1138,6 +1148,23 @@ function parseAnimations(el) {
1138
1148
  }
1139
1149
  //#endregion
1140
1150
  //#region src/convert/style.ts
1151
+ var NUMERIC_STYLE_KEYS = [
1152
+ "textIndent",
1153
+ "lineHeight",
1154
+ "letterSpacing",
1155
+ "wordSpacing",
1156
+ "fontSize",
1157
+ "textStrokeWidth",
1158
+ "borderRadius",
1159
+ "opacity",
1160
+ "rotate",
1161
+ "scaleX",
1162
+ "scaleY",
1163
+ "skewX",
1164
+ "skewY",
1165
+ "translateX",
1166
+ "translateY"
1167
+ ];
1141
1168
  function getStyle(el, clone = false) {
1142
1169
  let style = el.style ?? el;
1143
1170
  if (clone) {
@@ -1154,6 +1181,11 @@ function getStyle(el, clone = false) {
1154
1181
  delete style.backgroundUrl;
1155
1182
  delete style.elements;
1156
1183
  delete style.imageTransform;
1184
+ for (const key of NUMERIC_STYLE_KEYS) if (key in style) {
1185
+ const value = normalizeNumber(style[key]);
1186
+ if (value === void 0) delete style[key];
1187
+ else style[key] = value;
1188
+ }
1157
1189
  }
1158
1190
  return style;
1159
1191
  }
@@ -1626,7 +1658,7 @@ function signedArea(data, start, end, dim) {
1626
1658
  return sum;
1627
1659
  }
1628
1660
  //#endregion
1629
- //#region ../../node_modules/.pnpm/modern-path2d@1.6.0/node_modules/modern-path2d/dist/index.mjs
1661
+ //#region ../../node_modules/.pnpm/modern-path2d@1.7.0/node_modules/modern-path2d/dist/index.mjs
1630
1662
  function drawPoint(ctx, x, y, options = {}) {
1631
1663
  const { radius = 1 } = options;
1632
1664
  ctx.moveTo(x, y);
@@ -1732,7 +1764,7 @@ var Vector2 = class Vector2 {
1732
1764
  return this.set(this._x * x, this._y * y);
1733
1765
  }
1734
1766
  divide(x = 0, y = x) {
1735
- return this.set(this._x / x, this._y / y);
1767
+ return this.set(x === 0 ? this._x : this._x / x, y === 0 ? this._y : this._y / y);
1736
1768
  }
1737
1769
  cross(p) {
1738
1770
  return this._x * p.y - this._y * p.x;
@@ -1910,9 +1942,9 @@ function getIntersectionPoint(p1, p2, q1, q2) {
1910
1942
  const s = q2.clone().sub(q1);
1911
1943
  const q1p1 = q1.clone().sub(p1);
1912
1944
  const crossRS = r.cross(s);
1913
- if (crossRS === 0) return new Vector2((p1.x + q1.x) / 2, (p1.y + q1.y) / 2);
1945
+ if (crossRS === 0) return null;
1914
1946
  const t = q1p1.cross(s) / crossRS;
1915
- if (Math.abs(t) > 1) return new Vector2((p1.x + q1.x) / 2, (p1.y + q1.y) / 2);
1947
+ if (Math.abs(t) > 1) return null;
1916
1948
  return new Vector2(p1.x + t * r.x, p1.y + t * r.y);
1917
1949
  }
1918
1950
  var FUNCTIONS_RE = /([\w-]+)\((.+?)\)/g;
@@ -2118,7 +2150,7 @@ function recursive(points, x1, y1, x2, y2, x3, y3, distanceTolerance, level) {
2118
2150
  function cross(ax, ay, bx, by, cx, cy) {
2119
2151
  return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
2120
2152
  }
2121
- function windingNumber(px, py, polygon) {
2153
+ function windingNumber$1(px, py, polygon) {
2122
2154
  const polygonLen = polygon.length;
2123
2155
  let wn = 0;
2124
2156
  for (let i = 0, j = polygonLen - 2; i < polygonLen; j = i, i += 2) {
@@ -2137,11 +2169,18 @@ function distance(p1, p2) {
2137
2169
  const dy = p2[1] - p1[1];
2138
2170
  return Math.sqrt(dx * dx + dy * dy);
2139
2171
  }
2172
+ function aabbIntersects(a, b) {
2173
+ return a.minX <= b.maxX && a.maxX >= b.minX && a.minY <= b.maxY && a.maxY >= b.minY;
2174
+ }
2140
2175
  function nonzeroFillRule(paths) {
2141
2176
  const results = paths.map((_, i) => ({ index: i }));
2142
- const testPointsGroups = paths.map((path) => {
2177
+ const bboxes = [];
2178
+ const testPointsGroups = paths.map((path, pathIndex) => {
2143
2179
  const len = path.length;
2144
- if (!len) return [];
2180
+ if (!len) {
2181
+ bboxes[pathIndex] = null;
2182
+ return [];
2183
+ }
2145
2184
  let xMinYAuto = [Number.MAX_SAFE_INTEGER, 0];
2146
2185
  let xAutoYMin = [0, Number.MAX_SAFE_INTEGER];
2147
2186
  let xMaxYAuto = [Number.MIN_SAFE_INTEGER, 0];
@@ -2154,6 +2193,12 @@ function nonzeroFillRule(paths) {
2154
2193
  if (xMaxYAuto[0] < x) xMaxYAuto = [x, y];
2155
2194
  if (xAutoYMax[1] < y) xAutoYMax = [x, y];
2156
2195
  }
2196
+ bboxes[pathIndex] = {
2197
+ minX: xMinYAuto[0],
2198
+ minY: xAutoYMin[1],
2199
+ maxX: xMaxYAuto[0],
2200
+ maxY: xAutoYMax[1]
2201
+ };
2157
2202
  const mid = [(xMinYAuto[0] + xMaxYAuto[0]) / 2, (xAutoYMin[1] + xAutoYMax[1]) / 2];
2158
2203
  let xMidYMinDx;
2159
2204
  let xMidYMaxDx;
@@ -2199,13 +2244,16 @@ function nonzeroFillRule(paths) {
2199
2244
  for (let i = 0, len = paths.length; i < len; i++) {
2200
2245
  const _results = [];
2201
2246
  const testPoints = testPointsGroups[i];
2247
+ const boxI = bboxes[i];
2202
2248
  for (let j = 0; j < len; j++) {
2203
2249
  if (i === j) continue;
2250
+ const boxJ = bboxes[j];
2251
+ if (!boxI || !boxJ || !aabbIntersects(boxI, boxJ)) continue;
2204
2252
  const wnMap = {};
2205
2253
  const wnList = [];
2206
2254
  for (let p = 0, pLen = testPoints.length; p < pLen; p++) {
2207
2255
  const [x, y] = testPoints[p];
2208
- const winding = windingNumber(x, y, paths[j]);
2256
+ const winding = windingNumber$1(x, y, paths[j]);
2209
2257
  wnMap[winding] = (wnMap[winding] ?? 0) + 1;
2210
2258
  wnList.push(winding);
2211
2259
  }
@@ -2223,6 +2271,88 @@ function nonzeroFillRule(paths) {
2223
2271
  }
2224
2272
  return results;
2225
2273
  }
2274
+ function isLeft(ax, ay, bx, by, px, py) {
2275
+ return (bx - ax) * (py - ay) - (px - ax) * (by - ay);
2276
+ }
2277
+ function windingNumber(px, py, vertices) {
2278
+ const len = vertices.length;
2279
+ let wn = 0;
2280
+ for (let i = 0; i < len; i += 2) {
2281
+ const ax = vertices[i];
2282
+ const ay = vertices[i + 1];
2283
+ const k = (i + 2) % len;
2284
+ const bx = vertices[k];
2285
+ const by = vertices[k + 1];
2286
+ if (ay <= py) {
2287
+ if (by > py && isLeft(ax, ay, bx, by, px, py) > 0) wn++;
2288
+ } else if (by <= py && isLeft(ax, ay, bx, by, px, py) < 0) wn--;
2289
+ }
2290
+ return wn;
2291
+ }
2292
+ function crossingNumber(px, py, vertices) {
2293
+ const len = vertices.length;
2294
+ let cn = 0;
2295
+ for (let i = 0; i < len; i += 2) {
2296
+ const ax = vertices[i];
2297
+ const ay = vertices[i + 1];
2298
+ const k = (i + 2) % len;
2299
+ const bx = vertices[k];
2300
+ const by = vertices[k + 1];
2301
+ if (ay <= py && by > py || ay > py && by <= py) {
2302
+ if (px < ax + (py - ay) / (by - ay) * (bx - ax)) cn++;
2303
+ }
2304
+ }
2305
+ return cn;
2306
+ }
2307
+ function segmentDistance(px, py, ax, ay, bx, by) {
2308
+ const dx = bx - ax;
2309
+ const dy = by - ay;
2310
+ const lenSq = dx * dx + dy * dy;
2311
+ let t = lenSq === 0 ? 0 : ((px - ax) * dx + (py - ay) * dy) / lenSq;
2312
+ if (t < 0) t = 0;
2313
+ else if (t > 1) t = 1;
2314
+ const cx = ax + t * dx;
2315
+ const cy = ay + t * dy;
2316
+ return Math.hypot(px - cx, py - cy);
2317
+ }
2318
+ function pointInPolygon(point, vertices, fillRule = "nonzero") {
2319
+ if (vertices.length < 6) return false;
2320
+ if (fillRule === "evenodd") return (crossingNumber(point.x, point.y, vertices) & 1) === 1;
2321
+ return windingNumber(point.x, point.y, vertices) !== 0;
2322
+ }
2323
+ function pointInPolygons(point, polygons, fillRule = "nonzero") {
2324
+ const { x, y } = point;
2325
+ if (fillRule === "evenodd") {
2326
+ let cn = 0;
2327
+ for (let i = 0, len = polygons.length; i < len; i++) {
2328
+ const ring = polygons[i];
2329
+ if (ring.length >= 6) cn += crossingNumber(x, y, ring);
2330
+ }
2331
+ return (cn & 1) === 1;
2332
+ }
2333
+ let wn = 0;
2334
+ for (let i = 0, len = polygons.length; i < len; i++) {
2335
+ const ring = polygons[i];
2336
+ if (ring.length >= 6) wn += windingNumber(x, y, ring);
2337
+ }
2338
+ return wn !== 0;
2339
+ }
2340
+ function pointToPolylineDistance(point, vertices, closed = false) {
2341
+ const len = vertices.length;
2342
+ if (len < 2) return Infinity;
2343
+ const { x: px, y: py } = point;
2344
+ if (len === 2) return Math.hypot(px - vertices[0], py - vertices[1]);
2345
+ let min = Infinity;
2346
+ for (let i = 0; i < len - 2; i += 2) {
2347
+ const d = segmentDistance(px, py, vertices[i], vertices[i + 1], vertices[i + 2], vertices[i + 3]);
2348
+ if (d < min) min = d;
2349
+ }
2350
+ if (closed && len >= 6) {
2351
+ const d = segmentDistance(px, py, vertices[len - 2], vertices[len - 1], vertices[0], vertices[1]);
2352
+ if (d < min) min = d;
2353
+ }
2354
+ return min;
2355
+ }
2226
2356
  function quadraticBezierP0(t, p) {
2227
2357
  const k = 1 - t;
2228
2358
  return k * k * p;
@@ -3728,6 +3858,39 @@ function svgToPath2DSet(svg) {
3728
3858
  var Curve = class {
3729
3859
  arcLengthDivision = 200;
3730
3860
  _lengths = [];
3861
+ _adaptiveCache;
3862
+ /**
3863
+ * Parent composite, set lazily when a composite caches its children. Lets
3864
+ * {@link invalidate} propagate up so an ancestor's caches refresh too.
3865
+ */
3866
+ _owner;
3867
+ _invalidating = false;
3868
+ /**
3869
+ * Drop cached arc lengths and the cached sampled outline used by hit testing, then
3870
+ * bubble up to {@link _owner}. Called automatically by {@link applyTransform} and the
3871
+ * `Path2D` mutators; call it manually after mutating control-point coordinates in place —
3872
+ * the caches cannot observe such mutations.
3873
+ */
3874
+ invalidate() {
3875
+ if (this._invalidating) return this;
3876
+ this._invalidating = true;
3877
+ this._invalidateSelf();
3878
+ this._owner?.invalidate();
3879
+ this._invalidating = false;
3880
+ return this;
3881
+ }
3882
+ /** Clears this curve's own caches. Composites also clear their children (see override). */
3883
+ _invalidateSelf() {
3884
+ this._lengths.length = 0;
3885
+ this._adaptiveCache = void 0;
3886
+ }
3887
+ /**
3888
+ * Sampled outline cached for repeated hit tests (read-only — do not mutate the result).
3889
+ * Invalidated by {@link invalidate}.
3890
+ */
3891
+ _getCachedAdaptiveVertices() {
3892
+ return this._adaptiveCache ??= this.getAdaptiveVertices();
3893
+ }
3731
3894
  getPointAt(u, output = new Vector2()) {
3732
3895
  return this.getPoint(this.getUToTMapping(u), output);
3733
3896
  }
@@ -3743,6 +3906,7 @@ var Curve = class {
3743
3906
  if (isFunction) transform(p);
3744
3907
  else transform.apply(p, p);
3745
3908
  });
3909
+ this.invalidate();
3746
3910
  return this;
3747
3911
  }
3748
3912
  getUnevenVertices(count = 5, output = []) {
@@ -3869,12 +4033,21 @@ var Curve = class {
3869
4033
  return mid;
3870
4034
  }
3871
4035
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
3872
- const potins = this.getPoints();
3873
- for (let i = 0, len = potins.length; i < len; i++) {
3874
- const p = potins[i];
3875
- min.clampMin(p);
3876
- max.clampMax(p);
4036
+ const vertices = this.getAdaptiveVertices();
4037
+ let minX = min.x;
4038
+ let minY = min.y;
4039
+ let maxX = max.x;
4040
+ let maxY = max.y;
4041
+ for (let i = 0, len = vertices.length; i < len; i += 2) {
4042
+ const x = vertices[i];
4043
+ const y = vertices[i + 1];
4044
+ if (x < minX) minX = x;
4045
+ if (y < minY) minY = y;
4046
+ if (x > maxX) maxX = x;
4047
+ if (y > maxY) maxY = y;
3877
4048
  }
4049
+ min.set(minX, minY);
4050
+ max.set(maxX, maxY);
3878
4051
  return {
3879
4052
  min: min.finite(),
3880
4053
  max: max.finite()
@@ -3884,6 +4057,42 @@ var Curve = class {
3884
4057
  const { min, max } = this.getMinMax();
3885
4058
  return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
3886
4059
  }
4060
+ /**
4061
+ * Test whether a point lies inside the area enclosed by this curve.
4062
+ *
4063
+ * The curve is sampled via {@link getAdaptiveVertices} into a single implicitly closed
4064
+ * ring. This is purely geometric (it ignores any `fill`/`stroke` style), mirroring
4065
+ * `CanvasRenderingContext2D.isPointInPath`.
4066
+ *
4067
+ * Composites that hold multiple sub-paths (e.g. {@link Path2D}) override this so holes
4068
+ * are honored — a single `Curve` is always one ring.
4069
+ */
4070
+ isPointInFill(point, options = {}) {
4071
+ return pointInPolygon(point, this._getCachedAdaptiveVertices(), options.fillRule);
4072
+ }
4073
+ /**
4074
+ * Test whether a point lies on this curve's stroke, i.e. within `strokeWidth / 2 + tolerance`
4075
+ * of the sampled outline. The point must be in the same coordinate space as the curve.
4076
+ *
4077
+ * Options: `strokeWidth` (path units, default `1`), `tolerance` (extra hit slack in path
4078
+ * units, default `0` — useful for thin strokes; no coordinate scaling is assumed, so convert
4079
+ * pixel tolerance to path units upstream if your path is normalized), and `closed` (whether
4080
+ * to include the closing edge from the last vertex back to the first).
4081
+ */
4082
+ isPointInStroke(point, options = {}) {
4083
+ const { strokeWidth = 1, tolerance = 0, closed = false } = options;
4084
+ return pointToPolylineDistance(point, this._getCachedAdaptiveVertices(), closed) <= strokeWidth / 2 + tolerance;
4085
+ }
4086
+ /**
4087
+ * Concise PathKit-style fill containment test: `contains(x, y)` is shorthand for
4088
+ * {@link isPointInFill} with a `{ x, y }` point.
4089
+ */
4090
+ contains(x, y, options = {}) {
4091
+ return this.isPointInFill({
4092
+ x,
4093
+ y
4094
+ }, options);
4095
+ }
3887
4096
  getFillVertices(_options) {
3888
4097
  return this.getAdaptiveVertices();
3889
4098
  }
@@ -4013,6 +4222,64 @@ var RoundCurve = class extends Curve {
4013
4222
  }
4014
4223
  return output.set(_x, _y);
4015
4224
  }
4225
+ /**
4226
+ * Point on the ellipse at an absolute angle (mirrors {@link getPoint}'s parameterization,
4227
+ * ignoring `_diff`).
4228
+ */
4229
+ _pointAtAngle(angle, output) {
4230
+ let x = this.cx + this.rx * Math.cos(angle);
4231
+ let y = this.cy + this.ry * Math.sin(angle);
4232
+ if (this.rotate !== 0) {
4233
+ const cos = Math.cos(this.rotate);
4234
+ const sin = Math.sin(this.rotate);
4235
+ const tx = x - this.cx;
4236
+ const ty = y - this.cy;
4237
+ x = tx * cos - ty * sin + this.cx;
4238
+ y = tx * sin + ty * cos + this.cy;
4239
+ }
4240
+ return output.set(x, y);
4241
+ }
4242
+ /**
4243
+ * Analytical bounds of the (elliptical) arc: the start/end points plus the per-axis
4244
+ * extrema angles that fall within the swept interval. Matches {@link getPoint}, so it is
4245
+ * exact for `ArcCurve`/`EllipseCurve`. The `_diff` offset (used only by the legacy
4246
+ * `_getAdaptiveVerticesByCircle` path) is intentionally ignored here.
4247
+ */
4248
+ getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
4249
+ const { startAngle, rotate } = this;
4250
+ const delta = this._getDeltaAngle();
4251
+ const cosT = Math.cos(rotate);
4252
+ const sinT = Math.sin(rotate);
4253
+ const p = tempV2;
4254
+ let minX = min.x;
4255
+ let minY = min.y;
4256
+ let maxX = max.x;
4257
+ let maxY = max.y;
4258
+ const consider = (angle) => {
4259
+ this._pointAtAngle(angle, p);
4260
+ if (p.x < minX) minX = p.x;
4261
+ if (p.y < minY) minY = p.y;
4262
+ if (p.x > maxX) maxX = p.x;
4263
+ if (p.y > maxY) maxY = p.y;
4264
+ };
4265
+ consider(startAngle);
4266
+ consider(startAngle + delta);
4267
+ const ax = Math.atan2(-this.ry * sinT, this.rx * cosT);
4268
+ const ay = Math.atan2(this.ry * cosT, this.rx * sinT);
4269
+ const bases = [
4270
+ ax,
4271
+ ax + Math.PI,
4272
+ ay,
4273
+ ay + Math.PI
4274
+ ];
4275
+ for (let i = 0; i < 4; i++) if (angleInSweep(bases[i], startAngle, delta)) consider(bases[i]);
4276
+ min.set(minX, minY);
4277
+ max.set(maxX, maxY);
4278
+ return {
4279
+ min: min.finite(),
4280
+ max: max.finite()
4281
+ };
4282
+ }
4016
4283
  toCommands() {
4017
4284
  const { cx, cy, rx, ry, startAngle, endAngle, clockwise, rotate } = this;
4018
4285
  const startX = cx + rx * Math.cos(startAngle) * Math.cos(rotate) - ry * Math.sin(startAngle) * Math.sin(rotate);
@@ -4083,6 +4350,7 @@ var RoundCurve = class extends Curve {
4083
4350
  this.cy = tempV2.y;
4084
4351
  if (isTransformSkewed(transform)) transfEllipseGeneric(this, transform);
4085
4352
  else transfEllipseNoSkew(this, transform);
4353
+ this.invalidate();
4086
4354
  return this;
4087
4355
  }
4088
4356
  getControlPointRefs() {
@@ -4212,6 +4480,18 @@ var RoundCurve = class extends Curve {
4212
4480
  return this;
4213
4481
  }
4214
4482
  };
4483
+ function angleInSweep(a, start, delta) {
4484
+ const PI_2 = Math.PI * 2;
4485
+ const eps = 1e-9;
4486
+ if (Math.abs(delta) >= PI_2 - eps) return true;
4487
+ let off = (a - start) % PI_2;
4488
+ if (delta >= 0) {
4489
+ if (off < -1e-9) off += PI_2;
4490
+ return off >= -1e-9 && off <= delta + eps;
4491
+ }
4492
+ if (off > eps) off -= PI_2;
4493
+ return off <= eps && off >= delta - eps;
4494
+ }
4215
4495
  function transfEllipseGeneric(curve, m) {
4216
4496
  const a = curve.rx;
4217
4497
  const b = curve.ry;
@@ -4412,6 +4692,22 @@ var CompositeCurve = class CompositeCurve extends Curve {
4412
4692
  super();
4413
4693
  this.curves = curves;
4414
4694
  }
4695
+ _adaptiveCacheLen = -1;
4696
+ _invalidateSelf() {
4697
+ super._invalidateSelf();
4698
+ this._adaptiveCacheLen = -1;
4699
+ this.curves.forEach((curve) => curve.invalidate());
4700
+ }
4701
+ _getCachedAdaptiveVertices() {
4702
+ if (!this._adaptiveCache || this._adaptiveCacheLen !== this.curves.length) {
4703
+ this.curves.forEach((curve) => {
4704
+ curve._owner = this;
4705
+ });
4706
+ this._adaptiveCache = this.getAdaptiveVertices();
4707
+ this._adaptiveCacheLen = this.curves.length;
4708
+ }
4709
+ return this._adaptiveCache;
4710
+ }
4415
4711
  getFlatCurves() {
4416
4712
  return this.curves.flatMap((curve) => {
4417
4713
  if (curve instanceof CompositeCurve) return curve.getFlatCurves();
@@ -4446,6 +4742,7 @@ var CompositeCurve = class CompositeCurve extends Curve {
4446
4742
  updateLengths() {
4447
4743
  const lengths = [];
4448
4744
  for (let i = 0, sum = 0, len = this.curves.length; i < len; i++) {
4745
+ this.curves[i]._owner = this;
4449
4746
  sum += this.curves[i].getLength();
4450
4747
  lengths.push(sum);
4451
4748
  }
@@ -4499,7 +4796,10 @@ var CompositeCurve = class CompositeCurve extends Curve {
4499
4796
  }
4500
4797
  }
4501
4798
  applyTransform(transform) {
4799
+ this._invalidating = true;
4502
4800
  this.curves.forEach((curve) => curve.applyTransform(transform));
4801
+ this._invalidating = false;
4802
+ this.invalidate();
4503
4803
  return this;
4504
4804
  }
4505
4805
  getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
@@ -4756,7 +5056,7 @@ var RectangleCurve = class extends PolygonCurve {
4756
5056
  return this;
4757
5057
  }
4758
5058
  };
4759
- var RoundRectangleCurve = class extends RoundCurve {
5059
+ var RoundRectangleCurve = class extends CompositeCurve {
4760
5060
  constructor(x = 0, y = 0, width = 1, height = 1, radius = 1) {
4761
5061
  super();
4762
5062
  this.x = x;
@@ -4767,25 +5067,44 @@ var RoundRectangleCurve = class extends RoundCurve {
4767
5067
  this.update();
4768
5068
  }
4769
5069
  update() {
4770
- const { x, y, width, height, radius } = this;
4771
- const halfWidth = width / 2;
4772
- const halfHeight = height / 2;
4773
- const cx = x + halfWidth;
4774
- const cy = y + halfHeight;
4775
- const rx = Math.max(0, Math.min(radius, Math.min(halfWidth, halfHeight)));
4776
- const ry = rx;
4777
- this._center = new Vector2(cx, cy);
4778
- this._radius = new Vector2(rx, ry);
4779
- this._diff = new Vector2(halfWidth - rx, halfHeight - ry);
5070
+ const { x, y, width, height } = this;
5071
+ const r = Math.max(0, Math.min(this.radius, Math.abs(width) / 2, Math.abs(height) / 2));
5072
+ const x0 = x;
5073
+ const x1 = x + r;
5074
+ const x2 = x + width - r;
5075
+ const x3 = x + width;
5076
+ const y0 = y;
5077
+ const y1 = y + r;
5078
+ const y2 = y + height - r;
5079
+ const y3 = y + height;
5080
+ if (r <= 0) this.curves = [
5081
+ LineCurve.from(x0, y0, x3, y0),
5082
+ LineCurve.from(x3, y0, x3, y3),
5083
+ LineCurve.from(x3, y3, x0, y3),
5084
+ LineCurve.from(x0, y3, x0, y0)
5085
+ ];
5086
+ else {
5087
+ const HALF_PI = Math.PI / 2;
5088
+ this.curves = [
5089
+ LineCurve.from(x1, y0, x2, y0),
5090
+ new ArcCurve(x2, y1, r, -HALF_PI, 0, true),
5091
+ LineCurve.from(x3, y1, x3, y2),
5092
+ new ArcCurve(x2, y2, r, 0, HALF_PI, true),
5093
+ LineCurve.from(x2, y3, x1, y3),
5094
+ new ArcCurve(x1, y2, r, HALF_PI, Math.PI, true),
5095
+ LineCurve.from(x0, y2, x0, y1),
5096
+ new ArcCurve(x1, y1, r, Math.PI, Math.PI * 1.5, true)
5097
+ ];
5098
+ }
5099
+ this.invalidate();
4780
5100
  return this;
4781
5101
  }
4782
5102
  drawTo(ctx) {
4783
- const { x, y, width, height, radius } = this;
4784
- ctx.roundRect(x, y, width, height, radius);
5103
+ ctx.roundRect(this.x, this.y, this.width, this.height, this.radius);
4785
5104
  return this;
4786
5105
  }
4787
5106
  copyFrom(source) {
4788
- super.copyFrom(source);
5107
+ this.arcLengthDivision = source.arcLengthDivision;
4789
5108
  this.x = source.x;
4790
5109
  this.y = source.y;
4791
5110
  this.width = source.width;
@@ -4862,6 +5181,16 @@ var CurvePath = class extends CompositeCurve {
4862
5181
  getFillVertices(options) {
4863
5182
  return this._closeVertices(super.getFillVertices(options));
4864
5183
  }
5184
+ /**
5185
+ * Same as {@link Curve.isPointInStroke}, but `closed` defaults to this sub-path's actual
5186
+ * closed-ness: explicitly `autoClose`, or geometrically closed (first vertex === last).
5187
+ */
5188
+ isPointInStroke(point, options = {}) {
5189
+ const { strokeWidth = 1, tolerance = 0 } = options;
5190
+ const vertices = this._getCachedAdaptiveVertices();
5191
+ const len = vertices.length;
5192
+ return pointToPolylineDistance(point, vertices, options.closed ?? (this.autoClose || len >= 6 && vertices[0] === vertices[len - 2] && vertices[1] === vertices[len - 1])) <= strokeWidth / 2 + tolerance;
5193
+ }
4865
5194
  _setCurrentPoint(point) {
4866
5195
  this.currentPoint = new Vector2(point.x, point.y);
4867
5196
  if (!this.startPoint) this.startPoint = this.currentPoint.clone();
@@ -4999,6 +5328,8 @@ var CurvePath = class extends CompositeCurve {
4999
5328
  };
5000
5329
  var Path2D = class Path2D extends CompositeCurve {
5001
5330
  _meta;
5331
+ _ringsCache;
5332
+ _ringsCacheLen = -1;
5002
5333
  currentCurve = new CurvePath();
5003
5334
  style;
5004
5335
  get startPoint() {
@@ -5110,6 +5441,7 @@ var Path2D = class Path2D extends CompositeCurve {
5110
5441
  this.getControlPointRefs().forEach((point) => {
5111
5442
  point.scale(sx, sy, target);
5112
5443
  });
5444
+ this.invalidate();
5113
5445
  return this;
5114
5446
  }
5115
5447
  skew(ax, ay = 0, target = {
@@ -5119,6 +5451,7 @@ var Path2D = class Path2D extends CompositeCurve {
5119
5451
  this.getControlPointRefs().forEach((point) => {
5120
5452
  point.skew(ax, ay, target);
5121
5453
  });
5454
+ this.invalidate();
5122
5455
  return this;
5123
5456
  }
5124
5457
  rotate(rad, target = {
@@ -5128,6 +5461,7 @@ var Path2D = class Path2D extends CompositeCurve {
5128
5461
  this.getControlPointRefs().forEach((point) => {
5129
5462
  point.rotate(rad, target);
5130
5463
  });
5464
+ this.invalidate();
5131
5465
  return this;
5132
5466
  }
5133
5467
  bold(b) {
@@ -5173,47 +5507,67 @@ var Path2D = class Path2D extends CompositeCurve {
5173
5507
  }
5174
5508
  });
5175
5509
  });
5510
+ this.invalidate();
5176
5511
  return this;
5177
5512
  }
5513
+ /**
5514
+ * Test whether a point lies inside the filled area of this path.
5515
+ *
5516
+ * Each sub-path ({@link CurvePath}) is sampled into its own ring and all rings are
5517
+ * evaluated together via {@link pointInPolygons}, so holes (donut / hollow shapes) are
5518
+ * honored. This is purely geometric and ignores `style.fill` — for the `fill: 'none'`
5519
+ * fallback, gate the call upstream (see {@link Path2DSet.hitTest}).
5520
+ *
5521
+ * Defaults `fillRule` to `style.fillRule`, then `'nonzero'` (matching SVG/Canvas).
5522
+ */
5523
+ _invalidateSelf() {
5524
+ super._invalidateSelf();
5525
+ this._ringsCache = void 0;
5526
+ this._ringsCacheLen = -1;
5527
+ }
5528
+ /** Per-sub-path sampled rings, cached for repeated hit tests. */
5529
+ _getRings() {
5530
+ if (!this._ringsCache || this._ringsCacheLen !== this.curves.length) {
5531
+ this._ringsCache = this.curves.map((curve) => {
5532
+ curve._owner = this;
5533
+ return curve.getAdaptiveVertices();
5534
+ });
5535
+ this._ringsCacheLen = this.curves.length;
5536
+ }
5537
+ return this._ringsCache;
5538
+ }
5539
+ isPointInFill(point, options = {}) {
5540
+ const fillRule = options.fillRule ?? this.style.fillRule ?? "nonzero";
5541
+ return pointInPolygons(point, this._getRings(), fillRule);
5542
+ }
5543
+ /**
5544
+ * Test whether a point lies on this path's stroke. A hit on any sub-path counts.
5545
+ *
5546
+ * Defaults `strokeWidth` to this path's own {@link strokeWidth} (which is `0` when
5547
+ * `style.stroke` is `'none'`). Each sub-path infers its own closed-ness unless `closed`
5548
+ * is given explicitly.
5549
+ */
5550
+ isPointInStroke(point, options = {}) {
5551
+ const strokeWidth = options.strokeWidth ?? this.strokeWidth;
5552
+ const { tolerance = 0, closed } = options;
5553
+ return this.curves.some((curve) => curve.isPointInStroke(point, {
5554
+ strokeWidth,
5555
+ tolerance,
5556
+ closed
5557
+ }));
5558
+ }
5178
5559
  getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
5179
- const strokeWidth = this.strokeWidth;
5180
5560
  this.curves.forEach((curve) => {
5181
5561
  curve.getMinMax(min, max);
5182
- if (withStyle) {
5183
- if (strokeWidth > 1) {
5184
- const halfStrokeWidth = strokeWidth / 2;
5185
- const isClockwise = curve.isClockwise();
5186
- const points = [];
5187
- for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivision) {
5188
- const point = curve.getPoint(t);
5189
- const normal = curve.getNormal(t);
5190
- const dist1 = normal.clone().scale(isClockwise ? halfStrokeWidth : -halfStrokeWidth);
5191
- const dist2 = normal.clone().scale(isClockwise ? -halfStrokeWidth : halfStrokeWidth);
5192
- points.push(point.clone().add(dist1), point.clone().add(dist2), point.clone().add({
5193
- x: halfStrokeWidth,
5194
- y: 0
5195
- }), point.clone().add({
5196
- x: -halfStrokeWidth,
5197
- y: 0
5198
- }), point.clone().add({
5199
- x: 0,
5200
- y: halfStrokeWidth
5201
- }), point.clone().add({
5202
- x: 0,
5203
- y: -halfStrokeWidth
5204
- }), point.clone().add({
5205
- x: halfStrokeWidth,
5206
- y: halfStrokeWidth
5207
- }), point.clone().add({
5208
- x: -halfStrokeWidth,
5209
- y: -halfStrokeWidth
5210
- }));
5211
- }
5212
- min.clampMin(...points);
5213
- max.clampMax(...points);
5214
- }
5215
- }
5216
5562
  });
5563
+ if (withStyle) {
5564
+ const strokeWidth = this.strokeWidth;
5565
+ if (strokeWidth > 1 && Number.isFinite(min.x)) {
5566
+ const half = strokeWidth / 2;
5567
+ min.set(min.x - half, min.y - half);
5568
+ max.set(max.x + half, max.y + half);
5569
+ }
5570
+ }
5217
5571
  return {
5218
5572
  min: min.finite(),
5219
5573
  max: max.finite()
@@ -5352,6 +5706,42 @@ var Path2DSet = class {
5352
5706
  this.paths = paths;
5353
5707
  this.viewBox = viewBox;
5354
5708
  }
5709
+ /**
5710
+ * Test whether a point lies inside the filled area of any path in this set.
5711
+ * Purely geometric (ignores `fill: 'none'`); use {@link hitTest} for style-aware hits.
5712
+ */
5713
+ isPointInFill(point, options = {}) {
5714
+ return this.paths.some((path) => path.isPointInFill(point, options));
5715
+ }
5716
+ /**
5717
+ * Concise PathKit-style fill containment test across the whole set; shorthand for
5718
+ * {@link isPointInFill} with a `{ x, y }` point.
5719
+ */
5720
+ contains(x, y, options = {}) {
5721
+ return this.isPointInFill({
5722
+ x,
5723
+ y
5724
+ }, options);
5725
+ }
5726
+ /**
5727
+ * Find the topmost path hit by a point, or `undefined` if none.
5728
+ *
5729
+ * Paths are tested top-to-bottom (last drawn first). For each path a fill hit is checked
5730
+ * first (skipped when `style.fill` is `'none'`), then — if `stroke` is enabled — a stroke
5731
+ * hit (skipped when `style.stroke` is `'none'`). This honors the "fill: none falls back to
5732
+ * stroke" rule; the coordinate space of `point` must match the paths (no scaling assumed).
5733
+ *
5734
+ * Options: `stroke` (also test strokes, default `true`), `tolerance` (extra stroke hit slack
5735
+ * in path units, default `0`), and `fillRule` (overrides each path's own fill rule).
5736
+ */
5737
+ hitTest(point, options = {}) {
5738
+ const { stroke = true, tolerance, fillRule } = options;
5739
+ for (let i = this.paths.length - 1; i >= 0; i--) {
5740
+ const path = this.paths[i];
5741
+ if ((path.style.fill ?? "#000") !== "none" && path.isPointInFill(point, { fillRule })) return path;
5742
+ if (stroke && (path.style.stroke ?? "none") !== "none" && path.isPointInStroke(point, { tolerance })) return path;
5743
+ }
5744
+ }
5355
5745
  getBoundingBox(withStyle = true) {
5356
5746
  if (!this.paths.length) return;
5357
5747
  const min = Vector2.MAX;
@@ -5655,7 +6045,7 @@ function _createPresetShape(node, options) {
5655
6045
  var root;
5656
6046
  async function getRoot() {
5657
6047
  if (!root) {
5658
- const presetShapeDefinitions = await import("./presetShapeDefinitions-C0rBrGs4.js").then((rep) => rep.default);
6048
+ const presetShapeDefinitions = await import("./presetShapeDefinitions-NAoQKZ6z.js").then((rep) => rep.default);
5659
6049
  root = OoxmlNode.fromXML(presetShapeDefinitions);
5660
6050
  }
5661
6051
  return root;
@@ -5716,6 +6106,221 @@ function convertSvgLinearGradient(value) {
5716
6106
  }
5717
6107
  }
5718
6108
  //#endregion
6109
+ //#region src/convert/image.ts
6110
+ function cachedFetchImageBitmap(url) {
6111
+ if (typeof url === "string" && url.startsWith("http")) return assets.loadBy(url).then((blob) => assets.fetchImageBitmap(blob));
6112
+ return assets.fetchImageBitmap(url);
6113
+ }
6114
+ async function convertImageElementToUrl(el) {
6115
+ const { cropping = {}, transform = {}, style = {}, maskUrl, imageEffects = [], imageEffectsRatio = 1 } = el;
6116
+ const url = el.clipUrl || el.url;
6117
+ const { translateX = 0, translateY = 0, zoom = 1 } = transform ?? {};
6118
+ const { scaleX = 1, scaleY = 1, filter } = style;
6119
+ if (translateX === 0 && translateY === 0 && zoom === 1 && scaleX === 1 && scaleY === 1 && !maskUrl && !filter && !imageEffects.length) return url;
6120
+ const img = await cachedFetchImageBitmap(url);
6121
+ const { originWidth = img.width, originHeight = img.height, imageWidth = originWidth, imageHeight = originHeight } = transform;
6122
+ const { width = originWidth, height = originHeight } = style;
6123
+ const dpr = window.devicePixelRatio || 1;
6124
+ const [canvas, ctx] = createCanvas(width, height, dpr);
6125
+ if (filter) ctx.filter = filter;
6126
+ ctx.scale(scaleX, scaleY);
6127
+ ctx.translate(scaleX < 0 ? -width : 0, scaleY < 0 ? -height : 0);
6128
+ if (maskUrl) {
6129
+ const mask = await cachedFetchImageBitmap(maskUrl);
6130
+ ctx.drawImage(mask, 0, 0, cropping?.maskWidth ?? width, cropping?.maskHeight ?? height);
6131
+ ctx.globalCompositeOperation = "source-in";
6132
+ mask.close();
6133
+ }
6134
+ const dw = imageWidth * zoom;
6135
+ const dh = imageHeight * zoom;
6136
+ const dx = -(dw / 2 - imageWidth / 2) + translateX;
6137
+ const dy = -(dh / 2 - imageHeight / 2) + translateY;
6138
+ ctx.drawImage(img, 0, 0, img.width, img.height, dx, dy, dw, dh);
6139
+ img.close();
6140
+ ctx.globalCompositeOperation = "source-over";
6141
+ if (imageEffects.length > 0) {
6142
+ const scale = .9;
6143
+ const center = {
6144
+ x: (width - width * scale) / 2,
6145
+ y: (height - height * scale) / 2
6146
+ };
6147
+ const canvasBitmap = await createImageBitmap(canvas);
6148
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
6149
+ ctx.scale(scale, scale);
6150
+ for (let i = imageEffects.length - 1; i >= 0; i--) {
6151
+ const { filling, offset, stroke } = imageEffects[i];
6152
+ let effectCanvas = canvasBitmap;
6153
+ if (filling) {
6154
+ const [canvas1, ctx1] = createCanvas(width, height, dpr);
6155
+ ctx1.drawImage(effectCanvas, 0, 0, width, height);
6156
+ ctx1.globalCompositeOperation = "source-in";
6157
+ if (filling.color) {
6158
+ const [canvas2, ctx2] = createCanvas(width, height, dpr);
6159
+ ctx2.fillStyle = filling.color;
6160
+ ctx2.fillRect(0, 0, width, height);
6161
+ ctx1.drawImage(canvas2, 0, 0, width, height);
6162
+ } else if (filling.imageContent?.image) {
6163
+ const img2 = await cachedFetchImageBitmap(filling.imageContent.image);
6164
+ ctx1.drawImage(img2, 0, 0, width, height);
6165
+ img2.close();
6166
+ }
6167
+ effectCanvas = canvas1;
6168
+ }
6169
+ stroke?.forEach(({ width, color }) => {
6170
+ effectCanvas = new ImageStroke().use((ctx, image, options) => {
6171
+ const [, ctx1] = createCanvas(image.width, image.height);
6172
+ ctx1.drawImage(image, 0, 0);
6173
+ const paths = getContours(ctx1);
6174
+ const x = options.thickness;
6175
+ const y = options.thickness;
6176
+ ctx.strokeStyle = options.color;
6177
+ ctx.lineWidth = options.thickness * 2;
6178
+ ctx.lineJoin = "round";
6179
+ paths.forEach((path) => {
6180
+ ctx.beginPath();
6181
+ ctx.moveTo(x + path[0].x, y + path[1].y);
6182
+ for (let i = 1; i < path.length; i++) ctx.lineTo(x + path[i].x, y + path[i].y);
6183
+ ctx.closePath();
6184
+ });
6185
+ ctx.stroke();
6186
+ }).make(effectCanvas, {
6187
+ color,
6188
+ thickness: width / 50 * imageEffectsRatio
6189
+ });
6190
+ });
6191
+ if (offset) {
6192
+ let { x, y } = offset;
6193
+ x = x / 50 * imageEffectsRatio * 200;
6194
+ y = y / 50 * imageEffectsRatio * 200;
6195
+ ctx.drawImage(effectCanvas, x + center.x, y + center.y, width, height);
6196
+ } else ctx.drawImage(effectCanvas, center.x, center.y, width, height);
6197
+ }
6198
+ canvasBitmap.close();
6199
+ }
6200
+ return await new Promise((resolve) => {
6201
+ canvas.toBlob((blob) => {
6202
+ try {
6203
+ resolve(URL.createObjectURL(blob));
6204
+ } catch (e) {
6205
+ console.error(`Failed to URL.createObjectURL, url: ${url}`, e);
6206
+ resolve(url);
6207
+ }
6208
+ });
6209
+ });
6210
+ }
6211
+ function createCanvas(width, height, ratio = 1) {
6212
+ const canvas = document.createElement("canvas");
6213
+ canvas.width = width * ratio;
6214
+ canvas.height = height * ratio;
6215
+ canvas.style.width = `${width}px`;
6216
+ canvas.style.height = `${height}px`;
6217
+ const ctx = canvas.getContext("2d");
6218
+ ctx.scale(ratio, ratio);
6219
+ return [canvas, ctx];
6220
+ }
6221
+ var ImageStroke = class {
6222
+ canvas = document.createElement("canvas");
6223
+ method;
6224
+ use(method) {
6225
+ this.method = method;
6226
+ return this;
6227
+ }
6228
+ make(image, options) {
6229
+ const { canvas } = this;
6230
+ const ctx = this.canvas.getContext("2d");
6231
+ const strokeSize = options.thickness * 2;
6232
+ const [resultWidth, resultHeight] = [image.width, image.height].map((val) => val + strokeSize);
6233
+ if (resultWidth !== canvas.width || resultHeight !== canvas.height) {
6234
+ canvas.width = resultWidth;
6235
+ canvas.height = resultHeight;
6236
+ }
6237
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
6238
+ this.method(ctx, image, options);
6239
+ ctx.drawImage(image, options.thickness, options.thickness);
6240
+ return canvas;
6241
+ }
6242
+ };
6243
+ function getContours(ctx) {
6244
+ const cx = 0;
6245
+ const cy = 0;
6246
+ const canvasWidth = ctx.canvas.width;
6247
+ const canvasHeight = ctx.canvas.height;
6248
+ const paths = [];
6249
+ let lastPos = 3;
6250
+ const alpha = 100;
6251
+ const trace = () => {
6252
+ const path = [];
6253
+ const data = new Uint32Array(ctx.getImageData(cx, cy, canvasWidth, canvasHeight).data.buffer);
6254
+ let x;
6255
+ let y;
6256
+ let startX;
6257
+ let startY;
6258
+ let startPos = -1;
6259
+ let step;
6260
+ let prevStep = 9;
6261
+ const steps = [
6262
+ 9,
6263
+ 0,
6264
+ 3,
6265
+ 3,
6266
+ 2,
6267
+ 0,
6268
+ 9,
6269
+ 3,
6270
+ 1,
6271
+ 9,
6272
+ 1,
6273
+ 1,
6274
+ 2,
6275
+ 0,
6276
+ 2,
6277
+ 9
6278
+ ];
6279
+ let time = 50;
6280
+ function getState(x, y) {
6281
+ return x >= 0 && y >= 0 && x < canvasWidth && y < canvasHeight ? data[y * canvasWidth + x] >>> 24 > alpha : false;
6282
+ }
6283
+ function getNextStep(x, y) {
6284
+ let v = 0;
6285
+ if (getState(x - 1, y - 1)) v += 1;
6286
+ if (getState(x, y - 1)) v += 2;
6287
+ if (getState(x - 1, y)) v += 4;
6288
+ if (getState(x, y)) v += 8;
6289
+ if (time > 50) time += 10;
6290
+ else time += 10;
6291
+ if (v === 6) return prevStep === 0 ? 2 : 3;
6292
+ else if (v === 9) return prevStep === 3 ? 0 : 1;
6293
+ else return steps[v];
6294
+ }
6295
+ for (let i = lastPos; i < data.length; i++) if (data[i] >>> 24 > alpha) {
6296
+ startPos = lastPos = i;
6297
+ break;
6298
+ }
6299
+ if (startPos >= 0) {
6300
+ x = startX = startPos % canvasWidth;
6301
+ y = startY = Math.floor(startPos / canvasWidth);
6302
+ do {
6303
+ step = getNextStep(x, y);
6304
+ if (step === 0) y--;
6305
+ else if (step === 1) y++;
6306
+ else if (step === 2) x--;
6307
+ else if (step === 3) x++;
6308
+ if (step !== prevStep) {
6309
+ path.push({
6310
+ x: x + cx,
6311
+ y: y + cy
6312
+ });
6313
+ prevStep = step;
6314
+ }
6315
+ } while (x !== startX || y !== startY);
6316
+ }
6317
+ paths.push(path);
6318
+ return path;
6319
+ };
6320
+ trace();
6321
+ return paths;
6322
+ }
6323
+ //#endregion
5719
6324
  //#region src/convert/svg.ts
5720
6325
  async function convertSvgElementToUrl(el) {
5721
6326
  const { id, doc, url, style = {}, background = {} } = el;
@@ -5724,25 +6329,23 @@ async function convertSvgElementToUrl(el) {
5724
6329
  const svg = new DOMParser().parseFromString(xml.replace(new RegExp(`#el-${id} `, "gi"), "").replace(/data-colors\s/, " ").replace(/[a-z-]+="([^\s<]*<\S*)"/gi, ""), "image/svg+xml").documentElement;
5725
6330
  if (!(svg instanceof SVGElement)) throw new TypeError(`Failed to DOMParser, parse svg to DOM error: ${xml}`);
5726
6331
  const images = svg.querySelectorAll("image");
5727
- for (let i = 0; i < images.length; i++) {
5728
- const image = images[i];
6332
+ await Promise.all(Array.from(images).map(async (image) => {
5729
6333
  const url = image.href.baseVal;
5730
- if (!url.startsWith("http")) continue;
5731
- image?.setAttribute("href", await assets.fetchImageBitmap(url).then((bitmap) => {
5732
- const canvas = document.createElement("canvas");
5733
- canvas.width = bitmap.width;
5734
- canvas.height = bitmap.height;
5735
- canvas.getContext("2d")?.drawImage(bitmap, 0, 0);
5736
- bitmap.close();
5737
- return canvas.toDataURL("image/png");
5738
- }));
5739
- }
6334
+ if (!url.startsWith("http")) return;
6335
+ const bitmap = await cachedFetchImageBitmap(url);
6336
+ const canvas = document.createElement("canvas");
6337
+ canvas.width = bitmap.width;
6338
+ canvas.height = bitmap.height;
6339
+ canvas.getContext("2d")?.drawImage(bitmap, 0, 0);
6340
+ bitmap.close();
6341
+ image.setAttribute("href", canvas.toDataURL("image/png"));
6342
+ }));
5740
6343
  if (background.src) {
5741
6344
  const fillId = `#${id}-fill-blip`;
5742
6345
  const fillPattern = svg.querySelector(fillId);
5743
6346
  const fillImage = fillPattern?.querySelector("image");
5744
6347
  if (fillPattern && fillImage) try {
5745
- const base64Url = await assets.fetchImageBitmap(background.src).then((bitmap) => {
6348
+ const base64Url = await cachedFetchImageBitmap(background.src).then((bitmap) => {
5746
6349
  const canvas = document.createElement("canvas");
5747
6350
  canvas.width = bitmap.width;
5748
6351
  canvas.height = bitmap.height;
@@ -6142,217 +6745,6 @@ async function convertDoc(doc, options = {}) {
6142
6745
  };
6143
6746
  }
6144
6747
  //#endregion
6145
- //#region src/convert/image.ts
6146
- async function convertImageElementToUrl(el) {
6147
- const { cropping = {}, transform = {}, style = {}, maskUrl, imageEffects = [], imageEffectsRatio = 1 } = el;
6148
- const url = el.clipUrl || el.url;
6149
- const { translateX = 0, translateY = 0, zoom = 1 } = transform ?? {};
6150
- const { scaleX = 1, scaleY = 1, filter } = style;
6151
- if (translateX === 0 && translateY === 0 && zoom === 1 && scaleX === 1 && scaleY === 1 && !maskUrl && !filter && !imageEffects.length) return url;
6152
- const img = await assets.fetchImageBitmap(url);
6153
- const { originWidth = img.width, originHeight = img.height, imageWidth = originWidth, imageHeight = originHeight } = transform;
6154
- const { width = originWidth, height = originHeight } = style;
6155
- const dpr = window.devicePixelRatio || 1;
6156
- const [canvas, ctx] = createCanvas(width, height, dpr);
6157
- if (filter) ctx.filter = filter;
6158
- ctx.scale(scaleX, scaleY);
6159
- ctx.translate(scaleX < 0 ? -width : 0, scaleY < 0 ? -height : 0);
6160
- if (maskUrl) {
6161
- const mask = await assets.fetchImageBitmap(maskUrl);
6162
- ctx.drawImage(mask, 0, 0, cropping?.maskWidth ?? width, cropping?.maskHeight ?? height);
6163
- ctx.globalCompositeOperation = "source-in";
6164
- mask.close();
6165
- }
6166
- const dw = imageWidth * zoom;
6167
- const dh = imageHeight * zoom;
6168
- const dx = -(dw / 2 - imageWidth / 2) + translateX;
6169
- const dy = -(dh / 2 - imageHeight / 2) + translateY;
6170
- ctx.drawImage(img, 0, 0, img.width, img.height, dx, dy, dw, dh);
6171
- img.close();
6172
- ctx.globalCompositeOperation = "source-over";
6173
- if (imageEffects.length > 0) {
6174
- const scale = .9;
6175
- const center = {
6176
- x: (width - width * scale) / 2,
6177
- y: (height - height * scale) / 2
6178
- };
6179
- const canvasBitmap = await createImageBitmap(canvas);
6180
- ctx.clearRect(0, 0, canvas.width, canvas.height);
6181
- ctx.scale(scale, scale);
6182
- for (let i = imageEffects.length - 1; i >= 0; i--) {
6183
- const { filling, offset, stroke } = imageEffects[i];
6184
- let effectCanvas = canvasBitmap;
6185
- if (filling) {
6186
- const [canvas1, ctx1] = createCanvas(width, height, dpr);
6187
- ctx1.drawImage(effectCanvas, 0, 0, width, height);
6188
- ctx1.globalCompositeOperation = "source-in";
6189
- if (filling.color) {
6190
- const [canvas2, ctx2] = createCanvas(width, height, dpr);
6191
- ctx2.fillStyle = filling.color;
6192
- ctx2.fillRect(0, 0, width, height);
6193
- ctx1.drawImage(canvas2, 0, 0, width, height);
6194
- } else if (filling.imageContent?.image) {
6195
- const img2 = await assets.fetchImageBitmap(filling.imageContent.image);
6196
- ctx1.drawImage(img2, 0, 0, width, height);
6197
- img2.close();
6198
- }
6199
- effectCanvas = canvas1;
6200
- }
6201
- stroke?.forEach(({ width, color }) => {
6202
- effectCanvas = new ImageStroke().use((ctx, image, options) => {
6203
- const [, ctx1] = createCanvas(image.width, image.height);
6204
- ctx1.drawImage(image, 0, 0);
6205
- const paths = getContours(ctx1);
6206
- const x = options.thickness;
6207
- const y = options.thickness;
6208
- ctx.strokeStyle = options.color;
6209
- ctx.lineWidth = options.thickness * 2;
6210
- ctx.lineJoin = "round";
6211
- paths.forEach((path) => {
6212
- ctx.beginPath();
6213
- ctx.moveTo(x + path[0].x, y + path[1].y);
6214
- for (let i = 1; i < path.length; i++) ctx.lineTo(x + path[i].x, y + path[i].y);
6215
- ctx.closePath();
6216
- });
6217
- ctx.stroke();
6218
- }).make(effectCanvas, {
6219
- color,
6220
- thickness: width / 50 * imageEffectsRatio
6221
- });
6222
- });
6223
- if (offset) {
6224
- let { x, y } = offset;
6225
- x = x / 50 * imageEffectsRatio * 200;
6226
- y = y / 50 * imageEffectsRatio * 200;
6227
- ctx.drawImage(effectCanvas, x + center.x, y + center.y, width, height);
6228
- } else ctx.drawImage(effectCanvas, center.x, center.y, width, height);
6229
- }
6230
- canvasBitmap.close();
6231
- }
6232
- return await new Promise((resolve) => {
6233
- canvas.toBlob((blob) => {
6234
- try {
6235
- resolve(URL.createObjectURL(blob));
6236
- } catch (e) {
6237
- console.error(`Failed to URL.createObjectURL, url: ${url}`, e);
6238
- resolve(url);
6239
- }
6240
- });
6241
- });
6242
- }
6243
- function createCanvas(width, height, ratio = 1) {
6244
- const canvas = document.createElement("canvas");
6245
- canvas.width = width * ratio;
6246
- canvas.height = height * ratio;
6247
- canvas.style.width = `${width}px`;
6248
- canvas.style.height = `${height}px`;
6249
- const ctx = canvas.getContext("2d");
6250
- ctx.scale(ratio, ratio);
6251
- return [canvas, ctx];
6252
- }
6253
- var ImageStroke = class {
6254
- canvas = document.createElement("canvas");
6255
- method;
6256
- use(method) {
6257
- this.method = method;
6258
- return this;
6259
- }
6260
- make(image, options) {
6261
- const { canvas } = this;
6262
- const ctx = this.canvas.getContext("2d");
6263
- const strokeSize = options.thickness * 2;
6264
- const [resultWidth, resultHeight] = [image.width, image.height].map((val) => val + strokeSize);
6265
- if (resultWidth !== canvas.width || resultHeight !== canvas.height) {
6266
- canvas.width = resultWidth;
6267
- canvas.height = resultHeight;
6268
- }
6269
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
6270
- this.method(ctx, image, options);
6271
- ctx.drawImage(image, options.thickness, options.thickness);
6272
- return canvas;
6273
- }
6274
- };
6275
- function getContours(ctx) {
6276
- const cx = 0;
6277
- const cy = 0;
6278
- const canvasWidth = ctx.canvas.width;
6279
- const canvasHeight = ctx.canvas.height;
6280
- const paths = [];
6281
- let lastPos = 3;
6282
- const alpha = 100;
6283
- const trace = () => {
6284
- const path = [];
6285
- const data = new Uint32Array(ctx.getImageData(cx, cy, canvasWidth, canvasHeight).data.buffer);
6286
- let x;
6287
- let y;
6288
- let startX;
6289
- let startY;
6290
- let startPos = -1;
6291
- let step;
6292
- let prevStep = 9;
6293
- const steps = [
6294
- 9,
6295
- 0,
6296
- 3,
6297
- 3,
6298
- 2,
6299
- 0,
6300
- 9,
6301
- 3,
6302
- 1,
6303
- 9,
6304
- 1,
6305
- 1,
6306
- 2,
6307
- 0,
6308
- 2,
6309
- 9
6310
- ];
6311
- let time = 50;
6312
- function getState(x, y) {
6313
- return x >= 0 && y >= 0 && x < canvasWidth && y < canvasHeight ? data[y * canvasWidth + x] >>> 24 > alpha : false;
6314
- }
6315
- function getNextStep(x, y) {
6316
- let v = 0;
6317
- if (getState(x - 1, y - 1)) v += 1;
6318
- if (getState(x, y - 1)) v += 2;
6319
- if (getState(x - 1, y)) v += 4;
6320
- if (getState(x, y)) v += 8;
6321
- if (time > 50) time += 10;
6322
- else time += 10;
6323
- if (v === 6) return prevStep === 0 ? 2 : 3;
6324
- else if (v === 9) return prevStep === 3 ? 0 : 1;
6325
- else return steps[v];
6326
- }
6327
- for (let i = lastPos; i < data.length; i++) if (data[i] >>> 24 > alpha) {
6328
- startPos = lastPos = i;
6329
- break;
6330
- }
6331
- if (startPos >= 0) {
6332
- x = startX = startPos % canvasWidth;
6333
- y = startY = Math.floor(startPos / canvasWidth);
6334
- do {
6335
- step = getNextStep(x, y);
6336
- if (step === 0) y--;
6337
- else if (step === 1) y++;
6338
- else if (step === 2) x--;
6339
- else if (step === 3) x++;
6340
- if (step !== prevStep) {
6341
- path.push({
6342
- x: x + cx,
6343
- y: y + cy
6344
- });
6345
- prevStep = step;
6346
- }
6347
- } while (x !== startX || y !== startY);
6348
- }
6349
- paths.push(path);
6350
- return path;
6351
- };
6352
- trace();
6353
- return paths;
6354
- }
6355
- //#endregion
6356
6748
  //#region src/loaders/bidTid.ts
6357
6749
  function bidTidLoader(editor, api) {
6358
6750
  const { config, http } = editor;
@@ -6572,4 +6964,4 @@ var options = {
6572
6964
  //#region src/index.ts
6573
6965
  var src_default = plugin;
6574
6966
  //#endregion
6575
- export { bidTidLoader, bigeLoader, clipboardLoader, convertAnimation, convertBackground, convertDoc, convertElement, convertImageElementToUrl, convertLayout, convertSvgElementToUrl, convertTextContent, convertTextEffects, convertTextStyle, croppingToCropRect, src_default as default, getStyle, getTextContents, options, parseAnimations, plugin, transformToCropRect, useFonts };
6967
+ export { bidTidLoader, bigeLoader, cachedFetchImageBitmap, clipboardLoader, convertAnimation, convertBackground, convertDoc, convertElement, convertImageElementToUrl, convertLayout, convertSvgElementToUrl, convertTextContent, convertTextEffects, convertTextStyle, croppingToCropRect, src_default as default, getStyle, getTextContents, options, parseAnimations, plugin, transformToCropRect, useFonts };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mce/bigesj",
3
3
  "type": "module",
4
- "version": "0.18.4",
4
+ "version": "0.19.0",
5
5
  "description": "Plugin for mce",
6
6
  "author": "wxm",
7
7
  "license": "MIT",
@@ -46,10 +46,10 @@
46
46
  ],
47
47
  "dependencies": {
48
48
  "fflate": "^0.8.3",
49
- "modern-openxml": "^1.10.1"
49
+ "modern-openxml": "^1.12.3"
50
50
  },
51
51
  "devDependencies": {
52
- "mce": "0.18.4"
52
+ "mce": "0.19.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "mce": "^0"