From 82f1ee1755c3653e3d80f154f2840920627189d5 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Wed, 23 Feb 2022 12:20:35 +0100
Subject: [PATCH 1/2] Re-factor the `Catalog.viewerPreferences` method

This removes the `ViewerPreferencesValidators` structure, and thus (slightly) simplifies the code overall. With these changes we only have to iterate through, and validate, the actually available Dictionary entries.
---
 src/core/catalog.js | 136 ++++++++++++++++++++------------------------
 1 file changed, 62 insertions(+), 74 deletions(-)

diff --git a/src/core/catalog.js b/src/core/catalog.js
index d4e925809..8611eab9b 100644
--- a/src/core/catalog.js
+++ b/src/core/catalog.js
@@ -776,44 +776,30 @@ class Catalog {
   }
 
   get viewerPreferences() {
-    const ViewerPreferencesValidators = {
-      HideToolbar: isBool,
-      HideMenubar: isBool,
-      HideWindowUI: isBool,
-      FitWindow: isBool,
-      CenterWindow: isBool,
-      DisplayDocTitle: isBool,
-      NonFullScreenPageMode: isName,
-      Direction: isName,
-      ViewArea: isName,
-      ViewClip: isName,
-      PrintArea: isName,
-      PrintClip: isName,
-      PrintScaling: isName,
-      Duplex: isName,
-      PickTrayByPDFSize: isBool,
-      PrintPageRange: Array.isArray,
-      NumCopies: Number.isInteger,
-    };
-
     const obj = this._catDict.get("ViewerPreferences");
+    if (!(obj instanceof Dict)) {
+      return shadow(this, "viewerPreferences", null);
+    }
     let prefs = null;
 
-    if (obj instanceof Dict) {
-      for (const key in ViewerPreferencesValidators) {
-        if (!obj.has(key)) {
-          continue;
-        }
-        const value = obj.get(key);
-        // Make sure the (standard) value conforms to the specification.
-        if (!ViewerPreferencesValidators[key](value)) {
-          info(`Bad value in ViewerPreferences for "${key}".`);
-          continue;
-        }
-        let prefValue;
+    for (const key of obj.getKeys()) {
+      const value = obj.get(key);
+      let prefValue;
 
-        switch (key) {
-          case "NonFullScreenPageMode":
+      switch (key) {
+        case "HideToolbar":
+        case "HideMenubar":
+        case "HideWindowUI":
+        case "FitWindow":
+        case "CenterWindow":
+        case "DisplayDocTitle":
+        case "PickTrayByPDFSize":
+          if (typeof value === "boolean") {
+            prefValue = value;
+          }
+          break;
+        case "NonFullScreenPageMode":
+          if (value instanceof Name) {
             switch (value.name) {
               case "UseNone":
               case "UseOutlines":
@@ -824,8 +810,10 @@ class Catalog {
               default:
                 prefValue = "UseNone";
             }
-            break;
-          case "Direction":
+          }
+          break;
+        case "Direction":
+          if (value instanceof Name) {
             switch (value.name) {
               case "L2R":
               case "R2L":
@@ -834,11 +822,13 @@ class Catalog {
               default:
                 prefValue = "L2R";
             }
-            break;
-          case "ViewArea":
-          case "ViewClip":
-          case "PrintArea":
-          case "PrintClip":
+          }
+          break;
+        case "ViewArea":
+        case "ViewClip":
+        case "PrintArea":
+        case "PrintClip":
+          if (value instanceof Name) {
             switch (value.name) {
               case "MediaBox":
               case "CropBox":
@@ -850,8 +840,10 @@ class Catalog {
               default:
                 prefValue = "CropBox";
             }
-            break;
-          case "PrintScaling":
+          }
+          break;
+        case "PrintScaling":
+          if (value instanceof Name) {
             switch (value.name) {
               case "None":
               case "AppDefault":
@@ -860,8 +852,10 @@ class Catalog {
               default:
                 prefValue = "AppDefault";
             }
-            break;
-          case "Duplex":
+          }
+          break;
+        case "Duplex":
+          if (value instanceof Name) {
             switch (value.name) {
               case "Simplex":
               case "DuplexFlipShortEdge":
@@ -871,13 +865,11 @@ class Catalog {
               default:
                 prefValue = "None";
             }
-            break;
-          case "PrintPageRange":
-            const length = value.length;
-            if (length % 2 !== 0) {
-              // The number of elements must be even.
-              break;
-            }
+          }
+          break;
+        case "PrintPageRange":
+          // The number of elements must be even.
+          if (Array.isArray(value) && value.length % 2 === 0) {
             const isValid = value.every((page, i, arr) => {
               return (
                 Number.isInteger(page) &&
@@ -889,30 +881,26 @@ class Catalog {
             if (isValid) {
               prefValue = value;
             }
-            break;
-          case "NumCopies":
-            if (value > 0) {
-              prefValue = value;
-            }
-            break;
-          default:
-            if (typeof value !== "boolean") {
-              throw new FormatError(
-                `viewerPreferences - expected a boolean value for: ${key}`
-              );
-            }
-            prefValue = value;
-        }
-
-        if (prefValue !== undefined) {
-          if (!prefs) {
-            prefs = Object.create(null);
           }
-          prefs[key] = prefValue;
-        } else {
-          info(`Bad value in ViewerPreferences for "${key}".`);
-        }
+          break;
+        case "NumCopies":
+          if (Number.isInteger(value) && value > 0) {
+            prefValue = value;
+          }
+          break;
+        default:
+          warn(`Ignoring non-standard key in ViewerPreferences: ${key}.`);
+          continue;
       }
+
+      if (prefValue === undefined) {
+        warn(`Bad value, for key "${key}", in ViewerPreferences: ${value}.`);
+        continue;
+      }
+      if (!prefs) {
+        prefs = Object.create(null);
+      }
+      prefs[key] = prefValue;
     }
     return shadow(this, "viewerPreferences", prefs);
   }

From 3704283f5bbbaf0013accc98ba36d96aea382a4b Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Wed, 23 Feb 2022 12:25:13 +0100
Subject: [PATCH 2/2] Remove the `isBool` helper function

The call-sites are replaced by direct `typeof`-checks instead, which removes unnecessary function calls.
---
 src/core/catalog.js    |  5 ++---
 src/core/function.js   |  9 ++++-----
 src/shared/util.js     |  5 -----
 test/unit/util_spec.js | 17 -----------------
 4 files changed, 6 insertions(+), 30 deletions(-)

diff --git a/src/core/catalog.js b/src/core/catalog.js
index 8611eab9b..15d225bc9 100644
--- a/src/core/catalog.js
+++ b/src/core/catalog.js
@@ -25,7 +25,6 @@ import {
   DocumentActionEventType,
   FormatError,
   info,
-  isBool,
   isString,
   objectSize,
   PermissionFlag,
@@ -221,7 +220,7 @@ class Catalog {
         continue;
       }
       const value = obj.get(key);
-      if (!isBool(value)) {
+      if (typeof value !== "boolean") {
         continue;
       }
       markInfo[key] = value;
@@ -1521,7 +1520,7 @@ class Catalog {
           }
           // The 'NewWindow' property, equal to `LinkTarget.BLANK`.
           const newWindow = action.get("NewWindow");
-          if (isBool(newWindow)) {
+          if (typeof newWindow === "boolean") {
             resultObj.newWindow = newWindow;
           }
           break;
diff --git a/src/core/function.js b/src/core/function.js
index d402d067b..37710610f 100644
--- a/src/core/function.js
+++ b/src/core/function.js
@@ -17,7 +17,6 @@ import { Dict, Ref } from "./primitives.js";
 import {
   FormatError,
   info,
-  isBool,
   IsEvalSupportedCached,
   shadow,
   unreachable,
@@ -627,7 +626,7 @@ class PostScriptEvaluator {
         case "and":
           b = stack.pop();
           a = stack.pop();
-          if (isBool(a) && isBool(b)) {
+          if (typeof a === "boolean" && typeof b === "boolean") {
             stack.push(a && b);
           } else {
             stack.push(a & b);
@@ -751,7 +750,7 @@ class PostScriptEvaluator {
           break;
         case "not":
           a = stack.pop();
-          if (isBool(a)) {
+          if (typeof a === "boolean") {
             stack.push(!a);
           } else {
             stack.push(~a);
@@ -760,7 +759,7 @@ class PostScriptEvaluator {
         case "or":
           b = stack.pop();
           a = stack.pop();
-          if (isBool(a) && isBool(b)) {
+          if (typeof a === "boolean" && typeof b === "boolean") {
             stack.push(a || b);
           } else {
             stack.push(a | b);
@@ -802,7 +801,7 @@ class PostScriptEvaluator {
         case "xor":
           b = stack.pop();
           a = stack.pop();
-          if (isBool(a) && isBool(b)) {
+          if (typeof a === "boolean" && typeof b === "boolean") {
             stack.push(a !== b);
           } else {
             stack.push(a ^ b);
diff --git a/src/shared/util.js b/src/shared/util.js
index 4e84ea58d..de0a2e5c5 100644
--- a/src/shared/util.js
+++ b/src/shared/util.js
@@ -1030,10 +1030,6 @@ function utf8StringToString(str) {
   return unescape(encodeURIComponent(str));
 }
 
-function isBool(v) {
-  return typeof v === "boolean";
-}
-
 function isString(v) {
   return typeof v === "string";
 }
@@ -1139,7 +1135,6 @@ export {
   isArrayBuffer,
   isArrayEqual,
   isAscii,
-  isBool,
   IsEvalSupportedCached,
   IsLittleEndianCached,
   isSameOrigin,
diff --git a/test/unit/util_spec.js b/test/unit/util_spec.js
index 8f70e7054..506cc8480 100644
--- a/test/unit/util_spec.js
+++ b/test/unit/util_spec.js
@@ -21,7 +21,6 @@ import {
   getModificationDate,
   isArrayBuffer,
   isAscii,
-  isBool,
   isSameOrigin,
   isString,
   string32,
@@ -74,22 +73,6 @@ describe("util", function () {
     });
   });
 
-  describe("isBool", function () {
-    it("handles boolean values", function () {
-      expect(isBool(true)).toEqual(true);
-      expect(isBool(false)).toEqual(true);
-    });
-
-    it("handles non-boolean values", function () {
-      expect(isBool("true")).toEqual(false);
-      expect(isBool("false")).toEqual(false);
-      expect(isBool(1)).toEqual(false);
-      expect(isBool(0)).toEqual(false);
-      expect(isBool(null)).toEqual(false);
-      expect(isBool(undefined)).toEqual(false);
-    });
-  });
-
   describe("isString", function () {
     it("handles string values", function () {
       expect(isString("foo")).toEqual(true);