modern-text 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -79,21 +79,22 @@ function drawPath(options) {
79
79
  function setupView(ctx, pixelRatio, boundingBox) {
80
80
  const { left, top, width, height } = boundingBox;
81
81
  const view = ctx.canvas;
82
- view.dataset.viewbox = String(`${left} ${top} ${width} ${height}`);
82
+ view.dataset.viewBox = String(`${left} ${top} ${width} ${height}`);
83
83
  view.dataset.pixelRatio = String(pixelRatio);
84
- const canvasWidth = width + left;
85
- const canvasHeight = height + top;
84
+ const canvasWidth = width + Math.abs(left);
85
+ const canvasHeight = height + Math.abs(top);
86
86
  view.width = Math.max(1, Math.ceil(canvasWidth * pixelRatio));
87
87
  view.height = Math.max(1, Math.ceil(canvasHeight * pixelRatio));
88
88
  view.style.width = `${canvasWidth}px`;
89
89
  view.style.height = `${canvasHeight}px`;
90
- ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
90
+ ctx.clearRect(0, 0, view.width, view.height);
91
91
  ctx.scale(pixelRatio, pixelRatio);
92
+ ctx.translate(-Math.min(0, left), -Math.min(0, top));
92
93
  }
93
94
 
94
95
  function uploadColors(ctx, text) {
95
- const { paragraphs, computedStyle: style, renderBoundingBox } = text;
96
- uploadColor(style, renderBoundingBox, ctx);
96
+ const { paragraphs, computedStyle: style, glyphBox } = text;
97
+ uploadColor(style, glyphBox, ctx);
97
98
  paragraphs.forEach((paragraph) => {
98
99
  uploadColor(paragraph.computedStyle, paragraph.lineBox, ctx);
99
100
  paragraph.fragments.forEach((fragment) => {
@@ -150,7 +151,6 @@ class Character {
150
151
  __publicField$3(this, "lineBox", new modernPath2d.BoundingBox());
151
152
  __publicField$3(this, "inlineBox", new modernPath2d.BoundingBox());
152
153
  __publicField$3(this, "glyphBox");
153
- __publicField$3(this, "center");
154
154
  __publicField$3(this, "underlinePosition", 0);
155
155
  __publicField$3(this, "underlineThickness", 0);
156
156
  __publicField$3(this, "yStrikeoutPosition", 0);
@@ -159,6 +159,9 @@ class Character {
159
159
  __publicField$3(this, "centerDiviation", 0);
160
160
  __publicField$3(this, "path", new modernPath2d.Path2D());
161
161
  }
162
+ get center() {
163
+ return this.glyphBox?.center;
164
+ }
162
165
  get computedStyle() {
163
166
  return this.parent.computedStyle;
164
167
  }
@@ -288,7 +291,6 @@ class Character {
288
291
  };
289
292
  this.path = path;
290
293
  this.glyphBox = this.getGlyphBoundingBox();
291
- this.center = this.glyphBox?.getCenterPoint();
292
294
  return this;
293
295
  }
294
296
  update() {
@@ -747,8 +749,8 @@ function getTransformMatrix(a, b, c, isVertical) {
747
749
  y: c.height / b.width
748
750
  };
749
751
  }
750
- const offset = c.getCenterPoint().add(
751
- a.getCenterPoint().sub(b.getCenterPoint()).scale(scale.x, scale.y)
752
+ const offset = c.center.add(
753
+ a.center.sub(b.center).scale(scale.x, scale.y)
752
754
  ).sub({
753
755
  x: a.width / 2 * scale.x,
754
756
  y: a.height / 2 * scale.y
@@ -979,7 +981,7 @@ function render() {
979
981
  return boxes.length ? modernPath2d.BoundingBox.from(...boxes) : void 0;
980
982
  },
981
983
  render: (ctx, text) => {
982
- const { characters, paragraphs, renderBoundingBox, effects, style } = text;
984
+ const { characters, paragraphs, glyphBox, effects, style } = text;
983
985
  function fillBackground(color, box) {
984
986
  ctx.fillStyle = color;
985
987
  ctx.fillRect(box.left, box.top, box.width, box.height);
@@ -994,7 +996,7 @@ function render() {
994
996
  });
995
997
  if (effects) {
996
998
  effects.forEach((style2) => {
997
- uploadColor(style2, renderBoundingBox, ctx);
999
+ uploadColor(style2, glyphBox, ctx);
998
1000
  ctx.save();
999
1001
  const [a, c, e, b, d, f] = getTransform2D(text, style2).transpose().elements;
1000
1002
  ctx.transform(a, b, c, d, e, f);
@@ -1022,13 +1024,13 @@ function render() {
1022
1024
  });
1023
1025
  }
1024
1026
  function getTransform2D(text, style) {
1025
- const { fontSize, renderBoundingBox } = text;
1027
+ const { fontSize, glyphBox } = text;
1026
1028
  const translateX = (style.translateX ?? 0) * fontSize;
1027
1029
  const translateY = (style.translateY ?? 0) * fontSize;
1028
1030
  const PI_2 = Math.PI * 2;
1029
1031
  const skewX = (style.skewX ?? 0) / 360 * PI_2;
1030
1032
  const skewY = (style.skewY ?? 0) / 360 * PI_2;
1031
- const { left, top, width, height } = renderBoundingBox;
1033
+ const { left, top, width, height } = glyphBox;
1032
1034
  const centerX = left + width / 2;
1033
1035
  const centerY = top + height / 2;
1034
1036
  tempM1.identity();
@@ -1103,8 +1105,10 @@ class Text {
1103
1105
  __publicField(this, "needsUpdate", true);
1104
1106
  __publicField(this, "computedStyle", { ...defaultTextStyles });
1105
1107
  __publicField(this, "paragraphs", []);
1108
+ __publicField(this, "lineBox", new modernPath2d.BoundingBox());
1109
+ __publicField(this, "glyphBox", new modernPath2d.BoundingBox());
1110
+ __publicField(this, "pathBox", new modernPath2d.BoundingBox());
1106
1111
  __publicField(this, "boundingBox", new modernPath2d.BoundingBox());
1107
- __publicField(this, "renderBoundingBox", new modernPath2d.BoundingBox());
1108
1112
  __publicField(this, "parser", new Parser(this));
1109
1113
  __publicField(this, "measurer", new Measurer(this));
1110
1114
  __publicField(this, "plugins", /* @__PURE__ */ new Map());
@@ -1130,19 +1134,35 @@ class Text {
1130
1134
  }
1131
1135
  measure(dom = this.measureDom) {
1132
1136
  this.computedStyle = { ...defaultTextStyles, ...this.style };
1133
- const oldParagraphs = this.paragraphs;
1134
- const oldRenderBoundingBox = this.renderBoundingBox;
1137
+ const old = {
1138
+ paragraphs: this.paragraphs,
1139
+ lineBox: this.lineBox,
1140
+ glyphBox: this.glyphBox,
1141
+ pathBox: this.pathBox,
1142
+ boundingBox: this.boundingBox
1143
+ };
1135
1144
  this.paragraphs = this.parser.parse();
1136
1145
  const result = this.measurer.measure(dom);
1137
- const characters = this.characters;
1138
- characters.forEach((c) => c.update());
1146
+ this.paragraphs = result.paragraphs;
1147
+ this.lineBox = result.boundingBox;
1148
+ this.characters.forEach((c) => {
1149
+ c.update();
1150
+ });
1139
1151
  const plugins = [...this.plugins.values()];
1140
1152
  plugins.sort((a, b) => (a.updateOrder ?? 0) - (b.updateOrder ?? 0)).forEach((plugin) => {
1141
1153
  plugin.update?.(this);
1142
1154
  });
1155
+ this.updateGlyphBox().updatePathBox().updateBoundingBox();
1156
+ for (const key in old) {
1157
+ result[key] = this[key];
1158
+ this[key] = old[key];
1159
+ }
1160
+ return result;
1161
+ }
1162
+ updateGlyphBox() {
1143
1163
  const min = modernPath2d.Vector2.MAX;
1144
1164
  const max = modernPath2d.Vector2.MIN;
1145
- characters.forEach((c) => {
1165
+ this.characters.forEach((c) => {
1146
1166
  if (!c.getGlyphMinMax(min, max)) {
1147
1167
  const { inlineBox } = c;
1148
1168
  const a = new modernPath2d.Vector2(inlineBox.left, inlineBox.top);
@@ -1151,30 +1171,47 @@ class Text {
1151
1171
  max.max(a, b);
1152
1172
  }
1153
1173
  });
1154
- this.renderBoundingBox = new modernPath2d.BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
1155
- this.renderBoundingBox = modernPath2d.BoundingBox.from(
1156
- this.renderBoundingBox,
1174
+ this.glyphBox = new modernPath2d.BoundingBox(
1175
+ min.x,
1176
+ min.y,
1177
+ max.x - min.x,
1178
+ max.y - min.y
1179
+ );
1180
+ return this;
1181
+ }
1182
+ updatePathBox() {
1183
+ const plugins = [...this.plugins.values()];
1184
+ this.pathBox = modernPath2d.BoundingBox.from(
1185
+ this.glyphBox,
1157
1186
  ...plugins.map((plugin) => {
1158
- if (plugin.getBoundingBox) {
1159
- return plugin.getBoundingBox(this);
1160
- }
1161
- return modernPath2d.getPathsBoundingBox(plugin.paths ?? []);
1187
+ return plugin.getBoundingBox ? plugin.getBoundingBox(this) : modernPath2d.getPathsBoundingBox(plugin.paths ?? []);
1162
1188
  }).filter(Boolean)
1163
1189
  );
1164
- result.renderBoundingBox = this.renderBoundingBox;
1165
- this.paragraphs = oldParagraphs;
1166
- this.renderBoundingBox = oldRenderBoundingBox;
1167
- return result;
1190
+ return this;
1191
+ }
1192
+ updateBoundingBox() {
1193
+ const { lineBox, glyphBox, pathBox } = this;
1194
+ const left = pathBox.left + lineBox.left - glyphBox.left;
1195
+ const top = pathBox.top + lineBox.top - glyphBox.top;
1196
+ const right = pathBox.right + Math.max(0, lineBox.right - glyphBox.right);
1197
+ const bottom = pathBox.bottom + Math.max(0, lineBox.bottom - glyphBox.bottom);
1198
+ this.boundingBox = new modernPath2d.BoundingBox(
1199
+ left,
1200
+ top,
1201
+ right - left,
1202
+ bottom - top
1203
+ );
1204
+ return this;
1168
1205
  }
1169
1206
  requestUpdate() {
1170
1207
  this.needsUpdate = true;
1171
1208
  return this;
1172
1209
  }
1173
1210
  update() {
1174
- const { paragraphs, boundingBox, renderBoundingBox } = this.measure();
1175
- this.paragraphs = paragraphs;
1176
- this.boundingBox = boundingBox;
1177
- this.renderBoundingBox = renderBoundingBox;
1211
+ const result = this.measure();
1212
+ for (const key in result) {
1213
+ this[key] = result[key];
1214
+ }
1178
1215
  return this;
1179
1216
  }
1180
1217
  render(options) {
@@ -1186,7 +1223,7 @@ class Text {
1186
1223
  if (this.needsUpdate) {
1187
1224
  this.update();
1188
1225
  }
1189
- setupView(ctx, pixelRatio, this.renderBoundingBox);
1226
+ setupView(ctx, pixelRatio, this.boundingBox);
1190
1227
  uploadColors(ctx, this);
1191
1228
  const plugins = [...this.plugins.values()];
1192
1229
  plugins.sort((a, b) => (a.renderOrder ?? 0) - (b.renderOrder ?? 0)).forEach((plugin) => {
package/dist/index.d.cts CHANGED
@@ -117,7 +117,6 @@ declare class Character {
117
117
  lineBox: BoundingBox;
118
118
  inlineBox: BoundingBox;
119
119
  glyphBox: BoundingBox | undefined;
120
- center: Vector2 | undefined;
121
120
  underlinePosition: number;
122
121
  underlineThickness: number;
123
122
  yStrikeoutPosition: number;
@@ -125,6 +124,7 @@ declare class Character {
125
124
  baseline: number;
126
125
  centerDiviation: number;
127
126
  path: Path2D;
127
+ get center(): Vector2 | undefined;
128
128
  get computedStyle(): TextStyle;
129
129
  get isVertical(): boolean;
130
130
  get fontSize(): number;
@@ -155,6 +155,18 @@ declare class Paragraph {
155
155
  addFragment(content: string, style?: Partial<TextStyle>): Fragment;
156
156
  }
157
157
 
158
+ type PromiseLike<T> = T | Promise<T>;
159
+ interface Plugin {
160
+ name: string;
161
+ paths?: Path2D[];
162
+ getBoundingBox?: (text: Text) => BoundingBox | undefined;
163
+ updateOrder?: number;
164
+ update?: (text: Text) => PromiseLike<void>;
165
+ renderOrder?: number;
166
+ render?: (ctx: CanvasRenderingContext2D, text: Text) => PromiseLike<void>;
167
+ }
168
+ declare function definePlugin(options: Plugin): Plugin;
169
+
158
170
  interface MeasuredParagraph {
159
171
  paragraphIndex: number;
160
172
  left: number;
@@ -214,18 +226,6 @@ declare class Measurer {
214
226
  measure(dom?: HTMLElement): MeasureDomResult;
215
227
  }
216
228
 
217
- type PromiseLike<T> = T | Promise<T>;
218
- interface Plugin {
219
- name: string;
220
- paths?: Path2D[];
221
- getBoundingBox?: (text: Text) => BoundingBox | undefined;
222
- updateOrder?: number;
223
- update?: (text: Text) => PromiseLike<void>;
224
- renderOrder?: number;
225
- render?: (ctx: CanvasRenderingContext2D, text: Text) => PromiseLike<void>;
226
- }
227
- declare function definePlugin(options: Plugin): Plugin;
228
-
229
229
  declare class Parser {
230
230
  protected _text: Text;
231
231
  constructor(_text: Text);
@@ -242,9 +242,13 @@ interface TextOptions {
242
242
  measureDom?: HTMLElement;
243
243
  effects?: Partial<TextStyle>[];
244
244
  }
245
- type MeasureResult = MeasureDomResult & {
246
- renderBoundingBox: BoundingBox;
247
- };
245
+ interface MeasureResult {
246
+ paragraphs: Paragraph[];
247
+ lineBox: BoundingBox;
248
+ glyphBox: BoundingBox;
249
+ pathBox: BoundingBox;
250
+ boundingBox: BoundingBox;
251
+ }
248
252
  declare const defaultTextStyles: TextStyle;
249
253
  declare class Text {
250
254
  content: TextContent;
@@ -254,8 +258,10 @@ declare class Text {
254
258
  needsUpdate: boolean;
255
259
  computedStyle: TextStyle;
256
260
  paragraphs: Paragraph[];
261
+ lineBox: BoundingBox;
262
+ glyphBox: BoundingBox;
263
+ pathBox: BoundingBox;
257
264
  boundingBox: BoundingBox;
258
- renderBoundingBox: BoundingBox;
259
265
  parser: Parser;
260
266
  measurer: Measurer;
261
267
  plugins: Map<string, Plugin>;
@@ -265,6 +271,9 @@ declare class Text {
265
271
  constructor(options?: TextOptions);
266
272
  use(plugin: Plugin): this;
267
273
  measure(dom?: HTMLElement | undefined): MeasureResult;
274
+ updateGlyphBox(): this;
275
+ updatePathBox(): this;
276
+ updateBoundingBox(): this;
268
277
  requestUpdate(): this;
269
278
  update(): this;
270
279
  render(options: TextRenderOptions): this;
package/dist/index.d.mts CHANGED
@@ -117,7 +117,6 @@ declare class Character {
117
117
  lineBox: BoundingBox;
118
118
  inlineBox: BoundingBox;
119
119
  glyphBox: BoundingBox | undefined;
120
- center: Vector2 | undefined;
121
120
  underlinePosition: number;
122
121
  underlineThickness: number;
123
122
  yStrikeoutPosition: number;
@@ -125,6 +124,7 @@ declare class Character {
125
124
  baseline: number;
126
125
  centerDiviation: number;
127
126
  path: Path2D;
127
+ get center(): Vector2 | undefined;
128
128
  get computedStyle(): TextStyle;
129
129
  get isVertical(): boolean;
130
130
  get fontSize(): number;
@@ -155,6 +155,18 @@ declare class Paragraph {
155
155
  addFragment(content: string, style?: Partial<TextStyle>): Fragment;
156
156
  }
157
157
 
158
+ type PromiseLike<T> = T | Promise<T>;
159
+ interface Plugin {
160
+ name: string;
161
+ paths?: Path2D[];
162
+ getBoundingBox?: (text: Text) => BoundingBox | undefined;
163
+ updateOrder?: number;
164
+ update?: (text: Text) => PromiseLike<void>;
165
+ renderOrder?: number;
166
+ render?: (ctx: CanvasRenderingContext2D, text: Text) => PromiseLike<void>;
167
+ }
168
+ declare function definePlugin(options: Plugin): Plugin;
169
+
158
170
  interface MeasuredParagraph {
159
171
  paragraphIndex: number;
160
172
  left: number;
@@ -214,18 +226,6 @@ declare class Measurer {
214
226
  measure(dom?: HTMLElement): MeasureDomResult;
215
227
  }
216
228
 
217
- type PromiseLike<T> = T | Promise<T>;
218
- interface Plugin {
219
- name: string;
220
- paths?: Path2D[];
221
- getBoundingBox?: (text: Text) => BoundingBox | undefined;
222
- updateOrder?: number;
223
- update?: (text: Text) => PromiseLike<void>;
224
- renderOrder?: number;
225
- render?: (ctx: CanvasRenderingContext2D, text: Text) => PromiseLike<void>;
226
- }
227
- declare function definePlugin(options: Plugin): Plugin;
228
-
229
229
  declare class Parser {
230
230
  protected _text: Text;
231
231
  constructor(_text: Text);
@@ -242,9 +242,13 @@ interface TextOptions {
242
242
  measureDom?: HTMLElement;
243
243
  effects?: Partial<TextStyle>[];
244
244
  }
245
- type MeasureResult = MeasureDomResult & {
246
- renderBoundingBox: BoundingBox;
247
- };
245
+ interface MeasureResult {
246
+ paragraphs: Paragraph[];
247
+ lineBox: BoundingBox;
248
+ glyphBox: BoundingBox;
249
+ pathBox: BoundingBox;
250
+ boundingBox: BoundingBox;
251
+ }
248
252
  declare const defaultTextStyles: TextStyle;
249
253
  declare class Text {
250
254
  content: TextContent;
@@ -254,8 +258,10 @@ declare class Text {
254
258
  needsUpdate: boolean;
255
259
  computedStyle: TextStyle;
256
260
  paragraphs: Paragraph[];
261
+ lineBox: BoundingBox;
262
+ glyphBox: BoundingBox;
263
+ pathBox: BoundingBox;
257
264
  boundingBox: BoundingBox;
258
- renderBoundingBox: BoundingBox;
259
265
  parser: Parser;
260
266
  measurer: Measurer;
261
267
  plugins: Map<string, Plugin>;
@@ -265,6 +271,9 @@ declare class Text {
265
271
  constructor(options?: TextOptions);
266
272
  use(plugin: Plugin): this;
267
273
  measure(dom?: HTMLElement | undefined): MeasureResult;
274
+ updateGlyphBox(): this;
275
+ updatePathBox(): this;
276
+ updateBoundingBox(): this;
268
277
  requestUpdate(): this;
269
278
  update(): this;
270
279
  render(options: TextRenderOptions): this;
package/dist/index.d.ts CHANGED
@@ -117,7 +117,6 @@ declare class Character {
117
117
  lineBox: BoundingBox;
118
118
  inlineBox: BoundingBox;
119
119
  glyphBox: BoundingBox | undefined;
120
- center: Vector2 | undefined;
121
120
  underlinePosition: number;
122
121
  underlineThickness: number;
123
122
  yStrikeoutPosition: number;
@@ -125,6 +124,7 @@ declare class Character {
125
124
  baseline: number;
126
125
  centerDiviation: number;
127
126
  path: Path2D;
127
+ get center(): Vector2 | undefined;
128
128
  get computedStyle(): TextStyle;
129
129
  get isVertical(): boolean;
130
130
  get fontSize(): number;
@@ -155,6 +155,18 @@ declare class Paragraph {
155
155
  addFragment(content: string, style?: Partial<TextStyle>): Fragment;
156
156
  }
157
157
 
158
+ type PromiseLike<T> = T | Promise<T>;
159
+ interface Plugin {
160
+ name: string;
161
+ paths?: Path2D[];
162
+ getBoundingBox?: (text: Text) => BoundingBox | undefined;
163
+ updateOrder?: number;
164
+ update?: (text: Text) => PromiseLike<void>;
165
+ renderOrder?: number;
166
+ render?: (ctx: CanvasRenderingContext2D, text: Text) => PromiseLike<void>;
167
+ }
168
+ declare function definePlugin(options: Plugin): Plugin;
169
+
158
170
  interface MeasuredParagraph {
159
171
  paragraphIndex: number;
160
172
  left: number;
@@ -214,18 +226,6 @@ declare class Measurer {
214
226
  measure(dom?: HTMLElement): MeasureDomResult;
215
227
  }
216
228
 
217
- type PromiseLike<T> = T | Promise<T>;
218
- interface Plugin {
219
- name: string;
220
- paths?: Path2D[];
221
- getBoundingBox?: (text: Text) => BoundingBox | undefined;
222
- updateOrder?: number;
223
- update?: (text: Text) => PromiseLike<void>;
224
- renderOrder?: number;
225
- render?: (ctx: CanvasRenderingContext2D, text: Text) => PromiseLike<void>;
226
- }
227
- declare function definePlugin(options: Plugin): Plugin;
228
-
229
229
  declare class Parser {
230
230
  protected _text: Text;
231
231
  constructor(_text: Text);
@@ -242,9 +242,13 @@ interface TextOptions {
242
242
  measureDom?: HTMLElement;
243
243
  effects?: Partial<TextStyle>[];
244
244
  }
245
- type MeasureResult = MeasureDomResult & {
246
- renderBoundingBox: BoundingBox;
247
- };
245
+ interface MeasureResult {
246
+ paragraphs: Paragraph[];
247
+ lineBox: BoundingBox;
248
+ glyphBox: BoundingBox;
249
+ pathBox: BoundingBox;
250
+ boundingBox: BoundingBox;
251
+ }
248
252
  declare const defaultTextStyles: TextStyle;
249
253
  declare class Text {
250
254
  content: TextContent;
@@ -254,8 +258,10 @@ declare class Text {
254
258
  needsUpdate: boolean;
255
259
  computedStyle: TextStyle;
256
260
  paragraphs: Paragraph[];
261
+ lineBox: BoundingBox;
262
+ glyphBox: BoundingBox;
263
+ pathBox: BoundingBox;
257
264
  boundingBox: BoundingBox;
258
- renderBoundingBox: BoundingBox;
259
265
  parser: Parser;
260
266
  measurer: Measurer;
261
267
  plugins: Map<string, Plugin>;
@@ -265,6 +271,9 @@ declare class Text {
265
271
  constructor(options?: TextOptions);
266
272
  use(plugin: Plugin): this;
267
273
  measure(dom?: HTMLElement | undefined): MeasureResult;
274
+ updateGlyphBox(): this;
275
+ updatePathBox(): this;
276
+ updateBoundingBox(): this;
268
277
  requestUpdate(): this;
269
278
  update(): this;
270
279
  render(options: TextRenderOptions): this;