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

« back to all changes in this revision

Viewing changes to src/calibre/ebooks/lrf/__init__.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
1
__license__   = 'GPL v3'
2
2
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
3
 
""" 
4
 
This package contains logic to read and write LRF files. 
5
 
The LRF file format is documented at U{http://www.sven.de/librie/Librie/LrfFormat}. 
6
 
"""
7
 
import sys, os
8
 
from optparse import OptionValueError
9
 
from htmlentitydefs import name2codepoint
 
3
"""
 
4
This package contains logic to read and write LRF files.
 
5
The LRF file format is documented at U{http://www.sven.de/librie/Librie/LrfFormat}.
 
6
"""
10
7
from uuid import uuid4
11
8
 
12
9
from calibre.ebooks.lrf.pylrs.pylrs import Book as _Book
13
 
from calibre.ebooks.lrf.pylrs.pylrs import TextBlock, Header, PutObj, \
14
 
                                             Paragraph, TextStyle, BlockStyle
 
10
from calibre.ebooks.lrf.pylrs.pylrs import TextBlock, Header, \
 
11
                                             TextStyle, BlockStyle
15
12
from calibre.ebooks.lrf.fonts import FONT_FILE_MAP
16
13
from calibre.ebooks import ConversionError
17
 
from calibre import __appname__, __version__, __author__, iswindows
18
 
from calibre.utils.config import OptionParser
19
14
 
20
15
__docformat__ = "epytext"
21
16
 
22
 
preferred_source_formats = [
23
 
                            'LIT',
24
 
                            'MOBI',
25
 
                            'EPUB',
26
 
                            'ODT',
27
 
                            'HTML',
28
 
                            'HTM',
29
 
                            'XHTM',
30
 
                            'XHTML',
31
 
                            'PRC',
32
 
                            'AZW',
33
 
                            'FB2',
34
 
                            'RTF',
35
 
                            'PDF',
36
 
                            'TXT',
37
 
                            'ZIP',
38
 
                            'RAR'
39
 
                            ]
40
 
 
41
17
class LRFParseError(Exception):
42
18
    pass
43
19
 
55
31
    header_height    = 30 #: In px
56
32
    default_fonts    = { 'sans': "Swis721 BT Roman", 'mono': "Courier10 BT Roman",
57
33
                         'serif': "Dutch801 Rm BT Roman"}
58
 
    
59
 
    name = 'prs500' 
60
 
    
61
 
profile_map = {
62
 
               PRS500_PROFILE.name : PRS500_PROFILE,
63
 
               }
64
 
    
65
 
def profile_from_string(option, opt_str, value, parser):
66
 
    try:
67
 
        profile = profile_map[value]
68
 
        setattr(parser.values, option.dest, profile)
69
 
    except KeyError:
70
 
        raise OptionValueError('Profile: '+value+' is not implemented. Implemented profiles: %s'%(profile_map.keys()))
71
 
    
72
 
def option_parser(usage, gui_mode=False):
73
 
    parser = OptionParser(usage=usage, gui_mode=gui_mode)
74
 
    metadata = parser.add_option_group('METADATA OPTIONS')
75
 
    metadata.add_option("-t", "--title", action="store", type="string", default=None,\
76
 
                    dest="title", help=_("Set the title. Default: filename."))
77
 
    metadata.add_option("-a", "--author", action="store", type="string", \
78
 
                    dest="author", help=_("Set the author(s). Multiple authors should be set as a comma separated list. Default: %default"), 
79
 
                    default=_('Unknown'))
80
 
    metadata.add_option("--comment", action="store", type="string", \
81
 
                    dest="freetext", help=_("Set the comment."), default=_('Unknown'))
82
 
    metadata.add_option("--category", action="store", type="string", \
83
 
                    dest="category", help=_("Set the category"), default=_('Unknown'))    
84
 
    metadata.add_option('--title-sort', action='store', default='', dest='title_sort',
85
 
                      help=_('Sort key for the title'))
86
 
    metadata.add_option('--author-sort', action='store', default='', dest='author_sort',
87
 
                      help=_('Sort key for the author'))
88
 
    metadata.add_option('--publisher', action='store', default=_('Unknown'), dest='publisher',
89
 
                      help=_('Publisher'))
90
 
    metadata.add_option('--cover', action='store', dest='cover', default=None, \
91
 
                        help=_('Path to file containing image to be used as cover'))
92
 
    metadata.add_option('--use-metadata-cover', action='store_true', default=False, 
93
 
                        help=_('If there is a cover graphic detected in the source file, use that instead of the specified cover.'))
94
 
     
95
 
    parser.add_option('-o', '--output', action='store', default=None, \
96
 
                      help=_('Output file name. Default is derived from input filename'))
97
 
    parser.add_option('--ignore-tables', action='store_true', default=False, dest='ignore_tables',
98
 
                      help=_('Render HTML tables as blocks of text instead of actual tables. This is neccessary if the HTML contains very large or complex tables.'))
99
 
    laf = parser.add_option_group('LOOK AND FEEL')
100
 
    laf.add_option('--base-font-size', action='store', type='float', default=10.,
101
 
                   help=_('''Specify the base font size in pts. All fonts are rescaled accordingly. This option obsoletes the --font-delta option and takes precedence over it. To use --font-delta, set this to 0. Default: %defaultpt'''))
102
 
    laf.add_option('--enable-autorotation', action='store_true', default=False, 
103
 
                   help=_('Enable autorotation of images that are wider than the screen width.'), 
104
 
                   dest='autorotation')
105
 
    laf.add_option('--wordspace', dest='wordspace', default=2.5, type='float',
106
 
                   help=_('Set the space between words in pts. Default is %default'))
107
 
    laf.add_option('--blank-after-para', action='store_true', default=False,
108
 
                   dest='blank_after_para', help=_('Separate paragraphs by blank lines.'))
109
 
    laf.add_option('--header', action='store_true', default=False, dest='header',
110
 
                      help=_('Add a header to all the pages with title and author.'))
111
 
    laf.add_option('--headerformat', default="%t by %a", dest='headerformat', type='string',
112
 
                        help=_('Set the format of the header. %a is replaced by the author and %t by the title. Default is %default'))
113
 
    laf.add_option('--header-separation', default=0, type='int', 
114
 
                   help=_('Add extra spacing below the header. Default is %default px.'))
115
 
    laf.add_option('--override-css', default=None, dest='_override_css', type='string',
116
 
                   help=_('Override the CSS. Can be either a path to a CSS stylesheet or a string. If it is a string it is interpreted as CSS.'))
117
 
    laf.add_option('--use-spine', default=False, dest='use_spine', action='store_true',
118
 
                   help=_('Use the <spine> element from the OPF file to determine the order in which the HTML files are appended to the LRF. The .opf file must be in the same directory as the base HTML file.'))
119
 
    laf.add_option('--minimum-indent', default=0, type='float', 
120
 
                   help=_('Minimum paragraph indent (the indent of the first line of a paragraph) in pts. Default: %default'))
121
 
    laf.add_option('--font-delta', action='store', type='float', default=0., \
122
 
                  help=_("""Increase the font size by 2 * FONT_DELTA pts and """
123
 
                  '''the line spacing by FONT_DELTA pts. FONT_DELTA can be a fraction.'''
124
 
                  """If FONT_DELTA is negative, the font size is decreased."""),
125
 
                  dest='font_delta')
126
 
    laf.add_option('--ignore-colors', action='store_true', default=False, dest='ignore_colors',
127
 
                      help=_('Render all content as black on white instead of the colors specified by the HTML or CSS.'))
128
 
    
129
 
    page = parser.add_option_group('PAGE OPTIONS')
130
 
    profiles = profile_map.keys()
131
 
    page.add_option('-p', '--profile', default=PRS500_PROFILE, dest='profile', type='choice',
132
 
                      choices=profiles, action='callback', callback=profile_from_string,
133
 
                      help=_('''Profile of the target device for which this LRF is '''
134
 
                      '''being generated. The profile determines things like the '''
135
 
                      '''resolution and screen size of the target device. '''
136
 
                      '''Default: %s Supported profiles: ''')%(PRS500_PROFILE.name,)+\
137
 
                      ', '.join(profiles))
138
 
    page.add_option('--left-margin', default=20, dest='left_margin', type='int',
139
 
                    help=_('''Left margin of page. Default is %default px.'''))
140
 
    page.add_option('--right-margin', default=20, dest='right_margin', type='int',
141
 
                    help=_('''Right margin of page. Default is %default px.'''))
142
 
    page.add_option('--top-margin', default=10, dest='top_margin', type='int',
143
 
                    help=_('''Top margin of page. Default is %default px.'''))
144
 
    page.add_option('--bottom-margin', default=0, dest='bottom_margin', type='int',
145
 
                    help=_('''Bottom margin of page. Default is %default px.'''))
146
 
    page.add_option('--render-tables-as-images', default=False, action='store_true',
147
 
                   help=_('Render tables in the HTML as images (useful if the document has large or complex tables)'))
148
 
    page.add_option('--text-size-multiplier-for-rendered-tables', type='float', default=1.0,
149
 
                   help=_('Multiply the size of text in rendered tables by this factor. Default is %default'))
150
 
    
151
 
    link = parser.add_option_group('LINK PROCESSING OPTIONS')
152
 
    link.add_option('--link-levels', action='store', type='int', default=sys.maxint, \
153
 
                      dest='link_levels',
154
 
                      help=_(r'''The maximum number of levels to recursively process '''
155
 
                              '''links. A value of 0 means thats links are not followed. '''
156
 
                              '''A negative value means that <a> tags are ignored.'''))
157
 
    link.add_option('--link-exclude', dest='link_exclude', default='@',
158
 
                      help=_('''A regular expression. <a> tags whose href '''
159
 
                      '''matches will be ignored. Defaults to %default'''))
160
 
    link.add_option('--no-links-in-toc', action='store_true', default=False,
161
 
                      dest='no_links_in_toc',
162
 
                      help=_('''Don't add links to the table of contents.'''))
163
 
    chapter = parser.add_option_group('CHAPTER OPTIONS')
164
 
    chapter.add_option('--disable-chapter-detection', action='store_true', 
165
 
                      default=False, dest='disable_chapter_detection', 
166
 
                      help=_('''Prevent the automatic detection chapters.'''))
167
 
    chapter.add_option('--chapter-regex', dest='chapter_regex', 
168
 
                      default='chapter|book|appendix',
169
 
                      help=_('''The regular expression used to detect chapter titles.'''
170
 
                      ''' It is searched for in heading tags (h1-h6). Defaults to %default'''))
171
 
    chapter.add_option('--chapter-attr', default='$,,$', 
172
 
                       help=_('Detect a chapter beginning at an element having the specified attribute. The format for this option is tagname regexp,attribute name,attribute value regexp. For example to match all heading tags that have the attribute class="chapter" you would use "h\d,class,chapter". You can set the attribute to "none" to match only on tag names. So for example, to match all h2 tags, you would use "h2,none,". Default is %default'''))
173
 
    chapter.add_option('--page-break-before-tag', dest='page_break', default='h[12]',
174
 
                      help=_('''If html2lrf does not find any page breaks in the '''
175
 
                      '''html file and cannot detect chapter headings, it will '''
176
 
                      '''automatically insert page-breaks before the tags whose '''
177
 
                      '''names match this regular expression. Defaults to %default. '''
178
 
                      '''You can disable it by setting the regexp to "$". '''
179
 
                      '''The purpose of this option is to try to ensure that '''
180
 
                      '''there are no really long pages as this degrades the page '''
181
 
                      '''turn performance of the LRF. Thus this option is ignored '''
182
 
                      '''if the current page has only a few elements.'''))
183
 
    chapter.add_option('--force-page-break-before-tag', dest='force_page_break',
184
 
                       default='$', help=_('Force a page break before tags whose names match this regular expression.'))
185
 
    chapter.add_option('--force-page-break-before-attr', dest='force_page_break_attr',
186
 
                       default='$,,$', help=_('Force a page break before an element having the specified attribute. The format for this option is tagname regexp,attribute name,attribute value regexp. For example to match all heading tags that have the attribute class="chapter" you would use "h\d,class,chapter". Default is %default'''))
187
 
    chapter.add_option('--add-chapters-to-toc', action='store_true', 
188
 
                      default=False, dest='add_chapters_to_toc', 
189
 
                      help=_('''Add detected chapters to the table of contents.'''))
190
 
    prepro = parser.add_option_group('PREPROCESSING OPTIONS')
191
 
    prepro.add_option('--baen', action='store_true', default=False, dest='baen',
192
 
                      help=_('''Preprocess Baen HTML files to improve generated LRF.'''))
193
 
    prepro.add_option('--pdftohtml', action='store_true', default=False, dest='pdftohtml',
194
 
                      help=_('''You must add this option if processing files generated by pdftohtml, otherwise conversion will fail.'''))
195
 
    prepro.add_option('--book-designer', action='store_true', default=False, dest='book_designer',
196
 
                      help=_('''Use this option on html0 files from Book Designer.'''))
197
 
    
198
 
    fonts = parser.add_option_group('FONT FAMILIES', 
199
 
    _('''Specify trutype font families for serif, sans-serif and monospace fonts. '''
200
 
    '''These fonts will be embedded in the LRF file. Note that custom fonts lead to '''
201
 
    '''slower page turns. '''
202
 
    '''For example: '''
203
 
    '''--serif-family "Times New Roman"
204
 
    '''))
205
 
    fonts.add_option('--serif-family',  
206
 
                     default=None, dest='serif_family', type='string',
207
 
                     help=_('The serif family of fonts to embed'))
208
 
    fonts.add_option('--sans-family',   
209
 
                     default=None, dest='sans_family', type='string',
210
 
                     help=_('The sans-serif family of fonts to embed'))
211
 
    fonts.add_option('--mono-family',   
212
 
                     default=None, dest='mono_family', type='string',
213
 
                     help=_('The monospace family of fonts to embed'))
214
 
    
215
 
    debug = parser.add_option_group('DEBUG OPTIONS')
216
 
    debug.add_option('--verbose', dest='verbose', action='store_true', default=False,
217
 
                      help=_('''Be verbose while processing'''))
218
 
    debug.add_option('--lrs', action='store_true', dest='lrs', \
219
 
                      help=_('Convert to LRS'), default=False)
220
 
    parser.add_option('--minimize-memory-usage', action='store_true', default=False,
221
 
                      help=_('Minimize memory usage at the cost of longer processing times. Use this option if you are on a memory constrained machine.'))
222
 
    parser.add_option('--encoding', default=None, 
223
 
                      help=_('Specify the character encoding of the source file. If the output LRF file contains strange characters, try changing this option. A common encoding for files from windows computers is cp-1252. Another common choice is utf-8. The default is to try and guess the encoding.'))
224
 
    
225
 
    return parser
 
34
 
 
35
    name = 'prs500'
226
36
 
227
37
def find_custom_fonts(options, logger):
228
 
    from calibre.utils.fontconfig import files_for_family
 
38
    from calibre.utils.fonts import fontconfig
 
39
    files_for_family = fontconfig.files_for_family
229
40
    fonts = {'serif' : None, 'sans' : None, 'mono' : None}
230
41
    def family(cmd):
231
42
        return cmd.split(',')[-1].strip()
238
49
        f = family(options.sans_family)
239
50
        fonts['sans'] = files_for_family(f)
240
51
        if not fonts['sans']:
241
 
            logger.warn('Unable to find sans family %s'%f)        
 
52
            logger.warn('Unable to find sans family %s'%f)
242
53
    if options.mono_family:
243
54
        f = family(options.mono_family)
244
55
        fonts['mono'] = files_for_family(f)
245
56
        if not fonts['mono']:
246
 
            logger.warn('Unable to find mono family %s'%f)        
 
57
            logger.warn('Unable to find mono family %s'%f)
247
58
    return fonts
248
 
    
249
 
        
250
 
def Book(options, logger, font_delta=0, header=None, 
 
59
 
 
60
 
 
61
def Book(options, logger, font_delta=0, header=None,
251
62
         profile=PRS500_PROFILE, **settings):
252
63
    ps = {}
253
64
    ps['topmargin']      = options.top_margin
258
69
                                                 - profile.fudge
259
70
    if header:
260
71
        hdr = Header()
261
 
        hb = TextBlock(textStyle=TextStyle(align='foot', 
 
72
        hb = TextBlock(textStyle=TextStyle(align='foot',
262
73
                                           fontsize=int(profile.header_font_size*10)),
263
74
                       blockStyle=BlockStyle(blockwidth=ps['textwidth']))
264
75
        hb.append(header)
269
80
        ps['topmargin']  = 0
270
81
        ps['textheight'] = profile.screen_height - (options.bottom_margin + ps['topmargin']) \
271
82
                                                 - ps['headheight'] - ps['headsep'] - profile.fudge
272
 
        
 
83
 
273
84
    fontsize = int(10*profile.font_size+font_delta*20)
274
85
    baselineskip = fontsize + 20
275
86
    fonts = find_custom_fonts(options, logger)
276
 
    tsd = dict(fontsize=fontsize, 
277
 
               parindent=int(10*profile.parindent), 
 
87
    tsd = dict(fontsize=fontsize,
 
88
               parindent=int(10*profile.parindent),
278
89
               linespace=int(10*profile.line_space),
279
90
               baselineskip=baselineskip,
280
91
               wordspace=10*options.wordspace)
281
92
    if fonts['serif'] and fonts['serif'].has_key('normal'):
282
93
        tsd['fontfacename'] = fonts['serif']['normal'][1]
283
 
    
284
 
    book = _Book(textstyledefault=tsd, 
285
 
                pagestyledefault=ps, 
 
94
 
 
95
    book = _Book(textstyledefault=tsd,
 
96
                pagestyledefault=ps,
286
97
                blockstyledefault=dict(blockwidth=ps['textwidth']),
287
98
                bookid=uuid4().hex,
288
99
                **settings)
291
102
            for font in fonts[family].values():
292
103
                book.embed_font(*font)
293
104
                FONT_FILE_MAP[font[1]] = font[0]
294
 
    
 
105
 
295
106
    for family in ['serif', 'sans', 'mono']:
296
107
        if not fonts[family]:
297
108
            fonts[family] = { 'normal' : (None, profile.default_fonts[family]) }
299
110
            raise ConversionError, 'Could not find the normal version of the ' + family + ' font'
300
111
    return book, fonts
301
112
 
302
 
from calibre import entity_to_unicode