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
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
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 headerAccess-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/