Order numbers, tracking numbers, and asset IDs are easy to mistype when someone enters them by hand. Printing them as barcodes on the document lets a scanner read them in one pass and brings the error rate down. Getting a barcode onto a PDF from a web app used to mean generating a barcode image on the server and compositing it back into the document, which sends the file on a round trip.

Spire.PDF for JavaScript creates, modifies, and saves PDF documents in the browser through WebAssembly. Barcodes are drawn straight onto the page by 1D barcode objects such as PdfCode128BBarcode and PdfCode39Barcode, and files are read and written through a virtual file system (VFS), so the document never has to be uploaded to a server.

This article covers two features:

For installation and project configuration, refer to Integrating Spire.PDF for JavaScript in a React Project. The following examples assume Spire.PDF is installed and the WebAssembly module has been initialized.


Supported barcode types

The ten 1D barcode classes all derive from PdfBarcode; they differ in the characters they accept and in how densely they encode, so use the table below to pick one:

Class Symbology Characters supported
PdfCodabarBarcode Codabar Digits and - $ : / . +
PdfCode11Barcode Code 11 Digits and -
PdfCode32Barcode Code 32 Digits, common in pharmaceutical codes
PdfCode39Barcode Code 39 Digits, uppercase letters, space and - . $ / + %
PdfCode39ExtendedBarcode Code 39 Extended Full ASCII, represented by multi-character combinations
PdfCode93Barcode Code 93 Same character set as Code 39, higher density
PdfCode93ExtendedBarcode Code 93 Extended Full ASCII
PdfCode128ABarcode Code 128A Digits, uppercase letters, symbols and control characters
PdfCode128BBarcode Code 128B Digits, upper- and lowercase letters and symbols
PdfCode128CBarcode Code 128C Digits in pairs, the highest density
function App() {
  const drawBarcodeTypes = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the font file into the VFS for the type name above each barcode
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create the PDF document and add a page
    const doc = new pdfModule.PdfDocument();
    const page = doc.Pages.Add();

    // Font, color, and alignment for the type names; green sets them apart from the black bars
    const labelFont = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL.TTF', size: 12 });
    const labelBrush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Green() }) });
    const labelFormat = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Left });

    // The data to encode, the barcode class to use for each, and the type name shown above it
    const items = [
      { Barcode: pdfModule.PdfCodabarBarcode, label: 'Codabar', text: '00:12-3456/7890' },
      { Barcode: pdfModule.PdfCode11Barcode, label: 'Code 11', text: '123-4567890' },
      { Barcode: pdfModule.PdfCode32Barcode, label: 'Code 32', text: '16273849' },
      { Barcode: pdfModule.PdfCode39Barcode, label: 'Code 39', text: 'ORDER-2026-0007' },
      { Barcode: pdfModule.PdfCode39ExtendedBarcode, label: 'Code 39 Extended', text: 'Order 2026-0007' },
      { Barcode: pdfModule.PdfCode93Barcode, label: 'Code 93', text: 'ORDER-2026-0007' },
      { Barcode: pdfModule.PdfCode93ExtendedBarcode, label: 'Code 93 Extended', text: 'Order 2026-0007' },
      { Barcode: pdfModule.PdfCode128ABarcode, label: 'Code 128A', text: 'INVOICE 2026' },
      { Barcode: pdfModule.PdfCode128BBarcode, label: 'Code 128B', text: 'Order-2026-0001' },
      { Barcode: pdfModule.PdfCode128CBarcode, label: 'Code 128C', text: '20260001' },
    ];

    // Draw from the top of the page, one below the other
    let y = 20;
    for (const item of items) {
      // Label the barcode above it with the symbology it uses
      page.Canvas.DrawString({ s: `${item.label}:`, font: labelFont, brush: labelBrush, x: 20, y: y, format: labelFormat });

      // Create the barcode object from the text to encode
      const barcode = new item.Barcode({ text: item.text });

      // Show the readable text below the barcode
      barcode.TextDisplayLocation = pdfModule.TextLocation.Bottom;

      // Narrow the bars so all ten barcodes fit on one page
      barcode.BarHeight = 25;

      // Draw below the type name; this coordinate is the top-left corner of the barcode
      barcode.Draw({ page: page, location: new pdfModule.PointF(20, y + labelFont.Size + 4) });

      // Use the bottom edge of the area actually occupied to place the next one
      y = barcode.Bounds.Bottom + 10;
    }

    // Save the document
    const outputFileName = 'BarcodeTypes_result.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Drawing Multiple Barcode Types</h1>
      <button onClick={drawBarcodeTypes}>
        Draw Barcodes
      </button>
    </div>
  );
}

export default App;

Ten barcode types on one page, each labeled above with its symbology and below with the readable text

Ten barcode types on one page, each labeled above with its symbology and below with the readable text


Customizing the barcode appearance

The default look is black bars on white with the text underneath, and each part of it can be overridden, from the colors and the bar height to the quiet zone and the label font. BarColor and TextColor color the bars and the text separately, BackColor fills the area behind the barcode, BarHeight, NarrowBarWidth, and BarcodeToTextGapHeight control the dimensions, and QuietZone is the blank space the barcode needs around it.

function App() {
  const customizeBarcode = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the font file into the VFS for the barcode label
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create the PDF document and add a page
    const doc = new pdfModule.PdfDocument();
    const page = doc.Pages.Add();

    // The first barcode keeps the default look, for comparison
    const plain = new pdfModule.PdfCode39Barcode({ text: 'ORDER-2026-0007' });
    plain.TextDisplayLocation = pdfModule.TextLocation.Bottom;
    plain.Draw({ page: page, location: new pdfModule.PointF(20, 20) });

    // The second one has each property set explicitly
    const barcode = new pdfModule.PdfCode39Barcode({ text: 'ORDER-2026-0007' });
    barcode.TextDisplayLocation = pdfModule.TextLocation.Bottom;

    // Set the bars, the text, and the background separately
    barcode.BarColor = new pdfModule.PdfRGBColor({ color: pdfModule.Color.FromArgb(0, 60, 160) });
    barcode.TextColor = new pdfModule.PdfRGBColor({ color: pdfModule.Color.FromArgb(200, 0, 0) });
    barcode.BackColor = new pdfModule.PdfRGBColor({ color: pdfModule.Color.FromArgb(255, 255, 0) });

    // Bar height, narrow bar width, and the gap between the bars and the text
    barcode.BarHeight = 45;
    barcode.NarrowBarWidth = 1.6;
    barcode.BarcodeToTextGapHeight = 4;

    // Center the text and leave 5 points of blank space on each side
    barcode.TextAlignment = pdfModule.PdfBarcodeTextAlignment.Center;
    barcode.QuietZone.Top = 5;
    barcode.QuietZone.Bottom = 5;
    barcode.QuietZone.Left = 5;
    barcode.QuietZone.Right = 5;

    // Switch the label to 12-point Arial
    barcode.Font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL.TTF', size: 12 });

    // Place it below the first barcode
    barcode.Draw({ page: page, location: new pdfModule.PointF(20, plain.Bounds.Bottom + 30) });

    // Save the document
    const outputFileName = 'CustomBarcodeStyle_result.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Customizing the Barcode Style</h1>
      <button onClick={customizeBarcode}>
        Apply Styling
      </button>
    </div>
  );
}

export default App;

The lower barcode uses different bar colors, bar height, quiet zone, and label font

The lower barcode uses different bar colors, bar height, quiet zone, and label font


FAQ

PdfRGBColor reports "Ambiguous call" when it is constructed

Cause: the constructor of PdfRGBColor only accepts a Color object. Passing the three RGB components directly, as in new pdfModule.PdfRGBColor(0, 60, 160) or { r: 0, g: 60, b: 160 }, matches several overloads and throws Ambiguous call: arguments (object) match multiple overloads.

Solution: build a Color with Color.FromArgb first, then hand it to PdfRGBColor:

// Correct: produce a Color object first
barcode.BarColor = new pdfModule.PdfRGBColor({ color: pdfModule.Color.FromArgb(0, 60, 160) });

// A predefined color works too
barcode.TextColor = new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Blue() });

The same data produces barcodes of different lengths

Cause: symbologies encode at different densities, so the same text comes out shorter with Code128 than with Code39, and shorter still with Code128C. Raising NarrowBarWidth widens the whole barcode as well.

Solution: when the layout has to be fixed, use the Draw overload that takes a RectangleF to confine the barcode to an area, or adjust NarrowBarWidth across the board:

// However long the data is, the barcode stays inside the same rectangle
barcode.Draw({ page: page, rect: new pdfModule.RectangleF(20, 20, 250, 80) });

Can I add a QR code to a PDF

Cause: the 1D barcode classes Spire.PDF for JavaScript ships are PdfCodabarBarcode, PdfCode39Barcode, PdfCode128BBarcode, and the like. There is no QR code class, so a name such as PdfQRCodeBarcode evaluates to undefined.

Solution: generate the QR code as an image in the front end, then draw it into the page with PdfImage.FromStream and page.Canvas.DrawImage, leaving positioning and scaling to the canvas:

// qrBytes holds the PNG bytes of a QR code generated in the front end
const image = pdfModule.PdfImage.FromStream(new pdfModule.Stream(qrBytes));
page.Canvas.DrawImage({ image: image, x: 20, y: 20, width: 100, height: 100 });

Get a Free License

If you want to remove the evaluation message from the resulting documents, or to get rid of the feature limitations, please contact our sales team to obtain a temporary license valid for 30 days.

Some of the PDFs users upload are password protected. If the code processes them without checking first, it either throws and stops, or writes out an incomplete result. Checking at the entry point is cheaper than debugging afterwards: is the document encrypted, which password does it need, and are the candidate passwords in hand correct?

Spire.PDF for JavaScript reads PDF documents in the browser through WebAssembly and manages input files in a virtual file system (VFS), so no backend is involved. PdfDocument.IsPasswordProtected() is a static method: it reads the file directly, without opening the document and without a password. Once a document is loaded, PdfDocument.Security records whether the password used this time is in UserPassword or OwnerPassword, which identifies the role of that password.

This article covers three features:

For installation and project configuration, refer to Integrating Spire.PDF for JavaScript in a React Project. The following examples assume Spire.PDF is installed and the WebAssembly module has been initialized.


Checking whether a PDF is password protected

PdfDocument.IsPasswordProtected() takes a file name in the virtual file system and returns a boolean. It checks whether the document carries an encryption dictionary, so it needs neither an opened document nor a password. It reads a path inside the virtual file system, so window.spire.FetchFileToVFS() must run first; otherwise it throws File doesn't exist.

function App() {
  const checkPasswordProtection = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // File names to check
    const plainFileName = 'ContractTemplate.pdf';
    const lockedFileName = 'EncryptedContract.pdf';

    // The static method reads the virtual file system, so load the files first
    await window.spire.FetchFileToVFS(plainFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(lockedFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Check each file without opening the document or supplying a password
    const reportLines = [];
    for (const fileName of [plainFileName, lockedFileName]) {
      const isProtected = pdfModule.PdfDocument.IsPasswordProtected(fileName);
      reportLines.push(`${fileName}: ${isProtected ? 'password protected' : 'not encrypted'}`);
    }

    // Write the report into the virtual file system, then export it
    const outputFileName = 'EncryptionCheckResult.txt';
    const report = reportLines.join('\r\n');
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));

    // Read the generated file back from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Checking Whether a PDF Is Password Protected</h1>
      <button onClick={checkPasswordProtection}>
        Run Check
      </button>
    </div>
  );
}

export default App;

Exported report: ContractTemplate.pdf is not encrypted, EncryptedContract.pdf is password protected

Exported report: ContractTemplate.pdf is not encrypted, EncryptedContract.pdf is password protected


Checking whether a document requires an open password

IsPasswordProtected() returning true only means the document carries an encryption dictionary, not that a password is required to open it: a document with only a permission password still opens without one. To tell the two apart, call LoadFromFile() once without a password. A thrown exception means an open password is missing; a successful load means the document is merely restricted.

function App() {
  const checkOpenPassword = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // File names to check
    const fileNames = ['ContractTemplate.pdf', 'EncryptedContract.pdf'];
    for (const fileName of fileNames) {
      await window.spire.FetchFileToVFS(fileName, "", `${process.env.PUBLIC_URL}/data/`);
    }

    // Load without a password: a thrown exception means an open password is required
    const reportLines = [];
    for (const fileName of fileNames) {
      try {
        const doc = new pdfModule.PdfDocument();
        doc.LoadFromFile(fileName);
        reportLines.push(`${fileName}: ${doc.IsEncrypted ? 'opens directly, but the document is still restricted by permissions' : 'not encrypted, opens directly'}`);
        doc.Close();
      } catch (error) {
        reportLines.push(`${fileName}: open password required`);
      }
    }

    // Write the report into the virtual file system, then export it
    const outputFileName = 'OpenPasswordCheck.txt';
    const report = reportLines.join('\r\n');
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));

    // Read the generated file back from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Checking Whether a Document Requires an Open Password</h1>
      <button onClick={checkOpenPassword}>
        Run Check
      </button>
    </div>
  );
}

export default App;

Exported result: ContractTemplate.pdf opens directly, EncryptedContract.pdf requires an open password

Exported result: ContractTemplate.pdf opens directly, EncryptedContract.pdf requires an open password


Verifying candidate passwords and confirming the password role

Whether a password is correct is answered by whether LoadFromFile() loads successfully. On failure it always throws Can not open an encrypted document. The password is invalid., without distinguishing "no password supplied" from "wrong password". So check first whether the document is encrypted, skip the ones that are not, try passwords one by one on the encrypted ones, then read PdfDocument.Security to see whether the password used this time landed in UserPassword or OwnerPassword and confirm whether it is the open password or the permission password.

function App() {
  const verifyPassword = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check that the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Files to verify and candidate passwords
    const fileNames = ['ContractTemplate.pdf', 'EncryptedContract.pdf'];
    const candidates = ['wrong123', 'spire123', 'owner123'];

    // Both the static method and loading read the virtual file system, so load the files first
    for (const fileName of fileNames) {
      await window.spire.FetchFileToVFS(fileName, "", `${process.env.PUBLIC_URL}/data/`);
    }

    const reportLines = [];
    for (const fileName of fileNames) {
      // An unencrypted document needs no password verification
      if (!pdfModule.PdfDocument.IsPasswordProtected(fileName)) {
        reportLines.push(`${fileName}: this PDF document is not encrypted, no password verification needed`);
        continue;
      }

      // Encrypted: try each candidate; loading successfully means the password is correct
      for (const password of candidates) {
        try {
          const doc = new pdfModule.PdfDocument();
          doc.LoadFromFile(fileName, password);

          // Security records the password used this time, which gives its role
          const role = doc.Security.UserPassword ? 'open password' : 'permission password';
          reportLines.push(`${fileName}: password "${password}" is correct (${role})`);
          doc.Close();
        } catch (error) {
          reportLines.push(`${fileName}: password "${password}" is incorrect`);
        }
      }
    }

    // Write the report into the virtual file system, then export it
    const outputFileName = 'PasswordVerificationResult.txt';
    const report = reportLines.join('\r\n');
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));

    // Read the generated file back from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Verifying Candidate Passwords and Confirming the Password Role</h1>
      <button onClick={verifyPassword}>
        Run Verification
      </button>
    </div>
  );
}

export default App;

Exported result: the incorrect password is rejected, spire123 is the open password, owner123 is the permission password

Exported result: the incorrect password is rejected, spire123 is the open password, owner123 is the permission password


FAQ

IsPasswordProtected() throws File doesn't exist

Cause: the method reads a path inside the virtual file system, not an address the browser can fetch directly. When the file has not been loaded into the virtual file system, Spire.PDF cannot find the target and throws File doesn't exist Arg_ParamName_Name, fileName.

Solution: load the file into the virtual file system with FetchFileToVFS before calling the method, using the same name you pass afterwards:

// Load into the virtual file system first, then check
await window.spire.FetchFileToVFS('EncryptedContract.pdf', "", `${process.env.PUBLIC_URL}/data/`);
const isProtected = pdfModule.PdfDocument.IsPasswordProtected('EncryptedContract.pdf');

The document needs a password, but the error says the password is invalid

Cause: Spire.PDF returns the same message, Can not open an encrypted document. The password is invalid., for "no password supplied" and for "wrong password". The error text alone cannot tell them apart.

Solution: work in three steps. Use IsPasswordProtected() to check whether the document carries an encryption dictionary, load it without a password to separate "open password required" from "restricted by permissions only", and only then try candidate passwords, deciding correctness by whether an exception is thrown:

// Loading without a password fails → an open password is required; retry with a password → a throw means a miss
try {
  const doc = new pdfModule.PdfDocument();
  doc.LoadFromFile(fileName, password);
  // Loaded: read Security to confirm the role
} catch (error) {
  // Failed to load: this password does not work
}

Reading Security.Permissions after loading throws

Cause: the permission bits are a combination of several flags and do not necessarily match a member of the PdfPermissionsFlags enumeration, so reading them throws Invalid value for spirepdfPdfPermissionsFlags. In the same version, HasExtendedRight() throws ArgumentNullException as well.

Solution: use Security.UserPassword and Security.OwnerPassword instead to determine the role of the password used this time. A document loaded with the permission password carries all permissions, so Decrypt() can remove the protection directly; a document loaded with the open password needs the permission password supplied separately. To control permissions precisely, set them with PdfDocumentPrivilege while encrypting and keep a record yourself:

const role = doc.Security.UserPassword ? 'open password' : 'permission password';

// Loaded with the permission password: the protection can be removed directly
if (role === 'permission password') {
  doc.Decrypt();
}

Get a Free License

If you want to remove the evaluation message from the resulting documents, or to get rid of the feature limitations, please contact our sales team to obtain a temporary license valid for 30 days.

In corporate training, online courses and academic sharing, turning a course document into an "illustrated, voice-narrated" explainer video normally requires a whole toolchain of presentation, image processing, audio synthesis and video encoding. With the AI capability of Spire.Agent.Office, you only need to upload course documents in Word, PDF, Excel, Markdown or other formats and describe the goal in plain language to get a complete narrated video.

Compared with the traditional SDK API

Traditional Spire.Office for .NET API Spire.Agent.Office
Driving model Parse the course document, lay out slides page by page to build a Presentation, then use the API and ffmpeg to convert the Presentation to MP4 Describe the goal in natural language and the AI understands and orchestrates the execution path
Amount of code A large amount of code is required, and speech narration cannot be added A simple natural-language instruction
Multi-format parsing Separate parsing logic must be written for each format (Word/PDF/Excel/Markdown) The AI detects the document format and reads the content automatically
Changing requirements Adjust theme, page count, language or narration style → change code → compile → redeploy Change the instruction and it takes effect immediately

From course document to narrated video:

For product installation and SpireToken configuration, please refer to Integrating Spire.Agent.Office in a .NET Project. The examples below assume Spire.Agent.Office is installed and SpireToken is configured.


Step 1: From a course document to a Presentation

Use Spire.Agent.Office to distill the core points and generate a well-designed Presentation automatically.


using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;

// Load course documents in Word, PDF, Excel and Markdown (4 formats)
List<string> inputPaths = new List<string>
{
    @"AI-application-course.docx",
    @"research-paper.pdf",
    @"financial-analysis.xlsx",
    @"health-policy.md"
};

string outputDirectory = @"E:\output\";
string key = "**************************";
// Distill the key points of the course document and generate a teaching Presentation.
string instruction =
    "Treat the attached tutorial documents as the processing target : distill the core points of the AI-application tutorial, covering the basics, typical application areas, implementation steps, real cases, and outlook. Theme: use a calm professional gray-blue as the theme color (clean, minimalist, modern AI-tech feel). 1. ensure high-quality layout and typography 2. include appropriate illustrative charts/figures 3. keep a clean, minimalist style 4. generate 12 slides; ";

// Generate a Presentation from each of the Word, PDF, Excel and Markdown course documents in turn
foreach (string inputPath in inputPaths)
{
    if (!System.IO.File.Exists(inputPath)) continue;

    string savePath = System.IO.Path.Combine(outputDirectory,
        $"{System.IO.Path.GetFileNameWithoutExtension(inputPath)}.pptx");

    GeneratePPT(inputPath, instruction, savePath, key);
}

// AI-powered PPT generation
static PPTGenerationResult GeneratePPT(string input, string instruction, string savePath, string key)
{
    //AIOptions options
    AIOptions options = new AIOptions();
    //Set the SpireToken
    options.SpireToken = key;
    //Set the timeout
    options.TimeoutMs = 1000000;
    //If the document content is long, you can customize the token circuit breaker; the default fuse is 3,000,000
    options.TokenCircuitBreakerLimit = 5000000;
    using (Presentation ppt = new Presentation())
    {
        AIDocumentProcessor processor = ppt.AI(options);
        return processor.GeneratePresentation(input, instruction, savePath);
    }
}

Step 2: From a Presentation to a video

Based on the Presentation generated in step 1, a single instruction that asks for "voice narration" automatically produces an MP4 explainer video with spoken commentary.

using Spire.Agent.Office.AI;
using Spire.Agent.Office.Extensions;
using Spire.Presentation;

// Define the paths of the 4 PPT source files generated in the previous step
List<string> inputPaths = new List<string>
{
    @"AI-application-course.pptx",
    @"research-paper.pptx",
    @"financial-analysis.pptx",
    @"health-policy.pptx"
};

string outputDirectory = @"E:\output\";
string key = "**************************";
string instruction = "Create an MP4 explainer video of this presentation with synchronized voice narration in English.";

// Loop over each PPT to generate a video
foreach (string inputPath in inputPaths)
{
    if (!File.Exists(inputPath)) continue;
    
    string savePath = Path.Combine(outputDirectory, 
        $"{Path.GetFileNameWithoutExtension(inputPath)}.mp4");
    
    ExecuteDemoPPT(instruction, inputPath, savePath, key, null);
}

// Execute the PPT document AI processing (generate a video with voice narration)
static AIResult ExecuteDemoPPT(string instruction, string inputPath, string savePath, string key, string[] attachmentPaths)
{
    //AIOptions options
    AIOptions options = new AIOptions();
    //Set the SpireToken
    options.SpireToken = key;
    //Set the timeout
    options.TimeoutMs = 1000000;
    //Set the token circuit breaker
    options.TokenCircuitBreakerLimit = 5000000;
    using (Presentation ppt = new Presentation())
    {
        if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
        {
            ppt.LoadFromFile(inputPath);
        }
        AIDocumentProcessor processor = ppt.AI(options);
        return processor.ExecuteInstruction(ppt, instruction, savePath, attachmentPaths);
    }
}

Word document ----> Presentation ----> narrated .mp4 video

Generated teaching deck Pdf document ----> Presentation ----> narrated .mp4 video

Generated teaching deck Excel document ----> Presentation ----> narrated .mp4 video

Generated teaching deck Markdown document ----> Presentation ----> narrated .mp4 video

Generated teaching deck


FAQ

The generated PPT's page count does not match the course content

Cause: when the course document is large, the AI's choices when distilling and paginating can differ from expectations.

Solution: state the page count explicitly in the instruction, for example "generate 9 slides, one teaching segment per slide".

The result failed to generate

Cause: the source document contains a lot of data, so the analysis consumes a large number of tokens and exceeds the default token fuse limit.

Solution: configure TokenCircuitBreakerLimit yourself, as shown below:

AIOptions options = new AIOptions();
options.TokenCircuitBreakerLimit = 5000000;

Can multi-format course documents (Word/PDF/Excel/Markdown) all be converted?

Cause: each format is parsed differently, so users are not sure what works.

Solution: Spire.Agent.Office detects the format of the attached document and reads its content automatically; simply pass in the file of the corresponding format and state its origin in the instruction.


Get your SpireToken Key

Configure it in code:

AIOptions options = new AIOptions();
options.SpireToken = key;
page 22