Once the data is in, a table usually needs one last pass before it is usable: a product name squeezed into a sliver of a column, a paragraph of remarks running along a single line until the next non-empty cell cuts it off. Dragging column borders and row dividers by hand is slow, and once there are enough columns it is easy to miss a few. Spire.XLS for JavaScript performs this work 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.


Autofit the Height of a Single Row and the Width of a Single Column

AutoFitRow works out the height of the given row from its content, and AutoFitColumn works out the width of the given column the same way. Each one affects only the row or the column it is pointed at and leaves the rest of the table untouched, which is what you want when a single overflow is the only thing in the way.

Note that row height autofit only means anything for content that needs to wrap: with wrapping turned off the text always sits on one line and the height simply follows the font size, so there is no taller value to calculate. The steps are:

  1. Load the workbook and get the first worksheet.
  2. Autofit the height of that row with AutoFitRow.
  3. Autofit the width of that column with AutoFitColumn.
  4. Save the workbook.

Here is a complete code example that autofits the height of a single row and the width of a single column in React:

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

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

    // Autofit the height of row 2
    sheet.AutoFitRow(2);

    // Autofit the width of column 4
    sheet.AutoFitColumn(4);

    // Save the workbook
    const outputFileName = "AutoFitSingleRowColumn.xlsx";
    workbook.SaveToFile(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>Autofit Row Height and Column Width</h1>
      <button onClick={autoFitSingleRowColumn}>Start</button>
    </div>
  );
}

export default App;

After running, the effect of autofitting a single row height and a single column width:

Autofit a single row height and a single column width


Autofit the Heights of Multiple Rows and the Widths of Multiple Columns

When the whole table needs tidying, calling the methods row by row is not realistic. Calling AutoFitRows or AutoFitColumns on a range recalculates every row and every column the range covers from its own content, so a single call lines up the whole block — the kind of pass you want before exporting a report. The steps are:

  1. Load the workbook and get the first worksheet.
  2. Autofit the heights of the rows with AutoFitRows.
  3. Autofit the widths of the columns with AutoFitColumns.
  4. Save the workbook.

Here is a complete code example that autofits the heights of multiple rows and the widths of multiple columns in React:

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

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

    // Get the used range of the worksheet
    const range = sheet.AllocatedRange;

    // Autofit the height of every row in the range
    range.AutoFitRows();

    // Autofit the width of every column in the range
    range.AutoFitColumns();

    // Save the workbook
    const outputFileName = "AutoFitMultipleRowsColumns.xlsx";
    workbook.SaveToFile(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>Autofit Row Height and Column Width</h1>
      <button onClick={autoFitMultipleRowsColumns}>Start</button>
    </div>
  );
}

export default App;

After running, the effect of autofitting multiple row heights and multiple column widths:

Autofit multiple row heights and multiple column widths


FAQ

I called AutoFitRow() and the row height did not change at all?

Cause: Row height autofit only applies to content that needs to wrap. With wrapping turned off the text stays on a single line and the height follows the font size, so autofit arrives at the same value as the existing height and appears to have done nothing.

Solution: Set WrapText to true first, then autofit the row height:

// With wrapping off, autofitting the row height changes nothing
sheet.Range.get("D2").Style.WrapText = false;
sheet.AutoFitRow(2);

// With wrapping on, the height is recalculated from the wrapped line count
sheet.Range.get("D2").Style.WrapText = true;
sheet.AutoFitRow(2);

AutoFitColumns() has no effect on merged cells?

Cause: Column width autofit measures the content of individual cells. In a merged range only the top-left cell actually holds text and every other position in the range is empty, so the width it works out is only enough for that top-left content.

Solution: Set the width of a merged range by hand with ColumnWidth:

// A7:D7 is a merged range, so autofit cannot work out its combined width
sheet.Range.get("A7:D7").Merge();
sheet.Range.get("A7:D7").AutoFitColumns();

// Set the column width by hand so the merged text fits
sheet.Range.get("A7").ColumnWidth = 40;

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.

A scanned page that landed sideways, a landscape table sandwiched between portrait pages, or a document you want turned as a whole before printing — none of these need to be re-laid out. The rotation angle is a property of the PDF page itself; changing it only affects how the page is displayed, and the content on the page stays as it is.

Spire.PDF for JavaScript loads, modifies and saves PDF documents directly in the browser based on WebAssembly, reading and writing 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.PDF for JavaScript in a React Project. The following examples assume Spire.PDF is installed and the WebAssembly module has been initialized.


Rotate a New Page

To give every page in a section the same orientation, one setting on the section is enough: section.PageSettings.Rotate takes PdfPageRotateAngle.RotateAngle90, and all pages in that section are rotated 90 degrees clockwise. RotateAngle180 and RotateAngle270 are also available, and leaving it unset means no rotation.

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

    // Create a blank document
    const doc = new pdfModule.PdfDocument();

    // Add a section; every page in it shares a 90-degree clockwise rotation
    const section = doc.Sections.Add();
    section.PageSettings.Size = pdfModule.PdfPageSize.A4();
    section.PageSettings.Rotate = pdfModule.PdfPageRotateAngle.RotateAngle90;

    // Add a page to the section
    const page = section.Pages.Add();

    // Draw a line of text on the page
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 14 });
    page.Canvas.DrawString({
      s: 'This page is set to rotate 90 degrees at creation time',
      font: font,
      brush: pdfModule.PdfBrushes.get_Black(),
      x: 40,
      y: 60,
      format: new pdfModule.PdfStringFormat({ alignment: pdfModule.PdfTextAlignment.Left })
    });

    // Save and read back from the VFS to trigger the download
    const outputFileName = 'RotatedDocument.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    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 Rotated PDF</h1>
      <button onClick={createRotatedPdf}>
        Start Creating
      </button>
    </div>
  );
}

export default App;

If you only want to pass the angle while creating a page, the overload doc.Pages.Add(size, margins, rotation) works as well.

The A4 page is set to a 90-degree rotation at creation time:

The A4 page is set to a 90-degree rotation at creation time


Rotate an Existing Page

To change the orientation of an existing document, note that the angle lives in the page's own Rotation property: read the current value first, then add this call's rotation to it. What gets written back is the enum value of PdfPageRotateAngle (0 for no rotation, 1 for 90 degrees), not the angle itself.

function App() {
  const rotateExistingPage = 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 be processed 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);

    // Take the first page and add this call's rotation to its current angle
    const page = doc.Pages.get_Item(0);
    let rotation = page.Rotation.value + pdfModule.PdfPageRotateAngle.RotateAngle90.value;

    // The enum value only runs from 0 to 3; 4 means a full turn, back to no rotation
    if (rotation === 4) {
      rotation = 0;
    }
    page.Rotation = rotation;

    // Save and read back from the VFS to trigger the download
    const outputFileName = 'RotatedPage.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    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>Rotate a Specific Page</h1>
      <button onClick={rotateExistingPage}>
        Start Rotating
      </button>
    </div>
  );
}

export default App;

Only the first page is rotated 90 degrees, the remaining pages keep their orientation:

Only the first page is rotated 90 degrees; the remaining pages keep their orientation


FAQ

Why does assigning to Rotation report "Value is not an integer"

Cause: The write side of page.Rotation only accepts an integer. Passing a PdfPageRotateAngle enum object directly throws Assert failed: Value is not an integer: PdfPageRotateAngle.RotateAngle90 (object).

Solution: Take the enum's value and assign that:

page.Rotation = pdfModule.PdfPageRotateAngle.RotateAngle90.value;

Why does the page end up rotated 180 degrees after I pass 90

Cause: The enum values of PdfPageRotateAngle are 0, 1, 2 and 3, standing for 0, 90, 180 and 270 degrees. That value is what the write side expects, not the angle. Writing page.Rotation = 90 throws no error but does not give you 90 degrees, and looking the value up with PdfPageRotateAngle.fromValue(90) throws Invalid value for spirepdfPdfPageRotateAngle.

Solution: Convert to the enum value before writing:

// 90 degrees
page.Rotation = pdfModule.PdfPageRotateAngle.RotateAngle90.value;

// 180 degrees
page.Rotation = pdfModule.PdfPageRotateAngle.RotateAngle180.value;

Why does the rotation argument on section.Pages.Add(...) have no effect

Cause: The overload that takes a rotation argument, Add(size, margins, rotation), only works on doc.Pages. On section.Pages the argument is ignored, and the page orientation is decided by the PageSettings.Rotate of the section it belongs to. The /Rotate entry in the file stays 0, the page is not rotated, and no error is raised.

Solution: Pick one of the two forms below, and do not pass the argument to section.Pages:

// Form 1: set it on the section, applying to every page in that section
section.PageSettings.Rotate = pdfModule.PdfPageRotateAngle.RotateAngle90;
section.Pages.Add();

// Form 2: create the page on doc.Pages and pass the angle
const page = doc.Pages.Add(
  pdfModule.PdfPageSize.A4(),
  new pdfModule.PdfMargins(),
  pdfModule.PdfPageRotateAngle.RotateAngle90
);

A page can still be adjusted on its own after it has been created; page.Rotation overrides the section setting:

// Rotate only the second page to 180 degrees, leaving the rest untouched
doc.Pages.get_Item(1).Rotation = pdfModule.PdfPageRotateAngle.RotateAngle180.value;

Get a Free License

If you wish to remove the evaluation message from the result document or remove feature limitations, please contact sales to obtain a temporary license valid for 30 days.

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.

page 21