notations 1.0.1 → 1.0.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/lib/esm/shapes.js CHANGED
@@ -171,6 +171,9 @@ export class AtomView extends Shape {
171
171
  this.depth = 0;
172
172
  this.roleIndex = 0;
173
173
  }
174
+ get glyphOffset() {
175
+ return 0;
176
+ }
174
177
  }
175
178
  export class LeafAtomView extends AtomView {
176
179
  constructor(leafAtom) {
@@ -195,6 +198,8 @@ export class GroupView extends AtomView {
195
198
  this.defaultToNotes = true;
196
199
  this.needsLayout = true;
197
200
  this.scaleFactor = 1.0;
201
+ this.showContinuationMarkers = true;
202
+ this.continuationMarkerElements = [];
198
203
  this.atomSpacing = 5;
199
204
  this.setStyles(config || {});
200
205
  }
@@ -222,13 +227,21 @@ export class GroupView extends AtomView {
222
227
  return TSU.DOM.svgBBox(this.groupElement);
223
228
  }
224
229
  refreshMinSize() {
225
- let totalWidth = 0;
226
230
  let maxHeight = 0;
227
- this.atomViews.forEach((av, index) => {
231
+ let minWidthPerDuration = 0;
232
+ this.atomViews.forEach((av) => {
228
233
  const ms = av.minSize;
229
- totalWidth += ms.width + this.atomSpacing;
234
+ const dur = av.totalDuration;
235
+ if (!dur.isZero) {
236
+ const durValue = dur.num / dur.den;
237
+ const widthPerDur = (ms.width + this.atomSpacing) / durValue;
238
+ minWidthPerDuration = Math.max(minWidthPerDuration, widthPerDur);
239
+ }
230
240
  maxHeight = Math.max(maxHeight, ms.height);
231
241
  });
242
+ const totalDuration = this.group.totalChildDuration;
243
+ const totalDurValue = totalDuration.num / totalDuration.den;
244
+ const totalWidth = minWidthPerDuration * totalDurValue;
232
245
  return new TSU.Geom.Size(totalWidth * this.scaleFactor, maxHeight * this.scaleFactor);
233
246
  }
234
247
  updateBounds(x, y, w, h) {
@@ -240,32 +253,53 @@ export class GroupView extends AtomView {
240
253
  transform += " scale(" + this.scaleFactor + ")";
241
254
  }
242
255
  this.groupElement.setAttribute("transform", transform);
243
- let currX = 0;
244
256
  const currY = 0;
245
- if (true) {
246
- this.atomViews.forEach((av, index) => {
247
- av.setBounds(currX, currY, null, null, true);
248
- currX += this.atomSpacing + av.minSize.width;
249
- });
250
- }
251
- else {
252
- const totalDur = this.group.totalChildDuration;
253
- let currTime = ZERO;
254
- this.atomViews.forEach((av, index) => {
255
- const newX = currTime.timesNum(this.minSize.width).divby(this.group.duration).floor;
256
- if (newX >= currX) {
257
- currX = newX;
257
+ const totalDur = this.group.totalChildDuration;
258
+ const unscaledMinWidth = this.minSize.width / this.scaleFactor;
259
+ const groupWidth = this.hasWidth ? this.width / this.scaleFactor : unscaledMinWidth;
260
+ this.clearContinuationMarkers();
261
+ let currTime = ZERO;
262
+ this.atomViews.forEach((av, index) => {
263
+ const glyphX = totalDur.isZero ? 0 : currTime.timesNum(groupWidth).divby(totalDur).floor;
264
+ const xPos = Math.max(0, glyphX - av.glyphOffset);
265
+ av.setBounds(xPos, currY, null, null, true);
266
+ if (this.showContinuationMarkers && !totalDur.isZero) {
267
+ const atomDur = av.totalDuration;
268
+ const durValue = atomDur.num / atomDur.den;
269
+ if (durValue > 1) {
270
+ const numMarkers = Math.floor(durValue) - 1;
271
+ for (let i = 1; i <= numMarkers; i++) {
272
+ const markerTime = currTime.plus(atomDur.timesNum(i).divbyNum(Math.floor(durValue)));
273
+ const markerX = markerTime.timesNum(groupWidth).divby(totalDur).floor;
274
+ this.renderContinuationMarker(markerX, currY);
275
+ }
258
276
  }
259
- av.setBounds(currX, currY, null, null, true);
260
- currX += this.atomSpacing + av.minSize.width;
261
- currTime = currTime.plus(av.totalDuration);
262
- });
263
- }
277
+ }
278
+ currTime = currTime.plus(av.totalDuration);
279
+ });
264
280
  this.invalidateBounds();
265
281
  for (const e of this.embelishments)
266
282
  e.refreshLayout();
267
283
  this.invalidateBounds();
268
284
  }
285
+ clearContinuationMarkers() {
286
+ for (const el of this.continuationMarkerElements) {
287
+ el.remove();
288
+ }
289
+ this.continuationMarkerElements = [];
290
+ }
291
+ renderContinuationMarker(x, y) {
292
+ const marker = TSU.DOM.createSVGNode("text", {
293
+ parent: this.groupElement,
294
+ attrs: {
295
+ class: "continuationMarker",
296
+ x: x.toString(),
297
+ y: y.toString(),
298
+ },
299
+ text: ",",
300
+ });
301
+ this.continuationMarkerElements.push(marker);
302
+ }
269
303
  get embelishments() {
270
304
  if (!this._embelishments) {
271
305
  this._embelishments = this.createEmbelishments();
@@ -278,6 +312,8 @@ export class GroupView extends AtomView {
278
312
  setStyles(config) {
279
313
  if ("atomSpacing" in config)
280
314
  this.atomSpacing = config.atomSpacing;
315
+ if ("showContinuationMarkers" in config)
316
+ this.showContinuationMarkers = config.showContinuationMarkers;
281
317
  this.needsLayout = true;
282
318
  }
283
319
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shapes.js","sourceRoot":"","sources":["../../src/shapes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAyB,MAAM,QAAQ,CAAC;AASrD,MAAM,OAAgB,KAAK;IAA3B;QAEW,YAAO,GAAW,KAAK,CAAC,SAAS,EAAE,CAAC;QASnC,OAAE,GAAkB,IAAI,CAAC;QACzB,OAAE,GAAkB,IAAI,CAAC;QACzB,WAAM,GAAkB,IAAI,CAAC;QAC7B,YAAO,GAAkB,IAAI,CAAC;QAG9B,gBAAW,GAAiB,IAAI,CAAC;QAE3C,aAAQ,GAAY,EAAE,CAAC;IAmPzB,CAAC;IA7OC,IAAI,IAAI;QACN,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAMD,IAAI,OAAO;QACT,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACxC,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAmCD,gBAAgB;QACd,IAAI,CAAC,QAAQ,GAAG,IAAgC,CAAC;QACjD,IAAI,CAAC,KAAK,GAAG,IAAgC,CAAC;IAChD,CAAC;IAgBD,SAAS,CACP,CAAgB,EAChB,CAAgB,EAChB,CAAgB,EAChB,CAAgB,EAChB,WAAW,GAAG,KAAK;QAEnB,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACd,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACb,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACd,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACb,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;YACd,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACd,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACb,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACrB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACd,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACb,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QACD,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACvD,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;gBACd,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;YACf,CAAC;QACH,CAAC;QACD,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;gBACd,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;YACf,CAAC;QACH,CAAC;QACD,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;gBACd,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACrB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACnB,CAAC;QACH,CAAC;QACD,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;gBACd,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;YACpB,CAAC;QACH,CAAC;QACD,IAAI,WAAW;YAAE,IAAI,CAAC,aAAa,EAAE,CAAC;QAEtC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC;IAKD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC;IAKD,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC;IAKD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC;IAKD,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,CAAC;IAKD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACtB,CAAC;IAKD,IAAI,CAAC,CAAC,CAAgB;QAIpB,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;IAKD,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,CAAC,CAAC;IACX,CAAC;IAKD,IAAI,CAAC,CAAC,CAAgB;QACpB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;IAKD,IAAI,KAAK;QACP,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QAC5C,OAAO,CAAC,CAAC;IACX,CAAC;IAKD,IAAI,KAAK,CAAC,CAAgB;QACxB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;IAKD,IAAI,MAAM;QACR,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;QAC9C,OAAO,CAAC,CAAC;IACX,CAAC;IAKD,IAAI,MAAM,CAAC,CAAgB;QACzB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC;IAOD,aAAa;IAEb,CAAC;;AApQc,eAAS,GAAG,CAAC,AAAJ,CAAK;AA0Q/B,MAAM,OAAgB,YAAa,SAAQ,KAAK;CAAG;AAMnD,MAAM,OAAO,YAAgE,SAAQ,KAAK;IAKxF,YAA4B,OAAU;QACpC,KAAK,EAAE,CAAC;QADkB,YAAO,GAAP,OAAO,CAAG;IAEtC,CAAC;IAMS,WAAW;QACnB,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAMS,cAAc;QACtB,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAUS,YAAY,CACpB,CAAgB,EAChB,CAAgB,EAChB,CAAgB,EAChB,CAAgB;QAEhB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACtB,CAAC;IAMD,aAAa;QACX,IAAI,IAAI,CAAC,IAAI;YAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5D,IAAI,IAAI,CAAC,IAAI;YAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC;CACF;AAMD,MAAM,OAAgB,QAAS,SAAQ,KAAK;IAA5C;;QAEE,UAAK,GAAG,CAAC,CAAC;QAEV,cAAS,GAAG,CAAC,CAAC;IA2BhB,CAAC;CAAA;AAKD,MAAM,OAAgB,YAAa,SAAQ,QAAQ;IAKjD,YAAmB,QAAkB;QACnC,KAAK,EAAE,CAAC;QADS,aAAQ,GAAR,QAAQ,CAAU;IAErC,CAAC;IAKD,MAAM;QACJ,OAAO,IAAI,CAAC;IACd,CAAC;IAKD,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;IAC5B,CAAC;IAKD,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAChC,CAAC;CACF;AAKD,MAAM,OAAgB,SAAU,SAAQ,QAAQ;IAoB9C,YACS,KAAY,EACnB,MAAY;QAEZ,KAAK,EAAE,CAAC;QAHD,UAAK,GAAL,KAAK,CAAO;QAfX,cAAS,GAAe,EAAE,CAAC;QAGrC,mBAAc,GAAG,IAAI,CAAC;QAEtB,gBAAW,GAAG,IAAI,CAAC;QAEnB,gBAAW,GAAG,GAAG,CAAC;QAYhB,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC;QACrB,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC/B,CAAC;IAKD,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC;IACvC,CAAC;IAMD,cAAc,CAAC,MAA0B;QACvC,IAAI,CAAC,YAAY,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,EAAE;YAC7C,MAAM,EAAE,MAAM;YACd,KAAK,EAAE;gBACL,KAAK,EAAE,eAAe;gBACtB,EAAE,EAAE,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI;aACtC;SACF,CAAC,CAAC;QAGH,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAC3C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAC1B,CAAC;IAKD,MAAM;QACJ,OAAO,KAAK,CAAC;IACf,CAAC;IAMS,WAAW;QACnB,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5C,CAAC;IAMS,cAAc;QACtB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE;YACnC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC;YACtB,UAAU,IAAI,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC;YAC1C,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IACxF,CAAC;IAiBS,YAAY,CACpB,CAAgB,EAChB,CAAgB,EAChB,CAAgB,EAChB,CAAgB;QAEhB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACtB,CAAC;IAMD,aAAa;QACX,IAAI,SAAS,GAAG,YAAY,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC;QAC3D,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YACzB,SAAS,IAAI,SAAS,GAAG,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC;QAClD,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;QAOvD,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,KAAK,GAAG,CAAC,CAAC;QAEhB,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE;gBACnC,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC7C,KAAK,IAAI,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;YAC/C,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YAIN,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC;YAC/C,IAAI,QAAQ,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE;gBACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC;gBACpF,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;oBAClB,KAAK,GAAG,IAAI,CAAC;gBACf,CAAC;gBACD,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC7C,KAAK,IAAI,IAAI,CAAC,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;gBAC7C,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC;YAC7C,CAAC,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACxB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,aAAa;YAAE,CAAC,CAAC,aAAa,EAAE,CAAC;QACtD,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAC1B,CAAC;IAKD,IAAI,aAAa;QACf,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACnD,CAAC;QACD,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAMS,mBAAmB;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IAMD,SAAS,CAAC,MAAW;QACnB,IAAI,aAAa,IAAI,MAAM;YAAE,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;QACnE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC1B,CAAC;CACF","sourcesContent":["import * as TSU from \"@panyam/tsutils\";\nimport { ZERO, Atom, LeafAtom, Group } from \"./core\";\n\n/**\n * Base class for all renderable objects.\n *\n * Shape caches properties like bounding boxes to improve performance,\n * since bounding box calculations can be expensive. This also allows\n * testing layouts and positioning without worrying about implementation details.\n */\nexport abstract class Shape {\n private static idCounter = 0;\n readonly shapeId: number = Shape.idCounter++;\n\n /**\n * Note that x and y coordinates are not always the x and y coordinates\n * of the bounding box.\n * E.g., a circle's x and y coordinates are its center point and not the\n * top left corner.\n * These \"main\" coordinates are referred to as control coordinates.\n */\n protected _x: number | null = null;\n protected _y: number | null = null;\n protected _width: number | null = null;\n protected _height: number | null = null;\n protected _bbox: TSU.Geom.Rect;\n protected _minSize: TSU.Geom.Size;\n protected parentShape: Shape | null = null;\n /** Child shapes contained within this shape */\n children: Shape[] = [];\n\n /**\n * Gets the bounding box of this shape.\n * Calculates it if it hasn't been calculated yet.\n */\n get bbox(): TSU.Geom.Rect {\n if (!this._bbox) {\n this._bbox = this.refreshBBox();\n }\n return this._bbox;\n }\n\n /**\n * Gets the minimum size of this shape.\n * This is usually the size of the bounding box.\n */\n get minSize(): TSU.Geom.Size {\n if (!this._minSize) {\n this._minSize = this.refreshMinSize();\n }\n return this._minSize;\n }\n\n /**\n * Refreshes the bounding box of this shape.\n * Called when the shape knows the bbox it is tracking cannot be trusted\n * and has to be refreshed by calling native methods.\n * @returns The refreshed bounding box\n */\n protected abstract refreshBBox(): TSU.Geom.Rect;\n\n /**\n * Refreshes the minimum size of this shape.\n * @returns The refreshed minimum size\n */\n protected abstract refreshMinSize(): TSU.Geom.Size;\n\n /**\n * Updates the bounds of this shape.\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @returns The updated bounds values\n */\n protected abstract updateBounds(\n x: null | number,\n y: null | number,\n w: null | number,\n h: null | number,\n ): [number | null, number | null, number | null, number | null];\n\n /**\n * Invalidates the cached bounds of this shape.\n * Forces recalculation of bounding box and minimum size.\n */\n invalidateBounds(): void {\n this._minSize = null as unknown as TSU.Geom.Size;\n this._bbox = null as unknown as TSU.Geom.Rect;\n }\n\n /**\n * Sets the bounds of this shape.\n *\n * Note that null and NaN are valid values and mean the following:\n * - null: Don't change the value\n * - NaN: Set the value to null (use the bounding box's value)\n *\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @param applyLayout Whether to apply layout immediately\n * @returns The updated bounds values\n */\n setBounds(\n x: number | null,\n y: number | null,\n w: number | null,\n h: number | null,\n applyLayout = false,\n ): [number | null, number | null, number | null, number | null] {\n if (x != null) {\n if (isNaN(x)) {\n this._x = null;\n } else {\n this._x = x;\n }\n }\n if (y != null) {\n if (isNaN(y)) {\n this._y = null;\n } else {\n this._y = y;\n }\n }\n if (w != null) {\n if (isNaN(w)) {\n this._width = null;\n } else {\n this._width = w;\n }\n }\n if (h != null) {\n if (isNaN(h)) {\n this._height = null;\n } else {\n this._height = h;\n }\n }\n const [nx, ny, nw, nh] = this.updateBounds(x, y, w, h);\n if (nx != null) {\n if (isNaN(nx)) {\n this._x = null;\n } else {\n this._x = nx;\n }\n }\n if (ny != null) {\n if (isNaN(ny)) {\n this._y = null;\n } else {\n this._y = ny;\n }\n }\n if (nw != null) {\n if (isNaN(nw)) {\n this._width = null;\n } else {\n this._width = nw;\n }\n }\n if (nh != null) {\n if (isNaN(nh)) {\n this._height = null;\n } else {\n this._height = nh;\n }\n }\n if (applyLayout) this.refreshLayout();\n // this.resetBBox();\n return [nx, ny, nw, nh];\n }\n\n /**\n * Checks if this shape has an explicit x coordinate.\n */\n get hasX(): boolean {\n return this._x != null && !isNaN(this._x);\n }\n\n /**\n * Checks if this shape has an explicit y coordinate.\n */\n get hasY(): boolean {\n return this._y != null && !isNaN(this._y);\n }\n\n /**\n * Checks if this shape has an explicit width.\n */\n get hasWidth(): boolean {\n return this._width != null && !isNaN(this._width);\n }\n\n /**\n * Checks if this shape has an explicit height.\n */\n get hasHeight(): boolean {\n return this._height != null && !isNaN(this._height);\n }\n\n /**\n * Gets the x coordinate within the parent's coordinate system.\n */\n get x(): number {\n return this._x || 0;\n }\n\n /**\n * Sets the x coordinate within the parent's coordinate system.\n */\n set x(x: number | null) {\n // Here a manual x is being set - how does this interfere with the bounding box?\n // We should _x to the new value to indicate a manual value was set.\n // and reset bbox so that based on this x a new bbox may need to be calculated\n this.setBounds(x == null ? NaN : x, null, null, null);\n }\n\n /**\n * Gets the y coordinate within the parent's coordinate system.\n */\n get y(): number {\n if (this._y != null) return this._y;\n return 0; // this.bbox.y;\n }\n\n /**\n * Sets the y coordinate within the parent's coordinate system.\n */\n set y(y: number | null) {\n this.setBounds(null, y == null ? NaN : y, null, null);\n }\n\n /**\n * Gets the width of this shape.\n */\n get width(): number {\n if (this._width != null) return this._width;\n return 0; // this.bbox.width;\n }\n\n /**\n * Sets the width of this shape.\n */\n set width(w: number | null) {\n this.setBounds(null, null, w == null ? NaN : w, null);\n }\n\n /**\n * Gets the height of this shape.\n */\n get height(): number {\n if (this._height != null) return this._height;\n return 0; // this.bbox.height;\n }\n\n /**\n * Sets the height of this shape.\n */\n set height(h: number | null) {\n this.setBounds(null, null, null, h == null ? NaN : h);\n }\n\n /**\n * Refreshes the layout of this shape.\n * Called when bounds or other properties have changed to give the shape an\n * opportunity to layout its children. For shapes with no children this is a no-op.\n */\n refreshLayout(): void {\n // throw new Error(\"Implement this\");\n }\n}\n\n/**\n * Represents an embellishment applied to a musical element.\n */\nexport abstract class Embelishment extends Shape {}\n\n/**\n * A shape that wraps an SVG element.\n * ElementShape provides the base class for all shapes that are rendered as SVG elements.\n */\nexport class ElementShape<T extends SVGGraphicsElement = SVGGraphicsElement> extends Shape {\n /**\n * Creates a new ElementShape.\n * @param element The SVG element this shape wraps\n */\n constructor(public readonly element: T) {\n super();\n }\n\n /**\n * Refreshes the bounding box of this element.\n * @returns The refreshed bounding box\n */\n protected refreshBBox(): TSU.Geom.Rect {\n return TSU.DOM.svgBBox(this.element);\n }\n\n /**\n * Refreshes the minimum size of this element.\n * @returns The refreshed minimum size\n */\n protected refreshMinSize(): TSU.Geom.Size {\n return TSU.DOM.svgBBox(this.element);\n }\n\n /**\n * Updates the bounds of this element.\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @returns The updated bounds values\n */\n protected updateBounds(\n x: null | number,\n y: null | number,\n w: null | number,\n h: null | number,\n ): [number | null, number | null, number | null, number | null] {\n return [x, y, w, h];\n }\n\n /**\n * Refreshes the layout of this element.\n * Updates the element's attributes based on the shape's properties.\n */\n refreshLayout(): void {\n if (this.hasX) this.element.setAttribute(\"x\", \"\" + this._x);\n if (this.hasY) this.element.setAttribute(\"y\", \"\" + this._y);\n }\n}\n\n/**\n * Base class for views that represent atoms in the notation.\n * AtomView provides the visual representation of an atom.\n */\nexport abstract class AtomView extends Shape {\n /** Nesting depth of this atom in the structure */\n depth = 0;\n /** Index of the role containing this atom */\n roleIndex = 0;\n\n // LayoutMetrics for the AtomView so all atomviews laid out on the\n // same baseline will show up aligned vertically\n /** Baseline position for vertical alignment */\n baseline: number;\n /** Ascent (space above baseline) */\n ascent: number;\n /** Descent (space below baseline) */\n descent: number;\n /** Height of capital letters */\n capHeight: number;\n /** Space between lines */\n leading: number;\n\n /**\n * Checks if this atom view represents a leaf atom.\n */\n abstract isLeaf(): boolean;\n\n abstract get totalDuration(): TSU.Num.Fraction;\n\n /**\n * Creates the SVG elements needed for this atom view.\n * @param parent The parent SVG element to attach to\n */\n abstract createElements(parent: SVGGraphicsElement): void;\n}\n\n/**\n * A view for leaf atoms (those that cannot contain other atoms).\n */\nexport abstract class LeafAtomView extends AtomView {\n /**\n * Creates a new LeafAtomView.\n * @param leafAtom The leaf atom this view represents\n */\n constructor(public leafAtom: LeafAtom) {\n super();\n }\n\n /**\n * Leaf atom views always return true for isLeaf().\n */\n isLeaf(): boolean {\n return true;\n }\n\n /**\n * Gets a unique identifier for this view based on the atom's UUID.\n */\n get viewId(): number {\n return this.leafAtom.uuid;\n }\n\n /**\n * Returns the total duration of the atom rendered by this view.\n */\n get totalDuration(): TSU.Num.Fraction {\n return this.leafAtom.duration;\n }\n}\n\n/**\n * A view for group atoms that contain multiple child atoms.\n */\nexport abstract class GroupView extends AtomView {\n /** Space between atoms in this group */\n protected atomSpacing: number;\n /** The SVG group element for this view */\n protected groupElement: SVGGElement;\n /** Views for the atoms in this group */\n protected atomViews: AtomView[] = [];\n private _embelishments: Embelishment[];\n /** Whether this group represents notes by default */\n defaultToNotes = true;\n /** Whether this view needs layout */\n needsLayout = true;\n /** Scale factor for this group */\n scaleFactor = 1.0;\n\n /**\n * Creates a new GroupView.\n * @param group The group atom this view represents\n * @param config Optional configuration object\n */\n constructor(\n public group: Group,\n config?: any,\n ) {\n super();\n this.atomSpacing = 5;\n this.setStyles(config || {});\n }\n\n /**\n * Returns the total duration of the group rendered by this view.\n */\n get totalDuration(): TSU.Num.Fraction {\n return this.group.totalChildDuration;\n }\n\n /**\n * Creates the SVG elements needed for this group view.\n * @param parent The parent SVG element to attach to\n */\n createElements(parent: SVGGraphicsElement): void {\n this.groupElement = TSU.DOM.createSVGNode(\"g\", {\n parent: parent,\n attrs: {\n class: \"groupViewRoot\",\n id: \"groupViewRoot\" + this.group.uuid,\n },\n });\n\n // now create child atom views for each atom in this Group\n for (const atom of this.group.atoms.values()) {\n const atomView = this.createAtomView(atom);\n this.atomViews.push(atomView);\n }\n this.invalidateBounds();\n }\n\n /**\n * Group views always return false for isLeaf().\n */\n isLeaf(): boolean {\n return false;\n }\n\n /**\n * Refreshes the bounding box of this group.\n * @returns The refreshed bounding box\n */\n protected refreshBBox(): TSU.Geom.Rect {\n return TSU.DOM.svgBBox(this.groupElement);\n }\n\n /**\n * Refreshes the minimum size of this group.\n * @returns The refreshed minimum size\n */\n protected refreshMinSize(): TSU.Geom.Size {\n let totalWidth = 0;\n let maxHeight = 0;\n this.atomViews.forEach((av, index) => {\n const ms = av.minSize;\n totalWidth += ms.width + this.atomSpacing;\n maxHeight = Math.max(maxHeight, ms.height);\n });\n return new TSU.Geom.Size(totalWidth * this.scaleFactor, maxHeight * this.scaleFactor);\n }\n\n /**\n * Creates an atom view for a specific atom.\n * @param atom The atom to create a view for\n * @returns The created atom view\n */\n abstract createAtomView(atom: Atom): AtomView;\n\n /**\n * Updates the bounds of this group.\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @returns The updated bounds values\n */\n protected updateBounds(\n x: null | number,\n y: null | number,\n w: null | number,\n h: null | number,\n ): [number | null, number | null, number | null, number | null] {\n return [x, y, w, h];\n }\n\n /**\n * Refreshes the layout of this group.\n * Updates the position and size of the group and its child atoms.\n */\n refreshLayout(): void {\n let transform = \"translate(\" + this.x + \",\" + this.y + \")\";\n if (this.scaleFactor < 1) {\n transform += \" scale(\" + this.scaleFactor + \")\";\n }\n this.groupElement.setAttribute(\"transform\", transform);\n // if (this.widthChanged) {\n // All our atoms have to be laid out between startX and endX\n // old way of doing where we just set dx between atom views\n // this worked when atomviews were single glyphs. But\n // as atomViews can be complex (eg with accents and pre/post\n // spaces etc) explicitly setting x/y may be important\n let currX = 0;\n const currY = 0; // null; // this.y; // + 10;\n\n if (true) {\n this.atomViews.forEach((av, index) => {\n av.setBounds(currX, currY, null, null, true);\n currX += this.atomSpacing + av.minSize.width;\n });\n } else {\n // currently this is disabled because \"smaller\" beats are not EXPANDED to what they could be\n // smart alignment based layout where X = F(offset)\n // we want an atom's X offset to be something like (atom.timeOffset / group.duration) * groupWidth\n const totalDur = this.group.totalChildDuration;\n let currTime = ZERO;\n this.atomViews.forEach((av, index) => {\n const newX = currTime.timesNum(this.minSize.width).divby(this.group.duration).floor;\n if (newX >= currX) {\n currX = newX;\n }\n av.setBounds(currX, currY, null, null, true);\n currX += this.atomSpacing + av.minSize.width;\n currTime = currTime.plus(av.totalDuration);\n });\n }\n this.invalidateBounds();\n for (const e of this.embelishments) e.refreshLayout();\n this.invalidateBounds();\n }\n\n /**\n * Gets the embellishments for this group.\n */\n get embelishments(): Embelishment[] {\n if (!this._embelishments) {\n this._embelishments = this.createEmbelishments();\n }\n return this._embelishments;\n }\n\n /**\n * Creates the embellishments for this group.\n * @returns An array of embellishments\n */\n protected createEmbelishments(): Embelishment[] {\n return [];\n }\n\n /**\n * Sets the styles for this group.\n * @param config Style configuration object\n */\n setStyles(config: any): void {\n if (\"atomSpacing\" in config) this.atomSpacing = config.atomSpacing;\n this.needsLayout = true;\n }\n}\n"]}
1
+ {"version":3,"file":"shapes.js","sourceRoot":"","sources":["../../src/shapes.ts"],"names":[],"mappings":"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","sourcesContent":["import * as TSU from \"@panyam/tsutils\";\nimport { ZERO, Atom, LeafAtom, Group } from \"./core\";\n\n/**\n * Base class for all renderable objects.\n *\n * Shape caches properties like bounding boxes to improve performance,\n * since bounding box calculations can be expensive. This also allows\n * testing layouts and positioning without worrying about implementation details.\n */\nexport abstract class Shape {\n private static idCounter = 0;\n readonly shapeId: number = Shape.idCounter++;\n\n /**\n * Note that x and y coordinates are not always the x and y coordinates\n * of the bounding box.\n * E.g., a circle's x and y coordinates are its center point and not the\n * top left corner.\n * These \"main\" coordinates are referred to as control coordinates.\n */\n protected _x: number | null = null;\n protected _y: number | null = null;\n protected _width: number | null = null;\n protected _height: number | null = null;\n protected _bbox: TSU.Geom.Rect;\n protected _minSize: TSU.Geom.Size;\n protected parentShape: Shape | null = null;\n /** Child shapes contained within this shape */\n children: Shape[] = [];\n\n /**\n * Gets the bounding box of this shape.\n * Calculates it if it hasn't been calculated yet.\n */\n get bbox(): TSU.Geom.Rect {\n if (!this._bbox) {\n this._bbox = this.refreshBBox();\n }\n return this._bbox;\n }\n\n /**\n * Gets the minimum size of this shape.\n * This is usually the size of the bounding box.\n */\n get minSize(): TSU.Geom.Size {\n if (!this._minSize) {\n this._minSize = this.refreshMinSize();\n }\n return this._minSize;\n }\n\n /**\n * Refreshes the bounding box of this shape.\n * Called when the shape knows the bbox it is tracking cannot be trusted\n * and has to be refreshed by calling native methods.\n * @returns The refreshed bounding box\n */\n protected abstract refreshBBox(): TSU.Geom.Rect;\n\n /**\n * Refreshes the minimum size of this shape.\n * @returns The refreshed minimum size\n */\n protected abstract refreshMinSize(): TSU.Geom.Size;\n\n /**\n * Updates the bounds of this shape.\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @returns The updated bounds values\n */\n protected abstract updateBounds(\n x: null | number,\n y: null | number,\n w: null | number,\n h: null | number,\n ): [number | null, number | null, number | null, number | null];\n\n /**\n * Invalidates the cached bounds of this shape.\n * Forces recalculation of bounding box and minimum size.\n */\n invalidateBounds(): void {\n this._minSize = null as unknown as TSU.Geom.Size;\n this._bbox = null as unknown as TSU.Geom.Rect;\n }\n\n /**\n * Sets the bounds of this shape.\n *\n * Note that null and NaN are valid values and mean the following:\n * - null: Don't change the value\n * - NaN: Set the value to null (use the bounding box's value)\n *\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @param applyLayout Whether to apply layout immediately\n * @returns The updated bounds values\n */\n setBounds(\n x: number | null,\n y: number | null,\n w: number | null,\n h: number | null,\n applyLayout = false,\n ): [number | null, number | null, number | null, number | null] {\n if (x != null) {\n if (isNaN(x)) {\n this._x = null;\n } else {\n this._x = x;\n }\n }\n if (y != null) {\n if (isNaN(y)) {\n this._y = null;\n } else {\n this._y = y;\n }\n }\n if (w != null) {\n if (isNaN(w)) {\n this._width = null;\n } else {\n this._width = w;\n }\n }\n if (h != null) {\n if (isNaN(h)) {\n this._height = null;\n } else {\n this._height = h;\n }\n }\n const [nx, ny, nw, nh] = this.updateBounds(x, y, w, h);\n if (nx != null) {\n if (isNaN(nx)) {\n this._x = null;\n } else {\n this._x = nx;\n }\n }\n if (ny != null) {\n if (isNaN(ny)) {\n this._y = null;\n } else {\n this._y = ny;\n }\n }\n if (nw != null) {\n if (isNaN(nw)) {\n this._width = null;\n } else {\n this._width = nw;\n }\n }\n if (nh != null) {\n if (isNaN(nh)) {\n this._height = null;\n } else {\n this._height = nh;\n }\n }\n if (applyLayout) this.refreshLayout();\n // this.resetBBox();\n return [nx, ny, nw, nh];\n }\n\n /**\n * Checks if this shape has an explicit x coordinate.\n */\n get hasX(): boolean {\n return this._x != null && !isNaN(this._x);\n }\n\n /**\n * Checks if this shape has an explicit y coordinate.\n */\n get hasY(): boolean {\n return this._y != null && !isNaN(this._y);\n }\n\n /**\n * Checks if this shape has an explicit width.\n */\n get hasWidth(): boolean {\n return this._width != null && !isNaN(this._width);\n }\n\n /**\n * Checks if this shape has an explicit height.\n */\n get hasHeight(): boolean {\n return this._height != null && !isNaN(this._height);\n }\n\n /**\n * Gets the x coordinate within the parent's coordinate system.\n */\n get x(): number {\n return this._x || 0;\n }\n\n /**\n * Sets the x coordinate within the parent's coordinate system.\n */\n set x(x: number | null) {\n // Here a manual x is being set - how does this interfere with the bounding box?\n // We should _x to the new value to indicate a manual value was set.\n // and reset bbox so that based on this x a new bbox may need to be calculated\n this.setBounds(x == null ? NaN : x, null, null, null);\n }\n\n /**\n * Gets the y coordinate within the parent's coordinate system.\n */\n get y(): number {\n if (this._y != null) return this._y;\n return 0; // this.bbox.y;\n }\n\n /**\n * Sets the y coordinate within the parent's coordinate system.\n */\n set y(y: number | null) {\n this.setBounds(null, y == null ? NaN : y, null, null);\n }\n\n /**\n * Gets the width of this shape.\n */\n get width(): number {\n if (this._width != null) return this._width;\n return 0; // this.bbox.width;\n }\n\n /**\n * Sets the width of this shape.\n */\n set width(w: number | null) {\n this.setBounds(null, null, w == null ? NaN : w, null);\n }\n\n /**\n * Gets the height of this shape.\n */\n get height(): number {\n if (this._height != null) return this._height;\n return 0; // this.bbox.height;\n }\n\n /**\n * Sets the height of this shape.\n */\n set height(h: number | null) {\n this.setBounds(null, null, null, h == null ? NaN : h);\n }\n\n /**\n * Refreshes the layout of this shape.\n * Called when bounds or other properties have changed to give the shape an\n * opportunity to layout its children. For shapes with no children this is a no-op.\n */\n refreshLayout(): void {\n // throw new Error(\"Implement this\");\n }\n}\n\n/**\n * Represents an embellishment applied to a musical element.\n */\nexport abstract class Embelishment extends Shape {}\n\n/**\n * A shape that wraps an SVG element.\n * ElementShape provides the base class for all shapes that are rendered as SVG elements.\n */\nexport class ElementShape<T extends SVGGraphicsElement = SVGGraphicsElement> extends Shape {\n /**\n * Creates a new ElementShape.\n * @param element The SVG element this shape wraps\n */\n constructor(public readonly element: T) {\n super();\n }\n\n /**\n * Refreshes the bounding box of this element.\n * @returns The refreshed bounding box\n */\n protected refreshBBox(): TSU.Geom.Rect {\n return TSU.DOM.svgBBox(this.element);\n }\n\n /**\n * Refreshes the minimum size of this element.\n * @returns The refreshed minimum size\n */\n protected refreshMinSize(): TSU.Geom.Size {\n return TSU.DOM.svgBBox(this.element);\n }\n\n /**\n * Updates the bounds of this element.\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @returns The updated bounds values\n */\n protected updateBounds(\n x: null | number,\n y: null | number,\n w: null | number,\n h: null | number,\n ): [number | null, number | null, number | null, number | null] {\n return [x, y, w, h];\n }\n\n /**\n * Refreshes the layout of this element.\n * Updates the element's attributes based on the shape's properties.\n */\n refreshLayout(): void {\n if (this.hasX) this.element.setAttribute(\"x\", \"\" + this._x);\n if (this.hasY) this.element.setAttribute(\"y\", \"\" + this._y);\n }\n}\n\n/**\n * Base class for views that represent atoms in the notation.\n * AtomView provides the visual representation of an atom.\n */\nexport abstract class AtomView extends Shape {\n /** Nesting depth of this atom in the structure */\n depth = 0;\n /** Index of the role containing this atom */\n roleIndex = 0;\n\n // LayoutMetrics for the AtomView so all atomviews laid out on the\n // same baseline will show up aligned vertically\n /** Baseline position for vertical alignment */\n baseline: number;\n /** Ascent (space above baseline) */\n ascent: number;\n /** Descent (space below baseline) */\n descent: number;\n /** Height of capital letters */\n capHeight: number;\n /** Space between lines */\n leading: number;\n\n /**\n * Checks if this atom view represents a leaf atom.\n */\n abstract isLeaf(): boolean;\n\n abstract get totalDuration(): TSU.Num.Fraction;\n\n /**\n * Returns the horizontal offset from the atom's origin to where the note glyph starts.\n * This accounts for left embellishments that appear before the note.\n * Used by GroupView to align note glyphs at their correct time positions.\n *\n * Default is 0 (glyph starts at origin). Subclasses with left embellishments\n * should override to return the width of left-side decorations.\n */\n get glyphOffset(): number {\n return 0;\n }\n\n /**\n * Creates the SVG elements needed for this atom view.\n * @param parent The parent SVG element to attach to\n */\n abstract createElements(parent: SVGGraphicsElement): void;\n}\n\n/**\n * A view for leaf atoms (those that cannot contain other atoms).\n */\nexport abstract class LeafAtomView extends AtomView {\n /**\n * Creates a new LeafAtomView.\n * @param leafAtom The leaf atom this view represents\n */\n constructor(public leafAtom: LeafAtom) {\n super();\n }\n\n /**\n * Leaf atom views always return true for isLeaf().\n */\n isLeaf(): boolean {\n return true;\n }\n\n /**\n * Gets a unique identifier for this view based on the atom's UUID.\n */\n get viewId(): number {\n return this.leafAtom.uuid;\n }\n\n /**\n * Returns the total duration of the atom rendered by this view.\n */\n get totalDuration(): TSU.Num.Fraction {\n return this.leafAtom.duration;\n }\n}\n\n/**\n * A view for group atoms that contain multiple child atoms.\n */\nexport abstract class GroupView extends AtomView {\n /** Space between atoms in this group */\n protected atomSpacing: number;\n /** The SVG group element for this view */\n protected groupElement: SVGGElement;\n /** Views for the atoms in this group */\n protected atomViews: AtomView[] = [];\n private _embelishments: Embelishment[];\n /** Whether this group represents notes by default */\n defaultToNotes = true;\n /** Whether this view needs layout */\n needsLayout = true;\n /** Scale factor for this group */\n scaleFactor = 1.0;\n /**\n * When true, shows continuation markers (\",\") for atoms with duration > 1\n * instead of just leaving empty space.\n */\n showContinuationMarkers = true;\n /** SVG elements for continuation markers */\n protected continuationMarkerElements: SVGTextElement[] = [];\n\n /**\n * Creates a new GroupView.\n * @param group The group atom this view represents\n * @param config Optional configuration object\n */\n constructor(\n public group: Group,\n config?: any,\n ) {\n super();\n this.atomSpacing = 5;\n this.setStyles(config || {});\n }\n\n /**\n * Returns the total duration of the group rendered by this view.\n */\n get totalDuration(): TSU.Num.Fraction {\n return this.group.totalChildDuration;\n }\n\n /**\n * Creates the SVG elements needed for this group view.\n * @param parent The parent SVG element to attach to\n */\n createElements(parent: SVGGraphicsElement): void {\n this.groupElement = TSU.DOM.createSVGNode(\"g\", {\n parent: parent,\n attrs: {\n class: \"groupViewRoot\",\n id: \"groupViewRoot\" + this.group.uuid,\n },\n });\n\n // now create child atom views for each atom in this Group\n for (const atom of this.group.atoms.values()) {\n const atomView = this.createAtomView(atom);\n this.atomViews.push(atomView);\n }\n this.invalidateBounds();\n }\n\n /**\n * Group views always return false for isLeaf().\n */\n isLeaf(): boolean {\n return false;\n }\n\n /**\n * Refreshes the bounding box of this group.\n * @returns The refreshed bounding box\n */\n protected refreshBBox(): TSU.Geom.Rect {\n return TSU.DOM.svgBBox(this.groupElement);\n }\n\n /**\n * Refreshes the minimum size of this group using duration-based width calculation.\n *\n * ## Duration-Based Width Algorithm\n *\n * This algorithm ensures atoms with extended durations receive proportionally\n * more horizontal space. For example, with `\\beatDuration(4)` and input `S 2 R G M`:\n * - S has duration 1, R has duration 2, G has duration 1\n * - R should visually occupy twice the horizontal space of S or G\n *\n * ### Algorithm Steps:\n *\n * 1. **Calculate width per duration unit**: For each atom, compute the visual width\n * needed per unit of duration: `(visualWidth + spacing) / duration`\n *\n * 2. **Find maximum**: Take the maximum width-per-duration across all atoms.\n * This ensures every atom has enough space for its visual content.\n *\n * 3. **Scale by total duration**: Multiply the max width-per-duration by the\n * group's total duration to get the final group width.\n *\n * ### Example:\n * ```\n * Atoms: S(dur=1, width=10px), R(dur=2, width=10px), G(dur=1, width=10px)\n * Spacing: 5px\n *\n * Width per duration:\n * S: (10 + 5) / 1 = 15 px/unit\n * R: (10 + 5) / 2 = 7.5 px/unit\n * G: (10 + 5) / 1 = 15 px/unit\n *\n * Max width per duration: 15 px/unit\n * Total duration: 1 + 2 + 1 = 4 units\n * Group width: 15 * 4 = 60px\n *\n * Positioning:\n * S at x=0 (time 0/4 * 60 = 0)\n * R at x=15 (time 1/4 * 60 = 15)\n * G at x=45 (time 3/4 * 60 = 45)\n * ```\n *\n * @returns The refreshed minimum size\n */\n protected refreshMinSize(): TSU.Geom.Size {\n let maxHeight = 0;\n\n // Step 1: Calculate width per duration unit for each atom\n let minWidthPerDuration = 0;\n this.atomViews.forEach((av) => {\n const ms = av.minSize;\n const dur = av.totalDuration;\n if (!dur.isZero) {\n const durValue = dur.num / dur.den;\n const widthPerDur = (ms.width + this.atomSpacing) / durValue;\n // Step 2: Track maximum width per duration\n minWidthPerDuration = Math.max(minWidthPerDuration, widthPerDur);\n }\n maxHeight = Math.max(maxHeight, ms.height);\n });\n\n // Step 3: Scale by total duration\n const totalDuration = this.group.totalChildDuration;\n const totalDurValue = totalDuration.num / totalDuration.den;\n const totalWidth = minWidthPerDuration * totalDurValue;\n\n return new TSU.Geom.Size(totalWidth * this.scaleFactor, maxHeight * this.scaleFactor);\n }\n\n /**\n * Creates an atom view for a specific atom.\n * @param atom The atom to create a view for\n * @returns The created atom view\n */\n abstract createAtomView(atom: Atom): AtomView;\n\n /**\n * Updates the bounds of this group.\n * @param x New x coordinate, or null to keep current value\n * @param y New y coordinate, or null to keep current value\n * @param w New width, or null to keep current value\n * @param h New height, or null to keep current value\n * @returns The updated bounds values\n */\n protected updateBounds(\n x: null | number,\n y: null | number,\n w: null | number,\n h: null | number,\n ): [number | null, number | null, number | null, number | null] {\n return [x, y, w, h];\n }\n\n /**\n * Refreshes the layout of this group using duration-based positioning.\n *\n * ## Duration-Based Positioning Algorithm\n *\n * Atoms are positioned at x-coordinates proportional to their time offset\n * within the group's total duration. This ensures that atoms with extended\n * durations visually occupy the correct amount of horizontal space.\n *\n * ### Width Source Priority:\n *\n * 1. **Column width** (preferred): If width was set via `setBounds()` from the\n * grid layout system (ColAlign), use that width. This enables global alignment\n * across all beats in the same column.\n *\n * 2. **Minimum size**: Fall back to `minSize.width` calculated by `refreshMinSize()`.\n *\n * ### Positioning Formula:\n * ```\n * xPosition = (timeOffset / totalDuration) * groupWidth\n * ```\n *\n * ### Continuation Markers:\n *\n * When `showContinuationMarkers` is true (default), atoms with duration > 1\n * will have \",\" markers rendered at each additional time slot. For example,\n * an atom with duration 2 will show \"R ,\" instead of \"R \".\n *\n * This helps users visually understand that the note continues through\n * multiple time slots without relying on empty space alone.\n *\n * ### Example:\n * ```\n * Input: S 2 R G (with beatDuration=4)\n * Group width: 60px, Total duration: 4 units\n *\n * Positioning:\n * S at x=0 (time 0, offset 0/4 * 60 = 0)\n * R at x=15 (time 1, offset 1/4 * 60 = 15)\n * \",\" at x=30 (continuation marker for R at time 2)\n * G at x=45 (time 3, offset 3/4 * 60 = 45)\n * ```\n */\n refreshLayout(): void {\n let transform = \"translate(\" + this.x + \",\" + this.y + \")\";\n if (this.scaleFactor < 1) {\n transform += \" scale(\" + this.scaleFactor + \")\";\n }\n this.groupElement.setAttribute(\"transform\", transform);\n\n const currY = 0;\n const totalDur = this.group.totalChildDuration;\n\n // Width source priority: column width (for global alignment) > minSize\n const unscaledMinWidth = this.minSize.width / this.scaleFactor;\n const groupWidth = this.hasWidth ? this.width / this.scaleFactor : unscaledMinWidth;\n\n // Clear existing continuation markers before re-rendering\n this.clearContinuationMarkers();\n\n // Position each atom based on its time offset\n let currTime = ZERO;\n this.atomViews.forEach((av, index) => {\n // Calculate where the NOTE GLYPH should appear based on time offset\n const glyphX = totalDur.isZero ? 0 : currTime.timesNum(groupWidth).divby(totalDur).floor;\n // Subtract glyphOffset so left embellishments don't push the glyph past its time position\n // The atom origin is placed earlier, so the glyph ends up at the correct time position\n const xPos = Math.max(0, glyphX - av.glyphOffset);\n av.setBounds(xPos, currY, null, null, true);\n\n // Render continuation markers for atoms with duration > 1\n if (this.showContinuationMarkers && !totalDur.isZero) {\n const atomDur = av.totalDuration;\n const durValue = atomDur.num / atomDur.den;\n if (durValue > 1) {\n // Render one marker at each additional time slot within the atom's duration\n const numMarkers = Math.floor(durValue) - 1;\n for (let i = 1; i <= numMarkers; i++) {\n // Marker time = currTime + (atomDuration * i / floor(duration))\n const markerTime = currTime.plus(atomDur.timesNum(i).divbyNum(Math.floor(durValue)));\n const markerX = markerTime.timesNum(groupWidth).divby(totalDur).floor;\n this.renderContinuationMarker(markerX, currY);\n }\n }\n }\n\n currTime = currTime.plus(av.totalDuration);\n });\n\n this.invalidateBounds();\n for (const e of this.embelishments) e.refreshLayout();\n this.invalidateBounds();\n }\n\n /**\n * Clears all continuation marker elements.\n */\n protected clearContinuationMarkers(): void {\n for (const el of this.continuationMarkerElements) {\n el.remove();\n }\n this.continuationMarkerElements = [];\n }\n\n /**\n * Renders a continuation marker (\",\") at the specified position.\n * @param x X position for the marker\n * @param y Y position for the marker\n */\n protected renderContinuationMarker(x: number, y: number): void {\n const marker = TSU.DOM.createSVGNode(\"text\", {\n parent: this.groupElement,\n attrs: {\n class: \"continuationMarker\",\n x: x.toString(),\n y: y.toString(),\n },\n text: \",\",\n }) as SVGTextElement;\n this.continuationMarkerElements.push(marker);\n }\n\n /**\n * Gets the embellishments for this group.\n */\n get embelishments(): Embelishment[] {\n if (!this._embelishments) {\n this._embelishments = this.createEmbelishments();\n }\n return this._embelishments;\n }\n\n /**\n * Creates the embellishments for this group.\n * @returns An array of embellishments\n */\n protected createEmbelishments(): Embelishment[] {\n return [];\n }\n\n /**\n * Sets the styles for this group.\n * @param config Style configuration object\n */\n setStyles(config: any): void {\n if (\"atomSpacing\" in config) this.atomSpacing = config.atomSpacing;\n if (\"showContinuationMarkers\" in config) this.showContinuationMarkers = config.showContinuationMarkers;\n this.needsLayout = true;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notations",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "author": "Sriram Panyam",
5
5
  "description": "A package for modelling, parsing, laying out carnatic musical notation",
6
6
  "homepage": "https://github.com/panyam/notations#readme",
@@ -80,9 +80,6 @@
80
80
  "lintfix",
81
81
  "test"
82
82
  ],
83
- "directories": {
84
- "lib": "lib"
85
- },
86
83
  "scripts": {
87
84
  "clean": "rm -Rf lib && jest --clearCache && rm -rf static/lib",
88
85
  "builddocs": "npx typedoc",
@@ -95,8 +92,13 @@
95
92
  "lintfix": "eslint src --quiet --fix",
96
93
  "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"",
97
94
  "precommit-msg": "echo 'Pre-commit checks...' && exit 0",
95
+ "prepare": "npm run build",
96
+ "prepublishOnly": "echo prepublishOnly...",
98
97
  "preversion": "npm run build && npm run lintfix",
99
98
  "version": "git add -A src",
100
99
  "postversion": "git push && git push --tags"
100
+ },
101
+ "directories": {
102
+ "lib": "lib"
101
103
  }
102
- }
104
+ }