Below, please find the release notes for VirtualViewer v5.1. For questions, please contact us at questions@snowbound.com or by phone at (617) 607-2010.

Page Limit Configuration for .XLSX Documents

Rendering large XLSX documents is both time and memory intensive. VirtualViewer 5.0 saw significant improvements in rendering speed, but by their nature, these large documents still use a great deal of memory. If VirtualViewer is not allocated enough memory, or if many users are opening large XLSX documents, the viewer application may experience memory exceptions.

Now, a new configuration parameter allows administrators to limit how many pages of an XLSX document will be rendered. The parameter is an init-param set in web.xml, called xlsxMaximumPageLimit. An example may be found in web.example.xml.

Setting this configuration parameter will cut off XLSX documents at the given page maximum. For example, an administrator may set xlsxMaximumPageLimit to 2. An XLSX document with one or two pages will render normally. An XLSX document with five pages will be cut off. On the client viewer, the document will only display two pages. A dialog will show to the user with a warning message that the document has been cut off. Additionally, the page count will not be displayed, but will be represented with an ellipsis: ....

The document itself is not affected by this limit. Opening a document will only limit what is displayed to the user; it will not modify the original document. Since viewer functionality like export, save, and save as would produce a page-limited result, these buttons and API functions are disabled if a document has been limited by this parameter.

New Configuration

The new configuration parameter is an init-param in web.xml:

<init-param>
    <param-name>xlsxMaximumPageLimit</param-name>
    <param-value>0</param-value>
</init-param>

A value of 0 is the default, which represents no page limitation. With a value of 0 set in xlsxMaximumPageLimit, all XLSX documents will render completely. Acceptable values for the parameter are positive integers.

Performance Optimization for Document Load

VirtualViewer now displays long documents, particularly SnowDoc formats, significantly faster.

The first and simplest optimization was completely on the client. Documents with one thousand pages or more would take longer than necessary to load, and would halt all UI operations until all page thumbnails could be rendered. All other pieces of loading are asynchronous and do not lock up the UI, so this experience stood out. Now, thumbnail rendering has been optimized to quickly stub out all the necessary thumbnails, and only create the more extensive HTML structure when the thumbnail comes into view.

The more complex optimization adjusted how the client loads documents. Previously, VirtualViewer would load a document by first loading a document model with a great deal of metadata about the document, and then loading metadata about a page image, and finally loading a page image. Waiting for the document model to load is necessary for most viewer operations, but it can take a long time to create this data structure, particularly for long documents or for complex formats like docx.

With new RasterMaster development, SnowDoc formats like docx are now processed slightly differently. All of the long document processing operations are handled in a separate thread. This thread persists in the background until the document is fully processed, an error occurs, or the thread is interrupted. The servlet thread can either wait for the SnowDoc processing thread to complete, or it can retrieve individual pages while the document is still processing.

This new development allows VirtualViewer to start retrieving pages much earlier. Now, VirtualViewer requests the document model data structure at the same time as the first page. The document model data structure still needs to wait for all document processing to be complete, and users will not be able to perform several operations until that is loaded; but pages will begin loading immediately. This effect will be most dramatic with SnowDoc formats that are entering the cache.

New Callbacks

  • pageLoaded will be called once page load completes. Page load is the new initial load of a page, where the viewer will receive both metadata and image data for a page. The page image may fall out of the buffer and need to be reloaded; in that case, the image alone will be loaded. pageLoaded should be called only once for each page. The following parameters will be provided to the callback in the argument object:
    • documentId {String} The ID of the document whose page was loaded
    • displayName {String} If a new display name was sent with the page, this argument will store it. It will not yet be set in the viewer
    • paneIndex {number} The 0-based index of the pane displaying the loaded document
    • loadedPageNumber {number} The 0-indexed number of the loaded page
    • searchableStatusBeforeLoad {boolean} The document’s searchable status–whether the document has machine-parsable text–may change on page load. This sends the old value.
  • pageLoadError will be called if a page load fails. Page load is the new initial load of a page, where the viewer will receive both metadata and image data for a page. If page load fails, it will be retried once. The following parameters will be provided to the callback in the argument object:
    • documentId {String} The ID of the document whose page was loaded
    • finalTry {boolean} This will be true if the page failed on the second and final attempt to load
    • paneIndex {number} The 0-based index of the pane displaying the loaded document
    • pageNumber {number} The 0-indexed number of the failed page
  • pageCountUpdated will be called if the page count updates. We may load pages before the document is fully loaded; if we do, and therefore if we don’t have the correct page count yet, we update the page count as pages are loaded. The loaded page data will tell us if there’s a next page, and the page count will be updated accordingly. The following parameters will be provided to the callback in the argument object:
    • documentId {String} The ID of the document that is currently loading
    • paneIndex {number} The 0-based index of the pane displaying the loading document
    • oldPageCount {number} The page count before the update
    • newPageCount {number} The current page count, after the update

Stuck Thread Interruption

Most server platforms have tools to monitor and interrupt long-running application threads. Thread interruption is the safest way to stop a thread from processing. Attempting to kill a thread is extremely unsafe, and can create a number of insidious bugs.

With thread interruption, the server program will request that an application thread be interrupted. The listening application must act on the interruption by returning and exiting operation cleanly. Previously, VirtualViewer was not able to respond well to these thread interruption tools–the application did not check for interruption, and exited on error and success.

Now, VirtualViewer will check for interruption, and will return early if the thread has been interrupted. On the client side, the viewer receives a status code noting that operation has timed out. If the viewer receives three of these status codes for a particular document, it will no longer attempt to load the document.

Configuration

Each server platform will have a different mechanism for monitoring threads, and interrupting possibly stuck threads. For example, Tomcat implements a class called the StuckThreadDetectionValve; with an entry into server.xml, the server will use the StuckThreadDetectionValve to monitor long-running threads.

Changes to JavaScript Initialization Hooks

On initialization, VirtualViewer calls the two functions beforeVirtualViewerInit and afterVirtualViewerInit, if they exist. These functions are both intended to be defined to call custom code.

Simplification of beforeVirtualViewerInit

Now, the function beforeVirtualViewerInit is not required to return any value, and is not expected to call afterVirtualViewerInit. However, beforeVirtualViewerInit may return a value of true in order to cancel the VirtualViewer initialization process. If a VirtualViewer initialization API is called in beforeVirtualViewerInit, VirtualViewer will now detect that it has already been initialized, and will not attempt to reinitialize. VirtualViewer will call afterVirtualViewerInit; that is no longer the responsibility of the custom code. If beforeVirtualViewerInit has returned a value of true, initialization returns early.

Startup of VirtualViewer JavaScript code now follows this flow:

  1. A VirtualViewer instance is created and assigned to the global variable virtualViewer.

  2. beforeVirtualViewerInit is called, if it exists and is a function.

  3. If beforeVirtualViewerInit has returned a value of true, initialization is canceled.

  4. If the VirtualViewer instance has not yet been initialized, virtualViewer.initViaURL() is called. If the VirtualViewer instance has already been initialized, this step is skipped.

  5. afterVirtualViewerInit is called.

Now, both beforeVirtualViewerInit and afterVirtualViewerInit take no parameters and are expected to return no values; beforeVirtualViewerInit may return a value of true to cancel initialization, but otherwise, VirtualViewer will detect whether initialization has occurred.

New Locations for Custom Code

Previously, beforeVirtualViewerInit and afterVirtualViewerInit could be defined within index.html. This could clutter index.html, and make it more difficult to upgrade.

Now, there are two new files in the user-config directory, custom-code.js and custom-style.css. Both are loaded by index.html, and are intended to hold only custom code and style. The stubs of beforeVirtualViewerInit and afterVirtualViewerInit have been moved from index.html to custom-code.js.

If definitions of beforeVirtualViewerInit and afterVirtualViewerInit are still in index.html, they must be moved to custom-code.js or must be defined before the script tag that loads custom-code.js.

Speech Synthesis for Documents

Several browsers implement a Javascript speech synthesis API to manually provide text-to-speech controls. VirtualViewer now may use those API to read documents that contain text.

When a user opens a document that contains text, a button to toggle text-to-speech controls will appear on the toolbar. Clicking the button activates the control bar, which allows the user to play, stop and pause the text-to-speech; the user may also use this bar to skip pages with the next and previous buttons.

Configuration

To ensure that VirtualViewer can read the document text, set the enableTextExtraction parameter in config.js to true.

Known Limitations

VirtualViewer’s text-to-speech functionality will not read the correct text for documents with text that have rotated pages. If the user clicks rotate while the document is open, the text-to-speech function will read the correct text; once the user saves the document, the rotation is essentially “burned in” and the text itself is rotated, meaning that the text no longer can be read left-to-right.

The text-to-speech button will also remain deactivated if OCR is performed on a document, since OCR functions often return unpronounceable text.

Screen Reader Compatibility

VirtualViewer is now compatible with screen readers. In addition to the text-to-speech button described above, all the controls and the displayed document itself have been reorganized and updated to allow most screen readers to interact smoothly with the viewer. A screen reader will now read out navigation items as a user tabs through them or mouses over them, and will read out a displayed text document when it comes into focus. The text document is treated as the main content of the viewer.

The document may come into focus by tabbing through the viewer, or by tabbing to a new “skip to main content” link added at the top of the tab order, in the top left corner of the viewer.

Configuration

To ensure that VirtualViewer can read the document text, set the enableTextExtraction parameter in config.js to true.

Known Limitations

Essentially, VirtualViewer is extracting plain text from a document, and displaying it in a hidden HTML structure that a screen reader can navigate. Screen readers will therefore encounter the same limitations of the text-to-speech functionality described above, and to existing text selection, copy, and paste functionality existing in the viewer. A document may not be read if its format does not contain embedded text, if it was rotated such that the positional data of the text is no longer organized horizontally, or if OCR (Optical Character Recognition) returns poor results.

New Zooming Behavior for Document Compare

A new configuration item, vvConfig.zoomLock, controls whether two documents open in document compare will zoom together. This configuration is on by default; in order to turn it off, the configuration item must be set to false.

If vvConfig.zoomLock is not set or is set to true, zooming one document will zoom the other simultaneously. When opening a document in document compare, it will be initially set to the main document’s zoom rather than the default zoom set in User Preferences. If vvConfig.zoomLock is set to false, VirtualViewer’s previous behavior will apply, and documents may be zoomed independently in document compare.

Two new API functions programmatically control whether zoom is locked in document compare:

  • toggleZoomLock will set the zoom lock to the opposite of its current setting. This API takes no arguments.

  • setZoomLock allows zoom lock to be manually turned off and on.

    • lock {boolean} Pass in true to lock zoom for document compare, and false to unlock zoom.

Audio Support

In addition to video, VirtualViewer can now play audio files that are supported by most browsers. There is no editing or annotation support at this time; audio can only be viewed and downloaded. Audio format support will depend on the capabilities of the web browser.

Supported formats

VirtualViewer uses the browser’s HTML5 audio player to play audio, and can play all types of audio supported by a browser’s player. Most browsers support MP3, M4A, FLAC, Ogg and WAV. This browser compatibility chart has more details: https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats

User preferences

There are two new preference options for audio. These options can be viewed and modified, along with video preferences, in the Media Preferences tab in User Preferences:

  • Audio Loop: whether to start the audio again from the beginning when it has finished playing.
  • Continue Audio on Tab Switch: whether to keep playing an audio file after you have opened or switched to another document. If this is enabled, you can listen to an audio file while viewing other documents in VirtualViewer.

All of these options have equivalent config.js configuration options as defaults.

New and Changed Client-side API

  • setPageDisplayNames will set text display names for thumbnails, if vvConfig.displayThumbFooters is set to true. The pages will display the given names instead of “Page 1”, “Page 2”, and so on. This API was previously available through a non-public API, and this safer version, virtualViewer.setPageDisplayNames(["A", "B"]);, should now be used instead.
    • displayNames {string[]} An array of the display names to be used. This array should be the length of the document (the length returned by virtualViewer.getPageCount()). Any undefined or null values in the array will cause the page to display the page number.

Fixes and Changes

Updated Translations and Localization

Due to the accessibility improvements in VirtualViewer this release, many strings were updated and localization keys were shifted. VirtualViewer has significantly updated locale files as a result.

Zoom Settings in Internet Explorer

By default, VirtualViewer uses the Javascript image handling library pica to sharpen images when they are zoomed to less than 100%. The configuration item vvConfig.useBrowserScaling controls whether to use pica; on older browsers, pica can be slow. On Internet Explorer, pica may take minutes to zoom out an image, which uses an unacceptable amount of processing power and memory, when the user will likely close the document before seeing the zoomed image. This also has consequences for certain API like invertImage, which depend on the presence of a zoomed image.

Now, VirtualViewer disables pica by default when running on Internet Explorer, essentially treating the process as if vvConfig.useBrowserScaling were set to true. Set vvConfig.serverScaling to true to use server scaling instead.

Miscellaneous

  • The language attribute in the HTML tag is now dynamically set to reflect the settings in vvDefines and the browser
  • Bookmarks reference the correct pages after reordering or deleting pages in a document
  • The annotation indicator no longer appears on thumbnails of cropped pages–annotations may not be placed on cropped pages
  • When a document in an inactive tab is saved, it will update the tab name but will not erroneously display in the active tab
  • The API invertImage now functions as expected in Internet Explorer a