Skip to content

Commit

Permalink
Initial support for outlines (table of contents)
Browse files Browse the repository at this point in the history
  • Loading branch information
Flamenco committed Nov 11, 2014
1 parent 970f0fd commit a6d7af4
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 1 deletion.
20 changes: 19 additions & 1 deletion jspdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,17 @@ var jsPDF = (function(global) {
out(objectNumber + ' 0 obj');
return objectNumber;
},
// Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data
newObjectDeferred = function() {
objectNumber++;
offsets[objectNumber] = function(){
return content_length;
};
return objectNumber;
},
newObjectDeferredBegin = function(oid) {
offsets[oid] = content_length;
},
putStream = function(str) {
out('stream');
out(str);
Expand Down Expand Up @@ -770,7 +781,12 @@ var jsPDF = (function(global) {
out('0 ' + (objectNumber + 1));
out(p+' 65535 f ');
for (i = 1; i <= objectNumber; i++) {
out((p + offsets[i]).slice(-10) + ' 00000 n ');
var offset = offsets[i];
if (typeof offset === 'function'){
out((p + offsets[i]()).slice(-10) + ' 00000 n ');
}else{
out((p + offsets[i]).slice(-10) + ' 00000 n ');
}
}
// Trailer
out('trailer');
Expand Down Expand Up @@ -922,6 +938,8 @@ var jsPDF = (function(global) {
},
'collections' : {},
'newObject' : newObject,
'newObjectDeferred' : newObjectDeferred,
'newObjectDeferredBegin' : newObjectDeferredBegin,
'putStream' : putStream,
'events' : events,
// ratio that you use in multiplication of a given "size" number to arrive to 'point'
Expand Down
162 changes: 162 additions & 0 deletions jspdf.plugin.outline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* jsPDF Outline PlugIn
* Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/

/**
* Generates a PDF Outline
*/
;
(function(jsPDFAPI) {
'use strict';

jsPDFAPI.initOutlinePlugin = function() {

this.outline = {};
this.outline.root = {
children : []
};

this.internal.events.subscribe('postPutResources', function() {
if (this.outline.root.children.length > 0) {
var lines = this.outline.render().split(/\r\n/);
for (var int = 0; int < lines.length; int++) {
var line = lines[int];
if (line.endsWith('obj')){
var oid = line.split(' ')[0];
this.internal.newObjectDeferredBegin(oid);
}
this.internal.write(line);
}
}
});

this.internal.events.subscribe('putCatalog', function() {
if (this.outline.root.children.length > 0) {
this.internal.write("/Outlines", this.outline.makeRef(this.outline.root));
}
});

/**
* Options: pageNumber
*/
this.outline.add = function(parent,title,options) {
var item = {
title : title,
options : options,
children : []
};
if (parent == null) {
parent = this.root;
}
parent.children.push(item);
return item;
}

this.outline.render = function() {
this.outline.ctx = {};
this.outline.nextId = 1000;
this.outline.ctx.val = '';
this.outline.ctx.pdf = this;

this.outline.genIds_r(this.outline.root);
this.outline.renderRoot(this.outline.root);
this.outline.renderItems(this.outline.root);

return this.outline.ctx.val;
}.bind(this);

this.outline.genIds_r = function(node) {
node.id = this.internal.newObjectDeferred();
for (var i = 0; i < node.children.length; i++) {
this.outline.genIds_r(node.children[i]);
}
}.bind(this);

this.outline.renderRoot = function(node) {
this.objStart(node);
this.line('/Type /Outlines');
if (node.children.length > 0) {
this.line('/First ' + this.makeRef(node.children[0]));
this.line('/Last ' + this.makeRef(node.children[node.children.length - 1]));
}
this.line('/Count ' + this.count_r({
count : 0
}, node));
this.objEnd();
}

this.outline.renderItems = function(node) {
for (var i = 0; i < node.children.length; i++) {
var item = node.children[i];
this.objStart(item);

this.line('/Title ' + this.makeString(item.title));

this.line('/Parent ' + this.makeRef(node));
if (i > 0) {
this.line('/Prev ' + this.makeRef(node.children[i - 1]));
}
if (i < node.children.length - 1) {
this.line('/Next ' + this.makeRef(node.children[i + 1]));
}
if (item.children.length > 0) {
this.line('/First ' + this.makeRef(item.children[0]));
this.line('/Last ' + this.makeRef(item.children[item.children.length - 1]));
}

var count = this.count = this.count_r({
count : 0
}, item);
if (count > 0) {
this.line('/Count ' + count);
}

if (item.options) {
if (item.options.pageNumber) {
this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /XYZ 0 ' + this.ctx.pdf.internal.pageSize.height + ' 0]');
//this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /Fit]');
}
}
this.objEnd();
}
for (var i = 0; i < node.children.length; i++) {
var item = node.children[i];
this.renderItems(item);
}
}

this.outline.line = function(text) {
this.ctx.val += text + '\r\n';
}

this.outline.makeRef = function(node) {
return node.id + ' 0 R';
}

this.outline.makeString = function(val) {
return '(' + this.internal.pdfEscape(val) + ')';
}.bind(this);

this.outline.objStart = function(node) {
this.ctx.val += '\r\n' + node.id + ' 0 obj' + '\r\n<<\r\n';
}

this.outline.objEnd = function(node) {
this.ctx.val += '>> \r\n' + 'endobj' + '\r\n';
}

this.outline.count_r = function(ctx,node) {
for (var i = 0; i < node.children.length; i++) {
ctx.count++;
this.count_r(ctx, node.children[i]);
}
return ctx.count;
}
}

return this;
})(jsPDF.API);
88 changes: 88 additions & 0 deletions test/test_outline.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!doctype html>
<!--
/**
* jsPDF Outline PlugIn
* Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv
*
* Licensed under the MIT License.
* http://opensource.org/licenses/mit-license
*/
-->

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">

<title>Outline Test</title>

<script type="text/javascript" src="../jspdf.js"></script>
<!-- the plugin to test -->
<script type="text/javascript" src="../jspdf.plugin.outline.js"></script>

<script>
window.onload = function() {

var pdf = new jsPDF('p', 'pt', 'letter');
pdf.text(20, 20, 'Hello');
pdf.addPage();
pdf.text(20, 20, 'PDF');
pdf.addPage();
pdf.text(20, 20, 'World');

pdf.addPage();
pdf.text(20, 20, 'More');

pdf.initOutlinePlugin();
var node = pdf.outline.add(null, 'Test Pages', null);
pdf.outline.add(node, 'Hello', {pageNumber:1});
pdf.outline.add(node, 'PDF', {pageNumber:2});
pdf.outline.add(node, 'World', {pageNumber:3});

var node = pdf.outline.add(null, 'More Pages', null);
pdf.outline.add(node, 'More', {pageNumber:4});

// generate either the pdf or source code
if (getParameterByName('src') != 'true'){
var frame = document.getElementById('pdfframe');
frame.src = pdf.output('datauristring');
}
else{
var src = pdf.outline.render();
var src = pdf.output();
var content = document.getElementById('sourcecode');
raw = escapeHtml(src);
content.innerHTML = raw;
}

}

var entityMap = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': '&quot;',
"'": '&#39;',
"/": '&#x2F;'
};

function escapeHtml(string) {
return String(string).replace(/[&<>"'\/]/g, function (s) {
return entityMap[s];
});
}

function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
</script>

</head>

<body style='background-color: silver; margin: 0;'>
<pre id='sourcecode'></pre>
<iframe id='pdfframe' style='width: 100%; height: 100%; position: absolute'></iframe>
</body>
</html>

0 comments on commit a6d7af4

Please sign in to comment.