Search code examples
htmlgoogle-chromesvghtml5-canvaspdf.js

How to rotate Pdf.js canvas output in SVG in HTML in Chrome?


I am working on a web application whose HTML user interface involves SVG graphics at some point. A part of that SVG is subject to various transformations, such as rotation.

Within that transformed SVG graphic, I am now required to display (the first page of) a PDF file. For this purpose, I am using a <foreignObject> element that contains an HTML body with a <canvas> element that can serve as the target of the PDF.js library1.

Here is a minimal example that contains a <g> element with a rotate transform on it. The <g> element contains a regular SVG rectangle for comparison and the above construct with the PDF.js output:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.2/pdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.2/pdf.worker.min.js"></script>
</head>
<body>
<svg width="800" height="600">
<rect width="100%" height="100%" fill="rgb(186, 224, 255)"/>
<g transform="rotate(-30) translate(10, 20) scale(1.5)">
    <rect x="100px" y="50px" width="100px" height="80px" stroke="red" fill="none"/>
    <foreignObject x="150px" y="300px" width="200" height="150">
        <xhtml:body margin="0px" padding="0px" width="200px" height="150px" border="1px solid black">
            <canvas width="200" height="150" id="cnv"/>
        </xhtml:body>
    </foreignObject>
</g>
</svg>
<script type="text/javascript">
pdfjsLib.getDocument('https://en.wikipedia.org/api/rest_v1/page/pdf/Canvas_element').promise.then(function (pdf) {
    return pdf.getPage(1);
}).then(function (page) {
    var viewport = page.getViewport({
        scale: 1
    });
    var pdfCanvas = document.getElementById('cnv');
    pdfCanvas.width = viewport.width;
    pdfCanvas.height = viewport.height;
    var renderCtx = {
        canvasContext: pdfCanvas.getContext('2d'),
        viewport: viewport
    };
    page.render(renderCtx);
});

</script>
</body>
</html>

In Firefox 66, this appears as expected:

Firefox output

In Chrome 74, however, the canvas shows a seemingly arbitrary rectangle from the first page of the PDF (rather than its top left corner) and this output is not rotated, either:

Chrome output

Is there any way to work around this issue and rotated PDFs to display within my SVG in Chrome?


1: Somehow, whatever resource I looked at, the question of how to render PDF in JavaScript invariably seemed to lead to this library. Furthermore, from what I read on the library's website the SVG renderer is still very much work in progress and thus cannot be used at a place where customers may upload arbitrary PDF content.


Solution

  • I already met similar issue. Probably for performance, in chrome - canvas uses isolated context which is not derived from SVG. You may use img instead & render pdf to hidden canvas.

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8"/>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.2/pdf.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.2.2/pdf.worker.min.js"></script>
    </head>
    <body>
    
    <canvas width="200" height="150" id="cnv" style="position:absolute;left:-10000px"></canvas>
    
    <svg width="800" height="600">
    <rect width="100%" height="100%" fill="rgb(186, 224, 255)"/>
    <g transform="rotate(-30) translate(10, 20) scale(1.5)">
        <rect x="100px" y="50px" width="100px" height="80px" stroke="red" fill="none"/>
        <foreignObject x="150px" y="300px" width="200" height="150">
            <xhtml:body margin="0px" padding="0px" width="200px" height="150px" border="1px solid black">
                <img id="img" x="150px" y="300px" width="200" height="150"/>
            </xhtml:body>
        </foreignObject>
    </g>
    </svg>
    <script type="text/javascript">
    pdfjsLib.getDocument('https://en.wikipedia.org/api/rest_v1/page/pdf/Canvas_element').promise.then(function (pdf) {
        return pdf.getPage(1);
    }).then(function (page) {
        var viewport = page.getViewport({
            scale: 1
        });
        var pdfCanvas = document.getElementById('cnv');
        pdfCanvas.width = viewport.width;
        pdfCanvas.height = viewport.height;
        var renderCtx = {
            canvasContext: pdfCanvas.getContext('2d'),
            viewport: viewport
        };
        page.render(renderCtx);
    });
    
    // wait for page rendered, just use some pdf.js callback
    setTimeout(function(){
        const cnv = document.getElementById('cnv');
        document.getElementById('img').src = cnv.toDataURL("image/png");
    }, 2000);
    
    </script>
    </body>
    </html>