From 0f0b3de8214d7db665629ffe64a6db0d7782858f Mon Sep 17 00:00:00 2001
From: SheetJS <dev@sheetjs.com>
Date: Tue, 22 Mar 2022 16:08:08 -0400
Subject: [PATCH] popping IIFEs to appease rollup tree shaking

---
 .fossaignore          |  16 -----
 bits/04_base64.js     |  88 ++++++++++++--------------
 bits/18_cfb.js        |   9 ++-
 bits/29_xlsenum.js    |   7 +-
 bits/40_harb.js       |   8 +--
 bits/41_lotus.js      |   2 +-
 bits/45_rtf.js        |   2 +-
 bits/75_xlml.js       |   2 +-
 bits/76_xls.js        |   2 +-
 bits/83_numbers.js    |  12 ++++
 bits/87_read.js       |   6 +-
 bits/88_write.js      |   6 +-
 misc/18_esmcfb.js     |   9 ++-
 modules/04_base64.js  |  88 ++++++++++++--------------
 modules/04_base64.ts  |  76 +++++++++++-----------
 modules/83_numbers.js |  12 ++++
 modules/83_numbers.ts |   7 +-
 modules/Makefile      |   5 +-
 xlsx.flow.js          | 144 ++++++++++++++++++++++--------------------
 xlsx.js               | 143 ++++++++++++++++++++++-------------------
 xlsx.mjs              | 144 ++++++++++++++++++++++--------------------
 21 files changed, 410 insertions(+), 378 deletions(-)
 delete mode 100644 .fossaignore

diff --git a/.fossaignore b/.fossaignore
deleted file mode 100644
index fc7a5d1..0000000
--- a/.fossaignore
+++ /dev/null
@@ -1,16 +0,0 @@
-bits/
-demos/
-dist/
-docbits/
-misc/
-node_modules/
-types/
-tests/
-test_files
-*.md
-
-*.json
-*.log
-*.sh
-.DS_Store
-.Trashes
diff --git a/bits/04_base64.js b/bits/04_base64.js
index bf9ca0f..3a7159f 100644
--- a/bits/04_base64.js
+++ b/bits/04_base64.js
@@ -1,47 +1,43 @@
-var Base64 = function() {
-  var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-  return {
-    encode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      for (var i = 0; i < input.length; ) {
-        c1 = input.charCodeAt(i++);
-        e1 = c1 >> 2;
-        c2 = input.charCodeAt(i++);
-        e2 = (c1 & 3) << 4 | c2 >> 4;
-        c3 = input.charCodeAt(i++);
-        e3 = (c2 & 15) << 2 | c3 >> 6;
-        e4 = c3 & 63;
-        if (isNaN(c2)) {
-          e3 = e4 = 64;
-        } else if (isNaN(c3)) {
-          e4 = 64;
-        }
-        o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4);
-      }
-      return o;
-    },
-    decode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      input = input.replace(/[^\w\+\/\=]/g, "");
-      for (var i = 0; i < input.length; ) {
-        e1 = map.indexOf(input.charAt(i++));
-        e2 = map.indexOf(input.charAt(i++));
-        c1 = e1 << 2 | e2 >> 4;
-        o += String.fromCharCode(c1);
-        e3 = map.indexOf(input.charAt(i++));
-        c2 = (e2 & 15) << 4 | e3 >> 2;
-        if (e3 !== 64) {
-          o += String.fromCharCode(c2);
-        }
-        e4 = map.indexOf(input.charAt(i++));
-        c3 = (e3 & 3) << 6 | e4;
-        if (e4 !== 64) {
-          o += String.fromCharCode(c3);
-        }
-      }
-      return o;
+var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+function Base64_encode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  for (var i = 0; i < input.length; ) {
+    c1 = input.charCodeAt(i++);
+    e1 = c1 >> 2;
+    c2 = input.charCodeAt(i++);
+    e2 = (c1 & 3) << 4 | c2 >> 4;
+    c3 = input.charCodeAt(i++);
+    e3 = (c2 & 15) << 2 | c3 >> 6;
+    e4 = c3 & 63;
+    if (isNaN(c2)) {
+      e3 = e4 = 64;
+    } else if (isNaN(c3)) {
+      e4 = 64;
     }
-  };
-}();
+    o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
+  }
+  return o;
+}
+function Base64_decode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  input = input.replace(/[^\w\+\/\=]/g, "");
+  for (var i = 0; i < input.length; ) {
+    e1 = Base64_map.indexOf(input.charAt(i++));
+    e2 = Base64_map.indexOf(input.charAt(i++));
+    c1 = e1 << 2 | e2 >> 4;
+    o += String.fromCharCode(c1);
+    e3 = Base64_map.indexOf(input.charAt(i++));
+    c2 = (e2 & 15) << 4 | e3 >> 2;
+    if (e3 !== 64) {
+      o += String.fromCharCode(c2);
+    }
+    e4 = Base64_map.indexOf(input.charAt(i++));
+    c3 = (e3 & 3) << 6 | e4;
+    if (e4 !== 64) {
+      o += String.fromCharCode(c3);
+    }
+  }
+  return o;
+}
diff --git a/bits/18_cfb.js b/bits/18_cfb.js
index 7f6f6c2..4896c18 100644
--- a/bits/18_cfb.js
+++ b/bits/18_cfb.js
@@ -1,5 +1,4 @@
 /*::
-declare var Base64:any;
 declare var ReadShift:any;
 declare var CheckField:any;
 declare var prep_blob:any;
@@ -552,7 +551,7 @@ function read(blob/*:RawBytes|string*/, options/*:CFBReadOpts*/) {
 	}
 	switch(type || "base64") {
 		case "file": /*:: if(typeof blob !== 'string') throw "Must pass a filename when type='file'"; */return read_file(blob, options);
-		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64.decode(blob)), options);
+		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64_decode(blob)), options);
 		case "binary": /*:: if(typeof blob !== 'string') throw "Must pass a binary string when type='file'"; */return parse(s2a(blob), options);
 	}
 	return parse(/*::typeof blob == 'string' ? new Buffer(blob, 'utf-8') : */blob, options);
@@ -875,7 +874,7 @@ function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string
 	switch(options && options.type || "buffer") {
 		case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o;
 		case "binary": return typeof o == "string" ? o : a2s(o);
-		case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o));
+		case "base64": return Base64_encode(typeof o == "string" ? o : a2s(o));
 		case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o);
 			/* falls through */
 		case "array": return typeof o == "string" ? s2a(o) : o;
@@ -1584,7 +1583,7 @@ function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ {
 
 /* 76 character chunks TODO: intertwine encoding */
 function write_base64_76(bstr/*:string*/)/*:string*/ {
-	var data = Base64.encode(bstr);
+	var data = Base64_encode(bstr);
 	var o = [];
 	for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76));
 	return o.join("\r\n") + "\r\n";
@@ -1665,7 +1664,7 @@ function parse_mime(cfb/*:CFBContainer*/, data/*:Array<string>*/, root/*:string*
 	}
 	++di;
 	switch(cte.toLowerCase()) {
-		case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break;
+		case 'base64': fdata = s2a(Base64_decode(data.slice(di).join(""))); break;
 		case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break;
 		default: throw new Error("Unsupported Content-Transfer-Encoding " + cte);
 	}
diff --git a/bits/29_xlsenum.js b/bits/29_xlsenum.js
index d57d757..c8330b8 100644
--- a/bits/29_xlsenum.js
+++ b/bits/29_xlsenum.js
@@ -1,4 +1,5 @@
 /* [MS-OLEPS] 2.2 PropertyType */
+// Note: some tree shakers cannot handle VT_VECTOR | $CONST, hence extra vars
 //var VT_EMPTY    = 0x0000;
 //var VT_NULL     = 0x0001;
 var VT_I2       = 0x0002;
@@ -33,6 +34,8 @@ var VT_CF       = 0x0047;
 //var VT_CLSID    = 0x0048;
 //var VT_VERSIONED_STREAM = 0x0049;
 var VT_VECTOR   = 0x1000;
+var VT_VECTOR_VARIANT = 0x100C;
+var VT_VECTOR_LPSTR   = 0x101E;
 //var VT_ARRAY    = 0x2000;
 
 var VT_STRING   = 0x0050; // 2.3.3.1.11 VtString
@@ -52,8 +55,8 @@ var DocSummaryPIDDSI = {
 	/*::[*/0x09/*::]*/: { n: 'HiddenCount', t: VT_I4 },
 	/*::[*/0x0a/*::]*/: { n: 'MultimediaClipCount', t: VT_I4 },
 	/*::[*/0x0b/*::]*/: { n: 'ScaleCrop', t: VT_BOOL },
-	/*::[*/0x0c/*::]*/: { n: 'HeadingPairs', t: VT_VECTOR | VT_VARIANT },
-	/*::[*/0x0d/*::]*/: { n: 'TitlesOfParts', t: VT_VECTOR | VT_LPSTR },
+	/*::[*/0x0c/*::]*/: { n: 'HeadingPairs', t: VT_VECTOR_VARIANT /* VT_VECTOR | VT_VARIANT */ },
+	/*::[*/0x0d/*::]*/: { n: 'TitlesOfParts', t: VT_VECTOR_LPSTR /* VT_VECTOR | VT_LPSTR */ },
 	/*::[*/0x0e/*::]*/: { n: 'Manager', t: VT_STRING },
 	/*::[*/0x0f/*::]*/: { n: 'Company', t: VT_STRING },
 	/*::[*/0x10/*::]*/: { n: 'LinksUpToDate', t: VT_BOOL },
diff --git a/bits/40_harb.js b/bits/40_harb.js
index 7eaf49a..9e30280 100644
--- a/bits/40_harb.js
+++ b/bits/40_harb.js
@@ -63,7 +63,7 @@ function dbf_to_aoa(buf, opts)/*:AOA*/ {
 	var out/*:AOA*/ = [];
 	var d/*:Block*/ = (new_raw_buf(1)/*:any*/);
 	switch(opts.type) {
-		case 'base64': d = s2a(Base64.decode(buf)); break;
+		case 'base64': d = s2a(Base64_decode(buf)); break;
 		case 'binary': d = s2a(buf); break;
 		case 'buffer':
 		case 'array': d = buf; break;
@@ -394,7 +394,7 @@ var SYLK = /*#__PURE__*/(function() {
 	/* TODO: find an actual specification */
 	function sylk_to_aoa(d/*:RawData*/, opts)/*:[AOA, Worksheet]*/ {
 		switch(opts.type) {
-			case 'base64': return sylk_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return sylk_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return sylk_to_aoa_str(d, opts);
 			case 'buffer': return sylk_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return sylk_to_aoa_str(cc2str(d), opts);
@@ -597,7 +597,7 @@ var SYLK = /*#__PURE__*/(function() {
 var DIF = /*#__PURE__*/(function() {
 	function dif_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
 		switch(opts.type) {
-			case 'base64': return dif_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return dif_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return dif_to_aoa_str(d, opts);
 			case 'buffer': return dif_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return dif_to_aoa_str(cc2str(d), opts);
@@ -963,7 +963,7 @@ var PRN = /*#__PURE__*/(function() {
 	function prn_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ {
 		var str = "", bytes = opts.type == 'string' ? [0,0,0,0] : firstbyte(d, opts);
 		switch(opts.type) {
-			case 'base64': str = Base64.decode(d); break;
+			case 'base64': str = Base64_decode(d); break;
 			case 'binary': str = d; break;
 			case 'buffer':
 				if(opts.codepage == 65001) str = d.toString('utf8'); // TODO: test if buf
diff --git a/bits/41_lotus.js b/bits/41_lotus.js
index d7201cb..522fb12 100644
--- a/bits/41_lotus.js
+++ b/bits/41_lotus.js
@@ -16,7 +16,7 @@ var WK_ = /*#__PURE__*/(function() {
 
 	function lotus_to_workbook(d/*:RawData*/, opts) {
 		switch(opts.type) {
-			case 'base64': return lotus_to_workbook_buf(s2a(Base64.decode(d)), opts);
+			case 'base64': return lotus_to_workbook_buf(s2a(Base64_decode(d)), opts);
 			case 'binary': return lotus_to_workbook_buf(s2a(d), opts);
 			case 'buffer':
 			case 'array': return lotus_to_workbook_buf(d, opts);
diff --git a/bits/45_rtf.js b/bits/45_rtf.js
index d8a3adc..e01c25a 100644
--- a/bits/45_rtf.js
+++ b/bits/45_rtf.js
@@ -1,7 +1,7 @@
 var RTF = /*#__PURE__*/(function() {
 	function rtf_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ {
 		switch(opts.type) {
-			case 'base64': return rtf_to_sheet_str(Base64.decode(d), opts);
+			case 'base64': return rtf_to_sheet_str(Base64_decode(d), opts);
 			case 'binary': return rtf_to_sheet_str(d, opts);
 			case 'buffer': return rtf_to_sheet_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array':  return rtf_to_sheet_str(cc2str(d), opts);
diff --git a/bits/75_xlml.js b/bits/75_xlml.js
index 1943270..bafdfb0 100644
--- a/bits/75_xlml.js
+++ b/bits/75_xlml.js
@@ -920,7 +920,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ {
 function parse_xlml(data/*:RawBytes|string*/, opts)/*:Workbook*/ {
 	fix_read_opts(opts=opts||{});
 	switch(opts.type||"base64") {
-		case "base64": return parse_xlml_xml(Base64.decode(data), opts);
+		case "base64": return parse_xlml_xml(Base64_decode(data), opts);
 		case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts);
 		case "array": return parse_xlml_xml(a2s(data), opts);
 	}
diff --git a/bits/76_xls.js b/bits/76_xls.js
index ce3ba6a..049026d 100644
--- a/bits/76_xls.js
+++ b/bits/76_xls.js
@@ -669,7 +669,7 @@ if(cfb.FullPaths) {
 	WB = CFB.find(cfb, '/Workbook') || CFB.find(cfb, '/Book');
 } else {
 	switch(options.type) {
-		case 'base64': cfb = s2a(Base64.decode(cfb)); break;
+		case 'base64': cfb = s2a(Base64_decode(cfb)); break;
 		case 'binary': cfb = s2a(cfb); break;
 		case 'buffer': break;
 		case 'array': if(!Array.isArray(cfb)) cfb = Array.prototype.slice.call(cfb); break;
diff --git a/bits/83_numbers.js b/bits/83_numbers.js
index 39130c9..bd7c4d0 100644
--- a/bits/83_numbers.js
+++ b/bits/83_numbers.js
@@ -162,6 +162,18 @@ function parse_shallow(buf) {
   }
   return out;
 }
+function write_shallow(proto) {
+  var out = [];
+  proto.forEach(function(field, idx) {
+    field.forEach(function(item) {
+      out.push(write_varint49(idx * 8 + item.type));
+      if (item.type == 2)
+        out.push(write_varint49(item.data.length));
+      out.push(item.data);
+    });
+  });
+  return u8concat(out);
+}
 function mappa(data, cb) {
   if (!data)
     return [];
diff --git a/bits/87_read.js b/bits/87_read.js
index aee9762..c55302e 100644
--- a/bits/87_read.js
+++ b/bits/87_read.js
@@ -2,7 +2,7 @@ function firstbyte(f/*:RawData*/,o/*:?TypeOpts*/)/*:Array<number>*/ {
 	var x = "";
 	switch((o||{}).type || "base64") {
 		case 'buffer': return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
-		case 'base64': x = Base64.decode(f.slice(0,12)); break;
+		case 'base64': x = Base64_decode(f.slice(0,12)); break;
 		case 'binary': x = f; break;
 		case 'array':  return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
 		default: throw new Error("Unrecognized type " + (o && o.type || "undefined"));
@@ -36,7 +36,7 @@ function read_plaintext(data/*:string*/, o/*:ParseOpts*/)/*:Workbook*/ {
 function read_plaintext_raw(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 	var str = "", bytes = firstbyte(data, o);
 	switch(o.type) {
-		case 'base64': str = Base64.decode(data); break;
+		case 'base64': str = Base64_decode(data); break;
 		case 'binary': str = data; break;
 		case 'buffer': str = data.toString('binary'); break;
 		case 'array': str = cc2str(data); break;
@@ -49,7 +49,7 @@ function read_plaintext_raw(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 
 function read_utf16(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 	var d = data;
-	if(o.type == 'base64') d = Base64.decode(d);
+	if(o.type == 'base64') d = Base64_decode(d);
 	d = $cptable.utils.decode(1200, d.slice(2), 'str');
 	o.type = "binary";
 	return read_plaintext(d, o);
diff --git a/bits/88_write.js b/bits/88_write.js
index bb2dba9..9409b8e 100644
--- a/bits/88_write.js
+++ b/bits/88_write.js
@@ -57,7 +57,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/
 	if(!bom) bom = "";
 	var o = bom + out;
 	switch(opts.type) {
-		case "base64": return Base64.encode(utf8write(o));
+		case "base64": return Base64_encode(utf8write(o));
 		case "binary": return utf8write(o);
 		case "string": return out;
 		case "file": return write_dl(opts.file, o, 'utf8');
@@ -72,7 +72,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/
 
 function write_stxt_type(out/*:string*/, opts/*:WriteOpts*/)/*:any*/ {
 	switch(opts.type) {
-		case "base64": return Base64.encode(out);
+		case "base64": return Base64_encode(out);
 		case "binary": return out;
 		case "string": return out; /* override in sheet_to_txt */
 		case "file": return write_dl(opts.file, out, 'binary');
@@ -93,7 +93,7 @@ function write_binary_type(out, opts/*:WriteOpts*/)/*:any*/ {
 			var bstr = "";
 			// $FlowIgnore
 			for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
-			return opts.type == 'base64' ? Base64.encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
+			return opts.type == 'base64' ? Base64_encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
 		case "file": return write_dl(opts.file, out);
 		case "buffer": return out;
 		default: throw new Error("Unrecognized type " + opts.type);
diff --git a/misc/18_esmcfb.js b/misc/18_esmcfb.js
index fc77b59..0ca2cad 100644
--- a/misc/18_esmcfb.js
+++ b/misc/18_esmcfb.js
@@ -1,5 +1,4 @@
 /*::
-declare var Base64:any;
 declare var ReadShift:any;
 declare var CheckField:any;
 declare var prep_blob:any;
@@ -552,7 +551,7 @@ function read(blob/*:RawBytes|string*/, options/*:CFBReadOpts*/) {
 	}
 	switch(type || "base64") {
 		case "file": /*:: if(typeof blob !== 'string') throw "Must pass a filename when type='file'"; */return read_file(blob, options);
-		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64.decode(blob)), options);
+		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64_decode(blob)), options);
 		case "binary": /*:: if(typeof blob !== 'string') throw "Must pass a binary string when type='file'"; */return parse(s2a(blob), options);
 	}
 	return parse(/*::typeof blob == 'string' ? new Buffer(blob, 'utf-8') : */blob, options);
@@ -875,7 +874,7 @@ function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string
 	switch(options && options.type || "buffer") {
 		case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o;
 		case "binary": return typeof o == "string" ? o : a2s(o);
-		case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o));
+		case "base64": return Base64_encode(typeof o == "string" ? o : a2s(o));
 		case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o);
 			/* falls through */
 		case "array": return typeof o == "string" ? s2a(o) : o;
@@ -1584,7 +1583,7 @@ function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ {
 
 /* 76 character chunks TODO: intertwine encoding */
 function write_base64_76(bstr/*:string*/)/*:string*/ {
-	var data = Base64.encode(bstr);
+	var data = Base64_encode(bstr);
 	var o = [];
 	for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76));
 	return o.join("\r\n") + "\r\n";
@@ -1665,7 +1664,7 @@ function parse_mime(cfb/*:CFBContainer*/, data/*:Array<string>*/, root/*:string*
 	}
 	++di;
 	switch(cte.toLowerCase()) {
-		case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break;
+		case 'base64': fdata = s2a(Base64_decode(data.slice(di).join(""))); break;
 		case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break;
 		default: throw new Error("Unsupported Content-Transfer-Encoding " + cte);
 	}
diff --git a/modules/04_base64.js b/modules/04_base64.js
index bf9ca0f..3a7159f 100644
--- a/modules/04_base64.js
+++ b/modules/04_base64.js
@@ -1,47 +1,43 @@
-var Base64 = function() {
-  var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-  return {
-    encode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      for (var i = 0; i < input.length; ) {
-        c1 = input.charCodeAt(i++);
-        e1 = c1 >> 2;
-        c2 = input.charCodeAt(i++);
-        e2 = (c1 & 3) << 4 | c2 >> 4;
-        c3 = input.charCodeAt(i++);
-        e3 = (c2 & 15) << 2 | c3 >> 6;
-        e4 = c3 & 63;
-        if (isNaN(c2)) {
-          e3 = e4 = 64;
-        } else if (isNaN(c3)) {
-          e4 = 64;
-        }
-        o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4);
-      }
-      return o;
-    },
-    decode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      input = input.replace(/[^\w\+\/\=]/g, "");
-      for (var i = 0; i < input.length; ) {
-        e1 = map.indexOf(input.charAt(i++));
-        e2 = map.indexOf(input.charAt(i++));
-        c1 = e1 << 2 | e2 >> 4;
-        o += String.fromCharCode(c1);
-        e3 = map.indexOf(input.charAt(i++));
-        c2 = (e2 & 15) << 4 | e3 >> 2;
-        if (e3 !== 64) {
-          o += String.fromCharCode(c2);
-        }
-        e4 = map.indexOf(input.charAt(i++));
-        c3 = (e3 & 3) << 6 | e4;
-        if (e4 !== 64) {
-          o += String.fromCharCode(c3);
-        }
-      }
-      return o;
+var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+function Base64_encode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  for (var i = 0; i < input.length; ) {
+    c1 = input.charCodeAt(i++);
+    e1 = c1 >> 2;
+    c2 = input.charCodeAt(i++);
+    e2 = (c1 & 3) << 4 | c2 >> 4;
+    c3 = input.charCodeAt(i++);
+    e3 = (c2 & 15) << 2 | c3 >> 6;
+    e4 = c3 & 63;
+    if (isNaN(c2)) {
+      e3 = e4 = 64;
+    } else if (isNaN(c3)) {
+      e4 = 64;
     }
-  };
-}();
+    o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
+  }
+  return o;
+}
+function Base64_decode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  input = input.replace(/[^\w\+\/\=]/g, "");
+  for (var i = 0; i < input.length; ) {
+    e1 = Base64_map.indexOf(input.charAt(i++));
+    e2 = Base64_map.indexOf(input.charAt(i++));
+    c1 = e1 << 2 | e2 >> 4;
+    o += String.fromCharCode(c1);
+    e3 = Base64_map.indexOf(input.charAt(i++));
+    c2 = (e2 & 15) << 4 | e3 >> 2;
+    if (e3 !== 64) {
+      o += String.fromCharCode(c2);
+    }
+    e4 = Base64_map.indexOf(input.charAt(i++));
+    c3 = (e3 & 3) << 6 | e4;
+    if (e4 !== 64) {
+      o += String.fromCharCode(c3);
+    }
+  }
+  return o;
+}
diff --git a/modules/04_base64.ts b/modules/04_base64.ts
index 61e1b47..e6b676c 100644
--- a/modules/04_base64.ts
+++ b/modules/04_base64.ts
@@ -1,44 +1,40 @@
-var Base64 = (() => {
-	var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-	return {
-		encode: function(input: string): string {
-			var o = "";
-			var c1=0, c2=0, c3=0, e1=0, e2=0, e3=0, e4=0;
-			for(var i = 0; i < input.length; ) {
-				c1 = input.charCodeAt(i++);
-				e1 = (c1 >> 2);
+var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+function Base64_encode(input: string): string {
+	var o = "";
+	var c1=0, c2=0, c3=0, e1=0, e2=0, e3=0, e4=0;
+	for(var i = 0; i < input.length; ) {
+		c1 = input.charCodeAt(i++);
+		e1 = (c1 >> 2);
 
-				c2 = input.charCodeAt(i++);
-				e2 = ((c1 & 3) << 4) | (c2 >> 4);
+		c2 = input.charCodeAt(i++);
+		e2 = ((c1 & 3) << 4) | (c2 >> 4);
 
-				c3 = input.charCodeAt(i++);
-				e3 = ((c2 & 15) << 2) | (c3 >> 6);
-				e4 = (c3 & 63);
-				if (isNaN(c2)) { e3 = e4 = 64; }
-				else if (isNaN(c3)) { e4 = 64; }
-				o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4);
-			}
-			return o;
-		},
-		decode: function(input: string): string {
-			var o = "";
-			var c1=0, c2=0, c3=0, e1=0, e2=0, e3=0, e4=0;
-			input = input.replace(/[^\w\+\/\=]/g, "");
-			for(var i = 0; i < input.length;) {
-				e1 = map.indexOf(input.charAt(i++));
-				e2 = map.indexOf(input.charAt(i++));
-				c1 = (e1 << 2) | (e2 >> 4);
-				o += String.fromCharCode(c1);
+		c3 = input.charCodeAt(i++);
+		e3 = ((c2 & 15) << 2) | (c3 >> 6);
+		e4 = (c3 & 63);
+		if (isNaN(c2)) { e3 = e4 = 64; }
+		else if (isNaN(c3)) { e4 = 64; }
+		o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
+	}
+	return o;
+}
+function Base64_decode(input: string): string {
+	var o = "";
+	var c1=0, c2=0, c3=0, e1=0, e2=0, e3=0, e4=0;
+	input = input.replace(/[^\w\+\/\=]/g, "");
+	for(var i = 0; i < input.length;) {
+		e1 = Base64_map.indexOf(input.charAt(i++));
+		e2 = Base64_map.indexOf(input.charAt(i++));
+		c1 = (e1 << 2) | (e2 >> 4);
+		o += String.fromCharCode(c1);
 
-				e3 = map.indexOf(input.charAt(i++));
-				c2 = ((e2 & 15) << 4) | (e3 >> 2);
-				if (e3 !== 64) { o += String.fromCharCode(c2); }
+		e3 = Base64_map.indexOf(input.charAt(i++));
+		c2 = ((e2 & 15) << 4) | (e3 >> 2);
+		if (e3 !== 64) { o += String.fromCharCode(c2); }
 
-				e4 = map.indexOf(input.charAt(i++));
-				c3 = ((e3 & 3) << 6) | e4;
-				if (e4 !== 64) { o += String.fromCharCode(c3); }
-			}
-			return o;
-		}
-	};
-})();
+		e4 = Base64_map.indexOf(input.charAt(i++));
+		c3 = ((e3 & 3) << 6) | e4;
+		if (e4 !== 64) { o += String.fromCharCode(c3); }
+	}
+	return o;
+}
diff --git a/modules/83_numbers.js b/modules/83_numbers.js
index 39130c9..bd7c4d0 100644
--- a/modules/83_numbers.js
+++ b/modules/83_numbers.js
@@ -162,6 +162,18 @@ function parse_shallow(buf) {
   }
   return out;
 }
+function write_shallow(proto) {
+  var out = [];
+  proto.forEach(function(field, idx) {
+    field.forEach(function(item) {
+      out.push(write_varint49(idx * 8 + item.type));
+      if (item.type == 2)
+        out.push(write_varint49(item.data.length));
+      out.push(item.data);
+    });
+  });
+  return u8concat(out);
+}
 function mappa(data, cb) {
   if (!data)
     return [];
diff --git a/modules/83_numbers.ts b/modules/83_numbers.ts
index 405d770..d8364cb 100644
--- a/modules/83_numbers.ts
+++ b/modules/83_numbers.ts
@@ -90,6 +90,7 @@ function varint_to_i32(buf: Uint8Array): number {
 	}
 	return i32;
 }
+//<<export { varint_to_i32 };
 
 interface ProtoItem {
 	offset?: number;
@@ -128,18 +129,18 @@ function parse_shallow(buf: Uint8Array): ProtoMessage {
 	return out;
 }
 /** Serialize a shallow parse */
-/*
 function write_shallow(proto: ProtoMessage): Uint8Array {
 	var out: Uint8Array[] = [];
 	proto.forEach((field, idx) => {
 		field.forEach(item => {
 			out.push(write_varint49(idx * 8 + item.type));
+			if(item.type == 2) out.push(write_varint49(item.data.length));
 			out.push(item.data);
 		});
 	});
 	return u8concat(out);
 }
-*/
+//<<export { parse_shallow, write_shallow };
 
 /** Map over each entry in a repeated (or single-value) field */
 function mappa<U>(data: ProtoField, cb:(Uint8Array) => U): U[] {
@@ -173,6 +174,7 @@ function parse_iwa_file(buf: Uint8Array): IWAArchiveInfo[] {
 		ptr[0] += len;
 
 		var res: IWAArchiveInfo = {
+			/* TODO: technically ID is optional */
 			id: varint_to_i32(ai[1][0].data),
 			messages: []
 		};
@@ -189,6 +191,7 @@ function parse_iwa_file(buf: Uint8Array): IWAArchiveInfo[] {
 	}
 	return out;
 }
+//<<export { parse_iwa_file };
 
 /** Decompress a snappy chunk */
 function parse_snappy_chunk(type: number, buf: Uint8Array): Uint8Array {
diff --git a/modules/Makefile b/modules/Makefile
index 10d91d9..92a34b5 100644
--- a/modules/Makefile
+++ b/modules/Makefile
@@ -1,4 +1,5 @@
-LIBFILES=$(wildcard src/*.ts)
+LIBFILES=src/types.ts
+#LIBFILES=$(wildcard src/*.ts)
 TSFILES=$(wildcard *.ts)
 ENTRIES=$(subst .ts,.js,$(TSFILES))
 
@@ -10,7 +11,7 @@ all: $(ENTRIES)
 $(BAREJS): %.js: %.ts $(LIBFILES)
 	npx esbuild $< --outfile=$@ --platform=browser --target=es5
 
-%.node.js: %.node.ts $(LIBFILES)
+%.node.js: %.node.ts $(LIBFILES) src/numbers.ts
 	npx esbuild $< --bundle --external:xlsx --outfile=$@ --minify --platform=node
 	sed -i '' 's/ts-node/node/g' $@
 
diff --git a/xlsx.flow.js b/xlsx.flow.js
index f3502df..ea0d44c 100644
--- a/xlsx.flow.js
+++ b/xlsx.flow.js
@@ -84,53 +84,49 @@ if(typeof $cptable !== 'undefined') {
 }
 var DENSE = null;
 var DIF_XL = true;
-var Base64 = function() {
-  var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-  return {
-    encode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      for (var i = 0; i < input.length; ) {
-        c1 = input.charCodeAt(i++);
-        e1 = c1 >> 2;
-        c2 = input.charCodeAt(i++);
-        e2 = (c1 & 3) << 4 | c2 >> 4;
-        c3 = input.charCodeAt(i++);
-        e3 = (c2 & 15) << 2 | c3 >> 6;
-        e4 = c3 & 63;
-        if (isNaN(c2)) {
-          e3 = e4 = 64;
-        } else if (isNaN(c3)) {
-          e4 = 64;
-        }
-        o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4);
-      }
-      return o;
-    },
-    decode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      input = input.replace(/[^\w\+\/\=]/g, "");
-      for (var i = 0; i < input.length; ) {
-        e1 = map.indexOf(input.charAt(i++));
-        e2 = map.indexOf(input.charAt(i++));
-        c1 = e1 << 2 | e2 >> 4;
-        o += String.fromCharCode(c1);
-        e3 = map.indexOf(input.charAt(i++));
-        c2 = (e2 & 15) << 4 | e3 >> 2;
-        if (e3 !== 64) {
-          o += String.fromCharCode(c2);
-        }
-        e4 = map.indexOf(input.charAt(i++));
-        c3 = (e3 & 3) << 6 | e4;
-        if (e4 !== 64) {
-          o += String.fromCharCode(c3);
-        }
-      }
-      return o;
+var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+function Base64_encode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  for (var i = 0; i < input.length; ) {
+    c1 = input.charCodeAt(i++);
+    e1 = c1 >> 2;
+    c2 = input.charCodeAt(i++);
+    e2 = (c1 & 3) << 4 | c2 >> 4;
+    c3 = input.charCodeAt(i++);
+    e3 = (c2 & 15) << 2 | c3 >> 6;
+    e4 = c3 & 63;
+    if (isNaN(c2)) {
+      e3 = e4 = 64;
+    } else if (isNaN(c3)) {
+      e4 = 64;
     }
-  };
-}();
+    o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
+  }
+  return o;
+}
+function Base64_decode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  input = input.replace(/[^\w\+\/\=]/g, "");
+  for (var i = 0; i < input.length; ) {
+    e1 = Base64_map.indexOf(input.charAt(i++));
+    e2 = Base64_map.indexOf(input.charAt(i++));
+    c1 = e1 << 2 | e2 >> 4;
+    o += String.fromCharCode(c1);
+    e3 = Base64_map.indexOf(input.charAt(i++));
+    c2 = (e2 & 15) << 4 | e3 >> 2;
+    if (e3 !== 64) {
+      o += String.fromCharCode(c2);
+    }
+    e4 = Base64_map.indexOf(input.charAt(i++));
+    c3 = (e3 & 3) << 6 | e4;
+    if (e4 !== 64) {
+      o += String.fromCharCode(c3);
+    }
+  }
+  return o;
+}
 var has_buf = /*#__PURE__*/(function() { return typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && !!process.versions.node; })();
 
 var Buffer_from = /*#__PURE__*/(function() {
@@ -1325,7 +1321,6 @@ function dateNF_fix(str/*:string*/, dateNF/*:string*/, match/*:Array<string>*/)/
 }
 
 /*::
-declare var Base64:any;
 declare var ReadShift:any;
 declare var CheckField:any;
 declare var prep_blob:any;
@@ -1878,7 +1873,7 @@ function read(blob/*:RawBytes|string*/, options/*:CFBReadOpts*/) {
 	}
 	switch(type || "base64") {
 		case "file": /*:: if(typeof blob !== 'string') throw "Must pass a filename when type='file'"; */return read_file(blob, options);
-		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64.decode(blob)), options);
+		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64_decode(blob)), options);
 		case "binary": /*:: if(typeof blob !== 'string') throw "Must pass a binary string when type='file'"; */return parse(s2a(blob), options);
 	}
 	return parse(/*::typeof blob == 'string' ? new Buffer(blob, 'utf-8') : */blob, options);
@@ -2201,7 +2196,7 @@ function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string
 	switch(options && options.type || "buffer") {
 		case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o;
 		case "binary": return typeof o == "string" ? o : a2s(o);
-		case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o));
+		case "base64": return Base64_encode(typeof o == "string" ? o : a2s(o));
 		case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o);
 			/* falls through */
 		case "array": return typeof o == "string" ? s2a(o) : o;
@@ -2910,7 +2905,7 @@ function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ {
 
 /* 76 character chunks TODO: intertwine encoding */
 function write_base64_76(bstr/*:string*/)/*:string*/ {
-	var data = Base64.encode(bstr);
+	var data = Base64_encode(bstr);
 	var o = [];
 	for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76));
 	return o.join("\r\n") + "\r\n";
@@ -2991,7 +2986,7 @@ function parse_mime(cfb/*:CFBContainer*/, data/*:Array<string>*/, root/*:string*
 	}
 	++di;
 	switch(cte.toLowerCase()) {
-		case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break;
+		case 'base64': fdata = s2a(Base64_decode(data.slice(di).join(""))); break;
 		case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break;
 		default: throw new Error("Unsupported Content-Transfer-Encoding " + cte);
 	}
@@ -4653,6 +4648,7 @@ function parse_ClipboardFormatOrAnsiString(o) { return parse_ClipboardFormatOrSt
 function parse_ClipboardFormatOrUnicodeString(o) { return parse_ClipboardFormatOrString(o, 2); }
 
 /* [MS-OLEPS] 2.2 PropertyType */
+// Note: some tree shakers cannot handle VT_VECTOR | $CONST, hence extra vars
 //var VT_EMPTY    = 0x0000;
 //var VT_NULL     = 0x0001;
 var VT_I2       = 0x0002;
@@ -4687,6 +4683,8 @@ var VT_CF       = 0x0047;
 //var VT_CLSID    = 0x0048;
 //var VT_VERSIONED_STREAM = 0x0049;
 var VT_VECTOR   = 0x1000;
+var VT_VECTOR_VARIANT = 0x100C;
+var VT_VECTOR_LPSTR   = 0x101E;
 //var VT_ARRAY    = 0x2000;
 
 var VT_STRING   = 0x0050; // 2.3.3.1.11 VtString
@@ -4706,8 +4704,8 @@ var DocSummaryPIDDSI = {
 	/*::[*/0x09/*::]*/: { n: 'HiddenCount', t: VT_I4 },
 	/*::[*/0x0a/*::]*/: { n: 'MultimediaClipCount', t: VT_I4 },
 	/*::[*/0x0b/*::]*/: { n: 'ScaleCrop', t: VT_BOOL },
-	/*::[*/0x0c/*::]*/: { n: 'HeadingPairs', t: VT_VECTOR | VT_VARIANT },
-	/*::[*/0x0d/*::]*/: { n: 'TitlesOfParts', t: VT_VECTOR | VT_LPSTR },
+	/*::[*/0x0c/*::]*/: { n: 'HeadingPairs', t: VT_VECTOR_VARIANT /* VT_VECTOR | VT_VARIANT */ },
+	/*::[*/0x0d/*::]*/: { n: 'TitlesOfParts', t: VT_VECTOR_LPSTR /* VT_VECTOR | VT_LPSTR */ },
 	/*::[*/0x0e/*::]*/: { n: 'Manager', t: VT_STRING },
 	/*::[*/0x0f/*::]*/: { n: 'Company', t: VT_STRING },
 	/*::[*/0x10/*::]*/: { n: 'LinksUpToDate', t: VT_BOOL },
@@ -7590,7 +7588,7 @@ function dbf_to_aoa(buf, opts)/*:AOA*/ {
 	var out/*:AOA*/ = [];
 	var d/*:Block*/ = (new_raw_buf(1)/*:any*/);
 	switch(opts.type) {
-		case 'base64': d = s2a(Base64.decode(buf)); break;
+		case 'base64': d = s2a(Base64_decode(buf)); break;
 		case 'binary': d = s2a(buf); break;
 		case 'buffer':
 		case 'array': d = buf; break;
@@ -7921,7 +7919,7 @@ var SYLK = /*#__PURE__*/(function() {
 	/* TODO: find an actual specification */
 	function sylk_to_aoa(d/*:RawData*/, opts)/*:[AOA, Worksheet]*/ {
 		switch(opts.type) {
-			case 'base64': return sylk_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return sylk_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return sylk_to_aoa_str(d, opts);
 			case 'buffer': return sylk_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return sylk_to_aoa_str(cc2str(d), opts);
@@ -8124,7 +8122,7 @@ var SYLK = /*#__PURE__*/(function() {
 var DIF = /*#__PURE__*/(function() {
 	function dif_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
 		switch(opts.type) {
-			case 'base64': return dif_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return dif_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return dif_to_aoa_str(d, opts);
 			case 'buffer': return dif_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return dif_to_aoa_str(cc2str(d), opts);
@@ -8490,7 +8488,7 @@ var PRN = /*#__PURE__*/(function() {
 	function prn_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ {
 		var str = "", bytes = opts.type == 'string' ? [0,0,0,0] : firstbyte(d, opts);
 		switch(opts.type) {
-			case 'base64': str = Base64.decode(d); break;
+			case 'base64': str = Base64_decode(d); break;
 			case 'binary': str = d; break;
 			case 'buffer':
 				if(opts.codepage == 65001) str = d.toString('utf8'); // TODO: test if buf
@@ -8568,7 +8566,7 @@ var WK_ = /*#__PURE__*/(function() {
 
 	function lotus_to_workbook(d/*:RawData*/, opts) {
 		switch(opts.type) {
-			case 'base64': return lotus_to_workbook_buf(s2a(Base64.decode(d)), opts);
+			case 'base64': return lotus_to_workbook_buf(s2a(Base64_decode(d)), opts);
 			case 'binary': return lotus_to_workbook_buf(s2a(d), opts);
 			case 'buffer':
 			case 'array': return lotus_to_workbook_buf(d, opts);
@@ -9980,7 +9978,7 @@ function parse_FilePass(blob, length/*:number*/, opts) {
 var RTF = /*#__PURE__*/(function() {
 	function rtf_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ {
 		switch(opts.type) {
-			case 'base64': return rtf_to_sheet_str(Base64.decode(d), opts);
+			case 'base64': return rtf_to_sheet_str(Base64_decode(d), opts);
 			case 'binary': return rtf_to_sheet_str(d, opts);
 			case 'buffer': return rtf_to_sheet_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array':  return rtf_to_sheet_str(cc2str(d), opts);
@@ -18132,7 +18130,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ {
 function parse_xlml(data/*:RawBytes|string*/, opts)/*:Workbook*/ {
 	fix_read_opts(opts=opts||{});
 	switch(opts.type||"base64") {
-		case "base64": return parse_xlml_xml(Base64.decode(data), opts);
+		case "base64": return parse_xlml_xml(Base64_decode(data), opts);
 		case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts);
 		case "array": return parse_xlml_xml(a2s(data), opts);
 	}
@@ -19122,7 +19120,7 @@ if(cfb.FullPaths) {
 	WB = CFB.find(cfb, '/Workbook') || CFB.find(cfb, '/Book');
 } else {
 	switch(options.type) {
-		case 'base64': cfb = s2a(Base64.decode(cfb)); break;
+		case 'base64': cfb = s2a(Base64_decode(cfb)); break;
 		case 'binary': cfb = s2a(cfb); break;
 		case 'buffer': break;
 		case 'array': if(!Array.isArray(cfb)) cfb = Array.prototype.slice.call(cfb); break;
@@ -22159,6 +22157,18 @@ function parse_shallow(buf) {
   }
   return out;
 }
+function write_shallow(proto) {
+  var out = [];
+  proto.forEach(function(field, idx) {
+    field.forEach(function(item) {
+      out.push(write_varint49(idx * 8 + item.type));
+      if (item.type == 2)
+        out.push(write_varint49(item.data.length));
+      out.push(item.data);
+    });
+  });
+  return u8concat(out);
+}
 function mappa(data, cb) {
   if (!data)
     return [];
@@ -23303,7 +23313,7 @@ function firstbyte(f/*:RawData*/,o/*:?TypeOpts*/)/*:Array<number>*/ {
 	var x = "";
 	switch((o||{}).type || "base64") {
 		case 'buffer': return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
-		case 'base64': x = Base64.decode(f.slice(0,12)); break;
+		case 'base64': x = Base64_decode(f.slice(0,12)); break;
 		case 'binary': x = f; break;
 		case 'array':  return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
 		default: throw new Error("Unrecognized type " + (o && o.type || "undefined"));
@@ -23337,7 +23347,7 @@ function read_plaintext(data/*:string*/, o/*:ParseOpts*/)/*:Workbook*/ {
 function read_plaintext_raw(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 	var str = "", bytes = firstbyte(data, o);
 	switch(o.type) {
-		case 'base64': str = Base64.decode(data); break;
+		case 'base64': str = Base64_decode(data); break;
 		case 'binary': str = data; break;
 		case 'buffer': str = data.toString('binary'); break;
 		case 'array': str = cc2str(data); break;
@@ -23350,7 +23360,7 @@ function read_plaintext_raw(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 
 function read_utf16(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 	var d = data;
-	if(o.type == 'base64') d = Base64.decode(d);
+	if(o.type == 'base64') d = Base64_decode(d);
 	d = $cptable.utils.decode(1200, d.slice(2), 'str');
 	o.type = "binary";
 	return read_plaintext(d, o);
@@ -23476,7 +23486,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/
 	if(!bom) bom = "";
 	var o = bom + out;
 	switch(opts.type) {
-		case "base64": return Base64.encode(utf8write(o));
+		case "base64": return Base64_encode(utf8write(o));
 		case "binary": return utf8write(o);
 		case "string": return out;
 		case "file": return write_dl(opts.file, o, 'utf8');
@@ -23491,7 +23501,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/
 
 function write_stxt_type(out/*:string*/, opts/*:WriteOpts*/)/*:any*/ {
 	switch(opts.type) {
-		case "base64": return Base64.encode(out);
+		case "base64": return Base64_encode(out);
 		case "binary": return out;
 		case "string": return out; /* override in sheet_to_txt */
 		case "file": return write_dl(opts.file, out, 'binary');
@@ -23512,7 +23522,7 @@ function write_binary_type(out, opts/*:WriteOpts*/)/*:any*/ {
 			var bstr = "";
 			// $FlowIgnore
 			for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
-			return opts.type == 'base64' ? Base64.encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
+			return opts.type == 'base64' ? Base64_encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
 		case "file": return write_dl(opts.file, out);
 		case "buffer": return out;
 		default: throw new Error("Unrecognized type " + opts.type);
diff --git a/xlsx.js b/xlsx.js
index e7e1635..f737cf8 100644
--- a/xlsx.js
+++ b/xlsx.js
@@ -83,53 +83,49 @@ if(typeof $cptable !== 'undefined') {
 }
 var DENSE = null;
 var DIF_XL = true;
-var Base64 = function() {
-  var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-  return {
-    encode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      for (var i = 0; i < input.length; ) {
-        c1 = input.charCodeAt(i++);
-        e1 = c1 >> 2;
-        c2 = input.charCodeAt(i++);
-        e2 = (c1 & 3) << 4 | c2 >> 4;
-        c3 = input.charCodeAt(i++);
-        e3 = (c2 & 15) << 2 | c3 >> 6;
-        e4 = c3 & 63;
-        if (isNaN(c2)) {
-          e3 = e4 = 64;
-        } else if (isNaN(c3)) {
-          e4 = 64;
-        }
-        o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4);
-      }
-      return o;
-    },
-    decode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      input = input.replace(/[^\w\+\/\=]/g, "");
-      for (var i = 0; i < input.length; ) {
-        e1 = map.indexOf(input.charAt(i++));
-        e2 = map.indexOf(input.charAt(i++));
-        c1 = e1 << 2 | e2 >> 4;
-        o += String.fromCharCode(c1);
-        e3 = map.indexOf(input.charAt(i++));
-        c2 = (e2 & 15) << 4 | e3 >> 2;
-        if (e3 !== 64) {
-          o += String.fromCharCode(c2);
-        }
-        e4 = map.indexOf(input.charAt(i++));
-        c3 = (e3 & 3) << 6 | e4;
-        if (e4 !== 64) {
-          o += String.fromCharCode(c3);
-        }
-      }
-      return o;
+var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+function Base64_encode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  for (var i = 0; i < input.length; ) {
+    c1 = input.charCodeAt(i++);
+    e1 = c1 >> 2;
+    c2 = input.charCodeAt(i++);
+    e2 = (c1 & 3) << 4 | c2 >> 4;
+    c3 = input.charCodeAt(i++);
+    e3 = (c2 & 15) << 2 | c3 >> 6;
+    e4 = c3 & 63;
+    if (isNaN(c2)) {
+      e3 = e4 = 64;
+    } else if (isNaN(c3)) {
+      e4 = 64;
     }
-  };
-}();
+    o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
+  }
+  return o;
+}
+function Base64_decode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  input = input.replace(/[^\w\+\/\=]/g, "");
+  for (var i = 0; i < input.length; ) {
+    e1 = Base64_map.indexOf(input.charAt(i++));
+    e2 = Base64_map.indexOf(input.charAt(i++));
+    c1 = e1 << 2 | e2 >> 4;
+    o += String.fromCharCode(c1);
+    e3 = Base64_map.indexOf(input.charAt(i++));
+    c2 = (e2 & 15) << 4 | e3 >> 2;
+    if (e3 !== 64) {
+      o += String.fromCharCode(c2);
+    }
+    e4 = Base64_map.indexOf(input.charAt(i++));
+    c3 = (e3 & 3) << 6 | e4;
+    if (e4 !== 64) {
+      o += String.fromCharCode(c3);
+    }
+  }
+  return o;
+}
 var has_buf = (function() { return typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && !!process.versions.node; })();
 
 var Buffer_from = (function() {
@@ -1815,7 +1811,7 @@ function read(blob, options) {
 	}
 	switch(type || "base64") {
 		case "file": return read_file(blob, options);
-		case "base64": return parse(s2a(Base64.decode(blob)), options);
+		case "base64": return parse(s2a(Base64_decode(blob)), options);
 		case "binary": return parse(s2a(blob), options);
 	}
 	return parse(blob, options);
@@ -2132,7 +2128,7 @@ function write(cfb, options) {
 	switch(options && options.type || "buffer") {
 		case "file": get_fs(); fs.writeFileSync(options.filename, (o)); return o;
 		case "binary": return typeof o == "string" ? o : a2s(o);
-		case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o));
+		case "base64": return Base64_encode(typeof o == "string" ? o : a2s(o));
 		case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o);
 			/* falls through */
 		case "array": return typeof o == "string" ? s2a(o) : o;
@@ -2841,7 +2837,7 @@ function get_content_type(fi, fp) {
 
 /* 76 character chunks TODO: intertwine encoding */
 function write_base64_76(bstr) {
-	var data = Base64.encode(bstr);
+	var data = Base64_encode(bstr);
 	var o = [];
 	for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76));
 	return o.join("\r\n") + "\r\n";
@@ -2922,7 +2918,7 @@ function parse_mime(cfb, data, root) {
 	}
 	++di;
 	switch(cte.toLowerCase()) {
-		case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break;
+		case 'base64': fdata = s2a(Base64_decode(data.slice(di).join(""))); break;
 		case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break;
 		default: throw new Error("Unsupported Content-Transfer-Encoding " + cte);
 	}
@@ -4566,6 +4562,7 @@ function parse_ClipboardFormatOrAnsiString(o) { return parse_ClipboardFormatOrSt
 function parse_ClipboardFormatOrUnicodeString(o) { return parse_ClipboardFormatOrString(o, 2); }
 
 /* [MS-OLEPS] 2.2 PropertyType */
+// Note: some tree shakers cannot handle VT_VECTOR | $CONST, hence extra vars
 //var VT_EMPTY    = 0x0000;
 //var VT_NULL     = 0x0001;
 var VT_I2       = 0x0002;
@@ -4600,6 +4597,8 @@ var VT_CF       = 0x0047;
 //var VT_CLSID    = 0x0048;
 //var VT_VERSIONED_STREAM = 0x0049;
 var VT_VECTOR   = 0x1000;
+var VT_VECTOR_VARIANT = 0x100C;
+var VT_VECTOR_LPSTR   = 0x101E;
 //var VT_ARRAY    = 0x2000;
 
 var VT_STRING   = 0x0050; // 2.3.3.1.11 VtString
@@ -4619,8 +4618,8 @@ var DocSummaryPIDDSI = {
 0x09: { n: 'HiddenCount', t: VT_I4 },
 0x0a: { n: 'MultimediaClipCount', t: VT_I4 },
 0x0b: { n: 'ScaleCrop', t: VT_BOOL },
-0x0c: { n: 'HeadingPairs', t: VT_VECTOR | VT_VARIANT },
-0x0d: { n: 'TitlesOfParts', t: VT_VECTOR | VT_LPSTR },
+0x0c: { n: 'HeadingPairs', t: VT_VECTOR_VARIANT /* VT_VECTOR | VT_VARIANT */ },
+0x0d: { n: 'TitlesOfParts', t: VT_VECTOR_LPSTR /* VT_VECTOR | VT_LPSTR */ },
 0x0e: { n: 'Manager', t: VT_STRING },
 0x0f: { n: 'Company', t: VT_STRING },
 0x10: { n: 'LinksUpToDate', t: VT_BOOL },
@@ -7499,7 +7498,7 @@ function dbf_to_aoa(buf, opts) {
 	var out = [];
 	var d = (new_raw_buf(1));
 	switch(opts.type) {
-		case 'base64': d = s2a(Base64.decode(buf)); break;
+		case 'base64': d = s2a(Base64_decode(buf)); break;
 		case 'binary': d = s2a(buf); break;
 		case 'buffer':
 		case 'array': d = buf; break;
@@ -7829,7 +7828,7 @@ var SYLK = (function() {
 	/* TODO: find an actual specification */
 	function sylk_to_aoa(d, opts) {
 		switch(opts.type) {
-			case 'base64': return sylk_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return sylk_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return sylk_to_aoa_str(d, opts);
 			case 'buffer': return sylk_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return sylk_to_aoa_str(cc2str(d), opts);
@@ -8032,7 +8031,7 @@ var SYLK = (function() {
 var DIF = (function() {
 	function dif_to_aoa(d, opts) {
 		switch(opts.type) {
-			case 'base64': return dif_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return dif_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return dif_to_aoa_str(d, opts);
 			case 'buffer': return dif_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return dif_to_aoa_str(cc2str(d), opts);
@@ -8398,7 +8397,7 @@ var PRN = (function() {
 	function prn_to_sheet(d, opts) {
 		var str = "", bytes = opts.type == 'string' ? [0,0,0,0] : firstbyte(d, opts);
 		switch(opts.type) {
-			case 'base64': str = Base64.decode(d); break;
+			case 'base64': str = Base64_decode(d); break;
 			case 'binary': str = d; break;
 			case 'buffer':
 				if(opts.codepage == 65001) str = d.toString('utf8'); // TODO: test if buf
@@ -8476,7 +8475,7 @@ var WK_ = (function() {
 
 	function lotus_to_workbook(d, opts) {
 		switch(opts.type) {
-			case 'base64': return lotus_to_workbook_buf(s2a(Base64.decode(d)), opts);
+			case 'base64': return lotus_to_workbook_buf(s2a(Base64_decode(d)), opts);
 			case 'binary': return lotus_to_workbook_buf(s2a(d), opts);
 			case 'buffer':
 			case 'array': return lotus_to_workbook_buf(d, opts);
@@ -9887,7 +9886,7 @@ function parse_FilePass(blob, length, opts) {
 var RTF = (function() {
 	function rtf_to_sheet(d, opts) {
 		switch(opts.type) {
-			case 'base64': return rtf_to_sheet_str(Base64.decode(d), opts);
+			case 'base64': return rtf_to_sheet_str(Base64_decode(d), opts);
 			case 'binary': return rtf_to_sheet_str(d, opts);
 			case 'buffer': return rtf_to_sheet_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array':  return rtf_to_sheet_str(cc2str(d), opts);
@@ -18029,7 +18028,7 @@ Workbook.WBProps.date1904 = true;
 function parse_xlml(data, opts) {
 	fix_read_opts(opts=opts||{});
 	switch(opts.type||"base64") {
-		case "base64": return parse_xlml_xml(Base64.decode(data), opts);
+		case "base64": return parse_xlml_xml(Base64_decode(data), opts);
 		case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts);
 		case "array": return parse_xlml_xml(a2s(data), opts);
 	}
@@ -19011,7 +19010,7 @@ if(cfb.FullPaths) {
 	WB = CFB.find(cfb, '/Workbook') || CFB.find(cfb, '/Book');
 } else {
 	switch(options.type) {
-		case 'base64': cfb = s2a(Base64.decode(cfb)); break;
+		case 'base64': cfb = s2a(Base64_decode(cfb)); break;
 		case 'binary': cfb = s2a(cfb); break;
 		case 'buffer': break;
 		case 'array': if(!Array.isArray(cfb)) cfb = Array.prototype.slice.call(cfb); break;
@@ -22047,6 +22046,18 @@ function parse_shallow(buf) {
   }
   return out;
 }
+function write_shallow(proto) {
+  var out = [];
+  proto.forEach(function(field, idx) {
+    field.forEach(function(item) {
+      out.push(write_varint49(idx * 8 + item.type));
+      if (item.type == 2)
+        out.push(write_varint49(item.data.length));
+      out.push(item.data);
+    });
+  });
+  return u8concat(out);
+}
 function mappa(data, cb) {
   if (!data)
     return [];
@@ -23187,7 +23198,7 @@ function firstbyte(f,o) {
 	var x = "";
 	switch((o||{}).type || "base64") {
 		case 'buffer': return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
-		case 'base64': x = Base64.decode(f.slice(0,12)); break;
+		case 'base64': x = Base64_decode(f.slice(0,12)); break;
 		case 'binary': x = f; break;
 		case 'array':  return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
 		default: throw new Error("Unrecognized type " + (o && o.type || "undefined"));
@@ -23221,7 +23232,7 @@ function read_plaintext(data, o) {
 function read_plaintext_raw(data, o) {
 	var str = "", bytes = firstbyte(data, o);
 	switch(o.type) {
-		case 'base64': str = Base64.decode(data); break;
+		case 'base64': str = Base64_decode(data); break;
 		case 'binary': str = data; break;
 		case 'buffer': str = data.toString('binary'); break;
 		case 'array': str = cc2str(data); break;
@@ -23234,7 +23245,7 @@ function read_plaintext_raw(data, o) {
 
 function read_utf16(data, o) {
 	var d = data;
-	if(o.type == 'base64') d = Base64.decode(d);
+	if(o.type == 'base64') d = Base64_decode(d);
 	d = $cptable.utils.decode(1200, d.slice(2), 'str');
 	o.type = "binary";
 	return read_plaintext(d, o);
@@ -23359,7 +23370,7 @@ function write_string_type(out, opts, bom) {
 	if(!bom) bom = "";
 	var o = bom + out;
 	switch(opts.type) {
-		case "base64": return Base64.encode(utf8write(o));
+		case "base64": return Base64_encode(utf8write(o));
 		case "binary": return utf8write(o);
 		case "string": return out;
 		case "file": return write_dl(opts.file, o, 'utf8');
@@ -23374,7 +23385,7 @@ function write_string_type(out, opts, bom) {
 
 function write_stxt_type(out, opts) {
 	switch(opts.type) {
-		case "base64": return Base64.encode(out);
+		case "base64": return Base64_encode(out);
 		case "binary": return out;
 		case "string": return out; /* override in sheet_to_txt */
 		case "file": return write_dl(opts.file, out, 'binary');
@@ -23395,7 +23406,7 @@ function write_binary_type(out, opts) {
 			var bstr = "";
 			// $FlowIgnore
 			for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
-			return opts.type == 'base64' ? Base64.encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
+			return opts.type == 'base64' ? Base64_encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
 		case "file": return write_dl(opts.file, out);
 		case "buffer": return out;
 		default: throw new Error("Unrecognized type " + opts.type);
diff --git a/xlsx.mjs b/xlsx.mjs
index 5cdf2ff..0c43f50 100644
--- a/xlsx.mjs
+++ b/xlsx.mjs
@@ -81,53 +81,49 @@ function set_cptable(cptable) {
 export { set_cptable };
 var DENSE = null;
 var DIF_XL = true;
-var Base64 = function() {
-  var map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
-  return {
-    encode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      for (var i = 0; i < input.length; ) {
-        c1 = input.charCodeAt(i++);
-        e1 = c1 >> 2;
-        c2 = input.charCodeAt(i++);
-        e2 = (c1 & 3) << 4 | c2 >> 4;
-        c3 = input.charCodeAt(i++);
-        e3 = (c2 & 15) << 2 | c3 >> 6;
-        e4 = c3 & 63;
-        if (isNaN(c2)) {
-          e3 = e4 = 64;
-        } else if (isNaN(c3)) {
-          e4 = 64;
-        }
-        o += map.charAt(e1) + map.charAt(e2) + map.charAt(e3) + map.charAt(e4);
-      }
-      return o;
-    },
-    decode: function(input) {
-      var o = "";
-      var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
-      input = input.replace(/[^\w\+\/\=]/g, "");
-      for (var i = 0; i < input.length; ) {
-        e1 = map.indexOf(input.charAt(i++));
-        e2 = map.indexOf(input.charAt(i++));
-        c1 = e1 << 2 | e2 >> 4;
-        o += String.fromCharCode(c1);
-        e3 = map.indexOf(input.charAt(i++));
-        c2 = (e2 & 15) << 4 | e3 >> 2;
-        if (e3 !== 64) {
-          o += String.fromCharCode(c2);
-        }
-        e4 = map.indexOf(input.charAt(i++));
-        c3 = (e3 & 3) << 6 | e4;
-        if (e4 !== 64) {
-          o += String.fromCharCode(c3);
-        }
-      }
-      return o;
+var Base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+function Base64_encode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  for (var i = 0; i < input.length; ) {
+    c1 = input.charCodeAt(i++);
+    e1 = c1 >> 2;
+    c2 = input.charCodeAt(i++);
+    e2 = (c1 & 3) << 4 | c2 >> 4;
+    c3 = input.charCodeAt(i++);
+    e3 = (c2 & 15) << 2 | c3 >> 6;
+    e4 = c3 & 63;
+    if (isNaN(c2)) {
+      e3 = e4 = 64;
+    } else if (isNaN(c3)) {
+      e4 = 64;
     }
-  };
-}();
+    o += Base64_map.charAt(e1) + Base64_map.charAt(e2) + Base64_map.charAt(e3) + Base64_map.charAt(e4);
+  }
+  return o;
+}
+function Base64_decode(input) {
+  var o = "";
+  var c1 = 0, c2 = 0, c3 = 0, e1 = 0, e2 = 0, e3 = 0, e4 = 0;
+  input = input.replace(/[^\w\+\/\=]/g, "");
+  for (var i = 0; i < input.length; ) {
+    e1 = Base64_map.indexOf(input.charAt(i++));
+    e2 = Base64_map.indexOf(input.charAt(i++));
+    c1 = e1 << 2 | e2 >> 4;
+    o += String.fromCharCode(c1);
+    e3 = Base64_map.indexOf(input.charAt(i++));
+    c2 = (e2 & 15) << 4 | e3 >> 2;
+    if (e3 !== 64) {
+      o += String.fromCharCode(c2);
+    }
+    e4 = Base64_map.indexOf(input.charAt(i++));
+    c3 = (e3 & 3) << 6 | e4;
+    if (e4 !== 64) {
+      o += String.fromCharCode(c3);
+    }
+  }
+  return o;
+}
 var has_buf = /*#__PURE__*/(function() { return typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && !!process.versions.node; })();
 
 var Buffer_from = /*#__PURE__*/(function() {
@@ -1322,7 +1318,6 @@ function dateNF_fix(str/*:string*/, dateNF/*:string*/, match/*:Array<string>*/)/
 }
 
 /*::
-declare var Base64:any;
 declare var ReadShift:any;
 declare var CheckField:any;
 declare var prep_blob:any;
@@ -1875,7 +1870,7 @@ function read(blob/*:RawBytes|string*/, options/*:CFBReadOpts*/) {
 	}
 	switch(type || "base64") {
 		case "file": /*:: if(typeof blob !== 'string') throw "Must pass a filename when type='file'"; */return read_file(blob, options);
-		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64.decode(blob)), options);
+		case "base64": /*:: if(typeof blob !== 'string') throw "Must pass a base64-encoded binary string when type='file'"; */return parse(s2a(Base64_decode(blob)), options);
 		case "binary": /*:: if(typeof blob !== 'string') throw "Must pass a binary string when type='file'"; */return parse(s2a(blob), options);
 	}
 	return parse(/*::typeof blob == 'string' ? new Buffer(blob, 'utf-8') : */blob, options);
@@ -2198,7 +2193,7 @@ function write(cfb/*:CFBContainer*/, options/*:CFBWriteOpts*/)/*:RawBytes|string
 	switch(options && options.type || "buffer") {
 		case "file": get_fs(); fs.writeFileSync(options.filename, (o/*:any*/)); return o;
 		case "binary": return typeof o == "string" ? o : a2s(o);
-		case "base64": return Base64.encode(typeof o == "string" ? o : a2s(o));
+		case "base64": return Base64_encode(typeof o == "string" ? o : a2s(o));
 		case "buffer": if(has_buf) return Buffer.isBuffer(o) ? o : Buffer_from(o);
 			/* falls through */
 		case "array": return typeof o == "string" ? s2a(o) : o;
@@ -2907,7 +2902,7 @@ function get_content_type(fi/*:CFBEntry*/, fp/*:string*/)/*:string*/ {
 
 /* 76 character chunks TODO: intertwine encoding */
 function write_base64_76(bstr/*:string*/)/*:string*/ {
-	var data = Base64.encode(bstr);
+	var data = Base64_encode(bstr);
 	var o = [];
 	for(var i = 0; i < data.length; i+= 76) o.push(data.slice(i, i+76));
 	return o.join("\r\n") + "\r\n";
@@ -2988,7 +2983,7 @@ function parse_mime(cfb/*:CFBContainer*/, data/*:Array<string>*/, root/*:string*
 	}
 	++di;
 	switch(cte.toLowerCase()) {
-		case 'base64': fdata = s2a(Base64.decode(data.slice(di).join(""))); break;
+		case 'base64': fdata = s2a(Base64_decode(data.slice(di).join(""))); break;
 		case 'quoted-printable': fdata = parse_quoted_printable(data.slice(di)); break;
 		default: throw new Error("Unsupported Content-Transfer-Encoding " + cte);
 	}
@@ -4651,6 +4646,7 @@ function parse_ClipboardFormatOrAnsiString(o) { return parse_ClipboardFormatOrSt
 function parse_ClipboardFormatOrUnicodeString(o) { return parse_ClipboardFormatOrString(o, 2); }
 
 /* [MS-OLEPS] 2.2 PropertyType */
+// Note: some tree shakers cannot handle VT_VECTOR | $CONST, hence extra vars
 //var VT_EMPTY    = 0x0000;
 //var VT_NULL     = 0x0001;
 var VT_I2       = 0x0002;
@@ -4685,6 +4681,8 @@ var VT_CF       = 0x0047;
 //var VT_CLSID    = 0x0048;
 //var VT_VERSIONED_STREAM = 0x0049;
 var VT_VECTOR   = 0x1000;
+var VT_VECTOR_VARIANT = 0x100C;
+var VT_VECTOR_LPSTR   = 0x101E;
 //var VT_ARRAY    = 0x2000;
 
 var VT_STRING   = 0x0050; // 2.3.3.1.11 VtString
@@ -4704,8 +4702,8 @@ var DocSummaryPIDDSI = {
 	/*::[*/0x09/*::]*/: { n: 'HiddenCount', t: VT_I4 },
 	/*::[*/0x0a/*::]*/: { n: 'MultimediaClipCount', t: VT_I4 },
 	/*::[*/0x0b/*::]*/: { n: 'ScaleCrop', t: VT_BOOL },
-	/*::[*/0x0c/*::]*/: { n: 'HeadingPairs', t: VT_VECTOR | VT_VARIANT },
-	/*::[*/0x0d/*::]*/: { n: 'TitlesOfParts', t: VT_VECTOR | VT_LPSTR },
+	/*::[*/0x0c/*::]*/: { n: 'HeadingPairs', t: VT_VECTOR_VARIANT /* VT_VECTOR | VT_VARIANT */ },
+	/*::[*/0x0d/*::]*/: { n: 'TitlesOfParts', t: VT_VECTOR_LPSTR /* VT_VECTOR | VT_LPSTR */ },
 	/*::[*/0x0e/*::]*/: { n: 'Manager', t: VT_STRING },
 	/*::[*/0x0f/*::]*/: { n: 'Company', t: VT_STRING },
 	/*::[*/0x10/*::]*/: { n: 'LinksUpToDate', t: VT_BOOL },
@@ -7588,7 +7586,7 @@ function dbf_to_aoa(buf, opts)/*:AOA*/ {
 	var out/*:AOA*/ = [];
 	var d/*:Block*/ = (new_raw_buf(1)/*:any*/);
 	switch(opts.type) {
-		case 'base64': d = s2a(Base64.decode(buf)); break;
+		case 'base64': d = s2a(Base64_decode(buf)); break;
 		case 'binary': d = s2a(buf); break;
 		case 'buffer':
 		case 'array': d = buf; break;
@@ -7919,7 +7917,7 @@ var SYLK = /*#__PURE__*/(function() {
 	/* TODO: find an actual specification */
 	function sylk_to_aoa(d/*:RawData*/, opts)/*:[AOA, Worksheet]*/ {
 		switch(opts.type) {
-			case 'base64': return sylk_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return sylk_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return sylk_to_aoa_str(d, opts);
 			case 'buffer': return sylk_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return sylk_to_aoa_str(cc2str(d), opts);
@@ -8122,7 +8120,7 @@ var SYLK = /*#__PURE__*/(function() {
 var DIF = /*#__PURE__*/(function() {
 	function dif_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
 		switch(opts.type) {
-			case 'base64': return dif_to_aoa_str(Base64.decode(d), opts);
+			case 'base64': return dif_to_aoa_str(Base64_decode(d), opts);
 			case 'binary': return dif_to_aoa_str(d, opts);
 			case 'buffer': return dif_to_aoa_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array': return dif_to_aoa_str(cc2str(d), opts);
@@ -8488,7 +8486,7 @@ var PRN = /*#__PURE__*/(function() {
 	function prn_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ {
 		var str = "", bytes = opts.type == 'string' ? [0,0,0,0] : firstbyte(d, opts);
 		switch(opts.type) {
-			case 'base64': str = Base64.decode(d); break;
+			case 'base64': str = Base64_decode(d); break;
 			case 'binary': str = d; break;
 			case 'buffer':
 				if(opts.codepage == 65001) str = d.toString('utf8'); // TODO: test if buf
@@ -8566,7 +8564,7 @@ var WK_ = /*#__PURE__*/(function() {
 
 	function lotus_to_workbook(d/*:RawData*/, opts) {
 		switch(opts.type) {
-			case 'base64': return lotus_to_workbook_buf(s2a(Base64.decode(d)), opts);
+			case 'base64': return lotus_to_workbook_buf(s2a(Base64_decode(d)), opts);
 			case 'binary': return lotus_to_workbook_buf(s2a(d), opts);
 			case 'buffer':
 			case 'array': return lotus_to_workbook_buf(d, opts);
@@ -9978,7 +9976,7 @@ function parse_FilePass(blob, length/*:number*/, opts) {
 var RTF = /*#__PURE__*/(function() {
 	function rtf_to_sheet(d/*:RawData*/, opts)/*:Worksheet*/ {
 		switch(opts.type) {
-			case 'base64': return rtf_to_sheet_str(Base64.decode(d), opts);
+			case 'base64': return rtf_to_sheet_str(Base64_decode(d), opts);
 			case 'binary': return rtf_to_sheet_str(d, opts);
 			case 'buffer': return rtf_to_sheet_str(has_buf && Buffer.isBuffer(d) ? d.toString('binary') : a2s(d), opts);
 			case 'array':  return rtf_to_sheet_str(cc2str(d), opts);
@@ -18130,7 +18128,7 @@ function parse_xlml_xml(d, _opts)/*:Workbook*/ {
 function parse_xlml(data/*:RawBytes|string*/, opts)/*:Workbook*/ {
 	fix_read_opts(opts=opts||{});
 	switch(opts.type||"base64") {
-		case "base64": return parse_xlml_xml(Base64.decode(data), opts);
+		case "base64": return parse_xlml_xml(Base64_decode(data), opts);
 		case "binary": case "buffer": case "file": return parse_xlml_xml(data, opts);
 		case "array": return parse_xlml_xml(a2s(data), opts);
 	}
@@ -19120,7 +19118,7 @@ if(cfb.FullPaths) {
 	WB = CFB.find(cfb, '/Workbook') || CFB.find(cfb, '/Book');
 } else {
 	switch(options.type) {
-		case 'base64': cfb = s2a(Base64.decode(cfb)); break;
+		case 'base64': cfb = s2a(Base64_decode(cfb)); break;
 		case 'binary': cfb = s2a(cfb); break;
 		case 'buffer': break;
 		case 'array': if(!Array.isArray(cfb)) cfb = Array.prototype.slice.call(cfb); break;
@@ -22157,6 +22155,18 @@ function parse_shallow(buf) {
   }
   return out;
 }
+function write_shallow(proto) {
+  var out = [];
+  proto.forEach(function(field, idx) {
+    field.forEach(function(item) {
+      out.push(write_varint49(idx * 8 + item.type));
+      if (item.type == 2)
+        out.push(write_varint49(item.data.length));
+      out.push(item.data);
+    });
+  });
+  return u8concat(out);
+}
 function mappa(data, cb) {
   if (!data)
     return [];
@@ -23301,7 +23311,7 @@ function firstbyte(f/*:RawData*/,o/*:?TypeOpts*/)/*:Array<number>*/ {
 	var x = "";
 	switch((o||{}).type || "base64") {
 		case 'buffer': return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
-		case 'base64': x = Base64.decode(f.slice(0,12)); break;
+		case 'base64': x = Base64_decode(f.slice(0,12)); break;
 		case 'binary': x = f; break;
 		case 'array':  return [f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7]];
 		default: throw new Error("Unrecognized type " + (o && o.type || "undefined"));
@@ -23335,7 +23345,7 @@ function read_plaintext(data/*:string*/, o/*:ParseOpts*/)/*:Workbook*/ {
 function read_plaintext_raw(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 	var str = "", bytes = firstbyte(data, o);
 	switch(o.type) {
-		case 'base64': str = Base64.decode(data); break;
+		case 'base64': str = Base64_decode(data); break;
 		case 'binary': str = data; break;
 		case 'buffer': str = data.toString('binary'); break;
 		case 'array': str = cc2str(data); break;
@@ -23348,7 +23358,7 @@ function read_plaintext_raw(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 
 function read_utf16(data/*:RawData*/, o/*:ParseOpts*/)/*:Workbook*/ {
 	var d = data;
-	if(o.type == 'base64') d = Base64.decode(d);
+	if(o.type == 'base64') d = Base64_decode(d);
 	d = $cptable.utils.decode(1200, d.slice(2), 'str');
 	o.type = "binary";
 	return read_plaintext(d, o);
@@ -23474,7 +23484,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/
 	if(!bom) bom = "";
 	var o = bom + out;
 	switch(opts.type) {
-		case "base64": return Base64.encode(utf8write(o));
+		case "base64": return Base64_encode(utf8write(o));
 		case "binary": return utf8write(o);
 		case "string": return out;
 		case "file": return write_dl(opts.file, o, 'utf8');
@@ -23489,7 +23499,7 @@ function write_string_type(out/*:string*/, opts/*:WriteOpts*/, bom/*:?string*/)/
 
 function write_stxt_type(out/*:string*/, opts/*:WriteOpts*/)/*:any*/ {
 	switch(opts.type) {
-		case "base64": return Base64.encode(out);
+		case "base64": return Base64_encode(out);
 		case "binary": return out;
 		case "string": return out; /* override in sheet_to_txt */
 		case "file": return write_dl(opts.file, out, 'binary');
@@ -23510,7 +23520,7 @@ function write_binary_type(out, opts/*:WriteOpts*/)/*:any*/ {
 			var bstr = "";
 			// $FlowIgnore
 			for(var i = 0; i < out.length; ++i) bstr += String.fromCharCode(out[i]);
-			return opts.type == 'base64' ? Base64.encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
+			return opts.type == 'base64' ? Base64_encode(bstr) : opts.type == 'string' ? utf8read(bstr) : bstr;
 		case "file": return write_dl(opts.file, out);
 		case "buffer": return out;
 		default: throw new Error("Unrecognized type " + opts.type);