@weasel-js/text 1.12.0 → 1.13.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.
@@ -619,6 +619,180 @@ function orderLine(paragraphs, start, count) {
619
619
  return { visual: restoreRemoved([...kept], count, (pos) => levels[pos] % 2 === 1), levels };
620
620
  }
621
621
 
622
+ // src/layout/layoutColumns.ts
623
+ var NUM = 12;
624
+ var X = 0;
625
+ var STEP = 1;
626
+ var ADVANCE = 2;
627
+ var FONT_SIZE = 3;
628
+ var BASELINE = 4;
629
+ var SRC_INDEX = 5;
630
+ var SRC_END = 6;
631
+ var CP3 = 7;
632
+ var LEVEL = 8;
633
+ var FLAGS = 9;
634
+ var RUN = 10;
635
+ var VISUAL = 11;
636
+ var DRAWS_INK = 1;
637
+ var CLUSTER_END = 2;
638
+ var REF = 4;
639
+ var TEMPLATE_LIMIT = 1 << 16;
640
+ var template = [0.5];
641
+ function doubles(n) {
642
+ if (n > TEMPLATE_LIMIT) return new Array(n).fill(0.5);
643
+ while (template.length < n) template = template.concat(template);
644
+ return template.slice(0, n);
645
+ }
646
+ var LayoutColumns = class {
647
+ num;
648
+ ref;
649
+ constructor(size) {
650
+ this.num = doubles(size * NUM);
651
+ this.ref = new Array(size * REF);
652
+ }
653
+ /** Store code point `i`: entry `e`, set at `x` on a baseline at `baselineY`,
654
+ * moving the pen `step`. */
655
+ set(i, e, level, x, step, baselineY) {
656
+ const n = i * NUM, r = i * REF;
657
+ const { num, ref } = this;
658
+ num[n + X] = x;
659
+ num[n + STEP] = step;
660
+ num[n + ADVANCE] = e.advance;
661
+ num[n + FONT_SIZE] = e.fontSize;
662
+ num[n + BASELINE] = baselineY;
663
+ num[n + SRC_INDEX] = e.srcIndex;
664
+ num[n + SRC_END] = e.srcEnd;
665
+ num[n + CP3] = e.cp;
666
+ num[n + LEVEL] = level;
667
+ num[n + RUN] = e.run;
668
+ num[n + FLAGS] = (e.drawsInk ? DRAWS_INK : 0) | (e.clusterEnd ? CLUSTER_END : 0);
669
+ ref[r] = e.resolved;
670
+ ref[r + 1] = e.metrics;
671
+ ref[r + 2] = e.font;
672
+ ref[r + 3] = e.glyph;
673
+ }
674
+ /** Record that the `slot`-th code point the pen reaches on the line starting
675
+ * at `start` is the line's `pos`-th in logical order. */
676
+ setVisual(start, slot, pos) {
677
+ this.num[(start + slot) * NUM + VISUAL] = pos;
678
+ }
679
+ /** Logical index, within its line, of the glyph at visual `slot`. */
680
+ visual(start, slot) {
681
+ return this.num[(start + slot) * NUM + VISUAL];
682
+ }
683
+ /** Fill `out` with code point `start + pos`'s placed glyph and return it.
684
+ * Every field is overwritten, so one object can walk a whole layout. */
685
+ glyph(start, pos, out) {
686
+ const i = start + pos;
687
+ const n = i * NUM, r = i * REF;
688
+ const { num, ref } = this;
689
+ out.run = num[n + RUN];
690
+ out.cell = pos;
691
+ out.cp = num[n + CP3];
692
+ out.fontSize = num[n + FONT_SIZE];
693
+ out.resolved = ref[r];
694
+ out.metrics = ref[r + 1];
695
+ out.font = ref[r + 2];
696
+ out.glyph = ref[r + 3];
697
+ out.x = num[n + X];
698
+ out.baselineY = num[n + BASELINE];
699
+ out.advance = num[n + ADVANCE];
700
+ out.step = num[n + STEP];
701
+ return out;
702
+ }
703
+ /** Code point `start + pos`'s cell, as a new object. */
704
+ cell(start, pos) {
705
+ const n = (start + pos) * NUM;
706
+ const { num } = this;
707
+ const flags = num[n + FLAGS];
708
+ return {
709
+ srcIndex: num[n + SRC_INDEX],
710
+ srcEnd: num[n + SRC_END],
711
+ cp: num[n + CP3],
712
+ x: num[n + X],
713
+ advance: num[n + STEP],
714
+ level: num[n + LEVEL],
715
+ drawsInk: (flags & DRAWS_INK) !== 0,
716
+ clusterEnd: (flags & CLUSTER_END) !== 0
717
+ };
718
+ }
719
+ };
720
+ function blankGlyph() {
721
+ return {
722
+ run: 0,
723
+ cell: 0,
724
+ cp: 0,
725
+ fontSize: 0.5,
726
+ resolved: null,
727
+ metrics: null,
728
+ font: null,
729
+ glyph: null,
730
+ x: 0.5,
731
+ baselineY: 0.5,
732
+ advance: 0.5,
733
+ step: 0.5
734
+ };
735
+ }
736
+ var LineBox = class _LineBox {
737
+ x0;
738
+ y0;
739
+ x1;
740
+ y1;
741
+ baselineY;
742
+ srcEnd;
743
+ justified;
744
+ #cols;
745
+ #start;
746
+ #count;
747
+ constructor(f, cols, start, count) {
748
+ this.x0 = f.x0;
749
+ this.y0 = f.y0;
750
+ this.x1 = f.x1;
751
+ this.y1 = f.y1;
752
+ this.baselineY = f.baselineY;
753
+ this.srcEnd = f.srcEnd;
754
+ this.justified = f.justified;
755
+ this.#cols = cols;
756
+ this.#start = start;
757
+ this.#count = count;
758
+ Object.defineProperties(this, _LineBox.#views);
759
+ }
760
+ /** The line's code points in its layout's columns, for a reader that walks
761
+ * them without building the views; null for a line built by hand. */
762
+ static span(line) {
763
+ return line instanceof _LineBox ? { cols: line.#cols, start: line.#start, count: line.#count } : null;
764
+ }
765
+ /** How many code points the line holds, without building a view. */
766
+ static count(line) {
767
+ return line instanceof _LineBox ? line.#count : line.glyphs.length;
768
+ }
769
+ static #views = {
770
+ cells: lazy("cells", (l) => {
771
+ const out = new Array(l.#count);
772
+ for (let pos = 0; pos < l.#count; pos++) out[pos] = l.#cols.cell(l.#start, pos);
773
+ return out;
774
+ }),
775
+ glyphs: lazy("glyphs", (l) => {
776
+ const out = new Array(l.#count);
777
+ for (let slot = 0; slot < l.#count; slot++) {
778
+ out[slot] = l.#cols.glyph(l.#start, l.#cols.visual(l.#start, slot), blankGlyph());
779
+ }
780
+ return out;
781
+ })
782
+ };
783
+ };
784
+ function lazy(key, build) {
785
+ return {
786
+ enumerable: true,
787
+ configurable: true,
788
+ get() {
789
+ const value = build(this);
790
+ Object.defineProperty(this, key, { value, enumerable: true, configurable: true, writable: false });
791
+ return value;
792
+ }
793
+ };
794
+ }
795
+
622
796
  // src/measure/graphemes.ts
623
797
  var segmenter;
624
798
  var NEEDS_SEGMENTING = /[\r\u0300-\uffff]/;
@@ -808,8 +982,9 @@ function aboveBaseline(e, lineHeight) {
808
982
  if (e.metrics.descent === void 0) return ascent;
809
983
  return (e.lineSize * lineHeight - ascent - e.metrics.descent * k) / 2 + ascent;
810
984
  }
811
- function breakLines(entries, maxWidth, lineHeight) {
812
- const sum = new Float64Array(entries.length + 1);
985
+ function breakLines(entries, cps, maxWidth, lineHeight) {
986
+ const sum = doubles(entries.length + 1);
987
+ sum[0] = 0;
813
988
  const tabs = [];
814
989
  entries.forEach((e, k) => {
815
990
  sum[k + 1] = sum[k] + e.kerningBefore + e.advance + e.tracking;
@@ -817,7 +992,7 @@ function breakLines(entries, maxWidth, lineHeight) {
817
992
  });
818
993
  const flatWidth = (start, end) => entries[start].advance + entries[start].tracking + sum[end] - sum[start + 1];
819
994
  const widthOf = tabs.length === 0 ? flatWidth : (start, end) => widthWithTabs(start, end, tabs, (k) => entries[k].tab, flatWidth);
820
- return wrapLines(entries.map((e) => e.cp), maxWidth, widthOf).map((span) => {
995
+ return wrapLines(cps, maxWidth, widthOf).map((span) => {
821
996
  const line = {
822
997
  start: span.start,
823
998
  entries: [],
@@ -870,66 +1045,41 @@ function alignOffset(align, alignWidth, inkWidth) {
870
1045
  const slack = alignWidth - inkWidth;
871
1046
  return align === "center" ? slack / 2 : slack;
872
1047
  }
873
- function placeLine(line, y, order, align, alignWidth, justify) {
1048
+ var NO_GAPS = /* @__PURE__ */ new Set();
1049
+ function placeLine(line, y, order, align, alignWidth, justify, cols, start) {
874
1050
  const { entries } = line;
875
1051
  const hung = hungWidth(entries);
876
1052
  const inkWidth = line.width - hung;
877
1053
  const slack = alignWidth - inkWidth;
878
- const gaps = justify && line.wrapped && Number.isFinite(alignWidth) && slack > 0 ? wordGaps(entries) : /* @__PURE__ */ new Set();
1054
+ const gaps = justify && line.wrapped && Number.isFinite(alignWidth) && slack > 0 ? wordGaps(entries) : NO_GAPS;
879
1055
  const gapExtra = gaps.size > 0 ? slack / gaps.size : 0;
880
1056
  const spread = gapExtra * gaps.size;
881
1057
  const inkLeft = spread > 0 ? 0 : alignOffset(align, alignWidth, inkWidth);
882
1058
  const hangsLeft = hung > 0 && order.levels[entries.length - 1] % 2 === 1;
883
1059
  const left = hangsLeft ? inkLeft - hung : inkLeft;
884
1060
  const lineBaselineY = y + line.ascent;
885
- const cells = new Array(entries.length);
886
- const glyphs = [];
887
1061
  let penX = left;
888
1062
  let prevPos = -1;
1063
+ let slot = 0;
889
1064
  for (const pos of order.visual) {
890
1065
  const e = entries[pos];
891
1066
  penX += prevPos < 0 ? 0 : pos === prevPos + 1 ? e.kerningBefore : prevPos === pos + 1 ? entries[prevPos].kerningBefore : 0;
892
1067
  prevPos = pos;
893
1068
  const step = e.advance + e.tracking + (gaps.has(pos) ? gapExtra : 0);
894
- cells[pos] = {
895
- srcIndex: e.srcIndex,
896
- srcEnd: e.srcEnd,
897
- cp: e.cp,
898
- x: penX,
899
- advance: step,
900
- level: order.levels[pos],
901
- drawsInk: e.drawsInk,
902
- clusterEnd: e.clusterEnd
903
- };
904
- glyphs.push({
905
- run: e.run,
906
- cell: pos,
907
- cp: e.cp,
908
- fontSize: e.fontSize,
909
- resolved: e.resolved,
910
- metrics: e.metrics,
911
- font: e.font,
912
- glyph: e.glyph,
913
- x: penX,
914
- // Positive raises, and y grows down, so the shift subtracts.
915
- baselineY: lineBaselineY - e.baselineShift,
916
- advance: e.advance,
917
- step
918
- });
1069
+ cols.set(start + pos, e, order.levels[pos], penX, step, lineBaselineY - e.baselineShift);
1070
+ cols.setVisual(start, slot++, pos);
919
1071
  penX += step;
920
1072
  }
921
1073
  return {
922
- box: {
1074
+ box: new LineBox({
923
1075
  x0: left,
924
1076
  y0: y,
925
1077
  x1: left + line.width + spread,
926
1078
  y1: y + line.height,
927
1079
  baselineY: lineBaselineY,
928
- cells,
929
- glyphs,
930
1080
  srcEnd: line.srcEnd,
931
1081
  justified: spread > 0
932
- },
1082
+ }, cols, start, entries.length),
933
1083
  inkWidth: inkWidth + spread
934
1084
  };
935
1085
  }
@@ -956,14 +1106,20 @@ function layoutRuns(runs, opts) {
956
1106
  const { bidi } = o;
957
1107
  const paragraphs = bidi ? analyzeParagraphs(bidi, cps, (i) => isHardLineBreak(cps[i]), o.direction) : null;
958
1108
  if (!bidi) warnRtlWithoutEngine(cps);
1109
+ const broken = breakLines(entries, cps, o.maxWidth, o.lineHeight);
1110
+ let size = 0;
1111
+ for (const line of broken) size += line.entries.length;
1112
+ const cols = new LayoutColumns(size);
959
1113
  const lines = [];
960
1114
  let y = 0;
961
1115
  let width = 0;
962
- for (const line of breakLines(entries, o.maxWidth, o.lineHeight)) {
1116
+ let start = 0;
1117
+ for (const line of broken) {
963
1118
  const order = orderLine(paragraphs, line.start, line.entries.length);
964
1119
  if (bidi) line.entries = mirrorEntries(line.entries, order.levels, (cp) => bidi.mirror(cp), runs);
965
1120
  const reads = o.direction !== "auto" ? o.direction : directionOfLevel(line.entries.length > 0 ? paragraphs?.baseLevel(line.start) ?? 0 : 0);
966
- const placed = placeLine(line, y, order, resolveAlign(o.align, reads), o.alignWidth, o.justify);
1121
+ const placed = placeLine(line, y, order, resolveAlign(o.align, reads), o.alignWidth, o.justify, cols, start);
1122
+ start += line.entries.length;
967
1123
  lines.push(placed.box);
968
1124
  width = Math.max(width, placed.inkWidth);
969
1125
  y += line.height;
@@ -1038,6 +1194,6 @@ function _resetLayoutCacheForTests() {
1038
1194
  structuralGeneration = -1;
1039
1195
  }
1040
1196
 
1041
- export { BREAK_ALLOWED, BREAK_MANDATORY, DEFAULT_TAB_SIZE, DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, NO_BREAK, _resetBidiWarningForTests, _resetLayoutCacheForTests, cachedLayoutRuns, fontString, graphemeCount, hangs, isHardLineBreak, layoutRuns, lineBreakOpportunities, paragraphDirection, registerBidi, resolveAlign, resolveTextStyle, tabAdvance, widthWithTabs, wrapLines };
1042
- //# sourceMappingURL=chunk-ADRQIIHS.js.map
1043
- //# sourceMappingURL=chunk-ADRQIIHS.js.map
1197
+ export { BREAK_ALLOWED, BREAK_MANDATORY, DEFAULT_TAB_SIZE, DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, LAYOUT_CACHE_STRUCTURAL_LIMIT, LAYOUT_CACHE_VARIANT_LIMIT, LineBox, NO_BREAK, _resetBidiWarningForTests, _resetLayoutCacheForTests, blankGlyph, cachedLayoutRuns, doubles, fontString, graphemeCount, hangs, isHardLineBreak, layoutRuns, lazy, lineBreakOpportunities, paragraphDirection, registerBidi, resolveAlign, resolveTextStyle, tabAdvance, widthWithTabs, wrapLines };
1198
+ //# sourceMappingURL=chunk-DOTR55MR.js.map
1199
+ //# sourceMappingURL=chunk-DOTR55MR.js.map