diff --git a/CHANGELOG.md b/CHANGELOG.md index 535db5b13..6b30980fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Unreleased - Fix browserify transform sRGB_IEC61966_2_1.icc file +- Add support for spot colors (#756, #1464) ### [v0.15.0] - 2024-03-23 diff --git a/docs/annotations.md b/docs/annotations.md index 16c1841c6..0480e9e67 100644 --- a/docs/annotations.md +++ b/docs/annotations.md @@ -19,8 +19,10 @@ and some other properties. Here is a list of the available annotation methods: * `fileAnnotation(x, y, width, height, file, options)` Many of the annotations have a `color` option that you can specify. You can -use an array of RGB values, a hex color, or a named CSS color value for that -option. +use an array of RGB values, a hex color, a named CSS color value, or a named +spot color value for that option. + + If you are adding an annotation to a piece of text, such as a link or underline, you will need to know the width and height of the text in order to @@ -58,6 +60,12 @@ Here is an example that uses a few of the annotation types. .highlight(20, doc.y, doc.widthOfString('This text is highlighted!'), height) .text('This text is highlighted!'); + // Create text with a spot color + doc.addSpotColor('PANTONE185C', 0, 100, 78, 9) + doc.moveDown() + .fillColor('PANTONE185C') + .text('This text uses spot color!'); + // Create the crossed out text doc.moveDown() .strike(20, doc.y, doc.widthOfString('STRIKE!'), height) diff --git a/docs/guide.pdf b/docs/guide.pdf index b2c2c5846..8f642de0a 100644 Binary files a/docs/guide.pdf and b/docs/guide.pdf differ diff --git a/lib/mixins/SpotColor.js b/lib/mixins/SpotColor.js new file mode 100644 index 000000000..9a6fb9988 --- /dev/null +++ b/lib/mixins/SpotColor.js @@ -0,0 +1,21 @@ +export default class SpotColor { + constructor(doc, name, C, M, Y, K) { + this.id = 'CS' + Object.keys(doc.spotColors).length; + this.name = name; + this.values = [C, M, Y, K]; + this.ref = doc.ref([ + 'Separation', + this.name, + 'DeviceCMYK', + { + Range: [0, 1, 0, 1, 0, 1, 0, 1], + C0: [0, 0, 0, 0], + C1: this.values.map(value => value / 100), + FunctionType: 2, + Domain: [0, 1], + N: 1 + } + ]); + this.ref.end(); + } +} diff --git a/lib/mixins/color.js b/lib/mixins/color.js index 318c8ab14..e1cdcdaa4 100644 --- a/lib/mixins/color.js +++ b/lib/mixins/color.js @@ -1,11 +1,13 @@ import Gradient from '../gradient'; import pattern from '../pattern'; +import SpotColor from './SpotColor'; const { PDFGradient, PDFLinearGradient, PDFRadialGradient } = Gradient; const { PDFTilingPattern } = pattern; export default { initColor() { + this.spotColors = {}; // The opacity dictionaries this._opacityRegistry = {}; this._opacityCount = 0; @@ -26,6 +28,8 @@ export default { color = [hex >> 16, (hex >> 8) & 0xff, hex & 0xff]; } else if (namedColors[color]) { color = namedColors[color]; + } else if (this.spotColors[color]) { + return this.spotColors[color]; } } @@ -66,8 +70,12 @@ export default { const space = this._getColorSpace(color); this._setColorSpace(space, stroke); - color = color.join(' '); - this.addContent(`${color} ${op}`); + if (color instanceof SpotColor) { + this.page.colorSpaces[color.id] = color.ref; + this.addContent(`1 ${op}`); + } else { + this.addContent(`${color.join(' ')} ${op}`); + } return true; }, @@ -78,6 +86,7 @@ export default { }, _getColorSpace(color) { + if (color instanceof SpotColor) return color.id; return color.length === 4 ? 'DeviceCMYK' : 'DeviceRGB'; }, @@ -163,6 +172,12 @@ export default { pattern(bbox, xStep, yStep, stream) { return new PDFTilingPattern(this, bbox, xStep, yStep, stream); + }, + + addSpotColor: function(name, C, M, Y, K) { + const color = new SpotColor(this, name, C, M, Y, K); + this.spotColors[name] = color; + return this; } }; diff --git a/lib/object.js b/lib/object.js index e2ecc36fb..28ea6b62d 100644 --- a/lib/object.js +++ b/lib/object.js @@ -40,7 +40,7 @@ class PDFObject { static convert(object, encryptFn = null) { // String literals are converted to the PDF name type if (typeof object === 'string') { - return `/${object}`; + return /^[/[(\d].*/.test(object) ? object : `/${object}` // String objects are converted to PDF strings (UTF-16) } else if (object instanceof String) { diff --git a/lib/page.js b/lib/page.js index 32b8dc580..9279527be 100644 --- a/lib/page.js +++ b/lib/page.js @@ -157,6 +157,10 @@ class PDFPage { end() { this.dictionary.end(); + this.resources.data.ColorSpace = this.resources.data.ColorSpace || {}; + for (const color of Object.values(this.document.spotColors)) { + this.resources.data.ColorSpace[color.id] = `${color.ref.id} 0 R`; + } this.resources.end(); return this.content.end(); } diff --git a/tests/unit/color.spec.js b/tests/unit/color.spec.js index d201862fd..a9f27b43d 100644 --- a/tests/unit/color.spec.js +++ b/tests/unit/color.spec.js @@ -35,4 +35,13 @@ describe('color', function() { 1 ]); }); + + test('normalize with spot color', function() { + const doc = new PDFDocument(); + doc.addSpotColor('PANTONE 123 C', 0.1, 0.2, 0.3, 0.4); + + const color = doc._normalizeColor('PANTONE 123 C'); + expect(color.id).toEqual('CS0'); + expect(color.values).toEqual([0.1, 0.2, 0.3, 0.4]); + }); }); diff --git a/tests/unit/toContainChunk/index.js b/tests/unit/toContainChunk/index.js index 1eec21706..bf1fc0b80 100644 --- a/tests/unit/toContainChunk/index.js +++ b/tests/unit/toContainChunk/index.js @@ -1,4 +1,4 @@ -import diff from 'jest-diff'; +import { diff } from 'jest-diff'; const buildMessage = (utils, data, chunk, headIndex) => { let message;