├── .gitignore ├── img ├── docpreview.png └── object_docpreview.png ├── Viewer.js ├── images │ ├── texture.png │ ├── toolbarButton-download.png │ ├── toolbarButton-pageDown.png │ ├── toolbarButton-pageUp.png │ ├── toolbarButton-zoomIn.png │ ├── toolbarButton-zoomOut.png │ ├── toolbarButton-fullscreen.png │ ├── toolbarButton-menuArrows.png │ └── toolbarButton-presentation.png ├── PDFViewerPlugin.css ├── example.local.css ├── PluginLoader.js ├── v.html ├── ODFViewerPlugin.js ├── viewer.js ├── PDFViewerPlugin.js ├── TextLayerBuilder.js └── viewer.css ├── langs ├── en_US │ └── docpreview.lang └── fr_FR │ └── docpreview.lang ├── js └── docpreview.js.php └── core └── modules └── modDocPreview.class.php /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .project 3 | .settings/ 4 | /config.php 5 | *.log 6 | 7 | 8 | -------------------------------------------------------------------------------- /img/docpreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/img/docpreview.png -------------------------------------------------------------------------------- /img/object_docpreview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/img/object_docpreview.png -------------------------------------------------------------------------------- /Viewer.js/images/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/texture.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-download.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-pageDown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-pageDown.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-pageUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-pageUp.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-zoomIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-zoomIn.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-zoomOut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-zoomOut.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-fullscreen.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-menuArrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-menuArrows.png -------------------------------------------------------------------------------- /Viewer.js/images/toolbarButton-presentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ATM-Consulting/dolibarr_module_docpreview/main/Viewer.js/images/toolbarButton-presentation.png -------------------------------------------------------------------------------- /langs/en_US/docpreview.lang: -------------------------------------------------------------------------------- 1 | Module104070Name = Doc Preview 2 | Module104070Desc = Allow to preview the generated PDF within the browser 3 | 4 | Preview = Preview 5 | PreviewOf = Preview of -------------------------------------------------------------------------------- /langs/fr_FR/docpreview.lang: -------------------------------------------------------------------------------- 1 | Module104070Name = Aperçu document 2 | Module104070Desc = Permet de prévisualiser les documents PDF dans le navigateur 3 | 4 | Preview = Prévisualiser 5 | PreviewOf = Aperçu de 6 | -------------------------------------------------------------------------------- /Viewer.js/PDFViewerPlugin.css: -------------------------------------------------------------------------------- 1 | .page { 2 | margin: 7px auto 7px auto; 3 | position: relative; 4 | overflow: visible; 5 | background-clip: content-box; 6 | background-color: white; 7 | 8 | box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); 9 | -webkit-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); 10 | -moz-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); 11 | -ms-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); 12 | -o-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75); 13 | } 14 | 15 | .textLayer { 16 | position: absolute; 17 | left: 0; 18 | top: 0; 19 | right: 0; 20 | bottom: 0; 21 | color: #000; 22 | font-family: sans-serif; 23 | overflow: hidden; 24 | } 25 | 26 | .textLayer > div { 27 | color: transparent; 28 | position: absolute; 29 | line-height: 1; 30 | white-space: pre; 31 | cursor: text; 32 | overflow: hidden; 33 | } 34 | 35 | ::selection { background:rgba(0,0,255,0.3); } 36 | ::-moz-selection { background:rgba(0,0,255,0.3); } 37 | 38 | -------------------------------------------------------------------------------- /Viewer.js/example.local.css: -------------------------------------------------------------------------------- 1 | /* This is just a sample file with CSS rules. You should write your own @font-face declarations 2 | * to add support for your desired fonts. 3 | */ 4 | 5 | @font-face { 6 | font-family: 'Novecentowide Book'; 7 | src: url("/Viewer.js/fonts/Novecentowide-Bold-webfont.eot"); 8 | src: url("/Viewer.js/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"), 9 | url("/Viewer.js/fonts/Novecentowide-Bold-webfont.woff") format("woff"), 10 | url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"), 11 | url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg"); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | 16 | @font-face { 17 | font-family: 'exotica'; 18 | src: url('/Viewer.js/fonts/Exotica-webfont.eot'); 19 | src: url('/Viewer.js/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'), 20 | url('/Viewer.js/fonts/Exotica-webfont.woff') format('woff'), 21 | url('/Viewer.js/fonts/Exotica-webfont.ttf') format('truetype'), 22 | url('/Viewer.js/fonts/Exotica-webfont.svg#exoticamedium') format('svg'); 23 | font-weight: normal; 24 | font-style: normal; 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /js/docpreview.js.php: -------------------------------------------------------------------------------- 1 | load('docpreview@docpreview'); 6 | header('Content-Type: text/javascript'); 7 | //echo $langs->trans('PreviewOf'); 8 | ?> 9 | /* 10 | 11 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 |
30 | 35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 | 47 | 59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | ✖ 76 |
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /Viewer.js/ODFViewerPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (C) 2012 KO GmbH 4 | * 5 | * @licstart 6 | * The JavaScript code in this page is free software: you can redistribute it 7 | * and/or modify it under the terms of the GNU Affero General Public License 8 | * (GNU AGPL) as published by the Free Software Foundation, either version 3 of 9 | * the License, or (at your option) any later version. The code is distributed 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. 12 | * 13 | * As additional permission under GNU AGPL version 3 section 7, you 14 | * may distribute non-source (e.g., minimized or compacted) forms of 15 | * that code without the copy of the GNU GPL normally required by 16 | * section 4, provided you include this license notice and a URL 17 | * through which recipients can access the Corresponding Source. 18 | * 19 | * As a special exception to the AGPL, any HTML file which merely makes function 20 | * calls to this code, and for that purpose includes it by reference shall be 21 | * deemed a separate work for copyright law purposes. In addition, the copyright 22 | * holders of this code give you permission to combine this code with free 23 | * software libraries that are released under the GNU LGPL. You may copy and 24 | * distribute such a system following the terms of the GNU AGPL for this code 25 | * and the LGPL for the libraries. If you modify this code, you may extend this 26 | * exception to your version of the code, but you are not obligated to do so. 27 | * If you do not wish to do so, delete this exception statement from your 28 | * version. 29 | * 30 | * This license applies to this entire compilation. 31 | * @licend 32 | * @source: http://www.webodf.org/ 33 | * @source: http://gitorious.org/webodf/webodf/ 34 | */ 35 | 36 | /*global runtime, document, odf, console*/ 37 | 38 | function ODFViewerPlugin() { 39 | "use strict"; 40 | 41 | function init(callback) { 42 | var lib = document.createElement('script'); 43 | lib.async = false; 44 | lib.src = './webodf.js'; 45 | lib.type = 'text/javascript'; 46 | lib.onload = function () { 47 | runtime.currentDirectory = function () { 48 | return "../../webodf/lib"; 49 | }; 50 | runtime.libraryPaths = function () { 51 | return [ runtime.currentDirectory() ]; 52 | }; 53 | 54 | runtime.loadClass('odf.OdfCanvas'); 55 | callback(); 56 | }; 57 | 58 | document.getElementsByTagName('head')[0].appendChild(lib); 59 | } 60 | 61 | // that should probably be provided by webodf 62 | function nsResolver(prefix) { 63 | var ns = { 64 | 'draw' : "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", 65 | 'presentation' : "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0", 66 | 'text' : "urn:oasis:names:tc:opendocument:xmlns:text:1.0", 67 | 'office' : "urn:oasis:names:tc:opendocument:xmlns:office:1.0" 68 | }; 69 | return ns[prefix] || console.log('prefix [' + prefix + '] unknown.'); 70 | } 71 | 72 | var self = this, 73 | odfCanvas = null, 74 | odfElement = null, 75 | initialized = false, 76 | root = null, 77 | documentType = null, 78 | pages = [], 79 | currentPage = null; 80 | 81 | this.initialize = function (viewerElement, documentUrl) { 82 | // If the URL has a fragment (#...), try to load the file it represents 83 | init(function () { 84 | odfElement = document.getElementById('canvas'); 85 | odfCanvas = new odf.OdfCanvas(odfElement); 86 | odfCanvas.load(documentUrl); 87 | 88 | odfCanvas.addListener('statereadychange', function () { 89 | root = odfCanvas.odfContainer().rootElement; 90 | initialized = true; 91 | documentType = odfCanvas.odfContainer().getDocumentType(root); 92 | if (documentType === 'text') { 93 | odfCanvas.enableAnnotations(true); 94 | } 95 | self.onLoad(); 96 | }); 97 | }); 98 | }; 99 | 100 | this.isSlideshow = function () { 101 | return documentType === 'presentation'; 102 | }; 103 | 104 | this.onLoad = function () {}; 105 | 106 | this.getWidth = function () { 107 | return odfElement.clientWidth; 108 | }; 109 | 110 | this.getHeight = function () { 111 | return odfElement.clientHeight; 112 | }; 113 | 114 | this.fitToWidth = function (width) { 115 | odfCanvas.fitToWidth(width); 116 | }; 117 | 118 | this.fitToHeight = function (height) { 119 | odfCanvas.fitToHeight(height); 120 | }; 121 | 122 | this.fitToPage = function (width, height) { 123 | odfCanvas.fitToContainingElement(width, height); 124 | }; 125 | 126 | this.fitSmart = function (width) { 127 | odfCanvas.fitSmart(width); 128 | }; 129 | 130 | this.getZoomLevel = function () { 131 | return odfCanvas.getZoomLevel(); 132 | }; 133 | 134 | this.setZoomLevel = function (value) { 135 | odfCanvas.setZoomLevel(value); 136 | }; 137 | 138 | // return a list of tuples (pagename, pagenode) 139 | this.getPages = function () { 140 | var pageNodes = Array.prototype.slice.call(root.getElementsByTagNameNS(nsResolver('draw'), 'page')), 141 | pages = [], 142 | i, 143 | tuple; 144 | 145 | for (i = 0; i < pageNodes.length; i += 1) { 146 | tuple = [ 147 | pageNodes[i].getAttribute('draw:name'), 148 | pageNodes[i] 149 | ]; 150 | pages.push(tuple); 151 | } 152 | return pages; 153 | }; 154 | 155 | this.showPage = function (n) { 156 | odfCanvas.showPage(n); 157 | }; 158 | 159 | } 160 | -------------------------------------------------------------------------------- /core/modules/modDocPreview.class.php: -------------------------------------------------------------------------------- 1 | 3 | * Copyright (C) 2013 ATM Consulting 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /** 20 | * \defgroup DocPreview DocPreview module 21 | * \brief DocPreview module descriptor. 22 | * \file core/modules/modDocPreview.class.php 23 | * \ingroup DocPreview 24 | * \brief Description and activation file for module DocPreview 25 | */ 26 | include_once DOL_DOCUMENT_ROOT . "/core/modules/DolibarrModules.class.php"; 27 | 28 | /** 29 | * Description and activation class for module DocPreview 30 | */ 31 | class modDocPreview extends DolibarrModules 32 | { 33 | 34 | /** 35 | * Constructor. Define names, constants, directories, boxes, permissions 36 | * 37 | * @param DoliDB $db Database handler 38 | */ 39 | public function __construct($db) 40 | { 41 | global $langs, $conf; 42 | 43 | $this->db = $db; 44 | 45 | // Id for module (must be unique). 46 | // Use a free id here 47 | // (See in Home -> System information -> Dolibarr for list of used modules id). 48 | $this->numero = 104070; // 104000 to 104999 for ATM CONSULTING 49 | // Key text used to identify module (for permissions, menus, etc...) 50 | $this->rights_class = 'DocPreview'; 51 | 52 | // Family can be 'crm','financial','hr','projects','products','ecm','technic','other' 53 | // It is used to group modules in module setup page 54 | $this->family = "technic"; 55 | // Module label (no space allowed) 56 | // used if translation string 'ModuleXXXName' not found 57 | // (where XXX is value of numeric property 'numero' of module) 58 | $this->name = preg_replace('/^mod/i', '', get_class($this)); 59 | // Module description 60 | // used if translation string 'ModuleXXXDesc' not found 61 | // (where XXX is value of numeric property 'numero' of module) 62 | $this->description = "Preview for PDF attached in attachments"; 63 | // Possible values for version are: 'development', 'experimental' or version 64 | $this->version = '1.2.1'; 65 | // Key used in llx_const table to save module status enabled/disabled 66 | // (where DocPreview is value of property name of module in uppercase) 67 | $this->const_name = 'MAIN_MODULE_' . strtoupper($this->name); 68 | // Where to store the module in setup page 69 | // (0=common,1=interface,2=others,3=very specific) 70 | $this->special = 2; 71 | // Name of image file used for this module. 72 | // If file is in theme/yourtheme/img directory under name object_pictovalue.png 73 | // use this->picto='pictovalue' 74 | // If file is in module/img directory under name object_pictovalue.png 75 | // use this->picto='pictovalue@module' 76 | $this->picto = 'docpreview@docpreview'; // mypicto@DocPreview 77 | // Defined all module parts (triggers, login, substitutions, menus, css, etc...) 78 | // for default path (eg: /DocPreview/core/xxxxx) (0=disable, 1=enable) 79 | // for specific path of parts (eg: /DocPreview/core/modules/barcode) 80 | // for specific css file (eg: /DocPreview/css/DocPreview.css.php) 81 | 82 | // Data directories to create when module is enabled. 83 | // Example: this->dirs = array("/DocPreview/temp"); 84 | $this->dirs = array(); 85 | 86 | // Config pages. Put here list of php pages 87 | // stored into DocPreview/admin directory, used to setup module. 88 | $this->config_page_url = false; 89 | 90 | // Dependencies 91 | // List of modules id that must be enabled if this module is enabled 92 | $this->depends = array(); 93 | // List of modules id to disable if this one is disabled 94 | $this->requiredby = array(); 95 | // Minimum version of PHP required by module 96 | $this->phpmin = array(5, 0); 97 | // Minimum version of Dolibarr required by module 98 | $this->need_dolibarr_version = array(3, 3); 99 | $this->langfiles = array("docpreview@docpreview"); // langfiles@mymodule 100 | // Constants 101 | // List of particular constants to add when module is enabled 102 | // (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive) 103 | // Example: 104 | $this->const=array(); 105 | 106 | 107 | $this->module_parts = array( 108 | 'js' => array('/docpreview/js/docpreview.js.php') 109 | ); 110 | 111 | } 112 | 113 | /** 114 | * Function called when module is enabled. 115 | * The init function add constants, boxes, permissions and menus 116 | * (defined in constructor) into Dolibarr database. 117 | * It also creates data directories 118 | * 119 | * @param string $options Options when enabling module ('', 'noboxes') 120 | * @return int 1 if OK, 0 if KO 121 | */ 122 | public function init($options = '') 123 | { 124 | $sql = array(); 125 | 126 | $result = $this->loadTables(); 127 | 128 | 129 | return $this->_init($sql, $options); 130 | } 131 | 132 | /** 133 | * Function called when module is disabled. 134 | * Remove from database constants, boxes and permissions from Dolibarr database. 135 | * Data directories are not deleted 136 | * 137 | * @param string $options Options when enabling module ('', 'noboxes') 138 | * @return int 1 if OK, 0 if KO 139 | */ 140 | public function remove($options = '') 141 | { 142 | $sql = array(); 143 | 144 | return $this->_remove($sql, $options); 145 | } 146 | 147 | /** 148 | * Create tables, keys and data required by module 149 | * Files llx_table1.sql, llx_table1.key.sql llx_data.sql with create table, create keys 150 | * and create data commands must be stored in directory /DocPreview/sql/ 151 | * This function is called by this->init 152 | * 153 | * @return int <=0 if KO, >0 if OK 154 | */ 155 | private function loadTables() 156 | { 157 | return $this->_load_tables('/docpreview/sql/'); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Viewer.js/viewer.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2013 KO GmbH 4 | 5 | @licstart 6 | The JavaScript code in this page is free software: you can redistribute it 7 | and/or modify it under the terms of the GNU Affero General Public License 8 | (GNU AGPL) as published by the Free Software Foundation, either version 3 of 9 | the License, or (at your option) any later version. The code is distributed 10 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. 12 | 13 | As additional permission under GNU AGPL version 3 section 7, you 14 | may distribute non-source (e.g., minimized or compacted) forms of 15 | that code without the copy of the GNU GPL normally required by 16 | section 4, provided you include this license notice and a URL 17 | through which recipients can access the Corresponding Source. 18 | 19 | As a special exception to the AGPL, any HTML file which merely makes function 20 | calls to this code, and for that purpose includes it by reference shall be 21 | deemed a separate work for copyright law purposes. In addition, the copyright 22 | holders of this code give you permission to combine this code with free 23 | software libraries that are released under the GNU LGPL. You may copy and 24 | distribute such a system following the terms of the GNU AGPL for this code 25 | and the LGPL for the libraries. If you modify this code, you may extend this 26 | exception to your version of the code, but you are not obligated to do so. 27 | If you do not wish to do so, delete this exception statement from your 28 | version. 29 | 30 | This license applies to this entire compilation. 31 | @licend 32 | @source: http://www.webodf.org/ 33 | @source: http://gitorious.org/webodf/webodf/ 34 | */ 35 | function Viewer(c){function p(){return document.isFullScreen||document.mozFullScreen||document.webkitIsFullScreen}function u(a){var b=E.options,c,f=!1,d;for(d=0;d=a?a=1:a>n.length&&(a=n.length);c.showPage(a);e=a;document.getElementById("pageNumber").value=e};this.showNextPage=function(){b.showPage(e+1)};this.showPreviousPage=function(){b.showPage(e-1)};this.download=function(){var a=t.split("#")[0];window.open(a+"#viewer.action=download","_parent")};this.toggleFullScreen=function(){var a=B;p()?document.cancelFullScreen?document.cancelFullScreen():document.mozCancelFullScreen? 40 | document.mozCancelFullScreen():document.webkitCancelFullScreen&&document.webkitCancelFullScreen():a.requestFullScreen?a.requestFullScreen():a.mozRequestFullScreen?a.mozRequestFullScreen():a.webkitRequestFullScreen&&a.webkitRequestFullScreen()};this.togglePresentationMode=function(){var a=document.getElementById("titlebar"),h=document.getElementById("toolbarContainer"),e=document.getElementById("overlayCloseButton");l?(a.style.display=h.style.display="block",e.style.display="none",d.className="",d.onmouseup= 41 | function(){},d.oncontextmenu=function(){},d.onmousedown=function(){},g("auto"),m=c.isSlideshow()):(a.style.display=h.style.display="none",e.style.display="block",d.className="presentationMode",m=!0,d.onmousedown=function(a){a.preventDefault()},d.oncontextmenu=function(a){a.preventDefault()},d.onmouseup=function(a){a.preventDefault();1===a.which?b.showNextPage():b.showPreviousPage()},g("page-fit"));l=!l};this.getZoomLevel=function(){return c.getZoomLevel()};this.setZoomLevel=function(a){c.setZoomLevel(a)}; 42 | this.zoomOut=function(){var a=(b.getZoomLevel()/1.1).toFixed(2),a=Math.max(0.25,a);g(a,!0)};this.zoomIn=function(){var a=(1.1*b.getZoomLevel()).toFixed(2),a=Math.min(4,a);g(a,!0)};(function(){b.initialize();document.cancelFullScreen||(document.mozCancelFullScreen||document.webkitCancelFullScreen)||(document.getElementById("fullscreen").style.visibility="hidden");document.getElementById("overlayCloseButton").addEventListener("click",b.toggleFullScreen);document.getElementById("fullscreen").addEventListener("click", 43 | b.toggleFullScreen);document.getElementById("presentation").addEventListener("click",function(){p()||b.toggleFullScreen();b.togglePresentationMode()});document.addEventListener("fullscreenchange",q);document.addEventListener("webkitfullscreenchange",q);document.addEventListener("mozfullscreenchange",q);document.getElementById("download").addEventListener("click",function(){b.download()});document.getElementById("zoomOut").addEventListener("click",function(){b.zoomOut()});document.getElementById("zoomIn").addEventListener("click", 44 | function(){b.zoomIn()});document.getElementById("previous").addEventListener("click",function(){b.showPreviousPage()});document.getElementById("next").addEventListener("click",function(){b.showNextPage()});document.getElementById("previousPage").addEventListener("click",function(){b.showPreviousPage()});document.getElementById("nextPage").addEventListener("click",function(){b.showNextPage()});document.getElementById("pageNumber").addEventListener("change",function(){b.showPage(this.value)});document.getElementById("scaleSelect").addEventListener("change", 45 | function(){g(this.value)});d.addEventListener("click",r);s.addEventListener("click",r);window.addEventListener("scalechange",function(a){var b=document.getElementById("customScaleOption"),c=u(String(a.scale));b.selected=!1;c||(b.textContent=Math.round(1E4*a.scale)/100+"%",b.selected=!0)},!0);window.addEventListener("resize",function(a){A&&(document.getElementById("pageWidthOption").selected||document.getElementById("pageAutoOption").selected)&&g(document.getElementById("scaleSelect").value);r()}); 46 | window.addEventListener("keydown",function(a){var c=a.shiftKey;switch(a.keyCode){case 38:case 37:b.showPreviousPage();break;case 40:case 39:b.showNextPage();break;case 32:c?b.showPreviousPage():b.showNextPage()}})})()}; 47 | -------------------------------------------------------------------------------- /Viewer.js/PDFViewerPlugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (C) 2013 KO GmbH 4 | * 5 | * @licstart 6 | * The JavaScript code in this page is free software: you can redistribute it 7 | * and/or modify it under the terms of the GNU Affero General Public License 8 | * (GNU AGPL) as published by the Free Software Foundation, either version 3 of 9 | * the License, or (at your option) any later version. The code is distributed 10 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. 12 | * 13 | * As additional permission under GNU AGPL version 3 section 7, you 14 | * may distribute non-source (e.g., minimized or compacted) forms of 15 | * that code without the copy of the GNU GPL normally required by 16 | * section 4, provided you include this license notice and a URL 17 | * through which recipients can access the Corresponding Source. 18 | * 19 | * As a special exception to the AGPL, any HTML file which merely makes function 20 | * calls to this code, and for that purpose includes it by reference shall be 21 | * deemed a separate work for copyright law purposes. In addition, the copyright 22 | * holders of this code give you permission to combine this code with free 23 | * software libraries that are released under the GNU LGPL. You may copy and 24 | * distribute such a system following the terms of the GNU AGPL for this code 25 | * and the LGPL for the libraries. If you modify this code, you may extend this 26 | * exception to your version of the code, but you are not obligated to do so. 27 | * If you do not wish to do so, delete this exception statement from your 28 | * version. 29 | * 30 | * This license applies to this entire compilation. 31 | * @licend 32 | * @source: http://viewerjs.org/ 33 | * @source: http://github.com/kogmbh/Viewer.js 34 | */ 35 | 36 | /*global document, PDFJS, console, TextLayerBuilder*/ 37 | 38 | 39 | function PDFViewerPlugin() { 40 | "use strict"; 41 | 42 | function init(callback) { 43 | var pdfLib, textLayerLib, pluginCSS; 44 | 45 | pdfLib = document.createElement('script'); 46 | pdfLib.async = false; 47 | pdfLib.src = './pdf.js'; 48 | pdfLib.type = 'text/javascript'; 49 | pdfLib.onload = function () { 50 | textLayerLib = document.createElement('script'); 51 | textLayerLib.async = false; 52 | textLayerLib.src = './TextLayerBuilder.js'; 53 | textLayerLib.type = 'text/javascript'; 54 | textLayerLib.onload = callback; 55 | document.getElementsByTagName('head')[0].appendChild(textLayerLib); 56 | }; 57 | document.getElementsByTagName('head')[0].appendChild(pdfLib); 58 | 59 | pluginCSS = document.createElement('link'); 60 | pluginCSS.setAttribute("rel", "stylesheet"); 61 | pluginCSS.setAttribute("type", "text/css"); 62 | pluginCSS.setAttribute("href", "./PDFViewerPlugin.css"); 63 | document.head.appendChild(pluginCSS); 64 | } 65 | 66 | var self = this, 67 | pages = [], 68 | domPages = [], 69 | pageText = [], 70 | renderingStates = [], 71 | RENDERING = { 72 | BLANK: 0, 73 | RUNNING: 1, 74 | FINISHED: 2 75 | }, 76 | startedTextExtraction = false, 77 | container = null, 78 | initialized = false, 79 | pdfDocument = null, 80 | pageViewScroll = null, 81 | isPresentationMode = false, 82 | scale = 1, 83 | currentPage = 1, 84 | pageWidth, 85 | pageHeight, 86 | createdPageCount = 0; 87 | 88 | function scrollIntoView(elem) { 89 | elem.parentNode.scrollTop = elem.offsetTop; 90 | } 91 | 92 | function isScrolledIntoView(elem) { 93 | var docViewTop = container.scrollTop, 94 | docViewBottom = docViewTop + container.clientHeight, 95 | elemTop = elem.offsetTop, 96 | elemBottom = elemTop + elem.clientHeight; 97 | 98 | // Is in view if either the top or the bottom of the page is between the 99 | // document viewport bounds, 100 | // or if the top is above the viewport and the bottom is below it. 101 | return (elemTop >= docViewTop && elemTop < docViewBottom) 102 | || (elemBottom >= docViewTop && elemBottom < docViewBottom) 103 | || (elemTop < docViewTop && elemBottom >= docViewBottom); 104 | } 105 | 106 | function getDomPage(page) { 107 | return domPages[page.pageInfo.pageIndex]; 108 | } 109 | function getPageText(page) { 110 | return pageText[page.pageInfo.pageIndex]; 111 | } 112 | function getRenderingStatus(page) { 113 | return renderingStates[page.pageInfo.pageIndex]; 114 | } 115 | function setRenderingStatus(page, renderStatus) { 116 | renderingStates[page.pageInfo.pageIndex] = renderStatus; 117 | } 118 | 119 | function updatePageDimensions(page, width, height) { 120 | var domPage = getDomPage(page), 121 | canvas = domPage.getElementsByTagName('canvas')[0], 122 | textLayer = domPage.getElementsByTagName('div')[0]; 123 | 124 | domPage.style.width = width; 125 | domPage.style.height = height; 126 | 127 | canvas.width = width; 128 | canvas.height = height; 129 | 130 | textLayer.style.width = width; 131 | textLayer.style.height = height; 132 | 133 | // Once the page dimension is updated, the rendering state is blank. 134 | setRenderingStatus(page, RENDERING.BLANK); 135 | } 136 | 137 | function renderPage(page) { 138 | var domPage = getDomPage(page), 139 | textLayer = getPageText(page), 140 | canvas = domPage.getElementsByTagName('canvas')[0]; 141 | 142 | if (getRenderingStatus(page) === RENDERING.BLANK) { 143 | setRenderingStatus(page, RENDERING.RUNNING); 144 | page.render({ 145 | canvasContext: canvas.getContext('2d'), 146 | textLayer: textLayer, 147 | viewport: page.getViewport(scale) 148 | }).then(function () { 149 | setRenderingStatus(page, RENDERING.FINISHED); 150 | }); 151 | } 152 | } 153 | 154 | function createPage(page) { 155 | var pageNumber, 156 | textLayerDiv, 157 | textLayer, 158 | canvas, 159 | domPage, 160 | viewport; 161 | 162 | pageNumber = page.pageInfo.pageIndex + 1; 163 | 164 | viewport = page.getViewport(scale); 165 | 166 | domPage = document.createElement('div'); 167 | domPage.id = 'pageContainer' + pageNumber; 168 | domPage.className = 'page'; 169 | 170 | canvas = document.createElement('canvas'); 171 | canvas.id = 'canvas' + pageNumber; 172 | 173 | textLayerDiv = document.createElement('div'); 174 | textLayerDiv.className = 'textLayer'; 175 | textLayerDiv.id = 'textLayer' + pageNumber; 176 | 177 | container.appendChild(domPage); 178 | domPage.appendChild(canvas); 179 | domPage.appendChild(textLayerDiv); 180 | 181 | pages.push(page); 182 | domPages.push(domPage); 183 | renderingStates.push(RENDERING.BLANK); 184 | 185 | updatePageDimensions(page, viewport.width, viewport.height); 186 | pageWidth = viewport.width; 187 | pageHeight = viewport.height; 188 | 189 | textLayer = new TextLayerBuilder({ 190 | textLayerDiv: textLayerDiv, 191 | pageIndex: pageNumber - 1 192 | }); 193 | page.getTextContent().then(function (textContent) { 194 | textLayer.setTextContent(textContent); 195 | }); 196 | pageText.push(textLayer); 197 | 198 | createdPageCount += 1; 199 | if (createdPageCount === (pdfDocument.numPages)) { 200 | self.onLoad(); 201 | } 202 | } 203 | 204 | this.initialize = function (viewContainer, location) { 205 | var self = this, 206 | i, 207 | pluginCSS; 208 | 209 | init(function () { 210 | PDFJS.workerSrc = "./pdf.worker.js"; 211 | PDFJS.getDocument(location).then(function loadPDF(doc) { 212 | pdfDocument = doc; 213 | container = viewContainer; 214 | 215 | for (i = 0; i < pdfDocument.numPages; i += 1) { 216 | pdfDocument.getPage(i + 1).then(createPage); 217 | } 218 | 219 | initialized = true; 220 | }); 221 | }); 222 | }; 223 | 224 | this.isSlideshow = function () { 225 | // A very simple but generally true guess - if the width is greater than the height, treat it as a slideshow 226 | return pageWidth > pageHeight; 227 | }; 228 | 229 | this.onLoad = function () {}; 230 | 231 | this.getPages = function () { 232 | return domPages; 233 | }; 234 | 235 | this.getWidth = function () { 236 | return pageWidth; 237 | }; 238 | 239 | this.getHeight = function () { 240 | return pageHeight; 241 | }; 242 | 243 | this.fitToWidth = function (width) { 244 | var zoomLevel; 245 | 246 | if (self.getWidth() === width) { 247 | return; 248 | } 249 | zoomLevel = width / pageWidth; 250 | self.setZoomLevel(zoomLevel); 251 | }; 252 | 253 | this.fitToHeight = function (height) { 254 | var zoomLevel; 255 | 256 | if (self.getHeight() === height) { 257 | return; 258 | } 259 | zoomLevel = height / pageHeight; 260 | self.setZoomLevel(zoomLevel); 261 | }; 262 | 263 | this.fitToPage = function (width, height) { 264 | var zoomLevel = width / pageWidth; 265 | if (height / pageHeight < zoomLevel) { 266 | zoomLevel = height / pageHeight; 267 | } 268 | self.setZoomLevel(zoomLevel); 269 | }; 270 | 271 | this.fitSmart = function (width, height) { 272 | var zoomLevel = width / pageWidth; 273 | if (height && (height / pageHeight) < zoomLevel) { 274 | zoomLevel = height / pageHeight; 275 | } 276 | zoomLevel = Math.min(1.0, zoomLevel); 277 | self.setZoomLevel(zoomLevel); 278 | }; 279 | 280 | this.setZoomLevel = function (zoomLevel) { 281 | var i; 282 | 283 | if (scale !== zoomLevel) { 284 | scale = zoomLevel; 285 | 286 | for (i = 0; i < pages.length; i += 1) { 287 | updatePageDimensions(pages[i], pageWidth * scale, pageHeight * scale); 288 | } 289 | } 290 | }; 291 | 292 | this.getZoomLevel = function () { 293 | return scale; 294 | }; 295 | 296 | this.onScroll = function () { 297 | var i; 298 | 299 | for (i = 0; i < domPages.length; i += 1) { 300 | if (isScrolledIntoView(domPages[i])) { 301 | if (getRenderingStatus(pages[i]) === RENDERING.BLANK) { 302 | renderPage(pages[i]); 303 | } 304 | } 305 | } 306 | }; 307 | 308 | this.getPageInView = function () { 309 | var i; 310 | for (i = 0; i < domPages.length; i += 1) { 311 | if (isScrolledIntoView(domPages[i])) { 312 | return i + 1; 313 | } 314 | } 315 | }; 316 | 317 | this.showPage = function (n) { 318 | scrollIntoView(domPages[n - 1]); 319 | }; 320 | } 321 | -------------------------------------------------------------------------------- /Viewer.js/TextLayerBuilder.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* Copyright 2012 Mozilla Foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // adapted for Viewer.js by KO GmbH 18 | 19 | /** 20 | * TextLayerBuilder provides text-selection 21 | * functionality for the PDF. It does this 22 | * by creating overlay divs over the PDF 23 | * text. This divs contain text that matches 24 | * the PDF text they are overlaying. This 25 | * object also provides for a way to highlight 26 | * text that is being searched for. 27 | */ 28 | var CustomStyle = (function CustomStyleClosure() { 29 | 30 | // As noted on: http://www.zachstronaut.com/posts/2009/02/17/ 31 | // animate-css-transforms-firefox-webkit.html 32 | // in some versions of IE9 it is critical that ms appear in this list 33 | // before Moz 34 | var prefixes = ['ms', 'Moz', 'Webkit', 'O']; 35 | var _cache = { }; 36 | 37 | function CustomStyle() { 38 | } 39 | 40 | CustomStyle.getProp = function get(propName, element) { 41 | // check cache only when no element is given 42 | if (arguments.length == 1 && typeof _cache[propName] == 'string') { 43 | return _cache[propName]; 44 | } 45 | 46 | element = element || document.documentElement; 47 | var style = element.style, prefixed, uPropName; 48 | 49 | // test standard property first 50 | if (typeof style[propName] == 'string') { 51 | return (_cache[propName] = propName); 52 | } 53 | 54 | // capitalize 55 | uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); 56 | 57 | // test vendor specific properties 58 | for (var i = 0, l = prefixes.length; i < l; i++) { 59 | prefixed = prefixes[i] + uPropName; 60 | if (typeof style[prefixed] == 'string') { 61 | return (_cache[propName] = prefixed); 62 | } 63 | } 64 | 65 | //if all fails then set to undefined 66 | return (_cache[propName] = 'undefined'); 67 | }; 68 | 69 | CustomStyle.setProp = function set(propName, element, str) { 70 | var prop = this.getProp(propName); 71 | if (prop != 'undefined') 72 | element.style[prop] = str; 73 | }; 74 | 75 | return CustomStyle; 76 | })(); 77 | 78 | var TextLayerBuilder = function textLayerBuilder(options) { 79 | var textLayerFrag = document.createDocumentFragment(); 80 | 81 | this.textLayerDiv = options.textLayerDiv; 82 | this.layoutDone = false; 83 | this.divContentDone = false; 84 | this.pageIdx = options.pageIndex; 85 | this.matches = []; 86 | this.lastScrollSource = options.lastScrollSource; 87 | 88 | if(typeof PDFFindController === 'undefined') { 89 | window.PDFFindController = null; 90 | } 91 | 92 | if(typeof this.lastScrollSource === 'undefined') { 93 | this.lastScrollSource = null; 94 | } 95 | 96 | this.beginLayout = function textLayerBuilderBeginLayout() { 97 | this.textDivs = []; 98 | this.renderingDone = false; 99 | }; 100 | 101 | this.endLayout = function textLayerBuilderEndLayout() { 102 | this.layoutDone = true; 103 | this.insertDivContent(); 104 | }; 105 | 106 | this.renderLayer = function textLayerBuilderRenderLayer() { 107 | var self = this; 108 | var textDivs = this.textDivs; 109 | var bidiTexts = this.textContent.bidiTexts; 110 | var textLayerDiv = this.textLayerDiv; 111 | var canvas = document.createElement('canvas'); 112 | var ctx = canvas.getContext('2d'); 113 | 114 | // No point in rendering so many divs as it'd make the browser unusable 115 | // even after the divs are rendered 116 | var MAX_TEXT_DIVS_TO_RENDER = 100000; 117 | if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) 118 | return; 119 | 120 | for (var i = 0, ii = textDivs.length; i < ii; i++) { 121 | var textDiv = textDivs[i]; 122 | if ('isWhitespace' in textDiv.dataset) { 123 | continue; 124 | } 125 | textLayerFrag.appendChild(textDiv); 126 | 127 | ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily; 128 | var width = ctx.measureText(textDiv.textContent).width; 129 | 130 | if (width > 0) { 131 | var textScale = textDiv.dataset.canvasWidth / width; 132 | var rotation = textDiv.dataset.angle; 133 | var transform = 'scale(' + textScale + ', 1)'; 134 | if (bidiTexts[i].dir === 'ttb') { 135 | rotation += 90; 136 | } 137 | transform = 'rotate(' + rotation + 'deg) ' + transform; 138 | CustomStyle.setProp('transform' , textDiv, transform); 139 | CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); 140 | 141 | textLayerDiv.appendChild(textDiv); 142 | } 143 | } 144 | 145 | this.renderingDone = true; 146 | this.updateMatches(); 147 | 148 | textLayerDiv.appendChild(textLayerFrag); 149 | }; 150 | 151 | this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() { 152 | // Schedule renderLayout() if user has been scrolling, otherwise 153 | // run it right away 154 | var RENDER_DELAY = 200; // in ms 155 | var self = this; 156 | var lastScroll = this.lastScrollSource === null ? 157 | 0 : this.lastScrollSource.lastScroll; 158 | 159 | if (Date.now() - lastScroll > RENDER_DELAY) { 160 | // Render right away 161 | this.renderLayer(); 162 | } else { 163 | // Schedule 164 | if (this.renderTimer) 165 | clearTimeout(this.renderTimer); 166 | this.renderTimer = setTimeout(function() { 167 | self.setupRenderLayoutTimer(); 168 | }, RENDER_DELAY); 169 | } 170 | }; 171 | 172 | this.appendText = function textLayerBuilderAppendText(geom) { 173 | var textDiv = document.createElement('div'); 174 | 175 | // vScale and hScale already contain the scaling to pixel units 176 | var fontHeight = geom.fontSize * Math.abs(geom.vScale); 177 | textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale); 178 | textDiv.dataset.fontName = geom.fontName; 179 | textDiv.dataset.angle = geom.angle * (180 / Math.PI); 180 | 181 | textDiv.style.fontSize = fontHeight + 'px'; 182 | textDiv.style.fontFamily = geom.fontFamily; 183 | textDiv.style.left = (geom.x + (fontHeight * Math.sin(geom.angle))) + 'px'; 184 | textDiv.style.top = (geom.y - (fontHeight * Math.cos(geom.angle))) + 'px'; 185 | 186 | // The content of the div is set in the `setTextContent` function. 187 | 188 | this.textDivs.push(textDiv); 189 | }; 190 | 191 | this.insertDivContent = function textLayerUpdateTextContent() { 192 | // Only set the content of the divs once layout has finished, the content 193 | // for the divs is available and content is not yet set on the divs. 194 | if (!this.layoutDone || this.divContentDone || !this.textContent) 195 | return; 196 | 197 | this.divContentDone = true; 198 | 199 | var textDivs = this.textDivs; 200 | var bidiTexts = this.textContent.bidiTexts; 201 | 202 | for (var i = 0; i < bidiTexts.length; i++) { 203 | var bidiText = bidiTexts[i]; 204 | var textDiv = textDivs[i]; 205 | if (!/\S/.test(bidiText.str)) { 206 | textDiv.dataset.isWhitespace = true; 207 | continue; 208 | } 209 | 210 | textDiv.textContent = bidiText.str; 211 | // bidiText.dir may be 'ttb' for vertical texts. 212 | textDiv.dir = bidiText.dir === 'rtl' ? 'rtl' : 'ltr'; 213 | } 214 | 215 | this.setupRenderLayoutTimer(); 216 | }; 217 | 218 | this.setTextContent = function textLayerBuilderSetTextContent(textContent) { 219 | this.textContent = textContent; 220 | this.insertDivContent(); 221 | }; 222 | 223 | this.convertMatches = function textLayerBuilderConvertMatches(matches) { 224 | var i = 0; 225 | var iIndex = 0; 226 | var bidiTexts = this.textContent.bidiTexts; 227 | var end = bidiTexts.length - 1; 228 | var queryLen = PDFFindController === null ? 229 | 0 : PDFFindController.state.query.length; 230 | 231 | var lastDivIdx = -1; 232 | var pos; 233 | 234 | var ret = []; 235 | 236 | // Loop over all the matches. 237 | for (var m = 0; m < matches.length; m++) { 238 | var matchIdx = matches[m]; 239 | // # Calculate the begin position. 240 | 241 | // Loop over the divIdxs. 242 | while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { 243 | iIndex += bidiTexts[i].str.length; 244 | i++; 245 | } 246 | 247 | // TODO: Do proper handling here if something goes wrong. 248 | if (i == bidiTexts.length) { 249 | console.error('Could not find matching mapping'); 250 | } 251 | 252 | var match = { 253 | begin: { 254 | divIdx: i, 255 | offset: matchIdx - iIndex 256 | } 257 | }; 258 | 259 | // # Calculate the end position. 260 | matchIdx += queryLen; 261 | 262 | // Somewhat same array as above, but use a > instead of >= to get the end 263 | // position right. 264 | while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { 265 | iIndex += bidiTexts[i].str.length; 266 | i++; 267 | } 268 | 269 | match.end = { 270 | divIdx: i, 271 | offset: matchIdx - iIndex 272 | }; 273 | ret.push(match); 274 | } 275 | 276 | return ret; 277 | }; 278 | 279 | this.renderMatches = function textLayerBuilder_renderMatches(matches) { 280 | // Early exit if there is nothing to render. 281 | if (matches.length === 0) { 282 | return; 283 | } 284 | 285 | var bidiTexts = this.textContent.bidiTexts; 286 | var textDivs = this.textDivs; 287 | var prevEnd = null; 288 | var isSelectedPage = PDFFindController === null ? 289 | false : (this.pageIdx === PDFFindController.selected.pageIdx); 290 | 291 | var selectedMatchIdx = PDFFindController === null ? 292 | -1 : PDFFindController.selected.matchIdx; 293 | 294 | var highlightAll = PDFFindController === null ? 295 | false : PDFFindController.state.highlightAll; 296 | 297 | var infty = { 298 | divIdx: -1, 299 | offset: undefined 300 | }; 301 | 302 | function beginText(begin, className) { 303 | var divIdx = begin.divIdx; 304 | var div = textDivs[divIdx]; 305 | div.textContent = ''; 306 | 307 | var content = bidiTexts[divIdx].str.substring(0, begin.offset); 308 | var node = document.createTextNode(content); 309 | if (className) { 310 | var isSelected = isSelectedPage && 311 | divIdx === selectedMatchIdx; 312 | var span = document.createElement('span'); 313 | span.className = className + (isSelected ? ' selected' : ''); 314 | span.appendChild(node); 315 | div.appendChild(span); 316 | return; 317 | } 318 | div.appendChild(node); 319 | } 320 | 321 | function appendText(from, to, className) { 322 | var divIdx = from.divIdx; 323 | var div = textDivs[divIdx]; 324 | 325 | var content = bidiTexts[divIdx].str.substring(from.offset, to.offset); 326 | var node = document.createTextNode(content); 327 | if (className) { 328 | var span = document.createElement('span'); 329 | span.className = className; 330 | span.appendChild(node); 331 | div.appendChild(span); 332 | return; 333 | } 334 | div.appendChild(node); 335 | } 336 | 337 | function highlightDiv(divIdx, className) { 338 | textDivs[divIdx].className = className; 339 | } 340 | 341 | var i0 = selectedMatchIdx, i1 = i0 + 1, i; 342 | 343 | if (highlightAll) { 344 | i0 = 0; 345 | i1 = matches.length; 346 | } else if (!isSelectedPage) { 347 | // Not highlighting all and this isn't the selected page, so do nothing. 348 | return; 349 | } 350 | 351 | for (i = i0; i < i1; i++) { 352 | var match = matches[i]; 353 | var begin = match.begin; 354 | var end = match.end; 355 | 356 | var isSelected = isSelectedPage && i === selectedMatchIdx; 357 | var highlightSuffix = (isSelected ? ' selected' : ''); 358 | if (isSelected) 359 | scrollIntoView(textDivs[begin.divIdx], {top: -50}); 360 | 361 | // Match inside new div. 362 | if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { 363 | // If there was a previous div, then add the text at the end 364 | if (prevEnd !== null) { 365 | appendText(prevEnd, infty); 366 | } 367 | // clears the divs and set the content until the begin point. 368 | beginText(begin); 369 | } else { 370 | appendText(prevEnd, begin); 371 | } 372 | 373 | if (begin.divIdx === end.divIdx) { 374 | appendText(begin, end, 'highlight' + highlightSuffix); 375 | } else { 376 | appendText(begin, infty, 'highlight begin' + highlightSuffix); 377 | for (var n = begin.divIdx + 1; n < end.divIdx; n++) { 378 | highlightDiv(n, 'highlight middle' + highlightSuffix); 379 | } 380 | beginText(end, 'highlight end' + highlightSuffix); 381 | } 382 | prevEnd = end; 383 | } 384 | 385 | if (prevEnd) { 386 | appendText(prevEnd, infty); 387 | } 388 | }; 389 | 390 | this.updateMatches = function textLayerUpdateMatches() { 391 | // Only show matches, once all rendering is done. 392 | if (!this.renderingDone) 393 | return; 394 | 395 | // Clear out all matches. 396 | var matches = this.matches; 397 | var textDivs = this.textDivs; 398 | var bidiTexts = this.textContent.bidiTexts; 399 | var clearedUntilDivIdx = -1; 400 | 401 | // Clear out all current matches. 402 | for (var i = 0; i < matches.length; i++) { 403 | var match = matches[i]; 404 | var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); 405 | for (var n = begin; n <= match.end.divIdx; n++) { 406 | var div = textDivs[n]; 407 | div.textContent = bidiTexts[n].str; 408 | div.className = ''; 409 | } 410 | clearedUntilDivIdx = match.end.divIdx + 1; 411 | } 412 | 413 | if (PDFFindController === null || !PDFFindController.active) 414 | return; 415 | 416 | // Convert the matches on the page controller into the match format used 417 | // for the textLayer. 418 | this.matches = matches = 419 | this.convertMatches(PDFFindController === null ? 420 | [] : (PDFFindController.pageMatches[this.pageIdx] || [])); 421 | 422 | this.renderMatches(this.matches); 423 | }; 424 | }; 425 | 426 | -------------------------------------------------------------------------------- /Viewer.js/viewer.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | font-family: sans-serif; 8 | } 9 | 10 | .titlebar > span, 11 | .toolbarLabel, 12 | input, 13 | button, 14 | select { 15 | font: message-box; 16 | } 17 | 18 | #titlebar { 19 | position: absolute; 20 | z-index: 2; 21 | top: 0px; 22 | left: 0px; 23 | height: 32px; 24 | width: 100%; 25 | overflow: hidden; 26 | 27 | -webkit-box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75); 28 | -moz-box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75); 29 | box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75); 30 | 31 | background-image: url(images/texture.png), linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); 32 | background-image: url(images/texture.png), -webkit-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); 33 | background-image: url(images/texture.png), -moz-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); 34 | background-image: url(images/texture.png), -ms-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); 35 | background-image: url(images/texture.png), -o-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99)); 36 | } 37 | 38 | #documentName { 39 | margin-left: 10px; 40 | margin-top: 8px; 41 | color: #F2F2F2; 42 | font-size: 14px; 43 | line-height: 14px; 44 | font-family: sans-serif; 45 | } 46 | 47 | #toolbarContainer { 48 | position: absolute; 49 | z-index: 2; 50 | bottom: 0px; 51 | left: 0px; 52 | height: 32px; 53 | width: 100%; 54 | overflow: hidden; 55 | 56 | -webkit-box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75); 57 | -moz-box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75); 58 | box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75); 59 | 60 | background-image: url(images/texture.png), linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); 61 | background-image: url(images/texture.png), -webkit-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); 62 | background-image: url(images/texture.png), -moz-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); 63 | background-image: url(images/texture.png), -ms-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); 64 | background-image: url(images/texture.png), -o-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95)); 65 | } 66 | 67 | #toolbar { 68 | position: relative; 69 | } 70 | 71 | #toolbarMiddleContainer, #toolbarLeft { 72 | visibility: hidden; 73 | } 74 | 75 | html[dir='ltr'] #toolbarLeft { 76 | margin-left: -1px; 77 | } 78 | html[dir='rtl'] #toolbarRight { 79 | margin-left: -1px; 80 | } 81 | html[dir='ltr'] #toolbarLeft, 82 | html[dir='rtl'] #toolbarRight { 83 | position: absolute; 84 | top: 0; 85 | left: 0; 86 | } 87 | html[dir='ltr'] #toolbarRight, 88 | html[dir='rtl'] #toolbarLeft { 89 | position: absolute; 90 | top: 0; 91 | right: 0; 92 | } 93 | html[dir='ltr'] #toolbarLeft > *, 94 | html[dir='ltr'] #toolbarMiddle > *, 95 | html[dir='ltr'] #toolbarRight > * { 96 | float: left; 97 | } 98 | html[dir='rtl'] #toolbarLeft > *, 99 | html[dir='rtl'] #toolbarMiddle > *, 100 | html[dir='rtl'] #toolbarRight > * { 101 | float: right; 102 | } 103 | 104 | /* outer/inner center provides horizontal center */ 105 | html[dir='ltr'] .outerCenter { 106 | float: right; 107 | position: relative; 108 | right: 50%; 109 | } 110 | html[dir='rtl'] .outerCenter { 111 | float: left; 112 | position: relative; 113 | left: 50%; 114 | } 115 | html[dir='ltr'] .innerCenter { 116 | float: right; 117 | position: relative; 118 | right: -50%; 119 | } 120 | html[dir='rtl'] .innerCenter { 121 | float: left; 122 | position: relative; 123 | left: -50%; 124 | } 125 | 126 | html[dir='ltr'] .splitToolbarButton { 127 | margin: 3px 2px 4px 0; 128 | display: inline-block; 129 | } 130 | html[dir='rtl'] .splitToolbarButton { 131 | margin: 3px 0 4px 2px; 132 | display: inline-block; 133 | } 134 | html[dir='ltr'] .splitToolbarButton > .toolbarButton { 135 | border-radius: 0; 136 | float: left; 137 | } 138 | html[dir='rtl'] .splitToolbarButton > .toolbarButton { 139 | border-radius: 0; 140 | float: right; 141 | } 142 | 143 | .splitToolbarButton.toggled .toolbarButton { 144 | margin: 0; 145 | } 146 | 147 | .toolbarButton { 148 | border: 0 none; 149 | background-color: rgba(0, 0, 0, 0); 150 | width: 32px; 151 | height: 25px; 152 | border-radius: 2px; 153 | background-image: none; 154 | } 155 | 156 | html[dir='ltr'] .toolbarButton, 157 | html[dir='ltr'] .dropdownToolbarButton { 158 | margin: 3px 2px 4px 0; 159 | } 160 | html[dir='rtl'] .toolbarButton, 161 | html[dir='rtl'] .dropdownToolbarButton { 162 | margin: 3px 0 4px 2px; 163 | } 164 | 165 | .toolbarButton:hover, 166 | .toolbarButton:focus, 167 | .dropdownToolbarButton { 168 | background-color: hsla(0,0%,0%,.12); 169 | background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 170 | background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 171 | background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 172 | background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 173 | background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 174 | background-clip: padding-box; 175 | border: 1px solid hsla(0,0%,0%,.35); 176 | border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); 177 | box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, 178 | 0 0 1px hsla(0,0%,100%,.15) inset, 179 | 0 1px 0 hsla(0,0%,100%,.05); 180 | } 181 | 182 | .toolbarButton:hover:active, 183 | .dropdownToolbarButton:hover:active { 184 | background-color: hsla(0,0%,0%,.2); 185 | background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 186 | background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 187 | background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 188 | background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 189 | background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 190 | border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45); 191 | box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset, 192 | 0 0 1px hsla(0,0%,0%,.2) inset, 193 | 0 1px 0 hsla(0,0%,100%,.05); 194 | } 195 | 196 | .splitToolbarButton:hover > .toolbarButton, 197 | .splitToolbarButton:focus > .toolbarButton, 198 | .splitToolbarButton.toggled > .toolbarButton, 199 | .toolbarButton.textButton { 200 | background-color: hsla(0,0%,0%,.12); 201 | background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 202 | background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 203 | background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 204 | background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 205 | background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 206 | background-clip: padding-box; 207 | border: 1px solid hsla(0,0%,0%,.35); 208 | border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); 209 | box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, 210 | 0 0 1px hsla(0,0%,100%,.15) inset, 211 | 0 1px 0 hsla(0,0%,100%,.05); 212 | -webkit-transition-property: background-color, border-color, box-shadow; 213 | -webkit-transition-duration: 150ms; 214 | -webkit-transition-timing-function: ease; 215 | -moz-transition-property: background-color, border-color, box-shadow; 216 | -moz-transition-duration: 150ms; 217 | -moz-transition-timing-function: ease; 218 | -ms-transition-property: background-color, border-color, box-shadow; 219 | -ms-transition-duration: 150ms; 220 | -ms-transition-timing-function: ease; 221 | -o-transition-property: background-color, border-color, box-shadow; 222 | -o-transition-duration: 150ms; 223 | -o-transition-timing-function: ease; 224 | transition-property: background-color, border-color, box-shadow; 225 | transition-duration: 150ms; 226 | transition-timing-function: ease; 227 | 228 | } 229 | .splitToolbarButton > .toolbarButton:hover, 230 | .splitToolbarButton > .toolbarButton:focus, 231 | .dropdownToolbarButton:hover, 232 | .toolbarButton.textButton:hover, 233 | .toolbarButton.textButton:focus { 234 | background-color: hsla(0,0%,0%,.2); 235 | box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, 236 | 0 0 1px hsla(0,0%,100%,.15) inset, 237 | 0 0 1px hsla(0,0%,0%,.05); 238 | z-index: 199; 239 | } 240 | 241 | 242 | .splitToolbarButton:hover > .toolbarButton, 243 | .splitToolbarButton:focus > .toolbarButton, 244 | .splitToolbarButton.toggled > .toolbarButton, 245 | .toolbarButton.textButton { 246 | background-color: hsla(0,0%,0%,.12); 247 | background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 248 | background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 249 | background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 250 | background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 251 | background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0)); 252 | background-clip: padding-box; 253 | border: 1px solid hsla(0,0%,0%,.35); 254 | border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); 255 | box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, 256 | 0 0 1px hsla(0,0%,100%,.15) inset, 257 | 0 1px 0 hsla(0,0%,100%,.05); 258 | -webkit-transition-property: background-color, border-color, box-shadow; 259 | -webkit-transition-duration: 150ms; 260 | -webkit-transition-timing-function: ease; 261 | -moz-transition-property: background-color, border-color, box-shadow; 262 | -moz-transition-duration: 150ms; 263 | -moz-transition-timing-function: ease; 264 | -ms-transition-property: background-color, border-color, box-shadow; 265 | -ms-transition-duration: 150ms; 266 | -ms-transition-timing-function: ease; 267 | -o-transition-property: background-color, border-color, box-shadow; 268 | -o-transition-duration: 150ms; 269 | -o-transition-timing-function: ease; 270 | transition-property: background-color, border-color, box-shadow; 271 | transition-duration: 150ms; 272 | transition-timing-function: ease; 273 | 274 | } 275 | .splitToolbarButton > .toolbarButton:hover, 276 | .splitToolbarButton > .toolbarButton:focus, 277 | .dropdownToolbarButton:hover, 278 | .toolbarButton.textButton:hover, 279 | .toolbarButton.textButton:focus { 280 | background-color: hsla(0,0%,0%,.2); 281 | box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset, 282 | 0 0 1px hsla(0,0%,100%,.15) inset, 283 | 0 0 1px hsla(0,0%,0%,.05); 284 | z-index: 199; 285 | } 286 | 287 | .dropdownToolbarButton { 288 | border: 1px solid #333 !important; 289 | } 290 | 291 | .toolbarButton, 292 | .dropdownToolbarButton { 293 | min-width: 16px; 294 | padding: 2px 6px 2px; 295 | border: 1px solid transparent; 296 | border-radius: 2px; 297 | color: hsl(0,0%,95%); 298 | font-size: 12px; 299 | line-height: 14px; 300 | -webkit-user-select:none; 301 | -moz-user-select:none; 302 | -ms-user-select:none; 303 | /* Opera does not support user-select, use <... unselectable="on"> instead */ 304 | cursor: default; 305 | -webkit-transition-property: background-color, border-color, box-shadow; 306 | -webkit-transition-duration: 150ms; 307 | -webkit-transition-timing-function: ease; 308 | -moz-transition-property: background-color, border-color, box-shadow; 309 | -moz-transition-duration: 150ms; 310 | -moz-transition-timing-function: ease; 311 | -ms-transition-property: background-color, border-color, box-shadow; 312 | -ms-transition-duration: 150ms; 313 | -ms-transition-timing-function: ease; 314 | -o-transition-property: background-color, border-color, box-shadow; 315 | -o-transition-duration: 150ms; 316 | -o-transition-timing-function: ease; 317 | transition-property: background-color, border-color, box-shadow; 318 | transition-duration: 150ms; 319 | transition-timing-function: ease; 320 | } 321 | 322 | html[dir='ltr'] .toolbarButton, 323 | html[dir='ltr'] .dropdownToolbarButton { 324 | margin: 3px 2px 4px 0; 325 | } 326 | html[dir='rtl'] .toolbarButton, 327 | html[dir='rtl'] .dropdownToolbarButton { 328 | margin: 3px 0 4px 2px; 329 | } 330 | 331 | .splitToolbarButton:hover > .splitToolbarButtonSeparator, 332 | .splitToolbarButton.toggled > .splitToolbarButtonSeparator { 333 | padding: 12px 0; 334 | margin: 0; 335 | box-shadow: 0 0 0 1px hsla(0,0%,100%,.03); 336 | -webkit-transition-property: padding; 337 | -webkit-transition-duration: 10ms; 338 | -webkit-transition-timing-function: ease; 339 | -moz-transition-property: padding; 340 | -moz-transition-duration: 10ms; 341 | -moz-transition-timing-function: ease; 342 | -ms-transition-property: padding; 343 | -ms-transition-duration: 10ms; 344 | -ms-transition-timing-function: ease; 345 | -o-transition-property: padding; 346 | -o-transition-duration: 10ms; 347 | -o-transition-timing-function: ease; 348 | transition-property: padding; 349 | transition-duration: 10ms; 350 | transition-timing-function: ease; 351 | } 352 | 353 | .toolbarButton.toggled:hover:active, 354 | .splitToolbarButton > .toolbarButton:hover:active { 355 | background-color: hsla(0,0%,0%,.4); 356 | border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55); 357 | box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset, 358 | 0 0 1px hsla(0,0%,0%,.3) inset, 359 | 0 1px 0 hsla(0,0%,100%,.05); 360 | } 361 | 362 | html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child, 363 | html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child { 364 | position: relative; 365 | margin: 0; 366 | margin-left: 4px; 367 | margin-right: -1px; 368 | border-top-left-radius: 2px; 369 | border-bottom-left-radius: 2px; 370 | border-right-color: transparent; 371 | } 372 | html[dir='ltr'] .splitToolbarButton > .toolbarButton:last-child, 373 | html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child { 374 | position: relative; 375 | margin: 0; 376 | margin-left: -1px; 377 | border-top-right-radius: 2px; 378 | border-bottom-right-radius: 2px; 379 | border-left-color: transparent; 380 | } 381 | .splitToolbarButtonSeparator { 382 | padding: 8px 0; 383 | width: 1px; 384 | background-color: hsla(0,0%,00%,.5); 385 | z-index: 99; 386 | box-shadow: 0 0 0 1px hsla(0,0%,100%,.08); 387 | display: inline-block; 388 | margin: 5px 0; 389 | } 390 | html[dir='ltr'] .splitToolbarButtonSeparator { 391 | float:left; 392 | } 393 | html[dir='rtl'] .splitToolbarButtonSeparator { 394 | float:right; 395 | } 396 | 397 | .dropdownToolbarButton { 398 | min-width: 120px; 399 | max-width: 120px; 400 | padding: 4px 2px 4px; 401 | overflow: hidden; 402 | background: url(images/toolbarButton-menuArrows.png) no-repeat; 403 | } 404 | 405 | .dropdownToolbarButton > select { 406 | -webkit-appearance: none; 407 | -moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */ 408 | min-width: 140px; 409 | font-size: 12px; 410 | color: hsl(0,0%,95%); 411 | margin:0; 412 | padding:0; 413 | border:none; 414 | background: rgba(0,0,0,0); /* Opera does not support 'transparent'