From 54de4dc434d190e8d1970a9dd899cdbb6458c736 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 10 Jun 2009 18:01:43 -0700 Subject: [PATCH 1/4] MOBI Output: Chapter to chapter indexing now works --- src/calibre/ebooks/mobi/writer.py | 465 ++++++++++++++++++++++++------ 1 file changed, 378 insertions(+), 87 deletions(-) diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 291e937b2c..d3b494af1d 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -88,6 +88,7 @@ def encode(data): DECINT_FORWARD = 0 DECINT_BACKWARD = 1 def decint(value, direction): + # Encode vwi bytes = [] while True: b = value & 0x7f @@ -324,6 +325,8 @@ class MobiWriter(object): self._imagemax = imagemax or OTHER_MAX_IMAGE_SIZE self._prefer_author_sort = prefer_author_sort self._primary_index_record = None + self._HTMLRecords = [] + self._tbSequence = "" @classmethod def generate(cls, opts): @@ -358,7 +361,8 @@ class MobiWriter(object): def _generate_content(self): self._map_image_names() self._generate_text() - if INDEXING and not self.opts.no_mobi_index: + #if INDEXING and not self.opts.no_mobi_index: + if INDEXING : try: self._generate_index() except: @@ -403,6 +407,130 @@ class MobiWriter(object): text.seek(npos) return data, overlap + def _build_HTMLRecords_Data_List(self): + # Assemble a HTMLRecordData instance for each HTML record + + numberOfHTMLRecords = ( self._content_length // RECORD_SIZE ) + 1 + + # Create a list of HTMLRecordData class instances + x = numberOfHTMLRecords + while x: + self._HTMLRecords.append(HTMLRecordData()) + x -= 1 + + toc = self._oeb.toc + myIndex = 0 + myEndingRecord = 0 + entries = list(toc.iter())[1:] + # borrowed from _generate_indxt + for i, child in enumerate(entries): + if not child.title or not child.title.strip(): + continue + h = child.href + if h not in self._id_offsets: + self._oeb.log.warning('Could not find TOC entry:', child.title) + continue + offset = self._id_offsets[h] + length = None + for sibling in entries[i+1:]: + h2 = sibling.href + if h2 in self._id_offsets: + offset2 = self._id_offsets[h2] + if offset2 > offset: + length = offset2 - offset + break + if length is None: + length = self._content_length - offset + + # Calculate the HTML record for this entry + myStartingRecord = offset // RECORD_SIZE + + # If no one has taken the openingNode slot, it must be us + if self._HTMLRecords[myStartingRecord].openingNode == -1 : + self._HTMLRecords[myStartingRecord].openingNode = myIndex + + # Bump the node count for this HTML record + # Special case if we're the first so we get a true node count + if self._HTMLRecords[myStartingRecord].currentSectionNodeCount == -1: + self._HTMLRecords[myStartingRecord].currentSectionNodeCount = 1 + else: + self._HTMLRecords[myStartingRecord].currentSectionNodeCount += 1 + + # Calculate the ending HTMLRecord of this entry + myEndingRecord = (offset + length) // RECORD_SIZE + + # Tell the future HTML records about us + if myEndingRecord > myStartingRecord : + interimSpanRecord = myStartingRecord + 1 + while interimSpanRecord <= myEndingRecord : + self._HTMLRecords[interimSpanRecord].continuingNode = myIndex + self._HTMLRecords[interimSpanRecord].currentSectionNodeCount = 1 + interimSpanRecord += 1 + + ctoc_offset = self._ctoc_map[child] + last_name = "%04d" % myIndex + myIndex += 1 + ''' + # Dump the accumulated HTML Record Data + x = 0 + while x < len(self._HTMLRecords): + self._HTMLRecords[x].dumpData(x, self._oeb) + x += 1 + ''' + def _build_TBS_Book(self, nrecords, lastrecord): + + # Variables for trailing byte sequence + tbsType = 0x00 + tbSequence = "" + #print "_build_TBS_Book: nrecords = %d, lastrecord = %d" % (nrecords, lastrecord) + # Generate TBS for type 0x002 - mobi_book + if nrecords == 0 : + # First HTML record is a special case + if self._HTMLRecords[nrecords].currentSectionNodeCount == 1 : + tbsType = 2 + else : + tbsType = 6 + + tbSequence = decint(tbsType, DECINT_BACKWARD) + tbSequence += decint(0x00, DECINT_BACKWARD) + # Don't write a nodecount for opening type 2 record + if tbsType != 2 : + tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) + tbSequence += decint(len(tbSequence) + 1, DECINT_BACKWARD) + + else : + # Determine tbsType for HTMLRecords > 0 + if nrecords == lastrecord and self._HTMLRecords[nrecords].currentSectionNodeCount == 1 : + # Ending record with singleton node + tbsType = 2 + + elif self._HTMLRecords[nrecords].continuingNode > 0 and self._HTMLRecords[nrecords].openingNode == -1 : + # This is a span-only record + tbsType = 3 + # Zero out the nodeCount with a pre-formed vwi + self._HTMLRecords[nrecords].currentSectionNodeCount = 0x80 + + else : + tbsType = 6 + + + # Shift the openingNode index << 3 + shiftedNCXEntry = self._HTMLRecords[nrecords].continuingNode << 3 + # Add the TBS type + shiftedNCXEntry |= tbsType + + # Assemble the TBS + tbSequence = decint(shiftedNCXEntry, DECINT_BACKWARD) + tbSequence += decint(0x00, DECINT_BACKWARD) + # Don't write a nodecount for terminating type 2 record + if tbsType != 2 : + tbSequence += chr(self._HTMLRecords[nrecords].currentSectionNodeCount) + tbSequence += decint(len(tbSequence) + 1, DECINT_BACKWARD) + + # print "record %d: tbsType %d" % (nrecords, tbsType) + self._tbSequence = tbSequence + + def _generate_text(self): self._oeb.logger.info('Serializing markup content...') serializer = Serializer(self._oeb, self._images) @@ -414,10 +542,25 @@ class MobiWriter(object): text = StringIO(text) buf = [] nrecords = 0 + lastrecord = (self._content_length // RECORD_SIZE ) + offset = 0 + if self._compression != UNCOMPRESSED: self._oeb.logger.info('Compressing markup content...') data, overlap = self._read_text_record(text) + + # GR borrowed this from generate_index + # We seem to need it before calling self._build_HTMLRecords_Data_List() + ctoc = self._generate_ctoc() + + # Build the HTMLRecords list so we can assemble the trailing bytes sequences in the following while loop + toc = self._oeb.toc + entries = list(toc.iter())[1:] + hasNCXEntries = True if len(entries) else False + if hasNCXEntries : + self._build_HTMLRecords_Data_List() + while len(data) > 0: if self._compression == PALMDOC: data = compress_doc(data) @@ -428,24 +571,59 @@ class MobiWriter(object): nextra = 0 pbreak = 0 running = offset - while breaks and (breaks[0] - offset) < RECORD_SIZE: - pbreak = (breaks.pop(0) - running) >> 3 - encoded = decint(pbreak, DECINT_FORWARD) - record.write(encoded) - running += pbreak << 3 - nextra += len(encoded) - lsize = 1 - while True: - size = decint(nextra + lsize, DECINT_BACKWARD) - if len(size) == lsize: - break - lsize += 1 - record.write(size) + + # Write Trailing Byte Sequence + if INDEXING and hasNCXEntries : + # Dispatch to different TBS generators based upon publication type + booktype = 0x101 if self.opts.mobi_periodical else 0x002 + if booktype == 0x002 : + self._build_TBS_Book(nrecords, lastrecord) + #elif booktype == flatPeriodical : + # tbSequence = self._build_TBS_FlatPeriodicalTBS() + #elif booktype == structuredPeriodical : + # tbSequence = self._build_TBS_StructuredPeriodicalTBS() + else : + raise NotImplementedError('Indexing for periodicals not implemented') + + # Dump the current HTML Record Data / TBS + # GR diagnostics + if False : + self._HTMLRecords[nrecords].dumpData(nrecords, self._oeb) + outstr = '' + for eachbyte in self._tbSequence: + outstr += '0x%02X ' % ord(eachbyte) + self._oeb.logger.info(' Trailing Byte Sequence: %s\n' % outstr) + + # Write the sequence + record.write(self._tbSequence) + + else : + # Marshall's original code + while breaks and (breaks[0] - offset) < RECORD_SIZE: + # .pop returns item, removes it from list + pbreak = (breaks.pop(0) - running) >> 3 + self._oeb.logger.info('pbreak = 0x%X' % pbreak ) + encoded = decint(pbreak, DECINT_FORWARD) + record.write(encoded) + running += pbreak << 3 + nextra += len(encoded) + + lsize = 1 + while True: + size = decint(nextra + lsize, DECINT_BACKWARD) + if len(size) == lsize: + break + lsize += 1 + + # Writing vwi length byte here + record.write(size) + self._records.append(record.getvalue()) buf.append(self._records[-1]) nrecords += 1 offset += RECORD_SIZE data, overlap = self._read_text_record(text) + if INDEXING: extra = sum(map(len, buf))%4 if extra == 0: @@ -455,6 +633,7 @@ class MobiWriter(object): self._text_nrecords = nrecords def _generate_indxt(self, ctoc): + if self.opts.mobi_periodical: raise NotImplementedError('Indexing for periodicals not implemented') toc = self._oeb.toc @@ -471,7 +650,7 @@ class MobiWriter(object): pos = 0xc0 + indxt.tell() indices.write(pack('>H', pos)) - name = "%4d"%count + name = "%04d"%count indxt.write(chr(len(name)) + name) indxt.write(INDXT['chapter']) indxt.write(decint(offset, DECINT_FORWARD)) @@ -502,7 +681,7 @@ class MobiWriter(object): add_node(child, offset, length, c) ctoc_offset = self._ctoc_map[child] - last_name = "%4d"%c + last_name = "%04d"%c c += 1 return align_block(indxt.getvalue()), c, \ @@ -510,7 +689,7 @@ class MobiWriter(object): def _generate_index(self): - self._oeb.log('Generating index...') + self._oeb.log('Generating primary index...') self._primary_index_record = None ctoc = self._generate_ctoc() indxt, indxt_count, indices, last_name = \ @@ -519,6 +698,8 @@ class MobiWriter(object): self._oeb.log.warn('Input document has no TOC. No index generated.') return + # GR: indx0 => INDX0[0] + # indx1 => INDX1[0] indx1 = StringIO() indx1.write('INDX'+pack('>I', 0xc0)) # header length @@ -575,7 +756,9 @@ class MobiWriter(object): header.write(pack('>I', 1)) # 0x1c - 0x1f : Text encoding ? - header.write(pack('>I', 650001)) + # header.write(pack('>I', 650001)) + # GR: This needs to be either 0xFDE9 or 0x4E4 + header.write(pack('>I', 0xFDE9)) # 0x20 - 0x23 : Language code? header.write(iana2mobi(str(self._oeb.metadata.language[0]))) @@ -615,56 +798,58 @@ class MobiWriter(object): self._primary_index_record = len(self._records) self._records.extend([indx0, indx1, ctoc]) - # Write secondary index records - tagx = TAGX['secondary_'+\ - ('periodical' if self.opts.mobi_periodical else 'book')] - tagx_len = 8 + len(tagx) + # Turn this off for now + if False: + # Write secondary index records + tagx = TAGX['secondary_'+\ + ('periodical' if self.opts.mobi_periodical else 'book')] + tagx_len = 8 + len(tagx) - indx0 = StringIO() - indx0.write('INDX'+pack('>I', 0xc0)+'\0'*8) - indx0.write(pack('>I', 0x02)) - indx0.write(pack('>I', 0xc0+tagx_len+4)) - indx0.write(pack('>I', 1)) - indx0.write(pack('>I', 65001)) - indx0.write('\xff'*4) - indx0.write(pack('>I', 1)) - indx0.write('\0'*4) - indx0.write('\0'*136) - indx0.write(pack('>I', 0xc0)) - indx0.write('\0'*8) - indx0.write('TAGX'+pack('>I', tagx_len)+tagx) - if self.opts.mobi_periodical: - raise NotImplementedError - else: - indx0.write('\0'*3 + '\x01' + 'IDXT' + '\0\xd4\0\0') - indx1 = StringIO() - indx1.write('INDX' + pack('>I', 0xc0) + '\0'*4) - indx1.write(pack('>I', 1)) - extra = 0xf0 if self.opts.mobi_periodical else 4 - indx1.write('\0'*4 + pack('>I', 0xc0+extra)) - num = 4 if self.opts.mobi_periodical else 1 - indx1.write(pack('>I', num)) - indx1.write('\xff'*8) - indx1.write('\0'*(0xc0-indx1.tell())) - if self.opts.mobi_periodical: - raise NotImplementedError - else: - indx1.write('\0\x01\x80\0') - indx1.write('IDXT') - if self.opts.mobi_periodical: - raise NotImplementedError - else: - indx1.write('\0\xc0\0\0') + indx0 = StringIO() + indx0.write('INDX'+pack('>I', 0xc0)+'\0'*8) + indx0.write(pack('>I', 0x02)) + indx0.write(pack('>I', 0xc0+tagx_len+4)) + indx0.write(pack('>I', 1)) + indx0.write(pack('>I', 65001)) + indx0.write('\xff'*4) + indx0.write(pack('>I', 1)) + indx0.write('\0'*4) + indx0.write('\0'*136) + indx0.write(pack('>I', 0xc0)) + indx0.write('\0'*8) + indx0.write('TAGX'+pack('>I', tagx_len)+tagx) + if self.opts.mobi_periodical: + raise NotImplementedError + else: + indx0.write('\0'*3 + '\x01' + 'IDXT' + '\0\xd4\0\0') + indx1 = StringIO() + indx1.write('INDX' + pack('>I', 0xc0) + '\0'*4) + indx1.write(pack('>I', 1)) + extra = 0xf0 if self.opts.mobi_periodical else 4 + indx1.write('\0'*4 + pack('>I', 0xc0+extra)) + num = 4 if self.opts.mobi_periodical else 1 + indx1.write(pack('>I', num)) + indx1.write('\xff'*8) + indx1.write('\0'*(0xc0-indx1.tell())) + if self.opts.mobi_periodical: + raise NotImplementedError + else: + indx1.write('\0\x01\x80\0') + indx1.write('IDXT') + if self.opts.mobi_periodical: + raise NotImplementedError + else: + indx1.write('\0\xc0\0\0') - indx0, indx1 = indx0.getvalue(), indx1.getvalue() - self._records.extend((indx0, indx1)) - if self.opts.verbose > 3: - from tempfile import mkdtemp - import os - t = mkdtemp() - for i, n in enumerate(['sindx1', 'sindx0', 'ctoc', 'indx0', 'indx1']): - open(os.path.join(t, n+'.bin'), 'wb').write(self._records[-(i+1)]) - self._oeb.log.debug('Index records dumped to', t) + indx0, indx1 = indx0.getvalue(), indx1.getvalue() + self._records.extend((indx0, indx1)) + if self.opts.verbose > 3: + from tempfile import mkdtemp + import os + t = mkdtemp() + for i, n in enumerate(['sindx1', 'sindx0', 'ctoc', 'indx0', 'indx1']): + open(os.path.join(t, n+'.bin'), 'wb').write(self._records[-(i+1)]) + self._oeb.log.debug('Index records dumped to', t) @@ -714,16 +899,22 @@ class MobiWriter(object): self._first_image_record = len(self._records)-1 def _generate_end_records(self): - self._flis_number = len(self._records) - self._records.append( - 'FLIS\0\0\0\x08\0\x41\0\0\0\0\0\0\xff\xff\xff\xff\0\x01\0\x03\0\0\0\x03\0\0\0\x01'+ - '\xff'*4) - fcis = 'FCIS\x00\x00\x00\x14\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x00\x00' - fcis += pack('>I', self._text_length) - fcis += '\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x08\x00\x01\x00\x01\x00\x00\x00\x00' - self._fcis_number = len(self._records) - self._records.append(fcis) - self._records.append('\xE9\x8E\x0D\x0A') + if True: + self._flis_number = len(self._records) + self._records.append('\xE9\x8E\x0D\x0A') + + else: + # This adds the binary blobs of FLIS and FCIS, which don't seem to be necessary + self._flis_number = len(self._records) + self._records.append( + 'FLIS\0\0\0\x08\0\x41\0\0\0\0\0\0\xff\xff\xff\xff\0\x01\0\x03\0\0\0\x03\0\0\0\x01'+ + '\xff'*4) + fcis = 'FCIS\x00\x00\x00\x14\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x00\x00' + fcis += pack('>I', self._text_length) + fcis += '\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x08\x00\x01\x00\x01\x00\x00\x00\x00' + self._fcis_number = len(self._records) + self._records.append(fcis) + self._records.append('\xE9\x8E\x0D\x0A') def _generate_record0(self): metadata = self._oeb.metadata @@ -760,9 +951,14 @@ class MobiWriter(object): # 0x18 - 0x1f : Unknown record0.write('\xff' * 8) + # 0x20 - 0x23 : Secondary index record - record0.write(pack('>I', 0xffffffff if self._primary_index_record is - None else self._primary_index_record+3)) + # Turned off as it seems unnecessary + if True: + record0.write(pack('>I', 0xffffffff)) + else: + record0.write(pack('>I', 0xffffffff if self._primary_index_record is + None else self._primary_index_record+3)) # 0x24 - 0x3f : Unknown record0.write('\xff' * 28) @@ -819,16 +1015,33 @@ class MobiWriter(object): record0.write('\0\0\0\x01') # 0xb8 - 0xbb : FCIS record number - record0.write(pack('>I', self._fcis_number)) + # Turned off, these are optional and not understood yet + if True: + # 0xb8 - 0xbb : FCIS record number + record0.write(pack('>I', 0xffffffff)) - # 0xbc - 0xbf : Unknown (FCIS record count?) - record0.write(pack('>I', 1)) + # 0xbc - 0xbf : Unknown (FCIS record count?) + record0.write(pack('>I', 0xffffffff)) - # 0xc0 - 0xc3 : FLIS record number - record0.write(pack('>I', self._flis_number)) + # 0xc0 - 0xc3 : FLIS record number + record0.write(pack('>I', 0xffffffff)) - # 0xc4 - 0xc7 : Unknown (FLIS record count?) - record0.write(pack('>I', 1)) + # 0xc4 - 0xc7 : Unknown (FLIS record count?) + record0.write(pack('>I', 1)) + + else: + # Write these if FCIS/FLIS turned on + # 0xb8 - 0xbb : FCIS record number + record0.write(pack('>I', self._fcis_number)) + + # 0xbc - 0xbf : Unknown (FCIS record count?) + record0.write(pack('>I', 1)) + + # 0xc0 - 0xc3 : FLIS record number + record0.write(pack('>I', self._flis_number)) + + # 0xc4 - 0xc7 : Unknown (FLIS record count?) + record0.write(pack('>I', 1)) # 0xc8 - 0xcf : Unknown record0.write('\0'*8) @@ -839,9 +1052,14 @@ class MobiWriter(object): # 0xe0 - 0xe3 : Extra record data # The '5' is a bitmask of extra record data at the end: # - 0x1: (?) + # - 0x2: GR # - 0x4: # Of course, the formats aren't quite the same. - record0.write(pack('>I', 5)) + # GR: Use 2 for indexed files + if INDEXING : + record0.write(pack('>I', 2)) + else: + record0.write(pack('>I', 5)) # 0xe4 - 0xe7 : Primary index record record0.write(pack('>I', 0xffffffff if self._primary_index_record is @@ -891,11 +1109,14 @@ class MobiWriter(object): if index is not None: exth.write(pack('>III', 0xca, 0x0c, index - 1)) nrecs += 1 - if INDEXING: + + # Not sure what these are, but not needed for indexing + if False : # Write unknown EXTH records as 0s for code, size in [(204,4), (205,4), (206,4), (207,4), (300,40)]: exth.write(pack('>II', code, 8+size)+'\0'*size) nrecs += 1 + exth = exth.getvalue() trail = len(exth) % 4 pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte @@ -935,3 +1156,73 @@ class MobiWriter(object): self._write(record) +class HTMLRecordData(object): + def __init__(self): + self._continuingNode = -1 + self._continuingNodeParent = -1 + self._openingNode = -1 + self._openingNodeParent = -1 + self._currentSectionNodeCount = -1 + self._nextSectionNumber = -1 + self._nextSectionOpeningNode = -1 + self._nextSectionNodeCount = -1 + + def getContinuingNode(self): + return self._continuingNode + def setContinuingNode(self, value): + self._continuingNode = value + continuingNode = property(getContinuingNode, setContinuingNode, None, None) + + def getContinuingNodeParent(self): + return self._continuingNodeParent + def setContinuingNodeParent(self, value): + self._continuingNodeParent = value + continuingNodeParent = property(getContinuingNodeParent, setContinuingNodeParent, None, None) + + def getOpeningNode(self): + return self._openingNode + def setOpeningNode(self, value): + self._openingNode = value + openingNode = property(getOpeningNode, setOpeningNode, None, None) + + def getOpeningNodeParent(self): + return self._openingNodeParent + def setOpeningNodeParent(self, value): + self._openingNodeParent = value + openingNodeParent = property(getOpeningNodeParent, setOpeningNodeParent, None, None) + + def getCurrentSectionNodeCount(self): + return self._currentSectionNodeCount + def setCurrentSectionNodeCount(self, value): + self._currentSectionNodeCount = value + currentSectionNodeCount = property(getCurrentSectionNodeCount, setCurrentSectionNodeCount, None, None) + + def getNextSectionNumber(self): + return self._nextSectionNumber + def setNextSectionNumber(self, value): + self._nextSectionNumber = value + nextSectionNumber = property(getNextSectionNumber, setNextSectionNumber, None, None) + + def getNextSectionOpeningNode(self): + return self._nextSectionOpeningNode + def setNextSectionOpeningNode(self, value): + self._nextSectionOpeningNode = value + nextSectionOpeningNode = property(getNextSectionOpeningNode, setNextSectionOpeningNode, None, None) + + def getNextSectionNodeCount(self): + return self._nextSectionNodeCount + def setNextSectionNodeCount(self, value): + self._nextSectionNodeCount = value + nextSectionNodeCount = property(getNextSectionNodeCount, setNextSectionNodeCount, None, None) + + def dumpData(self, recordNumber, oeb): + oeb.logger.info( "--- Summary of HTML Record 0x%x [%d] indexing ---" % (recordNumber, recordNumber) ) + oeb.logger.info( " continuingNode: %03d" % self.continuingNode ) + oeb.logger.info( " continuingNodeParent: %03d" % self.continuingNodeParent ) + oeb.logger.info( " openingNode: %03d" % self.openingNode ) + oeb.logger.info( " openingNodeParent: %03d" % self.openingNodeParent ) + oeb.logger.info( " currentSectionNodeCount: %03d" % self.currentSectionNodeCount ) + oeb.logger.info( " nextSectionNumber: %03d" % self.nextSectionNumber ) + oeb.logger.info( " nextSectionOpeningNode: %03d" % self.nextSectionOpeningNode ) + oeb.logger.info( " nextSectionNodeCount: %03d" % self.nextSectionNodeCount ) + From 2dabc47ee6231a84121ce07ae7d1bd8422f40668 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 10 Jun 2009 18:47:10 -0700 Subject: [PATCH 2/4] Handle lengths in units of ex and en --- src/calibre/ebooks/oeb/stylizer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 8cac967890..daf5d21d2a 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -279,7 +279,7 @@ class Stylizer(object): class Style(object): - UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|px|mm|cm|in|pt|pc)$') + UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$') def __init__(self, element, stylizer): self._element = element @@ -362,6 +362,11 @@ class Style(object): elif unit == 'em': font = font or self.fontSize result = value * font + elif unit in ('ex', 'en'): + # This is a hack for ex since we have no way to know + # the x-height of the font + font = font or self.fontSize + result = value * font * 0.5 elif unit == 'pc': result = value * 12.0 elif unit == 'mm': From c073180d030b0ba9f03a8db365525fb381daed7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 10 Jun 2009 19:58:15 -0700 Subject: [PATCH 3/4] ... --- src/calibre/linux.py | 1 - src/calibre/utils/ipc/server.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index a9a5556afa..34f7abb195 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -23,7 +23,6 @@ entry_points = { 'calibre-server = calibre.library.server:main', 'lrf2lrs = calibre.ebooks.lrf.lrfparser:main', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main', - 'isbndb = calibre.ebooks.metadata.isbndb:main', 'librarything = calibre.ebooks.metadata.library_thing:main', 'calibre-debug = calibre.debug:main', 'calibredb = calibre.library.cli:main', diff --git a/src/calibre/utils/ipc/server.py b/src/calibre/utils/ipc/server.py index 25f6c49183..70f77d7122 100644 --- a/src/calibre/utils/ipc/server.py +++ b/src/calibre/utils/ipc/server.py @@ -11,12 +11,12 @@ from math import ceil from threading import Thread, RLock from Queue import Queue, Empty from multiprocessing.connection import Listener -from multiprocessing import cpu_count from collections import deque from binascii import hexlify from calibre.utils.ipc.launch import Worker from calibre.utils.ipc.worker import PARALLEL_FUNCS +from calibre import detect_ncpus as cpu_count _counter = 0 From 97386ccb9d5ce41a20e517ad65e8de1aa8f07384 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 10 Jun 2009 23:04:40 -0700 Subject: [PATCH 4/4] Fix #2572 (Error when Restoring from Tray) --- src/calibre/ebooks/metadata/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 1a5af8966c..4c2c6c744a 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -61,8 +61,9 @@ def roman(num): def fmt_sidx(i, fmt='%.2f', use_roman=False): - if i is None: + if i is None or i == '': i = 1 + i = float(i) if int(i) == float(i): return roman(int(i)) if use_roman else '%d'%int(i) try: