-
Estilo:
-
+
+
+
+
+
+ {{ activeElementInfo?.context === 'table-cell' ? 'Celda de tabla' : 'Texto' }}
+
+
+
+
+
-
-
-
+
+
-
-
- Tamaño:
+
-
-
-
+
+
-
-
-
Alinear:
+
-
-
-
+
+
-
-
-
-
-
-
Elemento de texto seleccionado
+
+
+ {{ activeElementInfo?.context === 'table-cell' ? 'Formateando celda de tabla' : 'Formateando texto' }}
+
\ No newline at end of file
diff --git a/src/pages/Maquetador/Index.vue b/src/pages/Maquetador/Index.vue
index 75de218..6024e36 100644
--- a/src/pages/Maquetador/Index.vue
+++ b/src/pages/Maquetador/Index.vue
@@ -41,15 +41,25 @@ const moveElement = (moveData) => {
/** Referencias */
const viewportRef = ref(null);
-/** Referencias adicionales */
-const textFormatterElement = ref(null);
+/** Referencias adicionales para TextFormatter */
+const selectedElement = ref(null);
+const activeTextElement = ref(null);
const showTextFormatter = ref(false);
const selectElement = (elementId) => {
selectedElementId.value = elementId;
const selectedEl = allElements.value.find(el => el.id === elementId);
- showTextFormatter.value = selectedEl?.type === 'text';
- textFormatterElement.value = selectedEl;
+ selectedElement.value = selectedEl;
+
+ // Mostrar TextFormatter si es texto o tabla
+ showTextFormatter.value = selectedEl?.type === 'text' || selectedEl?.type === 'table';
+
+ // Si es texto directo, activar como activeTextElement
+ if (selectedEl?.type === 'text') {
+ activeTextElement.value = selectedEl;
+ } else {
+ activeTextElement.value = null;
+ }
};
const updateElement = (update) => {
@@ -57,40 +67,20 @@ const updateElement = (update) => {
if (element) {
Object.assign(element, update);
- // Si se actualiza el formato, actualizar la referencia
- if (update.formatting && textFormatterElement.value?.id === update.id) {
- textFormatterElement.value = { ...element };
+ // Actualizar referencias si es necesario
+ if (selectedElement.value?.id === update.id) {
+ selectedElement.value = { ...element };
+ }
+ if (activeTextElement.value?.id === update.id) {
+ activeTextElement.value = { ...element };
}
}
};
-const handleSmartAlign = (alignData) => {
- const element = allElements.value.find(el => el.id === alignData.id);
- if (element && element.type === 'text') {
- const pageWidth = currentPageSize.value.width;
- const elementWidth = element.width || 200;
-
- let newX = element.x;
- switch (alignData.align) {
- case 'center':
- newX = (pageWidth - elementWidth) / 2;
- break;
- case 'right':
- newX = pageWidth - elementWidth - 50;
- break;
- case 'left':
- default:
- newX = 50;
- }
-
- // Actualizar posición y formato
- Object.assign(element, {
- x: newX,
- formatting: alignData.formatting
- });
-
- textFormatterElement.value = { ...element };
- }
+// Manejar selección de texto dentro de celdas de tabla
+const handleTextSelected = (data) => {
+ activeTextElement.value = data.element;
+ showTextFormatter.value = true;
};
/** Tipos de elementos disponibles */
@@ -270,9 +260,6 @@ const exportPDF = async () => {
// Convertir dimensiones de pixels a puntos (1 px = 0.75 puntos)
const pageWidthPoints = currentPageSize.value.width * 0.75;
const pageHeightPoints = currentPageSize.value.height * 0.75;
-
- let currentPDFPage = pdfDoc.addPage([pageWidthPoints, pageHeightPoints]);
- let currentPageHeight = pageHeightPoints;
// Obtener fuentes
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
@@ -283,11 +270,9 @@ const exportPDF = async () => {
for (let pageIndex = 0; pageIndex < pages.value.length; pageIndex++) {
const page = pages.value[pageIndex];
- // Crear nueva página PDF si no es la primera
- if (pageIndex > 0) {
- currentPDFPage = pdfDoc.addPage([pageWidthPoints, pageHeightPoints]);
- currentPageHeight = pageHeightPoints;
- }
+ // Crear nueva página PDF
+ let currentPDFPage = pdfDoc.addPage([pageWidthPoints, pageHeightPoints]);
+ let currentPageHeight = pageHeightPoints;
// Ordenar elementos de esta página por posición
const sortedElements = [...page.elements].sort((a, b) => {
@@ -308,16 +293,15 @@ const exportPDF = async () => {
switch (element.type) {
case 'text':
- if (element.content) {
+ if (element.content && element.content.trim()) {
// Obtener formato del elemento
const formatting = element.formatting || {};
- const fontSize = formatting.fontSize || 12;
+ const fontSize = Math.max(8, Math.min(72, formatting.fontSize || 12));
const isBold = formatting.bold || false;
- const isItalic = formatting.italic || false;
const textAlign = formatting.textAlign || 'left';
// Convertir color hexadecimal a RGB
- let textColor = rgb(0, 0, 0); // Negro por defecto
+ let textColor = rgb(0, 0, 0);
if (formatting.color) {
const hex = formatting.color.replace('#', '');
const r = parseInt(hex.substr(0, 2), 16) / 255;
@@ -327,51 +311,66 @@ const exportPDF = async () => {
}
// Seleccionar fuente
- let font = helveticaFont;
- if (isBold && !isItalic) {
- font = helveticaBoldFont;
- } else if (isBold && isItalic) {
- // Para negrita + cursiva, usar negrita (limitación de PDF-lib)
- font = helveticaBoldFont;
- }
+ let font = isBold ? helveticaBoldFont : helveticaFont;
- // Dividir texto largo en líneas
- const words = element.content.split(' ');
- const lines = [];
- let currentLine = '';
+ // NUEVO: Dividir por párrafos (saltos de línea)
+ const paragraphs = element.content.split('\n');
const maxWidth = Math.min(pageWidthPoints - 100, (element.width || 200) * scaleX);
+ const lineHeight = fontSize * 1.4;
+ let currentY = y;
- for (const word of words) {
- const testLine = currentLine + (currentLine ? ' ' : '') + word;
- const testWidth = font.widthOfTextAtSize(testLine, fontSize);
-
- if (testWidth <= maxWidth) {
- currentLine = testLine;
- } else {
- if (currentLine) lines.push(currentLine);
- currentLine = word;
- }
- }
- if (currentLine) lines.push(currentLine);
-
- // Calcular posición X según alineación
- lines.forEach((line, index) => {
- let lineX = x;
- const lineWidth = font.widthOfTextAtSize(line, fontSize);
-
- if (textAlign === 'center') {
- lineX = x + (maxWidth - lineWidth) / 2;
- } else if (textAlign === 'right') {
- lineX = x + maxWidth - lineWidth;
+ paragraphs.forEach((paragraph, paragraphIndex) => {
+ if (paragraph.trim() === '') {
+ // Párrafo vacío - solo agregar espacio
+ currentY -= lineHeight;
+ return;
}
- currentPDFPage.drawText(line, {
- x: Math.max(50, lineX),
- y: y - (index * (fontSize + 3)),
- size: fontSize,
- font: font,
- color: textColor,
+ // Dividir párrafo en líneas que caben en el ancho
+ const words = paragraph.split(' ');
+ const lines = [];
+ let currentLine = '';
+
+ for (const word of words) {
+ const testLine = currentLine + (currentLine ? ' ' : '') + word;
+ const testWidth = font.widthOfTextAtSize(testLine, fontSize);
+
+ if (testWidth <= maxWidth) {
+ currentLine = testLine;
+ } else {
+ if (currentLine) lines.push(currentLine);
+ currentLine = word;
+ }
+ }
+ if (currentLine) lines.push(currentLine);
+
+ // Dibujar cada línea del párrafo
+ lines.forEach((line, lineIndex) => {
+ let lineX = x;
+ const lineWidth = font.widthOfTextAtSize(line, fontSize);
+
+ // Calcular posición X según alineación
+ if (textAlign === 'center') {
+ lineX = x + (maxWidth - lineWidth) / 2;
+ } else if (textAlign === 'right') {
+ lineX = x + maxWidth - lineWidth;
+ }
+
+ currentPDFPage.drawText(line, {
+ x: Math.max(50, lineX),
+ y: currentY,
+ size: fontSize,
+ font: font,
+ color: textColor,
+ });
+
+ currentY -= lineHeight;
});
+
+ // Agregar espacio extra entre párrafos
+ if (paragraphIndex < paragraphs.length - 1) {
+ currentY -= lineHeight * 0.3;
+ }
});
}
break;
@@ -380,10 +379,7 @@ const exportPDF = async () => {
if (element.content && element.content.startsWith('data:image')) {
try {
const base64Data = element.content.split(',')[1];
- const imageBytes = Uint8Array.from(
- atob(base64Data),
- c => c.charCodeAt(0)
- );
+ const imageBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
let image;
if (element.content.includes('image/jpeg') || element.content.includes('image/jpg')) {
@@ -424,7 +420,7 @@ const exportPDF = async () => {
borderWidth: 1,
});
- currentPDFPage.drawText('[Imagen no disponible]', {
+ currentPDFPage.drawText('[Error al cargar imagen]', {
x: x + 5,
y: y - 35,
size: 10,
@@ -432,74 +428,95 @@ const exportPDF = async () => {
color: rgb(0.7, 0.7, 0.7),
});
}
- } else {
- // Placeholder para imagen vacía
- currentPDFPage.drawRectangle({
- x: x,
- y: y - 60,
- width: 100 * scaleX,
- height: 60 * scaleY,
- borderColor: rgb(0.7, 0.7, 0.7),
- borderWidth: 1,
- borderDashArray: [3, 3],
- });
-
- currentPDFPage.drawText('[Imagen]', {
- x: x + 25,
- y: y - 35,
- size: 10,
- font: helveticaFont,
- color: rgb(0.7, 0.7, 0.7),
- });
}
break;
case 'table':
- if (element.content && element.content.data) {
+ if (element.content && element.content.data && Array.isArray(element.content.data)) {
const tableData = element.content.data;
- const cellWidth = 80 * scaleX;
- const cellHeight = 20 * scaleY;
- const tableWidth = tableData[0].length * cellWidth;
- const tableHeight = tableData.length * cellHeight;
+ const totalCols = tableData[0]?.length || 0;
+ const totalRows = tableData.length;
+
+ if (totalCols > 0 && totalRows > 0) {
+ const availableWidth = Math.min(pageWidthPoints - 100, (element.width || 300) * scaleX);
+ const cellWidth = availableWidth / totalCols;
+ const cellHeight = 25;
+ const tableWidth = cellWidth * totalCols;
+ const tableHeight = cellHeight * totalRows;
- currentPDFPage.drawRectangle({
- x: x,
- y: y - tableHeight,
- width: tableWidth,
- height: tableHeight,
- borderColor: rgb(0.3, 0.3, 0.3),
- borderWidth: 1,
- });
-
- tableData.forEach((row, rowIndex) => {
- row.forEach((cell, colIndex) => {
- const cellX = x + (colIndex * cellWidth);
- const cellY = y - (rowIndex * cellHeight) - 15;
-
- currentPDFPage.drawRectangle({
- x: cellX,
- y: y - ((rowIndex + 1) * cellHeight),
- width: cellWidth,
- height: cellHeight,
- borderColor: rgb(0.7, 0.7, 0.7),
- borderWidth: 0.5,
- });
-
- const maxChars = Math.floor(cellWidth / 8);
- const displayText = cell.length > maxChars ?
- cell.substring(0, maxChars - 3) + '...' : cell;
-
- currentPDFPage.drawText(displayText, {
- x: cellX + 5,
- y: cellY,
- size: 8,
- font: helveticaFont,
- color: rowIndex === 0 ? rgb(0.2, 0.2, 0.8) : rgb(0, 0, 0),
- });
+ // Dibujar borde exterior de la tabla
+ currentPDFPage.drawRectangle({
+ x: x,
+ y: y - tableHeight,
+ width: tableWidth,
+ height: tableHeight,
+ borderColor: rgb(0.2, 0.2, 0.2),
+ borderWidth: 1.5,
});
- });
+
+ // Dibujar cada celda
+ tableData.forEach((row, rowIndex) => {
+ if (Array.isArray(row)) {
+ row.forEach((cell, colIndex) => {
+ const cellX = x + (colIndex * cellWidth);
+ const cellY = y - (rowIndex * cellHeight);
+
+ // Dibujar borde de celda
+ currentPDFPage.drawRectangle({
+ x: cellX,
+ y: cellY - cellHeight,
+ width: cellWidth,
+ height: cellHeight,
+ borderColor: rgb(0.7, 0.7, 0.7),
+ borderWidth: 0.5,
+ });
+
+ // Fondo especial para headers
+ if (rowIndex === 0) {
+ currentPDFPage.drawRectangle({
+ x: cellX,
+ y: cellY - cellHeight,
+ width: cellWidth,
+ height: cellHeight,
+ color: rgb(0.95, 0.95, 1),
+ });
+ }
+
+ // Dibujar texto de la celda
+ if (cell && typeof cell === 'string' && cell.trim()) {
+ const maxChars = Math.max(1, Math.floor(cellWidth / 6));
+ let displayText = cell.trim();
+
+ if (displayText.length > maxChars) {
+ displayText = displayText.substring(0, maxChars - 3) + '...';
+ }
+
+ const fontSize = Math.max(8, Math.min(12, cellWidth / 8));
+ const font = rowIndex === 0 ? helveticaBoldFont : helveticaFont;
+ const textColor = rowIndex === 0 ? rgb(0.1, 0.1, 0.6) : rgb(0, 0, 0);
+
+ const textWidth = font.widthOfTextAtSize(displayText, fontSize);
+ const textX = cellX + (cellWidth - textWidth) / 2;
+ const textY = cellY - cellHeight/2 - fontSize/2;
+
+ currentPDFPage.drawText(displayText, {
+ x: Math.max(cellX + 2, textX),
+ y: Math.max(cellY - cellHeight + 5, textY),
+ size: fontSize,
+ font: font,
+ color: textColor,
+ });
+ }
+ });
+ }
+ });
+ }
}
break;
+
+ default:
+ console.warn(`Tipo de elemento no soportado: ${element.type}`);
+ break;
}
}
}
@@ -522,7 +539,7 @@ const exportPDF = async () => {
} catch (error) {
console.error('Error al generar PDF:', error);
- window.Notify.error('Error al generar el PDF');
+ window.Notify.error('Error al generar el PDF: ' + error.message);
} finally {
isExporting.value = false;
}
@@ -574,14 +591,14 @@ onMounted(() => {
-
-
-
+
+
+
-
-
-
-
+
+
+
+
Diseñador de Documentos
@@ -589,7 +606,7 @@ onMounted(() => {
@@ -597,12 +614,8 @@ onMounted(() => {
-
-
+
+
Elementos
-
+
Arrastra elementos al canvas para crear tu documento
-
+
+
{
/>
-
-
- Agregar rápido
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ documentTitle }}
-
-
-
-
-
- {{ totalElements }} elemento{{ totalElements !== 1 ? 's' : '' }} • {{ pages.length }} página{{ pages.length !== 1 ? 's' : '' }}
-
+
+
+
+ Agregar rápido
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+ {{ documentTitle }}
+
+
+
+
+
+ {{ totalElements }} elemento{{ totalElements !== 1 ? 's' : '' }} • {{ pages.length }} página{{ pages.length !== 1 ? 's' : '' }}
+
+
+
+
+
{
@delete="deleteElement"
@update="updateElement"
@move="moveElement"
+ @text-selected="handleTextSelected"
/>