In teaching work, lesson preparation is the most time-consuming and skill-demanding task for every teacher. When you receive a textbook, you need to read through each chapter and section, distill core knowledge points, organize the knowledge logic, and then design teaching objectives, determine teaching key and difficult points, arrange the complete teaching process of introduction — new teaching — consolidation — summary, and finally compile it into a standardized lesson plan. A complete lesson plan often takes several hours, and different teachers vary greatly in analysis depth and lesson plan structure, making it difficult to ensure consistent quality.

Comparison with Traditional SDK API Processing

Traditional Spire.Office for .NET API Spire.Agent.Office
Driving approach Write code to parse the textbook paragraph by paragraph: load document → iterate paragraphs → extract keywords → manually assemble the lesson plan; every step requires code control Describe the parsing and generation goals in natural language, and AI automatically understands the textbook and generates the lesson plan
Code volume Requires a large amount of code to maintain the knowledge point library, paragraph classification rules, and lesson plan template logic Only configuration code + 1 natural language instruction
Lesson plan structure Teaching objectives, key/difficult points, and teaching process must each be hard-coded with a set of generation logic AI automatically generates a structurally complete lesson plan according to subject standards
Textbook understanding Can only match mechanically by keywords, unable to understand the relationships and hierarchy between knowledge points AI understands the textbook based on semantics, extracting chapter themes, test points, and teaching suggestions
Maintainability Different subjects and textbook versions require separate development and maintenance The analysis scope and lesson plan style can be adjusted at any time in natural language

This article explains how to use the Word AI capability of Spire.Agent.Office to analyze textbooks and automatically generate lesson plans. Together, they form a complete lesson preparation pipeline: first use AI to parse the textbook PDF, organize unit key points and key/difficult points, then generate a standardized, content-complete lesson plan based on the analysis results.

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


Intelligent Textbook Analysis

Intelligent textbook analysis is the starting point of the entire lesson preparation workflow, suitable for quickly establishing an overall understanding of the textbook before reading the whole book. The core idea is: pass the electronic textbook PDF as an attachment, let AI parse the textbook content, organize the core knowledge, key and difficult points, learning suggestions, and the connections between chapters according to the chapters, and generate a Word unit textbook analysis document. Teachers can use it to complete unit teaching planning, and subsequent lesson plan generation is also based on it.

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

// Textbook PDF (multiple chapters can be passed in)
string[] attachments = new string[] {
    "E:\\Input\\Textbook-Rational_Numbers.pdf",
    "E:\\Input\\Textbook-Addition_and_Subtraction_of_Algebraic_Expressions.pdf",
    "E:\\Input\\Textbook-Linear_Equations_in_One_Variable.pdf"
};
// Save path
string savePath = "E:\\Output\\Textbook_Analysis.docx";
// SpireToken Key
string key = "**********************";
// Natural language instruction
string instruction =
    "Please analyze the textbook content in the attached PDFs, and from the perspective of a lesson-preparing teacher, help me organize a textbook analysis suitable for daily lesson preparation.\n" +
    "For each chapter, explain the chapter's core knowledge content, teaching key and difficult points, and recommended class hours.\n" +
    "Try to preserve the key concepts and typical example points of each section, and supplement the common difficulties and error-prone points students encounter when learning this chapter.\n" +
    "Also describe the connections between chapters. Please strictly analyze based on the actual content of the textbook in the PDFs and do not fabricate anything.\n" +
    "Generate a Word document with a clear structure so that I can arrange the unit teaching plan accordingly, and subsequent lesson plans will also be based on this analysis.";

// Call the Word document processing function
AIResult result = ExecuteDemoWord(instruction, savePath, key, attachments);

// Execute Word document AI processing
static AIResult ExecuteDemoWord(string instruction, string savePath, string key, string[] attachments)
{
    // Create an AIOptions configuration object
    AIOptions options = new AIOptions();
    // Set the SpireToken Key
    options.SpireToken = key;

    // Use the Document object to process the Word document
    using (Document doc = new Document())
    {
        // Create the AI document processor
        AIDocumentProcessor processor = doc.AI(options);

        // Execute the AI instruction
        return processor.ExecuteInstruction(doc, instruction, savePath, attachments);
    }
}

AI-generated Word textbook analysis document Textbook analysis document

The analysis document unfolds by chapter, clearly explaining each chapter's core knowledge, key and difficult points, and recommended class hours, and also supplements students' common learning difficulties, error-prone points, and the connections between chapters. Teachers only need to provide the electronic PDF of the textbook to complete the whole-book analysis, quickly identify key chapters, and reasonably allocate class hours; this unit textbook analysis can also be directly used as background material for the subsequent lesson plan generation.


Automated Word Lesson Plan Generation

Fine-grained lesson preparation for a single class can be further advanced on the basis of the textbook analysis in the first section. The core idea is: directly use the unit textbook analysis generated in the first section as input, and let AI generate a structurally complete, ready-to-use lesson plan based on the analysis of the relevant section, including student analysis, teaching objectives, teaching key and difficult points, teaching preparation, teaching process, blackboard design, and tiered after-class assignments.

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

// The unit textbook analysis generated in the first section (already includes the analysis of the relevant section content)
string inputPath = "E:\\Input\\Textbook_Analysis.docx";
// Save path
string savePath = "E:\\Output\\Linear_Equations_in_One_Variable-Lesson_Plan.docx";
// SpireToken Key
string key = "**********************";
// Natural language instruction
string instruction =
    "Based on the content of the \"Linear Equations in One Variable\" section in the unit textbook analysis document, " +
    "help me write a complete lesson plan Word document. It is recommended to include: " +
    "student analysis, teaching objectives (knowledge and skills, process and methods, emotional attitude and values), teaching key and difficult points, teaching preparation, " +
    "teaching process (introduction, new teaching, consolidation practice, class summary), blackboard design, and tiered after-class assignments. " +
    "The teaching objectives and key/difficult points must closely match the textbook content, and the teaching process must be specific about how the teacher guides and how students learn in each segment. " +
    "The after-class assignments should be tiered into basic and advanced questions. Please format according to a standardized lesson plan layout, unify the heading levels and fonts, and finally save and output in DOCX format";

// Call the Word document processing function
AIResult result = ExecuteDemoWord(instruction, inputPath, savePath, key, null);

// Execute Word document AI processing
static AIResult ExecuteDemoWord(string instruction, string inputPath, string savePath, string key, string[] attachmentPaths)
{
    // Create an AIOptions configuration object
    AIOptions options = new AIOptions();
    // Set the SpireToken Key
    options.SpireToken = key;

    // Use the Document object to process the Word document
    using (Document doc = new Document())
    {
        // Load the unit textbook analysis document as the context for lesson plan generation
        if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
        {
            doc.LoadFromFile(inputPath);
        }
        // Create the AI document processor
        AIDocumentProcessor processor = doc.AI(options);

        // Execute the AI instruction
        return processor.ExecuteInstruction(doc, instruction, savePath, attachmentPaths);
    }
}

AI-generated Word lesson plan document Automatically generated Word lesson plan

The generated lesson plan has a complete structure and content that closely matches the textbook, covering student analysis and tiered after-class assignments as well. Based directly on the unit textbook analysis, teachers can get a first draft of the lesson, then adjust and polish it, saving the time of writing from scratch. For multiple classes in the same unit, the same unit textbook analysis can be reused to generate lesson plans section by section and then proofread them uniformly, turning lesson preparation from "writing word by word" into "localized modification".


FAQ

Teaching objectives not matching the textbook content

Reason: AI's generation of teaching objectives depends on its understanding of the textbook theme. If the textbook content is too extensive or the instruction is too general, the objectives may diverge from the actual teaching content.

Solution: Limit the analysis scope in the instruction (such as specifying the chapter name), explicitly require the objectives to be developed from three dimensions, and bind the requirement "must be written based on the actual textbook content".

Lesson plan structure not standardized, missing sections

Reason: The section structure that the lesson plan should contain is not specified in the instruction, and the structure AI generates by default may not match the school template.

Solution: List the sections the lesson plan must include in order in the instruction (such as introduction, new teaching, consolidation, summary), and AI will output strictly according to this structure.

Analysis report missing test points or knowledge points

Reason: The textbook has too many chapters, or the same knowledge point is scattered across multiple chapters, making the analysis report incomplete.

Solution: Pass the complete textbook or the PDFs of relevant chapters as attachments, and specify the knowledge types to focus on in the instruction (such as "focus on frequently tested question types and examples").

Inconsistent formatting in the generated lesson plan

Reason: The layout requirements of the lesson plan are not specified in the instruction, and the heading levels, fonts, and paragraph styles output by AI may be inconsistent.

Solution: Add descriptions such as "format according to a standardized lesson plan layout and unify heading levels and fonts" to the instruction.


Get the SpireToken Key

Configure it in your code:

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

In corporate legal and compliance management scenarios, contract review is one of the most time-consuming and error-prone tasks. Every contract involves a large number of rights and obligations clauses — liquidated damages, payment terms, disclaimer clauses, breach liability, dispute resolution, and more. Any clause that is unfavorable to your side or ambiguously worded may lead to legal disputes or financial losses in the future. Traditional approaches rely on legal professionals reading and annotating each clause manually; a single contract of dozens of pages often takes hours, and review standards vary from person to person.

Comparison with Traditional SDK API Processing

Traditional Spire.Office for .NET API Spire.Agent.Office
Driving approach Write code to parse clauses one by one: load document → iterate paragraphs → regex match keywords → judge risk → highlight and annotate; every step requires code control Describe the review goal in natural language, and AI automatically identifies and annotates risk clauses
Code volume Requires a large amount of code to maintain the clause risk rule library, keyword matching, and annotation logic Only configuration code + 1 natural language instruction
Risk rules Risk judgment relies on hard-coded keywords; new risk types require code changes AI understands clauses semantically and can identify new risks not covered by the rules
Review stance Review logic for each contract type must be developed separately A single phrase like "review from our side" in the instruction switches the review stance
Maintainability The risk rule library requires continuous manual maintenance Review scope and rules can be adjusted at any time in natural language

This article explains how to use the Word AI capability of Spire.Agent.Office to review contract clauses and annotate risks. You can choose to highlight risk clauses on the original contract and add comments, or batch review and output a structured risk review report, meeting contract review needs of different scales and scenarios.

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


Risk Clause Highlighting and Annotation

Risk clause highlighting and annotation suits in-depth review of important contracts. The core idea is: let AI review contract clauses one by one, identify clauses that are unfavorable to your side or carry legal risks, highlight them in yellow in place and add comments, so legal professionals can view the risk points directly on the contract.

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

// Path of the contract file to be reviewed
string inputPath = "E:\\Input\\Software_Contract.docx";
// Save path
string savePath = "E:\\Output\\Review.docx";
// Output directory
string OutDir = "E:\\Output";
// SpireToken Key
string key = "xxxxx";
// Natural language instruction
string instruction =
    "Review all clauses in the current contract document and identify clauses that are unfavorable to the purchaser or carry legal risks, including but not limited to: " +
    "excessively high liquidated damages, stringent payment terms, overly broad disclaimer clauses, missing breach liability provisions, unfavorable court jurisdiction agreements, unclear intellectual property ownership, etc. " +
    "For each risk clause, perform the following operations: 1. Highlight the risk clause text in yellow; 2. Add a comment in place, noting the risk point, risk level (high/medium/low), and modification suggestions. " +
    "After processing, keep the same layout, styles, and fonts as the original document, and finally save and output in DOCX format";

// Call the Word document processing function
AIResult result = ExecuteDemoWord(instruction, inputPath, savePath, key, OutDir, null);

// Execute Word document AI processing
static AIResult ExecuteDemoWord(string instruction, string inputPath, string savePath, string key, string output, string[] attachmentPaths)
{
    // Create an AIOptions configuration object
    AIOptions options = new AIOptions();
    // Set the working directory to the output directory
    options.WorkDir = output;
    // Set the SpireToken Key
    options.SpireToken = key;

    // Use the Document object to process the Word document
    using (Document doc = new Document())
    {
        // Load the contract document from file
        if (!string.IsNullOrEmpty(inputPath) && File.Exists(inputPath))
        {
            doc.LoadFromFile(inputPath);
        }
        // Create the AI document processor
        AIDocumentProcessor processor = doc.AI(options);

        // Execute the AI instruction
        return processor.ExecuteInstruction(doc, instruction, savePath, attachmentPaths);
    }
}

Contract after AI highlighting and annotation Contract with highlighted annotations

In the reviewed contract, risk clauses are highlighted in yellow, and the comments clearly state the risk points and modification suggestions. Legal professionals can quickly locate the highlighted positions without reading the original text line by line, and can directly discuss modification plans with the business side based on the comments.


Batch Review and Review Report

For quick screening of large batches of contracts (such as contract renewal or supplier qualification review), batch review with a structured review report is more suitable. The core idea is: let AI review multiple contracts one by one, consolidate the risk clauses of each contract into a risk list, and output it as an MD report for statistics, tracking, and tiered processing.

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

// Paths of multiple contract files to be reviewed
string[] attachments = new string[] {
    "E:\\Input\\Purchase_Contract_EN.docx",   // Purchase contract
    "E:\\Input\\Sales_Contract_EN.docx",   // Sales contract
    "E:\\Input\\Labor_Contract_EN.docx"    // Labor contract
};
// Save path (null here; the output folder path set below will be used)
string savePath = "E:\\Output\\Structural_Review_Output.md";
// Output directory
string OutDir = "E:\\Output";
// SpireToken Key
string key = "xxxxx";
// Natural language instruction
string instruction =
    "Review the contract documents in the attachments one by one, extract risk clauses, and output a Markdown review report: " +
    "The report contains a table with fixed columns: Contract Name | Clause Number | Clause Original Text | Risk Level (High/Medium/Low) | Risk Type | Risk Description | Modification Suggestion. " +
    "Sort by risk level from high to low; the clause original text must be quoted from the contract, truncated with … after 20 characters, and must not be fabricated.";

// Call the Word document processing function
AIResult result = ExecuteDemoWord(instruction, savePath, key, OutDir, attachments);

// Execute Word document AI processing
static AIResult ExecuteDemoWord(string instruction, string savePath, string key, string output, string[] attachments)
{
    // Create an AIOptions configuration object
    AIOptions options = new AIOptions();
    // Set the working directory to the output directory
    options.WorkDir = output;
    // Set the SpireToken Key
    options.SpireToken = key;

    // Use the Document object to process the Word document
    using (Document doc = new Document())
    {
        // Create the AI document processor
        AIDocumentProcessor processor = doc.AI(options);

        // Execute the AI instruction
        return processor.ExecuteInstruction(doc, instruction, savePath, attachments);
    }
}

Contract risk review report output by AI Contract risk review report

Each row in the review report corresponds to a risk clause and contains the clause original text, risk level, risk type, and modification suggestion. Legal professionals can sort by risk level to prioritize high-risk clauses, or export the report for risk ledger tracking in a contract management system.


FAQ

Risk clauses identified inaccurately

Reason: AI's judgment of "unfavorable clauses" depends on the review stance. From your side's perspective versus the counterparty's perspective, the risk judgment for the same clause may be completely opposite.

Solution: Specify the review stance clearly in the instruction, such as "review from the purchaser's perspective", and add a list of risk types to focus on. AI will strictly follow this stance and scope.

Document style changes after highlighting

Reason: The AI model automatically modified or added content during processing.

Solution: Add a description such as "keep the same layout, styles, and fonts as the original document" to the instruction.

Review report does not accurately correspond to contract clauses

Reason: Clause numbers are inconsistent, or the same clause is scattered across multiple places in the contract, causing the clause original text in the report to not match the contract.

Solution: In the instruction, require AI to quote the clause original text and note the source of the clause number, for easy manual verification and location.


Get the SpireToken Key

Configure it in your code:

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

Images are one of the most intuitive forms of content presentation and distribution, while PDF documents preserve the original layout and are widely used for the storage and transmission of formal files. When displaying PDF content on web pages, mini programs, social platforms, or emails, distributing PDF files directly is often inconvenient — converting them to image formats such as PNG or JPEG first enables quick preview and sharing. Conversely, consolidating scanned documents or image assets into PDF makes batch archiving and cross-platform distribution easier. Real-world business often requires flexible switching between the two forms: converting PDF contracts to images for online preview and quick sharing, or converting scanned image assets to PDF for unified archiving and circulation.

Spire.PDF for JavaScript performs bidirectional conversion between PDF and images entirely in the browser via WebAssembly, managing input and output files through a virtual file system (VFS) with no backend server required.

This article covers two core features:

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


Convert PDF to Image

The core of PDF-to-image conversion is to render the content, fonts, and graphics elements of every page in a PDF document into independent bitmap data. Spire.PDF for JavaScript generates an image stream for each page through the PdfDocument object's SaveAsImage method, loops through all Pages.Count pages and saves each page as a PNG image with stream.Save, then bundles the images into a ZIP file with JSZip for a one-click download, without needing to handle pixel and page coordinate mapping manually.

import JSZip from "jszip";

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

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

    // Load the PDF file and fonts into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Flowers.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Create an output directory to hold the converted images
    let outputDirectoryName = "ImagesFolders/";
    window.dotnetRuntime.Module.FS.mkdirTree(outputDirectoryName);

    // Loop through each page and save it as an image
    for (let i = 0; i < doc.Pages.Count; i++) {
      const outputFileName = outputDirectoryName + "ConvertedImages_" + i + ".png";
      let stream = doc.SaveAsImage({ pageIndex: i });
      stream.Save(outputFileName);
      stream.Dispose();
    }

    doc.Dispose();

    // Read the converted files from VFS and trigger download
    const zip = new JSZip();
    let items = await window.dotnetRuntime.Module.FS.readdir(outputDirectoryName);
    items = items.filter((item) => item !== "."

      && item !== "..");
    for (const item of items) {
      const itemPath = `${outputDirectoryName}/${item}`;
      const fileData = await window.dotnetRuntime.Module.FS.readFile(itemPath);
      zip.file(item, fileData);
    }

    // Convert the ZIP to a Blob and trigger the browser download
    const zipBlob = await zip.generateAsync({ type: "blob" });
    const url = URL.createObjectURL(zipBlob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'ImagesFolders';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert PDF To Image</h1>
      <button onClick={convertToImage}>
        Generate
      </button>
    </div>
  );
}

export default App;

Each page of the PDF exported as a PNG image via SaveAsImage and bundled into a ZIP file for download

Each page of the PDF exported as a PNG image via SaveAsImage and bundled into a ZIP file for download

Adjust the DPI Resolution of Exported Images

When exporting images with the SaveAsImage method, the default resolution is 96 DPI, which is suitable for screen preview, but text and lines may appear jagged or blurry when zoomed in. For sharper images, specify the resolution via the dpiX and dpiY parameters of SaveAsImage, for example set it to 150 DPI:

// Export each page as an image at 150 DPI
for (let i = 0; i < doc.Pages.Count; i++) {
  let stream = doc.SaveAsImage({ pageIndex: i, dpiX: 150, dpiY: 150 });
  stream.Save(outputDirectoryName + "highres_" + i + ".png");
  stream.Dispose();
}

The higher the DPI value, the sharper the exported image, but the larger the file size. Choose a balance between clarity and file size based on your actual use case.


Convert Image to PDF

Image-to-PDF conversion is commonly used to consolidate scanned documents or design assets into PDF for archiving. Spire.PDF for JavaScript creates a new document with the PdfDocument object, loads the image with the PdfImage.FromFile method, adds a page via Pages.Add, draws the image onto the page at its original size with the Canvas.DrawImage method, and finally saves it as a standard PDF with the SaveToFile method using the FileFormat.PDF enum value.

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

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

    // Load the image file into VFS
    const inputFileName = 'Scenery.png';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object
    let doc = new pdfModule.PdfDocument();

    // Add a page
    let page = doc.Pages.Add();

    // Load the image
    let image = pdfModule.PdfImage.FromFile(inputFileName);

    // Calculate the scale ratio so the image fits the page completely
    let widthFitRate = image.PhysicalDimension.Width / page.Canvas.ClientSize.Width;
    let heightFitRate = image.PhysicalDimension.Height / page.Canvas.ClientSize.Height;
    let fitRate = Math.max(widthFitRate, heightFitRate);

    // Calculate the scaled dimensions of the image
    let fitWidth = image.PhysicalDimension.Width / fitRate;
    let fitHeight = image.PhysicalDimension.Height / fitRate;

    // Draw the image onto the page
    page.Canvas.DrawImage({ image: image, x: 0, y: 30, width: fitWidth, height: fitHeight });

    const outputFileName = 'ImageToPDF.pdf';

    // Save as PDF format
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.PDF });
    doc.Close();

    // Read the converted file from VFS and trigger 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>Convert Image To PDF</h1>
      <button onClick={convertImageToPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

PDF document generated after loading an image via PdfImage.FromFile and drawing it with Canvas.DrawImage

PDF document generated after loading an image via PdfImage.FromFile and drawing it with Canvas.DrawImage

Load Images from a Memory Stream

Besides loading directly from a file with PdfImage.FromFile, images can also be loaded from a memory stream via the PdfImage.FromStream method. This approach suits scenarios where the image data comes from an API response or a database field, or where bytes need to be read before processing. See the code below:

// Read image bytes from VFS and build a memory stream
let bytes = window.dotnetRuntime.Module.FS.readFile(inputFileName);
let stream = new pdfModule.Stream(bytes);

// Load the image from the memory stream
let image = pdfModule.PdfImage.FromStream(stream);

The image can then be drawn onto a PDF page with the page.Canvas.DrawImage method and saved as a standard PDF using SaveToFile.


FAQ

Garbled text in the converted image

Reason: PDF relies on font embedding to ensure consistent cross-platform rendering. If the input PDF uses non-embedded fonts and the corresponding font files are not loaded in the VFS, text may appear garbled after conversion.

Solution: Make sure the required TrueType font files (e.g., ARIALUNI.TTF) are loaded into the /Library/Fonts/ directory in VFS before calling the conversion. ARIALUNI.TTF covers common CJK characters and is the recommended font for ensuring conversion quality.

Which image formats are supported for conversion?

Reason: Different business scenarios require different bitmap formats. For example, PNG is commonly used for web preview and JPEG for photos.

Solution: Spire.PDF for JavaScript can render PDF pages to common bitmap formats such as PNG, JPEG, and BMP. After generating the image stream with SaveAsImage, simply replace the file extension of the output file name with the target format (e.g., .jpg, .bmp, .png) in stream.Save to output the corresponding image format.


Get a Free License

If you want to remove the evaluation messages in the resulting documents or get rid of functional limitations, contact sales to obtain a 30-day temporary license.

XPS (XML Paper Specification) is a fixed-layout document format introduced by Microsoft, widely used in electronic document printing, archiving, and distribution scenarios, with native support in the Windows platform ecosystem. XPS describes document structure based on XML, offering advantages such as clear structure, easy validation, and digital signing. Meanwhile, PDF remains indispensable as an internationally recognized document format for cross-platform distribution. Real-world business often requires flexible switching between the two formats: converting existing PDF contracts to XPS for printing and archiving in Windows environments, or converting XPS documents to PDF for cross-platform distribution and collaboration.

Spire.PDF for JavaScript performs bidirectional conversion between PDF and XPS entirely in the browser via WebAssembly, managing input and output files through a virtual file system (VFS) with no backend server required.

This article covers two core features:

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


Convert PDF to XPS

The core of PDF-to-XPS conversion is to re-encode the page content, fonts, and graphics elements from a PDF document into an XML description structure compliant with the XPS standard. Spire.PDF for JavaScript accomplishes this in one step through the PdfDocument object's SaveToFile method with the FileFormat.XPS enum value, eliminating the need to handle underlying format differences manually.

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

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

    // Load fonts and PDF file into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Reading_EN.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Define the output file name for XPS format
    const outputFileName = 'OutputXPS.xps';

    // Save as XPS format
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.XPS });
    doc.Close();

    // Read the converted file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.ms-xpsdocument' });
    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>Convert PDF To XPS</h1>
      <button onClick={convertToXPS}>
        Generate
      </button>
    </div>
  );
}

export default App;

XPS output generated after conversion via SaveToFile with FileFormat.XPS

XPS output generated after conversion via SaveToFile with FileFormat.XPS


Convert XPS to PDF

XPS-to-PDF conversion is a common requirement in document cross-platform distribution scenarios. Spire.PDF for JavaScript loads XPS fixed-layout documents through the PdfDocument object's LoadFromXPS method and then exports them as standard PDF files via the SaveToFile method with the FileFormat.PDF enum value, preserving the original document's layout and visual appearance.

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

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

    // Load the XPS file into VFS
    const inputFileName = 'Lease_Agreement_EN.xps';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfDocument object and load the XPS document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromXPS(inputFileName);

    // Define the output file name for PDF format
    const outputFileName = 'OutputPDF.pdf';

    // Save as PDF format
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.PDF });
    doc.Close();

    // Read the converted file from VFS and trigger 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>Convert XPS To PDF</h1>
      <button onClick={convertXPSToPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

PDF document generated after loading XPS via the PdfDocument LoadFromXPS method and converting

PDF document generated after loading XPS via the PdfDocument LoadFromXPS method and converting


FAQ

Can encrypted PDFs be converted to XPS?

Password-protected encrypted PDFs cannot be saved as XPS directly via SaveToFile — the document must be decrypted first.

Solution: Provide the password when loading the PDF via the second parameter of LoadFromFile, then save as XPS:

// Load a password-protected PDF document
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName, "password");

// Save as XPS format
doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.XPS });
doc.Close();

Get a Free License

If you want to remove the evaluation messages in the resulting documents or get rid of functional limitations, contact sales to obtain a 30-day temporary license.

OFD (Open Fixed-layout Document) is a national standard fixed-layout document format widely used in e-invoices, e-certificates, administrative approvals, and other government and financial scenarios. OFD describes document structure based on XML, offering advantages such as independent control and information security. Meanwhile, PDF remains indispensable as an internationally recognized document format for cross-platform distribution. Real-world business often requires flexible switching between the two formats: receiving OFD-format e-invoices and converting them to PDF for printing and distribution, or converting existing PDF contracts to OFD to meet government platform upload requirements.

Spire.PDF for JavaScript performs bidirectional conversion between PDF and OFD entirely in the browser via WebAssembly, managing input and output files through a virtual file system (VFS) with no backend server required.

This article covers two core features:

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


Convert PDF to OFD

The core of PDF-to-OFD conversion is to re-encode the page content, fonts, and graphics elements from a PDF document into an XML description structure compliant with the OFD standard. Spire.PDF for JavaScript accomplishes this in one step through the PdfDocument object's SaveToFile method with the FileFormat.OFD enum value, eliminating the need to handle underlying format differences manually.

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

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

    // Load fonts and PDF file into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TemplateIntroduction-en.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfDocument object and load the PDF document
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Define the output file name for OFD format
    const outputFileName = 'OutputOFD.ofd';

    // Save as OFD format
    doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.OFD });
    doc.Close();

    // Read the converted file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/ofd' });
    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>Convert PDF To OFD</h1>
      <button onClick={convertToOFD}>
        Generate
      </button>
    </div>
  );
}

export default App;

OFD output generated after conversion via SaveToFile with FileFormat.OFD

OFD output generated after conversion via SaveToFile with FileFormat.OFD


Convert OFD to PDF

OFD-to-PDF conversion is a common requirement in government electronic document distribution scenarios. Spire.PDF for JavaScript provides the OfdConverter component, which is specifically designed to parse OFD fixed-layout documents and export them as standard PDF files while preserving the original document's layout and visual appearance.

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

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

    // Load fonts and OFD file into VFS
    await window.spire.FetchFileToVFS('Arial.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Invoice_EN.ofd';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create OfdConverter object and pass the OFD file path
    let converter = new pdfModule.OfdConverter(inputFileName);
    
    // Define the output file name for PDF format
    const outputFileName = 'OutputPDF.pdf';

    // Convert to PDF format
    converter.ToPdf(outputFileName);
    converter.Dispose();

    // Read the converted file from VFS and trigger 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>Convert OFD To PDF</h1>
      <button onClick={convertOFDToPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

Standard PDF output generated after conversion via OfdConverter

Standard PDF output generated after conversion via OfdConverter


FAQ

Can encrypted PDFs be converted to OFD?

Password-protected encrypted PDFs cannot be saved as OFD directly via SaveToFile — the document must be decrypted first.

Solution: Provide the password when loading the PDF via the second parameter of LoadFromFile, then save as OFD:

// Load a password-protected PDF document
let doc = new pdfModule.PdfDocument();
doc.LoadFromFile(inputFileName, "password");

// Save as OFD format
doc.SaveToFile({ fileName: outputFileName, fileFormat: pdfModule.FileFormat.OFD });
doc.Close();

Garbled text in the converted OFD document

OFD relies on font embedding to ensure consistent cross-platform rendering. If the input PDF uses non-embedded fonts and the corresponding font files are not loaded in the VFS, text may appear garbled after conversion.

Solution: Make sure the required TrueType font files (e.g., ARIALUNI.TTF) are loaded into the /Library/Fonts/ directory in VFS before calling the conversion. ARIALUNI.TTF covers common CJK characters and is the recommended font for ensuring conversion quality.


Get a Free License

Spire.PDF for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

PDF/A is an ISO-standardized long-term archival format that embeds fonts, color profiles, and metadata into a unified compliance level, ensuring documents remain faithfully reproducible for decades regardless of the PDF reader used. In contrast, standard PDF offers greater flexibility for everyday editing and content extraction. Real-world business often requires switching between these two formats: converting contracts to PDF/A for regulatory compliance during archiving, and restoring them to standard PDF for text extraction during audit review.

Spire.PDF for JavaScript performs bidirectional PDF/PDF/A conversion entirely in the browser via WebAssembly, managing input and output files through a virtual file system (VFS) with no backend server required.

This article covers two core features:

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


Convert PDF to PDF/A

The core of PDF/A archival conversion is to consolidate fonts, color profiles, and metadata in a standard PDF into ISO-compliant levels. Spire.PDF handles this in one step through the PdfStandardsConverter component, supporting multiple compliance levels including PDF/A-1a, PDF/A-1b, PDF/A-2a, PDF/A-2b, PDF/A-3a, and PDF/A-3b.

The conversion standards supported by PdfStandardsConverter and their use cases are as follows:

Method Standard Description
ToPdfA1B PDF/A-1b Based on PDF 1.4, guarantees visual appearance reproducibility only — the most commonly used archival level
ToPdfA1A PDF/A-1a Requires document tags and structure information on top of 1b, supports accessible reading
ToPdfA2A PDF/A-2a Based on PDF 1.7, requires tags and structure info, supports layers and transparency
ToPdfA2B PDF/A-2b PDF/A-2 basic conformance level, allows transparency, layers, and embedded OLE objects
ToPdfA3A PDF/A-3a Allows embedding XML, Excel, and other arbitrary format files as attachments on top of 2a
ToPdfA3B PDF/A-3b PDF/A-3 basic conformance level, supports embedding arbitrary format attachments
ToPdfX1A2001 PDF/X-1a:2001 Print exchange standard, suitable for publishing and printing workflows

The following example demonstrates converting a PDF to PDF/A-2B using ToPdfA2B:

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

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

    // Load fonts and PDF file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'MovieCatalog.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfStandardsConverter
    let converter = new pdfModule.PdfStandardsConverter({ filePath: inputFileName });
    
    // Convert to PDF/A-2B format
    const outputFileName = 'ToPDFA_result.pdf';
    converter.ToPdfA2B({ filePath: outputFileName });

    // // Convert to PDF/A-1a
    // converter.ToPdfA1A({ filePath: outputFileName });

    // // Convert to PDF/A-2a
    // converter.ToPdfA2A({ filePath: outputFileName });

    // // Convert to PDF/A-2b
    // converter.ToPdfA2B({ filePath: outputFileName });

    // // Convert to PDF/A-3a
    // converter.ToPdfA3A({ filePath: outputFileName });

    // // Convert to PDF/A-3b
    // converter.ToPdfA3B({ filePath: outputFileName });

    // // Convert to PDF/X-1a:2001
    // converter.ToPdfX1A2001({ filePath: outputFileName });

    // Read the converted file from VFS and trigger 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);

    // Release resources
    converter.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert PDF To PDF/A</h1>
      <button onClick={convertToPDFA}>
        Generate
      </button>
    </div>
  );
}

export default App;

PDF/A output generated after conversion via PdfStandardsConverter

PDF/A output generated after conversion via PdfStandardsConverter


Convert PDF/A to PDF

PDF/A is the standard format for long-term archiving, but in everyday editing and content extraction scenarios, you may need to restore PDF/A back to standard PDF. Spire.PDF for JavaScript achieves this reverse conversion by loading the PDF/A document and copying content page by page into a new document, ensuring the output standard PDF is free of PDF/A compliance constraints.

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

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

    // Load fonts and PDF file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'PDFA_Sample.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create PdfDocument object
    let doc = new pdfModule.PdfDocument();
    doc.LoadFromFile(inputFileName);

    // Create a new PDF document to draw content onto
    let newDoc = new pdfModule.PdfNewDocument();
    newDoc.CompressionLevel = pdfModule.PdfCompressionLevel.None;

    // Iterate through each page in the original document
    for (let i = 0; i < doc.Pages.Count; i++) {
      let page = doc.Pages.get_Item(i);
      // Get the current page size
      let size = page.Size;

      // Add a new page with the same size and no margins
      let newPage = newDoc.Pages.Add({ size: size, margins: new pdfModule.PdfMargins() });

      // Draw the original page content onto the new page
      let template = page.CreateTemplate();
      let layoutWidget = new pdfModule.PdfLayoutWidget(template.H);
      layoutWidget.Draw({ page: newPage, x: 0, y: 0 });
      // page.CreateTemplate().Draw({page: newPage, x: 0, y: 0}); 
    }

    // Define the output file name
    const outputFileName = "PDFAToPdf_result.pdf";

    // Save the document to the specified path
    newDoc.Save(outputFileName);

    // Read the generated file from VFS and trigger 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);

    // Release resources
    newDoc.Dispose();
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert PDF/A To Normal PDF</h1>
      <button onClick={convertToNormalPDF}>
        Generate
      </button>
    </div>
  );
}

export default App;

Standard PDF output generated by creating a new document and copying pages

Standard PDF output generated by creating a new document and copying pages


FAQ

Converted PDF/A file size is much larger than the original

PDF/A requires all fonts used in the document to be fully embedded to ensure correct rendering on any device. If the original document uses non-embedded system fonts, the font data will be written completely into the output file during conversion, resulting in a larger file size. This is an inherent requirement of PDF/A compliance. To minimize file size, consider using font subsetting (embedding only the characters actually used) or compressing image content before generating the source PDF.

Can encrypted PDFs be converted to PDF/A?

Encrypted PDFs that require a password to open cannot be processed directly by PdfStandardsConverter. The password must be provided when loading the document.

The PdfStandardsConverter constructor supports a password parameter for converting password-protected PDFs to PDF/A:

// Create PdfStandardsConverter with password
let converter = new pdfModule.PdfStandardsConverter({ filePath: inputFileName, password: "123456" });
converter.ToPdfA2A({ filePath: outputFileName });
converter.Dispose();

"File not found" or "Invalid PDF format" error when loading PDF/A

PDF/A documents must first be converted via PdfStandardsConverter, or properly loaded into the virtual file system (VFS) via FetchFileToVFS. Common mistakes include passing the wrong file name or path, or executing subsequent operations before the file has finished loading. Verify that the file has been loaded into VFS via FetchFileToVFS and that the file name (including extension) matches exactly. Use await to ensure the file is ready before proceeding.


Get a Free License

Spire.PDF for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

Bookmarks are invisible positioning markers in Word documents that act as coordinates, precisely marking a location or a range of text. But the true value of bookmarks goes beyond positioning—by programmatically retrieving content within a bookmark range, replacing placeholder text, removing unwanted content, or inserting text, paragraphs, tables, and images at bookmark positions, developers can implement advanced document processing workflows such as automatic contract template filling, dynamic report data injection, and batch form content cleanup. The combination of "read, write, delete, and insert" operations around bookmark content forms the core of Word automation.

Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, handling bookmark content retrieval, replacement, deletion, and element insertion directly — all managed through a virtual file system (VFS) with no backend server required.

This article covers four core features:

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.


Get Bookmark Content

Getting bookmark content is the prerequisite for any bookmark operation. After locating a bookmark with BookmarksNavigator, the GetBookmarkContent method returns the content within the bookmark range as a TextBodyPart object, which developers can iterate through its BodyItems collection to retrieve elements.

function App() {
  const bookmarkContent = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'ContractTemplate_en.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Create a BookmarksNavigator and move to the bookmark
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("myBookmark");
    let textBodyPart = navigator.GetBookmarkContent();

    // Iterate through elements in the bookmark content and extract text
    let text = "";
    for (let i = 0; i < textBodyPart.BodyItems.Count; i++) {
      let item = textBodyPart.BodyItems.get_Item(i);
      if (item instanceof docModule.Paragraph) {
        for (let j = 0; j < item.ChildObjects.Count; j++) {
          let childObject = item.ChildObjects.get_Item(j);
          if (childObject instanceof docModule.TextRange) {
            text += childObject.Text;
          }
        }
      }
    }

    // Save as a .txt file
    const outputFileName = "GetBookmarkContent.txt";

    // Write the text file to VFS and trigger download
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, text);

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);

    // Release resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get Bookmark Content from Word Document</h1>
      <button onClick={bookmarkContent}>
        Generate
      </button>
    </div>
  );
}

export default App;

Executing the code above extracts the text content from the bookmark "myBookmark" and saves it as a separate .txt file:

Extracted bookmark text content


Replace Bookmark Content

Replacing bookmark content is the most common operation in document template filling. After locating a bookmark with BookmarksNavigator, the ReplaceBookmarkContent method supports replacement with both plain text and complex elements like tables, making it ideal for placeholder replacement in contract generation, report filling, and similar scenarios.

function App() {
  const replaceBookmarkContent = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load fonts and Word file into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'BookmarkSample.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Create a BookmarksNavigator and move to the bookmark
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("Bookmark1");

    // Replace the content of "书签1" — with text
    navigator.ReplaceBookmarkContent({ text: "This is the text that will replace the bookmark.", saveFormatting: true });

    // Continue to replace "书签2" — with a table
    navigator.MoveToBookmark("Bookmark2");

    // Create a table
    let table = new docModule.Table(doc, true);
    table.ResetCells(4, 5);

    // Create data and fill it into the table
    let dt = [
      ["City", "Province", "Population", "Area (km²)", "Abbrev."],
      ["Beijing", "Beijing", "21.89M", "16410", "BJ"],
      ["Shanghai", "Shanghai", "24.75M", "6340", "SH"],
      ["Guangzhou", "Guangdong", "18.67M", "7434", "GZ"]];
    for (let i = 0; i < 4; i++) {
      for (let j = 0; j < 5; j++) {
        table.Rows.get_Item(i).Cells.get_Item(j).AddParagraph().AppendText(dt[i][j]);
      }
    }

    // Create a TextBodyPart instance and add the table to it
    let part = new docModule.TextBodyPart({ doc: doc });
    part.BodyItems.Add(table);

    // Replace the current bookmark content with the TextBodyPart
    navigator.ReplaceBookmarkContent({ bodyPart: part });

    // Save as a new .docx file
    const outputFileName = "ReplaceBookmark.docx";
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

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

    // Release resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Replace Bookmark Content in Word</h1>
      <button onClick={replaceBookmarkContent}>
        Generate
      </button>
    </div>
  );
}

export default App;

This method supports replacing bookmark content with plain text or complex elements like tables. The bookmark marker itself is preserved after replacement, making it easy to locate again later. The figure below shows the result:

Bookmark content replaced with text and table


Delete Bookmark Content

Deleting bookmark content and removing a bookmark marker are two different operations. After locating a bookmark with BookmarksNavigator, calling DeleteBookmarkContent removes the text content within the bookmark range while preserving the bookmark marker itself for later refilling. If you only need to clear the content while keeping the positioning marker, this method is the preferred choice.

function App() {
  const deleteBookmarkContent = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'ContractTemplate_en.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Create a BookmarksNavigator and move to the bookmark
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("myBookmark");

    // Delete bookmark content, keep the bookmark marker
    navigator.DeleteBookmarkContent(true);

    // Save as a new .docx file
    const outputFileName = "RemoveBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

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

    // Release resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete Bookmark Content in Word</h1>
      <button onClick={deleteBookmarkContent}>
        Generate
      </button>
    </div>
  );
}

export default App;

DeleteBookmarkContent removes only the text content within the bookmark range — the bookmark marker itself remains. Bookmarks.Remove, on the other hand, removes the bookmark marker, leaving the text within the range unaffected.

After execution, the text within the bookmark "myBookmark" is removed, but the bookmark marker stays in the document:

Bookmark content deleted — marker retained, content cleared


Insert Text, Paragraphs, Tables, and Images at a Bookmark

Spire.Doc supports flexibly inserting various types of document elements at bookmark positions. It provides InsertText, InsertParagraph, and InsertTable methods for inserting text, paragraphs, and tables. Elements can also be inserted based on the index of the bookmark start node within the paragraph's ChildObjects collection.

function App() {
  const insertElementsAtBookmark = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load fonts and Word file into VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'BookmarkSample1.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document object and load the file
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Move to the bookmark position
    let navigator = new docModule.BookmarksNavigator(doc);
    navigator.MoveToBookmark("Bookmark1");

    // 1. Insert text
    navigator.InsertText("This is the inserted text content.", true);

    // 2. Insert paragraph
    let newParagraph = new docModule.Paragraph(doc);
    newParagraph.AppendText("This is the inserted paragraph content.")
    navigator.MoveToBookmark("Bookmark2");
    navigator.InsertParagraph(newParagraph);

    // 3. Insert table — 2 rows, 3 columns
    let table = new docModule.Table(doc, true);
    table.ResetCells(2, 3);
    table.Rows.get_Item(0).Cells.get_Item(0).AddParagraph().AppendText("Name");
    table.Rows.get_Item(0).Cells.get_Item(1).AddParagraph().AppendText("Quantity");
    table.Rows.get_Item(0).Cells.get_Item(2).AddParagraph().AppendText("Note");
    table.Rows.get_Item(1).Cells.get_Item(0).AddParagraph().AppendText("Product A");
    table.Rows.get_Item(1).Cells.get_Item(1).AddParagraph().AppendText("100");
    table.Rows.get_Item(1).Cells.get_Item(2).AddParagraph().AppendText("In Stock");

    navigator.MoveToBookmark("Bookmark3");
    navigator.InsertTable(table);

    // 4. Insert image
    const imageFileName = 'pic.png';
    await window.spire.FetchFileToVFS(imageFileName, '', `${process.env.PUBLIC_URL}/data/`);
    let picture = new docModule.DocPicture(doc);
    picture.LoadImage(imageFileName);
    picture.Width = 100;
    picture.Height = 200;

    navigator.MoveToBookmark("Bookmark4");
    // Get the bookmark start node
    let start = navigator.CurrentBookmark.BookmarkStart;
    // Get the paragraph containing the bookmark
    let bookmarkPara = start.OwnerParagraph;
    // Get the index of the bookmark start node in the paragraph
    let startIndex = bookmarkPara.ChildObjects.IndexOf(start);
    // Insert the image after the bookmark start node
    bookmarkPara.ChildObjects.Insert(startIndex + 1, picture);

    // Save as a .docx file
    const outputFileName = "InsertToBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

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

    // Release Document resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Insert Elements at Bookmark Position</h1>
      <button onClick={insertElementsAtBookmark}>
        Generate
      </button>
    </div>
  );
}

export default App;

The figure below shows the generated document with text, paragraph, table, and image inserted at bookmark positions:

Text, paragraph, table, and image inserted at bookmark positions


FAQ

Formatting (font, size, color) is lost after replacing bookmark content — how to keep it?

ReplaceBookmarkContent replaces with plain text by default, discarding the original formatting. To preserve the bookmark's existing formatting, pass saveFormatting: true:

navigator.ReplaceBookmarkContent({ text: "New content", saveFormatting: true });

The replacement text will then inherit the original font, size, color, and other formatting from the bookmark.

How to batch process multiple bookmarks in a document?

Iterate through the doc.Bookmarks collection, locating and operating on each bookmark one by one:

for (let i = 0; i < doc.Bookmarks.Count; i++) {
    let bookmark = doc.Bookmarks.get_Item(i);
    navigator.MoveToBookmark(bookmark.Name);
    // Perform replace, delete, or insert operations
}

What's the difference between DeleteBookmarkContent and removing a bookmark marker?

  • DeleteBookmarkContent: Clears only the content within the bookmark range. The bookmark marker stays in the document, so you can still locate it by name and fill in new content later.
  • Bookmarks.Remove: Removes the bookmark marker itself. The content within the bookmark range is unaffected, but the bookmark name disappears and can no longer be located.

Choose the appropriate operation based on your needs: use DeleteBookmarkContent if you need to keep the "placeholder" capability, or remove the marker if the bookmark is no longer needed.

When inserting multiple elements at the same bookmark, why does only the last one take effect?

Methods like InsertText, InsertParagraph, and InsertTable insert based on the bookmark's current position. When inserting multiple times at the same bookmark, subsequent insertions may overwrite or shift previously inserted content. It is recommended to use separate bookmarks for each insertion, or re-locate the bookmark after each insert before proceeding with the next operation.


Get a Free License

Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

A bookmark is like an invisible "anchor" in a Word document, able to accurately locate a specific position or selected text. Whether it's a fill-in area in a contract template, a key section to jump to in a long document, or a data insertion point when generating reports in batch, bookmarks are the critical anchor behind these operations. Developers can use bookmarks for dynamic content filling, navigation, content extraction, and other advanced features, making bookmark management one of the most commonly used capabilities in Word automation.

Spire.Doc for JavaScript runs entirely in the browser via WebAssembly, handling bookmark creation, navigation, and deletion directly — all managed through a virtual file system (VFS) with no backend server required.

This article covers three core features:

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.


Add a Bookmark to a Paragraph

To add a bookmark in an existing document, use AppendBookmarkStart and AppendBookmarkEnd to mark the bookmark region on a paragraph. You can add bookmark markers to existing paragraphs or append a new paragraph with a bookmark. Spire.Doc also supports nested bookmarks for building hierarchical structures.

function App() {
  const createBookmarkInWord = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

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

    // Load the Word file into VFS
    const inputFileName = 'ChinaTravelGuide.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Get the first section and add bookmarks
    let section = doc.Sections.get_Item(0);

    AddBookmark(section);

    // Save as a .docx file
    const outputFileName = "AddBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

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

    // Release resources
    doc.Dispose();
  };

  function AddBookmark(section) {
    // Bookmark 1: add bookmark markers around existing paragraphs
    let paraStart = section.Paragraphs.get_Item(1); 
    let paraEnd = section.Paragraphs.get_Item(3); 

    paraStart.AppendBookmarkStart("Bookmark1");  
    paraEnd.AppendBookmarkEnd("Bookmark1"); 

    // Bookmark 2: add a new paragraph with a bookmark
    let paragraph = section.AddParagraph(); 
    paragraph.AppendBookmarkStart("Bookmark2"); 
    paragraph.AppendText("This is a new paragraph");
    paragraph.AppendBookmarkEnd("Bookmark2"); 
  }

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Bookmark in Word</h1>
      <button onClick={createBookmarkInWord}>
        Generate
      </button>
    </div>
  );
}

export default App;

Bookmarks added to the generated Word document

Bookmarks added to the generated Word document


Add a Bookmark to Selected Text

To add a bookmark to specific text within an existing paragraph, first locate the text with FindAllString, create bookmark objects using the BookmarkStart and BookmarkEnd constructors, then insert the start marker before and the end marker after the matched TextRange via ChildObjects.Insert.

function App() {
  const addBookmarkForMatchedText = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'ChinaTravelGuide.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Find all occurrences of "Street" in the document
    let textSelections = doc.FindAllString('Street', false, true);

    // Iterate over each match and insert bookmark start/end markers
    for (let i = 0; i < textSelections.length; i++) {

      // Create bookmark start and end objects (named "Bookmark_0", "Bookmark_1", ...)
      let start = new docModule.BookmarkStart(doc, "Bookmark_" + i);
      let end = new docModule.BookmarkEnd(doc, "Bookmark_" + i);

      let selection = textSelections[i];

      // Get the TextRange of the matched text
      let textRange = selection.GetAsOneRange();

      // Get the paragraph containing the matched text
      let para = textRange.OwnerParagraph;

      // Get the index of the TextRange within the paragraph's child objects
      let index = para.ChildObjects.IndexOf(textRange);

      // Insert the bookmark start before the TextRange and the bookmark end after it
      para.ChildObjects.Insert(index, start);
      para.ChildObjects.Insert(index + 2, end);
    }

    // Save as a new .docx file
    const outputFileName = "AddBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

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

    // Release document resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Bookmarks for Specific Text in Word Documents</h1>
      <button onClick={addBookmarkForMatchedText}>
        Generate
      </button>
    </div>
  );
}

export default App;

This approach is ideal for scenarios where you need to add positioning markers on top of an existing document, such as marking fill-in areas in a completed contract. The figure below shows the result after execution:

Bookmarks added for specific text in the generated Word document


Remove a Bookmark

Removing a bookmark only removes the bookmark markers themselves — the text content within the bookmark range is preserved. Retrieve the bookmark object from the document.Bookmarks collection, then call the Remove method to delete it.

function App() {
  const deleteBookmark = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the WASM module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Word file into VFS
    const inputFileName = 'AddBookmark.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

    // Load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile(inputFileName);

    // Get the bookmark by name
    let bookmark = doc.Bookmarks.get_Item("Bookmark_1");

    // // Get the bookmark by index
    // let bookmark = doc.Bookmarks.get_Item(0);

    // Remove the bookmark (keep its content)
    doc.Bookmarks.Remove(bookmark);

    // Save as a new .docx file
    const outputFileName = "DeleteBookmark.docx";

    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });

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

    // Release document resources
    doc.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Delete Bookmark in Word Document</h1>
      <button onClick={deleteBookmark}>
        Generate
      </button>
    </div>
  );
}

export default App;

After the bookmark is removed, its markers disappear from the document, but the text within the bookmark range is preserved.

Document after bookmark markers are removed


FAQ

Duplicate bookmark name error

Cause: Bookmark names must be unique within a Word document. Adding a bookmark with a duplicate name causes an error.

Solution: Check whether the name already exists before adding the bookmark:

if (document.Bookmarks.FindByName("MyBookmark") === null) {
    paragraph.AppendBookmarkStart("MyBookmark");
    paragraph.AppendText("Content");
    paragraph.AppendBookmarkEnd("MyBookmark");
}

What is the difference between removing a bookmark and deleting its content?

Cause: Spire.Doc's Bookmarks.Remove only removes the bookmark markers (start and end), leaving the text content between them untouched.

Solution: Choose the appropriate operation based on your needs:

// Remove only the bookmark markers, keep the text
document.Bookmarks.Remove(bookmark);

// Remove the bookmark and its content (via BookmarksNavigator)
let navigator = new docModule.BookmarksNavigator(doc);
navigator.MoveToBookmark("MyBookmark");
navigator.DeleteBookmarkContent();

Do AppendBookmarkStart and AppendBookmarkEnd have to be on the same paragraph?

Cause: The start and end markers can be on different paragraphs — the "Bookmark1" example in the code above demonstrates cross-paragraph usage. The key constraint is that the document object structure within the bookmark range must remain intact. Bookmarks cannot span across table cells, since cells are independent containers and doing so may cause the bookmark to be unrecognized.

Solution: If the bookmark range crosses a table cell boundary, adjust the start or end position so that the bookmark closes within the same cell.


Get a Free License

Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.

Exporting Excel worksheets and charts to SVG vector graphics lets you display data clearly at any resolution on the web, while keeping text selectable and searchable. Spire.XLS for JavaScript runs entirely in the browser via WebAssembly, using a virtual file system (VFS) to manage input and output files — no backend server required.

This article covers two core features:

For installation and project setup, refer to Integrating Spire.XLS for JavaScript in a React Project. The examples below assume Spire.XLS is installed and the WebAssembly module is initialized.


Worksheet to SVG

Converting a worksheet to SVG involves three steps: first, load the font files and the target Excel file into the WASM virtual file system via FetchFileToVFS; then instantiate a Workbook, load the file, retrieve the target worksheet, and call ToSVGStream to render it into a Stream object; finally, read the generated SVG file from VFS, wrap it as a Blob, and trigger a browser download.

function App() {
  const sheetToSVG = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

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

    // Load fonts and the Excel file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ImageHeaderFooter.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook and get the first worksheet
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });
    const sheet = workbook.Worksheets.get(0);

    // Convert the worksheet to an SVG stream
    const outputFileName = "Worksheet.svg";
    let fs = new xlsModule.Stream(outputFileName);
    sheet.ToSVGStream(fs, 0, 0, 0, 0);
    fs.Flush();
    fs.Dispose();

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

    // Release resources
    workbook.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert Excel To SVG</h1>
      <button onClick={sheetToSVG}>
        Generate
      </button>
    </div>
  );
}

export default App;

SVG output generated from a worksheet via ToSVGStream

SVG output generated from a worksheet via ToSVGStream


ChartSheet to SVG

A ChartSheet is a special type of worksheet that contains an embedded chart instead of cell data. The conversion process is similar to worksheet-to-SVG, with two key differences: retrieve the chartsheet by name using GetChartSheetByName("Chart1") instead of by index; and call ToSVGStream(fs) without specifying cell range parameters, since the rendering area is determined by the chart itself.

function App() {
  const chartsheetToSVG = async () => {
    // Get the Spire.XLS WASM module
    const xlsModule = window.wasmModule?.spirexls;

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

    // Load fonts and the Excel file into VFS
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ChartSheet.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile({ fileName: inputFileName });

    // Get the chartsheet by name
    let cs = workbook.GetChartSheetByName("Chart1");

    // Define the output file name
    const outputFileName = 'ChartSheetToSVG-out.svg';

    // Create a stream and convert the chartsheet to SVG
    const fs = new xlsModule.Stream(outputFileName);
    cs.ToSVGStream(fs);
    fs.Flush();

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

    // Release resources
    workbook.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert Chartsheet To SVG</h1>
      <button onClick={chartsheetToSVG}>
        Generate
      </button>
    </div>
  );
}

export default App;

SVG output generated from a chartsheet via ToSVGStream

SVG output generated from a chartsheet via ToSVGStream


SVG vs PNG Comparison

Feature SVG PNG
Scaling Quality Sharp at any zoom Blurry when enlarged
Text Selectable and searchable Rasterized (flat image)
File Size Small (a few KB) Large at high resolutions
CSS Styling Supports inline styles Not supported
Post-processing Editable in Illustrator, Inkscape Requires pixel-level editing
Browser Embedding <img> or <embed> <img> tag

Recommendation: Use SVG for web reports or scenarios where selectable text matters; use PNG when compatibility with image editors or legacy systems is required.

FAQ

Missing or garbled SVG text

Cause: The required font files are not present in the WASM virtual file system. ToSVGStream reads fonts from VFS when rendering text — if fonts are not preloaded, text areas will appear blank or garbled.

Solution: Load the font files into VFS via FetchFileToVFS before conversion:

await window.spire.FetchFileToVFS(
  'ARIAL.TTF', '/Library/Fonts/', '/'
);

SVG file cannot be opened or appears corrupted

Cause: The MIME type is incorrect when creating the Blob, so the browser cannot properly identify the file format.

Solution: Use the correct SVG MIME type:

const blob = new Blob([data], {
  type: "image/svg+xml;charset=utf-8"
});

Get a Free License

Spire.XLS for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.