From e2c9029600a53dd6adc36dbe104644a872bde51e Mon Sep 17 00:00:00 2001
From: Alexey Voytenko <alexvgml@gmail.com>
Date: Sat, 8 Feb 2014 19:21:15 +1100
Subject: [PATCH 1/5] outlines API initiated

---
 demo/out.pdf        | Bin 654166 -> 1767 bytes
 demo/test.coffee    |  94 +++++++-------------------------------------
 lib/document.coffee |  39 +++++++++++++++---
 lib/outline.coffee  |  34 ++++++++++++++++
 lib/page.coffee     |   5 ++-
 lib/store.coffee    |  41 +++++++++++++------
 6 files changed, 116 insertions(+), 97 deletions(-)
 create mode 100644 lib/outline.coffee

diff --git a/demo/out.pdf b/demo/out.pdf
index 3df9ad7699f79fa86db55956ca37fb662c44ddb0..541eef662eeedbc2ffb68454a1bfbe4f505d719b 100644
GIT binary patch
diff --git a/demo/test.coffee b/demo/test.coffee
index 97ff78d6..20bf48d2 100644
--- a/demo/test.coffee
+++ b/demo/test.coffee
@@ -1,92 +1,26 @@
 PDFDocument = require 'pdfkit'
-tiger = require './tiger'
+
+options = new Object
+options.hasOutlines = true
 
 # Create a new PDFDocument
-doc = new PDFDocument
+doc = new PDFDocument(options)
 
 # Set some meta data
 doc.info['Title'] = 'Test Document'
-doc.info['Author'] = 'Devon Govett'
+doc.info['Author'] = 'Alex'
 
-# Register a font name for use later
-doc.registerFont('Palatino', 'fonts/PalatinoBold.ttf')
+# Set the font, draw some text
+doc.fontSize(25)
+   .text('Hello, world!', 100, 100)
 
-# Set the font, draw some text, and embed an image
-doc.font('Palatino')
-   .fontSize(25)
-   .text('Some text with an embedded font!', 100, 100)
-   .fontSize(18)
-   .text('PNG and JPEG images:')
-   .image('images/test.png', 100, 160, width: 412)
-   .image('images/test.jpeg', 190, 400, height: 300)
+doc.addOutline("First page", doc.page)
 
 # Add another page
 doc.addPage()
-   .fontSize(25)
-   .text 'Here is some vector graphics...', 100, 100
-
-# Draw a triangle and a circle
-doc.save()
-   .moveTo(100, 150)
-   .lineTo(100, 250)
-   .lineTo(200, 250)
-   .fill("#FF3300")
-   
-doc.circle(280, 200, 50)
-   .fill("#6600FF")
-
-doc.scale(0.6)
-   .translate(470, -380)
-   .path('M 250,75 L 323,301 131,161 369,161 177,301 z') # render an SVG path
-   .fill('red', 'even-odd') # fill using the even-odd winding rule
-   .restore()
-   
-loremIpsum = '''
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta. Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi, ut tincidunt lacus arcu eu elit. Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
-Mauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget gravida ante hendrerit. Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. Curabitur tempor massa tortor.
-'''
-
-# Draw some text wrapped to 412 points wide
-doc.text('And here is some wrapped text...', 100, 300)
-   .font('Helvetica', 13)
-   .moveDown() # move down 1 line
-   .text(loremIpsum, width: 412, align: 'justify', indent: 30, paragraphGap: 5)
-
-# Add another page, and set the font back   
-doc.addPage()
-   .font('Palatino', 25)
-   .text('Rendering some SVG paths...', 100, 100)
-   .translate(220, 300)
-
-# Render each path that makes up the tiger image
-for part in tiger
-    doc.save()
-    doc.path(part.path) # render an SVG path
-    
-    if part['stroke-width']
-        doc.lineWidth part['stroke-width']
-    
-    if part.fill isnt 'none' and part.stroke isnt 'none'
-        doc.fillAndStroke(part.fill, part.stroke)
-    else
-        unless part.fill is 'none'
-            doc.fill(part.fill)
-            
-        unless part.stroke is 'none'
-            doc.stroke(part.stroke)
-            
-    doc.restore()
-
-# Add some text with annotations            
-doc.addPage()
-   .fillColor("blue")
-   .text('Here is a link!', 100, 100)
-   .underline(100, 100, 160, 27, color: "#0000FF")
-   .link(100, 100, 160, 27, 'http://google.com/')
+doc.fontSize(10)
+doc.text('This is a second page.', 100, 100)
 
-# Add a list with a font loaded from a TrueType collection file   
-doc.fillColor('#000')
-   .font('fonts/Chalkboard.ttc', 'Chalkboard', 16)
-   .list(['One', 'Two', 'Three'], 100, 150)
-        
-doc.write 'out.pdf'
\ No newline at end of file
+doc.addOutline("Second page", doc.page);
+  
+doc.write 'out.pdf'
diff --git a/lib/document.coffee b/lib/document.coffee
index 353bdbe5..3020c6b5 100644
--- a/lib/document.coffee
+++ b/lib/document.coffee
@@ -8,6 +8,7 @@ PDFObjectStore = require './store'
 PDFObject = require './object'
 PDFReference = require './reference'
 PDFPage = require './page'
+PDFOutline = require './outline'
 
 class PDFDocument
     constructor: (@options = {}) ->
@@ -15,17 +16,20 @@ class PDFDocument
         @version = 1.3
         
         # Whether streams should be compressed
-        @compress = yes
+        @compress = no
         
         # The PDF object store
-        @store = new PDFObjectStore
+        @store = new PDFObjectStore(@options)
         
         # A list of pages in this document
         @pages = []
         
         # The current page
         @page = null
-        
+         
+        # A list of outlines in this document
+        @outlines = []
+
         # Initialize mixins
         @initColor()
         @initVector()
@@ -78,6 +82,31 @@ class PDFDocument
         @transform 1, 0, 0, -1, 0, @page.height
         
         return this
+
+    addOutline: (title, dest, options = @options) ->
+
+        if not options.hasOutlines
+            console.log "this document does not have outlines"
+            return this
+
+        # create an outline object
+        @outline = new PDFOutline(this, title, dest, options)
+
+        # insert into outlines store list
+        if @outlines.length == 0
+            @store.outlines.data['First'] = @outline.dictionary
+        else
+            prev = @outlines[@outlines.length-1]
+            prev.dictionary.data['Next'] = @outline.dictionary
+            @store.outlines.data['Last'] = @outline.dictionary
+
+        # add to the outlines list
+        @outlines.push @outline
+
+        # add the outline to the object store
+        @store.addOutline @outline
+
+        return this
         
     ref: (data) ->
         @store.ref(data)
@@ -85,7 +114,7 @@ class PDFDocument
     addContent: (str) ->
         @page.content.add str
         return this # make chaining possible
-        
+           
     write: (filename, fn) ->
         @output (out) ->
             fs.writeFile filename, out, 'binary', fn
@@ -127,7 +156,7 @@ class PDFDocument
         # 4 binary chars, as recommended by the spec
         out.push "%\xFF\xFF\xFF\xFF\n"
         return out
-        
+
     generateBody: (out, fn) ->
         offset = out.join('\n').length
         
diff --git a/lib/outline.coffee b/lib/outline.coffee
new file mode 100644
index 00000000..3f125acb
--- /dev/null
+++ b/lib/outline.coffee
@@ -0,0 +1,34 @@
+###
+PDFOutline - represents a table of contents in the PDF 1.3 document
+By Alex
+###
+
+PDFObject = require './object'
+
+class PDFOutline
+    constructor: (@document, title, dest, options = {}) ->
+
+        @title  = title
+        @dest   = dest
+        @length = @document.outlines.length
+
+        # The outline dictionary - link to the objects in the document
+        if @length == 0
+            @dictionary = @document.ref
+                Title:  PDFObject.s @title
+                Parent: @document.store.outlines
+                Dest:   [@dest.dictionary, 'XYZ', 0, 792, 0]
+        else
+            @dictionary = @document.ref
+                Title:  PDFObject.s @title
+                Parent: @document.store.outlines
+                Prev:   @document.outlines[@length-1].dictionary
+                Dest:   [@dest.dictionary, 'XYZ', 0, 792, 0]
+
+        #optional: a link to the nested outline sublevel
+
+    # seems nothing to finalize
+    # finalize: (fn) ->
+    #    @content.finalize(@document.compress, fn)
+
+module.exports = PDFOutline
diff --git a/lib/page.coffee b/lib/page.coffee
index 553f2919..ba3f4800 100644
--- a/lib/page.coffee
+++ b/lib/page.coffee
@@ -5,9 +5,12 @@ By Devon Govett
 
 class PDFPage
     constructor: (@document, options = {}) ->
+
         @size = options.size or 'letter'
         @layout = options.layout or 'portrait'
         
+        @title = options.title
+        
         # if margin was passed as a single number
         if typeof options.margin is 'number'
             @margins = 
@@ -118,4 +121,4 @@ class PDFPage
         LETTER: [612.00, 792.00]
         TABLOID: [792.00, 1224.00]
         
-module.exports = PDFPage
\ No newline at end of file
+module.exports = PDFPage
diff --git a/lib/store.coffee b/lib/store.coffee
index 080f3fc9..a51a6a01 100644
--- a/lib/store.coffee
+++ b/lib/store.coffee
@@ -6,19 +6,34 @@ By Devon Govett
 PDFReference = require './reference'
 
 class PDFObjectStore
-    constructor: ->
+    constructor: (@options = {})->
+
         @objects = {}
         @length = 0
-                
-        @root = @ref
-            Type: 'Catalog'
-            Pages: @ref
-                Type: 'Pages'
-                Count: 0
-                Kids: []
-            
+        
+        if @options? and @options.hasOutlines
+            @root = @ref
+                Type: 'Catalog'
+                Pages: @ref
+                    Type: 'Pages'
+                    Count: 0
+                    Kids: []
+                Outlines: @ref
+                    Type: 'Outlines'
+                    Count: 0
+                PageMode: 'UseOutlines'
+        else
+            @root = @ref
+                Type: 'Catalog'
+                Pages: @ref
+                    Type: 'Pages'
+                    Count: 0
+                    Kids: []
+        
         @pages = @root.data['Pages']
-            
+
+        @outlines = @root.data['Outlines']        
+    
     ref: (data) ->
         @push ++@length, data
         
@@ -30,5 +45,9 @@ class PDFObjectStore
     addPage: (page) ->
         @pages.data['Kids'].push(page.dictionary)
         @pages.data['Count']++
+
+    addOutline: (outline) ->
+        @outlines.data['Count']++
+
         
-module.exports = PDFObjectStore
\ No newline at end of file
+module.exports = PDFObjectStore

From 6d8b94d5ee85a15e084ebf019ebfb0d59a083e0d Mon Sep 17 00:00:00 2001
From: Alexey Voytenko <alexvgml@gmail.com>
Date: Wed, 19 Feb 2014 13:32:31 +1100
Subject: [PATCH 2/5] separate test for outlines

---
 demo/out_outlines.pdf     | Bin 0 -> 1767 bytes
 demo/test_outlines.coffee |  26 ++++++++++++++++++++++++++
 2 files changed, 26 insertions(+)
 create mode 100644 demo/out_outlines.pdf
 create mode 100644 demo/test_outlines.coffee

diff --git a/demo/out_outlines.pdf b/demo/out_outlines.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..541eef662eeedbc2ffb68454a1bfbe4f505d719b
GIT binary patch
diff --git a/demo/test_outlines.coffee b/demo/test_outlines.coffee
new file mode 100644
index 00000000..9a7a77ea
--- /dev/null
+++ b/demo/test_outlines.coffee
@@ -0,0 +1,26 @@
+PDFDocument = require 'pdfkit'
+
+options = new Object
+options.hasOutlines = true
+
+# Create a new PDFDocument
+doc = new PDFDocument(options)
+
+# Set some meta data
+doc.info['Title'] = 'Test Document'
+doc.info['Author'] = 'Alex'
+
+# Set the font, draw some text
+doc.fontSize(25)
+   .text('Hello, world!', 100, 100)
+
+doc.addOutline("First page", doc.page)
+
+# Add another page
+doc.addPage()
+doc.fontSize(10)
+doc.text('This is a second page.', 100, 100)
+
+doc.addOutline("Second page", doc.page);
+  
+doc.write 'out_outlines.pdf'

From 2af94a53f64bbfcf21ff929f0b6dd5da591de65e Mon Sep 17 00:00:00 2001
From: Alexey Voytenko <alexvgml@gmail.com>
Date: Sun, 2 Mar 2014 22:06:09 +1100
Subject: [PATCH 3/5] outline points to the text position

---
 lib/document.coffee | 10 +++++-----
 lib/outline.coffee  | 26 +++++++++++++-------------
 lib/store.coffee    |  4 ++--
 3 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/lib/document.coffee b/lib/document.coffee
index 9c369112..643c5ef0 100644
--- a/lib/document.coffee
+++ b/lib/document.coffee
@@ -14,7 +14,7 @@ class PDFDocument
     constructor: (@options = {}) ->
         # PDF version
         @version = 1.3
-        
+       
         # Whether streams should be compressed
         @compress = no
         
@@ -83,14 +83,14 @@ class PDFDocument
         
         return this
 
-    addOutline: (title, dest, options = @options) ->
-
-        if not options.hasOutlines
+    addOutline: (title, dest, unicode) ->
+ 
+        if not @options.hasOutlines
             console.log "this document does not have outlines"
             return this
 
         # create an outline object
-        @outline = new PDFOutline(this, title, dest, options)
+        @outline = new PDFOutline(this, title, dest, unicode)
 
         # insert into outlines store list
         if @outlines.length == 0
diff --git a/lib/outline.coffee b/lib/outline.coffee
index 3f125acb..c868185e 100644
--- a/lib/outline.coffee
+++ b/lib/outline.coffee
@@ -4,31 +4,31 @@ By Alex
 ###
 
 PDFObject = require './object'
+PDFPage   = require './page'
 
 class PDFOutline
-    constructor: (@document, title, dest, options = {}) ->
+    constructor: ( @document, @title, @dest, @unicode ) ->
 
-        @title  = title
-        @dest   = dest
-        @length = @document.outlines.length
+        @length  = @document.outlines.length
+
+        # XY - coordinates of page top left in user space system,
+        # Z  - zoom factor (null for X,Y,Z - value unchanged)
+        y = @document.page.height - @document.y
+        destination = [@dest.dictionary, 'XYZ', 0, y, null]
 
         # The outline dictionary - link to the objects in the document
         if @length == 0
             @dictionary = @document.ref
-                Title:  PDFObject.s @title
+                Title:  PDFObject.s @title, @unicode
                 Parent: @document.store.outlines
-                Dest:   [@dest.dictionary, 'XYZ', 0, 792, 0]
+                Dest:   destination
         else
             @dictionary = @document.ref
-                Title:  PDFObject.s @title
+                Title:  PDFObject.s @title, @unicode
                 Parent: @document.store.outlines
                 Prev:   @document.outlines[@length-1].dictionary
-                Dest:   [@dest.dictionary, 'XYZ', 0, 792, 0]
-
-        #optional: a link to the nested outline sublevel
+                Dest:   destination
 
-    # seems nothing to finalize
-    # finalize: (fn) ->
-    #    @content.finalize(@document.compress, fn)
+        #todo: a link to the nested outline sublevel
 
 module.exports = PDFOutline
diff --git a/lib/store.coffee b/lib/store.coffee
index a51a6a01..dcbcc6f6 100644
--- a/lib/store.coffee
+++ b/lib/store.coffee
@@ -10,8 +10,8 @@ class PDFObjectStore
 
         @objects = {}
         @length = 0
-        
-        if @options? and @options.hasOutlines
+
+        if @options.hasOutlines 
             @root = @ref
                 Type: 'Catalog'
                 Pages: @ref

From 1de159010c6882956ed60571019582add7c6a45b Mon Sep 17 00:00:00 2001
From: Alexey Voytenko <alexvgml@gmail.com>
Date: Wed, 26 Mar 2014 23:55:51 +1100
Subject: [PATCH 4/5] levels of outlines

---
 demo/out_outlines.pdf     | Bin 1767 -> 9809 bytes
 demo/test_outlines.coffee |  85 +++++++++++++++++++++++++------
 lib/document.coffee       | 103 +++++++++++++++++++++++++++++++-------
 lib/outline.coffee        |  68 ++++++++++++++++---------
 lib/store.coffee          |  35 +++++--------
 5 files changed, 210 insertions(+), 81 deletions(-)

diff --git a/demo/out_outlines.pdf b/demo/out_outlines.pdf
index 541eef662eeedbc2ffb68454a1bfbe4f505d719b..7a05e389a04352b09fd48be595da4d25fa7cf3eb 100644
GIT binary patch
diff --git a/demo/test_outlines.coffee b/demo/test_outlines.coffee
index 9a7a77ea..b75a1e5a 100644
--- a/demo/test_outlines.coffee
+++ b/demo/test_outlines.coffee
@@ -1,26 +1,79 @@
 PDFDocument = require 'pdfkit'
 
-options = new Object
-options.hasOutlines = true
+docOptions = {
+         hasOutlines: true,   # required for outlines
+         margins: { top : 40, left : 40, bottom : 40, right : 30 } }
+
 
 # Create a new PDFDocument
-doc = new PDFDocument(options)
+doc = new PDFDocument(docOptions)
+doc.fontSize(16)
+title = 'Test Document With Outlines'
+
+# add outline on current level (0), with outline options: 
+# 'FitH' - top coordinate is positioned at the top edge of the window,
+#          document fits the entire width.
+# Can be any type according to PDF 1.3 specification, 7.2.1 Destinations (Table 7.2)
+doc.addOutline( title, doc.page, { type: 'FitH', top : 0 } )
+
+# text to point by outline should be added next
+doc.text(title, { paragraphGap : 8, align : 'center' } )
+
+doc.fontSize(12)  # usual text
+
+doc.info['Title']  = title
+doc.info['Author'] = 'Alexey Voytenko'
+
+loremIpsum_0_1 = 'LOREM IPSUM DOLOR.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam in suscipit purus.
+ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; 
+ Vivamus nec hendrerit felis. Morbi aliquam facilisis risus eu lacinia. Sed eu leo in turpis fringilla hendrerit. Ut nec accumsan nisl. 
+  Suspendisse rhoncus nisl posuere tortor tempus et dapibus elit porta.'
+
+loremIpsum_1_1 = 'CRAS LEO NEQUE.
+  Cras leo neque, elementum a rhoncus ut, vestibulum non nibh. Phasellus pretium justo turpis. 
+  Etiam vulputate, odio vitae tincidunt ultricies, eros odio dapibus nisi,
+   ut tincidunt lacus arcu eu elit.'
+
+loremIpsum_1_2 = 'AENEAN VELIT ERAT.
+Aenean velit erat, vehicula eget lacinia ut, dignissim non tellus. 
+Aliquam nec lacus mi, sed vestibulum nunc. Suspendisse potenti. Curabitur vitae sem turpis. 
+Vestibulum sed neque eget dolor dapibus porttitor at sit amet sem. Fusce a turpis lorem. 
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
+Mauris at ante tellus. Vestibulum a metus lectus. Praesent tempor purus a lacus blandit eget 
+gravida ante hendrerit.'
+
+loremIpsum_0_2 = 'CRAS ET EROS METUS.
+Cras et eros metus. Sed commodo malesuada eros, vitae interdum augue semper quis. 
+Fusce id magna nunc. Curabitur sollicitudin placerat semper. Cras et mi neque, a dignissim risus. 
+Nulla venenatis porta lacus, vel rhoncus lectus tempor vitae. Duis sagittis venenatis rutrum. 
+Curabitur tempor massa tortor.'
+
+# add outline on current level (0) with default options - 'XYZ', positioning on text
+doc.addOutline('Lorem ipsum dolor', doc.page);
+
+doc.text( loremIpsum_0_1,
+   { paragraphGap : 2, indent : 30, align : 'justify' } )
+
+# create next level (1) of table of contents and add outline
+doc.addSublevelOutline('Cras leo neque', doc.page);
+doc.text( loremIpsum_1_1, { paragraphGap : 2, indent : 30, align : 'justify' } );
+
+# add outline on the same level 1 with positioning on text
+doc.addOutline('Aenean velit erat', doc.page );
+
+doc.text( loremIpsum_1_2, { paragraphGap : 2, indent : 30, align : 'justify' } );
 
-# Set some meta data
-doc.info['Title'] = 'Test Document'
-doc.info['Author'] = 'Alex'
+# go up in outlines levels
+doc.endOutlineSublevel();
 
-# Set the font, draw some text
-doc.fontSize(25)
-   .text('Hello, world!', 100, 100)
+outl_options = { type: 'XYZ', unicode: true }
 
-doc.addOutline("First page", doc.page)
+# add outline on the level 0 with positioning on text ('XYZ' is by default), 
+# using non-ASCII characters in outline title: set unicode in this case 
+doc.addOutline('Тестовый заголовок', doc.page, outl_options);
 
-# Add another page
-doc.addPage()
-doc.fontSize(10)
-doc.text('This is a second page.', 100, 100)
+doc.text( loremIpsum_0_2, { paragraphGap : 2, indent : 30, align : 'justify' } );
 
-doc.addOutline("Second page", doc.page);
-  
 doc.write 'out_outlines.pdf'
+console.log "out_outlines.pdf created" 
diff --git a/lib/document.coffee b/lib/document.coffee
index 643c5ef0..b993cc76 100644
--- a/lib/document.coffee
+++ b/lib/document.coffee
@@ -27,9 +27,15 @@ class PDFDocument
         # The current page
         @page = null
          
-        # A list of outlines in this document
-        @outlines = []
-
+        # Outlines
+        @outlines = []  # Generally, a list which nodes can be 
+                        # lists itself - sublevel outline containers
+        @root_outlines = @store.outlines
+        @cur       = @outlines  # link to the current  level list
+        @levelHead = @root_outlines  # the head node of current outlines list
+        @levs  = []  # stack of list heads to go up
+        @prevs = []  # stack of nested levels containers to go up
+      
         # Initialize mixins
         @initColor()
         @initVector()
@@ -63,7 +69,7 @@ class PDFDocument
     mixin 'text'
     mixin 'images'
     mixin 'annotations'
-        
+
     addPage: (options = @options) ->
         # create a page object
         @page = new PDFPage(this, options)
@@ -83,28 +89,89 @@ class PDFDocument
         
         return this
 
-    addOutline: (title, dest, unicode) ->
+    isRoot: () ->
+        return @prevs.length == 0
+
+    addOutline: (title, dest, options) ->
  
         if not @options.hasOutlines
-            console.log "this document does not have outlines"
             return this
 
+        prevNode = null
+        if @isRoot()
+            parent = @root_outlines
+        else
+            parent = @levelHead.dictionary
+
+        if @cur.length > 0  # can be on Root level
+            prevNode = @cur[@cur.length-1]
+            if prevNode instanceof Array # prev is a sublevel list
+                prevNode = @cur[@cur.length-2]  # there *must* be an element before
+
         # create an outline object
-        @outline = new PDFOutline(this, title, dest, unicode)
+        outline = new PDFOutline(this, parent, title, dest, options)
+        
+        # update list header info
+        if @outlines.length == 0  # init root level list
+            @root_outlines.data['First'] = outline.dictionary
 
-        # insert into outlines store list
-        if @outlines.length == 0
-            @store.outlines.data['First'] = @outline.dictionary
-        else
-            prev = @outlines[@outlines.length-1]
-            prev.dictionary.data['Next'] = @outline.dictionary
-            @store.outlines.data['Last'] = @outline.dictionary
+        parent.data['Last'] = outline.dictionary   # can be root level
+        parent.data['Count']++
 
-        # add to the outlines list
-        @outlines.push @outline
+        if prevNode != null   # not first in the list
+            outline.dictionary.data['Prev']  = prevNode.dictionary
+            prevNode.dictionary.data['Next'] = outline.dictionary
 
-        # add the outline to the object store
-        @store.addOutline @outline
+        # add to the current level list
+        @cur.push outline
+
+        return this
+
+    addSublevelOutline: (title, dest, options) ->
+
+        if not @options.hasOutlines
+            return this
+
+        if @cur.length == 0 or ( @cur[@cur.length-1] instanceof Array )
+            console.log("add current level outline first")
+            return this
+
+        @levs.push @levelHead
+
+        @levelHead = @cur[@cur.length-1]  # head of sublevel list
+        @prevs.push @cur  # remember up level container
+        @cur.push []      # add sublevel container
+        @cur = @cur[@cur.length-1]
+
+        outline = new PDFOutline(this, @levelHead.dictionary, title, dest, options)
+
+        @cur.push outline
+        @levelHead.dictionary.data['First'] = outline.dictionary
+        @levelHead.dictionary.data['Last']  = outline.dictionary
+        @levelHead.dictionary.data['Count'] = 1  # important: not ++ (field is absent now)
+
+        return this
+
+    endOutlineSublevel: () ->
+
+        if not @options.hasOutlines
+            return this
+
+        if @isRoot()
+            console.log("cannot end root level")
+            return this
+
+        # restore previous list as current
+        @cur  = @prevs.pop()
+
+        count = @levelHead.dictionary.data['Count']
+        @levelHead = @levs.pop()
+
+        # add child sublist counter (will contain all child counters)
+        if @isRoot()
+            @levelHead.data['Count'] += count
+        else
+            @levelHead.dictionary.data['Count'] += count
 
         return this
         
diff --git a/lib/outline.coffee b/lib/outline.coffee
index c868185e..4a924f2d 100644
--- a/lib/outline.coffee
+++ b/lib/outline.coffee
@@ -1,34 +1,54 @@
 ###
 PDFOutline - represents a table of contents in the PDF 1.3 document
-By Alex
+By Alexey Voytenko
+see also methods addOutline, addSublevelOutline, endOutlineSublevel
+of PDFDocument class
 ###
 
 PDFObject = require './object'
 PDFPage   = require './page'
 
 class PDFOutline
-    constructor: ( @document, @title, @dest, @unicode ) ->
-
-        @length  = @document.outlines.length
-
-        # XY - coordinates of page top left in user space system,
-        # Z  - zoom factor (null for X,Y,Z - value unchanged)
-        y = @document.page.height - @document.y
-        destination = [@dest.dictionary, 'XYZ', 0, y, null]
-
-        # The outline dictionary - link to the objects in the document
-        if @length == 0
-            @dictionary = @document.ref
-                Title:  PDFObject.s @title, @unicode
-                Parent: @document.store.outlines
-                Dest:   destination
-        else
-            @dictionary = @document.ref
-                Title:  PDFObject.s @title, @unicode
-                Parent: @document.store.outlines
-                Prev:   @document.outlines[@length-1].dictionary
-                Dest:   destination
-
-        #todo: a link to the nested outline sublevel
+    constructor: ( @document, parent, @title, @dest, @options = {} ) ->
+
+        @type    = @options.type or 'XYZ'
+        @unicode = @options.unicode or false
+
+        # to convert library y coordinate to requred user space coordinate
+        height = @document.page.height
+
+        # see PDFReference 1.3, 7.2.1 Destinations
+        switch @type  
+            when 'Fit', 'FitB'
+                destination = [@dest.dictionary, @type]
+
+            when 'FitH', 'FitBH'
+                top = height - ( @options.top or 0 )
+                destination = [@dest.dictionary, @type, top]
+
+            when 'FitV', 'FitBV'
+                top = height - ( @options.left or 0 )
+                destination = [@dest.dictionary, @type, left]
+
+            when 'FitR'
+                left   = @options.left or 0
+                bottom = height - ( @options.bottom or 0 )
+                right  = @options.right or 0
+                top    = height - ( @options.top or 0 )
+                destination = [@dest.dictionary, @type, left, bottom, right, top]
+
+            else  # assuming 'XYZ' - most common
+                # XY - coordinates of page top left in user space system,
+                # Z  - zoom factor (null for X,Y,Z - previous value unchanged)
+                X = @options.X or 0
+                Y = height - ( @options.Y or @document.y )
+                Z = @options.Z or null
+                destination = [@dest.dictionary, @type, X, Y, Z]
+
+        # The outline dictionary
+        @dictionary = @document.ref
+            Title:  PDFObject.s @title, @unicode  # need to specify if unicode
+            Parent: parent      # dictionary
+            Dest:   destination
 
 module.exports = PDFOutline
diff --git a/lib/store.coffee b/lib/store.coffee
index dcbcc6f6..febcb7f6 100644
--- a/lib/store.coffee
+++ b/lib/store.coffee
@@ -2,7 +2,7 @@
 PDFObjectStore - stores the object heirarchy for the PDF document
 By Devon Govett
 ###
-
+ 
 PDFReference = require './reference'
 
 class PDFObjectStore
@@ -11,27 +11,20 @@ class PDFObjectStore
         @objects = {}
         @length = 0
 
-        if @options.hasOutlines 
-            @root = @ref
-                Type: 'Catalog'
-                Pages: @ref
-                    Type: 'Pages'
-                    Count: 0
-                    Kids: []
-                Outlines: @ref
+        @root = @ref
+            Type: 'Catalog'
+            Pages: @ref
+                Type: 'Pages'
+                Count: 0
+                Kids: []
+
+        if @options.hasOutlines
+            @root.data['PageMode'] = 'UseOutlines'
+            @root.data['Outlines'] = @ref
                     Type: 'Outlines'
                     Count: 0
-                PageMode: 'UseOutlines'
-        else
-            @root = @ref
-                Type: 'Catalog'
-                Pages: @ref
-                    Type: 'Pages'
-                    Count: 0
-                    Kids: []
-        
-        @pages = @root.data['Pages']
 
+        @pages    = @root.data['Pages']
         @outlines = @root.data['Outlines']        
     
     ref: (data) ->
@@ -45,9 +38,5 @@ class PDFObjectStore
     addPage: (page) ->
         @pages.data['Kids'].push(page.dictionary)
         @pages.data['Count']++
-
-    addOutline: (outline) ->
-        @outlines.data['Count']++
-
         
 module.exports = PDFObjectStore

From 3410835e165e6139ead1177801957b210987f6a9 Mon Sep 17 00:00:00 2001
From: Alexey Voytenko <alexvgml@gmail.com>
Date: Thu, 27 Mar 2014 16:12:26 +1100
Subject: [PATCH 5/5] compress enabled

---
 lib/document.coffee | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/document.coffee b/lib/document.coffee
index 2e0d7e7a..bf5696cb 100644
--- a/lib/document.coffee
+++ b/lib/document.coffee
@@ -18,7 +18,7 @@ class PDFDocument extends stream.Readable
     @version = 1.3
     
     # Whether streams should be compressed
-    @compress = no  # yes
+    @compress = yes
     
     # The PDF object store
     @_offsets = []
