diff --git a/src/server/services/PdfService.ts b/src/server/services/PdfService.ts index f73d36c..2b37adc 100644 --- a/src/server/services/PdfService.ts +++ b/src/server/services/PdfService.ts @@ -266,36 +266,36 @@ export class PdfService { .replace(/style="[^"]*?text-indent:\s*(\d+(?:\.\d+)?(?:pt|px|em))[^"]*?"/gi, 'data-indent="$1"') .replace(/style="[^"]*?text-align:\s*(center|justify|right)[^"]*?"/gi, 'data-align="$1"') // Remove todos os outros atributos de estilo - .replace(/\s+style="[^"]*?"/gi, '') + .replace(/\s+style="[^"]*?"/gi, '')\s*(\d+(?:\.\d+)?(?:pt|px|em))[^"]*?"/gi, 'data-indent="$1"') // Substitui classes específicas por atributos de dados para preservar formatação .replace(/class="[^"]*?MsoListParagraph[^"]*?"/gi, 'data-list-para="true"') - // Normaliza blockquote e indentações - .replace(/
]*>/gi, '') - .replace(/<\/blockquote>/gi, '
') - // Trata listas + // Normaliza blockquote e indentaçõesatributos de dados para preservar formatação + .replace(/]*>/gi, '')list-para="true"') + .replace(/<\/blockquote>/gi, '
')s + // Trata listaskquote[^>]*>/gi, '') .replace(/
]*>/gi, ' • ') .replace(/<\/li>/gi, '
'); - + .replace(/]*>/gi, ' • ') // Remove todas as tags, exceto , , , , ,
,, // e mantém os atributos data-* que acabamos de criar para formatação - return simplified - .replace(/<(b|strong|i|em|u|p|span|br)([^>]*)>/gi, (match, tag, attrs) => { + return simplifiedom alinhamento para nosso formato especial + .replace(/<(b|strong|i|em|u|p|span|br)([^>]*)>/gi, (match, tag, attrs) => {>]*>/gi, '
') { - // Finaliza o segmento atual se não vazio - if (currentSegment.text) { - segments.push({ ...currentSegment }); - currentSegment.text = ''; - } - currentSegment.isBold = false; - } else if (tag === '' || tag === '') { - // Finaliza o segmento atual se não vazio - if (currentSegment.text) { - segments.push({ ...currentSegment }); - currentSegment.text = ''; - } - currentSegment.isItalic = true; - } else if (tag === '' || tag === '') { - // Finaliza o segmento atual se não vazio - if (currentSegment.text) { - segments.push({ ...currentSegment }); - currentSegment.text = ''; - } - currentSegment.isItalic = false; - } - - lastIndex = match.index + match[0].length; - } - - // Adiciona o texto restante - if (lastIndex < textToProcess.length) { - currentSegment.text += textToProcess.substring(lastIndex); - } - - // Adiciona o último segmento se não estiver vazio - if (currentSegment.text) { - segments.push(currentSegment); - } - - // Processa cada segmento e divide em linhas - const lines: { segments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number }[] = []; - let currentLine: { segments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number } = { - segments: [], - totalWidth: 0 - }; - - for (const segment of segments) { - const words = segment.text.split(/\s+/); - - for (let i = 0; i < words.length; i++) { - const word = words[i]; - if (!word) continue; - - // Adiciona espaço antes se não for a primeira palavra da linha - const wordWithSpace = currentLine.segments.length > 0 ? ' ' + word : word; - // Seleciona a fonte correta com base na formatação - let segmentFont; - if (segment.isBold && segment.isItalic) { - segmentFont = fontBoldItalic; - } else if (segment.isBold) { - segmentFont = fontBold; - } else if (segment.isItalic) { - segmentFont = fontItalic; - } else { - segmentFont = font; - } - - const wordWidth = segmentFont.widthOfTextAtSize(wordWithSpace, fontSize); - - // Se a palavra cabe na linha atual - if (currentLine.totalWidth + wordWidth <= maxWidth) { - currentLine.segments.push({ - text: wordWithSpace, - isBold: segment.isBold, - isItalic: segment.isItalic, - width: wordWidth, - font: segmentFont - }); - currentLine.totalWidth += wordWidth; - } else { - // Se a linha atual não está vazia, finalize-a - if (currentLine.segments.length > 0) { - lines.push(currentLine); - currentLine = { segments: [], totalWidth: 0 }; - } - - // Se a palavra for maior que a largura máxima, divida-a - if (segmentFont.widthOfTextAtSize(word, fontSize) > maxWidth) { - // Implementação simplificada: apenas adiciona a palavra muito longa - // em uma nova linha, mesmo que ultrapasse a largura máxima - currentLine.segments.push({ - text: word, - isBold: segment.isBold, - isItalic: segment.isItalic, - width: segmentFont.widthOfTextAtSize(word, fontSize), - font: segmentFont - }); - currentLine.totalWidth = segmentFont.widthOfTextAtSize(word, fontSize); - lines.push(currentLine); - currentLine = { segments: [], totalWidth: 0 }; - } else { - // Caso contrário, adicione a palavra na nova linha - currentLine.segments.push({ - text: word, - isBold: segment.isBold, - isItalic: segment.isItalic, - width: segmentFont.widthOfTextAtSize(word, fontSize), - font: segmentFont - }); - currentLine.totalWidth = segmentFont.widthOfTextAtSize(word, fontSize); - } - } - } - } - - // Adiciona a última linha se não estiver vazia - if (currentLine.segments.length > 0) { - lines.push(currentLine); - } - - // Se não houver linhas após o processamento, retorne a posição Y atual - if (lines.length === 0) { - return y; - } - - // Verifica se todas as linhas cabem na página atual - const totalHeight = lines.length * (fontSize * 1.2); - if (y - totalHeight < marginBottom) { - return null; // Precisa de uma nova página - } - - // Renderiza as linhas - let currentY = y; - const lineHeight = fontSize * 1.2; - - for (const line of lines) { - let currentX = x; - - // Ajusta o X inicial com base no alinhamento - if (textAlign === 'center') { - currentX = x + (maxWidth - line.totalWidth) / 2; - } else if (textAlign === 'justify' && line !== lines[lines.length - 1] && line.segments.length > 1) { - // Para justificado, ajusta apenas linhas que não sejam a última e tenham mais de um segmento - const extraSpace = maxWidth - line.totalWidth; - const spaceBetweenSegments = extraSpace / (line.segments.length - 1); - - for (const segment of line.segments) { - const font = segment.font; - page.drawText(segment.text, { - x: currentX, - y: currentY, - size: fontSize, - font: font, - color: color - }); - currentX += segment.width + spaceBetweenSegments; - } - } else { - // Para alinhamento à esquerda ou a última linha de justificado - for (const segment of line.segments) { - const font = segment.font; - page.drawText(segment.text, { - x: currentX, - y: currentY, - size: fontSize, - font: font, - color: color - }); - currentX += segment.width; - } - } - - currentY -= lineHeight; - } - - return currentY; - } - - const texto = decodeHtmlEntities( + } .replace(/”/g, '”') + .replace(/€/g, '€') + const texto = decodeHtmlEntities( .replace(/¥/g, '¥') stripUnsupportedTags( this.processarTemplate(conteudo, escritorio) - .replace(/§§ALIGN=$1§§'); // Preserva apenas data-* atributos const dataAttrs = attrs.match(/\s+data-[^=]+="[^"]*"/gi) || []; - return `<${tag}${dataAttrs.join('')}>`; + return `<${tag}${dataAttrs.join('')}>`;a-indent="([^"]+)"[^>]*>/gi, '
§§INDENT=$1§§'); }) - // Remove todas as outras tags não suportadas + // Remove todas as outras tags não suportadasto interno .replace(/<(?!\/?(?:b|strong|i|em|u|br|p|span))[^>]+>/gi, '') // Converte números seguidos de parêntese (como em listas numeradas) em formato padronizado - .replace(/(\d+\))\s+/g, '$1 '); - } - + .replace(/(\d+\))\s+/g, '$1 '); '**') + } .replace(/<\/?(?:em|i)>/gi, '_') + .replace(/<\/?u>/gi, '_') // Função para decodificar entidades HTML comuns (acentuação, símbolos, º, etc) function decodeHtmlEntities(str: string): string { - if (!str) return ''; - + if (!str) return '';]*>/gi, '') + // Remove todas as outras tags // Como estamos em Node.js, usamos apenas o método de substituição - return str + return str(/\r\n/g, '\n') // Acentos minúsculos e maiúsculos .replace(/Á/g, 'Á').replace(/á/g, 'á') .replace(/É/g, 'É').replace(/é/g, 'é') @@ -303,429 +303,208 @@ export class PdfService { .replace(/Ó/g, 'Ó').replace(/ó/g, 'ó') .replace(/Ú/g, 'Ú').replace(/ú/g, 'ú') .replace(/À/g, 'À').replace(/à/g, 'à') - .replace(/È/g, 'È').replace(/è/g, 'è') + .replace(/È/g, 'È').replace(/è/g, 'è')ação, símbolos, º, etc) .replace(/Ì/g, 'Ì').replace(/ì/g, 'ì') .replace(/Ò/g, 'Ò').replace(/ò/g, 'ò') .replace(/Ù/g, 'Ù').replace(/ù/g, 'ù') - .replace(/Â/g, 'Â').replace(/â/g, 'â') + .replace(/Â/g, 'Â').replace(/â/g, 'â')e substituição .replace(/Ê/g, 'Ê').replace(/ê/g, 'ê') .replace(/Î/g, 'Î').replace(/î/g, 'î') - .replace(/Ô/g, 'Ô').replace(/ô/g, 'ô') - .replace(/Û/g, 'Û').replace(/û/g, 'û') + .replace(/Ô/g, 'Ô').replace(/ô/g, 'ô')') + .replace(/Û/g, 'Û').replace(/û/g, 'û')') .replace(/Ã/g, 'Ã').replace(/ã/g, 'ã') .replace(/Õ/g, 'Õ').replace(/õ/g, 'õ') .replace(/Ç/g, 'Ç').replace(/ç/g, 'ç') .replace(/Ñ/g, 'Ñ').replace(/ñ/g, 'ñ') - .replace(/Ü/g, 'Ü').replace(/ü/g, 'ü') - // Símbolos comuns - .replace(/º/g, 'º') - .replace(/ª/g, 'ª') - .replace(/§/g, '§') - .replace(/°/g, '°') - .replace(/©/g, '©') - .replace(/®/g, '®') - .replace(/¶/g, '¶') - .replace(/•/g, '•') - .replace(/–/g, '–') - .replace(/—/g, '—') - .replace(/«/g, '«') - .replace(/»/g, '»') + .replace(/Ü/g, 'Ü').replace(/ü/g, 'ü')'è') + // Símbolos comuns/g, 'Ì').replace(/ì/g, 'ì') + .replace(/º/g, 'º')').replace(/ò/g, 'ò') + .replace(/ª/g, 'ª')').replace(/ù/g, 'ù') + .replace(/§/g, '§')).replace(/â/g, 'â') + .replace(/°/g, '°')').replace(/ê/g, 'ê') + .replace(/©/g, '©')).replace(/î/g, 'î') + .replace(/®/g, '®')').replace(/ô/g, 'ô') + .replace(/¶/g, '¶')).replace(/û/g, 'û') + .replace(/•/g, '•')').replace(/ã/g, 'ã') + .replace(/–/g, '–')).replace(/õ/g, 'õ') + .replace(/—/g, '—')).replace(/ç/g, 'ç') + .replace(/«/g, '«')).replace(/ñ/g, 'ñ') + .replace(/»/g, '»')replace(/ü/g, 'ü') .replace(/‘/g, '‘') .replace(/’/g, '’') .replace(/“/g, '“') .replace(/”/g, '”') .replace(/€/g, '€') - .replace(/¥/g, '¥') + .replace(/¥/g, '¥')) .replace(/£/g, '£') .replace(/¢/g, '¢') .replace(/ /g, ' ') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, "'") - .replace(/</g, '<') - .replace(/>/g, '>') - // Numéricos + .replace(/&/g, '&')') + .replace(/"/g, '"')) + .replace(/'/g, "'")') + .replace(/</g, '<')»') + .replace(/>/g, '>')‘') + // Numéricossquo;/g, '’') .replace(/(\d+);/g, (m, code) => String.fromCharCode(code)); - } - - /** - * Renderiza um parágrafo com formatação inline (negrito, itálico, etc) - * Retorna a nova posição Y após renderizar o parágrafo, ou null se precisar de nova página - */ - function renderInlineFormattedParagraph( - page: any, - text: string, - x: number, - y: number, - font: any, - fontBold: any, - fontItalic: any, - fontBoldItalic: any, - fontSize: number, - color: any, - align: 'left' | 'center' | 'justify' = 'left', - maxWidth: number, - forceNewPage: boolean = false, - marginTop: number, - marginBottom: number, - pageHeight: number - ): number | null { - if (forceNewPage || y < marginBottom + fontSize * 2) { - return null; // Indica necessidade de nova página - } - - // Dividir o texto em palavras preservando as tags - const segments: { text: string; isBold: boolean; isItalic: boolean }[] = []; - let currentSegment = { text: '', isBold: false, isItalic: false }; - - // Regex para encontrar tags , , , , , , , - const regex = /<\/?(?:b|strong|i|em)>/gi; - let lastIndex = 0; - let match; - - // Clone do texto para manipulação - let textToProcess = text; - - // Processar tags de indentação e alinhamento - let indentPt = 0; - let textAlign = align; - - const indentMatch = textToProcess.match(/^§§INDENT=([0-9.]+(?:pt|px|em))§§/); - if (indentMatch) { - const indentValue = indentMatch[1]; - indentPt = parseInt(indentValue); - if (isNaN(indentPt)) indentPt = 20; // Padrão: 20pt - textToProcess = textToProcess.substring(indentMatch[0].length); - x += indentPt; - maxWidth -= indentPt; - } - - const alignMatch = textToProcess.match(/^§§ALIGN=(center|justify|right)§§/); - if (alignMatch) { - textAlign = alignMatch[1] as 'center' | 'justify' | 'left'; - textToProcess = textToProcess.substring(alignMatch[0].length); - } - - while ((match = regex.exec(textToProcess)) !== null) { - // Adiciona texto entre as tags - if (match.index > lastIndex) { - currentSegment.text += textToProcess.substring(lastIndex, match.index); - } - - // Processa a tag - const tag = match[0].toLowerCase(); - if (tag === '' || tag === '') { - // Finaliza o segmento atual se não vazio - if (currentSegment.text) { - segments.push({ ...currentSegment }); - currentSegment.text = ''; - } - currentSegment.isBold = true; - } else if (tag === '' || tag === '
/gi, '\n') - .replace(/]*>/gi, '§§INDENT=$1§§') - .replace(/
]*>/gi, '§§ALIGN=$1§§') - .replace(/
]*>/gi, '• ') - .replace(/
]*>/gi, '§§INDENT=20pt§§') - .replace(/<\/p>/gi, '\n\n') - .replace(/
]*>/gi, '') - .replace(/\r\n/g, '\n') - .replace(/\r/g, '\n') - .replace(/\n{3,}/g, '\n\n') - .trim() - ) + ) .replace(/ /g, ' ') ); +(/"/g, '"') + // Process paragraph alignment and formatting#39;/g, "'") + const paragrafos = texto.split(/\n{2,}/).map(p => {(/</g, '<') + let paragraph = p.trim();(/>/g, '>') + if (!paragraph) return '';icos + (\d+);/g, (m, code) => String.fromCharCode(code)); + // Process paragraph alignment + let align = 'justify'; // default + const alignMatch = paragraph.match(/^§§ALIGN=(center|justify|right)§§/); + if (alignMatch) {um parágrafo com formatação inline (negrito, itálico, etc) + align = alignMatch[1];rágrafo, ou null se precisar de nova página + paragraph = paragraph.substring(alignMatch[0].length); + }ragraph( + + // Process indentation + let indent = 0; + const indentMatch = paragraph.match(/^§§INDENT=([0-9.]+)(?:pt|px|em)§§/); + if (indentMatch) { + indent = parseFloat(indentMatch[1]); + paragraph = paragraph.substring(indentMatch[0].length);ontItalic: any, + } fontBoldItalic: any, if (forceNewPage || y < marginBottom + fontSize * 2) { + + // Mark paragraph with alignment and indentation + return `§§ALIGN=${align}§§§§INDENT=${indent}§§${paragraph}`; + }).filter(Boolean); maxWidth: number, // Dividir o texto em palavras preservando as tags - const paragrafos = texto.split(/\n{2,}/).map(p => p.trim()).filter(Boolean); - - let timbradoPdf: PDFDocument | null = null; - let timbradoPageCount = 0; - let timbradoPages: any[] = []; - if (timbradoExists) { - const timbradoBytes = await fs.readFile(timbradoPath); - timbradoPdf = await PDFDocument.load(timbradoBytes); - timbradoPageCount = timbradoPdf.getPageCount(); + let timbradoPdf: PDFDocument | null = null;}; + let timbradoPageCount = 0;er, + let timbradoPages: any[] = [];: numberara encontrar tags , , , , , , , + if (timbradoExists) { number | null {const regex = /<\/?(?:b|strong|i|em)>/gi; + const timbradoBytes = await fs.readFile(timbradoPath);om + fontSize * 2) { + timbradoPdf = await PDFDocument.load(timbradoBytes);necessidade de nova página + timbradoPageCount = timbradoPdf.getPageCount();} timbradoPages = []; - for (let i = 0; i < timbradoPageCount; i++) { - const [embedded] = await pdfDoc.embedPages([timbradoPdf.getPage(i)]); - timbradoPages.push(embedded); + for (let i = 0; i < timbradoPageCount; i++) {o em palavras preservando as tags = text; + const [embedded] = await pdfDoc.embedPages([timbradoPdf.getPage(i)]);: string; isBold: boolean; isItalic: boolean }[] = []; + timbradoPages.push(embedded);let currentSegment = { text: '', isBold: false, isItalic: false };// Processar tags de indentação e alinhamento } - } - - let currentPar = 0; + }ntrar tags , , , , , , , ign; +/gi; + let currentPar = 0;[0-9.]+(?:pt|px|em))§§/); let pageIndex = 0; while (currentPar < paragrafos.length) { - let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]); - if (timbradoPages.length > 0) { - const embedded = timbradoPages[pageIndex % timbradoPages.length]; - page.drawPage(embedded, { + let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);o para manipulaçãoseInt(indentValue); + if (timbradoPages.length > 0) {t;indentPt = 20; // Padrão: 20pt + const embedded = timbradoPages[pageIndex % timbradoPages.length];textToProcess = textToProcess.substring(indentMatch[0].length); + page.drawPage(embedded, {// Processar tags de indentação e alinhamento x += indentPt; x: 0, - y: 0, + y: 0,lign; width: PAGE_WIDTH, - height: PAGE_HEIGHT - }); - } + height: PAGE_HEIGHT|px|em))§§/);|right)§§/); + });f (indentMatch) {f (alignMatch) { + } const indentValue = indentMatch[1]; textAlign = alignMatch[1] as 'center' | 'justify' | 'left'; let y = PAGE_HEIGHT - margens.top; - while (currentPar < paragrafos.length) { - let indent = paragrafos[currentPar].startsWith('§§INDENT§§'); - let indent2 = paragrafos[currentPar].startsWith('§§INDENT2§§'); - let center = paragrafos[currentPar].startsWith('§§CENTER§§'); - let align: 'center' | 'justify' | 'left' = center ? 'center' : 'justify'; - let text = paragrafos[currentPar] - .replace(/^§§INDENT2§§/, '') - .replace(/^§§INDENT§§/, '') - .replace(/^§§CENTER§§/, '') + while (currentPar < paragrafos.length) { 20; // Padrão: 20pt + const paraText = paragrafos[currentPar];substring(indentMatch[0].length); + + // Extract alignmentaxWidth -= indentPt;/ Adiciona texto entre as tags + let align: 'left' | 'center' | 'justify' = 'left';f (match.index > lastIndex) { + const alignMatch = paraText.match(/^§§ALIGN=(center|justify|right)§§/);ss.substring(lastIndex, match.index); + if (alignMatch) {h(/^§§ALIGN=(center|justify|right)§§/); + align = alignMatch[1] as 'center' | 'justify' | 'left'; + }stify' | 'left'; + s.substring(alignMatch[0].length);Case(); + // Extract indentation + let indent = 0; + const indentMatch = paraText.match(/§§INDENT=([0-9.]+)§§/); ((match = regex.exec(textToProcess)) !== null) {f (currentSegment.text) { + if (indentMatch) {egment }); + indent = parseFloat(indentMatch[1]); + }tring(lastIndex, match.index); + + // Clean text + let text = paraTextio + .replace(/^§§ALIGN=(center|justify|right)§§/, '')st tag = match[0].toLowerCase();f (currentSegment.text) { + .replace(/§§INDENT=[0-9.]+§§/, '')trong>') {gment }); .trim(); - - // Se o parágrafo for centralizado, força alinhamento center e ignora recuo - if (center) { - indent = false; - indent2 = false; - align = 'center'; - } - - text = decodeHtmlEntities(text); - - let x = margens.left; - let parWidth = MAX_WIDTH; - if (indent2) { - // Recuo maior para blockquote/citação - x += 40; - parWidth -= 40; - } else if (indent) { - x += 20; - parWidth -= 20; - } - + + // Process bold/italic formattingntSegment });lse; + text = text + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/_(.*?)_/g, '$1');urrentSegment.isBold = true;f (currentSegment.text) { + === '') {ment }); + let x = margens.left + indent; + let parWidth = MAX_WIDTH - indent; + ntSegment });true; let yAfter = renderInlineFormattedParagraph( page, - text, - x, - y, - font, + text,urrentSegment.isBold = false;f (currentSegment.text) { + x,= '') {ent }); + y, // Finaliza o segmento atual se não vazio currentSegment.text = ''; + font, if (currentSegment.text) { } fontBold, - fontItalic, - fontBoldItalic, - FONT_SIZE, - rgb(0, 0, 0), + fontItalic, currentSegment.text = ''; } + fontBoldItalic, } + FONT_SIZE,= true;match[0].length; + rgb(0, 0, 0),'') { align, - parWidth, - false, + parWidth, if (currentSegment.text) {/ Adiciona o texto restante + false, segments.push({ ...currentSegment });if (lastIndex < textToProcess.length) { margens.top, margens.bottom, - PAGE_HEIGHT - ); - if (yAfter === null) { + PAGE_HEIGHTlse; + ); }/ Adiciona o último segmento se não estiver vazio + if (yAfter === null) { if (currentSegment.text) { pageIndex++; break; } - y = yAfter - FONT_SIZE * 0.5; - currentPar++; - if (y < margens.bottom + FONT_SIZE * 1.5) break; - } + y = yAfter - FONT_SIZE * 0.5;xto restante segmento e divide em linhas + currentPar++;textToProcess.length) {egments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number }[] = []; + if (y < margens.bottom + FONT_SIZE * 1.5) break;currentSegment.text += textToProcess.substring(lastIndex);t currentLine: { segments: { text: string; isBold: boolean; isItalic: boolean; width: number; font: any }[]; totalWidth: number } = { + }} segments: [], pageIndex++; - } - + }er vazio + (currentSegment.text) { const pdfBytes = await pdfDoc.save(); return Buffer.from(pdfBytes); } catch (error) { - console.error('Erro geral ao gerar PDF:', error); - throw error; - } + console.error('Erro geral ao gerar PDF:', error);rocessa cada segmento e divide em linhasr (let i = 0; i < words.length; i++) { + throw error; boolean; width: number; font: any }[]; totalWidth: number }[] = []; + }n; width: number; font: any }[]; totalWidth: number } = { } - +ntes se não for a primeira palavra da linha private processarTemplate(template: string, dados: any): string { return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => { - const keyParts = key.trim().split('.'); - let value = dados; + const keyParts = key.trim().split('.');{ + let value = dados;split(/\s+/);ment.isItalic) { - for (const part of keyParts) { - if (value && typeof value === 'object' && part in value) { - value = value[part]; - } else { - value = undefined; - break; - } + for (const part of keyParts) {gth; i++) {{ + if (value && typeof value === 'object' && part in value) {rd = words[i];tFont = fontBold; + value = value[part];talic) { + } else {segmentFont = fontItalic; + value = undefined;// Adiciona espaço antes se não for a primeira palavra da linha} else { + break;; + }// Seleciona a fonte correta com base na formatação} } - - return value !== undefined ? value : match; - }); - } + ntSize); + return value !== undefined ? value : match;c; + });d) {linha atual + }wordWidth <= maxWidth) { +} + + + + + + + + + + + + + +} } }); return value !== undefined ? value : match; } } break; value = undefined; } else { value = value[part]; if (value && typeof value === 'object' && part in value) { } }