adding configured header and footer images to pdf exports

This commit is contained in:
Maximilian Krambach 2019-01-25 12:59:10 +01:00
parent c406362da5
commit 93a9a8aef3
2 changed files with 64 additions and 62 deletions

View File

@ -8,15 +8,6 @@ import { TranslateService } from '@ngx-translate/core';
import { ConfigService } from './config.service'; import { ConfigService } from './config.service';
import { HttpService } from './http.service'; import { HttpService } from './http.service';
/**
* An interface for the mapping of image placeholder name to the url of the
* image
*/
export interface ImagePlaceHolder {
placeholder: string;
url: string;
}
/** /**
* Provides the general document structure for PDF documents, such as page margins, header, footer and styles. * Provides the general document structure for PDF documents, such as page margins, header, footer and styles.
* Also provides general purpose open and download functions. * Also provides general purpose open and download functions.
@ -34,6 +25,12 @@ export interface ImagePlaceHolder {
providedIn: 'root' providedIn: 'root'
}) })
export class PdfDocumentService { export class PdfDocumentService {
/**
* A list of all images to add to the virtual file system.
* May still be filling at header and footer creation
*/
private imageUrls: string[] = [];
/** /**
* Constructor * Constructor
* *
@ -48,13 +45,11 @@ export class PdfDocumentService {
) {} ) {}
/** /**
* Define the pdfmake virtual file system for fonts * Define the pdfmake virtual file system, adding the fonts
* *
* @param images an optional mapping of images urls to be fetched and inserted
* into placeholders
* @returns the vfs-object * @returns the vfs-object
*/ */
private async initVfs(images?: ImagePlaceHolder[]): Promise<object> { private async initVfs(): Promise<object> {
const fontPathList: string[] = Array.from( const fontPathList: string[] = Array.from(
// create a list without redundancies // create a list without redundancies
new Set( new Set(
@ -72,17 +67,7 @@ export class PdfDocumentService {
}; };
}); });
}); });
let imagePromises = []; const binaryDataUrls = await Promise.all(promises);
if (images && images.length) {
imagePromises = images.map(image => {
return this.convertUrlToBase64(image.url).then(base64 => {
return {
[image.placeholder]: base64
};
});
});
}
const binaryDataUrls = await Promise.all(promises.concat(imagePromises));
let vfs = {}; let vfs = {};
binaryDataUrls.map(entry => { binaryDataUrls.map(entry => {
vfs = { vfs = {
@ -99,7 +84,7 @@ export class PdfDocumentService {
* @param url file url * @param url file url
* @returns a promise with a base64 string * @returns a promise with a base64 string
*/ */
public async convertUrlToBase64(url: string): Promise<string> { private async convertUrlToBase64(url: string): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
const headers = new HttpHeaders(); const headers = new HttpHeaders();
this.httpService.get<Blob>(url, {}, {}, headers, 'blob').then(file => { this.httpService.get<Blob>(url, {}, {}, headers, 'blob').then(file => {
@ -133,17 +118,14 @@ export class PdfDocumentService {
* *
* @param documentContent the content of the pdf as object * @param documentContent the content of the pdf as object
* @param metadata * @param metadata
* @param images Array of optional images (url, placeholder) to be inserted * @param imageUrls Array of optional images (url, placeholder) to be inserted
* @returns the pdf document definition ready to export * @returns the pdf document definition ready to export
*/ */
private async getStandardPaper( private async getStandardPaper(documentContent: object, metadata?: object, imageUrls?: string[]): Promise<object> {
documentContent: object,
metadata?: object,
images?: ImagePlaceHolder[]
): Promise<object> {
this.initFonts(); this.initFonts();
pdfMake.vfs = await this.initVfs(images); this.imageUrls = imageUrls ? imageUrls : [];
return { pdfMake.vfs = await this.initVfs();
const result = {
pageSize: 'A4', pageSize: 'A4',
pageMargins: [75, 90, 75, 75], pageMargins: [75, 90, 75, 75],
defaultStyle: { defaultStyle: {
@ -157,9 +139,10 @@ export class PdfDocumentService {
}, },
info: metadata, info: metadata,
content: documentContent, content: documentContent,
styles: this.getStandardPaperStyles(), styles: this.getStandardPaperStyles()
images: this.getImageUrls()
}; };
await this.loadAllImages();
return result;
} }
/** /**
@ -167,27 +150,29 @@ export class PdfDocumentService {
* (e.g. ballots) * (e.g. ballots)
* *
* @param documentContent the content of the pdf as object * @param documentContent the content of the pdf as object
* @param image an optional image to insert into the ballot * @param imageUrl an optional image to insert into the ballot
* @returns the pdf document definition ready to export * @returns the pdf document definition ready to export
*/ */
private async getBallotPaper(documentContentObject: object, image?: ImagePlaceHolder): Promise<object> { private async getBallotPaper(documentContent: object, imageUrl?: string): Promise<object> {
const images = image ? [image] : null;
this.initFonts(); this.initFonts();
pdfMake.vfs = await this.initVfs(images); this.imageUrls = imageUrl ? [imageUrl] : [];
return { pdfMake.vfs = await this.initVfs();
const result = {
pageSize: 'A4', pageSize: 'A4',
pageMargins: [0, 0, 0, 0], pageMargins: [0, 0, 0, 0],
defaultStyle: { defaultStyle: {
font: 'PdfFont', font: 'PdfFont',
fontSize: 10 fontSize: 10
}, },
content: documentContentObject, content: documentContent,
styles: this.getBlankPaperStyles() styles: this.getBlankPaperStyles()
}; };
await this.loadAllImages();
return result;
} }
/** /**
* Define fonts * Define the fonts
*/ */
private initFonts(): void { private initFonts(): void {
pdfMake.fonts = { pdfMake.fonts = {
@ -222,6 +207,7 @@ export class PdfDocumentService {
fit: [180, 40], fit: [180, 40],
width: '20%' width: '20%'
}); });
this.imageUrls.push(logoHeaderLeftUrl);
} }
// add the header text if no logo on the right was specified // add the header text if no logo on the right was specified
@ -259,6 +245,7 @@ export class PdfDocumentService {
alignment: 'right', alignment: 'right',
width: '20%' width: '20%'
}); });
this.imageUrls.push(logoHeaderRightUrl);
} }
return { return {
@ -284,17 +271,17 @@ export class PdfDocumentService {
const columns = []; const columns = [];
let logoContainerWidth: string; let logoContainerWidth: string;
let pageNumberPosition: string; let pageNumberPosition: string;
let logoConteinerSize: Array<number>; let logoContainerSize: Array<number>;
let logoFooterLeftUrl = this.configService.instant<any>('logo_pdf_footer_L').path; let logoFooterLeftUrl = this.configService.instant<any>('logo_pdf_footer_L').path;
let logoFooterRightUrl = this.configService.instant<any>('logo_pdf_footer_R').path; let logoFooterRightUrl = this.configService.instant<any>('logo_pdf_footer_R').path;
// if there is a single logo, give it a lot of space // if there is a single logo, give it a lot of space
if (logoFooterLeftUrl && logoFooterRightUrl) { if (logoFooterLeftUrl && logoFooterRightUrl) {
logoContainerWidth = '20%'; logoContainerWidth = '20%';
logoConteinerSize = [180, 40]; logoContainerSize = [180, 40];
} else { } else {
logoContainerWidth = '80%'; logoContainerWidth = '80%';
logoConteinerSize = [400, 50]; logoContainerSize = [400, 50];
} }
// the position of the page number depends on the logos // the position of the page number depends on the logos
@ -315,10 +302,11 @@ export class PdfDocumentService {
} }
columns.push({ columns.push({
image: logoFooterLeftUrl, image: logoFooterLeftUrl,
fit: logoConteinerSize, fit: logoContainerSize,
width: logoContainerWidth, width: logoContainerWidth,
alignment: 'left' alignment: 'left'
}); });
this.imageUrls.push(logoFooterLeftUrl);
} }
// add the page number // add the page number
@ -335,10 +323,11 @@ export class PdfDocumentService {
} }
columns.push({ columns.push({
image: logoFooterRightUrl, image: logoFooterRightUrl,
fit: logoConteinerSize, fit: logoContainerSize,
width: logoContainerWidth, width: logoContainerWidth,
alignment: 'right' alignment: 'right'
}); });
this.imageUrls.push(logoFooterRightUrl);
} }
return { return {
@ -378,8 +367,7 @@ export class PdfDocumentService {
* @param logo (optional) url of a logo to be placed as ballot logo * @param logo (optional) url of a logo to be placed as ballot logo
*/ */
public downloadWithBallotPaper(docDefinition: object, filename: string, logo?: string): void { public downloadWithBallotPaper(docDefinition: object, filename: string, logo?: string): void {
const images: ImagePlaceHolder = logo ? { placeholder: 'ballot-logo', url: logo } : null; this.getBallotPaper(docDefinition, logo).then(doc => {
this.getBallotPaper(docDefinition, images).then(doc => {
this.createPdf(doc, filename); this.createPdf(doc, filename);
}); });
} }
@ -396,18 +384,6 @@ export class PdfDocumentService {
}); });
} }
/**
* TODO
*
* Should create an images section in the document definition holding the base64 strings
* for the urls
*
* @returns an object containing the image names and the corresponding base64 strings
*/
private getImageUrls(): object {
return {};
}
/** /**
* Definition of styles for standard papers * Definition of styles for standard papers
* *
@ -493,4 +469,27 @@ export class PdfDocumentService {
} }
}; };
} }
/**
* Triggers the addition of all images found during creation(including header and footer)
* to the vfs.
*/
private async loadAllImages(): Promise<void> {
const promises = this.imageUrls.map(image => {
return this.addImageToVfS(image);
});
await Promise.all(promises);
}
/**
* Creates an image in the pdfMake virtual file system, if it doesn't yet exist there
*
* @param url
*/
private async addImageToVfS(url: string): Promise<void> {
if (!pdfMake.vfs[url]) {
const base64 = await this.convertUrlToBase64(url);
pdfMake.vfs[url] = base64;
}
}
} }

View File

@ -71,6 +71,9 @@ export class MotionPollPdfService {
this.configService.get('general_event_name').subscribe(name => (this.eventName = name)); this.configService.get('general_event_name').subscribe(name => (this.eventName = name));
this.configService.get('logo_pdf_ballot_paper').subscribe(url => { this.configService.get('logo_pdf_ballot_paper').subscribe(url => {
if (url && url.path) { if (url && url.path) {
if (url.path.indexOf('/') === 0) {
url.path = url.path.substr(1); // remove prepending slash
}
this.logo = url.path; this.logo = url.path;
} }
}); });
@ -218,7 +221,7 @@ export class MotionPollPdfService {
if (this.logo) { if (this.logo) {
columns.push({ columns.push({
image: 'ballot-logo', // fixed dummy name not used outside ballot creation image: this.logo,
fit: [90, 25], fit: [90, 25],
alignment: 'right', alignment: 'right',
width: '40%' width: '40%'