From 5223541c1f530706b7ceb8aaa025b571d6a51d4c Mon Sep 17 00:00:00 2001 From: Simon Chenard Date: Mon, 11 Aug 2014 16:36:35 +0200 Subject: [PATCH 1/6] working but ugly code --- CMakeLists.txt | 1 + src/HTMLRenderer/HTMLRenderer.h | 6 ++ src/HTMLRenderer/form.cc | 103 ++++++++++++++++++++++++++++++++ src/HTMLRenderer/general.cc | 4 ++ src/Param.h | 3 + src/pdf2htmlEX.cc | 3 + 6 files changed, 120 insertions(+) create mode 100644 src/HTMLRenderer/form.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0aad4..017b43f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ set(PDF2HTMLEX_SRC ${PDF2HTMLEX_SRC} src/HTMLRenderer/general.cc src/HTMLRenderer/image.cc src/HTMLRenderer/font.cc + src/HTMLRenderer/form.cc src/HTMLRenderer/link.cc src/HTMLRenderer/outline.cc src/HTMLRenderer/state.cc diff --git a/src/HTMLRenderer/HTMLRenderer.h b/src/HTMLRenderer/HTMLRenderer.h index 80ef25d..6335d6e 100644 --- a/src/HTMLRenderer/HTMLRenderer.h +++ b/src/HTMLRenderer/HTMLRenderer.h @@ -20,6 +20,10 @@ #include #include +// for form.cc +#include +#include + #include "pdf2htmlEX-config.h" #include "Param.h" @@ -165,6 +169,8 @@ protected: void process_outline(void); void process_outline_items(GooList * items); + void process_form(std::ostream & out); + void set_stream_flags (std::ostream & out); void dump_css(void); diff --git a/src/HTMLRenderer/form.cc b/src/HTMLRenderer/form.cc new file mode 100644 index 0000000..d2870a9 --- /dev/null +++ b/src/HTMLRenderer/form.cc @@ -0,0 +1,103 @@ +/* + * form.cc + * + * Handling Forms + * + * by Simon Chenard + * 2014.07.25 + */ + +#include +#include +#include + +#include "HTMLRenderer.h" +#include "util/namespace.h" +#include "util/misc.h" + +namespace pdf2htmlEX { + +using std::ostream; +using std::cerr; + +void HTMLRenderer::process_form(ostream & out) +{ + FormPageWidgets * widgets = cur_catalog->getPage(pageNum)->getFormWidgets(); + int num = widgets->getNumWidgets(); + std::ostringstream derp; + + for(int i = 0; i < num; i++) + { + FormWidget * w = widgets->getWidget(i); + double x1, y1, x2, y2; + int width, height, font_size; + + w->getRect(&x1, &y1, &x2, &y2); + x1 = x1 * param.zoom; + x2 = x2 * param.zoom; + y1 = y1 * param.zoom; + y2 = y2 * param.zoom; + + width = x2 - x1; + height = y2 - y1; + + if(w->getType() == formText) + { + font_size = height / 2; + + out << "" << endl; + } + + if(w->getType() == formButton) + { + out << "
X
" << endl; + + } + } + + //output, at the end, the necessary css + if(num > 0) { + //this is usable by the whole document and as such should be in dump_css + out << "" << endl; + + //this is currently page specific + out << "" << endl; + + } +} + +} diff --git a/src/HTMLRenderer/general.cc b/src/HTMLRenderer/general.cc index a7d4e4e..b614a38 100644 --- a/src/HTMLRenderer/general.cc +++ b/src/HTMLRenderer/general.cc @@ -252,6 +252,10 @@ void HTMLRenderer::endPage() { html_text_page.dump_css(f_css.fs); html_text_page.clear(); + // process form + if(param.include_forms) + process_form(*f_curpage); + // process links before the page is closed cur_doc->processLinks(this, pageNum); diff --git a/src/Param.h b/src/Param.h index b2330ed..9491d42 100644 --- a/src/Param.h +++ b/src/Param.h @@ -62,6 +62,9 @@ struct Param int tounicode; int optimize_text; + // adobe form + int include_forms; + // background image std::string bg_format; int svg_node_count_limit; diff --git a/src/pdf2htmlEX.cc b/src/pdf2htmlEX.cc index 3d4972a..845b5bf 100644 --- a/src/pdf2htmlEX.cc +++ b/src/pdf2htmlEX.cc @@ -189,6 +189,9 @@ void parse_options (int argc, char **argv) .add("optimize-text", ¶m.optimize_text, 0, "try to reduce the number of HTML elements used for text") .add("correct-text-visibility", ¶m.correct_text_visibility, 0, "try to detect texts covered by other graphics and properly arrange them") + // adobe forms + .add("include-forms", ¶m.include_forms, 0, "include text fields and such") + // background image .add("bg-format", ¶m.bg_format, "png", "specify background image format") .add("svg-node-count-limit", ¶m.svg_node_count_limit, -1, "if node count in a svg background image exceeds this limit," From f8404691edb7fb82e3383bb6cc7d6f8779a5a2d7 Mon Sep 17 00:00:00 2001 From: Simon Chenard Date: Thu, 14 Aug 2014 10:35:27 +0200 Subject: [PATCH 2/6] cleanup a bit, output css only once --- src/HTMLRenderer/HTMLRenderer.h | 3 ++- src/HTMLRenderer/form.cc | 28 +++++++++++++--------------- src/HTMLRenderer/general.cc | 6 ++++++ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/HTMLRenderer/HTMLRenderer.h b/src/HTMLRenderer/HTMLRenderer.h index 6335d6e..cc40089 100644 --- a/src/HTMLRenderer/HTMLRenderer.h +++ b/src/HTMLRenderer/HTMLRenderer.h @@ -170,7 +170,8 @@ protected: void process_outline_items(GooList * items); void process_form(std::ostream & out); - + void dump_form_css(std::ostream & out); + void set_stream_flags (std::ostream & out); void dump_css(void); diff --git a/src/HTMLRenderer/form.cc b/src/HTMLRenderer/form.cc index d2870a9..7e55ba8 100644 --- a/src/HTMLRenderer/form.cc +++ b/src/HTMLRenderer/form.cc @@ -24,7 +24,6 @@ void HTMLRenderer::process_form(ostream & out) { FormPageWidgets * widgets = cur_catalog->getPage(pageNum)->getFormWidgets(); int num = widgets->getNumWidgets(); - std::ostringstream derp; for(int i = 0; i < num; i++) { @@ -61,25 +60,14 @@ void HTMLRenderer::process_form(ostream & out) << " style=\"opacity:0.0; position: absolute; left: " << std::to_string(x1) << "px; bottom: " << std::to_string(y1) << "px;" << "width: " << std::to_string(width) << "px; height: " << std::to_string(height) << - "px; font-size: 20px; \" class=\"checkbox-" << + "px; font-size: 20px; \" class=\"checkbox checkbox-" << std::to_string(pageNum) << "\">X" << endl; } } - //output, at the end, the necessary css + //output, at the end, the necessary js if(num > 0) { - //this is usable by the whole document and as such should be in dump_css - out << "" << endl; - //this is currently page specific out << "" << endl; - } } +void HTMLRenderer::dump_form_css(ostream & out) +{ + out << ".text_input {" << + "border: none; " << + "background-color: rgba(255, 255, 255, 0.0);" << + "}" << endl << + ".checkbox:hover {" << + "cursor: pointer;" << + "}" << endl; +} + } diff --git a/src/HTMLRenderer/general.cc b/src/HTMLRenderer/general.cc index b614a38..6aac48c 100644 --- a/src/HTMLRenderer/general.cc +++ b/src/HTMLRenderer/general.cc @@ -395,6 +395,12 @@ void HTMLRenderer::pre_process(PDFDoc * doc) void HTMLRenderer::post_process(void) { dump_css(); + + if (param.include_forms) + { + dump_form_css(f_css.fs); + } + // close files if they opened // it's better to brace single liner LLVM complains if (param.process_outline) From ea226cf3021a2068bec7af5e729f4c84a86e036b Mon Sep 17 00:00:00 2001 From: Simon Chenard Date: Thu, 2 Oct 2014 16:08:42 -0400 Subject: [PATCH 3/6] Corrections as per Wang Lu's suggestions --- pdf2htmlEX.1.in | 4 + share/base.css.in | 9 + share/pdf2htmlEX.js.in | 17 + share/pdf2htmlEX.min.js | 934 +++++++++++++++++++++++++++++ src/HTMLRenderer/HTMLRenderer.h | 3 +- src/HTMLRenderer/form.cc | 36 +- src/HTMLRenderer/general.cc | 7 +- src/Param.h | 4 +- src/css_class_names.cmakelists.txt | 2 + src/pdf2htmlEX.cc | 4 +- src/util/css_const.h.in | 3 + 11 files changed, 975 insertions(+), 48 deletions(-) create mode 100644 share/pdf2htmlEX.min.js diff --git a/pdf2htmlEX.1.in b/pdf2htmlEX.1.in index 1f44984..d6e5faa 100644 --- a/pdf2htmlEX.1.in +++ b/pdf2htmlEX.1.in @@ -138,6 +138,10 @@ Whether to show outline in the generated HTML .B \-\-process-annotation <0|1> (Default: 0) Whether to show annotation in the generated HTML +.TP +.B \-\-process-forms <0|1> (Default: 0) +Whether to include text fields and radio buttons in the generated HTML + .TP .B \-\-printing <0|1> (Default: 1) Enable printing support. Disabling this option may reduce the size of CSS. diff --git a/share/base.css.in b/share/base.css.in index 7f61507..14e6282 100644 --- a/share/base.css.in +++ b/share/base.css.in @@ -190,4 +190,13 @@ -ms-transform-origin:0% 100%; -webkit-transform-origin:0% 100%; } +/* for the forms */ +.@CSS_INPUT_TEXT_CN@ { + border: none; + background-color: rgba(255, 255, 255, 0.0); +} + +.@CSS_INPUT_RADIO_CN@:hover { + cursor: pointer; +} /* Base CSS END */ diff --git a/share/pdf2htmlEX.js.in b/share/pdf2htmlEX.js.in index 636c94a..7b228bd 100644 --- a/share/pdf2htmlEX.js.in +++ b/share/pdf2htmlEX.js.in @@ -260,7 +260,24 @@ Viewer.prototype = { this.pre_hide_pages(); }, + initialize_radio_button : function() { + var elements = document.getElementsByClassName('ir'); + + for(var i = 0; i < elements.length; i++) { + var r = elements[i]; + + r.addEventListener('click', function() { + if(this.style.opacity == 1) + this.style.opacity = 0; + else + this.style.opacity = 1; + }); + } + }, + init_after_loading_content : function() { + this.initialize_radio_button(); + this.sidebar = document.getElementById(this.config['sidebar_id']); this.outline = document.getElementById(this.config['outline_id']); this.container = document.getElementById(this.config['container_id']); diff --git a/share/pdf2htmlEX.min.js b/share/pdf2htmlEX.min.js new file mode 100644 index 0000000..571e36c --- /dev/null +++ b/share/pdf2htmlEX.min.js @@ -0,0 +1,934 @@ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab filetype=javascript : */ +/** + * @license pdf2htmlEX.js: Core UI functions for pdf2htmlEX + * Copyright 2012,2013 Lu Wang and other contributors + * https://github.com/coolwanglu/pdf2htmlEX/blob/master/share/LICENSE + */ + +/* + * 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. + * + */ + +'use strict'; + +var pdf2htmlEX = window['pdf2htmlEX'] = window['pdf2htmlEX'] || {}; + +/** + * @const + * @struct + */ +var CSS_CLASS_NAMES = { + page_frame : 'pf', + page_content_box : 'pc', + page_data : 'pi', + background_image : 'bi', + link : 'l', + __dummy__ : 'no comma' +}; + +/** + * configurations of Viewer + * @const + * @dict + */ +var DEFAULT_CONFIG = { + // id of the element to put the pages in + 'container_id' : 'page-container', + // id of the element for sidebar (to open and close) + 'sidebar_id' : 'sidebar', + // id of the element for outline + 'outline_id' : 'outline', + // class for the loading indicator + 'loading_indicator_cls' : 'loading-indicator', + // How many page shall we preload that are below the last visible page + 'preload_pages' : 3, + // how many ms should we wait before actually rendering the pages and after a scroll event + 'render_timeout' : 100, + // zoom ratio step for each zoom in/out event + 'scale_step' : 0.9, + // register global key handler + 'key_handler' : true, + // register hashchange handler + 'hashchange_handler' : true, + + '__dummy__' : 'no comma' +}; + +/** @const */ +var EPS = 1e-6; + +/************************************/ +/* utility function */ +/** + * @param{Array.} ctm + */ +function invert(ctm) { + var det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; + 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 + ]; +}; +/** + * @param{Array.} ctm + * @param{Array.} pos + */ +function transform(ctm, pos) { + return [ctm[0] * pos[0] + ctm[2] * pos[1] + ctm[4] + ,ctm[1] * pos[0] + ctm[3] * pos[1] + ctm[5]]; +}; + +/** + * @param{Element} ele + */ +function get_page_number(ele) { + return parseInt(ele.getAttribute('data-page-no'), 16); +}; + +/** + * @param{NodeList} eles + */ +function disable_dragstart(eles) { + for (var i = 0, l = eles.length; i < l; ++i) { + eles[i].addEventListener('dragstart', function() { + return false; + }, false); + } +}; + +/** + * @param{...Object} var_args + */ +function clone_and_extend_objs(var_args) { + var result_obj = {}; + for (var i = 0, l = arguments.length; i < l; ++i) { + var cur_obj = arguments[i]; + for (var k in cur_obj) { + if (cur_obj.hasOwnProperty(k)) { + result_obj[k] = cur_obj[k]; + } + } + } + return result_obj; +}; + +/** + * @constructor + * @param{Element} page The element for the page + */ +function Page(page) { + if (!page) return; + + this.loaded = false; + this.shown = false; + this.page = page; // page frame element + + this.num = get_page_number(page); + + // page size + // Need to make rescale work when page_content_box is not loaded, yet + this.original_height = page.clientHeight; + this.original_width = page.clientWidth; + + // content box + var content_box = page.getElementsByClassName(CSS_CLASS_NAMES.page_content_box)[0]; + + // if page is loaded + if (content_box) { + this.content_box = content_box; + /* + * scale ratios + * + * original_scale : the first one + * cur_scale : currently using + */ + this.original_scale = this.cur_scale = this.original_height / content_box.clientHeight; + this.page_data = JSON.parse(page.getElementsByClassName(CSS_CLASS_NAMES.page_data)[0].getAttribute('data-data')); + + this.ctm = this.page_data['ctm']; + this.ictm = invert(this.ctm); + + this.loaded = true; + } +}; +Page.prototype = { + /* hide & show are for contents, the page frame is still there */ + hide : function(){ + if (this.loaded && this.shown) { + this.content_box.classList.remove('opened'); + this.shown = false; + } + }, + show : function(){ + if (this.loaded && !this.shown) { + this.content_box.classList.add('opened'); + this.shown = true; + } + }, + /** + * @param{number} ratio + */ + rescale : function(ratio) { + if (ratio === 0) { + // reset scale + this.cur_scale = this.original_scale; + } else { + this.cur_scale = ratio; + } + + // scale the content box + if (this.loaded) { + var cbs = this.content_box.style; + cbs.msTransform = cbs.webkitTransform = cbs.transform = 'scale('+this.cur_scale.toFixed(3)+')'; + } + + // stretch the page frame to hold the place + { + var ps = this.page.style; + ps.height = (this.original_height * this.cur_scale) + 'px'; + ps.width = (this.original_width * this.cur_scale) + 'px'; + } + }, + /* + * return the coordinate of the top-left corner of container + * in our coordinate system + * assuming that p.parentNode === p.offsetParent + */ + view_position : function () { + var p = this.page; + var c = p.parentNode; + return [c.scrollLeft - p.offsetLeft - p.clientLeft + ,c.scrollTop - p.offsetTop - p.clientTop]; + }, + height : function () { + return this.page.clientHeight; + }, + width : function () { + return this.page.clientWidth; + } +}; + +/** + * @constructor + * @param{Object=} config + */ +function Viewer(config) { + this.config = clone_and_extend_objs(DEFAULT_CONFIG, (arguments.length > 0 ? config : {})); + this.pages_loading = []; + this.init_before_loading_content(); + + var self = this; + document.addEventListener('DOMContentLoaded', function(){ + self.init_after_loading_content(); + }, false); +}; + +Viewer.prototype = { + scale : 1, + /* + * index of the active page (the one with largest visible area) + * which estimates the page currently being viewed + */ + cur_page_idx : 0, + + /* + * index of the first visible page + * used when determining current view + */ + first_page_idx : 0, + + init_before_loading_content : function() { + /* hide all pages before loading, will reveal only visible ones later */ + this.pre_hide_pages(); + }, + + initialize_radio_button : function() { + var elements = document.getElementsByClassName('ir'); + + for(var i = 0; i < elements.length; i++) { + var r = elements[i]; + + r.addEventListener('click', function() { + if(this.style.opacity == 1) + this.style.opacity = 0; + else + this.style.opacity = 1; + }); + } + }, + + init_after_loading_content : function() { + this.initialize_radio_button(); + + this.sidebar = document.getElementById(this.config['sidebar_id']); + this.outline = document.getElementById(this.config['outline_id']); + this.container = document.getElementById(this.config['container_id']); + this.loading_indicator = document.getElementsByClassName(this.config['loading_indicator_cls'])[0]; + + + { + // Open the outline if nonempty + var empty = true; + var nodes = this.outline.childNodes; + for (var i = 0, l = nodes.length; i < l; ++i) { + var cur_node = nodes[i]; + if (cur_node.nodeName.toLowerCase() === 'ul') { + empty = false; + break; + } + } + if (!empty) + this.sidebar.classList.add('opened'); + } + + this.find_pages(); + // do nothing if there's nothing + if(this.pages.length == 0) return; + + // disable dragging of background images + disable_dragstart(document.getElementsByClassName(CSS_CLASS_NAMES.background_image)); + + if (this.config['key_handler']) + this.register_key_handler(); + + var self = this; + + if (this.config['hashchange_handler']) { + window.addEventListener('hashchange', function(e) { + self.navigate_to_dest(document.location.hash.substring(1)); + }, false); + } + + // register schedule rendering + // renew old schedules since scroll() may be called frequently + this.container.addEventListener('scroll', function() { + self.update_page_idx(); + self.schedule_render(true); + }, false); + + // handle links + [this.container, this.outline].forEach(function(ele) { + ele.addEventListener('click', self.link_handler.bind(self), false); + }); + + this.render(); + }, + + /* + * 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 + */ + find_pages : function() { + var new_pages = []; + var new_page_map = {}; + var nodes = this.container.childNodes; + for (var i = 0, l = nodes.length; i < l; ++i) { + var cur_node = nodes[i]; + if ((cur_node.nodeType === Node.ELEMENT_NODE) + && cur_node.classList.contains(CSS_CLASS_NAMES.page_frame)) { + var p = new Page(cur_node); + new_pages.push(p); + new_page_map[p.num] = new_pages.length - 1; + } + } + this.pages = new_pages; + this.page_map = new_page_map; + }, + + /** + * @param{number} idx + * @param{number=} pages_to_preload + * @param{function(Page)=} callback + * + * TODO: remove callback -> promise ? + */ + load_page : function(idx, pages_to_preload, callback) { + var pages = this.pages; + if (idx >= pages.length) + return; // Page does not exist + + var cur_page = pages[idx]; + if (cur_page.loaded) + return; // Page is loaded + + if (this.pages_loading[idx]) + return; // Page is already loading + + var cur_page_ele = cur_page.page; + var url = cur_page_ele.getAttribute('data-page-url'); + if (url) { + this.pages_loading[idx] = true; // set semaphore + + // add a copy of the loading indicator + var new_loading_indicator = this.loading_indicator.cloneNode(); + new_loading_indicator.classList.add('active'); + cur_page_ele.appendChild(new_loading_indicator); + + // load data + { + var self = this; + var _idx = idx; + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.onload = function(){ + if (xhr.status === 200 || xhr.status === 0) { + // find the page element in the data + var div = document.createElement('div'); + div.innerHTML = xhr.responseText; + + var new_page = null; + var nodes = div.childNodes; + for (var i = 0, l = nodes.length; i < l; ++i) { + var cur_node = nodes[i]; + if ((cur_node.nodeType === Node.ELEMENT_NODE) + && cur_node.classList.contains(CSS_CLASS_NAMES.page_frame)) { + new_page = cur_node; + break; + } + } + + // replace the old page with loaded data + // the loading indicator on this page should also be destroyed + var p = self.pages[_idx]; + self.container.replaceChild(new_page, p.page); + p = new Page(new_page); + self.pages[_idx] = p; + + p.hide(); + p.rescale(self.scale); + + // disable background image dragging + disable_dragstart(new_page.getElementsByClassName(CSS_CLASS_NAMES.background_image)); + + self.schedule_render(false); + + if (callback){ callback(p); } + } + + // Reset loading token + delete self.pages_loading[_idx]; + }; + xhr.send(null); + } + } + // Concurrent prefetch of the next pages + if (pages_to_preload === undefined) + pages_to_preload = this.config['preload_pages']; + + if (--pages_to_preload > 0) { + var self = this; + setTimeout(function() { + self.load_page(idx+1, pages_to_preload); + },0); + } + }, + + /* + * Hide all pages that have no 'opened' class + * The 'opened' class will be added to visible pages by JavaScript + * We cannot add this in the default CSS because JavaScript may be disabled + */ + pre_hide_pages : function() { + /* pages might have not been loaded yet, so add a CSS rule */ + var s = '@media screen{.'+CSS_CLASS_NAMES.page_content_box+'{display:none;}}'; + var n = document.createElement('style'); + if (n.styleSheet) { + n.styleSheet.cssText = s; + } else { + n.appendChild(document.createTextNode(s)); + } + document.head.appendChild(n); + }, + + /* + * show visible pages and hide invisible pages + */ + render : function () { + var container = this.container; + /* + * show the pages that are 'nearly' visible -- it's right above or below the container + * + * all the y values are in the all-page element's coordinate system + */ + var container_min_y = container.scrollTop; + var container_height = container.clientHeight; + var container_max_y = container_min_y + container_height; + var visible_min_y = container_min_y - container_height; + var visible_max_y = container_max_y + container_height; + + var cur_page_fully_visible = false; + var cur_page_idx = this.cur_page_idx; + var max_visible_page_idx = cur_page_idx; + var max_visible_ratio = 0.0; + + var pl = this.pages; + for (var i = 0, l = pl.length; i < l; ++i) { + var cur_page = pl[i]; + var cur_page_ele = cur_page.page; + var page_min_y = cur_page_ele.offsetTop + cur_page_ele.clientTop; + var page_height = cur_page_ele.clientHeight; + var page_max_y = page_min_y + page_height; + if ((page_min_y <= visible_max_y) && (page_max_y >= visible_min_y)) + { + // cur_page is 'nearly' visible, show it or load it + if (cur_page.loaded) { + cur_page.show(); + } else { + this.load_page(i); + } + } else { + cur_page.hide(); + } + } + }, + /* + * update cur_page_idx and first_page_idx + * normally called upon scrolling + */ + update_page_idx: function () { + var pages = this.pages; + var pages_len = pages.length; + // there is no chance that cur_page_idx or first_page_idx is modified + if (pages_len < 2) return; + + var container = this.container; + var container_min_y = container.scrollTop; + var container_max_y = container_min_y + container.clientHeight; + + // binary search for the first page + // whose bottom border is below the top border of the container + var first_idx = -1; + var last_idx = pages_len; + var rest_len = last_idx - first_idx; + // TODO: use current first_page_idx as a hint? + while(rest_len > 1) { + var idx = first_idx + Math.floor(rest_len / 2); + var cur_page_ele = pages[idx].page; + if (cur_page_ele.offsetTop + cur_page_ele.clientTop + cur_page_ele.clientHeight >= container_min_y) { + last_idx = idx; + } else { + first_idx = idx; + } + rest_len = last_idx - first_idx; + } + + /* + * with malformed settings it is possible that no page is visible, e.g. + * - the container is to thin, which lies in the margin between two pages + * - all pages are completely above or below the container + * but we just assume that they won't happen. + */ + this.first_page_idx = last_idx; + + // find the page with largest visible area + var cur_page_idx = this.cur_page_idx; + var max_visible_page_idx = cur_page_idx; + var max_visible_ratio = 0.0; + + for(var i = last_idx; i < pages_len; ++i) { + var cur_page_ele = pages[i].page; + var page_min_y = cur_page_ele.offsetTop + cur_page_ele.clientTop; + var page_height = cur_page_ele.clientHeight; + var page_max_y = page_min_y + page_height; + if (page_min_y > container_max_y) break; + + // check the visible fraction of the page + var page_visible_ratio = ( Math.min(container_max_y, page_max_y) + - Math.max(container_min_y, page_min_y) + ) / page_height; + + // stay with the current page if it is still fully visible + if ((i === cur_page_idx) && (Math.abs(page_visible_ratio - 1.0) <= EPS)) { + max_visible_page_idx = cur_page_idx; + break; + } + + if (page_visible_ratio > max_visible_ratio) { + max_visible_ratio = page_visible_ratio; + max_visible_page_idx = i; + } + } + + this.cur_page_idx = max_visible_page_idx; + }, + + /** + * @param{boolean} renew renew the existing schedule instead of using the old one + */ + schedule_render : function(renew) { + if (this.render_timer !== undefined) { + if (!renew) return; + clearTimeout(this.render_timer); + } + + var self = this; + this.render_timer = setTimeout(function () { + /* + * render() may trigger load_page(), which may in turn trigger another render() + * so delete render_timer first + */ + delete self.render_timer; + self.render(); + }, this.config['render_timeout']); + }, + + /* + * Handling key events, zooming, scrolling etc. + */ + register_key_handler: function () { + /* + * 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 self = this; + + // Firefox specific event, so that we can prevent browser from zooming + window.addEventListener('DOMMouseScroll', function(e) { + if (e.ctrlKey) { + e.preventDefault(); + var container = self.container; + var rect = container.getBoundingClientRect(); + var fixed_point = [e.clientX - rect['left'] - container.clientLeft + ,e.clientY - rect['top'] - container.clientTop]; + self.rescale(Math.pow(self.config['scale_step'], e.detail), true, fixed_point); + } + }, false); + + window.addEventListener('keydown', function(e) { + var handled = false; + /* + var cmd = (e.ctrlKey ? 1 : 0) + | (e.altKey ? 2 : 0) + | (e.shiftKey ? 4 : 0) + | (e.metaKey ? 8 : 0) + ; + */ + var with_ctrl = e.ctrlKey || e.metaKey; + var with_alt = e.altKey; + switch (e.keyCode) { + case 61: // FF/Mac '=' + case 107: // FF '+' and '=' + case 187: // Chrome '+' + if (with_ctrl){ + self.rescale(1.0 / self.config['scale_step'], true); + handled = true; + } + break; + case 173: // FF/Mac '-' + case 109: // FF '-' + case 189: // Chrome '-' + if (with_ctrl){ + self.rescale(self.config['scale_step'], true); + handled = true; + } + break; + case 48: // '0' + if (with_ctrl){ + self.rescale(0, false); + handled = true; + } + break; + case 33: // Page UP: + if (with_alt) { // alt-pageup -> scroll one page up + self.scroll_to(self.cur_page_idx - 1); + } else { // pageup -> scroll one screen up + self.container.scrollTop -= self.container.clientHeight; + } + handled = true; + break; + case 34: // Page DOWN + if (with_alt) { // alt-pagedown -> scroll one page down + self.scroll_to(self.cur_page_idx + 1); + } else { // pagedown -> scroll one screen down + self.container.scrollTop += self.container.clientHeight; + } + handled = true; + break; + case 35: // End + self.container.scrollTop = self.container.scrollHeight; + handled = true; + break; + case 36: // Home + self.container.scrollTop = 0; + handled = true; + break; + } + if (handled) { + e.preventDefault(); + return; + } + }, false); + }, + + /** + * @param{number} ratio + * @param{boolean} is_relative + * @param{Array.=} fixed_point preserve the position (relative to the top-left corner of the viewer) after rescaling + */ + rescale : function (ratio, is_relative, fixed_point) { + var old_scale = this.scale; + var new_scale = old_scale; + // set new scale + if (ratio === 0) { + new_scale = 1; + is_relative = false; + } else if (is_relative) + new_scale *= ratio; + else + new_scale = ratio; + + this.scale = new_scale; + + if (!fixed_point) + fixed_point = [0,0]; + + // translate fixed_point to the coordinate system of all pages + var container = this.container; + fixed_point[0] += container.scrollLeft; + fixed_point[1] += container.scrollTop; + + // find the visible page that contains the fixed point + // if the fixed point lies between two pages (including their borders), it's contained in the first one + var pl = this.pages; + var pl_len = pl.length; + for (var i = this.first_page_idx; i < pl_len; ++i) { + var p = pl[i].page; + if (p.offsetTop + p.clientTop >= fixed_point[1]) + break; + } + var fixed_point_page_idx = i - 1; + + // determine the new scroll position + // each-value consists of two parts, one inside the page, which is affected by rescaling, + // the other is outside, (e.g. borders and margins), which is not affected + + // if the fixed_point is above the first page, use the first page as the reference + if (fixed_point_page_idx < 0) + fixed_point_page_idx = 0; + + var fp_p = pl[fixed_point_page_idx].page; + var fp_p_width = fp_p.clientWidth; + var fp_p_height = fp_p.clientHeight; + + var fp_x_ref = fp_p.offsetLeft + fp_p.clientLeft; + var fp_x_inside = fixed_point[0] - fp_x_ref; + if (fp_x_inside < 0) + fp_x_inside = 0; + else if (fp_x_inside > fp_p_width) + fp_x_inside = fp_p_width; + + var fp_y_ref = fp_p.offsetTop + fp_p.clientTop; + var fp_y_inside = fixed_point[1] - fp_y_ref; + if (fp_y_inside < 0) + fp_y_inside = 0; + else if (fp_y_inside > fp_p_height) + fp_y_inside = fp_p_height; + + // Rescale pages + for (var i = 0; i < pl_len; ++i) + pl[i].rescale(new_scale); + + // Correct container scroll to keep view aligned while zooming + container.scrollLeft += fp_x_inside / old_scale * new_scale + fp_p.offsetLeft + fp_p.clientLeft - fp_x_inside - fp_x_ref; + container.scrollTop += fp_y_inside / old_scale * new_scale + fp_p.offsetTop + fp_p.clientTop - fp_y_inside - fp_y_ref; + + // some pages' visibility may be toggled, wait for next render() + // renew old schedules since rescale() may be called frequently + this.schedule_render(true); + }, + + fit_width : function () { + var page_idx = this.cur_page_idx; + this.rescale(this.container.clientWidth / this.pages[page_idx].width(), false); + this.scroll_to(page_idx); + }, + + fit_height : function () { + var page_idx = this.cur_page_idx; + this.rescale(this.container.clientHeight / this.pages[page_idx].height(), false); + this.scroll_to(page_idx); + }, + /** + * @param{Node} ele + */ + get_containing_page : function(ele) { + /* get the page obj containing obj */ + while(ele) { + if ((ele.nodeType === Node.ELEMENT_NODE) + && ele.classList.contains(CSS_CLASS_NAMES.page_frame)) { + /* + * Get original page number and map it to index of pages + * TODO: store the index on the dom element + */ + var pn = get_page_number(/** @type{Element} */(ele)); + var pm = this.page_map; + return (pn in pm) ? this.pages[pm[pn]] : null; + } + ele = ele.parentNode; + } + return null; + }, + + /** + * @param{Event} e + */ + link_handler : function (e) { + var target = /** @type{Node} */(e.target); + var detail_str = /** @type{string} */ (target.getAttribute('data-dest-detail')); + if (!detail_str) return; + + this.navigate_to_dest(detail_str, this.get_containing_page(target)); + e.preventDefault(); + }, + + /** + * @param{string} detail_str may come from user provided hashtag, need sanitzing + * @param{Page=} src_page page containing the source event (e.g. link) + */ + navigate_to_dest : function(detail_str, src_page) { + try { + var detail = JSON.parse(detail_str); + } catch(e) { + return; + } + + if(!(detail instanceof Array)) return; + + 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 = this.pages[target_page_idx]; + + for (var i = 2, l = detail.length; i < l; ++i) { + var d = detail[i]; + if(!((d === null) || (typeof d === 'number'))) + return; + } + + while(detail.length < 6) + detail.push(null); + + // cur_page might be undefined, e.g. from Outline + var cur_page = src_page || this.pages[this.cur_page_idx]; + + var cur_pos = cur_page.view_position(); + cur_pos = transform(cur_page.ictm, [cur_pos[0], cur_page.height()-cur_pos[1]]); + + var zoom = this.scale; + var pos = [0,0]; + var upside_down = true; + var ok = false; + + // position specified in `detail` are in the raw coordinate system of the page (unscaled) + var scale = this.scale; + // TODO: fitb* + // TODO: BBox + switch(detail[1]) { + case 'XYZ': + pos = [ (detail[2] === null) ? cur_pos[0] : detail[2] * scale + , (detail[3] === null) ? cur_pos[1] : detail[3] * scale ]; + zoom = detail[4]; + if ((zoom === null) || (zoom === 0)) + zoom = this.scale; + ok = true; + break; + case 'Fit': + case 'FitB': + pos = [0,0]; + ok = true; + break; + case 'FitH': + case 'FitBH': + pos = [0, (detail[2] === null) ? cur_pos[1] : detail[2] * scale]; + ok = true; + break; + case 'FitV': + case 'FitBV': + pos = [(detail[2] === null) ? cur_pos[0] : detail[2] * scale, 0]; + ok = true; + break; + case 'FitR': + /* locate the top-left corner of the rectangle */ + // TODO + pos = [detail[2] * scale, detail[5] * scale]; + upside_down = false; + ok = true; + break; + default: + break; + } + + if (!ok) return; + + this.rescale(zoom, false); + + + var self = this; + /** + * page should of type Page + * @param{Page} page + */ + var transform_and_scroll = function(page) { + pos = transform(page.ctm, pos); + if (upside_down) { + pos[1] = page.height() - pos[1]; + } + self.scroll_to(target_page_idx, pos); + }; + + if (target_page.loaded) { + transform_and_scroll(target_page); + } else { + // TODO: scroll_to may finish before load_page + + // Scroll to the exact position once loaded. + this.load_page(target_page_idx, undefined, transform_and_scroll); + + // In the meantime page gets loaded, scroll approximately position for maximum responsiveness. + this.scroll_to(target_page_idx); + } + }, + + /** + * @param{number} page_idx + * @param{Array.=} pos [x,y] where (0,0) is the top-left corner + */ + scroll_to : function(page_idx, pos) { + var pl = this.pages; + if ((page_idx < 0) || (page_idx >= pl.length)) return; + var target_page = pl[page_idx]; + var cur_target_pos = target_page.view_position(); + + if (pos === undefined) + pos = [0,0]; + + var container = this.container; + container.scrollLeft += pos[0] - cur_target_pos[0]; + container.scrollTop += pos[1] - cur_target_pos[1]; + } +}; + +// export pdf2htmlEX.Viewer +pdf2htmlEX['Viewer'] = Viewer; diff --git a/src/HTMLRenderer/HTMLRenderer.h b/src/HTMLRenderer/HTMLRenderer.h index cc40089..6335d6e 100644 --- a/src/HTMLRenderer/HTMLRenderer.h +++ b/src/HTMLRenderer/HTMLRenderer.h @@ -170,8 +170,7 @@ protected: void process_outline_items(GooList * items); void process_form(std::ostream & out); - void dump_form_css(std::ostream & out); - + void set_stream_flags (std::ostream & out); void dump_css(void); diff --git a/src/HTMLRenderer/form.cc b/src/HTMLRenderer/form.cc index 7e55ba8..02b5036 100644 --- a/src/HTMLRenderer/form.cc +++ b/src/HTMLRenderer/form.cc @@ -50,7 +50,7 @@ void HTMLRenderer::process_form(ostream & out) "px; bottom: " << std::to_string(y1) << "px;" << "width: " << std::to_string(width) << "px; height: " << std::to_string(height) << "px; line-height: " << std::to_string(height) << "px; font-size: " - << std::to_string(font_size) << "px;\" class=\"text_input\" />" << endl; + << std::to_string(font_size) << "px;\" class=\"" << CSS::INPUT_TEXT_CN << "\" />" << endl; } if(w->getType() == formButton) @@ -60,42 +60,10 @@ void HTMLRenderer::process_form(ostream & out) << " style=\"opacity:0.0; position: absolute; left: " << std::to_string(x1) << "px; bottom: " << std::to_string(y1) << "px;" << "width: " << std::to_string(width) << "px; height: " << std::to_string(height) << - "px; font-size: 20px; \" class=\"checkbox checkbox-" << - std::to_string(pageNum) << "\">X" << endl; + "px; font-size: 20px; \" class=\"" << CSS::INPUT_RADIO_CN << "\">X" << endl; } } - - //output, at the end, the necessary js - if(num > 0) { - //this is currently page specific - out << "" << endl; - } -} - -void HTMLRenderer::dump_form_css(ostream & out) -{ - out << ".text_input {" << - "border: none; " << - "background-color: rgba(255, 255, 255, 0.0);" << - "}" << endl << - ".checkbox:hover {" << - "cursor: pointer;" << - "}" << endl; } } diff --git a/src/HTMLRenderer/general.cc b/src/HTMLRenderer/general.cc index 6aac48c..a370a15 100644 --- a/src/HTMLRenderer/general.cc +++ b/src/HTMLRenderer/general.cc @@ -253,7 +253,7 @@ void HTMLRenderer::endPage() { html_text_page.clear(); // process form - if(param.include_forms) + if(param.process_forms) process_form(*f_curpage); // process links before the page is closed @@ -396,11 +396,6 @@ void HTMLRenderer::post_process(void) { dump_css(); - if (param.include_forms) - { - dump_form_css(f_css.fs); - } - // close files if they opened // it's better to brace single liner LLVM complains if (param.process_outline) diff --git a/src/Param.h b/src/Param.h index 9491d42..eea1bd9 100644 --- a/src/Param.h +++ b/src/Param.h @@ -38,6 +38,7 @@ struct Param int process_nontext; int process_outline; int process_annotation; + int process_forms; int correct_text_visibility; int printing; int fallback; @@ -62,9 +63,6 @@ struct Param int tounicode; int optimize_text; - // adobe form - int include_forms; - // background image std::string bg_format; int svg_node_count_limit; diff --git a/src/css_class_names.cmakelists.txt b/src/css_class_names.cmakelists.txt index 9240c6e..19c6f8b 100644 --- a/src/css_class_names.cmakelists.txt +++ b/src/css_class_names.cmakelists.txt @@ -34,3 +34,5 @@ set(CSS_WIDTH_CN "w") # Width set(CSS_BOTTTOM_CN "y") # Y set(CSS_CSS_DRAW_CN "d") # Draw set(CSS_LINK_CN "l") # Link +set(CSS_INPUT_TEXT_CN "it") # Text input +set(CSS_INPUT_RADIO_CN "ir") # Radio button diff --git a/src/pdf2htmlEX.cc b/src/pdf2htmlEX.cc index 845b5bf..aa17b59 100644 --- a/src/pdf2htmlEX.cc +++ b/src/pdf2htmlEX.cc @@ -164,6 +164,7 @@ void parse_options (int argc, char **argv) .add("process-nontext", ¶m.process_nontext, 1, "render graphics in addition to text") .add("process-outline", ¶m.process_outline, 1, "show outline in HTML") .add("process-annotation", ¶m.process_annotation, 0, "show annotation in HTML") + .add("process-forms", ¶m.process_forms, 0, "include text fields and radio buttons") .add("printing", ¶m.printing, 1, "enable printing support") .add("fallback", ¶m.fallback, 0, "output in fallback mode") .add("tmp-file-size-limit", ¶m.tmp_file_size_limit, -1, "Maximum size (in KB) used by temporary files, -1 for no limit.") @@ -189,9 +190,6 @@ void parse_options (int argc, char **argv) .add("optimize-text", ¶m.optimize_text, 0, "try to reduce the number of HTML elements used for text") .add("correct-text-visibility", ¶m.correct_text_visibility, 0, "try to detect texts covered by other graphics and properly arrange them") - // adobe forms - .add("include-forms", ¶m.include_forms, 0, "include text fields and such") - // background image .add("bg-format", ¶m.bg_format, "png", "specify background image format") .add("svg-node-count-limit", ¶m.svg_node_count_limit, -1, "if node count in a svg background image exceeds this limit," diff --git a/src/util/css_const.h.in b/src/util/css_const.h.in index 143adcb..795ed10 100644 --- a/src/util/css_const.h.in +++ b/src/util/css_const.h.in @@ -56,6 +56,9 @@ const char * const BOTTOM_CN = "@CSS_BOTTTOM_CN@"; const char * const CSS_DRAW_CN = "@CSS_CSS_DRAW_CN@"; const char * const LINK_CN = "@CSS_LINK_CN@"; +const char * const INPUT_TEXT_CN = "@CSS_INPUT_TEXT_CN@"; +const char * const INPUT_RADIO_CN = "@CSS_INPUT_RADIO_CN@"; + } } From 6fe0262427932006ada67250b7fa73ed0fa207ec Mon Sep 17 00:00:00 2001 From: Simon Chenard Date: Thu, 16 Oct 2014 10:34:41 -0400 Subject: [PATCH 4/6] The cross symbol for checkboxes is now an image. --- share/base.css.in | 4 ++++ share/pdf2htmlEX.js.in | 6 +++--- share/pdf2htmlEX.min.js | 6 +++--- src/HTMLRenderer/form.cc | 7 +++++-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/share/base.css.in b/share/base.css.in index 14e6282..91eb5be 100644 --- a/share/base.css.in +++ b/share/base.css.in @@ -199,4 +199,8 @@ .@CSS_INPUT_RADIO_CN@:hover { cursor: pointer; } + +.checked { + background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3goQDSYgDiGofgAAAslJREFUOMvtlM9LFGEYx7/vvOPM6ywuuyPFihWFBUsdNnA6KLIh+QPx4KWExULdHQ/9A9EfUodYmATDYg/iRewQzklFWxcEBcGgEplDkDtI6sw4PzrIbrOuedBb9MALD7zv+3m+z4/3Bf7bZS2bzQIAcrmcMDExcTeXy10DAFVVAQDksgFUVZ1ljD3yfd+0LOuFpmnvVVW9GHhkZAQcxwkNDQ2FSCQyRMgJxnVdy7KstKZpn7nwha6urqqfTqfPBAJAuVymlNLXoigOhfd5nmeiKL5TVTV+lmIKwAOA7u5u6Lped2BsbOwjY6yf4zgQQkAIAcedaPR9H67r3uYBQFEUFItFtLe332lpaVkUBOHK3t5eRtf1DwAwODiIubk5DA8PM8bYW1EU+wEgCIJqsCAIQAiB7/u253k2BQDDMJBKpa4mEon5eDx+UxAESJL0uK2t7XosFlvSdf0QAEmlUnlRFJ9Waho2Qghc1/U9z3uWz+eX+Wr+lL6SZfleEAQIggA8z6OpqSknimIvYyybSCReMsZ6TislhCAIAti2Dc/zejVNWwCAavN8339j27YbTg0AGGM3WltbP4WhlRWq6Q/btrs1TVsYHx+vNgqKoqBUKn2NRqPFxsbGJzzP05puUlpt0ukyOI6z7zjOwNTU1OLo6CgmJyf/gA3DgKIoWF1d/cIY24/FYgOU0pp0z/Ityzo8Pj5OTk9PbwHA+vp6zWghDC+VSiuRSOQgGo32UErJ38CO42wdHR09LBQK3zKZDDY2NupmFmF4R0cHVlZWlmRZ/iVJUn9FeWWcCCE4ODjYtG27Z2Zm5juAOmgdGAB2d3cBADs7O8uSJN2SZfl+WKlpmpumaT6Yn58vn/fs6XmbhmHMNjc3tzDGFI7jYJrm5vb29sDa2trPC/9aiqJUy5pOp4f6+vqeJ5PJBAB0dnZe/t8NBajx/z37Df5OGX8d13xzAAAAAElFTkSuQmCC); +} /* Base CSS END */ diff --git a/share/pdf2htmlEX.js.in b/share/pdf2htmlEX.js.in index 7b228bd..118c8d6 100644 --- a/share/pdf2htmlEX.js.in +++ b/share/pdf2htmlEX.js.in @@ -267,10 +267,10 @@ Viewer.prototype = { var r = elements[i]; r.addEventListener('click', function() { - if(this.style.opacity == 1) - this.style.opacity = 0; + if(this.className.search("checked") == -1) + this.className += " checked"; else - this.style.opacity = 1; + this.className = "ir"; }); } }, diff --git a/share/pdf2htmlEX.min.js b/share/pdf2htmlEX.min.js index 571e36c..4aa848e 100644 --- a/share/pdf2htmlEX.min.js +++ b/share/pdf2htmlEX.min.js @@ -267,10 +267,10 @@ Viewer.prototype = { var r = elements[i]; r.addEventListener('click', function() { - if(this.style.opacity == 1) - this.style.opacity = 0; + if(this.className.search("checked") == -1) + this.className += " checked"; else - this.style.opacity = 1; + this.className = "ir"; }); } }, diff --git a/src/HTMLRenderer/form.cc b/src/HTMLRenderer/form.cc index 02b5036..72e6fdf 100644 --- a/src/HTMLRenderer/form.cc +++ b/src/HTMLRenderer/form.cc @@ -55,12 +55,15 @@ void HTMLRenderer::process_form(ostream & out) if(w->getType() == formButton) { + width += 3; + height += 3; + out << "
X
" << endl; + "px; \" class=\"" << CSS::INPUT_RADIO_CN << "\">" << endl; } } From c9201c6a1d629096ce44c6a747af7f967213fe05 Mon Sep 17 00:00:00 2001 From: Simon Chenard Date: Fri, 14 Nov 2014 14:28:17 -0500 Subject: [PATCH 5/6] some more corrections and cleaning --- pdf2htmlEX.1.in | 2 +- share/base.css.in | 3 - share/fancy.css.in | 3 + share/pdf2htmlEX.min.js | 934 ----------------------------- src/HTMLRenderer/HTMLRenderer.h | 2 +- src/HTMLRenderer/form.cc | 37 +- src/HTMLRenderer/general.cc | 2 +- src/Param.h | 2 +- src/css_class_names.cmakelists.txt | 1 + src/pdf2htmlEX.cc | 2 +- src/util/css_const.h.in | 1 + 11 files changed, 30 insertions(+), 959 deletions(-) delete mode 100644 share/pdf2htmlEX.min.js diff --git a/pdf2htmlEX.1.in b/pdf2htmlEX.1.in index d6e5faa..6852507 100644 --- a/pdf2htmlEX.1.in +++ b/pdf2htmlEX.1.in @@ -139,7 +139,7 @@ Whether to show outline in the generated HTML Whether to show annotation in the generated HTML .TP -.B \-\-process-forms <0|1> (Default: 0) +.B \-\-process-form <0|1> (Default: 0) Whether to include text fields and radio buttons in the generated HTML .TP diff --git a/share/base.css.in b/share/base.css.in index 91eb5be..a15a23f 100644 --- a/share/base.css.in +++ b/share/base.css.in @@ -200,7 +200,4 @@ cursor: pointer; } -.checked { - background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3goQDSYgDiGofgAAAslJREFUOMvtlM9LFGEYx7/vvOPM6ywuuyPFihWFBUsdNnA6KLIh+QPx4KWExULdHQ/9A9EfUodYmATDYg/iRewQzklFWxcEBcGgEplDkDtI6sw4PzrIbrOuedBb9MALD7zv+3m+z4/3Bf7bZS2bzQIAcrmcMDExcTeXy10DAFVVAQDksgFUVZ1ljD3yfd+0LOuFpmnvVVW9GHhkZAQcxwkNDQ2FSCQyRMgJxnVdy7KstKZpn7nwha6urqqfTqfPBAJAuVymlNLXoigOhfd5nmeiKL5TVTV+lmIKwAOA7u5u6Lped2BsbOwjY6yf4zgQQkAIAcedaPR9H67r3uYBQFEUFItFtLe332lpaVkUBOHK3t5eRtf1DwAwODiIubk5DA8PM8bYW1EU+wEgCIJqsCAIQAiB7/u253k2BQDDMJBKpa4mEon5eDx+UxAESJL0uK2t7XosFlvSdf0QAEmlUnlRFJ9Waho2Qghc1/U9z3uWz+eX+Wr+lL6SZfleEAQIggA8z6OpqSknimIvYyybSCReMsZ6TislhCAIAti2Dc/zejVNWwCAavN8339j27YbTg0AGGM3WltbP4WhlRWq6Q/btrs1TVsYHx+vNgqKoqBUKn2NRqPFxsbGJzzP05puUlpt0ukyOI6z7zjOwNTU1OLo6CgmJyf/gA3DgKIoWF1d/cIY24/FYgOU0pp0z/Ityzo8Pj5OTk9PbwHA+vp6zWghDC+VSiuRSOQgGo32UErJ38CO42wdHR09LBQK3zKZDDY2NupmFmF4R0cHVlZWlmRZ/iVJUn9FeWWcCCE4ODjYtG27Z2Zm5juAOmgdGAB2d3cBADs7O8uSJN2SZfl+WKlpmpumaT6Yn58vn/fs6XmbhmHMNjc3tzDGFI7jYJrm5vb29sDa2trPC/9aiqJUy5pOp4f6+vqeJ5PJBAB0dnZe/t8NBajx/z37Df5OGX8d13xzAAAAAElFTkSuQmCC); -} /* Base CSS END */ diff --git a/share/fancy.css.in b/share/fancy.css.in index b8d9dc5..bdba3f6 100644 --- a/share/fancy.css.in +++ b/share/fancy.css.in @@ -81,5 +81,8 @@ -webkit-animation: swing 1.5s ease-in-out 0.01s infinite alternate none; animation: swing 1.5s ease-in-out 0.01s infinite alternate none; } + .@CSS_RADIO_CHECKED_CN@ { + background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3goQDSYgDiGofgAAAslJREFUOMvtlM9LFGEYx7/vvOPM6ywuuyPFihWFBUsdNnA6KLIh+QPx4KWExULdHQ/9A9EfUodYmATDYg/iRewQzklFWxcEBcGgEplDkDtI6sw4PzrIbrOuedBb9MALD7zv+3m+z4/3Bf7bZS2bzQIAcrmcMDExcTeXy10DAFVVAQDksgFUVZ1ljD3yfd+0LOuFpmnvVVW9GHhkZAQcxwkNDQ2FSCQyRMgJxnVdy7KstKZpn7nwha6urqqfTqfPBAJAuVymlNLXoigOhfd5nmeiKL5TVTV+lmIKwAOA7u5u6Lped2BsbOwjY6yf4zgQQkAIAcedaPR9H67r3uYBQFEUFItFtLe332lpaVkUBOHK3t5eRtf1DwAwODiIubk5DA8PM8bYW1EU+wEgCIJqsCAIQAiB7/u253k2BQDDMJBKpa4mEon5eDx+UxAESJL0uK2t7XosFlvSdf0QAEmlUnlRFJ9Waho2Qghc1/U9z3uWz+eX+Wr+lL6SZfleEAQIggA8z6OpqSknimIvYyybSCReMsZ6TislhCAIAti2Dc/zejVNWwCAavN8339j27YbTg0AGGM3WltbP4WhlRWq6Q/btrs1TVsYHx+vNgqKoqBUKn2NRqPFxsbGJzzP05puUlpt0ukyOI6z7zjOwNTU1OLo6CgmJyf/gA3DgKIoWF1d/cIY24/FYgOU0pp0z/Ityzo8Pj5OTk9PbwHA+vp6zWghDC+VSiuRSOQgGo32UErJ38CO42wdHR09LBQK3zKZDDY2NupmFmF4R0cHVlZWlmRZ/iVJUn9FeWWcCCE4ODjYtG27Z2Zm5juAOmgdGAB2d3cBADs7O8uSJN2SZfl+WKlpmpumaT6Yn58vn/fs6XmbhmHMNjc3tzDGFI7jYJrm5vb29sDa2trPC/9aiqJUy5pOp4f6+vqeJ5PJBAB0dnZe/t8NBajx/z37Df5OGX8d13xzAAAAAElFTkSuQmCC); + } } /* Fancy CSS END */ diff --git a/share/pdf2htmlEX.min.js b/share/pdf2htmlEX.min.js deleted file mode 100644 index 4aa848e..0000000 --- a/share/pdf2htmlEX.min.js +++ /dev/null @@ -1,934 +0,0 @@ -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab filetype=javascript : */ -/** - * @license pdf2htmlEX.js: Core UI functions for pdf2htmlEX - * Copyright 2012,2013 Lu Wang and other contributors - * https://github.com/coolwanglu/pdf2htmlEX/blob/master/share/LICENSE - */ - -/* - * 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. - * - */ - -'use strict'; - -var pdf2htmlEX = window['pdf2htmlEX'] = window['pdf2htmlEX'] || {}; - -/** - * @const - * @struct - */ -var CSS_CLASS_NAMES = { - page_frame : 'pf', - page_content_box : 'pc', - page_data : 'pi', - background_image : 'bi', - link : 'l', - __dummy__ : 'no comma' -}; - -/** - * configurations of Viewer - * @const - * @dict - */ -var DEFAULT_CONFIG = { - // id of the element to put the pages in - 'container_id' : 'page-container', - // id of the element for sidebar (to open and close) - 'sidebar_id' : 'sidebar', - // id of the element for outline - 'outline_id' : 'outline', - // class for the loading indicator - 'loading_indicator_cls' : 'loading-indicator', - // How many page shall we preload that are below the last visible page - 'preload_pages' : 3, - // how many ms should we wait before actually rendering the pages and after a scroll event - 'render_timeout' : 100, - // zoom ratio step for each zoom in/out event - 'scale_step' : 0.9, - // register global key handler - 'key_handler' : true, - // register hashchange handler - 'hashchange_handler' : true, - - '__dummy__' : 'no comma' -}; - -/** @const */ -var EPS = 1e-6; - -/************************************/ -/* utility function */ -/** - * @param{Array.} ctm - */ -function invert(ctm) { - var det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; - 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 - ]; -}; -/** - * @param{Array.} ctm - * @param{Array.} pos - */ -function transform(ctm, pos) { - return [ctm[0] * pos[0] + ctm[2] * pos[1] + ctm[4] - ,ctm[1] * pos[0] + ctm[3] * pos[1] + ctm[5]]; -}; - -/** - * @param{Element} ele - */ -function get_page_number(ele) { - return parseInt(ele.getAttribute('data-page-no'), 16); -}; - -/** - * @param{NodeList} eles - */ -function disable_dragstart(eles) { - for (var i = 0, l = eles.length; i < l; ++i) { - eles[i].addEventListener('dragstart', function() { - return false; - }, false); - } -}; - -/** - * @param{...Object} var_args - */ -function clone_and_extend_objs(var_args) { - var result_obj = {}; - for (var i = 0, l = arguments.length; i < l; ++i) { - var cur_obj = arguments[i]; - for (var k in cur_obj) { - if (cur_obj.hasOwnProperty(k)) { - result_obj[k] = cur_obj[k]; - } - } - } - return result_obj; -}; - -/** - * @constructor - * @param{Element} page The element for the page - */ -function Page(page) { - if (!page) return; - - this.loaded = false; - this.shown = false; - this.page = page; // page frame element - - this.num = get_page_number(page); - - // page size - // Need to make rescale work when page_content_box is not loaded, yet - this.original_height = page.clientHeight; - this.original_width = page.clientWidth; - - // content box - var content_box = page.getElementsByClassName(CSS_CLASS_NAMES.page_content_box)[0]; - - // if page is loaded - if (content_box) { - this.content_box = content_box; - /* - * scale ratios - * - * original_scale : the first one - * cur_scale : currently using - */ - this.original_scale = this.cur_scale = this.original_height / content_box.clientHeight; - this.page_data = JSON.parse(page.getElementsByClassName(CSS_CLASS_NAMES.page_data)[0].getAttribute('data-data')); - - this.ctm = this.page_data['ctm']; - this.ictm = invert(this.ctm); - - this.loaded = true; - } -}; -Page.prototype = { - /* hide & show are for contents, the page frame is still there */ - hide : function(){ - if (this.loaded && this.shown) { - this.content_box.classList.remove('opened'); - this.shown = false; - } - }, - show : function(){ - if (this.loaded && !this.shown) { - this.content_box.classList.add('opened'); - this.shown = true; - } - }, - /** - * @param{number} ratio - */ - rescale : function(ratio) { - if (ratio === 0) { - // reset scale - this.cur_scale = this.original_scale; - } else { - this.cur_scale = ratio; - } - - // scale the content box - if (this.loaded) { - var cbs = this.content_box.style; - cbs.msTransform = cbs.webkitTransform = cbs.transform = 'scale('+this.cur_scale.toFixed(3)+')'; - } - - // stretch the page frame to hold the place - { - var ps = this.page.style; - ps.height = (this.original_height * this.cur_scale) + 'px'; - ps.width = (this.original_width * this.cur_scale) + 'px'; - } - }, - /* - * return the coordinate of the top-left corner of container - * in our coordinate system - * assuming that p.parentNode === p.offsetParent - */ - view_position : function () { - var p = this.page; - var c = p.parentNode; - return [c.scrollLeft - p.offsetLeft - p.clientLeft - ,c.scrollTop - p.offsetTop - p.clientTop]; - }, - height : function () { - return this.page.clientHeight; - }, - width : function () { - return this.page.clientWidth; - } -}; - -/** - * @constructor - * @param{Object=} config - */ -function Viewer(config) { - this.config = clone_and_extend_objs(DEFAULT_CONFIG, (arguments.length > 0 ? config : {})); - this.pages_loading = []; - this.init_before_loading_content(); - - var self = this; - document.addEventListener('DOMContentLoaded', function(){ - self.init_after_loading_content(); - }, false); -}; - -Viewer.prototype = { - scale : 1, - /* - * index of the active page (the one with largest visible area) - * which estimates the page currently being viewed - */ - cur_page_idx : 0, - - /* - * index of the first visible page - * used when determining current view - */ - first_page_idx : 0, - - init_before_loading_content : function() { - /* hide all pages before loading, will reveal only visible ones later */ - this.pre_hide_pages(); - }, - - initialize_radio_button : function() { - var elements = document.getElementsByClassName('ir'); - - for(var i = 0; i < elements.length; i++) { - var r = elements[i]; - - r.addEventListener('click', function() { - if(this.className.search("checked") == -1) - this.className += " checked"; - else - this.className = "ir"; - }); - } - }, - - init_after_loading_content : function() { - this.initialize_radio_button(); - - this.sidebar = document.getElementById(this.config['sidebar_id']); - this.outline = document.getElementById(this.config['outline_id']); - this.container = document.getElementById(this.config['container_id']); - this.loading_indicator = document.getElementsByClassName(this.config['loading_indicator_cls'])[0]; - - - { - // Open the outline if nonempty - var empty = true; - var nodes = this.outline.childNodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var cur_node = nodes[i]; - if (cur_node.nodeName.toLowerCase() === 'ul') { - empty = false; - break; - } - } - if (!empty) - this.sidebar.classList.add('opened'); - } - - this.find_pages(); - // do nothing if there's nothing - if(this.pages.length == 0) return; - - // disable dragging of background images - disable_dragstart(document.getElementsByClassName(CSS_CLASS_NAMES.background_image)); - - if (this.config['key_handler']) - this.register_key_handler(); - - var self = this; - - if (this.config['hashchange_handler']) { - window.addEventListener('hashchange', function(e) { - self.navigate_to_dest(document.location.hash.substring(1)); - }, false); - } - - // register schedule rendering - // renew old schedules since scroll() may be called frequently - this.container.addEventListener('scroll', function() { - self.update_page_idx(); - self.schedule_render(true); - }, false); - - // handle links - [this.container, this.outline].forEach(function(ele) { - ele.addEventListener('click', self.link_handler.bind(self), false); - }); - - this.render(); - }, - - /* - * 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 - */ - find_pages : function() { - var new_pages = []; - var new_page_map = {}; - var nodes = this.container.childNodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var cur_node = nodes[i]; - if ((cur_node.nodeType === Node.ELEMENT_NODE) - && cur_node.classList.contains(CSS_CLASS_NAMES.page_frame)) { - var p = new Page(cur_node); - new_pages.push(p); - new_page_map[p.num] = new_pages.length - 1; - } - } - this.pages = new_pages; - this.page_map = new_page_map; - }, - - /** - * @param{number} idx - * @param{number=} pages_to_preload - * @param{function(Page)=} callback - * - * TODO: remove callback -> promise ? - */ - load_page : function(idx, pages_to_preload, callback) { - var pages = this.pages; - if (idx >= pages.length) - return; // Page does not exist - - var cur_page = pages[idx]; - if (cur_page.loaded) - return; // Page is loaded - - if (this.pages_loading[idx]) - return; // Page is already loading - - var cur_page_ele = cur_page.page; - var url = cur_page_ele.getAttribute('data-page-url'); - if (url) { - this.pages_loading[idx] = true; // set semaphore - - // add a copy of the loading indicator - var new_loading_indicator = this.loading_indicator.cloneNode(); - new_loading_indicator.classList.add('active'); - cur_page_ele.appendChild(new_loading_indicator); - - // load data - { - var self = this; - var _idx = idx; - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.onload = function(){ - if (xhr.status === 200 || xhr.status === 0) { - // find the page element in the data - var div = document.createElement('div'); - div.innerHTML = xhr.responseText; - - var new_page = null; - var nodes = div.childNodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var cur_node = nodes[i]; - if ((cur_node.nodeType === Node.ELEMENT_NODE) - && cur_node.classList.contains(CSS_CLASS_NAMES.page_frame)) { - new_page = cur_node; - break; - } - } - - // replace the old page with loaded data - // the loading indicator on this page should also be destroyed - var p = self.pages[_idx]; - self.container.replaceChild(new_page, p.page); - p = new Page(new_page); - self.pages[_idx] = p; - - p.hide(); - p.rescale(self.scale); - - // disable background image dragging - disable_dragstart(new_page.getElementsByClassName(CSS_CLASS_NAMES.background_image)); - - self.schedule_render(false); - - if (callback){ callback(p); } - } - - // Reset loading token - delete self.pages_loading[_idx]; - }; - xhr.send(null); - } - } - // Concurrent prefetch of the next pages - if (pages_to_preload === undefined) - pages_to_preload = this.config['preload_pages']; - - if (--pages_to_preload > 0) { - var self = this; - setTimeout(function() { - self.load_page(idx+1, pages_to_preload); - },0); - } - }, - - /* - * Hide all pages that have no 'opened' class - * The 'opened' class will be added to visible pages by JavaScript - * We cannot add this in the default CSS because JavaScript may be disabled - */ - pre_hide_pages : function() { - /* pages might have not been loaded yet, so add a CSS rule */ - var s = '@media screen{.'+CSS_CLASS_NAMES.page_content_box+'{display:none;}}'; - var n = document.createElement('style'); - if (n.styleSheet) { - n.styleSheet.cssText = s; - } else { - n.appendChild(document.createTextNode(s)); - } - document.head.appendChild(n); - }, - - /* - * show visible pages and hide invisible pages - */ - render : function () { - var container = this.container; - /* - * show the pages that are 'nearly' visible -- it's right above or below the container - * - * all the y values are in the all-page element's coordinate system - */ - var container_min_y = container.scrollTop; - var container_height = container.clientHeight; - var container_max_y = container_min_y + container_height; - var visible_min_y = container_min_y - container_height; - var visible_max_y = container_max_y + container_height; - - var cur_page_fully_visible = false; - var cur_page_idx = this.cur_page_idx; - var max_visible_page_idx = cur_page_idx; - var max_visible_ratio = 0.0; - - var pl = this.pages; - for (var i = 0, l = pl.length; i < l; ++i) { - var cur_page = pl[i]; - var cur_page_ele = cur_page.page; - var page_min_y = cur_page_ele.offsetTop + cur_page_ele.clientTop; - var page_height = cur_page_ele.clientHeight; - var page_max_y = page_min_y + page_height; - if ((page_min_y <= visible_max_y) && (page_max_y >= visible_min_y)) - { - // cur_page is 'nearly' visible, show it or load it - if (cur_page.loaded) { - cur_page.show(); - } else { - this.load_page(i); - } - } else { - cur_page.hide(); - } - } - }, - /* - * update cur_page_idx and first_page_idx - * normally called upon scrolling - */ - update_page_idx: function () { - var pages = this.pages; - var pages_len = pages.length; - // there is no chance that cur_page_idx or first_page_idx is modified - if (pages_len < 2) return; - - var container = this.container; - var container_min_y = container.scrollTop; - var container_max_y = container_min_y + container.clientHeight; - - // binary search for the first page - // whose bottom border is below the top border of the container - var first_idx = -1; - var last_idx = pages_len; - var rest_len = last_idx - first_idx; - // TODO: use current first_page_idx as a hint? - while(rest_len > 1) { - var idx = first_idx + Math.floor(rest_len / 2); - var cur_page_ele = pages[idx].page; - if (cur_page_ele.offsetTop + cur_page_ele.clientTop + cur_page_ele.clientHeight >= container_min_y) { - last_idx = idx; - } else { - first_idx = idx; - } - rest_len = last_idx - first_idx; - } - - /* - * with malformed settings it is possible that no page is visible, e.g. - * - the container is to thin, which lies in the margin between two pages - * - all pages are completely above or below the container - * but we just assume that they won't happen. - */ - this.first_page_idx = last_idx; - - // find the page with largest visible area - var cur_page_idx = this.cur_page_idx; - var max_visible_page_idx = cur_page_idx; - var max_visible_ratio = 0.0; - - for(var i = last_idx; i < pages_len; ++i) { - var cur_page_ele = pages[i].page; - var page_min_y = cur_page_ele.offsetTop + cur_page_ele.clientTop; - var page_height = cur_page_ele.clientHeight; - var page_max_y = page_min_y + page_height; - if (page_min_y > container_max_y) break; - - // check the visible fraction of the page - var page_visible_ratio = ( Math.min(container_max_y, page_max_y) - - Math.max(container_min_y, page_min_y) - ) / page_height; - - // stay with the current page if it is still fully visible - if ((i === cur_page_idx) && (Math.abs(page_visible_ratio - 1.0) <= EPS)) { - max_visible_page_idx = cur_page_idx; - break; - } - - if (page_visible_ratio > max_visible_ratio) { - max_visible_ratio = page_visible_ratio; - max_visible_page_idx = i; - } - } - - this.cur_page_idx = max_visible_page_idx; - }, - - /** - * @param{boolean} renew renew the existing schedule instead of using the old one - */ - schedule_render : function(renew) { - if (this.render_timer !== undefined) { - if (!renew) return; - clearTimeout(this.render_timer); - } - - var self = this; - this.render_timer = setTimeout(function () { - /* - * render() may trigger load_page(), which may in turn trigger another render() - * so delete render_timer first - */ - delete self.render_timer; - self.render(); - }, this.config['render_timeout']); - }, - - /* - * Handling key events, zooming, scrolling etc. - */ - register_key_handler: function () { - /* - * 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 self = this; - - // Firefox specific event, so that we can prevent browser from zooming - window.addEventListener('DOMMouseScroll', function(e) { - if (e.ctrlKey) { - e.preventDefault(); - var container = self.container; - var rect = container.getBoundingClientRect(); - var fixed_point = [e.clientX - rect['left'] - container.clientLeft - ,e.clientY - rect['top'] - container.clientTop]; - self.rescale(Math.pow(self.config['scale_step'], e.detail), true, fixed_point); - } - }, false); - - window.addEventListener('keydown', function(e) { - var handled = false; - /* - var cmd = (e.ctrlKey ? 1 : 0) - | (e.altKey ? 2 : 0) - | (e.shiftKey ? 4 : 0) - | (e.metaKey ? 8 : 0) - ; - */ - var with_ctrl = e.ctrlKey || e.metaKey; - var with_alt = e.altKey; - switch (e.keyCode) { - case 61: // FF/Mac '=' - case 107: // FF '+' and '=' - case 187: // Chrome '+' - if (with_ctrl){ - self.rescale(1.0 / self.config['scale_step'], true); - handled = true; - } - break; - case 173: // FF/Mac '-' - case 109: // FF '-' - case 189: // Chrome '-' - if (with_ctrl){ - self.rescale(self.config['scale_step'], true); - handled = true; - } - break; - case 48: // '0' - if (with_ctrl){ - self.rescale(0, false); - handled = true; - } - break; - case 33: // Page UP: - if (with_alt) { // alt-pageup -> scroll one page up - self.scroll_to(self.cur_page_idx - 1); - } else { // pageup -> scroll one screen up - self.container.scrollTop -= self.container.clientHeight; - } - handled = true; - break; - case 34: // Page DOWN - if (with_alt) { // alt-pagedown -> scroll one page down - self.scroll_to(self.cur_page_idx + 1); - } else { // pagedown -> scroll one screen down - self.container.scrollTop += self.container.clientHeight; - } - handled = true; - break; - case 35: // End - self.container.scrollTop = self.container.scrollHeight; - handled = true; - break; - case 36: // Home - self.container.scrollTop = 0; - handled = true; - break; - } - if (handled) { - e.preventDefault(); - return; - } - }, false); - }, - - /** - * @param{number} ratio - * @param{boolean} is_relative - * @param{Array.=} fixed_point preserve the position (relative to the top-left corner of the viewer) after rescaling - */ - rescale : function (ratio, is_relative, fixed_point) { - var old_scale = this.scale; - var new_scale = old_scale; - // set new scale - if (ratio === 0) { - new_scale = 1; - is_relative = false; - } else if (is_relative) - new_scale *= ratio; - else - new_scale = ratio; - - this.scale = new_scale; - - if (!fixed_point) - fixed_point = [0,0]; - - // translate fixed_point to the coordinate system of all pages - var container = this.container; - fixed_point[0] += container.scrollLeft; - fixed_point[1] += container.scrollTop; - - // find the visible page that contains the fixed point - // if the fixed point lies between two pages (including their borders), it's contained in the first one - var pl = this.pages; - var pl_len = pl.length; - for (var i = this.first_page_idx; i < pl_len; ++i) { - var p = pl[i].page; - if (p.offsetTop + p.clientTop >= fixed_point[1]) - break; - } - var fixed_point_page_idx = i - 1; - - // determine the new scroll position - // each-value consists of two parts, one inside the page, which is affected by rescaling, - // the other is outside, (e.g. borders and margins), which is not affected - - // if the fixed_point is above the first page, use the first page as the reference - if (fixed_point_page_idx < 0) - fixed_point_page_idx = 0; - - var fp_p = pl[fixed_point_page_idx].page; - var fp_p_width = fp_p.clientWidth; - var fp_p_height = fp_p.clientHeight; - - var fp_x_ref = fp_p.offsetLeft + fp_p.clientLeft; - var fp_x_inside = fixed_point[0] - fp_x_ref; - if (fp_x_inside < 0) - fp_x_inside = 0; - else if (fp_x_inside > fp_p_width) - fp_x_inside = fp_p_width; - - var fp_y_ref = fp_p.offsetTop + fp_p.clientTop; - var fp_y_inside = fixed_point[1] - fp_y_ref; - if (fp_y_inside < 0) - fp_y_inside = 0; - else if (fp_y_inside > fp_p_height) - fp_y_inside = fp_p_height; - - // Rescale pages - for (var i = 0; i < pl_len; ++i) - pl[i].rescale(new_scale); - - // Correct container scroll to keep view aligned while zooming - container.scrollLeft += fp_x_inside / old_scale * new_scale + fp_p.offsetLeft + fp_p.clientLeft - fp_x_inside - fp_x_ref; - container.scrollTop += fp_y_inside / old_scale * new_scale + fp_p.offsetTop + fp_p.clientTop - fp_y_inside - fp_y_ref; - - // some pages' visibility may be toggled, wait for next render() - // renew old schedules since rescale() may be called frequently - this.schedule_render(true); - }, - - fit_width : function () { - var page_idx = this.cur_page_idx; - this.rescale(this.container.clientWidth / this.pages[page_idx].width(), false); - this.scroll_to(page_idx); - }, - - fit_height : function () { - var page_idx = this.cur_page_idx; - this.rescale(this.container.clientHeight / this.pages[page_idx].height(), false); - this.scroll_to(page_idx); - }, - /** - * @param{Node} ele - */ - get_containing_page : function(ele) { - /* get the page obj containing obj */ - while(ele) { - if ((ele.nodeType === Node.ELEMENT_NODE) - && ele.classList.contains(CSS_CLASS_NAMES.page_frame)) { - /* - * Get original page number and map it to index of pages - * TODO: store the index on the dom element - */ - var pn = get_page_number(/** @type{Element} */(ele)); - var pm = this.page_map; - return (pn in pm) ? this.pages[pm[pn]] : null; - } - ele = ele.parentNode; - } - return null; - }, - - /** - * @param{Event} e - */ - link_handler : function (e) { - var target = /** @type{Node} */(e.target); - var detail_str = /** @type{string} */ (target.getAttribute('data-dest-detail')); - if (!detail_str) return; - - this.navigate_to_dest(detail_str, this.get_containing_page(target)); - e.preventDefault(); - }, - - /** - * @param{string} detail_str may come from user provided hashtag, need sanitzing - * @param{Page=} src_page page containing the source event (e.g. link) - */ - navigate_to_dest : function(detail_str, src_page) { - try { - var detail = JSON.parse(detail_str); - } catch(e) { - return; - } - - if(!(detail instanceof Array)) return; - - 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 = this.pages[target_page_idx]; - - for (var i = 2, l = detail.length; i < l; ++i) { - var d = detail[i]; - if(!((d === null) || (typeof d === 'number'))) - return; - } - - while(detail.length < 6) - detail.push(null); - - // cur_page might be undefined, e.g. from Outline - var cur_page = src_page || this.pages[this.cur_page_idx]; - - var cur_pos = cur_page.view_position(); - cur_pos = transform(cur_page.ictm, [cur_pos[0], cur_page.height()-cur_pos[1]]); - - var zoom = this.scale; - var pos = [0,0]; - var upside_down = true; - var ok = false; - - // position specified in `detail` are in the raw coordinate system of the page (unscaled) - var scale = this.scale; - // TODO: fitb* - // TODO: BBox - switch(detail[1]) { - case 'XYZ': - pos = [ (detail[2] === null) ? cur_pos[0] : detail[2] * scale - , (detail[3] === null) ? cur_pos[1] : detail[3] * scale ]; - zoom = detail[4]; - if ((zoom === null) || (zoom === 0)) - zoom = this.scale; - ok = true; - break; - case 'Fit': - case 'FitB': - pos = [0,0]; - ok = true; - break; - case 'FitH': - case 'FitBH': - pos = [0, (detail[2] === null) ? cur_pos[1] : detail[2] * scale]; - ok = true; - break; - case 'FitV': - case 'FitBV': - pos = [(detail[2] === null) ? cur_pos[0] : detail[2] * scale, 0]; - ok = true; - break; - case 'FitR': - /* locate the top-left corner of the rectangle */ - // TODO - pos = [detail[2] * scale, detail[5] * scale]; - upside_down = false; - ok = true; - break; - default: - break; - } - - if (!ok) return; - - this.rescale(zoom, false); - - - var self = this; - /** - * page should of type Page - * @param{Page} page - */ - var transform_and_scroll = function(page) { - pos = transform(page.ctm, pos); - if (upside_down) { - pos[1] = page.height() - pos[1]; - } - self.scroll_to(target_page_idx, pos); - }; - - if (target_page.loaded) { - transform_and_scroll(target_page); - } else { - // TODO: scroll_to may finish before load_page - - // Scroll to the exact position once loaded. - this.load_page(target_page_idx, undefined, transform_and_scroll); - - // In the meantime page gets loaded, scroll approximately position for maximum responsiveness. - this.scroll_to(target_page_idx); - } - }, - - /** - * @param{number} page_idx - * @param{Array.=} pos [x,y] where (0,0) is the top-left corner - */ - scroll_to : function(page_idx, pos) { - var pl = this.pages; - if ((page_idx < 0) || (page_idx >= pl.length)) return; - var target_page = pl[page_idx]; - var cur_target_pos = target_page.view_position(); - - if (pos === undefined) - pos = [0,0]; - - var container = this.container; - container.scrollLeft += pos[0] - cur_target_pos[0]; - container.scrollTop += pos[1] - cur_target_pos[1]; - } -}; - -// export pdf2htmlEX.Viewer -pdf2htmlEX['Viewer'] = Viewer; diff --git a/src/HTMLRenderer/HTMLRenderer.h b/src/HTMLRenderer/HTMLRenderer.h index 6335d6e..d47e91c 100644 --- a/src/HTMLRenderer/HTMLRenderer.h +++ b/src/HTMLRenderer/HTMLRenderer.h @@ -169,7 +169,7 @@ protected: void process_outline(void); void process_outline_items(GooList * items); - void process_form(std::ostream & out); + void process_form(std::ofstream & out); void set_stream_flags (std::ostream & out); diff --git a/src/HTMLRenderer/form.cc b/src/HTMLRenderer/form.cc index 72e6fdf..5ac3054 100644 --- a/src/HTMLRenderer/form.cc +++ b/src/HTMLRenderer/form.cc @@ -17,10 +17,10 @@ namespace pdf2htmlEX { -using std::ostream; +using std::ofstream; using std::cerr; -void HTMLRenderer::process_form(ostream & out) +void HTMLRenderer::process_form(ofstream & out) { FormPageWidgets * widgets = cur_catalog->getPage(pageNum)->getFormWidgets(); int num = widgets->getNumWidgets(); @@ -28,8 +28,8 @@ void HTMLRenderer::process_form(ostream & out) for(int i = 0; i < num; i++) { FormWidget * w = widgets->getWidget(i); - double x1, y1, x2, y2; - int width, height, font_size; + double x1, y1, x2, y2, width, font_size; + int height; w->getRect(&x1, &y1, &x2, &y2); x1 = x1 * param.zoom; @@ -44,13 +44,15 @@ void HTMLRenderer::process_form(ostream & out) { font_size = height / 2; - out << "" << endl; + out + << "" << endl; } if(w->getType() == formButton) @@ -58,12 +60,13 @@ void HTMLRenderer::process_form(ostream & out) width += 3; height += 3; - out << "
" << endl; + out + << "
" << endl; } } diff --git a/src/HTMLRenderer/general.cc b/src/HTMLRenderer/general.cc index a370a15..6c17ef5 100644 --- a/src/HTMLRenderer/general.cc +++ b/src/HTMLRenderer/general.cc @@ -253,7 +253,7 @@ void HTMLRenderer::endPage() { html_text_page.clear(); // process form - if(param.process_forms) + if(param.process_form) process_form(*f_curpage); // process links before the page is closed diff --git a/src/Param.h b/src/Param.h index eea1bd9..84fa426 100644 --- a/src/Param.h +++ b/src/Param.h @@ -38,7 +38,7 @@ struct Param int process_nontext; int process_outline; int process_annotation; - int process_forms; + int process_form; int correct_text_visibility; int printing; int fallback; diff --git a/src/css_class_names.cmakelists.txt b/src/css_class_names.cmakelists.txt index 19c6f8b..067d95a 100644 --- a/src/css_class_names.cmakelists.txt +++ b/src/css_class_names.cmakelists.txt @@ -36,3 +36,4 @@ set(CSS_CSS_DRAW_CN "d") # Draw set(CSS_LINK_CN "l") # Link set(CSS_INPUT_TEXT_CN "it") # Text input set(CSS_INPUT_RADIO_CN "ir") # Radio button +set(CSS_RADIO_CHECKED_CN "checked") # Show picture of checked out radio button diff --git a/src/pdf2htmlEX.cc b/src/pdf2htmlEX.cc index aa17b59..53a96ed 100644 --- a/src/pdf2htmlEX.cc +++ b/src/pdf2htmlEX.cc @@ -164,7 +164,7 @@ void parse_options (int argc, char **argv) .add("process-nontext", ¶m.process_nontext, 1, "render graphics in addition to text") .add("process-outline", ¶m.process_outline, 1, "show outline in HTML") .add("process-annotation", ¶m.process_annotation, 0, "show annotation in HTML") - .add("process-forms", ¶m.process_forms, 0, "include text fields and radio buttons") + .add("process-form", ¶m.process_form, 0, "include text fields and radio buttons") .add("printing", ¶m.printing, 1, "enable printing support") .add("fallback", ¶m.fallback, 0, "output in fallback mode") .add("tmp-file-size-limit", ¶m.tmp_file_size_limit, -1, "Maximum size (in KB) used by temporary files, -1 for no limit.") diff --git a/src/util/css_const.h.in b/src/util/css_const.h.in index 795ed10..08c23fc 100644 --- a/src/util/css_const.h.in +++ b/src/util/css_const.h.in @@ -58,6 +58,7 @@ const char * const LINK_CN = "@CSS_LINK_CN@"; const char * const INPUT_TEXT_CN = "@CSS_INPUT_TEXT_CN@"; const char * const INPUT_RADIO_CN = "@CSS_INPUT_RADIO_CN@"; +const char * const RADIO_CHECKED_CN = "@CSS_RADIO_CHECKED_CN@"; } } From 87bc6c06d4b3a12e2afbb34cc7da31c51f6517b0 Mon Sep 17 00:00:00 2001 From: Simon Chenard Date: Wed, 3 Dec 2014 14:31:38 -0500 Subject: [PATCH 6/6] small CSS fix for the radio button image --- src/HTMLRenderer/form.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HTMLRenderer/form.cc b/src/HTMLRenderer/form.cc index 5ac3054..b8b0521 100644 --- a/src/HTMLRenderer/form.cc +++ b/src/HTMLRenderer/form.cc @@ -65,7 +65,7 @@ void HTMLRenderer::process_form(ofstream & out) << " style=\"position: absolute; left: " << x1 << "px; bottom: " << y1 << "px;" << " width: " << width << "px; height: " - << std::to_string(height) << "px; \" class=\"" + << std::to_string(height) << "px; background-size: cover;\" class=\"" << CSS::INPUT_RADIO_CN << "\">" << endl; }