2013-03-31 09:41:38 +00:00
|
|
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab filetype=javascript : */
|
2013-10-30 07:50:07 +00:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Core UI functions for pdf2htmlEX
|
|
|
|
* Copyright 2012,2013 Lu Wang <coolwanglu@gmail.com> and other contributors
|
|
|
|
* https://github.com/coolwanglu/pdf2htmlEX/blob/master/share/LICENSE
|
|
|
|
*/
|
2012-09-22 14:47:44 +00:00
|
|
|
/*
|
2013-10-22 07:03:07 +00:00
|
|
|
* pdf2htmlEX.js : a simple UI for pdf2htmlEX
|
2012-09-22 14:47:44 +00:00
|
|
|
*
|
2013-10-19 07:06:32 +00:00
|
|
|
* Copyright 2012,2013 Lu Wang <coolwanglu@gmail.com> and other contributors
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dependencies:
|
|
|
|
* jQuery
|
2012-09-22 14:47:44 +00:00
|
|
|
*/
|
|
|
|
|
2013-10-20 08:35:31 +00:00
|
|
|
/*
|
|
|
|
* Attention:
|
|
|
|
* This files is to be optimized by closure-compiler,
|
|
|
|
* so pay attention to the forms of property names:
|
|
|
|
*
|
|
|
|
* string/bracket form is safe, won't be optimized:
|
|
|
|
* var obj={ 'a':'b' }; obj['a'] = 'b';
|
|
|
|
* name/dot form will be optimized, the name is likely to be modified:
|
|
|
|
* var obj={ a:'b' }; obj.a = 'b';
|
|
|
|
*
|
|
|
|
* Either form can be used for internal objects,
|
|
|
|
* but must be consistent for each one respectively.
|
|
|
|
*
|
|
|
|
* string/bracket form must be used for external objects
|
|
|
|
* e.g. DEFAULT_CONFIG, object stored in page-data
|
|
|
|
* property names are part of the `protocol` in these cases.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2013-05-29 18:31:48 +00:00
|
|
|
'use strict';
|
|
|
|
|
2013-09-30 07:50:15 +00:00
|
|
|
/* The namespace */
|
2013-10-19 07:06:32 +00:00
|
|
|
(function(pdf2htmlEX){
|
2013-10-22 13:32:02 +00:00
|
|
|
/**
|
|
|
|
* @const
|
|
|
|
* @struct
|
|
|
|
*/
|
2013-10-22 07:03:07 +00:00
|
|
|
var CSS_CLASS_NAMES = {
|
|
|
|
page_frame : '@CSS_PAGE_FRAME_CN@',
|
|
|
|
page_content_box : '@CSS_PAGE_CONTENT_BOX_CN@',
|
|
|
|
page_data : '@CSS_PAGE_DATA_CN@',
|
|
|
|
background_image : '@CSS_BACKGROUND_IMAGE_CN@',
|
|
|
|
link : '@CSS_LINK_CN@',
|
|
|
|
__dummy__ : 'no comma'
|
|
|
|
};
|
2013-09-30 07:50:15 +00:00
|
|
|
|
2013-10-22 13:32:02 +00:00
|
|
|
/**
|
|
|
|
* @const
|
|
|
|
* @dict
|
|
|
|
*/
|
2013-10-05 08:11:03 +00:00
|
|
|
var DEFAULT_CONFIG = {
|
|
|
|
// id of the element to put the pages in
|
2013-10-19 07:06:32 +00:00
|
|
|
'container_id' : 'page-container',
|
2013-10-05 08:11:03 +00:00
|
|
|
// id of the element for sidebar (to open and close)
|
2013-10-19 07:06:32 +00:00
|
|
|
'sidebar_id' : 'sidebar',
|
2013-10-05 08:11:03 +00:00
|
|
|
// id of the element for outline
|
2013-10-19 07:06:32 +00:00
|
|
|
'outline_id' : 'outline',
|
2013-10-05 08:11:03 +00:00
|
|
|
// class for the loading indicator
|
2013-10-20 08:35:31 +00:00
|
|
|
'loading_indicator_cls' : 'loading-indicator',
|
2013-10-05 08:11:03 +00:00
|
|
|
// How many page shall we preload that are below the last visible page
|
2013-10-19 07:06:32 +00:00
|
|
|
'preload_pages' : 3,
|
2013-10-05 08:11:03 +00:00
|
|
|
// Smooth zoom is enabled when the number of pages shown is less than the threshold
|
|
|
|
// Otherwise page content is hidden and redrawn after a delay (function schedule_render).
|
|
|
|
// 0: disable smooth zoom optimizations (less CPU usage but flickering on zoom)
|
2013-10-19 07:06:32 +00:00
|
|
|
'smooth_zoom_threshold' : 4,
|
2013-10-05 08:26:24 +00:00
|
|
|
// how many ms should we wait before actually rendering the pages and after a scroll event
|
2013-10-19 07:06:32 +00:00
|
|
|
'render_timeout' : 100,
|
2013-10-05 08:26:24 +00:00
|
|
|
// zoom ratio step for each zoom in/out event
|
2013-10-19 07:06:32 +00:00
|
|
|
'scale_step' : 0.9,
|
2013-09-30 07:50:15 +00:00
|
|
|
|
2013-10-22 13:32:02 +00:00
|
|
|
'__dummy__' : 'no comma'
|
2013-10-05 08:11:03 +00:00
|
|
|
};
|
2012-09-25 16:27:58 +00:00
|
|
|
|
2013-10-22 07:03:07 +00:00
|
|
|
/** @const */
|
2012-09-23 09:26:12 +00:00
|
|
|
var EPS = 1e-6;
|
2013-10-22 13:32:02 +00:00
|
|
|
|
|
|
|
function invert(ctm) {
|
2012-09-25 11:29:59 +00:00
|
|
|
var det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
|
2013-10-22 13:32:02 +00:00
|
|
|
return [ ctm[3] / det
|
|
|
|
,-ctm[1] / det
|
|
|
|
,-ctm[2] / det
|
|
|
|
,ctm[0] / det
|
|
|
|
,(ctm[2] * ctm[5] - ctm[3] * ctm[4]) / det
|
|
|
|
,(ctm[1] * ctm[4] - ctm[0] * ctm[5]) / det
|
|
|
|
];
|
2012-09-25 11:29:59 +00:00
|
|
|
};
|
2013-10-22 13:32:02 +00:00
|
|
|
function transform(ctm, pos) {
|
2012-09-25 11:29:59 +00:00
|
|
|
return [ctm[0] * pos[0] + ctm[2] * pos[1] + ctm[4]
|
|
|
|
,ctm[1] * pos[0] + ctm[3] * pos[1] + ctm[5]];
|
|
|
|
};
|
2013-10-22 07:03:07 +00:00
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @struct
|
|
|
|
*/
|
2013-10-22 13:32:02 +00:00
|
|
|
function Page(page, container) {
|
|
|
|
if(!page) return;
|
2012-09-25 11:29:59 +00:00
|
|
|
|
2013-06-12 15:53:14 +00:00
|
|
|
this.loaded = false;
|
2013-07-16 15:42:02 +00:00
|
|
|
this.shown = false;
|
2013-11-01 07:34:18 +00:00
|
|
|
this.$p = $(page); // page frame element
|
2013-06-13 15:00:42 +00:00
|
|
|
this.$container = $(container);
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-06-13 15:00:42 +00:00
|
|
|
this.n = parseInt(this.$p.data('page-no'), 16);
|
2013-11-01 07:34:18 +00:00
|
|
|
// content box
|
2013-10-20 08:35:31 +00:00
|
|
|
this.$b = $('.'+CSS_CLASS_NAMES.page_content_box, this.$p);
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-09-30 07:50:15 +00:00
|
|
|
// page size
|
|
|
|
// Need to make rescale work when page_content_box is not loaded, yet
|
|
|
|
this.h = this.$p.height();
|
2013-06-13 15:00:42 +00:00
|
|
|
this.w = this.$p.width();
|
2012-09-25 11:29:59 +00:00
|
|
|
|
2013-06-12 15:53:14 +00:00
|
|
|
// if page is loaded
|
2013-06-13 15:00:42 +00:00
|
|
|
if (this.$b.length > 0) {
|
2013-06-12 15:53:14 +00:00
|
|
|
/*
|
|
|
|
* scale ratios
|
|
|
|
*
|
|
|
|
* default_r : the first one
|
|
|
|
* set_r : last set
|
|
|
|
* cur_r : currently using
|
|
|
|
*/
|
2013-07-16 18:19:25 +00:00
|
|
|
this.default_r = this.set_r = this.cur_r = this.h / this.$b.height();
|
2013-10-20 08:35:31 +00:00
|
|
|
this.page_data = $($('.'+CSS_CLASS_NAMES.page_data, this.$p)[0]).data('data');
|
2012-09-25 11:29:59 +00:00
|
|
|
|
2013-10-20 08:35:31 +00:00
|
|
|
this.ctm = this.page_data['ctm'];
|
2013-06-12 15:53:14 +00:00
|
|
|
this.ictm = invert(this.ctm);
|
|
|
|
|
|
|
|
this.loaded = true;
|
|
|
|
}
|
2012-09-23 09:26:12 +00:00
|
|
|
};
|
2013-10-22 07:03:07 +00:00
|
|
|
$.extend(Page.prototype, /** @lends {Page.prototype} */ {
|
2013-02-06 11:52:34 +00:00
|
|
|
/* hide & show are for contents, the page frame is still there */
|
2012-09-25 16:27:58 +00:00
|
|
|
hide : function(){
|
2013-06-13 15:00:42 +00:00
|
|
|
this.$b.removeClass('opened');
|
2013-07-16 15:42:02 +00:00
|
|
|
this.shown = false;
|
2012-09-25 16:27:58 +00:00
|
|
|
},
|
|
|
|
show : function(){
|
2013-07-16 18:19:25 +00:00
|
|
|
if (this.loaded) {
|
|
|
|
if(Math.abs(this.set_r - this.cur_r) > EPS) {
|
|
|
|
this.cur_r = this.set_r;
|
|
|
|
this.$b.css('transform', 'scale('+this.cur_r.toFixed(3)+')');
|
|
|
|
}
|
|
|
|
if (! this.shown) {
|
|
|
|
this.$b.addClass('opened');
|
|
|
|
this.shown = true;
|
|
|
|
}
|
2012-09-25 16:27:58 +00:00
|
|
|
}
|
|
|
|
},
|
2013-11-01 08:11:44 +00:00
|
|
|
/**
|
|
|
|
* @param {boolean=} force_show Force showing the page for smooth rescaling
|
|
|
|
*/
|
|
|
|
rescale : function(ratio, force_show) {
|
2012-09-25 16:27:58 +00:00
|
|
|
if(ratio == 0) {
|
|
|
|
this.set_r = this.default_r;
|
|
|
|
} else {
|
|
|
|
this.set_r = ratio;
|
|
|
|
}
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2013-11-01 08:11:44 +00:00
|
|
|
if (force_show)
|
2013-07-17 07:47:13 +00:00
|
|
|
this.show(); // Refresh content
|
|
|
|
else
|
|
|
|
this.hide(); // Wait for redraw
|
2012-09-25 13:56:48 +00:00
|
|
|
|
2013-11-04 15:53:38 +00:00
|
|
|
this.$p.height(this.h * this.set_r);
|
|
|
|
this.$p.width(this.w * this.set_r);
|
2012-09-25 16:27:58 +00:00
|
|
|
},
|
2013-05-02 08:45:00 +00:00
|
|
|
/* return if any part of this page is shown in the container */
|
2012-09-25 16:27:58 +00:00
|
|
|
is_visible : function() {
|
|
|
|
var off = this.position();
|
2013-07-16 18:19:25 +00:00
|
|
|
return !((off[1] > this.h) || (off[1] + this.$container.height() < 0));
|
2012-09-25 16:27:58 +00:00
|
|
|
},
|
2013-05-02 08:45:00 +00:00
|
|
|
/* return if this page or any neighbor of it is visible */
|
|
|
|
is_nearly_visible : function() {
|
|
|
|
var off = this.position();
|
2013-11-01 07:34:18 +00:00
|
|
|
/*
|
|
|
|
* I should use the height of the previous page or the next page here
|
2013-05-02 08:45:00 +00:00
|
|
|
* but since they are not easily available, just use '*2', which should be a good estimate in most cases
|
|
|
|
*/
|
2013-07-16 18:19:25 +00:00
|
|
|
return !((off[1] > this.h * 2) || (off[1] + this.$container.height() * 2 < 0));
|
2013-05-02 08:45:00 +00:00
|
|
|
},
|
2013-11-01 07:34:18 +00:00
|
|
|
/*
|
|
|
|
* return the coordinate of the top-left corner of container
|
2013-08-08 22:53:44 +00:00
|
|
|
* in our coordinate system
|
2012-09-25 16:27:58 +00:00
|
|
|
*/
|
|
|
|
position : function () {
|
2013-11-04 16:13:32 +00:00
|
|
|
var rect = this.$p[0].getBoundingClientRect();
|
|
|
|
// getBoundingClientRect will be called on $container
|
|
|
|
// for a lot of times(= #pages), just hope JIT works
|
|
|
|
var rect_c = this.$container[0].getBoundingClientRect();
|
|
|
|
return [rect_c.left-rect.left, rect_c.top-rect.top];
|
2013-11-04 14:32:00 +00:00
|
|
|
/*
|
2013-06-13 15:00:42 +00:00
|
|
|
var off = this.$p.offset();
|
|
|
|
var off_c = this.$container.offset();
|
2012-09-25 16:27:58 +00:00
|
|
|
return [off_c.left-off.left, off_c.top-off.top];
|
2013-11-04 14:32:00 +00:00
|
|
|
*/
|
2012-09-25 16:27:58 +00:00
|
|
|
}
|
|
|
|
});
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2013-10-22 07:03:07 +00:00
|
|
|
/**
|
|
|
|
* export pdf2htmlEX.Viewer
|
|
|
|
* @constructor
|
|
|
|
*/
|
2013-10-22 13:32:02 +00:00
|
|
|
function Viewer(config) {
|
2013-10-21 07:04:34 +00:00
|
|
|
/* do nothing if jQuery is not ready */
|
2013-10-28 08:29:58 +00:00
|
|
|
if(!jQuery) return;
|
2013-10-21 07:04:34 +00:00
|
|
|
|
2013-10-05 08:11:03 +00:00
|
|
|
this.config = $.extend({}, DEFAULT_CONFIG, config);
|
2013-10-28 08:05:24 +00:00
|
|
|
this.pages_loading = [];
|
2012-09-25 14:40:21 +00:00
|
|
|
this.init_before_loading_content();
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2012-09-25 14:40:21 +00:00
|
|
|
var _ = this;
|
|
|
|
$(function(){_.init_after_loading_content();});
|
|
|
|
};
|
|
|
|
|
2013-10-22 07:03:07 +00:00
|
|
|
$.extend(Viewer.prototype, /** @lends {Viewer.prototype} */ {
|
2013-07-16 15:42:02 +00:00
|
|
|
scale : 1,
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2012-09-23 09:26:12 +00:00
|
|
|
init_before_loading_content : function() {
|
|
|
|
/*hide all pages before loading, will reveal only visible ones later */
|
|
|
|
this.pre_hide_pages();
|
|
|
|
},
|
2013-06-13 15:00:42 +00:00
|
|
|
|
2012-09-23 09:26:12 +00:00
|
|
|
init_after_loading_content : function() {
|
2013-10-05 08:11:03 +00:00
|
|
|
this.$sidebar = $('#'+this.config['sidebar_id']);
|
|
|
|
this.$outline = $('#'+this.config['outline_id']);
|
|
|
|
this.$container = $('#'+this.config['container_id']);
|
|
|
|
this.$loading_indicator = $('.'+this.config['loading_indicator_cls']);
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2013-03-01 06:02:11 +00:00
|
|
|
// Open the outline if nonempty
|
2013-07-16 15:42:02 +00:00
|
|
|
if(this.$outline.children().length > 0) {
|
2013-06-13 15:00:42 +00:00
|
|
|
this.$sidebar.addClass('opened');
|
2013-01-28 14:11:42 +00:00
|
|
|
}
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-05-02 08:09:42 +00:00
|
|
|
this.find_pages();
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2013-03-01 06:02:11 +00:00
|
|
|
// register schedule rendering
|
2012-09-23 09:26:12 +00:00
|
|
|
var _ = this;
|
2013-06-13 15:00:42 +00:00
|
|
|
this.$container.scroll(function(){ _.schedule_render(); });
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2013-10-06 11:53:37 +00:00
|
|
|
//this.register_key_handler();
|
2013-07-03 07:57:00 +00:00
|
|
|
|
2013-03-01 06:02:11 +00:00
|
|
|
// handle links
|
2013-10-20 08:35:31 +00:00
|
|
|
this.$container.add(this.$outline).on('click', '.'+CSS_CLASS_NAMES.link, this, this.link_handler);
|
2012-09-23 05:04:29 +00:00
|
|
|
|
2012-09-23 09:26:12 +00:00
|
|
|
this.render();
|
2013-05-02 08:09:42 +00:00
|
|
|
},
|
2013-07-16 15:42:02 +00:00
|
|
|
|
2013-10-28 08:05:24 +00:00
|
|
|
/*
|
|
|
|
* set up this.pages and this.page_map
|
|
|
|
* pages is an array holding all the Page objects
|
|
|
|
* page-Map maps an original page number (in PDF) to the corresponding index in page
|
|
|
|
*/
|
2013-05-02 08:09:42 +00:00
|
|
|
find_pages : function() {
|
2013-10-28 08:05:24 +00:00
|
|
|
var new_pages = [];
|
|
|
|
var new_page_map = {};
|
2013-11-04 16:13:32 +00:00
|
|
|
var $pl= this.$container.find('.'+CSS_CLASS_NAMES.page_frame);
|
2013-09-30 07:50:15 +00:00
|
|
|
/* don't use for(..in..) since $pl is more than an Array */
|
2013-06-13 15:00:42 +00:00
|
|
|
for(var i = 0, l = $pl.length; i < l; ++i) {
|
|
|
|
var p = new Page($pl[i], this.$container);
|
2013-10-28 08:29:58 +00:00
|
|
|
new_page_map[p.n] = i;
|
2013-10-28 08:05:24 +00:00
|
|
|
new_pages.push(p);
|
2013-05-02 08:09:42 +00:00
|
|
|
}
|
|
|
|
this.pages = new_pages;
|
2013-10-28 08:05:24 +00:00
|
|
|
this.page_map = new_page_map;
|
2013-05-02 08:09:42 +00:00
|
|
|
},
|
2013-06-13 15:00:42 +00:00
|
|
|
|
2013-06-13 10:39:52 +00:00
|
|
|
load_page : function(idx, pages_to_preload, successCallback, errorCallback) {
|
2013-10-28 08:29:58 +00:00
|
|
|
var pages = this.pages;
|
|
|
|
if (idx >= pages.length)
|
2013-06-13 10:39:52 +00:00
|
|
|
return; // Page does not exist
|
|
|
|
|
2013-11-01 07:34:18 +00:00
|
|
|
var cur_page = pages[idx];
|
|
|
|
if (cur_page.loaded)
|
2013-06-12 15:53:14 +00:00
|
|
|
return; // Page is loaded
|
|
|
|
|
2013-06-13 10:39:52 +00:00
|
|
|
if (this.pages_loading[idx])
|
2013-06-12 15:53:14 +00:00
|
|
|
return; // Page is already loading
|
|
|
|
|
2013-11-01 07:34:18 +00:00
|
|
|
var page_no_hex = cur_page.n.toString(16);
|
|
|
|
var $pf = cur_page.$p;
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-09-28 05:20:45 +00:00
|
|
|
this.$loading_indicator.clone().show().appendTo($pf);
|
2013-09-28 03:58:01 +00:00
|
|
|
|
2013-06-12 15:53:14 +00:00
|
|
|
var url = $pf.data('page-url');
|
2013-11-01 07:34:18 +00:00
|
|
|
if (url) {
|
2013-06-13 10:39:52 +00:00
|
|
|
this.pages_loading[idx] = true; // Set semaphore
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-11-01 07:34:18 +00:00
|
|
|
/* closure variables */
|
|
|
|
var _ = this;
|
|
|
|
var _idx = idx;
|
|
|
|
|
2013-05-02 08:09:42 +00:00
|
|
|
$.ajax({
|
2013-06-12 15:53:14 +00:00
|
|
|
url: url,
|
2013-05-02 08:09:42 +00:00
|
|
|
dataType: 'text'
|
|
|
|
}).done(function(data){
|
2013-10-07 07:25:47 +00:00
|
|
|
// replace the old page with loaded data
|
|
|
|
// the loading indicator on this page should also be destroyed
|
2013-11-01 07:34:18 +00:00
|
|
|
var p = _.pages[idx];
|
2013-11-04 15:53:38 +00:00
|
|
|
p.$p.replaceWith(data);
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-11-01 08:11:44 +00:00
|
|
|
var $new_pf = _.$container.find('#' + CSS_CLASS_NAMES.page_frame + p.n.toString(16));
|
2013-11-01 07:34:18 +00:00
|
|
|
p = new Page($new_pf, _.$container);
|
|
|
|
p.hide();
|
|
|
|
p.rescale(_.scale);
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-10-06 11:53:37 +00:00
|
|
|
// disable background image dragging
|
2013-10-20 08:35:31 +00:00
|
|
|
$new_pf.find('.'+CSS_CLASS_NAMES.background_image).on('dragstart', function(e){return false;});
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-11-01 07:34:18 +00:00
|
|
|
_.pages[idx] = p;
|
|
|
|
_.schedule_render();
|
|
|
|
|
2013-10-06 11:53:37 +00:00
|
|
|
// Reset loading token
|
|
|
|
delete _.pages_loading[idx];
|
2013-06-12 15:53:14 +00:00
|
|
|
|
2013-10-28 08:29:58 +00:00
|
|
|
if (successCallback) successCallback(_.pages[idx]);
|
2013-11-01 07:34:18 +00:00
|
|
|
}).fail(function(jqXHR, textStatus, errorThrown){
|
2013-06-13 10:39:52 +00:00
|
|
|
// Reset loading token
|
|
|
|
delete _.pages_loading[idx];
|
|
|
|
|
|
|
|
if (errorCallback) errorCallback();
|
|
|
|
});
|
2013-05-02 08:09:42 +00:00
|
|
|
}
|
2013-06-12 15:53:14 +00:00
|
|
|
// Concurrent prefetch of the next pages
|
|
|
|
if (pages_to_preload === undefined)
|
2013-10-05 08:11:03 +00:00
|
|
|
pages_to_preload = this.config['preload_pages'];
|
2013-06-12 15:53:14 +00:00
|
|
|
|
|
|
|
if (--pages_to_preload > 0)
|
2013-11-01 07:34:18 +00:00
|
|
|
this.load_page(idx+1, pages_to_preload);
|
2012-09-23 09:26:12 +00:00
|
|
|
},
|
2013-06-13 15:00:42 +00:00
|
|
|
|
2012-09-23 09:26:12 +00:00
|
|
|
pre_hide_pages : function() {
|
|
|
|
/* pages might have not been loaded yet, so add a CSS rule */
|
2013-10-20 08:35:31 +00:00
|
|
|
var s = '@media screen{.'+CSS_CLASS_NAMES.page_content_box+'{display:none;}}';
|
2012-09-25 11:29:59 +00:00
|
|
|
var n = document.createElement('style');
|
|
|
|
n.type = 'text/css';
|
2012-09-23 09:26:12 +00:00
|
|
|
if (n.styleSheet) {
|
|
|
|
n.styleSheet.cssText = s;
|
|
|
|
} else {
|
|
|
|
n.appendChild(document.createTextNode(s));
|
|
|
|
}
|
2012-09-25 11:29:59 +00:00
|
|
|
document.getElementsByTagName('head')[0].appendChild(n);
|
2012-09-23 09:26:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
render : function () {
|
|
|
|
/* hide (positional) invisible pages */
|
|
|
|
var pl = this.pages;
|
2013-10-28 08:29:58 +00:00
|
|
|
var pm = this.page_map;
|
2013-10-28 08:05:24 +00:00
|
|
|
for(var i = 0, l = pl.length; i < l; ++i) {
|
2012-09-23 09:26:12 +00:00
|
|
|
var p = pl[i];
|
2013-05-02 08:45:00 +00:00
|
|
|
if(p.is_nearly_visible()){
|
2013-06-12 15:53:14 +00:00
|
|
|
if (p.loaded) {
|
|
|
|
p.show();
|
|
|
|
} else
|
2013-10-28 08:29:58 +00:00
|
|
|
this.load_page(pm[p.n]);
|
2012-09-25 13:56:48 +00:00
|
|
|
} else {
|
2012-09-25 11:29:59 +00:00
|
|
|
p.hide();
|
2012-09-25 13:56:48 +00:00
|
|
|
}
|
2012-09-22 14:47:44 +00:00
|
|
|
}
|
2012-09-23 09:26:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
schedule_render : function() {
|
|
|
|
if(this.render_timer)
|
|
|
|
clearTimeout(this.render_timer);
|
|
|
|
|
|
|
|
var _ = this;
|
|
|
|
this.render_timer = setTimeout(function () {
|
|
|
|
_.render();
|
2013-10-05 08:26:24 +00:00
|
|
|
}, this.config['render_timeout']);
|
2012-09-23 09:26:12 +00:00
|
|
|
},
|
|
|
|
|
2013-10-06 11:53:37 +00:00
|
|
|
/*
|
|
|
|
* Handling key events, zooming, scrolling etc.
|
|
|
|
*/
|
|
|
|
register_key_handler: function () {
|
2012-09-23 09:26:12 +00:00
|
|
|
/*
|
|
|
|
* When user try to zoom in/out using ctrl + +/- or mouse wheel
|
|
|
|
* handle this and prevent the default behaviours
|
|
|
|
*
|
|
|
|
* Code credit to PDF.js
|
|
|
|
*/
|
|
|
|
var _ = this;
|
|
|
|
// Firefox specific event, so that we can prevent browser from zooming
|
|
|
|
$(window).on('DOMMouseScroll', function(e) {
|
|
|
|
if (e.ctrlKey) {
|
|
|
|
e.preventDefault();
|
2013-10-05 08:26:24 +00:00
|
|
|
_.rescale(Math.pow(_.config['scale_step'], e.detail), true);
|
2012-09-23 09:26:12 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$(window).on('keydown', function keydown(e) {
|
2013-10-06 11:53:37 +00:00
|
|
|
var handled = false;
|
2013-10-22 13:32:02 +00:00
|
|
|
var cmd = (e.ctrlKey ? 1 : 0)
|
|
|
|
| (e.altKey ? 2 : 0)
|
|
|
|
| (e.shiftKey ? 4 : 0)
|
|
|
|
| (e.metaKey ? 8 : 0)
|
|
|
|
;
|
2013-10-06 11:53:37 +00:00
|
|
|
var with_ctrl = (cmd == 9);
|
|
|
|
var with_alt = (cmd == 2);
|
|
|
|
switch (e.keyCode) {
|
|
|
|
case 61: // FF/Mac '='
|
|
|
|
case 107: // FF '+' and '='
|
|
|
|
case 187: // Chrome '+'
|
|
|
|
if(with_ctrl){
|
2013-10-05 08:26:24 +00:00
|
|
|
_.rescale(1.0 / _.config['scale_step'], true);
|
2013-10-06 11:53:37 +00:00
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 173: // FF/Mac '-'
|
|
|
|
case 109: // FF '-'
|
|
|
|
case 189: // Chrome '-'
|
|
|
|
if(with_ctrl){
|
2013-10-05 08:26:24 +00:00
|
|
|
_.rescale(_.config['scale_step'], true);
|
2013-10-06 11:53:37 +00:00
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 48: // '0'
|
|
|
|
if(with_ctrl){
|
2012-09-23 09:26:12 +00:00
|
|
|
_.rescale(0, false);
|
2013-10-06 11:53:37 +00:00
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 33: // Page UP:
|
|
|
|
if (with_alt) { // alt-pageup -> scroll one page up
|
|
|
|
_.scroll_to(_.get_prev_page());
|
|
|
|
} else { // pageup -> scroll one screen up
|
|
|
|
_.$container.scrollTop(_.$container.scrollTop()-_.$container.height());
|
|
|
|
}
|
|
|
|
handled = true;
|
|
|
|
break;
|
|
|
|
case 34: // Page DOWN
|
|
|
|
if (with_alt) { // alt-pagedown -> scroll one page down
|
|
|
|
_.scroll_to(_.get_next_page());
|
|
|
|
} else { // pagedown -> scroll one screen down
|
|
|
|
_.$container.scrollTop(_.$container.scrollTop()+_.$container.height());
|
|
|
|
}
|
|
|
|
handled = true;
|
|
|
|
break;
|
|
|
|
case 35: // End
|
|
|
|
if (with_ctrl) {
|
|
|
|
_.$container.scrollTop(_.$container[0].scrollHeight);
|
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 36: // Home
|
|
|
|
if (e.with_ctrl) {
|
|
|
|
_.$container.scrollTop(0);
|
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
break;
|
2012-09-22 16:17:30 +00:00
|
|
|
}
|
2013-10-06 11:53:37 +00:00
|
|
|
if(handled) {
|
2012-09-24 12:20:34 +00:00
|
|
|
e.preventDefault();
|
2013-07-03 20:02:53 +00:00
|
|
|
return;
|
2012-09-22 16:17:30 +00:00
|
|
|
}
|
2013-10-06 11:57:44 +00:00
|
|
|
});
|
2013-07-03 07:57:00 +00:00
|
|
|
},
|
|
|
|
|
2013-10-22 13:32:02 +00:00
|
|
|
// TODO
|
|
|
|
get_next_page : function() { return undefined; },
|
|
|
|
get_prev_page : function() { return undefined; },
|
2012-09-23 09:26:12 +00:00
|
|
|
|
2013-07-16 15:42:02 +00:00
|
|
|
rescale : function (ratio, is_relative, offsetX, offsetY) {
|
|
|
|
if (! offsetX)
|
|
|
|
offsetX = 0;
|
|
|
|
if (! offsetY)
|
|
|
|
offsetY = 0;
|
2013-07-16 18:19:25 +00:00
|
|
|
|
2013-07-16 15:42:02 +00:00
|
|
|
// Save offset of the active page
|
|
|
|
var active_page = this.get_active_page();
|
2013-09-30 07:50:15 +00:00
|
|
|
if(!active_page) return;
|
|
|
|
|
2013-11-04 16:13:32 +00:00
|
|
|
var prev_offset = active_page.$p[0].getBoundingClientRect();
|
2013-07-16 15:42:02 +00:00
|
|
|
var old_scale = this.scale;
|
2013-09-30 07:50:15 +00:00
|
|
|
var pl = this.pages;
|
2013-07-16 15:42:02 +00:00
|
|
|
|
2013-07-16 18:19:25 +00:00
|
|
|
var prerendering_enabled = false;
|
2013-10-05 08:11:03 +00:00
|
|
|
if (this.config['smooth_zoom_threshold'] > 0) {
|
2013-07-16 18:19:25 +00:00
|
|
|
// Immediate rendering optimizations enabled to improve reactiveness while zooming
|
|
|
|
// Find out which pages are visible
|
|
|
|
var min_visible, max_visible;
|
2013-10-28 08:29:58 +00:00
|
|
|
// TODO: page index
|
|
|
|
min_visible = max_visible = this.page_map[active_page.n];
|
2013-09-30 07:50:15 +00:00
|
|
|
while (pl[min_visible] && pl[min_visible].is_visible()) { --min_visible; }
|
|
|
|
++ min_visible;
|
|
|
|
while (pl[max_visible] && pl[max_visible].is_visible()) { ++max_visible; }
|
|
|
|
-- max_visible;
|
2013-07-16 18:19:25 +00:00
|
|
|
|
|
|
|
// If less then the threshold, enable prerendering on selected pages
|
2013-10-05 08:11:03 +00:00
|
|
|
if (max_visible - min_visible + 1 < this.config['smooth_zoom_threshold'])
|
2013-07-16 18:19:25 +00:00
|
|
|
prerendering_enabled = true;
|
|
|
|
}
|
|
|
|
|
2013-07-16 15:42:02 +00:00
|
|
|
// Set new scale
|
|
|
|
if (is_relative)
|
|
|
|
this.scale *= ratio;
|
|
|
|
else
|
|
|
|
this.scale = ratio;
|
|
|
|
|
|
|
|
// Rescale pages
|
2013-10-28 08:05:24 +00:00
|
|
|
for(var i = 0, l = pl.length; i < l; ++i) {
|
2013-09-30 07:50:15 +00:00
|
|
|
if (prerendering_enabled && i >= min_visible && i <= max_visible)
|
2013-07-17 07:47:13 +00:00
|
|
|
pl[i].rescale(this.scale, true); // Force immediate refresh
|
|
|
|
else
|
|
|
|
pl[i].rescale(this.scale); // Delayed refresh
|
2012-09-23 05:04:29 +00:00
|
|
|
}
|
2012-09-22 16:17:30 +00:00
|
|
|
|
2013-07-16 15:42:02 +00:00
|
|
|
// Correct container scroll to keep view aligned while zooming
|
2013-11-04 16:13:32 +00:00
|
|
|
var correction_top = active_page.$p[0].getBoundingClientRect().top - prev_offset.top;
|
2013-07-16 18:19:25 +00:00
|
|
|
this.$container.scrollTop( this.$container.scrollTop() + correction_top + offsetY );
|
2013-07-16 15:42:02 +00:00
|
|
|
|
|
|
|
// Take the center of the view as a reference
|
|
|
|
var prev_center_x = this.$container.width() / 2 - prev_offset.left;
|
|
|
|
// Calculate the difference respect the center of the view after the zooming
|
|
|
|
var correction_left = prev_center_x * (this.scale/old_scale - 1) + active_page.$p.offset().left - prev_offset.left;
|
|
|
|
// Scroll the container accordingly to keep alignment to the initial reference
|
2013-07-16 18:19:25 +00:00
|
|
|
this.$container.scrollLeft( this.$container.scrollLeft() + correction_left + offsetX );
|
2013-07-16 15:42:02 +00:00
|
|
|
|
2013-07-16 18:19:25 +00:00
|
|
|
// Delayed rendering for pages not already shown
|
2012-09-23 09:26:12 +00:00
|
|
|
this.schedule_render();
|
|
|
|
},
|
2012-09-22 16:17:30 +00:00
|
|
|
|
2013-07-16 15:42:02 +00:00
|
|
|
fit_width : function () {
|
2013-07-16 18:19:25 +00:00
|
|
|
var active_page = this.get_active_page();
|
2013-09-30 07:50:15 +00:00
|
|
|
if(!active_page) return;
|
2013-07-16 15:42:02 +00:00
|
|
|
|
2013-07-16 18:19:25 +00:00
|
|
|
this.rescale(this.$container.width() / active_page.w, false);
|
2013-10-28 08:29:58 +00:00
|
|
|
this.scroll_to(this.page_map[active_page.n], [0,0]);
|
2013-07-16 15:42:02 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
fit_height : function () {
|
|
|
|
var active_page = this.get_active_page();
|
2013-09-30 07:50:15 +00:00
|
|
|
if(!active_page) return;
|
2013-07-16 15:42:02 +00:00
|
|
|
|
2013-07-16 18:19:25 +00:00
|
|
|
this.rescale(this.$container.height() / active_page.h, false);
|
2013-10-28 08:29:58 +00:00
|
|
|
this.scroll_to(this.page_map[active_page.n], [0,0]);
|
2013-07-16 15:42:02 +00:00
|
|
|
},
|
|
|
|
|
2013-10-31 08:32:33 +00:00
|
|
|
// TODO: preserve active_page idx after rescaling
|
2013-07-16 15:42:02 +00:00
|
|
|
get_active_page : function () {
|
2013-11-01 08:11:44 +00:00
|
|
|
var pl = this.pages;
|
2013-10-28 08:05:24 +00:00
|
|
|
for(var i = 0, l = pl.length; i < l; ++i) {
|
2013-10-31 08:32:33 +00:00
|
|
|
if (pl[i].is_visible())
|
|
|
|
return pl[i];
|
2013-10-06 11:53:37 +00:00
|
|
|
}
|
2013-10-31 08:32:33 +00:00
|
|
|
return;
|
2013-07-16 15:42:02 +00:00
|
|
|
},
|
|
|
|
|
2012-09-25 11:29:59 +00:00
|
|
|
get_containing_page : function(obj) {
|
|
|
|
/* get the page obj containing obj */
|
2013-10-28 08:05:24 +00:00
|
|
|
var $p = obj.closest('.'+CSS_CLASS_NAMES.page_frame);
|
|
|
|
if($p.length == 0) return;
|
|
|
|
/*
|
|
|
|
* Get original page number and map it to index of pages
|
|
|
|
* TODO: store the index on the dom element
|
|
|
|
*/
|
|
|
|
var pn = (new Page($p[0], null)).n;
|
|
|
|
var pm = this.page_map;
|
|
|
|
return (pn in pm) && this.pages[pm[pn]];
|
2012-09-25 11:29:59 +00:00
|
|
|
},
|
|
|
|
|
2013-01-28 13:01:02 +00:00
|
|
|
link_handler : function (e) {
|
2012-09-25 11:29:59 +00:00
|
|
|
var _ = e.data;
|
|
|
|
var t = $(e.currentTarget);
|
2012-09-25 13:56:48 +00:00
|
|
|
|
2013-01-28 15:58:00 +00:00
|
|
|
var cur_pos = [0,0];
|
|
|
|
|
|
|
|
// cur_page might be undefined, e.g. from Outline
|
|
|
|
var cur_page = _.get_containing_page(t);
|
2013-10-31 08:32:33 +00:00
|
|
|
if(cur_page)
|
2013-01-28 15:58:00 +00:00
|
|
|
{
|
|
|
|
cur_pos = cur_page.position();
|
|
|
|
//get the coordinates in default user system
|
2013-07-16 18:19:25 +00:00
|
|
|
cur_pos = transform(cur_page.ictm, [cur_pos[0], cur_page.h-cur_pos[1]]);
|
2013-01-28 15:58:00 +00:00
|
|
|
}
|
2012-09-25 11:29:59 +00:00
|
|
|
|
2013-10-22 13:32:02 +00:00
|
|
|
var detail_str = /** @type{string} */ (t.attr('data-dest-detail'));
|
2013-10-22 11:42:56 +00:00
|
|
|
if(detail_str === undefined) return;
|
2012-09-25 11:29:59 +00:00
|
|
|
|
|
|
|
var ok = false;
|
2012-09-26 08:00:55 +00:00
|
|
|
var detail = JSON.parse(detail_str);
|
2012-09-25 11:29:59 +00:00
|
|
|
|
2013-10-28 08:05:24 +00:00
|
|
|
var target_page_no = detail[0];
|
|
|
|
var page_map = this.page_map;
|
|
|
|
if(!(target_page_no in page_map)) return;
|
|
|
|
var target_page_idx = page_map[target_page_no];
|
|
|
|
var target_page = _.pages[target_page_idx];
|
2012-09-25 13:56:48 +00:00
|
|
|
|
2012-09-26 08:00:55 +00:00
|
|
|
var pos = [0,0];
|
|
|
|
var upside_down = true;
|
|
|
|
// TODO: zoom
|
|
|
|
// TODO: BBox
|
|
|
|
switch(detail[1]) {
|
|
|
|
case 'XYZ':
|
|
|
|
pos = [(detail[2] == null) ? cur_pos[0] : detail[2]
|
2013-10-06 11:53:37 +00:00
|
|
|
,(detail[3] == null) ? cur_pos[1] : detail[3]];
|
2012-09-26 08:00:55 +00:00
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
case 'Fit':
|
|
|
|
case 'FitB':
|
|
|
|
pos = [0,0];
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
case 'FitH':
|
|
|
|
case 'FitBH':
|
2013-07-16 18:19:25 +00:00
|
|
|
pos = [0, (detail[2] == null) ? cur_pos[1] : detail[2]];
|
2012-09-26 08:00:55 +00:00
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
case 'FitV':
|
|
|
|
case 'FitBV':
|
|
|
|
pos = [(detail[2] == null) ? cur_pos[0] : detail[2], 0];
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
case 'FitR':
|
|
|
|
/* locate the top-left corner of the rectangle */
|
|
|
|
pos = [detail[2], detail[5]];
|
|
|
|
upside_down = false;
|
|
|
|
ok = true;
|
|
|
|
break;
|
2012-09-25 11:29:59 +00:00
|
|
|
default:
|
2012-09-26 08:00:55 +00:00
|
|
|
ok = false;
|
2012-09-25 11:29:59 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-09-26 08:00:55 +00:00
|
|
|
if(ok) {
|
2013-10-28 08:05:24 +00:00
|
|
|
/* page should of type Page */
|
|
|
|
var transform_and_scroll = function(page) {
|
|
|
|
pos = transform(page.ctm, pos);
|
2013-06-13 10:39:52 +00:00
|
|
|
if(upside_down) {
|
2013-10-28 08:05:24 +00:00
|
|
|
pos[1] = page.h - pos[1];
|
2013-06-12 15:53:14 +00:00
|
|
|
}
|
2013-10-28 08:05:24 +00:00
|
|
|
_.scroll_to(target_page_idx, pos);
|
2013-07-16 18:19:25 +00:00
|
|
|
};
|
2013-06-13 10:39:52 +00:00
|
|
|
|
|
|
|
if (target_page.loaded) {
|
2013-10-28 08:05:24 +00:00
|
|
|
transform_and_scroll(target_page);
|
2013-06-12 15:53:14 +00:00
|
|
|
} else {
|
|
|
|
// Scroll to the exact position once loaded.
|
2013-10-28 08:29:58 +00:00
|
|
|
_.load_page(target_page_idx, undefined, transform_and_scroll);
|
2013-06-12 15:53:14 +00:00
|
|
|
|
|
|
|
// In the meantime page gets loaded, scroll approximately position for maximum responsiveness.
|
2013-10-28 08:05:24 +00:00
|
|
|
_.scroll_to(target_page_idx, [0,0]);
|
2012-09-26 08:00:55 +00:00
|
|
|
}
|
2012-09-25 11:29:59 +00:00
|
|
|
e.preventDefault();
|
2012-09-26 08:00:55 +00:00
|
|
|
}
|
2012-09-25 14:24:36 +00:00
|
|
|
},
|
2013-07-03 07:57:00 +00:00
|
|
|
|
2012-09-26 08:00:55 +00:00
|
|
|
/* pos=[x,y], where (0,0) is the top-left corner */
|
2013-10-28 08:05:24 +00:00
|
|
|
scroll_to : function(page_idx, pos) {
|
|
|
|
var target_page = this.pages[page_idx];
|
2013-10-22 11:42:56 +00:00
|
|
|
if(target_page === undefined) return;
|
2012-09-25 14:24:36 +00:00
|
|
|
|
2013-10-22 11:42:56 +00:00
|
|
|
if(pos === undefined)
|
2013-10-06 11:53:37 +00:00
|
|
|
pos = [0,0];
|
|
|
|
|
2012-09-25 14:24:36 +00:00
|
|
|
var cur_target_pos = target_page.position();
|
|
|
|
|
2013-06-13 15:00:42 +00:00
|
|
|
this.$container.scrollLeft(this.$container.scrollLeft()-cur_target_pos[0]+pos[0]);
|
|
|
|
this.$container.scrollTop(this.$container.scrollTop()-cur_target_pos[1]+pos[1]);
|
2012-09-25 14:24:36 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
__last_member__ : 'no comma' /*,*/
|
2012-09-25 14:40:21 +00:00
|
|
|
});
|
2013-10-22 13:32:02 +00:00
|
|
|
|
|
|
|
pdf2htmlEX['Viewer'] = Viewer;
|
2013-10-22 07:03:07 +00:00
|
|
|
})(window['pdf2htmlEX'] = window['pdf2htmlEX'] || {});
|