1.0.0: a new hope
This commit is contained in:
commit
40c6036085
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
github: SheetJSDev
|
||||
custom: https://sheetjs.com
|
||||
open_collective: s5s
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
fonts/
|
||||
node_modules/
|
||||
*.[wWeE][mM][fF]
|
||||
*.[pP][nN][gG]
|
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (C) 2020-present SheetJS LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
18
Makefile
Normal file
18
Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
.PHONY: all
|
||||
all: node browser
|
||||
|
||||
.PHONY: src/index.ts
|
||||
js/index.js: src/index.ts
|
||||
#tsc src/index.ts
|
||||
tsc
|
||||
|
||||
node: js/index.js
|
||||
webpack-cli --config misc/webpack.node.config.js
|
||||
|
||||
browser: js/index.js
|
||||
webpack-cli --config misc/webpack.browser.config.js
|
||||
|
||||
.PHONY: dist prod
|
||||
dist prod: js/index.js
|
||||
webpack-cli --mode=production --config misc/webpack.node.config.js
|
||||
webpack-cli --mode=production --config misc/webpack.browser.config.js
|
92
README.md
Normal file
92
README.md
Normal file
@ -0,0 +1,92 @@
|
||||
# js-wmf
|
||||
|
||||
Processor for Windows MetaFile (WMF) files in JS (for the browser and nodejs).
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
With [npm](https://www.npmjs.org/package/wmf):
|
||||
|
||||
```bash
|
||||
$ npm install wmf
|
||||
```
|
||||
|
||||
In the browser:
|
||||
|
||||
```html
|
||||
<script src="wmf.js"></script>
|
||||
```
|
||||
|
||||
The browser exposes a variable `WMF`.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The `data` argument is expected to be an `ArrayBuffer`, `Uint8Array` or `Buffer`
|
||||
|
||||
- `WMF.image_size(data)` extracts the image offset and extents, returns an Array
|
||||
`[width, height]` where both metrics are measured in pixels.
|
||||
|
||||
- `WMF.draw_canvas(data, canvas)` parses the WMF and draws to a `Canvas`.
|
||||
|
||||
### Notes
|
||||
|
||||
- The library assumes the global `ImageData` is available. For nodejs-powered
|
||||
canvas implementations, a shim must be exposed as a global. Using the `canvas`
|
||||
npm package:
|
||||
|
||||
```js
|
||||
const { createImageData } = require("canvas");
|
||||
global.ImageData = createImageData;
|
||||
```
|
||||
|
||||
- `OffscreenCanvas` in Chrome and some other Canvas implementations require
|
||||
the dimensions in the constructor:
|
||||
|
||||
```js
|
||||
const size = WMF.image_size(data);
|
||||
const canvas = new OffscreenCanvas(size[0], size[1]);
|
||||
```
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
<details>
|
||||
<summary><b>Browser Fetch into canvas</b> (click to show)</summary>
|
||||
|
||||
```js
|
||||
// assume `canvas` is a DOM element
|
||||
(async() => {
|
||||
const res = await fetch("url/for/image.wmf");
|
||||
const ab = await res.arrayBuffer();
|
||||
WMF.draw_canvas(ab, document.getElementById("canvas"));
|
||||
})();
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>NodeJS (using `canvas` npm module)</b> (click to show)</summary>
|
||||
|
||||
```js
|
||||
const { createCanvas, createImageData } = require("canvas");
|
||||
global.ImageData = createImageData;
|
||||
|
||||
const size = WMF.image_size(data);
|
||||
const canvas = createCanvas(size[0], size[1]);
|
||||
WMF.draw_canvas(data, canvas);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Please consult the attached LICENSE file for details. All rights not explicitly
|
||||
granted by the Apache 2.0 License are reserved by the Original Author.
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [MS-WMF]: Windows Metafile Format
|
||||
|
2
dist/wmf.js
vendored
Normal file
2
dist/wmf.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/wmf.js.map
vendored
Normal file
1
dist/wmf.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/wmf.node.js
vendored
Normal file
2
dist/wmf.node.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/wmf.node.js.map
vendored
Normal file
1
dist/wmf.node.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
94
index.html
Normal file
94
index.html
Normal file
@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- wmf.js (C) 2020-present SheetJS http://sheetjs.com -->
|
||||
<!-- vim: set ts=2: -->
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>wmf.js Live Demo</title>
|
||||
<style>
|
||||
#drop{
|
||||
border:2px dashed #bbb;
|
||||
-moz-border-radius:5px;
|
||||
-webkit-border-radius:5px;
|
||||
border-radius:5px;
|
||||
padding:25px;
|
||||
text-align:center;
|
||||
font:20pt bold,"Vollkorn";color:#bbb
|
||||
}
|
||||
#b64data{
|
||||
width:100%;
|
||||
}
|
||||
a { text-decoration: none }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre>
|
||||
<b><a href="http://sheetjs.com">SheetJS wmf.js Preview Live Demo</a></b>
|
||||
|
||||
<a href="https://github.com/SheetJS/js-wmf">Source Code Repo</a>
|
||||
<a href="https://github.com/SheetJS/js-wmf/issues">Issues? Something look weird? Click here and report an issue</a>
|
||||
<div id="drop">Drop a WMF File here to Preview data</div>
|
||||
<input type="file" name="xlfile" id="xlf" /> ... or click here to select a file
|
||||
|
||||
<pre id="out"></pre>
|
||||
<canvas id="htmlout"></canvas>
|
||||
<br />
|
||||
<script src="dist/wmf.js"></script>
|
||||
<script>
|
||||
/*jshint browser:true */
|
||||
/* eslint-env browser */
|
||||
/*global Uint8Array, console */
|
||||
/*global WMF */
|
||||
/* exported b64it, setfmt */
|
||||
/* eslint no-use-before-define:0 */
|
||||
var HTMLOUT = document.getElementById('htmlout');
|
||||
|
||||
var do_file = function do_file(files) {
|
||||
var f = files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
var data = e.target.result;
|
||||
WMF.draw_canvas(data, HTMLOUT);
|
||||
};
|
||||
reader.readAsArrayBuffer(f);
|
||||
};
|
||||
|
||||
(function() {
|
||||
var drop = document.getElementById('drop');
|
||||
if(!drop.addEventListener) return;
|
||||
|
||||
function handleDrop(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
do_file(e.dataTransfer.files);
|
||||
}
|
||||
|
||||
function handleDragover(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
drop.addEventListener('dragenter', handleDragover, false);
|
||||
drop.addEventListener('dragover', handleDragover, false);
|
||||
drop.addEventListener('drop', handleDrop, false);
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var xlf = document.getElementById('xlf');
|
||||
if(!xlf.addEventListener) return;
|
||||
function handleFile(e) { do_file(e.target.files); }
|
||||
xlf.addEventListener('change', handleFile, false);
|
||||
})();
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-36810333-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
37
js/Records.js
Normal file
37
js/Records.js
Normal file
@ -0,0 +1,37 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/* 2.1.1.1 RecordType Enumeration */
|
||||
exports.WMFRecords = {
|
||||
0x0000: { n: "META_EOF" },
|
||||
0x0626: { n: "META_ESCAPE" },
|
||||
0x0940: { n: "META_DIBBITBLT" },
|
||||
0x0B41: { n: "META_DIBSTRETCHBLT" },
|
||||
0x0A32: { n: "META_EXTTEXTOUT" },
|
||||
0x0325: { n: "META_POLYLINE" },
|
||||
0x0324: { n: "META_POLYGON" },
|
||||
0x0538: { n: "META_POLYPOLYGON" },
|
||||
0x02FC: { n: "META_CREATEBRUSHINDIRECT" },
|
||||
0x02FB: { n: "META_CREATEFONTINDIRECT" },
|
||||
0x02FA: { n: "META_CREATEPENINDIRECT" },
|
||||
0x01F0: { n: "META_DELETEOBJECT" },
|
||||
0x012C: { n: "META_SELECTCLIPREGION" },
|
||||
0x012D: { n: "META_SELECTOBJECT" },
|
||||
0x0416: { n: "META_INTERSECTCLIPRECT" },
|
||||
0x0035: { n: "META_REALIZEPALETTE" },
|
||||
0x0127: { n: "META_RESTOREDC" },
|
||||
0x001E: { n: "META_SAVEDC" },
|
||||
0x0102: { n: "META_SETBKMODE" },
|
||||
0x0103: { n: "META_SETMAPMODE" },
|
||||
0x0037: { n: "META_SETPALENTRIES" },
|
||||
0x0106: { n: "META_SETPOLYFILLMODE" },
|
||||
0x0107: { n: "META_SETSTRETCHBLTMODE" },
|
||||
0x012E: { n: "META_SETTEXTALIGN" },
|
||||
0x0209: { n: "META_SETTEXTCOLOR" },
|
||||
0x020C: { n: "META_SETWINDOWEXT" },
|
||||
0x020B: { n: "META_SETWINDOWORG" },
|
||||
0xFFFF: { n: "META_SHEETJS" }
|
||||
};
|
||||
exports.WMFEscapes = {
|
||||
0x000F: { n: "META_ESCAPE_ENHANCED_METAFILE" }
|
||||
};
|
||||
//# sourceMappingURL=Records.js.map
|
1
js/Records.js.map
Normal file
1
js/Records.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"Records.js","sourceRoot":"","sources":["../src/Records.ts"],"names":[],"mappings":";;AASA,oCAAoC;AACvB,QAAA,UAAU,GAA+B;IACrD,MAAM,EAAE,EAAE,CAAC,EAAE,UAAU,EAAE;IACzB,MAAM,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE;IAE5B,MAAM,EAAE,EAAE,CAAC,EAAE,gBAAgB,EAAE;IAC/B,MAAM,EAAE,EAAE,CAAC,EAAE,oBAAoB,EAAE;IAEnC,MAAM,EAAE,EAAE,CAAC,EAAE,iBAAiB,EAAE;IAChC,MAAM,EAAE,EAAE,CAAC,EAAE,eAAe,EAAE;IAC9B,MAAM,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE;IAC7B,MAAM,EAAE,EAAE,CAAC,EAAE,kBAAkB,EAAE;IAEjC,MAAM,EAAE,EAAE,CAAC,EAAE,0BAA0B,EAAE;IACzC,MAAM,EAAE,EAAE,CAAC,EAAE,yBAAyB,EAAE;IACxC,MAAM,EAAE,EAAE,CAAC,EAAE,wBAAwB,EAAE;IACvC,MAAM,EAAE,EAAE,CAAC,EAAE,mBAAmB,EAAE;IAClC,MAAM,EAAE,EAAE,CAAC,EAAE,uBAAuB,EAAE;IACtC,MAAM,EAAE,EAAE,CAAC,EAAE,mBAAmB,EAAE;IAElC,MAAM,EAAE,EAAE,CAAC,EAAE,wBAAwB,EAAE;IACvC,MAAM,EAAE,EAAE,CAAC,EAAE,qBAAqB,EAAE;IACpC,MAAM,EAAE,EAAE,CAAC,EAAE,gBAAgB,EAAE;IAC/B,MAAM,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE;IAC5B,MAAM,EAAE,EAAE,CAAC,EAAE,gBAAgB,EAAE;IAC/B,MAAM,EAAE,EAAE,CAAC,EAAE,iBAAiB,EAAE;IAChC,MAAM,EAAE,EAAE,CAAC,EAAE,oBAAoB,EAAE;IACnC,MAAM,EAAE,EAAE,CAAC,EAAE,sBAAsB,EAAE;IACrC,MAAM,EAAE,EAAE,CAAC,EAAE,wBAAwB,EAAE;IACvC,MAAM,EAAE,EAAE,CAAC,EAAE,mBAAmB,EAAE;IAClC,MAAM,EAAE,EAAE,CAAC,EAAE,mBAAmB,EAAE;IAClC,MAAM,EAAE,EAAE,CAAC,EAAE,mBAAmB,EAAE;IAClC,MAAM,EAAE,EAAE,CAAC,EAAE,mBAAmB,EAAE;IAElC,MAAM,EAAE,EAAE,CAAC,EAAE,cAAc,EAAE;CAC7B,CAAC;AAEW,QAAA,UAAU,GAA+B;IACrD,MAAM,EAAE,EAAE,CAAC,EAAE,+BAA+B,EAAE;CAC9C,CAAC"}
|
112
js/canvas.js
Normal file
112
js/canvas.js
Normal file
@ -0,0 +1,112 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
var util_1 = require("./util");
|
||||
var wmf_1 = require("./wmf");
|
||||
exports.css_color = function (clr) { return "#" + (clr & 0xFF).toString(16).padStart(2, "0") + ((clr >> 8) & 0xFF).toString(16).padStart(2, "0") + ((clr >> 16) & 0xFF).toString(16).padStart(2, "0"); };
|
||||
exports.set_ctx_state = function (ctx, state) {
|
||||
if (!state)
|
||||
return;
|
||||
var font = "";
|
||||
if (state.Font) {
|
||||
if (state.Font.Italic)
|
||||
font += " italic";
|
||||
if (state.Font.Weight)
|
||||
font += " " + (state.Font.Weight == 700 ? "bold" : state.Font.Weight == 400 ? "" : state.Font.Weight);
|
||||
if (state.Font.Height < 0)
|
||||
font += " " + -state.Font.Height + "px";
|
||||
else if (state.Font.Height > 0)
|
||||
font += " " + state.Font.Height + "px";
|
||||
var name_1 = state.Font.Name || "";
|
||||
if (name_1 == "System")
|
||||
name_1 = "Calibri"; // TODO: default sys font is Segoe UI
|
||||
if (name_1)
|
||||
font += " '" + name_1 + "', sans-serif";
|
||||
ctx.font = font.trim();
|
||||
}
|
||||
};
|
||||
// TODO: DIB BIT ORDER?
|
||||
exports.render_actions_to_context = function (out, ctx) {
|
||||
out.forEach(function (act) {
|
||||
ctx.save();
|
||||
exports.set_ctx_state(ctx, act.s);
|
||||
switch (act.t) {
|
||||
case "poly":
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(act.p[0][0], act.p[0][1]);
|
||||
act.p.slice(1).forEach(function (_a) {
|
||||
var x = _a[0], y = _a[1];
|
||||
ctx.lineTo(x, y);
|
||||
});
|
||||
if (act.g)
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
break;
|
||||
case "text":
|
||||
if (act.s && act.s.TextColor)
|
||||
ctx.fillStyle = exports.css_color(act.s.TextColor);
|
||||
if (act.s.Font.Angle != 0) {
|
||||
ctx.translate(act.p[0], act.p[1]);
|
||||
ctx.rotate(-act.s.Font.Angle * Math.PI / 180);
|
||||
ctx.fillText(act.v, 0, 0);
|
||||
ctx.translate(-act.p[0], -act.p[1]);
|
||||
}
|
||||
else
|
||||
ctx.fillText(act.v, act.p[0], act.p[1]);
|
||||
break;
|
||||
case "cpy":
|
||||
{
|
||||
// TODO: base on ROP
|
||||
var idata = ctx.getImageData(act.src[0][0], act.src[1][0], act.src[0][1], act.src[1][1]);
|
||||
ctx.putImageData(idata, act.dst[0], act.dst[1]);
|
||||
}
|
||||
break;
|
||||
case "str": {
|
||||
if (act.data && act.data.BitCount == 24 && act.data.ImageData) {
|
||||
var _o = new Uint8ClampedArray(act.data.Width * act.data.Height * 4);
|
||||
for (var i = 0; i < act.data.Width * act.data.Height; ++i) {
|
||||
var j = (i % act.data.Width) + act.data.Width * (act.data.Height - 1 - Math.floor(i / act.data.Width));
|
||||
_o[4 * i] = act.data.ImageData[3 * j + 2];
|
||||
_o[4 * i + 1] = act.data.ImageData[3 * j + 1];
|
||||
_o[4 * i + 2] = act.data.ImageData[3 * j];
|
||||
_o[4 * i + 3] = 255;
|
||||
}
|
||||
var idata = new ImageData(_o, act.data.Width, act.data.Height);
|
||||
ctx.putImageData(idata, act.dst[0][0], act.dst[1][0]);
|
||||
}
|
||||
// TODO: ROP et al
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
});
|
||||
};
|
||||
exports.render_canvas = function (out, image) {
|
||||
var ctx;
|
||||
/* find first action with window info */
|
||||
out.forEach(function (act) {
|
||||
if (ctx)
|
||||
return;
|
||||
if (!act.s)
|
||||
return;
|
||||
if (!act.s.Extent || !act.s.Origin)
|
||||
return;
|
||||
image.width = act.s.Extent[0] - act.s.Origin[0];
|
||||
image.height = act.s.Extent[1] - act.s.Origin[1];
|
||||
ctx = image.getContext('2d');
|
||||
ctx.save();
|
||||
ctx.fillStyle = 'rgb(255,255,255)';
|
||||
ctx.fillRect(0, 0, act.s.Extent[0] - act.s.Origin[0], act.s.Extent[1] - act.s.Origin[1]);
|
||||
ctx.restore();
|
||||
});
|
||||
if (!ctx)
|
||||
ctx = image.getContext('2d');
|
||||
exports.render_actions_to_context(out, ctx);
|
||||
};
|
||||
exports.draw_canvas = function (data, image) {
|
||||
if (data instanceof ArrayBuffer)
|
||||
return exports.draw_canvas(new Uint8Array(data), image);
|
||||
util_1.prep_blob(data, 0);
|
||||
var out = wmf_1.get_actions_prepped_bytes(data);
|
||||
return exports.render_canvas(out, image);
|
||||
};
|
||||
//# sourceMappingURL=canvas.js.map
|
1
js/canvas.js.map
Normal file
1
js/canvas.js.map
Normal file
File diff suppressed because one or more lines are too long
21
js/index.js
Normal file
21
js/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
var util_1 = require("./util");
|
||||
var wmf_1 = require("./wmf");
|
||||
var canvas_1 = require("./canvas");
|
||||
exports.draw_canvas = canvas_1.draw_canvas;
|
||||
exports.render_canvas = canvas_1.render_canvas;
|
||||
exports.get_actions = function (data) {
|
||||
if (data instanceof ArrayBuffer)
|
||||
return exports.get_actions(new Uint8Array(data));
|
||||
util_1.prep_blob(data, 0);
|
||||
return wmf_1.get_actions_prepped_bytes(data);
|
||||
};
|
||||
exports.image_size = function (data) {
|
||||
if (data instanceof ArrayBuffer)
|
||||
return exports.image_size(new Uint8Array(data));
|
||||
util_1.prep_blob(data, 0);
|
||||
return wmf_1.image_size_prepped_bytes(data);
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
1
js/index.js.map
Normal file
1
js/index.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,iEAAiE;AACjE,+BAAiD;AACjD,6BAAmF;AAEnF,mCAAsD;AAA7C,+BAAA,WAAW,CAAA;AAAE,iCAAA,aAAa,CAAA;AAEtB,QAAA,WAAW,GAAG,UAAC,IAAuC;IAClE,IAAG,IAAI,YAAY,WAAW;QAAE,OAAO,mBAAW,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACzE,gBAAS,CAAE,IAAY,EAAE,CAAC,CAAC,CAAC;IAC5B,OAAO,+BAAyB,CAAC,IAAoB,CAAC,CAAC;AACxD,CAAC,CAAA;AAEY,QAAA,UAAU,GAAG,UAAC,IAAuC;IACjE,IAAG,IAAI,YAAY,WAAW;QAAE,OAAO,kBAAU,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE,gBAAS,CAAE,IAAY,EAAE,CAAC,CAAC,CAAC;IAC5B,OAAO,8BAAwB,CAAC,IAAoB,CAAC,CAAC;AACvD,CAAC,CAAA"}
|
395
js/util.js
Normal file
395
js/util.js
Normal file
@ -0,0 +1,395 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// ---
|
||||
var has_buf = !!(typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && process.versions.node);
|
||||
exports.has_buf = has_buf;
|
||||
var Buffer_from;
|
||||
exports.Buffer_from = Buffer_from;
|
||||
if (typeof Buffer !== 'undefined') {
|
||||
var nbfs = !Buffer.from;
|
||||
if (!nbfs)
|
||||
try {
|
||||
Buffer.from("foo", "utf8");
|
||||
}
|
||||
catch (e) {
|
||||
nbfs = true;
|
||||
}
|
||||
exports.Buffer_from = Buffer_from = nbfs ? (function (buf, enc) { return (enc) ? new Buffer(buf, enc) : new Buffer(buf); }) : Buffer.from.bind(Buffer);
|
||||
if (!Buffer.alloc)
|
||||
Buffer.alloc = function (n) { return new Buffer(n); };
|
||||
if (!Buffer.allocUnsafe)
|
||||
Buffer.allocUnsafe = function (n) { return new Buffer(n); };
|
||||
}
|
||||
exports.new_raw_buf = function (len) { return has_buf ? Buffer.alloc(len) : new Array(len); };
|
||||
exports.new_unsafe_buf = function (len) { return has_buf ? Buffer.allocUnsafe(len) : new Array(len); };
|
||||
exports._chr = function (c) { return String.fromCharCode(c); };
|
||||
exports.chr0 = /\u0000/g; // eslint-disable-line no-control-regex
|
||||
exports.chr1 = /[\u0001-\u0006]/g; // eslint-disable-line no-control-regex
|
||||
// ---
|
||||
var read_double_le = function (b, idx) {
|
||||
var s = 1 - 2 * (b[idx + 7] >>> 7);
|
||||
var e = ((b[idx + 7] & 0x7f) << 4) + ((b[idx + 6] >>> 4) & 0x0f);
|
||||
var m = (b[idx + 6] & 0x0f);
|
||||
for (var i = 5; i >= 0; --i)
|
||||
m = m * 256 + b[idx + i];
|
||||
if (e == 0x7ff)
|
||||
return m == 0 ? (s * Infinity) : NaN;
|
||||
if (e == 0)
|
||||
e = -1022;
|
||||
else {
|
||||
e -= 1023;
|
||||
m += Math.pow(2, 52);
|
||||
}
|
||||
return s * Math.pow(2, e - 52) * m;
|
||||
};
|
||||
var write_double_le = function (b, v, idx) {
|
||||
var bs = ((((v < 0) || (1 / v == -Infinity)) ? 1 : 0) << 7);
|
||||
var e = 0, m = 0;
|
||||
var av = bs ? (-v) : v;
|
||||
if (!isFinite(av)) {
|
||||
e = 0x7ff;
|
||||
m = isNaN(v) ? 0x6969 : 0;
|
||||
}
|
||||
else if (av == 0)
|
||||
e = m = 0;
|
||||
else {
|
||||
e = Math.floor(Math.log(av) / Math.LN2);
|
||||
m = av * Math.pow(2, 52 - e);
|
||||
if ((e <= -1023) && (!isFinite(m) || (m < Math.pow(2, 52)))) {
|
||||
e = -1022;
|
||||
}
|
||||
else {
|
||||
m -= Math.pow(2, 52);
|
||||
e += 1023;
|
||||
}
|
||||
}
|
||||
for (var i = 0; i <= 5; ++i, m /= 256)
|
||||
b[idx + i] = m & 0xff;
|
||||
b[idx + 6] = ((e & 0x0f) << 4) | (m & 0xf);
|
||||
b[idx + 7] = (e >> 4) | bs;
|
||||
};
|
||||
var __toBuffer = function (bufs /*:Array<Array<RawBytes> >*/) {
|
||||
var x = [];
|
||||
for (var i = 0; i < bufs[0].length; ++i)
|
||||
if (bufs[0][i])
|
||||
for (var j = 0, L = bufs[0][i].length; j < L; j += 10240)
|
||||
x.push.apply(x, bufs[0][i].slice(j, j + 10240));
|
||||
return x;
|
||||
};
|
||||
var ___toBuffer = __toBuffer;
|
||||
var __readUInt8 = function (b, idx) { return b[idx]; };
|
||||
var __readUInt16LE = function (b, idx) { return (b[idx + 1] * (1 << 8)) + b[idx]; };
|
||||
var __readInt16LE = function (b, idx) { var u = (b[idx + 1] * (1 << 8)) + b[idx]; return (u < 0x8000) ? u : ((0xffff - u + 1) * -1); };
|
||||
var __readUInt32LE = function (b, idx) { return b[idx + 3] * (1 << 24) + (b[idx + 2] << 16) + (b[idx + 1] << 8) + b[idx]; };
|
||||
var __readInt32LE = function (b, idx) { return (b[idx + 3] << 24) | (b[idx + 2] << 16) | (b[idx + 1] << 8) | b[idx]; };
|
||||
var __readInt32BE = function (b, idx) { return (b[idx] << 24) | (b[idx + 1] << 16) | (b[idx + 2] << 8) | b[idx + 3]; };
|
||||
var __utf16le = function (b, s, e) {
|
||||
var ss = [];
|
||||
for (var i = s; i < e; i += 2)
|
||||
ss.push(String.fromCharCode(__readUInt16LE(b, i)));
|
||||
return ss.join("").replace(exports.chr0, '');
|
||||
};
|
||||
exports.__utf16le = __utf16le;
|
||||
var ___utf16le = __utf16le;
|
||||
var __hexlify = function (b /*:RawBytes|CFBlob*/, s, l) { var ss = []; for (var i = s; i < s + l; ++i)
|
||||
ss.push(("0" + b[i].toString(16)).slice(-2)); return ss.join(""); };
|
||||
var ___hexlify = __hexlify;
|
||||
var __utf8 = function (b /*:RawBytes|CFBlob*/, s, e) { var ss = []; for (var i = s; i < e; i++)
|
||||
ss.push(String.fromCharCode(__readUInt8(b, i))); return ss.join(""); };
|
||||
var ___utf8 = __utf8;
|
||||
var __lpstr = function (b /*:RawBytes|CFBlob*/, i) { var len = __readUInt32LE(b, i); return len > 0 ? __utf8(b, i + 4, i + 4 + len - 1) : ""; };
|
||||
var ___lpstr = __lpstr;
|
||||
var __cpstr = function (b /*:RawBytes|CFBlob*/, i) { var len = __readUInt32LE(b, i); return len > 0 ? __utf8(b, i + 4, i + 4 + len - 1) : ""; };
|
||||
var ___cpstr = __cpstr;
|
||||
var __lpwstr = function (b /*:RawBytes|CFBlob*/, i) { var len = 2 * __readUInt32LE(b, i); return len > 0 ? __utf8(b, i + 4, i + 4 + len - 1) : ""; };
|
||||
var ___lpwstr = __lpwstr;
|
||||
var __lpp4, ___lpp4;
|
||||
__lpp4 = ___lpp4 = function lpp4_(b /*:RawBytes|CFBlob*/, i) { var len = __readUInt32LE(b, i); return len > 0 ? __utf16le(b, i + 4, i + 4 + len) : ""; };
|
||||
var ___8lpp4 = function (b /*:RawBytes|CFBlob*/, i) { var len = __readUInt32LE(b, i); return len > 0 ? __utf8(b, i + 4, i + 4 + len) : ""; };
|
||||
var __8lpp4 = ___8lpp4;
|
||||
var ___double = function (b /*:RawBytes|CFBlob*/, idx) { return read_double_le(b, idx); };
|
||||
var __double = ___double;
|
||||
if (has_buf) {
|
||||
exports.__utf16le = __utf16le = function (b /*:RawBytes|CFBlob*/, s, e) { return (!Buffer.isBuffer(b)) ? ___utf16le(b, s, e) : b.toString('utf16le', s, e).replace(exports.chr0, ''); };
|
||||
__hexlify = function (b /*:RawBytes|CFBlob*/, s, l) { return Buffer.isBuffer(b) ? b.toString('hex', s, s + l) : ___hexlify(b, s, l); };
|
||||
__lpstr = function (b /*:RawBytes|CFBlob*/, i) { if (!Buffer.isBuffer(b))
|
||||
return ___lpstr(b, i); var len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8', i + 4, i + 4 + len - 1) : ""; };
|
||||
__cpstr = function (b /*:RawBytes|CFBlob*/, i) { if (!Buffer.isBuffer(b))
|
||||
return ___cpstr(b, i); var len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8', i + 4, i + 4 + len - 1) : ""; };
|
||||
__lpwstr = function (b /*:RawBytes|CFBlob*/, i) { if (!Buffer.isBuffer(b))
|
||||
return ___lpwstr(b, i); var len = 2 * b.readUInt32LE(i); return b.toString('utf16le', i + 4, i + 4 + len - 1); };
|
||||
__lpp4 = function (b /*:RawBytes|CFBlob*/, i) { if (!Buffer.isBuffer(b))
|
||||
return ___lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf16le', i + 4, i + 4 + len); };
|
||||
__8lpp4 = function (b /*:RawBytes|CFBlob*/, i) { if (!Buffer.isBuffer(b))
|
||||
return ___8lpp4(b, i); var len = b.readUInt32LE(i); return b.toString('utf8', i + 4, i + 4 + len); };
|
||||
__utf8 = function (b /*:RawBytes|CFBlob*/, s, e) { return (Buffer.isBuffer(b)) ? b.toString('utf8', s, e) : ___utf8(b, s, e); };
|
||||
__toBuffer = function (bufs) { return (bufs[0].length > 0 && Buffer.isBuffer(bufs[0][0])) ? Buffer.concat(bufs[0]) : ___toBuffer(bufs); };
|
||||
__double = function (b /*:RawBytes|CFBlob*/, i) { return (Buffer.isBuffer(b)) ? b.readDoubleLE(i) : ___double(b, i); };
|
||||
}
|
||||
function ReadShift(size, t) {
|
||||
var o = "", oI = 0, oR, w, vv, i, loc;
|
||||
var oo = [];
|
||||
switch (t) {
|
||||
case 'dbcs':
|
||||
loc = this.l;
|
||||
if (has_buf && Buffer.isBuffer(this))
|
||||
o = this.slice(this.l, this.l + 2 * size).toString("utf16le");
|
||||
else
|
||||
for (i = 0; i < size; ++i) {
|
||||
o += String.fromCharCode(__readUInt16LE(this, loc));
|
||||
loc += 2;
|
||||
}
|
||||
size *= 2;
|
||||
break;
|
||||
case 'utf8':
|
||||
o = __utf8(this, this.l, this.l + size);
|
||||
break;
|
||||
case 'utf16le':
|
||||
size *= 2;
|
||||
o = __utf16le(this, this.l, this.l + size);
|
||||
break;
|
||||
case 'wstr':
|
||||
return ReadShift.call(this, size, 'dbcs');
|
||||
/* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */
|
||||
case 'lpstr-ansi':
|
||||
o = __lpstr(this, this.l);
|
||||
size = 4 + __readUInt32LE(this, this.l);
|
||||
break;
|
||||
case 'lpstr-cp':
|
||||
o = __cpstr(this, this.l);
|
||||
size = 4 + __readUInt32LE(this, this.l);
|
||||
break;
|
||||
/* [MS-OLEDS] 2.1.5 LengthPrefixedUnicodeString */
|
||||
case 'lpwstr':
|
||||
o = __lpwstr(this, this.l);
|
||||
size = 4 + 2 * __readUInt32LE(this, this.l);
|
||||
break;
|
||||
/* [MS-OFFCRYPTO] 2.1.2 Length-Prefixed Padded Unicode String (UNICODE-LP-P4) */
|
||||
case 'lpp4':
|
||||
size = 4 + __readUInt32LE(this, this.l);
|
||||
o = __lpp4(this, this.l);
|
||||
if (size & 0x02)
|
||||
size += 2;
|
||||
break;
|
||||
/* [MS-OFFCRYPTO] 2.1.3 Length-Prefixed UTF-8 String (UTF-8-LP-P4) */
|
||||
case '8lpp4':
|
||||
size = 4 + __readUInt32LE(this, this.l);
|
||||
o = __8lpp4(this, this.l);
|
||||
if (size & 0x03)
|
||||
size += 4 - (size & 0x03);
|
||||
break;
|
||||
case 'cstr':
|
||||
size = 0;
|
||||
o = "";
|
||||
while ((w = __readUInt8(this, this.l + size++)) !== 0)
|
||||
oo.push(String.fromCharCode(w));
|
||||
o = oo.join("");
|
||||
break;
|
||||
case '_wstr':
|
||||
size = 0;
|
||||
o = "";
|
||||
while ((w = __readUInt16LE(this, this.l + size)) !== 0) {
|
||||
oo.push(String.fromCharCode(w));
|
||||
size += 2;
|
||||
}
|
||||
size += 2;
|
||||
o = oo.join("");
|
||||
break;
|
||||
/* sbcs and dbcs support continue records in the SST way TODO codepages */
|
||||
case 'dbcs-cont':
|
||||
o = "";
|
||||
loc = this.l;
|
||||
for (i = 0; i < size; ++i) {
|
||||
if (this.lens && this.lens.indexOf(loc) !== -1) {
|
||||
w = __readUInt8(this, loc);
|
||||
this.l = loc + 1;
|
||||
vv = ReadShift.call(this, size - i, w ? 'dbcs-cont' : 'sbcs-cont');
|
||||
return oo.join("") + vv;
|
||||
}
|
||||
oo.push(String.fromCharCode(__readUInt16LE(this, loc)));
|
||||
loc += 2;
|
||||
}
|
||||
o = oo.join("");
|
||||
size *= 2;
|
||||
break;
|
||||
case 'cpstr':
|
||||
/* falls through */
|
||||
case 'sbcs-cont':
|
||||
o = "";
|
||||
loc = this.l;
|
||||
for (i = 0; i != size; ++i) {
|
||||
if (this.lens && this.lens.indexOf(loc) !== -1) {
|
||||
w = __readUInt8(this, loc);
|
||||
this.l = loc + 1;
|
||||
vv = ReadShift.call(this, size - i, w ? 'dbcs-cont' : 'sbcs-cont');
|
||||
return oo.join("") + vv;
|
||||
}
|
||||
oo.push(String.fromCharCode(__readUInt8(this, loc)));
|
||||
loc += 1;
|
||||
}
|
||||
o = oo.join("");
|
||||
break;
|
||||
default:
|
||||
switch (size) {
|
||||
case 1:
|
||||
oI = __readUInt8(this, this.l);
|
||||
this.l++;
|
||||
return oI;
|
||||
case 2:
|
||||
oI = (t === 'i' ? __readInt16LE : __readUInt16LE)(this, this.l);
|
||||
this.l += 2;
|
||||
return oI;
|
||||
case 4:
|
||||
case -4:
|
||||
if (t === 'i' || ((this[this.l + 3] & 0x80) === 0)) {
|
||||
oI = ((size > 0) ? __readInt32LE : __readInt32BE)(this, this.l);
|
||||
this.l += 4;
|
||||
return oI;
|
||||
}
|
||||
else {
|
||||
oR = __readUInt32LE(this, this.l);
|
||||
this.l += 4;
|
||||
}
|
||||
return oR;
|
||||
case 8:
|
||||
case -8:
|
||||
if (t === 'f') {
|
||||
if (size == 8)
|
||||
oR = __double(this, this.l);
|
||||
else
|
||||
oR = __double([this[this.l + 7], this[this.l + 6], this[this.l + 5], this[this.l + 4], this[this.l + 3], this[this.l + 2], this[this.l + 1], this[this.l + 0]], 0);
|
||||
this.l += 8;
|
||||
return oR;
|
||||
}
|
||||
else
|
||||
size = 8;
|
||||
/* falls through */
|
||||
case 16:
|
||||
o = __hexlify(this, this.l, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.l += size;
|
||||
return o;
|
||||
}
|
||||
exports.ReadShift = ReadShift;
|
||||
var __writeUInt32LE = function (b /*:RawBytes|CFBlob*/, val, idx) { b[idx] = (val & 0xFF); b[idx + 1] = ((val >>> 8) & 0xFF); b[idx + 2] = ((val >>> 16) & 0xFF); b[idx + 3] = ((val >>> 24) & 0xFF); };
|
||||
var __writeInt32LE = function (b /*:RawBytes|CFBlob*/, val, idx) { b[idx] = (val & 0xFF); b[idx + 1] = ((val >> 8) & 0xFF); b[idx + 2] = ((val >> 16) & 0xFF); b[idx + 3] = ((val >> 24) & 0xFF); };
|
||||
var __writeUInt16LE = function (b /*:RawBytes|CFBlob*/, val, idx) { b[idx] = (val & 0xFF); b[idx + 1] = ((val >>> 8) & 0xFF); };
|
||||
function WriteShift(t, val, f) {
|
||||
var size = 0, i = 0;
|
||||
if (f === 'dbcs') {
|
||||
if (typeof val !== 'string')
|
||||
throw new Error("expected string");
|
||||
for (i = 0; i != val.length; ++i)
|
||||
__writeUInt16LE(this, val.charCodeAt(i), this.l + 2 * i);
|
||||
size = 2 * val.length;
|
||||
}
|
||||
else if (f === 'sbcs') {
|
||||
{
|
||||
val = val.replace(/[^\x00-\x7F]/g, "_"); // eslint-disable-line no-control-regex
|
||||
for (i = 0; i != val.length; ++i)
|
||||
this[this.l + i] = (val.charCodeAt(i) & 0xFF);
|
||||
}
|
||||
size = val.length;
|
||||
}
|
||||
else if (f === 'hex') {
|
||||
for (; i < t; ++i) {
|
||||
this[this.l++] = (parseInt(val.slice(2 * i, 2 * i + 2), 16) || 0);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
else if (f === 'utf16le') {
|
||||
/*:: if(typeof val !== "string") throw new Error("unreachable"); */
|
||||
var end = Math.min(this.l + t, this.length);
|
||||
for (i = 0; i < Math.min(val.length, t); ++i) {
|
||||
var cc = val.charCodeAt(i);
|
||||
this[this.l++] = (cc & 0xff);
|
||||
this[this.l++] = (cc >> 8);
|
||||
}
|
||||
while (this.l < end)
|
||||
this[this.l++] = 0;
|
||||
return this;
|
||||
}
|
||||
else if (typeof val === 'number')
|
||||
switch (t) {
|
||||
case 1:
|
||||
size = 1;
|
||||
this[this.l] = val & 0xFF;
|
||||
break;
|
||||
case 2:
|
||||
size = 2;
|
||||
this[this.l] = val & 0xFF;
|
||||
val >>>= 8;
|
||||
this[this.l + 1] = val & 0xFF;
|
||||
break;
|
||||
case 3:
|
||||
size = 3;
|
||||
this[this.l] = val & 0xFF;
|
||||
val >>>= 8;
|
||||
this[this.l + 1] = val & 0xFF;
|
||||
val >>>= 8;
|
||||
this[this.l + 2] = val & 0xFF;
|
||||
break;
|
||||
case 4:
|
||||
size = 4;
|
||||
__writeUInt32LE(this, val, this.l);
|
||||
break;
|
||||
case 8:
|
||||
size = 8;
|
||||
if (f === 'f') {
|
||||
write_double_le(this, val, this.l);
|
||||
break;
|
||||
}
|
||||
/* falls through */
|
||||
case 16: break;
|
||||
case -4:
|
||||
size = 4;
|
||||
__writeInt32LE(this, val, this.l);
|
||||
break;
|
||||
}
|
||||
this.l += size;
|
||||
return this;
|
||||
}
|
||||
exports.WriteShift = WriteShift;
|
||||
function CheckField(hexstr, fld) {
|
||||
var m = __hexlify(this, this.l, hexstr.length >> 1);
|
||||
if (m !== hexstr)
|
||||
throw new Error(fld + 'Expected ' + hexstr + ' saw ' + m);
|
||||
this.l += hexstr.length >> 1;
|
||||
}
|
||||
exports.CheckField = CheckField;
|
||||
var prep_blob = function (blob, pos) {
|
||||
blob.l = pos;
|
||||
blob.read_shift = ReadShift;
|
||||
blob.chk = CheckField;
|
||||
blob.write_shift = WriteShift;
|
||||
};
|
||||
exports.prep_blob = prep_blob;
|
||||
var new_buf = function (sz) {
|
||||
var o = exports.new_raw_buf(sz);
|
||||
prep_blob(o, 0);
|
||||
return o;
|
||||
};
|
||||
exports.new_buf = new_buf;
|
||||
// ---
|
||||
var __bconcat = function (bufs /*:Array<RawBytes>*/) {
|
||||
var is_all_arrays = true;
|
||||
for (var w = 0; w < bufs.length; ++w)
|
||||
if (!Array.isArray(bufs[w]))
|
||||
is_all_arrays = false;
|
||||
if (is_all_arrays)
|
||||
return [].concat.apply([], bufs);
|
||||
var maxlen = 0, i = 0;
|
||||
for (i = 0; i < bufs.length; ++i)
|
||||
maxlen += bufs[i].length;
|
||||
var o = new Uint8Array(maxlen);
|
||||
for (i = 0, maxlen = 0; i < bufs.length; maxlen += bufs[i].length, ++i)
|
||||
o.set(bufs[i], maxlen);
|
||||
return o;
|
||||
};
|
||||
var bconcat = __bconcat;
|
||||
exports.bconcat = bconcat;
|
||||
if (has_buf)
|
||||
exports.bconcat = bconcat = function (bufs) { return Buffer.isBuffer(bufs[0]) ? Buffer.concat(bufs) : [].concat.apply([], bufs); };
|
||||
//# sourceMappingURL=util.js.map
|
1
js/util.js.map
Normal file
1
js/util.js.map
Normal file
File diff suppressed because one or more lines are too long
428
js/wmf.js
Normal file
428
js/wmf.js
Normal file
@ -0,0 +1,428 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
var util_1 = require("./util");
|
||||
var Records_1 = require("./Records");
|
||||
var parse_emf = function (data) {
|
||||
//try { require("fs").writeFileSync("out.emf", data); } catch(e) {}
|
||||
};
|
||||
/* 2.2.2.9 */
|
||||
var parse_dib = function (data) {
|
||||
if (data.length == 0)
|
||||
return null;
|
||||
util_1.prep_blob(data, 0);
|
||||
/* DIBHeaderInfo */
|
||||
var HeaderSize = data.read_shift(4);
|
||||
var Width = 0, Height = 0, Planes = 0, BitCount = 0;
|
||||
var Compression = 0, ImageSize = 0, XPelsPerMeter = 0, YPelsPerMeter = 0, ColorUsed = 0, ColorImportant = 0;
|
||||
if (HeaderSize == 0x0C) {
|
||||
Width = data.read_shift(2);
|
||||
Height = data.read_shift(2);
|
||||
}
|
||||
else {
|
||||
Width = data.read_shift(4, 'i');
|
||||
Height = data.read_shift(4, 'i');
|
||||
}
|
||||
Planes = data.read_shift(2);
|
||||
BitCount = data.read_shift(2);
|
||||
var out = {
|
||||
Width: Width,
|
||||
Height: Height,
|
||||
BitCount: BitCount,
|
||||
};
|
||||
if (HeaderSize != 0x0C) {
|
||||
Compression = data.read_shift(4);
|
||||
ImageSize = data.read_shift(4);
|
||||
XPelsPerMeter = data.read_shift(4, 'i');
|
||||
YPelsPerMeter = data.read_shift(4, 'i');
|
||||
ColorUsed = data.read_shift(4);
|
||||
ColorImportant = data.read_shift(4);
|
||||
out["Compression"] = Compression;
|
||||
if (BitCount == 24 && ImageSize > Height * 3 * Width)
|
||||
Width = out["Width"] = ImageSize / (Height * 3);
|
||||
}
|
||||
/* Colors */
|
||||
/* BitmapBuffer */
|
||||
if (ImageSize == data.length - data.l) {
|
||||
out["ImageData"] = data.slice(data.l, data.length);
|
||||
util_1.prep_blob(out["ImageData"], 0);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
var add_to_objects = function (objects, obj) {
|
||||
var found = false;
|
||||
for (var i = 0; i < objects.length; ++i)
|
||||
if (!objects[i]) {
|
||||
objects[i] = obj;
|
||||
found = true;
|
||||
}
|
||||
if (!found)
|
||||
objects.push(obj);
|
||||
};
|
||||
exports.get_actions_prepped_bytes = function (data) {
|
||||
var out = [];
|
||||
/* 2.3.22 META_HEADER */
|
||||
// Type (2 bytes) must be 1 or 2
|
||||
var h = data.read_shift(2);
|
||||
if (h != 1 && h != 2)
|
||||
throw "Header: Type " + h + " must be 1 or 2";
|
||||
// HeaderSize expected to be 9
|
||||
if ((h = data.read_shift(2)) != 9)
|
||||
throw "Header: HeaderSize " + h + " must be 9";
|
||||
// Version (2 bytes) 1 or 3
|
||||
h = data.read_shift(2);
|
||||
if (h != 0x0100 && h != 0x0300)
|
||||
throw "Header: Version " + h + " must be 0x0100 or 0x0300";
|
||||
// SizeLow
|
||||
// SizeHigh
|
||||
// #Objects
|
||||
// MaxRecord
|
||||
// NumberOfMembers
|
||||
data.l = 18;
|
||||
var rt = 0;
|
||||
/* used for EMF */
|
||||
var escapecnt = 0;
|
||||
var CommentRecordCount = 0;
|
||||
var RemainingBytes = 0;
|
||||
var EnhancedMetafileDataSize = 0;
|
||||
var bufs = [];
|
||||
var objects = [];
|
||||
var states = [];
|
||||
var state = {};
|
||||
var sidx = -1;
|
||||
while (data.l < data.length) {
|
||||
h = data.read_shift(4);
|
||||
var end = data.l + h * 2 - 4;
|
||||
rt = data.read_shift(2);
|
||||
var Record = Records_1.WMFRecords[rt];
|
||||
if (rt == 0x0000)
|
||||
break; // META_EOF
|
||||
switch (rt) {
|
||||
case 0x0626:
|
||||
{ // META_ESCAPE
|
||||
var EscapeFunction = data.read_shift(2);
|
||||
var Escape = Records_1.WMFEscapes[EscapeFunction];
|
||||
//console.log("::", Escape);
|
||||
/* 2.3.6 */
|
||||
switch (EscapeFunction) {
|
||||
case 0x000F:
|
||||
{ // META_ESCAPE_ENHANCED_METAFILE
|
||||
var ByteCount = data.read_shift(2);
|
||||
var tmp = data.read_shift(4);
|
||||
if (tmp != 0x43464D57)
|
||||
throw "Escape: Comment ID 0x" + tmp.toString(16) + " != 0x43464D57";
|
||||
tmp = data.read_shift(4);
|
||||
if (tmp != 0x00000001)
|
||||
throw "Escape: Comment Type 0x" + tmp.toString(16) + " != 0x00000001";
|
||||
tmp = data.read_shift(4);
|
||||
if (tmp != 0x00010000)
|
||||
throw "Escape: Version 0x" + tmp.toString(16) + " != 0x00010000";
|
||||
var Checksum = data.read_shift(2);
|
||||
data.l += 4; // Flags
|
||||
if (escapecnt == 0) {
|
||||
CommentRecordCount = data.read_shift(4); // total number of records
|
||||
}
|
||||
else {
|
||||
var _CommentRecordCount = data.read_shift(4);
|
||||
if (_CommentRecordCount != CommentRecordCount)
|
||||
throw "Escape: CommentRecordCount " + _CommentRecordCount + " != " + CommentRecordCount;
|
||||
}
|
||||
var CurrentRecordSize = data.read_shift(4); // size of this record
|
||||
var _RemainingBytes = data.read_shift(4);
|
||||
if (escapecnt > 0 && CurrentRecordSize + _RemainingBytes != RemainingBytes)
|
||||
throw "Escape: " + RemainingBytes + " != " + CurrentRecordSize + " + " + _RemainingBytes;
|
||||
RemainingBytes = _RemainingBytes;
|
||||
var _EnhancedMetafileDataSize = data.read_shift(4);
|
||||
if (escapecnt == 0) {
|
||||
if (_EnhancedMetafileDataSize != CurrentRecordSize + _RemainingBytes)
|
||||
throw "Escape: " + _EnhancedMetafileDataSize + " != " + CurrentRecordSize + " + " + _RemainingBytes;
|
||||
EnhancedMetafileDataSize = _EnhancedMetafileDataSize;
|
||||
}
|
||||
else if (EnhancedMetafileDataSize != _EnhancedMetafileDataSize)
|
||||
throw "Escape: " + EnhancedMetafileDataSize + " != " + _EnhancedMetafileDataSize;
|
||||
if (ByteCount != (end - data.l) + 34)
|
||||
throw "Escape: Sizes " + ByteCount + " != " + (end - data.l) + " + 34";
|
||||
if (end - data.l != CurrentRecordSize)
|
||||
throw "Escape: CRSize " + CurrentRecordSize + " != " + (end - data.l);
|
||||
bufs.push(data.slice(data.l, end));
|
||||
++escapecnt;
|
||||
if (escapecnt == CommentRecordCount) {
|
||||
var prepped = util_1.bconcat(bufs);
|
||||
util_1.prep_blob(prepped, 0);
|
||||
parse_emf(prepped);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: throw "Escape: Unrecognized META_ESCAPE Type 0x" + EscapeFunction.toString(16);
|
||||
}
|
||||
}
|
||||
break;
|
||||
// #region 2.3.1 Bitmap Record Types
|
||||
case 0x0940:
|
||||
{ // 2.3.1.2 META_DIBBITBLT
|
||||
var has_bitmap = h != (rt >> 8) + 3;
|
||||
var RasterOperation = data.read_shift(4);
|
||||
var YSrc = data.read_shift(2, "i");
|
||||
var XSrc = data.read_shift(2, "i");
|
||||
if (!has_bitmap)
|
||||
data.l += 2;
|
||||
var Height = data.read_shift(2, "i");
|
||||
var Width = data.read_shift(2, "i");
|
||||
var YDest = data.read_shift(2, "i");
|
||||
var XDest = data.read_shift(2, "i");
|
||||
var res = {
|
||||
t: "cpy",
|
||||
src: [[XSrc, Width], [YSrc, Height]],
|
||||
dst: [XDest, YDest],
|
||||
rop: RasterOperation,
|
||||
s: Object.assign({}, state)
|
||||
};
|
||||
if (has_bitmap) {
|
||||
var DIB = parse_dib(data.slice(data.l, end));
|
||||
res.data = DIB;
|
||||
}
|
||||
out.push(res);
|
||||
}
|
||||
break;
|
||||
case 0x0B41:
|
||||
{ // 2.3.1.3 META_DIBSTRETCHBLT
|
||||
var has_bitmap = h != (rt >> 8) + 3;
|
||||
var RasterOperation = data.read_shift(4);
|
||||
var SrcHeight = data.read_shift(2, "i");
|
||||
var SrcWidth = data.read_shift(2, "i");
|
||||
var YSrc = data.read_shift(2, "i");
|
||||
var XSrc = data.read_shift(2, "i");
|
||||
if (!has_bitmap)
|
||||
data.l += 2;
|
||||
var DestHeight = data.read_shift(2, "i");
|
||||
var DestWidth = data.read_shift(2, "i");
|
||||
var YDest = data.read_shift(2, "i");
|
||||
var XDest = data.read_shift(2, "i");
|
||||
var res = {
|
||||
t: "str",
|
||||
src: [[XSrc, SrcWidth], [YSrc, SrcHeight]],
|
||||
dst: [[XDest, DestWidth], [YDest, DestHeight]],
|
||||
rop: RasterOperation,
|
||||
s: Object.assign({}, state)
|
||||
};
|
||||
if (has_bitmap) {
|
||||
var DIB = parse_dib(data.slice(data.l, end));
|
||||
res.data = DIB;
|
||||
}
|
||||
out.push(res);
|
||||
}
|
||||
break;
|
||||
// #endregion
|
||||
// #region 2.3.3 Drawing Record Types
|
||||
case 0x0A32:
|
||||
{ // 2.3.3.5 META_EXTTEXTOUT
|
||||
var Y = data.read_shift(2);
|
||||
var X = data.read_shift(2);
|
||||
var StringLength = data.read_shift(2);
|
||||
var fwOpts = data.read_shift(2); // 2.1.2.2
|
||||
if (fwOpts & 0x06) {
|
||||
data.l += 8; // Rectangle 2.2.2.18 (for clipping/opaquing)
|
||||
}
|
||||
var str = data.read_shift(StringLength, 'cpstr');
|
||||
if (data.l < end) { /* TODO: Dx */ }
|
||||
out.push({ t: "text", v: str, p: [X, Y], s: Object.assign({}, state) });
|
||||
/* TODO!! */
|
||||
}
|
||||
break;
|
||||
case 0x0325: // 2.3.3.14 META_POLYLINE
|
||||
case 0x0324: // 2.3.3.15 META_POLYGON
|
||||
{
|
||||
var nPoints = data.read_shift(2);
|
||||
var points = [];
|
||||
for (var i = 0; i < nPoints; ++i)
|
||||
points.push([data.read_shift(2), data.read_shift(2)]);
|
||||
out.push({ t: "poly", p: points, g: rt !== 0x0325, s: state });
|
||||
}
|
||||
break;
|
||||
case 0x0538:
|
||||
{ // 2.3.3.16 META_POLYPOLYGON
|
||||
var nPolygons = data.read_shift(2);
|
||||
var polys = [];
|
||||
var szs = [];
|
||||
/* 2.2.2.17 PolyPolygon */
|
||||
for (var i = 0; i < nPolygons; ++i)
|
||||
szs[i] = data.read_shift(2);
|
||||
for (var i = 0; i < szs.length; ++i) {
|
||||
polys[i] = [];
|
||||
for (var j = 0; j < szs[i]; ++j)
|
||||
polys[i].push([data.read_shift(2), data.read_shift(2)]);
|
||||
out.push({ t: "poly", p: polys[i], g: true, s: state });
|
||||
}
|
||||
}
|
||||
break;
|
||||
// #endregion
|
||||
// #region 2.3.4 Object Record Types
|
||||
case 0x02FC:
|
||||
{ // 2.3.4.1 META_CREATEBRUSHINDIRECT
|
||||
var obj = {};
|
||||
obj.Brush = {
|
||||
Style: data.read_shift(2),
|
||||
Color: data.read_shift(4),
|
||||
Hatch: data.read_shift(2)
|
||||
};
|
||||
add_to_objects(objects, obj);
|
||||
}
|
||||
break;
|
||||
case 0x02FB:
|
||||
{ // 2.3.4.2 META_CREATEFONTINDIRECT
|
||||
var obj = {};
|
||||
obj.Font = {};
|
||||
/* 2.2.1.2 Font TODO!! */
|
||||
var Height = data.read_shift(2, "i");
|
||||
var Width = data.read_shift(2, "i");
|
||||
var Escapement = data.read_shift(2, "i");
|
||||
var Orientation = data.read_shift(2, "i");
|
||||
var Weight = data.read_shift(2, "i");
|
||||
var Italic = !!data.read_shift(1);
|
||||
var Underline = !!data.read_shift(1);
|
||||
var StrikeOut = !!data.read_shift(1);
|
||||
var CharSet = data.read_shift(1);
|
||||
var OutPrecision = data.read_shift(1);
|
||||
var ClipPrecision = data.read_shift(1);
|
||||
var Quality = data.read_shift(1);
|
||||
var PitchAndFamily = data.read_shift(1);
|
||||
var Facename = data.read_shift(32, "cstr");
|
||||
obj.Font.Name = Facename;
|
||||
obj.Font.Height = Height;
|
||||
obj.Font.Weight = Weight;
|
||||
obj.Font.Italic = Italic;
|
||||
obj.Font.Angle = Escapement / 10;
|
||||
add_to_objects(objects, obj);
|
||||
}
|
||||
break;
|
||||
case 0x02FA:
|
||||
{ // 2.3.4.5 META_CREATEPENINDIRECT
|
||||
var obj = {};
|
||||
obj.Pen = {
|
||||
Style: data.read_shift(2),
|
||||
Width: data.read_shift(4) & 0xFF,
|
||||
Color: data.read_shift(4)
|
||||
};
|
||||
add_to_objects(objects, obj);
|
||||
}
|
||||
break;
|
||||
case 0x01F0:
|
||||
{ // 2.3.4.7 META_DELETEOBJECT
|
||||
var ObjectIndex = data.read_shift(2);
|
||||
//console.log("DELETE", ObjectIndex, objects[ObjectIndex]);
|
||||
objects[ObjectIndex] = null;
|
||||
}
|
||||
break;
|
||||
case 0x012C:
|
||||
{ // 2.3.4.9 META_SELECTCLIPREGION
|
||||
var Region = data.read_shift(2);
|
||||
//console.log("CLIPREGION", Region, objects[Region]);
|
||||
//Object.assign(state, objects[Region]);
|
||||
}
|
||||
break;
|
||||
case 0x012D:
|
||||
{ // 2.3.4.10 META_SELECTOBJECT
|
||||
var ObjectIndex = data.read_shift(2);
|
||||
//console.log("SELECT", ObjectIndex, objects[ObjectIndex]);
|
||||
Object.assign(state, objects[ObjectIndex]);
|
||||
// TODO!!
|
||||
}
|
||||
break;
|
||||
// #endregion
|
||||
// #region 2.3.5 State Record Types
|
||||
case 0x0416: // 2.3.5.3 META_INTERSECTCLIPRECT
|
||||
state.ClipRect = [[0, 0], [0, 0]];
|
||||
state.ClipRect[1][1] = data.read_shift(2);
|
||||
state.ClipRect[1][0] = data.read_shift(2);
|
||||
state.ClipRect[0][1] = data.read_shift(2);
|
||||
state.ClipRect[0][0] = data.read_shift(2);
|
||||
break;
|
||||
case 0x0127:
|
||||
{ // 2.3.5.10 META_RESTOREDC
|
||||
var nSavedDC = data.read_shift(2, 'i');
|
||||
state = states[sidx = (nSavedDC >= 0 ? nSavedDC : sidx + nSavedDC)];
|
||||
}
|
||||
break;
|
||||
case 0x001E: // 2.3.5.11 META_SAVEDC
|
||||
states.push(state);
|
||||
sidx = states.length - 1;
|
||||
state = JSON.parse(JSON.stringify(state));
|
||||
break;
|
||||
case 0x0102: // 2.3.5.15 META_SETBKMODE
|
||||
state.BkMode = data.read_shift(2);
|
||||
break;
|
||||
case 0x0103: // 2.3.5.17 META_SETMAPMODE
|
||||
state.MapMode = data.read_shift(2);
|
||||
break;
|
||||
case 0x0106: // 2.3.5.20 META_SETPOLYFILLMODE
|
||||
state.PolyFillMode = data.read_shift(2);
|
||||
break;
|
||||
case 0x0107: // 2.3.5.23 META_SETSTRETCHBLTMODE
|
||||
state.StretchMode = data.read_shift(2);
|
||||
break;
|
||||
case 0x012E: // 2.3.5.24 META_SETTEXTALIGN
|
||||
state.TextAlignmentMode = data.read_shift(2);
|
||||
break;
|
||||
case 0x0209: // 2.3.5.26 META_SETTEXTCOLOR
|
||||
state.TextColor = data.read_shift(4);
|
||||
break;
|
||||
case 0x020C: // 2.3.5.30 META_SETWINDOWEXT
|
||||
state.Extent = [0, 0];
|
||||
state.Extent[1] = data.read_shift(2);
|
||||
state.Extent[0] = data.read_shift(2);
|
||||
break;
|
||||
case 0x020B: // 2.3.5.31 META_SETWINDOWORG
|
||||
state.Origin = [0, 0];
|
||||
state.Origin[1] = data.read_shift(2);
|
||||
state.Origin[0] = data.read_shift(2);
|
||||
break;
|
||||
// #endregion
|
||||
default:
|
||||
if (!Record)
|
||||
throw "Record: Unrecognized type 0x" + rt.toString(16);
|
||||
console.log(Record);
|
||||
}
|
||||
data.l = end;
|
||||
//if(rt != 0x0626) console.log(Record);
|
||||
}
|
||||
if (rt !== 0)
|
||||
throw "Record: Last Record Type " + rt + " is not EOF type";
|
||||
return out;
|
||||
};
|
||||
exports.image_size_prepped_bytes = function (data) {
|
||||
var origin = [NaN, NaN], extents = [NaN, NaN];
|
||||
/* 2.3.22 META_HEADER */
|
||||
// Type (2 bytes) must be 1 or 2
|
||||
var h = data.read_shift(2);
|
||||
if (h != 1 && h != 2)
|
||||
throw "Header: Type " + h + " must be 1 or 2";
|
||||
// HeaderSize expected to be 9
|
||||
if ((h = data.read_shift(2)) != 9)
|
||||
throw "Header: HeaderSize " + h + " must be 9";
|
||||
// Version (2 bytes) 1 or 3
|
||||
h = data.read_shift(2);
|
||||
if (h != 0x0100 && h != 0x0300)
|
||||
throw "Header: Version " + h + " must be 0x0100 or 0x0300";
|
||||
data.l = 18;
|
||||
var rt = 0;
|
||||
while (data.l < data.length) {
|
||||
h = data.read_shift(4);
|
||||
var end = data.l + h * 2 - 4;
|
||||
rt = data.read_shift(2);
|
||||
if (rt == 0x0000)
|
||||
break; // META_EOF
|
||||
switch (rt) {
|
||||
case 0x020C: // 2.3.5.30 META_SETWINDOWEXT
|
||||
extents[1] = data.read_shift(2);
|
||||
extents[0] = data.read_shift(2);
|
||||
break;
|
||||
case 0x020B: // 2.3.5.31 META_SETWINDOWORG
|
||||
origin[1] = data.read_shift(2);
|
||||
origin[0] = data.read_shift(2);
|
||||
break;
|
||||
}
|
||||
data.l = end;
|
||||
}
|
||||
return [extents[0] - origin[0], extents[1] - origin[1]];
|
||||
};
|
||||
//# sourceMappingURL=wmf.js.map
|
1
js/wmf.js.map
Normal file
1
js/wmf.js.map
Normal file
File diff suppressed because one or more lines are too long
3
misc/entry.js
Normal file
3
misc/entry.js
Normal file
@ -0,0 +1,3 @@
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
var WMF = require("../js/");
|
||||
module.exports = WMF;
|
37
misc/webpack.browser.config.js
Normal file
37
misc/webpack.browser.config.js
Normal file
@ -0,0 +1,37 @@
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const banner = '/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */';
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: './misc/entry.js',
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
library: 'WMF',
|
||||
libraryTarget: 'var',
|
||||
filename: 'wmf.js',
|
||||
},
|
||||
node: { process: false },
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ['source-map-loader'],
|
||||
enforce: 'pre'
|
||||
}
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
output: { beautify: false, preamble: banner }
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.BannerPlugin({ banner, raw: true, entryOnly: true })
|
||||
]
|
||||
};
|
39
misc/webpack.node.config.js
Normal file
39
misc/webpack.node.config.js
Normal file
@ -0,0 +1,39 @@
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
var nodeExternals = require('webpack-node-externals');
|
||||
|
||||
const banner = '/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */';
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: './misc/entry.js',
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
library: 'WMF',
|
||||
libraryTarget: 'commonjs2',
|
||||
filename: 'wmf.node.js'
|
||||
},
|
||||
target: 'node',
|
||||
externals: [nodeExternals()],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ['source-map-loader'],
|
||||
enforce: 'pre'
|
||||
}
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new UglifyJsPlugin({
|
||||
uglifyOptions: {
|
||||
output: { beautify: false, preamble: banner }
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.BannerPlugin({ banner, raw: true, entryOnly: true })
|
||||
]
|
||||
};
|
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "wmf",
|
||||
"version": "1.0.0",
|
||||
"author": "sheetjs",
|
||||
"description": "Windows MetaFile (WMF) parser",
|
||||
"keywords": [ "wmf", "image" ],
|
||||
"main": "./dist/wmf.node.js",
|
||||
"browser": "./js/index.js",
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"source-map-loader": "^0.2.4",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0"
|
||||
},
|
||||
"homepage": "https://sheetjs.com/",
|
||||
"files": ["dist/", "LICENSE", "README.md", "js/"],
|
||||
"bugs": { "url": "https://github.com/SheetJS/js-wmf/issues" },
|
||||
"license": "Apache-2.0",
|
||||
"engines": { "node": ">=12.0" }
|
||||
}
|
50
src/Records.ts
Normal file
50
src/Records.ts
Normal file
@ -0,0 +1,50 @@
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
export interface WMFRecord {
|
||||
n: string;
|
||||
}
|
||||
|
||||
export interface WMFEscape {
|
||||
n: string;
|
||||
}
|
||||
|
||||
/* 2.1.1.1 RecordType Enumeration */
|
||||
export const WMFRecords: {[key: number]: WMFRecord} = {
|
||||
0x0000: { n: "META_EOF" }, // 2.3.2.1
|
||||
0x0626: { n: "META_ESCAPE" }, // 2.3.6.1
|
||||
|
||||
0x0940: { n: "META_DIBBITBLT" }, // 2.3.1.2
|
||||
0x0B41: { n: "META_DIBSTRETCHBLT" }, // 2.3.1.3
|
||||
|
||||
0x0A32: { n: "META_EXTTEXTOUT" }, // 2.3.3.5
|
||||
0x0325: { n: "META_POLYLINE" }, // 2.3.3.14
|
||||
0x0324: { n: "META_POLYGON" }, // 2.3.3.15
|
||||
0x0538: { n: "META_POLYPOLYGON" }, // 2.3.3.16
|
||||
|
||||
0x02FC: { n: "META_CREATEBRUSHINDIRECT" }, // 2.3.4.1
|
||||
0x02FB: { n: "META_CREATEFONTINDIRECT" }, // 2.3.4.2
|
||||
0x02FA: { n: "META_CREATEPENINDIRECT" }, // 2.3.4.5
|
||||
0x01F0: { n: "META_DELETEOBJECT" }, // 2.3.4.7
|
||||
0x012C: { n: "META_SELECTCLIPREGION" }, // 2.3.4.9
|
||||
0x012D: { n: "META_SELECTOBJECT" }, // 2.3.4.10
|
||||
|
||||
0x0416: { n: "META_INTERSECTCLIPRECT" }, // 2.3.5.3
|
||||
0x0035: { n: "META_REALIZEPALETTE" }, // 2.3.5.8
|
||||
0x0127: { n: "META_RESTOREDC" }, // 2.3.5.10
|
||||
0x001E: { n: "META_SAVEDC" }, // 2.3.5.11
|
||||
0x0102: { n: "META_SETBKMODE" }, // 2.3.5.15
|
||||
0x0103: { n: "META_SETMAPMODE" }, // 2.3.5.17
|
||||
0x0037: { n: "META_SETPALENTRIES" }, // 2.3.5.19
|
||||
0x0106: { n: "META_SETPOLYFILLMODE" }, // 2.3.5.20
|
||||
0x0107: { n: "META_SETSTRETCHBLTMODE" }, // 2.3.5.23
|
||||
0x012E: { n: "META_SETTEXTALIGN" }, // 2.3.5.24
|
||||
0x0209: { n: "META_SETTEXTCOLOR" }, // 2.3.5.26
|
||||
0x020C: { n: "META_SETWINDOWEXT" }, // 2.3.5.30
|
||||
0x020B: { n: "META_SETWINDOWORG" }, // 2.3.5.31
|
||||
|
||||
0xFFFF: { n: "META_SHEETJS" }
|
||||
};
|
||||
|
||||
export const WMFEscapes: {[key: number]: WMFEscape} = {
|
||||
0x000F: { n: "META_ESCAPE_ENHANCED_METAFILE" }
|
||||
};
|
||||
|
98
src/canvas.ts
Normal file
98
src/canvas.ts
Normal file
@ -0,0 +1,98 @@
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
import { PreppedBytes, prep_blob } from './util';
|
||||
import { Action, PlaybackDeviceContextState, get_actions_prepped_bytes } from './wmf'
|
||||
|
||||
export const css_color = (clr: number): string => `#${(clr & 0xFF).toString(16).padStart(2, "0")}${((clr>>8) & 0xFF).toString(16).padStart(2, "0")}${((clr>>16) & 0xFF).toString(16).padStart(2, "0")}`
|
||||
|
||||
export const set_ctx_state = (ctx: CanvasRenderingContext2D, state: PlaybackDeviceContextState): void => {
|
||||
if(!state) return;
|
||||
let font = "";
|
||||
if(state.Font) {
|
||||
if(state.Font.Italic) font += " italic";
|
||||
if(state.Font.Weight) font += ` ${state.Font.Weight == 700 ? "bold" : state.Font.Weight == 400 ? "" : state.Font.Weight}`;
|
||||
if(state.Font.Height < 0) font += ` ${-state.Font.Height}px`;
|
||||
else if(state.Font.Height > 0) font += ` ${state.Font.Height}px`;
|
||||
let name = state.Font.Name || "";
|
||||
if(name == "System") name = "Calibri"; // TODO: default sys font is Segoe UI
|
||||
if(name) font += ` '${name}', sans-serif`;
|
||||
ctx.font = font.trim();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: DIB BIT ORDER?
|
||||
export const render_actions_to_context = (out: Action[], ctx: CanvasRenderingContext2D) => {
|
||||
out.forEach(act => {
|
||||
ctx.save();
|
||||
set_ctx_state(ctx, act.s);
|
||||
switch(act.t) {
|
||||
case "poly":
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(act.p[0][0], act.p[0][1]);
|
||||
act.p.slice(1).forEach(([x,y]) => {
|
||||
ctx.lineTo(x, y);
|
||||
});
|
||||
if(act.g) ctx.closePath();
|
||||
ctx.stroke();
|
||||
break;
|
||||
case "text":
|
||||
if(act.s && act.s.TextColor) ctx.fillStyle = css_color(act.s.TextColor);
|
||||
if(act.s.Font.Angle != 0) {
|
||||
ctx.translate(act.p[0], act.p[1]);
|
||||
ctx.rotate(-act.s.Font.Angle * Math.PI / 180);
|
||||
ctx.fillText(act.v, 0, 0);
|
||||
ctx.translate(-act.p[0], -act.p[1]);
|
||||
}
|
||||
else ctx.fillText(act.v, act.p[0], act.p[1]);
|
||||
break;
|
||||
case "cpy": {
|
||||
// TODO: base on ROP
|
||||
const idata = ctx.getImageData(act.src[0][0], act.src[1][0], act.src[0][1], act.src[1][1]);
|
||||
ctx.putImageData(idata, act.dst[0], act.dst[1]);
|
||||
} break;
|
||||
case "str": {
|
||||
if(act.data && act.data.BitCount == 24 && act.data.ImageData) {
|
||||
const _o = new Uint8ClampedArray(act.data.Width * act.data.Height * 4);
|
||||
for(let i = 0; i < act.data.Width * act.data.Height; ++i) {
|
||||
const j = (i % act.data.Width) + act.data.Width * (act.data.Height - 1 - Math.floor(i / act.data.Width));
|
||||
_o[4*i] = act.data.ImageData[3*j+2];
|
||||
_o[4*i+1] = act.data.ImageData[3*j+1];
|
||||
_o[4*i+2] = act.data.ImageData[3*j];
|
||||
_o[4*i+3] = 255;
|
||||
}
|
||||
const idata = new ImageData(_o, act.data.Width, act.data.Height);
|
||||
ctx.putImageData(idata, act.dst[0][0], act.dst[1][0]);
|
||||
}
|
||||
// TODO: ROP et al
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
});
|
||||
}
|
||||
|
||||
export const render_canvas = (out: Action[], image: HTMLCanvasElement): void => {
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
|
||||
/* find first action with window info */
|
||||
out.forEach(act => {
|
||||
if(ctx) return;
|
||||
if(!act.s) return;
|
||||
if(!act.s.Extent || !act.s.Origin) return;
|
||||
image.width = act.s.Extent[0] - act.s.Origin[0];
|
||||
image.height = act.s.Extent[1] - act.s.Origin[1];
|
||||
ctx = image.getContext('2d');
|
||||
ctx.save();
|
||||
ctx.fillStyle = 'rgb(255,255,255)';
|
||||
ctx.fillRect(0, 0, act.s.Extent[0] - act.s.Origin[0], act.s.Extent[1] - act.s.Origin[1])
|
||||
ctx.restore();
|
||||
});
|
||||
|
||||
if(!ctx) ctx = image.getContext('2d');
|
||||
render_actions_to_context(out, ctx);
|
||||
}
|
||||
|
||||
export const draw_canvas = (data: Buffer | Uint8Array | ArrayBuffer, image: HTMLCanvasElement): void => {
|
||||
if(data instanceof ArrayBuffer) return draw_canvas(new Uint8Array(data), image);
|
||||
prep_blob((data as any), 0);
|
||||
const out: Action[] = get_actions_prepped_bytes(data as PreppedBytes);
|
||||
return render_canvas(out, image);
|
||||
};
|
17
src/index.ts
Normal file
17
src/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
import { PreppedBytes, prep_blob } from './util';
|
||||
import { Action, get_actions_prepped_bytes, image_size_prepped_bytes } from './wmf'
|
||||
|
||||
export { draw_canvas, render_canvas } from './canvas';
|
||||
|
||||
export const get_actions = (data: Buffer | Uint8Array | ArrayBuffer): Action[] => {
|
||||
if(data instanceof ArrayBuffer) return get_actions(new Uint8Array(data));
|
||||
prep_blob((data as any), 0);
|
||||
return get_actions_prepped_bytes(data as PreppedBytes);
|
||||
}
|
||||
|
||||
export const image_size = (data: Buffer | Uint8Array | ArrayBuffer): [number, number] => {
|
||||
if(data instanceof ArrayBuffer) return image_size(new Uint8Array(data));
|
||||
prep_blob((data as any), 0);
|
||||
return image_size_prepped_bytes(data as PreppedBytes);
|
||||
}
|
297
src/util.ts
Normal file
297
src/util.ts
Normal file
@ -0,0 +1,297 @@
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
export type RawBytes = Buffer | number[];
|
||||
|
||||
export type PreppedBytes = RawBytes & {
|
||||
l: number;
|
||||
read_shift(size: 1): number;
|
||||
read_shift(size: 2): number;
|
||||
read_shift(size: 2, t: "i"): number;
|
||||
read_shift(size: 4): number;
|
||||
read_shift(size: 4, t: "i"): number;
|
||||
read_shift(size: 8, t: "f"): number;
|
||||
read_shift(size: number, t: "cstr"): string;
|
||||
read_shift(size: number, t: "cpstr"): string;
|
||||
read_shift(size: number, t: "_wstr"): string;
|
||||
read_shift(size: number, t?: string): number|string;
|
||||
chk(hexstr: string, fld: string): void;
|
||||
write_shift(t: number, val: string|number, f?: string): void;
|
||||
};
|
||||
|
||||
// ---
|
||||
|
||||
const has_buf = !!(typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && process.versions.node);
|
||||
|
||||
let Buffer_from: typeof Buffer.from;
|
||||
|
||||
if(typeof Buffer !== 'undefined') {
|
||||
let nbfs = !Buffer.from;
|
||||
if(!nbfs) try {
|
||||
Buffer.from("foo", "utf8");
|
||||
} catch(e) { nbfs = true; }
|
||||
Buffer_from = nbfs ? ((buf, enc?: string): Buffer => (enc) ? new Buffer(buf, (enc as BufferEncoding)) : new Buffer(buf)) : Buffer.from.bind(Buffer);
|
||||
if(!Buffer.alloc) Buffer.alloc = function(n: number): Buffer { return new Buffer(n); };
|
||||
if(!Buffer.allocUnsafe) Buffer.allocUnsafe = function(n: number): Buffer { return new Buffer(n); };
|
||||
}
|
||||
|
||||
export { Buffer_from, has_buf };
|
||||
|
||||
export const new_raw_buf = (len: number): Buffer|number[] => has_buf ? Buffer.alloc(len) : new Array(len);
|
||||
|
||||
export const new_unsafe_buf = (len: number): Buffer|number[] => has_buf ? Buffer.allocUnsafe(len) : new Array(len);
|
||||
|
||||
export const _chr = (c: number): string => String.fromCharCode(c);
|
||||
|
||||
export const chr0 = /\u0000/g; // eslint-disable-line no-control-regex
|
||||
export const chr1 = /[\u0001-\u0006]/g; // eslint-disable-line no-control-regex
|
||||
|
||||
// ---
|
||||
|
||||
const read_double_le = (b: RawBytes, idx: number): number => {
|
||||
const s = 1 - 2 * (b[idx + 7] >>> 7);
|
||||
let e = ((b[idx + 7] & 0x7f) << 4) + ((b[idx + 6] >>> 4) & 0x0f);
|
||||
let m = (b[idx+6]&0x0f);
|
||||
for(let i = 5; i >= 0; --i) m = m * 256 + b[idx + i];
|
||||
if(e == 0x7ff) return m == 0 ? (s * Infinity) : NaN;
|
||||
if(e == 0) e = -1022;
|
||||
else { e -= 1023; m += Math.pow(2,52); }
|
||||
return s * Math.pow(2, e - 52) * m;
|
||||
};
|
||||
|
||||
const write_double_le = (b: RawBytes, v: number, idx: number): void => {
|
||||
const bs = ((((v < 0) || (1/v == -Infinity)) ? 1 : 0) << 7);
|
||||
let e = 0, m = 0;
|
||||
const av = bs ? (-v) : v;
|
||||
if(!isFinite(av)) { e = 0x7ff; m = isNaN(v) ? 0x6969 : 0; }
|
||||
else if(av == 0) e = m = 0;
|
||||
else {
|
||||
e = Math.floor(Math.log(av) / Math.LN2);
|
||||
m = av * Math.pow(2, 52 - e);
|
||||
if((e <= -1023) && (!isFinite(m) || (m < Math.pow(2,52)))) { e = -1022; }
|
||||
else { m -= Math.pow(2,52); e+=1023; }
|
||||
}
|
||||
for(let i = 0; i <= 5; ++i, m/=256) b[idx + i] = m & 0xff;
|
||||
b[idx + 6] = ((e & 0x0f) << 4) | (m & 0xf);
|
||||
b[idx + 7] = (e >> 4) | bs;
|
||||
};
|
||||
|
||||
let __toBuffer = (bufs/*:Array<Array<RawBytes> >*/): RawBytes => {
|
||||
const x: number[] =[];
|
||||
for(let i=0; i<bufs[0].length; ++i) if(bufs[0][i])
|
||||
for(let j=0, L=bufs[0][i].length; j<L; j+=10240) x.push(...bufs[0][i].slice(j,j+10240));
|
||||
return x;
|
||||
};
|
||||
const ___toBuffer = __toBuffer;
|
||||
|
||||
const __readUInt8 = (b: RawBytes, idx: number): number => b[idx];
|
||||
const __readUInt16LE = (b: RawBytes, idx: number): number => (b[idx+1]*(1<<8))+b[idx];
|
||||
const __readInt16LE = (b: RawBytes, idx: number): number => { const u = (b[idx+1]*(1<<8))+b[idx]; return (u < 0x8000) ? u : ((0xffff - u + 1) * -1); };
|
||||
const __readUInt32LE = (b: RawBytes, idx: number): number => b[idx+3]*(1<<24)+(b[idx+2]<<16)+(b[idx+1]<<8)+b[idx];
|
||||
const __readInt32LE = (b: RawBytes, idx: number): number => (b[idx+3]<<24)|(b[idx+2]<<16)|(b[idx+1]<<8)|b[idx];
|
||||
const __readInt32BE = (b: RawBytes, idx: number): number => (b[idx]<<24)|(b[idx+1]<<16)|(b[idx+2]<<8)|b[idx+3];
|
||||
|
||||
let __utf16le = (b: RawBytes, s: number, e: number): string => {
|
||||
const ss: string[] = [];
|
||||
for(let i=s; i<e; i+=2) ss.push(String.fromCharCode(__readUInt16LE(b,i)));
|
||||
return ss.join("").replace(chr0,'');
|
||||
};
|
||||
const ___utf16le = __utf16le;
|
||||
let __hexlify = function(b/*:RawBytes|CFBlob*/,s: number,l: number): string { const ss: string[] = []; for(let i=s; i<s+l; ++i) ss.push(("0" + b[i].toString(16)).slice(-2)); return ss.join(""); };
|
||||
const ___hexlify = __hexlify;
|
||||
let __utf8 = function(b/*:RawBytes|CFBlob*/,s: number,e: number): string { const ss=[]; for(let i=s; i<e; i++) ss.push(String.fromCharCode(__readUInt8(b,i))); return ss.join(""); };
|
||||
const ___utf8 = __utf8;
|
||||
let __lpstr = function(b/*:RawBytes|CFBlob*/,i: number): string { const len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
|
||||
const ___lpstr = __lpstr;
|
||||
let __cpstr = function(b/*:RawBytes|CFBlob*/,i: number): string { const len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
|
||||
const ___cpstr = __cpstr;
|
||||
let __lpwstr = function(b/*:RawBytes|CFBlob*/,i: number): string { const len = 2*__readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len-1) : "";};
|
||||
const ___lpwstr = __lpwstr;
|
||||
let __lpp4, ___lpp4;
|
||||
__lpp4 = ___lpp4 = function lpp4_(b/*:RawBytes|CFBlob*/,i: number): string { const len = __readUInt32LE(b,i); return len > 0 ? __utf16le(b, i+4,i+4+len) : "";};
|
||||
const ___8lpp4 = function(b/*:RawBytes|CFBlob*/,i: number): string { const len = __readUInt32LE(b,i); return len > 0 ? __utf8(b, i+4,i+4+len) : "";};
|
||||
let __8lpp4 = ___8lpp4;
|
||||
const ___double = (b/*:RawBytes|CFBlob*/, idx: number): number => read_double_le(b, idx);
|
||||
let __double = ___double;
|
||||
|
||||
if(has_buf) {
|
||||
__utf16le = (b/*:RawBytes|CFBlob*/,s: number,e: number): string => (!Buffer.isBuffer(b)) ? ___utf16le(b,s,e) : b.toString('utf16le',s,e).replace(chr0,'');
|
||||
__hexlify = (b/*:RawBytes|CFBlob*/,s: number,l: number): string => Buffer.isBuffer(b) ? b.toString('hex',s,s+l) : ___hexlify(b,s,l);
|
||||
__lpstr = (b/*:RawBytes|CFBlob*/, i: number): string => { if(!Buffer.isBuffer(b)) return ___lpstr(b, i); const len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8',i+4,i+4+len-1) : "";};
|
||||
__cpstr = (b/*:RawBytes|CFBlob*/, i: number): string => { if(!Buffer.isBuffer(b)) return ___cpstr(b, i); const len = b.readUInt32LE(i); return len > 0 ? b.toString('utf8',i+4,i+4+len-1) : "";};
|
||||
__lpwstr = (b/*:RawBytes|CFBlob*/, i: number): string => { if(!Buffer.isBuffer(b)) return ___lpwstr(b, i); const len = 2*b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len-1);};
|
||||
__lpp4 = (b/*:RawBytes|CFBlob*/, i: number): string => { if(!Buffer.isBuffer(b)) return ___lpp4(b, i); const len = b.readUInt32LE(i); return b.toString('utf16le',i+4,i+4+len);};
|
||||
__8lpp4 = (b/*:RawBytes|CFBlob*/, i: number): string => { if(!Buffer.isBuffer(b)) return ___8lpp4(b, i); const len = b.readUInt32LE(i); return b.toString('utf8',i+4,i+4+len);};
|
||||
__utf8 = (b/*:RawBytes|CFBlob*/, s: number, e: number): string => (Buffer.isBuffer(b)) ? b.toString('utf8',s,e) : ___utf8(b,s,e);
|
||||
__toBuffer = (bufs): RawBytes => (bufs[0].length > 0 && Buffer.isBuffer(bufs[0][0])) ? Buffer.concat(bufs[0]) : ___toBuffer(bufs);
|
||||
__double = (b/*:RawBytes|CFBlob*/, i: number): number => (Buffer.isBuffer(b)) ? b.readDoubleLE(i) : ___double(b,i);
|
||||
}
|
||||
|
||||
function ReadShift(size: 1): number;
|
||||
function ReadShift(size: 2): number;
|
||||
function ReadShift(size: 2, t: "i"): number;
|
||||
function ReadShift(size: 4): number;
|
||||
function ReadShift(size: 4, t: "i"): number;
|
||||
function ReadShift(size: 8, t: "f"): number;
|
||||
function ReadShift(size: number, t: "cstr"): string;
|
||||
function ReadShift(size: number, t: "cpstr"): string;
|
||||
function ReadShift(size: number, t: "_wstr"): string;
|
||||
function ReadShift(size: number, t?: string): number|string {
|
||||
let o="", oI = 0, oR, w, vv, i, loc;
|
||||
const oo = [];
|
||||
switch(t) {
|
||||
case 'dbcs':
|
||||
loc = this.l;
|
||||
if(has_buf && Buffer.isBuffer(this)) o = this.slice(this.l, this.l+2*size).toString("utf16le");
|
||||
else for(i = 0; i < size; ++i) { o+=String.fromCharCode(__readUInt16LE(this, loc)); loc+=2; }
|
||||
size *= 2;
|
||||
break;
|
||||
|
||||
case 'utf8': o = __utf8(this, this.l, this.l + size); break;
|
||||
case 'utf16le': size *= 2; o = __utf16le(this, this.l, this.l + size); break;
|
||||
|
||||
case 'wstr':
|
||||
return ReadShift.call(this, size, 'dbcs');
|
||||
|
||||
/* [MS-OLEDS] 2.1.4 LengthPrefixedAnsiString */
|
||||
case 'lpstr-ansi': o = __lpstr(this, this.l); size = 4 + __readUInt32LE(this, this.l); break;
|
||||
case 'lpstr-cp': o = __cpstr(this, this.l); size = 4 + __readUInt32LE(this, this.l); break;
|
||||
/* [MS-OLEDS] 2.1.5 LengthPrefixedUnicodeString */
|
||||
case 'lpwstr': o = __lpwstr(this, this.l); size = 4 + 2 * __readUInt32LE(this, this.l); break;
|
||||
/* [MS-OFFCRYPTO] 2.1.2 Length-Prefixed Padded Unicode String (UNICODE-LP-P4) */
|
||||
case 'lpp4': size = 4 + __readUInt32LE(this, this.l); o = __lpp4(this, this.l); if(size & 0x02) size += 2; break;
|
||||
/* [MS-OFFCRYPTO] 2.1.3 Length-Prefixed UTF-8 String (UTF-8-LP-P4) */
|
||||
case '8lpp4': size = 4 + __readUInt32LE(this, this.l); o = __8lpp4(this, this.l); if(size & 0x03) size += 4 - (size & 0x03); break;
|
||||
|
||||
case 'cstr': size = 0; o = "";
|
||||
while((w=__readUInt8(this, this.l + size++))!==0) oo.push(String.fromCharCode(w));
|
||||
o = oo.join(""); break;
|
||||
case '_wstr': size = 0; o = "";
|
||||
while((w=__readUInt16LE(this,this.l +size))!==0){oo.push(String.fromCharCode(w));size+=2;}
|
||||
size+=2; o = oo.join(""); break;
|
||||
|
||||
/* sbcs and dbcs support continue records in the SST way TODO codepages */
|
||||
case 'dbcs-cont': o = ""; loc = this.l;
|
||||
for(i = 0; i < size; ++i) {
|
||||
if(this.lens && this.lens.indexOf(loc) !== -1) {
|
||||
w = __readUInt8(this, loc);
|
||||
this.l = loc + 1;
|
||||
vv = ReadShift.call(this, size-i, w ? 'dbcs-cont' : 'sbcs-cont');
|
||||
return oo.join("") + vv;
|
||||
}
|
||||
oo.push(String.fromCharCode(__readUInt16LE(this, loc)));
|
||||
loc+=2;
|
||||
} o = oo.join(""); size *= 2; break;
|
||||
|
||||
case 'cpstr':
|
||||
/* falls through */
|
||||
case 'sbcs-cont': o = ""; loc = this.l;
|
||||
for(i = 0; i != size; ++i) {
|
||||
if(this.lens && this.lens.indexOf(loc) !== -1) {
|
||||
w = __readUInt8(this, loc);
|
||||
this.l = loc + 1;
|
||||
vv = ReadShift.call(this, size-i, w ? 'dbcs-cont' : 'sbcs-cont');
|
||||
return oo.join("") + vv;
|
||||
}
|
||||
oo.push(String.fromCharCode(__readUInt8(this, loc)));
|
||||
loc+=1;
|
||||
} o = oo.join(""); break;
|
||||
|
||||
default:
|
||||
switch(size) {
|
||||
case 1: oI = __readUInt8(this, this.l); this.l++; return oI;
|
||||
case 2: oI = (t === 'i' ? __readInt16LE : __readUInt16LE)(this, this.l); this.l += 2; return oI;
|
||||
case 4: case -4:
|
||||
if(t === 'i' || ((this[this.l+3] & 0x80)===0)) { oI = ((size > 0) ? __readInt32LE : __readInt32BE)(this, this.l); this.l += 4; return oI; }
|
||||
else { oR = __readUInt32LE(this, this.l); this.l += 4; } return oR;
|
||||
case 8: case -8:
|
||||
if(t === 'f') {
|
||||
if(size == 8) oR = __double(this, this.l);
|
||||
else oR = __double([this[this.l+7],this[this.l+6],this[this.l+5],this[this.l+4],this[this.l+3],this[this.l+2],this[this.l+1],this[this.l+0]], 0);
|
||||
this.l += 8; return oR;
|
||||
} else size = 8;
|
||||
/* falls through */
|
||||
case 16: o = __hexlify(this, this.l, size); break;
|
||||
}}
|
||||
this.l+=size; return o;
|
||||
}
|
||||
|
||||
const __writeUInt32LE = (b/*:RawBytes|CFBlob*/, val: number, idx: number): void => { b[idx] = (val & 0xFF); b[idx+1] = ((val >>> 8) & 0xFF); b[idx+2] = ((val >>> 16) & 0xFF); b[idx+3] = ((val >>> 24) & 0xFF); };
|
||||
const __writeInt32LE = (b/*:RawBytes|CFBlob*/, val: number, idx: number): void => { b[idx] = (val & 0xFF); b[idx+1] = ((val >> 8) & 0xFF); b[idx+2] = ((val >> 16) & 0xFF); b[idx+3] = ((val >> 24) & 0xFF); };
|
||||
const __writeUInt16LE = (b/*:RawBytes|CFBlob*/, val: number, idx: number): void => { b[idx] = (val & 0xFF); b[idx+1] = ((val >>> 8) & 0xFF); };
|
||||
|
||||
function WriteShift(t: number, val: string|number, f?: string): void {
|
||||
let size = 0, i = 0;
|
||||
if(f === 'dbcs') {
|
||||
if(typeof val !== 'string') throw new Error("expected string");
|
||||
for(i = 0; i != val.length; ++i) __writeUInt16LE(this, val.charCodeAt(i), this.l + 2 * i);
|
||||
size = 2 * val.length;
|
||||
} else if(f === 'sbcs') {
|
||||
{
|
||||
val = (val as string).replace(/[^\x00-\x7F]/g, "_"); // eslint-disable-line no-control-regex
|
||||
for(i = 0; i != val.length; ++i) this[this.l + i] = (val.charCodeAt(i) & 0xFF);
|
||||
}
|
||||
size = val.length;
|
||||
} else if(f === 'hex') {
|
||||
for(; i < t; ++i) {
|
||||
this[this.l++] = (parseInt((val as string).slice(2*i, 2*i+2), 16)||0);
|
||||
} return this;
|
||||
} else if(f === 'utf16le') {
|
||||
/*:: if(typeof val !== "string") throw new Error("unreachable"); */
|
||||
const end: number = Math.min(this.l + t, this.length);
|
||||
for(i = 0; i < Math.min((val as string).length, t); ++i) {
|
||||
const cc = (val as string).charCodeAt(i);
|
||||
this[this.l++] = (cc & 0xff);
|
||||
this[this.l++] = (cc >> 8);
|
||||
}
|
||||
while(this.l < end) this[this.l++] = 0;
|
||||
return this;
|
||||
} else if(typeof val === 'number') switch(t) {
|
||||
case 1: size = 1; this[this.l] = val&0xFF; break;
|
||||
case 2: size = 2; this[this.l] = val&0xFF; val >>>= 8; this[this.l+1] = val&0xFF; break;
|
||||
case 3: size = 3; this[this.l] = val&0xFF; val >>>= 8; this[this.l+1] = val&0xFF; val >>>= 8; this[this.l+2] = val&0xFF; break;
|
||||
case 4: size = 4; __writeUInt32LE(this, val, this.l); break;
|
||||
case 8: size = 8; if(f === 'f') { write_double_le(this, val, this.l); break; }
|
||||
/* falls through */
|
||||
case 16: break;
|
||||
case -4: size = 4; __writeInt32LE(this, val, this.l); break;
|
||||
}
|
||||
this.l += size; return this;
|
||||
}
|
||||
|
||||
function CheckField(hexstr: string, fld: string): void {
|
||||
const m = __hexlify(this,this.l,hexstr.length>>1);
|
||||
if(m !== hexstr) throw new Error(fld + 'Expected ' + hexstr + ' saw ' + m);
|
||||
this.l += hexstr.length>>1;
|
||||
}
|
||||
|
||||
const prep_blob = (blob: PreppedBytes, pos: number): void => {
|
||||
blob.l = pos;
|
||||
blob.read_shift = ReadShift;
|
||||
blob.chk = CheckField;
|
||||
blob.write_shift = WriteShift;
|
||||
};
|
||||
|
||||
const new_buf = (sz: number): PreppedBytes => {
|
||||
const o = (new_raw_buf(sz) as PreppedBytes);
|
||||
prep_blob(o, 0);
|
||||
return o;
|
||||
};
|
||||
|
||||
export { ReadShift, WriteShift, CheckField, prep_blob, new_buf, __utf16le };
|
||||
|
||||
// ---
|
||||
|
||||
const __bconcat = function(bufs/*:Array<RawBytes>*/): Buffer | Uint8Array | number[] {
|
||||
let is_all_arrays = true;
|
||||
for(let w = 0; w < bufs.length; ++w) if(!Array.isArray(bufs[w])) is_all_arrays = false;
|
||||
if(is_all_arrays) return [].concat(...bufs);
|
||||
let maxlen = 0, i = 0;
|
||||
for(i = 0; i < bufs.length; ++i) maxlen += bufs[i].length;
|
||||
const o = new Uint8Array(maxlen);
|
||||
for(i = 0, maxlen = 0; i < bufs.length; maxlen += bufs[i].length, ++i) o.set(bufs[i], maxlen);
|
||||
return o;
|
||||
};
|
||||
let bconcat = __bconcat;
|
||||
|
||||
if(has_buf) bconcat = (bufs): Buffer | Uint8Array | number[] => Buffer.isBuffer(bufs[0]) ? Buffer.concat(bufs) : [].concat(...bufs);
|
||||
|
||||
export { bconcat };
|
533
src/wmf.ts
Normal file
533
src/wmf.ts
Normal file
@ -0,0 +1,533 @@
|
||||
/*! wmf.js (C) 2020-present SheetJS LLC -- https://sheetjs.com */
|
||||
import { PreppedBytes, RawBytes, bconcat, prep_blob } from './util';
|
||||
import { WMFRecords, WMFEscapes } from './Records';
|
||||
|
||||
export interface Brush {
|
||||
/** Style (MS-WMF 2.1.1.4) */
|
||||
Style?: Number;
|
||||
/** Brush color RGB */
|
||||
Color?: number;
|
||||
/** Hatch Type (2.1.1.12 if brush is hatched) */
|
||||
Hatch?: number;
|
||||
}
|
||||
|
||||
export interface Pen {
|
||||
Style?: number;
|
||||
Width?: number;
|
||||
Color?: number;
|
||||
}
|
||||
|
||||
export interface Font {
|
||||
Name?: string;
|
||||
Height?: number;
|
||||
Italic?: boolean;
|
||||
Weight?: number;
|
||||
Angle?: number;
|
||||
}
|
||||
|
||||
export interface PlaybackDeviceContextState {
|
||||
/** Mapping mode (MS-WMF 2.1.1.16) */
|
||||
MapMode?: number;
|
||||
/** Output window origin (X, Y) */
|
||||
Origin?: [number, number];
|
||||
/** Output window extents (X, Y) */
|
||||
Extent?: [number, number];
|
||||
/** Background Mix Mode (MS-WMF 2.1.1.20) */
|
||||
BkMode?: number;
|
||||
/** Polygon fill mode (MS-WMF 2.1.1.25) */
|
||||
PolyFillMode?: number;
|
||||
/** Bitmap stretching mode (MS-WMF 2.1.1.30) */
|
||||
StretchMode?: number;
|
||||
/** Text alignment mode (MS-WMF 2.1.2.3 / 2.1.2.4) */
|
||||
TextAlignmentMode?: number;
|
||||
/** Text foreground color RGB */
|
||||
TextColor?: number;
|
||||
/** Brush */
|
||||
Brush?: Brush;
|
||||
/** Font */
|
||||
Font?: Font;
|
||||
/** Pen */
|
||||
Pen?: Pen;
|
||||
/** Clipping Region (x,y) LT (x,y) RB */
|
||||
ClipRect?: [[number, number], [number, number]];
|
||||
}
|
||||
|
||||
/** [x, y] */
|
||||
export type Point = [ number, number ];
|
||||
|
||||
export interface ActionCommon {
|
||||
/** State */
|
||||
s?: PlaybackDeviceContextState;
|
||||
}
|
||||
|
||||
/** Draw Text */
|
||||
export interface ActionText extends ActionCommon {
|
||||
/** Action Type */
|
||||
t: "text";
|
||||
|
||||
/** Text */
|
||||
v: string;
|
||||
|
||||
/** Origin */
|
||||
p?: Point;
|
||||
}
|
||||
|
||||
/** Draw Polygon (shape with stroke/fill) / Polyline (stroke only) */
|
||||
export interface ActionPoly extends ActionCommon {
|
||||
/** Action Type */
|
||||
t: "poly";
|
||||
|
||||
/** Points */
|
||||
p: Point[];
|
||||
|
||||
/** Polygon (true) or Polyline (false) */
|
||||
g: boolean;
|
||||
}
|
||||
|
||||
export interface ActionRaster {
|
||||
/** Raster Operaton 2.1.1.31 */
|
||||
rop?: number;
|
||||
}
|
||||
|
||||
export interface ActionCpy extends ActionCommon, ActionRaster {
|
||||
t: "cpy";
|
||||
|
||||
/** Source [[X, W], [Y, H]] */
|
||||
src: [[number, number], [number, number]];
|
||||
|
||||
dst: Point;
|
||||
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export interface ActionStr extends ActionCommon, ActionRaster {
|
||||
t: "str";
|
||||
|
||||
/** Source [[X, W], [Y, H]] */
|
||||
src: [[number, number], [number, number]];
|
||||
|
||||
/** Dest [[X, W], [Y, H]] */
|
||||
dst: [[number, number], [number, number]];
|
||||
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export type Action = ActionText | ActionPoly | ActionCpy | ActionStr;
|
||||
|
||||
const parse_emf = (data: PreppedBytes): void => {
|
||||
//try { require("fs").writeFileSync("out.emf", data); } catch(e) {}
|
||||
}
|
||||
|
||||
/* 2.2.2.9 */
|
||||
const parse_dib = (data: PreppedBytes) => {
|
||||
if(data.length == 0) return null;
|
||||
prep_blob(data, 0);
|
||||
|
||||
/* DIBHeaderInfo */
|
||||
const HeaderSize = data.read_shift(4);
|
||||
let Width = 0, Height = 0, Planes = 0, BitCount = 0;
|
||||
let Compression = 0, ImageSize = 0, XPelsPerMeter = 0, YPelsPerMeter = 0, ColorUsed = 0, ColorImportant = 0;
|
||||
if(HeaderSize == 0x0C) {
|
||||
Width = data.read_shift(2);
|
||||
Height = data.read_shift(2);
|
||||
} else {
|
||||
Width = data.read_shift(4, 'i');
|
||||
Height = data.read_shift(4, 'i');
|
||||
}
|
||||
Planes = data.read_shift(2);
|
||||
BitCount = data.read_shift(2);
|
||||
|
||||
const out: object = {
|
||||
Width,
|
||||
Height,
|
||||
BitCount,
|
||||
};
|
||||
|
||||
if(HeaderSize != 0x0C) {
|
||||
Compression = data.read_shift(4);
|
||||
ImageSize = data.read_shift(4);
|
||||
XPelsPerMeter = data.read_shift(4, 'i');
|
||||
YPelsPerMeter = data.read_shift(4, 'i');
|
||||
ColorUsed = data.read_shift(4);
|
||||
ColorImportant = data.read_shift(4);
|
||||
out["Compression"] = Compression;
|
||||
if(BitCount == 24 && ImageSize > Height * 3 * Width) Width = out["Width"] = ImageSize / (Height * 3);
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
/* BitmapBuffer */
|
||||
if(ImageSize == data.length - data.l) {
|
||||
out["ImageData"] = data.slice(data.l, data.length);
|
||||
prep_blob(out["ImageData"], 0);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const add_to_objects = (objects: PlaybackDeviceContextState[], obj: PlaybackDeviceContextState): void => {
|
||||
let found = false;
|
||||
for(var i = 0; i < objects.length; ++i) if(!objects[i]) { objects[i] = obj; found = true; }
|
||||
if(!found) objects.push(obj);
|
||||
}
|
||||
|
||||
export const get_actions_prepped_bytes = (data: PreppedBytes): Action[] => {
|
||||
const out: Action[] = [];
|
||||
|
||||
/* 2.3.22 META_HEADER */
|
||||
// Type (2 bytes) must be 1 or 2
|
||||
let h = data.read_shift(2);
|
||||
if(h != 1 && h != 2) throw `Header: Type ${h} must be 1 or 2`;
|
||||
// HeaderSize expected to be 9
|
||||
if((h = data.read_shift(2)) != 9) throw `Header: HeaderSize ${h} must be 9`;
|
||||
// Version (2 bytes) 1 or 3
|
||||
h = data.read_shift(2);
|
||||
if(h != 0x0100 && h != 0x0300) throw `Header: Version ${h} must be 0x0100 or 0x0300`;
|
||||
// SizeLow
|
||||
// SizeHigh
|
||||
// #Objects
|
||||
// MaxRecord
|
||||
// NumberOfMembers
|
||||
data.l = 18;
|
||||
|
||||
let rt = 0;
|
||||
|
||||
/* used for EMF */
|
||||
let escapecnt = 0;
|
||||
let CommentRecordCount = 0;
|
||||
let RemainingBytes = 0;
|
||||
let EnhancedMetafileDataSize = 0;
|
||||
let bufs: RawBytes[] = [];
|
||||
|
||||
let objects: PlaybackDeviceContextState[] = [];
|
||||
let states: PlaybackDeviceContextState[] = [];
|
||||
let state: PlaybackDeviceContextState = {};
|
||||
let sidx = -1;
|
||||
|
||||
while(data.l < data.length) {
|
||||
h = data.read_shift(4);
|
||||
const end = data.l + h*2 - 4;
|
||||
|
||||
rt = data.read_shift(2);
|
||||
let Record = WMFRecords[rt];
|
||||
if(rt == 0x0000) break; // META_EOF
|
||||
|
||||
switch(rt) {
|
||||
case 0x0626: { // META_ESCAPE
|
||||
const EscapeFunction = data.read_shift(2);
|
||||
const Escape = WMFEscapes[EscapeFunction];
|
||||
//console.log("::", Escape);
|
||||
/* 2.3.6 */
|
||||
switch(EscapeFunction) {
|
||||
case 0x000F: { // META_ESCAPE_ENHANCED_METAFILE
|
||||
const ByteCount = data.read_shift(2);
|
||||
let tmp = data.read_shift(4);
|
||||
if(tmp != 0x43464D57) throw `Escape: Comment ID 0x${tmp.toString(16)} != 0x43464D57`;
|
||||
tmp = data.read_shift(4);
|
||||
if(tmp != 0x00000001) throw `Escape: Comment Type 0x${tmp.toString(16)} != 0x00000001`;
|
||||
tmp = data.read_shift(4);
|
||||
if(tmp != 0x00010000) throw `Escape: Version 0x${tmp.toString(16)} != 0x00010000`;
|
||||
|
||||
const Checksum = data.read_shift(2);
|
||||
|
||||
data.l += 4; // Flags
|
||||
if(escapecnt == 0) {
|
||||
CommentRecordCount = data.read_shift(4); // total number of records
|
||||
} else {
|
||||
const _CommentRecordCount = data.read_shift(4);
|
||||
if(_CommentRecordCount != CommentRecordCount) throw `Escape: CommentRecordCount ${_CommentRecordCount} != ${CommentRecordCount}`;
|
||||
}
|
||||
const CurrentRecordSize = data.read_shift(4); // size of this record
|
||||
const _RemainingBytes = data.read_shift(4);
|
||||
if(escapecnt > 0 && CurrentRecordSize + _RemainingBytes != RemainingBytes) throw `Escape: ${RemainingBytes} != ${CurrentRecordSize} + ${_RemainingBytes}`;
|
||||
RemainingBytes = _RemainingBytes;
|
||||
const _EnhancedMetafileDataSize = data.read_shift(4);
|
||||
if(escapecnt == 0) {
|
||||
if(_EnhancedMetafileDataSize != CurrentRecordSize + _RemainingBytes) throw `Escape: ${_EnhancedMetafileDataSize} != ${CurrentRecordSize} + ${_RemainingBytes}`;
|
||||
EnhancedMetafileDataSize = _EnhancedMetafileDataSize;
|
||||
} else if(EnhancedMetafileDataSize != _EnhancedMetafileDataSize) throw `Escape: ${EnhancedMetafileDataSize} != ${_EnhancedMetafileDataSize}`;
|
||||
|
||||
if(ByteCount != (end - data.l) + 34) throw `Escape: Sizes ${ByteCount} != ${end - data.l} + 34`
|
||||
if(end - data.l != CurrentRecordSize) throw `Escape: CRSize ${CurrentRecordSize} != ${end - data.l}`;
|
||||
bufs.push(data.slice(data.l, end));
|
||||
++escapecnt;
|
||||
if(escapecnt == CommentRecordCount) {
|
||||
const prepped: PreppedBytes = bconcat(bufs) as PreppedBytes;
|
||||
prep_blob(prepped, 0);
|
||||
parse_emf(prepped);
|
||||
}
|
||||
} break;
|
||||
default: throw `Escape: Unrecognized META_ESCAPE Type 0x${EscapeFunction.toString(16)}`;
|
||||
}
|
||||
} break;
|
||||
|
||||
// #region 2.3.1 Bitmap Record Types
|
||||
|
||||
case 0x0940: { // 2.3.1.2 META_DIBBITBLT
|
||||
const has_bitmap = h != (rt>>8)+3;
|
||||
const RasterOperation = data.read_shift(4);
|
||||
const YSrc = data.read_shift(2, "i");
|
||||
const XSrc = data.read_shift(2, "i");
|
||||
if(!has_bitmap) data.l += 2;
|
||||
const Height = data.read_shift(2, "i");
|
||||
const Width = data.read_shift(2, "i");
|
||||
const YDest = data.read_shift(2, "i");
|
||||
const XDest = data.read_shift(2, "i");
|
||||
const res: ActionCpy = {
|
||||
t: "cpy",
|
||||
src: [[XSrc, Width], [YSrc, Height]],
|
||||
dst: [XDest, YDest],
|
||||
rop: RasterOperation,
|
||||
s: Object.assign({}, state)
|
||||
};
|
||||
if(has_bitmap) {
|
||||
const DIB = parse_dib(data.slice(data.l, end) as PreppedBytes);
|
||||
res.data = DIB;
|
||||
}
|
||||
out.push(res);
|
||||
} break;
|
||||
|
||||
case 0x0B41: { // 2.3.1.3 META_DIBSTRETCHBLT
|
||||
const has_bitmap = h != (rt>>8)+3;
|
||||
const RasterOperation = data.read_shift(4);
|
||||
const SrcHeight = data.read_shift(2, "i");
|
||||
const SrcWidth = data.read_shift(2, "i");
|
||||
const YSrc = data.read_shift(2, "i");
|
||||
const XSrc = data.read_shift(2, "i");
|
||||
if(!has_bitmap) data.l += 2;
|
||||
const DestHeight = data.read_shift(2, "i");
|
||||
const DestWidth = data.read_shift(2, "i");
|
||||
const YDest = data.read_shift(2, "i");
|
||||
const XDest = data.read_shift(2, "i");
|
||||
const res: ActionStr = {
|
||||
t: "str",
|
||||
src: [[XSrc, SrcWidth], [YSrc, SrcHeight]],
|
||||
dst: [[XDest, DestWidth], [YDest, DestHeight]],
|
||||
rop: RasterOperation,
|
||||
s: Object.assign({}, state)
|
||||
};
|
||||
if(has_bitmap) {
|
||||
const DIB = parse_dib(data.slice(data.l, end) as PreppedBytes);
|
||||
res.data = DIB;
|
||||
}
|
||||
out.push(res);
|
||||
} break;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region 2.3.3 Drawing Record Types
|
||||
|
||||
case 0x0A32: { // 2.3.3.5 META_EXTTEXTOUT
|
||||
const Y = data.read_shift(2);
|
||||
const X = data.read_shift(2);
|
||||
const StringLength = data.read_shift(2);
|
||||
const fwOpts = data.read_shift(2); // 2.1.2.2
|
||||
if(fwOpts & 0x06) {
|
||||
data.l += 8; // Rectangle 2.2.2.18 (for clipping/opaquing)
|
||||
}
|
||||
const str = data.read_shift(StringLength, 'cpstr');
|
||||
if(data.l < end){/* TODO: Dx */}
|
||||
out.push({t: "text", v: str, p: [X, Y], s: Object.assign({}, state)});
|
||||
/* TODO!! */
|
||||
} break;
|
||||
|
||||
case 0x0325: // 2.3.3.14 META_POLYLINE
|
||||
case 0x0324: // 2.3.3.15 META_POLYGON
|
||||
{
|
||||
const nPoints = data.read_shift(2);
|
||||
const points: Array<Point> = [];
|
||||
for(let i = 0; i < nPoints; ++i) points.push([data.read_shift(2), data.read_shift(2)])
|
||||
out.push({t: "poly", p: points, g: rt !== 0x0325, s: state});
|
||||
} break;
|
||||
|
||||
case 0x0538: { // 2.3.3.16 META_POLYPOLYGON
|
||||
const nPolygons = data.read_shift(2);
|
||||
const polys: Array<Array<Point> > = [];
|
||||
const szs: number[] = [];
|
||||
/* 2.2.2.17 PolyPolygon */
|
||||
for(let i = 0; i < nPolygons; ++i) szs[i] = data.read_shift(2);
|
||||
for(let i = 0; i < szs.length; ++i) {
|
||||
polys[i] = [];
|
||||
for(let j = 0; j < szs[i]; ++j) polys[i].push([data.read_shift(2), data.read_shift(2)])
|
||||
out.push({t: "poly", p: polys[i], g: true, s: state});
|
||||
}
|
||||
} break;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region 2.3.4 Object Record Types
|
||||
|
||||
case 0x02FC: { // 2.3.4.1 META_CREATEBRUSHINDIRECT
|
||||
const obj: PlaybackDeviceContextState = {};
|
||||
obj.Brush = {
|
||||
Style: data.read_shift(2),
|
||||
Color: data.read_shift(4),
|
||||
Hatch: data.read_shift(2)
|
||||
};
|
||||
add_to_objects(objects, obj);
|
||||
} break;
|
||||
|
||||
case 0x02FB: { // 2.3.4.2 META_CREATEFONTINDIRECT
|
||||
const obj: PlaybackDeviceContextState = {};
|
||||
obj.Font = {};
|
||||
/* 2.2.1.2 Font TODO!! */
|
||||
const Height = data.read_shift(2, "i");
|
||||
const Width = data.read_shift(2, "i");
|
||||
const Escapement = data.read_shift(2, "i");
|
||||
const Orientation = data.read_shift(2, "i");
|
||||
const Weight = data.read_shift(2, "i");
|
||||
const Italic = !!data.read_shift(1);
|
||||
const Underline = !!data.read_shift(1);
|
||||
const StrikeOut = !!data.read_shift(1);
|
||||
const CharSet = data.read_shift(1);
|
||||
const OutPrecision = data.read_shift(1);
|
||||
const ClipPrecision = data.read_shift(1);
|
||||
const Quality = data.read_shift(1);
|
||||
const PitchAndFamily = data.read_shift(1);
|
||||
const Facename = data.read_shift(32, "cstr");
|
||||
obj.Font.Name = Facename;
|
||||
obj.Font.Height = Height;
|
||||
obj.Font.Weight = Weight;
|
||||
obj.Font.Italic = Italic;
|
||||
obj.Font.Angle = Escapement / 10;
|
||||
add_to_objects(objects, obj);
|
||||
} break;
|
||||
|
||||
case 0x02FA: { // 2.3.4.5 META_CREATEPENINDIRECT
|
||||
const obj: PlaybackDeviceContextState = {};
|
||||
obj.Pen = {
|
||||
Style: data.read_shift(2),
|
||||
Width: data.read_shift(4) & 0xFF,
|
||||
Color: data.read_shift(4)
|
||||
};
|
||||
add_to_objects(objects, obj);
|
||||
} break;
|
||||
|
||||
case 0x01F0: { // 2.3.4.7 META_DELETEOBJECT
|
||||
const ObjectIndex = data.read_shift(2);
|
||||
//console.log("DELETE", ObjectIndex, objects[ObjectIndex]);
|
||||
objects[ObjectIndex] = null;
|
||||
} break;
|
||||
|
||||
case 0x012C: { // 2.3.4.9 META_SELECTCLIPREGION
|
||||
const Region = data.read_shift(2);
|
||||
//console.log("CLIPREGION", Region, objects[Region]);
|
||||
//Object.assign(state, objects[Region]);
|
||||
} break;
|
||||
|
||||
case 0x012D: { // 2.3.4.10 META_SELECTOBJECT
|
||||
const ObjectIndex = data.read_shift(2);
|
||||
//console.log("SELECT", ObjectIndex, objects[ObjectIndex]);
|
||||
Object.assign(state, objects[ObjectIndex]);
|
||||
// TODO!!
|
||||
} break;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region 2.3.5 State Record Types
|
||||
|
||||
case 0x0416: // 2.3.5.3 META_INTERSECTCLIPRECT
|
||||
state.ClipRect = [[0,0],[0,0]];
|
||||
state.ClipRect[1][1] = data.read_shift(2);
|
||||
state.ClipRect[1][0] = data.read_shift(2);
|
||||
state.ClipRect[0][1] = data.read_shift(2);
|
||||
state.ClipRect[0][0] = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x0127: { // 2.3.5.10 META_RESTOREDC
|
||||
const nSavedDC = data.read_shift(2, 'i');
|
||||
state = states[sidx = (nSavedDC >= 0 ? nSavedDC : sidx + nSavedDC)];
|
||||
} break;
|
||||
|
||||
case 0x001E: // 2.3.5.11 META_SAVEDC
|
||||
states.push(state);
|
||||
sidx = states.length - 1;
|
||||
state = JSON.parse(JSON.stringify(state));
|
||||
break;
|
||||
|
||||
case 0x0102: // 2.3.5.15 META_SETBKMODE
|
||||
state.BkMode = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x0103: // 2.3.5.17 META_SETMAPMODE
|
||||
state.MapMode = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x0106: // 2.3.5.20 META_SETPOLYFILLMODE
|
||||
state.PolyFillMode = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x0107: // 2.3.5.23 META_SETSTRETCHBLTMODE
|
||||
state.StretchMode = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x012E: // 2.3.5.24 META_SETTEXTALIGN
|
||||
state.TextAlignmentMode = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x0209: // 2.3.5.26 META_SETTEXTCOLOR
|
||||
state.TextColor = data.read_shift(4);
|
||||
break;
|
||||
|
||||
case 0x020C: // 2.3.5.30 META_SETWINDOWEXT
|
||||
state.Extent = [0, 0];
|
||||
state.Extent[1] = data.read_shift(2);
|
||||
state.Extent[0] = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x020B: // 2.3.5.31 META_SETWINDOWORG
|
||||
state.Origin = [0, 0];
|
||||
state.Origin[1] = data.read_shift(2);
|
||||
state.Origin[0] = data.read_shift(2);
|
||||
break;
|
||||
|
||||
// #endregion
|
||||
|
||||
default:
|
||||
if(!Record) throw `Record: Unrecognized type 0x${rt.toString(16)}`;
|
||||
console.log(Record);
|
||||
}
|
||||
data.l = end;
|
||||
//if(rt != 0x0626) console.log(Record);
|
||||
}
|
||||
if(rt !== 0) throw `Record: Last Record Type ${rt} is not EOF type`;
|
||||
return out;
|
||||
}
|
||||
|
||||
export const image_size_prepped_bytes = (data: PreppedBytes): [number, number] => {
|
||||
const origin: Point = [NaN, NaN], extents: Point = [NaN, NaN];
|
||||
|
||||
/* 2.3.22 META_HEADER */
|
||||
// Type (2 bytes) must be 1 or 2
|
||||
let h = data.read_shift(2);
|
||||
if(h != 1 && h != 2) throw `Header: Type ${h} must be 1 or 2`;
|
||||
// HeaderSize expected to be 9
|
||||
if((h = data.read_shift(2)) != 9) throw `Header: HeaderSize ${h} must be 9`;
|
||||
// Version (2 bytes) 1 or 3
|
||||
h = data.read_shift(2);
|
||||
if(h != 0x0100 && h != 0x0300) throw `Header: Version ${h} must be 0x0100 or 0x0300`;
|
||||
data.l = 18;
|
||||
|
||||
let rt = 0;
|
||||
|
||||
while(data.l < data.length) {
|
||||
h = data.read_shift(4);
|
||||
const end = data.l + h*2 - 4;
|
||||
|
||||
rt = data.read_shift(2);
|
||||
if(rt == 0x0000) break; // META_EOF
|
||||
|
||||
switch(rt) {
|
||||
case 0x020C: // 2.3.5.30 META_SETWINDOWEXT
|
||||
extents[1] = data.read_shift(2);
|
||||
extents[0] = data.read_shift(2);
|
||||
break;
|
||||
|
||||
case 0x020B: // 2.3.5.31 META_SETWINDOWORG
|
||||
origin[1] = data.read_shift(2);
|
||||
origin[0] = data.read_shift(2);
|
||||
break;
|
||||
}
|
||||
data.l = end;
|
||||
}
|
||||
|
||||
return [extents[0] - origin[0], extents[1] - origin[1]];
|
||||
};
|
2
test/.gitignore
vendored
Normal file
2
test/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.cache
|
||||
dist
|
17
test/fetch.html
Normal file
17
test/fetch.html
Normal file
@ -0,0 +1,17 @@
|
||||
<html><body>
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script src="wmf.js"></script>
|
||||
<script>
|
||||
(async() => {
|
||||
const res = await fetch("static/image1.wmf");
|
||||
const ab = await res.arrayBuffer();
|
||||
WMF.draw_canvas(ab, document.getElementById("canvas"));
|
||||
})();
|
||||
</script>
|
||||
</body></html>
|
21
test/filereader.html
Normal file
21
test/filereader.html
Normal file
@ -0,0 +1,21 @@
|
||||
<html><body>
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<input type="file" id="file"><label for="file">Click here to select a file</label><br/>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script src="wmf.js"></script>
|
||||
<script>
|
||||
document.getElementById("file").addEventListener("change", (e) => {
|
||||
const files = e.target.files, f = files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt) => {
|
||||
WMF.draw_canvas(evt.target.result, document.getElementById("canvas"));
|
||||
};
|
||||
reader.readAsArrayBuffer(f);
|
||||
}, false);
|
||||
</script>
|
||||
</body></html>
|
20
test/node-canvas.js
Normal file
20
test/node-canvas.js
Normal file
@ -0,0 +1,20 @@
|
||||
const fs = require("fs");
|
||||
const { createCanvas, createImageData } = require("canvas");
|
||||
const WMF = require("../");
|
||||
|
||||
/* WMF uses ImageData -- make it visible to the library */
|
||||
global.ImageData = createImageData;
|
||||
|
||||
/* read data */
|
||||
const data = fs.readFileSync(process.argv[2] || "./static/image1.wmf");
|
||||
|
||||
/* create canvas */
|
||||
const size = WMF.image_size(data);
|
||||
const canvas = createCanvas(size[0], size[1]);
|
||||
|
||||
/* do it! */
|
||||
WMF.draw_canvas(data, canvas);
|
||||
|
||||
/* export to file */
|
||||
const res = canvas.toBuffer("image/png");
|
||||
fs.writeFileSync("out.png", res);
|
17
test/parcel.html
Normal file
17
test/parcel.html
Normal file
@ -0,0 +1,17 @@
|
||||
<html><body>
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script>
|
||||
const urlparams = new URLSearchParams(window.location.search);
|
||||
//window.MODE = "DIRECT"; // draw to an on-screen canvas
|
||||
//window.MODE = "INDIRECT"; // draw to an orphan canvas and copy
|
||||
//window.MODE = "OFFSCREEN"; // draw to an OffscreenCanvas and copy
|
||||
window.MODE = urlparams.get("mode") || "OFFSCREEN";
|
||||
</script>
|
||||
<script src="./parcel.js"></script>
|
||||
</body></html>
|
38
test/parcel.js
Normal file
38
test/parcel.js
Normal file
@ -0,0 +1,38 @@
|
||||
import * as WMF from '../';
|
||||
import * as fs from 'fs';
|
||||
|
||||
Buffer; // this line is required by Parcel to pull in the buffer shim
|
||||
|
||||
const data = fs.readFileSync("static/image1.wmf");
|
||||
const domelt/*: HTMLCanvasElement*/ = document.getElementById("canvas")/* as HTMLCanvasElement*/;
|
||||
|
||||
const mode = window["MODE"] || "OFFSCREEN";
|
||||
console.log(mode);
|
||||
switch(mode) {
|
||||
case "DIRECT": {
|
||||
/* draw_canvas automatically resizes the canvas */
|
||||
WMF.draw_canvas(data, domelt);
|
||||
} break;
|
||||
case "INDIRECT": {
|
||||
const newelt = document.createElement("canvas");
|
||||
WMF.draw_canvas(data, newelt);
|
||||
/* the copy_canvas helper performs a resize of the new canvas */
|
||||
copy_canvas(domelt, newelt);
|
||||
} break;
|
||||
case "OFFSCREEN": {
|
||||
/* OffscreenCanvas requires the size beforehand */
|
||||
const size = WMF.image_size(data);
|
||||
const newelt = new OffscreenCanvas(size[0], size[1]);
|
||||
WMF.draw_canvas(data, newelt);
|
||||
copy_canvas(domelt, newelt);
|
||||
} break;
|
||||
}
|
||||
|
||||
function copy_canvas(dst, src) {
|
||||
dst.height = src.height;
|
||||
dst.width = src.width;
|
||||
const ctxdst = dst.getContext('2d');
|
||||
const ctxsrc = src.getContext('2d');
|
||||
const imdata = ctxsrc.getImageData(0, 0, domelt.width, domelt.height);
|
||||
ctxdst.putImageData(imdata, 0, 0);
|
||||
}
|
1
test/wmf.js
Symbolic link
1
test/wmf.js
Symbolic link
@ -0,0 +1 @@
|
||||
../dist/wmf.js
|
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./js",
|
||||
"lib": [ "es5", "DOM" ],
|
||||
"sourceMap": true,
|
||||
"noImplicitReturns": true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user