Contracts and reports that go out to others usually carry a header and a footer beyond the body: the header says whose document this is, and the footer holds the page number and a note like "Internal use only". Typing them in by hand is slow and easy to miss, and handing the job to a server means the document has to leave the user's device first.

This article shows how to use Spire.PDF for JavaScript to add a text header and footer, an image header and footer, and automatic page numbers to an existing PDF. It loads, modifies and saves PDF documents directly in the browser with WebAssembly, draws the header and footer inside the blank areas the page already has, and reads and writes files through the virtual file system (VFS), with no backend involved.

This article covers three core features:

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


Coordinate system

On an existing PDF page, the coordinate system is set up as follows: the origin is at the top-left corner of the page, the X axis extends to the right, and the Y axis extends downward (as shown below). Values are in points, and the drawing coordinates in the three features below all follow this convention.

Coordinate system of a PDF page


Add a text header and footer

To give every page a fixed header and footer, draw the text at the target coordinates with DrawString on PdfPageBase.Canvas.

function App() {
  const addTextHeaderFooter = 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 font into the VFS for the header and footer text
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

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

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

    // Page margins, plus the pen, font and alignment shared by header and footer
    const margin = doc.PageSettings.Margins;
    const pen = new pdfModule.PdfPen({ brush: pdfModule.PdfBrushes.get_Black(), width: 0.75 });
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL.TTF', size: 10 });
    const brush = pdfModule.PdfBrushes.get_Black();
    const rightAlign = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Right });
    rightAlign.MeasureTrailingSpaces = true;

    // Gap between the separator line and the text
    const space = font.Height * 0.75;

    // Draw the header and footer page by page
    for (let i = 0; i < doc.Pages.Count; i++) {
      const canvas = doc.Pages.get_Item(i).Canvas;
      const x = margin.Left;
      const width = canvas.ClientSize.Width - margin.Left - margin.Right;

      // Header: draw the separator above the content area, right-align the title above the line
      let y = margin.Top - space;
      canvas.DrawLine({ pen: pen, x1: x, y1: y, x2: x + width, y2: y });
      canvas.DrawString({
        s: 'E-iceblue',
        font: font,
        brush: brush,
        x: x + width,
        y: y - font.Height,
        format: rightAlign
      });

      // Footer: draw the separator below the content area, right-align the note below the line
      y = canvas.ClientSize.Height - margin.Bottom + space;
      canvas.DrawLine({ pen: pen, x1: x, y1: y, x2: x + width, y2: y });
      canvas.DrawString({
        s: 'Internal use only',
        font: font,
        brush: brush,
        x: x + width,
        y: y + 1,
        format: rightAlign
      });
    }

    const outputFileName = 'Text_Header_Footer.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>Add a Text Header and Footer</h1>
      <button onClick={addTextHeaderFooter}>
        Start Adding
      </button>
    </div>
  );
}

export default App;

Every page carries a right-aligned header and footer, each separated from the body by a separator line:

Every page carries a right-aligned header and footer, each separated from the body by a separator line


Add an image to the header and footer

A company logo often goes into the header and footer too. PdfImage.FromFile reads the image from the virtual file system, and Canvas.DrawImage draws it by its top-left coordinates. The image size is in pixels, so convert it into points with PdfUnitConvertor first to line it up with the page coordinates.

function App() {
  const addImageHeaderFooter = 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 input PDF and the logo image into the VFS
    const inputFileName = 'Multipage_Document.pdf';
    const imageFileName = 'E-iceblue.png';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);
    await window.spire.FetchFileToVFS(imageFileName, "", `${process.env.PUBLIC_URL}/data/`);

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

    // Read the logo image and convert its pixel size into points
    const image = pdfModule.PdfImage.FromFile(imageFileName);
    const unitCvtr = new pdfModule.PdfUnitConvertor();
    const pointWidth = unitCvtr.ConvertUnits(image.Width, pdfModule.PdfGraphicsUnit.Pixel, pdfModule.PdfGraphicsUnit.Point);
    const pointHeight = unitCvtr.ConvertUnits(image.Height, pdfModule.PdfGraphicsUnit.Pixel, pdfModule.PdfGraphicsUnit.Point);

    // Draw the logo at the top-right of the header and the bottom-left of the footer, page by page
    for (let i = 0; i < doc.Pages.Count; i++) {
      const page = doc.Pages.get_Item(i);
      const canvas = page.Canvas;

      // Top-right of the header
      canvas.DrawImage({
        image: image,
        x: page.ActualSize.Width - pointWidth - 55,
        y: 20
      });

      // Bottom-left of the footer
      canvas.DrawImage({
        image: image,
        x: 55,
        y: page.ActualSize.Height - pointHeight - 20
      });
    }

    const outputFileName = 'Image_Header_Footer.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>Add an Image to the Header and Footer</h1>
      <button onClick={addImageHeaderFooter}>
        Start Adding
      </button>
    </div>
  );
}

export default App;

The company logo lands at the top-right of the header and the bottom-left of the footer:

The company logo lands at the top-right of the header and the bottom-left of the footer, sized by the converted point value


Add automatic page numbers and total page count

The product provides the PdfPageNumberField and PdfPageCountField classes to get the current page number and the total number of pages in a PDF document. They can be merged into a PdfCompositeField that formats the output as "Page X of Y", where X is the current page number and Y is the total page count.

function App() {
  const addPageNumbers = 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 font into the VFS for the page number text
    await window.spire.FetchFileToVFS('ARIAL.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

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

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

    // Page margins, plus the pen, font and alignment for the page number text
    const margin = doc.PageSettings.Margins;
    const pen = new pdfModule.PdfPen({ brush: pdfModule.PdfBrushes.get_Black(), width: 0.75 });
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL.TTF', size: 10 });
    const brush = pdfModule.PdfBrushes.get_Black();
    const rightAlign = new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Right });
    rightAlign.MeasureTrailingSpaces = true;
    const space = font.Height * 0.75;

    // Two dynamic fields: the current page number and the total page count
    const number = new pdfModule.PdfPageNumberField();
    const count = new pdfModule.PdfPageCountField();

    // Join them into "Page X of Y" by placeholder order; the list order matches {0} and {1}
    const compositeField = new pdfModule.PdfCompositeField({
      font: font,
      brush: brush,
      text: 'Page {0} of {1}',
      list: [number, count]
    });
    compositeField.StringFormat = rightAlign;

    // Draw the separator line and place the right-aligned page number, page by page
    for (let i = 0; i < doc.Pages.Count; i++) {
      const canvas = doc.Pages.get_Item(i).Canvas;
      const x = margin.Left;
      const width = canvas.ClientSize.Width - margin.Left - margin.Right;

      // Draw a separator line below the content area
      const y = canvas.ClientSize.Height - margin.Bottom + space;
      canvas.DrawLine({ pen: pen, x1: x, y1: y, x2: x + width, y2: y });

      // Measure the text and place the page number below the line, flush with the right edge
      const size = font.MeasureString({ text: 'Page 1 of 1' });
      compositeField.Bounds = new pdfModule.RectangleF({
        x: x + width - size.Width,
        y: y + 1,
        width: size.Width,
        height: size.Height
      });

      // Draw the dynamic field directly onto the canvas
      compositeField.Draw({ graphics: canvas });
    }

    const outputFileName = 'Page_Number_Footer.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>Add Page Numbers</h1>
      <button onClick={addPageNumbers}>
        Start Adding
      </button>
    </div>
  );
}

export default App;

The footer shows "Page X of Y" right-aligned below the separator line:

The footer shows


Frequently Asked Questions

The header and footer are drawn on top of the body text

Cause: The header and footer are layered onto the page, and their position comes from the coordinates derived from PageSettings.Margins. If the loaded PDF has very little blank space at the top and bottom, that text overlaps the body.

Solution: Save the original page as a template first, and after drawing the header and footer, draw the original content back into place so the body and the header/footer each take their own layer. If you don't need layers, use a source file with enough blank space at the top and bottom:

// Save the current page content as a template
const template = page.CreateTemplate();

// After drawing the header and footer, lay the original content back over the full page
const graphicsWidget = new pdfModule.PdfGraphicsWidget(template.H);
graphicsWidget.Draw({ graphics: canvas, location: new pdfModule.PointF(0, 0) });

The header and footer text shows up as boxes

Cause: The font has no glyphs for those characters. Rendering CJK text with a Latin font such as ARIAL.TTF finds no matching glyph, so the page shows empty boxes.

Solution: Switch to a font file that includes those glyphs, and load it into /Library/Fonts/ in the virtual file system first:

// Load a CJK-capable font first, then hand it to PdfTrueTypeFont
await window.spire.FetchFileToVFS('SIMSUN.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/SIMSUN.TTF', size: 10 });

The page number numbers don't match

Cause: The text of PdfCompositeField uses {0} and {1} as placeholders, filled in the order given by list. Putting PdfPageCountField before PdfPageNumberField, or swapping the two placeholders in text, makes the page number and the total page count trade places.

Solution: Keep the placeholder order consistent with list: page number first, total page count second:

const compositeField = new pdfModule.PdfCompositeField({
  font: font,
  brush: brush,
  text: 'Page {0} of {1}',
  list: [number, count]
});

Get a Free License

If you want to remove the evaluation message from the resulting document, or get past the feature limits, contact sales for a temporary 30-day license.

TXT files are everywhere — exported logs, body text pasted out of Word, reports a system generates in bulk. To hand one to someone else you usually convert it to PDF first: a TXT has no notion of layout or paper, it just runs on and on, and the same file looks different on every device and in every editor. In the past you either opened it in a desktop program and saved as, or uploaded it to a server to convert — the former doesn't fit into a web flow, and the latter means the document leaves the user's machine.

This article shows how to convert TXT text to PDF with Spire.PDF for JavaScript. It runs on WebAssembly to generate and save PDF documents directly in the browser; the whole conversion happens locally, reading and writing files through a virtual file system (VFS), with no backend involved.

For installation and project setup, see Integrate Spire.PDF for JavaScript in a React project. The example below assumes Spire.PDF is installed and the WebAssembly module has been initialized.


Convert TXT Text to PDF

Spire.PDF for JavaScript provides PdfTextWidget to place a whole block of plain text onto a PDF page. Font, line spacing and alignment go to PdfStringFormat; when the text fills the current page, PdfTextLayout wraps and starts a new page on its own, so a long TXT needs no manual pagination.

function App() {
  const convertTxtToPdf = 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 a TrueType font into the virtual file system for the body text
    await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Load the TXT file to convert into the VFS
    const inputFileName = 'Product_Introduction.txt';
    await window.spire.FetchFileToVFS(inputFileName, "", `${process.env.PUBLIC_URL}/data/`);

    // Read the TXT bytes from the VFS and decode them to a string
    const textBytes = window.dotnetRuntime.Module.FS.readFile(inputFileName);
    const text = new TextDecoder('utf-8').decode(textBytes);

    // Create the document, section and page
    let doc = new pdfModule.PdfDocument();
    let section = doc.Sections.Add();
    let page = section.Pages.Add();

    // Use the embedded TrueType font for the body, and set line spacing and left alignment
    let font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 12 });
    let format = new pdfModule.PdfStringFormat();
    format.LineSpacing = 20;
    format.Alignment = pdfModule.PdfTextAlignment.Left;

    // Break to a new page when the page is full, so the overflow flows onto new pages
    let textLayout = new pdfModule.PdfTextLayout();
    textLayout.Break = pdfModule.PdfLayoutBreakType.FitPage;
    textLayout.Layout = pdfModule.PdfLayoutType.Paginate;

    // Start the text at the top-left of the page, leaving 20 points of room at the top
    let bounds = new pdfModule.RectangleF({
      location: new pdfModule.PointF(0, 20),
      size: page.Canvas.ClientSize
    });

    // Hold the whole text in a PdfTextWidget and hand it to the layout engine
    let textWidget = new pdfModule.PdfTextWidget({
      text: text,
      font: font,
      brush: pdfModule.PdfBrushes.get_Black()
    });
    textWidget.StringFormat = format;

    let layoutWidget = new pdfModule.PdfLayoutWidget(textWidget.H);
    layoutWidget.Draw({ page: page, layoutRectangle: bounds, format: textLayout });

    // Save the document
    const outputFileName = 'Product_Introduction.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>Convert TXT Text to PDF</h1>
      <button onClick={convertTxtToPdf}>
        Start
      </button>
    </div>
  );
}

export default App;

The generated PDF spreads the long TXT text across pages automatically:

The generated PDF, with the long TXT text laid out across pages


FAQ

Chinese characters show up as boxes or blank after conversion

Cause: the Helvetica font PdfTextWidget uses by default is a PDF built-in Western font with Latin glyphs only. When it meets a Chinese character it has no glyph to use, and the character shows up blank or as a box on the page.

Solution: switch to PdfTrueTypeFont loaded from a font file that contains Chinese glyphs, and put that file into the VFS /Library/Fonts/ first:

// Read the font from the project's static directory into the VFS
await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

// Point fontFile at the font path in the VFS
let font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 12 });

Long text doesn't paginate and only fills one page

Cause: PdfTextWidget only draws inside the rectangle you pass in, and content that overflows is not moved to the next page by itself — whether it paginates is decided by PdfTextLayout.Layout.

Solution: set PdfTextLayout.Layout to PdfLayoutType.Paginate and Break to PdfLayoutBreakType.FitPage, then pass that object as the format argument to Draw:

let textLayout = new pdfModule.PdfTextLayout();
textLayout.Break = pdfModule.PdfLayoutBreakType.FitPage;   // break as soon as a page is full
textLayout.Layout = pdfModule.PdfLayoutType.Paginate;      // paginate automatically

layoutWidget.Draw({ page: page, layoutRectangle: bounds, format: textLayout });

Tab indents in the TXT don't line up in the PDF

Cause: the text you read out is plain characters, and a tab is just a \t with no information about how many character widths it spans. During layout every \t is treated as the same width, so indentation that lined up through \t or extra spaces in the original no longer lines up in the PDF.

Solution: for table-like content that needs exact alignment, pad it out with spaces or draw it as a table instead; for plain prose meant to be read, output it as is.


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.

The number of atoms in a chemical formula, the exponent in a mathematical expression, the square or cube on a unit symbol, the charge at the end of an ion — these characters are set one size smaller and shifted off the baseline, and that is how readers expect to find them. Typed at the same size as the surrounding text, they have to be inferred from context. Excel rich text lets individual characters inside a single cell carry their own formatting, and superscript and subscript are the two forms used most often. Spire.XLS for JavaScript performs this directly 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 key features:

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


Set the Subscript of Text

Superscript and subscript are not whole-cell font properties: RichText.SetFont applies a font to a slice of the text inside a cell rather than to the cell as a whole. Three things have to line up — write the text as a rich-text run, prepare a font that carries the shift, then apply that font to the character range that should move. The steps are as follows:

  1. Load the font into the VFS.
  2. Create a workbook and get the first worksheet.
  3. Write the sample labels, then the chemical formulas with RichText.Text.
  4. Create a font object with workbook.CreateFont() and set IsSubscript to true.
  5. Apply the font to the digits with RichText.SetFont(startIndex, endIndex, font), then save the workbook.

Here is a complete code example that sets the subscript of cell text in React:

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

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

    // Load the font into the VFS
    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 header row and the sample labels
    sheet.Range.get("A1").Text = "Item";
    sheet.Range.get("B1").Text = "Result";
    sheet.Range.get("A2").Text = "Water";
    sheet.Range.get("A3").Text = "Sulfuric acid";

    // Write the chemical formulas
    const waterRange = sheet.Range.get("B2");
    waterRange.RichText.Text = "H2O";
    const acidRange = sheet.Range.get("B3");
    acidRange.RichText.Text = "H2SO4";

    // Create a font and set IsSubscript to true
    const font = workbook.CreateFont();
    font.IsSubscript = true;

    // Apply the subscript font to the digits only: indices start at 0 and the range is inclusive
    waterRange.RichText.SetFont(1, 1, font);
    acidRange.RichText.SetFont(1, 1, font);
    acidRange.RichText.SetFont(4, 4, font);

    // Widen columns A and B so the rich text is fully visible
    sheet.Range.get("A1:A3").ColumnWidth = 18;
    sheet.Range.get("B1:B3").ColumnWidth = 18;

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

    // Dispose of the workbook object to free resources
    workbook.Dispose();

    // 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: '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 Subscript</h1>
      <button onClick={setSubscript}>Start</button>
    </div>
  );
}

export default App;

After running, the effect of setting the subscript of text:

Set the subscript of text


Set the Superscript of Text

Superscript is the mirror image of subscript; the only difference is the direction the font shifts — up rather than down. Superscript suits powers and exponents — the squares in the Pythagorean theorem, the squared cm2 of an area unit, the cubed m3 of a volume unit. A single RichText.SetFont call covers one contiguous range, so text such as a2 + b2 = c2, with three characters to raise, takes three calls. The steps are as follows:

  1. Load the font into the VFS.
  2. Create a workbook and get the first worksheet.
  3. Write the sample labels, then the text that receives the formatting with RichText.Text.
  4. Create a font object with workbook.CreateFont() and set IsSuperscript to true.
  5. Apply the font to the characters that should be raised with RichText.SetFont(startIndex, endIndex, font), then save the workbook.

Here is a complete code example that sets the superscript of cell text in React:

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

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

    // Load the font into the VFS
    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 header row and the sample labels
    sheet.Range.get("A1").Text = "Item";
    sheet.Range.get("B1").Text = "Result";
    sheet.Range.get("A2").Text = "Pythagorean theorem";
    sheet.Range.get("A3").Text = "Area unit";
    sheet.Range.get("A4").Text = "Volume unit";

    // Write the text that receives the formatting
    const theoremRange = sheet.Range.get("B2");
    theoremRange.RichText.Text = "a2 + b2 = c2";
    const areaRange = sheet.Range.get("B3");
    areaRange.RichText.Text = "cm2";
    const volumeRange = sheet.Range.get("B4");
    volumeRange.RichText.Text = "m3";

    // Create a font and set IsSuperscript to true
    const font = workbook.CreateFont();
    font.IsSuperscript = true;

    // Apply the superscript font to the characters that should be raised
    theoremRange.RichText.SetFont(1, 1, font);
    theoremRange.RichText.SetFont(6, 6, font);
    theoremRange.RichText.SetFont(11, 11, font);
    areaRange.RichText.SetFont(2, 2, font);
    volumeRange.RichText.SetFont(1, 1, font);

    // Widen columns A and B so the rich text is fully visible
    sheet.Range.get("A1:A4").ColumnWidth = 18;
    sheet.Range.get("B1:B4").ColumnWidth = 18;

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

    // Dispose of the workbook object to free resources
    workbook.Dispose();

    // 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: '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 Superscript</h1>
      <button onClick={setSuperscript}>Start</button>
    </div>
  );
}

export default App;

After running, the effect of setting the superscript of text:

Set the superscript of text


FAQ

How do I remove a superscript or subscript that has already been set?

Cause: Superscript and subscript are formats on individual characters. Calling SetFont again over the same range overwrites those characters with the new font's properties, so applying a font without the shift clears the effect.

Solution: Create a font with IsSubscript or IsSuperscript set to false, and call SetFont over the original range:

// Overwrite the subscript with a plain font
const normal = workbook.CreateFont();
normal.IsSubscript = false;
range.RichText.SetFont(1, 1, normal);

How can I tell whether a character is superscript when reading the file back?

Cause: The shift is a format on the character, not part of the cell's text. Range.Text, Range.Value and RichText.Text all return plain text, so they cannot show which characters carry it.

Solution: Read the font at the character's position and check its superscript and subscript properties. Positions count from 0, the same as with SetFont:

// Read the font at index 1 — the middle "2" in H2O
const font = range.RichText.GetFont(1);
console.log(font.IsSubscript);   // true
console.log(font.IsSuperscript); // false

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 11