From 87dd3258422881f5f20d1a52b1a5914bfb50ad98 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 1 Oct 2012 18:52:25 +0530 Subject: [PATCH] Fix #1058531 (When editing metadata, the 'year' field is displayed like '101' instead of 2012) --- .../ebooks/conversion/plugins/pdf_output.py | 38 +++++---- src/calibre/gui2/metadata/basic_widgets.py | 21 +++-- src/calibre/utils/fonts/utils.py | 84 +++++++++++++++---- 3 files changed, 106 insertions(+), 37 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/pdf_output.py b/src/calibre/ebooks/conversion/plugins/pdf_output.py index 3019255270..e9046bcfeb 100644 --- a/src/calibre/ebooks/conversion/plugins/pdf_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdf_output.py @@ -151,28 +151,32 @@ class PDFOutput(OutputFormatPlugin): oeb_output.convert(oeb_book, oeb_dir, self.input_plugin, self.opts, self.log) if iswindows: - from calibre.utils.fonts.utils import remove_embed_restriction + # from calibre.utils.fonts.utils import remove_embed_restriction # On windows Qt generates an image based PDF if the html uses # embedded fonts. See https://launchpad.net/bugs/1053906 for f in walk(oeb_dir): if f.rpartition('.')[-1].lower() in {'ttf', 'otf'}: - fixed = False - with open(f, 'r+b') as s: - raw = s.read() - try: - raw = remove_embed_restriction(raw) - except: - self.log.exception('Failed to remove embedding' - ' restriction from font %s, ignoring it'% - os.path.basename(f)) - else: - s.seek(0) - s.truncate() - s.write(raw) - fixed = True + os.remove(f) + # It's not the font embedding restriction that causes + # this, even after removing the restriction, Qt still + # generates an image based document. Theoretically, it + # fixed = False + # with open(f, 'r+b') as s: + # raw = s.read() + # try: + # raw = remove_embed_restriction(raw) + # except: + # self.log.exception('Failed to remove embedding' + # ' restriction from font %s, ignoring it'% + # os.path.basename(f)) + # else: + # s.seek(0) + # s.truncate() + # s.write(raw) + # fixed = True - if not fixed: - os.remove(f) + # if not fixed: + # os.remove(f) opfpath = glob.glob(os.path.join(oeb_dir, '*.opf'))[0] opf = OPF(opfpath, os.path.dirname(opfpath)) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 6e764e90d5..d009cc2182 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -9,10 +9,11 @@ __docformat__ = 'restructuredtext en' import textwrap, re, os, errno, shutil -from PyQt4.Qt import (Qt, QDateTimeEdit, pyqtSignal, QMessageBox, - QIcon, QToolButton, QWidget, QLabel, QGridLayout, QApplication, - QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QDialog, QMenu, - QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, QAction) +from PyQt4.Qt import (Qt, QDateTimeEdit, pyqtSignal, QMessageBox, QIcon, + QToolButton, QWidget, QLabel, QGridLayout, QApplication, + QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QDialog, QMenu, + QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, + QAction, QCalendarWidget, QDate) from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView from calibre.utils.icu import sort_key @@ -1371,7 +1372,15 @@ class PublisherEdit(EditWithComplete): # {{{ # }}} -class DateEdit(QDateTimeEdit): # {{{ +# DateEdit {{{ + +class CalendarWidget(QCalendarWidget): + + def showEvent(self, ev): + if self.selectedDate().year() == UNDEFINED_DATE.year: + self.setSelectedDate(QDate.currentDate()) + +class DateEdit(QDateTimeEdit): TOOLTIP = '' LABEL = _('&Date:') @@ -1388,6 +1397,8 @@ class DateEdit(QDateTimeEdit): # {{{ fmt = self.FMT self.setDisplayFormat(fmt) self.setCalendarPopup(True) + self.cw = CalendarWidget(self) + self.setCalendarWidget(self.cw) self.setMinimumDateTime(UNDEFINED_QDATETIME) self.setSpecialValueText(_('Undefined')) self.clear_button = QToolButton(parent) diff --git a/src/calibre/utils/fonts/utils.py b/src/calibre/utils/fonts/utils.py index 6822cbe4dd..f20f238481 100644 --- a/src/calibre/utils/fonts/utils.py +++ b/src/calibre/utils/fonts/utils.py @@ -18,23 +18,23 @@ def is_truetype_font(raw): sfnt_version = raw[:4] return (sfnt_version in {b'\x00\x01\x00\x00', b'OTTO'}, sfnt_version) -def get_table(raw, name): - ''' Get the raw table bytes for the specified table in the font ''' +def get_tables(raw): num_tables = struct.unpack_from(b'>H', raw, 4)[0] offset = 4*3 # start of the table record entries - table_offset = table_checksum = table_length = table_index = table = None - name = bytes(name.lower()) for i in xrange(num_tables): - table_tag = raw[offset:offset+4] - if table_tag.lower() == name: - table_checksum, table_offset, table_length = struct.unpack_from( - b'>3L', raw, offset+4) - table_index = offset - break + table_tag, table_checksum, table_offset, table_length = struct.unpack_from( + b'>4s3L', raw, offset) + yield (table_tag, raw[table_offset:table_offset+table_length], offset, + table_offset, table_checksum) offset += 4*4 - if table_offset is not None: - table = raw[table_offset:table_offset+table_length] - return table, table_index, table_offset, table_checksum + +def get_table(raw, name): + ''' Get the raw table bytes for the specified table in the font ''' + name = bytes(name.lower()) + for table_tag, table, table_index, table_offset, table_checksum in get_tables(raw): + if table_tag.lower() == name: + return table, table_index, table_offset, table_checksum + return None, None, None, None def get_font_characteristics(raw): ''' @@ -154,13 +154,59 @@ def get_font_names(raw): return family_name, subfamily_name, full_name +def checksum_of_block(raw): + extra = 4 - len(raw)%4 + raw += b'\0'*extra + num = len(raw)//4 + return sum(struct.unpack(b'>%dI'%num, raw)) % (1<<32) + +def verify_checksums(raw): + head_table = None + for table_tag, table, table_index, table_offset, table_checksum in get_tables(raw): + if table_tag.lower() == b'head': + version, fontrev, checksum_adj = struct.unpack_from(b'>ffL', table) + head_table = table + offset = table_offset + checksum = table_checksum + elif checksum_of_block(table) != table_checksum: + raise ValueError('The %r table has an incorrect checksum'%table_tag) + + if head_table is not None: + table = head_table + table = table[:8] + struct.pack(b'>I', 0) + table[12:] + raw = raw[:offset] + table + raw[offset+len(table):] + # Check the checksum of the head table + if checksum_of_block(table) != checksum: + raise ValueError('Checksum of head table not correct') + # Check the checksum of the entire font + checksum = checksum_of_block(raw) + q = (0xB1B0AFBA - checksum) & 0xffffffff + if q != checksum_adj: + raise ValueError('Checksum of entire font incorrect') + +def set_checksum_adjustment(f): + offset = get_table(f.getvalue(), 'head')[2] + offset += 8 + f.seek(offset) + f.write(struct.pack(b'>I', 0)) + checksum = checksum_of_block(f.getvalue()) + q = (0xB1B0AFBA - checksum) & 0xffffffff + f.seek(offset) + f.write(struct.pack(b'>I', q)) + +def set_table_checksum(f, name): + table, table_index, table_offset, table_checksum = get_table(f.getvalue(), name) + checksum = checksum_of_block(table) + if checksum != table_checksum: + f.seek(table_index + 4) + f.write(struct.pack(b'>I', checksum)) def remove_embed_restriction(raw): ok, sig = is_truetype_font(raw) if not ok: raise UnsupportedFont('Not a supported font, sfnt_version: %r'%sig) - table, table_index, table_offset = get_table(raw, 'os/2') + table, table_index, table_offset = get_table(raw, 'os/2')[:3] if table is None: raise UnsupportedFont('Not a supported font, has no OS/2 table') @@ -172,7 +218,12 @@ def remove_embed_restriction(raw): f = BytesIO(raw) f.seek(fs_type_offset + table_offset) f.write(struct.pack(b'>H', 0)) - return f.getvalue() + + set_table_checksum(f, 'os/2') + set_checksum_adjustment(f) + raw = f.getvalue() + verify_checksums(raw) + return raw def test(): import sys, os @@ -181,6 +232,9 @@ def test(): raw = open(f, 'rb').read() print (get_font_names(raw)) print (get_font_characteristics(raw)) + verify_checksums(raw) + remove_embed_restriction(raw) + if __name__ == '__main__': test()