From fb091d503126d4fd2bf86170a6696eeb9842be4d Mon Sep 17 00:00:00 2001 From: Vincent Giersch Date: Thu, 5 Feb 2026 10:05:27 +0100 Subject: [PATCH 1/3] feat(embed): add getPDF() method for PDF export Add getPDF() method to the Embed SDK allowing customers to export scores as PDF documents with options for: - Result format (Uint8Array or dataURL) - Part filtering (array of part UUIDs) - Concert pitch export - Multi-measure rests - Colored note outlining for B&W printing Co-Authored-By: Claude Opus 4.5 --- src/embed.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/embed.ts b/src/embed.ts index aeac2738..c22927e4 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -436,6 +436,68 @@ class Embed { return this.call('getMIDI').then(data => new Uint8Array(data as [number])); } + /** + * Convert the displayed score to PDF + * + * Exports the currently loaded score as a PDF file. The PDF is generated client-side + * and includes all pages of the score with proper page layout. + * + * @param options - Export options: + * - `result`: Output format - 'Uint8Array' (default) or 'dataURL' + * - `parts`: Filter to specific parts (array of part UUIDs). If not specified, all parts are included. + * - `isConcertPitch`: Export in concert pitch instead of transposed (default: false) + * - `multiMeasuresRests`: Merge consecutive empty measures, useful for individual parts (default: false) + * - `outlineColoredNotes`: Outline colored notes for B&W printing (default: false) + * @returns A promise that resolves with the PDF data + * @throws {TypeError} If options parameter is invalid + * @throws {Error} If no score is loaded or conversion fails + * + * @example + * // Get PDF as Uint8Array + * const pdfData = await embed.getPDF(); + * const blob = new Blob([pdfData], { type: 'application/pdf' }); + * + * @example + * // Get PDF as data URL + * const dataUrl = await embed.getPDF({ result: 'dataURL' }); + * + * @example + * // Export specific parts with options + * const parts = await embed.getParts(); + * const violinPdf = await embed.getPDF({ + * parts: [parts[0].uuid], + * multiMeasuresRests: true, + * isConcertPitch: true + * }); + */ + getPDF(options?: { + /** Export result (either a PDF returned as Uint8Array or in dataURL) */ + result?: 'Uint8Array' | 'dataURL'; + /** Filter to specific parts (array of part UUIDs) */ + parts?: string[]; + /** Export in concert pitch instead of transposed (default: false) */ + isConcertPitch?: boolean; + /** Merge consecutive empty measures (default: false) */ + multiMeasuresRests?: boolean; + /** Outline colored notes for B&W printing (default: false) */ + outlineColoredNotes?: boolean; + }): Promise { + return new Promise((resolve, reject) => { + options = options || {}; + if (typeof options !== 'object') { + return reject(new TypeError('Options must be an object')); + } + this.call('getPDF', options) + .then(data => { + if (typeof data === 'string') { + return resolve(data); + } + resolve(new Uint8Array(data as [number])); + }) + .catch(reject); + }); + } + /** * Get the metadata of the score (for scores hosted on Flat) * From 2c220916dad39db5f0160f3e7382eb607218a83b Mon Sep 17 00:00:00 2001 From: Vincent Giersch Date: Thu, 5 Feb 2026 10:08:31 +0100 Subject: [PATCH 2/3] test(embed): add unit and integration tests for getPDF method - Add unit tests for options validation - Add integration tests for PDF export with various options Co-Authored-By: Claude Opus 4.5 --- test/integration/embed-integration.js | 37 +++++++++++++++++++ test/unit/embed.js | 52 +++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/test/integration/embed-integration.js b/test/integration/embed-integration.js index e241c7cf..fbb459ab 100644 --- a/test/integration/embed-integration.js +++ b/test/integration/embed-integration.js @@ -480,6 +480,43 @@ describe('Integration - Embed', () => { }); }); + describe('PDF export #full', () => { + it('should export in PDF (no options)', done => { + const { embed } = createEmbedForScoreId(PUBLIC_SCORE); + + embed.getPDF().then(pdf => { + assert.ok(pdf instanceof Uint8Array); + assert.ok(pdf.length > 0); + // PDF magic bytes: %PDF + assert.equal(pdf[0], 0x25); // % + assert.equal(pdf[1], 0x50); // P + assert.equal(pdf[2], 0x44); // D + assert.equal(pdf[3], 0x46); // F + done(); + }); + }); + + it('should export in PDF (data URL)', done => { + const { embed } = createEmbedForScoreId(PUBLIC_SCORE); + + embed.getPDF({ result: 'dataURL' }).then(pdf => { + assert.equal(typeof pdf, 'string'); + assert.equal(pdf.indexOf('data:application/pdf;base64,'), 0); + done(); + }); + }); + + it('should export in PDF with concert pitch option', done => { + const { embed } = createEmbedForScoreId(PUBLIC_SCORE); + + embed.getPDF({ isConcertPitch: true }).then(pdf => { + assert.ok(pdf instanceof Uint8Array); + assert.ok(pdf.length > 0); + done(); + }); + }); + }); + describe('MIDI import/export', () => { it('shoud load a MIDI file in a blank embed', done => { var container = document.createElement('div'); diff --git a/test/unit/embed.js b/test/unit/embed.js index f27c0cd8..1629b873 100644 --- a/test/unit/embed.js +++ b/test/unit/embed.js @@ -173,4 +173,56 @@ describe('Unit - Embed tests', () => { container.removeChild(embed.element); }); }); + + describe('getPDF method', () => { + it('should have getPDF method available', () => { + const container = document.getElementById('container'); + const embed = new Flat.Embed(container, { score: '1234' }); + assert.equal(typeof embed.getPDF, 'function'); + container.removeChild(embed.element); + }); + + it('should reject when options is not an object', done => { + const container = document.getElementById('container'); + const embed = new Flat.Embed(container, { score: '1234' }); + embed.getPDF('invalid').catch(err => { + assert.ok(err instanceof TypeError); + assert.equal(err.message, 'Options must be an object'); + container.removeChild(embed.element); + done(); + }); + }); + + it('should accept valid options object', () => { + const container = document.getElementById('container'); + const embed = new Flat.Embed(container, { score: '1234' }); + // This should not throw - the actual call will fail since no score is loaded, + // but the options validation should pass + const promise = embed.getPDF({ + result: 'Uint8Array', + parts: ['uuid-1', 'uuid-2'], + isConcertPitch: true, + multiMeasuresRests: true, + outlineColoredNotes: true, + }); + assert.ok(promise instanceof Promise); + container.removeChild(embed.element); + }); + + it('should accept empty options object', () => { + const container = document.getElementById('container'); + const embed = new Flat.Embed(container, { score: '1234' }); + const promise = embed.getPDF({}); + assert.ok(promise instanceof Promise); + container.removeChild(embed.element); + }); + + it('should accept undefined options', () => { + const container = document.getElementById('container'); + const embed = new Flat.Embed(container, { score: '1234' }); + const promise = embed.getPDF(); + assert.ok(promise instanceof Promise); + container.removeChild(embed.element); + }); + }); }); From 6c0912413ae075bb6e9585e27c2ff79a5893353a Mon Sep 17 00:00:00 2001 From: Vincent Giersch Date: Fri, 6 Feb 2026 11:26:12 +0100 Subject: [PATCH 3/3] refactor(embed): remove dataURL option from getPDF, always return Uint8Array Co-Authored-By: Claude Opus 4.6 --- src/embed.ts | 14 ++------------ test/integration/embed-integration.js | 10 ---------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/embed.ts b/src/embed.ts index c22927e4..d6ddc6f8 100644 --- a/src/embed.ts +++ b/src/embed.ts @@ -443,12 +443,11 @@ class Embed { * and includes all pages of the score with proper page layout. * * @param options - Export options: - * - `result`: Output format - 'Uint8Array' (default) or 'dataURL' * - `parts`: Filter to specific parts (array of part UUIDs). If not specified, all parts are included. * - `isConcertPitch`: Export in concert pitch instead of transposed (default: false) * - `multiMeasuresRests`: Merge consecutive empty measures, useful for individual parts (default: false) * - `outlineColoredNotes`: Outline colored notes for B&W printing (default: false) - * @returns A promise that resolves with the PDF data + * @returns A promise that resolves with a Uint8Array containing the PDF data * @throws {TypeError} If options parameter is invalid * @throws {Error} If no score is loaded or conversion fails * @@ -458,10 +457,6 @@ class Embed { * const blob = new Blob([pdfData], { type: 'application/pdf' }); * * @example - * // Get PDF as data URL - * const dataUrl = await embed.getPDF({ result: 'dataURL' }); - * - * @example * // Export specific parts with options * const parts = await embed.getParts(); * const violinPdf = await embed.getPDF({ @@ -471,8 +466,6 @@ class Embed { * }); */ getPDF(options?: { - /** Export result (either a PDF returned as Uint8Array or in dataURL) */ - result?: 'Uint8Array' | 'dataURL'; /** Filter to specific parts (array of part UUIDs) */ parts?: string[]; /** Export in concert pitch instead of transposed (default: false) */ @@ -481,7 +474,7 @@ class Embed { multiMeasuresRests?: boolean; /** Outline colored notes for B&W printing (default: false) */ outlineColoredNotes?: boolean; - }): Promise { + }): Promise { return new Promise((resolve, reject) => { options = options || {}; if (typeof options !== 'object') { @@ -489,9 +482,6 @@ class Embed { } this.call('getPDF', options) .then(data => { - if (typeof data === 'string') { - return resolve(data); - } resolve(new Uint8Array(data as [number])); }) .catch(reject); diff --git a/test/integration/embed-integration.js b/test/integration/embed-integration.js index fbb459ab..d1aaaaf2 100644 --- a/test/integration/embed-integration.js +++ b/test/integration/embed-integration.js @@ -496,16 +496,6 @@ describe('Integration - Embed', () => { }); }); - it('should export in PDF (data URL)', done => { - const { embed } = createEmbedForScoreId(PUBLIC_SCORE); - - embed.getPDF({ result: 'dataURL' }).then(pdf => { - assert.equal(typeof pdf, 'string'); - assert.equal(pdf.indexOf('data:application/pdf;base64,'), 0); - done(); - }); - }); - it('should export in PDF with concert pitch option', done => { const { embed } = createEmbedForScoreId(PUBLIC_SCORE);