~ubuntu-branches/ubuntu/karmic/calibre/karmic

« back to all changes in this revision

Viewing changes to src/calibre/ebooks/rtf/rtfml.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090730124941-qjdsmri25zt8zocn
Tags: 0.6.3+dfsg-0ubuntu1
* New upstream release. Please see http://calibre.kovidgoyal.net/new_in_6/
  for the list of new features and changes.
* remove_postinstall.patch: Update for new version.
* build_debug.patch: Does not apply any more, disable for now. Might not be
  necessary any more.
* debian/copyright: Fix reference to versionless GPL.
* debian/rules: Drop obsolete dh_desktop call.
* debian/rules: Add workaround for weird Python 2.6 setuptools behaviour of
  putting compiled .so files into src/calibre/plugins/calibre/plugins
  instead of src/calibre/plugins.
* debian/rules: Drop hal fdi moving, new upstream version does not use hal
  any more. Drop hal dependency, too.
* debian/rules: Install udev rules into /lib/udev/rules.d.
* Add debian/calibre.preinst: Remove unmodified
  /etc/udev/rules.d/95-calibre.rules on upgrade.
* debian/control: Bump Python dependencies to 2.6, since upstream needs
  it now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
 
 
3
__license__ = 'GPL 3'
 
4
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
 
5
__docformat__ = 'restructuredtext en'
 
6
 
 
7
'''
 
8
Transform OEB content into RTF markup
 
9
'''
 
10
 
 
11
import os
 
12
import re
 
13
 
 
14
try:
 
15
    from PIL import Image
 
16
    Image
 
17
except ImportError:
 
18
    import Image
 
19
 
 
20
import cStringIO
 
21
 
 
22
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace, \
 
23
    OEB_IMAGES
 
24
from calibre.ebooks.oeb.stylizer import Stylizer
 
25
from calibre.ebooks.metadata import authors_to_string
 
26
 
 
27
TAGS = {
 
28
    'b': '\\b',
 
29
    'del': '\\deleted',
 
30
    'h1': '\\b \\par \\pard \\hyphpar',
 
31
    'h2': '\\b \\par \\pard \\hyphpar',
 
32
    'h3': '\\b \\par \\pard \\hyphpar',
 
33
    'h4': '\\b \\par \\pard \\hyphpar',
 
34
    'h5': '\\b \\par \\pard \\hyphpar',
 
35
    'h6': '\\b \\par \\pard \\hyphpar',
 
36
    'li': '\\par \\pard \\hyphpar \t',
 
37
    'p': '\\par \\pard \\hyphpar \t',
 
38
    'sub': '\\sub',
 
39
    'sup': '\\super',
 
40
    'u': '\\ul',
 
41
}
 
42
 
 
43
SINGLE_TAGS = {
 
44
    'br': '\n{\\line }\n',
 
45
    'div': '\n{\\line }\n',
 
46
}
 
47
 
 
48
SINGLE_TAGS_END = {
 
49
    'div': '\n{\\line }\n',
 
50
}
 
51
 
 
52
STYLES = [
 
53
    ('display', {'block': '\\par \\pard \\hyphpar'}),
 
54
    ('font-weight', {'bold': '\\b', 'bolder': '\\b'}),
 
55
    ('font-style', {'italic': '\\i'}),
 
56
    ('text-align', {'center': '\\qc', 'left': '\\ql', 'right': '\\qr'}),
 
57
    ('text-decoration', {'line-through': '\\strike', 'underline': '\\ul'}),
 
58
]
 
59
 
 
60
BLOCK_TAGS = [
 
61
    'p',
 
62
    'h1',
 
63
    'h2',
 
64
    'h3',
 
65
    'h4',
 
66
    'h5',
 
67
    'h6',
 
68
    'li',
 
69
]
 
70
 
 
71
BLOCK_STYLES = [
 
72
    'block'
 
73
]
 
74
 
 
75
'''
 
76
TODO:
 
77
    * Tables
 
78
    * Fonts
 
79
'''
 
80
class RTFMLizer(object):
 
81
 
 
82
    def __init__(self, log):
 
83
        self.log = log
 
84
 
 
85
    def extract_content(self, oeb_book, opts):
 
86
        self.log.info('Converting XHTML to RTF markup...')
 
87
        self.oeb_book = oeb_book
 
88
        self.opts = opts
 
89
        return self.mlize_spine()
 
90
 
 
91
    def mlize_spine(self):
 
92
        output = self.header()
 
93
        if 'titlepage' in self.oeb_book.guide:
 
94
            href = self.oeb_book.guide['titlepage'].href
 
95
            item = self.oeb_book.manifest.hrefs[href]
 
96
            if item.spine_position is None:
 
97
                stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts.output_profile)
 
98
                output += self.dump_text(item.data.find(XHTML('body')), stylizer)
 
99
                output += '{\\page } '
 
100
        for item in self.oeb_book.spine:
 
101
            self.log.debug('Converting %s to RTF markup...' % item.href)
 
102
            stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts.output_profile)
 
103
            output += self.dump_text(item.data.find(XHTML('body')), stylizer)
 
104
        output += self.footer()
 
105
        output = self.insert_images(output)
 
106
        output = self.clean_text(output)
 
107
 
 
108
        return output
 
109
 
 
110
    def header(self):
 
111
        return u'{\\rtf1{\\info{\\title %s}{\\author %s}}\\ansi\\ansicpg1252\\deff0\\deflang1033' % (self.oeb_book.metadata.title[0].value, authors_to_string([x.value for x in self.oeb_book.metadata.creator]))
 
112
 
 
113
    def footer(self):
 
114
        return ' }'
 
115
 
 
116
    def insert_images(self, text):
 
117
        for item in self.oeb_book.manifest:
 
118
            if item.media_type in OEB_IMAGES:
 
119
                src = os.path.basename(item.href)
 
120
                data, width, height = self.image_to_hexstring(item.data)
 
121
                text = text.replace('SPECIAL_IMAGE-%s-REPLACE_ME' % src, '\n\n{\\*\\shppict{\\pict\\picw%i\\pich%i\\jpegblip \n%s\n}}\n\n' % (width, height, data))
 
122
        return text
 
123
 
 
124
    def image_to_hexstring(self, data):
 
125
        im = Image.open(cStringIO.StringIO(data))
 
126
        data = cStringIO.StringIO()
 
127
        im.save(data, 'JPEG')
 
128
        data = data.getvalue()
 
129
 
 
130
        raw_hex = ''
 
131
        for char in data:
 
132
            raw_hex += hex(ord(char)).replace('0x', '').rjust(2, '0')
 
133
 
 
134
        # Images must be broken up so that they are no longer than 129 chars
 
135
        # per line
 
136
        hex_string = ''
 
137
        col = 1
 
138
        for char in raw_hex:
 
139
            if col == 129:
 
140
                hex_string += '\n'
 
141
                col = 1
 
142
            col += 1
 
143
            hex_string += char
 
144
 
 
145
        return (hex_string, im.size[0], im.size[1])
 
146
 
 
147
    def clean_text(self, text):
 
148
        # Remove excess spaces at beginning and end of lines
 
149
        text = re.sub('(?m)^[ ]+', '', text)
 
150
        text = re.sub('(?m)[ ]+$', '', text)
 
151
 
 
152
        # Remove excessive newlines
 
153
        #text = re.sub('%s{1,1}' % os.linesep, '%s%s' % (os.linesep, os.linesep), text)
 
154
        text = re.sub('%s{3,}' % os.linesep, '%s%s' % (os.linesep, os.linesep), text)
 
155
 
 
156
        # Remove excessive spaces
 
157
        text = re.sub('[ ]{2,}', ' ', text)
 
158
 
 
159
        text = re.sub(r'(\{\\line \}\s*){3,}', r'{\\line }{\\line }', text)
 
160
        #text = re.compile(r'(\{\\line \}\s*)+(?P<brackets>}*)\s*\{\\par').sub(lambda mo: r'%s{\\par' % mo.group('brackets'), text)
 
161
 
 
162
        # Remove non-breaking spaces
 
163
        text = text.replace(u'\xa0', ' ')
 
164
        text = text.replace('\n\r', '\n')
 
165
 
 
166
        return text
 
167
 
 
168
    def dump_text(self, elem, stylizer, tag_stack=[]):
 
169
        if not isinstance(elem.tag, basestring) \
 
170
           or namespace(elem.tag) != XHTML_NS:
 
171
            return u''
 
172
 
 
173
        text = u''
 
174
        style = stylizer.style(elem)
 
175
 
 
176
        if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \
 
177
           or style['visibility'] == 'hidden':
 
178
            return u''
 
179
 
 
180
        tag = barename(elem.tag)
 
181
        tag_count = 0
 
182
 
 
183
        # Are we in a paragraph block?
 
184
        if tag in BLOCK_TAGS or style['display'] in BLOCK_STYLES:
 
185
            if 'block' not in tag_stack:
 
186
                tag_count += 1
 
187
                tag_stack.append('block')
 
188
 
 
189
        # Process tags that need special processing and that do not have inner
 
190
        # text. Usually these require an argument
 
191
        if tag == 'img':
 
192
            src = os.path.basename(elem.get('src'))
 
193
            block_start = ''
 
194
            block_end = ''
 
195
            if 'block' not in tag_stack:
 
196
                block_start = '{\\par \\pard \\hyphpar '
 
197
                block_end = '}'
 
198
            text += '%s SPECIAL_IMAGE-%s-REPLACE_ME %s' % (block_start, src, block_end)
 
199
 
 
200
        single_tag = SINGLE_TAGS.get(tag, None)
 
201
        if single_tag:
 
202
            text += single_tag
 
203
 
 
204
        rtf_tag = TAGS.get(tag, None)
 
205
        if rtf_tag and rtf_tag not in tag_stack:
 
206
            tag_count += 1
 
207
            text += '{%s\n' % rtf_tag
 
208
            tag_stack.append(rtf_tag)
 
209
 
 
210
        # Processes style information
 
211
        for s in STYLES:
 
212
            style_tag = s[1].get(style[s[0]], None)
 
213
            if style_tag and style_tag not in tag_stack:
 
214
                tag_count += 1
 
215
                text += '{%s\n' % style_tag
 
216
                tag_stack.append(style_tag)
 
217
 
 
218
        # Proccess tags that contain text.
 
219
        if hasattr(elem, 'text') and elem.text != None and elem.text.strip() != '':
 
220
            text += '%s' % elem.text
 
221
 
 
222
        for item in elem:
 
223
            text += self.dump_text(item, stylizer, tag_stack)
 
224
 
 
225
        for i in range(0, tag_count):
 
226
            end_tag =  tag_stack.pop()
 
227
            if end_tag != 'block':
 
228
                text += u'}'
 
229
 
 
230
        single_tag_end = SINGLE_TAGS_END.get(tag, None)
 
231
        if single_tag_end:
 
232
            text += single_tag_end
 
233
 
 
234
        if hasattr(elem, 'tail') and elem.tail != None and elem.tail.strip() != '':
 
235
            if 'block' in tag_stack:
 
236
                text += '%s ' % elem.tail
 
237
            else:
 
238
                text += '{\\par \\pard \\hyphpar %s}' % elem.tail
 
239
 
 
240
        return text