diff --git a/Dockerfile b/Dockerfile index 534a9a8..a33ef3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,18 @@ FROM node:20 as dependencies WORKDIR /app + +# Install required system dependencies for Puppeteer +RUN apt-get update && apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon-x11-0 libgbm1 libasound2 libxshmfence1 fonts-liberation fonts-noto fonts-noto-core fonts-noto-ui-core fonts-deva fonts-indic gconf-service libappindicator3-1 libnspr4 libx11-xcb1 xdg-utils --no-install-recommends && rm -rf /var/lib/apt/lists/* + COPY . ./ RUN npm cache clean --force RUN npm i typeorm RUN npm i cache-manager RUN npm i --legacy-peer-deps -# Install required system dependencies for Puppeteer -RUN apt-get update && apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon-x11-0 libgbm1 libasound2 libxshmfence1 fonts-liberation gconf-service libappindicator3-1 libnspr4 libx11-xcb1 xdg-utils --no-install-recommends +# Install Chrome for Puppeteer +RUN npx puppeteer browsers install chrome EXPOSE 3000 CMD ["npm", "start"] diff --git a/src/modules/certificate/certificate.contoller.ts b/src/modules/certificate/certificate.contoller.ts index 1b64a30..4aa6995 100644 --- a/src/modules/certificate/certificate.contoller.ts +++ b/src/modules/certificate/certificate.contoller.ts @@ -104,12 +104,21 @@ export class CertificateController { @Post('render-PDF') async renderCertificatePDFFromHTML( @Body() renderCertificateDto: RenderCertificateDTO, - @Res({ passthrough: true }) response, - ): Promise { - return await this.certificateService.renderPDFFromHTML( + @Res({ passthrough: true }) response: Response, + ): Promise { + const streamableFile = await this.certificateService.renderPDFFromHTML( renderCertificateDto.credentialId, renderCertificateDto.templateId, response, ); + + // Set proper headers for PDF download + response.setHeader('Content-Type', 'application/pdf'); + response.setHeader( + 'Content-Disposition', + `attachment; filename="certificate-${renderCertificateDto.credentialId}.pdf"`, + ); + + return streamableFile; } } diff --git a/src/modules/certificate/certificate.service.ts b/src/modules/certificate/certificate.service.ts index daaa495..9b94362 100644 --- a/src/modules/certificate/certificate.service.ts +++ b/src/modules/certificate/certificate.service.ts @@ -412,7 +412,53 @@ export class CertificateService { Accept: 'text/html', }, }); - response.data = response?.data?.replace('**Id**', credentialId); + let htmlContent = response?.data?.replace('**Id**', credentialId); + + // Inject font support for Devanagari script (Marathi/Hindi) if not already present + if (!htmlContent.includes('fonts.googleapis.com') && !htmlContent.includes('@font-face')) { + const fontInjection = ` + + + + + `; + + // Ensure charset is set first + if (!htmlContent.includes('charset')) { + if (htmlContent.includes('')) { + htmlContent = htmlContent.replace('', ''); + } else if (htmlContent.includes('')) { + htmlContent = htmlContent.replace('', ''); + } + } + + // Inject fonts into head if head exists, otherwise wrap content + if (htmlContent.includes('')) { + htmlContent = htmlContent.replace('', `${fontInjection}`); + } else if (htmlContent.includes('')) { + htmlContent = htmlContent.replace('', `${fontInjection}`); + } else { + // If no head tag, wrap content and add head + if (!htmlContent.includes('')) { + htmlContent = `${fontInjection}${htmlContent}`; + } else { + htmlContent = htmlContent.replace('', `${fontInjection}`); + } + } + } else { + // Ensure charset is set even if fonts are already present + if (!htmlContent.includes('charset')) { + if (htmlContent.includes('')) { + htmlContent = htmlContent.replace('', ''); + } else if (htmlContent.includes('')) { + htmlContent = htmlContent.replace('', ''); + } + } + } // Launch Puppeteer const browser = await puppeteer.launch({ @@ -423,18 +469,28 @@ export class CertificateService { '--disable-gpu', '--disable-dev-shm-usage', '--disable-software-rasterizer', + '--font-render-hinting=none', ], }); const page = await browser.newPage(); - // Set HTML content - await page.setContent(response.data, { waitUntil: 'load' }); + // Set viewport for better rendering + await page.setViewport({ width: 1200, height: 1600 }); + + // Set HTML content and wait for fonts to load + await page.setContent(htmlContent, { + waitUntil: 'networkidle0', // Wait for all network requests to finish (including fonts) + }); + + // Additional wait to ensure fonts are fully loaded + await page.evaluateHandle(() => document.fonts.ready); // Generate PDF const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true, + preferCSSPageSize: false, margin: { top: '10mm', bottom: '10mm', @@ -445,23 +501,12 @@ export class CertificateService { await browser.close(); - res.setHeader('Content-Type', 'application/pdf'); - res.setHeader( - 'Content-Disposition', - 'attachment; filename="generated.pdf"', - ); - res.setHeader('Content-Length', pdfBuffer.length); - res.end(pdfBuffer); + // Return StreamableFile instead of calling res.end() + return new StreamableFile(pdfBuffer); } catch (error) { console.log('error: ', error); - this.loggerService.error('Error fetching credentials:', error); - return APIResponse.error( - res, - apiId, - 'Error fetching credentials', - 'INTERNAL_SERVER_ERROR', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + this.loggerService.error('Error generating PDF:', error); + throw error; } } }