Merging #581 messes up test case numbered links
This is really bad ...

Unicode vs Bytes stuff ... gripes... :-/

diff --git a/.gitignore b/.gitignore
index d4e4df71..cf7afbbf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ __pycache__
 bin/
 develop-eggs/
 eggs/
+.coverage
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 78fb6a95..0b1ed5c6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,5 +10,4 @@ before_install:
  - ./bin/buildout
 
 script:
- - nosetests -x -i regulartest -i sphinxtest
- 
+ - nosetests -i regulartest -i sphinxtest
diff --git a/bootstrap.py b/bootstrap.py
index a5454777..dbfe96b4 100644
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -19,8 +19,13 @@ use the -c option to specify an alternate configuration file.
 
 $Id$
 """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
 
-import os, shutil, sys, tempfile, urllib2
+import os, shutil, sys, tempfile, urllib.request, urllib.error, urllib.parse
 from optparse import OptionParser
 
 tmpeggs = tempfile.mkdtemp()
@@ -54,12 +59,12 @@ try:
 except ImportError:
     ez = {}
     if USE_DISTRIBUTE:
-        exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
-                         ).read() in ez
+        exec(urllib.request.urlopen('http://python-distribute.org/distribute_setup.py'
+                         ).read(), ez)
         ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
     else:
-        exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
-                             ).read() in ez
+        exec(urllib.request.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                             ).read(), ez)
         ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
 
     if to_reload:
diff --git a/gui/Ui_about.py b/gui/Ui_about.py
index f38f2c6a..0e398bc9 100644
--- a/gui/Ui_about.py
+++ b/gui/Ui_about.py
@@ -11,6 +11,9 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Dialog(object):
diff --git a/gui/Ui_configdialog.py b/gui/Ui_configdialog.py
index b168449a..4c8d75ca 100644
--- a/gui/Ui_configdialog.py
+++ b/gui/Ui_configdialog.py
@@ -11,6 +11,9 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Dialog(object):
diff --git a/gui/Ui_main.py b/gui/Ui_main.py
index 351eb4c6..d33e0e9b 100644
--- a/gui/Ui_main.py
+++ b/gui/Ui_main.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_MainWindow(object):
diff --git a/gui/Ui_pagesetup.py b/gui/Ui_pagesetup.py
index 19518771..877a860e 100644
--- a/gui/Ui_pagesetup.py
+++ b/gui/Ui_pagesetup.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Form(object):
diff --git a/gui/Ui_pagetemplates.py b/gui/Ui_pagetemplates.py
index 1f730c44..9bd91d24 100644
--- a/gui/Ui_pagetemplates.py
+++ b/gui/Ui_pagetemplates.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Form(object):
diff --git a/gui/Ui_pdf.py b/gui/Ui_pdf.py
index 6e990d4d..ee3c7556 100644
--- a/gui/Ui_pdf.py
+++ b/gui/Ui_pdf.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Form(object):
diff --git a/gui/Ui_searchwidget.py b/gui/Ui_searchwidget.py
index 90067334..454a77a5 100644
--- a/gui/Ui_searchwidget.py
+++ b/gui/Ui_searchwidget.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Form(object):
@@ -48,7 +49,7 @@ class Ui_Form(object):
         self.next.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
         self.next.setAutoRaise(True)
         self.next.setObjectName("next")
-        self.horizontalLayout.addWidget(self.next)
+        self.horizontalLayout.addWidget(self.__next__)
         self.matchCase = QtGui.QCheckBox(Form)
         self.matchCase.setObjectName("matchCase")
         self.horizontalLayout.addWidget(self.matchCase)
diff --git a/gui/Ui_style.py b/gui/Ui_style.py
index 8cd62aef..925cff7b 100644
--- a/gui/Ui_style.py
+++ b/gui/Ui_style.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Form(object):
diff --git a/gui/Ui_stylesheets.py b/gui/Ui_stylesheets.py
index 6e761a39..59b02f52 100644
--- a/gui/Ui_stylesheets.py
+++ b/gui/Ui_stylesheets.py
@@ -11,6 +11,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object
 from PyQt4 import QtCore, QtGui
 
 class Ui_Form(object):
diff --git a/gui/codeeditor.py b/gui/codeeditor.py
index 515203b0..e2e3eab3 100644
--- a/gui/codeeditor.py
+++ b/gui/codeeditor.py
@@ -4,6 +4,8 @@ from __future__ import print_function
 from __future__ import unicode_literals
 from __future__ import division
 from __future__ import absolute_import
+from builtins import next
+from builtins import str
 import sys
 from PyQt4 import QtGui, QtCore
 
@@ -139,7 +141,7 @@ if __name__ == "__main__":
     js.show()
 
     def validateJSON():
-        style=unicode(js.toPlainText())
+        style=str(js.toPlainText())
         if not style.strip(): #no point in validating an empty string
             return
         pos=None
diff --git a/gui/configdialog.py b/gui/configdialog.py
index 1416f5dc..b887b6f8 100644
--- a/gui/configdialog.py
+++ b/gui/configdialog.py
@@ -5,6 +5,8 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import zip
+from builtins import str
 
 class ConfigDialog(QtGui.QDialog):
   def __init__(self, parent):
@@ -50,20 +52,20 @@ class ConfigDialog(QtGui.QDialog):
           label=QtGui.QLabel(optionName+":")
           label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
           text=QtGui.QLineEdit()
-          text.setText(unicode(config.getValue(sectionName, optionName, definition[1])))          
+          text.setText(str(config.getValue(sectionName, optionName, definition[1])))          
           layout.addWidget(label, row, 0, 1, 1)
           layout.addWidget(text, row, 1, 1, 1)
-          self.values[sectionName+'/'+optionName]=[text, lambda text: unicode(text.text())]
+          self.values[sectionName+'/'+optionName]=[text, lambda text: str(text.text())]
 
         elif definition[0]=='password':
           label=QtGui.QLabel(optionName+":")
           label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)
           text=QtGui.QLineEdit()
           text.setEchoMode(QtGui.QLineEdit.Password)
-          text.setText(unicode(config.getValue(sectionName, optionName, definition[1])))          
+          text.setText(str(config.getValue(sectionName, optionName, definition[1])))          
           layout.addWidget(label, row, 0, 1, 1)
           layout.addWidget(text, row, 1, 1, 1)
-          self.values[sectionName+'/'+optionName]=[text, lambda text: unicode(text.text())]
+          self.values[sectionName+'/'+optionName]=[text, lambda text: str(text.text())]
 
         help=QtGui.QLabel(definition[2])
         help.setWordWrap(True)
diff --git a/gui/highlighter.py b/gui/highlighter.py
index 539a9114..e37c1503 100644
--- a/gui/highlighter.py
+++ b/gui/highlighter.py
@@ -3,6 +3,8 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
+from builtins import range
 import sys
 import re
 from PyQt4 import QtCore, QtGui
@@ -88,7 +90,7 @@ class Highlighter(QtGui.QSyntaxHighlighter):
         # The \n is not really needed, but sometimes  
         # you are in an empty last block, so your position is
         # **after** the end of the document.
-        text=unicode(self.document().toPlainText())+'\n'
+        text=str(self.document().toPlainText())+'\n'
         
         # Yes, re-highlight the whole document.
         # There **must** be some optimizacion possibilities
@@ -114,7 +116,7 @@ class Highlighter(QtGui.QSyntaxHighlighter):
         # Just apply the formatting to this block.
         # For titles, it may be necessary to backtrack
         # and format a couple of blocks **earlier**.
-        for i in range(len(unicode(text))):
+        for i in range(len(str(text))):
             try:
                 self.setFormat(i,1,self.formatter.data[p+i])
             except IndexError:
diff --git a/gui/main.py b/gui/main.py
index 9d2c1adb..e9a9b3ab 100755
--- a/gui/main.py
+++ b/gui/main.py
@@ -6,14 +6,18 @@ from __future__ import print_function
 from __future__ import unicode_literals
 from __future__ import division
 from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import range
 
 import os,sys,tempfile,re,functools,time,types,glob,codecs
 from pprint import pprint
 from copy import copy
 from multiprocessing import Process, Queue
-from Queue import Empty
+from queue import Empty
 from hashlib import md5
-from StringIO import StringIO
+from io import StringIO
 from rst2pdf.createpdf import RstToPdf
 from rst2pdf.styles import StyleSheet
 from rst2pdf.log import log
@@ -157,7 +161,7 @@ class Main(QtGui.QMainWindow):
 
         self.ui.pdfbar.addAction(self.pdf.ui.previous)
         self.ui.pdfbar.addWidget(self.ui.pageNum)
-        self.ui.pdfbar.addAction(self.pdf.ui.next)
+        self.ui.pdfbar.addAction(self.pdf.ui.__next__)
         self.ui.pdfbar.addSeparator()
         self.ui.pdfbar.addAction(self.pdf.ui.zoomin)
         self.ui.pdfbar.addAction(self.pdf.ui.zoomout)
@@ -262,7 +266,7 @@ class Main(QtGui.QMainWindow):
         if self.ui.searchWidget.ui.matchCase.isChecked():
             flags=flags|QtGui.QTextDocument.FindCaseSensitively
 
-        text=unicode(self.ui.searchWidget.ui.text.text())
+        text=str(self.ui.searchWidget.ui.text.text())
 
         print('Serching for:',text)
 
@@ -278,7 +282,7 @@ class Main(QtGui.QMainWindow):
     def clipChanged(self, mode=None):
         if mode is None: return
         if mode == QtGui.QClipboard.Clipboard:
-            if unicode(self.clipBoard.text()):
+            if str(self.clipBoard.text()):
                 self.ui.actionPaste1.setEnabled(True)
                 self.ui.actionPaste2.setEnabled(True)
             else:
@@ -334,7 +338,7 @@ class Main(QtGui.QMainWindow):
         # the current stylesheet
 
         try:
-            data=json.loads(unicode(self.ui.style.toPlainText()))
+            data=json.loads(str(self.ui.style.toPlainText()))
         except: # TODO: fail if sheet doesn't validate
             data={}
         config=ConfigDialog(data=copy(data))
@@ -375,7 +379,7 @@ class Main(QtGui.QMainWindow):
         if self.text_fname is not None:
             f=codecs.open(self.text_fname,'w+','utf-8')
             f.seek(0)
-            f.write(unicode(self.ui.text.toPlainText()))
+            f.write(str(self.ui.text.toPlainText()))
             f.close()
         else:
             self.on_actionSaveAs_Text_triggered()
@@ -384,7 +388,7 @@ class Main(QtGui.QMainWindow):
     def on_actionSaveAs_Text_triggered(self, b=None):
         if b is not None: return
 
-        fname=unicode(QtGui.QFileDialog.getSaveFileName(self,
+        fname=str(QtGui.QFileDialog.getSaveFileName(self,
                             'Save As',
                             os.getcwd(),
                             'reSt files (*.txt *.rst)'
@@ -411,7 +415,7 @@ class Main(QtGui.QMainWindow):
         if self.style_fname is not None:
             f=codecs.open(self.style_fname,'w+','utf-8')
             f.seek(0)
-            f.write(unicode(self.ui.style.toPlainText()))
+            f.write(str(self.ui.style.toPlainText()))
             f.close()
         else:
             self.on_actionSaveAs_Style_triggered()
@@ -420,7 +424,7 @@ class Main(QtGui.QMainWindow):
     def on_actionSaveAs_Style_triggered(self, b=None):
         if b is not None: return
 
-        fname=unicode(QtGui.QFileDialog.getSaveFileName(self,
+        fname=str(QtGui.QFileDialog.getSaveFileName(self,
                             'Save As',
                             os.getcwd(),
                             'style files (*.json *.style)'
@@ -461,7 +465,7 @@ class Main(QtGui.QMainWindow):
     def on_actionSaveAs_PDF_triggered(self, b=None):
         if b is not None: return
 
-        fname=unicode(QtGui.QFileDialog.getSaveFileName(self,
+        fname=str(QtGui.QFileDialog.getSaveFileName(self,
                             'Save As',
                             os.getcwd(),
                             'PDF files (*.pdf)'
@@ -495,7 +499,7 @@ class Main(QtGui.QMainWindow):
         if m:
             self.pdf.gotoPosition(*m)
     def validateStyle(self):
-        style=unicode(self.ui.style.toPlainText())
+        style=str(self.ui.style.toPlainText())
         if not style.strip(): #no point in validating an empty string
             self.statusMessage.setText('')
             return
@@ -524,8 +528,8 @@ class Main(QtGui.QMainWindow):
 
     def on_actionRender_triggered(self, b=None, preview=True):
         if b is not None: return
-        text=unicode(self.ui.text.toPlainText())
-        style=unicode(self.ui.style.toPlainText())
+        text=str(self.ui.text.toPlainText())
+        style=str(self.ui.style.toPlainText())
         self.hl1.rehighlight()
         m1=md5()
         m1.update(text.encode('utf-8'))
@@ -862,7 +866,7 @@ class ConfigDialog(QtGui.QDialog):
             'Page Setup':PageSetup,
             'Page Templates':PageTemplates,
         }
-        keys=self.pages.keys()
+        keys=list(self.pages.keys())
         keys.sort()
         for page in keys:
             self.ui.pagelist.addItem(page)
@@ -876,7 +880,7 @@ class ConfigDialog(QtGui.QDialog):
         self.styles = StyleSheet([style_file])
         os.unlink(style_file)
 
-        text=unicode(text)
+        text=str(text)
         if self.curPageWidget:
             self.curPageWidget.hide()
             self.curPageWidget.deleteLater()
@@ -928,7 +932,7 @@ class PageTemplates(QtGui.QWidget):
 
     def applyChanges(self):
         # TODO: validate everything
-        self.frame=[unicode(w.text()) for w in [
+        self.frame=[str(w.text()) for w in [
             self.ui.left,
             self.ui.top,
             self.ui.width,
@@ -937,13 +941,13 @@ class PageTemplates(QtGui.QWidget):
         self.template["frames"][self.frameIndex]=self.frame
         self.template['showFooter']=self.ui.footer.isChecked()
         self.template['showHeader']=self.ui.header.isChecked()
-        if unicode(self.ui.background.text()):
-            self.template['background']=unicode(self.ui.background.text())
+        if str(self.ui.background.text()):
+            self.template['background']=str(self.ui.background.text())
         self.updatePreview()
 
     def on_templates_currentIndexChanged(self, text):
         if not isinstance(text,StringTypes): return
-        text=unicode(text)
+        text=str(text)
         self.template=self.templates[text]
         self.ui.frames.clear()
         for i in range(0, len(self.template['frames'])):
@@ -1063,37 +1067,37 @@ class PageSetup(QtGui.QWidget):
 
         self.ui.firstTemplate.insertItem(0,ft)
         self.ui.firstTemplate.setCurrentIndex(0)
-        self.ui.margin_top.setText(unicode(self.stylesheet.page['margin-top']))
-        self.ui.margin_bottom.setText(unicode(self.stylesheet.page['margin-bottom']))
-        self.ui.margin_left.setText(unicode(self.stylesheet.page['margin-left']))
-        self.ui.margin_right.setText(unicode(self.stylesheet.page['margin-right']))
-        self.ui.margin_gutter.setText(unicode(self.stylesheet.page['margin-gutter']))
-        self.ui.spacing_header.setText(unicode(self.stylesheet.page['spacing-header']))
-        self.ui.spacing_footer.setText(unicode(self.stylesheet.page['spacing-footer']))
+        self.ui.margin_top.setText(str(self.stylesheet.page['margin-top']))
+        self.ui.margin_bottom.setText(str(self.stylesheet.page['margin-bottom']))
+        self.ui.margin_left.setText(str(self.stylesheet.page['margin-left']))
+        self.ui.margin_right.setText(str(self.stylesheet.page['margin-right']))
+        self.ui.margin_gutter.setText(str(self.stylesheet.page['margin-gutter']))
+        self.ui.spacing_header.setText(str(self.stylesheet.page['spacing-header']))
+        self.ui.spacing_footer.setText(str(self.stylesheet.page['spacing-footer']))
         self.pageImage=None
         self.applyChanges()
 
     def applyChanges(self):
-        if unicode(self.ui.size.currentText())==u'Custom':
+        if str(self.ui.size.currentText())==u'Custom':
             # FIXME: % makes no sense for page size
             self.ui.width.setEnabled(True)
             self.ui.height.setEnabled(True)
-            self.pw=self.stylesheet.adjustUnits(unicode(self.ui.width.text()),1000) or 0
-            self.ph=self.stylesheet.adjustUnits(unicode(self.ui.height.text()),1000) or 0
+            self.pw=self.stylesheet.adjustUnits(str(self.ui.width.text()),1000) or 0
+            self.ph=self.stylesheet.adjustUnits(str(self.ui.height.text()),1000) or 0
         else:
             self.ui.width.setEnabled(False)
             self.ui.height.setEnabled(False)
-            self.size=unicode(self.ui.size.currentText())
+            self.size=str(self.ui.size.currentText())
             self.pw=pagesizes.__dict__[self.size.upper()][0]
             self.ph=pagesizes.__dict__[self.size.upper()][1]
 
-        self.lm=self.stylesheet.adjustUnits(unicode(self.ui.margin_left.text()),self.pw) or 0
-        self.rm=self.stylesheet.adjustUnits(unicode(self.ui.margin_right.text()),self.pw) or 0
-        self.tm=self.stylesheet.adjustUnits(unicode(self.ui.margin_top.text()),self.ph) or 0
-        self.bm=self.stylesheet.adjustUnits(unicode(self.ui.margin_bottom.text()),self.ph) or 0
-        self.ts=self.stylesheet.adjustUnits(unicode(self.ui.spacing_header.text()),self.ph) or 0
-        self.bs=self.stylesheet.adjustUnits(unicode(self.ui.spacing_footer.text()),self.ph) or 0
-        self.gm=self.stylesheet.adjustUnits(unicode(self.ui.margin_gutter.text()),self.pw) or 0
+        self.lm=self.stylesheet.adjustUnits(str(self.ui.margin_left.text()),self.pw) or 0
+        self.rm=self.stylesheet.adjustUnits(str(self.ui.margin_right.text()),self.pw) or 0
+        self.tm=self.stylesheet.adjustUnits(str(self.ui.margin_top.text()),self.ph) or 0
+        self.bm=self.stylesheet.adjustUnits(str(self.ui.margin_bottom.text()),self.ph) or 0
+        self.ts=self.stylesheet.adjustUnits(str(self.ui.spacing_header.text()),self.ph) or 0
+        self.bs=self.stylesheet.adjustUnits(str(self.ui.spacing_footer.text()),self.ph) or 0
+        self.gm=self.stylesheet.adjustUnits(str(self.ui.margin_gutter.text()),self.pw) or 0
         self.pageImage=QtGui.QImage(int(self.pw),
                                     int(self.ph),
                                     QtGui.QImage.Format_RGB32)
@@ -1127,17 +1131,17 @@ class PageSetup(QtGui.QWidget):
         self.ui.preview.setPixmap(pm.scaled(self.pw*self.scale,self.ph*self.scale))
 
         self.data["pageSetup"]= {
-            "size": unicode(self.ui.size.currentText()).lower(),
-            "width": unicode(self.ui.width.text()),
-            "height": unicode(self.ui.height.text()),
-            "margin-top": unicode(self.ui.margin_top.text()),
-            "margin-bottom": unicode(self.ui.margin_bottom.text()),
-            "margin-left": unicode(self.ui.margin_left.text()),
-            "margin-right": unicode(self.ui.margin_right.text()),
-            "margin-gutter": unicode(self.ui.margin_gutter.text()),
-            "spacing-header": unicode(self.ui.spacing_header.text()),
-            "spacing-footer": unicode(self.ui.spacing_footer.text()),
-            "firstTemplate": unicode(self.ui.firstTemplate.currentText())
+            "size": str(self.ui.size.currentText()).lower(),
+            "width": str(self.ui.width.text()),
+            "height": str(self.ui.height.text()),
+            "margin-top": str(self.ui.margin_top.text()),
+            "margin-bottom": str(self.ui.margin_bottom.text()),
+            "margin-left": str(self.ui.margin_left.text()),
+            "margin-right": str(self.ui.margin_right.text()),
+            "margin-gutter": str(self.ui.margin_gutter.text()),
+            "spacing-header": str(self.ui.spacing_header.text()),
+            "spacing-footer": str(self.ui.spacing_footer.text()),
+            "firstTemplate": str(self.ui.firstTemplate.currentText())
           }
 
         if self.data['pageSetup']['size']==u'custom':
@@ -1246,7 +1250,7 @@ class StyleSheets(QtGui.QWidget):
         '''%(head,body))
 
     def applyChanges(self):
-        self.data.update({'options':{'stylesheets':[unicode(self.ui.custom.item(x).text()) \
+        self.data.update({'options':{'stylesheets':[str(self.ui.custom.item(x).text()) \
             for x in range(self.ui.custom.count())]}})
         self.updatePreview()
 
diff --git a/requirements.txt b/requirements.txt
index 44dd512e..4bfec4fd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
+future
 pyhyphen
 nose
 reportlab
diff --git a/rst2pdf/basenodehandler.py b/rst2pdf/basenodehandler.py
index c3ca7a20..40670e89 100644
--- a/rst2pdf/basenodehandler.py
+++ b/rst2pdf/basenodehandler.py
@@ -37,6 +37,7 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import object
 
 import types
 import inspect
@@ -44,6 +45,7 @@ from .log import log, nodeid
 from .smartypants import smartyPants
 import docutils.nodes
 from .flowables import BoundByWidth, TocEntry
+from future.utils import with_metaclass
 
 
 class MetaHelper(type):
@@ -105,12 +107,11 @@ class MetaHelper(type):
             cls._classinit()
 
 
-class NodeHandler(object):
+class NodeHandler(with_metaclass(MetaHelper, object)):
     ''' NodeHandler classes are used to dispatch
        to the correct class to handle some node class
        type, via a dispatchdict in the main class.
     '''
-    __metaclass__ = MetaHelper
 
     @classmethod
     def _classpreinit(baseclass, clstype, name, bases, clsdict):
diff --git a/rst2pdf/config.py b/rst2pdf/config.py
index 60078a85..0661dadd 100644
--- a/rst2pdf/config.py
+++ b/rst2pdf/config.py
@@ -5,9 +5,11 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
 
 
-import ConfigParser
+import configparser
 import os
 from rst2pdf.rson import loads
 
@@ -30,14 +32,14 @@ class ConfigError(Exception):
         self.modulename = modulename
         self.msg = msg
 
-conf = ConfigParser.SafeConfigParser()
+conf = configparser.SafeConfigParser()
 
 def parseConfig(extracf=None):
     global conf
     cflist = ["/etc/rst2pdf.conf", cfname]
     if extracf:
         cflist.append(extracf)
-    conf = ConfigParser.SafeConfigParser()
+    conf = configparser.SafeConfigParser()
     conf.read(cflist)
     
 parseConfig()
diff --git a/rst2pdf/counter_role.py b/rst2pdf/counter_role.py
index 908d04b0..7787e52c 100644
--- a/rst2pdf/counter_role.py
+++ b/rst2pdf/counter_role.py
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
 from docutils.nodes import Text, target
 
 values = {}
@@ -21,7 +22,7 @@ class CounterNode(Text):
         values[self.name]=self.value+1
         
     def astext(self):
-        return unicode(self.value)
+        return str(self.value)
 
 def counter_fn(name, rawtext, text, lineno, inliner, options={}, content=[]):
     n=CounterNode(text)
diff --git a/rst2pdf/createpdf.py b/rst2pdf/createpdf.py
index 324c8b12..bdd9526b 100644
--- a/rst2pdf/createpdf.py
+++ b/rst2pdf/createpdf.py
@@ -43,6 +43,11 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import division
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import range
+from builtins import object
 __docformat__ = 'reStructuredText'
 
 # Import Psyco if available
@@ -56,9 +61,9 @@ import re
 import string
 from . import config
 import logging
-from cStringIO import StringIO
+from io import StringIO
 from os.path import abspath, dirname, expanduser, join
-from urlparse import urljoin, urlparse, urlunparse
+from urllib.parse import urljoin, urlparse, urlunparse
 from copy import copy, deepcopy
 from optparse import OptionParser
 from pprint import pprint
@@ -633,7 +638,7 @@ class RstToPdf(object):
 
         def cleantags(s):
             re.sub(r'<[^>]*?>', '',
-                unicode(s).strip())
+                str(s).strip())
 
         pdfdoc = FancyDocTemplate(
             output,
@@ -845,7 +850,7 @@ def setPageCounter(counter=None, style=None):
     elif _counterStyle=='loweralpha':
         ptext=string.lowercase[_counter%26]
     else:
-        ptext=unicode(_counter)
+        ptext=str(_counter)
     return ptext
 
 class MyContainer(_Container, Flowable):
@@ -912,13 +917,13 @@ class HeaderOrFooter(object):
         pnum=setPageCounter()
 
         def replace(text):
-            if not isinstance(text, unicode):
+            if not isinstance(text, str):
                 try:
-                    text = unicode(text, e.encoding)
+                    text = str(text, e.encoding)
                 except AttributeError:
-                    text = unicode(text, 'utf-8')
+                    text = str(text, 'utf-8')
                 except TypeError:
-                    text = unicode(text, 'utf-8')
+                    text = str(text, 'utf-8')
 
             text = text.replace(u'###Page###', pnum)
             if '###Total###' in text:
diff --git a/rst2pdf/dumpstyle.py b/rst2pdf/dumpstyle.py
index b782657f..f162b04f 100755
--- a/rst2pdf/dumpstyle.py
+++ b/rst2pdf/dumpstyle.py
@@ -9,6 +9,8 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import division
+from builtins import str
+from past.builtins import basestring
 
 import sys
 import os
@@ -72,7 +74,7 @@ def dumps(obj, forcestyledict=True):
         if not obj:
             result.append('{}')
             return
-        obj = sorted(obj.iteritems())
+        obj = sorted(obj.items())
         multiline = indent and ( len(obj) > 2 or
                     len(obj) == 2 and (
                          isinstance(obj[0][-1], (list, dict)) or
@@ -99,7 +101,7 @@ def dumps(obj, forcestyledict=True):
     dumpfuncs = {float: dofloat, int: doint, basestring: dostr,
                      list: dolist, dict: dodict, type(None): donone}
 
-    dumpfuncs = dumpfuncs.items()
+    dumpfuncs = list(dumpfuncs.items())
 
     def dumprecurse(result, obj, indent='\n', indentnow=True):
         if indentnow:
diff --git a/rst2pdf/extensions/dotted_toc.py b/rst2pdf/extensions/dotted_toc.py
index dad61f36..1e7ff770 100644
--- a/rst2pdf/extensions/dotted_toc.py
+++ b/rst2pdf/extensions/dotted_toc.py
@@ -40,6 +40,7 @@
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
 from copy import copy
 import reportlab
 from reportlab.platypus.tableofcontents import drawPageNumbers
@@ -137,8 +138,8 @@ class DottedTableOfContents(genelements.MyTableOfContents):
             style.textColor = self.linkColor
             key = self.refid_lut.get((level, text, pageNum), None)
             if key:
-                if not isinstance(text, unicode):
-                    text = unicode(text, 'utf-8')
+                if not isinstance(text, str):
+                    text = str(text, 'utf-8')
                 text = u'<a href="#%s">%s</a>' % (key, text)
 
             para = Paragraph('%s<onDraw name="%s" label="%s"/>' % (text, funcname, len(end_info)), style)
diff --git a/rst2pdf/extensions/fancytitles.py b/rst2pdf/extensions/fancytitles.py
index 3f198b60..64a1d030 100644
--- a/rst2pdf/extensions/fancytitles.py
+++ b/rst2pdf/extensions/fancytitles.py
@@ -2,6 +2,7 @@
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
 import rst2pdf.genelements as genelements
 from rst2pdf.flowables import Heading, MyPageBreak
 from rst2pdf.image import MyImage
@@ -64,7 +65,7 @@ class FancyTitleHandler(genelements.HandleParagraph, docutils.nodes.title):
                 maxdepth=6
 
             # The parent ID is the refid + an ID to make it unique for Sphinx
-            parent_id=(node.parent.get('ids', [None]) or [None])[0]+u'-'+unicode(id(node))
+            parent_id=(node.parent.get('ids', [None]) or [None])[0]+u'-'+str(id(node))
             if client.depth > 1:
                 node.elements = [ Heading(text,
                         client.styles['heading%d'%min(client.depth, maxdepth)],
diff --git a/rst2pdf/extensions/inkscape_r2p.py b/rst2pdf/extensions/inkscape_r2p.py
index 1c4e3a39..0c24ef88 100644
--- a/rst2pdf/extensions/inkscape_r2p.py
+++ b/rst2pdf/extensions/inkscape_r2p.py
@@ -14,6 +14,7 @@ the vectorpdf code to process the PDF.
 from __future__ import absolute_import
 from __future__ import print_function
 from __future__ import division
+from builtins import str
 
 import sys, os, tempfile, subprocess
 from weakref import WeakKeyDictionary
diff --git a/rst2pdf/extensions/preprocess_r2p.py b/rst2pdf/extensions/preprocess_r2p.py
index ecf2a896..88a94be3 100644
--- a/rst2pdf/extensions/preprocess_r2p.py
+++ b/rst2pdf/extensions/preprocess_r2p.py
@@ -103,6 +103,8 @@ file were automatically removed.
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
+from builtins import object
 
 import os
 import re
diff --git a/rst2pdf/extensions/vectorpdf_r2p.py b/rst2pdf/extensions/vectorpdf_r2p.py
index 221959df..72276a07 100644
--- a/rst2pdf/extensions/vectorpdf_r2p.py
+++ b/rst2pdf/extensions/vectorpdf_r2p.py
@@ -4,6 +4,8 @@
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
+from builtins import object
 import sys
 import os
 from weakref import WeakKeyDictionary
@@ -23,7 +25,7 @@ try:
     from rst2pdf.opt_imports import LazyImports
 except ImportError:
     # This is just to make nosetest happy on the CI server
-    class Flowable:
+    class Flowable(object):
         pass
 
         # TODO:  Looks the same as for other images, because I
diff --git a/rst2pdf/findfonts.py b/rst2pdf/findfonts.py
index 90ff7895..862a794d 100644
--- a/rst2pdf/findfonts.py
+++ b/rst2pdf/findfonts.py
@@ -10,6 +10,9 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import division
+from future import standard_library
+standard_library.install_aliases()
+from builtins import zip
 
 import os
 import sys
@@ -218,7 +221,7 @@ def findTTFont(fname):
         # ctypes with EnumFontFamiliesEx
 
         def get_nt_fname(ftname):
-            import _winreg as _w
+            import winreg as _w
             fontkey = _w.OpenKey(_w.HKEY_LOCAL_MACHINE,
                 "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts")
             fontname = ftname + " (TrueType)"
diff --git a/rst2pdf/flowables.py b/rst2pdf/flowables.py
index 97d57299..db6e774b 100644
--- a/rst2pdf/flowables.py
+++ b/rst2pdf/flowables.py
@@ -8,6 +8,11 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import zip
+from builtins import map
+from builtins import hex
+from builtins import str
+from builtins import range
 __docformat__ = 'reStructuredText'
 
 from copy import copy
@@ -231,7 +236,7 @@ class DelayedTable(Table):
             kwargs['total']=w
             return styles.adjustUnits(*args, **kwargs)
         #adjust=functools.partial(styles.adjustUnits, total=w)
-        self.colWidths=map(adjust, self._colWidths)
+        self.colWidths=list(map(adjust, self._colWidths))
         #colWidths = [_w * _tw for _w in self.colWidths]
         self.t = Table(self.data, colWidths=self.colWidths,
             style=self.style, repeatRows=self.repeatrows,
@@ -256,7 +261,7 @@ class DelayedTable(Table):
             hex(id(self)), self._frameName(),
             getattr(self, 'name', '')
                 and (' name="%s"' % getattr(self, 'name', '')) or '',
-                unicode(self.data[0])[:180])
+                str(self.data[0])[:180])
 
 def tablepadding(padding):
     if not isinstance(padding,(list,tuple)):
@@ -283,7 +288,7 @@ class SplitTable(DelayedTable):
             hex(id(self)), self._frameName(),
             getattr(self, 'name', '')
                 and (' name="%s"' % getattr(self, 'name', '')) or '',
-                unicode(self.data[0][1])[:180])
+                str(self.data[0][1])[:180])
 
     def split(self,w,h):
         _w,_h=self.wrap(w, h)
@@ -720,7 +725,7 @@ class BoundByWidth(Flowable):
             hex(id(self)), self._frameName(),
             getattr(self, 'name', '')
                 and (' name="%s"' % getattr(self, 'name', '')) or '',
-                unicode([c.identity() for c in self.content])[:80])
+                str([c.identity() for c in self.content])[:80])
 
     def wrap(self, availWidth, availHeight):
         """If we need more width than we have, complain, keep a scale"""
@@ -812,7 +817,7 @@ class BoxedContainer(BoundByWidth):
         self.mode = mode
 
     def identity(self, maxLen=None):
-        return unicode([u"BoxedContainer containing: ",
+        return str([u"BoxedContainer containing: ",
             [c.identity() for c in self.content]])[:80]
 
     def draw(self):
@@ -1002,8 +1007,8 @@ class MyTableOfContents(TableOfContents):
             if label:
                 pre = u'<a href="%s" color="%s">' % (label, self.linkColor)
                 post = u'</a>'
-                if not isinstance(text, unicode):
-                    text = unicode(text, 'utf-8')
+                if not isinstance(text, str):
+                    text = str(text, 'utf-8')
                 text = pre + text + post
             else:
                 pre = ''
@@ -1039,7 +1044,7 @@ class MyTableOfContents(TableOfContents):
                 return False
 
             log.info('TOC entries that moved in this pass:')
-            for i in xrange(len(self._entries)):
+            for i in range(len(self._entries)):
                 if self._entries[i] != self._lastEntries[i]:
                     log.info(str(self._entries[i]))
                     log.info(str(self._lastEntries[i]))
diff --git a/rst2pdf/genelements.py b/rst2pdf/genelements.py
index d551b1a8..4eb28755 100644
--- a/rst2pdf/genelements.py
+++ b/rst2pdf/genelements.py
@@ -43,6 +43,10 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import zip
+from builtins import map
+from builtins import str
+from builtins import range
 import os
 import tempfile
 import re
@@ -134,7 +138,7 @@ class HandleTGroup(NodeHandler, docutils.nodes.tgroup):
 
         # colWidths are in no specific unit, really. Maybe ems.
         # Convert them to %
-        colWidths=map(int, colWidths)
+        colWidths=list(map(int, colWidths))
         tot=sum(colWidths)
         colWidths=["%s%%"%((100.*w)/tot) for w in colWidths]
 
@@ -145,7 +149,7 @@ class HandleTGroup(NodeHandler, docutils.nodes.tgroup):
 
         data = []
         cellStyles = []
-        rowids = range(0, len(rows))
+        rowids = list(range(0, len(rows)))
         for row, i in zip(rows, rowids):
             r = []
             j = 0
@@ -239,7 +243,7 @@ class HandleTitle(HandleParagraph, docutils.nodes.title):
                 maxdepth=6
 
             # The parent ID is the refid + an ID to make it unique for Sphinx
-            parent_id=(node.parent.get('ids', [None]) or [None])[0]+u'-'+unicode(id(node))
+            parent_id=(node.parent.get('ids', [None]) or [None])[0]+u'-'+str(id(node))
             node.elements = [ Heading(text,
                     client.styles['heading%d'%min(client.depth, maxdepth)],
                     level=client.depth-1,
@@ -318,8 +322,8 @@ class HandleAuthor(NodeHandler, docutils.nodes.author):
             fb = client.gather_pdftext(node)
 
             t_style=TableStyle(client.styles['field-list'].commands)
-            colWidths=map(client.styles.adjustUnits,
-                client.styles['field-list'].colWidths)
+            colWidths=list(map(client.styles.adjustUnits,
+                client.styles['field-list'].colWidths))
 
             node.elements = [Table(
                 [[Paragraph(client.text_for_label("author", style)+":",
@@ -350,7 +354,7 @@ class HandleFList(NodeHandler):
         t_style=TableStyle(client.styles['field-list'].commands)
         colWidths=client.styles['field-list'].colWidths
         if self.adjustwidths:
-            colWidths = map(client.styles.adjustUnits, colWidths)
+            colWidths = list(map(client.styles.adjustUnits, colWidths))
         label=client.text_for_label(self.labeltext, style)+":"
         t = self.TableType([[Paragraph(label, style=client.styles['fieldname']),
                     Paragraph(fb, style)]],
@@ -370,7 +374,7 @@ class HandleAddress(HandleFList, docutils.nodes.address):
         t_style=TableStyle(client.styles['field-list'].commands)
         colWidths=client.styles['field-list'].colWidths
         if self.adjustwidths:
-            colWidths = map(client.styles.adjustUnits, colWidths)
+            colWidths = list(map(client.styles.adjustUnits, colWidths))
         label=client.text_for_label(self.labeltext, style)+":"
         t = self.TableType([[Paragraph(label, style=client.styles['fieldname']),
                              XPreformatted(fb, style)]
diff --git a/rst2pdf/genpdftext.py b/rst2pdf/genpdftext.py
index 2487f4ad..675424de 100644
--- a/rst2pdf/genpdftext.py
+++ b/rst2pdf/genpdftext.py
@@ -10,12 +10,15 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
 import os
 from xml.sax.saxutils import escape
 from .log import log, nodeid
 from .basenodehandler import NodeHandler
 import docutils.nodes
-from urlparse import urljoin, urlparse
+from urllib.parse import urljoin, urlparse
 from reportlab.lib.units import cm
 from .opt_imports import Paragraph
 
diff --git a/rst2pdf/image.py b/rst2pdf/image.py
index 2fd72b5f..4b410147 100644
--- a/rst2pdf/image.py
+++ b/rst2pdf/image.py
@@ -4,6 +4,9 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
 import os
 from os.path import abspath, dirname, expanduser, join
 import sys
@@ -13,7 +16,7 @@ from reportlab.platypus.flowables import Image, Flowable
 from .log import log, nodeid
 from reportlab.lib.units import *
 import glob
-import urllib
+import urllib.request, urllib.parse, urllib.error
 
 from .opt_imports import LazyImports
 
@@ -82,7 +85,7 @@ class MyImage (Flowable):
 
         if filename.split("://")[0].lower() in ('http','ftp','https'):
             try:
-                filename2, _ = urllib.urlretrieve(filename)
+                filename2, _ = urllib.request.urlretrieve(filename)
                 if filename != filename2:
                     client.to_unlink.append(filename2)
                     filename = filename2
@@ -287,7 +290,7 @@ class MyImage (Flowable):
         if uri.split("://")[0].lower() not in ('http','ftp','https'):
             uri = os.path.join(client.basedir,uri)
         else:
-            uri, _ = urllib.urlretrieve(uri)
+            uri, _ = urllib.request.urlretrieve(uri)
             client.to_unlink.append(uri)
 
         srcinfo = client, uri
diff --git a/rst2pdf/languages.py b/rst2pdf/languages.py
index 26975d73..b5af7d1d 100644
--- a/rst2pdf/languages.py
+++ b/rst2pdf/languages.py
@@ -8,6 +8,8 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
+from builtins import object
 from docutils.languages import get_language as get_language
 
 from rst2pdf.log import log
diff --git a/rst2pdf/log.py b/rst2pdf/log.py
index 95106688..8db5070b 100644
--- a/rst2pdf/log.py
+++ b/rst2pdf/log.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import str
 import logging
 import sys
 
diff --git a/rst2pdf/math_flowable.py b/rst2pdf/math_flowable.py
index d516c40f..17a1aea1 100644
--- a/rst2pdf/math_flowable.py
+++ b/rst2pdf/math_flowable.py
@@ -5,6 +5,8 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import str
+from builtins import chr
 import tempfile
 import os
 import re
@@ -86,7 +88,7 @@ class Math(Flowable):
                     col_conv=ColorConverter()
                     rgb_color=col_conv.to_rgb(self.color)
                     canv.setFillColorRGB(rgb_color[0],rgb_color[1],rgb_color[2])
-                    canv.drawString(ox, oy, unichr(num))
+                    canv.drawString(ox, oy, chr(num))
 
                 canv.setLineWidth(0)
                 canv.setDash([])
@@ -151,7 +153,7 @@ class Math(Flowable):
             draw = ImageDraw.Draw(img)
             for ox, oy, fontname, fontsize, num, symbol_name in glyphs:
                 font = ImageFont.truetype(fontname, int(fontsize*scale))
-                tw, th = draw.textsize(unichr(num), font=font)
+                tw, th = draw.textsize(chr(num), font=font)
                 # No, I don't understand why that 4 is there.
                 # As we used to say in the pure math
                 # department, that was a numerical solution.
@@ -159,7 +161,7 @@ class Math(Flowable):
                 fc=col_conv.to_rgb(self.color)
                 rgb_color=(int(fc[0]*255),int(fc[1]*255),int(fc[2]*255))
                 draw.text((ox*scale, (height - oy - fontsize + 4)*scale),
-                           unichr(num), font=font,fill=rgb_color)
+                           chr(num), font=font,fill=rgb_color)
             for ox, oy, w, h in rects:
                 x1 = ox*scale
                 x2 = x1 + w*scale
diff --git a/rst2pdf/opt_imports.py b/rst2pdf/opt_imports.py
index e2466523..20e8249c 100644
--- a/rst2pdf/opt_imports.py
+++ b/rst2pdf/opt_imports.py
@@ -14,6 +14,7 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import object
 
 import os
 import sys
diff --git a/rst2pdf/pdfbuilder.py b/rst2pdf/pdfbuilder.py
index a10ba389..812796f6 100644
--- a/rst2pdf/pdfbuilder.py
+++ b/rst2pdf/pdfbuilder.py
@@ -16,6 +16,11 @@ from __future__ import print_function
 from __future__ import unicode_literals
 from __future__ import division
 from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from builtins import map
+from builtins import object
 
 import logging
 try:
@@ -32,8 +37,8 @@ from pprint import pprint
 from copy import copy, deepcopy
 from xml.sax.saxutils import unescape, escape
 from traceback import print_exc
-from cStringIO import StringIO
-from urlparse import urljoin, urlparse, urlunparse
+from io import StringIO
+from urllib.parse import urljoin, urlparse, urlunparse
 
 from pygments.lexers import get_lexer_by_name, guess_lexer
 
@@ -87,7 +92,7 @@ class PDFBuilder(Builder):
                     opts={}
                 self.info("processing " + targetname + "... ", nonl=1)
                 self.opts = opts
-                class dummy:
+                class dummy(object):
                     extensions=self.config.pdf_extensions
 
                 createpdf.add_extensions(dummy())
@@ -137,7 +142,7 @@ class PDFBuilder(Builder):
                 self.info(red("FAILED"))
 
     def init_document_data(self):
-        preliminary_document_data = map(list, self.config.pdf_documents)
+        preliminary_document_data = list(map(list, self.config.pdf_documents))
         if not preliminary_document_data:
             self.warn('no "pdf_documents" config value found; no documents '
                       'will be written')
@@ -166,7 +171,7 @@ class PDFBuilder(Builder):
             tree = tree.deepcopy()
             for toctreenode in tree.traverse(addnodes.toctree):
                 newnodes = []
-                includefiles = map(str, toctreenode['includefiles'])
+                includefiles = list(map(str, toctreenode['includefiles']))
                 for includefile in includefiles:
                     try:
                         self.info(darkgreen(includefile) + " ", nonl=1)
@@ -220,7 +225,7 @@ class PDFBuilder(Builder):
         # html_domain_indices can be False/True or a list of index names
         indices_config = self.config.pdf_domain_indices
         if indices_config and hasattr(self.env, 'domains'):
-            for domain in self.env.domains.itervalues():
+            for domain in self.env.domains.values():
                 for indexcls in domain.indices:
                     indexname = '%s-%s' % (domain.name, indexcls.name)
                     if isinstance(indices_config, list):
@@ -347,7 +352,7 @@ class PDFBuilder(Builder):
                 if doc[0]==docname:
                     return "pdf:"+doc[1]+'.pdf'
             # It can be in some other document's toctree
-            for indexname, toctree in self.env.toctree_includes.items():
+            for indexname, toctree in list(self.env.toctree_includes.items()):
                 if docname in toctree:
                     for doc in self.document_data:
                         if doc[0]==indexname:
@@ -568,7 +573,7 @@ class PDFWriter(writers.Writer):
                 def add_template_path(path):
                     return os.path.join(self.srcdir, path)
 
-                cover_path.extend(map(add_template_path, self.config.templates_path))
+                cover_path.extend(list(map(add_template_path, self.config.templates_path)))
 
                 cover_file=None
                 for d in cover_path:
@@ -815,7 +820,7 @@ def try_parse(src):
     if sys.version_info >= (2, 5):
         src = 'from __future__ import with_statement\n' + src
 
-    if isinstance(src, unicode):
+    if isinstance(src, str):
         # Non-ASCII chars will only occur in string literals
         # and comments.  If we wanted to give them to the parser
         # correctly, we'd have to find out the correct source
@@ -905,9 +910,9 @@ def setup(app):
     app.add_config_value('section_header_depth',2, None)
     app.add_config_value('pdf_baseurl', urlunparse(['file',os.getcwd()+os.sep,'','','','']), None)
 
-    author_texescaped = unicode(app.config.copyright)\
+    author_texescaped = str(app.config.copyright)\
                                .translate(texescape.tex_escape_map)
-    project_doc_texescaped = unicode(app.config.project + ' Documentation')\
+    project_doc_texescaped = str(app.config.project + ' Documentation')\
                                      .translate(texescape.tex_escape_map)
     app.config.pdf_documents.append((app.config.master_doc,
                                      app.config.project,
diff --git a/rst2pdf/pygments_code_block_directive.py b/rst2pdf/pygments_code_block_directive.py
index d5a6a662..353bf615 100755
--- a/rst2pdf/pygments_code_block_directive.py
+++ b/rst2pdf/pygments_code_block_directive.py
@@ -30,6 +30,11 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import next
+from builtins import zip
+from builtins import str
+from builtins import range
+from builtins import object
 
 
 # Requirements
@@ -92,7 +97,7 @@ class DocutilsInterface(object):
     def lex(self):
         # Get lexer for language (use text as fallback)
         try:
-            if self.language and unicode(self.language).lower() != 'none':
+            if self.language and str(self.language).lower() != 'none':
                 lexer = get_lexer_by_name(self.language.lower(),
                                         **self.custom_args
                                         )
@@ -249,7 +254,7 @@ def code_block_directive(name, arguments, options, content, lineno,
             # The first piece, pass as-is
             code_block += nodes.Text(values[0], values[0])
             # On the second and later pieces, insert \n and linenos
-            linenos = range(lineno, lineno + len(values))
+            linenos = list(range(lineno, lineno + len(values)))
             for chunk, ln in zip(values, linenos)[1:]:
                 if ln <= total_lines:
                     code_block += nodes.inline(fstr % ln, fstr % ln, classes=['linenumber'])
diff --git a/rst2pdf/rson.py b/rst2pdf/rson.py
index 1fc795fd..47ff878c 100644
--- a/rst2pdf/rson.py
+++ b/rst2pdf/rson.py
@@ -25,6 +25,12 @@ from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
 
+from future.utils import implements_iterator
+
+from builtins import str, bytes, next, object
+from past.builtins import basestring
+
+
 __version__ = '0.08'
 
 __author__ = 'Patrick Maupin <pmaupin@gmail.com>'
@@ -134,7 +140,7 @@ class Tokenizer(list):
     splitter = re.compile(pattern).split
 
     @classmethod
-    def factory(cls, len=len, iter=iter, unicode=unicode, isinstance=isinstance):
+    def factory(cls, len=len, iter=iter, unicode=str, isinstance=isinstance):
         splitter = cls.splitter
         delimiterset = set(cls.delimiterset) | set('"')
 
@@ -143,8 +149,8 @@ class Tokenizer(list):
             self.client = client
 
             # Deal with 8 bit bytes for now
-            if isinstance(source, unicode):
-                source = source.encode('utf-8')
+            if isinstance(source, bytes):
+                source = str(source, 'utf-8')
 
             # Convert MS-DOS or Mac line endings to the one true way
             source = source.replace('\r\n', '\n').replace('\r', '\n')
@@ -157,7 +163,7 @@ class Tokenizer(list):
 
             # Set up to iterate over the source and add to the destination list
             sourceiter = iter(sourcelist)
-            next = sourceiter.next
+            next = sourceiter.__next__
             offset -= len(next())
 
             # Strip comment from first line
@@ -170,7 +176,7 @@ class Tokenizer(list):
 
             # Preallocate the list
             self.append(None)
-            self *= len(sourcelist) / 2 + 1
+            self *= len(sourcelist) // 2 + 1
             index = 0
 
             # Create all the tokens
@@ -243,7 +249,7 @@ def make_hashable(what):
         return what
     except TypeError:
         if isinstance(what, dict):
-            return tuple(sorted(make_hashable(x) for x in what.iteritems()))
+            return tuple(sorted(make_hashable(x) for x in what.items()))
         return tuple(make_hashable(x) for x in what)
 
 class BaseObjects(object):
@@ -270,7 +276,7 @@ class BaseObjects(object):
         ''' By default, RSON objects are dictionaries that
             allow attribute access to their existing contents.
         '''
-        
+
         def __getattr__(self, key):
             return self[key]
         def __setattr__(self, key, value):
@@ -345,7 +351,7 @@ class Dispatcher(object):
             if not kw:
                 return default_loads(s)
 
-            key = tuple(sorted(kw.iteritems()))
+            key = tuple(sorted(kw.items()))
             func = cached(key)
             if func is None:
                 # Begin some real ugliness here -- just modify our instance to
@@ -364,9 +370,9 @@ class QuotedToken(object):
     '''
 
     parse_quoted_str = staticmethod(
-          lambda token, s, unicode=unicode: unicode(s, 'utf-8'))
-    parse_encoded_chr = unichr
-    parse_join_str = u''.join
+          lambda token, s, unicode=str: str(s, 'utf-8'))
+    parse_encoded_chr = chr
+    parse_join_str = ''.join
     cachestrings = False
 
     quoted_splitter = re.compile(r'(\\u[0-9a-fA-F]{4}|\\.|")').split
@@ -408,7 +414,7 @@ class QuotedToken(object):
                 result = [result]
                 append = result.append
                 s = iter(s)
-                next = s.next
+                next = s.__next__
                 next()
                 for special in s:
                     nonmatch = next()
@@ -440,7 +446,8 @@ class QuotedToken(object):
                 nonmatch2 = next()
             except:
                 ok = False
-            ok = ok and not nonmatch and uni2.startswith(r'\u') and len(uni2) == 6
+
+            ok = ok and not nonmatch and uni2.startswith('\\u') and len(uni2) == 6
             if ok:
                 nonmatch = uni2
                 uni = 0x10000 + (((uni - 0xd800) << 10) | (int(uni2[2:], 16) - 0xdc00))
@@ -493,7 +500,7 @@ class UnquotedToken(object):
         lambda s: int(s.replace('_', ''), 0))
     parse_float = float
     parse_unquoted_str = staticmethod(
-        lambda token, unicode=unicode: unicode(token[2], 'utf-8'))
+        lambda token, unicode=str: str(token[2], 'utf-8'))
 
     special_strings = dict(true = True, false = False, null = None)
 
@@ -898,8 +905,8 @@ class RsonParser(object):
             tokens = tokenizer(source, None)
             tokens.stringcache = {}.setdefault
             tokens.client_info = client_info
-            next = tokens.next
-            value, token = parse_recurse([next()], next, tokens)
+            nextval = iter(tokens).__next__
+            value, token = parse_recurse([nextval()], nextval, tokens)
             if token[1] != '@':
                 error('Unexpected additional data', token)
 
diff --git a/rst2pdf/sphinxnodes.py b/rst2pdf/sphinxnodes.py
index 17597c7c..3f2cb24a 100644
--- a/rst2pdf/sphinxnodes.py
+++ b/rst2pdf/sphinxnodes.py
@@ -21,6 +21,7 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import str
 
 from copy import copy
 
@@ -49,7 +50,7 @@ class SphinxHandler(NodeHandler):
             sphinx-specific handlers.
         '''
         mydict = {}
-        for key, value in self._baseclass.dispatchdict.iteritems():
+        for key, value in self._baseclass.dispatchdict.items():
             value = copy(value)
             value.sphinxmode = True
             mydict[key] = value
diff --git a/rst2pdf/styles.py b/rst2pdf/styles.py
index 097abd8c..638a6b59 100644
--- a/rst2pdf/styles.py
+++ b/rst2pdf/styles.py
@@ -8,6 +8,10 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import map
+from builtins import zip
+from builtins import str
+from builtins import object
 import os
 import sys
 import re
@@ -52,7 +56,7 @@ class StyleSheet(object):
         '''
         styles = data.get('styles', {})
         try:
-            stylenames = styles.keys()
+            stylenames = list(styles.keys())
         except AttributeError:
             for style in styles:
                 yield style
@@ -101,13 +105,13 @@ class StyleSheet(object):
         if font_path is None:
             font_path=[]
         font_path+=['.', os.path.join(self.PATH, 'fonts')]
-        self.FontSearchPath = map(os.path.expanduser, font_path)
+        self.FontSearchPath = list(map(os.path.expanduser, font_path))
 
         if style_path is None:
             style_path=[]
         style_path+=['.', os.path.join(self.PATH, 'styles'),
                       '~/.rst2pdf/styles']
-        self.StyleSearchPath = map(os.path.expanduser, style_path)
+        self.StyleSearchPath = list(map(os.path.expanduser, style_path))
         self.FontSearchPath=list(set(self.FontSearchPath))
         self.StyleSearchPath=list(set(self.StyleSearchPath))
 
@@ -224,7 +228,7 @@ class StyleSheet(object):
             for font in embedded:
                 try:
                     # Just a font name, try to embed it
-                    if isinstance(font, unicode):
+                    if isinstance(font, str):
                         # See if we can find the font
                         fname, pos = findfonts.guessFont(font)
                         if font in embedded_fontnames:
diff --git a/rst2pdf/svgimage.py b/rst2pdf/svgimage.py
index 883cdf81..f9aaf025 100644
--- a/rst2pdf/svgimage.py
+++ b/rst2pdf/svgimage.py
@@ -5,6 +5,7 @@ from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
+from builtins import str
 import os
 
 from reportlab.platypus import Flowable, Paragraph
diff --git a/rst2pdf/tenjin.py b/rst2pdf/tenjin.py
index fa6fd13f..c8ee501f 100644
--- a/rst2pdf/tenjin.py
+++ b/rst2pdf/tenjin.py
@@ -43,6 +43,10 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from future import standard_library
+standard_library.install_aliases()
+
+from builtins import str, bytes, object
 
 __revision__ = "$Rev: 137 $"[6:-2]
 __release__  = "0.6.2"
@@ -84,8 +88,8 @@ def _write_file_with_lock(filename, content):
 
 def _create_module(module_name):
     """ex. mod = _create_module('tenjin.util')"""
-    import new
-    mod = new.module(module_name.split('.')[-1])
+    from types import ModuleType
+    mod = ModuleType(module_name.split('.')[-1].encode())
     sys.modules[module_name] = mod
     return mod
 
@@ -109,10 +113,11 @@ def _create_helpers_module():
              >>> to_str(123)
              '123'
         """
-        if val is None:              return ''
-        if isinstance(val, str):     return val
-        if isinstance(val, unicode): return val
-        return str(val)
+        if val is None:
+            return ''
+        if isinstance(val, str):
+            return val
+        return str(val, 'utf-8')
 
     def generate_tostrfunc(encoding):
         """Generate 'to_str' function which encodes unicode to str.
@@ -126,10 +131,11 @@ def _create_helpers_module():
               print output
         """
         def to_str(val):
-            if val is None:               return ''
-            if isinstance(val, str):      return val
-            if isinstance(val, unicode):  return val.encode(encoding)
-            return str(val)
+            if val is None:
+                return ''
+            if isinstance(val, str):
+                return val
+            return str(val, 'utf-8')
         return to_str
 
     def echo(string):
@@ -211,7 +217,7 @@ def _create_helpers_module():
 
     def _decode_params(s):
         """decode <`#...#`> and <`$...$`> into #{...} and ${...}"""
-        from urllib import unquote
+        from urllib.parse import unquote
         dct = { 'lt':'<', 'gt':'>', 'amp':'&', 'quot':'"', '#039':"'", }
         def unescape(s):
             #return s.replace('&lt;', '<').replace('&gt;', '>').replace('&quot;', '"').replace('&#039;', "'").replace('&amp;',  '&')
@@ -489,7 +495,7 @@ class Template(object):
              >>> script = template.convert(input, filename)   # filename is optional
              >>> print script
         """
-        if self.encoding and isinstance(input, str):
+        if self.encoding and isinstance(input, bytes):
             input = input.decode(self.encoding)
         self._reset(input, filename)
         buf = []
@@ -952,8 +958,8 @@ class Engine(object):
 
     def _store_cachefile_for_script(self, cache_filename, template):
         s = template.script
-        if template.encoding and isinstance(s, unicode):
-            s = s.encode(template.encoding)
+        if template.encoding and isinstance(s, bytes):
+            s = str(s, template.encoding)
             #s = s.encode('utf-8')
         if template.args is not None:
             s = "#@ARGS %s\n%s" % (', '.join(template.args), s)
diff --git a/rst2pdf/tests/autotest.py b/rst2pdf/tests/autotest.py
index b3fffb0f..e331ae39 100755
--- a/rst2pdf/tests/autotest.py
+++ b/rst2pdf/tests/autotest.py
@@ -16,6 +16,10 @@ See LICENSE.txt for licensing terms
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from builtins import str
+from builtins import object
 
 import os
 import sys
@@ -108,7 +112,7 @@ class MD5Info(dict):
     def __str__(self):
         ''' Return the string to output to the MD5 file '''
         result = []
-        for name, value in sorted(self.iteritems()):
+        for name, value in sorted(self.items()):
             if not name.endswith(self.suffix):
                 continue
             result.append('%s = [' % name)
@@ -149,17 +153,17 @@ class MD5Info(dict):
         # Create a dictionary of relevant current information
         # in the database.
         oldinfo = dict((key, values)
-                        for (key, values) in self.iteritems()
+                        for (key, values) in self.items()
                             if key.endswith(suffix))
 
         # Create sets and strip the sentinels while
         # working with the dictionary.
         newinfo = dict((key, set(values) - sentinel)
-                    for (key, values) in oldinfo.iteritems())
+                    for (key, values) in oldinfo.items())
 
         # Create an inverse mapping of MD5s to key names
         inverse = {}
-        for key,values in newinfo.iteritems():
+        for key,values in newinfo.items():
             for value in values:
                 inverse.setdefault(value, set()).add(key)
 
@@ -169,7 +173,7 @@ class MD5Info(dict):
         # either report an error, or just remove one of
         # the possible answers if it is the same answer
         # we give by default.
-        for value, keys in inverse.iteritems():
+        for value, keys in inverse.items():
             if len(keys) > 1 and new_key in keys:
                 keys.remove(new_key)
                 newinfo[new_key].remove(value)
@@ -185,7 +189,7 @@ class MD5Info(dict):
 
         # Create a canonical version of the dictionary,
         # by adding sentinels and sorting the results.
-        for key, value in newinfo.iteritems():
+        for key, value in newinfo.items():
             newinfo[key] = sorted(value | sentinel)
 
         # See if we changed anything
@@ -350,7 +354,7 @@ def run_testlist(testfiles=None, incremental=False, fastfork=None, do_text= Fals
             break
     print()
     print('Final checksum statistics:', end=' ')
-    print(', '.join(sorted('%s=%s' % x for x in results.iteritems())))
+    print(', '.join(sorted('%s=%s' % x for x in results.items())))
     print()
 
 def parse_commandline():
diff --git a/rst2pdf/tests/execmgr.py b/rst2pdf/tests/execmgr.py
index 00f1bc73..8f99ece9 100755
--- a/rst2pdf/tests/execmgr.py
+++ b/rst2pdf/tests/execmgr.py
@@ -21,6 +21,10 @@ Currently only works under Linux.
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import chr
+from builtins import str
+from builtins import range
+from builtins import object
 
 import sys
 import subprocess
@@ -198,7 +202,7 @@ class PipeReader(object):
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         ready = self.ready
         if not ready:
             allpipes = list(self.by_pipenum)
@@ -240,7 +244,7 @@ class LineSplitter(object):
 
     def __iter__(self):
         return self
-    def next(self):
+    def __next__(self):
         try:
             return self.prefix, self.lines.pop()
         except IndexError:
@@ -286,7 +290,7 @@ class TextOutExec(BaseExec):
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         lines = self.lines
         while not lines:
             self.checktimeout()
diff --git a/rst2pdf/tests/htmllog.py b/rst2pdf/tests/htmllog.py
index bb769ef0..6f476c34 100755
--- a/rst2pdf/tests/htmllog.py
+++ b/rst2pdf/tests/htmllog.py
@@ -25,7 +25,7 @@ def dumpinfo():
     if not mydict:
         print('\nNo log files found')
     exts = '.txt .style .cli'.split()
-    for name, values in sorted(mydict.iteritems()):
+    for name, values in sorted(mydict.items()):
         print('<h2>')
         print('\nCategory "%s"\n        (%d tests)\n' % (name, len(values)))
         print('</h2>')
diff --git a/rst2pdf/tests/input/pyurl3.py b/rst2pdf/tests/input/pyurl3.py
index 388163c2..7371b6d4 100644
--- a/rst2pdf/tests/input/pyurl3.py
+++ b/rst2pdf/tests/input/pyurl3.py
@@ -6,6 +6,10 @@
 * Definir tests para saber si el atajo es válido
 
 '''
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from builtins import object
 
 import os
 import string
diff --git a/rst2pdf/tests/input/test_extensions.py b/rst2pdf/tests/input/test_extensions.py
index b929ccb4..c9fb2f0d 100644
--- a/rst2pdf/tests/input/test_extensions.py
+++ b/rst2pdf/tests/input/test_extensions.py
@@ -1,7 +1,10 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
 import sys
 
-print """
+print("""
 
 This test should print the message from the sample extension,
 and then this message, and then generate an empty PDF.
-"""
+""")
diff --git a/rst2pdf/tests/input/test_issue_147.py b/rst2pdf/tests/input/test_issue_147.py
index 7da59bef..93092f23 100644
--- a/rst2pdf/tests/input/test_issue_147.py
+++ b/rst2pdf/tests/input/test_issue_147.py
@@ -1,4 +1,7 @@
 # -*- coding: utf-8 -*-
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
 from reportlab.platypus import SimpleDocTemplate, Paragraph
 from reportlab.lib.styles import ParagraphStyle
 from reportlab.lib.styles import getSampleStyleSheet
diff --git a/rst2pdf/tests/input/test_issue_173.py b/rst2pdf/tests/input/test_issue_173.py
index 41328b56..8a375048 100644
--- a/rst2pdf/tests/input/test_issue_173.py
+++ b/rst2pdf/tests/input/test_issue_173.py
@@ -1,6 +1,9 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
 from reportlab.platypus import SimpleDocTemplate, Paragraph
 from reportlab.platypus.doctemplate import Indenter
 from reportlab.platypus.tables import *
diff --git a/rst2pdf/tests/input/test_issue_216.py b/rst2pdf/tests/input/test_issue_216.py
index 523ed644..bf92a741 100644
--- a/rst2pdf/tests/input/test_issue_216.py
+++ b/rst2pdf/tests/input/test_issue_216.py
@@ -1,6 +1,9 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
 from reportlab.platypus import SimpleDocTemplate, Paragraph
 from reportlab.platypus.doctemplate import Indenter
 from reportlab.platypus.flowables import *
diff --git a/rst2pdf/tests/input/test_issue_277.py b/rst2pdf/tests/input/test_issue_277.py
index b3777cd4..a69f6ca8 100755
--- a/rst2pdf/tests/input/test_issue_277.py
+++ b/rst2pdf/tests/input/test_issue_277.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from builtins import range
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
@@ -17,7 +21,7 @@ def go():
         knstyle=copy(styles['Normal'])
         heading=Paragraph('A heading at the beginning of the document',knstyle)
         heading.keepWithNext=True
-        print [['This is the content'] for x in range(12)]
+        print([['This is the content'] for x in range(12)])
         content= Table([[Paragraph('This is the content',styles['Normal'])] for x in range(120)], style=ts)
         
         Story=[heading,content]
diff --git a/rst2pdf/tests/input/test_issue_410.py b/rst2pdf/tests/input/test_issue_410.py
index d16c3a67..b623c97c 100644
--- a/rst2pdf/tests/input/test_issue_410.py
+++ b/rst2pdf/tests/input/test_issue_410.py
@@ -1,3 +1,6 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
 def f():
     a = 1
     b = 2
diff --git a/rst2pdf/tests/input/test_issue_65.py b/rst2pdf/tests/input/test_issue_65.py
index 0204ca29..f82bd878 100644
--- a/rst2pdf/tests/input/test_issue_65.py
+++ b/rst2pdf/tests/input/test_issue_65.py
@@ -1,6 +1,9 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
 from reportlab.platypus import SimpleDocTemplate, Paragraph
 from reportlab.platypus.tables import *
 
diff --git a/rst2pdf/tests/md5/numbered_links_2.json b/rst2pdf/tests/md5/numbered_links_2.json
index 84a6e7d9..5f83f871 100644
--- a/rst2pdf/tests/md5/numbered_links_2.json
+++ b/rst2pdf/tests/md5/numbered_links_2.json
@@ -4,6 +4,7 @@ bad_md5 = [
 ]
 
 good_md5 = [
+		'27a7b8a945e8e85fbf1304c45f8f4ff9',
         '09d2e1c8ce047acc91211b809c08c6e2',
         '61ab76594bad3bdfd35188f710dcd8c5',
         'f3e51cf7febfa9d7c57853242f645868',
diff --git a/rst2pdf/tests/md5/styled_literal.json b/rst2pdf/tests/md5/styled_literal.json
index 5fea81e5..0693182c 100644
--- a/rst2pdf/tests/md5/styled_literal.json
+++ b/rst2pdf/tests/md5/styled_literal.json
@@ -4,6 +4,7 @@ bad_md5 = [
 ]
 
 good_md5 = [
+		'b766b7b1220a048a5712d72896892fba',
         '979f8ae305dec6a4c9c40d008343ae18',
         'afa33c1a8a168dff4e513c83ca1e374f',
         'sentinel'
diff --git a/rst2pdf/tests/md5/test_aafigure.json b/rst2pdf/tests/md5/test_aafigure.json
index d84b36e3..dde22319 100644
--- a/rst2pdf/tests/md5/test_aafigure.json
+++ b/rst2pdf/tests/md5/test_aafigure.json
@@ -3,6 +3,7 @@ bad_md5 = [
 ]
 
 good_md5 = [
+        '03c9e457550335e66d704b8cacb7bfad',
         '0f87f776b8f61ee3ebbf69e49ca990c6',
         '136639903c59cbddbc25ec36fdeb0c25',
         '1e4668aa525c6dca2773934da9544448',
diff --git a/rst2pdf/tests/md5/test_aafigure_extra.json b/rst2pdf/tests/md5/test_aafigure_extra.json
index d3efbb43..c5727b00 100644
--- a/rst2pdf/tests/md5/test_aafigure_extra.json
+++ b/rst2pdf/tests/md5/test_aafigure_extra.json
@@ -6,6 +6,7 @@ bad_md5 = [
 ]
 
 good_md5 = [
+        '275dbd1726625375b63a9eba79a1052c',
         '1430e87ba0e62c01414ebdde33b9b8fa',
         '23c99b1070cb2c304277246bf91d41bc',
         '28ed393c1f27466ad45e2866a4d75111',
diff --git a/rst2pdf/tests/md5/test_autoembedding_type1.json b/rst2pdf/tests/md5/test_autoembedding_type1.json
index 4a7ce644..af69562c 100644
--- a/rst2pdf/tests/md5/test_autoembedding_type1.json
+++ b/rst2pdf/tests/md5/test_autoembedding_type1.json
@@ -3,6 +3,7 @@ bad_md5 = [
 ]
 
 good_md5 = [
+        'f57ae0c94eeddb59ae51658ee7fb8c9e',
         '3a79d1604db56d150baa165b2fed4e44',
         '68f024d9c7e62966d7ee8d153c717f54',
         '8abc82b50940b7687980a70dbc8ae31e',
diff --git a/rst2pdf/tests/migrate_checksums.py b/rst2pdf/tests/migrate_checksums.py
index 4727aa86..ca8da929 100755
--- a/rst2pdf/tests/migrate_checksums.py
+++ b/rst2pdf/tests/migrate_checksums.py
@@ -67,7 +67,7 @@ def dumpinfo():
     if not mydict:
         print('\nNo log files found')
     migrate = set('good bad incomplete'.split())
-    for checksum_result, values in sorted(mydict.iteritems()):
+    for checksum_result, values in sorted(mydict.items()):
         if checksum_result not in migrate:
             continue
         names = ' '.join('input/%s' % x[0] for x in values)
diff --git a/rst2pdf/tests/parselogs.py b/rst2pdf/tests/parselogs.py
index 154811a1..35b48ea9 100755
--- a/rst2pdf/tests/parselogs.py
+++ b/rst2pdf/tests/parselogs.py
@@ -46,7 +46,7 @@ def dumpinfo():
     mydict = getcategories()
     if not mydict:
         print('\nNo log files found')
-    for name, values in sorted(mydict.iteritems()):
+    for name, values in sorted(mydict.items()):
         print('\nCategory "%s"\n        (%d tests)\n' % (name, len(values)))
         fmt = '%%-%ds  %%s' % max(len(x[0]) for x in values)
         for item in sorted(values):
diff --git a/rst2pdf/tests/test.py b/rst2pdf/tests/test.py
index 22ab0702..4aefcdbb 100644
--- a/rst2pdf/tests/test.py
+++ b/rst2pdf/tests/test.py
@@ -3,13 +3,14 @@
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import object, str, open
 from autotest import MD5Info, PathInfo, globjoin
 from autotest import run_single, dirname, checkmd5
 
 import sys, os
 import nose.plugins.skip
 
-class RunTest:
+class RunTest(object):
     def __init__(self,f):
         basename = os.path.basename(f)
         self.description = basename 
@@ -22,9 +23,8 @@ class RunTest:
         if os.path.exists(ignfile):
             self.skip=True
         if os.path.exists(md5file):
-            f = open(md5file, 'rb')
-            exec(f, info)
-            f.close()
+            with open(md5file, 'rb') as f:
+                exec(f.read(), info)
         if info.good_md5 in [[],['sentinel']]:
             # This is an open issue or something that can't be checked automatically
             self.openIssue=True
@@ -82,7 +82,7 @@ def run_installed_single(inpfname):
     return checkinfo, errcode
 
 
-class RunInstalledTest:
+class RunInstalledTest(object):
     def __init__(self,f):
         basename = os.path.basename(f)
         self.description = basename 
@@ -113,7 +113,7 @@ class RunInstalledTest:
                 raise nose.plugins.skip.SkipTest
             assert key == 'good', '%s is not good: %s'%(f,key)
 
-class RunSphinxTest:
+class RunSphinxTest(object):
     def __init__(self,f):
         basename = os.path.basename(f[:-1])
         self.description = basename 
diff --git a/rst2pdf/tests/zinspector12.py b/rst2pdf/tests/zinspector12.py
index 5c38b1dd..5c52bd76 100755
--- a/rst2pdf/tests/zinspector12.py
+++ b/rst2pdf/tests/zinspector12.py
@@ -82,7 +82,7 @@ def getimports(importf, ignore):
         exec(code, newdir)
         for key in basedir:
             del newdir[key]
-        for key, value in newdir.iteritems():
+        for key, value in newdir.items():
             oldcode = importinfo.setdefault(key, code)
             if oldcode is not code:
                 badimports.append((key, oldcode, code, globaldir[key], value))
@@ -105,7 +105,7 @@ def checkimports(importf, ignore):
     printed = False
 
     fmt = "\nSame '%s' imported twice:\n\n%s\n%s"
-    for (oldcode, code), deflist in sorted(same.iteritems()):
+    for (oldcode, code), deflist in sorted(same.items()):
         nevermind = set()
         x, y = oldcode.split('.', 1)[0], code.split('.', 1)[0]
         if ('.' in oldcode and '.' in code and
@@ -117,7 +117,7 @@ def checkimports(importf, ignore):
             printed = True
 
     fmt = "\nConflicting definitions for %s:\n\n%s\n%s"
-    for (oldcode, code), deflist in sorted(diff.iteritems()):
+    for (oldcode, code), deflist in sorted(diff.items()):
         deflist = ', '.join(sorted(deflist))
         print(fmt % (deflist, oldcode, code))
         printed = True
diff --git a/rst2pdf/uniconvsaver.py b/rst2pdf/uniconvsaver.py
index a4df3ea6..5a612107 100644
--- a/rst2pdf/uniconvsaver.py
+++ b/rst2pdf/uniconvsaver.py
@@ -29,6 +29,8 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
+from builtins import range
+from builtins import object
 from math import atan2, pi
 import PIL
 from app import _,Bezier, EmptyPattern, Rotation, Translation, _sketch
@@ -53,7 +55,7 @@ def make_pdf_path(pdfpath, paths):
 			pdfpath.close()
 	return pdfpath
 
-class PDFDevice:
+class PDFDevice(object):
 
 	has_axial_gradient = 0
 	has_radial_gradient = 0
@@ -140,7 +142,7 @@ class PDFDevice:
 
 
 
-class PDFGenSaver:
+class PDFGenSaver(object):
 
 	def __init__(self, file, filename, document, options):
 		self.file = file
diff --git a/rst2pdf/utils.py b/rst2pdf/utils.py
index 59da1371..80aeb5c0 100644
--- a/rst2pdf/utils.py
+++ b/rst2pdf/utils.py
@@ -8,6 +8,7 @@ from __future__ import print_function
 from __future__ import absolute_import
 from __future__ import unicode_literals
 from __future__ import division
+from builtins import str
 import shlex
 
 from .flowables import *
diff --git a/rst2pdf/writer.py b/rst2pdf/writer.py
index 0bf7c803..d2ffbc83 100644
--- a/rst2pdf/writer.py
+++ b/rst2pdf/writer.py
@@ -5,7 +5,10 @@ from __future__ import unicode_literals
 from __future__ import print_function
 from __future__ import division
 from __future__ import absolute_import
-from StringIO import StringIO
+from future import standard_library
+standard_library.install_aliases()
+from builtins import str
+from io import StringIO
 
 from docutils import writers
 
@@ -30,7 +33,7 @@ class PdfWriter(writers.Writer):
         sio = StringIO('')
         createpdf.RstToPdf(sphinx=True).createPdf(
             doctree=self.document, output=sio, compressed=False)
-        self.output = unicode(sio.getvalue(), 'utf-8', 'ignore')
+        self.output = str(sio.getvalue(), 'utf-8', 'ignore')
 
     def supports(self, format):
         """This writer supports all format-specific elements."""
diff --git a/setup.py b/setup.py
index 1e287a98..eef34f23 100644
--- a/setup.py
+++ b/setup.py
@@ -41,6 +41,7 @@ install_requires = [
         'reportlab>=2.4',
         'Pygments',
         'pdfrw',
+        'future',
         ]
 
 try:
['gui/Ui_pagetemplates.py', 'rst2pdf/tests/autotest.py', 'rst2pdf/tests/md5/numbered_links_2.json', 'rst2pdf/extensions/vectorpdf_r2p.py', 'rst2pdf/svgimage.py', 'rst2pdf/tests/md5/test_aafigure.json', 'rst2pdf/tests/md5/test_autoembedding_type1.json', 'gui/Ui_searchwidget.py', 'rst2pdf/genelements.py', 'gui/main.py', 'rst2pdf/tests/migrate_checksums.py', 'gui/Ui_stylesheets.py', 'rst2pdf/languages.py', '.travis.yml', 'rst2pdf/tests/test.py', 'gui/configdialog.py', 'rst2pdf/flowables.py', 'rst2pdf/utils.py', 'rst2pdf/tests/execmgr.py', 'rst2pdf/tests/input/test_issue_216.py', 'bootstrap.py', 'rst2pdf/dumpstyle.py', '.gitignore', 'rst2pdf/extensions/inkscape_r2p.py', 'rst2pdf/tenjin.py', 'rst2pdf/tests/md5/styled_literal.json', 'rst2pdf/tests/input/test_issue_410.py', 'rst2pdf/opt_imports.py', 'rst2pdf/basenodehandler.py', 'rst2pdf/log.py', 'rst2pdf/image.py', 'rst2pdf/styles.py', 'gui/Ui_pagesetup.py', 'rst2pdf/sphinxnodes.py', 'gui/Ui_about.py', 'gui/Ui_pdf.py', 'rst2pdf/pdfbuilder.py', 'rst2pdf/extensions/dotted_toc.py', 'rst2pdf/tests/input/test_issue_65.py', 'rst2pdf/tests/parselogs.py', 'setup.py', 'gui/codeeditor.py', 'rst2pdf/math_flowable.py', 'rst2pdf/tests/input/test_extensions.py', 'rst2pdf/extensions/preprocess_r2p.py', 'rst2pdf/tests/input/test_issue_277.py', 'rst2pdf/findfonts.py', 'rst2pdf/writer.py', 'gui/Ui_configdialog.py', 'rst2pdf/extensions/fancytitles.py', 'gui/Ui_main.py', 'rst2pdf/counter_role.py', 'rst2pdf/rson.py', 'rst2pdf/uniconvsaver.py', 'rst2pdf/tests/input/test_issue_147.py', 'rst2pdf/tests/input/pyurl3.py', 'gui/Ui_style.py', 'rst2pdf/config.py', 'rst2pdf/tests/input/test_issue_173.py', 'gui/highlighter.py', 'rst2pdf/pygments_code_block_directive.py', 'rst2pdf/createpdf.py', 'requirements.txt', 'rst2pdf/tests/md5/test_aafigure_extra.json', 'rst2pdf/genpdftext.py', 'rst2pdf/tests/htmllog.py', 'rst2pdf/tests/zinspector12.py']