@@ -7,6 +7,7 @@ import logger from "../config/logger";
77import { SectionModel } from "../models/section.model" ;
88import { TypographyModel } from "../models/brand-identity.model" ;
99import { cacheService } from "./cache.service" ;
10+ import axios from "axios" ;
1011
1112export interface PdfGenerationOptions {
1213 title ?: string ;
@@ -615,7 +616,7 @@ export class PdfService {
615616 } = options ;
616617
617618 // Nettoyer les sections en supprimant le préfixe "html" du contenu data
618- const cleanedSections = sections . map ( ( section ) => {
619+ let cleanedSections = sections . map ( ( section ) => {
619620 if (
620621 section . data &&
621622 typeof section . data === "string" &&
@@ -629,6 +630,9 @@ export class PdfService {
629630 return section ;
630631 } ) ;
631632
633+ // Convertir les URLs d'images en data URIs pour que Puppeteer puisse les charger
634+ cleanedSections = await this . convertImageUrlsToDataUris ( cleanedSections ) ;
635+
632636 logger . info ( `sections length: ${ cleanedSections . length } ` ) ;
633637 // Générer la clé de cache basée sur le contenu nettoyé
634638 const cacheKey = PdfService . generateCacheKey ( {
@@ -992,4 +996,121 @@ export class PdfService {
992996 logger . warn ( `Failed to cleanup temporary PDF file: ${ pdfPath } ` , error ) ;
993997 }
994998 }
999+
1000+ /**
1001+ * Convertit les URLs d'images dans les sections en data URIs
1002+ * pour que Puppeteer puisse les charger sans problème de CORS/authentification
1003+ */
1004+ private async convertImageUrlsToDataUris (
1005+ sections : SectionModel [ ]
1006+ ) : Promise < SectionModel [ ] > {
1007+ logger . info ( "Converting image URLs to data URIs for PDF generation" ) ;
1008+
1009+ const convertedSections = await Promise . all (
1010+ sections . map ( async ( section ) => {
1011+ if ( ! section . data || typeof section . data !== "string" ) {
1012+ return section ;
1013+ }
1014+
1015+ let htmlContent = section . data ;
1016+
1017+ // Regex pour trouver toutes les balises img avec src
1018+ const imgRegex = / < i m g [ ^ > ] + s r c = [ " ' ] ( [ ^ " ' ] + ) [ " ' ] [ ^ > ] * > / gi;
1019+ const matches = [ ...htmlContent . matchAll ( imgRegex ) ] ;
1020+
1021+ if ( matches . length === 0 ) {
1022+ return section ;
1023+ }
1024+
1025+ logger . info (
1026+ `Found ${ matches . length } images in section: ${ section . name } `
1027+ ) ;
1028+
1029+ // Convertir chaque URL en data URI
1030+ for ( const match of matches ) {
1031+ const fullImgTag = match [ 0 ] ;
1032+ const imageUrl = match [ 1 ] ;
1033+
1034+ try {
1035+ // Vérifier si c'est déjà un data URI
1036+ if ( imageUrl . startsWith ( "data:" ) ) {
1037+ continue ;
1038+ }
1039+
1040+ // Télécharger l'image
1041+ const dataUri = await this . downloadImageAsDataUri ( imageUrl ) ;
1042+
1043+ if ( dataUri ) {
1044+ // Remplacer l'URL par le data URI dans la balise img
1045+ const newImgTag = fullImgTag . replace ( imageUrl , dataUri ) ;
1046+ htmlContent = htmlContent . replace ( fullImgTag , newImgTag ) ;
1047+ logger . info (
1048+ `Converted image URL to data URI in section: ${ section . name } `
1049+ ) ;
1050+ }
1051+ } catch ( error ) {
1052+ logger . warn (
1053+ `Failed to convert image URL to data URI: ${ imageUrl } ` ,
1054+ error
1055+ ) ;
1056+ // Continue avec les autres images même si une échoue
1057+ }
1058+ }
1059+
1060+ return {
1061+ ...section ,
1062+ data : htmlContent ,
1063+ } ;
1064+ } )
1065+ ) ;
1066+
1067+ logger . info ( "Finished converting image URLs to data URIs" ) ;
1068+ return convertedSections ;
1069+ }
1070+
1071+ /**
1072+ * Télécharge une image depuis une URL et la convertit en data URI
1073+ */
1074+ private async downloadImageAsDataUri ( imageUrl : string ) : Promise < string | null > {
1075+ try {
1076+ logger . info ( `Downloading image from URL: ${ imageUrl . substring ( 0 , 50 ) } ...` ) ;
1077+
1078+ // Télécharger l'image
1079+ const response = await axios . get ( imageUrl , {
1080+ responseType : "arraybuffer" ,
1081+ timeout : 10000 , // 10 secondes timeout
1082+ headers : {
1083+ "User-Agent" : "Mozilla/5.0 (compatible; PdfService/1.0)" ,
1084+ } ,
1085+ } ) ;
1086+
1087+ // Déterminer le type MIME depuis les headers ou l'URL
1088+ let mimeType =
1089+ response . headers [ "content-type" ] || "image/svg+xml" ;
1090+
1091+ // Si c'est un SVG, s'assurer que le MIME type est correct
1092+ if ( imageUrl . toLowerCase ( ) . endsWith ( ".svg" ) ) {
1093+ mimeType = "image/svg+xml" ;
1094+ } else if ( imageUrl . toLowerCase ( ) . endsWith ( ".png" ) ) {
1095+ mimeType = "image/png" ;
1096+ } else if ( imageUrl . toLowerCase ( ) . endsWith ( ".jpg" ) || imageUrl . toLowerCase ( ) . endsWith ( ".jpeg" ) ) {
1097+ mimeType = "image/jpeg" ;
1098+ }
1099+
1100+ // Convertir en base64
1101+ const base64 = Buffer . from ( response . data ) . toString ( "base64" ) ;
1102+ const dataUri = `data:${ mimeType } ;base64,${ base64 } ` ;
1103+
1104+ logger . info (
1105+ `Successfully converted image to data URI (${ mimeType } , ${ Math . round (
1106+ base64 . length / 1024
1107+ ) } KB)`
1108+ ) ;
1109+
1110+ return dataUri ;
1111+ } catch ( error ) {
1112+ logger . error ( `Error downloading image from ${ imageUrl } :` , error ) ;
1113+ return null ;
1114+ }
1115+ }
9951116}
0 commit comments