From 682672db8ea93cac9c37f4919220930788ba2da7 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 16 May 2018 13:49:01 +0200 Subject: [PATCH 1/4] Change the signature of the `JpegImage` constructor, to allow passing in various options directly --- src/core/jpeg_stream.js | 10 +++++++--- src/core/jpg.js | 12 ++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/jpeg_stream.js b/src/core/jpeg_stream.js index 88a2d50cf..cc8070a67 100644 --- a/src/core/jpeg_stream.js +++ b/src/core/jpeg_stream.js @@ -63,7 +63,10 @@ let JpegStream = (function JpegStreamClosure() { if (this.eof) { return; } - let jpegImage = new JpegImage(); + let jpegOptions = { + decodeTransform: undefined, + colorTransform: undefined, + }; // Checking if values need to be transformed before conversion. let decodeArr = this.dict.getArray('Decode', 'D'); @@ -81,16 +84,17 @@ let JpegStream = (function JpegStreamClosure() { } } if (transformNeeded) { - jpegImage.decodeTransform = transform; + jpegOptions.decodeTransform = transform; } } // Fetching the 'ColorTransform' entry, if it exists. if (isDict(this.params)) { let colorTransform = this.params.get('ColorTransform'); if (Number.isInteger(colorTransform)) { - jpegImage.colorTransform = colorTransform; + jpegOptions.colorTransform = colorTransform; } } + const jpegImage = new JpegImage(jpegOptions); jpegImage.parse(this.bytes); let data = jpegImage.getData(this.drawWidth, this.drawHeight, diff --git a/src/core/jpg.js b/src/core/jpg.js index 7c29935b7..b85e1d0ed 100644 --- a/src/core/jpg.js +++ b/src/core/jpg.js @@ -94,9 +94,9 @@ var JpegImage = (function JpegImageClosure() { var dctSqrt2 = 5793; // sqrt(2) var dctSqrt1d2 = 2896; // sqrt(2) / 2 - function JpegImage() { - this.decodeTransform = null; - this.colorTransform = -1; + function JpegImage({ decodeTransform = null, colorTransform = -1, } = {}) { + this._decodeTransform = decodeTransform; + this._colorTransform = colorTransform; } function buildHuffmanTable(codeLengths, values) { @@ -1013,7 +1013,7 @@ var JpegImage = (function JpegImageClosure() { } // decodeTransform contains pairs of multiplier (-256..256) and additive - var transform = this.decodeTransform; + const transform = this._decodeTransform; if (transform) { for (i = 0; i < dataLength;) { for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { @@ -1030,7 +1030,7 @@ var JpegImage = (function JpegImageClosure() { return !!this.adobe.transformCode; } if (this.numComponents === 3) { - if (this.colorTransform === 0) { + if (this._colorTransform === 0) { // If the Adobe transform marker is not present and the image // dictionary has a 'ColorTransform' entry, explicitly set to `0`, // then the colours should *not* be transformed. @@ -1039,7 +1039,7 @@ var JpegImage = (function JpegImageClosure() { return true; } // `this.numComponents !== 3` - if (this.colorTransform === 1) { + if (this._colorTransform === 1) { // If the Adobe transform marker is not present and the image // dictionary has a 'ColorTransform' entry, explicitly set to `1`, // then the colours should be transformed. From bfc88ead66b0983fb3e79527c5af14a96989b3ae Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 16 May 2018 13:49:12 +0200 Subject: [PATCH 2/4] Expose a `Jbig2Image.parse` method, by re-instating the `parseJbig2` function The purpose of this patch is to hopefully provide *slightly* better user ergonomics, if/when the PDF.js image decoders are used standalone. This implementation is (basically) reverting the changes in PR 9386, in conjunction with code from the `parse` method found at https://github.com/notmasteryet/jpgjs/blob/master/src/pdfjs.js --- src/core/jbig2.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/core/jbig2.js b/src/core/jbig2.js index 9f833f979..272573cef 100644 --- a/src/core/jbig2.js +++ b/src/core/jbig2.js @@ -1192,6 +1192,47 @@ var Jbig2Image = (function Jbig2ImageClosure() { return visitor.buffer; } + function parseJbig2(data) { + let position = 0, end = data.length; + + if (data[position] !== 0x97 || data[position + 1] !== 0x4A || + data[position + 2] !== 0x42 || data[position + 3] !== 0x32 || + data[position + 4] !== 0x0D || data[position + 5] !== 0x0A || + data[position + 6] !== 0x1A || data[position + 7] !== 0x0A) { + throw new Jbig2Error('parseJbig2 - invalid header.'); + } + + let header = Object.create(null); + position += 8; + const flags = data[position++]; + header.randomAccess = !(flags & 1); + if (!(flags & 2)) { + header.numberOfPages = readUint32(data, position); + position += 4; + } + + let segments = readSegments(header, data, position, end); + let visitor = new SimpleSegmentVisitor(); + processSegments(segments, visitor); + + const { width, height, } = visitor.currentPageInfo; + const bitPacked = visitor.buffer; + let imgData = new Uint8ClampedArray(width * height); + let q = 0, k = 0; + for (let i = 0; i < height; i++) { + let mask = 0, buffer; + for (let j = 0; j < width; j++) { + if (!mask) { + mask = 128; buffer = bitPacked[k++]; + } + imgData[q++] = (buffer & mask) ? 0 : 255; + mask >>= 1; + } + } + + return { imgData, width, height, }; + } + function SimpleSegmentVisitor() {} SimpleSegmentVisitor.prototype = { @@ -2095,9 +2136,16 @@ var Jbig2Image = (function Jbig2ImageClosure() { function Jbig2Image() {} Jbig2Image.prototype = { - parseChunks: function Jbig2Image_parseChunks(chunks) { + parseChunks(chunks) { return parseJbig2Chunks(chunks); }, + + parse(data) { + const { imgData, width, height, } = parseJbig2(data); + this.width = width; + this.height = height; + return imgData; + }, }; return Jbig2Image; From 303537bcb162159dec3a87d78240bdc219b52afe Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 16 May 2018 13:49:26 +0200 Subject: [PATCH 3/4] Add a `gulp image_decoders` command to allow packaging/distributing the image decoders (i.e. jpg.js, jpx.js, jbig2.js) separately from the main PDF.js library Please note that the standalone `pdf.image_decoders.js` file will be including the complete `src/shared/util.js` file, despite only using parts of it.[1] This was done *purposely*, to not negatively impact the readability/maintainability of the core PDF.js code. Furthermore, to ensure that the compatibility is the same in the regular PDF.js library *and* in the the standalone image decoders, `src/shared/compatibility.js` was included as well. To (hopefully) prevent future complaints about the size of the built `pdf.image_decoders.js` file, a few existing async-related polyfills are being skipped (since all of the image decoders are completely synchronous). Obviously this required adding a couple of pre-processor statements, but given that these are all limited to "compatibility" code, I think this might be OK!? --- [1] However, please note that previous commits moved `PageViewport` and `MessageHandler` out of `src/shared/util.js` which reduced its size. --- gulpfile.js | 45 ++++++++++++++++++++++++++++++++-- src/pdf.image_decoders.js | 45 ++++++++++++++++++++++++++++++++++ src/shared/compatibility.js | 10 ++++++++ src/shared/streams_polyfill.js | 15 ++++++++++-- 4 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 src/pdf.image_decoders.js diff --git a/gulpfile.js b/gulpfile.js index acd104d4e..a70c04ef1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -50,6 +50,7 @@ var BASELINE_DIR = BUILD_DIR + 'baseline/'; var MOZCENTRAL_BASELINE_DIR = BUILD_DIR + 'mozcentral.baseline/'; var GENERIC_DIR = BUILD_DIR + 'generic/'; var COMPONENTS_DIR = BUILD_DIR + 'components/'; +var IMAGE_DECODERS_DIR = BUILD_DIR + 'image_decoders'; var MINIFIED_DIR = BUILD_DIR + 'minified/'; var JSDOC_BUILD_DIR = BUILD_DIR + 'jsdoc/'; var GH_PAGES_DIR = BUILD_DIR + 'gh-pages/'; @@ -70,7 +71,7 @@ var builder = require('./external/builder/builder.js'); var CONFIG_FILE = 'pdfjs.config'; var config = JSON.parse(fs.readFileSync(CONFIG_FILE).toString()); -// Default Autoprefixer config used for generic, components, minifed-pre +// Default Autoprefixer config used for generic, components, minified-pre var AUTOPREFIXER_CONFIG = { browsers: [ 'last 2 versions', @@ -96,6 +97,7 @@ var DEFINES = { COMPONENTS: false, LIB: false, SKIP_BABEL: false, + IMAGE_DECODERS: false, }; function safeSpawnSync(command, parameters, options) { @@ -306,6 +308,22 @@ function createComponentsBundle(defines) { .pipe(replaceJSRootName(componentsAMDName, 'pdfjsViewer')); } +function createImageDecodersBundle(defines) { + var imageDecodersAMDName = 'pdfjs-dist/image_decoders/pdf.image_decoders'; + var imageDecodersOutputName = 'pdf.image_decoders.js'; + + var componentsFileConfig = createWebpackConfig(defines, { + filename: imageDecodersOutputName, + library: imageDecodersAMDName, + libraryTarget: 'umd', + umdNamedDefine: true, + }); + return gulp.src('./src/pdf.image_decoders.js') + .pipe(webpack2Stream(componentsFileConfig)) + .pipe(replaceWebpackRequire()) + .pipe(replaceJSRootName(imageDecodersAMDName, 'pdfjsImageDecoders')); +} + function checkFile(path) { try { var stat = fs.lstatSync(path); @@ -631,6 +649,15 @@ gulp.task('components', ['buildnumber'], function () { ]); }); +gulp.task('image_decoders', ['buildnumber'], function() { + console.log(); + console.log('### Creating image decoders'); + var defines = builder.merge(DEFINES, { GENERIC: true, + IMAGE_DECODERS: true, }); + + return createImageDecodersBundle(defines).pipe(gulp.dest(IMAGE_DECODERS_DIR)); +}); + gulp.task('minified-pre', ['buildnumber', 'locale'], function () { console.log(); console.log('### Creating minified viewer'); @@ -641,6 +668,8 @@ gulp.task('minified-pre', ['buildnumber', 'locale'], function () { return merge([ createBundle(defines).pipe(gulp.dest(MINIFIED_DIR + 'build')), createWebBundle(defines).pipe(gulp.dest(MINIFIED_DIR + 'web')), + createImageDecodersBundle(builder.merge(defines, { IMAGE_DECODERS: true, })) + .pipe(gulp.dest(MINIFIED_DIR + 'image_decoders')), gulp.src(COMMON_WEB_FILES, { base: 'web/', }) .pipe(gulp.dest(MINIFIED_DIR + 'web')), gulp.src([ @@ -666,6 +695,8 @@ gulp.task('minified-post', ['minified-pre'], function () { var pdfFile = fs.readFileSync(MINIFIED_DIR + '/build/pdf.js').toString(); var pdfWorkerFile = fs.readFileSync(MINIFIED_DIR + '/build/pdf.worker.js').toString(); + var pdfImageDecodersFile = fs.readFileSync(MINIFIED_DIR + + '/image_decoders/pdf.image_decoders.js').toString(); var viewerFiles = { 'pdf.js': pdfFile, 'viewer.js': fs.readFileSync(MINIFIED_DIR + '/web/viewer.js').toString(), @@ -684,6 +715,8 @@ gulp.task('minified-post', ['minified-pre'], function () { UglifyES.minify(pdfFile).code); fs.writeFileSync(MINIFIED_DIR + '/build/pdf.worker.min.js', UglifyES.minify(pdfWorkerFile, optsForHugeFile).code); + fs.writeFileSync(MINIFIED_DIR + 'image_decoders/pdf.image_decoders.min.js', + UglifyES.minify(pdfImageDecodersFile).code); console.log(); console.log('### Cleaning js files'); @@ -696,6 +729,8 @@ gulp.task('minified-post', ['minified-pre'], function () { MINIFIED_DIR + '/build/pdf.js'); fs.renameSync(MINIFIED_DIR + '/build/pdf.worker.min.js', MINIFIED_DIR + '/build/pdf.worker.js'); + fs.renameSync(MINIFIED_DIR + '/image_decoders/pdf.image_decoders.min.js', + MINIFIED_DIR + '/image_decoders/pdf.image_decoders.js'); }); gulp.task('minified', ['minified-post']); @@ -1138,7 +1173,8 @@ gulp.task('gh-pages-git', ['gh-pages-prepare', 'wintersmith'], function () { gulp.task('web', ['gh-pages-prepare', 'wintersmith', 'gh-pages-git']); -gulp.task('dist-pre', ['generic', 'components', 'lib', 'minified'], function() { +gulp.task('dist-pre', + ['generic', 'components', 'image_decoders', 'lib', 'minified'], function() { var VERSION = getVersionJSON().version; console.log(); @@ -1228,8 +1264,13 @@ gulp.task('dist-pre', ['generic', 'components', 'lib', 'minified'], function() { gulp.src(MINIFIED_DIR + 'build/pdf.worker.js') .pipe(rename('pdf.worker.min.js')) .pipe(gulp.dest(DIST_DIR + 'build/')), + gulp.src(MINIFIED_DIR + 'image_decoders/pdf.image_decoders.js') + .pipe(rename('pdf.image_decoders.min.js')) + .pipe(gulp.dest(DIST_DIR + 'image_decoders/')), gulp.src(COMPONENTS_DIR + '**/*', { base: COMPONENTS_DIR, }) .pipe(gulp.dest(DIST_DIR + 'web/')), + gulp.src(IMAGE_DECODERS_DIR + '**/*', { base: IMAGE_DECODERS_DIR, }) + .pipe(gulp.dest(DIST_DIR + 'image_decoders')), gulp.src(LIB_DIR + '**/*', { base: LIB_DIR, }) .pipe(gulp.dest(DIST_DIR + 'lib/')), ]); diff --git a/src/pdf.image_decoders.js b/src/pdf.image_decoders.js new file mode 100644 index 000000000..d7581e830 --- /dev/null +++ b/src/pdf.image_decoders.js @@ -0,0 +1,45 @@ +/* Copyright 2018 Mozilla Foundation + * + * 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. + */ +/* eslint-disable no-unused-vars */ + +import { getVerbosityLevel, setVerbosityLevel } from './shared/util'; +import { Jbig2mage } from './core/jbig2'; +import { JpegImage } from './core/jpg'; +import { JpxImage } from './core/jpx'; + +// To ensure that the standalone PDF.js image decoders have the same +// browser/environment compatibility as the regular PDF.js library, +// the standard set of polyfills are thus included in this build as well. +// +// Given that the (current) image decoders don't use all of the features +// of the complete PDF.js library, e.g. they are completely synchronous, +// some of the larger polyfills are thus unnecessary. +// +// In an attempt to reduce the size of the standalone PDF.js image decoders, +// the following polyfills are currently being excluded: +// - ReadableStream +// - Promise +// - URL + +const pdfjsVersion = PDFJSDev.eval('BUNDLE_VERSION'); +const pdfjsBuild = PDFJSDev.eval('BUNDLE_BUILD'); + +export { + Jbig2mage, + JpegImage, + JpxImage, + getVerbosityLevel, + setVerbosityLevel, +}; diff --git a/src/shared/compatibility.js b/src/shared/compatibility.js index 5a4911357..8a731a7a1 100644 --- a/src/shared/compatibility.js +++ b/src/shared/compatibility.js @@ -138,6 +138,11 @@ const hasDOM = typeof window === 'object' && typeof document === 'object'; // Support: IE, Safari<8, Chrome<32 (function checkPromise() { + if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('IMAGE_DECODERS')) { + // The current image decoders are synchronous, hence `Promise` shouldn't + // need to be polyfilled for the IMAGE_DECODERS build target. + return; + } if (globalScope.Promise) { return; } @@ -157,6 +162,11 @@ const hasDOM = typeof window === 'object' && typeof document === 'object'; /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ (function checkURLConstructor() { + if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('IMAGE_DECODERS')) { + // The current image decoders doesn't utilize the `URL` constructor, hence + // it shouldn't need to be polyfilled for the IMAGE_DECODERS build target. + return; + } // feature detect for URL constructor var hasWorkingUrl = false; try { diff --git a/src/shared/streams_polyfill.js b/src/shared/streams_polyfill.js index 7c8e85d87..962a26f3b 100644 --- a/src/shared/streams_polyfill.js +++ b/src/shared/streams_polyfill.js @@ -31,6 +31,17 @@ if (typeof ReadableStream !== 'undefined') { if (isReadableStreamSupported) { exports.ReadableStream = ReadableStream; } else { - exports.ReadableStream = - require('../../external/streams/streams-lib').ReadableStream; + if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('IMAGE_DECODERS')) { + class DummyReadableStream { + constructor() { + throw new Error('The current image decoders are synchronous, ' + + 'hence `ReadableStream` shouldn\'t need to be ' + + 'polyfilled for the IMAGE_DECODERS build target.'); + } + } + exports.ReadableStream = DummyReadableStream; + } else { + exports.ReadableStream = + require('../../external/streams/streams-lib').ReadableStream; + } } From 5984c24415c5cd793221799ef356e411c06100c2 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 16 May 2018 13:49:33 +0200 Subject: [PATCH 4/4] Add a basic `JpegImage` example using the standalone image decoders The image was "borrowed" from one of the examples found at https://github.com/notmasteryet/jpgjs/tree/master/images --- examples/image_decoders/fish.jpg | Bin 0 -> 22306 bytes examples/image_decoders/jpeg_viewer.html | 40 ++++++++++++ examples/image_decoders/jpeg_viewer.js | 75 +++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 examples/image_decoders/fish.jpg create mode 100644 examples/image_decoders/jpeg_viewer.html create mode 100644 examples/image_decoders/jpeg_viewer.js diff --git a/examples/image_decoders/fish.jpg b/examples/image_decoders/fish.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa5fbd0d96a58150b800984bf87d18ff50d5c1a1 GIT binary patch literal 22306 zcmbTd1y~%x(l$EF;_mM5?i$?PT^D!P;BLX)AvgqwKybI<1Px9gKyV0z2U6aYlq0nq>AF?jv{ORp{emHyW^%sT*p zd;JB?&dI@E{6Cn3oxK?J&sP5Y0f1tt|7isTQw;te3{1Yt0e|+8_2eZbP1Q71W#pBl zU%L+g$Z|?{&Mr{s0N~{8>8>FwL9VB7Kn^zxKmiB)!p;;Hq~CaxvU@_majPC6+8NPy8bP{e8t!n z4u3J!U;SXazjp8yGcQxw9Vt z{PUjwN&%REbxZ!LWL^#dUS1YX)>rlaoBp?z|K|GNgTG_@PmUY)e~cLjcltkN|B?Mq znM)}E@ZY`a=G}kFEOG##BN71c*Z)&ST?_zd5dhHs`M>T5_piO!dV0DFva$L2_^{eR zELs05^xyRV$nbB@|26op>#_d5-oJH6E(NhR_j2?k|Ep9>7e^N_cXAI`b4v(0%m3Ag z|9=bqSGWGFAIusMYlu6<`SnsduU2N~Z1XzY&Q^Awb}r83cFzB+5&r)v_FsMY3;$`? zuK=t3H-K%*3ZQ?+2Egaj05ld705&RotpWXe+>{V>fWJ?kF4^%v?fwy=W zzy5^su(KimTP&%eMQ-Wk?)?|PJ`;aWFaRQe24DjO04YEP&;u+0C%^{?0dD|lKmkw% zv;cj;1h52b0Vlv6@BspWP#_A32i^f0KrT=Olmpd31JDd~06o9}FbYfobHFmN0qg*W zz<1yhxCfqJ-7Opl1%w460Fi-cKujP`5I;y1Bn?smX@K-WW*{4oGsp`R2nq+qgHl1c zpb}6us0q{s8URgz=0R(qUC;^W8uSDOhC+tIh9ZHYg<^x^hZ2WUgwldChO&WjgYt)p zfJ%bOhAM@sgKCEwfSQ6@hT4Jp4s{0xz(`z z0DlCJffvC$;4|<~Xjo`0XmV&4Xd!4hXl-aqXgBD$&VrU@~Fe!?eMSz%0WY!ra5c!s5cx!Scb%!Ro`>!v?@6z!tzZ z!uG??!|uc0!NI{1z%jvzz^THS!+F9*!R5f!!S%r{z#YOp!Xv?x!E?gPz#G6j!-v6V z!q>p}!Y{xd!#^XSBhVlSBB&x*A^0IABa|b2MEHzwi13Vvfk=laf~bXPj~I%Wjo5%V zg1CuzgM@@ci6nrej%0@vf|QN)0cjj*7wIQ5CNd+kB(f2*7jhDEC2~LV8uASa3JNXC z8x#W+Pn0B-YLp?AZInk;EL2uhc~mRZVAMR+cGLyb3p4~Y8Z>b<6EuIcOtcoX&uC}p z2Z_)G6yU)(qc+uT46?DmSGNI?qh+m zsIVllEV06|%CSbU4zXde>9FOo?Xlyr>#=9CFL5w&xN&rFd~xz{KH==(f^lha<#8Qx zlX07IS8$*3Nbtn*Ab7EO4R{N95BNm*Z}1`barlk+%lJV!Uog@hx7XGGXULPVBCaYW5T>%>sRjKmtm0mNm*)5Lcqq$IK=t|U1mLnLRUxTJ4L z?Mc%~dr42ou*gKo?8wr|`p8bnamdBV9m%uFhsiG~h$-YKyeLX2W+|R2=_s`+!zddm zx2O=Q_^GU^(y0ciE~&|=m8k=%YpK_15NP;mY-zG+#%LaB>1cInqiH*6ztQ2-$F^ z(~>ifbBPOuONJ|$tCQ=Bn~593UC8}~2a89UCz@xF=Y^M#*PXY4_k@p{&x|jhZJAp&FrY!gRtA;d0@95lRtrkrI&|QF2i; z(PGgZF$ytru~MsY0XTtkR(hsLHFRscxy!sky0jtHY|RspqL5X>e!+YK&=OX_{)* zXx?i{XeDcHXwz$ZX%Fh4=@{u$>pbX6>89%L=&|Vq=}qYq>f7me8o(Lo7`!*QGn6*W zFg!HkF^V!;F{U&2HJ&gbG;uWfWQt~LZrWl7W2S3XWA@8j)x6aF&O+WI-{R6z(lX2P zyVV=3bgL7H2qYD9Y%OA)YW>Yd#3s$=#8%8U)Ar0x(k|ET%3jXC$o|1W)uGbikE5<* zqZ7Q7nNycDrn7_dunVb+ugknElWUagj+>xchTEmPqWgOfkcY8HrziI7WNO-r-Ye2; z*IU#(&->9w$EU>?!`In&+K0huFpwfJG;lXaJgDR?=&i-u!C1?oq4JLeWJr&@px~v$0&U zIkCUvtl}o)+2b?gewUGs(FSmmRHtJwo-Oy4t37EoL{-NxyyMHd5!tR`LX#A1y%(Mg>MQQ zib#s$i=K+@iocY|mb91BmS&d0mU)*QmFtv`Rq$6-zbAYj|Nf=YxpJpUqiVRCue!R1 zxF)$4s@ALaq|T^ru3oCXvw@|dq!G6<{sZvA>%(c2S<_0hV)I}Ne@jCvO>2G|R$E*< zsNJvqvctAxr&GUkzDuEN@T1VjwrCMSJDTs?qAs7ppJd)*RQa*1gxCH$paH zHsiL?w=%bhw@be=d~Mv}-TAaDwL871xwo-zx&QsZYlKc7XNW1Qz-&|kFvc=KcWQt$Hc%Khrkb;1q7P30~3?ckl--S)lH{qsZI zBf(?UPyU}{Pr6UX&pyB4er3NfynOtv_Mw!*afX5dgJGe;uLK7J z0}Trg2M_=EgMf&Fgn)pI2oH~hhJ=iQ`br2$=oo0I7=Q8KM*fPwO2I-y!=fUf2mh~m4KxfC7#0A*!N1lLf?jL?21EZQ zFf<4X27vvu4j{h@L7{`uUnfP^n`|{dXPN~Jm71fug41u2!lfc%r8G|nly=|HrXu2? zdbv&F_@89L2qoR&p2pkSy5iDm^z5%RcgG)C1_F2PhsF+FsLw^tEB?)KfnPP#KSpOU z>>WP{r{&1F^KUf~ucOIr!27kp?Ws#6ty=9y+m|p}cMUFQ8AN5GdVb1OcCT)E2O`Z) z39s61DNH;jT0O;zoAGpYVwwr+KK@}pikDbPZ>r3Gz+c^wWMnDvzQJQ~ybHfQs=3TG zze^vB9I|KmA0|^ws(yg1A5maC7QHJgwz^xR&d%`SpM(1B>~!ikdC7cNWHO??cVLYm|A8OZ7hQ7|b89s-hi=~0oW&#j_76a{F%Y=Y4Yw}* za}~~Mos!u|e;Y@`pe_PxDKD$9bm`M^+-#||G#S?zLXp$;uU#bX^#mjBBAL&JC2!|O zRIL&G zlRffGhakZU*JZO*F)?c=%0Fiw=DGKN$^BZ9^x+nn#z7 zdcrG^4SN;C`HCBur3B{`7gB=7q4GmZuFo$0DudB!(*4w@-hIsd$pVca)*j>K^=)Y- zr-=mE8Y#UbzA)5|Zh~#-RNkV_IIGnkyuK`ZmxkApaouuK)kg4Zigbn}QeVw6PI)w{ zdiXkE4$k2SEfc~3A75p;xH)E-8J1xIT=Lh{Hsvw{&6P~LLiJzFGL4gMg`6vGPyrHj zE^TUbxA3VhfB({`ZyvR{wkVE%C)^Ztf|DoRqrEQjd;)Yt$z1Tpqke;lcxY720Zi<% z*zdraq|&mXlZDVH-+Xwqu|H*|rh5kE*chu1v>0pdr#4uZ}v1m3s4n)_h@w0QK*l_`+wEy%s>`-NRS6ygKy_$L27knxO z!a&{;YIsv!FXO4s?~VA`Rky2l2Y#z`q5P_~hFuT%bsr0YaBq4&gI}EqD>?uLf`0Xb zFktAv>p!oK7%uv8=1fG^gg z6_k-4$!Lg=sV8)3rEo4dqx$$QlPN|zQ(+i_&v_UTQu~3^#~k85W$EBxgSg#)?JnOb z78FEJ_N2F_7~~^{fYMf5A}@yHKR=Fc-Jm6j;898BXdG_te8F%bJmNaJ71=;|(uT@O zE$FQ?CS!(Mf4<)ur<8-E0&SsP_!)bA{H_S+4zbHYX`_sI-L%?7a@Y3?-bok3sC{pH z#+lW>#5UVDZ8u&f2dU)8f_0uhbx1UK(VOkPyOGfVx`K*SCEY#p5_vS;c?*~51MSWi z1if1#VfEYAV~&MI7TL40Ul27U==w zu0KG+W>4zX!rGN+)c3Zs6cyCTz!#l6-sa!h$2)~n4wCWXUsXtVj_!Ng80GEW7EfoM z)l#pj*Kmh!%YTuua#HG@U9cCA4&`Jyqv?qv#a-AOLWb&>s#hG@-o)E;Qm(s4kKcG!px2%h;A8(+KfV;@8RO1t$^;k-)rGb(XD`td5R! z=6Sa(x0&eJJ`aE#g?-;wZbzE2hJAk6Y+7Vt#$iI)!unHJ_Yvy+ln1q{L6IbWrtLv{ zGp4Kqa&vtj;phcFME*Wb5tUal=7QYA!?7+unT@y+5{Exfk;X8d%cqMzgGW!IAQiZ6#;#keR6{V3p)RZ&8%--9<+#vyFJZl9YHvAa#!) zyQL&oj-Z@H=8yn-y@#)l@_b0sHz1ZG_+%(yMW-!@^{Y*Csyej0L1!*%3pevMOn-Y@ zs^8;p0+wXs1Y!?ka@zg&Nugv%th-HlhD=4I%BF+EZqA;p;8Lz%B_}f<^V18t`{d2q zyE=bH8HQ%1>0LMTZvg`B%$#a?J#Xc>I+cRpNN5`F{EAkSOTsS*AmhPVu z{t)NcwdNzR%V6#@Y?Pr!t4DZ>=;*CX>Bz*e{4V6M{H$@ZU(k^3mvQ)ZO#HN5!pdY& zFD2266f#-JbAv{MB=kl2EXZDP$JVYyq*0K%PkkT2)< zW|5e2EWJI8H_NTYcxcaAx`l+*ZAE%him_`V|Jk=2!$+8tbnT*aewzcVd_yX`A`j80 zOVuIl9b~3YxM!A8Y^Z~3pfa0MLgK~!G}5y_Ky{K>k+q=+Fa54;N%F`z)!BhtFJ{d^ zC*HA(Iybd+tFBQBRw``kD&Uo&hNX%I z+O?)@sbs-}VP7We(QR~ZRGTNuP$<@$6?nNG3ym$p4>yQTvdIjV~ApY%V&pgIq|tq1b=`rBRf9k!L0Ox+U`$Ko8nY&XgCZC$~v4U z*6E8wS_E8j1mYNmDb!D)?P?K}=^Bk&Q^P*v2ZZt*ZTZ758r$RX0c&{C2Yu677-r7SO$Cn-8Szc?fK*~q5gM0J`< zGM&BFvQ!?v4u#bQ^GFsZ>HrQf;dChP+L!KtN3ZmH~SX$f7x?y@v0Lg#Kzq)(WbQp3O0ndVi(jkzuTuzmjDN&ZASl>>M=T&H3@|1%1qZq~ z3>G$}x*I2#g!w-v53g~H7-+xAkP{L-f^nwKBN;MRww6hPZ&UW3?t8(Mq0#{DPL4IA z!gQBIxhw&GGp{f8yKh4}cGiJx6QkZ$N^enR@ABU^HOkE{qZ@Wxog!e^T22ZetfmEE z_kqV>rcG@NS|B!5Jyt>Sl)ZZbMtu|9TUZA7s%$%&5Ytq)-p_Mer71oq^=Xm&6ubVG zk@!YdF+;(0Y>er5$)QXTD* zBdeQQp7uOa0hsg?*jaD0E#;ag6uQ|f57rA}Z}}iBQIT&)E3n-zg`*U{vU-87(T>;VQ$EJPKK<)W~DYBgM&k4s#`1Jkx%lgrooBlL> zl(Q4^^C|Y`MKY+Km-jj0`>tu#HF!+OzvMEFFftcXiuO|!jcjMKX!V_Fh3E?xuI=W^ z>P9SXYps2GWzw0`+`VOQrLomb3WJ!_9sdAfHWqRR2N`o0@=^&HQUR2_>XMWniEbZ6 zK8d}W3gcf>0YSeW;J+J;f9Z9)i2;pC!9fXwjwP;+O~r{rPHpb?4`YG)Yb;CL`)R~v zxEK1DFHK9Oxlh<8$Yu>v4PUe^w>K=ZW}p;Fo;eG2e<~m^4KVBy@Mw%uYU*6*Gc-i( z8ERPS`O>WsQJT`UX5^`tu9)w1qcWN-Sn97z=T_#SBGpcACqDC2SU>Z=#D^+drm6Gg zXWEzl0btlqZ^$!;+0B{df$y?ns%QN%Zyw3r37(X9aT8T zQ4=Zz6dm24T#wXOQ`|YJ6BZLRG391I=o@I>#p$uswHpetbd`RR$YAK5Vsm(NQUhX5L}!*vi;dn+qq;JQD9YQ9T@&dFn&BxY+N~P{ z{7wVPTv@ggl~cH6@Q-~i(2Z@v*fN@Daq9^gmolh9gt^Y+w>-b{O9=__+*sc84>GAY z^w?zbw(P|)^p2nGJu@fiP}&vO8pSQAz7Tt4$}o8SxV2^5VkV4?{skK~=lc3min1`AXs=jOf< z*5vv$=P?Ra;u=UX9H0YiD4ijlq||JfwM`$8VjmAW5$Ec@wVF7h(0PU8(XJ`q!CgZm zP1%A!)9B64uT31dF|^{JQiqS_S6%+_RN`)+6?}6Lg~yT+4zup>p)rr_EbhC-lv0hE{9Usc zyyLQQ#vh}L?mJX=*BSjt4xJAbxevV6!d0o^wC09RwSEUoD_km1<1q4m)zVi?xKQ`? z=6ly`#Z$hEn87I8VJmhRj*c@kk5PEUnWwmI44|mhZoGxa{Q(eNziNlfkjo^BfAWfS za>jkxKlx7dM0qNbz5<~L630)f{#29bRwo!Sk$@d&uta*=fI^c?0LAg+C*tHV+D9%Z zv*-7ZO4ubI_C+oDP8c$NDYzOLI$nX7+$TNGVKeA^EK6~uXdZN%Pt z*~QijUEr;cirGyPNih9Vico(9BfpLpK1)zi3@UU0_9RZAgAubs-fib@`E7l88DPCH zO`+fgPo$ETvm7VopvI4~y%ceTvI}X$`xwCxiks|TuJISP^#8LPMxag(8g4t03aS!LZN*KlF|jq6B0?!^wc zT$njV<%O*bg|_d$%RX*|)pL=apnOjq*w6?A3)w)vR4;>0nr_Fg$ZB=YPJNVvJn8iK z8sW1t*peFo(J?nld9jSosb_mtzZqj8f=#^JtAORb)~UA`x_Z@uwBRhv+3eK|R`WdA zYb^%H?mmhPeVaqDH5sB)!u{o;r*o8$MkmVGAagtpIaCAgXm0v4L$RF>F{Lb?&{V1Y_?28yUpNr?viDC zv>fe{-S=>mK9eX`o%tZbdTp`X$3w6FO<=F>4K4@+dH7@F1u^4zP81WqO-B-JH4(B60_KG@Q zHbB6C>MCaWSzdXtc(9gE$X{W#UrF;S`2IPMO zsh{oqlqEP)UkPYq?^8LEHn@5h?lsBm$g`nfhAb*_v!~)6vB@#(I?jkpLP&R_6X%Tb zl{4I3CDK@cLjJmH9)666MzFS(kZ#GFSC)x%B7+Qf$!P}hu-ItzGy-ynL6fjmy(dihM1qwoAh<3kUJfJT|sQRm0==IXG=%S-60ADy(VKfMa8 zo>=N5orW1%-rEZ-6VaZhNfy*xo}iHo&0c?-L?f1jplIkwFe6rQj`2?!jj3h{yrlTV zOBs$qHnS{Hy924!>ODtv6(&X$~dW5(tW5O?eXK=81(l{rfX5~tl|Eik0KHXf9hjj~r4mRpd zNm3v&FO26_j2fvLjp;YW4nt~(@64frJyl@0##q{{7cAaD->Jb{?b%68YA?wlOS{Bk29H?x$eI;w!bEW$Ry9<9x3r}AB&ZnPGg&QCwj6jU+NSkKOAyfEr>1de(fxH=SB;z{6a^7Vn#@L zo*|7px$BPNT>);Im%_=fBRBf!ei1p6nt$rVitM=bt!@uTGd#Qjb=iVwnE-fa&fS~F zC_crA0E4dg<1jJo@@ZT+OM1*r)ppVOYAdXOGnbD7_6$0Ghx?$IK&TGQlWoaeqF$Bm zwC3KYIjIce=-~tjN}!Q*f>5;?U2C|$i0TiJ4vKMzD;kh8a^+ga&p8&5!cPh)C8QLn z>XL_*00u_+bmb#~Vm=eQ1*8J7H{PlK{G0R^ctfJ$xNMTXbA-@5kiOXAckxxw<# zeu_F`ADR&fCP5pvAA+zTYtEBvlhMSUM(l_w6r0PHi_1u@LJdUJ5MEDpfj4PB)b5oK z&H~}~A1B+S`(ie4qG`ZdV3U=Yy>vSbk9^P`;|8R=cy8QwFw6B?*YdIzv!{j7Tzpr? ziosjuJGds#>^dWqW5WGqr9Y!Ow3G^U@`kN7QLD};n2{=%4Bj>bYBEcZLJPw-fiXOw zLv0me53ym&8WfOmZ7Y)I?9Gqxqo_7yTgD3GQ4?U;{6$DlaXN}|3JFCblpffHeo>k#%rXYZtfPE zQqnXqzk7-PKY;;AjAC%%GC_!Flrk}S(-5tg!rWS9>6xLoSn*AzZhf#|djGW0YQ03{ zPLKM|w;_6+m<%gq@gMX;n;$9@6uXl(0n-?RYwM3uHyMv&Ss21D zwp2y5l#UCp{#jEkThWjX$+ud9vwI-t9SR53Di0F9iO(NE_ekG{XP|*5Ntm`J)B4VX zp($}L@PXa_AfCQQOv;(VTcNX3qm7MVn5D2BF+?@TTSD(T;;kAtj#m3-|Dhwi!%^Lq zy=U*7Qn-8Dn6Q?@8p z*)w>(I4+-Y_<>_-BXYlee$f6WG5G@mjh-=eSp?RQ_Vf(IwXy)^m9n-&yC@z0yRe^< zM+jEEWh_4;-6- zH9lg5!K|N=_S&YWVdS{UqHY`SwFUDYt@$mG**umO3aBpF+^N_TQ5Ejk-1joAx0*Ja zDDb=wrEP<>YC_N1^zlM$RNYt;1T$KEmswGdG}d)Y(Ye9g9JpR{Y(Eh+y8QwvXM9jW z3@3erbr)=IJk|wwfzn0$G|NFg=Sm3sGSio zD&*D`{S4^Jwg^Cwi?#Q39%H&qb#v6m-h;VG9dYw#x*D8Se`EFU#U%%&{;_$DvkEd5 zhsWpXytkZ8XWTChOze}n%^P#y`7?>ScWk@ac_i$!mn`%?kY?sKq#_B3Bm2F-tHiu) zN^!J5W1PKkLGdvI2Zh6JR=#yM=ARjfVLAcut z_jDe17$sL9Y_ArHv))^hgVPFP7oNesV+k~xNj9RBx7M+g3?rV<4a_1K;MQY$z*fZg z;mQO}ctmONK|kdwNo1?(${e|25$eP|v-72Nq=%P_o0?XOLexL^!aQV@hfX);blawD)4n3-MTNK!Jyy=~AqL?C+){~QhXv^ToU{a#4q5!*!=E+5 z6$tudj=a_6eU#9ynPTXgR5v3iN(CjQJ%to*b|$NQK@BXlcG#gskqe`^ax$op*pqlL zxmPDOuc}2!V3=$@Bs_M~6hjFtkQ)D%U}vzH7~nzTS65Sms=~Q^6Kk>yhZm*v z@EEAXgxgjaxr(RJQuFP1l92wJbK_CN1B2(3&6RT|MWLxvDIIB8r{g0l2MTuoGUd_1 z$V7)3g-0Le53C$84AUk%>$;qo9WNs+QrB>FO19FZrds~V+S77sRbDu@w!`)yt$uUU z0E9Tki64BIL5J@GPt=DUIlhFZ){oS;Uvc4vghYHhpFlU*S^xTghIfcz*6^YGYt?cO z>YkCOArS_NVH0z!!rp#yu!v@K{qVw%jY5w5=h1wJ9>O68vC8K2J~0KVAqV&KxAUSO zvDo)XX%HGIj?}^_{GJB*=kA@kv<#6>ZHUw5A~-+hiHn-SieeM>Lg?`Wq$(VP<#a>M zxRt!AoupJTn!m1df4_lQl<&ana8IGKTIA78G2(z79WZ=q{Z*QJqs!zBi?lUFmB!p7 zj!QG@5@V+pa__P&ujt}zBBxzD5T*3?X^b&I{G{N>hq(lYx|z@!KDuhzBgmj9B-qJkZOQqts+UD<$*3 zgI=b5!+kh&-HTNv3G=%4ko%MHl*VVLMzpJlJSN`-nwgUXa*k4#IZG@*kL*iw{7+IG zN2r$3PFqh;lR9X&CM7qHKKMTRzN}R5nIB1QNUIG$&F^%@yw~^jl6J$V{J|?b#korD zGX>S5I}fi>ccIVYvpLN)fr6UCgI@9DLGbvT$S~cw;o&aR=0Vm$ex5NUn7WuDa81*s z@sN1l!re9KH&tg_Xi2{WbpU_dEt9%aiC-+Ch>S#)G705}l2a@AI1b^Q z`48vxr~#4j?emhgzfx55%c{eMx0H}}c-W{{d^2J2z;txW9G;>yF~Y^~Rr!B zU~cd$u6&QZJYoyqQ5-Q*59G4okQGi=*Vic2MR(EOy_w3GGb0R3K{~uJr?B;vtgkk9 zMZ6};G&?nth5xanPULZU?f&baip_dRGQ zmqvh?^|01d!e~Q42CQfJ!6YZDq}u~2sW>wT!4*$P#o@zILxT`Mf1>`ktd%)fB(1uB z_Bk=%hATtH%AA|5OVbsLM-D3m$-UzpJA=kkp`SZbU%zJW8cj13T@+XHEI@8eh7tq9 zTR$MUs5lp?kLV#qw&Yh7Kas;8M@}N)rdvEk3#Q>%cl~VFsK`ibKGin~*PmY+r{0#E zXeFH8_bfee*euOGEuBZmh2Pi6+BJ)Z_RmP7QOok3ElzkQ*Nwp5YC}Z+0Bgiw{qFU@ z(*mlSXjw)-`6#)W46{E*&;2*~$-YM9vl413HWEWEm#BRrriB&}`y|M38pafHu&Sf` ziE3LpA@}RdmTs4QR>apfT0_K+z&CX~^r&QlU1gV=jEl=~Ld-g1$@ZlBt z5!Gh(OWKYo zRugsW69k9OH|Hcx<_m{|7fA@2*~Eq{4o*KN#RgJv%?y6@?kUr%CoLWJZDLf{KMk9n zCCKD7BWoSiP=0v3FsbfLPb0}Rp7=nb7_ImrhZMmttG#`Xb+cGn7$F3ow?|UY^~$*8 zDQ8m2SGv;O4bXGVx=j~qbH@KIb!1jYXgl&Yfg%^nqM3w9|1m^3HP-V6<7JE{iu)T! z4P~ZK!SGk_wPZ-(T25q|bbL~AVceh~(F#S+&^#yWs z6c-QFOcHU4&r-y@g3-{880!Xmc%?T?FthN}!pWH7Be+(fy7AUpBHN7cKi!x-ioGsa zKgGdVmrc952gI6+ao!qu-XA0r-M};phOKK6AQ#%mOLf8!(oM12D_mQBN<;M;LOQkb zW5tq<>{xKlUUD}&Os#&p!|+vsXnrRtT?QJL6wS?1MIkoh{^FzWF_u`H4C8y2y^uym zD5I+h@{{0_cqk-(L7@72s>hYsNym~(PeLC<#Osp|x%4hFo3UAMl`TP76J8t{50xN|b{+LWsBi040s( z(zY3SMx&*0g=IqZHXNzNF8`e-eGFjpt zUQ}+_O7gmP;K+T!GU#F?F_{n`ohDJz@!6`UT&xM>^y~bosOf`gOy)=3L7V6TML-lk zaoK;DdF|Vved_|$0R!$Yawf*yo`7)DZ5I=QG&;GZ0N9!Sj~s1nF-xvN6*9@ln+zGH z{eo4;tG?->4ID_J8mNQEGb+UWZ3v4fc6sw|)+luN3WEga>Eaie`kB;t7z!w#cHO>6 z4%d~QT~JX5j?@SGMrt9Wkn6nvp~i)OfN%jJhHOR5Ah{PPD;aEG`>&+C#H!zT>zzncLEu6lRy!koP|>0lpezb zfj6}ogKrpw`D&Px`8H&PNE#cpK-wcn4UuIwcyE@j3O0gJzoTnrOG|lb9Z7K$mf;|c z{XQ2Y&coj`tc3Gx&|d6@sO{?&r_)k@K+=oY4$I-@+yhFGrJvZ(gyB{7k>Tw91i@Pg z(CJ&7z2Lssy0rVReUH4sUAZDd!RSf7eS2oV=?Gr8rT&o!{Ezo6(ecoy=t$+79j@Vk z1L#(NQ4WVwK?kGz65vNheTOO>>7XUAsN~RUz~cAVKLGwWE1Nx+(DTr~Ib!{Okw1Vj zO~LcEtSj@+8(Zhb%#mn50CC6fb}7qa-g`r{cRgHB(;w5h zl(mtZ2hnjRx6o)n657M&Y%S+VxHqYZ`J#V*`7~K@Ek(RqF0H`#-*<3j4W&}luNlF8 zKdt7D4k8oHWcB%#rxQ`lf0eK5E&6v>2IqX&^AEAx%AS>b8=LC-<7h`w|HG*2LNXDl z#a4K;rx8@a%$!o$9zCcTJZQOo{@4Nqcs8H$hXvJ#NfK_cFHw4^ovsCaiEOSQ5kKB4 zkNVc+u9DbvN-R=o!!k57kZlUnD;p+|{B_A}=*j?|X#!sGcY}PY@4n4|uUOv*yT3v{ zm87F!5v`K&*iNFwU2vZ+IoIi%a>qosPh~H9HQ=*o;V2dEw1vpRnJ0vPB=zl5OPaY> zlRl(jx_DyM=btU4`aeuVHoF-6kt6(A1<;n8^l%N}5=Ch~v%iR6A59#L2E5%SBl-h0 z^W}Y2Yd&Tb|83RgXz0z=8*lwXC-UByUBoxq;ce=S(&yAsZBd`!ay)?@6e!`|nM6hp zbefIAV&V0bwMf0pq-;u1@SVbjN<}!;^)t<+X&M4-?1#(+0 z^xW%Yk(+Oc{wA!_V*E9beD(VGT#|GE6~H+=w|T+S?%*tAQRrp`RG zWl>C$W8z~+HyW>TjLgsHbAgO!jpqEkET+pra$L;cA~skwbZ!^MI$>ragqh~bdC>+` z`KWVO7R^r1@aQvL1t&S#mUjx_(XsBhz3c%Ft%w3oGyR6G8m-R$C_d?Ldn2N3V< zo9#O|?DqlvQQK%yA7#VJPa4tim#Ea{C{8_~t9D+7wCC80rXkG35cbz;l}p3FZyXuIOiNb*B}S*Zc$`D|9$=`|)=|T% zTJQ=3#b*$Zqp-F+T(yQ(hS>yzgh>2-1y5KdAf{2STG{>0)P2ls&cRZXyEfml5B@I0 z3Vf?<0vXtq30X;8JJ2PWp~ip-FT1$+ZJI&)s^}bd+uXN?h3b?sRe3vaN$U1bJK8O2 zm8X>EeZ81d>=C+ZQbTXr2Jm@=Y@QhjjD|fPZCGvQ@EGv+On-t61zGpV$<|8xb>5y* zbc%zs!eBeWQ!CIzr5pn>?*ec5^ESul4dePV^4P3ti4wY+XTKt21xkR-Pe?RL$lvZ> zDjjG40mwrtEOu-i%9&%#69R`(9rv?YBy@d|#J&#-vg++YI9pG;9H%)jAmMc!5+5?z zvRRs%?bHn^n5JTt+AItZ#4|bY(0wwZ)%xtQ!sv}uT1YwY)Qzt2<5i(xI7ms#npv7- zmSQCAlAVU&72$QO^Du_9a1luxU_?{A@Op4 z_X?f-UnvH!rkpF^ZWr$R2dGC5?I@@}s9l1VS{t5@L_#DY)#p?$eC4Pq$*>Ts03d7YFPPUjQ?Deh9= ze)vcQ!jx8;=rUX@u`2Xc&AAw_?IsPcY(j9=$1w(586s<&%>7YnRSwTjkJ~i!(8g|$ zBS*djI>kc!*rS$mf+A$4WxnMDxKQM9Aqsf`gJzxIB9uuSa8=WI>Nwd=ky#U@_s)ef zKh%X6v`$z~4c{bmJ$>WA$dslT9$dD??PI0lb-{Uu_!YBr8l`^)K0)Qlf|4O{MG>IT z;D4N?t>D3;+j^Tb#W=)Tmi#gxFZWKm84k-CNMSe>?IeHr4mVs43nPw()|1Y4AUWfM z12LUN#!4aG`t0jm!#B_TzGF2XrWSetpmKb7oxQ@k8Bd8Z3XmgSrM{HZ;{=pX%S(QO zP*+$75w9401JZB??PCV{OVG&PHcdd$W3kfBMfls31zH7%*k{xKsMLKIOwDlKm#{g& zd@+;bJeWQfJ!6COZJ14r|J!JsktN8Q0iLoXSZZ@@s?#OA#lQRy0vaU}!l{ps9Q*f|{I_D+P3 z$!GME`>{s63aw|C^<%MW(L;U-vL1kJ*kxsXO(p9_r1BU9xn#!)dEz_9U^u16Eng4P zpE!V#nt$A;@6y|-lYq}PrumLM+TGUJD>9%4{T&Noh#Sl70_Gu8!n|YW0rW7G9>d`9 zx})Jh6!Yo^N{hnbR6__t$vQ=&Ns#PWB5&gRQUydy&XPQV_q!5%QeM z!!bVK0bPi_V`*g8k6`t@HrhvWlwC51=Nu6*V*QSGdlK)cS%aH-4~LqvU@d+dBl743 zfoaByie@%!Far1SqKTa~GKo_)<}I<3QV5R#@(C$M-_(*hy|S-zvbyMZ9uh>Nv5+4u z%q1&wlvqJl5!C|`DNZ-h-atfgpUXZhL=ZPiZ;gB}Wz*D&e+nT%m24`kHPr#P3cSy3 zn7~%H)+gXMSB@SLJbQU*s5k%VRhP6G@4odCSItnDEhae2q{+}@x0mVG5rmvyjp~Ab zXc3J0Ny$q!J1}1B?RRO?JH+mM7G%u$?mM9X8+XR&?QG&<^T0;m^S951LNGu%{ceVEn{BLGBIAw+8u zI?`-Ri1U=aHxB32j#FlsSHmU_bMq1-054)u%3<|Kpnw zy@wwak=$V+r!^2c2kllg9j^i2MLF)0nTFaHLTSS+XhK2)M4ahYXag&#J0%pAhEQVT1@$1a%ZLraN62QZ z611oSVVODJI|8j5>u}(lD2B;w$@cKDt3?m$#AFf-^Os`lh?f`y7+t*J0}CeLO@DWa zZN#uh%iUm@n(`h00Nk8e79eXKa$D1rMf4^yQ|@W)=nGtq2~%3&JPiQfI0;}u6R|@u zQ5@_>IIGg;h}pA_E8Ykoa)^4KFX`4D7qdef4HQ}04H{SAbRZB(OOmz1=dWb1r#K#)d}zQ1lmDyCTOYz3cwRY!pPh~qb^E@b!b869E!v@EL{VzeiGoq zP87Ki&vJ`92ylmdxe|n+9N4B45sisB9f44{eq3`VAuNnSwX9M&Cd8!+`U;;6I7pNL z@e>!E1oPWs8Pe<7OHjXYwKV}i&eCgwHX zPDPR$7cq=VU|8m{Z;>%8^~c`=Nf5=JvKB-l?~%COoxCzqwL8{vcY@4B5EbfhN9YPn z*SpK9063WnJ~i~nP`HQhRzJKgVlxmRli~{L+|h}|N03B0s*F2xQUZ-YNR&7rWif^O1_&T^l1r;w z%PtgxX;g`Z$vOGKV;h-3H~RZx?BzSkCMWF}`hj&aN4hN| zEyH2!7)U0(d-t4y!WP~GQAp)nudm)jj5>z*f=RY)U6_LEUhn(!lZFPs!c-YP%zzM# zArlaSP$CJ`KDfk#@xxfHYZL?+>{fm7%}H*P`@@2tPHctwxeo!86*2_}EPUZr4I39s z>lCPj16%7K7%>!TH$RgMDGkf_$5@fX?fqo?vdxLb&sk)(MiP2~Fe$uUBqTMeQXqG{ zB~d3CA)Pudk*lBB$x{s02{d!U$h^0tNW;NMYg8t&m=MAM15-BQ<5QJoF@Zv;^$mYX zGi1SZpZWH}Mv_70;e1X8NsSj%J!KT1vNe*O-^}^-kc%vv0`ccMlU8|`A%ireJz^Ud z0j0nyU>deP#z~S1CjS8QEfue9089n9tPnD83g&$Ei!3z%067~U0MwiZ5sY-^h3P(# zJ>l%Rz*sL4hRHi789;xl#nJ=GYW}R9vIQTkhCvL>3Y~rN<$$CbcZ3jI=sJvwXf~@- z{{UEuXGn=LWceqd=MIjfDtMCMS)^77O#`$7NNNCdVF>YPK(`@ayH*?hpNPY@!BFw6 z0T`G_f*pCMPD$OMszf_o2K{8hh*3<$2=#e5*li#@Vj_hBV)u~+A{6doR}!;)7oS-D z2ylN(A1#ZnS%)?b^kb5YLV@yRv0iC1vFylKnLEl>1ST*9^xhk30ygq}ag*7g%#sh= z9f+nPs5kSAE^2&>d1i5eV#6g5Q}gkPh2U}snA{|w|OCHxT_GO>hGdsr(sPl)+oea3q<_U zI6}p$na-yb;4#Z?0=hn!hLrRfIB!basB%w5bSMui`pX8;3n4CCJFu@201uIVaEc9) zNZt4IjnL~4BJYzO-Xt8r%2g0&i8Ycilwvm&v}I?r7*qibvMwMj`^hC4OA{QwPsUs2 zzDHyBzz}Ch=Xe4cB4Xc({*2KTRA0woG7hj^wIg&bPZ+IVITleO@Hn7~B1q|qc>r8X z8itY+=e#Rmy%0<%sFJ*5jVRJfchlE65TbHUr(YL<+Mo*=o#f=b`w&|n-Qa=2sZ*JM zI08_$U(DdrD293d`r~0#2NqhMa)$NK%)x!*L3n06g-9GU%e1+>9 zh}fU}b8|mSH-B7==~4)u+%EKB zF=8Ks6+U+$20#?cx|!ZPFnFp64Fw?~TPPFV<Mr4@2p`c ziZ($ZIPogY{G0~Z;UeZ^3>+A9LjM5V2nluOMkPK2$vD$b!D>t*DLYbds90>VG1wb$ z#8&7JJa*C%tj3T{5Dyrt$6S3fAyRTYX9Xmvm)!WpGnN>Eb?Fg4R~f=rHU!7TeeiyE z6?U8L?dK#8%uGDV@q;x0lWsVib~P|iWj;wG8K97*T8|DB6vq@}1SOSdb(GOTB)pTs zg7#2Tp8bEAgcL)P7i;TzASBbE@7swBQZ|T1`#HT@fTRn{cx`zt7J3&HtjNfTEUXlq z0ZLDJ)L55s#!Z6}CL#XkCNNyT2*q#3G&hx)r-#I0TP`C>x@6RTum~uCFC#OYjkx~0 z2)wY-ea<9TUGb7lqe5i>Zp93W?f|I9j42bk4%5Z~gVu?VC%^Q^y-?nHoR5-;Bz1U$ zmd7an0JzIAm@VRI1ALG3A>o>Y`rtwLhCdQ>j>_szFh-Ch8WZF5kX$=s ztYNG{i#$I}g=L_-zrPrv)DXnDfgdQZ&;I~D0%xXs^^>ssc`-{p23ktS+!DiIc?qKL z1|x`|z)U*vj4NrVImGV1G7J;n3enVeye6ELep|Z0Bq%M8Ae@;niAlkek-TmF{{Xqs z1kJIqTM*dAnlC+Mcm<-dkyTwE>mTf9brx>3cH#uL&v@$UF?c)Q&&EZQ4iFEn3A9v* ztVVh8o5_1(5x@+IFxjz@gmJ3^nHEf)Ok81aZVM8#;4`^o>_&VW0aRrMQ4uAST7cB} z_ku@hK#8IAoDw48XnY9c9Mx(OU>cXKX0~=$VZ5OUDVW5PuY1KA6Hj?Cy#a#LEk=hB zqh6;Gx6tc}FwYLq9xJ3+te(e07)$3RD-|E-=M+hH5Pa4fZFSJ^=NJ%-QBQH_At_W{ zJG^Samgl)U#o)EOq5gik18cm{_kpBaeFOUNNOUdDeCUOGK5^zad8;>Y#CE-5GV4ts zj((UDtT0%++#|Wr%3t(3Yy?!I9%a@oPQlhu1BngXkBoqm$i!O7FX7RQyTUm)u*6R| zHS`b$c^rj7#sIzAWPAuWpjV<1{Be5cCOlMOg4utHHBNj|YH6Br`<+lvTNfHrY*rg=O zNB}9(0QvIZ<_f&8&I+|De5d!@CBV*vo!=!9*BMVTS@NSh_~!${gC)ryn)$>fW%J** zxX1?<7_iY~W#bAcGGFh!V=WrNB;Sr|pgKL=k|UXvK+~ff2UmaQ2`Fn(mk&c4 zXrsnnA{?q!Wb~=Q!Wkfj&i??f9ZrZIcD-@N1KM?)K;gE`l7w>p@sk?oiJT4}8`1Rp zV~Qvp)*M*3`7mh)j!OwmPkr8KeXi5*>g9VhX?4TKvX0XVSD07vhR`S)CZiEKQPI;c z(mt6WTU)hHKU|hN)li$)bIv-X-E$B3Q<8P`Y*Va`NQPOttwrR9eH?KjDhHYR$()%Y zWXKtgR~8y4n8i2P*DE0B5d?<8S#V?O7@2G&2b^mnms6bpNxJat(U6jx^coFbHA1=* zTQR8_6fwpQ!90V@4xlj8n>#Vzyr?LR8EGz?^@$uYTzupLQ9QEB+y3&#*_1a!oXTr< z#^WF$06=h4KgEn95GkYH5Ksi9d*>%IU&|VjsTzIdgx>!EIrf}=x%mG8@kX}!e_8#W O{{Z+uE^m*>=l|JY*9L0< literal 0 HcmV?d00001 diff --git a/examples/image_decoders/jpeg_viewer.html b/examples/image_decoders/jpeg_viewer.html new file mode 100644 index 000000000..47af71b0d --- /dev/null +++ b/examples/image_decoders/jpeg_viewer.html @@ -0,0 +1,40 @@ + + + + + + + + PDF.js standalone JpegImage parser + + + + + + + + + + + + diff --git a/examples/image_decoders/jpeg_viewer.js b/examples/image_decoders/jpeg_viewer.js new file mode 100644 index 000000000..5bc81ecf3 --- /dev/null +++ b/examples/image_decoders/jpeg_viewer.js @@ -0,0 +1,75 @@ +/* Copyright 2018 Mozilla Foundation + * + * 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. + */ + +'use strict'; + +if (!pdfjsImageDecoders.JpegImage) { + alert('Please build the pdfjs-dist library using `gulp dist-install`'); +} + +var JPEG_IMAGE = 'fish.jpg'; + +var jpegCanvas = document.getElementById('jpegCanvas'); +var jpegCtx = jpegCanvas.getContext('2d'); + +// Load the image data, and convert it to a Uint8Array. +// +var nonBinaryRequest = false; +var request = new XMLHttpRequest(); +request.open('GET', JPEG_IMAGE, false); +try { + request.responseType = 'arraybuffer'; + nonBinaryRequest = request.responseType !== 'arraybuffer'; +} catch (e) { + nonBinaryRequest = true; +} +if (nonBinaryRequest && request.overrideMimeType) { + request.overrideMimeType('text/plain; charset=x-user-defined'); +} +request.send(null); + +var typedArrayImage; +if (nonBinaryRequest) { + var str = request.responseText, length = str.length; + var bytes = new Uint8Array(length); + for (var i = 0; i < length; ++i) { + bytes[i] = str.charCodeAt(i) & 0xFF; + } + typedArrayImage = bytes; +} else { + typedArrayImage = new Uint8Array(request.response); +} + +// Parse the image data using `JpegImage`. +// +var jpegImage = new pdfjsImageDecoders.JpegImage(); +jpegImage.parse(typedArrayImage); + +var width = jpegImage.width, height = jpegImage.height; +var jpegData = jpegImage.getData(width, height, /* forceRGB = */ true); + +// Render the JPEG image on a . +// +var imageData = jpegCtx.createImageData(width, height); +var imageBytes = imageData.data; +for (var i = 0, j = 0, ii = width * height * 4; i < ii;) { + imageBytes[i++] = jpegData[j++]; + imageBytes[i++] = jpegData[j++]; + imageBytes[i++] = jpegData[j++]; + imageBytes[i++] = 255; +} +jpegCanvas.width = width, jpegCanvas.height = height; +jpegCtx.putImageData(imageData, 0, 0); +