Garante proporcao exata e sem deformacao das imagens nas exportacoes de PDF de quadrinhos (Retrato e Paisagem)

This commit is contained in:
2026-08-31 15:02:22 +00:00
parent 93fc6a1123
commit 1fc7961ed9
+75 -41
View File
@@ -4899,24 +4899,36 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
}
});
// Helper para converter URL de imagem local em base64
const getBase64Image = async (imgUrl) => {
// Helper para converter URL de imagem local em base64 e obter dimensões reais sem deformar
const getBase64ImageDetails = async (imgUrl) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
const width = img.naturalWidth || img.width || 1280;
const height = img.naturalHeight || img.height || 720;
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
resolve(canvas.toDataURL('image/jpeg'));
resolve({
dataUrl: canvas.toDataURL('image/jpeg'),
width: width,
height: height,
aspectRatio: width / height
});
};
img.onerror = (e) => reject(new Error('Falha ao carregar imagem para o PDF: ' + imgUrl));
img.src = imgUrl;
});
};
const getBase64Image = async (imgUrl) => {
const details = await getBase64ImageDetails(imgUrl);
return details.dataUrl;
};
// Helper para execução concorrente controlada
const mapConcurrent = async (items, limit, fn) => {
const results = new Array(items.length);
@@ -5444,51 +5456,62 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
doc.setFont('helvetica', 'bold');
doc.setFontSize(18);
doc.setTextColor(30, 41, 59);
doc.text(title, 297, 50, { align: 'center' });
doc.text(title, 297, 45, { align: 'center' });
// Imagem
const base64Img = await getBase64Image(panel.imageUrl);
const imgWidth = 515;
const imgHeight = selectedComicsRatio === '16:9' ? 290 : 386;
doc.addImage(base64Img, 'JPEG', 40, 80, imgWidth, imgHeight);
const imgDetails = await getBase64ImageDetails(panel.imageUrl);
const boxWidth = 515;
const boxMaxHeight = 350;
const textHeight = 150;
// Altura esticada para a caixa de diálogo (160pt de altura - aprox dobro)
const textHeight = 160;
const textY = 80 + imgHeight;
// Cálculo exato mantendo a proporção real da imagem (sem esticar)
let renderW = boxWidth;
let renderH = renderW / imgDetails.aspectRatio;
if (renderH > boxMaxHeight) {
renderH = boxMaxHeight;
renderW = renderH * imgDetails.aspectRatio;
}
// Caixa de diálogo com fundo cinza claro e borda integrada
const imgX = 40 + (boxWidth - renderW) / 2;
const imgY = 70 + (boxMaxHeight - renderH) / 2;
const cardY = 70;
const textY = cardY + boxMaxHeight;
// Desenha a imagem centralizada e proporcional
doc.addImage(imgDetails.dataUrl, 'JPEG', imgX, imgY, renderW, renderH);
// Fundo da caixa de texto
doc.setFillColor(248, 250, 252);
doc.rect(40, textY, imgWidth, textHeight, 'F');
doc.rect(40, textY, boxWidth, textHeight, 'F');
// Moldura unificada ao redor de toda a imagem + texto
// Moldura unificada ao redor de todo o quadro
doc.setDrawColor(30, 41, 59);
doc.setLineWidth(2);
doc.rect(40, 80, imgWidth, imgHeight + textHeight, 'D');
doc.rect(40, cardY, boxWidth, boxMaxHeight + textHeight, 'D');
// Linha divisória entre a imagem e a caixa de texto
// Linha divisória entre imagem e texto
doc.line(40, textY, 555, textY);
// Texto maior e mais legível (dobro do tamanho relativo - 15pt)
// Texto legível
if (panel.dialogue) {
doc.setFont('helvetica', 'normal');
doc.setFontSize(15);
doc.setTextColor(15, 23, 42);
const splitText = doc.splitTextToSize(panel.dialogue, imgWidth - 40);
const splitText = doc.splitTextToSize(panel.dialogue, boxWidth - 40);
doc.text(splitText, 60, textY + 35);
}
// Círculo discreto com o número do quadrinho no canto superior esquerdo da imagem
// Numeração do quadrinho
doc.setFillColor(254, 240, 138);
doc.circle(65, 105, 12, 'F');
doc.circle(65, 95, 12, 'F');
doc.setDrawColor(30, 41, 59);
doc.setLineWidth(1.5);
doc.circle(65, 105, 12, 'D');
doc.circle(65, 95, 12, 'D');
doc.setFont('helvetica', 'bold');
doc.setFontSize(10);
doc.setTextColor(30, 41, 59);
doc.text(String(panel.panel_number), 65, 109, { align: 'center' });
doc.text(String(panel.panel_number), 65, 99, { align: 'center' });
// Rodapé
doc.setFont('helvetica', 'italic');
@@ -5526,7 +5549,7 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
for (let page = 0; page < totalPages; page++) {
if (page > 0) doc.addPage();
// Título centralizado no topo da página paisagem
// Título centralizado no topo
doc.setFont('helvetica', 'bold');
doc.setFontSize(18);
doc.setTextColor(30, 41, 59);
@@ -5539,40 +5562,51 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
const panel = generatedPanelsData[index];
const startX = col === 0 ? 40 : 426;
const cardWidth = 376;
const boxWidth = 376;
const boxMaxHeight = 230;
const textHeight = 140;
// Proporções exatas mantidas
const imgHeight = selectedComicsRatio === '16:9' ? 211 : 282;
const textHeight = 140; // Dobro de tamanho (antes era 65)
const imgDetails = await getBase64ImageDetails(panel.imageUrl);
// Imagem do quadrinho
const base64Img = await getBase64Image(panel.imageUrl);
doc.addImage(base64Img, 'JPEG', startX, 70, cardWidth, imgHeight);
// Cálculo rigoroso da dimensão sem deformar (preserva aspect ratio real)
let renderW = boxWidth;
let renderH = renderW / imgDetails.aspectRatio;
if (renderH > boxMaxHeight) {
renderH = boxMaxHeight;
renderW = renderH * imgDetails.aspectRatio;
}
const imgX = startX + (boxWidth - renderW) / 2;
const imgY = 70 + (boxMaxHeight - renderH) / 2;
const cardY = 70;
const textY = cardY + boxMaxHeight;
// Imagem do quadrinho centralizada e mantendo proporções exatas
doc.addImage(imgDetails.dataUrl, 'JPEG', imgX, imgY, renderW, renderH);
// Fundo da caixa de texto
const textY = 70 + imgHeight;
doc.setFillColor(248, 250, 252);
doc.rect(startX, textY, cardWidth, textHeight, 'F');
doc.rect(startX, textY, boxWidth, textHeight, 'F');
// Moldura unificada ao redor do quadro (imagem + texto)
doc.setDrawColor(30, 41, 59);
doc.setLineWidth(2);
doc.rect(startX, 70, cardWidth, imgHeight + textHeight, 'D');
doc.rect(startX, cardY, boxWidth, boxMaxHeight + textHeight, 'D');
// Linha separando a imagem da caixa de texto
doc.line(startX, textY, startX + cardWidth, textY);
// Linha separadora entre imagem e caixa de texto
doc.line(startX, textY, startX + boxWidth, textY);
// Legenda formatada em tamanho maior (13pt)
// Legenda formatada
if (panel.dialogue) {
doc.setFont('helvetica', 'normal');
doc.setFontSize(13);
doc.setTextColor(15, 23, 42);
const splitText = doc.splitTextToSize(panel.dialogue, cardWidth - 30);
const splitText = doc.splitTextToSize(panel.dialogue, boxWidth - 30);
doc.text(splitText, startX + 15, textY + 30);
}
// Círculo discreto com o numeral do quadro no canto superior esquerdo da imagem
// Numeral do quadro
doc.setFillColor(254, 240, 138);
doc.circle(startX + 20, 90, 11, 'F');
doc.setDrawColor(30, 41, 59);