2019-01-22 11:59:16 +01:00
|
|
|
|
import { HttpHeaders } from '@angular/common/http';
|
2019-01-18 20:25:06 +01:00
|
|
|
|
import { Injectable } from '@angular/core';
|
|
|
|
|
import { saveAs } from 'file-saver';
|
|
|
|
|
|
2019-01-22 11:59:16 +01:00
|
|
|
|
import pdfMake from 'pdfmake/build/pdfmake';
|
2019-01-18 20:25:06 +01:00
|
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
|
|
|
|
|
|
|
|
import { ConfigService } from './config.service';
|
2019-01-31 13:40:27 +01:00
|
|
|
|
import { HttpService } from '../core-services/http.service';
|
2019-07-03 16:42:08 +02:00
|
|
|
|
import { ExportFormData } from 'app/site/motions/modules/motion-list/components/motion-export-dialog/motion-export-dialog.component';
|
2019-01-18 20:25:06 +01:00
|
|
|
|
|
2019-05-13 18:19:29 +02:00
|
|
|
|
/**
|
|
|
|
|
* Enumeration to define possible values for the styling.
|
|
|
|
|
*/
|
|
|
|
|
export enum StyleType {
|
|
|
|
|
DEFAULT = 'tocEntry',
|
2019-06-04 14:41:12 +02:00
|
|
|
|
SUBTITLE = 'subtitle',
|
|
|
|
|
SUB_ENTRY = 'tocSubEntry',
|
2019-05-13 18:19:29 +02:00
|
|
|
|
CATEGORY_SECTION = 'tocCategorySection'
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-04 14:41:12 +02:00
|
|
|
|
/**
|
|
|
|
|
* Enumeration to describe the type of borders.
|
|
|
|
|
*/
|
|
|
|
|
export enum BorderType {
|
|
|
|
|
DEFAULT = 'noBorders',
|
|
|
|
|
LIGHT_HORIZONTAL_LINES = 'lightHorizontalLines',
|
|
|
|
|
HEADER_ONLY = 'headerLineOnly'
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-26 15:34:27 +02:00
|
|
|
|
/**
|
|
|
|
|
* Custom PDF error class to handle errors in a safer way
|
|
|
|
|
*/
|
|
|
|
|
export class PdfError extends Error {
|
|
|
|
|
public __proto__: PdfError;
|
|
|
|
|
|
|
|
|
|
public constructor(public message: string) {
|
|
|
|
|
super(message);
|
|
|
|
|
const trueProto = new.target.prototype;
|
|
|
|
|
this.__proto__ = trueProto;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-22 15:24:02 +01:00
|
|
|
|
/**
|
2019-01-18 20:25:06 +01:00
|
|
|
|
* Provides the general document structure for PDF documents, such as page margins, header, footer and styles.
|
|
|
|
|
* Also provides general purpose open and download functions.
|
|
|
|
|
*
|
|
|
|
|
* Use a local pdf service (i.e. MotionPdfService) to get the document definition for the content and use this service to
|
|
|
|
|
* open or download the pdf document
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* ```ts
|
|
|
|
|
* const motionContent = this.motionPdfService.motionToDocDef(this.motion);
|
|
|
|
|
* this.this.pdfDocumentService.open(motionContent);
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
@Injectable({
|
|
|
|
|
providedIn: 'root'
|
|
|
|
|
})
|
|
|
|
|
export class PdfDocumentService {
|
2019-01-25 12:59:10 +01:00
|
|
|
|
/**
|
|
|
|
|
* A list of all images to add to the virtual file system.
|
|
|
|
|
* May still be filling at header and footer creation
|
|
|
|
|
*/
|
|
|
|
|
private imageUrls: string[] = [];
|
|
|
|
|
|
2019-04-26 16:23:48 +02:00
|
|
|
|
/**
|
|
|
|
|
* Table layout that switches the background color every other row.
|
|
|
|
|
* @example
|
|
|
|
|
* ```ts
|
|
|
|
|
* layout: this.pdfDocumentService.switchColorTableLayout
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
public switchColorTableLayout = {
|
|
|
|
|
hLineWidth: rowIndex => {
|
|
|
|
|
return rowIndex === 1;
|
|
|
|
|
},
|
|
|
|
|
vLineWidth: () => {
|
|
|
|
|
return 0;
|
|
|
|
|
},
|
|
|
|
|
fillColor: rowIndex => {
|
|
|
|
|
return rowIndex % 2 === 0 ? '#EEEEEE' : null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2019-01-18 20:25:06 +01:00
|
|
|
|
/**
|
|
|
|
|
* Constructor
|
|
|
|
|
*
|
|
|
|
|
* @param translate translations
|
|
|
|
|
* @param configService read config values
|
2019-01-22 11:59:16 +01:00
|
|
|
|
* @param mediaManageService to read out font files as media data
|
2019-01-18 20:25:06 +01:00
|
|
|
|
*/
|
2019-01-22 11:59:16 +01:00
|
|
|
|
public constructor(
|
|
|
|
|
private translate: TranslateService,
|
|
|
|
|
private configService: ConfigService,
|
|
|
|
|
private httpService: HttpService
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-01-25 12:59:10 +01:00
|
|
|
|
* Define the pdfmake virtual file system, adding the fonts
|
2019-01-22 11:59:16 +01:00
|
|
|
|
*
|
|
|
|
|
* @returns the vfs-object
|
|
|
|
|
*/
|
2019-01-25 12:59:10 +01:00
|
|
|
|
private async initVfs(): Promise<object> {
|
2019-01-22 11:59:16 +01:00
|
|
|
|
const fontPathList: string[] = Array.from(
|
|
|
|
|
// create a list without redundancies
|
|
|
|
|
new Set(
|
|
|
|
|
this.configService
|
|
|
|
|
.instant<string[]>('fonts_available')
|
|
|
|
|
.map(available => this.configService.instant<any>(available))
|
|
|
|
|
.map(font => font.path || `/${font.default}`)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const promises = fontPathList.map(fontPath => {
|
|
|
|
|
return this.convertUrlToBase64(fontPath).then(base64 => {
|
|
|
|
|
return {
|
2019-01-22 15:24:02 +01:00
|
|
|
|
[fontPath.split('/').pop()]: base64
|
2019-01-22 11:59:16 +01:00
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-01-25 12:59:10 +01:00
|
|
|
|
const binaryDataUrls = await Promise.all(promises);
|
2019-01-22 11:59:16 +01:00
|
|
|
|
let vfs = {};
|
2019-01-22 15:24:02 +01:00
|
|
|
|
binaryDataUrls.map(entry => {
|
2019-01-22 11:59:16 +01:00
|
|
|
|
vfs = {
|
|
|
|
|
...vfs,
|
|
|
|
|
...entry
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
return vfs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-01-22 15:24:02 +01:00
|
|
|
|
* Retrieves a binary file from the url and returns a base64 value
|
2019-01-22 11:59:16 +01:00
|
|
|
|
*
|
2019-01-22 15:24:02 +01:00
|
|
|
|
* @param url file url
|
|
|
|
|
* @returns a promise with a base64 string
|
2019-01-22 11:59:16 +01:00
|
|
|
|
*/
|
2019-01-25 12:59:10 +01:00
|
|
|
|
private async convertUrlToBase64(url: string): Promise<string> {
|
2019-01-22 15:24:02 +01:00
|
|
|
|
return new Promise<string>((resolve, reject) => {
|
|
|
|
|
const headers = new HttpHeaders();
|
|
|
|
|
this.httpService.get<Blob>(url, {}, {}, headers, 'blob').then(file => {
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
|
reader.onload = () => {
|
|
|
|
|
const resultStr: string = reader.result as string;
|
|
|
|
|
resolve(resultStr.split(',')[1]);
|
|
|
|
|
};
|
|
|
|
|
reader.onerror = error => {
|
|
|
|
|
reject(error);
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
});
|
2019-01-22 11:59:16 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the name of a font from the value of the given
|
|
|
|
|
* config variable.
|
|
|
|
|
*
|
|
|
|
|
* @param fontType the config variable of the font (font_regular, font_italic)
|
|
|
|
|
* @returns the name of the selected font
|
|
|
|
|
*/
|
|
|
|
|
private getFontName(fontType: string): string {
|
|
|
|
|
const font = this.configService.instant<any>(fontType);
|
|
|
|
|
return (font.path || `/${font.default}`).split('/').pop();
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Overall document definition and styles for the most PDF documents
|
|
|
|
|
*
|
|
|
|
|
* @param documentContent the content of the pdf as object
|
2019-01-22 15:24:02 +01:00
|
|
|
|
* @param metadata
|
2019-01-25 12:59:10 +01:00
|
|
|
|
* @param imageUrls Array of optional images (url, placeholder) to be inserted
|
2019-01-31 19:18:58 +01:00
|
|
|
|
* @param customMargins optionally overrides the margins
|
|
|
|
|
* @param landscape optional landscape page orientation instead of default portrait
|
2019-01-18 20:25:06 +01:00
|
|
|
|
* @returns the pdf document definition ready to export
|
|
|
|
|
*/
|
2019-01-31 19:18:58 +01:00
|
|
|
|
private async getStandardPaper(
|
|
|
|
|
documentContent: object,
|
|
|
|
|
metadata?: object,
|
2019-07-03 16:42:08 +02:00
|
|
|
|
exportInfo?: ExportFormData,
|
2019-01-31 19:18:58 +01:00
|
|
|
|
imageUrls?: string[],
|
|
|
|
|
customMargins?: [number, number, number, number],
|
|
|
|
|
landscape?: boolean
|
|
|
|
|
): Promise<object> {
|
2019-01-22 15:24:02 +01:00
|
|
|
|
this.initFonts();
|
2019-01-25 12:59:10 +01:00
|
|
|
|
this.imageUrls = imageUrls ? imageUrls : [];
|
|
|
|
|
pdfMake.vfs = await this.initVfs();
|
2019-05-20 16:52:36 +02:00
|
|
|
|
const pageSize = this.configService.instant('general_export_pdf_pagesize');
|
|
|
|
|
const defaultMargins = pageSize === 'A5' ? [45, 30, 45, 45] : [75, 90, 75, 75];
|
2019-02-20 15:02:30 +01:00
|
|
|
|
// needs to be done before, cause the footer is async
|
|
|
|
|
this.loadFooterImages();
|
2019-01-25 12:59:10 +01:00
|
|
|
|
const result = {
|
2019-05-20 16:52:36 +02:00
|
|
|
|
pageSize: pageSize || 'A4',
|
2019-02-12 13:52:44 +01:00
|
|
|
|
pageOrientation: landscape ? 'landscape' : 'portrait',
|
2019-05-20 16:52:36 +02:00
|
|
|
|
pageMargins: customMargins || defaultMargins,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
defaultStyle: {
|
2019-01-22 11:59:16 +01:00
|
|
|
|
font: 'PdfFont',
|
|
|
|
|
fontSize: this.configService.instant('general_export_pdf_fontsize')
|
2019-01-18 20:25:06 +01:00
|
|
|
|
},
|
2019-01-31 19:18:58 +01:00
|
|
|
|
header: this.getHeader(customMargins ? [customMargins[0], customMargins[2]] : null),
|
2019-07-03 16:42:08 +02:00
|
|
|
|
|
2019-01-18 20:25:06 +01:00
|
|
|
|
// TODO: option for no footer, wherever this can be defined
|
|
|
|
|
footer: (currentPage, pageCount) => {
|
2019-01-31 19:18:58 +01:00
|
|
|
|
return this.getFooter(
|
|
|
|
|
currentPage,
|
|
|
|
|
pageCount,
|
2019-07-03 16:42:08 +02:00
|
|
|
|
customMargins ? [customMargins[0], customMargins[2]] : null,
|
|
|
|
|
exportInfo
|
2019-01-31 19:18:58 +01:00
|
|
|
|
);
|
2019-01-18 20:25:06 +01:00
|
|
|
|
},
|
|
|
|
|
info: metadata,
|
|
|
|
|
content: documentContent,
|
2019-01-25 12:59:10 +01:00
|
|
|
|
styles: this.getStandardPaperStyles()
|
2019-01-18 20:25:06 +01:00
|
|
|
|
};
|
2019-01-25 12:59:10 +01:00
|
|
|
|
await this.loadAllImages();
|
|
|
|
|
return result;
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-22 15:24:02 +01:00
|
|
|
|
/**
|
|
|
|
|
* Overall document definition and styles for blank PDF documents
|
|
|
|
|
* (e.g. ballots)
|
|
|
|
|
*
|
|
|
|
|
* @param documentContent the content of the pdf as object
|
2019-01-25 12:59:10 +01:00
|
|
|
|
* @param imageUrl an optional image to insert into the ballot
|
2019-01-22 15:24:02 +01:00
|
|
|
|
* @returns the pdf document definition ready to export
|
|
|
|
|
*/
|
2019-01-25 12:59:10 +01:00
|
|
|
|
private async getBallotPaper(documentContent: object, imageUrl?: string): Promise<object> {
|
2019-01-22 15:24:02 +01:00
|
|
|
|
this.initFonts();
|
2019-01-25 12:59:10 +01:00
|
|
|
|
this.imageUrls = imageUrl ? [imageUrl] : [];
|
|
|
|
|
pdfMake.vfs = await this.initVfs();
|
|
|
|
|
const result = {
|
2019-01-22 15:24:02 +01:00
|
|
|
|
pageSize: 'A4',
|
|
|
|
|
pageMargins: [0, 0, 0, 0],
|
|
|
|
|
defaultStyle: {
|
|
|
|
|
font: 'PdfFont',
|
|
|
|
|
fontSize: 10
|
|
|
|
|
},
|
2019-01-25 12:59:10 +01:00
|
|
|
|
content: documentContent,
|
2019-01-22 15:24:02 +01:00
|
|
|
|
styles: this.getBlankPaperStyles()
|
|
|
|
|
};
|
2019-01-25 12:59:10 +01:00
|
|
|
|
await this.loadAllImages();
|
|
|
|
|
return result;
|
2019-01-22 15:24:02 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-01-25 12:59:10 +01:00
|
|
|
|
* Define the fonts
|
2019-01-22 15:24:02 +01:00
|
|
|
|
*/
|
|
|
|
|
private initFonts(): void {
|
|
|
|
|
pdfMake.fonts = {
|
|
|
|
|
PdfFont: {
|
|
|
|
|
normal: this.getFontName('font_regular'),
|
|
|
|
|
bold: this.getFontName('font_bold'),
|
|
|
|
|
italics: this.getFontName('font_italic'),
|
|
|
|
|
bolditalics: this.getFontName('font_bold_italic')
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 20:25:06 +01:00
|
|
|
|
/**
|
|
|
|
|
* Creates the header doc definition for normal PDF documents
|
|
|
|
|
*
|
2019-01-31 19:18:58 +01:00
|
|
|
|
* @param lrMargin optional margin overrides
|
2019-01-18 20:25:06 +01:00
|
|
|
|
* @returns an object that contains the necessary header definition
|
|
|
|
|
*/
|
2019-01-31 19:18:58 +01:00
|
|
|
|
private getHeader(lrMargin?: [number, number]): object {
|
2019-01-18 20:25:06 +01:00
|
|
|
|
// check for the required logos
|
2019-01-22 11:59:16 +01:00
|
|
|
|
let logoHeaderLeftUrl = this.configService.instant<any>('logo_pdf_header_L').path;
|
|
|
|
|
let logoHeaderRightUrl = this.configService.instant<any>('logo_pdf_header_R').path;
|
2019-01-18 20:25:06 +01:00
|
|
|
|
let text;
|
|
|
|
|
const columns = [];
|
|
|
|
|
|
|
|
|
|
// add the left logo to the header column
|
|
|
|
|
if (logoHeaderLeftUrl) {
|
|
|
|
|
if (logoHeaderLeftUrl.indexOf('/') === 0) {
|
|
|
|
|
logoHeaderLeftUrl = logoHeaderLeftUrl.substr(1); // remove trailing /
|
|
|
|
|
}
|
|
|
|
|
columns.push({
|
|
|
|
|
image: logoHeaderLeftUrl,
|
|
|
|
|
fit: [180, 40],
|
|
|
|
|
width: '20%'
|
|
|
|
|
});
|
2019-01-25 12:59:10 +01:00
|
|
|
|
this.imageUrls.push(logoHeaderLeftUrl);
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add the header text if no logo on the right was specified
|
|
|
|
|
if (logoHeaderLeftUrl && logoHeaderRightUrl) {
|
|
|
|
|
text = '';
|
|
|
|
|
} else {
|
|
|
|
|
const line1 = [
|
|
|
|
|
this.translate.instant(this.configService.instant('general_event_name')),
|
|
|
|
|
this.translate.instant(this.configService.instant('general_event_description'))
|
|
|
|
|
]
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.join(' – ');
|
|
|
|
|
const line2 = [
|
|
|
|
|
this.configService.instant('general_event_location'),
|
|
|
|
|
this.configService.instant('general_event_date')
|
|
|
|
|
]
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.join(', ');
|
|
|
|
|
text = [line1, line2].join('\n');
|
|
|
|
|
}
|
|
|
|
|
columns.push({
|
|
|
|
|
text: text,
|
|
|
|
|
style: 'headerText',
|
|
|
|
|
alignment: logoHeaderRightUrl ? 'left' : 'right'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// add the logo to the right
|
|
|
|
|
if (logoHeaderRightUrl) {
|
|
|
|
|
if (logoHeaderRightUrl.indexOf('/') === 0) {
|
|
|
|
|
logoHeaderRightUrl = logoHeaderRightUrl.substr(1); // remove trailing /
|
|
|
|
|
}
|
|
|
|
|
columns.push({
|
|
|
|
|
image: logoHeaderRightUrl,
|
|
|
|
|
fit: [180, 40],
|
|
|
|
|
alignment: 'right',
|
|
|
|
|
width: '20%'
|
|
|
|
|
});
|
2019-01-25 12:59:10 +01:00
|
|
|
|
this.imageUrls.push(logoHeaderRightUrl);
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|
2019-01-31 19:18:58 +01:00
|
|
|
|
const margin = [lrMargin ? lrMargin[0] : 75, 30, lrMargin ? lrMargin[0] : 75, 10];
|
|
|
|
|
// pdfmake order: [left, top, right, bottom]
|
2019-01-18 20:25:06 +01:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
color: '#555',
|
|
|
|
|
fontSize: 9,
|
2019-01-31 19:18:58 +01:00
|
|
|
|
margin: margin,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
columns: columns,
|
|
|
|
|
columnGap: 10
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates the footer doc definition for normal PDF documents.
|
|
|
|
|
* Adds page numbers into the footer
|
|
|
|
|
*
|
|
|
|
|
* @param currentPage holds the number of the current page
|
|
|
|
|
* @param pageCount holds the page count
|
2019-01-31 19:18:58 +01:00
|
|
|
|
* @param lrMargin optionally overriding the margins
|
2019-01-18 20:25:06 +01:00
|
|
|
|
* @returns the footer doc definition
|
|
|
|
|
*/
|
2019-07-03 16:42:08 +02:00
|
|
|
|
private getFooter(
|
|
|
|
|
currentPage: number,
|
|
|
|
|
pageCount: number,
|
|
|
|
|
lrMargin?: [number, number],
|
|
|
|
|
exportInfo?: ExportFormData
|
|
|
|
|
): object {
|
2019-01-18 20:25:06 +01:00
|
|
|
|
const columns = [];
|
2019-07-03 16:42:08 +02:00
|
|
|
|
const showPage = exportInfo && exportInfo.pdfOptions ? exportInfo.pdfOptions.includes('page') : true;
|
|
|
|
|
const showDate = exportInfo && exportInfo.pdfOptions ? exportInfo.pdfOptions.includes('date') : false;
|
2019-01-18 20:25:06 +01:00
|
|
|
|
let logoContainerWidth: string;
|
|
|
|
|
let pageNumberPosition: string;
|
2019-02-14 13:17:59 +01:00
|
|
|
|
let logoContainerSize: number[];
|
2019-02-20 15:02:30 +01:00
|
|
|
|
const logoFooterLeftUrl = this.configService.instant<any>('logo_pdf_footer_L').path;
|
|
|
|
|
const logoFooterRightUrl = this.configService.instant<any>('logo_pdf_footer_R').path;
|
2019-01-18 20:25:06 +01:00
|
|
|
|
|
2019-07-03 16:42:08 +02:00
|
|
|
|
let footerText = '';
|
|
|
|
|
if (showPage) {
|
|
|
|
|
footerText += `${currentPage} / ${pageCount}`;
|
|
|
|
|
if (showDate) {
|
|
|
|
|
footerText += '\n';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (showDate) {
|
|
|
|
|
footerText += `(${this.translate.instant('Created on')}: ${new Date().toLocaleDateString(
|
|
|
|
|
this.translate.currentLang
|
|
|
|
|
)})`;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-18 20:25:06 +01:00
|
|
|
|
// if there is a single logo, give it a lot of space
|
|
|
|
|
if (logoFooterLeftUrl && logoFooterRightUrl) {
|
|
|
|
|
logoContainerWidth = '20%';
|
2019-01-25 12:59:10 +01:00
|
|
|
|
logoContainerSize = [180, 40];
|
2019-01-18 20:25:06 +01:00
|
|
|
|
} else {
|
|
|
|
|
logoContainerWidth = '80%';
|
2019-01-25 12:59:10 +01:00
|
|
|
|
logoContainerSize = [400, 50];
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// the position of the page number depends on the logos
|
|
|
|
|
if (logoFooterLeftUrl && logoFooterRightUrl) {
|
|
|
|
|
pageNumberPosition = 'center';
|
|
|
|
|
} else if (logoFooterLeftUrl && !logoFooterRightUrl) {
|
|
|
|
|
pageNumberPosition = 'right';
|
|
|
|
|
} else if (logoFooterRightUrl && !logoFooterLeftUrl) {
|
|
|
|
|
pageNumberPosition = 'left';
|
|
|
|
|
} else {
|
|
|
|
|
pageNumberPosition = this.configService.instant('general_export_pdf_pagenumber_alignment');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add the left footer logo, if any
|
|
|
|
|
if (logoFooterLeftUrl) {
|
|
|
|
|
columns.push({
|
|
|
|
|
image: logoFooterLeftUrl,
|
2019-01-25 12:59:10 +01:00
|
|
|
|
fit: logoContainerSize,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
width: logoContainerWidth,
|
|
|
|
|
alignment: 'left'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add the page number
|
|
|
|
|
columns.push({
|
2019-07-03 16:42:08 +02:00
|
|
|
|
text: footerText,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
style: 'footerPageNumber',
|
|
|
|
|
alignment: pageNumberPosition
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// add the right footer logo, if any
|
|
|
|
|
if (logoFooterRightUrl) {
|
|
|
|
|
columns.push({
|
|
|
|
|
image: logoFooterRightUrl,
|
2019-01-25 12:59:10 +01:00
|
|
|
|
fit: logoContainerSize,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
width: logoContainerWidth,
|
|
|
|
|
alignment: 'right'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 19:18:58 +01:00
|
|
|
|
const margin = [lrMargin ? lrMargin[0] : 75, 0, lrMargin ? lrMargin[0] : 75, 10];
|
2019-01-18 20:25:06 +01:00
|
|
|
|
return {
|
2019-01-31 19:18:58 +01:00
|
|
|
|
margin: margin,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
columns: columns,
|
|
|
|
|
columnGap: 10
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2019-01-22 15:24:02 +01:00
|
|
|
|
* Downloads a pdf with the standard page definitions.
|
2019-01-18 20:25:06 +01:00
|
|
|
|
*
|
|
|
|
|
* @param docDefinition the structure of the PDF document
|
2019-01-22 15:24:02 +01:00
|
|
|
|
* @param filename the name of the file to use
|
|
|
|
|
* @param metadata
|
2019-01-18 20:25:06 +01:00
|
|
|
|
*/
|
2019-07-03 16:42:08 +02:00
|
|
|
|
public download(docDefinition: object, filename: string, metadata?: object, exportInfo?: ExportFormData): void {
|
|
|
|
|
this.getStandardPaper(docDefinition, metadata, exportInfo).then(doc => {
|
2019-01-22 15:24:02 +01:00
|
|
|
|
this.createPdf(doc, filename);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 19:18:58 +01:00
|
|
|
|
/**
|
|
|
|
|
* Downloads a pdf in landscape orientation
|
|
|
|
|
*
|
|
|
|
|
* @param docDefinition the structure of the PDF document
|
|
|
|
|
* @param filename the name of the file to use
|
|
|
|
|
* @param metadata
|
|
|
|
|
*/
|
|
|
|
|
public downloadLandscape(docDefinition: object, filename: string, metadata?: object): void {
|
2019-07-03 16:42:08 +02:00
|
|
|
|
this.getStandardPaper(docDefinition, metadata, null, null, [50, 80, 50, 75], true).then(doc => {
|
2019-01-31 19:18:58 +01:00
|
|
|
|
this.createPdf(doc, filename);
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-01-22 15:24:02 +01:00
|
|
|
|
/**
|
|
|
|
|
* Downloads a pdf with the ballot papet page definitions.
|
|
|
|
|
*
|
|
|
|
|
* @param docDefinition the structure of the PDF document
|
|
|
|
|
* @param filename the name of the file to use
|
|
|
|
|
* @param logo (optional) url of a logo to be placed as ballot logo
|
|
|
|
|
*/
|
|
|
|
|
public downloadWithBallotPaper(docDefinition: object, filename: string, logo?: string): void {
|
2019-01-25 12:59:10 +01:00
|
|
|
|
this.getBallotPaper(docDefinition, logo).then(doc => {
|
2019-01-22 15:24:02 +01:00
|
|
|
|
this.createPdf(doc, filename);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Triggers the actual page creation and saving.
|
|
|
|
|
*
|
|
|
|
|
* @param doc the finished layout
|
|
|
|
|
* @param filename the filename (without extension) to save as
|
|
|
|
|
*/
|
|
|
|
|
private createPdf(doc: object, filename: string): void {
|
|
|
|
|
pdfMake.createPdf(doc).getBlob(blob => {
|
|
|
|
|
saveAs(blob, `${filename}.pdf`, { autoBOM: true });
|
2019-01-22 11:59:16 +01:00
|
|
|
|
});
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Definition of styles for standard papers
|
|
|
|
|
*
|
|
|
|
|
* @returns an object that contains all pdf styles
|
|
|
|
|
*/
|
|
|
|
|
private getStandardPaperStyles(): object {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
const pageSize = this.configService.instant('general_export_pdf_pagesize');
|
2019-01-18 20:25:06 +01:00
|
|
|
|
return {
|
|
|
|
|
title: {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
fontSize: pageSize === 'A5' ? 14 : 16,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
margin: [0, 0, 0, 20],
|
|
|
|
|
bold: true
|
|
|
|
|
},
|
|
|
|
|
subtitle: {
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
margin: [0, -20, 0, 20],
|
|
|
|
|
color: 'grey'
|
|
|
|
|
},
|
2019-01-24 14:40:05 +01:00
|
|
|
|
preamble: {
|
|
|
|
|
margin: [0, 0, 0, 10]
|
|
|
|
|
},
|
2019-01-18 20:25:06 +01:00
|
|
|
|
headerText: {
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
margin: [0, 10, 0, 0]
|
|
|
|
|
},
|
|
|
|
|
footerPageNumber: {
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
margin: [0, 15, 0, 0],
|
|
|
|
|
color: '#555'
|
|
|
|
|
},
|
|
|
|
|
boldText: {
|
|
|
|
|
bold: true
|
|
|
|
|
},
|
|
|
|
|
smallText: {
|
|
|
|
|
fontSize: 8
|
|
|
|
|
},
|
2019-01-24 14:40:05 +01:00
|
|
|
|
heading2: {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
fontSize: pageSize === 'A5' ? 12 : 14,
|
2019-01-24 14:40:05 +01:00
|
|
|
|
margin: [0, 0, 0, 10],
|
|
|
|
|
bold: true
|
|
|
|
|
},
|
2019-01-18 20:25:06 +01:00
|
|
|
|
heading3: {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
fontSize: pageSize === 'A5' ? 10 : 12,
|
2019-01-18 20:25:06 +01:00
|
|
|
|
margin: [0, 10, 0, 0],
|
|
|
|
|
bold: true
|
2019-01-24 14:40:05 +01:00
|
|
|
|
},
|
2019-01-25 18:14:44 +01:00
|
|
|
|
userDataHeading: {
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
margin: [0, 10],
|
|
|
|
|
bold: true
|
|
|
|
|
},
|
|
|
|
|
userDataTopic: {
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
margin: [0, 5]
|
|
|
|
|
},
|
|
|
|
|
userDataValue: {
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
margin: [15, 5]
|
|
|
|
|
},
|
2019-01-24 14:40:05 +01:00
|
|
|
|
tocEntry: {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
fontSize: pageSize === 'A5' ? 10 : 11,
|
2019-01-24 14:40:05 +01:00
|
|
|
|
margin: [0, 0, 0, 0],
|
|
|
|
|
bold: false
|
|
|
|
|
},
|
2019-06-04 14:41:12 +02:00
|
|
|
|
tocHeaderRow: {
|
|
|
|
|
fontSize: 8,
|
|
|
|
|
italics: true
|
|
|
|
|
},
|
|
|
|
|
tocSubEntry: {
|
|
|
|
|
fontSize: pageSize === 'A5' ? 9 : 10,
|
|
|
|
|
color: '#404040'
|
|
|
|
|
},
|
2019-01-24 14:40:05 +01:00
|
|
|
|
tocCategoryEntry: {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
fontSize: pageSize === 'A5' ? 10 : 11,
|
2019-01-24 14:40:05 +01:00
|
|
|
|
margin: [10, 0, 0, 0],
|
|
|
|
|
bold: false
|
|
|
|
|
},
|
|
|
|
|
tocCategoryTitle: {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
fontSize: pageSize === 'A5' ? 10 : 11,
|
2019-01-24 14:40:05 +01:00
|
|
|
|
margin: [0, 0, 0, 4],
|
|
|
|
|
bold: true
|
|
|
|
|
},
|
2019-06-28 13:39:16 +02:00
|
|
|
|
tocSubcategoryTitle: {
|
|
|
|
|
fontSize: pageSize === 'A5' ? 9 : 10,
|
|
|
|
|
margin: [0, 0, 0, 4],
|
|
|
|
|
bold: true
|
|
|
|
|
},
|
2019-01-24 14:40:05 +01:00
|
|
|
|
tocCategorySection: {
|
|
|
|
|
margin: [0, 0, 0, 10]
|
2019-01-25 18:14:44 +01:00
|
|
|
|
},
|
|
|
|
|
userDataTitle: {
|
|
|
|
|
fontSize: 26,
|
|
|
|
|
margin: [0, 0, 0, 0],
|
|
|
|
|
bold: true
|
|
|
|
|
},
|
|
|
|
|
tableHeader: {
|
|
|
|
|
bold: true,
|
|
|
|
|
fillColor: 'white'
|
2019-01-25 15:36:02 +01:00
|
|
|
|
},
|
|
|
|
|
listParent: {
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
margin: [0, 5]
|
|
|
|
|
},
|
|
|
|
|
listChild: {
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
margin: [0, 5]
|
2019-04-26 16:23:48 +02:00
|
|
|
|
},
|
|
|
|
|
textItem: {
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
margin: [0, 7]
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2019-01-22 15:24:02 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Definition of styles for ballot papers
|
|
|
|
|
*
|
|
|
|
|
* @returns an object that contains a limited set of pdf styles
|
|
|
|
|
* used for ballots
|
|
|
|
|
*/
|
|
|
|
|
private getBlankPaperStyles(): object {
|
|
|
|
|
return {
|
|
|
|
|
title: {
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
bold: true,
|
|
|
|
|
margin: [30, 30, 0, 0]
|
|
|
|
|
},
|
|
|
|
|
description: {
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
margin: [30, 0, 0, 0]
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2019-01-25 12:59:10 +01:00
|
|
|
|
|
2019-02-20 15:02:30 +01:00
|
|
|
|
/**
|
|
|
|
|
* Adds the footer images to the imageUrls-list, cause the create footer function is async and
|
|
|
|
|
* potentially called after loadAllImages was called.
|
|
|
|
|
*/
|
|
|
|
|
private loadFooterImages(): void {
|
|
|
|
|
const logoFooterLeftUrl = this.configService.instant<any>('logo_pdf_footer_L').path;
|
|
|
|
|
const logoFooterRightUrl = this.configService.instant<any>('logo_pdf_footer_R').path;
|
|
|
|
|
|
|
|
|
|
if (logoFooterLeftUrl) {
|
|
|
|
|
this.imageUrls.push(logoFooterLeftUrl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (logoFooterRightUrl) {
|
|
|
|
|
this.imageUrls.push(logoFooterRightUrl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-25 12:59:10 +01:00
|
|
|
|
/**
|
|
|
|
|
* 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> {
|
2019-02-20 15:02:30 +01:00
|
|
|
|
if (url.indexOf('/') === 0) {
|
|
|
|
|
url = url.substr(1);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-25 12:59:10 +01:00
|
|
|
|
if (!pdfMake.vfs[url]) {
|
|
|
|
|
const base64 = await this.convertUrlToBase64(url);
|
|
|
|
|
pdfMake.vfs[url] = base64;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-13 18:19:29 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates the title for the motion list as pdfmake doc definition
|
|
|
|
|
*
|
|
|
|
|
* @returns The motion list title for the PDF document
|
|
|
|
|
*/
|
|
|
|
|
public createTitle(configVariable: string): object {
|
|
|
|
|
const titleText = this.translate.instant(this.configService.instant<string>(configVariable));
|
2019-06-04 14:41:12 +02:00
|
|
|
|
|
2019-05-13 18:19:29 +02:00
|
|
|
|
return {
|
|
|
|
|
text: titleText,
|
|
|
|
|
style: 'title'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates the preamble for the motion list as pdfmake doc definition
|
|
|
|
|
*
|
|
|
|
|
* @returns The motion list preamble for the PDF document
|
|
|
|
|
*/
|
|
|
|
|
public createPreamble(configVariable: string): object {
|
|
|
|
|
const preambleText = this.configService.instant<string>(configVariable);
|
|
|
|
|
|
|
|
|
|
if (preambleText) {
|
|
|
|
|
return {
|
|
|
|
|
text: preambleText,
|
|
|
|
|
style: 'preamble'
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getPageBreak(): Object {
|
|
|
|
|
return {
|
|
|
|
|
text: '',
|
|
|
|
|
pageBreak: 'after'
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generates the table definition for the TOC
|
|
|
|
|
*
|
|
|
|
|
* @param tocBody the body of the table
|
|
|
|
|
* @returns The table of contents as doc definition
|
|
|
|
|
*/
|
2019-06-04 14:41:12 +02:00
|
|
|
|
public createTocTableDef(
|
|
|
|
|
tocBody: object[],
|
|
|
|
|
style: StyleType = StyleType.DEFAULT,
|
|
|
|
|
borderStyle: BorderType = BorderType.DEFAULT,
|
|
|
|
|
...header: object[]
|
|
|
|
|
): object {
|
2019-05-13 18:19:29 +02:00
|
|
|
|
return {
|
|
|
|
|
table: {
|
2019-06-04 14:41:12 +02:00
|
|
|
|
headerRows: header[0] ? header.length : 0,
|
|
|
|
|
keepWithHeaderRows: header[0] ? header.length : 0,
|
|
|
|
|
dontBreakRows: true,
|
2019-05-13 18:19:29 +02:00
|
|
|
|
widths: ['auto', '*', 'auto'],
|
2019-06-04 14:41:12 +02:00
|
|
|
|
body: header[0] ? [...header, ...tocBody] : tocBody
|
2019-05-13 18:19:29 +02:00
|
|
|
|
},
|
2019-06-04 14:41:12 +02:00
|
|
|
|
layout: borderStyle,
|
2019-05-13 18:19:29 +02:00
|
|
|
|
style: style
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Function, that creates a line for the 'Table of contents'
|
|
|
|
|
*
|
|
|
|
|
* @param identifier The identifier/prefix for the line
|
|
|
|
|
* @param title The name of the line
|
|
|
|
|
* @param pageReference Defaults to the page, where the object begins
|
|
|
|
|
* @param style Optional style. Defaults to `'tocEntry'`
|
|
|
|
|
*
|
|
|
|
|
* @returns A line for the toc
|
|
|
|
|
*/
|
|
|
|
|
public createTocLine(
|
|
|
|
|
identifier: string,
|
|
|
|
|
title: string,
|
|
|
|
|
pageReference: string,
|
2019-06-04 14:41:12 +02:00
|
|
|
|
style: StyleType = StyleType.DEFAULT,
|
|
|
|
|
...subTitle: object[]
|
|
|
|
|
): Array<Object> {
|
2019-05-13 18:19:29 +02:00
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
text: identifier,
|
|
|
|
|
style: style
|
|
|
|
|
},
|
|
|
|
|
{
|
2019-06-04 14:41:12 +02:00
|
|
|
|
text: [title, ...subTitle],
|
2019-05-13 18:19:29 +02:00
|
|
|
|
style: 'tocEntry'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
pageReference: pageReference,
|
|
|
|
|
style: 'tocEntry',
|
|
|
|
|
alignment: 'right'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
}
|
2019-06-04 14:41:12 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Function to create an inline line in the toc.
|
|
|
|
|
*
|
|
|
|
|
* @param text The text for the line.
|
|
|
|
|
* @param bold Optional boolean, if the text should be bold - defaults to `false`.
|
|
|
|
|
*
|
|
|
|
|
* @returns {Object} An object for `DocDefinition` for `pdf-make`.
|
|
|
|
|
*/
|
|
|
|
|
public createTocLineInline(text: string): Object {
|
|
|
|
|
return {
|
|
|
|
|
text: '\n' + text,
|
|
|
|
|
style: StyleType.SUB_ENTRY
|
|
|
|
|
};
|
|
|
|
|
}
|
2019-06-11 17:55:56 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Draw a circle on its position on the paper
|
|
|
|
|
*
|
|
|
|
|
* @param y vertical offset
|
|
|
|
|
* @param size the size of the circle
|
|
|
|
|
* @returns an array containing one circle definition for pdfMake
|
|
|
|
|
*/
|
|
|
|
|
public drawCircle(y: number, size: number): object[] {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
type: 'ellipse',
|
|
|
|
|
x: 0,
|
|
|
|
|
y: y,
|
|
|
|
|
lineColor: 'black',
|
|
|
|
|
r1: size,
|
|
|
|
|
r2: size
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
}
|
2019-01-18 20:25:06 +01:00
|
|
|
|
}
|