diff --git a/src/calibre/ebooks/pdf/render/common.py b/src/calibre/ebooks/pdf/render/common.py index 5abfe60a84..5a63fd8b99 100644 --- a/src/calibre/ebooks/pdf/render/common.py +++ b/src/calibre/ebooks/pdf/render/common.py @@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en' import codecs, zlib from io import BytesIO +from struct import pack EOL = b'\n' @@ -49,7 +50,7 @@ def serialize(o, stream): o.pdf_serialize(stream) elif isinstance(o, bool): stream.write(b'true' if o else b'false') - elif isinstance(o, (int, float)): + elif isinstance(o, (int, long, float)): stream.write(type(u'')(o).encode('ascii')) elif o is None: stream.write(b'null') @@ -78,14 +79,30 @@ class String(unicode): raw = codecs.BOM_UTF16_BE + s.encode('utf-16-be') stream.write(b'('+raw+b')') +class GlyphIndex(object): + + def __init__(self, code, compress): + self.code = code + self.compress = compress + + def pdf_serialize(self, stream): + if self.compress: + stream.write(pack(b'>sHs', b'(', self.code, b')')) + else: + byts = bytearray(pack(b'>H', self.code)) + stream.write('<%s>'%''.join(map( + lambda x: bytes(hex(x)[2:]).rjust(2, b'0'), byts))) + class Dictionary(dict): def pdf_serialize(self, stream): stream.write(b'<<' + EOL) - for k, v in self.iteritems(): + sorted_keys = sorted(self.iterkeys(), + key=lambda x:((' ' if x == 'Type' else '')+x)) + for k in sorted_keys: serialize(Name(k), stream) stream.write(b' ') - serialize(v, stream) + serialize(self[k], stream) stream.write(EOL) stream.write(b'>>' + EOL) @@ -116,6 +133,9 @@ class Stream(BytesIO): BytesIO.__init__(self) self.compress = compress + def add_extra_keys(self, d): + pass + def pdf_serialize(self, stream): raw = self.getvalue() dl = len(raw) @@ -125,6 +145,7 @@ class Stream(BytesIO): raw = zlib.compress(raw) d = InlineDictionary({'Length':len(raw), 'DL':dl}) + self.add_extra_keys(d) if filters: d['Filter'] = filters serialize(d, stream) diff --git a/src/calibre/ebooks/pdf/render/engine.py b/src/calibre/ebooks/pdf/render/engine.py index 5751f5fa01..4dea75241a 100644 --- a/src/calibre/ebooks/pdf/render/engine.py +++ b/src/calibre/ebooks/pdf/render/engine.py @@ -13,10 +13,10 @@ from collections import namedtuple from functools import wraps from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter, - QTransform, QPainterPath, QRawFont) + QTransform, QPainterPath, QTextOption, QTextLayout) from calibre.constants import DEBUG -from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path, Text) +from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path) from calibre.ebooks.pdf.render.common import inch, A4 from calibre.utils.fonts.sfnt.container import Sfnt from calibre.utils.fonts.sfnt.metrics import FontMetrics @@ -236,6 +236,9 @@ class PdfEngine(QPaintEngine): self.xscale, self.yscale = sx, sy self.graphics_state = GraphicsState() self.errors = [] + self.text_option = QTextOption() + self.text_option.setWrapMode(QTextOption.NoWrap) + self.fonts = {} def init_page(self): self.pdf.transform(self.pdf_system) @@ -353,44 +356,41 @@ class PdfEngine(QPaintEngine): @store_error def drawTextItem(self, point, text_item): - # super(PdfEngine, self).drawTextItem(point+QPoint(0, 300), text_item) - f = text_item.font() - px, pt = f.pixelSize(), f.pointSizeF() - if px == -1: - sz = pt/self.yscale - else: - sz = px + # super(PdfEngine, self).drawTextItem(point+QPoint(0, 0), text_item) + text = type(u'')(text_item.text()).replace('\n', ' ') + tl = QTextLayout(text, text_item.font(), self.paintDevice()) + self.text_option.setTextDirection(Qt.RightToLeft if + text_item.renderFlags() & text_item.RightToLeft else Qt.LeftToRight) + tl.setTextOption(self.text_option) + tl.setPosition(point) + tl.beginLayout() + line = tl.createLine() + if not line.isValid(): + tl.endLayout() + return + line.setLineWidth(int(1e12)) + tl.endLayout() + for run in tl.glyphRuns(): + rf = run.rawFont() + name = hash(bytes(rf.fontTable('name'))) + if name not in self.fonts: + self.fonts[name] = FontMetrics(Sfnt(rf)) + metrics = self.fonts[name] + indices = run.glyphIndexes() + glyphs = [] + pdf_pos = point + first_baseline = None + for i, pos in enumerate(run.positions()): + if first_baseline is None: + first_baseline = pos.y() + glyph_pos = point + pos + delta = glyph_pos - pdf_pos + glyphs.append((delta.x(), pos.y()-first_baseline, indices[i])) + pdf_pos = glyph_pos + + self.pdf.draw_glyph_run([1, 0, 0, -1, point.x(), + point.y()], rf.pixelSize(), metrics, glyphs) - r = QRawFont.fromFont(f) - metrics = FontMetrics(Sfnt(r)) - to = Text() - to.size = sz - to.set_transform(1, 0, 0, -1, point.x(), point.y()) - stretch = f.stretch() - if stretch != 100: - to.horizontal_scale = stretch - ws = f.wordSpacing() - if ws != 0: - to.word_spacing = ws - spacing = f.letterSpacing() - st = f.letterSpacingType() - text = type(u'')(text_item.text()) - if st == f.AbsoluteSpacing and spacing != 0: - to.char_space = spacing/self.scale - if st == f.PercentageSpacing and spacing not in {100, 0}: - # TODO: Figure out why the results from uncommenting the super - # class call above differ. The advance widths are the same as those - # reported by QRawfont, so presumably, Qt use some other - # algorithm, I can't be bothered to track it down. This behavior is - # correct as per the Qt docs' description of PercentageSpacing - widths = [w*-1 for w in metrics.advance_widths(text, - sz, f.stretch()/100.)] - to.glyph_adjust = ((spacing-100)/100., widths) - to.text = text - with self: - self.graphics_state.apply_fill(self.graphics_state.current_state['stroke'], - self, self.pdf) - self.pdf.draw_text(to) @store_error def drawPolygon(self, points, mode): @@ -460,40 +460,40 @@ if __name__ == '__main__': xmax, ymax = p.viewport().width(), p.viewport().height() try: p.drawRect(0, 0, xmax, ymax) - p.drawPolyline(QPoint(0, 0), QPoint(xmax, 0), QPoint(xmax, ymax), - QPoint(0, ymax), QPoint(0, 0)) - pp = QPainterPath() - pp.addRect(0, 0, xmax, ymax) - p.drawPath(pp) - p.save() - for i in xrange(3): - col = [0, 0, 0, 200] - col[i] = 255 - p.setOpacity(0.3) - p.setBrush(QBrush(QColor(*col))) - p.drawRect(0, 0, xmax/10, xmax/10) - p.translate(xmax/10, xmax/10) - p.scale(1, 1.5) - p.restore() + # p.drawPolyline(QPoint(0, 0), QPoint(xmax, 0), QPoint(xmax, ymax), + # QPoint(0, ymax), QPoint(0, 0)) + # pp = QPainterPath() + # pp.addRect(0, 0, xmax, ymax) + # p.drawPath(pp) + # p.save() + # for i in xrange(3): + # col = [0, 0, 0, 200] + # col[i] = 255 + # p.setOpacity(0.3) + # p.setBrush(QBrush(QColor(*col))) + # p.drawRect(0, 0, xmax/10, xmax/10) + # p.translate(xmax/10, xmax/10) + # p.scale(1, 1.5) + # p.restore() - p.save() - p.drawLine(0, 0, 5000, 0) - p.rotate(45) - p.drawLine(0, 0, 5000, 0) - p.restore() + # p.save() + # p.drawLine(0, 0, 5000, 0) + # p.rotate(45) + # p.drawLine(0, 0, 5000, 0) + # p.restore() f = p.font() - f.setPointSize(48) - f.setLetterSpacing(f.PercentageSpacing, 200) + f.setPointSize(24) + # f.setLetterSpacing(f.PercentageSpacing, 200) # f.setUnderline(True) # f.setOverline(True) # f.setStrikeOut(True) - f.setFamily('Times New Roman') + f.setFamily('Calibri') p.setFont(f) # p.scale(2, 2) # p.rotate(45) - p.setPen(QColor(0, 0, 255)) - p.drawText(QPoint(100, 300), 'Some text') + # p.setPen(QColor(0, 0, 255)) + p.drawText(QPoint(100, 300), 'Some text ū --- Д AV') finally: p.end() if dev.engine.errors: diff --git a/src/calibre/ebooks/pdf/render/fonts.py b/src/calibre/ebooks/pdf/render/fonts.py index 846d3fb144..dee7748e82 100644 --- a/src/calibre/ebooks/pdf/render/fonts.py +++ b/src/calibre/ebooks/pdf/render/fonts.py @@ -7,7 +7,13 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from calibre.ebooks.pdf.render.common import ( +import re +from itertools import izip, groupby +from operator import itemgetter +from collections import Counter +from future_builtins import map + +from calibre.ebooks.pdf.render.common import (Array, String, Stream, Dictionary, Name) STANDARD_FONTS = { @@ -16,11 +22,129 @@ STANDARD_FONTS = { 'Helvetica-Oblique', 'Courier-Oblique', 'Times-BoldItalic', 'Helvetica-BoldOblique', 'Courier-BoldOblique', } +''' +Notes +======= + +We must use Type 0 CID keyed fonts to represent unicode text. + +For TrueType +-------------- + +The mapping from the text strings to glyph ids is defined by two things: + + The /Encoding key of the Type-0 font dictionary + The /CIDToGIDMap key of the descendant font dictionary (for TrueType fonts) + +We set /Encoding to /Identity-H and /CIDToGIDMap to /Identity. This means that +text strings are interpreted as a sequence of two-byte numbers, high order byte +first. Each number gets mapped to a glyph id equal to itself by the +/CIDToGIDMap. + +''' + +class FontStream(Stream): + + def __init__(self, is_otf): + Stream.__init__(self) + self.is_otf = is_otf + + def add_extra_keys(self, d): + d['Length1'] = d['DL'] + if self.is_otf: + d['Subtype'] = Name('OpenType') + +class Font(object): + + def __init__(self, metrics, num, objects): + self.metrics = metrics + self.subset_tag = bytes(re.sub('.', lambda m: chr(int(m.group())+ord('A')), + oct(num))).rjust(6, b'A').decode('ascii') + self.font_stream = FontStream(metrics.is_otf) + self.font_descriptor = Dictionary({ + 'Type': Name('FontDescriptor'), + 'FontName': Name(metrics.postscript_name), + 'Flags': 0b100, # Symbolic font + 'FontBBox': Array(metrics.pdf_bbox), + 'ItalicAngle': metrics.post.italic_angle, + 'Ascent': metrics.pdf_ascent, + 'Descent': metrics.pdf_descent, + 'CapHeight': metrics.pdf_capheight, + 'AvgWidth': metrics.pdf_avg_width, + 'StemV': metrics.pdf_stemv, + }) + self.descendant_font = Dictionary({ + 'Type':Name('Font'), + 'Subtype':Name('CIDFontType' + ('0' if metrics.is_otf else '2')), + 'BaseFont': self.font_descriptor['FontName'], + 'FontDescriptor':objects.add(self.font_descriptor), + 'CIDSystemInfo':Dictionary({ + 'Registry':String('Adobe'), + 'Ordering':String('Identity'), + 'Supplement':0, + }), + }) + if not metrics.is_otf: + self.descendant_font['CIDToGIDMap'] = Name('Identity') + + self.font_dict = Dictionary({ + 'Type':Name('Font'), + 'Subtype':Name('Type0'), + 'Encoding':Name('Identity-H'), + 'BaseFont':self.descendant_font['BaseFont'], + 'DescendantFonts':Array([objects.add(self.descendant_font)]), + }) + + self.used_glyphs = set() + + def embed(self, objects): + # TODO: Subsetting and OpenType + self.font_descriptor['FontFile2'] = objects.add(self.font_stream) + self.write_widths(objects) + self.metrics.os2.zero_fstype() + self.metrics.sfnt(self.font_stream) + + def write_widths(self, objects): + glyphs = sorted(self.used_glyphs|{0}) + widths = {g:self.metrics.pdf_scale(w) for g, w in izip(glyphs, + self.metrics.glyph_widths(glyphs))} + counter = Counter() + for g, w in widths.iteritems(): + counter[w] += 1 + most_common = counter.most_common(1)[0][0] + self.descendant_font['DW'] = most_common + widths = {g:w for g, w in widths.iteritems() if w != most_common} + + groups = Array() + for k, g in groupby(enumerate(widths.iterkeys()), lambda (i,x):i-x): + group = list(map(itemgetter(1), g)) + gwidths = [widths[g] for g in group] + if len(set(gwidths)) == 1 and len(group) > 1: + w = (min(group), max(group), gwidths[0]) + else: + w = (min(group), Array(gwidths)) + groups.extend(w) + self.descendant_font['W'] = objects.add(groups) + + class FontManager(object): def __init__(self, objects): self.objects = objects self.std_map = {} + self.font_map = {} + self.fonts = [] + + def add_font(self, font_metrics, glyph_ids): + if font_metrics not in self.font_map: + self.fonts.append(Font(font_metrics, len(self.fonts), + self.objects)) + d = self.objects.add(self.fonts[-1].font_dict) + self.font_map[font_metrics] = (d, self.fonts[-1]) + + fontref, font = self.font_map[font_metrics] + font.used_glyphs |= glyph_ids + return fontref def add_standard_font(self, name): if name not in STANDARD_FONTS: @@ -33,3 +157,7 @@ class FontManager(object): })) return self.std_map[name] + def embed_fonts(self): + for font in self.fonts: + font.embed(self.objects) + diff --git a/src/calibre/ebooks/pdf/render/serialize.py b/src/calibre/ebooks/pdf/render/serialize.py index a5ae944356..92521fd0db 100644 --- a/src/calibre/ebooks/pdf/render/serialize.py +++ b/src/calibre/ebooks/pdf/render/serialize.py @@ -14,7 +14,8 @@ from collections import namedtuple from calibre.constants import (__appname__, __version__) from calibre.ebooks.pdf.render.common import ( - Reference, EOL, serialize, Stream, Dictionary, String, Name, Array) + Reference, EOL, serialize, Stream, Dictionary, String, Name, Array, + GlyphIndex) from calibre.ebooks.pdf.render.fonts import FontManager PDFVER = b'%PDF-1.6' @@ -357,9 +358,24 @@ class PDFStream(object): name = self.current_page.add_font(fontref) text_object.pdf_serialize(self.current_page, name) + def draw_glyph_run(self, transform, size, font_metrics, glyphs): + glyph_ids = {x[-1] for x in glyphs} + fontref = self.font_manager.add_font(font_metrics, glyph_ids) + name = self.current_page.add_font(fontref) + self.current_page.write(b'BT ') + serialize(Name(name), self.current_page) + self.current_page.write(' %g Tf '%size) + self.current_page.write('%s Tm '%' '.join(map(type(u''), transform))) + for x, y, glyph_id in glyphs: + self.current_page.write('%g %g Td '%(x, y)) + serialize(GlyphIndex(glyph_id, self.compress), self.current_page) + self.current_page.write(' Tj ') + self.current_page.write_line(b' ET') + def end(self): if self.current_page.getvalue(): self.end_page() + self.font_manager.embed_fonts() inforef = self.objects.add(self.info) self.objects.pdf_serialize(self.stream) self.write_line() diff --git a/src/calibre/utils/fonts/sfnt/container.py b/src/calibre/utils/fonts/sfnt/container.py index 92eb814337..4514721d2b 100644 --- a/src/calibre/utils/fonts/sfnt/container.py +++ b/src/calibre/utils/fonts/sfnt/container.py @@ -66,6 +66,8 @@ class Sfnt(object): if table: self.tables[table_tag] = self.TABLE_MAP.get( table_tag, UnknownTable)(table) + self.sfnt_version = (b'\0\x01\0\0' if b'glyf' in self.tables + else b'OTTO') def __getitem__(self, key): return self.tables[key] @@ -102,8 +104,8 @@ class Sfnt(object): ans[tag] = len(self[tag]) return ans - def __call__(self): - stream = BytesIO() + def __call__(self, stream=None): + stream = BytesIO() if stream is None else stream def spack(*args): stream.write(pack(*args)) diff --git a/src/calibre/utils/fonts/sfnt/head.py b/src/calibre/utils/fonts/sfnt/head.py index 1a1a919860..0297e8e7c8 100644 --- a/src/calibre/utils/fonts/sfnt/head.py +++ b/src/calibre/utils/fonts/sfnt/head.py @@ -8,7 +8,7 @@ __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' from itertools import izip -from struct import unpack_from, pack +from struct import unpack_from, pack, calcsize from calibre.utils.fonts.sfnt import UnknownTable, DateTimeProperty, FixedProperty from calibre.utils.fonts.sfnt.errors import UnsupportedFont @@ -32,10 +32,10 @@ class HeadTable(UnknownTable): 'units_per_em' , 'H', '_created' , 'q', '_modified' , 'q', - 'x_min' , 'H', - 'y_min' , 'H', - 'x_max' , 'H', - 'y_max' , 'H', + 'x_min' , 'h', + 'y_min' , 'h', + 'x_max' , 'h', + 'y_max' , 'h', 'mac_style' , 'H', 'lowest_rec_ppem' , 'H', 'font_direction_hint' , 'h', @@ -99,21 +99,58 @@ class HorizontalHeader(UnknownTable): class OS2Table(UnknownTable): - version_number = FixedProperty('_version') - def read_data(self): if hasattr(self, 'char_width'): return - from calibre.utils.fonts.utils import get_font_characteristics - vals = get_font_characteristics(self.raw, raw_is_table=True, - return_all=True) - for i, attr in enumerate(( - '_version', 'char_width', 'weight', 'width', 'fs_type', - 'subscript_x_size', 'subscript_y_size', 'subscript_x_offset', - 'subscript_y_offset', 'superscript_x_size', 'superscript_y_size', - 'superscript_x_offset', 'superscript_y_offset', 'strikeout_size', - 'strikeout_position', 'family_class', 'panose', 'selection', - 'is_italic', 'is_bold', 'is_regular')): - setattr(self, attr, vals[i]) + ver, = unpack_from(b'>H', self.raw) + field_types = [ + 'version' , 'H', + 'average_char_width', 'h', + 'weight_class', 'H', + 'width_class', 'H', + 'fs_type', 'H', + 'subscript_x_size', 'h', + 'subscript_y_size', 'h', + 'subscript_x_offset', 'h', + 'subscript_y_offset', 'h', + 'superscript_x_size', 'h', + 'superscript_y_size', 'h', + 'superscript_x_offset', 'h', + 'superscript_y_offset', 'h', + 'strikeout_size', 'h', + 'strikeout_position', 'h', + 'family_class', 'h', + 'panose', '10s', + 'ranges', '16s', + 'vendor_id', '4s', + 'selection', 'H', + 'first_char_index', 'H', + 'last_char_index', 'H', + 'typo_ascender', 'h', + 'typo_descender', 'h', + 'typo_line_gap', 'h', + 'win_ascent', 'H', + 'win_descent', 'H', + ] + if ver > 1: + field_types += [ + 'code_page_range', '8s', + 'x_height', 'h', + 'cap_height', 'h', + 'default_char', 'H', + 'break_char', 'H', + 'max_context', 'H', + ] + + self._fmt = ('>%s'%(''.join(field_types[1::2]))).encode('ascii') + self._fields = field_types[0::2] + + for f, val in izip(self._fields, unpack_from(self._fmt, self.raw)): + setattr(self, f, val) + + def zero_fstype(self): + prefix = calcsize(b'>HhHH') + self.raw = self.raw[:prefix] + b'\0\0' + self.raw[prefix+2:] + self.fs_type = 0 class PostTable(UnknownTable): diff --git a/src/calibre/utils/fonts/sfnt/metrics.py b/src/calibre/utils/fonts/sfnt/metrics.py index a75138aadd..4f86948ff2 100644 --- a/src/calibre/utils/fonts/sfnt/metrics.py +++ b/src/calibre/utils/fonts/sfnt/metrics.py @@ -21,25 +21,42 @@ class FontMetrics(object): def __init__(self, sfnt): self.sfnt = sfnt + self.head = self.sfnt[b'head'] hhea = self.sfnt[b'hhea'] hhea.read_data(self.sfnt[b'hmtx']) self.ascent = hhea.ascender self.descent = hhea.descender + self.bbox = ( self.head.x_min, self.head.y_min, self.head.x_max, + self.head.y_max ) self._advance_widths = hhea.advance_widths self.cmap = self.sfnt[b'cmap'] - self.head = self.sfnt[b'head'] self.units_per_em = self.head.units_per_em self.os2 = self.sfnt[b'OS/2'] self.os2.read_data() self.post = self.sfnt[b'post'] self.post.read_data() self.names = get_all_font_names(self.sfnt[b'name'].raw, raw_is_table=True) + self.is_otf = 'CFF ' in self.sfnt.tables + self._sig = hash(self.sfnt[b'name'].raw) + + # Metrics for embedding in PDF + pdf_scale = self.pdf_scale = lambda x:int(round(x*1000./self.units_per_em)) + self.pdf_ascent, self.pdf_descent = map(pdf_scale, + (self.os2.typo_ascender, self.os2.typo_descender)) + self.pdf_bbox = tuple(map(pdf_scale, self.bbox)) + self.pdf_capheight = pdf_scale(getattr(self.os2, 'cap_height', + self.os2.typo_ascender)) + self.pdf_avg_width = pdf_scale(self.os2.average_char_width) + self.pdf_stemv = 50 + int((self.os2.weight_class / 65.0) ** 2) + + def __hash__(self): + return self._sig @property def postscript_name(self): if 'postscript_name' in self.names: - return self.names['postscript_name'] - return self.names['full_name'].replace(' ', '') + return self.names['postscript_name'].replace(' ', '-') + return self.names['full_name'].replace(' ', '-') def underline_thickness(self, pixel_size=12.0): 'Thickness for lines (in pixels) at the specified size' @@ -74,10 +91,14 @@ class FontMetrics(object): chars = tuple(map(ord, string)) cmap = self.cmap.get_character_map(chars) glyph_ids = (cmap[c] for c in chars) - last = len(self._advance_widths) pixel_size_x = stretch * pixel_size xscale = pixel_size_x / self.units_per_em - return tuple(self._advance_widths[i if i < last else -1]*xscale for i in glyph_ids) + return tuple(i*xscale for i in self.glyph_widths(glyph_ids)) + + def glyph_widths(self, glyph_ids): + last = len(self._advance_widths) + return tuple(self._advance_widths[i if i < last else -1] for i in + glyph_ids) def width(self, string, pixel_size=12.0, stretch=1.0): 'The width of the string at the specified pixel size and stretch, in pixels' @@ -86,9 +107,15 @@ class FontMetrics(object): if __name__ == '__main__': import sys from calibre.utils.fonts.sfnt.container import Sfnt - with open(sys.argv[-2], 'rb') as f: + with open(sys.argv[-1], 'rb') as f: raw = f.read() sfnt = Sfnt(raw) m = FontMetrics(sfnt) - print (m.advance_widths(sys.argv[-1])) + print ('Ascent:', m.pdf_ascent) + print ('Descent:', m.pdf_descent) + print ('PDF BBox:', m.pdf_bbox) + print ('CapHeight:', m.pdf_capheight) + print ('AvgWidth:', m.pdf_avg_width) + print ('ItalicAngle', m.post.italic_angle) + print ('StemV', m.pdf_stemv) diff --git a/src/calibre/utils/fonts/utils.py b/src/calibre/utils/fonts/utils.py index 47ebb925bf..a5a6230a0c 100644 --- a/src/calibre/utils/fonts/utils.py +++ b/src/calibre/utils/fonts/utils.py @@ -64,11 +64,8 @@ def get_font_characteristics(raw, raw_is_table=False, return_all=False): offset = struct.calcsize(common_fields) panose = struct.unpack_from(b'>10B', os2_table, offset) offset += 10 - (range1,) = struct.unpack_from(b'>L', os2_table, offset) - offset += struct.calcsize(b'>L') - if version > 0: - range2, range3, range4 = struct.unpack_from(b'>3L', os2_table, offset) - offset += struct.calcsize(b'>3L') + (range1, range2, range3, range4) = struct.unpack_from(b'>4L', os2_table, offset) + offset += struct.calcsize(b'>4L') vendor_id = os2_table[offset:offset+4] vendor_id offset += 4