Developer Guide for Foxit PDF SDK for Web (9.0)
Contents
Foxit PDF SDK for Web Overview
Foxit PDF SDK for Web is a lightweight powerful PDF library for web applications developed taking all advantages of Foxit’s signature core rendering engine. Using the SDK, developers can deploy and customize a complete PDF viewer to display, annotate, fill forms and sign documents in a web browser. Integrating Foxit PDF SDK for Web into a zero-footprint web application allows end users to view PDF documents on any type of device without installing anything.
Why Foxit PDF SDK for Web is your choice
Foxit is a leading software provider of solutions for reading, editing, creating, organizing, and securing PDF documents. Foxit PDF SDK for Web is a cross-platform solution for PDF online viewing. Foxit PDF SDK for Web enterprise edition has been chosen by many of the world’s leading firms for integration into their solutions. Customers choose this product for the following reasons:
Fully customizable
Developers can easily design a unique style for their Foxit PDF SDK for Web user interface, making it consistent to their own branding and other web applications.
Easy to integrate
Developers can easily integrate Foxit PDF SDK for Web by referring to the product’s knowledge base and writing a small amount of code to display and edit PDF files. The web-based pdf viewer also provides a large amount of interfaces to connect users and user data.
Standard and consistent annotation data
The annotations in Foxit PDF SDK for Web are consistent when viewing and editing in other applications, as well as following industry-leading professional standards for quality and compliance.
Powered by Foxit’s high fidelity rendering PDF engine
The core technology of Foxit PDF SDK for Web is based on Foxit’s PDF engine, which is trusted by a large number of well-known companies. Foxit’s powerful engine makes document viewing fast and consistent in all environments.
In addition, Foxit’s products are offered with the full support of our dedicated support engineers if support and maintenance options are purchased. Updates are released on a regular basis. Foxit PDF SDK for Web is the most cost-effective choice if you want to develop a cross-platform web PDF document viewer.
Audience and Scope
This document is primarily intended for developers who need to integrate the Foxit PDF SDK for Web into their web applications. It includes the direct reference examples as well as custom front-end APIs for customization.
Your Web Application
Foxit PDF SDK for Web provides a solution that enables a web viewer to interact with PDFs seamlessly without any plugins or local applications. Developers should prepare a PDF hosting server like Nginx, Apache or the HTTP server in Node.js platform and do the usual configuration before using Foxit PDF SDK for Web.
Browser Support
Foxit PDF SDK for Web currently supports all modern browsers. From version 9.0, Internet Explorer browser is no longer supported.
Evaluation
Foxit PDF SDK for Web allows users to download the trial version to evaluate the SDK. The trial version is the same as the standard version except for the 15-day limitation for free trial and the trial watermarks in the generated pages. After the evaluation period expires, customers should contact the Foxit sales team and purchase licenses to continue using Foxit PDF SDK for Web.
License
Developers are required to purchase licenses to use Foxit PDF SDK for Web in their solutions. Licenses grant users permission to release their applications based on Foxit PDF SDK for Web. However, users are prohibited to distribute any documents, sample codes, or source codes in the released packages of Foxit PDF SDK for Web to any third party without the permission from Foxit Software Incorporated.
Getting Started
Understanding the Package Structure
Package Introduction
Foxit PDF SDK for Web provides three packages as follows:
Light package: FoxitPDFSDKForWeb_9_XXX_NoFonts.zip (excludes font resources)
Standard package: FoxitPDFSDKForWeb_9_XXX.zip (includes font resources)
Full package: FoxitPDFSDKForWeb_9_XXX_Full.zip (includes font resources, document comparison and advanced editor)
If you already have the font resources or only want to use online fonts, you can choose the light package if you don’t want to make any changes to the font library and don’t care about the size of the package, choose the standard package. If you want some advanced features, such as document comparison or advanced editor, choose the full package.
The package contains the following folder structure:
Folder/File | Description |
docs: | Contains API reference documents and Foxit PDF SDK for Web’s developer guide. |
examples: | A series of demos and examples of how to take advantage of all Foxit PDF SDK for Web features. |
external | Font resources (only for full package). |
integrations | Integration samples for wrapping Foxit PDF SDK for Web into current popular JavaScript frameworks (AngularJS/React.js/Vue.js). |
lib | Foxit PDF SDK for Web core libraries. |
server | http-server and the Node.js scripts for a series of server-based utility applications to use in the viewer. |
legal.txt | Legal and copyright information. |
package.json | Project description file. |
The “lib” folder’s file structure is provided as follows:
jr-engine | Front-end rendering engine. |
locales | Internationalized entries data for using the viewer in different languages. Every language is placed in a different directory with its own label. |
PDFViewCtrl | Plugins for the PDFViewCtrl library. |
stamps | Stamps resources, image files and templates. |
assets | Contains the template resource files needed by the document comparison function, currently only included in the full package. |
uix-addons | All plugins for the UIExtensions project. |
adaptive.js | A responsive design script to adapt the viewer to mobile devices |
PDFViewCtrl.css | CSS file for the PDFViewCtrl viewer UI style. |
PDFViewCtrl.full.js | Complete script file for the PDFViewCtrl viewer library. |
PDFViewCtrl.js | Script file for the PDFViewCtrl viewer library without third-party libraries. |
PDFViewCtrl.polyfills.js | Browser-adapted polyfill script file for the PDFViewCtrl viewer library. |
PDFViewCtrl.vendor.js | Third-party libraries script used by PDFViewCtrl (See the lists later). |
preload-jr-worker.js | Worker script for loading resources of JS engine in parallel to the UI for improving the viewer loading speed. |
UIExtension.css | The default CSS file of the UI. |
UIExtension.vw.css | The CSS file using vmin unit. |
UIExtension.full.js | Complete script file for the UIExtension full-featured viewer library. |
UIExtension.js | Script file for the UIExtension viewer library without third-party libraries |
UIExtension.polyfills.js | Browser-adapted polyfill script file for the UIExtensions viewer library. |
UIExtension.vendor.js | Third-party libraries script used by UIExtension (See the lists later). |
WebPDFJRWorker.js | Script files running in the Web Worker, which are used for calling the front-end rendering engine. |
WebPDFSRWorker.js | Script files running in the Web Worker, which are used for calling the server rendering engine. |
*.d.ts | “*.d.ts” files are used to provide TypeScript (version 3.3 or higher) with type information about APIs written in JavaScript. The purpose is to enable IDEs to recognize it and provide us with code hints, as well as perform static type checking during compilation, providing convenience while ensuring the accuracy of calling APIs. |
Package.json
Foxit PDF SDK for Web provides a package.json file to help developers quickly deploy and use the SDK, and make it easy to integrate into their project. The content is as follows:
{ "name": "foxit-pdf-sdk-for-web", "version": "9.0.0", "description": "Foxit pdf sdk for web.", "author": "Foxit Software Inc.", "main": "./lib/PDFViewCtrl.full.js", "scripts": { "start": "concurrently --kill-others \"npm run start-http-server\" \"npm run start-snapshot-server\"", "start-snapshot-server": "node ./server/snapshot/src/index -p 3002", "start-http-server": "node ./server/index" }, "devDependencies": { "boxen": "^4.1.0", "chalk": "^2.4.1", "concurrently": "^4.1.0", "http-proxy-middleware": "^0.19.1", "koa": "^2.7.0", "koa-body": "^4.0.4", "koa-body-parser": "^1.1.2", "koa-router": "^7.4.0", "koa2-connect": "^1.0.2", "lru-cache": "^4.1.3", "raw-body": "^2.3.3", "require-dir": "^1.0.0", "serve-handler": "^6.0.2" }, "serve": { "port": 8080, "public": "/", "proxy": { "target": "http://127.0.0.1:3002", "changeOrigin": true } } }
The third-party libraries used in Foxit PDF SDK for Web
Foxit PDF SDK for Web provides its script files in two versions: the full version script that includes the third-party libraries, and the regular script without any third-party libraries. If your project already uses the dependencies included in the SDK’s third-party libraries, you don’t need to re-install them.
The PDFViewCtrl.full.js script contains:
PDFViewCtrl.full.js Complete script file for the PDFViewCtrl viewer library.
PDFViewCtrl.polyfills.jsBrowser-adapted polyfill script file for the PDFViewCtrl viewer library.
PDFViewCtrl.vendor.jsThird-party libraries script used by PDFViewCtrl (See the list of vendors below this section).
PDFViewCtrl.jsScript file for the PDFViewCtrl viewer library without third-party libraries.
So, PDFViewCtrl.polyfills.js + PDFViewCtrl.vendor.js + PDFViewCtrl.js = PDFViewCtrl.full.js.
Essentially, the two scripts below are the same thing:
<script src="../FoxitPDFSDKForWeb/lib/PDFViewCtrl.full.js"></script> <script src="../FoxitPDFSDKForWeb/lib/ PDFViewCtrl.polyfills.js"></script> <script src="../FoxitPDFSDKForWeb/lib/PDFViewCtrl.vendor.js"></script> <script src="../FoxitPDFSDKForWeb/lib/PDFViewCtrl.js"></script>
The third-party libraries contained in PDFViewCtrl.vendor.js are outlined below:
jquery i18next i18next-chained-backend i18next-localstorage-backend i18next-xhr-backend jquery-contextmenu dialog-polyfill hammerjs eventemitter3
The UIExtension.full.js script contains:
UIExtension.full.js Complete script file for the UIExtension viewer library.
UIExtension.polyfills.js Browser-adapted polyfill script file for the UIExtension viewer library.
UIExtension.vendor.jsThird-party libraries script used by UIExtension (See the list of vendors below this section).
UIExtension.jsScript file for the UIExtension viewer library without third-party libraries.
So, UIExtension.polyfills.js + UIExtension.vendor.js + UIExtension.js = UIExtension.full.js.
Essentially, the two scripts below are the same thing:
<script src="../FoxitPDFSDKForWeb/lib/UIExtension.full.js"></script> <script src="../FoxitPDFSDKForWeb/lib/UIExtension.polyfills.js"></script> <script src="../FoxitPDFSDKForWeb/lib/UIExtension.vendor.js"></script> <script src="../FoxitPDFSDKForWeb/lib/UIExtension.js"></script>
The third-party libraries contained in UIExtension.vendor.js are outlined below:
jquery i18next i18next-chained-backend i18next-localstorage-backend i18next-xhr-backend dialog-polyfill hammerjs eventemitter3 spectrum-colorpicker file-saver
Quickly Run Examples
Foxit PDF SDK for Web comes with a lot of example projects and files for building the viewer and/or implementing additional functionality. These examples are provided in the examples folder of Foxit PDF SDK for Web. To run them, initialize your (local) web server, open your browser and add the localhost (https://localhost:port) or corresponding IP number URL. The directory list of files will be displayed and you can choose which sample to use.
To quickly get a web server running on your local system, you can use node.js http-server:
http-server
Additionally, you can append the ‘-o’ command to open directly in your browser window:
http-server –o
You can also use Python’s SimpleHTTPServe module:
python -m http.server 8000
You may want to refer to Set up local server for more information.
See also
Start Http Server using Nginx
Start Http Server using Nodejs
Integration
This section will help you to quickly get started with using Foxit PDF SDK for Web to build a simple web PDF viewer and a full-featured PDF viewer with step-by-step instructions provided.
Preparations
Create a new web project
Create a new directory as a project folder, such as “D:/test_web”.
Copy the “lib“, “server“, and “external” (if you need to use the font resources) folders, as well as the “package.json” file from Foxit PDF SDK for Web package to “D:/test_web”.
Copy a PDF file (for example, the demo guide in the “docs” folder) to “D:/test_web”.
Create a html file (index.html) in the “D:/test_web” folder. Then the directory structure is:
test_web +-- lib (copy from the Foxit PDF SDK for Web package) +-- server (copy from the Foxit PDF SDK for Web package) +-- package.json (copy from the Foxit PDF SDK for Web package) +-- index.html
The whole content of the index.html is:
<html> <head> <meta charset="utf-8"> <style> .fv__ui-tab-nav li span { color: #636363; } .flex-row { display: flex; flex-direction: row; } </style> <!-- ignore other unimportant code --> </head> <body> </body> </html>
Integrate the basic webViewer into your project
This section will describe how to integrate the basic webViewer sample using PDFViewCtrl based on the above created project. Just follow the steps below:
Add styles (/lib/PDFViewCtrl.css) to the <head> tag of the HTML page:
<link rel="stylesheet" type="text/css" href="./lib/PDFViewCtrl.css">
Import the “PDFViewCtrl.full.js” library found in the “lib” folder:
<script src="./lib/PDFViewCtrl.full.js"></script>
In the HTML <body> tag, add the <div> elements as the web viewer container:
<div id="pdf-viewer"></div>
Initialize PDFViewCtrl:
<script> var licenseSN = "Your license SN"; var licenseKey = "Your license Key"; </script> <script> var PDFViewer = PDFViewCtrl.PDFViewer; var pdfViewer = new PDFViewer({ libPath: './lib', // the library path of Web SDK. jr: { licenseSN: licenseSN, licenseKey: licenseKey, } }); pdfViewer.init('#pdf-viewer'); // the div (id="pdf-viewer") <script>
Note: The trial values of licenseSN and licenseKey can be found in the examples/license-key.js file of Foxit PDF SDK for Web package.
Open a PDF document:
// modify the file path as your need. fetch('/FoxitPDFSDKforWeb_DemoGuide.pdf').then(function(response) { response.arrayBuffer().then(function(buffer) { pdfViewer.openPDFByFile(buffer); }) })
The above steps are the key points of integrating the simple demo to your created project using PDFViewCtrl. After finishing it, refresh your browser (<index.html>).
Now, in this simple web PDF viewer, you can zoom in/out the PDF document by right-clicking anywhere on the page to select the zoom in or zoom out options.
The whole content of the index.html is:
<html> <head> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="./lib/PDFViewCtrl.css"> <!-- You can delete the following style because it doesn't work in this project --> <style> .fv__ui-tab-nav li span { color: #636363; } .flex-row { display: flex; flex-direction: row; } </style> <!-- ignore other unimportant code --> </head> <body> <div id="pdf-viewer"></div> <script src="./lib/PDFViewCtrl.full.js"></script> <script> var licenseSN = "Your license SN"; var licenseKey = "Your license Key"; </script> <script> var PDFViewer = PDFViewCtrl.PDFViewer; var pdfViewer = new PDFViewer({ libPath: './lib', // the library path of Web SDK. jr: { licenseSN: licenseSN, licenseKey: licenseKey, } }); pdfViewer.init('#pdf-viewer'); // the div (id="pdf-viewer") // modify the file path as your need. fetch('/FoxitPDFSDKforWeb_DemoGuide.pdf').then(function (response) { response.arrayBuffer().then(function (buffer) { pdfViewer.openPDFByFile(buffer); }) }) </script> </body> </html>
Integrate the complete webViewer into your project
The previous section introduces how to integrate the basic webViewer sample using PDFViewCtrl, which is just a simple web PDF viewer. In this section, we will show you how to integrate the advanced webViewer using UIExtension based on the Preparations. Follow the steps below:
Add styles (/lib/UIExtension.css) to the <head> tag of the HTML page.
<link rel="stylesheet" type="text/css" href="./lib/UIExtension.css">
Import the “UIExtension.full.js” library found in the “lib” folder:
<script src="./lib/UIExtension.full.js"></script>
In the HTML <body> tag, add the <div> elements as the webViewer container:
<div id="pdf-ui"></div>
Initialize UIExtension:
<script> var licenseSN = "Your license SN"; var licenseKey = "Your license Key"; </script> <script> var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: './lib', // the library path of web sdk. jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: '#pdf-ui' // the div (id="pdf-ui"). }); <script>
Note: The trial values of licenseSN and licenseKey can be found in the examples/license-key.js file of Foxit PDF SDK for Web package.
Open a PDF document:
// modify the file path as your need. fetch('/FoxitPDFSDKforWeb_DemoGuide.pdf').then(function(response) { response.arrayBuffer().then(function(buffer) { pdfui.openPDFByFile(buffer); }) })
The above steps are the key points of integrating the advanced webViewer to your created project using UIExtension. After finishing it, refresh your browser (<index.html>).
Now, it is a full-featured web PDF viewer, you can view/edit/comment/protect the PDF document as desired.
The whole content of the index.html is:
<html> <head> <meta charset="utf-8"> <link rel="stylesheet" type="text/css" href="./lib/UIExtension.css"> <style> .fv__ui-tab-nav li span { color: #636363; } .flex-row { display: flex; flex-direction: row; } </style> <!-- ignore other unimportant code --> </head> <body> <div id="pdf-ui"></div> <script src="./lib/UIExtension.full.js"></script> <script> var licenseSN = "Your license SN"; var licenseKey = "Your license Key"; </script> <script> var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: './lib', // the library path of web sdk. jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: '#pdf-ui' // the div (id="pdf-ui"). }); // modify the file path as your need. fetch('/FoxitPDFSDKforWeb_DemoGuide.pdf').then(function (response) { response.arrayBuffer().then(function (buffer) { pdfui.openPDFByFile(buffer); }) }) </script> </body> </html>
Integration Modes
Integrate as a Global Variable
You can integrate the Foxit PDF SDK for Web to your project as a global variable:
<script src="./lib/PDFViewCtrl.full.js"></script> var PDFViewer = PDFViewCtrl.PDFViewer; var pdfViewer = new PDFViewer(…)
For a working example, check out the complete_WebViewer demo in the “examples/UIExtension” folder.”
Integrate as module
For more integration modes, you may want to check our working examples in the “examples/UIExtension/integrate-as-module/” directory.
Example
Example Projects
UIExtension
Complete webViewer
It is a ready-to-go application that you can run directly or integrate into your project with full features provided by Foxit PDF SDK for Web. This application uses the full-featured package “UIExtension.full.js” for the PDF view and document parsing.
Source folder: /examples/UIExtension/complete_webViewer.
Integration Examples
These examples walk you through integrating Foxit PDF SDK for Web as an es-module, amd or commonJS module. For a global variable integration sample, refer to the code on /examples/UIExtension/complete_webViewer/index.html
Source folder: /examples/UIExtension/integrate-as-module.
Customize Global Annotation Properties
Provide an example to show how to set default annotation properties by using either of the constructor option or the function.
Source folder: /examples/UIExtension/default_annot_config.
Customize Tooltips Example
Provide an example to show how to customize tooltips on sidebar and toolbar.
Source folder: /examples/UIExtension/tooltip.
Asynchronous/Synchronous annotation loading
This demo shows how the annotations in a PDF can be automatically loading in both options, async and synchronous by using the ‘lazy’ property on the ‘<commentlist-sidebar-panel>’ tag to true or false.
Source folder: /examples/UIExtension/commentlist-loadmode.
Customize Text Selection
This demo provides an example to show how to create a custom controller for text selection.
Source folder: /examples/UIExtension/custom-text-selection-tool.
Customize Annotations Pop-up
The default behavior of double clicking an annotation in webViewer is to trigger the comment panel. This demo guides you how to change the default event by adding the pop-up layer and overwriting the onDoubleTap event.
Source folder: /examples/UIExtension/custom_annotations_popup.
Customize User Interface
Provide code examples to show how to customize user interface. One introduces a non-adaptive sample for PC browser, the other guides how to set up adaptivity for across browsers. webViewer detects the ‘navigator.userAgent’ in browser when initializing and determines the UI layout accordingly – PC or mobile.
Source folder: /examples/UIExtension/custom_appearance.
Customize Layout by Templates
Provide examples to show the built-in templates in UIExtension and the reference methods. This example is suitable for users who need to fine-tune the template.
Source folder: /examples/UIExtension/layout_templates.
Customize Components by Fragments
Provide examples to show how to modify components and set up components configuration by using fragments.
Source folder: /examples/UIExtension/fragment_usage.
Annotation Data Migration Example
Provide an example to show how to migrate annotations JSON data from v6 to v7 to avoid data lost.
Source folder: /examples/UIExtension/migrateAnnotData.
PWA Example
Provide an example to show how to implement a progressive web app
Source folder: /examples/UIExtension/pwa.
UI Widgets Examples
These are examples referenced by UIExtension.components.widget in API Reference. Each sample shows the usage of a component (including how to pass parameters, event binding, and so on).
Source folder: /examples/UIExtension/tutorials/widgets.
Addon Usage Examples
With this example, you will learn how to merge addons and reference the merged-add.js in your code.
Source folder: /examples/UIExtension/use-merged-addon.
Webpack Scaffold Project
This project provides an open-source code of UI addon for customization.
Go to Project Page
PDFViewCtrl
Basic webViewer
It is a basic webViewer that demonstrates how to call Foxit PDF SDK for Web API to load a PDF document, and zoom in/out the document. This demo uses the “PDFViewCtrl.full.js” package in the “lib” folder.
Source folder: /examples/PDFViewCtrl/basic_webViewer.
Overwrite PDFPageRendering Example
Provide an example to show how to add a custom UI to the nodes of each PDF page by overwriting the PDFPageRendering class, such as a UI of adding a loading dynamic figure or a similar progress bar.
Source folder: /examples/PDFViewCtrl/override-rendering.
Preload Worker Example
Provide an example to show how to load the worker scripts of the JR engine in advance, to get performance benefit of reducing initializing time.
Source folder: /examples/PDFViewCtrl/preload-worker.
Asynchronous Loading Example
Provide an example to show how to async opening files from URL.
Source folder: /examples/PDFViewCtrl/url.
Offline Example
This example demonstrates how to register the “service-worker.js” found in the “examples/PDFViewCtrl/service-worker” folder to better cache the core dependency files “gsdk.js” and font files in a browser supported by the service worker, in order to speed up the reloading time or use the offline mode.
Source folder: /examples/PDFViewCtrl/service-worker.
Inline DIV Example
This example renders the simple UI of Foxit PDF SDK for Web to a div container with a specified size.
Source folder: /examples/PDFViewCtrl/div.
FileOpen Plugin Example
Provide an example for opening a fileOpen protection file.
Source folder: /examples/PDFViewCtrl/fileopen.
Page Layout Rewriting Example
This example shows how to create a single view page layout and navigate page by up and down arrow keys without scrolling feature. By this example, you will learn how to register and inherit IViewMode to implement your own layout and customize navigating page postures.
Source folder: /examples/PDFViewCtrl/view-mode.
Document Password Re-encryption Example
Provide an example to show how to open a document with password re-encryption. The password re-encryption node.js example can be found at … \server\encrypt-password.
Source folder: /examples/PDFViewCtrl/encrypt-password.
Page Manipulation Example
Provide an example to show how to manipulate pages.
Source folder: /examples/PDFViewCtrl/ppo.
Form Widgets Adding Example
Provide an example for creating supported form widgets.
Source folder: /examples/PDFViewCtrl/add-form-fields.
Annotation Creating Example
Provide examples to show how to inherit a StateHandler class of link, screen and textMarkup annotation to implement the annotation creating class.
Source folder: /examples/PDFViewCtrl/create-annot.
License Validation Tool
Provide a tool for verifying license validation.
Source folder: /examples/PDFViewCtrl/check-license.
HTTP Server Configuration Examples
Start Http Server using Nginx
Using Windows as an example, assume that Nginx was installed on your system already. When you have Nginx server running, you can directly modify the ‘nginx.conf’ in the conf directory, here we directly modify the configuration file to make webViewer run. Please follows the steps below:
Download Foxit PDF SDK for Web package, unzip it to a folder.
Locate to the Nginx/conf folder, open the nginx.conf file, add the following listening information:
server { listen 8080; server_name 127.0.0.1; location / { alias “gotopath/FoxitPDFSDKForWeb/”; charset utf8; index index.html; } } |
Restart Nginx server, now you can access the webViewer at
Complete webViewer address: http://localhost:8080/examples/UIExtension/complete_webViewer/
Basic webViewer address:
http://localhost:8080/examples/PDFViewCtrl/basic_webViewer/
Note: You can run the webViewer according to the above configuration, but at that time the snapshot feature cannot work correctly. The snapshot cannot be cached to clipboard, so that you cannot paste it to the location as desired. Please follow the steps below to build the snapshot server:
Install node.js 9.0 or higher, if it is already installed, skip it.
In a command prompt, navigate to the root directory of Foxit PDF SDK for Web.
Type npm install to install the required dependencies.
Type npm run start-snapshot-server to start the snapshot server (the default port is 3002).
Note: If you want to specify the port for snapshot server as desired, you can change it in the “server/snapshot/package.json” file of Foxit PDF SDK for Web package. To find the default port 3002, and change it as you wish:
Configure Nginx reverse proxy in nginx.conf:
server { listen 8080; server_name 127.0.0.1; location / { alias “gotopath/FoxitPDFSDKForWeb/”; charset utf8; index index.html; } location ~ ^/snapshot/(.+)$ { proxy_pass http://127.0.0.1:3002/snapshot/$1$is_args$args; proxy_redirect off; proxy_request_buffering on; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } |
Restart Nginx server, and refresh your browser, the snapshot feature should work correctly.
Start Http Server using Nodejs
Assume that Node.js 9.0 or higher is available on your system already. Then follow the steps below to run the webViewer:
Download Foxit PDF SDK for Web package, unzip it, navigate to the root folder and execute to install dependencies:
npm install
Run the web server with the command below:
npm start
The webViewer can be assessed with the following address:
Complete webViewer address: http://localhost:8080/examples/UIExtension/complete_webViewer/
Basic webViewer address:
http://localhost:8080/examples/PDFViewCtrl/basic_webViewer/
Note: Using this method, you do not need to configure the proxy, the snapshot feature can be used normally. If you want to specify the ports for http-server and snapshot server as desired, you can change the two ports in the package.json file of Foxit PDF SDK for Web package.
To change the port for http-server and snapshot server, find the default port 8080 and 3002 as below, and change it as you wish:
"serve": { "port": 8080, "public": "/", "proxy": { "target": "http://127.0.0.1:3002", "changeOrigin": true } }
Scaffold Demo
This is a scaffold demo for UIExtension, including an open-source UI addon. It shows how to customize UI and how to use declaration file. The demo can be accessed at ./examples/UIExtension/scaffoldDemo.
How to run this demo
Setup library
Execute command npm run setup in the demo root folder.
This setup would:
Add the lib directory to the dependency list as a local npm repository..
install all npm package that needed.
Run Demo
Execute command: npm start
Source code
The structure of src folder:
│ addons.js — shows how to use foxit addons.
│ index.js — entry.
│
└─addonExample — an addon example.
│ addon.info.json — addon entry file, which specified all related file in an addon.
│ index.css — style sheet
│ index.js — addon script entry. DON’T modify it’s file name.
│
├─locales — i18n files
│ en-US.json
│ zh-CN.json
│
├─stateHandlers — State Handler Classes which extend IStateHandler
│ addTextField.js
│
└─templates
custom-dialog.art — Art template for customized dialog. tab-template.art — Art template for customized tool bar.
Entry file
The src/addonExample/index.js file is the script entry of addon. View source file for more details.
Features
Digital Signature
In this section, you will learn about the general steps to sign and verify signature, related signature APIs, ways to interact with the digital signature, and the test signature service routes we provided.
Steps to sign and verify digital signature on PDF
To sign and verify a digital signature on PDF, you should go over the following procedures:
Sign Document
Generate a file stream which contains signature’s byteRange. You may refer to PDF Reference 1.7+ for details.
Calculate the message digest of the content covered by signature’s byteRange. This can be implemented by calling PDFUI.registerSignHandler(signerInfo) or PDFDoc.sign(signInfo,digestSignHandler).
Get signedData by signing the digest using certification. This can be implemented by calling PDFUI.registerSignHandler(signerInfo) or PDFDoc.sign(signInfo,digestSignHandler).
Write the signedData into the file stream. The signedData’s position is specified in byteRange.
Verify signature
Get the original(unmodified) file content, the byteRange of signature, the signed data and signer.
Calculate the message digest of the content covered by signature’s byteRange. This can be implemented by calling PDFUI.setVerifyHandler(verifyFunction) or PDFDoc.verifySignature(signatureField, verifyHandler).
Verify the digest and signed data, and output the verified state result which includes information about document changes, issuer and timestamp status, ect.. This can be implemented by calling PDFUI.setVerifyHandler(verifyFunction) or PDFDoc.verifySignature(signatureField, verifyHandler).
Related digital signature APIs
PDFUI.registerSignHandler(signerInfo)
Currently, Foxit PDF SDK for Web supports two formats of signature filter: adbe.pkcs7.detached and adbe.pkcs7.sha1.
The digest algorithms supported by adbe.pkcs7.detached are: ‘sha1’, ‘sha256’, and ‘sha384’.
The digest algorithm supported by adbe.pkcs7.sha1 is: ‘sha1’.
This method is used to register signer data. Here are the examples code:
Use adbe.pkcs7.detached and sha256:
pdfui.registerSignHandler({ filter: "Adobe.PPKLite", subfilter: "adbe.pkcs7.detached", flag: 0x100, distinguishName: "[email protected]", location: "FZ", reason: "Test", signer: "web sdk", showTime: true, sign: (setting, plainContent) => { return requestData("post", "http://localhost:7777/digest_and_sign", "arraybuffer", { subfilter: setting.subfilter, md: "sha256", // "sha1", "sha256", "sha384" plain: plainContent, }); }, });
Use adbe.pkcs7.sha1 and sha1:
pdfui.registerSignHandler({ filter: "Adobe.PPKLite", subfilter: "adbe.pkcs7.sha1", flag: 0x100, distinguishName: "[email protected]", location: "FZ", reason: "Test", signer: "web sdk", sign: (signInfo,plainContent) => { //sign handler which complete the signing action, return a Promise with signed data; //function getDigest() and sign() should be completed by user. let digest = getDigest(plainContent); //plainContent is a Blob data let signData = sign(digest); return Promise.resolve(signData); }, });
PDFUI.setVerifyHandler(verifyFunction)
This method is used to set verification handler which will be called when a signature is being verified. Verification handler returns a verifying result state called Signature_State. Here is the example code:
pdfui.setVerifyHandler((signatureField, plainBuffer, signedData) => { //function getDigest() and verify() should be completed by user. let digest = getDigest(plainBuffer); let verifiedStatus = verify( signatureField.getFilter(), signatureField.getSubfilter(), signatureField.getSigner(), digest, signedData ); return Promise.resolve(verifiedStatus); });
PDFDoc.sign(signInfo,digestSignHandler)
This method is used to sign the document. A message digest and sign function are required. Here is the example code:
/** * @returns {Blob} - File stream of signed document. */ const signResult= await pdfdoc.sign(signInfo, plainContent) => { //function getSignData() should be completed by developer. return Promise.resolve(getSignData(signInfo,plainContent)) // plainContent is a Blob data. });
PDFDoc.verifySignature(signatureField, verifyHandler)
This method is used to verify the signature. A callback function is required. Here is the example code:
/** * @returns {number} - Signature state. */ var result = await singedPDF.verifySignature( pdfform.getField("Signature_0"), function verify(signatureField, plainBuffer, signedData, hasDataOutOfScope) { //function verifySignData() should be completed by developer. let signInfo = { byteRange: signatureField.getByteRange(), signer: signatureField.getSigner(), filter: signatureField.getFilter(), subfilter: signatureField.getSubfilter(), }; return Promise.resolve(verifySignData(signInfo, buffer)); } );
PDFSignature Class
PDFSignature.isSigned() – Check if the current signature is signed or not.
PDFSignature.getByteRange() – Get byte range which specifies scope of file stream of current signature.
PDFSignature.getFilter() – Get the current signature filter.
PDFSignature.getSubfilter() – Get the current signature subfilter.
Interact with the digital signature feature
You can try our signature workflow by the way of using API or UI. This workflow is based on the Node.js backend which can be accessed at ./server/pkcs7 in our package.
Method 1 Programmatically place a signature on the current document
Run https://webviewer-demo.foxitsoftware.com/ with starting a service.
Run the following code on the console. A signature field will be automatically created and a digital signature will be placed on it.
A signed document will be downloaded and reopened in your viewer. You can click on the signature field to verify it.
// this code example assumes you are running the signature service on a local host and using the default port 7777. var pdfviewer = await pdfui.getPDFViewer(); var pdfdoc = await pdfviewer.getCurrentPDFDoc(); var signInfo = { filter: "Adobe.PPKLite", subfilter: "adbe.pkcs7.sha1", rect: { left: 10, bottom: 10, right: 300, top: 300 }, pageIndex: 0, flag: 511, signer: "signer", reason: "reason", email: "email", distinguishName: "distinguishName", location: "loc", text: "text", }; const signResult = await pdfdoc.sign(signInfo, (signInfo, plainContent) => { return requestData( "post", "http://127.0.0.1:7777/digest_and_sign", "arraybuffer", { plain: plainContent} ); }); // open the signed PDF const singedPDF = await pdfviewer.openPDFByFile(signResult); var pdfform = await singedPDF.loadPDFForm(); var verify = (signatureField, plainBuffer, signedData, hasDataOutOfScope) => { return requestData("post", "http://127.0.0.1:7777/verify", "text", { filter: signatureField.getFilter(), subfilter: signatureField.getSubfilter(), signer: signatureField.getSigner(), plainContent: new Blob([plainBuffer]), signedData: new Blob([signedData]), }); }; var result = singedPDF.verifySignature(pdfform.getField("Signature_0"), verify);
Method 2 Place a signature from the UI
Let’s use our online viewer https://webviewer-demo.foxitsoftware.com/ to experience how it works.
Preparation
Open https://webviewer-demo.foxitsoftware.com/ on your browser.
Add and sign a signature
Click the signature button in the Form tab to switch to the addSignatureStateHandler.
Click to draw a rectangle field on the page.
Click Hand tool or press Esc key to switch to the handStateHandler.
Set the sign information on the pop-up box and click Ok to sign it. The signed document will be downloaded and re-opened automatically.
Verify signature
Click the signed signature field with the hand tool to verify it. A prompt box will be pop-up reporting the verifying result.
Note: To make this signature workflow work, we have referenced the following callback code in the index.html file of the complete_webViewer, and run a signature service on our backend.
//the variable `origin` refers to the service http address where your signature service is running. //signature handlers var requestData = (type, url, responseType, body) => { return new Promise((res, rej) => { var xmlHttp = new XMLHttpRequest(); xmlHttp.open(type, url); xmlHttp.responseType = responseType || "arraybuffer"; let formData = new FormData(); if (body) { for (let key in body) { if (body[key] instanceof Blob) { formData.append(key, body[key], key); } else { formData.append(key, body[key]); } } } xmlHttp.onload = (e) => { let status = xmlHttp.status; if ((status >= 200 && status < 300) || status === 304) { res(xmlHttp.response); } }; xmlHttp.send(body ? formData : null); }); }; //set signature information and function. This function can be called to register different algorithm and information for signing //the api `/digest_and_sign` is used to calculate the digest and return the signed data pdfui.registerSignHandler({ filter: "Adobe.PPKLite", subfilter: "adbe.pkcs7.sha1", flag: 0x100, distinguishName: "[email protected]", location: "FZ", reason: "Test", signer: "web sdk", showTime: true, sign: (setting, plainContent) => { return requestData("post", "origin", "arraybuffer", { plain: plainContent, }); }, }); //set signature verification function //the api /verify is used to verify the state of signature pdfui.setVerifyHandler((signatureField, plainBuffer, signedData) => { return requestData("post", "origin", "text", { filter: signatureField.getFilter(), subfilter: signatureField.getSubfilter(), signer: signatureField.getSigner(), plainContent: new Blob([plainBuffer]), signedData: new Blob([signedData]), }); });
About signature HTTP service
If you don’t have backend signature service available, you can use the following HTTP service routes which we provide for the test purpose.
Server in US:
http://webviewer-demo.foxitsoftware.com/signature/digest_and_sign
http://webviewer-demo.foxitsoftware.com/signature/verify
Server in China:
http://webviewer-demo.foxitsoftware.cn/signature/digest_and_sign
http://webviewer-demo.foxitsoftware.cn/signature/verify
Import and Export
Annotation
The Annotation supports three types of files to import/export data: XFDF, FDF and JSON. The following table lists what annotations currently don’t support to import/export.
File Type | If all annots support | What not support |
XFDF/FDF | Mostly | Screen Image, Link, Sound |
JSON | Mostly | Screen Image, Link, Sound |
API
The following table list APIs that Foxit PDF SDK for Web provides to import/export data file.
Method | XFDF/FDF | JSON | JSON |
Import | PDFDoc.importAnnotsFromFDF() | PDFDoc.importAnnotsFromJSON(annotsJson) | PDFPage.addAnnot(annotJson) |
Export | PDFDoc.exportAnnotsToFDF() | PDFDoc.exportAnnotsToJSON() | Annot.exportToJson() |
It is recommended to use a corresponding method to import and export data. For example, If PDFDoc.exportAnnotsToJSON() is called to export data, then it would better the PDFDoc.importAnnotsFromJSON(annotsJson) is used to import.
Note: Adding exported JSON data to the document via the PDFPage.addAnnot method can lead to loss of binary data streams for some annotations, such as Stamp and fileAttachment. This is because the PDFPage.addAnnot method does not support JSON data that contains binary streams. Therefore, if the data exported by PDFDoc.exportAnnotsToJSON contains binary streams, then it cannot be passed to the PDFPage.addAnnot method.
var pdfViewer = await pdfui.getPDFViewer(); var test = {ExportDataFile:'http://pathToSourceFile.pdf',ImportDatafile:'http://pathToTargetFile.pdf'}; var resp = await fetch(test.ExportDataFile); var file = await resp.blob(); var pdfdoc = await pdfViewer.openPDFByFile(file); var annotJson = await pdfdoc.exportAnnotsToJSON(); var newResp = await fetch(test.ImportDatafile); var newFile = await newResp.blob() var newPdfdoc = await pdfViewer.openPDFByFile(newFile); for(var i=0;i<annotJson.length;i++){ var newPage = await newPdfdoc.getPageByIndex(annotJson[i].page); var newAnnot = await newPage.addAnnot(annotJson[i]); }
Form
The Form supports three standard types of files to import/export data: XFDF, FDF and XML.
API
The following table list APIs that Foxit PDF SDK for Web provides to import/export data file.
PDFDoc.exportFormToFile(fileType)
PDFDoc.importFormFromFile(file, isXML)
Stamp and Customization
Foxit PDF SDK for Web provides a wide range of stamp features that users can implement with the APIs and default icons. This section will walk you through how manage stamps and add a stamp into PDF.
Default stamp list
Foxit PDF SDK for web provides a default stamp list in Viewer as follows:
{ "stamp": { "Static": { "Approved": { "url": "xxx://url.url", "fileType": "pdf" }, "Completed": { "url": "xxx://url.url", "fileType": "pdf" }, "Confidential": { "url": "xxx://url.url", "fileType": "pdf" }, "Draft": { "url": "xxx://url.url", "fileType": "pdf" }, "Revised": { "url": "xxx://url.url", "fileType": "pdf" }, "Emergency": { "url": "xxx://url.url", "fileType": "pdf" }, "Expired": { "url": "xxx://url.url", "fileType": "pdf" }, "Final": { "url": "xxx://url.url", "fileType": "pdf" }, "Received": { "url": "xxx://url.url", "fileType": "pdf" }, "Reviewed": { "url": "xxx://url.url", "fileType": "pdf" } }, "SignHere": { "Accepted": { "url": "xxx://url.url", "fileType": "pdf" }, "Initial": { "url": "xxx://url.url", "fileType": "pdf" }, "Rejected": { "url": "xxx://url.url", "fileType": "pdf" }, "SignHere": { "url": "xxx://url.url", "fileType": "pdf" }, "Witness": { "url": "xxx://url.url", "fileType": "pdf" } }, "Dynamic": { "Approved": { "url": "xxx://url.url", "fileType": "pdf" }, "Confidential": { "url": "xxx://url.url", "fileType": "pdf" }, "Received": { "url": "xxx://url.url", "fileType": "pdf" }, "Reviewed": { "url": "xxx://url.url", "fileType": "pdf" }, "Revised": { "url": "xxx://url.url", "fileType": "pdf" } } }
Manage Stamp list
The default stamp list doesn’t allow changes. However, you can create your own stamps to replace the default ones, and then edit them. The first step is to make a PDF and corresponding svg file. You can refer to the examples in lib\stamps\en-US\DynamicStamps folder.
Create a custom stamp list
A custom stamp list can be predefined by calling the API pdfViewer.initAnnotationIcons() and loaded into the viewer. Once the following code runs, the default stamp list will be overwritten.
var initIcons = { MyCategory1: { StampName1: { filetype: "jpg", url: "http://stamp.jpg" } }, MyCategory2: { StampName2: { fileType: "png", url: "stamp.png" } }, ... }; var pdfViewer = await pdfui.getPDFViewer(); await pdfViewer.initAnnotationIcons({ stamp: initIcons });
Remove custom stamps
//remove a stamp with the category and name as 'MyCategory1' and 'StampName1' from you stamp list var pdfViewer = await pdfui.getPDFViewer(); await pdfViewer.removeAnnotationIcon('stamp','MyCategory1','StampName1') //clear the whole stamp list var pdfViewer = await pdfui.getPDFViewer(); await pdfViewer.removeAnnotationIcon('stamp','','StampName1') //clear all stampes under 'MyCategory1' var pdfViewer = await pdfui.getPDFViewer(); await pdfViewer.removeAnnotationIcon('stamp','MyCategory1','')
Add a new custom stamp
var icons = { annotType: "stamp", fileType: "png", url: "http://stamp.png", // width:80, // height:30, category: "MyCategory", name: "MyStamp" }; var pdfViewer = await pdfui.getPDFViewer(); await pdfViewer.addAnnotationIcon(icons);
About the stamp category and name
Stamps are organized by category and name. To find out what stamps already exist in your list, the easy way is to check the category and name information by calling pdfui.getAnnotationIcons(). Here are code samples.
Get the stamp category and name
//list all available stamps await pdfui.getAnnotationIcons("stamp", false); //list only custom stamps await pdfui.getAnnotationIcons("stamp", true);
You also execute the following code to output the existing stamps.
var allIcons = pdfui.getAnnotationIcons("stamp", false); var iconNames = []; for (var categoryKey in allIcons) { var category = allIcons[categoryKey]; for (var name in category) { iconNames.push({ category: categoryKey, name }); } } console.log(iconNames);
Add a stamp onto page in Viewer
The stamp list can be found by dropping down the stamp tool under Comment tab in Viewer. You can click a stamp icon and place it to a desired place on the page.
If you want to create a custom stamp and add it onto page, follow the steps below:
1. In Advanced Web Viewer, drop down the stamp tool under Comment tab,select Custom Stamps.
2. In the pop-up Create Custom Stamp dialog box, click File -> Browse… and choose an image file, or click File -> Enter File URL, input the URL address where the PDF and svg files are stored.
3. Fill in the category, name, width and height, as well as choose a type from drop-down menu to create a stamp. Then, the created stamp will appear in the Stamp list.
4. Click the created stamp icon and place it to a desired place on the page.
Add a custom stamp onto page by API
Before calling the PDFPage.addAnnot to add a custom stamp which doesn’t exist in your stamp list, you should call PDFViewer.addAnnotationIcon() to add it into stamp list. If not doing this, the stamp appearance will display incorrectly on the page.
var icons = { annotType: "stamp", fileType: "png", url: "http://stamp.png", // width:80, // height:30, category: "MyCategory", name: "MyStamp" }; var stamp = { type:'stamp', rect:{left:0,bottom:0,right:200,top:100}, icon:'MyStamp', iconCategory:'MyCategory' }; var pdfViewer = await pdfui.getPDFViewer(); var pdfDoc = await pdfViewer.getCurrentPDFDoc(); var page = await pdfDoc.getPageByIndex(0); await pdfViewer.addAnnotationIcon(icons); await page.addAnnot(stamp)
If you only want to add a new stamp onto the page without adding the stamp icon in your stamp list of your viewer, you can run the following code:
pdfpage.addAnnot({ type: PDFViewCtrl.PDF.annots.constant.Annot_Type.stamp, rect: { left: 0, right: 300, top: 450, bottom: 0 }, iconInfo: { annotType: PDFViewCtrl.PDF.annots.constant.Annot_Type.stamp, category: "category", name: "name", fileType: "pdf", url: "http://path/file.pdf" } });
Set the default tool to a particular stamp in Viewer
Use the PDFUI constructor option to define a stamp as the default tool handler:
pdfui = new UIExtension.PDFUI({ customs: { defaultStateHandler: PDFViewCtrl.STATE_HANDLER_NAMESSTATE_HANDLER_CREATE_STAMP handlerParams: { category: 'SignHere', name: 'SignHere' } }; })
Use the API StateHandlerManager.switchTo() to set default tool:
pdfui.getStateHandlerManager().then(handlerManager => handlerManager.switchTo( PDFViewCtrl.STATE_HANDLER_NAMES.STATE_HANDLER_CREATE_STAMP, { category: "SignHere", name: "SignHere" url: "http://xxx/xx.png", // or "blob:http://xxxxx" showUrl: "http://xxx/xx.png", // or "blob:http://xxxxx" fileType:'png', width: 80, height: 30, }) );
Related APIs
APIs | Description |
PDFViewer.initAnnotationIcons(icons) | Initialize the icon of the annotation (after setting, the default icon will not be displayed) |
PDFViewer.addAnnotationIcon(icon) | Add a single icon |
PDFViewer.removeAnnotationIcon(type,category,name) | Remove a single icon |
PDFUI.getAnnotationIcons(annotType,onlyCustomized) | Get custom icon |
StateHandlerManager.switchTo(name,params) | Switch to addStampStateHandler |
PDFViewer.setFormatOfDynamicStamp(seperator,timeFormat) | Set the format of dynamic information |
PDFPage.addAnnot(json) | Add stamp with specifying the existing icon as the style of stamp |
Custom Speech Synthesizer
The speech synthesizer is a text-to-speech service that is used to convert text into sounds that approximate the sound of human speech. It can work with the Read Aloud feature to provide a powerful text-to-speech function which can read aloud page contents.
Depending on the speech technology, the sounds generated may be somewhat stilted and artificial sounding, or sound very much like the voice of a real person.
To better demonstrate how you could use different text-to-speech technology with our Foxit PDF SDK for Web, we take browser native Web Speech API as an example in the following section Customize PDFTextToSpeechSynthesis, and use the Google cloud text-to-speech API in the section of Integrating 3rd Party TTS Service.
Speech Synthesizer APIs
PDFTextToSpeechSynthesis Interface Specification
interface PDFTextToSpeechSynthesis { status: PDFTextToSpeechSynthesisStatus; supported(): boolean; pause(): void; resume(): void; stop():void; play(utterances: IterableIterator<Promise<PDFTextToSpeechUtterance>>, options?: ReadAloudOptions): Promise<void>; updateOptions(options: Partial<ReadAloudOptions>): void; }
status Properties
The status enumerates the current reading aloud state. It can be defined as below:
enum PDFTextToSpeechSynthesisStatus { playing, paused, stopped, }
The default value is stopped.
supported():boolean Method
This method is used to detect if the PDFTextToSpeechSynthesis is supported in your current client environment. If there is a 3rd party speech service running in the background, you only need to check if HTML<audio> is supported on your client side.
Note: The client here could either be a browser, or something others such as Electron, Apache Cordova, etc.
Code Example:
class CustomPDFTextToSpeechSynthesis { supported(): boolean { return typeof window.HTMLAudioElement === 'function'; } // .... other methods }
pause(), resume and stop() Methods
These methods are used to control the state of reading aloud. Through these methods, the PDFTextToSpeechSynthesis can manage the voice media to pause, resume, stop, and specify the status property.
updateOptions(options: Partial<ReadAloudOptions>) Method
This method is used to update the PDFTextToSpeechSynthesis in the reading aloud state, such as change the voice volume.
play(utterances: IterableIterator<Promise<PDFTextToSpeechUtterance>>, options?: ReadAloudOptions): Promise<void> Method
Parameter Description:
utterances: This is an IterableIterator that contains the content of the text to be read as well as the page number and coordinate information, which can be used with for…of to iterate.
options: This is an optional parameter that contains the speed, pitch, volume of the playback and the ‘external’ parameter, where ‘external’ is the parameter object passed to the third party speech synthesizer service.
Customize PDFTextToSpeechSynthesis
Method 1: Implement PDFTextToSpeechSynthesis interface
Notice: This demo only supports in Chrome, Firefox, Chromium Edge.
<html> </html> <script> const PDFTextToSpeechSynthesisStatus = UIExtension.PDFViewCtrl.readAloud.PDFTextToSpeechSynthesisStatus; class CustomPDFTextToSpeechSynthesis { constructor() { this.playingOptions = {}; this.status = PDFTextToSpeechSynthesisStatus.stopped; } supported() { return typeof window.speechSynthesis !== 'undefined'; } pause() { this.status = PDFTextToSpeechSynthesisStatus.paused; window.speechSynthesis.pause(); } resume() { this.status = PDFTextToSpeechSynthesisStatus.playing; window.speechSynthesis.resume(); } stop() { this.status = PDFTextToSpeechSynthesisStatus.stopped; window.speechSynthesis.cancel(); } /** * @param {IterableIterator<Promise<PDFTextToSpeechUtterance>>} utterances * @param {ReadAloudOptions} options * */ async play(utterances, options) { for await (const utterance of utterances) { const nativeSpeechUtterance = new window.SpeechSynthesisUtterance(utterance.text); const { pitch, rate, volume } = Object.assign( {}, this.playingOptions, options || {} ); if(typeof pitch === 'number') { nativeSpeechUtterance.pitch = pitch; } if(typeof rate === 'number') { nativeSpeechUtterance.rate = rate; } if(typeof volume === 'number') { nativeSpeechUtterance.volume = volume; } await new Promise((resolve, reject) => { nativeSpeechUtterance.onend = resolve; nativeSpeechUtterance.onabort = resolve; nativeSpeechUtterance.onerror = reject; speechSynthesis.speak(nativeSpeechUtterance); }); } } updateOptions(options) { Object.assign(this.playingOptions, options); } } var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: UIExtension.appearances.ribbon, addons: [ libPath + '/uix-addons/read-aloud' ] }); pdfui.getReadAloudService().then(function(service) { service.setSpeechSynthesis(new CustomPDFTextToSpeechSynthesis()); }); </script>
Method 2: Use AbstractPDFTextToSpeechSynthesis to customize the speech synthesizer
<html> </html> <script> const PDFTextToSpeechSynthesisStatus = UIExtension.PDFViewCtrl.readAloud.PDFTextToSpeechSynthesisStatus; const AbstractPDFTextToSpeechSynthesis = UIExtension.PDFViewCtrl.readAloud.AbstractPDFTextToSpeechSynthesis; const CustomPDFTextToSpeechSynthesis = AbstractPDFTextToSpeechSynthesis.extend({ init(){}, supported() { return typeof window.speechSynthesis !== 'undefined'; }, doPause() { window.speechSynthesis.pause(); }, doResume() { window.speechSynthesis.resume(); }, doStop() { window.speechSynthesis.cancel(); }, /** * @param {string} text * @param {ReadAloudOptions | undefined} options */ async speakText(text, options) { const nativeSpeechUtterance = new window.SpeechSynthesisUtterance(utterance.text); const { pitch, rate, volume } = Object.assign( {}, this.playingOptions, options || {} ); if(typeof pitch === 'number') { nativeSpeechUtterance.pitch = pitch; } if(typeof rate === 'number') { nativeSpeechUtterance.rate = rate; } if(typeof volume === 'number') { nativeSpeechUtterance.volume = volume; } await new Promise((resolve, reject) => { nativeSpeechUtterance.onend = resolve; nativeSpeechUtterance.onabort = resolve; nativeSpeechUtterance.onerror = reject; speechSynthesis.speak(nativeSpeechUtterance); }); } }) const libPath = window.top.location.origin + '/lib'; const pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: UIExtension.appearances.ribbon, addons: [ libPath + '/uix-addons/read-aloud' ] }); pdfui.getReadAloudService().then(function(service) { service.setSpeechSynthesis(new CustomPDFTextToSpeechSynthesis()); }); </script>
The Difference of PDFTextToSpeechSynthesis and AbstractPDFTextToSpeechSynthesis
The Method 1 customizes speech synthesizer by implementing the interface PDFTextToSpeechSynthesis. It needs to manually manage state changes as well as iterate the list of ‘utterances’ by for await…of. Each item in the ‘Utterance’ list is a text block obtained from PDFPage. In some cases, the text block may just contain a part of a word or sentence, which requires merging text blocks to build up a complete word and sentence for better speech synthesizing. This merging operation can be completed in the play() method.
The Method 2 customizes speech synthesizer by inheriting the AbstractPDFTextToSpeechSynthesis abstract class. It doesn’t require to manually manage state and iterate utterances list, but needs correctly call window.SpeechSynthesisUtterance to generate speech and play the voice based on the received text and parameters. These received text blocks will be automatically merged by AbstractPDFTextToSpeechSynthesis. However currently it is tough to guarantee that all the combined text blocks in different language environments would comprise complete words or sentences, as such if you are strict with reading correctness with each sentence and word, you are recommended to use Method 1.
Integrate with 3rd Party TTS Service
We take @google-cloud/text-to-speech as an example in this section.
Server
To start with Google Cloud Text-to-Speech server library with favorite programming language, refer to https://cloud.google.com/text-to-speech/docs/quickstarts.
Client
var readAloud = UIExtension.PDFViewCtrl.readAloud; var PDFTextToSpeechSynthesisStatus = readAloud.PDFTextToSpeechSynthesisStatus; var AbstractPDFTextToSpeechSynthesis = readAloud.AbstractPDFTextToSpeechSynthesis; var SPEECH_SYNTHESIS_URL = '<server url>'; // the server API address var ThirdpartyPDFTextToSpeechSynthesis = AbstractPDFTextToSpeechSynthesis.extend({ init: function() { this.audioElement = null; }, supported: function() { return typeof window.HTMLAudioElement === 'function' && document.createElement('audio') instanceof window.HTMLAudioElement; }, doPause: function() { if(this.audioElement) { this.audioElement.pause(); } }, doStop: function() { if(this.audioElement) { this.audioElement.pause(); this.audioElement.currentTime = 0; this.audioElement = null; } }, doResume: function() { if(this.audioElement) { this.audioElement.play(); } }, onCurrentPlayingOptionsUpdated: function() { if(!this.audioElement) { return; } var options = this.currentPlayingOptions; if (this.status === PDFTextToSpeechSynthesisStatus.playing) { if(options.volume >= 0 && options.volume <= 1) { this.audioElement.volume = options.volume; } } }, speakText: function(text, options) { var audioElement = document.createElement('audio'); this.audioElement = audioElement; if(options.volume >= 0 && options.volume <= 1) { audioElement.volume = options.volume; } return this.speechSynthesis(text, options).then(function(src) { return new Promise(function(resolve, reject) { audioElement.src = src; audioElement.onended = function() { resolve(); }; audioElement.onabort = function() { resolve(); }; audioElement.onerror = function(e) { reject(e); }; audioElement.play(); }).finally(function() { URL.revokeObjectURL(src); }); }); }, // If the server API request method or parameter form is not consistent with the following implementation, it will need to be adjusted accordingly. speechSynthesis: function(text, options) { var url = SPEECH_SYNTHESIS_URL + '?' + this.buildURIQueries(text, options); return fetch(url).then(function(response) { if(response.status >= 400) { return response.json().then(function(json) { return Promise.reject(JSON.parse(json).error); }); } return response.blob(); }).then(function (blob) { return URL.createObjectURL(blob); }); }, buildURIQueries: function(text, options) { var queries = [ 'text=' + encodeURIComponent(text) ]; if(!options) { return queries.join('&'); } if(typeof options.rate === 'number') { queries.push( 'rate=' + options.rate ); } if(typeof options.spitch === 'number') { queries.push('spitch=' + options.spitch); } if(typeof options.lang === 'string') { queries.push('lang=' + encodeURIComponent(options.lang)); } if(typeof options.voice === 'string') { queries.push('voice=' + encodeURIComponent(options.voice)); } if(typeof options.external !== 'undefined') { queries.push('external=' + encodeURIComponent(JSON.stringify(options.external))); } return queries.join('&'); } });
Use the custom speech synthesizer:
pdfui.getReadAloudService().then(function(service) { serivce.set(new ThirdpartyPDFTextToSpeechSynthesis()); });
Snapshot Tool
API
Capture the picture of the specified area on the page
const pageRender = pdfViewer.getPDFPageRender(pageIndex); pageRender.getSnapshot(left, top, width, height).then(imageBlob => { // Get the image stream. });
Capture the picture of the specified area on the page
pdfViewer.takeSnapshot(pageIndex, left, top, width, height).then(imageBlob=>{ // Get the image stream. });
Copy image data to clipboard
pdfViewer.copySnapshot(imageBlob).then(function(){ // Succeed to Copy image data to clipboard. });
Image hosting service
The API for image hosting service
The interface for uploading images
pdfViewer.uploadImage(imageBlob).then(function(imgURL){ // Succeed to upload image blob data to the snapshot server. });
request method: POST, request address: /snapshot/upload?filefield={}, BODY is the image stream, return: /snapshot/image/{imageid}
The interface for downloading images
request method: GET, request address: /snapshot/image/{imageid}
Customize image hosting service
new PDFViewer({ snapshotServer: new SnapshotServer({ // The interface for uploading images. uploadSnapshotAPIPath: 'snapshot/upload', // Parse the contents that responds from the server, and parse it to a image URL. The default implementation is return resp. // Assuming that the server returns {success: true, data: {url: '/snapshot/image/xxx'}}, then you need to implement it as follow: render: function(resp) { if(resp.success) { return resp.data.url; } else { throw new Error('Snapshot server error'); } } }) })
Example
// If you need to upload images to a specified server, then you need to create a custom image hosting service. const SnapshotServer = PDFViewCtrl.SnapshotServer; const pdfui = new PDFUI({ ... viewerOptions: { snapshotServer: new SnapshotServer({ origin: location.origin, uploadSnapshotAPIPath: 'snapshot/upload', payloadFieldName: 'file', method: 'POST' render: function(resp) { if(resp.success) { return resp.data.url; } else { throw new Error('Snapshot server error'); } } }) ... } }) var pdfviewer = await pdfui.getPDFViewer(); // Capture the picture of the specified area on the page. var imageBlob = await pdfviewer.takeSnapshot(0, 0, 0, 200, 200); // Upload images to a specified server. var uploadResult = await pdfviewer.uploadImage(imageBlob); // Capture the picture and copy it to the clipboard var copyResult = await pdfviewer.copySnapshot(uploadResult);
Compare PDFs by overlaying PDF pages
Starting from version 9.0.0, Foxit PDF SDK for Web provides APIs to support comparing PDFs by overlaying PDF pages, and then generate a new comparison result image which can intuitively display the differences between the two PDF pages.
A Simple Example
In this section, a simple example will be provided to show you how to compare PDFs by overlaying PDF pages. It includes: open document, create document, get PDF page, compare pages, generate result page, and export the result document.
First, initialize a blank project, and then the subsequent operations will be done based on this project.
const libPath = window.top.location.origin + '/lib'; const pdfViewer = new PDFViewCtrl.PDFViewer({ libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }); pdfViewer.init(document.body);
Load the documents that need to be compared
async function loadFiles() { const sourcePDFDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/test-doc1.pdf' } }); const targetPDFDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/test-doc2.pdf' } }); return { sourcePDFDoc, targetPDFDoc }; }
Foxit PDF SDK for Web provides two ways to load documents:
loadPDFDocByHttpRangeRequest: using this API, you can asynchronously load PDF documents as required from remote.
loadPDFDocByFile: using this API, you can load the file stream in the memory, or select files on the hard disk via <input type=”file”>.
The purpose of loading documents is that you can edit and read the documents without rendering them. Here is just for demonstration, so we use this method. In an actual project, you can also do further operations by getting the current opened document (PDFViewer.getCurrentPDFDoc).
Create a blank document
The blank document will be used to save the comparison result.
function createBlankDoc() { return pdfViewer.createNewDoc(); }
Get the bitmaps of the pages that need to be compared
async function getPageBitmaps(loadedFiles) { const { sourcePDFDoc, targetPDFDoc } = loadedFiles; const sourcePage = await sourcePDFDoc.getPageByIndex(0); const sourceBitmap = await sourcePage.render(1); const targetPage = await targetPDFDoc.getPageByIndex(0); const targetBitmap = await targetPage.render(1); return { sourceBitmap, targetBitmap }; }
If you need to use other options (for example scale and rotation), please input parameters (scale, rotate) in the render method, and refer to the PDFPage.render interface in the API Reference.
Start to compare documents
function comparePageBitmap(sourceBitmap, targetBitmap) { const DiffColor = PDFViewCtrl.overlayComparison.DiffColor; const service = pdfViewer.getOverlayComparisonService(); const resultCanvas = service.compareImageData({ sourceBitmap, targetBitmap, combinePixelsOptions: { showDiffColor: true, sourceDiffColor: DiffColor.RED, targetDiffColor: DiffColor.BLUE, sourceOpacity: 0xFF, targetOpacity: 0xFF }, transformation: { translateX: 0, translateY: 0, rotate: 2 / 180 * Math.PI } }); return new Promise(resolve => { resultCanvas.toBlob(blob => { const fr = new FileReader(); fr.onloadend = () => { resolve({ buffer: fr.result, width: resultCanvas.width, height: resultCanvas.height }); }; fr.readAsArrayBuffer(blob); }); }) }
Note:
The differences between the documents will be marked only when shouldShowDiff is set to true.
sourceDiffColor and targetDiffColor do not support all colors, you must select one of the colors in the DiffColor enumeration.
The value range of sourceOpacity and targetOpacity is 0~0xFF.
Insert the comparison result into the PDF page
async function insertResultIntoNewDoc(newDoc, resultImageData) { const page = await newDoc.getPageByIndex(0); // resultImageData is the return object of the comparePageBitmap function mentioned in the above example. // Convert the unit of width and height from pixel to point. const newPageWidth = resultImageData.width / 4 * 3; const newPageHeight = resultImageData.height / 4 * 3; // Reset the size of PDF page to make it the same size as the comparison image. await page.setPageSize(newPageWidth, newPageHeight); // Last, insert it into the PDF page as a PDF image object. await page.addImage(resultImageData.buffer, { left: 0, right: newPageWidth, bottom:0, top: newPageHeight }); }
Export the result document
async function exportResultPDFDocFile(newDoc) { return newDoc.getFile(); }
PDFDoc.getFile interface will return the blob object of the document. You can use the general method to download this file stream or open the document by PDFDoc.openPDFDocByFile interface to see the effect.
The final effect
Integrate all the above code, the final effect is as follows, please click run button to run the example:
<html> </html> <script> const libPath = window.top.location.origin + '/lib'; const pdfViewer = new PDFViewCtrl.PDFViewer({ libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }); pdfViewer.init(document.body); (async () => { const loadedFiles = await loadFiles(); const newDoc = await createBlankDoc(); const { sourceBitmap, targetBitmap } = await getPageBitmaps(loadedFiles); const resultImageData = await comparePageBitmap(sourceBitmap, targetBitmap); await pdfViewer.zoomTo(1); await insertResultIntoNewDoc(newDoc, resultImageData); })() async function loadFiles() { const sourcePDFDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/test-doc1.pdf' } }); const targetPDFDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/test-doc2.pdf' } }); return { sourcePDFDoc, targetPDFDoc }; } function createBlankDoc() { return pdfViewer.createNewDoc(); } async function getPageBitmaps(loadedFiles) { const { sourcePDFDoc, targetPDFDoc } = loadedFiles; const sourcePage = await sourcePDFDoc.getPageByIndex(0); const sourceBitmap = await sourcePage.render(1); const targetPage = await targetPDFDoc.getPageByIndex(0); const targetBitmap = await targetPage.render(1); return { sourceBitmap, targetBitmap }; } function comparePageBitmap(sourceBitmap, targetBitmap) { const DiffColor = PDFViewCtrl.overlayComparison.DiffColor; const service = pdfViewer.getOverlayComparisonService(); const resultCanvas = service.compareImageData({ sourceBitmap, targetBitmap, combinePixelsOptions: { showDiffColor: true, sourceDiffColor: DiffColor.RED, targetDiffColor: DiffColor.BLUE, sourceOpacity: 0xFF, targetOpacity: 0xFF }, transformation: { translateX: 0, translateY: 0, rotate: 2 / 180 * Math.PI } }); return new Promise(resolve => { resultCanvas.toBlob(blob => { const fr = new FileReader(); fr.onloadend = () => { resolve({ buffer: fr.result, width: resultCanvas.width, height: resultCanvas.height }); }; fr.readAsArrayBuffer(blob); }); }) } async function insertResultIntoNewDoc(newDoc, resultImageData) { const page = await newDoc.getPageByIndex(0); const newPageWidth = resultImageData.width / 4 * 3; const newPageHeight = resultImageData.height / 4 * 3; await page.setPageSize(newPageWidth, newPageHeight); await page.addImage(resultImageData.buffer, { left: 0, right: newPageWidth, bottom:0, top: newPageHeight }); } async function exportResultPDFDocFile(newDoc) { return newDoc.getFile(); } </script>
Note:
To minimize the interference of irrelevant code and make the code more intuitive, the above example is written using ESNext syntax. Please use a modern browser to open the developer guide document and run the example. If you need to be compatible with older browsers, please use JavaScript transpiler such as babel in your project.
Compare PDF Files
Starting from version 8.5.0, Foxit PDF SDK for Web provides APIs to compare PDF files in the full package, which can compare the differences between text, annotations, page objects (images, paths), and watermarks in two PDF files. In general, to compare PDF files using the APIs, first input two different documents, then return a result document with detailed difference information, finally open the result document to intuitively see the difference information. Following will explain the usage of the APIs in detail.
APIs Preview
https://developers.foxit.com/developer-hub/document/developer-guide-pdf-sdk-web
A Simple Example
In this section, we will show you how to compare two documents and control the content display in the result document. The following example will be done based on a created PDFViewer instance.
Load documents
Before you start, load two documents using PDFViewer.loadPDFDocByFile or PDFViewer.loadPDFDocByHttpRangeRequest. These two APIs are used to load PDF documents, and then return a PDFDoc object, without rendering documents on the view.
const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-base.pdf' } }); const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-other.pdf' } });
Start to compare documents
After loading the documents, you just need to get the id values of the documents through PDFDoc.getId, and then you can start to compare documents.
const baseDocId = baseDoc.getId(); const otherDocId = otherDoc.getId(); const comparedDoc = await pdfViewer.compareDocuments( baseDocId, otherDocId, { // The file name of baseDoc, which will be displayed in the result document. baseFileName: 'baseFile.pdf', // The file name of otherDoc, which will be displayed in the result document. otherFileName: 'otherFile.pdf', // The file name of the result document. resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf' } );
Preview of the effect:
<script> const libPath = window.top.location.origin + '/lib'; const pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { fontPath: 'http://webpdf.foxitsoftware.com/webfonts', licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: UIExtension.appearances.adaptive, addons: libPath + '/uix-addons/allInOne.js' }); (async function() { const pdfViewer = await pdfui.getPDFViewer(); const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-base.pdf' } }); const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-other.pdf' } }); const baseDocId = baseDoc.getId(); const otherDocId = otherDoc.getId(); const comparedDoc = await pdfViewer.compareDocuments( baseDocId, otherDocId, { baseFileName: 'baseFile.pdf', otherFileName: 'otherFile.pdf', resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf' } ); const comparedDocFile = await comparedDoc.getFile(); pdfui.openPDFByFile(comparedDocFile); })() </script>
The Parameters of compareDocuments method
Page range
You can specify the page range of the two documents to be compared through the following two parameters:
pdfViewer.compareDocuments( baseDocId, otherDocId, { // ... basePageRange: { from: 0, end: 2 }, otherPageRange: { from: 1, end: 3 }, options: { // ... } } )
Both basePageRange and otherPageRange objects should include from and end properties which represent the starting and ending page indexes of the pages to be compared. In the above example code, the final pages to be compared are: baseDoc: [0, 1, 2], otherDoc: [1, 2, 3]. Please note that the number of pages specified in the basePageRange and otherPageRange should be same
options parameters
options are used to specify the objects that need to be compared and the comparison method. The parameters are explained as follows:
// Whether to compare tables, default is false, which means not to compare. compareTable: false, // Whether to detect page deletions and insertions, default is false, which means not to detect. detectPage: false, // The width of the outline border of the marker, and the unit is point. lineThickness: { delete: 2, insert: 2, replace: 2 }, // This parameter is used to set the marker color for different types of differences. The format of marker color is 0xRRGGBB without transparent channel, and the transparency needs to be specified in opacity. markingColor: { delete: 0xfa0505, insert: 0x149bff, replace: 0xffcc00 }, // This parameter is used to set the transparency of different types of differences. opacity: { delete: 100, insert: 100, replace: 100 }, // Whether only to compare text differences. If the value is true, only compare the text differences, otherwise compare the annotations, page oobjects (include text), and so on. textOnly: false
Following is an example with using the above parameters:
Click OK on the pop-up dialog.
<script> const libPath = window.top.location.origin + '/lib'; const pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { fontPath: 'http://webpdf.foxitsoftware.com/webfonts', licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: UIExtension.appearances.adaptive, addons: libPath + '/uix-addons/allInOne.js' }); class ComparisonOptionsLayerComponent extends UIExtension.SeniorComponentFactory.createSuperClass({ template: ` <layer class="center fv__ui-comparison-options-dialog" @var.dialog="$component" style="width: 680px" append-to="body"> <layer-view class="fv__ui-comparison-dialog-body"> <div class="fv__ui-layout-row"> <div class="fv__ui-layout-col-1"> <fieldset class="fv__ui-comparison-fieldset"> <legend>comparison:options-dialog.include</legend> <div class="fv__ui-comparison-fieldset-content"> <form-group label="comparison:options-dialog.compareTextOnly" direction="rtl"> <checkbox @model="dialog.currentOptions.textOnly" name="compareTextOnly"></checkbox> </form-group> <form-group label="comparison:options-dialog.compareTable" direction="rtl"> <checkbox @model="dialog.currentOptions.compareTable" name="compareTable"></checkbox> </form-group> <form-group label="comparison:options-dialog.detectPage" direction="rtl"> <checkbox @model="dialog.currentOptions.detectPage" name="detectPageDeletionsOrInserts"></checkbox> </form-group> </div> </fieldset> </div> <div class="fv__ui-layout-col-1"> <fieldset class="fv__ui-comparison-fieldset"> <legend>comparison:options-dialog.markingColor</legend> <div class="fv__ui-comparison-fieldset-content"> <form-group label="comparison:options-dialog.replaceObjects"> <inline-color-picker name="fv--comparison-options-replace-marking-color" @model="dialog.currentOptions.markingColor.replace|comparison:color"></inline-color-picker> </form-group> <form-group label="comparison:options-dialog.insertObjects"> <inline-color-picker name="fv--comparison-options-insert-marking-color" @model="dialog.currentOptions.markingColor.insert|comparison:color"></inline-color-picker> </form-group> <form-group label="comparison:options-dialog.deleteObjects"> <inline-color-picker name="fv--comparison-options-delete-marking-color" @model="dialog.currentOptions.markingColor.delete|comparison:color"></inline-color-picker> </form-group> </div> </fieldset> </div> </div> <div class="fv__ui-layout-row"> <div class="fv__ui-layout-col-1"> <fieldset class="fv__ui-comparison-fieldset"> <legend>comparison:options-dialog.opacity</legend> <div class="fv__ui-comparison-fieldset-content fv__ui-comparison-options-checkbox-list"> <form-group label="comparison:options-dialog.replaceObjects"> <number type="number" min="0" max="100" step="1" suffix="%" @model="dialog.currentOptions.opacity.replace" name="fv--comparison-options-replace-opacity-replace"></number> </form-group> <form-group label="comparison:options-dialog.insertObjects"> <number type="number" min="0" max="100" step="1" suffix="%" @model="dialog.currentOptions.opacity.insert" name="fv--comparison-options-replace-opacity-insert"></number> </form-group> <form-group label="comparison:options-dialog.deleteObjects"> <number type="number" min="0" max="100" step="1" suffix="%" @model="dialog.currentOptions.opacity.delete" name="fv--comparison-options-replace-opacity-delete"></number> </form-group> </div> </fieldset> </div> <div class="fv__ui-layout-col-1"> <fieldset class="fv__ui-comparison-fieldset"> <legend>comparison:options-dialog.lineThickness</legend> <div class="fv__ui-comparison-fieldset-content fv__ui-comparison-options-checkbox-list"> <form-group label="comparison:options-dialog.replaceObjects"> <number type="number" min="1" max="12" step="1" @model="dialog.currentOptions.lineThickness.replace" name="fv--comparison-options-replace-lineThickness-replace"></number> </form-group> <form-group label="comparison:options-dialog.insertObjects"> <number type="number" min="1" max="12" step="1" @model="dialog.currentOptions.lineThickness.insert" name="fv--comparison-options-replace-lineThickness-insert"></number> </form-group> <form-group label="comparison:options-dialog.deleteObjects"> <number type="number" min="1" max="12" step="1" @model="dialog.currentOptions.lineThickness.delete" name="fv--comparison-options-replace-lineThickness-delete"></number> </form-group> </div> </fieldset> </div> </div> </layer-view> <div class="fv__ui-comparison-dialog-footer fv__ui-layout-row"> <div class="fv__ui-layout-col-1 fv__ui-comparison-footer-buttons"> <xbutton text="dialog.ok" class="fv__ui-dialog-button fv__ui-dialog-ok-button" name="fv__ui-dialog-ok-button" @on.click="dialog.ok()"></xbutton> <xbutton text="dialog.cancel" class="fv__ui-dialog-button fv__ui-dialog-cancel-button" name="fv__ui-dialog-cancel-button" @on.click="dialog.cancel()" @cannotBeDisabled></xbutton> </div> </div> </layer> ` }) { static getName() { return 'comparison-options-layer' } currentOptions = this.getDefaultOptions(); getDefaultOptions() { return { compareTable: false, detectPage: false, lineThickness: { delete: 2, insert: 2, replace: 2 }, markingColor: { delete: 0xfa0505, insert: 0x149bff, replace: 0xffcc00 }, opacity: { delete: 100, insert: 100, replace: 100 }, textOnly: false }; } onOk(callback) { this.onOkCallback = callback; } ok() { if(typeof this.onOkCallback == 'function') { this.onOkCallback(this.currentOptions); } this.hide(); this.currentOptions = this.getDefaultOptions(); } cancel() { this.hide(); this.currentOptions = this.getDefaultOptions(); } } UIExtension.modular.root().registerComponent(ComparisonOptionsLayerComponent); (async function() { const root = await pdfui.getRootComponent(); root.append('<comparison-options-layer visible>'); const pdfViewer = await pdfui.getPDFViewer(); const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-base.pdf' } }); const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-other.pdf' } }); const baseDocId = baseDoc.getId(); const otherDocId = otherDoc.getId(); const optionsLayer = root.querySelector('@comparison-options-layer'); optionsLayer.onOk(async options => { const comparedDoc = await pdfViewer.compareDocuments( baseDocId, otherDocId, { baseFileName: 'baseFile.pdf', otherFileName: 'otherFile.pdf', resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf', options: options } ); console.log(options); const comparedDocFile = await comparedDoc.getFile(); pdfui.openPDFByFile(comparedDocFile); }); })() </script>
The progress of comparing documents
When the documents that need to be compared are large, it will take more time to generate the result document. At this time, the fourth parameter of the compareDocuments method can accept a callback function to get the processing progress information:
pdfViewer.compareDocuments( baseDocId, otherDocId, { ... }, (currentRate) => { console.log(currentRate); } )
The range of values for currentRate is from 0 to 100. You can use this value to update the progress bar on the UI.
An example with progress bar:
<script> const libPath = window.top.location.origin + '/lib'; const pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { fontPath: 'http://webpdf.foxitsoftware.com/webfonts', licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: UIExtension.appearances.adaptive, addons: libPath + '/uix-addons/allInOne.js' }); class ProgressBarComponent extends UIExtension.SeniorComponentFactory.createSuperClass({ template: `<layer class="center" visible @var.self="$component"> @{self.currentRate + '%'} </layer>` }) { static getName() { return 'progress-bar-layer' } currentRate = 0; setCurrentRate(rate) { this.currentRate = rate; this.digest(); if(rate >= 100) { setTimeout(() => { this.hide(); }, 500); } } } UIExtension.modular.root().registerComponent(ProgressBarComponent); (async function() { const pdfViewer = await pdfui.getPDFViewer(); const baseDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-base.pdf' } }); const otherDoc = await pdfViewer.loadPDFDocByHttpRangeRequest({ range: { url: '/assets/compare-other.pdf' } }); const baseDocId = baseDoc.getId(); const otherDocId = otherDoc.getId(); const rootComponent = await pdfui.getRootComponent(); rootComponent.append('<progress-bar-layer>'); const comparedDoc = await pdfViewer.compareDocuments( baseDocId, otherDocId, { baseFileName: 'baseFile.pdf', otherFileName: 'otherFile.pdf', resultFileName: pdfViewer.i18n.t('comparison:resultFileName') || 'The result of comparison.pdf' }, currentRate => { rootComponent.querySelector('@progress-bar-layer').setCurrentRate(currentRate); } ); const comparedDocFile = await comparedDoc.getFile(); pdfui.openPDFByFile(comparedDocFile); })() </script> { "iframeOptions": { "style": "height: 600px" } }
Determine whether a document is a comparison result document
The dictionary information of the PDF file generated by the PDFViewer.compareDocuments interface can be used to determine whether a document is a comparison result document. In the SDK, it uses PDFDoc.isCompareDoc() method.
pdfViewer.eventEmitter.on(PDFViewCtrl.ViewerEvent.openFileSuccess, doc => { doc.isCompareDoc(); })
Below is a typical dictionary information for a comparison result document. the /PieceInfo at the end of the object (1 0) points to the (244 0) object, that is, points to the dictionary entry of /ComparePDF. So, you can determine whether a document is a comparison result document with this information.
1 0 obj <</AcroForm 110 0 R/Pages 2 0 R/ViewerPreferences <<>>/OCProperties <</OCGs [62 0 R 63 0 R 64 0 R 65 0 R 66 0 R 67 0 R 68 0 R]/D <</Order [62 0 R 63 0 R 64 0 R 65 0 R 66 0 R 67 0 R 68 0 R]/ON [62 0 R 63 0 R 64 0 R]/OFF [65 0 R 66 0 R 67 0 R 68 0 R]>>>>/Names 367 0 R/PageLayout(TwoColumnLeft)/Type/Catalog/PieceInfo 244 0 R>> endobj ... 244 0 obj <</ComparePDF 235 0 R>> endobj ... 235 0 obj <</Private 236 0 R>> endobj 236 0 obj <</Differences 237 0 R>> endobj 237 0 obj <</Nums [1 238 0 R 2 239 0 R 3 240 0 R 4 241 0 R 5 242 0 R 6 243 0 R]>> endobj ...
Customize Dynamic Stamps
Differences between dynamic stamps and standard stamps
Type | Dependencies | Files that need to be prepared | Support to set |
Dynamic stamps | The uix-addons/customer-dynamic-stamp is required. By default, this addon is included in AllInOne.js. If you are not using AllInOne.js, ensure that this addon is referenced in the PDFUI initialization. | image | Support to set background image, text, font, color and its position |
Standard stamps | none | PDF and svg | image |
The process for creating a custom dynamic stamp
We provide a default dynamic stamp operation process in the Complete WebViewer (hereinafter referred to as UI). You can experience this feature by clicking Comment-> Create-> Create Dynamic Stamp. The logical flow of this implementation is as follows:
The UI provides a background image preprocessed by the frontend by default.
After the user inputs the required category, name and text of the dynamic stamp, the UI will transfer this data to the PDF Data layer.
According to the data received from the step #1 and step #2, the PDF Data layer draws preprocessed image and generates a text form field, updates the form field data in real time, exports the images, and sends them to the UI layer as an icon of the stamp list.
When the user selects the dynamic stamp icon created in step #3 from the stamp list and then clicks on the page to create stamp, the stamp data will be synchronized with the PDF data layer, which indicates that the dynamic stamp has been created successfully.
Create a custom dynamic stamp
Create a custom dynamic stamp through UI
Users can create a custom dynamic stamp directly by clicking comment-> Create-> Create Dynamic Stamp on the UI. Among them, the background image of the dynamic stamp can be passed in and managed by the user through the interfaces. The following code shows how to manage the background image of dynamic stamp by importing or deleting operations and so on.
// Get the dropdown component of custom dynamic stamp template const templates = await pdfui.getComponentByName("stamp-templates"); // Import the dropdown button and its callback function through append method templates.append("<dropdown-button name='test' url='xxx.png'>test</dropdown-button>", [{ target: 'test', config: { // Set the callback function callback: async function () { // Get the dialog component of custom dynamic stamp const dialog = await pdfui.getComponentByName("fv--custom-dynamic-stamp-dialog") // Set the corresponding template information dialog.controller.selectTemplate({ name: 'test', url: "xxx.png" }) } } }])
Create a custom dynamic stamp through APIs
Add a custom dynamic stamp
var param = [{ category:'category', name:'MyStamp', fileData:'http://stamp.png', field:{ textType:PDFViewCtrl.PDF.constant.STAMP_TEXT_TYPE.CUSTOM_TEXT, value:'custom text', font:{ name:'Helvetica', color:0, }, rect:{ left:0, right:30, top:30, bottom:0, } }, }] // Add a custom dynamic stamp pdfui.callAddonAPI( 'CustomDynamicStamp', 'setDynamicStamp',[param])
Remove a custom dynamic stamp
var param = [{ category:'stamp',//The directory of the dynamic stamp names:[ 'MyStamp', //The name of the dynamic stamp ] }] // Remove a custom dynamic stamp pdfui.callAddonAPI( 'CustomDynamicStamp', 'removeDynamicStamp',[param])
Get all custom dynamic stamps
// Get dynamic stamps pdfui.callAddonAPI( 'CustomDynamicStamp', 'getDynamicStamp')
The Edit Modules in Foxit PDF SDK for Web
Foxit PDF SDK for Web provides three types of packages: Light package (excludes font resources), Standard package (includes font resources) and Full package (includes font resources and Document Comparison). Light/Standard package uses a same Edit module, and Full package uses an advanced Edit module. In order to distinguish these two Edit modules, the Edit module of the Light/Standard package will be named Std Edit, and the Edit module of the Full package will be named Adv Edit.
The Full package uses Adv Edit by default, and it also includes the Std Edit module. Users can switch them based on their needs. The Adv Edit has more advantages in terms of interaction, but it needs license permissions, otherwise it will not be able to use.
The core features of Std Edit support editing based on PDF content object (text object, image object and shape object). Users can add PDF content objects and modify the font style (font, font size, and color) of the text objects.
The core features of Adv Edit not only support the editing based on PDF content, but also support text block editing. On the basis of the Std Edit, more features have been added.
Text block editing:
Font style, alignment, bullets, line/word spacing, character scale, and so on
Join and split
Search and Replace text
Shape object:
Preset path object
Create shading object
Edit the properties of the shape object
Following is a comparison of the two modules.
UI comparison
The ribbon under Edit Tab of Std Edit:
The ribbon under Edit Tab of Adv Edit:
The ribbon of the right panel of Adv Edit:
Adv Edit not only has all the features of the Std Edit, but also provides more other features. For example, text style/level/transformation, paragraph-related functions, and shape style, etc.
Feature comparison
Features | Std Edit | Adv Edit | Comparison Result |
API & Event | The relevant interfaces and events have been exposed | No APIs & Events | Adv Edit does not support customization through APIs |
License Permission | none | Require a separate Adv Edit module license to be included in SDK license | Adv Edit has a license limit |
Addon | edit-graphics, text-object, path-objects | pageEditor, find-replace | Rely on different add-ons individually |
How to switch to the standard editor in the Full package
For the Full package, it uses advanced editor by default, if you don’t have a license for advanced editor and want to enable the standard editor, you can refer to the following two methods.
Method 1: modify the Edit module to be displayed through the fragments parameter when initializing PDFUI object
fragments: [ { target: 'adv-edit-tab-group-mode', action: UIExtension.UIConsts.FRAGMENT_ACTION.REPLACE, template:` <group name="edit-tab-group-mode" retain-count="3"> <edit-pageobjects:edit-all-objects-button @async></edit-pageobjects:edit-all-objects-button> <add-image-ribbon-button></add-image-ribbon-button> <edit-text-object:add-text-ribbon-button @async></edit-text-object:add-text-ribbon-button> <edit-pageobjects:path-objects-ribbon-dropdown @async></edit-pageobjects:path-objects-ribbon-dropdown> </group> ` }, { target: 'edit-tab-group-editor', action: UIExtension.UIConsts.FRAGMENT_ACTION.REPLACE, template:` <group name="edit-tab-group-font" retain-count="5" @require-modules="edit-text-object"> <edit-text-object:text-bold-style-ribbon-button></edit-text-object:text-bold-style-ribbon-button> <edit-text-object:text-italic-style-ribbon-button></edit-text-object:text-italic-style-ribbon-button> <edit-text-object:font-color-picker></edit-text-object:font-color-picker> <edit-text-object:font-style-dropdown></edit-text-object:font-style-dropdown> </group> ` } ]
For more information about fragments, please refer to https://webviewer-demo.foxitsoftware.com/docs/developer-guide/ui-extension/basics/fragments.html.
Method 2: switch the Edit module through the interface of the Component object
// Get the advEditTabGroupMode component that is the editing functionality of the Adv Edit. var advEditTabGroupMode = await pdfui.getComponentByName("adv-edit-tab-group-mode"); // Remove the obtained advEditTabGroupMode component. advEditTabGroupMode.remove(); // Get the advEditTabGroupEditor component that is the editing functionality of the Adv Edit. var advEditTabGroupEditor = await pdfui.getComponentByName("edit-tab-group-editor"); // Remove the obtained advEditTabGroupEditor component. advEditTabGroupEditor.remove(); // Get the first editTabGroupHand component under the Edit tab that contains the hand function. var editTabGroupHand = await pdfui.getComponentByName("edit-tab-group-hand"); // Insert the target Edit module after the editTabGroupHand component. editTabGroupHand.after(` <group name="edit-tab-group-font" retain-count="5"> //group component <edit-text-object:text-bold-style-ribbon-button></edit-text-object:text-bold-style-ribbon-button> //text-object Bold component <edit-text-object:text-italic-style-ribbon-button></edit-text-object:text-italic-style-ribbon-button> //text-object Italic components <edit-text-object:font-color-picker></edit-text-object:font-color-picker> //text object Color component <edit-text-object:font-style-dropdown></edit-text-object:font-style-dropdown> //text object Font Name and Size components </group> `) editTabGroupHand.after(` <group name="edit-tab-group-mode" retain-count="3"> //group component <edit-pageobjects:edit-all-objects-button @async></edit-pageobjects:edit-all-objects-button> //The component of editing graphicobjects <add-image-ribbon-button></add-image-ribbon-button>//The component of adding image graphic object <edit-text-object:add-text-ribbon-button @async></edit-text-object:add-text-ribbon-button> //The component of adding text graphic object <edit-pageobjects:path-objects-ribbon-dropdown @async></edit-pageobjects:path-objects-ribbon-dropdown> //The component of adding path graphic object </group> `)
Collaboration
From version 8.5.2, the built-in collaboration solution has been replaced by the new web collaboration add-on which can help developers easily integrate real-time document collaboration into their web applications, please refer to the developer guide for more information.
Best Practice
Foxit PDF SDK for Web runs in a browser sandbox in a network environment. Choosing a correct website operation scheme and Foxit PDF SDK for Web configuration can make Foxit PDF SDK for Web run faster. The following section give references on website operation optimization and Foxit PDF SDK for Web configuration.
Website assets optimization
Gzip and Brotli compression
Compression is a way to shrink the assets size and reduce the downloading time. The following table shows the compressed size using gzip and brotli on UIExtension.css and UIExtension.full.js.
File | Original size | Gzip | Brotli |
UIExtension.css | 1.2M | 213kb | 156kb |
UIExtension.full.js | 2.6M | 534kb | 443kb |
NOTE: Although the brotli compression algorithm provided by Google is superior to gzip in compression ratio. But brotli is not natively supported by all browsers, such as Microsoft’s IE. Decompression of brotli in IE requires the use of a JavaScript engine. This time-consuming process offsets the advantages of Brotli and consumes website loading performance.
Cache
Caching resource files can avoid downloading the same assets again and again. The /lib library in the SDK and the font files in /external are recommended for front-end caching. To learn more, check out Google and Mozilla for HTTP cache.
Foxit PDF SDK for Web configuration
Read only
If the following scenario is your current needs, it is recommended that you load the Foxit PDF SDK for Web read-only to improve rendering performance.
Applicable scenario:
complex PDF documents generated by CAD
page rendering speed is high preference
no page editing requirements
Code Example:
<script src="path/to/UIExtension.full.js"></script> <script src="path/to/allInOne.js"></script> <script> var pdfui = new UIExtension.PDFUI({ ... viewerOptions:{ customs: { getDocPermissions: function () { return 0;// 0 means ReadOnly } } ... }) </script>
or
<script src="path/to/PDFViewCtrl.full.js"></script> <script> var pdfviewer = new PDFViewCtrl.PDFViewer({ ... customs: { getDocPermissions: function () { return 0;// 0 means ReadOnly } } ... }) </script>
Brotli compression
The core of Foxit PDF SDK for Web is the wasm/asm module compiled by emscripten. The module size is 8M / 13M, and the loading time varies depending on the browser performance. These two modules are compressed using Brotli by default. But Brotli is not natively supported by all browsers, such as Microsoft’s IE, click here to see the browser support for Brotli. Decompressing brotli in IE needs to use the browser’s JavaScript engine. This process takes time, and may offset the advantages of Brotli and then result a performance penalty.
It is recommended that you select the most suitable configuration by enabling and disabling Brotli in your test environment.
Code Example:
<script src="path/to/UIExtension.full.js"></script> <script src="path/to/allInOne.js"></script> <script> var pdfui = new UIExtension.PDFUI({ ... viewerOptions:{ jr: { brotli:{ core:false,// the default value is true which means to enable brotli,false means no brotli compression } } ... }) </script>
or
<script src="path/to/PDFViewCtrl.full.js"></script> <script> var pdfviewer = new PDFViewCtrl.PDFViewer({ ... jr: { brotli:{ core:false,// the default value is true which means to enable brotli,false means no brotli compression } } ... }) </script>
Preload webassembly artifacts
Starting from version 7.1.1, Foxit PDF SDK for Web provides a script file called “preload-jr-worker.js” to load webWorker scripts and wasm/asm in advance, this can greatly save document rendering time.
Code Example:
<body> <div id="pdf-ui"></div> <script> var licenseSN = "Your license SN"; var licenseKey = "Your license Key"; </script> <!-- Add the preload-jr-worker.js--> <script src="./lib/preload-jr-worker.js"></script> <script> var readyWorker = preloadJrWorker({ workerPath: './lib/', enginePath: './lib/jr-engine/gsdk', fontPath: './external/brotli', licenseSN: licenseSN, licenseKey: licenseKey }) </script> <script src="./lib/UIExtension.full.js"></script> <script> var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: './lib', // the library path of web sdk. jr: { readyWorker: readyWorker, } }, renderTo: '#pdf-ui', // the div (id="pdf-ui"). appearance: UIExtension.appearances.adaptive, addons: [ '```' ] }); ...
Tiling size
Foxit PDF SDK for Web performs raster scan when rendering the page. If the currently rendered page layout is too large, the rendering speed of the page will be extremely slow. It is recommended that you enable tileSize rendering mode when opening this large page layout. Currently, the supported tileSize range is 500-3000px. In our internal comprehensive test, the rendering speed is optimal with the tileSize being set as 1200px. But it may vary with your document complex. You can set different tileSize such as 200, 3600, and etc. in your test environment according to the needs of the actual scenario to obtain the most suitable configuration scheme.
Applicable scenario:
Complex documents with large page layout
Code Example:
<script src="path/to/UIExtension.full.js"></script> <script src="path/to/allInOne.js"></script> <script> var pdfui = new UIExtension.PDFUI({ ... viewerOptions:{ tileSize:1200, ... } ... }) </script>
or
<script src="path/to/PDFViewCtrl.full.js"></script> <script> var pdfviewer = new PDFViewCtrl.PDFViewer({ ... tileSize:1200, ... }) </script>
Tiling size and zoom
Foxit PDF SDK for Web opens PDF with fitWidth by default for desktop, and with actual scale by default for mobile. For mobile, if you display the pages in fitWidth mode, showing or hiding the left toolbar will cause the PDF pages to be re-rendered as the viewport size changes, which will affect the performance. To circumvent this problem, it is recommended to display the pages in actual scale.
For large but simple documents, fitWidth zoom ratio improves rendering speed. However, for CAD type of documents with large layout and objects on a page, fitWidth may slow down the page rendering on the contrary. That is because there are more page objects are required to render in the same height of viewport — see example below. For this type of document, the solution we recommend is to reduce the rendering object by adjusting the page zoom ratio and tileSize, thus increasing the rendering speed. You can do test in your environment to get the most appropriate configuration for your actual scenario.
Example
Take the visual range of 800 * 600, page object 3000 * 4000, and tileSize 200 as an example, let’s see how many page objects are required to render in the different zoom ratios.
Zoom | Origin of Coordinate Space | Page Objects |
fitWidth | the top-left corner | 3000*2400 |
0.5 | the top-left corner | 1600*1200 |
1 | the top-left corner | 800*600 |
Code Example:
<script src="path/to/PDFViewCtrl.full.js"></script> <script> var pdfviewer = new PDFViewCtrl.PDFViewer({ ... defaultScale = '0.5',// or '1' tileSize:200, ... }) </script>
Rendering mode
Starting in version 8.5, the option annotRenderingMode has been deprecated. By default, Foxit PDF SDK for Web uses the native mode (using WebAssembly as the rendering engine) to render annotations and form controls. The native render has been significantly optimized to ensure the rendering quality and speed, so the canvas rendering is no longer required.
Document loading
Synchronous loading
Synchronous loading is to first obtain the complete binary stream of the file for loading, which is a compromised way of memory and performance. For documents between 50M and 500M, this method is recommended.
Code Example:
<script src="path/to/UIExtension.full.js"></script> <script src="path/to/allInOne.js"></script> <script> var pdfui = new UIExtension.PDFUI({...}) var blob = getBlob(); pdfui.openPDFByFile(blob) </script>
Asynchronous loading
Asynchronous loading does not require a complete file stream, only the required part is obtained during loading. When the file is too large (greater than 500MB) and cannot be put in memory at all, or when you only need to request part of the document at a time, it is recommended to load the document in this way to get a good performance experience.
Code Example:
<script src="path/to/UIExtension.full.js"></script> <script> var pdfui = new UIExtension.PDFUI({...}) pdfui.openPDFByHttpRangeRequest({ range:{ url:'../../../docs/FoxitPDFSDKforWeb_DemoGuide.pdf', } }) </script>
Loading document from memory arrayBuffer
Loading from arrayBuffer is to store the entire file stream to and load from in wasm/asm memory. For small local documents (less than 500MB), or when the entire document stream can be obtained in a short time, it is recommended to load in this way. This method has the advantages of high reading efficiency and fast loading speed. To enable this method, pass in the callback function getLoadingMode() at the time of constructing the PDFUI. When it returns 1, it means that it is loaded from memory arrayBuffer.
Code Example:
<script src="path/to/UIExtension.full.js"></script> <script> var pdfui = new UIExtension.PDFUI({ ... customs:{ getLoadingMode:function(file){return 1} } ... }) </script>
If you have implemented your own file open control, you can use the following method to load:
var pdfui = new UIExtension.PDFUI({...}) ...//event bind context { var arrayBuffer=getArrayBuffer(); pdfui.openPDFByFile(arrayBuffer); } ...
I18n Entries Resources Management
Explanation
SDK: Foxit PDF SDK for Web.
Addon: The addon features in the uix-addons directory of Foxit PDF SDK for Web.
Entries: Defined in the JSON configuration file and placed in a directory named after the language code based on the language type.
Application layer: The upper layer architecture developed by the SDK interface.
Overview
This section provides some details about the management of i18n entries resources. It includes:
SDK entries resources file, namespace management
How to add a new language
How to rewrite some existing entries
Customize the entries of Addon
SDK I18n Entries Resource Management
The directory structure and the role of the file
In the SDK release package, internationalized entries are placed in the ‘lib/locales/’ directory and sorted by language code. Create the sub-directories based on the language code:
lib/locales
├── en-US
├── ja-JP
└── zh-CN
In the language code directory, there are ‘ui_.json’ and ‘viewer_.json’ files. If application layer is developed based on PDFViewCtrl library, it only relies on the ‘viewer_.json’ entry file; if application layer is developed based on UIExtension library, it relies on both the ‘ui_.json’ and ‘viewer_.json’ files.
The directory of the custom entry file
If the default entries of SDK cannot meet the needs of the application layer, so that you need to rewrite the entries, or add new languages. In this case, it is recommended that developers should create a new directory at the application layer to store the custom entries.
The structure of the created directory should be consistent with the entries directory structure of the SDK release package, and the name of the entries file must be ui_.json and viewer_.json, for example:
custom/locales
en-US
ui_.json
viewer_.json
ja-JP
ui_.json
viewer_.json
zh-CN
ui_.json
viewer_.json
After determining the entry directory path, specify the entry path when constructing a PDFUI or PDFViewer instance:
Based on PDFViewCtrl:
new PDFViewer({ i18nOptions: { absolutePath: '/custom/locales/' } })
Based on UIExtension:
new PDFUI({ i18n: { absolutePath: '/custom/locals' }, })
Verify the configuration in developer environment
Clear your browser caches to ensure the latest i18N resources will be loaded.
Refresh your browser, open the Network panel in DevTools, and check if the ui_.json or viewer_.json request url points your custom language path. If so, it means success.
Add new languages
Based on the above method of customizing the entry file directory, for adding new languages, you should only add the language code directory in the /custom/locales/ directory, and then write the entry file for the corresponding language according to the en-US entry.
Taking ko-KR for example, after adding new entry, the directory structure will look like:
/custom/locales
en-US
ui_.json
viewer_.json
ja-JP
ui_.json
viewer_.json
ko-KR
ui_.json
viewer_.json
zh-CN
ui_.json
viewer_.json
After finishing adding new languages, you can specify the default language when initializing the library:
Based on PDFViewCtrl:
const pdfViewer = new PDFViewer({ i18nOptions: { initOption: { lng: 'ko-KR' } } })
Based on UIExtension:
const pdfui = new PDFUI({ i18n: { lng: 'ko-KR' } })
In addition, you can switch languages dynamically:
pdfViewer.changeLanguage('ko-KR'); pdfui.changeLanguage('ko-KR');
Rewrite some of the entries
If most of the SDK entries can meet the requirements of the application layer, and just need to do some minor modification, then you can use the functions addResources and addResourceBundle of i18next.js to overwrite the entries.
Based on PDFViewCtrl:
pdfViewer.i18n.addResource('en-US', 'viewer_', 'contextmenu.hand.zoomin', 'Custom Zoom in'); pdfViewer.i18n.addResources('en-US', 'viewer_', { 'contextmenu.hand.zoomin': 'Custom Zoom in', 'contextmenu.hand.zoomout': 'Custom Zoom out' }); pdfViewer.i18n.addResourceBundle('en-US', 'viewer_', { contextmenu: { hand: { zoomin: 'Custom Zoom in', zoomout: 'Custom Zoom out' } } }, true, true);
Based on UIExtension:
pdfui.waitForInitialization().then(() => { pdfui.i18n.addResource('en-US', 'ui_', 'contextmenu.tools.handTool', 'Custom Hand Tool'); pdfui.i18n.addResources('en-US', 'ui_', { 'contextmenu.tools.handTool': 'Custom Hand Tool', 'contextmenu.tools.selectAnnotation': 'Custom Select Annotation Tool' }); pdfui.i18n.addResourceBundle('en-US', 'ui_', { contextmenu: { tools: { handTool: 'Custom Hand Tool', selectAnnotation: 'Custom Select Annotation Tool' } } }, true, true); // make the above configuration work on the interface. pdfui.getRootComponent().then(root => { root.localize(); }); })
Customize the entries of Addon
For Addon, please refer to this section Introduction to addons.
The following table lists all Addons and the corresponding entries namespaces:
Addon | i18n namespace |
edit-graphics | ega |
export-form | export |
file-property | file-property |
form-designer | form-designer |
h-continuous | h-continuous |
h-facing | h-facing |
h-single | h-single |
import-form | import |
recognition-form | recognition-form |
text-object | edit-text |
thumbnail | thumbnail |
When adding/overwriting the entries, you can use the namespaces in the above table to add/overwrite the entries of a specific addon, as follows:
pdfui.waitForInitialization().then(() => { pdfui.i18n.addResourceBundle('en-US', 'print', { dialog: { cancel: 'custom cancel' } }, true, true); pdfui.getRootComponent().then(root => { root.localize(); }); })
For more details about the addon entries, you can refer to the uix-addons/{addon-name}/locales/en-US.json file in the SDK release package.
Troubleshooting
Thumbnail Loading Error
This component is unavailable until “thumbnail” addon is loaded
With the release of version 7.3.0, the thumbnail component was modularized as an add-on. As thus, before migrating past versions to this version or higher, thumbnail components should be configured according to actual needs. Directly migrated versions without proper changes on thumbnail component will cause an error on your browser console during the initialization phase. To view the details of the error, open the browser DevTools and click Run at the top right of the following demo. Note: the following demo doesn’t run on legacy browsers.
Solutions
Reference thumbnail addon
If you need Thumbnails, you should reference /uix-addons/thumbnail when initializing PDFUI. Below is the code example:
Delete the tag in the layout-template section
If you don’t need thumbnail, then you should delete <thumbnail-sidebar-panel> tag to avoid the error. Below is the code example:
Auto Zoom when typewriting on iPhone
The page will automatically zoom in when typewriting on iPhone
This is a feature for iPhones, if you want to prevent page from Auto Zoom when typewriting, you can add the following code:
//*.html <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"> //*.js // Prevent page from Auto Zoom in Input text fields on iPhone. window.onload = function () { if(UIExtension.PDFViewCtrl.DeviceInfo.isIPHONE)return var lastTouchEnd = 0; document.addEventListener('touchend', function (event) { var now = (new Date()).getTime(); if (now - lastTouchEnd <= 300) { event.preventDefault(); } lastTouchEnd = now; }, false); };
Dynamically show/hide components
How to dynamically show/hide components
From version 8.5.2, three new APIs (keepState, revokeKeepState, isStateKept) are provided to help users show/hide components dynamically. keepState is a function specially provided to the application layer to manage the state of components, and is not be used by Web SDK internally. Currently, it is only valid for show/hide states. Users can first call show/hide function to control the visibility of the component, and then call keepState function to keep the current visibility state. The purpose of using keepState is to prevent SDK from modifying the visibility state at an uncertain time.
Example:
const redactApplyMenu = await pdfui.getComponentByName('fv--contextmenu-item-apply'); redactApplyMenu.hide(); // First hide component. redactApplyMenu.keepState(); // Keep the current hide state of the component, which prevents the component from being showed inside the SDK. // Revoke the keep-state when necessary. redactApplyMenu.revokeKeepState();
Basics
Appearance
Appearance is a class that defines the appearance of the UI, it provides a template to specify the layout of the UI and fragments to modify the layout and control the logic of the components.
Following is the declaration of the Appearance class, its sub-classes should override these methods to define new appearance:
class Appearance { constructor(pdfui); // Layout template. public getLayoutTemplate: () => string; // Return fragment configuration. public getDefaultFragments: () => UIFragmentOptions[]; // Triggered before inserting the component into the DOM tree. public beforeMounted: (root: Component) => void // Triggered after inserting the component into the DOM tree. public afterMounted: (root: Component) => void // Called to disable the component after closing PDF documents. protected disableAll: () => void; // Called to enable the component after closing PDF documents. protected enableAll: () => void; }
Custom Appearance Example
<html> <template id="layout-template"> <webpdf> <div name="toolbar" style="display: flex; flex-direction: row; padding: 6px;"> <open-localfile-button></open-localfile-button> <download-file-button></download-file-button> </div> <div class="fv__ui-body"> <viewer @touch-to-scroll></viewer> </div> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, getDefaultFragments: function() { return [{ target: 'toolbar', action: 'append', template: `<xbutton style="margin: 0 10px">appended via fragment configuration</xbutton>` }]; }, beforeMounted: function(root) { this.toolbarComponent = root.getComponentByName('toolbar') }, disableAll: function() { this.toolbarComponent.disable(); }, enableAll: function() { this.toolbarComponent.enable(); } }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Device Adaptation
If the UI layout needs to be adaptive to the devices, you should determine the device type based on the characteristic value of your current device, and then pass the different appearance instance to PDFUI. Please refer to the following example:
The following code can be used to simulate the operation of different devices using the device mode of Chrome DevTool on the desktop Chrome browser.
<html> </html> <script> var mobileAppearance = UIExtension.appearances.MobileAppearance; var desktopAppearance = UIExtension.appearances.RibbonAppearance; var tabletAppearance = UIExtension.appearances.RibbonAppearance; var isDesktop = PDFViewCtrl.DeviceInfo.isDesktop; var isMobile = PDFViewCtrl.DeviceInfo.isMobile; var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, // Provide different appearance depending on the device type. appearance: isDesktop? desktopAppearance : isMobile ? mobileAppearance : tabletAppearance, addons: [] }); </script>
Built-in appearances
// desktop appearance UIExtension.appearances.RibbonAppearance // mobile appearance UIExtension.appearances.MobileAppearance // select ribbon or mobile appearance according to the device type(support both desktop and mobile) UIExtension.appearances.AdaptiveAppearance
Modular
Modules are equivalent to a separate namespace, and UIExtension places all components, controllers and directives in different modules, which can avoid name conflicts. Currently, the modules are used in the following scenarios:
Root module: The basic components and directives are placed in the root module. Root module does not have module name, and does not need to add module name prefix when using it.
Business module:Business components and controller.
The module created by Addon.
Detailed information will be introduced in the related sections of Components.
Create a new module
const module = PDFUI.module('module-name', [ // ...dependencies ]);
The module name cannot be repeated, otherwise it will report errors.
The second parameter is a dependent module that you can pass a name or module object. If it has no dependent module, you can pass an empty array.
Get module object
Get root module object
The root module is the foundation of all modules, and it contains the information of all built-in components and layouts.
const root = PDFUI.root();
Get a custom module object
As with the method of creating module, but it does not have the second parameter. It will report errors when the module name does not exist.
const module = PDFUI.module('module-name');
The methods of the module object
Register new component
// Register a custom component. module.registerComponent(class ComponentClass extends UIExtension.Component{ static getName() { return 'custom-component'; } }); // or module.registerComponent(UIExtension.Component.extend('custom-component', { })); module.getComponentClass('custom-component');
Use the custom component in the template:
<module-name:custom-component></module-name:custom-component>
Register a pre-configured component
module.registerPreConfiguredComponent('pre-configured-btn', { template: '<xbutton name="pre-configured-btn"></xbutton>', config: [{ target: 'pre-configured-btn', callback: function() { alert('button click') } }] })
Use the component in the template:
<module-name:pre-configured-btn></module-name:pre-configured-btn>
Register Controller
module.registerController(class CustomController extends Controller { static getName() { return 'CustomController'; } handle() { alert('') } });
Or
module.controller('CustomController', { handle: function() { alert('') } });
Use the controller in the template:
<module-name:custom-component @controller="module-name:CustomController"></module-name:custom-component>
The layout template
Example
In Foxit PDF SDK for Web, templates are written with HTML that contains UIExtension specific elements, attributes and directives. UIExtension combines the layout template with information from the component, controller and directive to render UI in the browser. The following code snippet shows a template with UIExtension components and directives.
<webpdf> <div class="toolbar" style="display:flex;flex-direction:row;padding:6px"> <print:print-ribbon-button></print:print-ribbon-button> <ribbon-button name="freetext-typewriter" @tooltip tooltip-title="toolbar.tooltip.typewriter.title" @controller="states:CreateTypewriterController" icon-class="fv__icon-toolbar-typewriter" >toolbar.create.typewriter</ribbon-button> </div> <div class="fv__ui-body"> <viewer @touch-to-scroll></viewer> </div> <template name="template-container"> <print:print-dialog></print:print-dialog> </template> </webpdf>
Click “run” button to view the running result:
<html> <template id="layout-template-container"> <webpdf> <div name="mytoolbar" class="toolbar" style="display:flex;flex-direction:row;padding:6px"> <print:print-ribbon-button></print:print-ribbon-button> <ribbon-button name="freetext-typewriter" @tooltip tooltip-title="toolbar.tooltip.typewriter.title" @controller="states:CreateTypewriterController" icon-class="fv__icon-toolbar-typewriter" >toolbar.create.typewriter</ribbon-button> </div> <div class="fv__ui-body"> <viewer @touch-to-scroll></viewer> </div> <template name="template-container"> <print:print-dialog></print:print-dialog> </template> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template-container').innerHTML; }, beforeMounted: function(root) { this.toolbarComponent = root.getComponentByName('mytoolbar') }, disableAll: function() { this.toolbarComponent.disable(); }, enableAll: function() { this.toolbarComponent.enable(); } }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [ libPath + '/uix-addons/print' ] }); </script>
Description of the format of layout template
<!-- Layout template must take <webpdf> as the root component. --> <webpdf> <!-- Layout templates support all html tags and properties. --> <div class="toolbar" style="display:flex;flex-direction:row;padding:6px"> <!-- The colon prefix is the name of the component module, and the component name and module name must be in lowercase. --> <print:print-ribbon-button></print:print-ribbon-button> <!-- The parameters that begin with @ are used to mark directives, the content that follow with @ is the name of directives. If the directive is registered in a non-root module, then the directive name should be written in the @module-name:directive-name format. --> <ribbon-button name="freetext-typewriter" @tooltip tooltip-title="toolbar.tooltip.typewriter.title" @controller="states:CreateTypewriterController" icon-class="fv__icon-toolbar-typewriter" >toolbar.create.typewriter</ribbon-button> </div> <div class="fv__ui-body"> <!-- Viewer is used to display the area of PDF, so there must be a viewer component in the layout template --> <viewer @touch-to-scroll></viewer> </div> <!-- Template is a re-written html template tag. It is typically used to hold the components that do not need to be displayed immediately, such as dialog boxes, right-click menus, floating boxes, and etc.. --> <template name="template-container"> <print:print-dialog></print:print-dialog> </template> </webpdf>
How to specify layout templates and implement device adaptation
Please refer to the section Appearance.
Dynamically insert layout templates
Please click “run” button to run the example:
<script> var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, addons: [ libPath + '/uix-addons/print' ] }); pdfui.getComponentByName('home-tab-group-change-color') .then(component => { // after this component, insert a new group component. component.after(` <group> <xbutton name="alert-btn" class="fv__ui-toolbar-show-text-button">Alert</xbutton> </group> `, [{ target: 'alert-btn', config: { callback: function() { alert('Hello world') } } }]) }) </script>
The APIs that support inserting templates are as follows:
Component
#after(component|template, fragments)
#before(component|template, fragments)
ContainerComponent
#append(component|template, fragments)
#prepend(component|template, fragments)
#insert(component|template, index, fragments)
For more information about these APIs, please refer to the API Reference: Component and ContainerComponent.
Insert the layout template when initializing
Please refer to UI Fragments.
UI fragments
Fragments are a set of UI snippets, which can be used to insert, delete, or modify the components in UI template. It is suitable to facilitate a small amount of UI customization based on built-in templates.
If you need a lot of custom layout and device adaptation, please refer to the methods described in Appearance and layout template.
simple example
The following code will use fragments configuration to remove the comment-tab component from the mobile and desktop/tablet layouts. Click “run” to run the example, and you can use the device mode of Chrome DevTool to simulate the running effect of mobile/tablet.
<script> var CustomAppearance= UIExtension.appearances.AdaptiveAppearance.extend({ getDefaultFragments: function() { var isMobile = PDFViewCtrl.DeviceInfo.isMobile; if(isMobile) { // Fragment configuration for mobile devices. return [{ target: 'comment-tab', action: 'remove' },{ target: 'comment-tab-li', action: 'remove' }, { target: 'comment-tab-body', action: 'remove' }]; } else { // Fragment configuration for desktop/tablet devices. return [{ target: 'comment-tab', action: 'remove' }, { target: 'fv--comment-tab-paddle', action: 'remove' }, { target: 'hand-tool', config: { callback: { around: function(callback, args) { try{ console.info('before callback'); var ret; if(callback instanceof UIExtension.Controller) { ret = callback.handle(...args); } else { ret = callback.apply(this, args); } console.info('after callback'); return ret; }catch(e) { console.error(e, 'an error occurred'); } finally { console.info(''); } } } }, }]; } } }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, // Different appearances are available depending on the device type. appearance: CustomAppearance, addons: [] }); </script>
The description of the Fragment configuration parameters
target: The name of the control, and each name is unique.
action: Indicates the action mode of the fragment snippets. The default action mode is UIExtension.UIConsts.FRAGMENT_ACTION.EXT. The specifics are as follows:
UIExtension.UIConsts.FRAGMENT_ACTION.EXT: Extend the target control.
UIExtension.UIConsts.FRAGMENT_ACTION.BEFORE: Insert a new control before the target control.
UIExtension.UIConsts.FRAGMENT_ACTION.AFTER: Insert a new control after the target control.
UIExtension.UIConsts.FRAGMENT_ACTION.APPEND: Insert a new control into the target control (the target control must be a container).
UIExtension.UIConsts.FRAGMENT_ACTION.FILL: Empty the child space of the target control and fill with a new control. Make sure that the target control must be a container.
UIExtension.UIConsts.FRAGMENT_ACTION.REPLACE: Replace the target control with a new control.
UIExtension.UIConsts.FRAGMENT_ACTION.REMOVE: Delete the target control.
template: The template of the control. The content is in XML format and action is BEFORE/AFTER/APPEND/FILL/REPLACE.
config: Control configuration object. It is invalid when action is REMOVE.
config.target: The name of the control in the above template. It is only required when action is BEFORE/AFTER/APPEND/FILL/REPLACE.
config.attrs: Set the html property of the control.
config.callback: The business logic implementation of the control. There are three ways to implement it:
function: The events of control will call this function, and override the built-in callbacks. The basic components that support function are (xbutton, dropdown-button, context-menu-item). If you want to add functionalities based on the built-in callbacks, you can use the second method.
controller class: Controller class can listen for components lifecycle and handle more component events:
{ target: 'hand-tool', config: { callback: class extends UIExtension.Controller { mounted() { super.mounted(); this.component.element.addEventListener('hover', e => { console.info('mouse over', this.component) }) } handle() { console.info('hand-tool clicked') } } } }
decorator object: it contains a series of function hooks for blocking the execution of the controller handle method, including before, after, thrown, and around.
{ target: 'hand-tool', config: { callback: { before: function() { // The function executed before calling the handle method of controller. It can receive all parameters of the handle method. }, after: function(returnValue) { // The function executed after calling the handle method of controller. It can receive the return value and parameters of the handle function. }, thrown: function(error) { // The function executed when the handle method of controller throws an exception. It can receive the exception object and parameters. }, around: function(callback, args) { // It can receive the references and parameters of controller's handle method. Inside the around callback, you can execute code before/after running the handle function, or in the catch exception block. It also can decide whether to execute the handle method. try{ console.info('before callback'); var ret; if(callback instanceof UIExtension.Controller) { ret = callback.handle(...args); } else { ret = callback.apply(this, args); } console.info('after callback'); return ret; }catch(e) { console.error(e, 'an error occurred'); } finally { console.info(''); } } } } }
Note
It is recommended that only use fragment for UI fine-tuning. If you want to substantially modify the built-in layout, please refer to the methods described in Appearance and layout template.
Component selector
UIExtension provides a css-selector like syntax to make easier to search components. It’s usually used to configure the target property of fragments and component search.
Syntax
selector name | example | description |
name selector | ‘componentName’, ‘component_name’,’component-name’, ‘component-name1’, ‘1component’ | component name selectors can only include single-letter, number, underscore or minus character |
type selector | ‘@div’,’@dropdown-menu’, ‘@print:print-dialog’ | component type means the tag name defined in layout template, a type selector should start with @ character and single-letter, number, underscore or minus. Sometime including the component module name separated with colon character. |
star selector | ‘*’ | Selects all components |
children selector | ‘selector1>selector2’ | Selects all components which match selector2 where the parent is selector1 |
descendants | ‘selector1 selector2’ | Selects all selector2 components inside selector1 |
attribute selector | [attr=value] | Selects all components with property or attribute name of attr whose value equals to value |
attribute selector | [attr^=value] | Selects all components with property or attribute name of attr whose value begins with value |
attribute selector | [attr$=value] | Selects all components with property or attribute name of attr whose value ends with value |
attribute selector | [attr*=value] | Selects all components with property or attribute name of attr whose value contains with value |
attribute selector | [attr!=value] | Selects all components with property or attribute name of attr whose value not equals to value |
method selector | selector1::childAt(index) | Selects all components that are all the child at index of their parents selected by selector1 |
method selector | selector1::parent() | Selects all components that are all the parent component of their children selected by selector1 |
method selector | selector1::allAfter() | Selects all components of the same level that after the component set selected by selector1 |
method selector | selector1::allBefore() | Selects all components of the same level that before the component set selected by selector1 |
index-related selector | selector1::eq(index) | Selects the component by index value in components set selected by selector1 |
index-related selector | selector1::last() | Selects the last one component of the components set selected by selector1 |
index-related selector | selector1::first() | Selects the first one component of the components set selected by selector1, It’s equivalent to selector1:eq(0) |
Examples
<html> </html> <script> UIExtension.PDFUI.module('custom',[]) .controller('customController', { handle: function() { const root = this.component.getRoot(); const contextmenuItems = root.querySelectorAll('fv--page-contextmenu>@contextmenu-item'); contextmenuItems.forEach(function(contextmenu) { contextmenu.element.style.cssText += 'color: red'; }) } }) var CustomRibbonAppearance = UIExtension.appearances.RibbonAppearance.extend({ getDefaultFragments() { // remove the export comment dropdown menu! return [{ target: 'home-tab-group-hand::childAt(0)', action: 'after', template: `<xbutton class="fv__ui-toolbar-show-text-button">Click me!</xbutton>` },{ target: 'commentlist-export-comment::parent()', action: 'remove' }]; } }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomRibbonAppearance, addons: [] }); </script>
I18n
Custom resources
Please refer to this page.
Usage
<text> component
<text> is a component used to display text. It supports i18n entries. On the DOM tree, it does not create a new HTML Element, but a text node and inserts it into the DOM tree. The font style needs to be enclosed outside Other tags are set through CSS.
<html> <template id="layout-template"> <webpdf> <div> <span class="span-with-text-component"> <!-- The text "inline text" will be displayed --> <text>inline text</text> </span> <span class="span-with-text-component"> <!-- The text "Home" will be displayed --> <text>toolbar.tabs.home.title</text> </span> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <style> .span-with-text-component { color: red; font-size: 18px; font-style: bold; } </style> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
data-i18n attribute
data-i18n attribute is another way to show texts in a HTML element, difference from <text> component, data-i18n will cover all children and replace to the text.
<html> <template id="layout-template"> <webpdf> <div> <!-- The text "inline text" will be displayed --> <span class="span-with-text-component" data-i18n="inline text"> </span> <!-- The text "Home" will be displayed --> <span class="span-with-text-component" data-i18n="toolbar.tabs.home.title"> </span> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <style> .span-with-text-component { color: red; font-size: 18px; font-style: bold; padding: 0 1em; } </style> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
components supporting
<html> <template id="layout-template"> <webpdf> <div> <div> <!-- tab component example --> <gtab group="example-tab" body="tab-body-1">toolbar.tabs.home.title</gtab> <gtab group="example-tab" body="tab-body-2">toolbar.tabs.edit.title</gtab> <gtab group="example-tab" body="tab-body-3">toolbar.tabs.comment.title</gtab> </div> <div> <div name="tab-body-1" class="button-group"> <!-- The text "OK" will be displayed --> <xbutton>dialog.ok</xbutton> <xbutton text="dialog.ok"></xbutton> <file-selector>dialog.ok</file-selector> <file-selector text="dialog.ok"></file-selector> <dropdown text="dialog.ok"> </dropdown> </div> <div name="tab-body-2"></div> <div name="tab-body-3"></div> </div> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <style> .span-with-text-component { color: red; font-size: 18px; font-style: bold; padding: 0 1em; } .button-group { display: flex; } .button-group .fv__ui-button-text { width: 3em; text-align: center; } </style> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function() { // } }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Switch current language via API
<html></html> <style> .span-with-text-component { color: red; font-size: 18px; font-style: bold; padding: 0 1em; } </style> <script> UIExtension.PDFUI.module('custom', []) .controller('SwitchLanguageController', { mounted: function() { this.updateButtonText(); }, updateButtonText: function() { const pdfui = this.getPDFUI(); switch(pdfui.currentLanguage || navigator.language) { case 'en': case 'en-US': this.component.setText('Swith to Chinese'); break; case 'zh': case 'zh-CN': this.component.setText('切换为英文'); break; } }, handle: function() { const pdfui = this.getPDFUI(); switch(pdfui.currentLanguage) { case 'en': case 'en-US': pdfui.changeLanguage('zh-CN').then(() => { this.updateButtonText(); }); break; case 'zh': case 'zh-CN': pdfui.changeLanguage('en-US').then(() => { this.updateButtonText(); }); break; } } }); var CustomAppearance = UIExtension.appearances.AdaptiveAppearance.extend({ getDefaultFragments: function() { return [{ target: 'home-tab-group-hand', action: 'append', template: '<xbutton class="fv__ui-toolbar-show-text-button" @controller="custom:SwitchLanguageController"></xbutton>' }]; } }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Components
Basic Components
XButton component
Xbutton is the UIExtension button component. It can be used to customize icon, define whether to show text, whether to disable/enable button, etc.
Code examples
Simple xbutton example:
<html> <template id="layout-template"> <webpdf> <div> <xbutton>simple button(character data)</xbutton> <xbutton text="simple button(text property)"></xbutton> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Force to show text in built-in toolbar component
<html> <template id="layout-template"> <webpdf> <toolbar> <xbutton class="fv__ui-toolbar-show-text-button">Force to show text</xbutton> <xbutton icon-class="fv__icon-toolbar-hand">Text will be hidden</xbutton> </toolbar> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Customize icon-class
<html> <template id="layout-template"> <webpdf> <div> <xbutton icon-class="fv__icon-toolbar-hand">button with icon</xbutton> <xbutton icon-class="custom-icon-css-class">button with custom icon</xbutton> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script> <style> .custom-icon-css-class { background-repeat: no-repeat; background-position: center; background-image: url(data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzAwMTU1Yi04ODI1LTIwNDItYTIwNy0yNmQwZTVhNmJhMTUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Qjk0NjgyREIyM0E4MTFFOTgxREFDQTNEMjBCNDM5NTgiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Qjk0NjgyREEyM0E4MTFFOTgxREFDQTNEMjBCNDM5NTgiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZWIyZTI2YTItMTZlMy1hZTRmLTg1NTUtOTJmNmEyNGEyMDg1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOmUzMDAxNTViLTg4MjUtMjA0Mi1hMjA3LTI2ZDBlNWE2YmExNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjS81+AAAAHoSURBVHjavFa9SwJhGH8vWjRc+kLFpRIKUoMMKrcKobGprHBpa6m9RRz6AxqiqSmij6WtQaHRK7ioPIOmJjGxoiXOLfs913uCZ3f3StoDP54773me393z9SrVajXWSeliHZa2EWwe79W41mFIt5VDOp0m8hVgFZgBBoBX4Bo4Ac5SqdSXE7H0Ww0QfBjqPBQKRcPhMAsEAsztdjNN01ixWGSqqrJCoXALm2WQPBtfcLC+LTkS8OByPB4fjMVilm+Wy+VYNput4HKWSIwUGWKQNRDwtNwg+JRdcBOJgstpq3SZi5xASoSCk5Ad2ZOfaBclI5FI/Wbn4tCRhNsnRQmm/H5//eZD+3Qk8Hq9pCZECfqpW1oRl8tFqk+U4I1asRWpVquk3q2emwdNKZVKi8Fg8IddkhqmsrfHw3aXNhocyuUyqQd0YFNwdFYTwVE+n68T7K9tOX4B7HU/0RSdYkoVWZaF0kN2sL8nPyECPiyJTCZTcSKh57DT62xXZKtdNELLzNhFPp9P30VUUNSI9hC9OeXGAwwBKrDAl2FDDSSrA8e0TaPUwrxbFJ4SAg3BFTAKPALzQEWIwGaNm3/ycZIxMwkRtOPAeeFBn4Bx4LITJxqRzAF3gGY3aH8RmrjJfz/0pU7/bfkWYACxTcQvcW9G6AAAAABJRU5ErkJggg==); } </style>
Disable button
<html> <template id="layout-template"> <webpdf> <div> <xbutton disabled="true">disabled button</xbutton> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Click event handler
<html> <template id="layout-template"> <webpdf> <div> <xbutton name="alert-btn">Click Me!</xbutton> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, getDefaultFragments: function() { return [{ target: 'alert-btn', config: { callback: function() { alert('click button!'); } } }]; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Use controller to handle click event
<html> <template id="layout-template"> <webpdf> <div> <xbutton name="alert-btn">Click Me!</xbutton> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, getDefaultFragments: function() { return [{ target: 'alert-btn', config: { callback: UIExtension.controllers.Controller.extend({ handle: function() { alert("Click button!"); } }) } }]; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Use controller directive
<html> <template id="layout-template"> <webpdf> <div> <xbutton name="alert-btn" @controller="custom-module:ClickButtonController">Click Me!</xbutton> </div> <viewer></viewer> </webpdf> </template> </html> <script> var module = UIExtension.PDFUI.module('custom-module', []); module.controller('ClickButtonController', { handle: function() { alert("Click button!"); } }); var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
API
Xbutton object properties
Properties | Description | Type |
disabled | Button disabled status | boolean |
isVisible | Button visibility status | boolean |
Methods
Method | Description | Version |
setText(text: String): void | Set button text. It supports I18n entry | 7.0 |
setIconCls(cssClass: String): void | Set icon’s css-class of a button | 7.0 |
disable(): void | Disable button. The disabled button will not respond to the click event | 7.0 |
enable(): void | Enable button. The enabled button will respond to the click event | 7.0 |
show(): void | Show the hidden button | 7.0 |
hide(): void | Hide the button | 7.0 |
destroy(): void | Destroy the button component | 7.0 |
Events
Name | Description | Sample | Version |
click | Click button to trigger | button.on(‘click’, () => {}) | 7.0 |
Ribbon button component
<ribbon-button> has similar functions to XButton, but comparing to XButton, ribbon Button has a top-down structure of ICONS and text, and can be used as a Dropdown toggler.
Code examples
Simple ribbon-button example:
<html> <template id="layout-template"> <webpdf> <div> <ribbon-button text="simple ribbon button without icon"></ribbon-button> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Customize icon
<html> <template id="layout-template"> <webpdf> <div> <ribbon-button icon-class="fv__icon-toolbar-hand" text="button with built-in icon"></ribbon-button> <ribbon-button icon-class="custom-icon-css-class" text="button with custom icon"></ribbon-button> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script> <style> .custom-icon-css-class { background-repeat: no-repeat; background-position: center; background-image: url(data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzAwMTU1Yi04ODI1LTIwNDItYTIwNy0yNmQwZTVhNmJhMTUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Qjk0NjgyREIyM0E4MTFFOTgxREFDQTNEMjBCNDM5NTgiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Qjk0NjgyREEyM0E4MTFFOTgxREFDQTNEMjBCNDM5NTgiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZWIyZTI2YTItMTZlMy1hZTRmLTg1NTUtOTJmNmEyNGEyMDg1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOmUzMDAxNTViLTg4MjUtMjA0Mi1hMjA3LTI2ZDBlNWE2YmExNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjS81+AAAAHoSURBVHjavFa9SwJhGH8vWjRc+kLFpRIKUoMMKrcKobGprHBpa6m9RRz6AxqiqSmij6WtQaHRK7ioPIOmJjGxoiXOLfs913uCZ3f3StoDP54773me393z9SrVajXWSeliHZa2EWwe79W41mFIt5VDOp0m8hVgFZgBBoBX4Bo4Ac5SqdSXE7H0Ww0QfBjqPBQKRcPhMAsEAsztdjNN01ixWGSqqrJCoXALm2WQPBtfcLC+LTkS8OByPB4fjMVilm+Wy+VYNput4HKWSIwUGWKQNRDwtNwg+JRdcBOJgstpq3SZi5xASoSCk5Ad2ZOfaBclI5FI/Wbn4tCRhNsnRQmm/H5//eZD+3Qk8Hq9pCZECfqpW1oRl8tFqk+U4I1asRWpVquk3q2emwdNKZVKi8Fg8IddkhqmsrfHw3aXNhocyuUyqQd0YFNwdFYTwVE+n68T7K9tOX4B7HU/0RSdYkoVWZaF0kN2sL8nPyECPiyJTCZTcSKh57DT62xXZKtdNELLzNhFPp9P30VUUNSI9hC9OeXGAwwBKrDAl2FDDSSrA8e0TaPUwrxbFJ4SAg3BFTAKPALzQEWIwGaNm3/ycZIxMwkRtOPAeeFBn4Bx4LITJxqRzAF3gGY3aH8RmrjJfz/0pU7/bfkWYACxTcQvcW9G6AAAAABJRU5ErkJggg==); } </style>
Disabled button
<html> <template id="layout-template"> <webpdf> <div> <ribbon-button disabled="true" text="disabled button"></ribbon-button> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Click event handler
<html> <template id="layout-template"> <webpdf> <div> <ribbon-button name="alert-btn" text="Click Me!"></ribbon-button> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, getDefaultFragments: function() { return [{ target: 'alert-btn', config: { callback: function() { alert('click button!'); } } }]; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Use controller to handle click event
<html> <template id="layout-template"> <webpdf> <div> <ribbon-button name="alert-btn" text="Click Me!"></ribbon-button> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, getDefaultFragments: function() { return [{ target: 'alert-btn', config: { callback: UIExtension.controllers.Controller.extend({ handle: function() { alert("Click button!"); } }) } }]; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Use controller directive
<html> <template id="layout-template"> <webpdf> <div> <ribbon-button name="alert-btn" text="Click Me!" @controller="custom-module:ClickButtonController"></ribbon-button> </div> <viewer></viewer> </webpdf> </template> </html> <script> var module = UIExtension.PDFUI.module('custom-module', []); module.controller('ClickButtonController', { handle: function() { alert("Click button!"); } }); var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Nested dropdown component
<html> <template id="layout-template"> <webpdf> <div> <ribbon-button icon-class="fx-icon-ribbon_view_read-32" text="read-aloud:read-aloud.read.text" class="inline"> <dropdown icon-class="fv__icon-read-aloud-read" separate="false" > <xbutton icon-class="fx-icon-ribbon_view_read-16" text="read-aloud:read-aloud.read.text"></xbutton> <xbutton icon-class="fx-icon-ribbon_view_read_rate-32" text="read-aloud:read-aloud.rate.text"></xbutton> <xbutton icon-class="fx-icon-ribbon_view_read-32" text="read-aloud:read-aloud.volume.text"></xbutton> <xbutton icon-class="fx-icon-ribbon_view_read_pause-16" text="read-aloud:read-aloud.pause.text"></xbutton> </dropdown> </ribbon-button> </div> <viewer></viewer> </webpdf> </template> </html> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [ '/lib/uix-addons/read-aloud' ] }); </script>
API
Ribbon button properties
Properties | Description | Type |
disabled | Button disabled status | boolean |
isVisible | Button visibility status | boolean |
Methods
Method | Description | Version |
setText(text: String): void | Set button text. It supports I18n entry | 8.2.0 |
setIconCls(cssClass: String): void | Set icon’s css-class of a button | 8.2.0 |
disable(): void | Disable button. The disabled button will not respond to the click event | 8.2.0 |
enable(): void | Enable button. The enabled button will respond to the click event | 8.2.0 |
show(): void | Show the hidden button | 8.2.0 |
hide(): void | Hide the button | 8.2.0 |
destroy(): void | Destroy the button component | 8.2.0 |
Events
Name | Description | Sample | Version |
click | Click button to trigger | rbutton.on(‘click’, () => {}) | 8.2.0 |
File selector
The usage of File selector is almost same as button. It inherits from the XbuttonComponent and supports the accept property and the change event.
Code example
<html> <template id="layout-template"> <webpdf> <div class="file-selector-container"> <!-- accepts all type of files --> <file-selector accept="*.*">Select all type of file</file-selector> <!-- accepts PDF files --> <file-selector accept=".pdf">Select PDF</file-selector> <!-- accepts image files --> <file-selector accept=".png;.jpg;.bmp" @controller="custom:SelectSingleFileController">Select Image</file-selector> <!-- select multiple files --> <file-selector @controller="custom:SelectMultipleFileController" accept="image/*" multiple>Select multiple files</file-selector> <!-- use in dropdown --> <dropdown style="width: auto" text="dropdown with file selector" separate="false"> <file-selector accept=".xfdf;.fdf" text="import FDF/XFDF" icon-class="fv__icon-sidebar-import-comment"></file-selector> </dropdown> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <style> .file-selector-container { display: flex; flex-wrap: wrap; } .file-selector-container > .fv__ui-fileselector { flex: 1 1 auto; } </style> <script> UIExtension.PDFUI.module('custom', []) .controller('SelectSingleFileController', { handle: function(file) { alert('Selected file: ' + file.name); } }) .controller('SelectMultipleFileController', { handle: function(files) { alert('Selected files: \r\n' + files.map(it => it.name)); } }) var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
API
You may check button for more details.
Events
Name | Description | Example | Version |
change | Triggered when the button is clicked. If the file selector turns on multiple selection, file is an array, otherwise it is a single file instance | fileSelector.on(‘change’, (file) => { if(Array.isArray(file)) {} else {} }) | 7.4 |
Dropdown component
Code examples
Basic example
<html> <template id="layout-template"> <webpdf> <div> <dropdown icon-class="fv__icon-toolbar-shape" text="Dropdown"> <xbutton icon-class="fv__icon-toolbar-square">Square</xbutton> <xbutton icon-class="fv__icon-toolbar-circle">Circle</xbutton> <li class="fv__ui-dropdown-separator"></li> <file-selector>Select a file</file-selector> <li class="my-dropdown-list-item"> </li> </dropdown> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <style> .my-dropdown-list-item { padding: 10px 0; text-align: center; } .fv__ui-dropdown { width: auto; } </style> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, disableAll: function(){} }); var libPath = window.top.location.origin + '/lib'; var pdfui = new UIExtension.PDFUI({ viewerOptions: { libPath: libPath, jr: { licenseSN: licenseSN, licenseKey: licenseKey } }, renderTo: document.body, appearance: CustomAppearance, addons: [] }); </script>
Separation
A dropdown button can be divided into left and right parts. The left part consists of icons and text, and the right part is a drop-down arrow. When the separate parameter is set to false, you can click any one of the two parts to display the drop-down list. When the separate parameter is set to true, you can only click the right part (drop-down arrow) to display the drop-down list.
In the following demo, you will see two dropdown buttons as shown below:
Try to click the ‘Separated Dropdown’ button, you will notice the dropdown list can display only when the arrow is clicked. This is because the dropdown button has been separated, and only clicking-on-arrow can trigger the dropdown list. But you can make the dropdown list display by clicking any area on the Un-separated Dropdown button.
<html> <template id="layout-template"> <webpdf> <div> <!-- By default, the value of dropdown's 'separate' option is true --> <!-- Set selected="0" means when you click on the dropdown button, it will trigger the event for the first item in the dropdown list --> <dropdown name="separate-dropdown" icon-class="fv__icon-toolbar-square" text="Separated Dropdown" selected="0"> <xbutton name="separate-dropdown-square-btn" icon-class="fv__icon-toolbar-square">Square</xbutton> <xbutton icon-class="fv__icon-toolbar-circle">Circle</xbutton> <file-selector>Select a file</file-selector> <li class="my-dropdown-list-item"> html <li> tag </li> </dropdown> <dropdown name="non-separate-dropdown" icon-class="fv__icon-toolbar-shape" text="Un-separated Dropdown" separate="false"> <xbutton icon-class="fv__icon-toolbar-square">Square</xbutton> <xbutton icon-class="fv__icon-toolbar-circle">Circle</xbutton> </dropdown> </div> <div class="fv__ui-body"> <viewer></viewer> </div> </webpdf> </template> </html> <style> .my-dropdown-list-item { padding: 10px 0; text-align: center; } .fv__ui-dropdown { width: auto; } </style> <script> var CustomAppearance = UIExtension.appearances.Appearance.extend({ getLayoutTemplate: function() { return document.getElementById('layout-template').innerHTML; }, getDefaultFragments: