<?xml version="1.0" encoding="UTF-8"?>
<NeoAppBuilderPlugIn>
  <Title>neoPDF2</Title>
  <Author>SinLios Soluciones Digitales</Author>
  <Website>https://visualneo.com</Website>
  <EMail>info@sinlios.com</EMail>
  <Copyright>2025</Copyright>
  <Description>Load and edit existing PDF documents using pdf-lib library.
https://pdf-lib.js.org/

V: 1.0


</Description>
  <Icon20>C:\Users\sinli\OneDrive\CloudStation\visualneo\VisualNeoWeb\Plugins\neoPDF\neopdf-20.png</Icon20>
  <Icon32>C:\Users\sinli\OneDrive\CloudStation\visualneo\VisualNeoWeb\Plugins\neoPDF\neopdf-32.png</Icon32>
  <CompiledName>C:\Users\sinli\OneDrive\CloudStation\visualneo\VisualNeoWeb\Plugins\neoPdf2\neoPDF2.nabp</CompiledName>
  <AutoInstall>yes</AutoInstall>
  <Code>function neoPdf2Init(pdfName, callback){
  $App.PDFLibDocs = $App.PDFLibDocs || {};
  PDFLib.PDFDocument.create().then(function(doc){
      $App.PDFLibDocs[pdfName] = doc;
      if (callback){
         callback();
      }
  });
};

function neoPdf2LoadFromBase64(base64String, pdfName, callback){
  $App.PDFLibDocs = $App.PDFLibDocs || {};
  const bytes = Uint8Array.from(atob(base64String), c =&gt; c.charCodeAt(0));
  PDFLib.PDFDocument.load(bytes).then(function(doc){
      $App.PDFLibDocs[pdfName] = doc;
      neoPdf2EnsureSettings(pdfName);
      if (callback){
         callback();
      }
  });
};
function neoPdf2SimpleText(pdfName, textString, posX, posY, textAlign) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if(!doc || !settings){return;}
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const pageSize = page.getSize();
  const x = Number(posX) * 2.83465;
  const yStart = pageSize.height - (Number(posY) * 2.83465);

  const fontName = settings.fontName || "Helvetica";
  const fontStyle = settings.fontStyle || "Normal";
  const size = settings.fontSize || 12;
  const color = settings.color || PDFLib.rgb(0, 0, 0);
  const lineHeight = settings.lineHeight || 1.15;

  // Soporte para custom fonts
  let fontPromise;
  if (settings.customFonts &amp;&amp; settings.customFonts[fontName]) {
    // Si existe fuente personalizada con ese nombre, usarla
    fontPromise = Promise.resolve(settings.customFonts[fontName]);
  } else {
    // Si no, usar la estándar (como antes)
    let pdfLibFont;
    switch(fontName){
      case "Helvetica":
        if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.HelveticaBold;
        else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.HelveticaOblique;
        else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.HelveticaBoldOblique;
        else pdfLibFont = PDFLib.StandardFonts.Helvetica;
        break;
      case "TimesRoman":
        if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.TimesBold;
        else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.TimesItalic;
        else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.TimesBoldItalic;
        else pdfLibFont = PDFLib.StandardFonts.TimesRoman;
        break;
      case "Courier":
        if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.CourierBold;
        else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.CourierOblique;
        else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.CourierBoldOblique;
        else pdfLibFont = PDFLib.StandardFonts.Courier;
        break;
      case "Symbol":
        pdfLibFont = PDFLib.StandardFonts.Symbol; break;
      case "ZapfDingbats":
        pdfLibFont = PDFLib.StandardFonts.ZapfDingbats; break;
      default:
        pdfLibFont = PDFLib.StandardFonts.Helvetica; break;
    }
    fontPromise = doc.embedFont(pdfLibFont);
  }

  fontPromise.then(function(font) {
    // Divide en líneas por salto de línea
    const lines = textString.split('\n');
    lines.forEach((line, idx) =&gt; {
      let textWidth = font.widthOfTextAtSize(line, size);
      let drawX = x;
      // Text alignment
      if (textAlign === "center") drawX = x - textWidth/2;
      else if (textAlign === "right") drawX = x - textWidth;
      let drawY = yStart - idx * size * lineHeight;
      page.drawText(line, {
        x: drawX,
        y: drawY,
        size: size,
        font: font,
        color: color,
        lineHeight: lineHeight
      });
    });
  });
}
function neoPdf2Text(pdfName, textString, posX, posY, textAlign, textAngle, maxWidth, renderingMode) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if(!doc || !settings){return;}
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const pageSize = page.getSize();
  const x = Number(posX) * 2.83465;
  const yBase = pageSize.height - (Number(posY) * 2.83465);

  const fontName = settings.fontName || "Helvetica";
  const fontStyle = settings.fontStyle || "Normal";
  const size = settings.fontSize || 12;
  const color = settings.color || PDFLib.rgb(0,0,0);
  const lineHeight = settings.lineHeight || 1.15;

  // Soporte para custom fonts
  let fontPromise;
  if (settings.customFonts &amp;&amp; settings.customFonts[fontName]) {
    fontPromise = Promise.resolve(settings.customFonts[fontName]);
  } else {
    let pdfLibFont;
    switch(fontName){
      case "Helvetica":
        if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.HelveticaBold;
        else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.HelveticaOblique;
        else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.HelveticaBoldOblique;
        else pdfLibFont = PDFLib.StandardFonts.Helvetica;
        break;
      case "TimesRoman":
        if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.TimesBold;
        else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.TimesItalic;
        else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.TimesBoldItalic;
        else pdfLibFont = PDFLib.StandardFonts.TimesRoman;
        break;
      case "Courier":
        if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.CourierBold;
        else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.CourierOblique;
        else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.CourierBoldOblique;
        else pdfLibFont = PDFLib.StandardFonts.Courier;
        break;
      case "Symbol":
        pdfLibFont = PDFLib.StandardFonts.Symbol; break;
      case "ZapfDingbats":
        pdfLibFont = PDFLib.StandardFonts.ZapfDingbats; break;
      default:
        pdfLibFont = PDFLib.StandardFonts.Helvetica; break;
    }
    fontPromise = doc.embedFont(pdfLibFont);
  }

  fontPromise.then(function(font) {
    // 1. Divide en líneas por salto de línea
    let inputLines = textString.split('\n');
    let lines = [];
    // 2. Para cada línea, aplica word-wrap si procede
    if (maxWidth &amp;&amp; Number(maxWidth) &gt; 0) {
      inputLines.forEach(line =&gt; {
        const words = line.split(' ');
        let currentLine = '';
        words.forEach(word =&gt; {
          let testLine = currentLine ? currentLine + ' ' + word : word;
          let testWidth = font.widthOfTextAtSize(testLine, size);
          if (testWidth &gt; Number(maxWidth) * 2.83465 &amp;&amp; currentLine) {
            lines.push(currentLine);
            currentLine = word;
          } else {
            currentLine = testLine;
          }
        });
        if (currentLine) lines.push(currentLine);
      });
    } else {
      lines = inputLines;
    }
    let y = yBase;
    let rotate = PDFLib.degrees(Number(textAngle) || 0);
    lines.forEach((line, idx) =&gt; {
      let textWidth = font.widthOfTextAtSize(line, size);
      let drawX = x;
      if (textAlign === "center") drawX = x - textWidth/2;
      else if (textAlign === "right") drawX = x - textWidth;
      let drawY = y - idx * size * lineHeight;
      // Rendering mode: fill, stroke, fillThenStroke
      if (renderingMode === "stroke") {
        page.drawText(line, {
          x: drawX, y: drawY, size: size, font: font,
          color: undefined,
          borderColor: color,
          borderWidth: 0.7,
          rotate: rotate
        });
      } else if (renderingMode === "fillThenStroke") {
        page.drawText(line, {
          x: drawX, y: drawY, size: size, font: font,
          color: undefined,
          borderColor: color,
          borderWidth: 0.7,
          rotate: rotate
        });
        page.drawText(line, {
          x: drawX, y: drawY, size: size, font: font,
          color: color,
          rotate: rotate
        });
      } else {
        page.drawText(line, {
          x: drawX, y: drawY, size: size, font: font,
          color: color,
          rotate: rotate
        });
      }
    });
  });
}
function neoPdf2SaveToBase64(pdfName, resVar, callback){
  const doc = $App.PDFLibDocs &amp;&amp; $App.PDFLibDocs[pdfName];
  if(!doc){ return; }

  doc.saveAsBase64({ dataUri: true }).then(function(dataUri){
    $App[resVar] = dataUri;
    if (typeof callback === "function") callback();
    else if (typeof callback === "string" &amp;&amp; typeof window[callback] === "function") window[callback]();
  });
}

// Backward compatibility for older action templates using temp vars
function neoPdf2SaveToBase64b(){
  const pdfName = $App &amp;&amp; $App.NAB ? $App.NAB.tempPdf2Name : null;
  const resVar = $App &amp;&amp; $App.NAB ? $App.NAB.tempPdf2Var : null;
  if(!pdfName || !resVar) return;
  neoPdf2SaveToBase64(pdfName, resVar);
};

function neoPdf2Download(pdfName, fileName){
  const doc = $App.PDFLibDocs[pdfName];
  if(!doc){return;}
  if(!fileName || fileName==""){ fileName = pdfName; }
  doc.save().then(function(pdfBytes){
      const blob = new Blob([pdfBytes], { type: 'application/pdf' });
      const link = document.createElement('a');
      link.href = URL.createObjectURL(blob);
      link.download = fileName + '.pdf';
      link.click();
      URL.revokeObjectURL(link.href);
  });
};
function neoPdf2DrawRect(pdfName, posX, posY, width, height, colorR, colorG, colorB){
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if(!doc || !settings){return;}

  const pages = doc.getPages();
  let pageIndex = settings.currentPage || 1;
  if(pageIndex &lt; 1) pageIndex = 1;
  if(pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const size = page.getSize();

  // mm -&gt; pt (consistent with the rest of the plugin)
  const x = Number(posX) * 2.83465;
  const w = Number(width) * 2.83465;
  const h = Number(height) * 2.83465;
  const y = size.height - (Number(posY) * 2.83465) - h;

  page.drawRectangle({
    x: x,
    y: y,
    width: w,
    height: h,
    color: PDFLib.rgb((colorR||0)/255, (colorG||0)/255, (colorB||0)/255)
  });
}
function neoPdf2EnsureSettings(pdfName) {
  $App.PDFLibDocs[pdfName + "_settings"] = $App.PDFLibDocs[pdfName + "_settings"] || {
    fontName: "Helvetica",
    fontStyle: "Normal", // Normal, Bold, Italic, BoldItalic
    fontSize: 12,
    color: PDFLib.rgb(0, 0, 0),
    lineHeight: 1.15,
    fontObj: null // Se carga la fuente estándar a demanda
  };
}
function neoPdf2Font(pdfName, fontName) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].fontName = fontName;
  $App.PDFLibDocs[pdfName + "_settings"].fontObj = null; // Se recarga en el próximo texto
}
function neoPdf2FontStyle(pdfName, fontStyle) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].fontStyle = fontStyle;
  $App.PDFLibDocs[pdfName + "_settings"].fontObj = null;
}
function neoPdf2FontSize(pdfName, fontSize) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].fontSize = Number(fontSize);
}
function neoPdf2TextColor(pdfName, r, g, b) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].color = PDFLib.rgb((r || 0)/255, (g || 0)/255, (b || 0)/255);
}
function neoPdf2LineHeightFactor(pdfName, lineHeight) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].lineHeight = Number(lineHeight);
}
function neoPdf2SetPage(pdfName, pageNumber) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].currentPage = Number(pageNumber);
}
function neoPdf2AddPage(pdfName, orientation, paperSize) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  // Table of standard sizes in points
  const SIZES = {
    "A0":  [2383.94, 3370.39],  "A1":  [1683.78, 2383.94],
    "A2":  [1190.55, 1683.78],  "A3":  [841.89, 1190.55],
    "A4":  [595.28, 841.89],    "A5":  [419.53, 595.28],
    "A6":  [297.64, 419.53],    "A7":  [209.76, 297.64],
    "A8":  [147.40, 209.76],    "A9":  [104.88, 147.40],
    "LETTER": [612, 792],       "LEGAL": [612, 1008],
    "TABLOID": [792, 1224],     "LEDGER": [1224, 792],
    "GOVERNMENT-LETTER": [576, 756], "JUNIOR-LEGAL": [576, 360],
    "CREDIT-CARD": [153, 243]
  };
  let orient = (orientation &amp;&amp; orientation !== "" ? orientation : "portrait").toLowerCase();
  let sizeName = (paperSize &amp;&amp; paperSize !== "" ? paperSize : "A4").toUpperCase();
  let baseSize = SIZES[sizeName] ? [...SIZES[sizeName]] : [595.28, 841.89]; // Default A4
  let width, height;
  if (orient === "landscape") {
    width = Math.max(baseSize[0], baseSize[1]);
    height = Math.min(baseSize[0], baseSize[1]);
  } else {
    width = Math.min(baseSize[0], baseSize[1]);
    height = Math.max(baseSize[0], baseSize[1]);
  }

  // Si no se especifica orientación ni tamaño, copia última página
  const pages = doc.getPages();
  if (((!orientation || orientation === "") &amp;&amp; (!paperSize || paperSize === "")) &amp;&amp; pages.length &gt; 0) {
    const lastPageSize = pages[pages.length - 1].getSize();
    width = lastPageSize.width;
    height = lastPageSize.height;
  }

  doc.addPage([width, height]);
}
function neoPdf2MovePage(pdfName, fromPage, toPage, callback) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  const pages = doc.getPages();
  const n = pages.length;
  if (!n) return;

  let src = Number(fromPage) - 1;
  let dest = Number(toPage) - 1;
  if (src &lt; 0 || src &gt;= n || dest &lt; 0 || dest &gt;= n) return;

  // Build new page order
  const order = Array.from({length:n}, (_,i)=&gt;i);
  const moved = order.splice(src, 1)[0];
  order.splice(dest, 0, moved);

  // Rebuild PDF by copying pages in the new order (robust)
  PDFLib.PDFDocument.create().then(function(newDoc){
    newDoc.copyPages(doc, order).then(function(copied){
      copied.forEach(p =&gt; newDoc.addPage(p));
      $App.PDFLibDocs[pdfName] = newDoc;
      neoPdf2EnsureSettings(pdfName);
      if (typeof callback === "function") callback();
      else if (typeof callback === "string" &amp;&amp; typeof window[callback] === "function") window[callback]();
    });
  });
}
function neoPdf2InsertPage(pdfName, pageNumber) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  const pages = doc.getPages();
  let size = [595.28, 841.89]; // Default: A4
  const idx = Number(pageNumber) - 1;
  if (pages.length &gt; 0) {
    // Si el índice existe, tomamos el tamaño de esa página;
    // si no, del último.
    const refIdx = (idx &gt;= 0 &amp;&amp; idx &lt; pages.length) ? idx : (pages.length - 1);
    const refSize = pages[refIdx].getSize();
    size = [refSize.width, refSize.height];
  }
  doc.insertPage(idx, size);
}
function neoPdf2DeletePage(pdfName, pageNumber) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  const idx = Number(pageNumber) - 1;
  doc.removePage(idx);
}
async function neoPdf2CopyPage(pdfName, pageNumber) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  const idx = Number(pageNumber) - 1;
  const [copiedPage] = await doc.copyPages(doc, [idx]);
  doc.addPage(copiedPage);
}
function neoPdf2Render(pdfName, objId) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  doc.saveAsBase64({ dataUri: true }).then(function(dataUri) {
    var obj = document.getElementById(objId);
    if (obj) {
      // Si es un iframe o embed, mostramos el PDF
      obj.setAttribute("src", dataUri);
    }
  });
}
function neoPdf2RenderInNewTab(pdfName) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  doc.save().then(function(pdfBytes) {
    const blob = new Blob([pdfBytes], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    window.open(url, '_blank');
    setTimeout(function() { URL.revokeObjectURL(url); }, 10000);
  });
}

function neoPdf2GetProperties(pdfName, resVar) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) { $App[resVar] = ""; return; }
  const pages = doc.getPages();
  const pageCount = pages.length;
  let pageSize = { width: 0, height: 0 };
  if (pageCount &gt; 0) {
    pageSize = pages[0].getSize();
  }
  const metadata = doc.getTitle ? {
    title: doc.getTitle() || "",
    author: doc.getAuthor ? doc.getAuthor() : "",
    subject: doc.getSubject ? doc.getSubject() : "",
    keywords: doc.getKeywords ? doc.getKeywords() : ""
  } : {};
  $App[resVar] = JSON.stringify({
    pageCount: pageCount,
    width: pageSize.width,
    height: pageSize.height,
    ...metadata
  });
}
function neoPdf2Image(pdfName, imgObjId, imgFormat, posX, posY, imgWidth, imgHeight) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  const pages = doc.getPages();
  let page;
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if (pages.length === 0) {
    page = doc.addPage();
  } else {
    let pageIndex = settings &amp;&amp; settings.currentPage ? settings.currentPage : 1;
    if (pageIndex &lt; 1) pageIndex = 1;
    if (pageIndex &gt; pages.length) pageIndex = pages.length;
    page = pages[pageIndex - 1];
  }
  const pageSize = page.getSize();
  // mm -&gt; pt
  const x = Number(posX) * 2.83465;
  const y = pageSize.height - (Number(posY) * 2.83465) - (Number(imgHeight) * 2.83465);
  const w = Number(imgWidth) * 2.83465;
  const h = Number(imgHeight) * 2.83465;
  const imgElem = document.getElementById(imgObjId);
  if (!imgElem) return;
  // Convert image to base64 dataURL
  const canvas = document.createElement('canvas');
  canvas.width = imgElem.naturalWidth;
  canvas.height = imgElem.naturalHeight;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(imgElem, 0, 0);
  const dataUrl = canvas.toDataURL(
    imgFormat &amp;&amp; imgFormat.toUpperCase() === "PNG" ? "image/png" : "image/jpeg"
  );
  const imgType = imgFormat &amp;&amp; imgFormat.toUpperCase() === "PNG" ? "image/png" : "image/jpeg";
  fetch(dataUrl)
    .then(res =&gt; res.arrayBuffer())
    .then(buf =&gt; doc.embedJpg
      ? doc.embedJpg(new Uint8Array(buf))
      : doc.embedPng(new Uint8Array(buf))
    )
    .then(img =&gt; {
      page.drawImage(img, { x: x, y: y, width: w, height: h });
    });
}

function neoPdf2ImageBase64(pdfName, base64Data, imgFormat, posX, posY, imgWidth, imgHeight, callback) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  const pages = doc.getPages();
  let page;
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if (pages.length === 0) {
    page = doc.addPage();
  } else {
    let pageIndex = settings &amp;&amp; settings.currentPage ? settings.currentPage : 1;
    if (pageIndex &lt; 1) pageIndex = 1;
    if (pageIndex &gt; pages.length) pageIndex = pages.length;
    page = pages[pageIndex - 1];
  }
  const pageSize = page.getSize();
  // mm -&gt; pt
  const x = Number(posX) * 2.83465;
  const y = pageSize.height - (Number(posY) * 2.83465) - (Number(imgHeight) * 2.83465);
  const w = Number(imgWidth) * 2.83465;
  const h = Number(imgHeight) * 2.83465;
  let prefix = "";
  if (imgFormat &amp;&amp; imgFormat.toUpperCase() === "PNG") {
    prefix = "data:image/png;base64,";
  } else {
    prefix = "data:image/jpeg;base64,";
  }
  // Elimina cualquier prefijo ya existente para evitar duplicidad
  let base64 = base64Data.replace(/^data:image\/(png|jpeg|jpg);base64,/, "");
  const dataUrl = prefix + base64;
  fetch(dataUrl)
    .then(res =&gt; res.arrayBuffer())
    .then(buf =&gt; imgFormat &amp;&amp; imgFormat.toUpperCase() === "PNG"
      ? doc.embedPng(new Uint8Array(buf))
      : doc.embedJpg(new Uint8Array(buf))
    )
    .then(img =&gt; {
      page.drawImage(img, { x: x, y: y, width: w, height: h });
      if (typeof callback === "function") callback();
      else if (typeof callback === "string" &amp;&amp; typeof window[callback] === "function") window[callback]();
    });
}
function neoPdf2SetProperties(pdfName, pdfTitle, pdfSubject, pdfAuthor, pdfKeywords) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  // pdf-lib expone métodos setTitle, setAuthor, setSubject, setKeywords
  if (typeof doc.setTitle === "function")    doc.setTitle(pdfTitle || "");
  if (typeof doc.setAuthor === "function")   doc.setAuthor(pdfAuthor || "");
  if (typeof doc.setSubject === "function")  doc.setSubject(pdfSubject || "");
  if (typeof doc.setKeywords === "function") doc.setKeywords(pdfKeywords || "");
}
function neoPdf2DrawColor(pdfName, r, g, b) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].drawColor = PDFLib.rgb((r || 0) / 255, (g || 0) / 255, (b || 0) / 255);
}

function neoPdf2FillColor(pdfName, r, g, b) {
  neoPdf2EnsureSettings(pdfName);
  $App.PDFLibDocs[pdfName + "_settings"].fillColor = PDFLib.rgb((r || 0) / 255, (g || 0) / 255, (b || 0) / 255);
}

function neoPdf2LineWidth(pdfName, lineWidth) {
  neoPdf2EnsureSettings(pdfName);
  // pdf-lib drawLine usa width en puntos
  $App.PDFLibDocs[pdfName + "_settings"].lineWidth = Number(lineWidth) || 1;
}
// LINEA
function neoPdf2Line(pdfName, x1, y1, x2, y2) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if (!doc || !settings) return;
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const size = page.getSize();
  const x1pt = Number(x1) * 2.83465;
  const y1pt = size.height - (Number(y1) * 2.83465);
  const x2pt = Number(x2) * 2.83465;
  const y2pt = size.height - (Number(y2) * 2.83465);
  page.drawLine({
    start: { x: x1pt, y: y1pt },
    end: { x: x2pt, y: y2pt },
    thickness: settings.lineWidth || 1,
    color: settings.drawColor || PDFLib.rgb(0,0,0)
  });
}

// RECTÁNGULO
function neoPdf2Rect(pdfName, x, y, width, height, style) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if (!doc || !settings) return;
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const size = page.getSize();
  const xpt = Number(x) * 2.83465;
  const ypt = size.height - (Number(y) * 2.83465) - (Number(height) * 2.83465);
  const wpt = Number(width) * 2.83465;
  const hpt = Number(height) * 2.83465;
  if (style === "fill") {
    page.drawRectangle({
      x: xpt, y: ypt, width: wpt, height: hpt,
      color: settings.fillColor || PDFLib.rgb(0,0,0)
    });
  } else if (style === "stroke") {
    page.drawRectangle({
      x: xpt, y: ypt, width: wpt, height: hpt,
      borderColor: settings.drawColor || PDFLib.rgb(0,0,0),
      borderWidth: settings.lineWidth || 1
    });
  } else { // fillThenStroke
    page.drawRectangle({
      x: xpt, y: ypt, width: wpt, height: hpt,
      color: settings.fillColor || PDFLib.rgb(0,0,0),
      borderColor: settings.drawColor || PDFLib.rgb(0,0,0),
      borderWidth: settings.lineWidth || 1
    });
  }
}

// CÍRCULO
function neoPdf2Circle(pdfName, x, y, radius, style) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if (!doc || !settings) return;
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const size = page.getSize();
  const centerX = Number(x) * 2.83465;
  const centerY = size.height - (Number(y) * 2.83465);
  const r = Number(radius) * 2.83465;
  if (style === "fill") {
    page.drawEllipse({
      x: centerX, y: centerY,
      xScale: r, yScale: r,
      color: settings.fillColor || PDFLib.rgb(0,0,0)
    });
  } else if (style === "stroke") {
    page.drawEllipse({
      x: centerX, y: centerY,
      xScale: r, yScale: r,
      borderColor: settings.drawColor || PDFLib.rgb(0,0,0),
      borderWidth: settings.lineWidth || 1
    });
  } else { // fillThenStroke
    page.drawEllipse({
      x: centerX, y: centerY,
      xScale: r, yScale: r,
      color: settings.fillColor || PDFLib.rgb(0,0,0),
      borderColor: settings.drawColor || PDFLib.rgb(0,0,0),
      borderWidth: settings.lineWidth || 1
    });
  }
}

// ELIPSE
function neoPdf2Ellipse(pdfName, x, y, rx, ry, style) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if (!doc || !settings) return;
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const size = page.getSize();
  const centerX = Number(x) * 2.83465;
  const centerY = size.height - (Number(y) * 2.83465);
  const rxPt = Number(rx) * 2.83465;
  const ryPt = Number(ry) * 2.83465;
  if (style === "fill") {
    page.drawEllipse({
      x: centerX, y: centerY,
      xScale: rxPt, yScale: ryPt,
      color: settings.fillColor || PDFLib.rgb(0,0,0)
    });
  } else if (style === "stroke") {
    page.drawEllipse({
      x: centerX, y: centerY,
      xScale: rxPt, yScale: ryPt,
      borderColor: settings.drawColor || PDFLib.rgb(0,0,0),
      borderWidth: settings.lineWidth || 1
    });
  } else { // fillThenStroke
    page.drawEllipse({
      x: centerX, y: centerY,
      xScale: rxPt, yScale: ryPt,
      color: settings.fillColor || PDFLib.rgb(0,0,0),
      borderColor: settings.drawColor || PDFLib.rgb(0,0,0),
      borderWidth: settings.lineWidth || 1
    });
  }
}

// TRIÁNGULO
function neoPdf2Triangle(pdfName, x1, y1, x2, y2, x3, y3, style) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if (!doc || !settings) return;
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const size = page.getSize();
  const pt = v =&gt; [Number(v[0]) * 2.83465, size.height - (Number(v[1]) * 2.83465)];
  const p1 = pt([x1, y1]), p2 = pt([x2, y2]), p3 = pt([x3, y3]);
  const colorOpt = (style === "fill" || style === "fillThenStroke") ? { color: settings.fillColor || PDFLib.rgb(0,0,0) } : {};
  const borderOpt = (style === "stroke" || style === "fillThenStroke") ? {
    borderColor: settings.drawColor || PDFLib.rgb(0,0,0),
    borderWidth: settings.lineWidth || 1
  } : {};
  page.drawPolygon([p1, p2, p3], { ...colorOpt, ...borderOpt });
}
function neoPdf2GetPageCount(pdfName, resVar) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) { $App[resVar] = 0; return; }
  const pages = doc.getPages();
  $App[resVar] = pages.length;
}
// Enlace de texto a URL externa
function neoPdf2TextLink(pdfName, textString, posX, posY, url) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if(!doc || !settings){return;}
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const pageSize = page.getSize();
  // mm -&gt; pt
  const x = Number(posX) * 2.83465;
  const y = pageSize.height - (Number(posY) * 2.83465);
  const fontName = settings.fontName || "Helvetica";
  const fontStyle = settings.fontStyle || "Normal";
  let pdfLibFont;
  switch(fontName){
    case "Helvetica":
      if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.HelveticaBold;
      else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.HelveticaOblique;
      else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.HelveticaBoldOblique;
      else pdfLibFont = PDFLib.StandardFonts.Helvetica;
      break;
    case "TimesRoman":
      if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.TimesBold;
      else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.TimesItalic;
      else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.TimesBoldItalic;
      else pdfLibFont = PDFLib.StandardFonts.TimesRoman;
      break;
    case "Courier":
      if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.CourierBold;
      else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.CourierOblique;
      else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.CourierBoldOblique;
      else pdfLibFont = PDFLib.StandardFonts.Courier;
      break;
    case "Symbol":
      pdfLibFont = PDFLib.StandardFonts.Symbol; break;
    case "ZapfDingbats":
      pdfLibFont = PDFLib.StandardFonts.ZapfDingbats; break;
    default:
      pdfLibFont = PDFLib.StandardFonts.Helvetica; break;
  }
  doc.embedFont(pdfLibFont).then(function(font) {
    const size = settings.fontSize || 12;
    const color = settings.color || PDFLib.rgb(0,0,1); // azul por defecto para links
    const textWidth = font.widthOfTextAtSize(textString, size);
    const textHeight = size;
    page.drawText(textString, {
      x: x, y: y, size: size, font: font, color: color, underline: true
    });
    page.annotate({
      type: 'link',
      rect: [x, y, x + textWidth, y + textHeight],
      url: url
    });
  });
}

// Enlace a página interna (pageNumber es 1-based)
function neoPdf2TextLinkToPage(pdfName, textString, posX, posY, pageNumber) {
  neoPdf2EnsureSettings(pdfName);
  const doc = $App.PDFLibDocs[pdfName];
  const settings = $App.PDFLibDocs[pdfName + "_settings"];
  if(!doc || !settings){return;}
  const pages = doc.getPages();
  let pageIndex = settings.currentPage ? settings.currentPage : 1;
  if (pageIndex &lt; 1) pageIndex = 1;
  if (pageIndex &gt; pages.length) pageIndex = pages.length;
  const page = pages[pageIndex - 1];
  const pageSize = page.getSize();
  const x = Number(posX) * 2.83465;
  const y = pageSize.height - (Number(posY) * 2.83465);
  const fontName = settings.fontName || "Helvetica";
  const fontStyle = settings.fontStyle || "Normal";
  let pdfLibFont;
  switch(fontName){
    case "Helvetica":
      if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.HelveticaBold;
      else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.HelveticaOblique;
      else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.HelveticaBoldOblique;
      else pdfLibFont = PDFLib.StandardFonts.Helvetica;
      break;
    case "TimesRoman":
      if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.TimesBold;
      else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.TimesItalic;
      else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.TimesBoldItalic;
      else pdfLibFont = PDFLib.StandardFonts.TimesRoman;
      break;
    case "Courier":
      if(fontStyle === "Bold") pdfLibFont = PDFLib.StandardFonts.CourierBold;
      else if(fontStyle === "Italic") pdfLibFont = PDFLib.StandardFonts.CourierOblique;
      else if(fontStyle === "BoldItalic") pdfLibFont = PDFLib.StandardFonts.CourierBoldOblique;
      else pdfLibFont = PDFLib.StandardFonts.Courier;
      break;
    case "Symbol":
      pdfLibFont = PDFLib.StandardFonts.Symbol; break;
    case "ZapfDingbats":
      pdfLibFont = PDFLib.StandardFonts.ZapfDingbats; break;
    default:
      pdfLibFont = PDFLib.StandardFonts.Helvetica; break;
  }
  doc.embedFont(pdfLibFont).then(function(font) {
    const size = settings.fontSize || 12;
    const color = settings.color || PDFLib.rgb(0,0,1); // azul por defecto para links
    const textWidth = font.widthOfTextAtSize(textString, size);
    const textHeight = size;
    page.drawText(textString, {
      x: x, y: y, size: size, font: font, color: color, underline: true
    });
    page.annotate({
      type: 'link',
      rect: [x, y, x + textWidth, y + textHeight],
      page: Number(pageNumber) - 1 // pdf-lib es zero-based
    });
  });
}
function neoPdf2AddCustomFont(pdfName, fontVarName, fontBase64, callback) {
  const doc = $App.PDFLibDocs[pdfName];
  if (!doc) return;
  // Decodifica el base64 a Uint8Array
  const fontBytes = Uint8Array.from(atob(fontBase64), c =&gt; c.charCodeAt(0));
  doc.embedFont(fontBytes).then(font =&gt; {
    // Guardamos el objeto font en los settings del doc, por nombre
    neoPdf2EnsureSettings(pdfName);
    if (!$App.PDFLibDocs[pdfName + "_settings"].customFonts) {
      $App.PDFLibDocs[pdfName + "_settings"].customFonts = {};
    }
    $App.PDFLibDocs[pdfName + "_settings"].customFonts[fontVarName] = font;
    if (callback &amp;&amp; typeof window[callback] === "function") window[callback]();
  });
}



  </Code>
  <Files>
    <File>pdf-lib.min.js</File>
  </Files>
  <ActionList>
    <Action Name="neoPdf2Init">
      <Hint>Create a new empty PDF document (pdf-lib).</Hint>
      <Params>pdfName|STRING|PDF name (letters only, no spaces);callback|FUNCTION|Callback subroutine (optional)</Params>
      <Template>neoPdf2Init(pdfName, callback);</Template>
    </Action>
    <Action Name="neoPdf2LoadFromBase64">
      <Hint>Load an existing PDF (Base64 string) into a pdf-lib document.</Hint>
      <Params>base64String|STRING|Base64 encoded PDF (may include DataURI header);pdfName|STRING|PDF name for reference;callback|FUNCTION|Callback subroutine (optional)</Params>
      <Template>neoPdf2LoadFromBase64(base64String,pdfName,callback);</Template>
    </Action>
    <Action Name="neoPdf2GetProperties">
      <Hint>Get basic properties of the PDF document (page count, first page size, metadata) as a JSON string in a variable.</Hint>
      <Params>pdfName|STRING|PDF name;resVar|VARNAME|Variable to store the result (JSON string)</Params>
      <Template>neoPdf2GetProperties(pdfName,resVar);</Template>
    </Action>
    <Action Name="neoPdf2SetProperties">
      <Hint>Set the PDF document metadata: title, subject, author, and keywords.</Hint>
      <Params>pdfName|STRING|PDF name;pdfTitle|STRING|Document title;pdfSubject|STRING|Document subject;pdfAuthor|STRING|Document author;pdfKeywords|STRING|Document keywords (comma separated)</Params>
      <Template>neoPdf2SetProperties(pdfName,pdfTitle,pdfSubject,pdfAuthor,pdfKeywords);</Template>
    </Action>
    <Action Name="neoPdf2SimpleText">
      <Hint>Adds text to the selected page using the current style settings.</Hint>
      <Params>pdfName|STRING|PDF name;textString|STRING|Text to write;posX|FLOAT|X coordinate (in millimeters);posY|FLOAT|Y coordinate (in millimeters);textAlign|ENUMLIST=left,center,right|Text alignment</Params>
      <Template>neoPdf2SimpleText(pdfName,textString,posX,posY,textAlign);</Template>
    </Action>
    <Action Name="neoPdf2Text">
      <Hint>Adds text to page with full options (alignment, rotation, wrapping, rendering mode).</Hint>
      <Params>pdfName|STRING|PDF name;textString|STRING|Text to write;posX|FLOAT|X coordinate (in millimeters);posY|FLOAT|Y coordinate (in millimeters);textAlign|ENUMLIST=left,center,right|Text align;textAngle|FLOAT|Angle in degrees (counterclockwise);maxWidth|FLOAT|Max width in mm (0=no wrap);renderingMode|ENUMLIST=fill,stroke,fillThenStroke|Rendering mode</Params>
      <Template>neoPdf2Text(pdfName,textString,posX,posY,textAlign,textAngle,maxWidth,renderingMode);</Template>
    </Action>
    <Action Name="neoPdf2TextLink">
      <Hint>Add a clickable text link to an external URL.</Hint>
      <Params>pdfName|STRING|PDF name;textString|STRING|Text to write;posX|FLOAT|X coordinate (mm);posY|FLOAT|Y coordinate (mm);url|STRING|URL to link (e.g., https://google.com)</Params>
      <Template>neoPdf2TextLink(pdfName,textString,posX,posY,url);</Template>
    </Action>
    <Action Name="neoPdf2TextLinkToPage">
      <Hint>Add a clickable text link to another page in the PDF.</Hint>
      <Params>pdfName|STRING|PDF name;textString|STRING|Text to write;posX|FLOAT|X coordinate (mm);posY|FLOAT|Y coordinate (mm);pageNumber|INTEGER|Page number to link (1-based)</Params>
      <Template>neoPdf2TextLinkToPage(pdfName,textString,posX,posY,pageNumber);</Template>
    </Action>
    <Action Name="neoPdf2AddCustomFont">
      <Hint>Embed a custom font (TTF/OTF as base64) in the PDF document and assign it a reference name for later use.</Hint>
      <Params>pdfName|STRING|PDF name;fontVarName|STRING|Reference name for the font;fontBase64|HTML|TTF or OTF font file as base64 string;callback|FUNCTION|Callback subroutine (optional)</Params>
      <Template>neoPdf2AddCustomFont(pdfName,fontVarName,fontBase64,callback);</Template>
    </Action>
    <Action Name="neoPdf2Render">
      <Hint>Render the PDF document into the specified IFrame or embed object.</Hint>
      <Params>pdfName|STRING|PDF name;objId|OBJID|IFrame or embed object ID to render the PDF</Params>
      <Template>neoPdf2Render(pdfName,objId);</Template>
    </Action>
    <Action Name="neoPdf2RenderInNewTab">
      <Hint>Render the PDF document by opening it in a new browser tab.</Hint>
      <Params>pdfName|STRING|PDF name</Params>
      <Template>neoPdf2RenderInNewTab(pdfName);</Template>
    </Action>
    <Action Name="neoPdf2SaveToBase64">
      <Hint>Save the document as a Base64 DataURI and store it in a variable (async ~0.5s).</Hint>
      <Params>pdfName|STRING|PDF name;resVar|VARNAME|Variable to store result</Params>
      <Template>neoPdf2SaveToBase64(pdfName,resVar);</Template>
    </Action>
    <Action Name="neoPdf2Download">
      <Hint>Download the PDF document.</Hint>
      <Params>pdfName|STRING|PDF name;fileName|STRING|File name without extension (optional)</Params>
      <Template>neoPdf2Download(pdfName,fileName);</Template>
    </Action>
    <Action Name="neoPdf2Image">
      <Hint>Add an image from an IMG element in the document.</Hint>
      <Params>pdfName|STRING|PDF name;imgObjId|OBJID|Image object ID;imgFormat|ENUMLIST=JPEG,PNG|Image format;posX|FLOAT|X coordinate (mm);posY|FLOAT|Y coordinate (mm);imgWidth|FLOAT|Image width (mm);imgHeight|FLOAT|Image height (mm)</Params>
      <Template>neoPdf2Image(pdfName,imgObjId,imgFormat,posX,posY,imgWidth,imgHeight);</Template>
    </Action>
    <Action Name="neoPdf2ImageBase64">
      <Hint>Add an image from a Base64 encoded string.</Hint>
      <Params>pdfName|STRING|PDF name;base64Data|HTML|Base64 image data;imgFormat|ENUMLIST=JPEG,PNG|Image format;posX|FLOAT|X coordinate (mm);posY|FLOAT|Y coordinate (mm);imgWidth|FLOAT|Image width (mm);imgHeight|FLOAT|Image height (mm)</Params>
      <Template>neoPdf2ImageBase64(pdfName,base64Data,imgFormat,posX,posY,imgWidth,imgHeight);</Template>
    </Action>
    <Action Name="neoPdf2DrawColor">
      <Hint>Set the stroke color for shapes (RGB).</Hint>
      <Params>pdfName|STRING|PDF name;r|INTEGER|Red (0-255);g|INTEGER|Green (0-255);b|INTEGER|Blue (0-255)</Params>
      <Template>neoPdf2DrawColor(pdfName,r,g,b);</Template>
    </Action>
    <Action Name="neoPdf2FillColor">
      <Hint>Set the fill color for shapes (RGB).</Hint>
      <Params>pdfName|STRING|PDF name;r|INTEGER|Red (0-255);g|INTEGER|Green (0-255);b|INTEGER|Blue (0-255)</Params>
      <Template>neoPdf2FillColor(pdfName,r,g,b);</Template>
    </Action>
    <Action Name="neoPdf2LineWidth">
      <Hint>Set the line width (in points) for shapes.</Hint>
      <Params>pdfName|STRING|PDF name;lineWidth|FLOAT|Line width (pt)</Params>
      <Template>neoPdf2LineWidth(pdfName,lineWidth);</Template>
    </Action>
    <Action Name="neoPdf2Line">
      <Hint>Draw a line using the current stroke color and line width.</Hint>
      <Params>pdfName|STRING|PDF name;x1|FLOAT|Start X (mm);y1|FLOAT|Start Y (mm);x2|FLOAT|End X (mm);y2|FLOAT|End Y (mm)</Params>
      <Template>neoPdf2Line(pdfName,x1,y1,x2,y2);</Template>
    </Action>
    <Action Name="neoPdf2Rect">
      <Hint>Draw a rectangle with the current fill and/or stroke color.</Hint>
      <Params>pdfName|STRING|PDF name;x|FLOAT|Top-left X (mm);y|FLOAT|Top-left Y (mm);width|FLOAT|Width (mm);height|FLOAT|Height (mm);style|ENUMLIST=stroke,fill,fillThenStroke|Rectangle style</Params>
      <Template>neoPdf2Rect(pdfName,x,y,width,height,style);</Template>
    </Action>
    <Action Name="neoPdf2Circle">
      <Hint>Draw a circle with the current fill and/or stroke color.</Hint>
      <Params>pdfName|STRING|PDF name;x|FLOAT|Center X (mm);y|FLOAT|Center Y (mm);radius|FLOAT|Radius (mm);style|ENUMLIST=stroke,fill,fillThenStroke|Circle style</Params>
      <Template>neoPdf2Circle(pdfName,x,y,radius,style);</Template>
    </Action>
    <Action Name="neoPdf2Ellipse">
      <Hint>Draw an ellipse with the current fill and/or stroke color.</Hint>
      <Params>pdfName|STRING|PDF name;x|FLOAT|Center X (mm);y|FLOAT|Center Y (mm);rx|FLOAT|X radius (mm);ry|FLOAT|Y radius (mm);style|ENUMLIST=stroke,fill,fillThenStroke|Ellipse style</Params>
      <Template>neoPdf2Ellipse(pdfName,x,y,rx,ry,style);</Template>
    </Action>
    <Action Name="neoPdf2Triangle">
      <Hint>Draw a triangle with the current fill and/or stroke color.</Hint>
      <Params>pdfName|STRING|PDF name;x1|FLOAT|First X (mm);y1|FLOAT|First Y (mm);x2|FLOAT|Second X (mm);y2|FLOAT|Second Y (mm);x3|FLOAT|Third X (mm);y3|FLOAT|Third Y (mm);style|ENUMLIST=stroke,fill,fillThenStroke|Triangle style</Params>
      <Template>neoPdf2Triangle(pdfName,x1,y1,x2,y2,x3,y3,style);</Template>
    </Action>
    <Action Name="neoPdf2Font">
      <Hint>Set the font family (Helvetica, TimesRoman, Courier, etc.).</Hint>
      <Params>pdfName|STRING|PDF name;fontName|ENUMLIST=Helvetica,TimesRoman,Courier,Symbol,ZapfDingbats|Font family</Params>
      <Template>neoPdf2Font(pdfName,fontName);</Template>
    </Action>
    <Action Name="neoPdf2FontStyle">
      <Hint>Set the font style (Normal, Bold, Italic, BoldItalic).</Hint>
      <Params>pdfName|STRING|PDF name;fontStyle|ENUMLIST=Normal,Bold,Italic,BoldItalic|Font style</Params>
      <Template>neoPdf2FontStyle(pdfName,fontStyle);</Template>
    </Action>
    <Action Name="neoPdf2FontSize">
      <Hint>Set the font size (in points).</Hint>
      <Params>pdfName|STRING|PDF name;fontSize|FLOAT|Font size (pt)</Params>
      <Template>neoPdf2FontSize(pdfName,fontSize);</Template>
    </Action>
    <Action Name="neoPdf2TextColor">
      <Hint>Set the text color (RGB).</Hint>
      <Params>pdfName|STRING|PDF name;r|INTEGER|Red (0-255);g|INTEGER|Green (0-255);b|INTEGER|Blue (0-255)</Params>
      <Template>neoPdf2TextColor(pdfName,r,g,b);</Template>
    </Action>
    <Action Name="neoPdf2SetPage">
      <Hint>Select the current page for subsequent operations (1 = first page).</Hint>
      <Params>pdfName|STRING|PDF name;pageNumber|INTEGER|Page number (1 = first)</Params>
      <Template>neoPdf2SetPage(pdfName,pageNumber);</Template>
    </Action>
    <Action Name="neoPdf2AddPage">
      <Hint>Add a new page to the PDF document. If orientation and paperSize are omitted, uses the last page size or A4 portrait.</Hint>
      <Params>pdfName|STRING|PDF name;orientation|ENUMLIST=portrait,landscape|Orientation of the page (optional);paperSize|ENUMLIST=A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,LETTER,GOVERNMENT-LETTER,LEGAL,JUNIOR-LEGAL,LEDGER,TABLOID,CREDIT-CARD|Paper size (optional)</Params>
      <Template>neoPdf2AddPage(pdfName,orientation,paperSize);</Template>
    </Action>
    <Action Name="neoPdf2MovePage">
      <Hint>Move a page to a new position within the document.</Hint>
      <Params>pdfName|STRING|PDF name;fromPage|INTEGER|Page to move (1=first);toPage|INTEGER|New position (1=first)</Params>
      <Template>neoPdf2MovePage(pdfName,fromPage,toPage);</Template>
    </Action>
    <Action Name="neoPdf2InsertPage">
      <Hint>Insert a blank page before the specified page number.</Hint>
      <Params>pdfName|STRING|PDF name;pageNumber|INTEGER|Insert before this page (1=first)</Params>
      <Template>neoPdf2InsertPage(pdfName,pageNumber);</Template>
    </Action>
    <Action Name="neoPdf2DeletePage">
      <Hint>Delete the specified page</Hint>
      <Params>pdfName|STRING|PDF name;pageNumber|INTEGER|Page to delete (1=first)</Params>
      <Template>neoPdf2DeletePage(pdfName,pageNumber);</Template>
    </Action>
    <Action Name="neoPdf2CopyPage">
      <Hint>Duplicate the specified page and insert it at the end of the document.</Hint>
      <Params>pdfName|STRING|PDF name;pageNumber|INTEGER|Page to copy (1=first)</Params>
      <Template>neoPdf2CopyPage(pdfName,pageNumber);</Template>
    </Action>
    <Action Name="neoPdf2GetPageCount">
      <Hint>Get the current number of pages in the PDF and store it in a variable.</Hint>
      <Params>pdfName|STRING|PDF name;resVar|VARNAME|Variable to store the result</Params>
      <Template>neoPdf2GetPageCount(pdfName,resVar);</Template>
    </Action>
  </ActionList>
</NeoAppBuilderPlugIn>
