Repository: Hopding/pdf-lib
Pull request #426 (2020-04-26 16:14:58)

Fixups


Patch:
diff --git a/README.md b/README.md
index e46414b..e78b62c 100644
--- a/README.md
+++ b/README.md
@@ -53,8 +53,8 @@
   - [Embed PDF Pages](#embed-pdf-pages)
   - [Embed Font and Measure Text](#embed-font-and-measure-text)
   - [Set Document Metadata](#set-document-metadata)
+  - [Read Document Metadata](#read-document-metadata)
   - [Draw SVG Paths](#draw-svg-paths)
-  - [Read Metadata](#read-metadata)
 - [Complete Examples](#complete-examples)
 - [Installation](#installation)
 - [Documentation](#documentation)
@@ -466,6 +466,49 @@ const pdfBytes = await pdfDoc.save()
 //   • Rendered in an <iframe>
 ```
 
+### Read Document Metadata
+
+<!-- [Try the JSFiddle demo](https://jsfiddle.net/Hopding/eg8rfz3k/2/) -->
+
+<!-- prettier-ignore -->
+```js
+import { PDFDocument } from 'pdf-lib'
+
+// This should be a Uint8Array or ArrayBuffer
+// This data can be obtained in a number of different ways
+// If your running in a Node environment, you could use fs.readFile()
+// In the browser, you could make a fetch() call and use res.arrayBuffer()
+const existingPdfBytes = ...
+
+// Load a PDFDocument without updating its existing metadata
+const pdfDoc = await PDFDocument.load(existingPdfBytes, { 
+  updateMetadata: false 
+})
+
+// Print all available metadata fields
+console.log('Title:', pdfDoc.getTitle());
+console.log('Author:', pdfDoc.getAuthor());
+console.log('Subject:', pdfDoc.getSubject());
+console.log('Creator:', pdfDoc.getCreator());
+console.log('Keywords:', pdfDoc.getKeywords());
+console.log('Producer:', pdfDoc.getProducer());
+console.log('Creation Date:', pdfDoc.getCreationDate());
+console.log('Modification Date:', pdfDoc.getModificationDate());
+```
+
+This script outputs the following (_when [this PDF](assets/pdfs/with_cropbox.pdf) is used for the `existingPdfBytes` variable_):
+
+```
+Title: Microsoft Word - Basic Curriculum Vitae example.doc
+Author: Administrator
+Subject: undefined
+Creator: PScript5.dll Version 5.2
+Keywords: undefined
+Producer: Acrobat Distiller 8.1.0 (Windows)
+Creation Date: 2010-07-29T14:26:00.000Z
+Modification Date: 2010-07-29T14:26:00.000Z
+```
+
 ### Draw SVG Paths
 
 _This example produces [this PDF](assets/pdfs/examples/draw_svg_paths.pdf)_.
@@ -512,49 +555,6 @@ const pdfBytes = await pdfDoc.save()
 //   • Rendered in an <iframe>
 ```
 
-### Read Metadata
-
-<!-- [Try the JSFiddle demo](https://jsfiddle.net/Hopding/eg8rfz3k/2/) -->
-
-<!-- prettier-ignore -->
-```js
-import { PDFDocument } from 'pdf-lib'
-
-// This should be a Uint8Array or ArrayBuffer
-// This data can be obtained in a number of different ways
-// If your running in a Node environment, you could use fs.readFile()
-// In the browser, you could make a fetch() call and use res.arrayBuffer()
-const existingPdfBytes = ...
-
-// Load a PDFDocument without updating its existing metadata
-const pdfDoc = await PDFDocument.load(existingPdfBytes, { 
-  updateMetadata: false 
-})
-
-// Print all available metadata fields
-console.log('Title:', pdfDoc.getTitle());
-console.log('Author:', pdfDoc.getAuthor());
-console.log('Subject:', pdfDoc.getSubject());
-console.log('Creator:', pdfDoc.getCreator());
-console.log('Keywords:', pdfDoc.getKeywords());
-console.log('Producer:', pdfDoc.getProducer());
-console.log('Creation Date:', pdfDoc.getCreationDate());
-console.log('Modification Date:', pdfDoc.getModificationDate());
-```
-
-This script outputs the following (_when [this PDF](assets/pdfs/with_cropbox.pdf) is used for the `existingPdfBytes` variable_):
-
-```
-Title: Microsoft Word - Basic Curriculum Vitae example.doc
-Author: Administrator
-Subject: undefined
-Creator: PScript5.dll Version 5.2
-Keywords: undefined
-Producer: Acrobat Distiller 8.1.0 (Windows)
-Creation Date: 2010-07-29T14:26:00.000Z
-Modification Date: 2010-07-29T14:26:00.000Z
-```
-
 ## Complete Examples
 
 The [usage examples](#usage-examples) provide code that is brief and to the point, demonstrating the different features of `pdf-lib`. You can find complete working examples in the [`apps/`](apps/) directory. These apps are used to do manual testing of `pdf-lib` before every release (in addition to the [automated tests](tests/)).
diff --git a/apps/node/index.ts b/apps/node/index.ts
index ff079fb..a60ca19 100644
--- a/apps/node/index.ts
+++ b/apps/node/index.ts
@@ -81,6 +81,7 @@ const assets = {
         readImage('cat_riding_unicorn.jpg.base64'),
       ),
       minions_laughing: readImage('minions_laughing.jpg'),
+      cmyk_colorspace: readImage('cmyk_colorspace.jpg'),
     },
     png: {
       greyscale_bird: readImage('greyscale_bird.png'),
diff --git a/assets/images/Channel_digital_image_CMYK_color.jpg b/assets/images/cmyk_colorspace.jpg
similarity index 100%
rename from assets/images/Channel_digital_image_CMYK_color.jpg
rename to assets/images/cmyk_colorspace.jpg
diff --git a/scratchpad/index.ts b/scratchpad/index.ts
index cf36f87..8067ca3 100644
--- a/scratchpad/index.ts
+++ b/scratchpad/index.ts
@@ -4,23 +4,19 @@ import { openPdf, Reader } from './open';
 import { PDFDocument } from 'src/index';
 
 (async () => {
-  const pdfDoc = await PDFDocument.load(
-    fs.readFileSync('assets/pdfs/with_cropbox.pdf'),
-    { updateMetadata: false },
+  const pdfDoc = await PDFDocument.create();
+
+  const page = pdfDoc.addPage();
+
+  const img = await pdfDoc.embedJpg(
+    fs.readFileSync('assets/images/cmyk_colorspace.jpg'),
   );
 
-  console.log('Title:', pdfDoc.getTitle());
-  console.log('Author:', pdfDoc.getAuthor());
-  console.log('Subject:', pdfDoc.getSubject());
-  console.log('Creator:', pdfDoc.getCreator());
-  console.log('Keywords:', pdfDoc.getKeywords());
-  console.log('Producer:', pdfDoc.getProducer());
-  console.log('Creation Date:', pdfDoc.getCreationDate());
-  console.log('Modification Date:', pdfDoc.getModificationDate());
+  page.drawImage(img);
 
   const pdfBytes = await pdfDoc.save();
 
   fs.writeFileSync('./out.pdf', pdfBytes);
 
-  openPdf('./out.pdf', Reader.Acrobat);
+  openPdf('./out.pdf', Reader.Preview);
 })();
diff --git a/src/core/embedders/JpegEmbedder.ts b/src/core/embedders/JpegEmbedder.ts
index bdebcb3..94e6dc4 100644
--- a/src/core/embedders/JpegEmbedder.ts
+++ b/src/core/embedders/JpegEmbedder.ts
@@ -100,24 +100,19 @@ class JpegEmbedder {
       Height: this.height,
       ColorSpace: this.colorSpace,
       Filter: 'DCTDecode',
+
       // CMYK JPEG streams in PDF are typically stored complemented,
       // with 1 as 'off' and 0 as 'on' (PDF 32000-1:2008, 8.6.4.4).
-      // 
+      //
       // Standalone CMYK JPEG (usually exported by Photoshop) are
       // stored inverse, with 0 as 'off' and 1 as 'on', like RGB.
       //
       // Applying a swap here as a hedge that most bytes passing
       // through this method will benefit from it.
-      Decode: this.colorSpace === ColorSpace.DeviceCMYK && [
-        1,
-        0,
-        1,
-        0,
-        1,
-        0,
-        1,
-        0,
-      ],
+      Decode:
+        this.colorSpace === ColorSpace.DeviceCMYK
+          ? [1, 0, 1, 0, 1, 0, 1, 0]
+          : undefined,
     });
 
     if (ref) {
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
index 3fc4b73..30e2210 100644
--- a/src/utils/strings.ts
+++ b/src/utils/strings.ts
@@ -110,22 +110,12 @@ export const parseDate = (dateStr: string): Date | undefined => {
     offsetMins = '00',
   ] = match;
 
-  const date = new Date(`${year}-${month}-${day}:${hours}:${mins}:${secs}Z`);
-
-  // The relation to UTC, can be either +, - or Z.
-  // Javascript Date supports only UTC so offsets are added or subtracted.
-  switch (offsetSign) {
-    case '+': // Time is after UTC, so we offsets are subtracted
-      date.setHours(date.getHours() - Number(offsetHours));
-      date.setMinutes(date.getMinutes() - Number(offsetMins));
-      break;
-    case '-': // Time is before UTC, so we offsets are added
-      date.setHours(date.getHours() + Number(offsetHours));
-      date.setMinutes(date.getMinutes() + Number(offsetMins));
-      break;
-    case 'Z': // Time is already UTC, so nothing to do
-      break;
-  }
+  // http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
+  const tzOffset =
+    offsetSign === 'Z' ? 'Z' : `${offsetSign}${offsetHours}:${offsetMins}`;
+  const date = new Date(
+    `${year}-${month}-${day}T${hours}:${mins}:${secs}${tzOffset}`,
+  );
 
   return date;
 };


Test patch:
diff --git a/apps/node/tests/test3.ts b/apps/node/tests/test3.ts
index 3dd4651..1ed0084 100644
--- a/apps/node/tests/test3.ts
+++ b/apps/node/tests/test3.ts
@@ -19,11 +19,14 @@ export default async (assets: Assets) => {
   const catRidingUnicornImage = await pdfDoc.embedJpg(
     images.jpg.cat_riding_unicorn,
   );
+  const cmykImage = await pdfDoc.embedJpg(images.jpg.cmyk_colorspace);
+
   const catRidingUnicornDims = catRidingUnicornImage.scale(0.13);
+  const cmykDims = cmykImage.scale(0.5);
 
   const page0 = pdfDoc.insertPage(0, [305, 250]);
   const page1 = pdfDoc.getPage(1);
-  const page2 = pdfDoc.addPage([305, 125]);
+  const page2 = pdfDoc.addPage([305, 250]);
 
   const hotPink = rgb(1, 0, 1);
   const red = rgb(1, 0, 0);
@@ -58,7 +61,7 @@ export default async (assets: Assets) => {
   page2.setFontSize(24);
   page2.drawText('This is the last page!', {
     x: 30,
-    y: 60,
+    y: 215,
     font: helveticaFont,
     color: hotPink,
   });
@@ -68,6 +71,11 @@ export default async (assets: Assets) => {
     color: hotPink,
     thickness: 5,
   });
+  page2.drawImage(cmykImage, {
+    ...cmykDims,
+    x: 30,
+    y: 30,
+  });
 
   console.log('Title:', pdfDoc.getTitle());
   console.log('Author:', pdfDoc.getAuthor());
diff --git a/apps/rn/src/tests/test3.js b/apps/rn/src/tests/test3.js
index f3d5849..afc78df 100644
--- a/apps/rn/src/tests/test3.js
+++ b/apps/rn/src/tests/test3.js
@@ -3,9 +3,10 @@ import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
 import { fetchAsset, writePdf } from './assets';
 
 export default async () => {
-  const [inputPdfBytes, catRidingUnicornBytes] = await Promise.all([
+  const [inputPdfBytes, catRidingUnicornBytes, cmykBytes] = await Promise.all([
     fetchAsset('pdfs/with_update_sections.pdf'),
     fetchAsset('images/cat_riding_unicorn_resized.jpg'),
+    fetchAsset('images/cmyk_colorspace.jpg'),
   ]);
 
   const pdfDoc = await PDFDocument.load(inputPdfBytes, {
@@ -14,11 +15,14 @@ export default async () => {
 
   const helveticaFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
   const catRidingUnicornImage = await pdfDoc.embedJpg(catRidingUnicornBytes);
+  const cmykImage = await pdfDoc.embedJpg(cmykBytes);
+
   const catRidingUnicornDims = catRidingUnicornImage.scale(0.25);
+  const cmykDims = cmykImage.scale(0.5);
 
   const page0 = pdfDoc.insertPage(0, [305, 250]);
   const page1 = pdfDoc.getPage(1);
-  const page2 = pdfDoc.addPage([305, 125]);
+  const page2 = pdfDoc.addPage([305, 250]);
 
   const hotPink = rgb(1, 0, 1);
   const red = rgb(1, 0, 0);
@@ -53,16 +57,20 @@ export default async () => {
   page2.setFontSize(24);
   page2.drawText('This is the last page!', {
     x: 30,
-    y: 60,
+    y: 215,
     font: helveticaFont,
     color: hotPink,
   });
-  page2.drawRectangle({
-    x: 30,
-    y: 50,
-    width: lastPageTextWidth,
-    height: 5,
+  page2.drawLine({
+    start: { x: 30, y: 50 },
+    end: { x: 30 + lastPageTextWidth, y: 50 },
     color: hotPink,
+    thickness: 5,
+  });
+  page2.drawImage(cmykImage, {
+    ...cmykDims,
+    x: 30,
+    y: 30,
   });
 
   console.log('Title:', pdfDoc.getTitle());
diff --git a/apps/web/test3.html b/apps/web/test3.html
index 49352e9..e386027 100644
--- a/apps/web/test3.html
+++ b/apps/web/test3.html
@@ -48,9 +48,14 @@
     async function test() {
       const { degrees, PDFDocument, rgb, StandardFonts } = PDFLib;
 
-      const [inputPdfBase64, catRidingUnicornBytes] = await Promise.all([
+      const [
+        inputPdfBase64,
+        catRidingUnicornBytes,
+        cmykBytes,
+      ] = await Promise.all([
         fetchStringAsset('pdfs/with_update_sections.pdf.base64.uri'),
         fetchBinaryAsset('images/cat_riding_unicorn.jpg'),
+        fetchBinaryAsset('images/cmyk_colorspace.jpg'),
       ]);
 
       const pdfDoc = await PDFDocument.load(inputPdfBase64, {
@@ -61,11 +66,14 @@
       const catRidingUnicornImage = await pdfDoc.embedJpg(
         catRidingUnicornBytes,
       );
+      const cmykImage = await pdfDoc.embedJpg(cmykBytes);
+
       const catRidingUnicornDims = catRidingUnicornImage.scale(0.13);
+      const cmykDims = cmykImage.scale(0.5);
 
       const page0 = pdfDoc.insertPage(0, [305, 250]);
       const page1 = pdfDoc.getPage(1);
-      const page2 = pdfDoc.addPage([305, 125]);
+      const page2 = pdfDoc.addPage([305, 250]);
 
       const hotPink = rgb(1, 0, 1);
       const red = rgb(1, 0, 0);
@@ -103,16 +111,20 @@
       page2.setFontSize(24);
       page2.drawText('This is the last page!', {
         x: 30,
-        y: 60,
+        y: 215,
         font: helveticaFont,
         color: hotPink,
       });
-      page2.drawRectangle({
-        x: 30,
-        y: 50,
-        width: lastPageTextWidth,
-        height: 5,
+      page2.drawLine({
+        start: { x: 30, y: 50 },
+        end: { x: 30 + lastPageTextWidth, y: 50 },
         color: hotPink,
+        thickness: 5,
+      });
+      page2.drawImage(cmykImage, {
+        ...cmykDims,
+        x: 30,
+        y: 30,
       });
 
       console.log('Title:', pdfDoc.getTitle());
diff --git a/tests/core/embedders/JpegEmbedder.spec.ts b/tests/core/embedders/JpegEmbedder.spec.ts
index acf5743..18a2f16 100644
--- a/tests/core/embedders/JpegEmbedder.spec.ts
+++ b/tests/core/embedders/JpegEmbedder.spec.ts
@@ -3,7 +3,7 @@ import { JpegEmbedder, PDFContext, PDFRawStream, PDFRef } from 'src/core';
 
 const catUnicornJpg = fs.readFileSync('./assets/images/cat_riding_unicorn.jpg');
 const minionsLaughing = fs.readFileSync('./assets/images/minions_laughing.jpg');
-const cmykJpg = fs.readFileSync('./assets/images/Channel_digital_image_CMYK_color.jpg');
+const cmykJpg = fs.readFileSync('./assets/images/cmyk_colorspace.jpg');
 
 describe(`JpegEmbedder`, () => {
   it(`can be constructed with JpegEmbedder.for(...)`, async () => {