astro-lilypond 1.1.2 → 1.3.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.
@@ -1,8 +1,9 @@
1
1
  import type { AstroIntegrationLogger } from "astro";
2
- import type { Format } from "./render.js";
2
+ import type { Backend, Format } from "./render.js";
3
3
  export interface ExecLilypondOptions {
4
4
  binaryPath: string;
5
5
  format: Format;
6
+ backend: Backend;
6
7
  crop: boolean;
7
8
  resolution: number;
8
9
  includePaths: string[];
@@ -8,7 +8,7 @@ const MAX_BUFFER = 16 * 1024 * 1024; // 16 MiB
8
8
  * build output); throws a normalized Error on a non-zero exit or timeout.
9
9
  */
10
10
  export async function execLilyPond(options) {
11
- const { binaryPath, format, crop, resolution, includePaths, timeout, inputPath, outputBase, logger, } = options;
11
+ const { binaryPath, format, backend, crop, resolution, includePaths, timeout, inputPath, outputBase, logger, } = options;
12
12
  const args = [
13
13
  // Render the correct format
14
14
  `--format=${format}`,
@@ -20,9 +20,9 @@ export async function execLilyPond(options) {
20
20
  // inside of individual .ly files
21
21
  // Disable point-and-click behavior on SVGs when clicking noteheads
22
22
  "--define-default=no-point-and-click",
23
- // Use the `cairo` backend for graphics rendering; faster than the default `ps` backend
23
+ // `cairo` is faster, but only `ps` supports \postscript markup
24
24
  // https://lilypond.org/doc/v2.26/Documentation/usage/advanced-command_002dline-options-for-lilypond
25
- "--define-default=backend=cairo",
25
+ `--define-default=backend=${backend}`,
26
26
  // Resolution for generating PNGs (set in DPI)
27
27
  `--define-default=resolution=${resolution}`,
28
28
  // Set cropping
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import type { AstroIntegration } from "astro";
2
2
  import { type AstroComponentFactory } from "astro/runtime/server/index.js";
3
3
  import { type AutoInstallOptions } from "./binary/index.js";
4
4
  import { type PluginOptions } from "./plugins/index.js";
5
- import { type LilypondDefaults } from "./render.js";
6
- import { type LilypondMetadata } from "./utils/index.js";
5
+ import { type Backend, type LilypondDefaults } from "./render.js";
6
+ import { type LilypondMetadata, type RenderedHtmlOptions } from "./utils/index.js";
7
7
  export declare const LY_EXTENSIONS: readonly [".ly", ".lilypond", ".ily"];
8
8
  export type { AstroComponentFactory, AutoInstallOptions, LilypondDefaults, LilypondMetadata, PluginOptions as LilypondPluginOptions, };
9
9
  export interface LilypondPage {
@@ -29,6 +29,11 @@ export interface GetScoreOptions {
29
29
  * @default the `defaults.format` configured on the integration ("svg" unless overridden)
30
30
  */
31
31
  format?: "svg" | "png";
32
+ /**
33
+ * Graphics backend for `Score` and `pdf`.
34
+ * @default the `defaults.backend` configured on the integration ("cairo" unless overridden)
35
+ */
36
+ backend?: Backend;
32
37
  /**
33
38
  * Crop `Score` to a single tightly-fit image instead of full pages.
34
39
  * @default false
@@ -54,18 +59,13 @@ export interface GetScoreResult {
54
59
  meta: LilypondMetadata;
55
60
  raw: string;
56
61
  }
57
- interface ScoreImageProps {
58
- pageLimit?: number;
59
- class?: string;
60
- style?: string;
61
- alt?: string;
62
- }
62
+ type ScoreImageProps = RenderedHtmlOptions;
63
63
  /**
64
64
  * Renders a `LilypondScore` (from a `.ly`/`.ily`/`.lilypond` import,
65
65
  * or a `lilypondLoader()` entry) to a renderable `<Score />` component.
66
66
  */
67
67
  export declare function getScore(score: LilypondScore, options?: GetScoreOptions): Promise<GetScoreResult>;
68
- export interface ScoreProps extends ScoreImageProps, Pick<GetScoreOptions, "format" | "crop"> {
68
+ export interface ScoreProps extends ScoreImageProps, Pick<GetScoreOptions, "format" | "backend" | "crop"> {
69
69
  /**
70
70
  * A `LilypondScore` from a `.ly`/`.ily`/`.lilypond` import
71
71
  * or a `lilypondLoader()` entry.
package/dist/index.js CHANGED
@@ -10,11 +10,7 @@ export const LY_EXTENSIONS = [".ly", ".lilypond", ".ily"];
10
10
  function createScoreComponent(content) {
11
11
  return createComponent((_result, props) => {
12
12
  const alt = props.alt ?? content.alt ?? "";
13
- const html = renderedHtml(content.pages, alt, {
14
- class: props.class,
15
- style: props.style,
16
- pageLimit: props.pageLimit,
17
- });
13
+ const html = renderedHtml(content.pages, { ...props, alt });
18
14
  return renderTemplate `${unescapeHTML(html)}`;
19
15
  });
20
16
  }
@@ -34,8 +30,10 @@ function createErrorScoreComponent(error, title) {
34
30
  export async function getScore(score, options = {}) {
35
31
  const state = getLilypondState();
36
32
  const { logger } = state;
37
- const { resolution, cropScale, format: defaultFormat, } = resolveDefaults(state.defaults);
33
+ const { resolution, cropScale, backend: defaultBackend, format: defaultFormat, } = resolveDefaults(state.defaults);
38
34
  const format = options.format ?? defaultFormat;
35
+ const backend = options.backend ?? defaultBackend;
36
+ const defaults = { ...state.defaults, backend };
39
37
  const crop = options.crop ?? false;
40
38
  try {
41
39
  const [{ Score, pages }, pdf] = await Promise.all([
@@ -43,6 +41,7 @@ export async function getScore(score, options = {}) {
43
41
  const pages = await emitLilypondAsset({
44
42
  title: score.assetTitle,
45
43
  format,
44
+ backend,
46
45
  source: score.source,
47
46
  resolution,
48
47
  crop,
@@ -51,7 +50,7 @@ export async function getScore(score, options = {}) {
51
50
  render: () => render(score.source, {
52
51
  format,
53
52
  crop,
54
- defaults: state.defaults,
53
+ defaults,
55
54
  timeout: state.timeout,
56
55
  binaryPath: state.binaryPath,
57
56
  includePaths: score.includePaths,
@@ -68,11 +67,12 @@ export async function getScore(score, options = {}) {
68
67
  ? emitLilypondPdfAsset({
69
68
  title: score.assetTitle,
70
69
  source: score.source,
70
+ backend,
71
71
  binaryPath: state.binaryPath,
72
72
  render: () => render(score.source, {
73
73
  format: "pdf",
74
74
  crop: false,
75
- defaults: state.defaults,
75
+ defaults,
76
76
  timeout: state.timeout,
77
77
  binaryPath: state.binaryPath,
78
78
  includePaths: score.includePaths,
@@ -102,8 +102,12 @@ export async function getScore(score, options = {}) {
102
102
  * `getScore()` instead when you need those.
103
103
  */
104
104
  export const Score = createComponent(async (result, props) => {
105
- const { content, format, crop, ...imageProps } = props;
106
- const { Score: ContentScore } = await getScore(content, { format, crop });
105
+ const { content, format, backend, crop, ...imageProps } = props;
106
+ const { Score: ContentScore } = await getScore(content, {
107
+ format,
108
+ backend,
109
+ crop,
110
+ });
107
111
  return renderTemplate `${renderComponent(result, "Score", ContentScore, imageProps)}`;
108
112
  });
109
113
  function injectMarkdownPlugin(processor, key, plugin) {
@@ -9,13 +9,14 @@ import { altTextForBlock, emitLilypondAsset, prependVersion, renderedErrorHtml,
9
9
  export async function renderMarkdownBlock(options, block) {
10
10
  const { logger } = options;
11
11
  const { title } = block;
12
- const { version, format, resolution, cropScale } = resolveDefaults(options.defaults);
12
+ const { version, format, backend, resolution, cropScale } = resolveDefaults(options.defaults);
13
13
  const source = version ? prependVersion(block.value, version) : block.value;
14
14
  const alt = altTextForBlock(block.meta, block.value);
15
15
  try {
16
16
  const pages = await emitLilypondAsset({
17
17
  title,
18
18
  format,
19
+ backend,
19
20
  source,
20
21
  resolution,
21
22
  crop: true,
@@ -32,7 +33,7 @@ export async function renderMarkdownBlock(options, block) {
32
33
  logger,
33
34
  }),
34
35
  });
35
- return renderedHtml(pages, alt);
36
+ return renderedHtml(pages, { alt });
36
37
  }
37
38
  catch (err) {
38
39
  if (!options.isDev)
package/dist/render.d.ts CHANGED
@@ -2,6 +2,8 @@ import type { AstroIntegrationLogger } from "astro";
2
2
  import type { LilypondVersion } from "./types/lilypondVersion.js";
3
3
  export declare const FORMATS: readonly ["png", "svg", "pdf"];
4
4
  export type Format = (typeof FORMATS)[number];
5
+ export declare const BACKENDS: readonly ["cairo", "ps"];
6
+ export type Backend = (typeof BACKENDS)[number];
5
7
  /**
6
8
  * Defaults passed to each score for rendering.
7
9
  */
@@ -17,6 +19,12 @@ export interface LilypondDefaults {
17
19
  * @default "svg"
18
20
  */
19
21
  format?: "svg" | "png";
22
+ /**
23
+ * Graphics backend. `"cairo"` is recommended for faster output.
24
+ * `"ps"` is required for `\postscript` markup, and can't produce SVG.
25
+ * @default "cairo"
26
+ */
27
+ backend?: Backend;
20
28
  /**
21
29
  * Resolution in DPI (only applies to PNG).
22
30
  * @default 144
package/dist/render.js CHANGED
@@ -5,9 +5,11 @@ 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
+ export const BACKENDS = ["cairo", "ps"];
8
9
  const defaultLilypondDefaults = {
9
10
  version: "2.26.0",
10
11
  format: "svg",
12
+ backend: "cairo",
11
13
  resolution: 144,
12
14
  cropScale: 1.5,
13
15
  };
@@ -20,10 +22,16 @@ export const defaultOptions = {
20
22
  };
21
23
  export async function render(source, options) {
22
24
  const { format = defaultOptions.format, crop = defaultOptions.crop, binaryPath = defaultOptions.binaryPath, timeout = defaultOptions.timeout, includePaths = [], sourceName, logger, } = options;
23
- const { resolution } = resolveDefaults(options.defaults);
25
+ const { backend, resolution } = resolveDefaults(options.defaults);
24
26
  if (!FORMATS.includes(format)) {
25
27
  throw new Error(`${format} is not a supported format`);
26
28
  }
29
+ if (!BACKENDS.includes(backend)) {
30
+ throw new Error(`${backend} is not a supported backend`);
31
+ }
32
+ if (backend === "ps" && format === "svg") {
33
+ throw new Error('The "ps" backend can\'t render SVG; use format "png" or backend "cairo"');
34
+ }
27
35
  const dir = await mkdtemp(join(tmpdir(), "astro-lilypond-"));
28
36
  const inputPath = join(dir, safeInputFileName(sourceName));
29
37
  const outputBase = join(dir, "output");
@@ -32,6 +40,7 @@ export async function render(source, options) {
32
40
  await execLilyPond({
33
41
  binaryPath,
34
42
  format,
43
+ backend,
35
44
  crop,
36
45
  resolution,
37
46
  includePaths,
@@ -1,8 +1,9 @@
1
1
  import type { LilypondPage } from "../index.js";
2
- import type { Format } from "../render.js";
2
+ import type { Backend, Format } from "../render.js";
3
3
  export interface EmitLilypondAssetOptions {
4
4
  title: string;
5
5
  format: Format;
6
+ backend: Backend;
6
7
  source: string;
7
8
  resolution: number;
8
9
  crop: boolean;
@@ -4,8 +4,8 @@ export async function emitLilypondAsset(options) {
4
4
  if (!options.binaryPath) {
5
5
  throw new Error("astro-lilypond: please add the `lilypond()` integration to your Astro config.");
6
6
  }
7
- const { title, format, source, resolution, crop, sizeScale, render } = options;
8
- const assets = await emitAsset(`${title}.[hash].${format}`, [source, format, resolution, crop, sizeScale], async () => {
7
+ const { title, format, backend, source, resolution, crop, sizeScale, render, } = options;
8
+ const assets = await emitAsset(`${title}.[hash].${format}`, [source, format, backend, resolution, crop, sizeScale], async () => {
9
9
  const buffers = await render();
10
10
  return buffers.map((data) => {
11
11
  const dimensions = imageDimensionsFor(format, data);
@@ -1,7 +1,9 @@
1
1
  import type { LilypondPdfResult } from "../index.js";
2
+ import type { Backend } from "../render.js";
2
3
  export interface EmitLilypondPdfAssetOptions {
3
4
  title: string;
4
5
  source: string;
6
+ backend: Backend;
5
7
  binaryPath: string | undefined;
6
8
  render: () => Promise<Buffer[]>;
7
9
  }
@@ -3,8 +3,8 @@ export async function emitLilypondPdfAsset(options) {
3
3
  if (!options.binaryPath) {
4
4
  throw new Error("astro-lilypond: please add the `lilypond()` integration to your Astro config.");
5
5
  }
6
- const { title, source, render } = options;
7
- const asset = await emitAsset(`${title}.[hash].pdf`, [source, "pdf"], async () => {
6
+ const { title, source, backend, render } = options;
7
+ const asset = await emitAsset(`${title}.[hash].pdf`, [source, "pdf", backend], async () => {
8
8
  const [data] = await render();
9
9
  return { data };
10
10
  });
@@ -16,7 +16,7 @@ export type { LyHeaderFields } from "./parseLyHeader.js";
16
16
  export { extractMarkupText, parseLyHeader, parseLyHeaderFields, } from "./parseLyHeader.js";
17
17
  export { prependVersion } from "./prependVersion.js";
18
18
  export { renderedErrorHtml } from "./renderedErrorHtml.js";
19
- export { renderedHtml } from "./renderedHtml.js";
19
+ export { type RenderedHtmlOptions, renderedHtml } from "./renderedHtml.js";
20
20
  export { resolveDefaults } from "./resolveDefaults.js";
21
21
  export { sourceNameFor } from "./sourceNameFor.js";
22
22
  export { titleFor } from "./titleFor.js";
@@ -1,5 +1,6 @@
1
+ import type { HTMLAttributes } from "astro/types";
1
2
  import type { LilypondPage } from "../index.js";
2
- export interface RenderedHtmlOptions {
3
+ export interface RenderedHtmlOptions extends Partial<Pick<HTMLAttributes<"img">, "alt" | "loading" | "decoding" | "fetchpriority">> {
3
4
  /** Class applied to the outer `<img>` or `<ol>` tag. */
4
5
  class?: string;
5
6
  /** Inline styles applied to the outer `<img>` or `<ol>`. */
@@ -7,4 +8,4 @@ export interface RenderedHtmlOptions {
7
8
  /** Render only the first `n` pages. */
8
9
  pageLimit?: number;
9
10
  }
10
- export declare function renderedHtml(pages: LilypondPage[], alt: string, options?: RenderedHtmlOptions): string;
11
+ export declare function renderedHtml(pages: LilypondPage[], options?: RenderedHtmlOptions): string;
@@ -1,19 +1,23 @@
1
1
  import { addAttribute } from "astro/runtime/server/index.js";
2
- function imgTag(page, alt) {
3
- return `<img data-lilypond-image${addAttribute(page.src, "src")}${addAttribute(page.width, "width")}${addAttribute(page.height, "height")}${addAttribute(alt, "alt")}>`;
2
+ function imgTag(page, alt, imageAttrs) {
3
+ return `<img data-lilypond-image${addAttribute(page.src, "src")}${addAttribute(page.width, "width")}${addAttribute(page.height, "height")}${addAttribute(alt, "alt")}${imageAttrs}>`;
4
4
  }
5
- export function renderedHtml(pages, alt, options = {}) {
6
- const { class: className, style, pageLimit } = options;
5
+ export function renderedHtml(pages, options = {}) {
6
+ const { alt, class: className, style, pageLimit, loading, decoding, fetchpriority, } = options;
7
+ const resolvedAlt = alt ?? "";
7
8
  const limitedPages = pageLimit === undefined ? pages : pages.slice(0, pageLimit);
8
9
  if (limitedPages.length === 0)
9
10
  return "";
10
11
  const classAttr = addAttribute(className, "class");
11
12
  const styleAttr = addAttribute(style, "style");
13
+ const imageAttrs = addAttribute(loading, "loading") +
14
+ addAttribute(decoding, "decoding") +
15
+ addAttribute(fetchpriority, "fetchpriority");
12
16
  if (limitedPages.length === 1) {
13
17
  const page = limitedPages[0];
14
- return `<img data-lilypond-image${classAttr}${addAttribute(page.src, "src")}${addAttribute(page.width, "width")}${addAttribute(page.height, "height")}${addAttribute(alt, "alt")}${styleAttr}>`;
18
+ return `<img data-lilypond-image${classAttr}${addAttribute(page.src, "src")}${addAttribute(page.width, "width")}${addAttribute(page.height, "height")}${addAttribute(resolvedAlt, "alt")}${imageAttrs}${styleAttr}>`;
15
19
  }
16
20
  return `<ol data-lilypond-group${classAttr}${styleAttr}>${limitedPages
17
- .map((page) => `<li>${imgTag(page, alt)}</li>`)
21
+ .map((page) => `<li>${imgTag(page, resolvedAlt, imageAttrs)}</li>`)
18
22
  .join("")}</ol>`;
19
23
  }
@@ -1,10 +1,11 @@
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, format, resolution, cropScale } = defaultOptions.defaults;
4
+ const { version, format, backend, resolution, cropScale } = defaultOptions.defaults;
5
5
  return {
6
6
  version: defaults?.version ?? version,
7
7
  format: defaults?.format ?? format,
8
+ backend: defaults?.backend ?? backend,
8
9
  resolution: defaults?.resolution ?? resolution,
9
10
  cropScale: defaults?.cropScale ?? cropScale,
10
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-lilypond",
3
- "version": "1.1.2",
3
+ "version": "1.3.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",
@@ -46,8 +46,8 @@
46
46
  "utils"
47
47
  ],
48
48
  "peerDependencies": {
49
- "astro": ">=7.0.0",
50
- "@astrojs/markdown-remark": ">=7.0.0"
49
+ "@astrojs/markdown-remark": ">=7.0.0",
50
+ "astro": ">=7.0.0"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "@astrojs/markdown-remark": {
@@ -59,17 +59,17 @@
59
59
  "unist-util-visit": "^5.1.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@astrojs/markdown-remark": "^7.2.2",
63
- "@astrojs/markdown-satteri": "^0.3.5",
62
+ "@astrojs/markdown-remark": "^7.3.1",
63
+ "@astrojs/markdown-satteri": "^0.4.2",
64
64
  "@types/mdast": "^4.0.4",
65
- "@types/node": "^26.1.1",
66
- "@vitest/coverage-v8": "^4.1.10",
67
- "astro": "^7.1.6",
68
- "satteri": "^0.9.5",
65
+ "@types/node": "^26.6.3",
66
+ "@vitest/coverage-v8": "^5.0.2",
67
+ "astro": "^7.3.5",
68
+ "satteri": "^0.10.5",
69
69
  "typescript": "^7.0.2",
70
70
  "unified": "^11.0.5",
71
- "vite": "^8.0.0",
72
- "vitest": "^4.1.10"
71
+ "vite": "^8.3.1",
72
+ "vitest": "^5.0.2"
73
73
  },
74
74
  "scripts": {
75
75
  "build": "tsc --project tsconfig.build.json",