1
0
mirror of https://github.com/pdf2htmlEX/pdf2htmlEX.git synced 2024-12-22 04:50:09 +00:00

precise link dest

This commit is contained in:
Lu Wang 2012-09-25 19:29:59 +08:00
parent a3a67d48ae
commit e8d09a0115
6 changed files with 466 additions and 213 deletions

View File

@ -127,6 +127,7 @@ add_executable(pdf2htmlEX
src/HTMLRenderer/export.cc
src/HTMLRenderer/text.cc
src/HTMLRenderer/image.cc
src/HTMLRenderer/link.cc
src/include/namespace.h
src/HTMLRenderer/LineBuffer.cc
src/include/ffw.h

View File

@ -54,4 +54,9 @@ span {
.i {
position:absolute;
}
.j {
display:none;
}
.a {
}
/* Base CSS END */

View File

@ -12,10 +12,28 @@
var pdf2htmlEX = (function(){
var EPS = 1e-6;
var Page = function(page, box, visible) {
this.p = page;
this.b = box;
this.v = visible;
var invert = function(ctm) {
var det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
var ictm = new Array();
ictm[0] = ctm[3] / det;
ictm[1] = -ctm[1] / det;
ictm[2] = -ctm[2] / det;
ictm[3] = ctm[0] / det;
ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) / det;
ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) / det;
return ictm;
};
var transform = function(ctm, pos) {
return [ctm[0] * pos[0] + ctm[2] * pos[1] + ctm[4]
,ctm[1] * pos[0] + ctm[3] * pos[1] + ctm[5]];
};
var Page = function(page) {
if(page == undefined) return undefined;
this.p = $(page);
this.n = parseInt(this.p.attr('data-page-no'), 16);
this.b = $('.b', this.p);
/*
* scale ratios
*
@ -23,23 +41,22 @@ var pdf2htmlEX = (function(){
* set_r : last set
* cur_r : currently using
*/
this.default_r = this.set_r = this.cur_r = page.height() / box.height();
this.default_r = this.set_r = this.cur_r = this.p.height() / this.b.height();
this.data = JSON.parse($($('.j', this.p)[0]).attr('data-data'));
this.ctm = this.data.ctm;
this.ictm = invert(this.ctm);
};
Page.prototype.hide = function(){
if(this.v) {
this.v = false;
this.b.hide();
}
this.b.hide();
};
Page.prototype.show = function(){
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.v)) {
this.v = true;
this.b.show();
}
this.b.show();
};
Page.prototype.rescale = function(ratio, is_relative) {
if(ratio == 0) {
@ -55,7 +72,11 @@ var pdf2htmlEX = (function(){
this.p.height(this.b.height() * this.set_r);
this.p.width(this.b.width() * this.set_r);
};
Page.prototype.is_visible = function() {
var p = this.p;
var off = p.position();
return !((off.top + p.height() < 0) || (off.top > p.offsetParent().height()));
};
return {
@ -73,13 +94,14 @@ var pdf2htmlEX = (function(){
},
init_after_loading_content : function() {
this.container = $("#pdf-main");
this.container = $('#pdf-main');
var new_pages = new Array();
var pl= $(".p", this.container);
var pbl = $(".b", this.container);
var pl= $('.p', this.container);
/* don't use for(..in..) */
for(var i = 0, l = pl.length; i < l; ++i) {
new_pages.push(new Page($(pl[i]), $(pbl[i]), false));
var p = new Page(pl[i]);
new_pages[p.n] = p;
}
this.pages = new_pages;
@ -87,6 +109,8 @@ var pdf2htmlEX = (function(){
this.container.scroll(function(){ _.schedule_render(); });
//this.zoom_fixer();
this.container.on('click', '.a', this, this.annot_link_handler);
this.render();
},
@ -95,7 +119,7 @@ var pdf2htmlEX = (function(){
this.init_before_loading_content();
var _ = this;
$(document).ready(function(){_.init_after_loading_content();});
$(function(){_.init_after_loading_content();});
return this;
},
@ -103,47 +127,31 @@ var pdf2htmlEX = (function(){
pre_hide_pages : function() {
/* pages might have not been loaded yet, so add a CSS rule */
var s = '.b{display:none;}';
var n = document.createElement("style");
n.type = "text/css";
var n = document.createElement('style');
n.type = 'text/css';
if (n.styleSheet) {
n.styleSheet.cssText = s;
} else {
n.appendChild(document.createTextNode(s));
}
document.getElementsByTagName("head")[0].appendChild(n);
document.getElementsByTagName('head')[0].appendChild(n);
},
hide_pages : function() {
var pl = this.pages;
for(var i = 0, l = pl.length; i < l; ++i)
for(var i in pl)
pl[i].hide();
},
render : function () {
/* hide (positional) invisible pages */
var pl = this.pages;
var l = pl.length;
var ch = this.container.height();
var i;
for(i = 0; i < l; ++i) {
for(var i in pl) {
var p = pl[i];
if(p.p.position().top + p.p.height() >= 0) break;
if(i > 0) pl[i-1].hide();
}
if((i > 0) && (i < l)) pl[i-1].show();
for(; i < l; ++i) {
var p = pl[i];
p.show();
if(p.p.position().top > ch) break;
}
for(++i; i < l; ++i) {
pl[i].hide();
if(p.is_visible())
p.show();
else
p.hide();
}
},
@ -199,13 +207,57 @@ var pdf2htmlEX = (function(){
rescale : function (ratio, is_relative) {
var pl = this.pages;
for(var i = 0, l = pl.length; i < l; ++i) {
for(var i in pl) {
pl[i].rescale(ratio, is_relative);
}
this.schedule_render();
},
__last_member__ : 'no comma' /*,*/
get_containing_page : function(obj) {
/* get the page obj containing obj */
return this.pages[(new Page(obj.closest('.p')[0])).n];
},
annot_link_handler : function (e) {
var _ = e.data;
var t = $(e.currentTarget);
var cur_page = _.get_containing_page(t);
if(cur_page == undefined) return;
var off = cur_page.p.position();
var cur_pos = transform(cur_page.ictm, [-off.left, cur_page.p.height()+off.top]);
var detail_str = t.attr('data-dest-detail');
if(detail_str == undefined) return;
var ok = false;
var detail= JSON.parse(detail_str);
var target_page = _.pages[detail[0]];
if(target_page == undefined) return;
switch(detail[1]) {
case 'XYZ':
var pos = [(detail[2] == null) ? cur_pos[0] : detail[2]
,(detail[3] == null) ? cur_pos[1] : detail[3]];
pos = transform(cur_page.ctm, pos);
console.log(pos);
var off = target_page.p.position();
console.log(off);
_.container.scrollLeft(_.container.scrollLeft()+off.left+pos[0]);
_.container.scrollTop(_.container.scrollTop()+off.top+target_page.p.height()-pos[1]);
ok = true;
break;
default:
break;
}
if(ok)
e.preventDefault();
}, __last_member__ : 'no comma' /*,*/
}.init();
})();

View File

@ -1,7 +1,7 @@
/*
* general.cc
*
* Hanlding general stuffs
* Handling general stuffs
*
* by WangLu
* 2012.08.14
@ -12,7 +12,6 @@
#include <cmath>
#include <splash/SplashBitmap.h>
#include <Link.h>
#include "HTMLRenderer.h"
#include "BackgroundRenderer.h"
@ -155,7 +154,8 @@ void HTMLRenderer::startPage(int pageNum, GfxState *state)
assert((!line_opened) && "Open line in startPage detected!");
html_fout
<< "<div id=\"p" << pageNum << "\" class=\"p\" style=\"width:"
<< "<div id=\"p" << pageNum << "\" data-page-no=\"" << pageNum
<< "\" class=\"p\" style=\"width:"
<< (pageWidth) << "px;height:"
<< (pageHeight) << "px;\">"
<< "<div class=\"b\" style=\"width:"
@ -226,170 +226,29 @@ void HTMLRenderer::endPage() {
// process links before the page is closed
cur_doc->processLinks(this, pageNum);
// close box
html_fout << "</div>";
// dump info for js
// TODO: create a function for this
// BE CAREFUL WITH ESCAPES
html_fout << "<div class=\"j\" data-data='{";
//default CTM
html_fout << "\"ctm\":[";
for(int i = 0; i < 6; ++i)
{
if(i > 0) html_fout << ",";
html_fout << _round(default_ctm[i]);
}
html_fout << "]";
html_fout << "}'></div>";
// close page
html_fout << "</div></div>" << endl;
html_fout << "</div>" << endl;
}
/*
* Based on pdftohtml from poppler
* TODO: CSS for link rectangles
*/
void HTMLRenderer::processLink(AnnotLink * al)
{
std::string dest_str;
auto action = al->getAction();
if(action)
{
auto kind = action->getKind();
switch(kind)
{
case actionGoTo:
{
auto catalog = cur_doc->getCatalog();
auto * real_action = dynamic_cast<LinkGoTo*>(action);
LinkDest * dest = nullptr;
if(auto _ = real_action->getDest())
dest = _->copy();
else if (auto _ = real_action->getNamedDest())
dest = catalog->findDest(_);
if(dest)
{
int pageno = 0;
if(dest->isPageRef())
{
auto pageref = dest->getPageRef();
pageno = catalog->findPage(pageref.num, pageref.gen);
}
else
{
pageno = dest->getPageNum();
}
delete dest;
if(pageno > 0)
dest_str = (char*)str_fmt("#p%x", pageno);
}
}
break;
case actionGoToR:
{
cerr << "TODO: actionGoToR is not implemented." << endl;
}
break;
case actionURI:
{
auto * real_action = dynamic_cast<LinkURI*>(action);
dest_str = real_action->getURI()->getCString();
}
break;
case actionLaunch:
{
cerr << "TODO: actionLaunch is not implemented." << endl;
}
break;
default:
cerr << "Warning: unknown annotation type: " << kind << endl;
break;
}
}
if(dest_str != "")
{
html_fout << "<a href=\"" << dest_str << "\">";
}
html_fout << "<div style=\"";
double width = 0;
auto * border = al->getBorder();
if(border)
{
width = border->getWidth() * (param->zoom);
if(width > 0)
{
html_fout << "border-width:" << _round(width) << "px;";
auto style = border->getStyle();
switch(style)
{
case AnnotBorder::borderSolid:
html_fout << "border-style:solid;";
break;
case AnnotBorder::borderDashed:
html_fout << "border-style:dashed;";
break;
case AnnotBorder::borderBeveled:
html_fout << "border-style:outset;";
break;
case AnnotBorder::borderInset:
html_fout << "border-style:inset;";
break;
case AnnotBorder::borderUnderlined:
html_fout << "border-style:none;border-bottom-style:solid;";
break;
default:
cerr << "Warning:Unknown annotation border style: " << style << endl;
html_fout << "border-style:solid;";
}
auto color = al->getColor();
double r,g,b;
if(color && (color->getSpace() == AnnotColor::colorRGB))
{
const double * v = color->getValues();
r = v[0];
g = v[1];
b = v[2];
}
else
{
r = g = b = 0;
}
html_fout << "border-color:rgb("
<< dec << (int)dblToByte(r) << "," << (int)dblToByte(g) << "," << (int)dblToByte(b) << hex
<< ");";
}
else
{
html_fout << "border-style:none;";
}
}
else
{
html_fout << "border-style:none;";
}
// fix for IE
html_fout << "background-color:rgba(255,255,255,0.000001);";
double x1,x2,y1,y2;
al->getRect(&x1, &y1, &x2, &y2);
x1 = default_ctm[0] * x1 + default_ctm[2] * y1 + default_ctm[4];
y1 = default_ctm[1] * x1 + default_ctm[3] * y1 + default_ctm[5];
x2 = default_ctm[0] * x2 + default_ctm[2] * y2 + default_ctm[4];
y2 = default_ctm[1] * x2 + default_ctm[3] * y2 + default_ctm[5];
// TODO: check overlap when x2-x1-width<0 or y2-y1-width<0
html_fout << "position:absolute;"
<< "left:" << _round(x1 - width/2) << "px;"
<< "bottom:" << _round(y1 - width/2) << "px;"
<< "width:" << _round(x2-x1-width) << "px;"
<< "height:" << _round(y2-y1-width) << "px;";
html_fout << "background-color:rgb(0,0,0,0);";
html_fout << "\"></div>";
if(dest_str != "")
{
html_fout << "</a>";
}
}
void HTMLRenderer::pre_process()
{
// we may output utf8 characters, so always use binary

334
src/HTMLRenderer/link.cc Normal file
View File

@ -0,0 +1,334 @@
/*
* link.cc
*
* Handling links
*
* by WangLu
* 2012.09.25
*/
#include <iostream>
#include <sstream>
#include <algorithm>
#include <HTMLRenderer.h>
#include <Link.h>
#include "namespace.h"
namespace pdf2htmlEX {
using std::ostringstream;
using std::min;
using std::max;
static void _transform(const double * ctm, double & x, double & y)
{
double xx = x, yy = y;
x = ctm[0] * xx + ctm[2] * yy + ctm[4];
y = ctm[1] * xx + ctm[3] * yy + ctm[5];
}
static void _get_transformed_rect(AnnotLink * link, const double * ctm, double & x1, double & y1, double & x2, double & y2)
{
double _x1, _x2, _y1, _y2;
link->getRect(&_x1, &_y1, &_x2, &_y2);
_transform(ctm, _x1, _y1);
_transform(ctm, _x2, _y2);
x1 = min(_x1, _x2);
x2 = max(_x1, _x2);
y1 = min(_y1, _y2);
y2 = max(_y1, _y2);
}
/*
* In PDF, edges of the rectangle are in the middle of the borders
* In HTML, edges are completely outside the rectangle
*/
static void _fix_border_width(double & x1, double & y1, double & x2, double & y2,
double border_width, double & border_top_bottom_width, double & border_left_right_width)
{
double w = x2 - x1;
if(w > border_width)
{
x1 += border_width / 2;
x2 -= border_width / 2;
border_left_right_width = border_width;
}
else
{
x1 += w / 2;
x2 -= w / 2;
border_left_right_width = border_width + w/2;
}
double h = y2 - y1;
if(h > border_width)
{
y1 += border_width / 2;
y2 -= border_width / 2;
border_top_bottom_width = border_width;
}
else
{
y1 += h / 2;
y2 -= h / 2;
border_top_bottom_width = border_width + h/2;
}
}
/*
* The detailed rectangle area of the link destination
* Will be parsed and performed by Javascript
*/
static string get_dest_detail_str(int pageno, LinkDest * dest)
{
ostringstream sout;
// dec
sout << "[" << pageno;
if(dest)
{
switch(dest->getKind())
{
case destXYZ:
{
sout << ",\"XYZ\",";
if(dest->getChangeLeft())
sout << (dest->getLeft());
else
sout << "null";
sout << ",";
if(dest->getChangeTop())
sout << (dest->getTop());
else
sout << "null";
sout << ",";
if(dest->getChangeZoom())
sout << (dest->getZoom());
else
sout << "null";
}
break;
case destFit:
sout << ",\"Fit\"";
break;
case destFitH:
sout << ",\"FitH\",";
if(dest->getChangeTop())
sout << (dest->getTop());
else
sout << "null";
break;
case destFitV:
sout << ",\"FitV\",";
if(dest->getChangeLeft())
sout << (dest->getLeft());
else
sout << "null";
break;
case destFitR:
sout << ",\"FitR\","
<< (dest->getLeft()) << ","
<< (dest->getBottom()) << ","
<< (dest->getRight()) << ","
<< (dest->getTop());
break;
case destFitB:
sout << ",\"FitB\"";
break;
case destFitBH:
sout << ",\"FitBH\",";
if(dest->getChangeTop())
sout << (dest->getTop());
else
sout << "null";
break;
case destFitBV:
sout << ",\"FitBV\",";
if(dest->getChangeLeft())
sout << (dest->getLeft());
else
sout << "null";
break;
default:
break;
}
}
sout << "]";
return sout.str();
}
/*
* Based on pdftohtml from poppler
* TODO: CSS for link rectangles
*/
void HTMLRenderer::processLink(AnnotLink * al)
{
std::string dest_str, dest_detail_str;
auto action = al->getAction();
if(action)
{
auto kind = action->getKind();
switch(kind)
{
case actionGoTo:
{
auto catalog = cur_doc->getCatalog();
auto * real_action = dynamic_cast<LinkGoTo*>(action);
LinkDest * dest = nullptr;
if(auto _ = real_action->getDest())
dest = _->copy();
else if (auto _ = real_action->getNamedDest())
dest = catalog->findDest(_);
if(dest)
{
int pageno = 0;
if(dest->isPageRef())
{
auto pageref = dest->getPageRef();
pageno = catalog->findPage(pageref.num, pageref.gen);
}
else
{
pageno = dest->getPageNum();
}
if(pageno > 0)
{
dest_str = (char*)str_fmt("#p%x", pageno);
dest_detail_str = get_dest_detail_str(pageno, dest);
}
delete dest;
}
}
break;
case actionGoToR:
{
cerr << "TODO: actionGoToR is not implemented." << endl;
}
break;
case actionURI:
{
auto * real_action = dynamic_cast<LinkURI*>(action);
dest_str = real_action->getURI()->getCString();
}
break;
case actionLaunch:
{
cerr << "TODO: actionLaunch is not implemented." << endl;
}
break;
default:
cerr << "Warning: unknown annotation type: " << kind << endl;
break;
}
}
if(dest_str != "")
{
html_fout << "<a class=\"a\" href=\"" << dest_str << "\"";
if(dest_detail_str != "")
html_fout << " data-dest-detail='" << dest_detail_str << "'";
html_fout << ">";
}
html_fout << "<div style=\"";
double x1, y1, x2, y2;
_get_transformed_rect(al, default_ctm, x1, y1, x2, y2);
double border_width = 0;
double border_top_bottom_width = 0;
double border_left_right_width = 0;
auto * border = al->getBorder();
if(border)
{
border_width = border->getWidth() * (param->zoom);
if(border_width > 0)
{
{
_fix_border_width(x1, y1, x2, y1,
border_width, border_top_bottom_width, border_left_right_width);
if(abs(border_top_bottom_width - border_left_right_width) < EPS)
html_fout << "border-width:" << _round(border_top_bottom_width) << "px;";
else
html_fout << "border-width:" << _round(border_top_bottom_width) << "px " << _round(border_left_right_width) << "px;";
}
auto style = border->getStyle();
switch(style)
{
case AnnotBorder::borderSolid:
html_fout << "border-style:solid;";
break;
case AnnotBorder::borderDashed:
html_fout << "border-style:dashed;";
break;
case AnnotBorder::borderBeveled:
html_fout << "border-style:outset;";
break;
case AnnotBorder::borderInset:
html_fout << "border-style:inset;";
break;
case AnnotBorder::borderUnderlined:
html_fout << "border-style:none;border-bottom-style:solid;";
break;
default:
cerr << "Warning:Unknown annotation border style: " << style << endl;
html_fout << "border-style:solid;";
}
auto color = al->getColor();
double r,g,b;
if(color && (color->getSpace() == AnnotColor::colorRGB))
{
const double * v = color->getValues();
r = v[0];
g = v[1];
b = v[2];
}
else
{
r = g = b = 0;
}
html_fout << "border-color:rgb("
<< dec << (int)dblToByte(r) << "," << (int)dblToByte(g) << "," << (int)dblToByte(b) << hex
<< ");";
}
else
{
html_fout << "border-style:none;";
}
}
else
{
html_fout << "border-style:none;";
}
html_fout << "position:absolute;"
<< "left:" << _round(x1- border_left_right_width) << "px;"
<< "bottom:" << _round(y1 - border_top_bottom_width) << "px;"
<< "width:" << _round(x2-x1) << "px;"
<< "height:" << _round(y2-y1) << "px;";
// fix for IE
html_fout << "background-color:rgba(255,255,255,0.000001);";
html_fout << "\"></div>";
if(dest_str != "")
{
html_fout << "</a>";
}
}
}// namespace pdf2htmlEX

View File

@ -33,11 +33,13 @@
*
* CSS classes
*
* b - page Box
* p - Page
* l - Line
* _ - white space
* a - Annot link
* b - page Box
* l - Line
* i - Image
* j - Js data
* p - Page
*
* Reusable CSS classes
*