Repository: Hopding/pdf-lib
Pull request #503 (2020-06-23 19:02:40)

Add blendMode options
Added `blendMode` optional property for drawing methods.
Added tests for Node and Deno. (`test14.ts`)


Patch:
diff --git a/.gitignore b/.gitignore
index a3a90c2..7b6199b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@ isolate*.log
 .DS_Store
 out.pdf
 *.tgz
+
+.vscode/settings.json
\ No newline at end of file
diff --git a/apps/deno/index.ts b/apps/deno/index.ts
index 72e6de1..e39c6bd 100644
--- a/apps/deno/index.ts
+++ b/apps/deno/index.ts
@@ -1,5 +1,6 @@
 import { dirname } from 'https://deno.land/std@0.50.0/path/mod.ts';
 import { readLines } from 'https://deno.land/std@v0.50.0/io/bufio.ts';
+import { sep } from 'https://deno.land/std@v0.50.0/path/mod.ts';
 
 import test1 from './tests/test1.ts';
 import test10 from './tests/test10.ts';
@@ -32,7 +33,7 @@ const openPdf = (path: string, reader: string = '') => {
     // Deno.run({ cmd: ['open', '-a', 'Firefox', path] });
   } else if (Deno.build.os === 'windows') {
     // Opens with the default PDF Reader, has room for improvment
-    Deno.run({ cmd: ['start', path] });
+    Deno.run({ cmd: ['cmd', '/c', 'start', path] });
   } else {
     const msg1 = `Note: Automatically opening PDFs currently only works on Macs and Windows. If you're using a Linux machine, please consider contributing to expand support for this feature`;
     const msg2 = `(https://github.com/Hopding/pdf-lib/blob/master/apps/node/index.ts#L8-L17)\n`;
@@ -44,7 +45,7 @@ const openPdf = (path: string, reader: string = '') => {
 const tempDir = () => dirname(Deno.makeTempDirSync());
 
 const writePdfToTmp = (pdf: Uint8Array) => {
-  const path = `${tempDir()}/${Date.now()}.pdf`;
+  const path = `${tempDir()}${sep}${Date.now()}.pdf`;
   Deno.writeFileSync(path, pdf);
   return path;
 };
diff --git a/apps/node/index.ts b/apps/node/index.ts
index 7af47c1..33fb71c 100644
--- a/apps/node/index.ts
+++ b/apps/node/index.ts
@@ -1,7 +1,9 @@
 import { execSync } from 'child_process';
 import fs from 'fs';
 import os from 'os';
+import { sep } from 'path';
 import readline from 'readline';
+import { sep } from 'path';
 
 import test1 from './tests/test1';
 import test10 from './tests/test10';
@@ -37,7 +39,7 @@ const openPdf = (path: string, _reader?: string) => {
     // execSync(`open -a "Firefox" '${path}'`);
   } else if (process.platform === 'win32') {
     // Opens with the default PDF Reader, has room for improvment
-    execSync(`start '${path}'`);
+    execSync(`start ${path}`);
   } else {
     const msg1 = `Note: Automatically opening PDFs currently only works on Macs and Windows. If you're using a Linux machine, please consider contributing to expand support for this feature`;
     const msg2 = `(https://github.com/Hopding/pdf-lib/blob/master/apps/node/index.ts#L8-L17)\n`;
@@ -47,7 +49,7 @@ const openPdf = (path: string, _reader?: string) => {
 };
 
 const writePdfToTmp = (pdf: Uint8Array) => {
-  const path = `${os.tmpdir()}/${Date.now()}.pdf`;
+  const path = `${os.tmpdir()}${sep}${Date.now()}.pdf`;
   fs.writeFileSync(path, pdf);
   return path;
 };
diff --git a/rollup.config.js b/rollup.config.js
index cde0993..fb993df 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -6,10 +6,17 @@ import { terser } from 'rollup-plugin-terser';
 const { MINIFY, MODULE_TYPE } = process.env;
 
 const IgnoredWarnings = [
+  // Mac & Linux
   'Circular dependency: es/api/PDFDocument.js -> es/api/PDFFont.js -> es/api/PDFDocument.js',
   'Circular dependency: es/api/PDFDocument.js -> es/api/PDFImage.js -> es/api/PDFDocument.js',
   'Circular dependency: es/api/PDFDocument.js -> es/api/PDFPage.js -> es/api/PDFDocument.js',
   'Circular dependency: es/api/PDFDocument.js -> es/api/PDFEmbeddedPage.js -> es/api/PDFDocument.js',
+
+  // Windows
+  'Circular dependency: es\\api\\PDFDocument.js -> es\\api\\PDFFont.js -> es\\api\\PDFDocument.js',
+  'Circular dependency: es\\api\\PDFDocument.js -> es\\api\\PDFImage.js -> es\\api\\PDFDocument.js',
+  'Circular dependency: es\\api\\PDFDocument.js -> es\\api\\PDFPage.js -> es\\api\\PDFDocument.js',
+  'Circular dependency: es\\api\\PDFDocument.js -> es\\api\\PDFEmbeddedPage.js -> es\\api\\PDFDocument.js'
 ];
 
 // Silence circular dependency warnings we don't care about
@@ -27,4 +34,4 @@ export default {
     sourcemap: true,
   },
   plugins: [resolve(), commonjs(), json(), MINIFY === 'true' && terser()],
-};
+};
\ No newline at end of file
diff --git a/src/api/PDFPage.ts b/src/api/PDFPage.ts
index 03a6856..2405ea1 100644
--- a/src/api/PDFPage.ts
+++ b/src/api/PDFPage.ts
@@ -27,6 +27,7 @@ import {
   PDFPageDrawSquareOptions,
   PDFPageDrawSVGOptions,
   PDFPageDrawTextOptions,
+  BlendMode,
 } from 'src/api/PDFPageOptions';
 import { degrees, Rotation, toDegrees } from 'src/api/rotations';
 import { StandardFonts } from 'src/api/StandardFonts';
@@ -908,6 +909,7 @@ export default class PDFPage {
 
     const graphicsStateKey = this.maybeEmbedGraphicsState({
       opacity: options.opacity,
+      blendMode: options.blendMode,
     });
 
     const contentStream = this.getContentStream();
@@ -971,6 +973,7 @@ export default class PDFPage {
 
     const graphicsStateKey = this.maybeEmbedGraphicsState({
       opacity: options.opacity,
+      blendMode: options.blendMode,
     });
 
     const contentStream = this.getContentStream();
@@ -1044,6 +1047,7 @@ export default class PDFPage {
 
     const graphicsStateKey = this.maybeEmbedGraphicsState({
       opacity: options.opacity,
+      blendMode: options.blendMode,
     });
 
     // prettier-ignore
@@ -1128,6 +1132,7 @@ export default class PDFPage {
     const graphicsStateKey = this.maybeEmbedGraphicsState({
       opacity: options.opacity,
       borderOpacity: options.borderOpacity,
+      blendMode: options.blendMode,
     });
 
     if (!('color' in options) && !('borderColor' in options)) {
@@ -1181,6 +1186,7 @@ export default class PDFPage {
 
     const graphicsStateKey = this.maybeEmbedGraphicsState({
       borderOpacity: options.opacity,
+      blendMode: options.blendMode,
     });
 
     if (!('color' in options)) {
@@ -1241,6 +1247,7 @@ export default class PDFPage {
     const graphicsStateKey = this.maybeEmbedGraphicsState({
       opacity: options.opacity,
       borderOpacity: options.borderOpacity,
+      blendMode: options.blendMode,
     });
 
     if (!('color' in options) && !('borderColor' in options)) {
@@ -1324,9 +1331,12 @@ export default class PDFPage {
     ]);
     assertOrUndefined(options.borderWidth, 'options.borderWidth', ['number']);
 
+    assertOrUndefined(options.blendMode, 'options.blendMode', ['string']);
+
     const graphicsStateKey = this.maybeEmbedGraphicsState({
       opacity: options.opacity,
       borderOpacity: options.borderOpacity,
+      blendMode: options.blendMode,
     });
 
     if (!('color' in options) && !('borderColor' in options)) {
@@ -1397,13 +1407,21 @@ export default class PDFPage {
   private maybeEmbedGraphicsState(options: {
     opacity?: number;
     borderOpacity?: number;
+    blendMode?: BlendMode;
   }): string | undefined {
-    const { opacity, borderOpacity } = options;
-
-    if (opacity === undefined && borderOpacity === undefined) return undefined;
+    const { opacity, borderOpacity, blendMode } = options;
+
+    if (
+      opacity === undefined &&
+      borderOpacity === undefined &&
+      blendMode === undefined
+    ) {
+      return undefined;
+    }
 
     assertRangeOrUndefined(opacity, 'opacity', 0, 1);
     assertRangeOrUndefined(borderOpacity, 'borderOpacity', 0, 1);
+    assertOrUndefined(blendMode, 'blendMode', ['string']);
 
     const key = addRandomSuffix('GS', 10);
 
@@ -1411,6 +1429,7 @@ export default class PDFPage {
       Type: 'ExtGState',
       ca: opacity,
       CA: borderOpacity,
+      BM: blendMode,
     });
 
     this.node.setExtGState(PDFName.of(key), graphicsState);
diff --git a/src/api/PDFPageOptions.ts b/src/api/PDFPageOptions.ts
index b14f94d..60b57fa 100644
--- a/src/api/PDFPageOptions.ts
+++ b/src/api/PDFPageOptions.ts
@@ -3,9 +3,29 @@ import PDFFont from 'src/api/PDFFont';
 import { Rotation } from 'src/api/rotations';
 import { LineCapStyle } from 'src/api/operators';
 
+// export type BlendMode = 'Normal' | 'Multiply' | 'Screen' | 'Overlay' | 'Darken' | 'Lighten' | 'ColorDodge' | 'ColorBurn' | 'HardLight' | 'SoftLight' | 'Difference' | 'Exclusion';
+
+export enum BlendMode {
+  Normal = 'Normal',
+  Multiply = 'Multiply',
+  Screen = 'Screen',
+  Overlay = 'Overlay',
+  Darken = 'Darken',
+  Lighten = 'Lighten',
+  ColorDodge = 'ColorDodge',
+  ColorBurn = 'ColorBurn',
+  HardLight = 'HardLight',
+  SoftLight = 'SoftLight',
+  Difference = 'Difference',
+  Exclusion = 'Exclusion',
+}
+
+// export const knownBlendMode = (mode:string) => mode in BlendModes;
+
 export interface PDFPageDrawTextOptions {
   color?: Color;
   opacity?: number;
+  blendMode?: BlendMode;
   font?: PDFFont;
   size?: number;
   rotate?: Rotation;
@@ -27,6 +47,7 @@ export interface PDFPageDrawImageOptions {
   xSkew?: Rotation;
   ySkew?: Rotation;
   opacity?: number;
+  blendMode?: BlendMode;
 }
 
 export interface PDFPageDrawPageOptions {
@@ -40,6 +61,7 @@ export interface PDFPageDrawPageOptions {
   xSkew?: Rotation;
   ySkew?: Rotation;
   opacity?: number;
+  blendMode?: BlendMode;
 }
 
 export interface PDFPageDrawSVGOptions {
@@ -51,6 +73,7 @@ export interface PDFPageDrawSVGOptions {
   opacity?: number;
   borderColor?: Color;
   borderOpacity?: number;
+  blendMode?: BlendMode;
 }
 
 export interface PDFPageDrawLineOptions {
@@ -60,6 +83,7 @@ export interface PDFPageDrawLineOptions {
   color?: Color;
   lineCap?: LineCapStyle;
   opacity?: number;
+  blendMode?: BlendMode;
 }
 
 export interface PDFPageDrawRectangleOptions {
@@ -75,6 +99,7 @@ export interface PDFPageDrawRectangleOptions {
   opacity?: number;
   borderColor?: Color;
   borderOpacity?: number;
+  blendMode?: BlendMode;
 }
 
 export interface PDFPageDrawSquareOptions {
@@ -89,6 +114,7 @@ export interface PDFPageDrawSquareOptions {
   opacity?: number;
   borderColor?: Color;
   borderOpacity?: number;
+  blendMode?: BlendMode;
 }
 
 export interface PDFPageDrawEllipseOptions {
@@ -101,6 +127,7 @@ export interface PDFPageDrawEllipseOptions {
   borderColor?: Color;
   borderOpacity?: number;
   borderWidth?: number;
+  blendMode?: BlendMode;
 }
 
 export interface PDFPageDrawCircleOptions {
@@ -112,4 +139,5 @@ export interface PDFPageDrawCircleOptions {
   borderColor?: Color;
   borderOpacity?: number;
   borderWidth?: number;
+  blendMode?: BlendMode;
 }
diff --git a/src/core/operators/PDFOperatorNames.ts b/src/core/operators/PDFOperatorNames.ts
index 53cc99a..f9cad57 100644
--- a/src/core/operators/PDFOperatorNames.ts
+++ b/src/core/operators/PDFOperatorNames.ts
@@ -15,6 +15,9 @@ enum PDFOperatorNames {
   StrokingColorCmyk = 'K',
   StrokingColorspace = 'CS',
 
+  // Blend mode operator
+  BlendModeOperator = 'BM',
+
   // Marked Content Operators
   BeginMarkedContentSequence = 'BDC',
   BeginMarkedContent = 'BMC',
diff --git a/src/utils/png.ts b/src/utils/png.ts
index b41e810..60aad74 100644
--- a/src/utils/png.ts
+++ b/src/utils/png.ts
@@ -58,7 +58,7 @@ export class PNG {
 
     this.rgbChannel = rgbChannel;
 
-    const hasAlphaValues = alphaChannel.some((a) => a < 1);
+    const hasAlphaValues = alphaChannel.some((a) => a < 255);
     if (hasAlphaValues) this.alphaChannel = alphaChannel;
 
     this.type = getImageType(upng.ctype);


Test patch:
diff --git a/apps/deno/tests/test12.ts b/apps/deno/tests/test12.ts
index 249bf2b..e2370fc 100644
--- a/apps/deno/tests/test12.ts
+++ b/apps/deno/tests/test12.ts
@@ -1,24 +1,34 @@
 import { Assets } from '../index.ts';
-import { PageSizes, PDFDocument, rgb } from '../../../dist/pdf-lib.esm.js';
+import {
+  PageSizes,
+  PDFDocument,
+  BlendMode,
+  cmyk,
+  degrees,
+  rgb,
+} from '../../../dist/pdf-lib.esm.js';
+
+// import { Assets } from '..';
+// import { PageSizes, PDFDocument, BlendMode, cmyk, degrees, rgb } from '../../..';
 
 const inchToPt = (inches: number) => Math.round(inches * 72);
 
-export default async (_assets: Assets) => {
-  const pdfDoc = await PDFDocument.create();
+const modeNames = Object.values<string>(BlendMode);
 
-  const page1 = pdfDoc.addPage(PageSizes.Letter);
+const firstPage = async (pdfDoc: PDFDocument) => {
+  const page = pdfDoc.addPage(PageSizes.Letter);
 
   // SVG sample paths from
   // https://svgwg.org/svg2-draft/paths.html
 
   // bezier curve example
-  page1.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
+  page.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
     x: inchToPt(0.25),
     y: inchToPt(12),
   });
 
   // downward facing triangle
-  page1.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
+  page.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
     x: inchToPt(-1),
     y: inchToPt(12),
     color: rgb(1, 0, 0),
@@ -29,41 +39,43 @@ export default async (_assets: Assets) => {
   });
 
   // bezier control point adjustments
-  page1.drawSvgPath('M100,200 C100,100 400,100 400,200', {
+  page.drawSvgPath('M100,200 C100,100 400,100 400,200', {
     x: inchToPt(-1),
     y: inchToPt(9.25),
   });
-  page1.drawSvgPath('M600,200 C675,100 975,100 900,200', {
+  page.drawSvgPath('M600,200 C675,100 975,100 900,200', {
     x: inchToPt(-4.5),
     y: inchToPt(8.25),
   });
-  page1.drawSvgPath('M100,500 C25,400 475,400 400,500', {
+  page.drawSvgPath('M100,500 C25,400 475,400 400,500', {
     x: inchToPt(-1),
     y: inchToPt(11.25),
   });
-  page1.drawSvgPath('M600,500 C600,350 900,650 900,500', {
+  page.drawSvgPath('M600,500 C600,350 900,650 900,500', {
     x: inchToPt(-4.5),
     y: inchToPt(10.25),
   });
-  page1.drawSvgPath('M100,800 C175,700 325,700 400,800', {
+  page.drawSvgPath('M100,800 C175,700 325,700 400,800', {
     x: inchToPt(-1),
     y: inchToPt(13.35),
   });
-  page1.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
+  page.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
     x: inchToPt(-4.5),
     y: inchToPt(12.25),
   });
+};
 
-  const page2 = pdfDoc.addPage(PageSizes.Letter);
+const secondPage = async (pdfDoc: PDFDocument) => {
+  const page = pdfDoc.addPage(PageSizes.Letter);
 
   // quadratic bezier example
-  page2.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
+  page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
     x: inchToPt(-1),
     y: inchToPt(11),
     scale: 0.5,
     borderWidth: 2,
   });
-  page2.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
+  page.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
     x: inchToPt(-1),
     y: inchToPt(9),
     scale: 0.5,
@@ -71,21 +83,21 @@ export default async (_assets: Assets) => {
   });
 
   // arc examples
-  page2.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
+  page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
     x: inchToPt(-1),
     y: inchToPt(5.5),
     color: rgb(1, 0, 0),
     borderColor: rgb(0, 0, 1),
     borderWidth: 1,
   });
-  page2.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
+  page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
     x: inchToPt(-1),
     y: inchToPt(5.5),
     color: rgb(1, 1, 0),
     borderColor: rgb(0, 0, 1),
     borderWidth: 1,
   });
-  page2.drawSvgPath(
+  page.drawSvgPath(
     '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',
     {
       x: inchToPt(1),
@@ -95,7 +107,7 @@ export default async (_assets: Assets) => {
       borderWidth: 2,
     },
   );
-  page2.drawCircle({
+  page.drawCircle({
     x: inchToPt(3),
     y: inchToPt(5),
     color: rgb(0, 1, 1),
@@ -104,13 +116,163 @@ export default async (_assets: Assets) => {
     borderColor: rgb(1, 0, 1),
     borderOpacity: 0.2,
   });
-  page2.drawText('Semi-Transparent Text', {
+  page.drawText('Semi-Transparent Text', {
     color: rgb(0, 1, 1),
     opacity: 0.5,
     x: inchToPt(1),
     y: inchToPt(2.5),
     size: 50,
   });
+};
+
+const thirdPage = async (pdfDoc: PDFDocument, _assets: Assets) => {
+  const page = pdfDoc.addPage(PageSizes.Letter);
+
+  page.drawRectangle({
+    x: 30,
+    y: 30,
+    width: 100,
+    height: 732,
+    color: cmyk(0, 0.7, 0.3, 0),
+    blendMode: BlendMode.Normal,
+  });
+
+  page.drawRectangle({
+    x: 340,
+    y: 30,
+    width: 100,
+    height: 732,
+    color: cmyk(0.6, 0, 0.3, 0),
+    blendMode: BlendMode.Normal,
+  });
+
+  page.drawText(`pdf-lib Blend Mode Test`, {
+    size: 24,
+    x: 45,
+    y: 735,
+    color: cmyk(0.75, 0, 0, 0),
+    blendMode: BlendMode.Multiply,
+  });
+
+  // List all blend modes available
+  modeNames.forEach((m, i) => {
+    page.drawText(`blendMode: ${m}`, {
+      size: 14,
+      x: 40,
+      y: 700 - i * 20,
+      color: cmyk(0, 0, 0, 0.65),
+      blendMode: m as BlendMode,
+    });
+  });
+
+  // quadratic bezier example
+  page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
+    x: inchToPt(-1),
+    y: inchToPt(10),
+    scale: 0.5,
+    borderWidth: 6,
+    borderColor: cmyk(0, 0, 0, 1),
+    blendMode: BlendMode.Overlay,
+  });
+
+  // arc examples
+  page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
+    x: inchToPt(-1),
+    y: inchToPt(5.5),
+    color: cmyk(0, 1, 1, 0),
+    borderColor: cmyk(1, 0.7, 0, 0),
+    borderWidth: 2,
+    blendMode: BlendMode.HardLight,
+  });
+
+  page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
+    x: inchToPt(-1),
+    y: inchToPt(5.5),
+    color: cmyk(0, 0.3, 1, 0),
+    borderColor: cmyk(0, 0, 0, 1),
+    borderWidth: 2,
+    blendMode: BlendMode.Darken,
+  });
+
+  // rectangle example
+  page.drawRectangle({
+    x: 350,
+    y: 220,
+    width: 60,
+    height: 160,
+    rotate: degrees(-30),
+    color: cmyk(0.4, 0, 1, 0),
+    borderColor: cmyk(0, 1, 1, 0),
+    borderWidth: 2,
+    blendMode: BlendMode.ColorBurn,
+  });
+
+  // circle example
+  page.drawCircle({
+    x: inchToPt(3),
+    y: inchToPt(5),
+    color: cmyk(0.7, 1, 0.5, 0),
+    blendMode: BlendMode.ColorDodge,
+  });
+
+  // add alpha-image with 'Screen' blend mode
+  const pngImage = await pdfDoc.embedPng(
+    _assets.images.png.minions_banana_alpha,
+  );
+  const pngDims = pngImage.scale(0.5);
+
+  const cx = page.getWidth() / 2;
+  const cy = page.getHeight() / 2;
+
+  page.drawImage(pngImage, {
+    x: cx - pngDims.width / 2,
+    y: cy - pngDims.height / 2,
+    width: pngDims.width,
+    height: pngDims.height,
+    blendMode: BlendMode.Screen,
+  });
+
+  // embed page from other PDF using blendMode
+
+  const [embeddedPage] = await pdfDoc.embedPdf(_assets.pdfs.normal, [0]);
+  //const embeddedPage = await pdfDoc.embedPage(embeddedPdf.getPage(0));
+
+  const [px, py, scale] = [380, 100, 0.33];
+
+  const { width, height } = embeddedPage.scale(scale);
+
+  page.drawPage(embeddedPage, {
+    x: px,
+    y: py,
+    xScale: scale,
+    yScale: scale,
+    blendMode: BlendMode.Multiply,
+  });
+
+  page.drawRectangle({
+    x: px,
+    y: py,
+    width: width,
+    height: height,
+    borderColor: cmyk(0, 1, 1, 0),
+    borderWidth: 2,
+    blendMode: BlendMode.Normal,
+  });
+
+  page.drawText('Embedded PDF document (blendMode: Multiply)', {
+    size: 9,
+    x: px,
+    y: py - 12,
+    color: cmyk(0, 0, 0, 1),
+    blendMode: BlendMode.Multiply,
+  });
+};
+
+export default async (_assets: Assets) => {
+  const pdfDoc = await PDFDocument.create();
+  await firstPage(pdfDoc);
+  await secondPage(pdfDoc);
+  await thirdPage(pdfDoc, _assets);
 
   const pdfBytes = await pdfDoc.save();
   return pdfBytes;
diff --git a/apps/node/tests/test12.ts b/apps/node/tests/test12.ts
index 70c42ac..87191ab 100644
--- a/apps/node/tests/test12.ts
+++ b/apps/node/tests/test12.ts
@@ -1,24 +1,29 @@
 import { Assets } from '..';
-import { PageSizes, PDFDocument, rgb } from '../../..';
+import {
+  PageSizes,
+  PDFDocument,
+  BlendMode,
+  cmyk,
+  degrees,
+  rgb,
+} from '../../..';
 
 const inchToPt = (inches: number) => Math.round(inches * 72);
 
-export default async (_assets: Assets) => {
-  const pdfDoc = await PDFDocument.create();
-
-  const page1 = pdfDoc.addPage(PageSizes.Letter);
-
+const firstPage = async (pdfDoc: PDFDocument) => {
+  const page = pdfDoc.addPage(PageSizes.Letter);
+  
   // SVG sample paths from
   // https://svgwg.org/svg2-draft/paths.html
-
+  
   // bezier curve example
-  page1.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
+  page.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
     x: inchToPt(0.25),
     y: inchToPt(12),
   });
 
   // downward facing triangle
-  page1.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
+  page.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
     x: inchToPt(-1),
     y: inchToPt(12),
     color: rgb(1, 0, 0),
@@ -29,41 +34,43 @@ export default async (_assets: Assets) => {
   });
 
   // bezier control point adjustments
-  page1.drawSvgPath('M100,200 C100,100 400,100 400,200', {
+  page.drawSvgPath('M100,200 C100,100 400,100 400,200', {
     x: inchToPt(-1),
     y: inchToPt(9.25),
   });
-  page1.drawSvgPath('M600,200 C675,100 975,100 900,200', {
+  page.drawSvgPath('M600,200 C675,100 975,100 900,200', {
     x: inchToPt(-4.5),
     y: inchToPt(8.25),
   });
-  page1.drawSvgPath('M100,500 C25,400 475,400 400,500', {
+  page.drawSvgPath('M100,500 C25,400 475,400 400,500', {
     x: inchToPt(-1),
     y: inchToPt(11.25),
   });
-  page1.drawSvgPath('M600,500 C600,350 900,650 900,500', {
+  page.drawSvgPath('M600,500 C600,350 900,650 900,500', {
     x: inchToPt(-4.5),
     y: inchToPt(10.25),
   });
-  page1.drawSvgPath('M100,800 C175,700 325,700 400,800', {
+  page.drawSvgPath('M100,800 C175,700 325,700 400,800', {
     x: inchToPt(-1),
     y: inchToPt(13.35),
   });
-  page1.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
+  page.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
     x: inchToPt(-4.5),
     y: inchToPt(12.25),
   });
+};
 
-  const page2 = pdfDoc.addPage(PageSizes.Letter);
+const secondPage = async (pdfDoc: PDFDocument) => {
+  const page = pdfDoc.addPage(PageSizes.Letter);
 
   // quadratic bezier example
-  page2.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
+  page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
     x: inchToPt(-1),
     y: inchToPt(11),
     scale: 0.5,
     borderWidth: 2,
   });
-  page2.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
+  page.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
     x: inchToPt(-1),
     y: inchToPt(9),
     scale: 0.5,
@@ -71,21 +78,21 @@ export default async (_assets: Assets) => {
   });
 
   // arc examples
-  page2.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
+  page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
     x: inchToPt(-1),
     y: inchToPt(5.5),
     color: rgb(1, 0, 0),
     borderColor: rgb(0, 0, 1),
     borderWidth: 1,
   });
-  page2.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
+  page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
     x: inchToPt(-1),
     y: inchToPt(5.5),
     color: rgb(1, 1, 0),
     borderColor: rgb(0, 0, 1),
     borderWidth: 1,
   });
-  page2.drawSvgPath(
+  page.drawSvgPath(
     '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',
     {
       x: inchToPt(1),
@@ -95,7 +102,7 @@ export default async (_assets: Assets) => {
       borderWidth: 2,
     },
   );
-  page2.drawCircle({
+  page.drawCircle({
     x: inchToPt(3),
     y: inchToPt(5),
     color: rgb(0, 1, 1),
@@ -104,13 +111,165 @@ export default async (_assets: Assets) => {
     borderColor: rgb(1, 0, 1),
     borderOpacity: 0.2,
   });
-  page2.drawText('Semi-Transparent Text', {
+  page.drawText('Semi-Transparent Text', {
     color: rgb(0, 1, 1),
     opacity: 0.5,
     x: inchToPt(1),
     y: inchToPt(2.5),
     size: 50,
   });
+};
+
+const thirdPage = async (pdfDoc: PDFDocument, _assets: Assets) => {
+  const page = pdfDoc.addPage(PageSizes.Letter);
+
+  const modeNames = Object.values<string>(BlendMode);
+  
+  page.drawRectangle({
+    x: 30,
+    y: 30,
+    width: 100,
+    height: 732,
+    color: cmyk(0, 0.7, 0.3, 0),
+    blendMode: BlendMode.Normal,
+  });
+
+  page.drawRectangle({
+    x: 340,
+    y: 30,
+    width: 100,
+    height: 732,
+    color: cmyk(0.6, 0, 0.3, 0),
+    blendMode: BlendMode.Normal,
+  });
+
+  page.drawText(`pdf-lib Blend Mode Test`, {
+    size: 24,
+    x: 45,
+    y: 735,
+    color: cmyk(0.75, 0, 0, 0),
+    blendMode: BlendMode.Multiply,
+  });
+
+  // List all blend modes available
+  modeNames.forEach((m, i) => {
+    page.drawText(`blendMode: ${m}`, {
+      size: 14,
+      x: 40,
+      y: 700 - i * 20,
+      color: cmyk(0, 0, 0, 0.65),
+      blendMode: m as BlendMode,
+    });
+  });
+
+  // quadratic bezier example
+  page.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
+    x: inchToPt(-1),
+    y: inchToPt(10),
+    scale: 0.5,
+    borderWidth: 6,
+    borderColor: cmyk(0, 0, 0, 1),
+    blendMode: BlendMode.Overlay,
+  });
+
+  // arc examples
+  page.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
+    x: inchToPt(-1),
+    y: inchToPt(5.5),
+    color: cmyk(0, 1, 1, 0),
+    borderColor: cmyk(1, 0.7, 0, 0),
+    borderWidth: 2,
+    blendMode: BlendMode.HardLight,
+  });
+
+  page.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
+    x: inchToPt(-1),
+    y: inchToPt(5.5),
+    color: cmyk(0, 0.3, 1, 0),
+    borderColor: cmyk(0, 0, 0, 1),
+    borderWidth: 2,
+    blendMode: BlendMode.Darken,
+  });
+
+  // rectangle example
+  page.drawRectangle({
+    x: 350,
+    y: 220,
+    width: 60,
+    height: 160,
+    rotate: degrees(-30),
+    color: cmyk(0.4, 0, 1, 0),
+    borderColor: cmyk(0, 1, 1, 0),
+    borderWidth: 2,
+    blendMode: BlendMode.ColorBurn,
+  });
+
+  // circle example
+  page.drawCircle({
+    x: inchToPt(3),
+    y: inchToPt(5),
+    color: cmyk(0.7, 1, 0.5, 0),
+    blendMode: BlendMode.ColorDodge,
+  });
+
+  // add alpha-image with 'Screen' blend mode
+  const pngImage = await pdfDoc.embedPng(
+    _assets.images.png.minions_banana_alpha,
+  );
+  const pngDims = pngImage.scale(0.5);
+
+  const cx = page.getWidth() / 2;
+  const cy = page.getHeight() / 2;
+
+  page.drawImage(pngImage, {
+    x: cx - pngDims.width / 2,
+    y: cy - pngDims.height / 2,
+    width: pngDims.width,
+    height: pngDims.height,
+    blendMode: BlendMode.Screen,
+  });
+
+  // embed page from other PDF using blendMode
+
+  const [embeddedPage] = await pdfDoc.embedPdf(_assets.pdfs.normal, [0]);
+  //const embeddedPage = await pdfDoc.embedPage(embeddedPdf.getPage(0));
+
+  const [px, py, scale] = [380, 100, 0.33];
+
+  const { width, height } = embeddedPage.scale(scale);
+
+  page.drawPage(embeddedPage, {
+    x: px,
+    y: py,
+    xScale: scale,
+    yScale: scale,
+    blendMode: BlendMode.Multiply,
+  });
+
+  page.drawRectangle({
+    x: px,
+    y: py,
+    width: width,
+    height: height,
+    borderColor: cmyk(0, 1, 1, 0),
+    borderWidth: 2,
+    blendMode: BlendMode.Normal,
+  });
+
+  page.drawText('Embedded PDF document (blendMode: Multiply)', {
+    size: 9,
+    x: px,
+    y: py - 12,
+    color: cmyk(0, 0, 0, 1),
+    blendMode: BlendMode.Multiply,
+  });
+};
+
+export default async (_assets: Assets) => {
+  const pdfDoc = await PDFDocument.create();
+  await firstPage(pdfDoc);
+  await secondPage(pdfDoc);
+  await thirdPage(pdfDoc, _assets);
 
   const pdfBytes = await pdfDoc.save();
   return pdfBytes;
diff --git a/apps/web/test12.html b/apps/web/test12.html
index e138244..5c4453d 100644
--- a/apps/web/test12.html
+++ b/apps/web/test12.html
@@ -1,168 +1,331 @@
 <!DOCTYPE html>
 <html lang="en">
-  <head>
-    <meta
-      http-equiv="Content-Security-Policy"
-      content="
+
+<head>
+  <meta http-equiv="Content-Security-Policy" content="
         default-src 'self' 'unsafe-inline' blob: resource: https://unpkg.com/@pdf-lib/fontkit/dist/fontkit.umd.js;
         object-src 'self' blob:;
         frame-src 'self' blob:;
-      "
-    />
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
-    <link rel="stylesheet" type="text/css" href="/apps/web/index.css" />
-    <title>Test 12</title>
-    <script type="text/javascript" src="/dist/pdf-lib.js"></script>
-    <script type="text/javascript" src="/apps/web/utils.js"></script>
-  </head>
-
-  <body>
-    <div id="button-container">
-      <button onclick="window.location.href = '/apps/web/test11.html'">
-        Prev
-      </button>
-      <button onclick="test()">Run Test</button>
-      <button onclick="window.location.href = '/apps/web/test13.html'">
-        Next
-      </button>
-    </div>
-    <div id="animation-target"></div>
-    <iframe id="iframe"></iframe>
-  </body>
-
-  <script type="text/javascript">
-    startFpsTracker('animation-target');
-
-    const fetchBinaryAsset = (asset) =>
-      fetch(`/assets/${asset}`).then((res) => res.arrayBuffer());
-
-    const fetchStringAsset = (asset) =>
-      fetch(`/assets/${asset}`).then((res) => res.text());
-
-    const renderInIframe = (pdfBytes) => {
-      const blob = new Blob([pdfBytes], { type: 'application/pdf' });
-      const blobUrl = URL.createObjectURL(blob);
-      document.getElementById('iframe').src = blobUrl;
-    };
-
-    const inchToPt = (inches) => Math.round(inches * 72);
-
-    async function test() {
-      const { PageSizes, PDFDocument, rgb } = PDFLib;
-
-      const pdfDoc = await PDFDocument.create();
-
-      const page1 = pdfDoc.addPage(PageSizes.Letter);
-
-      // SVG sample paths from
-      // https://svgwg.org/svg2-draft/paths.html
-
-      // bezier curve example
-      page1.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
-        x: inchToPt(0.25),
-        y: inchToPt(12),
-      });
+      " />
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+  <link rel="stylesheet" type="text/css" href="/apps/web/index.css" />
+  <title>Test 12</title>
+  <script type="text/javascript" src="/dist/pdf-lib.js"></script>
+  <script type="text/javascript" src="/apps/web/utils.js"></script>
+</head>
 
-      // downward facing triangle
-      page1.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
-        x: inchToPt(-1),
-        y: inchToPt(12),
-        color: rgb(1, 0, 0),
-        borderColor: rgb(0, 0, 1),
-        borderWidth: 1,
-        opacity: 0.7,
-        borderOpacity: 0.7,
-      });
+<body>
+  <div id="button-container">
+    <button onclick="window.location.href = '/apps/web/test11.html'">
+      Prev
+    </button>
+    <button onclick="test()">Run Test</button>
+    <button onclick="window.location.href = '/apps/web/test13.html'">
+      Next
+    </button>
+  </div>
+  <div id="animation-target"></div>
+  <iframe id="iframe"></iframe>
+</body>
 
-      // bezier control point adjustments
-      page1.drawSvgPath('M100,200 C100,100 400,100 400,200', {
-        x: inchToPt(-1),
-        y: inchToPt(9.25),
-      });
-      page1.drawSvgPath('M600,200 C675,100 975,100 900,200', {
-        x: inchToPt(-4.5),
-        y: inchToPt(8.25),
-      });
-      page1.drawSvgPath('M100,500 C25,400 475,400 400,500', {
-        x: inchToPt(-1),
-        y: inchToPt(11.25),
-      });
-      page1.drawSvgPath('M600,500 C600,350 900,650 900,500', {
-        x: inchToPt(-4.5),
-        y: inchToPt(10.25),
-      });
-      page1.drawSvgPath('M100,800 C175,700 325,700 400,800', {
-        x: inchToPt(-1),
-        y: inchToPt(13.35),
-      });
-      page1.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
-        x: inchToPt(-4.5),
-        y: inchToPt(12.25),
-      });
+<script type="text/javascript">
+  startFpsTracker('animation-target');
 
-      const page2 = pdfDoc.addPage(PageSizes.Letter);
+  const fetchBinaryAsset = (asset) =>
+    fetch(`/assets/${asset}`).then((res) => res.arrayBuffer());
 
-      // quadratic bezier example
-      page2.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
-        x: inchToPt(-1),
-        y: inchToPt(11),
-        scale: 0.5,
-        borderWidth: 2,
-      });
-      page2.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
-        x: inchToPt(-1),
-        y: inchToPt(9),
+  const fetchStringAsset = (asset) =>
+    fetch(`/assets/${asset}`).then((res) => res.text());
+
+  const renderInIframe = (pdfBytes) => {
+    const blob = new Blob([pdfBytes], {
+      type: 'application/pdf'
+    });
+    const blobUrl = URL.createObjectURL(blob);
+    document.getElementById('iframe').src = blobUrl;
+  };
+
+  const inchToPt = (inches) => Math.round(inches * 72);
+
+  async function test() {
+    const {
+      PageSizes,
+      PDFDocument,
+      rgb,
+      cmyk,
+      BlendMode,
+      degrees
+    } = PDFLib;
+
+    const pdfDoc = await PDFDocument.create();
+
+    const page1 = pdfDoc.addPage(PageSizes.Letter);
+
+    // SVG sample paths from
+    // https://svgwg.org/svg2-draft/paths.html
+
+    // bezier curve example
+    page1.drawSvgPath('M100,200 C100,100 250,100 250,200 S400,300 400,200', {
+      x: inchToPt(0.25),
+      y: inchToPt(12),
+    });
+
+    // downward facing triangle
+    page1.drawSvgPath('M 100 100 L 300 100 L 200 300 z', {
+      x: inchToPt(-1),
+      y: inchToPt(12),
+      color: rgb(1, 0, 0),
+      borderColor: rgb(0, 0, 1),
+      borderWidth: 1,
+      opacity: 0.7,
+      borderOpacity: 0.7,
+    });
+
+    // bezier control point adjustments
+    page1.drawSvgPath('M100,200 C100,100 400,100 400,200', {
+      x: inchToPt(-1),
+      y: inchToPt(9.25),
+    });
+    page1.drawSvgPath('M600,200 C675,100 975,100 900,200', {
+      x: inchToPt(-4.5),
+      y: inchToPt(8.25),
+    });
+    page1.drawSvgPath('M100,500 C25,400 475,400 400,500', {
+      x: inchToPt(-1),
+      y: inchToPt(11.25),
+    });
+    page1.drawSvgPath('M600,500 C600,350 900,650 900,500', {
+      x: inchToPt(-4.5),
+      y: inchToPt(10.25),
+    });
+    page1.drawSvgPath('M100,800 C175,700 325,700 400,800', {
+      x: inchToPt(-1),
+      y: inchToPt(13.35),
+    });
+    page1.drawSvgPath('M600,800 C625,700 725,700 750,800 S875,900 900,800', {
+      x: inchToPt(-4.5),
+      y: inchToPt(12.25),
+    });
+
+    const page2 = pdfDoc.addPage(PageSizes.Letter);
+
+    // quadratic bezier example
+    page2.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
+      x: inchToPt(-1),
+      y: inchToPt(11),
+      scale: 0.5,
+      borderWidth: 2,
+    });
+    page2.drawSvgPath('M200,300 L400,50 L600,300 L800,550 L1000,300', {
+      x: inchToPt(-1),
+      y: inchToPt(9),
+      scale: 0.5,
+      borderWidth: 2,
+    });
+
+    // arc examples
+    page2.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
+      x: inchToPt(-1),
+      y: inchToPt(5.5),
+      color: rgb(1, 0, 0),
+      borderColor: rgb(0, 0, 1),
+      borderWidth: 1,
+    });
+    page2.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
+      x: inchToPt(-1),
+      y: inchToPt(5.5),
+      color: rgb(1, 1, 0),
+      borderColor: rgb(0, 0, 1),
+      borderWidth: 1,
+    });
+    page2.drawSvgPath(
+      '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', {
+        x: inchToPt(1),
+        y: inchToPt(3),
         scale: 0.5,
+        borderColor: rgb(1, 0, 0),
         borderWidth: 2,
-      });
+      },
+    );
+    page2.drawCircle({
+      x: inchToPt(3),
+      y: inchToPt(5),
+      color: rgb(0, 1, 1),
+      opacity: 0.1,
+      borderWidth: 3,
+      borderColor: rgb(1, 0, 1),
+      borderOpacity: 0.2,
+    });
+    page2.drawText('Semi-Transparent Text', {
+      color: rgb(0, 1, 1),
+      opacity: 0.5,
+      x: inchToPt(1),
+      y: inchToPt(2.5),
+      size: 50,
+    });
 
-      // arc examples
-      page2.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
-        x: inchToPt(-1),
-        y: inchToPt(5.5),
-        color: rgb(1, 0, 0),
-        borderColor: rgb(0, 0, 1),
-        borderWidth: 1,
-      });
-      page2.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
-        x: inchToPt(-1),
-        y: inchToPt(5.5),
-        color: rgb(1, 1, 0),
-        borderColor: rgb(0, 0, 1),
-        borderWidth: 1,
-      });
-      page2.drawSvgPath(
-        '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',
-        {
-          x: inchToPt(1),
-          y: inchToPt(3),
-          scale: 0.5,
-          borderColor: rgb(1, 0, 0),
-          borderWidth: 2,
-        },
-      );
-      page2.drawCircle({
-        x: inchToPt(3),
-        y: inchToPt(5),
-        color: rgb(0, 1, 1),
-        opacity: 0.1,
-        borderWidth: 3,
-        borderColor: rgb(1, 0, 1),
-        borderOpacity: 0.2,
-      });
-      page2.drawText('Semi-Transparent Text', {
-        color: rgb(0, 1, 1),
-        opacity: 0.5,
-        x: inchToPt(1),
-        y: inchToPt(2.5),
-        size: 50,
+    //
+    // Blend mode tests
+    //
+
+    const [
+        minionsBananaAlphaBytes,
+        normalPdf,
+      ] = await Promise.all([
+        fetchBinaryAsset('images/minions_banana_alpha.png'),
+        fetchBinaryAsset('pdfs/normal.pdf'),
+      ]);
+    const page3 = pdfDoc.addPage(PageSizes.Letter);
+
+    const modeNames = Object.values(BlendMode);
+
+    page3.drawRectangle({
+      x: 30,
+      y: 30,
+      width: 100,
+      height: 732,
+      color: cmyk(0, 0.7, 0.3, 0),
+      blendMode: BlendMode.Normal,
+    });
+
+    page3.drawRectangle({
+      x: 340,
+      y: 30,
+      width: 100,
+      height: 732,
+      color: cmyk(0.6, 0, 0.3, 0),
+      blendMode: BlendMode.Normal,
+    });
+
+    page3.drawText(`pdf-lib Blend Mode Test`, {
+      size: 24,
+      x: 45,
+      y: 735,
+      color: cmyk(0.75, 0, 0, 0),
+      blendMode: BlendMode.Multiply,
+    });
+
+    // List all blend modes available
+    modeNames.forEach((m, i) => {
+      page3.drawText(`blendMode: ${m}`, {
+        size: 14,
+        x: 40,
+        y: 700 - i * 20,
+        color: cmyk(0, 0, 0, 0.65),
+        blendMode: m,
       });
+    });
+
+    // quadratic bezier example
+    page3.drawSvgPath('M200,300 Q400,50 600,300 T1000,300', {
+      x: inchToPt(-1),
+      y: inchToPt(10),
+      scale: 0.5,
+      borderWidth: 6,
+      borderColor: cmyk(0, 0, 0, 1),
+      blendMode: BlendMode.Overlay,
+    });
+
+    // arc examples
+    page3.drawSvgPath('M300,200 h-150 a150,150 0 1,0 150,-150 z', {
+      x: inchToPt(-1),
+      y: inchToPt(5.5),
+      color: cmyk(0, 1, 1, 0),
+      borderColor: cmyk(1, 0.7, 0, 0),
+      borderWidth: 2,
+      blendMode: BlendMode.HardLight,
+    });
+
+    page3.drawSvgPath('M275,175 v-150 a150,150 0 0,0 -150,150 z', {
+      x: inchToPt(-1),
+      y: inchToPt(5.5),
+      color: cmyk(0, 0.3, 1, 0),
+      borderColor: cmyk(0, 0, 0, 1),
+      borderWidth: 2,
+      blendMode: BlendMode.Darken,
+    });
+
+    // rectangle example
+    page3.drawRectangle({
+      x: 350,
+      y: 220,
+      width: 60,
+      height: 160,
+      rotate: degrees(-30),
+      color: cmyk(0.4, 0, 1, 0),
+      borderColor: cmyk(0, 1, 1, 0),
+      borderWidth: 2,
+      blendMode: BlendMode.ColorBurn,
+    });
+
+    // circle example
+    page3.drawCircle({
+      x: inchToPt(3),
+      y: inchToPt(5),
+      color: cmyk(0.7, 1, 0.5, 0),
+      blendMode: BlendMode.ColorDodge,
+    });
+
+    // add alpha-image with 'Screen' blend mode
+    const pngImage = await pdfDoc.embedPng(
+      minionsBananaAlphaBytes
+    );
+    const pngDims = pngImage.scale(0.5);
+
+    const cx = page3.getWidth() / 2;
+    const cy = page3.getHeight() / 2;
+
+    page3.drawImage(pngImage, {
+      x: cx - pngDims.width / 2,
+      y: cy - pngDims.height / 2,
+      width: pngDims.width,
+      height: pngDims.height,
+      blendMode: BlendMode.Screen,
+    });
+
+    // embed page from other PDF using blendMode
+
+    const [embeddedPage] = await pdfDoc.embedPdf(normalPdf, [0]);
+
+    const [px, py, scale] = [380, 100, 0.33];
+
+    const {
+      width,
+      height
+    } = embeddedPage.scale(scale);
+
+    page3.drawPage(embeddedPage, {
+      x: px,
+      y: py,
+      xScale: scale,
+      yScale: scale,
+      blendMode: BlendMode.Multiply,
+    });
+
+    page3.drawRectangle({
+      x: px,
+      y: py,
+      width: width,
+      height: height,
+      borderColor: cmyk(0, 1, 1, 0),
+      borderWidth: 2,
+      blendMode: BlendMode.Normal,
+    });
+
+    page3.drawText('Embedded PDF document (blendMode: Multiply)', {
+      size: 9,
+      x: px,
+      y: py - 12,
+      color: cmyk(0, 0, 0, 1),
+      blendMode: BlendMode.Multiply,
+    });
+
+    const pdfBytes = await pdfDoc.save();
 
-      const pdfBytes = await pdfDoc.save();
+    renderInIframe(pdfBytes);
+  }
+</script>
 
-      renderInIframe(pdfBytes);
-    }
-  </script>
 </html>
diff --git a/tests/utils/png.spec.ts b/tests/utils/png.spec.ts
new file mode 100644
index 0000000..fdf9c1e
--- /dev/null
+++ b/tests/utils/png.spec.ts
@@ -0,0 +1,27 @@
+import { PNG } from 'src/utils/png';
+
+describe(`PNG`, () => {
+  it(`can load images with alpha values greater than 1`, () => {
+    // This Uint8Array contains a PNG image composed of a single pixel. It was
+    // generated with the following code in a browser:
+    // ```
+    // const ctx = c.getContext('2d');
+    // ctx.fillStyle = 'rgba(255, 120, 80, 0.5)';
+    // ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+    // ```
+    // The pixel has the following values: R=255, G=120, B=80, A=128
+    //
+    // prettier-ignore
+    const input = new Uint8Array([
+      137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1,
+      0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21, 196, 137, 0, 0, 0, 13, 73, 68, 65, 84,
+      24, 87, 99, 248, 95, 17, 208, 0, 0, 6, 137, 2, 72, 25, 58, 220, 62, 0, 0,
+      0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
+    ]);
+
+    const pngImage = PNG.load(input);
+
+    expect(pngImage.rgbChannel).toEqual(new Uint8Array([255, 120, 80]));
+    expect(pngImage.alphaChannel).toEqual(new Uint8Array([128]));
+  });
+});