Skip to content

Commit

Permalink
feat: download images in a PDF file
Browse files Browse the repository at this point in the history
  • Loading branch information
megheaiulian committed Feb 27, 2020
1 parent 5bfd528 commit ec91f79
Show file tree
Hide file tree
Showing 7 changed files with 612 additions and 110 deletions.
2 changes: 2 additions & 0 deletions cosmoz-image-viewer.html.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export const template = html`
</paper-icon-button>
<paper-icon-button class="nav" hidden$="[[!_showDetach]]" on-click="detach" icon="launch" title="[[ _('Detach image to separate window', t) ]]">
</paper-icon-button>
<paper-icon-button class="nav" on-click="onDownloadPdf" icon="icons:file-download" title="[[ _('Download images', t) ]]">
</paper-icon-button>
<paper-icon-button class="nav" hidden$="[[!_showFullscreen]]" on-click="openFullscreen" icon="icons:fullscreen" title="[[ _('Fullscreen image', t) ]]">
</paper-icon-button>
<paper-icon-button class="nav" hidden$="[[!showClose]]" on-click="_close" icon="icons:close" title="[[ _('Close fullscreen', t) ]]">
Expand Down
56 changes: 8 additions & 48 deletions cosmoz-image-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import { PolymerElement } from '@polymer/polymer/polymer-element';
import { IronResizableBehavior } from '@polymer/iron-resizable-behavior';
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class';
import { translatable } from '@neovici/cosmoz-i18next';

import { NullZipArchive } from '@neovici/nullxlsx';

import { download } from './lib/pdf';
import { template } from './cosmoz-image-viewer.html';

const globals = {
Expand Down Expand Up @@ -401,7 +399,7 @@ class CosmozImageViewer extends translatable(mixinBehaviors([
* @returns {Object} detached window object
*/
detach() {
const detachPrevented = !this.dispatchEvent(new CustomEvent('will-detach', {cancelable: true}));
const detachPrevented = !this.dispatchEvent(new CustomEvent('will-detach', { cancelable: true }));
if (detachPrevented) {
return false;
}
Expand Down Expand Up @@ -449,7 +447,8 @@ class CosmozImageViewer extends translatable(mixinBehaviors([

w.document.title = this._detachedWindowTitle;

w.addEventListener('download', ({detail}) => this.createZipFromUrls(detail).then(zip => this.downloadZip(zip)));
w.addEventListener('download', async ({ detail }) => await this.downloadPdf(detail));

w.addEventListener('beforeunload', globals.windowBeforeUnloadHandler);

if (w.ciw == null) {
Expand Down Expand Up @@ -498,50 +497,11 @@ class CosmozImageViewer extends translatable(mixinBehaviors([
this.detach();
}
}

downloadZip(zip) {
const a = document.body.appendChild(zip.createDownloadLink());
a.click();
document.body.removeChild(a);
async onDownloadPdf() {
await this.downloadPdf(this._resolvedImages);
}

createZipFromUrls(fileUrls) {
const options = {
credentials: this.credentials ? 'include' : 'omit'
},
fetches = fileUrls.map(url =>
fetch(url, options)
.then(response => response.arrayBuffer())
.then(data => ({
data,
url
}))
);

return Promise
.all(fetches)
.then(responses => {
const filenames = [],
zip = new NullZipArchive(this.downloadFileName, false);

for (const {url, data} of responses) {
let filename = url.replace(/^.*[\\/]/u, '');
const filenameParts = filename.split('.'),
sameFilenames = filenames.filter(f => f === filenameParts[0]);

if (sameFilenames.length > 0) {
filename = `${filenameParts[0]} (${sameFilenames.length + 1}).${filenameParts[1]}`;
}

zip.addFileFromUint8Array(filename, new Uint8Array(data));
filenames.push(filenameParts[0]);

if (fileUrls.length === zip.files.length) {
zip.generate();
return zip;
}
}
});
async downloadPdf(urls) {
return await download(urls, this.downloadFileName, this.credentials);
}
/**
* Toggles between initial zoom level and 1.5x initial zoom level.
Expand Down
71 changes: 71 additions & 0 deletions lib/pdf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* global jsPDF */
import 'jspdf';

export const fit = (rect, bounds) => {
const rectRatio = rect.width / rect.height,
boundsRatio = bounds.width / bounds.height;

return rectRatio > boundsRatio
? {
width: bounds.width,
height: rect.height * (bounds.width / rect.width)
}
: {
width: rect.height * (bounds.width / rect.width),
height: bounds.height
};
};

export const create = async (urls, credentials) => {
const options = {
credentials: credentials ? 'include' : 'omit'
},
responses = (await Promise.all(urls.map(
async url => {
const response = await fetch(url, options);
return response.ok
? {
url,
data: new Uint8Array(await response.arrayBuffer())
}
: undefined;
}
))).filter(Boolean),
pdf = new jsPDF(); // eslint-disable-line new-cap

responses.forEach(({
url, data
}, i) => {
const
padding = 2, //in mm
{ internal: { pageSize }} = pdf,
{
width, height
} = fit(pdf.getImageProperties(data), {
width: pageSize.getWidth() - padding * 2,
height: pageSize.getHeight() - padding * 2
});
if (i > 0) {
pdf.addPage();
}
pdf.addImage(data, url.split('.').pop().toUpperCase(), padding, padding, width, height);

});
return pdf.output('blob');
};

export const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob),
a = document.body.appendChild(document.createElement('a'));
a.href = url;
a.download = `${filename}.pdf`;
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};

export const download = async (urls, filename, credentials) => {
const blob = await create(urls, credentials);
await downloadBlob(blob, filename);
return blob;
};
Loading

0 comments on commit ec91f79

Please sign in to comment.