1

I am trying to generate a PDF from the design I have in HTML. I am working with pupeeter and backend mongodb I do have different templates in the frontend which they have different HTML. I have tried to save with puppeeter but I am not able to go further. Here is my code what I have tried.

PDF Controller

const puppeteer = require('puppeteer');
const fs = require('fs');

module.exports =  {
    async  createPDF () {
  // launch a new chrome instance
  const browser = await puppeteer.launch({
    headless: true
  })

  // create a new page
  const page = await browser.newPage()

  // set your html as the pages content
  const html = fs.readFileSync(`${__dirname}/template.html`, 'utf8')
  // create a pdf buffer
  const pdfBuffer = await page.pdf({
    format: 'A4'
  })

  // or a .pdf file
  await page.pdf({
    format: 'A4',
    path: `${__dirname}/cv.pdf`
  })

  // close the browser
  await browser.close()
}
}

routes.post("/pdf", PDFController.createPDF);

Service in FE.

@Injectable({providedIn: "root"})
export class PdfService {
  public baseUrl = environment.backend;


  constructor(private http: HttpClient) {
  }
  public setPDF(data) {
    return this.http.post(`${this.baseUrl}/pdf`, data).subscribe(res => console.log(res));
  }
  getPdf() {


    const httpOptions = {
      responseType: "blob" as "json",

    };

    return this.http.get(`${this.baseUrl}/help/cv`, httpOptions);
  }

}

Here is my TS.

    public downloadPdf() {
    this.pdfService.setPDF(this.model);
 
      this.pdfService.getPdf().subscribe((data) => {

          console.log(data);

      });

  }

Here is my `HTML' code.

<app-paginated-view [pageSize]="'A4'" *ngIf="model && model.theme === 'firstTemplate'"
                      [pageNumbers]="model?.showPageNumbers"
                      class="Grid-grid-column" id="content"
                      [style.color]="model.style?.color">
    <ng-container>
      <div class="Grid-grid-row" pageContent (click)="setFirstCat()" class="row" #content
           [ngClass]="{ 'isCatActive': selectedFirstCat}">
        <div class="Grid-grid-column Grid-grid-column-12">
          <div class="Header-header-header Header-header-minHeight first-template-header">
            <div class="Title-title-titleWrapper first-template-titleWrapper">
              <h4 *ngIf="model?.hideName">{{model.personalData[0].firstName}} {{model.personalData[0].lastName}}</h4>
              <h5>{{model?.newJobTitle}}</h5>
              <div [innerHtml]="model?.description"></div>
            </div>
            <div class="Photo-photo-photoWrapper" *ngIf="model?.showCVPhoto">
              <div class="Photo-photo-photo first-template-photo">
                <img [src]="model?.photo" height="100" style="cursor:  pointer">
              </div>
            </div>
          </div>
          <div *ngIf="selectedFirstCat">
            <div clickOutside (clickOutside)="removeClick()">
              <ul>
                <li class="fa fa-pencil addIconTop" (click)="editHeaderDialog({edit: true, model: model})"></li>
                <li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
              </ul>
            </div>
          </div>
        </div>
      </div>
    </ng-container>
    <ng-container class="Grid-grid-grid">
      <ng-container class="Unit-unit-unitGroup" *ngFor="let personalData of model?.personalData; let id = index">
        <div pageContent *ngIf="personalData.visible">
          <div class="Unit-unit-unitGroup" pageContent
               [ngClass]="{ 'isCatActive': selectedCategory === category.PersonalData}">
            <div *ngIf="selectedCategory === category.PersonalData">
              <div clickOutside (clickOutside)="removeClick()">
                <ul>
                  <li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
                  <li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
                  <li class="fa fa-arrow-down moveIconDown"></li>
                  <li class="fa fa-arrow-up moveIconTop"></li>
                </ul>
              </div>
            </div>

            <div pageContent class="col-md-12" (click)="setCategory(category.PersonalData)">
              <div class="row height">
                <div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
                <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height" [style.color]="model.style.color">
                  <div class="Text-text-wrapper">
                    <div class="Text-Text-text" >{{'category.PersonalData' | translate}}</div>
                  </div>
                </h3>
              </div>
            </div>
            <div pageContent class="container-fluid">
              <app-personal-data [personalData]="personalData" [model]="model" [id]="id"
                                 (deselectCategory)="test($event)">
              </app-personal-data>
            </div>
          </div>
        </div>
      </ng-container>

      <!-- Career Component -->
      <ng-container *ngFor="let careers of model?.careers; let id = index" class="Unit-unit-unitGroup">
        <div *ngIf="selectedCategory === category.Career">
          <div clickOutside (clickOutside)="removeClick()">
            <ul>
              <li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
              <li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
              <button (click)="deleteCareerCategory(id)" class="btn"><i
                class="fa fa-trash deleteIconRight"></i></button>
              <li class="fa fa-arrow-down moveIconDown"></li>
              <li class="fa fa-arrow-up moveIconTop"></li>
            </ul>
          </div>
        </div>
        <div pageContent class="col-md-12" (click)="setCategory(category.Career)">
          <div class="row height">
            <div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
            <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height" [style.color]="model.style.color">
              <div class="Text-text-wrapper">
                <div class="Text-Text-text">{{'category.Career' | translate}}</div>
              </div>
            </h3>
          </div>
        </div>
        <ng-container *ngFor="let careerObj of careers.subCategories; let i = index">
          <div pageContent class="container-fluid">
            <div pageContent [ngClass]="{ 'isActive': selectedCareerIndex === i}">
              <div class="Line-line-container" (click)="setCareerIndex(i)">
                <div class="Line-line-line">
                  <div class="Field-field-fieldBase first-template-fieldField">
                    <div class="Text-Text-wrapper">
                      <div pageContent class="Text-Text-text">
                        {{careerObj.startDate | date:'MM/yyyy'}}
                        <div class="float-right" *ngIf="!careerObj.today">{{careerObj.endDate | date:'MM/yyyy'}}</div>
                        <div class="float-right" *ngIf="careerObj.today">{{'career.present' | translate}}</div>
                      </div>
                    </div>
                  </div>
                  <div class="Field-field-fieldBase first-template-fieldValue">
                    <div class="Text-Text-wrapper">
                      <div pageContent class="Text-Text-text-wrapper">
                        <b>{{careerObj.role}}</b>
                      </div>
                    </div>
                  </div>
                  <div class="Field-field-fieldBase first-template-fieldValue">
                    <div class="Text-Text-wrapper">
                      <div pageContent class="Text-Text-text-wrapper">
                        {{careerObj.name}}
                      </div>
                    </div>
                  </div>
                  <div class="Field-field-fieldBase first-template-fieldValue">
                    <div class="Text-Text-wrapper">
                      <div pageContent class="Text-Text-text-wrapper" aria-multiline="true"
                           [innerHTML]="careerObj.description">
                      </div>
                    </div>
                  </div>
                  <div class="Field-field-fieldBase first-template-fieldValue" *ngIf="careerObj.showCompanyUrl">
                    <div class="Text-Text-wrapper">
                      <div pageContent class="Text-Text-text-wrapper">
                        <a target="_blank" [href]="careerObj.companyUrl">{{careerObj.companyUrl}}
                        </a>
                      </div>
                    </div>
                  </div>

                  <ng-container pageContent *ngIf="selectedCareerIndex === i">
                    <div clickOutside (clickOutside)="removeClick()">
                      <ul>
                        <li class="fa fa-pencil addIconTop"
                            (click)="editCareer({edit: true, career: careerObj, model: model})">
                        </li>
                        <li class="fa fa-plus addIconBottom"
                            (click)="addCareer({edit: false, model: model, career: false})"></li>
                        <button [disabled]="careers.subCategories.length < 2" (click)="deleteCareerSubCategory(i)"
                                class="btn"><i class="fa fa-trash deleteIconRight"></i></button>
                        <li class="fa fa-arrow-down moveIconDown"></li>
                        <li class="fa fa-arrow-up moveIconTop"></li>
                      </ul>
                    </div>
                  </ng-container>
                </div>
              </div>
            </div>
          </div>
        </ng-container>
        <ng-container *ngFor="let emptyObj of careers.emptySubContents; let iEmpty = index">
          <app-empty-object pageContent [emptyObj]="emptyObj" [iEmpty]="iEmpty" [model]="model" [isFromCareer]="true">
          </app-empty-object>
        </ng-container>
      </ng-container>

      <!--Education Component-->
      <ng-container *ngFor="let education of model?.education; let index = index" class="Unit-unit-unitGroup">
        <ng-container *ngIf="education.subCategories.length > 0">
          <div *ngIf="selectedCategory === category.Education"
               [ngClass]="{ 'isCatActive': selectedCategory === category.Education}">
            <div clickOutside (clickOutside)="removeClick()">
              <ul>
                <li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
                <li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
                <li class="fa fa-trash deleteIconRight" (click)="deleteEducationCategory(index)"></li>
                <li class="fa fa-arrow-down moveIconDown"></li>
                <li class="fa fa-arrow-up moveIconTop"></li>
              </ul>
            </div>
          </div>
          <div pageContent class="col-md-12" (click)="setCategory(category.Education)">
            <div class="row height">
              <div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
              <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
                <div class="Text-text-wrapper">
                  <div class="Text-Text-text">{{'category.Education' | translate}}</div>
                </div>
              </h3>
            </div>
          </div>
          <ng-container *ngFor="let educationObj of education.subCategories; let i = index">
            <div pageContent class="container-fluid">
              <div pageContent [ngClass]="{ 'isActive': selectedIndex === i}">
                <div pageContent class="Line-line-container" (click)="setIndex(i)">
                  <div class="Line-line-line">
                    <div class="Field-field-fieldBase first-template-fieldField">
                      <div class="Text-Text-wrapper">
                        <div pageContent class="Text-Text-text">
                          {{educationObj.startDate | date:'MM/yyyy'}}
                          <div class="float-right" *ngIf="!educationObj.today">{{educationObj.endDate | date:'MM/yyyy'}}
                          </div>
                          <div class="float-right" *ngIf="educationObj.today">{{'present' | translate}}</div>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div class="Field-field-fieldBase first-template-fieldValue">
                    <div class="Text-Text-wrapper">
                      <div class="Text-Text-text-wrapper">
                        <b>{{educationObj.title}}</b>
                      </div>
                    </div>
                  </div>
                  <div class="Field-field-fieldBase first-template-fieldValue">
                    <div class="Text-Text-wrapper">
                      <div class="Text-Text-text-wrapper">
                        {{educationObj.name}}
                      </div>
                    </div>
                  </div>
                  <div class="Field-field-fieldBase first-template-fieldValue">
                    <div class="Text-Text-wrapper">
                      <div class="Text-Text-text-wrapper" aria-multiline="true" [innerHTML]="educationObj.description">
                      </div>
                    </div>
                  </div>
                  <ng-container pageContent *ngIf="selectedIndex === i">
                    <div clickOutside (clickOutside)="removeClick()">
                      <ul>
                        <li class="fa fa-pencil addIconTop"
                            (click)="editEducation({edit: true, education: educationObj, model: model})"></li>
                        <button class="btn"><i class="fa fa-plus addIconBottom"
                                               (click)="addEducation({edit: false, model: model})"></i></button>
                        <button [disabled]="education.subCategories.length === 1"
                                (click)="deleteEducationSubCategory(i)" class="btn"><i
                          class="fa fa-trash deleteIconRight"></i></button>
                        <li class="fa fa-arrow-down moveIconDown"></li>
                        <li class="fa fa-arrow-up moveIconTop"></li>
                      </ul>
                    </div>
                  </ng-container>
                </div>
              </div>
            </div>
            <!--- <app-education pageContent [educationObj]="educationObj" [id]="i" [education]="education" [model]="model">
              </app-education> -->
          </ng-container>
        </ng-container>
      </ng-container>


      <!-- Skills Component-->
      <ng-container *ngFor="let skills of model?.skills; let i = index" class="Unit-unit-unitGroup">
        <div class="Unit-unit-unitGroup" pageContent [ngClass]="{ 'isCatActive': selectedCategory === category.Skills}">
          <div *ngIf="selectedCategory === category.Skills" (clickOutsideInner)="removeClick()">
            <div clickOutside (clickOutside)="removeClick()">
              <ul>
                <li class="fa fa-plus addIconTop" (click)="openDialog({model: model})"></li>
                <li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
                <li class="fa fa-trash deleteIconRight" (click)="deleteSkillsCategory(i)"></li>
                <li class="fa fa-arrow-down moveIconDown"></li>
                <li class="fa fa-arrow-up moveIconTop"></li>
              </ul>
            </div>
          </div>
          <div pageContent class="col-md-12" (click)="setCategory(category.Skills)">
            <div class="row height">
              <div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
              <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
                <div class="Text-text-wrapper">
                  <div class="Text-Text-text">{{'category.Skills' | translate}}</div>
                </div>
              </h3>
            </div>
          </div>
          <div pageContent class="container-fluid">
            <ng-container *ngFor="let skillObj of skills.subCategories; let i = index" class="col-md-12">
              <app-skills pageContent [skillObj]="skillObj" [id]="i" [skills]="skills" [model]="model"></app-skills>
            </ng-container>
          </div>
        </div>
      </ng-container>
      <!-- Files Component -->
      <ng-container *ngFor="let file of model?.files; let index = index" class="Unit-unit-unitGroup">

        <div *ngIf="selectedCategory === category.Files"
             [ngClass]="{ 'isCatActive': selectedCategory === category.Files}">
          <div clickOutside (clickOutside)="removeClick()">
            <ul>
              <li class="fa fa-plus addIconTop" (click)="openDialog({model:model})"></li>
              <li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
              <li class="fa fa-trash deleteIconRight" (click)="deleteEducationCategory(index)"></li>
              <li class="fa fa-arrow-down moveIconDown"></li>
              <li class="fa fa-arrow-up moveIconTop"></li>
            </ul>
          </div>
        </div>
        <div pageContent class="col-md-12" (click)="setCategory(category.Files)">
          <div class="row height">
            <div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
            <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
              <div class="Text-text-wrapper">
                <div class="Text-Text-text">{{'category.Files' | translate}}</div>
              </div>
            </h3>
          </div>
        </div>
        <ng-container *ngFor="let fileObj of file.subCategories; let i = index" class="col-md-12">
          <div pageContent class="container-fluid">
            <div pageContent [ngClass]="{ 'isActive': selectedFileIndex === i}">
              <div pageContent class="Line-line-container" (click)="setFileIndex(i)">
                <div class="Line-line-line">
                  <div class="Field-field-fieldBase first-template-fieldField">
                    <div pageContent class="Text-Text-wrapper">
                      <div class="Text-Text-text">
                        {{fileObj.name}}</div>
                    </div>
                  </div>
                </div>
                <div class="Field-field-fieldBase first-template-fieldValue" *ngIf="fileObj.link">
                  <div class="Text-Text-wrapper">
                    <div class="Text-Text-text-wrapper">
                      <a target="_blank" [href]="fileObj.link">{{fileObj.link}}
                      </a>
                    </div>
                  </div>
                </div>
                <div class="Field-field-fieldBase first-template-fieldValue">
                  <div class="Text-Text-wrapper">
                    <div class="Text-Text-text-wrapper" aria-multiline="true" [innerHTML]="fileObj.description">
                    </div>
                  </div>
                </div>
                <ng-container pageContent *ngIf="selectedFileIndex === i">
                  <div clickOutside (clickOutside)="removeClick()">
                    <ul>
                      <li class="fa fa-pencil addIconTop" (click)="editFile({edit: true, file: fileObj, model: model})">
                      </li>
                      <button (click)="addFile({edit: false, model: model})" class="btn"><i
                        class="fa fa-plus addIconBottom"></i></button>
                      <button [disabled]="file.subCategories.length === 1" (click)="deleteSubFile(i)" class="btn"><i
                        class="fa fa-trash deleteIconRight"></i></button>
                      <li class="fa fa-arrow-down moveIconDown"></li>
                      <li class="fa fa-arrow-up moveIconTop"></li>
                    </ul>
                  </div>
                </ng-container>
              </div>
            </div>
          </div>
        </ng-container>
      </ng-container>
      <!-- Empty Category -->
      <ng-container *ngFor="let emptyCat of model?.emptyCategory; let i = index" class="Unit-unit-unitGroup">
        <div *ngIf="selectedCategory === category.Another"
             [ngClass]="{ 'isCatActive': selectedCategory === category.Another}">
          <div clickOutside (clickOutside)="removeClick()">
            <ul>
              <li class="fa fa-pencil addIconTop"
                  (click)="editEmptyCategory({edit: true, model: model, emptyCategory: emptyCat})"></li>
              <li class="fa fa-plus addIconBottom" (click)="openDialog({model: model})"></li>
              <li class="fa fa-trash deleteIconRight" (click)="deleteEmptyCategory(i)"></li>
              <li class="fa fa-arrow-down moveIconDown"></li>
              <li class="fa fa-arrow-up moveIconTop"></li>
            </ul>
          </div>
        </div>
        <div pageContent class="col-md-12" (click)="setCategory(category.Another)">
          <div class="row height">
            <div class="col-md-4 col-sm-6 text-right tLine" [style.background]="model.style.color"></div>
            <h3 class="first-template-paragraphTitle Paragraph-paragraph-title height">
              <div class="Text-text-wrapper">
                <div class="Text-Text-text">{{emptyCat.name}}</div>
              </div>
            </h3>
          </div>
        </div>
        <ng-container *ngFor="let emptySubObj of emptyCat.emptySubContents; let i = index" class="col-md-12">
          <div pageContent class="container-fluid">
            <div pageContent [ngClass]="{ 'isActive': selectedEmptySubCat === i}">
              <div pageContent class="Line-line-container" (click)="setEmptySubCatIndex(i)">
                <div class="Line-line-line">
                  <div class="Field-field-fieldBase first-template-fieldField">
                    <div class="Text-Text-wrapper">
                      <div pageContent class="Text-Text-text">
                        {{emptySubObj.name}}</div>
                    </div>
                  </div>
                </div>
                <div class="Field-field-fieldBase first-template-fieldValue">
                  <div class="Text-Text-wrapper">
                    <div pageContent class="Text-Text-text-wrapper" aria-multiline="true"
                         [innerHTML]="emptySubObj.description">
                    </div>
                  </div>
                </div>
                <ng-container pageContent *ngIf="selectedEmptySubCat === i">
                  <div clickOutside (clickOutside)="removeClick()">
                    <ul>
                      <li class="fa fa-pencil addIconTop"
                          (click)="editEmptySubCat({edit: true, empty: emptySubObj, model: model, emptySubCat: true})">
                      </li>
                      <button (click)="addEmptySubCat({edit: false, model: model, emptySubCat: true})" class="btn"><i
                        class="fa fa-plus addIconBottom"></i></button>
                      <button [disabled]="emptyCat.emptySubContents.length === 1" (click)="deleteEmptyCatFile(i)"
                              class="btn"><i class="fa fa-trash deleteIconRight"></i></button>
                      <li class="fa fa-arrow-down moveIconDown"></li>
                      <li class="fa fa-arrow-up moveIconTop"></li>
                    </ul>
                  </div>
                </ng-container>
              </div>
            </div>
          </div>
        </ng-container>
      </ng-container>
      <!--Here closes the container for the whole page-->
    </ng-container>
    <div class="PageNumber-page-number-container">
      <div class="PageNumber-page-number-pageNumber">
        <span class="PageNumber-page-number-page">
          Page
        </span>
        <span>1 / 2</span>
      </div>
    </div>

  </app-paginated-view>

I am based on this page here how they download the PDF.

https://lebenslauf.com/

They take the design which is in the Frontend and they can download pdf within the design and date they have given in the UI. Is it something possible to do like that? In my PDF I cannot copy text or edit a text and the image or PDF it is very small.

I can add more code or to explain it better if someone does not understand See the attached photos how the pdf looks like. I have resized in google chrome just only to see better but instead it is a normal font as A4 page.

And this is the photo how my UI looks like.

enter image description here

TheCoderGuy
  • 771
  • 6
  • 24
  • 51
  • I've faced the same Problem. Copying or editing text is not going to be possible doing it this way (AFAIK). html2canvas essentially just takes a "screenshot" of the element you pass it, so its basically just an image. I suggest using puppeteer and any kind of Template Framework (I did it with Nunjucks) to generate the PDF in your backend. Some extra word but worth it in the end. (https://pptr.dev, https://mozilla.github.io/nunjucks/templating.html) – chrnx Nov 20 '20 at 09:57
  • @chrnx Thanks for your answer. I have looked into the `pupeteer` but I didnt find much documentation about it, how to implement which classes are used and things like that. – TheCoderGuy Nov 20 '20 at 10:01
  • You can search for 'PDF' in the puppeteer docs, there you'll see how you can create and then download it. You'd just have to provide a template with the `.setContent` function and you should be good to go. If you need I can write an answer with some more elaborate examples – chrnx Nov 20 '20 at 10:11
  • @chrnx It will be very kind from your side if you can write an answer. – TheCoderGuy Nov 20 '20 at 10:22
  • I've used pdfmake with Angular and interactives SVGs with text. It's all client side and has worked quite well. Maybe you could do something along the lines of https://stackoverflow.com/questions/34049956/generate-pdf-from-html-using-pdfmake-in-angularjs? – Daniel Gimenez Nov 30 '20 at 22:43
  • @DanielGimenez But this will create at first a image then it will convert into pdf. – TheCoderGuy Dec 01 '20 at 07:37
  • In my scenario when I place the SVG in the pdf the SVG text is preserved - it is still selectable. I'm actually using fabricJs to allow the user to work on the canvas, converting the canvas to an SVG, and then adding the SVG to the pdf. Note, if you're using a non-standard font you need to embed it. – Daniel Gimenez Dec 01 '20 at 14:28
  • Also, consider https://github.com/Aymkdn/html-to-pdfmake. I've never tried it but the demo seemed pretty impressive. – Daniel Gimenez Dec 01 '20 at 15:01
  • @DanielGimenez I am using now a library which works with `handlebars` and then generating my PDF. – TheCoderGuy Dec 01 '20 at 16:34

1 Answers1

3

What you're trying to achieve is, as far as i know, not possible using html2canvas. Since html2canvas only creates a "screenshot" of the element you pass it, it is just an image of the content, which is not editable. I would suggest using puppeteers .pdf() function and some kind of Template Framework (I used Nunjucks in my case, for no other reason than we use the same setup at work and I had examples) to generate the PDF in your backend (e.g. a Google Cloud Function).

Let's assume you have a src folder with a pdf.service.ts and a template.njk (or .html, doesn't matter)

In you're service you could have a function that looks something like this:

function getTemplate(data: YourDataInterface): string {
  const env = new Environment(new FileSystemLoader(__dirname));
  const tpl = env.getTemplate('template.njk');
  return tpl.render({ data });
}

data would be an object that's being passed to the template. Much like an @Input() in angular or props in react.

__dirname is the path to you directory, you can append any path to it if your template is in some kind of a subfolder.

The rest are Nunjucks functions and classes. You can read more about those here: https://mozilla.github.io/nunjucks/api.html

Your template could look something like this:

...
  <div class="Title-title-titleWrapper first-template-titleWrapper">
    <h4 *ngIf="model.hideName">{{ data.personalData[0].firstName }} {{ data.personalData[0].lastName }}</h4>
    <h5>{{ data.newJobTitle }}</h5>
    <div>{{ data?.description }}</div>
  </div>
...

data is again the object passed in the .render function.

You can read more about writing Nunjucks templates here: https://mozilla.github.io/nunjucks/templating.html

To actually generate the PDF with puppeteer, this could be starting point:

async function generatePdf(data: YourDataInterface): Promise<Buffer> {
  const browser = await launch({ args: ['--no-sandbox', '--disable-setup-sandbox', '--disable-web-security'] });

  try {
    const page = await browser.newPage();

    await page.setContent(getTemplate(data), {
      waitUntil: ['load', 'domcontentloaded', 'networkidle0']
    });
    await page.addStyleTag({ path: __dirname + '/styles.css' })

    return await page.pdf({ format: 'A4', printBackground: true });
  } finally {
    browser.close().catch(console.error);
  }
}

I'm not going to go into too much detail here, your can look up all the flags and functions and such in the puppeteer docs (https://pptr.dev)

This function with return a Promise<Buffer> which can be downloaded in your frontend. There's already enough answers on stackoverflow on how to do that.

Dharman
  • 30,962
  • 25
  • 85
  • 135
chrnx
  • 444
  • 1
  • 3
  • 17
  • Thanks for your time but where do I need to render this code ? In Frontend at angular application or at backend. Because I use mongoDB in backend. – TheCoderGuy Nov 20 '20 at 12:19
  • I don't really know your setup, this has to run in your Backend though. It would probably make sense for you to take a look at NestJS or some similar framework, if you haven't already. Then you just need to make an API call to your backends URL(using axios, graphql, angulars http client...), together with the data you want to display and the rest should happen in some kind of service in your backend. – chrnx Nov 20 '20 at 12:32
  • My Setup is something like this. I have all the logic in FE -> CSS, HTML, TS. And I only use backend to save data, to authenticate user, to save new users and save data for different users. Which these users has data and those can download within PDF. But with the correct style which is in FE. – TheCoderGuy Nov 20 '20 at 15:44
  • You could probably pass the components elementRef to to the backend and modify it to a usable format for puppeteer, if your users can change the design of the cv. You have to run puppeteer in some kind of node backend, else it won't work i don't think (don't quote me on that). Btw, are you communicating with the DB directly from the frontend? – chrnx Nov 20 '20 at 16:46
  • I am communicating direkt in backend with services from frontend. – TheCoderGuy Nov 20 '20 at 17:07