Adding annotations to a PDF, pulling data out of a region, or drawing a border around an image all start with knowing where the target element sits on the page. A PDF has no ready-made coordinate table: text is a series of drawing instructions, images are objects in the page resources, and the position information is scattered across their own matrices and rectangles. In the past you either wrote your own parser to pull those numbers out, or sent the file back to a server to handle.

Spire.PDF for JavaScript loads and parses PDF documents in the browser with WebAssembly and reads and writes files through a virtual file system (VFS), with no backend involved.

This article covers two core features:

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


Coordinate system

When Spire.PDF works with an existing PDF document, the origin of the coordinate system is at the top-left corner of the page. The X axis extends horizontally to the right from the origin, and the Y axis extends vertically downward from the origin (as shown below). Values are in points (1 point = 1/72 inch), and both Positions and Bounds in the two features below report coordinates in this system.

Spire.PDF coordinate system


Get the coordinates of specified text

Spire.PDF for JavaScript provides PdfTextFinder to look up text on a page by content, and every match reports the coordinates of where it lands. The search works one page at a time, so a multi-page document has to be processed page by page.

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

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

    // Get page 1
    let page = doc.Pages.get_Item(0);

    // Create a text finder and search for the given text, ignoring case
    let finder = new pdfModule.PdfTextFinder(page);
    finder.Options.Parameter = pdfModule.TextFindParameter.IgnoreCase;
    let results = finder.Find('Rose');

    // Collect the coordinates of each match
    let report = '';
    for (let i = 0; i < results.length; i++) {
      let find = results.get(i);
      let position = find.Positions[0];

      report += 'Match ' + (i + 1) + ': ' + find.Text + '\n';
      report += '  Coordinates: X = ' + position.X + ', Y = ' + position.Y + '\n';
    }

    // Write the report into the VFS
    const outputFileName = 'TextCoordinates.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));
    doc.Close();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get Text Coordinates</h1>
      <button onClick={getTextCoordinates}>
        Get
      </button>
    </div>
  );
}

export default App;

Coordinates of the matched text

Coordinates of the matched text


Get the coordinates of images on a page

Spire.PDF for JavaScript also provides PdfImageHelper to read the position of every image on a page. Images are already registered in the page resources, so the Bounds you get back gives the top-left coordinates directly. This also works one page at a time.

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

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

    // Get page 1
    let page = doc.Pages.get_Item(0);

    // Create an image helper and get the image info of this page
    let helper = new pdfModule.PdfImageHelper();
    let images = helper.GetImagesInfo(page);

    // Collect the coordinates of each image
    let report = '';
    for (let i = 0; i < images.length; i++) {
      let bounds = images[i].Bounds;

      report += 'Image ' + (i + 1) + ':' + '\n';
      report += '  Coordinates: X = ' + bounds.X + ', Y = ' + bounds.Y + '\n';
    }

    // Write the report into the VFS
    const outputFileName = 'ImageCoordinates.txt';
    window.dotnetRuntime.Module.FS.writeFile(outputFileName, new TextEncoder().encode(report));
    doc.Close();

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

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Get Image Coordinates</h1>
      <button onClick={getImageCoordinates}>
        Get
      </button>
    </div>
  );
}

export default App;

Coordinates of the three flower images on the page

Coordinates of the three flower images on the page


FAQ

A different letter case stops it from matching

Cause: The matching behavior of Find() is determined by Options.Parameter. The default value TextFindParameter.None searches by substring and is case-sensitive, so Rose and rose are treated as two different things.

Fix: Switch to the value you need. TextFindParameter is a flags enum, so values can be combined with bitwise OR:

// Ignore case
finder.Options.Parameter = pdfModule.TextFindParameter.IgnoreCase;

// Match whole words only, ignoring case
finder.Options.Parameter = pdfModule.TextFindParameter.WholeWord | pdfModule.TextFindParameter.IgnoreCase;

// Search with a regular expression: match Rosa or Tulipa in one pass
finder.Options.Parameter = pdfModule.TextFindParameter.Regex;
let results = finder.Find('Rosa|Tulipa');

The coordinates don't match what the PDF reader shows

Cause: Positions and Bounds use the page coordinate system described above; inside a PDF file (/MediaBox, content streams) the origin is at the bottom-left with Y increasing upward, so the two conventions differ by a full page height and a direct comparison will be off by the whole page.

Fix: Work with the top-left origin consistently. To convert to pixels, multiply by dpi / 72 — the factor is 1.333 at 96 dpi. The values you get are floating-point numbers, so round to two decimals before comparing if you need an exact match.

The image info doesn't include the shapes I can see on the page

Cause: GetImagesInfo returns the bitmap objects in the page resources. Lines, table borders, and color blocks drawn with vector instructions are not images; conversely, a full-page scan is a single image covering the page, and the text inside it cannot be searched.

Fix: Start by using the X and Y from Bounds to confirm where each image actually sits on the page. Lines and shapes drawn with vector instructions are not returned by GetImagesInfo, and text inside a scanned page cannot be found either — to locate those, use text extraction (PdfTextExtractor) or bring in OCR separately.


Get a Free License

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

A named range is not something you set up once and forget. As the data table is restructured, the original name may no longer fit, and the referred range can go stale when rows are added or removed. Some named ranges exist only as an intermediate helper for a formula and have no business showing up in the Name Manager. And named ranges that are no longer used, if kept forever, turn the name list into something long and hard to search. Modifying, hiding and deleting are therefore just as much a part of working with named ranges as creating them. Spire.XLS for JavaScript provides a complete named range management API and can perform all of the above in the browser through WebAssembly, with no backend service required.

This article covers three 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.


Modify a Named Range

Modifying covers two independent aspects: the name itself and the referred range. The name is reassigned through the Name property and the referred range through the RefersToRange property. The two can be changed separately, or together as in the example below. The steps are:

  1. Load the workbook and get the first worksheet.
  2. Take the named range to modify with workbook.NameRanges.get(0).
  3. Set Name to the new name.
  4. Point RefersToRange at the new cell range.
  5. Save the workbook.

The following is a complete code example that shows how to modify a named range in React:

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

    // Load the Excel file into VFS
    const inputFileName = 'AllNamedRanges.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

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

    // Change the name of the named range
    workbook.NameRanges.get(0).Name = "RegionData";

    // Change the cell range the named range refers to
    workbook.NameRanges.get(0).RefersToRange = sheet.Range.get("B2:C4");

    // Save the workbook
    const outputFileName = 'ModifyNamedRange.xlsx';
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result 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.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>Modify Named Range</h1>
      <button onClick={modifyNamedRange}>Start</button>
    </div>
  );
}

export default App;

After running, the effect of modifying a named range:

Modify Named Range


Hide a Named Range

Set the Visible property to false and the named range is hidden. A hidden named range is still stored in the workbook and formulas that refer to it are unaffected — it simply no longer appears in Excel's Name Manager and name box, which keeps the name list tidy. After hiding it, the example below also writes the formula =SUM(NameRange1) into cell F2: the formula still calculates normally, which is exactly what shows that the named range is only hidden, not deleted. The steps are:

  1. Load the workbook and get the first worksheet.
  2. Take the named range to hide.
  3. Set Visible to false.
  4. Write a formula that refers to the named range into a cell, to confirm it still works.
  5. Save the workbook.

The following is a complete code example that shows how to hide a named range in React:

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

    // Load the Excel file into VFS
    const inputFileName = 'AllNamedRanges.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

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

    // Hide the first named range
    workbook.NameRanges.get(0).Visible = false;

    // Write a formula that refers to the hidden named range, proving it still exists and works
    sheet.Range.get("F1").Text = "Sum After Hiding";
    sheet.Range.get("F2").Formula = "=SUM(NameRange1)";

    // Calculate the formulas so the saved file shows the result as soon as it is opened
    workbook.CalculateAllValue();

    // Save the workbook
    const outputFileName = 'HideNamedRange.xlsx';
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result 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.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>Hide Named Range</h1>
      <button onClick={hideNamedRange}>Start</button>
    </div>
  );
}

export default App;

After running, the effect of hiding a named range:

Hide Named Range

Note: The formula in cell F2 refers to the hidden NameRange1, and it still calculates 120. That shows the named range has only been hidden from view, not removed from the workbook.


Delete a Named Range

There are two ways to delete a named range: call Remove() when the name is known, or RemoveAt() when the position is known. Both remove the named range from the workbook entirely. The steps are:

  1. Load the workbook.
  2. Call Remove() to delete a named range by name.
  3. Call RemoveAt() to delete a named range by index.
  4. Save the workbook.

The following is a complete code example that shows how to delete a named range in React:

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

    // Load the Excel file into VFS
    const inputFileName = 'AllNamedRanges.xlsx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

    // Load the workbook
    const workbook = new xlsModule.Workbook();
    workbook.LoadFromFile(inputFileName);

    // Delete a named range by name
    workbook.NameRanges.Remove("NameRange2");

    // Delete a named range by index
    workbook.NameRanges.RemoveAt(0);

    // Save the workbook
    const outputFileName = 'DeleteNamedRange.xlsx';
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result 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.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>Delete Named Range</h1>
      <button onClick={deleteNamedRange}>Start</button>
    </div>
  );
}

export default App;

After running, the effect of deleting a named range:

Delete Named Range


FAQ

Why is the result of a formula missing when the saved file is opened?

Cause: Setting only the Formula property of a cell does not make Spire calculate it. The saved file then contains the formula itself but no calculated result value, so the cell comes up blank when the file is opened.

Solution: Call workbook.CalculateAllValue() before saving, to evaluate the formulas first:

// Calculate all formulas so the result value is written into the saved file
workbook.CalculateAllValue();

Can I pass a named range object to the delete API?

Cause: Remove() takes a name string. Passing a NameRange object does not match the expected type and throws Assert failed: Value is not a String, and nothing is deleted.

Solution: Pass the name when it is known, or the index when the position is known:

// Delete by name
workbook.NameRanges.Remove("NameRange2");

// Delete by index
workbook.NameRanges.RemoveAt(0);

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.

How readable a table is often has nothing to do with the data itself and everything to do with how the text sits inside its cells. Titles need to be centred, amounts need to be pushed right, multi-line descriptions need to be indented, a long sentence in a narrow column needs to fold, and a header set at an angle fits more information into limited column width. All of these are cell text layout settings. Spire.XLS for JavaScript performs them 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 four 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 Alignment of Text

Alignment works along two axes. Vertical alignment decides where the text sits within the height of the cell and is set through the VerticalAlignment property, which accepts Top, Center, Bottom and others. Horizontal alignment decides where the text sits within the width of the cell and is set through the HorizontalAlignment property, which accepts General, Left, Center, Right and others. The two are independent and can be combined freely. The steps are as follows:

  1. Create a workbook and get the first worksheet.
  2. Write the sample text.
  3. Set the vertical alignment through VerticalAlignment.
  4. Set the horizontal alignment through HorizontalAlignment.
  5. Save the workbook.

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

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

    // Create a new workbook
    const workbook = new xlsModule.Workbook();

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

    // Write the sample text for vertical alignment
    sheet.Range.get("A1").Text = "Alignment";
    sheet.Range.get("B1").Text = "Sample";
    sheet.Range.get("A2").Text = "Vertical Top";
    sheet.Range.get("B2").Text = "VerticalAlignType.Top";
    sheet.Range.get("A3").Text = "Vertical Center";
    sheet.Range.get("B3").Text = "VerticalAlignType.Center";
    sheet.Range.get("A4").Text = "Vertical Bottom";
    sheet.Range.get("B4").Text = "VerticalAlignType.Bottom";

    // Write the sample text for horizontal alignment
    sheet.Range.get("A6").Text = "Horizontal General";
    sheet.Range.get("B6").Text = "HorizontalAlignType.General";
    sheet.Range.get("A7").Text = "Horizontal Left";
    sheet.Range.get("B7").Text = "HorizontalAlignType.Left";
    sheet.Range.get("A8").Text = "Horizontal Center";
    sheet.Range.get("B8").Text = "HorizontalAlignType.Center";
    sheet.Range.get("A9").Text = "Horizontal Right";
    sheet.Range.get("B9").Text = "HorizontalAlignType.Right";

    // Set the vertical alignment
    sheet.Range.get("B2").Style.VerticalAlignment = xlsModule.VerticalAlignType.Top;
    sheet.Range.get("B3").Style.VerticalAlignment = xlsModule.VerticalAlignType.Center;
    sheet.Range.get("B4").Style.VerticalAlignment = xlsModule.VerticalAlignType.Bottom;

    // Set the horizontal alignment
    sheet.Range.get("B6").Style.HorizontalAlignment = xlsModule.HorizontalAlignType.General;
    sheet.Range.get("B7").Style.HorizontalAlignment = xlsModule.HorizontalAlignType.Left;
    sheet.Range.get("B8").Style.HorizontalAlignment = xlsModule.HorizontalAlignType.Center;
    sheet.Range.get("B9").Style.HorizontalAlignment = xlsModule.HorizontalAlignType.Right;

    // Widen column B and raise rows 2-4 so the alignment differences are visible
    sheet.Range.get("B1:B9").ColumnWidth = 32;
    sheet.Range.get("A2:B4").RowHeight = 40;

    // Save the workbook
    const outputFileName = "TextAlignment.xlsx";
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result 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.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 Text Alignment</h1>
      <button onClick={setTextAlignment}>Start</button>
    </div>
  );
}

export default App;

Vertical alignment is only visible once the row is tall enough, which is why the example sets rows 2-4 to a height of 40; horizontal alignment is at its clearest once the column is wide enough.

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

Set the alignment of text


Set the Indent of Text

Indentation leaves blank space on the left (or right) inside a cell, which suits data that has a hierarchy, such as "region → city". The indent level is set through the IndentLevel property, where one level is roughly one character wide. The steps are as follows:

  1. Create a workbook and get the first worksheet.
  2. Write the sample text.
  3. Set the horizontal alignment to left so the indentation takes effect.
  4. Set an increasing indent level through IndentLevel.
  5. Save the workbook.

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

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

    // Create a new workbook
    const workbook = new xlsModule.Workbook();

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

    // Write the sample text
    sheet.Range.get("A1").Text = "Indent Level";
    sheet.Range.get("B1").Text = "Sample";
    sheet.Range.get("A2").Text = "0";
    sheet.Range.get("B2").Text = "Worldwide";
    sheet.Range.get("A3").Text = "1";
    sheet.Range.get("B3").Text = "North Region";
    sheet.Range.get("A4").Text = "2";
    sheet.Range.get("B4").Text = "Beijing";
    sheet.Range.get("A5").Text = "3";
    sheet.Range.get("B5").Text = "Haidian District";

    // Indentation only takes effect together with left alignment
    sheet.Range.get("B2:B5").Style.HorizontalAlignment = xlsModule.HorizontalAlignType.Left;

    // Set the indentation level of the text
    sheet.Range.get("B2").Style.IndentLevel = 0;
    sheet.Range.get("B3").Style.IndentLevel = 1;
    sheet.Range.get("B4").Style.IndentLevel = 2;
    sheet.Range.get("B5").Style.IndentLevel = 3;

    // Widen column B so the indentation differences are visible
    sheet.Range.get("B1:B5").ColumnWidth = 32;

    // Save the workbook
    const outputFileName = "Indentation.xlsx";
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result 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.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 Text Indent</h1>
      <button onClick={setTextIndent}>Start</button>
    </div>
  );
}

export default App;

The four rows step in one level at a time, forming exactly the hierarchy "Worldwide → North Region → Beijing → Haidian District". Cell B5 has an IndentLevel of 3, so its text starts about three characters in from the left edge.

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

Set the indent of text


Set the Orientation of Text

Text orientation covers two independent settings. The first is the rotation angle, set through the Rotation property, where values 0 to 90 are degrees counterclockwise and -1 to -90 are degrees clockwise. There is also the special value 255, which stacks the text vertically one character per line; rotation is often used to fit a long header into a narrow column. The second is the reading order, set through the ReadingOrder property, which accepts LeftToRight, RightToLeft and Context. It decides which direction the mixed content in a cell is laid out from, and is used for languages written from right to left such as Arabic and Hebrew. Rotated or stacked text takes up far more height than usual, so the row height has to be raised at the same time to keep the text inside the cell. The steps are as follows:

  1. Create a workbook and get the first worksheet.
  2. Write the sample text.
  3. Set the rotation angle through Rotation.
  4. Set the reading order through ReadingOrder.
  5. Save the workbook.

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

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

    // Create a new workbook
    const workbook = new xlsModule.Workbook();

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

    // Write the sample text for the rotation angle
    sheet.Range.get("A1").Text = "Text Orientation";
    sheet.Range.get("B1").Text = "Sample";
    sheet.Range.get("A2").Text = "Counterclockwise 45";
    sheet.Range.get("B2").Text = "Rotation = 45";
    sheet.Range.get("A3").Text = "Counterclockwise 90";
    sheet.Range.get("B3").Text = "Rotation = 90";
    sheet.Range.get("A4").Text = "Clockwise 45";
    sheet.Range.get("B4").Text = "Rotation = -45";
    sheet.Range.get("A5").Text = "Stacked";
    sheet.Range.get("B5").Text = "Spire";

    // Write the sample text for the reading order: Latin mixed with Hebrew, so the
    // difference between the two directions is actually visible
    sheet.Range.get("A7").Text = "Left to Right";
    sheet.Range.get("B7").Text = "Spire.XLS שלום";
    sheet.Range.get("A8").Text = "Right to Left";
    sheet.Range.get("B8").Text = "Spire.XLS שלום";

    // Set the rotation angle of the text; 255 stacks the text vertically
    sheet.Range.get("B2").Style.Rotation = 45;
    sheet.Range.get("B3").Style.Rotation = 90;
    sheet.Range.get("B4").Style.Rotation = -45;
    sheet.Range.get("B5").Style.Rotation = 255;

    // Set the reading order of the text
    sheet.Range.get("B7").Style.ReadingOrder = xlsModule.ReadingOrderType.LeftToRight;
    sheet.Range.get("B8").Style.ReadingOrder = xlsModule.ReadingOrderType.RightToLeft;

    // Widen column B and raise rows 2-5 so the rotated and stacked text fits
    sheet.Range.get("B1:B8").ColumnWidth = 20;
    sheet.Range.get("A2:B5").RowHeight = 60;

    // Save the workbook
    const outputFileName = "TextOrientation.xlsx";
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result 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.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 Text Orientation</h1>
      <button onClick={setTextOrientation}>Start</button>
    </div>
  );
}

export default App;

In the example, B2, B3 and B4 are rotated 45 degrees, 90 degrees and -45 degrees respectively, and B5 uses a Rotation of 255, which stacks the text into a column running top to bottom; all five rows are given a height of 60. B7 and B8 hold the same mixed Latin and Hebrew string with opposite reading orders, and the Hebrew ends up on opposite sides in the two rows — which is exactly what reading order does to mixed content.

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

Set the orientation of text


Set the Wrapping of Text

When a piece of text is longer than the column, it spills over onto the neighbouring empty cell by default, and is cut off as soon as that neighbour has content of its own. Setting the WrapText property to true folds the text inside the cell instead; setting it to false returns the text to a single line. As with rotation, wrapping only changes how the text is laid out and does not adjust the row height by itself, so the row height is usually raised as well to show every folded line in full. The steps are as follows:

  1. Create a workbook and get the first worksheet.
  2. Write a long piece of text.
  3. Turn wrapping on or off through WrapText.
  4. Adjust the column width and row height so the folding is fully visible.
  5. Save the workbook.

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

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

    // Create a new workbook
    const workbook = new xlsModule.Workbook();

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

    // Write the sample text
    sheet.Range.get("A1").Text = "Wrap Text";
    sheet.Range.get("B1").Text = "Sample";
    sheet.Range.get("A2").Text = "On";
    sheet.Range.get("B2").Text = "Spire.XLS for JavaScript can wrap text inside a cell in the browser.";
    sheet.Range.get("A3").Text = "Off";
    sheet.Range.get("B3").Text = "Spire.XLS for JavaScript can wrap text inside a cell in the browser.";

    // Turn wrapping on so the text folds inside the cell when it is wider than the column
    sheet.Range.get("B2").Style.WrapText = true;

    // Turn wrapping off so the text stays on a single line
    sheet.Range.get("B3").Style.WrapText = false;

    // Narrow column B and raise rows 2-3 so the wrapping is visible
    sheet.Range.get("B1:B3").ColumnWidth = 24;
    sheet.Range.get("A2:B3").RowHeight = 60;

    // Save the workbook
    const outputFileName = "WrapText.xlsx";
    workbook.SaveToFile(outputFileName);

    // Release resources
    workbook.Dispose();

    // Read the result 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.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 Text Wrap</h1>
      <button onClick={setTextWrap}>Start</button>
    </div>
  );
}

export default App;

B2 and B3 hold the very same sentence; the only difference is the value of WrapText. B2 folds into several lines and shows in full, while B3 stays on one line. The cell to the right of B3 is empty, so the text spills into it; if there were content there, the overflow would simply be cut off.

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

Set the wrapping of text


FAQ

I set IndentLevel and the text is not indented at all?

Cause: Indentation is only displayed when the horizontal alignment is a non-General value such as Left or Right. Cells default to General alignment, and IndentLevel is ignored outright in that case — so setting only the indent level shows no change.

Solution: Set HorizontalAlignment first, then IndentLevel:

// Indentation only takes effect together with left alignment
sheet.Range.get("B2").Style.HorizontalAlignment = xlsModule.HorizontalAlignType.Left;
sheet.Range.get("B3").Style.HorizontalAlignment = xlsModule.HorizontalAlignType.Left;

// Set the indent level of the text
sheet.Range.get("B2").Style.IndentLevel = 1;
sheet.Range.get("B3").Style.IndentLevel = 2;

I want the text stacked vertically, one character per line — why does Rotation = 90 not do it?

Cause: The 0 to 90 and -1 to -90 ranges of Rotation only deal with the rotation angle. 90 merely lays the text on its side; it never breaks it into a column of single characters.

Solution: Stacked text needs the special value 255:

// 90 degrees simply rotates the text
sheet.Range.get("B2").Style.Rotation = 90;

// 255 stacks the text vertically, one character per line
sheet.Range.get("B3").Style.Rotation = 255;

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 21