Skip to content

Commit 09eab7d

Browse files
authoredJun 24, 2020
Add blendMode options (Hopding#503)
* Add blendMode options * Linted, new tests for Node, Deno & Web
1 parent b50bfdd commit 09eab7d

File tree

9 files changed

+736
-199
lines changed

9 files changed

+736
-199
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ isolate*.log
1515
.DS_Store
1616
out.pdf
1717
*.tgz
18+
19+
.vscode/settings.json

‎apps/deno/tests/test12.ts

+182-20
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
import { Assets } from '../index.ts';
2-
import { PageSizes, PDFDocument, rgb } from '../../../dist/pdf-lib.esm.js';
2+
import {
3+
PageSizes,
4+
PDFDocument,
5+
BlendMode,
6+
cmyk,
7+
degrees,
8+
rgb,
9+
} from '../../../dist/pdf-lib.esm.js';
10+
11+
// import { Assets } from '..';
12+
// import { PageSizes, PDFDocument, BlendMode, cmyk, degrees, rgb } from '../../..';
313

414
const inchToPt = (inches: number) => Math.round(inches * 72);
515

6-
export default async (_assets: Assets) => {
7-
const pdfDoc = await PDFDocument.create();
16+
const modeNames = Object.values<string>(BlendMode);
817

9-
const page1 = pdfDoc.addPage(PageSizes.Letter);
18+
const firstPage = async (pdfDoc: PDFDocument) => {
19+
const page = pdfDoc.addPage(PageSizes.Letter);
1020

1121
// SVG sample paths from
1222
// https://svgwg.org/svg2-draft/paths.html
1323

1424
// bezier curve example
15-
page1.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
25+
page.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
1626
x: inchToPt(0.25),
1727
y: inchToPt(12),
1828
});
1929

2030
// downward facing triangle
21-
page1.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
31+
page.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
2232
x: inchToPt(-1),
2333
y: inchToPt(12),
2434
color: rgb(1, 0, 0),
@@ -29,63 +39,65 @@ export default async (_assets: Assets) => {
2939
});
3040

3141
// bezier control point adjustments
32-
page1.drawSvgPath('M100,200 C100,100 400,100 400,200', {
42+
page.drawSvgPath('M100,200 C100,100 400,100 400,200', {
3343
x: inchToPt(-1),
3444
y: inchToPt(9.25),
3545
});
36-
page1.drawSvgPath('M600,200 C675,100 975,100 900,200', {
46+
page.drawSvgPath('M600,200 C675,100 975,100 900,200', {
3747
x: inchToPt(-4.5),
3848
y: inchToPt(8.25),
3949
});
40-
page1.drawSvgPath('M100,500 C25,400 475,400 400,500', {
50+
page.drawSvgPath('M100,500 C25,400 475,400 400,500', {
4151
x: inchToPt(-1),
4252
y: inchToPt(11.25),
4353
});
44-
page1.drawSvgPath('M600,500 C600,350 900,650 900,500', {
54+
page.drawSvgPath('M600,500 C600,350 900,650 900,500', {
4555
x: inchToPt(-4.5),
4656
y: inchToPt(10.25),
4757
});
48-
page1.drawSvgPath('M100,800 C175,700 325,700 400,800', {
58+
page.drawSvgPath('M100,800 C175,700 325,700 400,800', {
4959
x: inchToPt(-1),
5060
y: inchToPt(13.35),
5161
});
52-
page1.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
62+
page.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
5363
x: inchToPt(-4.5),
5464
y: inchToPt(12.25),
5565
});
66+
};
5667

57-
const page2 = pdfDoc.addPage(PageSizes.Letter);
68+
const secondPage = async (pdfDoc: PDFDocument) => {
69+
const page = pdfDoc.addPage(PageSizes.Letter);
5870

5971
// quadratic bezier example
60-
page2.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
72+
page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
6173
x: inchToPt(-1),
6274
y: inchToPt(11),
6375
scale: 0.5,
6476
borderWidth: 2,
6577
});
66-
page2.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
78+
page.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
6779
x: inchToPt(-1),
6880
y: inchToPt(9),
6981
scale: 0.5,
7082
borderWidth: 2,
7183
});
7284

7385
// arc examples
74-
page2.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
86+
page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
7587
x: inchToPt(-1),
7688
y: inchToPt(5.5),
7789
color: rgb(1, 0, 0),
7890
borderColor: rgb(0, 0, 1),
7991
borderWidth: 1,
8092
});
81-
page2.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
93+
page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
8294
x: inchToPt(-1),
8395
y: inchToPt(5.5),
8496
color: rgb(1, 1, 0),
8597
borderColor: rgb(0, 0, 1),
8698
borderWidth: 1,
8799
});
88-
page2.drawSvgPath(
100+
page.drawSvgPath(
89101
'M600,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25',
90102
{
91103
x: inchToPt(1),
@@ -95,7 +107,7 @@ export default async (_assets: Assets) => {
95107
borderWidth: 2,
96108
},
97109
);
98-
page2.drawCircle({
110+
page.drawCircle({
99111
x: inchToPt(3),
100112
y: inchToPt(5),
101113
color: rgb(0, 1, 1),
@@ -104,13 +116,163 @@ export default async (_assets: Assets) => {
104116
borderColor: rgb(1, 0, 1),
105117
borderOpacity: 0.2,
106118
});
107-
page2.drawText('Semi-Transparent Text', {
119+
page.drawText('Semi-Transparent Text', {
108120
color: rgb(0, 1, 1),
109121
opacity: 0.5,
110122
x: inchToPt(1),
111123
y: inchToPt(2.5),
112124
size: 50,
113125
});
126+
};
127+
128+
const thirdPage = async (pdfDoc: PDFDocument, _assets: Assets) => {
129+
const page = pdfDoc.addPage(PageSizes.Letter);
130+
131+
page.drawRectangle({
132+
x: 30,
133+
y: 30,
134+
width: 100,
135+
height: 732,
136+
color: cmyk(0, 0.7, 0.3, 0),
137+
blendMode: BlendMode.Normal,
138+
});
139+
140+
page.drawRectangle({
141+
x: 340,
142+
y: 30,
143+
width: 100,
144+
height: 732,
145+
color: cmyk(0.6, 0, 0.3, 0),
146+
blendMode: BlendMode.Normal,
147+
});
148+
149+
page.drawText(`pdf-lib Blend Mode Test`, {
150+
size: 24,
151+
x: 45,
152+
y: 735,
153+
color: cmyk(0.75, 0, 0, 0),
154+
blendMode: BlendMode.Multiply,
155+
});
156+
157+
// List all blend modes available
158+
modeNames.forEach((m, i) => {
159+
page.drawText(`blendMode: ${m}`, {
160+
size: 14,
161+
x: 40,
162+
y: 700 - i * 20,
163+
color: cmyk(0, 0, 0, 0.65),
164+
blendMode: m as BlendMode,
165+
});
166+
});
167+
168+
// quadratic bezier example
169+
page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
170+
x: inchToPt(-1),
171+
y: inchToPt(10),
172+
scale: 0.5,
173+
borderWidth: 6,
174+
borderColor: cmyk(0, 0, 0, 1),
175+
blendMode: BlendMode.Overlay,
176+
});
177+
178+
// arc examples
179+
page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
180+
x: inchToPt(-1),
181+
y: inchToPt(5.5),
182+
color: cmyk(0, 1, 1, 0),
183+
borderColor: cmyk(1, 0.7, 0, 0),
184+
borderWidth: 2,
185+
blendMode: BlendMode.HardLight,
186+
});
187+
188+
page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
189+
x: inchToPt(-1),
190+
y: inchToPt(5.5),
191+
color: cmyk(0, 0.3, 1, 0),
192+
borderColor: cmyk(0, 0, 0, 1),
193+
borderWidth: 2,
194+
blendMode: BlendMode.Darken,
195+
});
196+
197+
// rectangle example
198+
page.drawRectangle({
199+
x: 350,
200+
y: 220,
201+
width: 60,
202+
height: 160,
203+
rotate: degrees(-30),
204+
color: cmyk(0.4, 0, 1, 0),
205+
borderColor: cmyk(0, 1, 1, 0),
206+
borderWidth: 2,
207+
blendMode: BlendMode.ColorBurn,
208+
});
209+
210+
// circle example
211+
page.drawCircle({
212+
x: inchToPt(3),
213+
y: inchToPt(5),
214+
color: cmyk(0.7, 1, 0.5, 0),
215+
blendMode: BlendMode.ColorDodge,
216+
});
217+
218+
// add alpha-image with 'Screen' blend mode
219+
const pngImage = await pdfDoc.embedPng(
220+
_assets.images.png.minions_banana_alpha,
221+
);
222+
const pngDims = pngImage.scale(0.5);
223+
224+
const cx = page.getWidth() / 2;
225+
const cy = page.getHeight() / 2;
226+
227+
page.drawImage(pngImage, {
228+
x: cx - pngDims.width / 2,
229+
y: cy - pngDims.height / 2,
230+
width: pngDims.width,
231+
height: pngDims.height,
232+
blendMode: BlendMode.Screen,
233+
});
234+
235+
// embed page from other PDF using blendMode
236+
237+
const [embeddedPage] = await pdfDoc.embedPdf(_assets.pdfs.normal, [0]);
238+
//const embeddedPage = await pdfDoc.embedPage(embeddedPdf.getPage(0));
239+
240+
const [px, py, scale] = [380, 100, 0.33];
241+
242+
const { width, height } = embeddedPage.scale(scale);
243+
244+
page.drawPage(embeddedPage, {
245+
x: px,
246+
y: py,
247+
xScale: scale,
248+
yScale: scale,
249+
blendMode: BlendMode.Multiply,
250+
});
251+
252+
page.drawRectangle({
253+
x: px,
254+
y: py,
255+
width: width,
256+
height: height,
257+
borderColor: cmyk(0, 1, 1, 0),
258+
borderWidth: 2,
259+
blendMode: BlendMode.Normal,
260+
});
261+
262+
page.drawText('Embedded PDF document (blendMode: Multiply)', {
263+
size: 9,
264+
x: px,
265+
y: py - 12,
266+
color: cmyk(0, 0, 0, 1),
267+
blendMode: BlendMode.Multiply,
268+
});
269+
};
270+
271+
export default async (_assets: Assets) => {
272+
const pdfDoc = await PDFDocument.create();
273+
await firstPage(pdfDoc);
274+
await secondPage(pdfDoc);
275+
await thirdPage(pdfDoc, _assets);
114276

115277
const pdfBytes = await pdfDoc.save();
116278
return pdfBytes;

‎apps/node/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from 'fs';
33
import os from 'os';
44
import { sep } from 'path';
55
import readline from 'readline';
6+
import { sep } from 'path';
67

78
import test1 from './tests/test1';
89
import test10 from './tests/test10';

‎apps/node/tests/test12.ts

+182-23
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
import { Assets } from '..';
2-
import { PageSizes, PDFDocument, rgb } from '../../..';
2+
import {
3+
PageSizes,
4+
PDFDocument,
5+
BlendMode,
6+
cmyk,
7+
degrees,
8+
rgb,
9+
} from '../../..';
310

411
const inchToPt = (inches: number) => Math.round(inches * 72);
512

6-
export default async (_assets: Assets) => {
7-
const pdfDoc = await PDFDocument.create();
8-
9-
const page1 = pdfDoc.addPage(PageSizes.Letter);
10-
13+
const firstPage = async (pdfDoc: PDFDocument) => {
14+
const page = pdfDoc.addPage(PageSizes.Letter);
15+
1116
// SVG sample paths from
1217
// https://svgwg.org/svg2-draft/paths.html
13-
18+
1419
// bezier curve example
15-
page1.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
20+
page.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
1621
x: inchToPt(0.25),
1722
y: inchToPt(12),
1823
});
1924

2025
// downward facing triangle
21-
page1.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
26+
page.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
2227
x: inchToPt(-1),
2328
y: inchToPt(12),
2429
color: rgb(1, 0, 0),
@@ -29,63 +34,65 @@ export default async (_assets: Assets) => {
2934
});
3035

3136
// bezier control point adjustments
32-
page1.drawSvgPath('M100,200 C100,100 400,100 400,200', {
37+
page.drawSvgPath('M100,200 C100,100 400,100 400,200', {
3338
x: inchToPt(-1),
3439
y: inchToPt(9.25),
3540
});
36-
page1.drawSvgPath('M600,200 C675,100 975,100 900,200', {
41+
page.drawSvgPath('M600,200 C675,100 975,100 900,200', {
3742
x: inchToPt(-4.5),
3843
y: inchToPt(8.25),
3944
});
40-
page1.drawSvgPath('M100,500 C25,400 475,400 400,500', {
45+
page.drawSvgPath('M100,500 C25,400 475,400 400,500', {
4146
x: inchToPt(-1),
4247
y: inchToPt(11.25),
4348
});
44-
page1.drawSvgPath('M600,500 C600,350 900,650 900,500', {
49+
page.drawSvgPath('M600,500 C600,350 900,650 900,500', {
4550
x: inchToPt(-4.5),
4651
y: inchToPt(10.25),
4752
});
48-
page1.drawSvgPath('M100,800 C175,700 325,700 400,800', {
53+
page.drawSvgPath('M100,800 C175,700 325,700 400,800', {
4954
x: inchToPt(-1),
5055
y: inchToPt(13.35),
5156
});
52-
page1.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
57+
page.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
5358
x: inchToPt(-4.5),
5459
y: inchToPt(12.25),
5560
});
61+
};
5662

57-
const page2 = pdfDoc.addPage(PageSizes.Letter);
63+
const secondPage = async (pdfDoc: PDFDocument) => {
64+
const page = pdfDoc.addPage(PageSizes.Letter);
5865

5966
// quadratic bezier example
60-
page2.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
67+
page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
6168
x: inchToPt(-1),
6269
y: inchToPt(11),
6370
scale: 0.5,
6471
borderWidth: 2,
6572
});
66-
page2.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
73+
page.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
6774
x: inchToPt(-1),
6875
y: inchToPt(9),
6976
scale: 0.5,
7077
borderWidth: 2,
7178
});
7279

7380
// arc examples
74-
page2.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
81+
page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
7582
x: inchToPt(-1),
7683
y: inchToPt(5.5),
7784
color: rgb(1, 0, 0),
7885
borderColor: rgb(0, 0, 1),
7986
borderWidth: 1,
8087
});
81-
page2.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
88+
page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
8289
x: inchToPt(-1),
8390
y: inchToPt(5.5),
8491
color: rgb(1, 1, 0),
8592
borderColor: rgb(0, 0, 1),
8693
borderWidth: 1,
8794
});
88-
page2.drawSvgPath(
95+
page.drawSvgPath(
8996
'M600,350 l 50,-25 a25,25 -30 0,1 50,-25 l 50,-25 a25,50 -30 0,1 50,-25 l 50,-25 a25,75 -30 0,1 50,-25 l 50,-25 a25,100 -30 0,1 50,-25 l 50,-25',
9097
{
9198
x: inchToPt(1),
@@ -95,7 +102,7 @@ export default async (_assets: Assets) => {
95102
borderWidth: 2,
96103
},
97104
);
98-
page2.drawCircle({
105+
page.drawCircle({
99106
x: inchToPt(3),
100107
y: inchToPt(5),
101108
color: rgb(0, 1, 1),
@@ -104,13 +111,165 @@ export default async (_assets: Assets) => {
104111
borderColor: rgb(1, 0, 1),
105112
borderOpacity: 0.2,
106113
});
107-
page2.drawText('Semi-Transparent Text', {
114+
page.drawText('Semi-Transparent Text', {
108115
color: rgb(0, 1, 1),
109116
opacity: 0.5,
110117
x: inchToPt(1),
111118
y: inchToPt(2.5),
112119
size: 50,
113120
});
121+
};
122+
123+
const thirdPage = async (pdfDoc: PDFDocument, _assets: Assets) => {
124+
const page = pdfDoc.addPage(PageSizes.Letter);
125+
126+
const modeNames = Object.values<string>(BlendMode);
127+
128+
page.drawRectangle({
129+
x: 30,
130+
y: 30,
131+
width: 100,
132+
height: 732,
133+
color: cmyk(0, 0.7, 0.3, 0),
134+
blendMode: BlendMode.Normal,
135+
});
136+
137+
page.drawRectangle({
138+
x: 340,
139+
y: 30,
140+
width: 100,
141+
height: 732,
142+
color: cmyk(0.6, 0, 0.3, 0),
143+
blendMode: BlendMode.Normal,
144+
});
145+
146+
page.drawText(`pdf-lib Blend Mode Test`, {
147+
size: 24,
148+
x: 45,
149+
y: 735,
150+
color: cmyk(0.75, 0, 0, 0),
151+
blendMode: BlendMode.Multiply,
152+
});
153+
154+
// List all blend modes available
155+
modeNames.forEach((m, i) => {
156+
page.drawText(`blendMode: ${m}`, {
157+
size: 14,
158+
x: 40,
159+
y: 700 - i * 20,
160+
color: cmyk(0, 0, 0, 0.65),
161+
blendMode: m as BlendMode,
162+
});
163+
});
164+
165+
// quadratic bezier example
166+
page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
167+
x: inchToPt(-1),
168+
y: inchToPt(10),
169+
scale: 0.5,
170+
borderWidth: 6,
171+
borderColor: cmyk(0, 0, 0, 1),
172+
blendMode: BlendMode.Overlay,
173+
});
174+
175+
// arc examples
176+
page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
177+
x: inchToPt(-1),
178+
y: inchToPt(5.5),
179+
color: cmyk(0, 1, 1, 0),
180+
borderColor: cmyk(1, 0.7, 0, 0),
181+
borderWidth: 2,
182+
blendMode: BlendMode.HardLight,
183+
});
184+
185+
page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
186+
x: inchToPt(-1),
187+
y: inchToPt(5.5),
188+
color: cmyk(0, 0.3, 1, 0),
189+
borderColor: cmyk(0, 0, 0, 1),
190+
borderWidth: 2,
191+
blendMode: BlendMode.Darken,
192+
});
193+
194+
// rectangle example
195+
page.drawRectangle({
196+
x: 350,
197+
y: 220,
198+
width: 60,
199+
height: 160,
200+
rotate: degrees(-30),
201+
color: cmyk(0.4, 0, 1, 0),
202+
borderColor: cmyk(0, 1, 1, 0),
203+
borderWidth: 2,
204+
blendMode: BlendMode.ColorBurn,
205+
});
206+
207+
// circle example
208+
page.drawCircle({
209+
x: inchToPt(3),
210+
y: inchToPt(5),
211+
color: cmyk(0.7, 1, 0.5, 0),
212+
blendMode: BlendMode.ColorDodge,
213+
});
214+
215+
// add alpha-image with 'Screen' blend mode
216+
const pngImage = await pdfDoc.embedPng(
217+
_assets.images.png.minions_banana_alpha,
218+
);
219+
const pngDims = pngImage.scale(0.5);
220+
221+
const cx = page.getWidth() / 2;
222+
const cy = page.getHeight() / 2;
223+
224+
page.drawImage(pngImage, {
225+
x: cx - pngDims.width / 2,
226+
y: cy - pngDims.height / 2,
227+
width: pngDims.width,
228+
height: pngDims.height,
229+
blendMode: BlendMode.Screen,
230+
});
231+
232+
// embed page from other PDF using blendMode
233+
234+
const [embeddedPage] = await pdfDoc.embedPdf(_assets.pdfs.normal, [0]);
235+
//const embeddedPage = await pdfDoc.embedPage(embeddedPdf.getPage(0));
236+
237+
const [px, py, scale] = [380, 100, 0.33];
238+
239+
const { width, height } = embeddedPage.scale(scale);
240+
241+
page.drawPage(embeddedPage, {
242+
x: px,
243+
y: py,
244+
xScale: scale,
245+
yScale: scale,
246+
blendMode: BlendMode.Multiply,
247+
});
248+
249+
page.drawRectangle({
250+
x: px,
251+
y: py,
252+
width: width,
253+
height: height,
254+
borderColor: cmyk(0, 1, 1, 0),
255+
borderWidth: 2,
256+
blendMode: BlendMode.Normal,
257+
});
258+
259+
page.drawText('Embedded PDF document (blendMode: Multiply)', {
260+
size: 9,
261+
x: px,
262+
y: py - 12,
263+
color: cmyk(0, 0, 0, 1),
264+
blendMode: BlendMode.Multiply,
265+
});
266+
};
267+
268+
export default async (_assets: Assets) => {
269+
const pdfDoc = await PDFDocument.create();
270+
await firstPage(pdfDoc);
271+
await secondPage(pdfDoc);
272+
await thirdPage(pdfDoc, _assets);
114273

115274
const pdfBytes = await pdfDoc.save();
116275
return pdfBytes;

‎apps/web/test12.html

+315-152
Large diffs are not rendered by default.

‎rollup.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ export default {
3434
sourcemap: true,
3535
},
3636
plugins: [resolve(), commonjs(), json(), MINIFY === 'true' && terser()],
37-
};
37+
};

‎src/api/PDFPage.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
PDFPageDrawSquareOptions,
2828
PDFPageDrawSVGOptions,
2929
PDFPageDrawTextOptions,
30+
BlendMode,
3031
} from 'src/api/PDFPageOptions';
3132
import { degrees, Rotation, toDegrees } from 'src/api/rotations';
3233
import { StandardFonts } from 'src/api/StandardFonts';
@@ -908,6 +909,7 @@ export default class PDFPage {
908909

909910
const graphicsStateKey = this.maybeEmbedGraphicsState({
910911
opacity: options.opacity,
912+
blendMode: options.blendMode,
911913
});
912914

913915
const contentStream = this.getContentStream();
@@ -971,6 +973,7 @@ export default class PDFPage {
971973

972974
const graphicsStateKey = this.maybeEmbedGraphicsState({
973975
opacity: options.opacity,
976+
blendMode: options.blendMode,
974977
});
975978

976979
const contentStream = this.getContentStream();
@@ -1044,6 +1047,7 @@ export default class PDFPage {
10441047

10451048
const graphicsStateKey = this.maybeEmbedGraphicsState({
10461049
opacity: options.opacity,
1050+
blendMode: options.blendMode,
10471051
});
10481052

10491053
// prettier-ignore
@@ -1128,6 +1132,7 @@ export default class PDFPage {
11281132
const graphicsStateKey = this.maybeEmbedGraphicsState({
11291133
opacity: options.opacity,
11301134
borderOpacity: options.borderOpacity,
1135+
blendMode: options.blendMode,
11311136
});
11321137

11331138
if (!('color' in options) && !('borderColor' in options)) {
@@ -1181,6 +1186,7 @@ export default class PDFPage {
11811186

11821187
const graphicsStateKey = this.maybeEmbedGraphicsState({
11831188
borderOpacity: options.opacity,
1189+
blendMode: options.blendMode,
11841190
});
11851191

11861192
if (!('color' in options)) {
@@ -1241,6 +1247,7 @@ export default class PDFPage {
12411247
const graphicsStateKey = this.maybeEmbedGraphicsState({
12421248
opacity: options.opacity,
12431249
borderOpacity: options.borderOpacity,
1250+
blendMode: options.blendMode,
12441251
});
12451252

12461253
if (!('color' in options) && !('borderColor' in options)) {
@@ -1324,9 +1331,12 @@ export default class PDFPage {
13241331
]);
13251332
assertOrUndefined(options.borderWidth, 'options.borderWidth', ['number']);
13261333

1334+
assertOrUndefined(options.blendMode, 'options.blendMode', ['string']);
1335+
13271336
const graphicsStateKey = this.maybeEmbedGraphicsState({
13281337
opacity: options.opacity,
13291338
borderOpacity: options.borderOpacity,
1339+
blendMode: options.blendMode,
13301340
});
13311341

13321342
if (!('color' in options) && !('borderColor' in options)) {
@@ -1397,20 +1407,29 @@ export default class PDFPage {
13971407
private maybeEmbedGraphicsState(options: {
13981408
opacity?: number;
13991409
borderOpacity?: number;
1410+
blendMode?: BlendMode;
14001411
}): string | undefined {
1401-
const { opacity, borderOpacity } = options;
1402-
1403-
if (opacity === undefined && borderOpacity === undefined) return undefined;
1412+
const { opacity, borderOpacity, blendMode } = options;
1413+
1414+
if (
1415+
opacity === undefined &&
1416+
borderOpacity === undefined &&
1417+
blendMode === undefined
1418+
) {
1419+
return undefined;
1420+
}
14041421

14051422
assertRangeOrUndefined(opacity, 'opacity', 0, 1);
14061423
assertRangeOrUndefined(borderOpacity, 'borderOpacity', 0, 1);
1424+
assertOrUndefined(blendMode, 'blendMode', ['string']);
14071425

14081426
const key = addRandomSuffix('GS', 10);
14091427

14101428
const graphicsState = this.doc.context.obj({
14111429
Type: 'ExtGState',
14121430
ca: opacity,
14131431
CA: borderOpacity,
1432+
BM: blendMode,
14141433
});
14151434

14161435
this.node.setExtGState(PDFName.of(key), graphicsState);

‎src/api/PDFPageOptions.ts

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,29 @@ import PDFFont from 'src/api/PDFFont';
33
import { Rotation } from 'src/api/rotations';
44
import { LineCapStyle } from 'src/api/operators';
55

6+
// export type BlendMode = 'Normal' | 'Multiply' | 'Screen' | 'Overlay' | 'Darken' | 'Lighten' | 'ColorDodge' | 'ColorBurn' | 'HardLight' | 'SoftLight' | 'Difference' | 'Exclusion';
7+
8+
export enum BlendMode {
9+
Normal = 'Normal',
10+
Multiply = 'Multiply',
11+
Screen = 'Screen',
12+
Overlay = 'Overlay',
13+
Darken = 'Darken',
14+
Lighten = 'Lighten',
15+
ColorDodge = 'ColorDodge',
16+
ColorBurn = 'ColorBurn',
17+
HardLight = 'HardLight',
18+
SoftLight = 'SoftLight',
19+
Difference = 'Difference',
20+
Exclusion = 'Exclusion',
21+
}
22+
23+
// export const knownBlendMode = (mode:string) => mode in BlendModes;
24+
625
export interface PDFPageDrawTextOptions {
726
color?: Color;
827
opacity?: number;
28+
blendMode?: BlendMode;
929
font?: PDFFont;
1030
size?: number;
1131
rotate?: Rotation;
@@ -27,6 +47,7 @@ export interface PDFPageDrawImageOptions {
2747
xSkew?: Rotation;
2848
ySkew?: Rotation;
2949
opacity?: number;
50+
blendMode?: BlendMode;
3051
}
3152

3253
export interface PDFPageDrawPageOptions {
@@ -40,6 +61,7 @@ export interface PDFPageDrawPageOptions {
4061
xSkew?: Rotation;
4162
ySkew?: Rotation;
4263
opacity?: number;
64+
blendMode?: BlendMode;
4365
}
4466

4567
export interface PDFPageDrawSVGOptions {
@@ -51,6 +73,7 @@ export interface PDFPageDrawSVGOptions {
5173
opacity?: number;
5274
borderColor?: Color;
5375
borderOpacity?: number;
76+
blendMode?: BlendMode;
5477
}
5578

5679
export interface PDFPageDrawLineOptions {
@@ -60,6 +83,7 @@ export interface PDFPageDrawLineOptions {
6083
color?: Color;
6184
lineCap?: LineCapStyle;
6285
opacity?: number;
86+
blendMode?: BlendMode;
6387
}
6488

6589
export interface PDFPageDrawRectangleOptions {
@@ -75,6 +99,7 @@ export interface PDFPageDrawRectangleOptions {
7599
opacity?: number;
76100
borderColor?: Color;
77101
borderOpacity?: number;
102+
blendMode?: BlendMode;
78103
}
79104

80105
export interface PDFPageDrawSquareOptions {
@@ -89,6 +114,7 @@ export interface PDFPageDrawSquareOptions {
89114
opacity?: number;
90115
borderColor?: Color;
91116
borderOpacity?: number;
117+
blendMode?: BlendMode;
92118
}
93119

94120
export interface PDFPageDrawEllipseOptions {
@@ -101,6 +127,7 @@ export interface PDFPageDrawEllipseOptions {
101127
borderColor?: Color;
102128
borderOpacity?: number;
103129
borderWidth?: number;
130+
blendMode?: BlendMode;
104131
}
105132

106133
export interface PDFPageDrawCircleOptions {
@@ -112,4 +139,5 @@ export interface PDFPageDrawCircleOptions {
112139
borderColor?: Color;
113140
borderOpacity?: number;
114141
borderWidth?: number;
142+
blendMode?: BlendMode;
115143
}

‎src/core/operators/PDFOperatorNames.ts

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ enum PDFOperatorNames {
1515
StrokingColorCmyk = 'K',
1616
StrokingColorspace = 'CS',
1717

18+
// Blend mode operator
19+
BlendModeOperator = 'BM',
20+
1821
// Marked Content Operators
1922
BeginMarkedContentSequence = 'BDC',
2023
BeginMarkedContent = 'BMC',

0 commit comments

Comments
 (0)
Please sign in to comment.