@pixerate/editor 0.192.0 → 0.193.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.
Files changed (52) hide show
  1. package/dist/animation-editor/index.cjs +56 -56
  2. package/dist/animation-editor/index.js +2 -2
  3. package/dist/{chunk-ZQGZWFPU.cjs → chunk-3T5VCDIV.cjs} +97 -5
  4. package/dist/{chunk-RQ2EM5TY.js → chunk-5OC22IEO.js} +97 -5
  5. package/dist/{chunk-EGZM52EO.cjs → chunk-BRKVNXDZ.cjs} +354 -125
  6. package/dist/{chunk-XCDGM7TD.cjs → chunk-FR2Q2LVF.cjs} +35 -35
  7. package/dist/{chunk-LPIKXPBM.js → chunk-GHCXLQ47.js} +262 -33
  8. package/dist/{chunk-VRXXAXUT.js → chunk-MLC7HUHO.js} +1 -1
  9. package/dist/{chunk-2UR4OX3B.cjs → chunk-UYXL6ZWQ.cjs} +114 -97
  10. package/dist/{chunk-TXYXF32N.js → chunk-ZIDUPSP4.js} +35 -18
  11. package/dist/image-editor/controller.d.cts +16 -6
  12. package/dist/image-editor/controller.d.ts +16 -6
  13. package/dist/image-editor/index.cjs +867 -831
  14. package/dist/image-editor/index.js +103 -67
  15. package/dist/image-editor/layers/model.d.cts +7 -2
  16. package/dist/image-editor/layers/model.d.ts +7 -2
  17. package/dist/image-editor/layers/serialize.d.cts +2 -0
  18. package/dist/image-editor/layers/serialize.d.ts +2 -0
  19. package/dist/image-editor/layers/shape-type-ui.d.cts +8 -2
  20. package/dist/image-editor/layers/shape-type-ui.d.ts +8 -2
  21. package/dist/image-editor/layers/text.d.cts +6 -2
  22. package/dist/image-editor/layers/text.d.ts +6 -2
  23. package/dist/image-editor/psd/type.d.cts +2 -1
  24. package/dist/image-editor/psd/type.d.ts +2 -1
  25. package/dist/image-editor/tools.d.cts +1 -1
  26. package/dist/image-editor/tools.d.ts +1 -1
  27. package/dist/image-editor/type-tools.d.cts +2 -2
  28. package/dist/image-editor/type-tools.d.ts +2 -2
  29. package/dist/paint-editor/index.cjs +7 -7
  30. package/dist/paint-editor/index.js +2 -2
  31. package/dist/text/layout.d.cts +93 -12
  32. package/dist/text/layout.d.ts +93 -12
  33. package/dist/text/opentype.d.cts +13 -3
  34. package/dist/text/opentype.d.ts +13 -3
  35. package/dist/text/shapers.d.cts +12 -0
  36. package/dist/text/shapers.d.ts +12 -0
  37. package/dist/text/types.d.cts +36 -2
  38. package/dist/text/types.d.ts +36 -2
  39. package/dist/vector/controller.d.cts +8 -0
  40. package/dist/vector/controller.d.ts +8 -0
  41. package/dist/vector/index.cjs +1039 -967
  42. package/dist/vector/index.js +90 -18
  43. package/dist/vector/text.d.cts +5 -3
  44. package/dist/vector/text.d.ts +5 -3
  45. package/dist/vector/textPanel.d.cts +6 -0
  46. package/dist/vector/textPanel.d.ts +6 -0
  47. package/dist/vector/types.d.cts +9 -0
  48. package/dist/vector/types.d.ts +9 -0
  49. package/package.json +1 -1
  50. package/skills/pixerate-image-editor/SKILL.md +4 -3
  51. package/skills/pixerate-image-editor/references/shapes-and-type.md +17 -5
  52. package/skills/pixerate-vector/SKILL.md +9 -3
@@ -4435,6 +4435,40 @@ var isBreakAfter = (c) => c === " " || c === " " || c === "-" || c === "\u2010"
4435
4435
  var isTextSpace = (c) => c === " " || c === " ";
4436
4436
  var isSpace = isTextSpace;
4437
4437
  var isWide = (code) => code >= 11904 && code <= 40959 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65280 && code <= 65519;
4438
+ var isHigh = (code) => code >= 55296 && code <= 56319;
4439
+ var isLow = (code) => code >= 56320 && code <= 57343;
4440
+ var TR = /* @__PURE__ */ new Set([8229, 8230, 12296, 12297, 12298, 12299, 12300, 12301, 12302, 12303, 12304, 12305, 12308, 12309, 12310, 12311, 12312, 12313, 12314, 12315, 12316, 12336, 12448, 12540, 65288, 65289, 65293, 65306, 65307, 65309, 65339, 65341, 65343, 65371, 65372, 65373, 65374]);
4441
+ var TU = /* @__PURE__ */ new Set([12289, 12290, 65292, 65294, 12353, 12355, 12357, 12359, 12361, 12387, 12419, 12421, 12423, 12430, 12437, 12438, 12449, 12451, 12453, 12455, 12457, 12483, 12515, 12517, 12519, 12526, 12533, 12534]);
4442
+ var CORNER = /* @__PURE__ */ new Set([12289, 12290, 65292, 65294]);
4443
+ function verticalOrientationOf(cp) {
4444
+ if (TR.has(cp)) return "Tr";
4445
+ if (TU.has(cp) || cp >= 12784 && cp <= 12799) return "Tu";
4446
+ if (cp >= 4352 && cp <= 4607 || cp >= 11904 && cp <= 19903 || cp >= 19968 && cp <= 40959 || cp >= 40960 && cp <= 42191 || cp >= 43360 && cp <= 43391 || cp >= 44032 && cp <= 55295 || cp >= 63744 && cp <= 64255 || cp >= 65040 && cp <= 65055 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65511 || cp >= 126976 && cp <= 129791 || cp >= 131072 && cp <= 262143)
4447
+ return "U";
4448
+ return "R";
4449
+ }
4450
+ var applyMatrix2 = (m, x, y) => m ? { x: m[0] * x + m[2] * y + m[4], y: m[1] * x + m[3] * y + m[5] } : { x, y };
4451
+ function invertMatrix2(m) {
4452
+ const [a, b, c, d, e, f] = m;
4453
+ const det = a * d - b * c || 1;
4454
+ return [d / det, -b / det, -c / det, a / det, (c * f - d * e) / det, (b * e - a * f) / det];
4455
+ }
4456
+ function transformTextRect(m, r) {
4457
+ if (!m) return r;
4458
+ const pts = [applyMatrix2(m, r.x, r.y), applyMatrix2(m, r.x + r.width, r.y), applyMatrix2(m, r.x + r.width, r.y + r.height), applyMatrix2(m, r.x, r.y + r.height)];
4459
+ const x0 = Math.min(...pts.map((p) => p.x));
4460
+ const y0 = Math.min(...pts.map((p) => p.y));
4461
+ return { x: x0, y: y0, width: Math.max(...pts.map((p) => p.x)) - x0, height: Math.max(...pts.map((p) => p.y)) - y0 };
4462
+ }
4463
+ function lineToTextSpace(layout, p) {
4464
+ return applyMatrix2(layout.matrix, p.x, p.y);
4465
+ }
4466
+ function textToLineSpace(layout, p) {
4467
+ return layout.matrix ? applyMatrix2(invertMatrix2(layout.matrix), p.x, p.y) : { x: p.x, y: p.y };
4468
+ }
4469
+ function textLineRect(l) {
4470
+ return transformTextRect(l.matrix, { x: l.x, y: l.baseline - l.ascent, width: l.width, height: l.ascent + l.descent });
4471
+ }
4438
4472
  var layoutCache = /* @__PURE__ */ new WeakMap();
4439
4473
  function layoutText(layer, shaper) {
4440
4474
  let byShaper = layoutCache.get(layer);
@@ -4450,6 +4484,7 @@ function layoutText(layer, shaper) {
4450
4484
  }
4451
4485
  function computeLayout(layer, shaper) {
4452
4486
  const { text, frame } = layer;
4487
+ const vertical = layer.orientation === "vertical";
4453
4488
  const runs = fitRuns(layer.runs, text.length, DEFAULT_TEXT_STYLE);
4454
4489
  const missing = /* @__PURE__ */ new Set();
4455
4490
  const resolved = /* @__PURE__ */ new Map();
@@ -4465,7 +4500,15 @@ function computeLayout(layer, shaper) {
4465
4500
  return fam;
4466
4501
  };
4467
4502
  const resolvedFont = (style) => ({ ...fontOf(style), family: familyOf(fontOf(style)) });
4503
+ const verticalOf = (ch, style) => {
4504
+ const font = resolvedFont(style);
4505
+ if (shaper.vertical) return shaper.vertical(ch, font);
4506
+ const m = shaper.metrics(font);
4507
+ return { advance: m.ascent + m.descent, originY: m.ascent, alternate: false };
4508
+ };
4468
4509
  const box = frame.kind === "box" ? frame : null;
4510
+ const inlineSize = box ? vertical ? box.height : box.width : 0;
4511
+ const matrix = vertical ? [0, 1, -1, 0, box ? box.width : 0, 0] : void 0;
4469
4512
  const lines = [];
4470
4513
  let y = 0;
4471
4514
  let first = true;
@@ -4483,7 +4526,38 @@ function computeLayout(layer, shaper) {
4483
4526
  const seg = text.slice(i, j);
4484
4527
  const carets = shaper.measure(seg, resolvedFont(style));
4485
4528
  const track = (style.tracking ?? 0) / 1e3 * style.fontSize;
4486
- for (let k = 0; k < seg.length; k++) chars.push({ index: i + k, ch: seg[k], advance: (carets[k + 1] ?? 0) - (carets[k] ?? 0) + track, style });
4529
+ for (let k = 0; k < seg.length; k++) {
4530
+ const index = i + k;
4531
+ const advance = (carets[k + 1] ?? 0) - (carets[k] ?? 0);
4532
+ if (!vertical) {
4533
+ chars.push({ index, ch: seg[k], advance: advance + track, style });
4534
+ continue;
4535
+ }
4536
+ const code = seg.charCodeAt(k);
4537
+ if (isLow(code) && index > 0 && isHigh(text.charCodeAt(index - 1))) {
4538
+ const prev = chars[chars.length - 1];
4539
+ if (prev?.up) {
4540
+ chars.push({ index, ch: seg[k], advance: 0, style, up: prev.up, low: true });
4541
+ } else chars.push({ index, ch: seg[k], advance, style, low: true });
4542
+ continue;
4543
+ }
4544
+ const cp = text.codePointAt(index) ?? code;
4545
+ const kind = verticalOrientationOf(cp);
4546
+ let upright = kind === "U" || kind === "Tu" || kind === "R" && Boolean(style.upright) && !isSpace(seg[k]) && seg[k] !== "\n";
4547
+ let vm = null;
4548
+ const char = cp > 65535 ? text.slice(index, index + 2) : seg[k];
4549
+ if (upright || kind === "Tr") {
4550
+ vm = verticalOf(char, style);
4551
+ if (kind === "Tr") upright = vm.alternate;
4552
+ }
4553
+ if (!upright || !vm) {
4554
+ chars.push({ index, ch: seg[k], advance: advance + track, style });
4555
+ continue;
4556
+ }
4557
+ const corner = kind === "Tu" && !vm.alternate && CORNER.has(cp) ? style.fontSize * 0.55 : 0;
4558
+ const alone = shaper.measure(char, resolvedFont(style));
4559
+ chars.push({ index, ch: seg[k], advance: vm.advance + track, style, up: { width: alone[alone.length - 1] ?? advance, originY: vm.originY, vert: vm.alternate, dx: corner, dy: -corner } });
4560
+ }
4487
4561
  i = j;
4488
4562
  }
4489
4563
  const startIndent = pstyle.startIndent ?? 0;
@@ -4494,13 +4568,13 @@ function computeLayout(layer, shaper) {
4494
4568
  else {
4495
4569
  let s = 0;
4496
4570
  while (s < chars.length) {
4497
- const avail = box.width - startIndent - endIndent - (breaks.length === 0 ? firstIndent : 0);
4571
+ const avail = inlineSize - startIndent - endIndent - (breaks.length === 0 ? firstIndent : 0);
4498
4572
  let w = 0;
4499
4573
  let e = s;
4500
4574
  let lastBreak = -1;
4501
4575
  while (e < chars.length) {
4502
4576
  const c = chars[e];
4503
- if (!isSpace(c.ch) && w + c.advance > avail && e > s) break;
4577
+ if (!isSpace(c.ch) && w + c.advance > avail && e > s && !c.low) break;
4504
4578
  w += c.advance;
4505
4579
  if (isBreakAfter(c.ch) || isWide(c.ch.charCodeAt(0)) || e + 1 < chars.length && isWide(chars[e + 1].ch.charCodeAt(0))) lastBreak = e + 1;
4506
4580
  e++;
@@ -4521,9 +4595,14 @@ function computeLayout(layer, shaper) {
4521
4595
  let descent = 0;
4522
4596
  let leading = 0;
4523
4597
  for (const st of styles) {
4524
- const m = shaper.metrics(resolvedFont(st));
4525
- ascent = Math.max(ascent, m.ascent);
4526
- descent = Math.max(descent, m.descent);
4598
+ if (vertical) {
4599
+ ascent = Math.max(ascent, st.fontSize / 2);
4600
+ descent = ascent;
4601
+ } else {
4602
+ const m = shaper.metrics(resolvedFont(st));
4603
+ ascent = Math.max(ascent, m.ascent);
4604
+ descent = Math.max(descent, m.descent);
4605
+ }
4527
4606
  leading = Math.max(leading, st.leading ?? autoLeading * st.fontSize);
4528
4607
  }
4529
4608
  if (first) {
@@ -4537,13 +4616,13 @@ function computeLayout(layer, shaper) {
4537
4616
  let extraPerSpace = 0;
4538
4617
  if (box && align === "justify" && !lastLine) {
4539
4618
  const spaces = lineChars.slice(0, visibleEnd).filter((c) => isSpace(c.ch)).length;
4540
- const avail = box.width - startIndent - endIndent - (li === 0 ? firstIndent : 0);
4619
+ const avail = inlineSize - startIndent - endIndent - (li === 0 ? firstIndent : 0);
4541
4620
  if (spaces > 0 && avail > width) extraPerSpace = (avail - width) / spaces;
4542
4621
  }
4543
4622
  let x0;
4544
4623
  const fullWidth = width + extraPerSpace * lineChars.slice(0, visibleEnd).filter((c) => isSpace(c.ch)).length;
4545
4624
  if (box) {
4546
- const avail = box.width - startIndent - endIndent - (li === 0 ? firstIndent : 0);
4625
+ const avail = inlineSize - startIndent - endIndent - (li === 0 ? firstIndent : 0);
4547
4626
  x0 = indent + (align === "center" ? (avail - fullWidth) / 2 : align === "right" ? avail - fullWidth : 0);
4548
4627
  } else x0 = indent + (align === "center" ? -fullWidth / 2 : align === "right" ? -fullWidth : 0);
4549
4628
  const laid = [];
@@ -4557,27 +4636,57 @@ function computeLayout(layer, shaper) {
4557
4636
  let k = 0;
4558
4637
  while (k < lineChars.length) {
4559
4638
  const st = lineChars[k].style;
4639
+ const up = Boolean(lineChars[k].up);
4560
4640
  let m = k + 1;
4561
- while (m < lineChars.length && lineChars[m].style === st) m++;
4641
+ while (m < lineChars.length && lineChars[m].style === st && Boolean(lineChars[m].up) === up) m++;
4562
4642
  const font = resolvedFont(st);
4563
4643
  const fx = laid[k].x;
4564
4644
  const carets = [0];
4565
4645
  for (let q = k; q < m; q++) carets.push(laid[q].x + laid[q].advance - fx);
4566
- fragments.push({
4646
+ const shift = st.baselineShift ?? 0;
4647
+ let fy = y - shift;
4648
+ if (vertical && !up) {
4649
+ const hm = shaper.metrics(font);
4650
+ fy = y + (hm.ascent - hm.descent) / 2 - shift;
4651
+ }
4652
+ const fragment = {
4567
4653
  start: lineChars[k].index,
4568
4654
  end: lineChars[m - 1].index + 1,
4569
4655
  style: st,
4570
4656
  font,
4571
4657
  family: font.family,
4572
4658
  x: fx,
4573
- y: y - (st.baselineShift ?? 0),
4659
+ y: fy,
4574
4660
  carets,
4575
- spaced: Boolean(st.tracking) || extraPerSpace > 0
4576
- });
4661
+ spaced: Boolean(st.tracking) || extraPerSpace > 0 || up
4662
+ };
4663
+ if (vertical) {
4664
+ const glyphs = [];
4665
+ for (let q = k; q < m; q++) {
4666
+ const c = lineChars[q];
4667
+ const lc = laid[q];
4668
+ if (c.low && q > k) {
4669
+ const g = glyphs[glyphs.length - 1];
4670
+ g.length = 2;
4671
+ if (!up) g.advance += lc.advance;
4672
+ continue;
4673
+ }
4674
+ const p = applyMatrix2(matrix, lc.x, fy);
4675
+ if (c.up) {
4676
+ const u = c.up;
4677
+ glyphs.push({ index: c.index, length: 1, x: p.x - u.width / 2 + u.dx, y: p.y + u.originY + u.dy, angle: 0, advance: u.width, ascent: u.originY + u.dy, descent: lc.advance - u.originY - u.dy, vert: u.vert });
4678
+ } else glyphs.push({ index: c.index, length: 1, x: p.x, y: p.y, angle: 90, advance: lc.advance, ascent: fy - (y - ascent), descent: y + descent - fy, vert: false });
4679
+ }
4680
+ fragment.upright = up;
4681
+ fragment.glyphs = glyphs;
4682
+ }
4683
+ fragments.push(fragment);
4577
4684
  k = m;
4578
4685
  }
4579
4686
  const lineStart = lineChars.length ? lineChars[0].index : para.start;
4580
- lines.push({ start: lineStart, end: lineChars.length ? lineChars[lineChars.length - 1].index + 1 : para.start, baseline: y, ascent, descent, x: x0, width, chars: laid, fragments });
4687
+ const line = { start: lineStart, end: lineChars.length ? lineChars[lineChars.length - 1].index + 1 : para.start, baseline: y, ascent, descent, x: x0, width, chars: laid, fragments };
4688
+ if (matrix) line.matrix = matrix;
4689
+ lines.push(line);
4581
4690
  });
4582
4691
  y += pstyle.spaceAfter ?? 0;
4583
4692
  });
@@ -4594,12 +4703,15 @@ function computeLayout(layer, shaper) {
4594
4703
  const top = lines.length ? lines[0].baseline - lines[0].ascent : 0;
4595
4704
  const last = lines[lines.length - 1];
4596
4705
  const bottom = last ? last.baseline + last.descent : 0;
4597
- bounds = { x: x0, y: top, width: Math.max(0, x1 - x0), height: Math.max(0, bottom - top) };
4706
+ bounds = transformTextRect(matrix, { x: x0, y: top, width: Math.max(0, x1 - x0), height: Math.max(0, bottom - top) });
4598
4707
  }
4599
- return { lines, bounds, missingFonts: [...missing] };
4708
+ const out = { lines, bounds, missingFonts: [...missing], orientation: vertical ? "vertical" : "horizontal" };
4709
+ if (matrix) out.matrix = matrix;
4710
+ return out;
4600
4711
  }
4601
- function textIndexAt(layout, p) {
4712
+ function textIndexAt(layout, point) {
4602
4713
  if (!layout.lines.length) return 0;
4714
+ const p = textToLineSpace(layout, point);
4603
4715
  let line = layout.lines[layout.lines.length - 1];
4604
4716
  for (const l of layout.lines) {
4605
4717
  if (p.y <= l.baseline + l.descent) {
@@ -4614,23 +4726,60 @@ function textIndexAt(layout, p) {
4614
4726
  }
4615
4727
  function caretAt(layout, index) {
4616
4728
  const lines = layout.lines;
4617
- if (!lines.length) return { x: 0, top: 0, bottom: 0, line: 0 };
4729
+ const caret = (x, l2, line) => {
4730
+ const top = l2.baseline - l2.ascent;
4731
+ const bottom = l2.baseline + l2.descent;
4732
+ return { x, top, bottom, line, from: applyMatrix2(l2.matrix, x, top), to: applyMatrix2(l2.matrix, x, bottom) };
4733
+ };
4734
+ if (!lines.length) return { x: 0, top: 0, bottom: 0, line: 0, from: { x: 0, y: 0 }, to: { x: 0, y: 0 } };
4618
4735
  for (let li = 0; li < lines.length; li++) {
4619
4736
  const l2 = lines[li];
4620
4737
  const next = lines[li + 1];
4621
4738
  if (next && index >= next.start && (next.chars.length || index > l2.end || next.start > l2.end)) continue;
4622
4739
  const c = l2.chars.find((ch) => ch.index === index);
4623
4740
  const prev = l2.chars.find((ch) => ch.index === index - 1);
4624
- const x = c ? c.x : prev ? prev.x + prev.advance : l2.x;
4625
- return { x, top: l2.baseline - l2.ascent, bottom: l2.baseline + l2.descent, line: li };
4741
+ return caret(c ? c.x : prev ? prev.x + prev.advance : l2.x, l2, li);
4626
4742
  }
4627
4743
  const l = lines[lines.length - 1];
4628
- return { x: l.x + l.width, top: l.baseline - l.ascent, bottom: l.baseline + l.descent, line: lines.length - 1 };
4744
+ return caret(l.x + l.width, l, lines.length - 1);
4745
+ }
4746
+ function textSelectionQuads(layout, start, end) {
4747
+ const out = [];
4748
+ if (end <= start) return out;
4749
+ for (const l of layout.lines) {
4750
+ const chars = l.chars.filter((c) => c.index >= start && c.index < end);
4751
+ if (!chars.length) continue;
4752
+ const x0 = chars[0].x;
4753
+ const x1 = chars[chars.length - 1].x + chars[chars.length - 1].advance;
4754
+ const top = l.baseline - l.ascent;
4755
+ const bottom = l.baseline + l.descent;
4756
+ out.push([applyMatrix2(l.matrix, x0, top), applyMatrix2(l.matrix, x1, top), applyMatrix2(l.matrix, x1, bottom), applyMatrix2(l.matrix, x0, bottom)]);
4757
+ }
4758
+ return out;
4759
+ }
4760
+ function textArrowStep(orientation, key) {
4761
+ if (orientation === "vertical") return key === "ArrowUp" ? "previous" : key === "ArrowDown" ? "next" : key === "ArrowRight" ? "line-before" : "line-after";
4762
+ return key === "ArrowLeft" ? "previous" : key === "ArrowRight" ? "next" : key === "ArrowUp" ? "line-before" : "line-after";
4763
+ }
4764
+ function textLineMove(layout, index, dir, length2) {
4765
+ if (!layout.lines.length) return dir < 0 ? 0 : length2;
4766
+ const c = caretAt(layout, index);
4767
+ const target = layout.lines[c.line + dir];
4768
+ if (!target) return dir < 0 ? 0 : length2;
4769
+ const hit = target.chars.find((ch) => c.x < ch.x + ch.advance / 2);
4770
+ const last = target.chars[target.chars.length - 1];
4771
+ return hit ? hit.index : last ? last.index + 1 : target.start;
4629
4772
  }
4630
4773
  function textDecorations(f, l) {
4631
4774
  const out = [];
4632
4775
  const t = Math.max(1, f.style.fontSize / 15);
4633
4776
  const w = f.carets[f.carets.length - 1];
4777
+ if (l.matrix) {
4778
+ const centre = l.baseline - (f.style.baselineShift ?? 0);
4779
+ if (f.style.underline) out.push(transformTextRect(l.matrix, { x: f.x, y: centre - l.ascent, width: w, height: t }));
4780
+ if (f.style.strikethrough) out.push(transformTextRect(l.matrix, { x: f.x, y: centre - t / 2, width: w, height: t }));
4781
+ return out;
4782
+ }
4634
4783
  if (f.style.underline) out.push({ x: f.x, y: f.y + Math.max(t, l.descent * 0.35), width: w, height: t });
4635
4784
  if (f.style.strikethrough) out.push({ x: f.x, y: f.y - l.ascent * 0.3 - t / 2, width: w, height: t });
4636
4785
  return out;
@@ -4642,7 +4791,25 @@ var textRectPath = (r) => [
4642
4791
  { type: "L", x: r.x, y: r.y + r.height },
4643
4792
  { type: "Z" }
4644
4793
  ];
4794
+ function turnPath(path, x, y) {
4795
+ return path.map((c) => {
4796
+ if (c.type === "Z") return c;
4797
+ if (c.type === "M" || c.type === "L") return { type: c.type, x: x - c.y, y: y + c.x };
4798
+ if (c.type === "Q") return { type: "Q", x1: x - c.y1, y1: y + c.x1, x: x - c.y, y: y + c.x };
4799
+ return { type: "C", x1: x - c.y1, y1: y + c.x1, x2: x - c.y2, y2: y + c.x2, x: x - c.y, y: y + c.x };
4800
+ });
4801
+ }
4645
4802
  function textFragmentOutline(shaper, text, f) {
4803
+ if (f.glyphs) {
4804
+ const out2 = [];
4805
+ for (const g of f.glyphs) {
4806
+ const ch = text.slice(g.index, g.index + g.length);
4807
+ if (isSpace(ch)) continue;
4808
+ const opts = g.vert ? { vertical: true } : void 0;
4809
+ out2.push(...g.angle ? turnPath(shaper.outline(ch, f.font, 0, 0, opts), g.x, g.y) : shaper.outline(ch, f.font, g.x, g.y, opts));
4810
+ }
4811
+ return out2;
4812
+ }
4646
4813
  const s = text.slice(f.start, f.end);
4647
4814
  if (!f.spaced) return shaper.outline(s, f.font, f.x, f.y);
4648
4815
  const out = [];
@@ -4652,6 +4819,28 @@ function textFragmentOutline(shaper, text, f) {
4652
4819
  }
4653
4820
  return out;
4654
4821
  }
4822
+ function drawTextFragment(ctx, shaper, text, f) {
4823
+ if (f.glyphs) {
4824
+ for (const g of f.glyphs) {
4825
+ const ch = text.slice(g.index, g.index + g.length);
4826
+ if (isSpace(ch)) continue;
4827
+ const opts = g.vert ? { vertical: true } : void 0;
4828
+ if (!g.angle) {
4829
+ shaper.draw(ctx, ch, f.font, g.x, g.y, opts);
4830
+ continue;
4831
+ }
4832
+ ctx.save();
4833
+ ctx.translate(g.x, g.y);
4834
+ ctx.rotate(Math.PI / 2);
4835
+ shaper.draw(ctx, ch, f.font, 0, 0, opts);
4836
+ ctx.restore();
4837
+ }
4838
+ return;
4839
+ }
4840
+ const s = text.slice(f.start, f.end);
4841
+ if (!f.spaced) shaper.draw(ctx, s, f.font, f.x, f.y);
4842
+ else for (let k = 0; k < s.length; k++) if (!isSpace(s[k])) shaper.draw(ctx, s[k], f.font, f.x + f.carets[k], f.y);
4843
+ }
4655
4844
 
4656
4845
  // src/text/shapers.ts
4657
4846
  var GENERIC = /* @__PURE__ */ new Set(["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "math", "emoji", "fangsong"]);
@@ -4797,8 +4986,15 @@ function createOutlineTextShaper(fonts, options = {}) {
4797
4986
  for (let i = 1; i <= text.length; i++) out.push(face.getAdvanceWidth(text.slice(0, i), font.size, { kerning: true }));
4798
4987
  return out;
4799
4988
  },
4800
- outline(text, font, x, y) {
4801
- return toPath(pick(font).entry.face.getPath(text, x, y, font.size, { kerning: true }).commands);
4989
+ outline(text, font, x, y, opts) {
4990
+ return toPath(pick(font).entry.face.getPath(text, x, y, font.size, opts?.vertical ? { kerning: true, vertical: true } : { kerning: true }).commands);
4991
+ },
4992
+ vertical(char, font) {
4993
+ const { face } = pick(font).entry;
4994
+ const k = font.size / face.unitsPerEm;
4995
+ const v2 = face.getVerticalMetrics?.(char);
4996
+ if (v2) return { advance: v2.advance * k, originY: v2.originY * k, alternate: v2.alternate };
4997
+ return { advance: (face.ascender - face.descender) * k, originY: face.ascender * k, alternate: false };
4802
4998
  },
4803
4999
  families() {
4804
5000
  return [...new Set(entries.map((e) => e.family))];
@@ -4860,7 +5056,7 @@ function vectorTextFamilies() {
4860
5056
  return getVectorTextShaper().families?.() ?? ["sans-serif", "serif", "monospace"];
4861
5057
  }
4862
5058
  var DEFAULT_VECTOR_FONT = { fontFamily: "sans-serif", fontWeight: 400, fontSize: 24 };
4863
- var VECTOR_CHARACTER_STYLE_KEYS = ["fontFamily", "fontWeight", "italic", "fontSize", "tracking", "leading", "baselineShift", "underline", "strikethrough"];
5059
+ var VECTOR_CHARACTER_STYLE_KEYS = ["fontFamily", "fontWeight", "italic", "fontSize", "tracking", "leading", "baselineShift", "underline", "strikethrough", "upright"];
4864
5060
  var VECTOR_PARAGRAPH_STYLE_KEYS = ["align", "autoLeading", "firstLineIndent", "startIndent", "endIndent", "spaceBefore", "spaceAfter"];
4865
5061
  var VectorTextError = class extends Error {
4866
5062
  };
@@ -5005,7 +5201,8 @@ function textBlock(node) {
5005
5201
  return { length: r.end - r.start, style: { ...r.style, color: `#${i}` } };
5006
5202
  }),
5007
5203
  paragraphs: [{ length: text.length, style: { ...node.paragraph ?? {}, ...node.frame.kind === "path" ? { align: "left" } : {} } }],
5008
- frame: node.frame.kind === "area" ? { kind: "box", width: Math.max(0, node.frame.width), height: Math.max(0, node.frame.height) } : { kind: "point" }
5204
+ frame: node.frame.kind === "area" ? { kind: "box", width: Math.max(0, node.frame.width), height: Math.max(0, node.frame.height) } : { kind: "point" },
5205
+ ...node.orientation === "vertical" && node.frame.kind !== "path" ? { orientation: "vertical" } : {}
5009
5206
  };
5010
5207
  }
5011
5208
  var charAt = (text, i) => {
@@ -5039,13 +5236,21 @@ function computeLayout2(node, shaper) {
5039
5236
  };
5040
5237
  if (frame.kind !== "path") {
5041
5238
  text.lines.forEach((l, li) => {
5042
- if (frame.kind === "area" && li > 0 && l.baseline + l.descent > frame.height + 1e-6) {
5239
+ if (frame.kind === "area" && li > 0 && l.baseline + l.descent > (l.matrix ? frame.width : frame.height) + 1e-6) {
5043
5240
  overflow = true;
5044
5241
  return;
5045
5242
  }
5046
- lines.push({ start: l.start, end: l.end, baseline: origin.y + l.baseline, x: origin.x + l.x, width: l.width });
5243
+ if (l.matrix) {
5244
+ const top = lineToTextSpace(text, { x: l.x, y: l.baseline });
5245
+ lines.push({ start: l.start, end: l.end, baseline: origin.x + top.x, x: origin.y + top.y, width: l.width });
5246
+ } else lines.push({ start: l.start, end: l.end, baseline: origin.y + l.baseline, x: origin.x + l.x, width: l.width });
5047
5247
  for (const f of l.fragments) {
5048
5248
  const from = glyphs.length;
5249
+ if (f.glyphs) {
5250
+ for (const g of f.glyphs) glyphs.push({ index: g.index, char: block.text.slice(g.index, g.index + g.length), x: origin.x + g.x, y: origin.y + g.y, angle: g.angle, advance: g.advance, ascent: g.ascent, descent: g.descent, fragment: fragments.length });
5251
+ fragments.push({ style: styleOf(f), font: f.font, ...fillOf(f) !== void 0 ? { fill: fillOf(f) } : {}, from, to: glyphs.length, line: lines.length - 1, laid: { fragment: f, line: l } });
5252
+ continue;
5253
+ }
5049
5254
  for (let k = 0; k < f.end - f.start; k++) {
5050
5255
  const index = f.start + k;
5051
5256
  const ch = block.text[index];
@@ -5192,7 +5397,7 @@ function vectorTextPaths(node) {
5192
5397
  const glyphs = layout.glyphs.slice(f.from, f.to);
5193
5398
  if (!glyphs.length) return;
5194
5399
  if (!outlines) {
5195
- if (node.frame.kind === "path") {
5400
+ if (node.frame.kind === "path" || layout.text.orientation === "vertical") {
5196
5401
  for (const g of glyphs) if (!isTextSpace(g.char)) out.push(...polygon2(glyphBox(g)));
5197
5402
  } else {
5198
5403
  const first = glyphs[0];
@@ -5234,7 +5439,7 @@ function polygon2(pts) {
5234
5439
  return [{ type: "M", x: pts[0].x, y: pts[0].y }, ...pts.slice(1).map((p) => ({ type: "L", x: p.x, y: p.y })), { type: "Z" }];
5235
5440
  }
5236
5441
  function vectorTextPaintSource(node, fill, subpaths = []) {
5237
- const { type: _t, text: _x, font: _f, runs: _r, paragraph: _p, frame: _fr, ...rest } = node;
5442
+ const { type: _t, text: _x, font: _f, runs: _r, paragraph: _p, frame: _fr, orientation: _o, ...rest } = node;
5238
5443
  const out = { ...rest, type: "path", subpaths, fillRule: "nonzero" };
5239
5444
  if (fill === void 0) return out;
5240
5445
  if (Array.isArray(out.fills) && out.fills.length) {
@@ -5257,7 +5462,7 @@ function vectorTextCaret(node, index) {
5257
5462
  if (node.frame.kind !== "path") {
5258
5463
  const c = caretAt(layout.text, index);
5259
5464
  const o = layout.origin;
5260
- return { top: { x: o.x + c.x, y: o.y + c.top }, bottom: { x: o.x + c.x, y: o.y + c.bottom }, line: c.line };
5465
+ return { top: { x: o.x + c.from.x, y: o.y + c.from.y }, bottom: { x: o.x + c.to.x, y: o.y + c.to.y }, line: c.line };
5261
5466
  }
5262
5467
  const g = glyphs.find((x) => x.index >= index);
5263
5468
  if (g && g.index === index) return at2(g, 0);
@@ -5298,6 +5503,10 @@ function vectorTextSelectionQuads(node, start, end) {
5298
5503
  const layout = layoutVectorText(node);
5299
5504
  const picked = layout.glyphs.filter((g) => g.index >= start && g.index < end);
5300
5505
  if (node.frame.kind === "path") return picked.map(glyphBox);
5506
+ if (layout.text.orientation === "vertical") {
5507
+ const o = layout.origin;
5508
+ return textSelectionQuads(layout.text, start, end).map((q) => q.map((p) => ({ x: o.x + p.x, y: o.y + p.y })));
5509
+ }
5301
5510
  const byLine = /* @__PURE__ */ new Map();
5302
5511
  for (const g of picked) {
5303
5512
  const line = layout.fragments[g.fragment].line;
@@ -5336,6 +5545,7 @@ function patchVectorCharacterStyle(base, patch, path = "style") {
5336
5545
  case "italic":
5337
5546
  case "underline":
5338
5547
  case "strikethrough":
5548
+ case "upright":
5339
5549
  if (typeof v2 !== "boolean" && v2 !== null) fail(`${path}.${k} must be true or false.`);
5340
5550
  if (v2) out[k] = true;
5341
5551
  else delete out[k];
@@ -5413,6 +5623,11 @@ function normalizeVectorTextRuns(text, runs) {
5413
5623
  if (cleaned.every((r) => !Object.keys(r.style).length)) return null;
5414
5624
  return cleaned;
5415
5625
  }
5626
+ function vectorTextOrientation(value, path = "orientation") {
5627
+ if (value === void 0 || value === null || value === "horizontal") return void 0;
5628
+ if (value !== "vertical") fail(`${path} must be horizontal or vertical, got ${JSON.stringify(value)}.`);
5629
+ return "vertical";
5630
+ }
5416
5631
  function sanitizeVectorTextFields(value) {
5417
5632
  const text = typeof value.text === "string" ? value.text.replace(/\r\n?/g, "\n") : "";
5418
5633
  const font = patchVectorCharacterStyle({ ...DEFAULT_VECTOR_FONT }, value.font ?? {}, "font");
@@ -5425,7 +5640,8 @@ function sanitizeVectorTextFields(value) {
5425
5640
  ) : null;
5426
5641
  const paragraph = value.paragraph ? patchVectorParagraphStyle({}, value.paragraph) : null;
5427
5642
  const frame = sanitizeVectorTextFrame(value.frame ?? { kind: "point", x: 0, y: 0 });
5428
- return { text, font, ...runs ? { runs } : {}, ...paragraph && Object.keys(paragraph).length ? { paragraph } : {}, frame };
5643
+ const orientation = vectorTextOrientation(value.orientation);
5644
+ return { text, font, ...runs ? { runs } : {}, ...paragraph && Object.keys(paragraph).length ? { paragraph } : {}, frame, ...orientation ? { orientation } : {} };
5429
5645
  }
5430
5646
  function vectorTextRange(text, start, end) {
5431
5647
  const s = start === void 0 ? 0 : start;
@@ -5567,6 +5783,13 @@ function vectorTextBoxOutline(node) {
5567
5783
  layout.text.lines.forEach((l, i) => {
5568
5784
  const line = layout.lines[i];
5569
5785
  if (!line) return;
5786
+ if (l.matrix) {
5787
+ const r = textLineRect(l);
5788
+ const o = layout.origin;
5789
+ const h = Math.max(r.height, 1);
5790
+ out.push(...polygon2([{ x: o.x + r.x, y: o.y + r.y }, { x: o.x + r.x + r.width, y: o.y + r.y }, { x: o.x + r.x + r.width, y: o.y + r.y + h }, { x: o.x + r.x, y: o.y + r.y + h }]));
5791
+ return;
5792
+ }
5570
5793
  const top = line.baseline - l.ascent;
5571
5794
  const bottom = line.baseline + l.descent;
5572
5795
  const w = Math.max(l.width, 1);
@@ -8514,7 +8737,7 @@ function vectorDocumentToSvg(doc, options = {}) {
8514
8737
  };
8515
8738
  const emitTextElement = (node, depth, attrs) => {
8516
8739
  const geometry = vectorTextGeometryNode(node);
8517
- const fields = { text: node.text, font: node.font, ...node.runs?.length ? { runs: node.runs } : {}, ...node.paragraph ? { paragraph: node.paragraph } : {}, frame: node.frame, ...node.effects?.length ? { effects: node.effects } : {} };
8740
+ const fields = { text: node.text, font: node.font, ...node.runs?.length ? { runs: node.runs } : {}, ...node.paragraph ? { paragraph: node.paragraph } : {}, frame: node.frame, ...node.orientation ? { orientation: node.orientation } : {}, ...node.effects?.length ? { effects: node.effects } : {} };
8518
8741
  let data = ` data-vector-text="${escapeAttr2(JSON.stringify(fields))}"`;
8519
8742
  if (hasVectorPaintStacks(node)) data += ` data-vector-fills="${escapeAttr2(JSON.stringify(vectorFills(node)))}" data-vector-strokes="${escapeAttr2(JSON.stringify(vectorStrokes(node)))}"`;
8520
8743
  else {
@@ -10685,13 +10908,18 @@ export {
10685
10908
  paragraphRanges,
10686
10909
  fitParagraphRuns,
10687
10910
  fontOf,
10688
- isTextSpace,
10911
+ transformTextRect,
10912
+ lineToTextSpace,
10689
10913
  layoutText,
10690
10914
  textIndexAt,
10691
10915
  caretAt,
10916
+ textSelectionQuads,
10917
+ textArrowStep,
10918
+ textLineMove,
10692
10919
  textDecorations,
10693
10920
  textRectPath,
10694
10921
  textFragmentOutline,
10922
+ drawTextFragment,
10695
10923
  cssFontFamily,
10696
10924
  cssFont,
10697
10925
  createCanvasTextShaper,
@@ -10723,6 +10951,7 @@ export {
10723
10951
  patchVectorParagraphStyle,
10724
10952
  sanitizeVectorTextFrame,
10725
10953
  normalizeVectorTextRuns,
10954
+ vectorTextOrientation,
10726
10955
  sanitizeVectorTextFields,
10727
10956
  vectorTextRange,
10728
10957
  replaceVectorText,
@@ -18,7 +18,7 @@ import {
18
18
  unionBounds,
19
19
  vectorGradientPoints,
20
20
  widthProfileAt
21
- } from "./chunk-LPIKXPBM.js";
21
+ } from "./chunk-GHCXLQ47.js";
22
22
  import {
23
23
  segmentLength,
24
24
  segmentParamAtLength,