import { Injectable, OnInit } from '@angular/core';
import { UtilService } from '../util-service/util.service';
import { PdfDocument } from './pdf-document'
import { PublicationService } from '../publication-service/publication.service';
import { DataResolverService } from '../data-resolver/data-resolver.service';
import { LabelService } from '../label-service/label-service.service';
@Injectable({
providedIn: 'root'
})
export class PdfDownloadService implements OnInit {
headerFooterImg: any;
notFoundImg: any;
printPageGeneratedFromLabel: string;
printPageNumberOfPagesLabel: string;
constructor(
public utilService: UtilService,
public pubService: PublicationService,
public labelService: LabelService,
public dataService: DataResolverService
) {
// Load local images
this.headerFooterImg = new Image();
this.headerFooterImg.src = require('src/v2/assets/img/pdf-header-footer.png');
this.notFoundImg = new Image();
this.notFoundImg.src = require('src/v2/assets/img/pdf-not-found.png');
// Load label texts
this.labelService.getLabel('printPageGeneratedFrom').then(label => this.printPageGeneratedFromLabel = label);
this.labelService.getLabel('printPageNumberOfPages').then(label => this.printPageNumberOfPagesLabel = label);
}
ngOnInit(): void {
}
/**
* Adds a column of tables.
*/
private addTableColumn(pdf: PdfDocument, column: any, width: number): void {
const tableMarginBottom = 10;
pdf.cursorX = pdf.pageWidth - pdf.margins.left - width;
let tables = column.querySelectorAll('table');
for (let tableIndex = 0; tableIndex < tables.length; tableIndex++) {
let table = tables[tableIndex];
// Caption
let caption = '';
let captionElement = table.querySelector('.caption')
if (captionElement) {
caption = captionElement.innerHTML;
} else {
caption = table.parentNode.querySelector('h3').textContent;
}
pdf.addTable(table, caption, width);
}
pdf.cursorY += tableMarginBottom;
}
/**
* Adds a column of images.
*/
private addImageColumn(pdf: PdfDocument, column: any): void {
// Spacing
const imageMarginLeftRight = 50;
const imageMarginTopBottom = 30;
const imageWidth = pdf.pageWidth / 2 - imageMarginLeftRight * 2;
const imageHeight = 130;
const marginBottom = 40;
pdf.cursorX = pdf.pageWidth / 4 - imageWidth / 2;
// Parse imgs
let images = column.querySelectorAll('img');
for (let imageIndex = 0; imageIndex < images.length; imageIndex++) {
pdf.cursorY += imageMarginTopBottom;
try {
pdf.addImage(images[imageIndex], imageWidth, imageHeight);
} catch (Error) {
pdf.addImage(this.notFoundImg, imageWidth, imageHeight);
}
pdf.cursorY += imageMarginTopBottom;
}
pdf.cursorY += marginBottom;
}
/**
* Adds a row of boxes with appropriates images.
*/
private addAppropriates(pdf: PdfDocument, row: any): void {
// Spacing
const cardsPerRow = 4;
const cardMargin = 20
const cardWidth = (pdf.pageWidth - pdf.margins.left * 2 - cardMargin * (cardsPerRow - 1)) / cardsPerRow;
const cardPaddingRightLeft = 30;
const cardPaddingTop = 20;
const cardPaddingBottom = 30;
const marginBottom = 40;
pdf.cursorX = pdf.margins.left;
// Caption
let caption = row.querySelector('h3').textContent;
pdf.addText(caption, 12);
// Cards
let cards = row.querySelectorAll('.card');
for (let cardIndex = 0; cardIndex < cards.length; cardIndex++) {
let image = cards[cardIndex].querySelector('img');
let name = cards[cardIndex].querySelector('.card-body').textContent.trim();
pdf.addBox(pdf.cursorX, pdf.cursorY, cardWidth, cardWidth);
pdf.addImageAt(image, pdf.cursorX + cardPaddingRightLeft, pdf.cursorY + cardPaddingTop, cardWidth - cardPaddingRightLeft * 2, cardWidth / 2);
pdf.addTextAt(name, pdf.cursorX + cardWidth / 2, pdf.cursorY + cardWidth - cardPaddingBottom, 12, '#282828', 'center');
// Advance
if (cardIndex % cardsPerRow == cardsPerRow - 1 || cardIndex == cards.length - 1) {
pdf.cursorX = pdf.margins.left;
if (!pdf.pageBreak(cardWidth + cardMargin)) {
pdf.cursorY += cardWidth + cardMargin;
}
} else {
pdf.cursorX += cardWidth + cardMargin;
}
}
pdf.cursorY += marginBottom;
}
/**
* Parses a card element and fetches the title, info and description, along with its rendered height.
**/
private parseRelatedInfoCard(pdf: PdfDocument, element: any, contentWidth): any {
let contentHeight = 0;
// Card title
let title = element.querySelector('.card-header span').innerText;
if (title != '') {
contentHeight += 25;
}
// Card description
const fontSize = 10;
const spaceSize = 2;
let descLines = [];
let descElement = element.querySelector('.card-body>.card-text>p');
if (descElement) {
descLines = pdf.wordWrap(descElement.innerText, contentWidth);
contentHeight += descLines.length * (fontSize + spaceSize);
}
// Card links
let linkElements = element.querySelectorAll('a.list-group-item');
let links = [];
if (linkElements.length) {
for (let linkIndex = 0; linkIndex < linkElements.length; linkIndex++) {
let link = linkElements[linkIndex];
let titleElement = link.querySelector('.link-group-title');
let descElement = link.querySelector('p');
let height = 5;
height += fontSize;
if (descElement)
height += fontSize + 5;
height += 5;
// Get absolute link
let absLink = document.createElement('a');
absLink.href = link.getAttribute('href');
links.push({
title: titleElement.innerText,
href: absLink.href,
desc: descElement ? descElement.innerText : '',
height: height
})
contentHeight += height;
}
contentHeight += 10;
}
if (descLines.length || links.length) {
return { title: title, descLines: descLines, links: links, height: contentHeight };
}
return null;
}
/**
* Adds a row of boxes with related info.
**/
private addRelatedInfo(pdf: PdfDocument, row: any): void {
// Cards
let cards = row.querySelectorAll('.card');
const cardWidth = (pdf.pageWidth - pdf.margins.left * 2) / cards.length;
const cardPadding = 20;
const marginBottom = 40;
const linkColor = '#0f58d6';
// Find content and height
let cardContent = [];
let cardContentHeight = 0;
for (let cardIndex = 0; cardIndex < cards.length; cardIndex++) {
let content = this.parseRelatedInfoCard(pdf, cards[cardIndex], cardWidth - cardPadding * 2);
if (content) {
cardContent.push(content);
cardContentHeight = Math.max(cardContentHeight, content.height);
}
}
// No cards with renderable content, skip
if (cardContent.length == 0) {
return;
}
// Render
const cardHeight = cardContentHeight + cardPadding * 2;
pdf.pageBreak(cardHeight + 15)
// Header
let headerElement = row.parentNode.querySelector('h2'); // Preceding <h2>
if (headerElement) {
pdf.addText(headerElement.textContent, 12);
}
// Boxes
for (let contentIndex = 0; contentIndex < cardContent.length; contentIndex++) {
let contentY = cardPadding;
let content = cardContent[contentIndex];
pdf.addBox(pdf.cursorX, pdf.cursorY, cardWidth, cardHeight);
// Title
pdf.addTextAt(content.title, pdf.cursorX + cardPadding, pdf.cursorY + contentY, 12);
contentY += 25;
// Links
if (content.links.length) {
for (let linkIndex = 0; linkIndex < content.links.length; linkIndex++) {
pdf.addLink(pdf.cursorX + cardPadding, pdf.cursorY + contentY,
cardWidth - cardPadding * 2, content.links[linkIndex].height, content.links[linkIndex].href);
pdf.addLine(pdf.cursorX + cardPadding, pdf.cursorY + contentY + content.links[linkIndex].height,
pdf.cursorX + cardWidth - cardPadding * 2, pdf.cursorY + contentY + content.links[linkIndex].height);
pdf.addTextAt(content.links[linkIndex].title, pdf.cursorX + cardPadding + 5, pdf.cursorY + contentY + 5, 10, linkColor);
pdf.addTextAt(content.links[linkIndex].desc, pdf.cursorX + cardPadding + 5, pdf.cursorY + contentY + 20, 10);
contentY += content.links[linkIndex].height;
}
contentY += 10;
}
// Description
if (content.descLines.length) {
pdf.addTextAt(content.descLines, pdf.cursorX + cardPadding, pdf.cursorY + contentY, 10);
}
pdf.cursorX += cardWidth;
}
pdf.cursorX = pdf.margins.left;
pdf.cursorY += cardHeight + marginBottom;
}
/**
* Returns the Terms & Conditions text in a Promise.
*/
private getTermsAndConditions(): Promise<any>
{
const pubPath = this.pubService.getPublicationPath();
const pageUrl = `${pubPath}/footer/terms-and-conditions`;
return new Promise((resolve, reject) => {
this.dataService.getPageData(pageUrl).then(page => {
// Get HTML from extracted data
let html = this.utilService.extract(page, 'Regions', region => region.Name === 'main', 'Entities', [0], 'EmbeddedBodytext', [0], 'BodyText');
// Fix spacing
html = html.replace(/<br ? \/?>\n/g, "<br />");
html = html.replace(/<br ? \/?>/g, "<br />\n");
html = html.replace(/<\/strong>\n/g, "</strong>");
html = html.replace(/<\/strong>/g, "</strong>\n");
// Convert HTML to text
let tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
resolve(tempDiv.textContent);
});
});
}
/**
* Build the PDF using the data of the current product page and download it when finished.
*/
public download(): void {
// Spacing
const pdfMarginTop = 80;
const pdfMarginBottom = 60;
const pdfMarginLeft = 30;
// Create document
let pdf = new PdfDocument({
top: pdfMarginTop,
bottom: pdfMarginBottom,
left: pdfMarginLeft
});
// Page header & footer images
pdf.addPerPage((page: number) => {
// Header
pdf.addImageAt(this.headerFooterImg, 0, 0, pdf.pageWidth, 35);
// Generated on
let generatedText = this.printPageGeneratedFromLabel;
generatedText = generatedText.replace(/\{site\}/, window.location.hostname);
generatedText = generatedText.replace(/\{date\}/, this.utilService.getCurrentDate('-'));
pdf.addTextAt(generatedText, pdf.pageWidth - pdf.margins.left, 14, 10, 'white', 'right');
// Footer
pdf.addImageAt(this.headerFooterImg, 0, pdf.pageHeight - 35, pdf.pageWidth, 35);
// Page counter
let pageCounterText = this.printPageNumberOfPagesLabel;
pageCounterText = pageCounterText.replace(/\{page\}/, String(page));
pageCounterText = pageCounterText.replace(/\{total\}/, String(pdf.getTotalPages()));
pdf.addTextAt(pageCounterText, pdf.pageWidth - pdf.margins.left, pdf.pageHeight - 22, 10, 'white', 'right');
});
// Locations of top image and text
const cadImageX = 60;
const cadImageY = 60;
const cadImageWidth = 200;
const cadImageHeight = 200;
const textX = 290;
const textY = 80;
const textMarginBottom = 60;
// CAD image
try {
pdf.addImageAt(document.querySelector('.pdf-picture'), cadImageX, cadImageY, cadImageWidth, cadImageHeight);
} catch (Error) {
pdf.addImageAt(this.notFoundImg, cadImageX, cadImageY, cadImageWidth, cadImageHeight);
}
// Top info
let title = document.querySelector('.pdf-title').textContent.trim();
let category = document.querySelector('.pdf-category').textContent.trim();
let description = document.querySelector('.pdf-description').textContent.trim();
pdf.cursorX = textX;
pdf.cursorY = textY;
pdf.addText(title, 20);
pdf.addText(category, 16);
pdf.addText(description, 14);
// Headline
const headlineMinY = 220;
const headlineMarginBottom = 10;
pdf.cursorX = pdf.margins.left;
pdf.cursorY = Math.max(headlineMinY, pdf.cursorY) + textMarginBottom;
let headline = document.querySelector('.headline').textContent;
pdf.addText(headline, 16);
pdf.cursorY += headlineMarginBottom;
// Add content blocks
let pdfBlocks = document.querySelectorAll('content-block');
for (let blockIndex = 0; blockIndex < pdfBlocks.length; blockIndex++) {
let blockRows = pdfBlocks[blockIndex].querySelectorAll('.row');
for (let rowIndex = 0; rowIndex < blockRows.length; rowIndex++) {
// Appropriates
let appr = blockRows[rowIndex].querySelector('.appropriates');
if (appr) {
this.addAppropriates(pdf, appr);
continue;
}
// Full width table
let children = blockRows[rowIndex].children;
if (children.length == 1) {
pdf.cursorX = pdf.margins.left;
this.addTableColumn(pdf, children[0], pdf.pageWidth - pdf.margins.left * 2);
// Tables and images at half width
} else if (children.length == 2) {
// Tables
let tableStartCursorY = pdf.cursorY;
let tableStartPage = pdf.getCurrentPage();
pdf.cursorX = pdf.pageWidth / 2;
this.addTableColumn(pdf, children[1], pdf.pageWidth / 2 - pdf.margins.left);
let tableEndCursorY = pdf.cursorY;
let tableEndPage = pdf.getCurrentPage();
// Jump back to top
pdf.setCurrentPage(tableStartPage);
pdf.cursorY = tableStartCursorY;
// Images
this.addImageColumn(pdf, children[0]);
// Set cursor to the end of either the table or the images, whichever comes last
if (pdf.getCurrentPage() < tableEndPage) {
pdf.setCurrentPage(tableEndPage);
pdf.cursorY = tableEndCursorY;
} else if (pdf.getCurrentPage() == tableEndPage) {
pdf.cursorY = Math.max(pdf.cursorY, tableEndCursorY);
}
}
}
}
// Add related info
let infoCards = document.querySelectorAll('.card-group');
for (let cardIndex = 0; cardIndex < infoCards.length; cardIndex++) {
this.addRelatedInfo(pdf, infoCards[cardIndex]);
}
this.getTermsAndConditions().then(terms => {
// Terms and conditions on new page
pdf.addPage();
pdf.setCurrentPage(pdf.getTotalPages());
pdf.addTextAt(terms, pdf.margins.left, pdf.margins.top, 10);
// Finally download
let filename = title + '_' + this.utilService.getCurrentDate() + '.pdf';
pdf.save(filename);
});
}
}