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

Upvotes: 1

Views: 813

Answers (1)

igg
igg

Reputation: 2250

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

Upvotes: 2

Related Questions