astro-lilypond 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -31,7 +31,7 @@ export interface RenderOptions {
31
31
  format?: "svg" | "png";
32
32
  /**
33
33
  * Crop `Score` to a single tightly-fit image instead of full pages.
34
- * @default resolved from the integration's `defaults.crop`
34
+ * @default false
35
35
  */
36
36
  crop?: boolean;
37
37
  /**
@@ -54,11 +54,38 @@ export interface RenderResult {
54
54
  meta: LilypondMetadata;
55
55
  raw: string;
56
56
  }
57
+ interface ScoreImageProps {
58
+ pageLimit?: number;
59
+ class?: string;
60
+ style?: string;
61
+ alt?: string;
62
+ }
57
63
  /**
58
64
  * Renders a `LilypondScore` (from a `.ly`/`.ily`/`.lilypond` import,
59
65
  * or a `lilypondLoader()` entry) to a renderable `<Score />` component.
60
66
  */
61
67
  export declare function render(score: LilypondScore, options?: RenderOptions): Promise<RenderResult>;
68
+ export interface ScoreProps extends ScoreImageProps {
69
+ /**
70
+ * A `LilypondScore` from a `.ly`/`.ily`/`.lilypond` import
71
+ * or a `lilypondLoader()` entry.
72
+ */
73
+ content: LilypondScore;
74
+ /**
75
+ * @default "svg"
76
+ */
77
+ format?: "svg" | "png";
78
+ /**
79
+ * @default false
80
+ */
81
+ crop?: boolean;
82
+ }
83
+ /**
84
+ * Renders a `LilypondScore` directly, for the common case where none of
85
+ * `render()`'s `pageCount`, `meta`, `raw`, or `pdf` are needed. Use
86
+ * `render()` instead when you need those.
87
+ */
88
+ export declare const Score: AstroComponentFactory;
62
89
  export declare function renderAll(scores: LilypondScore[], options?: RenderOptions): Promise<RenderResult[]>;
63
90
  export interface LilypondOptions extends PluginOptions {
64
91
  /**
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { createComponent, renderTemplate, unescapeHTML, } from "astro/runtime/server/index.js";
1
+ import { createComponent, renderComponent, renderTemplate, unescapeHTML, } from "astro/runtime/server/index.js";
2
2
  import emitAssetIntegration from "astro-emit-asset";
3
3
  import { rehypePlugin, remarkPlugin, satteriPlugin, } from "./plugins/index.js";
4
- import { render as renderScore, resolveCrop, } from "./render.js";
4
+ import { render as renderScore } from "./render.js";
5
5
  import { getRenderState, resolveAndSetRenderState } from "./renderState.js";
6
6
  import { altTextFor, emitLilypondAsset, emitLilypondPdfAsset, includePathsFor, lyTypeDeclarationsFor, parseLyHeaderFields, prependVersion, renderedHtml, resolveDefaults, sourceNameFor, titleFor, toLilypondMetadata, } from "./utils/index.js";
7
7
  export const LY_EXTENSIONS = [".ly", ".lilypond", ".ily"];
@@ -22,9 +22,9 @@ function createScoreComponent(content) {
22
22
  */
23
23
  export async function render(score, options = {}) {
24
24
  const state = getRenderState();
25
- const { resolution, crop: cropSetting, cropScale, } = resolveDefaults(state.defaults);
25
+ const { resolution, cropScale } = resolveDefaults(state.defaults);
26
26
  const format = options.format ?? "svg";
27
- const crop = options.crop ?? resolveCrop(cropSetting, "component");
27
+ const crop = options.crop ?? false;
28
28
  const [{ Score, pageCount }, pdf] = await Promise.all([
29
29
  (async () => {
30
30
  const pages = await emitLilypondAsset({
@@ -69,6 +69,16 @@ export async function render(score, options = {}) {
69
69
  ]);
70
70
  return { Score, pageCount, pdf, meta: score.meta, raw: score.source };
71
71
  }
72
+ /**
73
+ * Renders a `LilypondScore` directly, for the common case where none of
74
+ * `render()`'s `pageCount`, `meta`, `raw`, or `pdf` are needed. Use
75
+ * `render()` instead when you need those.
76
+ */
77
+ export const Score = createComponent(async (result, props) => {
78
+ const { content, format, crop, ...imageProps } = props;
79
+ const { Score: ContentScore } = await render(content, { format, crop });
80
+ return renderTemplate `${renderComponent(result, "Score", ContentScore, imageProps)}`;
81
+ });
72
82
  export async function renderAll(scores, options = {}) {
73
83
  return Promise.all(scores.map((score) => render(score, options)));
74
84
  }
@@ -1,5 +1,5 @@
1
1
  import { visit } from "unist-util-visit";
2
- import { defaultOptions, render, resolveCrop } from "../render.js";
2
+ import { defaultOptions, render } from "../render.js";
3
3
  import { altTextForBlock, emitLilypondAsset, includePathsFor, isLilypondLang, prependVersion, renderedHtml, resolveDefaults, sourceNameFor, titleFor, } from "../utils/index.js";
4
4
  // Typed loosely so it's assignable to both RehypePlugin and the unified
5
5
  // Plugin generic regardless of which @types/hast version the host project pins.
@@ -30,22 +30,21 @@ export function rehypePlugin(options) {
30
30
  .filter((c) => c.type === "text")
31
31
  .map((c) => c.value)
32
32
  .join("");
33
- const { version, resolution, crop: cropSetting, cropScale, } = resolveDefaults(options.defaults);
33
+ const { version, resolution, cropScale } = resolveDefaults(options.defaults);
34
34
  const source = version ? prependVersion(raw, version) : raw;
35
35
  const format = options.format ?? defaultOptions.format;
36
- const crop = resolveCrop(cropSetting, "markdown");
37
36
  const alt = altTextForBlock(codeNode.data?.meta, raw);
38
37
  const promise = emitLilypondAsset({
39
38
  title,
40
39
  format,
41
40
  source,
42
41
  resolution,
43
- crop,
44
- sizeScale: crop ? cropScale : 1,
42
+ crop: true,
43
+ sizeScale: cropScale,
45
44
  binaryPath: options.binaryPath,
46
45
  render: () => render(source, {
47
46
  format,
48
- crop,
47
+ crop: true,
49
48
  defaults: options.defaults,
50
49
  timeout: options.timeout,
51
50
  binaryPath: options.binaryPath,
@@ -1,5 +1,5 @@
1
1
  import { visit } from "unist-util-visit";
2
- import { defaultOptions, render, resolveCrop } from "../render.js";
2
+ import { defaultOptions, render } from "../render.js";
3
3
  import { altTextForBlock, emitLilypondAsset, includePathsFor, isLilypondLang, prependVersion, renderedHtml, resolveDefaults, sourceNameFor, titleFor, } from "../utils/index.js";
4
4
  export const remarkPlugin = (options) => {
5
5
  return async (tree, file) => {
@@ -10,22 +10,21 @@ export const remarkPlugin = (options) => {
10
10
  visit(tree, "code", (node, index, parent) => {
11
11
  if (!isLilypondLang(node.lang) || index === undefined || !parent)
12
12
  return;
13
- const { version, resolution, crop: cropSetting, cropScale, } = resolveDefaults(options.defaults);
13
+ const { version, resolution, cropScale } = resolveDefaults(options.defaults);
14
14
  const source = version ? prependVersion(node.value, version) : node.value;
15
15
  const format = options.format ?? defaultOptions.format;
16
- const crop = resolveCrop(cropSetting, "markdown");
17
16
  const alt = altTextForBlock(node.meta, node.value);
18
17
  const promise = emitLilypondAsset({
19
18
  title,
20
19
  format,
21
20
  source,
22
21
  resolution,
23
- crop,
24
- sizeScale: crop ? cropScale : 1,
22
+ crop: true,
23
+ sizeScale: cropScale,
25
24
  binaryPath: options.binaryPath,
26
25
  render: () => render(source, {
27
26
  format,
28
- crop,
27
+ crop: true,
29
28
  defaults: options.defaults,
30
29
  timeout: options.timeout,
31
30
  binaryPath: options.binaryPath,
@@ -1,4 +1,4 @@
1
- import { defaultOptions, render, resolveCrop } from "../render.js";
1
+ import { defaultOptions, render } from "../render.js";
2
2
  import { altTextForBlock, emitLilypondAsset, includePathsFor, isLilypondLang, prependVersion, renderedHtml, resolveDefaults, sourceNameFor, titleFor, } from "../utils/index.js";
3
3
  export function satteriPlugin(options) {
4
4
  return {
@@ -9,25 +9,24 @@ export function satteriPlugin(options) {
9
9
  async code(node, ctx) {
10
10
  if (!isLilypondLang(node.lang))
11
11
  return undefined;
12
- const { version, resolution, crop: cropSetting, cropScale, } = resolveDefaults(options.defaults);
12
+ const { version, resolution, cropScale } = resolveDefaults(options.defaults);
13
13
  const source = version ? prependVersion(node.value, version) : node.value;
14
14
  const format = options.format ?? defaultOptions.format;
15
15
  const includePaths = includePathsFor(ctx.fileURL);
16
16
  const sourceName = sourceNameFor(ctx.fileURL);
17
17
  const title = titleFor(sourceName);
18
- const crop = resolveCrop(cropSetting, "markdown");
19
18
  const alt = altTextForBlock(node.meta, node.value);
20
19
  const pages = await emitLilypondAsset({
21
20
  title,
22
21
  format,
23
22
  source,
24
23
  resolution,
25
- crop,
26
- sizeScale: crop ? cropScale : 1,
24
+ crop: true,
25
+ sizeScale: cropScale,
27
26
  binaryPath: options.binaryPath,
28
27
  render: () => render(source, {
29
28
  format,
30
- crop,
29
+ crop: true,
31
30
  defaults: options.defaults,
32
31
  timeout: options.timeout,
33
32
  binaryPath: options.binaryPath,
package/dist/render.d.ts CHANGED
@@ -1,22 +1,6 @@
1
1
  import type { LilypondVersion } from "./types/lilypondVersion.js";
2
2
  export declare const FORMATS: readonly ["png", "svg", "pdf"];
3
3
  export type Format = (typeof FORMATS)[number];
4
- /**
5
- * Controls whether rendered output is cropped tightly to the content
6
- * bounding box, or left as full, potentially multi-page, output.
7
- *
8
- * - `true` — crop everywhere
9
- * - `false` — never crop by default
10
- * - `"markdown-only"` — crop Markdown fences, but leave `render()` uncropped by default
11
- */
12
- export type CropSetting = boolean | "markdown-only";
13
- /** Which kind of consumer is resolving a `CropSetting` — see `resolveCrop`. */
14
- export type CropContext = "markdown" | "component";
15
- /**
16
- * Resolves the three-way `CropSetting` into the plain boolean a given
17
- * consumer needs, per the semantics documented on `CropSetting`.
18
- */
19
- export declare function resolveCrop(cropSetting: CropSetting, context: CropContext): boolean;
20
4
  /**
21
5
  * Defaults passed to each score for rendering.
22
6
  */
@@ -32,12 +16,6 @@ export interface LilypondDefaults {
32
16
  * @default 144
33
17
  */
34
18
  resolution?: number;
35
- /**
36
- * Crop the output tightly to the content bounding box, producing one
37
- * continuous image instead of paginated output.
38
- * @default "markdown-only"
39
- */
40
- crop?: CropSetting;
41
19
  /**
42
20
  * Multiplies the `width`/`height` on a cropped score's `<img>` tag.
43
21
  * Helps compensate for LilyPond's internal size units (points/mm)
@@ -50,10 +28,10 @@ export interface LilypondDefaults {
50
28
  }
51
29
  /**
52
30
  * The subset of `LilypondDefaults` that `render()` itself reads. `version`
53
- * and `crop` are resolved by the caller before reaching `render()`
54
- * (`version` by prepending it to source text, `crop` via `resolveCrop`).
31
+ * is resolved by the caller before reaching `render()`, by prepending it to
32
+ * source text.
55
33
  */
56
- export type RenderDefaults = Omit<LilypondDefaults, "version" | "crop">;
34
+ export type RenderDefaults = Omit<LilypondDefaults, "version">;
57
35
  export interface RenderOptions {
58
36
  /**
59
37
  * Output format.
package/dist/render.js CHANGED
@@ -5,13 +5,6 @@ import { execLilyPond } from "./execLilyPond.js";
5
5
  import { readOutputFile, safeInputFileName } from "./readOutputFile.js";
6
6
  import { resolveDefaults } from "./utils/resolveDefaults.js";
7
7
  export const FORMATS = ["png", "svg", "pdf"];
8
- /**
9
- * Resolves the three-way `CropSetting` into the plain boolean a given
10
- * consumer needs, per the semantics documented on `CropSetting`.
11
- */
12
- export function resolveCrop(cropSetting, context) {
13
- return context === "markdown" ? cropSetting !== false : cropSetting === true;
14
- }
15
8
  export const defaultOptions = {
16
9
  format: "svg",
17
10
  crop: true,
@@ -20,7 +13,6 @@ export const defaultOptions = {
20
13
  defaults: {
21
14
  version: "2.26.0",
22
15
  resolution: 144,
23
- crop: "markdown-only",
24
16
  cropScale: 1.5,
25
17
  },
26
18
  };
@@ -1,5 +1,5 @@
1
1
  import type { vi } from "vitest";
2
- import type { emitLilypondAsset } from "./emitLilypondAsset.js";
2
+ import type { emitLilypondAsset } from "../emitLilypondAsset.js";
3
3
  type EmitLilypondAssetMock = ReturnType<typeof vi.mocked<typeof emitLilypondAsset>>;
4
4
  export declare function fakeEmitLilypondAsset(mock: EmitLilypondAssetMock, urlBase?: string): void;
5
5
  export declare function fakeEmitLilypondAssetPropagatingRenderErrors(mock: EmitLilypondAssetMock): void;
@@ -1,5 +1,5 @@
1
1
  import type { vi } from "vitest";
2
- import type { emitLilypondPdfAsset } from "./emitLilypondPdfAsset.js";
2
+ import type { emitLilypondPdfAsset } from "../emitLilypondPdfAsset.js";
3
3
  type EmitLilypondPdfAssetMock = ReturnType<typeof vi.mocked<typeof emitLilypondPdfAsset>>;
4
4
  export declare function fakeEmitLilypondPdfAsset(mock: EmitLilypondPdfAssetMock, urlBase?: string): void;
5
5
  export {};
@@ -209,9 +209,8 @@ function parseFieldValue(body, start) {
209
209
  i++;
210
210
  if (body[i] !== "{")
211
211
  return null;
212
+ // body is itself a properly-nested brace sequence (see allHeaderBodies), so this `{` always has a matching `}`.
212
213
  const end = matchBalancedBraces(body, i);
213
- if (end === -1)
214
- return null;
215
214
  const value = extractMarkupText(body.slice(i + 1, end));
216
215
  return value ? { value, end: end + 1 } : null;
217
216
  }
@@ -1,11 +1,10 @@
1
1
  import { defaultOptions } from "../render.js";
2
2
  /** Fills in any `defaults` fields left unset with `render.ts`'s own defaults. */
3
3
  export function resolveDefaults(defaults) {
4
- const { version, resolution, crop, cropScale } = defaultOptions.defaults;
4
+ const { version, resolution, cropScale } = defaultOptions.defaults;
5
5
  return {
6
6
  version: defaults?.version ?? version,
7
7
  resolution: defaults?.resolution ?? resolution,
8
- crop: defaults?.crop ?? crop,
9
8
  cropScale: defaults?.cropScale ?? cropScale,
10
9
  };
11
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-lilypond",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "An Astro integration for rendering Lilypond markup to music notation.",
5
5
  "homepage": "https://lilypond.ky.fyi",
6
6
  "author": "Ky Decker",
@@ -54,6 +54,7 @@
54
54
  "@astrojs/markdown-satteri": "^0.3.4",
55
55
  "@types/mdast": "^4.0.4",
56
56
  "@types/node": "^26.1.1",
57
+ "@vitest/coverage-v8": "^4.1.10",
57
58
  "astro": "^7.0.8",
58
59
  "satteri": "^0.9.5",
59
60
  "typescript": "^7.0.2",
@@ -64,6 +65,7 @@
64
65
  "scripts": {
65
66
  "build": "tsc --project tsconfig.build.json",
66
67
  "test": "vitest --project unit",
68
+ "test:coverage": "vitest run --project unit --coverage",
67
69
  "test:integration": "vitest run --project integration"
68
70
  }
69
71
  }