Numbers stored in Excel cells often need to be displayed in different formats, such as keeping two decimal places, adding thousands separators, displaying amounts as currency, highlighting negative numbers in red, or showing them as percentages or in scientific notation. Proper number formatting not only makes reports easier to read but also avoids errors caused by manual entry and layout. Spire.XLS for JavaScript completes this directly in the browser based on WebAssembly, and manages input/output files through a virtual file system (VFS), with no backend service required.

Spire.XLS for JavaScript sets number formats through the NumberFormat property of the cell range Range. NumberFormat accepts Excel-standard format code strings. The table below lists the commonly used format code symbols and their meanings:

Symbol Description
0 Digit placeholder that always displays a digit, padding with 0 when there are not enough digits
# Digit placeholder that shows only significant digits; extra zeros are not displayed
? Digit placeholder that leaves space for insignificant zeros, used to align decimal points
. Decimal point
, Thousands separator
% Multiplies the number by 100 and displays it as a percentage
E+ / E- Exponent part of scientific notation
; Separates the formats for positive numbers, negative numbers, zeros and text
[Red] and other color codes Sets the display color of the number, e.g. [Red], [Blue], [Green]
[DBNum1] / [DBNum2] Converts numbers to Chinese lowercase / uppercase numerals
@ Text placeholder that displays the number as text

This article covers three core features:

For installation and project configuration, 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.


Set Decimal Places and Thousands Separator

When processing amounts, statistics and other data, it is often necessary to keep numbers to a specified number of decimal places, or to add thousands separators for readability. Presenting the same values with the right format codes makes reports easier to scan and avoids manual padding or misplaced commas.

The steps are:

  1. Load the font file into the VFS for text measurement.
  2. Create a Workbook object and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use the Range.Text property to write the title, column headers, and the format codes used as description text.
  4. Use the Range.NumberValue property to write the values, and set the corresponding format codes with the Range.NumberFormat property.
  5. Use the Workbook.SaveToFile() method to save the workbook, then release resources with Workbook.Dispose().

Here is a complete code example showing how to set number formats with different decimal places and thousands separators for cells in React:

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

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

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

    // Create a new workbook and get the first worksheet
    const workbook = new xlsModule.Workbook();
    const sheet = workbook.Worksheets.get(0);

    // Write the title and column headers
    sheet.Range.get("B1").Text = "Number Formatting Sample";
    sheet.Range.get("B3").Text = "Format Code";
    sheet.Range.get("C3").Text = "Result";

    // Write the format code in column B, write the value in column C and apply the corresponding number format
    sheet.Range.get("B4").Text = "0";
    sheet.Range.get("C4").NumberValue = 1234.5678;
    sheet.Range.get("C4").NumberFormat = "0";

    sheet.Range.get("B5").Text = "0.00";
    sheet.Range.get("C5").NumberValue = 1234.5678;
    sheet.Range.get("C5").NumberFormat = "0.00";

    sheet.Range.get("B6").Text = "#,##0.00";
    sheet.Range.get("C6").NumberValue = 1234.5678;
    sheet.Range.get("C6").NumberFormat = "#,##0.00";

    // Add a background color to the format-code cells and set the column widths
    sheet.Range.get("B4:B6").Style.KnownColor = xlsModule.ExcelColors.Gray25Percent;
    sheet.SetColumnWidth(2, 26);
    sheet.SetColumnWidth(3, 20);

    // Save the document
    const outputFileName = 'NumberFormat_Decimals_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Set Decimal Places and Thousands Separator</h1>
      <button onClick={setDecimalPlacesAndThousands}>
        Start
      </button>
    </div>
  );
}

export default App;

The worksheet after setting decimal places and thousands separator:

Set Decimal Places and Thousands Separator


Set Currency Format and Highlight Negative Values

In financial reports, amounts are usually displayed in currency format, while negative numbers need to be highlighted so they can be spotted quickly. An Excel number format code can consist of several parts separated by semicolons, so positive and negative values in the same column can be displayed differently, and negative values can be colored on their own.

The steps are:

  1. Load the font file into the VFS for text measurement.
  2. Create a Workbook object and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use the Range.Text property to write the title, column headers, and the currency format codes used as description text.
  4. Use the Range.NumberValue property to write the values (including negative numbers), and set the format codes that contain a negative part with the Range.NumberFormat property.
  5. Use the Workbook.SaveToFile() method to save the workbook, then release resources with Workbook.Dispose().

Here is a complete code example showing how to set a currency format for amounts in React and display negative numbers in red:

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

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

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

    // Create a new workbook and get the first worksheet
    const workbook = new xlsModule.Workbook();
    const sheet = workbook.Worksheets.get(0);

    // Write the title and column headers
    sheet.Range.get("B1").Text = "Number Formatting Sample";
    sheet.Range.get("B3").Text = "Format Code";
    sheet.Range.get("C3").Text = "Result";

    // Set the currency format: two decimal places with a thousands separator
    sheet.Range.get("B4").Text = "$#,##0.00";
    sheet.Range.get("C4").NumberValue = 1234.5678;
    sheet.Range.get("C4").NumberFormat = "$#,##0.00";

    // Negative numbers with a thousands separator displayed in red
    sheet.Range.get("B5").Text = "#,##0.00;[Red]-#,##0.00";
    sheet.Range.get("C5").NumberValue = -1234.5678;
    sheet.Range.get("C5").NumberFormat = "#,##0.00;[Red]-#,##0.00";

    // Negative numbers with two decimal places displayed in red
    sheet.Range.get("B6").Text = "0.00;[Red]-0.00";
    sheet.Range.get("C6").NumberValue = -1234.5678;
    sheet.Range.get("C6").NumberFormat = "0.00;[Red]-0.00";

    // Add a background color to the format-code cells and set the column widths
    sheet.Range.get("B4:B6").Style.KnownColor = xlsModule.ExcelColors.Gray25Percent;
    sheet.SetColumnWidth(2, 26);
    sheet.SetColumnWidth(3, 20);

    // Save the document
    const outputFileName = 'NumberFormat_Currency_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Set Currency Format and Highlight Negative Values</h1>
      <button onClick={setCurrencyFormat}>
        Start
      </button>
    </div>
  );
}

export default App;

The worksheet after setting the currency format and highlighting negative values:

Set Currency Format and Highlight Negative Values


Set Percentage, Scientific Notation and Other Formats

Besides the common decimal, thousands separator and currency formats, reports often call for percentages, scientific notation and Chinese uppercase amounts. Percentages make proportions easier to compare, scientific notation suits very large or very small numbers, and Chinese uppercase is used on formal financial documents.

The steps are:

  1. Load the font file into the VFS for text measurement.
  2. Create a Workbook object and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use the Range.Text property to write the title, column headers, and the format codes used as description text.
  4. Use the Range.NumberValue property to write the values, and set the percentage, scientific notation or Chinese uppercase format codes respectively with the Range.NumberFormat property.
  5. Use the Workbook.SaveToFile() method to save the workbook, then release resources with Workbook.Dispose().

Here is a complete code example showing how to set percentage, scientific notation and Chinese uppercase formats for cells in React:

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

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

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

    // Create a new workbook and get the first worksheet
    const workbook = new xlsModule.Workbook();
    const sheet = workbook.Worksheets.get(0);

    // Write the title and column headers
    sheet.Range.get("B1").Text = "Number Formatting Sample";
    sheet.Range.get("B3").Text = "Format Code";
    sheet.Range.get("C3").Text = "Result";

    // Percentage format: the stored value is multiplied by 100 and a percent sign is shown
    sheet.Range.get("B4").Text = "0.00%";
    sheet.Range.get("C4").NumberValue = 0.1234;
    sheet.Range.get("C4").NumberFormat = "0.00%";

    // Scientific notation format
    sheet.Range.get("B5").Text = "0.00E+00";
    sheet.Range.get("C5").NumberValue = 123456789;
    sheet.Range.get("C5").NumberFormat = "0.00E+00";

    // Chinese uppercase format (uppercase for RMB amounts)
    sheet.Range.get("B6").Text = "[DBNum2][$-804]General";
    sheet.Range.get("C6").NumberValue = 1234567;
    sheet.Range.get("C6").NumberFormat = "[DBNum2][$-804]General";

    // Add a background color to the format-code cells and set the column widths
    sheet.Range.get("B4:B6").Style.KnownColor = xlsModule.ExcelColors.Gray25Percent;
    sheet.SetColumnWidth(2, 26);
    sheet.SetColumnWidth(3, 20);

    // Save the document
    const outputFileName = 'NumberFormat_Special_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Set Percent, Scientific Notation and Other Formats</h1>
      <button onClick={setSpecialNumberFormat}>
        Start
      </button>
    </div>
  );
}

export default App;

The worksheet after setting percentage, scientific notation and other special formats:

Set Percentage, Scientific Notation and Other Formats


FAQ

The displayed value does not change after setting the number format

Cause: The value written to the cell is actually stored as text, or the data itself is not numeric, so NumberFormat cannot take effect on a number.

Solution: Make sure the value is written with the Range.NumberValue property (rather than a Text or Value string) before setting the NumberFormat, for example:

sheet.Range.get("C4").NumberValue = 1234.5678;
sheet.Range.get("C4").NumberFormat = "0.00";

The value is enlarged after using the percentage format

Cause: Excel displays a percentage format as "stored value × 100". For example, the stored value 12.34 combined with the 0.00% format is displayed as 1234.00%, not 12.34%.

Solution: To display 12.34%, the stored value should be 0.1234. If the original data was entered as a percentage number, divide it by 100 before setting the format.

Negative numbers are not displayed in red

Cause: When a format code contains only one section, positive numbers, negative numbers and zeros all use that section, so [Red] in it affects every value. The color applies only to negative numbers when it is placed in the negative section.

Solution: Split the positive and negative parts with a semicolon and put [Red] in the negative part, for example 0.00;[Red]-0.00.


Obtain 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.

Word documents have long carried manuals, reports and contracts, while Markdown serves documentation sites, knowledge bases and code repositories as plain text. Moving existing Word content into Markdown is a routine step in documentation migration and content publishing.

If the conversion runs on a server, the file has to be uploaded first, and the original content leaves the user's device. This article performs the conversion in the browser with Spire.Doc for JavaScript: the Word document is read through a virtual file system (VFS), the export result is written back to the VFS, and the page then reads it and triggers the download. Everything runs in the browser as a WebAssembly module, so no Office installation and no backend service are required.

This article covers three features:

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


Convert a Word Document to Markdown

The conversion takes two steps: Document.LoadFromFile() reads the .docx into a document object, and Document.SaveToFile() writes a .md file with FileFormat.Markdown as the target format. The saved result first lands in the VFS and is then read and downloaded by the page.

The document structure is written out with the corresponding syntax; the main mappings are as follows:

Word content Markdown output
Paragraphs with the built-in Heading 1, Heading 2 and other heading styles applied # and ## headings of the matching level
Bold, italic **text**, *text*
Bulleted list - item
Numbered list 1. item
Table Pipe table with a bold header
Image ![](path), with the image written out separately
Hyperlink [text](url)

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the sample document Product Release Notes.docx into the virtual file system (VFS).
  3. Create a Document object and load the document.
  4. Set MarkdownExportOptions.ImagesFolder to images so that the images are written into that folder.
  5. Save the document with SaveToFile() as FileFormat.Markdown, producing Product Release Notes.md.
  6. Read the Markdown text and the image files from the VFS and trigger their browser downloads.

The code for converting a Word document to Markdown is as follows:

// Read the file from VFS and trigger the download
function downloadBlob(blob, fileName) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  a.click();
  URL.revokeObjectURL(url);
}

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the sample document into VFS
    await window.spire.FetchFileToVFS('Product Release Notes.docx', '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document instance and load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile('Product Release Notes.docx', docModule.FileFormat.Docx2016);

    // Write the images into the images folder in VFS and reference them by relative path
    doc.MarkdownExportOptions.ImagesFolder = 'images';

    // Save as Markdown; the result is written into VFS
    const outputFileName = 'Product Release Notes.md';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Markdown });
    doc.Dispose();

    const FS = window.dotnetRuntime.Module.FS;

    // Read the Markdown text and download it (UTF-8 encoding)
    const text = new TextDecoder('utf-8').decode(FS.readFile(outputFileName));
    downloadBlob(new Blob([text], { type: 'text/markdown;charset=utf-8' }), outputFileName);

    // The images are stored separately, so download them as well
    FS.readdir('images')
      .filter((name) => name !== '.' && name !== '..')
      .forEach((name) => {
        const data = FS.readFile(`images/${name}`);
        downloadBlob(new Blob([data], { type: 'image/png' }), name);
      });
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert Word to Markdown</h1>
      <button onClick={convertToMarkdown}>
        Convert and Download
      </button>
    </div>
  );
}

export default App;

Once the converted Markdown is rendered in a browser, the headings, lists, table, hyperlink and image all appear in their corresponding syntax:

Markdown generated from the Word document, rendered in a browser

Markdown generated from the Word document, rendered in a browser

Save Images to a Specified Folder

When ImagesFolder is not set, images are written into the root of the VFS under a name prefixed with the output file name, for example Product Release Notes_001.png, and the Markdown references them as /Product Release Notes_001.png. That form makes the files easy to locate directly, but the reference carries a root prefix. Once ImagesFolder is set to a relative folder, the images are written into that folder in the VFS and the references become relative paths:

doc.MarkdownExportOptions.ImagesFolder = 'images';

The conversion result then contains references such as images/Product Release Notes_001.png. The images have to be downloaded together with the .md file; the page can list the folder content with FS.readdir() and read and download each file, as shown in the example above. If the result should be a single file, use the Base64 approach described in the next section instead.


Embed Images as Base64

With MarkdownExportOptions.ImagesAsBase64 = true, the images in the document are no longer written out separately; they are encoded as data URIs and placed directly inside the brackets of ![](). The result is a single .md file, so image paths never break when the file is copied, uploaded or committed to a repository.

The trade-off is file size: Base64-encoded data is about 4/3 the size of the original image. With many images or high-resolution images, the size of a single Markdown file grows noticeably.

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the sample document Product Release Notes.docx into the virtual file system (VFS).
  3. Create a Document object and load the document.
  4. Set MarkdownExportOptions.ImagesAsBase64 to true.
  5. Save the document with SaveToFile() as FileFormat.Markdown, read the text from the VFS and trigger the download.

The code for embedding images as Base64 is as follows:

function downloadBlob(blob, fileName) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  a.click();
  URL.revokeObjectURL(url);
}

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the sample document into VFS
    await window.spire.FetchFileToVFS('Product Release Notes.docx', '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document instance and load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile('Product Release Notes.docx', docModule.FileFormat.Docx2016);

    // Write the images into the Markdown as inline Base64
    doc.MarkdownExportOptions.ImagesAsBase64 = true;

    // Save the result file
    const outputFileName = 'Product Release Notes - Inline Images.md';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Markdown });
    doc.Dispose();

    // Read the Markdown text and download it; no separate image download is needed
    const text = new TextDecoder('utf-8').decode(window.dotnetRuntime.Module.FS.readFile(outputFileName));
    downloadBlob(new Blob([text], { type: 'text/markdown;charset=utf-8' }), outputFileName);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Embed Images as Base64</h1>
      <button onClick={convertWithBase64}>
        Convert and Download
      </button>
    </div>
  );
}

export default App;

After embedding, the image appears in the Markdown as a long string starting with data:image/png;base64,, and no separate image file accompanies the document:

Image line in the Markdown after the image is embedded as Base64

Image line in the Markdown after the image is embedded as Base64


Customize the Markdown Export Options

Document.MarkdownExportOptions controls how the Markdown is written. Beyond image handling, lists, links, tables, underlines and equations can each be adjusted separately:

Option Common values Effect
ImagesFolder A folder name Images are written into the specified folder in the VFS and referenced from it
ImagesAsBase64 true / false Images are embedded as data URIs
ListOutputMode MarkdownSyntax / PlainText Emit list syntax, or escape the list markers as plain text
LinkOutputMode Auto / Inline / Reference Inline links, or reference-style links
SaveAsHtml None / Tables Keep tables in pipe syntax, or output them as HTML tables
SaveUnderlineFormatting true / false Whether underlines are kept as ++text++
OfficeMathOutputMode Text / Image / MathML The output form of equations

When SaveAsHtml is set to Tables, the export needs fonts to lay out the tables; if no fonts are loaded, saving throws Cannot find any fonts in specified font sources. The sample therefore loads the fonts into the VFS before converting. This step is not needed when only text, lists and images are exported.

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the fonts msyh.ttc and msyhbd.ttc and the sample document into the virtual file system (VFS).
  3. Create a Document object and load the Word document.
  4. Get MarkdownExportOptions and set the image folder, list, link, table and underline options in turn.
  5. Save the result file with SaveToFile() as FileFormat.Markdown.
  6. Read the Markdown text and the image files from the VFS and trigger their browser downloads.

The code for customizing the Markdown export options is as follows:

function downloadBlob(blob, fileName) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  a.click();
  URL.revokeObjectURL(url);
}

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Exporting HTML tables needs fonts for layout, so load the fonts into VFS first
    await window.spire.FetchFileToVFS('msyh.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    await window.spire.FetchFileToVFS('msyhbd.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Load the sample document into VFS
    await window.spire.FetchFileToVFS('Product Release Notes.docx', '', `${process.env.PUBLIC_URL}/data/`);

    // Create a Document instance and load the Word document
    const doc = new docModule.Document();
    doc.LoadFromFile('Product Release Notes.docx', docModule.FileFormat.Docx2016);

    // Customize the Markdown export options
    const options = doc.MarkdownExportOptions;
    options.ImagesFolder = 'images';
    options.ListOutputMode = docModule.MarkdownListOutputMode.PlainText;
    options.LinkOutputMode = docModule.MarkdownLinkOutputMode.Reference;
    options.SaveAsHtml = docModule.MarkdownSaveAsHtml.Tables;
    options.SaveUnderlineFormatting = true;

    // Save the result file
    const outputFileName = 'Product Release Notes - Export Options.md';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Markdown });
    doc.Dispose();

    const FS = window.dotnetRuntime.Module.FS;

    // Download the Markdown text
    const text = new TextDecoder('utf-8').decode(FS.readFile(outputFileName));
    downloadBlob(new Blob([text], { type: 'text/markdown;charset=utf-8' }), outputFileName);

    // Download the written images
    FS.readdir('images')
      .filter((name) => name !== '.' && name !== '..')
      .forEach((name) => downloadBlob(new Blob([FS.readFile(`images/${name}`)], { type: 'image/png' }), name));
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Customize the Markdown Export Options</h1>
      <button onClick={convertWithOptions}>
        Convert and Download
      </button>
    </div>
  );
}

export default App;

With these options in effect, lists, links, tables and underlines are each written in a different form. Part of the output is as follows:

\- Generation time for a single document dropped from 6 minutes to under 10 seconds.
\- Contract and payroll files no longer pass through a server.
\- Exported page counts match the on-screen preview.
\1. Replace the WebAssembly resource bundle in the frontend project.
\2. Clear the browser cache and reload the page.
\3. Check the conversion results with the sample document.

The frontend calls the document interfaces through a WebAssembly module. The full interface reference is available in [the online document platform API reference] , and the sample project ships with this release.

Read the ++compatibility notes++ before upgrading and confirm that the styles of existing documents are unaffected.

[the online document platform API reference]: https://www.e-iceblue.com/
[ref1]: images/Product Release Notes - Export Options_001.png

Set the Output Style of Lists and Links

ListOutputMode defaults to MarkdownSyntax, in which bullets and numbers are written as - and 1.; with PlainText, a backslash is placed before the marker, as in \- and \1., so the text renders as plain content and no longer forms a list.

LinkOutputMode defaults to Auto, in which hyperlinks are written in the inline form [text](url); with Reference, only [text] remains in the body text, and the actual addresses are collected in the link definitions at the end of the file — images likewise switch to ![ref1] plus a definition line. When a document has many links whose addresses need to be maintained in one place, the reference style is easier to read.

const options = doc.MarkdownExportOptions;
options.ListOutputMode = docModule.MarkdownListOutputMode.PlainText;
options.LinkOutputMode = docModule.MarkdownLinkOutputMode.Reference;

Save Tables as HTML

SaveAsHtml defaults to None, in which tables are written in pipe syntax with the header text in bold; with Tables, tables are output with <table> tags, the cell styles are written inline, and column widths, borders and text formatting are preserved. The output is a single line of HTML and is no longer bound by the limits of pipe syntax.

Two points are worth noting: first, the option requires fonts to be loaded into the VFS, otherwise saving reports an error; second, if a heading follows a table directly, some Markdown parsers merge the heading line into the table — leaving one empty paragraph after the table in Word avoids this.

options.SaveAsHtml = docModule.MarkdownSaveAsHtml.Tables;

Keep Underline Formatting

Markdown has no standard syntax for underlines, so the default export drops the underlines of the Word document. With SaveUnderlineFormatting = true, underlined text is written as ++text++. That notation is an extension syntax; whether the target platform renders it as an underline depends on the parser in use.

options.SaveUnderlineFormatting = true;

FAQ

Why are the image files missing from the converted Markdown

Cause: By default, images are written out separately from the .md file, in the root of the VFS under a name prefixed with the output file name. If the page only reads and downloads the .md file, the images naturally do not appear in the download.

Solution: Set ImagesFolder to name an image folder and download the files in that folder together with the .md file; alternatively set ImagesAsBase64 = true, so that the images are written into the Markdown itself and the result is a single file.

Why has the underline in Word disappeared from the Markdown

Cause: Markdown has no standard syntax for underlines, and SaveUnderlineFormatting defaults to false, so the underline formatting is dropped during export.

Solution: Set SaveUnderlineFormatting = true and underlined text is written as ++text++. Not every Markdown parser supports that syntax, so it is worth checking how the target platform renders it before relying on it.

Why were the headings not converted to # headings

Cause: Headings are recognized by style during export; only paragraphs with a built-in heading style such as Heading 1 or Heading 2 applied become # headings of the matching level. Paragraphs that are merely bold or set to a larger font size, or that use the Title style, are still output as ordinary paragraphs.

Solution: Apply a built-in heading style to the paragraphs that should become headings; a document title that uses the Title style can be changed to Heading 1, or the # can be added manually in the Markdown after conversion.

What should I do when a heading after a table is merged into the table

Cause: When there is no blank line between a table and the heading that follows it, some Markdown parsers treat the adjacent heading line as one more row of the table.

Solution: Leave one empty paragraph after the table in Word before writing the heading, so that the exported Markdown contains a blank line; alternatively add a blank line between the table and the heading after conversion.

Does the conversion upload the file to a server

Not in the usage described in this article. Spire.Doc for JavaScript runs in the browser on WebAssembly, the Word document and the conversion result are both read and written locally through the virtual file system (VFS), and the library itself does not send files to a server. Whether a file ultimately leaves the device depends on other code added to the page.


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.

Technical documentation, API references and release notes are increasingly maintained in Markdown: it is easy to keep in a code repository, easy to review, and easy to compare between versions. For external delivery, however, the recipient usually needs a Word or PDF document. Copying and pasting into a desktop application loses the formatting, and tables and code blocks have to be rearranged by hand; converting on a server takes the source file off the user's device.

This article converts Markdown to Word documents and PDF documents in the browser with Spire.Doc for JavaScript. The library runs in the browser as a WebAssembly module, the source file is read through a virtual file system (VFS), and the conversion result is written back to the VFS before the download is triggered. The whole process requires neither an Office installation nor any backend service.

This article covers three features:

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume that Spire.Doc is installed and the WebAssembly module is initialized. The sample Markdown source and the template document are placed in the public/data/ directory, and the font files in the public/static/font/ directory. To make them easy to run directly, the code for each feature is a complete single file, so the content of App.js can be replaced with it wholesale.


Convert a Markdown Document to Word

Spire.Doc for JavaScript loads the source file with Document.LoadFromFile(), and the second parameter specifies the file format. Pass FileFormat.Markdown when loading Markdown: the library parses headings, paragraphs, lists, tables, quotes, code blocks and images according to Markdown syntax rules, and turns them into the corresponding Word document objects.

The syntax used in the source file and the styles applied after conversion correspond as follows:

Markdown syntax Style applied after conversion Notes
# to ###### Heading 1 to Heading 6 The six heading levels map to the six built-in heading styles respectively
Ordinary paragraphs Normal Paragraph text and inline formatting such as bold and italic are kept
> quotes Quote Converted to an indented quote paragraph
- lists, 1. lists List paragraphs Bulleted and numbered lists become automatically numbered items
Pipe tables Word tables The first row becomes the header, and cells carry thin borders
Backtick inline code InlineCode A character-level style that affects only the marked text
Fenced code blocks FencedCode plus the language name A code block marked js, for example, is given FencedCodejs
Indented code blocks FencedCode Code blocks without a language label

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the Markdown source file into the virtual file system (VFS).
  3. Create a Document instance and load the source file with LoadFromFile() as FileFormat.Markdown.
  4. Save the document with SaveToFile() as a DOCX file.
  5. Read the result file from the VFS and trigger its browser download.

The code for converting a Markdown document to a Word document is as follows:

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Markdown source and the images it references into VFS
    await window.spire.FetchFileToVFS('Sample.md', "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('pipeline.png', "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
    await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);

    // Create a Document instance and load the source file as Markdown
    const doc = new docModule.Document();
    doc.LoadFromFile('Sample.md', docModule.FileFormat.Markdown);

    // Save as a Word document
    const outputFileName = 'MarkdownToWord.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2016 });
    doc.Dispose();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert Markdown to Word</h1>
      <button onClick={convertToWord}>
        Convert to Word
      </button>
    </div>
  );
}

export default App;

The converted Word document is shown below; the headings, lists, quotes, tables and code blocks have all been restored according to the structure of the source file:

Word document converted with the default styles

Word document converted with the default styles


Convert a Markdown Document to PDF

Once Markdown has been loaded, the result is a complete Word document object, so the same object can be saved as DOCX or exported directly to PDF without reading the source file again. For export, simply change the fileFormat of SaveToFile() to FileFormat.PDF. The page size and margins follow the section settings of the document, and the pagination is determined by the layout computed during export.

Unlike DOCX, PDF requires the text to be genuinely rasterized, so before export the fonts used by the document should be loaded into the font directory of the virtual file system; the examples place the fonts in /Library/Fonts/.

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the Markdown source file and the font files into the virtual file system (VFS).
  3. Create a Document instance and load the source file with LoadFromFile() as FileFormat.Markdown.
  4. Export the document with SaveToFile() as a PDF file.
  5. Read the result file from the VFS and trigger its browser download.

The code for converting a Markdown document to a PDF document is as follows:

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Markdown source and the fonts into VFS
    await window.spire.FetchFileToVFS('Sample.md', "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('pipeline.png', "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
    await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);

    // Create a Document instance and load the source file as Markdown
    const doc = new docModule.Document();
    doc.LoadFromFile('Sample.md', docModule.FileFormat.Markdown);

    // Export as a PDF document
    const outputFileName = 'MarkdownToPdf.pdf';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.PDF });
    doc.Dispose();

    // Read the generated file from 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>Convert Markdown to PDF</h1>
      <button onClick={convertToPdf}>
        Convert to PDF
      </button>
    </div>
  );
}

export default App;

The PDF document exported from the same Markdown is shown below:

PDF document exported from the same Markdown

PDF document exported from the same Markdown


Use Template Styles to Convert Markdown to Word

The document produced by the default rules uses the built-in styles, whose fonts, color schemes and line spacing are generic in appearance. In actual delivery, a document often has to meet the layout requirements of a company or department: which font size the headings use, which line spacing the body text uses, whether a separator line appears below a heading. These requirements have usually already been organized into a Word template; all that is needed is to copy the styles of the template onto the conversion result.

The current JavaScript version does not provide a method that copies all template styles at once, so the example handles it as follows: first find the style of the same name in the target document, then copy the character formatting and paragraph formatting of the template style onto it item by item. The style names in the template therefore have to match the names of the styles produced by the conversion; the common correspondences are as follows:

Template style name Affected content Settings in the sample template
Normal Body paragraphs Microsoft YaHei 10.5 pt, grey text, 1.35 line spacing
Heading 1 # level-one headings 18 pt bold, dark blue, separator line below
Heading 2 ## level-two headings 14 pt bold, dark green
Heading 3 ### level-three headings 12 pt bold, dark blue
Quote > quote paragraphs 10 pt italic, dark green, left indent

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the Markdown source file and the template document into the virtual file system (VFS).
  3. Create a Document instance and load the source file with LoadFromFile() as FileFormat.Markdown.
  4. Create another Document instance and load the template document, then take its styles by name.
  5. Use the copyStyle() helper to copy the character formatting and paragraph formatting of each template style onto the style of the same name in the target document.
  6. Save the result file with SaveToFile(), then read it from the VFS and trigger the download.

The code for converting Markdown with the template styles is as follows:

// Names of the styles to copy from the template; they must match the styles applied by the Markdown conversion
const TEMPLATE_STYLES = ['Normal', 'Heading 1', 'Heading 2', 'Heading 3', 'Quote'];

// Copy one style from the template document to the style of the same name in the target document
function copyStyle(src, dst) {
  // Character formatting: font, font size, bold, italic, text color
  const sf = src.CharacterFormat;
  const df = dst.CharacterFormat;
  df.FontName = sf.FontName;
  df.FontSize = sf.FontSize;
  df.Bold = sf.Bold;
  df.Italic = sf.Italic;
  df.TextColor = sf.TextColor;

  // Paragraph formatting: spacing before and after, line spacing, indent, alignment and pagination control
  const sp = src.ParagraphFormat;
  const dp = dst.ParagraphFormat;
  dp.BeforeSpacing = sp.BeforeSpacing;
  dp.AfterSpacing = sp.AfterSpacing;
  dp.LineSpacing = sp.LineSpacing;
  dp.LineSpacingRule = sp.LineSpacingRule;
  dp.LeftIndent = sp.LeftIndent;
  dp.FirstLineIndent = sp.FirstLineIndent;
  dp.HorizontalAlignment = sp.HorizontalAlignment;
  dp.KeepFollow = sp.KeepFollow;

  // Paragraph borders: the separator line below a heading
  const sb = sp.Borders.Bottom;
  const db = dp.Borders.Bottom;
  db.BorderType = sb.BorderType;
  db.LineWidth = sb.LineWidth;
  db.Color = sb.Color;
  db.Space = sb.Space;
}

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

    // Make sure the WASM module has fully loaded
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the Markdown source and the template document into VFS
    await window.spire.FetchFileToVFS('Sample.md', "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('Template.docx', "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
    await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);

    // Load the source file as Markdown
    const doc = new docModule.Document();
    doc.LoadFromFile('Sample.md', docModule.FileFormat.Markdown);

    // Load the template document and take only its styles
    const template = new docModule.Document();
    template.LoadFromFile('Template.docx');

    // Copy each template style onto the style of the same name in the target document
    TEMPLATE_STYLES.forEach((name) => {
      const src = template.Styles.FindByName(name);
      const dst = doc.Styles.FindByName(name);
      if (src && dst) {
        copyStyle(src, dst);
      }
    });
    template.Dispose();

    // Save as a Word document
    const outputFileName = 'MarkdownToWordWithTemplate.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2016 });
    doc.Dispose();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert Markdown with Template Styles</h1>
      <button onClick={convertWithTemplate}>
        Convert and Apply Template Styles
      </button>
    </div>
  );
}

export default App;

The first page of the Word document after the template styles are applied is shown below; the headings, body text, quotes and tables all follow the fonts, color scheme and line spacing of the template:

First page of the Word document after applying the template styles

First page of the Word document after applying the template styles


The Sample Markdown File

All three features share the same Sample.md, which is placed in the public/data/ directory. The sample is a product release note covering level-one to level-three headings, bulleted lists, quotes, pipe tables, fenced code blocks, inline code, images, and bold and italic text, so that the conversion result of each syntax in Word can be observed side by side.

Images are referenced by relative path; before conversion, the image files have to be loaded into the virtual file system under the same path as written in the Markdown.

The content of the sample Markdown file is as follows:

# Northstar Platform Release Notes

## Overview

Northstar 2026.4 focuses on **document processing inside the browser**. This release moves template rendering, format conversion and batch output into a single WebAssembly pipeline, so that no source file leaves the user's device.

The release has three goals:

- Reduce the time to generate one document from 6 minutes to under 10 seconds.
- Remove the server from the conversion path for contract and payroll files.
- Keep the output page count identical to the on-screen preview.

## What's New

### Browser-side conversion

All conversion work now runs in the browser through a WebAssembly module. Fonts and templates are loaded on demand through a virtual file system, and the result is written back to the same file system before download.

> Files are processed locally. The platform never stores source documents on the server.

### Supported input formats

| Format | Read | Write | Notes |
|---|---|---|---|
| Markdown | Yes | Yes | Headings, lists, tables, images |
| Word (.docx) | Yes | Yes | Styles and fields preserved |
| HTML | Yes | No | External CSS is ignored |
| PDF | Yes | Yes | Page setup follows the section |

### Markdown pipeline

The Markdown loader maps the source structure onto Word building blocks, and the result is exported from the same document object:

![Markdown to Word pipeline](pipeline.png)

- Headings map onto the built-in Heading 1 to Heading 6 styles.
- Bullet and numbered lists keep their nesting level.
- Pipe tables become Word tables with a header row.

```js
const doc = new Document();
doc.LoadFromFile('Sample.md', FileFormat.Markdown);
doc.SaveToFile('Result.docx', FileFormat.Docx2016);
```

## Upgrade Notes

*Existing styles are kept* when a document is loaded, and two behaviours changed in this release:

- Markdown headings now use the built-in Heading styles instead of direct formatting.
- Table borders default to a hairline, single, light grey line.

## Timeline

| Milestone | Date | Status |
|---|---|---|
| Design review | 2026-02-12 | Done |
| Core development | 2026-03-01 | Done |
| Integration testing | 2026-07-20 | In progress |
| Acceptance | 2026-10-15 | Planned |

The template document Template.docx only provides styles; its body content does not take part in the conversion. The style names in the template have to match the names of the styles produced by the conversion (Normal, Heading 1, Heading 2, Heading 3, Quote); styles whose names do not match are not copied.


FAQ

Why is the first paragraph of the converted document a red line of English text

Cause: This line is an evaluation notice inserted by Spire.Doc for JavaScript when the file is saved. Its content is Evaluation Warning: The document was created with Spire.Doc for JavaScript., and it has nothing to do with the content of the source file.

Solution: The notice exists only in the evaluation version; once a license is applied, the saved file no longer contains this paragraph.

Why do the converted headings and body text not use the font and line spacing I expect

Cause: LoadFromFile() converts Markdown with the built-in styles — headings use Heading 1 to Heading 6 and body text uses Normal — and these styles themselves do not define the fonts and color schemes commonly used in corporate documents.

Solution: Organize the layout requirements into a Word template and copy the styles as described in Use Template Styles to Convert Markdown to Word; alternatively, after loading, modify the CharacterFormat and ParagraphFormat of the corresponding styles in doc.Styles directly.

Why do the entries after the first one in a multi-item ordered list end up merged into the first paragraph

Cause: The parser recognizes ordered lists by paragraph; when there is no blank line between items, the text of the second item onwards is treated as a continuation line of the first, and the number is kept in the paragraph as plain text.

Solution: Leave a blank line between items so that each one becomes an independently numbered list item; bulleted lists are not subject to this restriction and can be written consecutively:

1. First step.

2. Second step.

3. Third step.

Why are the images in the Markdown not displayed, or displayed exceptionally large

Cause: Images are read from the virtual file system by the path written in the Markdown; when the file has not been loaded into the VFS it cannot be embedded. Image size is converted from the original pixel dimensions at 96 dpi, so an image with a large pixel width may exceed the text width on an A4 page.

Solution: Load the image into the VFS first, under the same path as written in the Markdown:

await window.spire.FetchFileToVFS('pipeline.png', "", `${process.env.PUBLIC_URL}/data/`);

To specify a width, write the image as an HTML img tag; the width attribute is written into the document in pixels:

<img src="pipeline.png" width="360">

What should I do when the Chinese font in the exported PDF is wrong, or appears blank or as boxes

Cause: A DOCX records only font names, which Word resolves when the file is opened; PDF requires the text to be rendered as graphics, so when the font file has not been loaded, the export can only use a substitute font or leave the text blank.

Solution: Before exporting to PDF, load the fonts used by the document into the font directory of the virtual file system; the examples place them in /Library/Fonts/. When there are many Chinese fonts, it is advisable to load both the regular and the bold face:

await window.spire.FetchFileToVFS('msyh.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);
await window.spire.FetchFileToVFS('msyhbd.ttc', "/Library/Fonts/", `${process.env.PUBLIC_URL}/static/font/`);

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.

When data is split into categories and subcategories, each carrying a value, rows of a table make it hard to see at a glance how much of the whole each category represents. A treemap nests rectangles by hierarchy and sizes each rectangle by its value, so subcategories of the same category sit side by side and their proportions can be compared directly. Turning such data into a treemap, then adding labels and fill colors, produces a chart that can go straight into a report or a shared document. Spire.XLS for JavaScript creates and renders treemap charts in the browser through WebAssembly, with input and output files managed by a virtual file system (VFS) and no backend service required.

This article covers two features:

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


Create a Treemap Chart

A treemap uses rectangle area to represent values and the nesting of rectangles to represent hierarchy. To build a usable chart, start with three columns, category, item, and value, then set the chart type to tree map and fix the chart position and title. Once the chart is created, saving it in an Excel workbook lets the chart be opened, edited, or shared in spreadsheet software.

How to create a treemap chart and what it produces:

Call Result
window.spire.FetchFileToVFS Loads the font file and the test data file into the VFS
workbook.LoadFromFile({ fileName }) Loads the workbook that holds the two-level data
workbook.Worksheets.get(0) Gets the first worksheet of the workbook
sheet.Charts.Add() Adds a chart object to the worksheet
chart.ChartType = xlsModule.ExcelChartType.TreeMap Sets the chart type to tree map
chart.DataRange = sheet.Range.get("A2:C11") Uses two hierarchy columns and one value column as the chart data
chart.ChartTitle Sets the chart title
chart.TopRow / chart.BottomRow / chart.LeftColumn / chart.RightColumn Sets the position and size of the chart on the worksheet
workbook.SaveToFile({ fileName }) Writes the workbook with the treemap chart into the VFS

The steps are:

  1. Load the font file and the Excel test file into the VFS with window.spire.FetchFileToVFS.
  2. Create a workbook with new Workbook() and load the test file with workbook.LoadFromFile.
  3. Get the first worksheet with workbook.Worksheets.get(0).
  4. Add a chart with sheet.Charts.Add() and set chart.ChartType to xlsModule.ExcelChartType.TreeMap.
  5. Set chart.DataRange to sheet.Range.get("A2:C11"), where the first two columns are the group and the item and the third column decides the rectangle area.
  6. Set chart.ChartTitle, then fix the chart position and size through chart.TopRow, chart.BottomRow, chart.LeftColumn, and chart.RightColumn.
  7. Call workbook.SaveToFile to write CreateTreeMap.xlsx, release the workbook with workbook.Dispose, and read the workbook back from the VFS to trigger the download.

Below is the complete example that creates a treemap chart and saves it in an Excel workbook in React:

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

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

    // Load the font and the test data file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TreeMapData.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 first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Add a chart and set its type to tree map
    const chart = sheet.Charts.Add();
    chart.ChartType = xlsModule.ExcelChartType.TreeMap;

    // Set the data range: the first two columns are the group and the item, the third is the value
    chart.DataRange = sheet.Range.get('A2:C11');

    // Set the position and size of the chart
    chart.TopRow = 1;
    chart.BottomRow = 20;
    chart.LeftColumn = 1;
    chart.RightColumn = 12;

    // Set the chart title
    chart.ChartTitle = 'Area by cities';

    // Save the workbook with the tree map chart into the VFS
    const outputFileName = 'CreateTreeMap.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Dispose of the workbook to release its resources
    workbook.Dispose();

    // Read the result back out of the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Create a Tree Map Chart in React</h1>
      <button id="create-treemap" onClick={createTreeMap}>Create Tree Map</button>
    </div>
  );
}

export default App;

The workbook after creating the treemap chart:

The workbook after creating the treemap chart


Set Treemap Labels and Fill Colors

By default, a treemap is rendered without any data labels, so readers can rely only on the legend to tell the groups apart and cannot see which item each area stands for. Rectangles in the same group also share one color, which makes neighboring areas less distinct. Turning on name labels for the rectangles and then assigning darker fill colors in data point order places each item name directly on its area; the white labels stay legible on the dark fills, and both the group structure and the relative sizes become easy to read.

How to set treemap labels and fill colors and what it produces:

Call Result
chart.Series.get(0).DataPoints Gets the data point collection, one point per rectangle
points.get(i).DataFormat.Fill.FillType = xlsModule.ShapeFillType.SolidColor Makes that rectangle use a solid fill
points.get(i).DataFormat.Fill.ForeColor = colors[i % colors.length] Assigns a fill color to that rectangle
chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true Enables category name labels, so item names appear on the rectangles
chart.Series.get(0).DataFormat.TreeMapLabelOption = xlsModule.ExcelTreeMapLabelOption.Overlapping Overlays item names directly on the rectangles and leaves group names in the group corner
xlsModule.ExcelTreeMapLabelOption.Banner Gives group names a strip at the top of each group and keeps item names inside the rectangles
xlsModule.ExcelTreeMapLabelOption.None Hides every label in the chart

The steps are:

  1. Load the test workbook and create the chart as described in the previous section, keeping the data range, position, and title unchanged.
  2. Get the data points with chart.Series.get(0).DataPoints, loop over the data rows, and set DataFormat.Fill.FillType to xlsModule.ShapeFillType.SolidColor and ForeColor to the chosen color for each point.
  3. Set chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName to true. Data labels are off by default on a treemap, so category names appear on the rectangles only after this is enabled.
  4. Set chart.Series.get(0).DataFormat.TreeMapLabelOption to xlsModule.ExcelTreeMapLabelOption.Overlapping so item names are drawn directly on the rectangles; use Banner instead when group names should take a strip at the top of each group.
  5. Write TreeMapStyled.xlsx with workbook.SaveToFile and trigger the download, then release the workbook with workbook.Dispose.

Below is the complete example. The chart setup is kept from the previous section so that it runs on its own; the additions are the fill colors and the labels:

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

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

    // Load the font and the test data file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'TreeMapData.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 first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Add a tree map chart and set its data range
    const chart = sheet.Charts.Add();
    chart.ChartType = xlsModule.ExcelChartType.TreeMap;
    chart.DataRange = sheet.Range.get('A2:C11');
    chart.TopRow = 1;
    chart.BottomRow = 20;
    chart.LeftColumn = 1;
    chart.RightColumn = 12;
    chart.ChartTitle = 'Area by cities';

    // Prepare the fill colors used in the loop
    const colors = [
      xlsModule.Color.get_DarkGreen(),
      xlsModule.Color.get_DarkBlue(),
      xlsModule.Color.get_DarkGoldenrod(),
      xlsModule.Color.get_DarkRed(),
      xlsModule.Color.get_DarkSlateGray(),
    ];

    // Set the fill color of every rectangle in data point order (10 data rows)
    const points = chart.Series.get(0).DataPoints;
    for (let i = 0; i < 10; i++) {
      points.get(i).DataFormat.Fill.FillType = xlsModule.ShapeFillType.SolidColor;
      points.get(i).DataFormat.Fill.ForeColor = colors[i % colors.length];
    }

    // Enable category name labels and overlay the item names on the rectangles
    chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true;
    chart.Series.get(0).DataFormat.TreeMapLabelOption = xlsModule.ExcelTreeMapLabelOption.Overlapping;

    // Save the workbook with the tree map chart into the VFS
    const outputFileName = 'TreeMapStyled.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Dispose of the workbook to release its resources
    workbook.Dispose();

    // Read the result back out of the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Create a Tree Map Chart in React</h1>
      <button id="style-treemap" onClick={styleTreeMap}>Set Labels and Fill Colors</button>
    </div>
  );
}

export default App;

The workbook after setting treemap labels and fill colors:

The workbook after setting treemap labels and fill colors


FAQ

Treemap labels do not appear

Cause: Data labels are off by default on a treemap, so no names are drawn on the rectangles. TreeMapLabelOption only controls how labels are laid out, and setting it alone does not make labels visible.

Solution: Set DataLabels.HasCategoryName on the data points to true first, then set TreeMapLabelOption: use Overlapping to draw item names directly on the rectangles, or Banner to give group names a strip at the top of each group. For example:

chart.Series.get(0).DataPoints.DefaultDataPoint.DataLabels.HasCategoryName = true;
chart.Series.get(0).DataFormat.TreeMapLabelOption = xlsModule.ExcelTreeMapLabelOption.Overlapping;

How can the position and size of a treemap be adjusted

Cause: The position of a chart is controlled by TopRow, BottomRow, LeftColumn, and RightColumn, which mark the first and last row and column the chart covers.

Solution: Adjust these four properties before saving the workbook. Larger values widen the range of rows and columns the chart covers, so the chart takes up more space on the worksheet.

How should the data for a treemap be arranged

Cause: A treemap reads hierarchy and values by column, so the order of the columns determines how groups, items, and areas correspond to one another.

Solution: Arrange the data in three columns: the first for the category, the second for the item, and the third for the value, and keep the value column numeric. Start chart.DataRange at the first data row, for example sheet.Range.get("A2:C11"), so the header row is not included.


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.

In everyday data analysis, a scatter chart is an intuitive tool for examining the relationship between two numeric variables — for example, exploring the correlation between product price and sales, or income and spending. With a scatter chart, you can quickly spot how data points are distributed and any potential relationships. Spire.XLS for JavaScript creates scatter charts directly in the browser based on WebAssembly, and manages input/output files through a virtual file system (VFS), with no backend service required.

This article covers two core features:

For installation and project configuration, 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.


Create a Basic Scatter Chart

A scatter chart places two sets of numeric values on the horizontal and vertical axes, using the position of each point to show how the two variables correspond. Compared with reading a table, it makes clusters, gaps and upward or downward relationships easier to see. Mapping the source data onto the chart and fixing its position on the worksheet is all it takes to start examining that relationship.

Creating a basic scatter chart:

Syntax Result
workbook.LoadFromFile(...) Loads the workbook that contains the source data
sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers }) Adds a scatter chart with data markers to the worksheet
chart.DataRange = sheet.Range.get("B2:B13") Sets the data range used by the chart
chart.Series.get(0).CategoryLabels = sheet.Range.get("A2:A13") Uses column A for the horizontal axis
chart.Series.get(0).Values = sheet.Range.get("B2:B13") Uses column B for the vertical axis
chart.LeftColumn / TopRow / RightColumn / BottomRow Positions the chart on the worksheet
chart.ChartTitle, PrimaryCategoryAxis.Title, PrimaryValueAxis.Title Sets the chart title and the axis titles
workbook.SaveToFile(...) Saves the workbook with the scatter chart to the VFS

The steps are:

  1. Load the font file and the source data file into the VFS.
  2. Create a Workbook object, load the Excel document with Workbook.LoadFromFile(), and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use the Worksheets.Charts.Add() method with the ScatterMarkers chart type to add a scatter chart.
  4. Set the chart's DataRange and SeriesDataFromRange, and specify the horizontal and vertical axis data with Series.get(0).CategoryLabels and Series.get(0).Values.
  5. Set the chart position with the LeftColumn, TopRow, RightColumn and BottomRow properties.
  6. Set the chart title with the ChartTitle property, and set the X-axis and Y-axis titles with the PrimaryCategoryAxis.Title and PrimaryValueAxis.Title properties.
  7. Use the Workbook.SaveToFile() method to save the document, then release resources with Dispose().

Here is a complete code example showing how to create a basic scatter chart in React:

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

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

    // Load the font and Excel file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ScatterChartData.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 first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Add a scatter chart with data markers
    let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers });

    // Set the chart's data range
    chart.DataRange = sheet.Range.get("B2:B13");
    chart.SeriesDataFromRange = false;

    // Set the series data: column A for the X-axis and column B for the Y-axis
    chart.Series.get(0).CategoryLabels = sheet.Range.get("A2:A13");
    chart.Series.get(0).Values = sheet.Range.get("B2:B13");

    // Set the position of the chart
    chart.LeftColumn = 1;
    chart.TopRow = 15;
    chart.RightColumn = 10;
    chart.BottomRow = 32;

    // Set the chart title
    chart.ChartTitle = "Scatter Chart";

    // Set the X-axis and Y-axis titles
    chart.PrimaryCategoryAxis.Title = "Car Price";
    chart.PrimaryValueAxis.Title = "Salary";

    // Save the document
    const outputFileName = 'CreateScatterChart_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Create Scatter Chart</h1>
      <button onClick={createScatterChart}>
        Start
      </button>
    </div>
  );
}

export default App;

The chart after creating a basic scatter chart:

Create a Basic Scatter Chart


Add a Trend Line to the Scatter Chart

A trend line adds a fitted curve on top of the scatter points to summarize the overall direction of the data. The more scattered the points are, the more useful the trend line becomes for judging whether the variables rise, fall or stay roughly flat together. Adding an exponential trend line to the chart's data series records that trend in the workbook.

Adding a trend line to the scatter chart:

Syntax Result
sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers }) Adds a scatter chart with data markers
chart.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Exponential }) Adds an exponential trend line to the first series
workbook.SaveToFile(...) Saves the workbook with the trend line to the VFS

The steps are:

  1. Load the font file and the source data file into the VFS.
  2. Create a Workbook object, load the Excel document with LoadFromFile(), and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use the Worksheets.Charts.Add() method to add a scatter chart, and set DataRange, CategoryLabels and Values.
  4. Set the chart title with the ChartTitle property, and set the X-axis and Y-axis titles with the axis title properties.
  5. Use the Series.get(0).TrendLines.Add() method to add an exponential trend line to the first data series.
  6. Use the Workbook.SaveToFile() method to save the document, then release resources with Dispose().

Here is a complete code example showing how to add an exponential trend line to a scatter chart in React:

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

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

    // Load the font and Excel file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'ScatterChartData.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 first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Add a scatter chart
    let chart = sheet.Charts.Add({ chartType: xlsModule.ExcelChartType.ScatterMarkers });
    chart.DataRange = sheet.Range.get("B2:B13");
    chart.SeriesDataFromRange = false;
    chart.Series.get(0).CategoryLabels = sheet.Range.get("A2:A13");
    chart.Series.get(0).Values = sheet.Range.get("B2:B13");

    // Set the position of the chart
    chart.LeftColumn = 1;
    chart.TopRow = 15;
    chart.RightColumn = 10;
    chart.BottomRow = 32;

    // Set the chart title
    chart.ChartTitle = "Scatter Chart with Trend Line";

    // Set the X-axis and Y-axis titles
    chart.PrimaryCategoryAxis.Title = "Car Price";
    chart.PrimaryValueAxis.Title = "Salary";

    // Add an exponential trend line to the first data series
    chart.Series.get(0).TrendLines.Add({ type: xlsModule.TrendLineType.Exponential });

    // Save the document
    const outputFileName = 'ScatterChartTrendLine_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Add Trend Line to Scatter Chart</h1>
      <button onClick={addTrendLine}>
        Start
      </button>
    </div>
  );
}

export default App;

The scatter chart after adding a trend line:

Add a Trend Line to the Scatter Chart


FAQ

Data points are all clustered at the bottom-left of the axes

Cause: The data range set for the chart includes the header row or empty cells. These non-numeric contents are treated as zero or ignored, so all data points cluster at the bottom-left of the axes.

Solution: Make sure the chart data range starts from the data rows (for example sheet.Range.get("A2:A13")), and that CategoryLabels and Values point to the same row range so the X and Y values correspond one-to-one.

Adding an exponential trend line fails

Cause: An exponential trend line requires all data values to be positive. When the data contains zero or negative values, the exponential regression fit cannot be completed, and the trend line may fail to be added or may not be displayed.

Solution: Check whether the data contains zero or negative values. If necessary, use a trend line type that fits the current data, such as linear (Linear) or polynomial (Polynomial).

How do the horizontal and vertical axis values correspond?

Cause: A scatter chart takes the horizontal axis from CategoryLabels and the vertical axis from Values. If the two ranges cover different rows or start at different rows, the points are paired incorrectly and may even fall outside the plot area.

Solution: Point CategoryLabels and Values at the same row range, for example rows 2 through 13, and change only the column. Each horizontal value then has a matching vertical value.


Obtain 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.

The header and footer in Excel are text or images placed at the top and bottom of every printed page. The text or images in the header and footer can display basic information about the page or document, such as the file name, company logo, page number, date and time. Spire.XLS for JavaScript completes this directly in the browser based on WebAssembly, managing input/output files through a virtual file system (VFS), with no backend service required.

The PageSetup class provided by Spire.XLS for JavaScript can configure the page of an Excel workbook, including setting the header and footer. The LeftHeader, CenterHeader, RightHeader, LeftFooter, CenterFooter, RightFooter properties in this class add content to the left, center and right of the header or footer. To add fields to the header or footer or to set the text format of the header and footer, you can use the format codes listed in the table below.

Code Description
&P Current page number
&N Total number of pages
&D Current date
&T Current time
&G Picture
&A The worksheet name
&F The file name
&B Bold text
&I Italic text
&U Underline text
&"Font Name" The font name, e.g. &"Arial"
& + Integer The font size, e.g. &12
&K + 16-digit color code The font color, e.g. &KFF0000

This article introduces two core features:

For installation and project configuration, 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.


Add an Image and Formatted Text to the Header

The header sits at the top of every page and is a natural place for a company logo, a document name or other fixed information. Putting the image on the left and text with its own font and color on the right gives every printed or previewed page the same identification without adding it page by page.

Adding an image and formatted text to the header:

Syntax Result
new xlsModule.Stream(imgFileName) Reads the image stream from the VFS
sheet.PageSetup.LeftHeaderImage = image Sets the image as the left header image
sheet.PageSetup.LeftHeader = "&G" Displays the image in the left header
sheet.PageSetup.RightHeader = "&\"Arial\"&B&10&K4253E2..." Writes text with the specified font, bold, size and color in the right header
sheet.ViewMode = xlsModule.ViewMode.Layout Switches to Page Layout view for previewing
workbook.SaveToFile(...) Saves the workbook with the header to the VFS

The steps are:

  1. Load the font, Excel file and image into the VFS.
  2. Create a Workbook object, load the Excel file with LoadFromFile(), and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use Stream to load the image, and set it as the image source of the left header through the PageSetup.LeftHeaderImage property.
  4. Use the PageSetup.LeftHeader property to display the image in the left header.
  5. Use the PageSetup.RightHeader property to insert formatted text in the right header.
  6. Switch the worksheet to Page Layout view with ViewMode.Layout to preview the header.
  7. Use the Workbook.SaveToFile() method to save the workbook, then release resources with Workbook.Dispose().

Here is a complete code example showing how to add an image and formatted text to the header of a worksheet in React:

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

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

    // Load the font, Excel file and image into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'HeaderFooter.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
    const imgFileName = 'logo.png';
    await window.spire.FetchFileToVFS(imgFileName, '', `${process.env.PUBLIC_URL}data/`);

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

    // Get the first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Load the image and set it as the image source of the left header
    const image = new xlsModule.Stream(imgFileName);
    sheet.PageSetup.LeftHeaderImage = image;
    sheet.PageSetup.LeftHeader = "&G";

    // Add formatted text to the right header
    sheet.PageSetup.RightHeader = "&\"Arial\"&B&10&K4253E2Ice Blue Technology Co., Ltd.  \n www.e-iceblue.com";

    // Switch to Page Layout view to preview the header and footer
    sheet.ViewMode = xlsModule.ViewMode.Layout;

    // Save the document
    const outputFileName = 'HeaderImageAndText_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Add Image and Formatted Text to Header</h1>
      <button onClick={addImageAndTextToHeader}>
        Start
      </button>
    </div>
  );
}

export default App;

The worksheet after adding an image and formatted text to the header:

Add an Image and Formatted Text to the Header


Add the Current Date and Page Number to the Footer

The footer sits at the bottom of every page and is commonly used for page numbers and dates. Both update automatically as pages are printed, so there is no need to maintain them in the sheet body; placing them on the left and right also helps readers find their position when paging through a report.

Adding the current date and page number to the footer:

Syntax Result
sheet.PageSetup.LeftFooter = "...&P..." Shows the current page number in the left footer
sheet.PageSetup.RightFooter = "...&D" Shows the current date in the right footer
sheet.ViewMode = xlsModule.ViewMode.Layout Switches to Page Layout view for previewing
workbook.SaveToFile(...) Saves the workbook with the footer to the VFS

The steps are:

  1. Load the font and Excel file into the VFS.
  2. Create a Workbook object, load the Excel file with LoadFromFile(), and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use the PageSetup.LeftFooter property to add a formatted page number to the left footer, customizing its format as you like.
  4. Use the PageSetup.RightFooter property to add the current date to the right footer, and change the appearance of the date text as needed.
  5. Switch the worksheet to Page Layout view with ViewMode.Layout to preview the footer.
  6. Use the Workbook.SaveToFile() method to save the workbook, then release resources with Workbook.Dispose().

Here is a complete code example showing how to add the current date and page number to the footer of a worksheet in React:

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

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

    // Load the font and Excel file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'HeaderFooter.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 first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Add the page number to the left footer; &P represents the current page number
    sheet.PageSetup.LeftFooter = "&\"Arial\"&B&10&K4253E2Page &P";

    // Add the current date to the right footer; &D represents the current date
    sheet.PageSetup.RightFooter = "&\"Arial\"&B&10&K4253E2&D";

    // Switch to Page Layout view to preview the header and footer
    sheet.ViewMode = xlsModule.ViewMode.Layout;

    // Save the document
    const outputFileName = 'FooterDatePageNumber_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Add Current Date and Page Number to Footer</h1>
      <button onClick={addDateAndPageNumberToFooter}>
        Start
      </button>
    </div>
  );
}

export default App;

The worksheet after adding the current date and page number to the footer:

Add the Current Date and Page Number to the Footer


FAQ

Headers and footers are not visible in Excel

Cause: An Excel worksheet is in Normal view by default, and headers and footers are only displayed in Page Layout view or when printing.

Solution: Switch the worksheet to Page Layout view in code with sheet.ViewMode = xlsModule.ViewMode.Layout;, or switch to the "Page Layout" view manually in Excel.

The picture in the header is not displayed

Cause: Only the image source (such as LeftHeaderImage) was set, but the &G placeholder was not used in the corresponding header text, or the image file was not loaded into the VFS correctly.

Solution: Make sure the image has been loaded into the VFS with FetchFileToVFS() and add &G to the header string, for example sheet.PageSetup.LeftHeader = "&G";.

How can the current page number and total page count appear together?

Cause: The footer used only the &P field, so it shows the current page number without any information about the total number of pages.

Solution: Put both &P and &N in the footer string, for example sheet.PageSetup.LeftFooter = "&P / &N";. When printing or previewing, the two fields are replaced with the current page number and the total page count respectively.


Obtain 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.

Excel does not have a built-in watermark feature, but in scenarios such as contracts, reports, and internal documents, we often need to mark files with identifiers like "Confidential" or "Sample" to prevent information from being copied and distributed at will. Spire.XLS for JavaScript processes Excel documents directly in the browser based on WebAssembly, and manages input and output files through a virtual file system (VFS), with no backend service required. With the header image feature, you can easily add and remove watermarks in worksheets.

This article covers two core features:

For installation and project configuration, 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.


Add a Watermark to an Excel Worksheet

Excel does not have a built-in watermark feature, yet contracts, reports and internal documents often need to mark their origin or scope of use. A watermark lays pale text across the center of the page, leaving the body readable while signaling what the document is for. Drawing the watermark as an image the size of the page and placing it in the header makes it appear on every page.

Adding a watermark to an Excel worksheet:

Syntax Result
sheet.PageSetup.PageWidth / PageHeight Gets the page size used to draw a matching watermark image
watermarkCanvas.toBlob(...) and FS.writeFile(...) Creates the watermark image and writes it to the VFS
new xlsModule.Stream(imgFileName) Reads the watermark image stream from the VFS
sheet.PageSetup.LeftHeaderImage = watermark Sets the watermark image as the left header image
sheet.PageSetup.LeftHeader = "&G" Displays the image in the header
sheet.ViewMode = xlsModule.ViewMode.Layout Switches to Page Layout view for previewing
workbook.SaveToFile(...) Saves the watermarked workbook to the VFS

The steps are:

  1. Load the font file and the input file into the VFS.
  2. Create a Workbook object, load the Excel document with LoadFromFile(), and get the first worksheet with Workbook.Worksheets.get(0).
  3. Use the Canvas API to draw a watermark image that matches the page size: a white background with centered LightCoral text rotated -45°.
  4. Save the watermark image to the virtual file system (VFS) and load it with a Stream object.
  5. Set the watermark image as the left header image (LeftHeaderImage) and set the header text to the image placeholder &G.
  6. Switch the worksheet to Page Layout view with ViewMode.Layout to preview the watermark effect.
  7. Use the Workbook.SaveToFile() method to save the document, then release resources with Workbook.Dispose().

Here is a complete code example showing how to add a watermark to a worksheet in React:

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

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

    // Load the font and the Excel file into the VFS
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Watermark.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 first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Define the watermark text, text color, and background color
    const watermarkText = 'Internal Use';
    const textColor = 'lightcoral';
    const backColor = 'white';

    // Draw the watermark image according to the page size (white background with centered text rotated -45°)
    const drawWatermarkImage = (text, color, bg, width, height) => {
      const canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;
      const ctx = canvas.getContext('2d');
      // Draw the background
      ctx.fillStyle = bg;
      ctx.fillRect(0, 0, width, height);
      // Set the text style and draw the rotated text at the center of the page
      ctx.fillStyle = color;
      ctx.font = 'bold 48px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.save();
      ctx.translate(width / 2, height / 2);
      ctx.rotate(-Math.PI / 4);
      ctx.fillText(text, 0, 0);
      ctx.restore();
      return canvas;
    };

    // Draw the watermark image and write it to the virtual file system (VFS)
    const watermarkCanvas = drawWatermarkImage(watermarkText, textColor, backColor, sheet.PageSetup.PageWidth, sheet.PageSetup.PageHeight);
    const watermarkBlob = await new Promise((resolve) => watermarkCanvas.toBlob(resolve, 'image/png'));
    const watermarkBytes = new Uint8Array(await watermarkBlob.arrayBuffer());
    const imgFileName = 'Watermark.png';
    window.dotnetRuntime.Module.FS.writeFile(imgFileName, watermarkBytes);

    // Load the watermark image
    const watermark = new xlsModule.Stream(imgFileName);

    // Set the watermark image as the left header image and display it with the &G placeholder
    sheet.PageSetup.LeftHeaderImage = watermark;
    sheet.PageSetup.LeftHeader = "&G";

    // Switch to Page Layout view to preview the watermark effect
    sheet.ViewMode = xlsModule.ViewMode.Layout;

    // Save the document
    const outputFileName = 'AddWatermark_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Add Watermark to Excel</h1>
      <button onClick={addWatermark}>
        Start
      </button>
    </div>
  );
}

export default App;

The worksheet after adding a watermark:

Add a Watermark to an Excel Worksheet


Remove a Watermark from an Excel Worksheet

Because the watermark is carried by the header, removing it means clearing the reference in that header. Once the header text is empty, the image is no longer displayed and the body of the worksheet stays as it is.

Removing a watermark from an Excel worksheet:

Syntax Result
workbook.LoadFromFile(...) Loads the watermarked workbook
workbook.Worksheets.get(0) Gets the first worksheet to process
sheet.PageSetup.LeftHeader = "" Clears the left header text and the image placeholder
workbook.SaveToFile(...) Saves the workbook without the watermark to the VFS

The steps are:

  1. Load the watermarked Excel file into the VFS.
  2. Create a Workbook object, load the document with LoadFromFile(), and get the first worksheet with Workbook.Worksheets.get(0).
  3. Set PageSetup.LeftHeader to an empty string to remove the watermark image placeholder from the header.
  4. Use the Workbook.SaveToFile() method to save the document, then release resources with Workbook.Dispose().

Here is a complete code example showing how to remove a watermark from a worksheet in React:

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

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

    // Load the watermarked Excel file into the VFS
    const inputFileName = 'AddWatermark.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 first worksheet
    const sheet = workbook.Worksheets.get(0);

    // Clear the left header text to remove the watermark
    sheet.PageSetup.LeftHeader = "";

    // Save the document
    const outputFileName = 'RemoveWatermark_output.xlsx';
    workbook.SaveToFile({ fileName: outputFileName, version: xlsModule.ExcelVersion.Version2010 });

    // Release resources
    workbook.Dispose();

    // Read the converted file from the VFS and trigger a download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Remove Watermark from Excel</h1>
      <button onClick={removeWatermark}>
        Start
      </button>
    </div>
  );
}

export default App;

The worksheet after removing the watermark:

Remove a Watermark from an Excel Worksheet


FAQ

The watermark is not visible in Normal view

Cause: A watermark implemented through a header image is only displayed in Page Layout view and print preview, and is not visible in Excel's Normal view.

Solution: Switch the worksheet view to Page Layout mode to preview the watermark, for example by setting sheet.ViewMode = xlsModule.ViewMode.Layout in the code.

The watermark position is different from expected

Cause: A header image watermark is displayed in the header area, and its horizontal position can be controlled by the left, center, and right header positions. If the position does not match expectations, it is usually because the header position set is inconsistent with the requirement.

Solution: Choose LeftHeaderImage, CenterHeaderImage, or RightHeaderImage to set the watermark image according to your needs, and set the corresponding image placeholder &G with the LeftHeader, CenterHeader, or RightHeader property.

Residual content remains in the file after removing the watermark

Cause: The watermark is implemented through a header image. If only part of the header area is cleared, other areas (such as the left or right header) may still contain the watermark image.

Solution: Make sure to clear the text of all header areas where the watermark was set, including LeftHeader, CenterHeader, and RightHeader.


Obtain 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.

The same PDF can look different when opened on different machines: if the font a document references is not installed locally, the viewer substitutes another one. Conversely, when brand standards or legal requirements say an outgoing file may only use specified fonts, you first need to know which fonts the original uses. Common browser-side PDF libraries mostly draw pages; they do not expose font resources.

This article uses Spire.PDF for JavaScript to read and replace the fonts in a PDF: first list the document's fonts with PdfDocument.UsedFonts, then replace the standard and non-embedded TrueType fonts with a new TrueType font via PdfUsedFont.Replace(). Everything runs in the browser; files are read and written through a virtual file system (VFS) and never uploaded to a server.

This article covers two core features:

For installation and project configuration, see Integrate Spire.PDF for JavaScript in a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Get the Fonts Used in a PDF

Spire.PDF for JavaScript provides the PdfDocument.UsedFonts property for reading every font used in a document. Each item in the collection is a PdfUsedFont, whose Name, Size, Style, and Type give the font name, size, style, and font type.

The steps are:

  1. Get the Spire.PDF WASM module and confirm it is ready.
  2. Load the sample PDF into the VFS, create a PdfDocument object, and load the file.
  3. Read PdfDocument.UsedFonts and take each PdfUsedFont from the collection.
  4. Put together each font's name, size, style, and type, and write them to a text file.
  5. Read the result file from the VFS and trigger a browser download.

The code for getting the fonts in a PDF is as follows:

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

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

    // Load the sample PDF into the VFS
    const inputFileName = 'Font_Sample.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

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

    // UsedFonts lists the fonts the document currently uses
    const fonts = doc.UsedFonts;

    const styleNames = new Map([
      [pdfModule.PdfFontStyle.Regular.value, 'Regular'],
      [pdfModule.PdfFontStyle.Bold.value, 'Bold'],
      [pdfModule.PdfFontStyle.Italic.value, 'Italic'],
      [pdfModule.PdfFontStyle.Underline.value, 'Underline'],
      [pdfModule.PdfFontStyle.Strikeout.value, 'Strikeout'],
    ]);
    const typeNames = new Map([
      [pdfModule.PdfFontType.Standard.value, 'Standard'],
      [pdfModule.PdfFontType.TrueType.value, 'TrueType'],
      [pdfModule.PdfFontType.TrueTypeEmbedded.value, 'Embedded TrueType'],
    ]);

    // Collect each font's name, size, style, and type
    let report = `Document: ${inputFileName}\r\nFont count: ${fonts.length}\r\n\r\n`;
    for (let i = 0; i < fonts.length; i++) {
      const font = fonts.get(i);
      report += `${i + 1}. ${font.Name}\r\n`;
      report += `   Size: ${font.Size}\r\n`;
      report += `   Style: ${styleNames.get(font.Style.value)}\r\n`;
      report += `   Type: ${typeNames.get(font.Type.value)}\r\n\r\n`;
    }

    // Write the font list into the VFS
    const outputFileName = 'FontList.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, report);
    doc.Close();

    // Read the result file 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>Get the Fonts Used in a PDF</h1>
      <button onClick={getFonts}>
        Get fonts
      </button>
    </div>
  );
}

export default App;

The font list is written to a text file, listing each font's name, size, style, and type:

The font list is written to a text file, listing each font's name, size, style, and type


Replace the Fonts Used in a PDF

Spire.PDF for JavaScript also provides the PdfUsedFont.Replace() method, which swaps one font in a document for another font object. It works only with standard Type1 fonts and non-embedded TrueType fonts; embedded fonts (such as subset-embedded CJK fonts) cannot be replaced and must be skipped.

The steps are:

  1. Get the Spire.PDF WASM module and confirm it is ready.
  2. Load the sample PDF and the replacement font file into the VFS, with the font file placed under /Library/Fonts/.
  3. Create a PdfDocument object, load the PDF, and read PdfDocument.UsedFonts.
  4. Create a PdfTrueTypeFont object, iterate over UsedFonts, skip embedded fonts, and call PdfUsedFont.Replace() on standard and non-embedded TrueType fonts.
  5. Save the result with PdfDocument.SaveToFile() and trigger a browser download.

The code for replacing the fonts in a PDF is as follows:

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

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

    // Load the replacement font and the sample PDF
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    const inputFileName = 'Font_Sample.pdf';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);

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

    // UsedFonts returns the fonts used in the document
    const fonts = doc.UsedFonts;
    const newFont = new pdfModule.PdfTrueTypeFont({
      fontFamily: 'Arial Unicode MS',
      size: 12,
      style: pdfModule.PdfFontStyle.Regular,
      unicode: true
    });

    // Replace one by one: Replace() supports only standard Type1 and non-embedded TrueType fonts
    for (let i = 0; i < fonts.length; i++) {
      const font = fonts.get(i);
      // Embedded fonts cannot be replaced; skip them
      if (font.Type.value === pdfModule.PdfFontType.TrueTypeEmbedded.value) {
        continue;
      }
      font.Replace(newFont);
    }

    // Save the result file
    const outputFileName = 'ReplacedFonts.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>Replace the Fonts Used in a PDF</h1>
      <button onClick={replaceFonts}>
        Replace fonts
      </button>
    </div>
  );
}

export default App;

After replacement, the text in the standard font uses the new TrueType font while embedded fonts stay unchanged:

After replacement, the text in the standard font uses the new TrueType font while embedded fonts stay unchanged


FAQ

Why do the text size and style change after replacement

Reason: Replace() takes a complete font object, and replacement applies the new object's own size and style; the original sizes of individual text runs are not preserved.

Solution: Set the target font's size and style to the values you want in the result. For example, to make everything 12 pt regular, pass size: 12 and style: PdfFontStyle.Regular to PdfTrueTypeFont.

Why do Chinese or Japanese characters turn into boxes or question marks after replacement

Reason: The target font has no glyphs for those characters, or PdfTrueTypeFont was created without Unicode enabled.

Solution: Use a font that covers the target language (such as Arial Unicode MS) and pass unicode: true when creating it. The font file must first be loaded into /Library/Fonts/ in the VFS so Spire.PDF can embed it.

Can I replace only one of the fonts

Reason: UsedFonts lists each font in the document, and Replace() acts on a single PdfUsedFont.

Solution: Check Name while iterating and replace only the matching font:

for (let i = 0; i < fonts.length; i++) {
  const font = fonts.get(i);
  if (font.Name === 'ArialMT') {
    font.Replace(newFont);
  }
}

Font names of subset-embedded fonts may carry prefixes such as BCDEEE+, so write the comparison against the Name you actually read from the list.

Why does Replace() report that the font is not a standard Type 1 or non-embedded TrueType font

Reason: PdfUsedFont.Replace() only works on standard Type1 fonts and non-embedded TrueType fonts. CJK fonts in PDFs are often subset-embedded TrueType fonts (UsedFonts reports them as Embedded TrueType), and Spire.PDF cannot replace them directly.

Solution: Check Type while iterating UsedFonts and call Replace() only on standard and non-embedded TrueType fonts; skip the rest, or prepare a document that uses standard or non-embedded fonts first.


Get a Free License

To remove the evaluation message from result documents or lift the feature limits, contact sales for a 30-day temporary license.

When a product manual or conference handout is printed as a booklet, the pages of a multi-page document have to be rearranged onto facing sheets in saddle-stitch order. In desktop software that means dragging pages by hand, which goes wrong easily once the page count grows, and every new document starts over. Hand it to a server, and the file has to leave the user's device first.

This article uses Spire.PDF for JavaScript to create PDF booklets. It loads, imposes, and saves PDFs in the browser via WebAssembly, reading and writing every file through a virtual file system (VFS) with no backend involved.

This article covers two core features:

For installation and project configuration, see Integrate Spire.PDF for JavaScript in a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Impose a PDF as a Two-Sided Booklet Automatically

Spire.PDF for JavaScript provides the PdfDocument.CreateBooklet() method, which imposes the pages of an existing PDF onto facing pages in saddle-stitch order. The width and height parameters set the sheet size of the finished product; when bothSides is true, the layout is arranged for double-sided printing.

The steps are:

  1. Get the Spire.PDF WASM module and confirm it is ready.
  2. Load the source PDF into the VFS.
  3. Create a PdfDocument object and call CreateBooklet() with a width equal to two A4 pages side by side and the height of one A4 page.
  4. Save the result with SaveToFile().
  5. Read the result file from the VFS and trigger a browser download.

The code for creating a booklet by automatic imposition is as follows:

function App() {
  const createBooklet = 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 source PDF into the VFS
    const inputFileName = 'CMS-Technical-Manual.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Spread size: width is two A4 pages side by side, height is one A4 page
    const width = pdfModule.PdfPageSize.A4().Width * 2;
    const height = pdfModule.PdfPageSize.A4().Height;

    // Create a document and impose the pages in saddle-stitch order
    const doc = new pdfModule.PdfDocument();
    doc.CreateBooklet({ fileName: inputFileName, width: width, height: height, bothSides: true });

    // Save the result file
    const outputFileName = 'Booklet_Auto.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    // Read the generated file from the VFS and trigger a 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>Create a Two-Sided Booklet</h1>
      <button onClick={createBooklet}>
        Create Booklet
      </button>
    </div>
  );
}

export default App;

The source pages are imposed onto spreads two at a time in saddle-stitch order:

Source pages imposed onto spreads two at a time in saddle-stitch order


Build Facing Pages in a Custom Page Order

Spire.PDF for JavaScript also provides the PdfPageBase.CreateTemplate() method, which extracts the content of a page into a template that PdfTemplate.Draw() can draw at a given position on a new page. A page-order array decides which two pages land on each sheet; the example puts consecutive pages side by side in reading order.

The steps are:

  1. Get the Spire.PDF WASM module and confirm it is ready.
  2. Load the source PDF into the VFS.
  3. Create a PdfDocument object and load the source document; then create an output document whose width is two A4 pages side by side and whose height is one A4 page.
  4. For each pair in the page order, take a page template with CreateTemplate() and draw it onto the left or right half of the spread with Draw().
  5. Save the result with SaveToFile(), then read it from the VFS and trigger a download.

The code for building facing pages manually is as follows:

function App() {
  const createCustomBooklet = 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 source PDF into the VFS
    const inputFileName = 'CMS-Technical-Manual.pdf';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Create a PdfDocument object and load the source document
    const srcDoc = new pdfModule.PdfDocument();
    srcDoc.LoadFromFile(inputFileName);
    const pageCount = srcDoc.Pages.Count;
    const a4 = pdfModule.PdfPageSize.A4();

    // Output document: width is two A4 pages side by side, height is one A4 page
    const bookletDoc = new pdfModule.PdfDocument();
    bookletDoc.PageSettings.Margins.All = 0;
    bookletDoc.PageSettings.Width = a4.Width * 2;
    bookletDoc.PageSettings.Height = a4.Height;

    // Two consecutive pages per sheet: 1|2, 3|4, 5|6, ...
    for (let i = 0; i < pageCount; i += 2) {
      const sheet = bookletDoc.Pages.Add();

      // Left half
      const leftTemplate = srcDoc.Pages.get_Item(i).CreateTemplate();
      leftTemplate.Draw({ page: sheet, location: new pdfModule.PointF(0, 0) });

      // Right half; with an odd page count the last sheet leaves the right side blank
      if (i + 1 < pageCount) {
        const rightTemplate = srcDoc.Pages.get_Item(i + 1).CreateTemplate();
        rightTemplate.Draw({ page: sheet, location: new pdfModule.PointF(a4.Width, 0) });
      }
    }

    // Save the result file
    const outputFileName = 'Booklet_CustomOrder.pdf';
    bookletDoc.SaveToFile(outputFileName);
    bookletDoc.Close();
    srcDoc.Close();

    // Read the generated file from the VFS and trigger a 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>Custom Page Order Imposition</h1>
      <button onClick={createCustomBooklet}>
        Build Facing Pages
      </button>
    </div>
  );
}

export default App;

Two consecutive pages sit side by side on one sheet; a 6-page document produces 3 spreads:

Two consecutive pages side by side on one sheet; a 6-page document produces 3 spreads


FAQ

What determines the paper size of the booklet

Reason: The width and height passed to CreateBooklet() describe the sheet size of the finished spread, not the size of a single page in the source document. This article uses "A4 width × 2" and "A4 height", which gives an A3-sized spread with one A4 page on each half.

Solution: To print on A4 paper, set the sheet to A4 landscape — width equal to the A4 height, height equal to the A4 width; each half then holds an A5 page, and the source pages are scaled down to half a sheet.

// A4 landscape: one A5 page on each half
const width = pdfModule.PdfPageSize.A4().Height;
const height = pdfModule.PdfPageSize.A4().Width;

What does the bothSides parameter do

Reason: bothSides tells CreateBooklet() whether the result will be printed double-sided. true groups pages into front and back pairs in saddle-stitch order and pads the page count to a multiple of 4; false lays pages out for single-sided printing and adds no blank pages.

Solution: Pass true when you print on a duplex printer and fold and staple the result; pass false for single-sided printing only. The print mode must match the imposed layout, or the page order will not line up.

How is the booklet arranged when the page count is not a multiple of 4

Reason: A saddle-stitch sheet holds four pages (two on each side); when the pages run out, the last sheet has blank positions.

Solution: With automatic imposition, CreateBooklet() pads the required spreads. With manual imposition, the loop checks i + 1 < pageCount before drawing the right half, so an unpaired page simply leaves the right side blank.

In manual imposition the content drifts to one side or runs off the sheet

Reason: PdfTemplate.Draw() draws at the template's original size by default. If a source page is wider than half a sheet, the right-hand page crosses the center line or extends past the edge.

Solution: Make the source page size match half the sheet. In this article the source pages are A4 and the spread is two A4 pages wide, so placing them at x = 0 and x = A4.Width aligns exactly. If the source size differs, use PdfCanvas.DrawTemplate() with size set to the half-sheet size; the template is scaled to fit.

sheet.Canvas.DrawTemplate({
  template: rightTemplate,
  location: new pdfModule.PointF(a4.Width, 0),
  size: new pdfModule.SizeF(a4.Width, a4.Height)
});

Get a Free License

If you want to remove the evaluation message from the result document or lift the feature limits, contact sales for a temporary license valid for 30 days.

When a workbook holds several worksheets, default names and look-alike tabs make it hard to tell one sheet from another. Renaming “Sheet1” to “Quarterly Sales” tells a colleague at a glance what the sheet contains; marking the sales, inventory and summary sheets with red, green and light blue tabs removes the need to open each one while switching. Both the name and the tab color live inside the workbook itself, so they survive distribution and archiving without any external notes. Spire.XLS for JavaScript performs these operations in the browser through WebAssembly, managing input and output files with a virtual file system (VFS) and requiring no backend service.

This article covers two core features:

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


Rename Excel Worksheets

A worksheet name is the first identifier a reader sees after opening the file. With default names such as “Sheet1” and “Sheet2”, even a well-filled workbook has to be opened sheet by sheet before the right table turns up; with a business name such as “Quarterly Sales”, the tab strip itself becomes a table of contents, and the label is preserved when the file is printed or exported. The sample workbook contains sales, inventory and summary data in that order. This section renames the first worksheet.

How to rename a worksheet and what it produces:

Call Result
workbook.Worksheets.get(0) Takes the first worksheet by index
sheet.Name Reads back the current worksheet name
sheet.Name = 'Quarterly Sales' Assigns the new worksheet name
workbook.SaveToFile Saves the renamed workbook to the VFS

The steps are:

  1. Load the font file and the test data file into the VFS with window.spire.FetchFileToVFS.
  2. Create a workbook with new Workbook() and load the input file with workbook.LoadFromFile.
  3. Take the first worksheet with workbook.Worksheets.get(0).
  4. Assign the new name to sheet.Name.
  5. Save the workbook with workbook.SaveToFile, then release the workbook object with workbook.Dispose.

The complete example below renames an Excel worksheet in React:

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

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

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

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

    // Assign the new worksheet name
    sheet.Name = 'Quarterly Sales';

    // Save the workbook
    const outputFileName = 'RenameWorksheet.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Dispose of the workbook to release its resources
    workbook.Dispose();

    // Read the result back out of the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Rename Excel Worksheets</h1>
      <button id="rename-worksheet" onClick={renameWorksheet}>Rename Worksheet</button>
    </div>
  );
}

export default App;

The workbook after renaming the first worksheet:

The workbook after renaming the first worksheet

Set Excel Worksheet Tab Colors

Even with descriptive names, the tab strip remains a row of similarly sized text, which has to be scanned sheet by sheet. Tab colors add a second identifier: red for sales, green for inventory and light blue for summary. Color is perceived before the text is read, so a reader can locate a group directly, and both the color and the name stay in the workbook when the file is shared. The three worksheets in the sample correspond to the three groups, so this section assigns each one a different tab color.

How to set worksheet tab colors and what it produces:

Call Result
workbook.Worksheets.get(0) Takes the worksheet whose tab will be colored
sheet.TabColor = xlsModule.Color.get_Red() Turns the worksheet tab red
sheet.TabColor = xlsModule.Color.get_Green() Turns the worksheet tab green
sheet.TabColor = xlsModule.Color.get_LightBlue() Turns the worksheet tab light blue
xlsModule.Color.FromArgb(255, 255, 193, 7) Builds a custom RGB color for TabColor

The steps are:

  1. Load the font file and the test data file into the VFS with window.spire.FetchFileToVFS.
  2. Create a workbook with new Workbook() and load the input file with workbook.LoadFromFile.
  3. Take the three worksheets in order with workbook.Worksheets.get(0), get(1) and get(2).
  4. Assign xlsModule.Color.get_Red(), get_Green() and get_LightBlue() to each worksheet's TabColor.
  5. Save the workbook with workbook.SaveToFile, then release the workbook object with workbook.Dispose.

The complete example below sets worksheet tab colors in React:

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

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

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

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

    // Set the tab colours to red, green and light blue in worksheet order
    workbook.Worksheets.get(0).TabColor = xlsModule.Color.get_Red();
    workbook.Worksheets.get(1).TabColor = xlsModule.Color.get_Green();
    workbook.Worksheets.get(2).TabColor = xlsModule.Color.get_LightBlue();

    // Save the workbook
    const outputFileName = 'SetTabColor.xlsx';
    workbook.SaveToFile({ fileName: outputFileName });

    // Dispose of the workbook to release its resources
    workbook.Dispose();

    // Read the result back out of the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
    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>Set Excel Worksheet Tab Colors</h1>
      <button id="set-tab-color" onClick={setTabColor}>Set Tab Colors</button>
    </div>
  );
}

export default App;

The workbook after setting tab colors for the three worksheets:

The workbook after setting tab colors for the three worksheets

Set a Custom Tab Color

When the built-in colors do not fit a report's palette, Color.FromArgb builds a custom color. Its four arguments are the alpha, red, green and blue channels, each from 0 to 255:

// Set the first worksheet tab to amber
workbook.Worksheets.get(0).TabColor = xlsModule.Color.FromArgb(255, 255, 193, 7);

FAQ

Worksheet rename reports a duplicate name

Cause: Worksheet names in a workbook must be unique, and the comparison ignores case. Assigning a name already used by another worksheet, such as “Sales” and “sales”, triggers the limit.

Solution: Before renaming, iterate over workbook.Worksheets, read each Name, and confirm the target name is free before assigning it to sheet.Name.

A sheet name containing a slash throws an error

Cause: Excel does not allow worksheet names to contain :, \, /, ?, *, [ or ]; Spire.XLS throws when such a name is assigned.

Solution: Replace the illegal characters with a hyphen or underscore before assigning the value to sheet.Name.

What happens when a worksheet name exceeds 31 characters

Cause: Excel limits worksheet names to 31 characters. Spire.XLS does not report an error; it truncates the name to 31 characters before writing it.

Solution: Check name.length before assigning and shorten the name when it is longer than 31 characters. The truncation happens during assignment, so comparing the saved name with the intended one will reveal the difference.

Getting a worksheet by name throws Arg_NullReferenceException

Cause: workbook.Worksheets.get('name') returns a worksheet only when the name matches exactly. A missing name, a trailing space, or a worksheet that has already been renamed all throw this exception.

Solution: Iterate over workbook.Worksheets and print each Name to confirm the current name before calling get. When the name comes from external input, trim leading and trailing spaces first.


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.

Page 1 of 19
page 1