diegomura / react-pdf

📄 Create PDF files using React

Home Page:https://react-pdf.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Not valid image extension

IDCreativ opened this issue · comments

Describe the bug
I made a component and i just try to display an image but it does not work. First, I had the CORS problem but it's OK now.

Here is my code :

import React from "react";
import { Text, View, Image } from "@react-pdf/renderer";
import header from "../styles/headerStyles";
import { HEADERS_URL } from "@lib/constants";

const HeaderElement = ({ debug, support }) => {

	const headerImg = support.supportHeader
		? HEADERS_URL + "/"+ support.supportHeader.imageName
		: null;

	console.log("headerImg", headerImg);

	return (
		<>
			<View style={header.headerContainer}>
				<Image source={{ uri: {headerImg}, method: "get", headers:{ "Access-Control-Allow-Origin": "*", crossOrigin: "anonymous" } }} />
			</View>
			<Text debug={debug} style={header.headerTitle}>
				{support.name}
			</Text>
		</>
	);
};

export default HeaderElement;
  • OS: MacOS
  • Browser : chrome, firefox
  • React-pdf version : 3.3.8
  • Next JS Version : 14.1.0

Hello @IDCreativ

Can you give the url of the picture you try to load ?

The error you describe is thrown here. https://github.com/diegomura/react-pdf/blob/master/packages/image/src/resolve.js#L168

So it may have a bug in detection or your image data is corrupt.

@diegomura
Another weird behavior is that there are 2 methods to detect JPG format from a Buffer and they are not identical
https://github.com/diegomura/react-pdf/blob/master/packages/image/src/resolve.js#L160
https://github.com/diegomura/react-pdf/blob/master/packages/image/src/jpeg.js#L38

it's weird !
When I recreate a small playground jpeg is detected by the 2 methods

https://codesandbox.io/p/sandbox/react-pdf-jpeg-f4r9fn

I think the problem is in the code that you have submitted

<Image source={{ uri: {headerImg} }} /> is like <Image source={{ uri: {headerImg: "pucture.jpg"} }} />

Try <Image source={{ uri: headerImg }} />

Now i have the Cors policy error again ... :'(

I didn't have that problem on my other app but it may be because image and app were on the same domain.

When i put this image link on the playground, i have the same problem.

The server hosting the image https://dashboard.agco-lisaprint.com/uploads/headers/valtra-banniere-1-6567636da034f850921862.jpg is not providing the header Access-Control-Allow-Origin

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin

So you will never be able to fetch this picture from a different domain.

2 solutions :

  • Add Access-Control-Allow-Origin header to the server response
  • Fetch the picture from a backend to skip CORS

I'm use Symfony 6.4 with API PlatForm and to handle CORS, i'm using Nelmio package 2.3. Here is my Nelmio config :


nelmio_cors:
    defaults:
        origin_regex: true
        allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
        allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
        allow_headers: ['Content-Type', 'Authorization', 'X-Requested-With', 'X-Auth-Token', 'X-Auth-User']
        expose_headers: ['Link']
        max_age: 3600
    paths:
        '^/': null

My .env.local has this variable :

CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'

So, the problem is :

  • All my API from Symfony/API Platform works fine with my NextJS app,
  • I can't display images from localhost:8000 to localhost:3000

See also my server logs :
SCR-20240219-pnco

What am i missing ?

Edit : I can display that image everywhere in my app but in the react-pdf render.

I tried to put an image from the same server and I have the "Not valid image extension" error.

Please, anyone with nextJS made it work ?

I tried to put an image from the same server and I have the "Not valid image extension" error.

Please, anyone with nextJS made it work ?

Its an issue with jpegs in particular. There are PRs open looking to address the error. I have a workaround in place until its resolved in my nextJS app, which is simply converting images to PNG on the fly using sharp on the API route generating the PDF file (As the images are on a customer blob I can't convert them ahead of time).

You can find other workarounds on this thread/pull requests

Another PR has been opened off the back of the comments in the above here:

@IDCreativ I used this utility method.

const convertImageToPNGBuffer = async (
    blobUrl: string,
    options: {
        resize: sharp.ResizeOptions;
        png: sharp.PngOptions;
    } = {
        resize: {
            fit: "contain",
            width: 700,
        },
        png: {
            quality: 90,
            compressionLevel: 5,
        },
    }
) => {
    const response = await fetch(blobUrl);
    const imageBuffer = await response.buffer();

    return sharp(imageBuffer)
        .resize(options.resize)
        .png(options.png)
        .toBuffer()
        .catch((error) => {
            console.error("Error converting image to PNG:", error, blobUrl);
            return undefined;
        });
};

Essentially, this takes a URL to an image, processes it with sharp to a PNG buffer (In my case optimised), and I use this to store the buffer alongside the URL and use the buffer instead. I wrapped this in a URL param ?compatibilityMode=true, so I can easily opt out of this when the fix is rolled out.

This isn't amazing as the execution time of this endpoint scales with the number of images, but for a handful of images, the time increase for pdf generation is actually not too bad.

Thank you for your help.

I managed it in a different way to display images from the remote server and the CORS problems. As I am the API dev, i made a controller that serve images with no cors. Here is an example (it's on a Symfony 6 with API Platform) :

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class FileController extends AbstractController
{
    #[Route('/generate-pdf/uploads/{fileFolder}/{filename}', name: 'serve_file', methods: ['GET'])]
    public function serveFile(string $fileFolder, string $filename): Response
    {
        $filePath = $this->getParameter('kernel.project_dir') . '/public/uploads/' . $fileFolder . "/" . $filename;

        if (!file_exists($filePath)) {
            throw $this->createNotFoundException('Le fichier n\'existe pas.');
        }

        $file = new File($filePath);
        $response = new Response(file_get_contents($filePath));

        $response->headers->set('Content-Type', $file->getMimeType());
        $response->headers->set('Access-Control-Allow-Origin', '*');

        return $response;
    }
}

My Nelmio Cors Bundle might not be well configured. That's not part of the package react-pdf/rendered.

I'll check later to manage differently.

Thank you for your help and i reported all your answers on my Notion so I can check it later.

The server hosting the image https://dashboard.agco-lisaprint.com/uploads/headers/valtra-banniere-1-6567636da034f850921862.jpg is not providing the header Access-Control-Allow-Origin

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin

So you will never be able to fetch this picture from a different domain.

2 solutions :

  • Add Access-Control-Allow-Origin header to the server response
  • Fetch the picture from a backend to skip CORS

This worked for me!:

const getImageFromUrl = (url) => {
    return {
         uri: url,
        method: "GET",
        headers: {
            "Access-Control-Allow-Origin": "*",
            "Cache-Control": "no-cache",
        },
    } as any
}

for developing heroku also has a backend cors skipper: https://cors-anywhere.herokuapp.com/