notations 1.0.4 → 1.0.6
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/README.md +1 -0
- package/dist/notations.umd.js +329 -84
- package/dist/notations.umd.min.js +2 -2
- package/dist/notations.umd.min.js.map +1 -1
- package/lib/cjs/block.d.ts +4 -0
- package/lib/cjs/block.js +24 -0
- package/lib/cjs/block.js.map +1 -1
- package/lib/cjs/core.d.ts +15 -0
- package/lib/cjs/core.js +144 -4
- package/lib/cjs/core.js.map +1 -1
- package/lib/cjs/entity.d.ts +4 -0
- package/lib/cjs/entity.js +12 -0
- package/lib/cjs/entity.js.map +1 -1
- package/lib/cjs/events.d.ts +56 -0
- package/lib/cjs/events.js +27 -0
- package/lib/cjs/events.js.map +1 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/shapes.d.ts +11 -0
- package/lib/cjs/shapes.js +25 -2
- package/lib/cjs/shapes.js.map +1 -1
- package/lib/esm/block.d.ts +4 -0
- package/lib/esm/block.js +24 -0
- package/lib/esm/block.js.map +1 -1
- package/lib/esm/core.d.ts +15 -0
- package/lib/esm/core.js +144 -4
- package/lib/esm/core.js.map +1 -1
- package/lib/esm/entity.d.ts +4 -0
- package/lib/esm/entity.js +12 -0
- package/lib/esm/entity.js.map +1 -1
- package/lib/esm/events.d.ts +56 -0
- package/lib/esm/events.js +24 -0
- package/lib/esm/events.js.map +1 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/shapes.d.ts +11 -0
- package/lib/esm/shapes.js +24 -2
- package/lib/esm/shapes.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"AAwBA,MAAM,CAAN,IAAY,cASX;AATD,WAAY,cAAc;IAExB,6BAAW,CAAA;IAEX,mCAAiB,CAAA;IAEjB,mCAAiB,CAAA;IAEjB,mCAAiB,CAAA;AACnB,CAAC,EATW,cAAc,KAAd,cAAc,QASzB;AAwBD,MAAM,CAAN,IAAY,cAKX;AALD,WAAY,cAAc;IAExB,6BAAW,CAAA;IAEX,mCAAiB,CAAA;AACnB,CAAC,EALW,cAAc,KAAd,cAAc,QAKzB;AAeD,MAAM,CAAN,IAAY,mBAKX;AALD,WAAY,mBAAmB;IAE7B,kCAAW,CAAA;IAEX,wCAAiB,CAAA;AACnB,CAAC,EALW,mBAAmB,KAAnB,mBAAmB,QAK9B;AAgJD,MAAM,CAAC,MAAM,WAAW,GAAG;IAEzB,aAAa,EAAE,cAAc;IAE7B,aAAa,EAAE,cAAc;IAE7B,aAAa,EAAE,cAAc;IAE7B,gBAAgB,EAAE,iBAAiB;CAC3B,CAAC","sourcesContent":["/**\n * Model change events for incremental updates.\n *\n * This module defines the event types, interfaces, and observer patterns for\n * change notifications on model entities (Group, Role, Line, Block). These\n * events enable incremental updates to the rendering pipeline without full\n * re-renders.\n *\n * Uses the Observer pattern instead of pub/sub EventHub for:\n * - Type safety: Observers have strongly-typed method signatures\n * - Traceability: Easy to see who is observing what\n * - Explicit contracts: Clear interface between observable and observers\n *\n * Note: This module uses generic types to avoid circular dependencies with\n * core.ts and block.ts. The actual types are enforced at the call sites.\n */\n\n// ============================================\n// Event Types and Payloads\n// ============================================\n\n/**\n * Event types for Atom-related changes.\n */\nexport enum AtomChangeType {\n /** Atoms were added to the end */\n ADD = \"add\",\n /** Atoms were inserted at a specific position */\n INSERT = \"insert\",\n /** Atoms were removed */\n REMOVE = \"remove\",\n /** An atom's properties changed (duration, value, etc.) */\n UPDATE = \"update\",\n}\n\n/**\n * Event payload for atom changes in a Group or Role.\n * Generic T is typically Atom from core.ts.\n */\nexport interface AtomChangeEvent<T = any> {\n /** The type of change */\n type: AtomChangeType;\n /** The atoms that were added, inserted, or removed */\n atoms: T[];\n /** For INSERT: the index where atoms were inserted */\n index?: number;\n /** For UPDATE: the specific property that changed */\n property?: string;\n /** For UPDATE: the old value of the property */\n oldValue?: any;\n /** For UPDATE: the new value of the property */\n newValue?: any;\n}\n\n/**\n * Event types for Role-related changes on a Line.\n */\nexport enum RoleChangeType {\n /** A role was added to the line */\n ADD = \"add\",\n /** A role was removed from the line */\n REMOVE = \"remove\",\n}\n\n/**\n * Event payload for role changes on a Line.\n */\nexport interface RoleChangeEvent {\n /** The type of change */\n type: RoleChangeType;\n /** The role that was added or removed */\n roleName: string;\n}\n\n/**\n * Event types for BlockItem changes on a Block.\n */\nexport enum BlockItemChangeType {\n /** An item was added to the block */\n ADD = \"add\",\n /** An item was removed from the block */\n REMOVE = \"remove\",\n}\n\n/**\n * Event payload for block item changes.\n * Generic T is typically BlockItem (Block | Line | RawBlock) from block.ts.\n */\nexport interface BlockItemChangeEvent<T = any> {\n /** The type of change */\n type: BlockItemChangeType;\n /** The item that was added or removed */\n item: T;\n /** For ADD: the index where the item was added */\n index?: number;\n}\n\n// ============================================\n// Observer Interfaces\n// ============================================\n\n/**\n * Observer interface for Group atom changes.\n * Implement this interface to receive notifications when atoms are\n * added, inserted, or removed from a Group.\n *\n * @template TAtom The atom type (defaults to any to avoid circular deps)\n * @template TGroup The group type (defaults to any to avoid circular deps)\n */\nexport interface GroupObserver<TAtom = any, TGroup = any> {\n /**\n * Called when atoms are added to the end of the group.\n * @param group The group that changed\n * @param atoms The atoms that were added\n * @param index The index where atoms were added\n */\n onAtomsAdded?(group: TGroup, atoms: TAtom[], index: number): void;\n\n /**\n * Called when atoms are inserted at a specific position.\n * @param group The group that changed\n * @param atoms The atoms that were inserted\n * @param index The index where atoms were inserted\n */\n onAtomsInserted?(group: TGroup, atoms: TAtom[], index: number): void;\n\n /**\n * Called when atoms are removed from the group.\n * @param group The group that changed\n * @param atoms The atoms that were removed\n */\n onAtomsRemoved?(group: TGroup, atoms: TAtom[]): void;\n}\n\n/**\n * Observer interface for Role atom changes.\n * Implement this interface to receive notifications when atoms are\n * added, inserted, or removed from a Role.\n *\n * @template TAtom The atom type (defaults to any to avoid circular deps)\n * @template TRole The role type (defaults to any to avoid circular deps)\n */\nexport interface RoleObserver<TAtom = any, TRole = any> {\n /**\n * Called when atoms are added to the end of the role.\n * @param role The role that changed\n * @param atoms The atoms that were added\n * @param index The index where atoms were added\n */\n onAtomsAdded?(role: TRole, atoms: TAtom[], index: number): void;\n\n /**\n * Called when atoms are inserted at a specific position.\n * @param role The role that changed\n * @param atoms The atoms that were inserted\n * @param index The index where atoms were inserted\n */\n onAtomsInserted?(role: TRole, atoms: TAtom[], index: number): void;\n\n /**\n * Called when atoms are removed from the role.\n * @param role The role that changed\n * @param atoms The atoms that were removed\n */\n onAtomsRemoved?(role: TRole, atoms: TAtom[]): void;\n}\n\n/**\n * Observer interface for Line role changes.\n * Implement this interface to receive notifications when roles are\n * added or removed from a Line.\n *\n * @template TRole The role type (defaults to any to avoid circular deps)\n * @template TLine The line type (defaults to any to avoid circular deps)\n */\nexport interface LineObserver<TRole = any, TLine = any> {\n /**\n * Called when a role is added to the line.\n * @param line The line that changed\n * @param roleName The name of the added role\n * @param role The role that was added\n */\n onRoleAdded?(line: TLine, roleName: string, role: TRole): void;\n\n /**\n * Called when a role is removed from the line.\n * @param line The line that changed\n * @param roleName The name of the removed role\n */\n onRoleRemoved?(line: TLine, roleName: string): void;\n}\n\n/**\n * Observer interface for Block item changes.\n * Implement this interface to receive notifications when items are\n * added or removed from a Block.\n *\n * @template TItem The block item type (defaults to any to avoid circular deps)\n * @template TBlock The block type (defaults to any to avoid circular deps)\n */\nexport interface BlockObserver<TItem = any, TBlock = any> {\n /**\n * Called when an item is added to the block.\n * @param block The block that changed\n * @param item The item that was added\n * @param index The index where the item was added\n */\n onItemAdded?(block: TBlock, item: TItem, index: number): void;\n\n /**\n * Called when an item is removed from the block.\n * @param block The block that changed\n * @param item The item that was removed\n * @param index The index where the item was located\n */\n onItemRemoved?(block: TBlock, item: TItem, index: number): void;\n}\n\n// ============================================\n// Legacy Event Names (kept for compatibility)\n// ============================================\n\n/**\n * Model event names as constants.\n * @deprecated Use observer interfaces instead\n */\nexport const ModelEvents = {\n /** Emitted when atoms change in a Group or Role */\n ATOMS_CHANGED: \"atomsChanged\",\n /** Emitted when roles change on a Line */\n ROLES_CHANGED: \"rolesChanged\",\n /** Emitted when block items change on a Block */\n ITEMS_CHANGED: \"itemsChanged\",\n /** Emitted when an atom's duration changes */\n DURATION_CHANGED: \"durationChanged\",\n} as const;\n\n/**\n * Type for model event names.\n * @deprecated Use observer interfaces instead\n */\nexport type ModelEventName = (typeof ModelEvents)[keyof typeof ModelEvents];\n"]}
|
package/lib/esm/index.d.ts
CHANGED
package/lib/esm/index.js
CHANGED
package/lib/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC","sourcesContent":["export * from \"./entity\";\nexport * from \"./cycle\";\nexport * from \"./core\";\nexport * from \"./iterators\";\nexport * from \"./layouts\";\nexport * from \"./grids\";\nexport * from \"./beats\";\nexport * from \"./notation\";\nexport * from \"./parser\";\nexport * from \"./shapes\";\nexport * from \"./loader\";\nexport * as Utils from \"./utils\";\nexport * as Carnatic from \"./carnatic\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,QAAQ,MAAM,YAAY,CAAC","sourcesContent":["export * from \"./entity\";\nexport * from \"./events\";\nexport * from \"./cycle\";\nexport * from \"./core\";\nexport * from \"./iterators\";\nexport * from \"./layouts\";\nexport * from \"./grids\";\nexport * from \"./beats\";\nexport * from \"./notation\";\nexport * from \"./parser\";\nexport * from \"./shapes\";\nexport * from \"./loader\";\nexport * as Utils from \"./utils\";\nexport * as Carnatic from \"./carnatic\";\n"]}
|
package/lib/esm/shapes.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import * as TSU from "@panyam/tsutils";
|
|
2
2
|
import { Atom, LeafAtom, Group } from "./core";
|
|
3
|
+
export interface CollisionLayoutItem {
|
|
4
|
+
timeOffset: TSU.Num.Fraction;
|
|
5
|
+
duration: TSU.Num.Fraction;
|
|
6
|
+
glyphOffset: number;
|
|
7
|
+
minWidth: number;
|
|
8
|
+
}
|
|
9
|
+
export interface CollisionLayoutResult {
|
|
10
|
+
x: number;
|
|
11
|
+
wasCollision: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function computeCollisionLayout(items: CollisionLayoutItem[], totalDuration: TSU.Num.Fraction, containerWidth: number): CollisionLayoutResult[];
|
|
3
14
|
export declare abstract class Shape {
|
|
4
15
|
private static idCounter;
|
|
5
16
|
readonly shapeId: number;
|
package/lib/esm/shapes.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import * as TSU from "@panyam/tsutils";
|
|
2
2
|
import { ZERO } from "./core";
|
|
3
|
+
export function computeCollisionLayout(items, totalDuration, containerWidth) {
|
|
4
|
+
const results = [];
|
|
5
|
+
let prevItemEndX = 0;
|
|
6
|
+
let currTime = ZERO;
|
|
7
|
+
for (const item of items) {
|
|
8
|
+
const glyphX = totalDuration.isZero ? 0 : currTime.timesNum(containerWidth).divby(totalDuration).floor;
|
|
9
|
+
let realX = glyphX - item.glyphOffset;
|
|
10
|
+
const wasCollision = realX < prevItemEndX;
|
|
11
|
+
if (wasCollision) {
|
|
12
|
+
realX = prevItemEndX;
|
|
13
|
+
}
|
|
14
|
+
results.push({ x: realX, wasCollision });
|
|
15
|
+
prevItemEndX = realX + item.minWidth;
|
|
16
|
+
currTime = currTime.plus(item.duration);
|
|
17
|
+
}
|
|
18
|
+
return results;
|
|
19
|
+
}
|
|
3
20
|
export class Shape {
|
|
4
21
|
constructor() {
|
|
5
22
|
this.shapeId = Shape.idCounter++;
|
|
@@ -259,10 +276,15 @@ export class GroupView extends AtomView {
|
|
|
259
276
|
const groupWidth = this.hasWidth ? this.width / this.scaleFactor : unscaledMinWidth;
|
|
260
277
|
this.clearContinuationMarkers();
|
|
261
278
|
let currTime = ZERO;
|
|
279
|
+
let prevNoteEndX = 0;
|
|
262
280
|
this.atomViews.forEach((av, index) => {
|
|
263
281
|
const glyphX = totalDur.isZero ? 0 : currTime.timesNum(groupWidth).divby(totalDur).floor;
|
|
264
|
-
|
|
265
|
-
|
|
282
|
+
let realX = glyphX - av.glyphOffset;
|
|
283
|
+
if (realX < prevNoteEndX) {
|
|
284
|
+
realX = prevNoteEndX;
|
|
285
|
+
}
|
|
286
|
+
av.setBounds(realX, currY, null, null, true);
|
|
287
|
+
prevNoteEndX = realX + av.minSize.width;
|
|
266
288
|
if (this.showContinuationMarkers && !totalDur.isZero) {
|
|
267
289
|
const atomDur = av.totalDuration;
|
|
268
290
|
const durValue = atomDur.num / atomDur.den;
|
package/lib/esm/shapes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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 * Represents an item to be positioned in collision-based layout.\n */\nexport interface CollisionLayoutItem {\n /** Time offset as a fraction (numerator/denominator) */\n timeOffset: TSU.Num.Fraction;\n /** Duration of this item */\n duration: TSU.Num.Fraction;\n /** Width of pre-embellishments (extends left from glyph position) */\n glyphOffset: number;\n /** Minimum width of the item (includes all embellishments and glyph) */\n minWidth: number;\n}\n\n/**\n * Result of collision-based layout for a single item.\n */\nexport interface CollisionLayoutResult {\n /** The calculated x position for the item */\n x: number;\n /** Whether the item was pushed right due to collision */\n wasCollision: boolean;\n}\n\n/**\n * Computes collision-based positions for a sequence of items within a container.\n *\n * ## Algorithm\n *\n * 1. Calculate ideal glyph position: `glyphX = (timeOffset / totalDuration) * containerWidth`\n * 2. Pre-embellishments extend left: `realX = glyphX - glyphOffset`\n * 3. Collision check: if `realX < prevItemEndX`, then `realX = prevItemEndX`\n * 4. Track: `prevItemEndX = realX + minWidth`\n *\n * @param items Items to position (must be in time order)\n * @param totalDuration Total duration of all items\n * @param containerWidth Width of the container to position items within\n * @returns Array of positions for each item\n */\nexport function computeCollisionLayout(\n items: CollisionLayoutItem[],\n totalDuration: TSU.Num.Fraction,\n containerWidth: number,\n): CollisionLayoutResult[] {\n const results: CollisionLayoutResult[] = [];\n let prevItemEndX = 0;\n let currTime = ZERO;\n\n for (const item of items) {\n // 1. Calculate ideal glyph position based on time offset\n const glyphX = totalDuration.isZero ? 0 : currTime.timesNum(containerWidth).divby(totalDuration).floor;\n\n // 2. Pre-embellishments extend left from glyph position\n let realX = glyphX - item.glyphOffset;\n\n // 3. Collision check: push right if overlapping previous item\n const wasCollision = realX < prevItemEndX;\n if (wasCollision) {\n realX = prevItemEndX;\n }\n\n results.push({ x: realX, wasCollision });\n\n // 4. Track end position for next collision check\n prevItemEndX = realX + item.minWidth;\n\n currTime = currTime.plus(item.duration);\n }\n\n return results;\n}\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 collision-based positioning.\n *\n * ## Collision-Based Layout Algorithm\n *\n * Atoms are positioned using time-based positioning with collision avoidance.\n * Each atom starts at its ideal time-based position, but is pushed right if\n * it would overlap with the previous atom (including embellishments).\n *\n * ### Algorithm:\n * ```\n * 1. Calculate ideal glyph position: glyphX = (time / totalDuration) * groupWidth\n * 2. Pre-embellishments extend left: realX = glyphX - preEmbellishmentWidth\n * 3. Collision check: if (realX < prevNoteEndX) realX = prevNoteEndX\n * 4. Position the atom at realX\n * 5. Track: prevNoteEndX = realX + atom.minSize.width\n * ```\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 * ### 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 * ### Example (no collisions):\n * ```\n * Input: S R G M (equal duration, no embellishments)\n * Group width: 60px, Total duration: 4 units\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=30 (time 2/4 * 60 = 30)\n * M at x=45 (time 3/4 * 60 = 45)\n * ```\n *\n * ### Example (with collision):\n * ```\n * Input: [Jaaru+S] R (S has 10px pre-embellishment, each atom 15px wide)\n * Group width: 60px, Total duration: 2 units\n *\n * Without collision avoidance:\n * S.glyphX = 0, S.realX = 0 - 10 = -10 (clamped to 0)\n * R.glyphX = 30, R overlaps with S\n *\n * With collision avoidance:\n * S at x=0 (prevNoteEndX becomes 15)\n * R.glyphX = 30, R.realX = 30 - 0 = 30\n * 30 >= 15, no collision, R at x=30\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 using collision-based layout\n // Atoms start at their time-based position, but are pushed right if they would\n // overlap with the previous atom (including embellishments)\n let currTime = ZERO;\n let prevNoteEndX = 0;\n this.atomViews.forEach((av, index) => {\n // 1. Calculate ideal glyph position based on time offset\n const glyphX = totalDur.isZero ? 0 : currTime.timesNum(groupWidth).divby(totalDur).floor;\n\n // 2. Pre-embellishments extend left from glyph position\n // realX is where the atom origin should be placed\n let realX = glyphX - av.glyphOffset;\n\n // 3. Collision check: push right if overlapping previous atom\n if (realX < prevNoteEndX) {\n realX = prevNoteEndX;\n }\n\n // 4. Position the atom\n av.setBounds(realX, currY, null, null, true);\n\n // 5. Track end position for next collision check\n prevNoteEndX = realX + av.minSize.width;\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.
|
|
3
|
+
"version": "1.0.6",
|
|
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",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"@eslint/js": "^9.39.2",
|
|
70
70
|
"@types/ace": "^0.0.52",
|
|
71
71
|
"@types/jest": "^29.5.14",
|
|
72
|
+
"@types/markdown-it": "^14.1.2",
|
|
72
73
|
"@types/request": "^2.48.12",
|
|
73
74
|
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
|
74
75
|
"@typescript-eslint/parser": "^8.51.0",
|
|
@@ -92,6 +93,7 @@
|
|
|
92
93
|
"jest-environment-jsdom": "^29.7.0",
|
|
93
94
|
"jest-mock": "^29.7.0",
|
|
94
95
|
"jsdom": "^26.0.0",
|
|
96
|
+
"markdown-it": "^14.1.0",
|
|
95
97
|
"npm-run-all": "^4.1.5",
|
|
96
98
|
"postcss-cli": "^11.0.1",
|
|
97
99
|
"pre-commit": "^1.2.2",
|