Search code examples
pdfcanvasreportkendo-ui-angular2

Low quality in PDF converting canvas in PDF using KendoPDF


I created a chart using html canvas. The result would like it to be printed as a PDF file using Kendo. It works, but the graphic quality is very poor. For the solution I need I can't use kendo chart for limitation reasons

report.html

<div class="width-100-perc text-center">
    <canvas id="canvas" width="100" height="100"></canvas>
    <br />
</div>

report.ts

drawChart() {
    console.log( 'foi');
    const canvas: HTMLCanvasElement = (<HTMLCanvasElement>document.getElementById('canvas'));
    console.log(this.series);
    if (canvas) {
        const ctx = canvas.getContext('2d');

        // Base offset distance of 10
        const offset = 0;
        let beginAngle = 0;
        let endAngle = 0;

        // Used to calculate the X and Y offset
        let offsetX, offsetY, medianAngle;

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fill();

        for (let i = 0; i < this.angles.length; i = i + 1) {
            beginAngle = endAngle;
            endAngle = endAngle + this.angles[i];

            // The medium angle is the average of two consecutive angles
            medianAngle = (endAngle + beginAngle) / 2;

            // X and Y calculations
            offsetX = Math.cos(medianAngle) * offset;
            offsetY = Math.sin(medianAngle) * offset;

            ctx.beginPath();
            ctx.fillStyle = this.series[0].data[i].color;

            // Adding the offsetX and offsetY to the center of the arc
            ctx.moveTo(50 + offsetX, 50 + offsetY);
            ctx.arc(50 + offsetX, 50 + offsetY, 40, beginAngle, endAngle);
            ctx.lineTo(50 + offsetX, 50 + offsetY);
            ctx.fill();
        }

        if (this.angles.length > 0) {
            ctx.beginPath();
            ctx.fillStyle = '#FFFFFF';
            ctx.arc(50, 50, 15, 0, 2 * Math.PI);
            ctx.fill();
        }
    }
}

The result is perfect in html, but in PDF the resulting image quality is very poor


Solution

  • This is not a problem with kendo's pdf export. Rather, it's inherent to the way the HTML canvas works. Your export looks distorted and pixelated because, at the end of the day, it's just a 100x100 image, which is rather low resolution. I'm assuming you want it to be that small since it is made to fit a specific part of the page. If you just directly export this canvas, that pixelated image is what you should expect.

    I can propose this workaround. You need to refactor your drawChart() method to take into account a scale (number). This would mean multiplying all x,y coordinates and dimensions by this value. By default, the scale is 1. When exporting to pdf, you will follow these steps:

    1. Change the scale to higher value, let's say 10
    2. Draw
    3. Export to pdf
    4. Change scale to 1 again
    5. Draw

    This way, the chart is temporarily redrawn using a higher resolution canvas. In it's high(er) resolution state, it's exported and then it's redrawn with its original dimensions.

    If you provide some example values of your this.angles and this.series I can refactor your drawChart() function to take this into account. As it stands, I can't. But I've prepared a similar example here. This is the ReportComponent I've created.

    report.component.html

    <button (click)="savePdf(false)">bad pdf</button>
    <button (click)="savePdf(true)">good pdf</button>
    <br/>
    <kendo-pdf-export #pdf>
        <canvas #canvas [width]="baseWidth" [height]="baseHeight"></canvas>
    </kendo-pdf-export>
    

    report.component.ts

    export class ReportComponent implements AfterViewInit {
      @ViewChild("canvas", { static: false })
      public canvasRef: ElementRef<HTMLCanvasElement>;
    
      @ViewChild("pdf", { static: false })
      public pdf: PDFExportComponent;
    
    
      @Input() public title: string = "";
    
      public scale: number = 1;
      public baseWidth: number = 100;
      public baseHeight: number = 100;
    
      constructor() {}
    
      ngAfterViewInit() {
        this.draw();
      }
    
      draw() {
        const canvas = this.canvasRef.nativeElement;
        canvas.width = this.baseWidth * this.scale; // scaled
        canvas.height = this.baseHeight * this.scale; // scaled
        const context = canvas.getContext("2d");
    
        const centerX = canvas.width / 2;
        const centerY = canvas.height / 2;
        const radius = 31.4 * this.scale; // scaled
    
        context.beginPath();
        context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
        context.fillStyle = "green";
        context.fill();
        context.lineWidth = 5 * this.scale; // scaled
        context.strokeStyle = "#003300";
        context.stroke();
      }
    
      savePdf(good: boolean) {
        if (good) {
          // scale 10x and re-draw
          this.scale = 10;
          this.draw();
          this.pdf.saveAs("good.pdf");
          this.scale = 1;
          this.draw();
        } else {
          // just draw as is
          this.pdf.saveAs("bad.pdf");
        }
      }
    }
    

    Test page

    Bad PDF Bad PDF

    Good PDF Good PDF