All Components

Drawing of HTML Elements

The Kendo UI Drawing API supports the conversion of an existing page, or part of it, to drawing primitives.

This allows you to further process the content and export it in various formats such as Portable Document Format (PDF), Scalable Vector Graphics (SVG), and Portable Network Graphics (PNG) ones.

Getting Started

To draw a DOM element into a Group, use the drawDOM function. Then, you can render it into an SVG, PDF, or HTML5 <canvas> format by utilizing one of the supported back-ends.

You have to append the DOM element to the document and fully render it. This means that you cannot draw an element which features the display: none or the visibility: hidden option.

The following example assumes that you have the demonstrated HTML on the page.

<div id="drawMe" class="...">
  ... more HTML code here...
</div>

Then, you can draw it by using the code demonstrated in the following example.

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div #drawMe>
        ... more HTML code here...
    </div>
  `
})
export class AppComponent {
  public onClick(element) {
    exportElement(element);
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

The drawDOM method takes a DOM element and returns a promise which delivers a Group object.

Configuration

When you draw HTML elements, the Drawing API provides the following configuration options:

Images in PDF

Images are properly exported only if their extensions are correct. For example, a PNG image with a JPG extension that is displayed on a page might not show up in the exported PDF file or might cause exceptions in the PDF reader.

For more information on loading images from different domains, refer to the section on known limitations.

By default, the drawDOM method creates clickable hyperlinks in the generated PDF document. To disable this behavior, use the avoidLinks option.

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div #drawMe>
        This is <a href="http://www.telerik.com/kendo-angular-ui/">a non-clickable link</a>.
    </div>
  `
})
export class AppComponent {
  public onClick(element) {
    exportElement(element, {
        avoidLinks: true
    });
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Multi-Page Content in PDF

The drawDOM method allows you to create a multi-page PDF by specifying manual page breaks. A page break occurs before each element that matches the forcePageBreak CSS selector.

The following example demonstrates how to split the content into multiple pages.

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div #drawMe>
        <h3>Page 1</h3>
        <h3 class='page-break'>Page 2</h3>
    </div>
  `
})
export class AppComponent {
  public onClick(element) {
    exportElement(element, {
        forcePageBreak: '.page-break'
    });
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Automatic Page Breaking

The drawDOM method supports automatic page breaking.

Overview

To achieve automatic page breaking when you export content in PDF, set the paperSize and margin PDF options. You will still be able to apply the forcePageBreak configuration to manually specify the break points.

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div #drawMe>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer felis libero, lobortis ac rutrum quis, varius a velit. Donec lacus erat, cursus sed porta quis, adipiscing et ligula. Duis volutpat, sem pharetra accumsan pharetra, mi ligula cursus felis, ac aliquet leo diam eget risus. Integer facilisis, justo cursus venenatis vehicula, massa nisl tempor sem, in ullamcorper neque mauris in orci.
        </p>
        <p>
            Ut orci ligula, varius ac consequat in, rhoncus in dolor. Mauris pulvinar molestie accumsan. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean velit ligula, pharetra quis aliquam sed, scelerisque sed sapien. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam dui mi, vulputate vitae pulvinar ac, condimentum sed eros.
        </p>
        <p>
            Aliquam at nisl quis est adipiscing bibendum. Nam malesuada eros facilisis arcu vulputate at aliquam nunc tempor. In commodo scelerisque enim, eget sodales lorem condimentum rutrum. Phasellus sem metus, ultricies at commodo in, tristique non est. Morbi vel mauris eget mauris commodo elementum. Nam eget libero lacus, ut sollicitudin ante. Nam odio quam, suscipit a fringilla eget, dignissim nec arcu. Donec tristique arcu ut sapien elementum pellentesque.
        </p>
        <p>
            Maecenas vitae eros vel enim molestie cursus. Proin ut lacinia ipsum. Nam at elit arcu, at porttitor ipsum. Praesent id viverra lorem. Nam lacinia elementum fermentum. Nulla facilisi. Nulla bibendum erat sed sem interdum suscipit. Vestibulum eget molestie leo. Aliquam erat volutpat. Ut sed nulla libero. Suspendisse id euismod quam. Aliquam interdum turpis vitae purus consectetur in pulvinar libero accumsan. In id augue dui, ac volutpat ante. Suspendisse purus est, ullamcorper id bibendum sed, placerat id leo.
        </p>
    </div>
  `,
  styles: [`
    p {
        font-size: 20px;
    }
  `]
})
export class AppComponent {
  public onClick(element) {
    exportElement(element, {
        paperSize: "A4",
        margin: "2cm"
    });
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Limitations

  • If an element does not have textual content, it will not be split across pages. For example, a <div> element that contains text will be split. If a <div> element that does not contain text but includes a border or a background image will not be split.

    If elements that are not subject to splitting fall on a page boundary, they will be moved to the next page along with all DOM nodes that follow them. If they do not fit on a single page, they will be truncated.

    The following nodes do not support automatic page breaking:

    • <img>
    • <tr>
    • <iframe>
    • <svg>
    • <object>
    • <canvas>
    • <input>
    • <textarea>
    • <select>
    • <video>
  • Positioned elements (position: absolute) do not support automatic page breaking. Elements with the position: fixed configuration are not supported at all. They will not show up in the output and are skipped over by the algorithm. For example, the input on an A4 page, which is demonstrated in the following example, will only display the Foo and the Baz paragraphs in the output file and the positioned <div> element will appear on the first page at height 1000. Because this dimension is beyond the page boundary, the content will be clipped.

    <p>Foo</p>
    <div style="position: absolute; top: 1000px">Bar</div>
    <p>Baz</p>
  • If the algorithm moves a node to the next page and even if the space to position it on the current page might be sufficient, all DOM nodes which follow it will be moved as well.

    The following example demonstrates floating elements that acquire this behavior.

    <p>
     some text before
     <img style="float: left" ... />
     some text after
    </p>

    This element might end up in a position where the whole text fits on the current page but the image is higher and would fall on the boundary. In this case, the image and a part of the following text will move to the next page.

When you request multi-page output through the forcePageBreak or paperSize options, you can additionally specify a page template. This template is inserted into each page before the output is produced and you can position it relatively to the page by using CSS. The template has to be a function which receives the number of the current page (pageNum) and the total number of pages (totalPages).

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div #drawMe>
        <h3>Page 1</h3>
        <h3 class='page-break'>Page 2</h3>
    </div>
  `
})
export class AppComponent {
  public onClick(element) {
    exportElement(element, {
        forcePageBreak: '.page-break',
        template: (data) => `<span>Page ${ data.pageNum } of ${ data.totalPages }</span>`
    });
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Scaling of Drawings

To obtain a drawing that is bigger or smaller than the original elements, use the scale option. This behavior is convenient when you generate a multi-page PDF output by using the automatic page breaking feature. Because usually the original dimensions look too big in PDF, you can specify a scale factor of 0.8, for example, to get a more suitable output for print.

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div #drawMe>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer felis libero, lobortis ac rutrum quis, varius a velit. Donec lacus erat, cursus sed porta quis, adipiscing et ligula. Duis volutpat, sem pharetra accumsan pharetra, mi ligula cursus felis, ac aliquet leo diam eget risus. Integer facilisis, justo cursus venenatis vehicula, massa nisl tempor sem, in ullamcorper neque mauris in orci.
        </p>
        <p>
            Ut orci ligula, varius ac consequat in, rhoncus in dolor. Mauris pulvinar molestie accumsan. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean velit ligula, pharetra quis aliquam sed, scelerisque sed sapien. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam dui mi, vulputate vitae pulvinar ac, condimentum sed eros.
        </p>
        <p>
            Aliquam at nisl quis est adipiscing bibendum. Nam malesuada eros facilisis arcu vulputate at aliquam nunc tempor. In commodo scelerisque enim, eget sodales lorem condimentum rutrum. Phasellus sem metus, ultricies at commodo in, tristique non est. Morbi vel mauris eget mauris commodo elementum. Nam eget libero lacus, ut sollicitudin ante. Nam odio quam, suscipit a fringilla eget, dignissim nec arcu. Donec tristique arcu ut sapien elementum pellentesque.
        </p>
        <p>
            Maecenas vitae eros vel enim molestie cursus. Proin ut lacinia ipsum. Nam at elit arcu, at porttitor ipsum. Praesent id viverra lorem. Nam lacinia elementum fermentum. Nulla facilisi. Nulla bibendum erat sed sem interdum suscipit. Vestibulum eget molestie leo. Aliquam erat volutpat. Ut sed nulla libero. Suspendisse id euismod quam. Aliquam interdum turpis vitae purus consectetur in pulvinar libero accumsan. In id augue dui, ac volutpat ante. Suspendisse purus est, ullamcorper id bibendum sed, placerat id leo.
        </p>
    </div>
  `,
  styles: [`
    p {
        font-size: 20px;
    }
  `]
})
export class AppComponent {
  public onClick(element) {
    exportElement(element, {
        paperSize: "A4",
        margin: "2cm",
        scale: 0.8
    });
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

The scale setting affects only the content. That is why the output paper size is still A4 and has a 2-centimeter margin in the previous example. However, when you position headers and footers while using a page template, you need to take scaling into account.

Split of Page Content

To prevent elements from being split across pages, use the keepTogether option. It has to be a CSS selector that is passable to querySelector.

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div #drawMe>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer felis libero, lobortis ac rutrum quis, varius a velit. Donec lacus erat, cursus sed porta quis, adipiscing et ligula. Duis volutpat, sem pharetra accumsan pharetra, mi ligula cursus felis, ac aliquet leo diam eget risus. Integer facilisis, justo cursus venenatis vehicula, massa nisl tempor sem, in ullamcorper neque mauris in orci.
        </p>
        <p>
            Ut orci ligula, varius ac consequat in, rhoncus in dolor. Mauris pulvinar molestie accumsan. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean velit ligula, pharetra quis aliquam sed, scelerisque sed sapien. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam dui mi, vulputate vitae pulvinar ac, condimentum sed eros.
        </p>
        <p>
            Aliquam at nisl quis est adipiscing bibendum. Nam malesuada eros facilisis arcu vulputate at aliquam nunc tempor. In commodo scelerisque enim, eget sodales lorem condimentum rutrum. Phasellus sem metus, ultricies at commodo in, tristique non est. Morbi vel mauris eget mauris commodo elementum. Nam eget libero lacus, ut sollicitudin ante. Nam odio quam, suscipit a fringilla eget, dignissim nec arcu. Donec tristique arcu ut sapien elementum pellentesque.
        </p>
        <p>
            Maecenas vitae eros vel enim molestie cursus. Proin ut lacinia ipsum. Nam at elit arcu, at porttitor ipsum. Praesent id viverra lorem. Nam lacinia elementum fermentum. Nulla facilisi. Nulla bibendum erat sed sem interdum suscipit. Vestibulum eget molestie leo. Aliquam erat volutpat. Ut sed nulla libero. Suspendisse id euismod quam. Aliquam interdum turpis vitae purus consectetur in pulvinar libero accumsan. In id augue dui, ac volutpat ante. Suspendisse purus est, ullamcorper id bibendum sed, placerat id leo.
        </p>
    </div>
  `,
  styles: [`
    p {
        font-size: 20px;
    }
  `]
})
export class AppComponent {
  public onClick(element) {
    exportElement(element, {
        paperSize: "A4",
        margin: "2cm",
        keepTogether: 'p'
    });
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

All elements from the previous example which have the "prevent-split" CSS class are kept within the boundaries of the pages and their content is not split. If they fall on a margin, they will be moved to the next page altogether.

Recurrent Table Headers

If your project requires you to repeat the <thead> elements on each page, pass the repeatHeaders: true option.

Customizing the Appearance

To change the appearance of the PDF output as it appears in the browser, write CSS rules that apply only to the PDF output.

The available approaches are:

The .k-pdf-export Class

The .k-pdf-export CSS class can be applied to a DOM element right before the drawing starts and remove it shortly afterwards.

The following example demonstrates how to define a style that places a border around all paragraphs in the PDF output.

.k-pdf-export p {
border: 2px solid black;
}

Drawing is essentially synchronous—no timeout exists between the moment the class is added and the moment it is removed. As a result, when the generation happens, no flash occurs in the browser.

When you use this approach, you cannot add background images. For more information on how to add background images, refer to the section on the <kendo-pdf-document> element.

The code in the following example is likely to fail because images are cached upfront and this one will miss.

.k-pdf-export p {
background: url("image.jpg");
}

The Element

This approach works only if your project requests multi-page documents, that is, only when either the forcePageBreak or the paperSize option is provided. To make it work in the cases when you need a single page, pass some dummy value to the forcePageBreak option such as forcePageBreak: "-".

When you use the <kendo-pdf-document> element, the DOM renderer creates a clone of the element which does the page-breaking without destroying the original content. The DOM renderer places the cloned element inside a custom <kendo-pdf-document> element which is hidden from the view. As a result, you can apply custom styles under kendo-pdf-document by restricting the rules to the elements.

You can also use this approach to add images.

kendo-pdf-document p {
    border: 2px solid black;
    background: url("image.jpg");
}

Off-Screen Content

To produce different content for export or keep content hidden from the user, position the container outside the screen. The container has to be fully rendered.

The following example uses an absolute positioning to move the container off the screen.

import { Component } from '@angular/core';
import { exportElement } from './export-element';

@Component({
  selector: 'my-app',
  template: `
    <button (click)="onClick(drawMe)">Export PDF...</button>
    <div class='off-screen'>
        <div #drawMe>
            <p>
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer felis libero, lobortis ac rutrum quis, varius a velit. Donec lacus erat, cursus sed porta quis, adipiscing et ligula. Duis volutpat, sem pharetra accumsan pharetra, mi ligula cursus felis, ac aliquet leo diam eget risus. Integer facilisis, justo cursus venenatis vehicula, massa nisl tempor sem, in ullamcorper neque mauris in orci.
            </p>
            <p>
                Ut orci ligula, varius ac consequat in, rhoncus in dolor. Mauris pulvinar molestie accumsan. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean velit ligula, pharetra quis aliquam sed, scelerisque sed sapien. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aliquam dui mi, vulputate vitae pulvinar ac, condimentum sed eros.
            </p>
            <p>
                Aliquam at nisl quis est adipiscing bibendum. Nam malesuada eros facilisis arcu vulputate at aliquam nunc tempor. In commodo scelerisque enim, eget sodales lorem condimentum rutrum. Phasellus sem metus, ultricies at commodo in, tristique non est. Morbi vel mauris eget mauris commodo elementum. Nam eget libero lacus, ut sollicitudin ante. Nam odio quam, suscipit a fringilla eget, dignissim nec arcu. Donec tristique arcu ut sapien elementum pellentesque.
            </p>
            <p>
                Maecenas vitae eros vel enim molestie cursus. Proin ut lacinia ipsum. Nam at elit arcu, at porttitor ipsum. Praesent id viverra lorem. Nam lacinia elementum fermentum. Nulla facilisi. Nulla bibendum erat sed sem interdum suscipit. Vestibulum eget molestie leo. Aliquam erat volutpat. Ut sed nulla libero. Suspendisse id euismod quam. Aliquam interdum turpis vitae purus consectetur in pulvinar libero accumsan. In id augue dui, ac volutpat ante. Suspendisse purus est, ullamcorper id bibendum sed, placerat id leo.
            </p>
        </div>
    </div>
  `,
  styles: [`
    p {
        font-size: 20px;
    }

    .off-screen {
        position: absolute;
        left: -10000px;
        top: 0;
    }
  `]
})
export class AppComponent {
  public onClick(element) {
    exportElement(element, {
        paperSize: "A4",
        margin: "2cm"
    });
  }
}

import { drawDOM, exportPDF, DrawOptions, Group } from '@progress/kendo-drawing';
import { saveAs } from '@progress/kendo-file-saver';

export function exportElement(element: HTMLElement, options?: DrawOptions) {
    drawDOM(element, options).then((group: Group) => {
        return exportPDF(group);
    }).then((dataUri) => {
        saveAs(dataUri, 'export.pdf');
    });
}

import { enableProdMode, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})

export class AppModule { }

enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule);

Dimensions and CSS Units

If you target PDF output, use the px unit. The usage of any units other than px, such as cm, in, mm, or pt, will cause unpredictable results. This section explains the reasons for that.

To draw the DOM, you inspect the computed styles of the elements. At this stage, all dimensions are converted to pixels. For example, if you take <div style='width: 1cm'> and if the dots-per-inch (DPI) setting is correctly displayed, this element is rendered by the browser on screen as being 1cm wide. However, when you query the width in its computed style, you get back 37.78125px. This value might vary depending on the display.

For simplicity and because the computed style yields back pixels, the PDF generator keeps a 1:1 mapping between the screen pixels and the default PDF unit, which is the typographic point (pt). This means that the same element will be rendered to PDF with a length of 37.78125pt. The conversion rules for these units are:

  • 1 pt = 1/72 in (points to inches)
  • 1 in = 2.54 cm (inches to centimeters)

If you put them together, you get the following result:

37.78125 pt = 37.78125/72 in
            = 2.54 * 37.78125/72 cm
            = 1.33 cm

It turns out that you specify that you need 1cm but the actual size on PDF is different from the required dimension and is 1.33cm.

To get a predictable layout in PDF, apply pixels to set all your dimensions. To calculate the values, use the following rules:

  • N cm = N * 72/2.54 px
  • N in = N * 72 px

The paperSize and margin options that you pass to drawDOM are exceptions from this behavior and you can use all unit types because they are not related to CSS or the display resolution.

Known Limitations

  • The drawing of HTML elements does not support right-to-left texts.
  • Images that are hosted on different domains will be rendered only if the server provides permissive Cross-Origin HTTP headers. Similarly, fonts might not be loaded across domains. Even with the proper CORS headers, Internet Explorer 9 is not able to load images or fonts from another domain and might raise an uncatchable security exception. If you need to support Internet Explorer 9, make sure that you host images and fonts on the same domain as the application.
  • If the source of the images is an SVG document, they are not exported in Internet Explorer and are considered to be tainted.
  • Because of CORS restrictions, the export might not work when the page is loaded from a local file (file:// protocol).
  • The export of vertically aligned elements might not work well with automatic page-breaking.
  • The content of the <iframe> and <svg> elements is not processed. For example, the Drawing API will not export it.
  • A <canvas> will be rendered as an image only if it is not tainted, that is, only if it does not display images from another domain.
  • The CSS box-shadow, text-shadow, and radial gradients are omitted. Linear gradients are supported.
  • Browser zooming that is different from 100% is not supported.
  • Only the solid border-style is rendered.
  • The border-collapse:collapse style of tables is not supported. To prevent double borders in the PDF output, avoid using adjacent borders for separate table cells.
  • The PDF 1.5 specification limits the maximum document size to 5080mm x 5080mm (200 x 200 inches). Larger files might not open in some viewers.
  • Shadow DOM is not rendered.
  • An SVG format that is referenced with the <img> tag will not render in Internet Explorer because the browser taints the canvas.
  • The rendering of the <select> elements is imperfect and leads to possible minor issues such as wrong padding or missing the drop-down arrow. It is recommended that you use the Kendo UI DropDownList component instead of a plain <select> element.
  • When the generated document is opened with Acrobat Reader and you try to use the Save As option from the file menu, the following error is thrown: The document could not be saved. There was a problem reading(23). The solution is to open the Acrobat Reader options (Edit > Preferences) and to uncheck Save As optimizes for Fast Web View in the Documents section, which is enabled by default. After this, Save As works without errors.

Supported Browsers

The drawing of HTML elements is tested and supported in the following desktop browsers:

  • Internet Explorer 9 and later.
  • Latest Chrome, Firefox, Safari, and Blink-based Opera versions.
In this article