From 836a623b5f3c3d62d0116114af481631b5fcb6ea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 30 Dec 2012 00:22:23 +0530 Subject: [PATCH] Fix serialization of floating point numbers --- src/calibre/ebooks/pdf/render/common.py | 20 +++++++++++++++++++- src/calibre/ebooks/pdf/render/engine.py | 7 ++++++- src/calibre/ebooks/pdf/render/serialize.py | 15 ++++++++------- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/calibre/ebooks/pdf/render/common.py b/src/calibre/ebooks/pdf/render/common.py index 5e470122c5..5be06b1b98 100644 --- a/src/calibre/ebooks/pdf/render/common.py +++ b/src/calibre/ebooks/pdf/render/common.py @@ -10,6 +10,7 @@ __docformat__ = 'restructuredtext en' import codecs, zlib from io import BytesIO from struct import pack +from decimal import Decimal EOL = b'\n' @@ -51,13 +52,30 @@ PAPER_SIZES = {k:globals()[k.upper()] for k in ('a0 a1 a2 a3 a4 a5 a6 b0 b1 b2' # Basic PDF datatypes {{{ +def format_float(f): + if abs(f) < 1e-7: + return '0' + places = 6 + a, b = type(u'')(Decimal(f).quantize(Decimal(10)**-places)).partition('.')[0::2] + b = b.rstrip('0') + if not b: + return '0' if a == '-0' else a + return '%s.%s'%(a, b) + +def fmtnum(o): + if isinstance(o, (int, long)): + return type(u'')(o) + return format_float(o) + def serialize(o, stream): if hasattr(o, 'pdf_serialize'): o.pdf_serialize(stream) elif isinstance(o, bool): stream.write(b'true' if o else b'false') - elif isinstance(o, (int, long, float)): + elif isinstance(o, (int, long)): stream.write(type(u'')(o).encode('ascii')) + elif isinstance(o, float): + stream.write(format_float(o).encode('ascii')) elif o is None: stream.write(b'null') else: diff --git a/src/calibre/ebooks/pdf/render/engine.py b/src/calibre/ebooks/pdf/render/engine.py index 8abc271b4d..4bdf38e123 100644 --- a/src/calibre/ebooks/pdf/render/engine.py +++ b/src/calibre/ebooks/pdf/render/engine.py @@ -10,6 +10,7 @@ __docformat__ = 'restructuredtext en' import sys, traceback from collections import namedtuple from functools import wraps, partial +from future_builtins import map import sip from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter, @@ -18,13 +19,17 @@ from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter, from calibre.constants import plugins from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path) -from calibre.ebooks.pdf.render.common import inch, A4 +from calibre.ebooks.pdf.render.common import inch, A4, fmtnum from calibre.utils.fonts.sfnt.container import Sfnt from calibre.utils.fonts.sfnt.metrics import FontMetrics Point = namedtuple('Point', 'x y') ColorState = namedtuple('ColorState', 'color opacity do') +def repr_transform(t): + vals = map(fmtnum, (t.m11(), t.m12(), t.m21(), t.m22(), t.dx(), t.dy())) + return '[%s]'%' '.join(vals) + def store_error(func): @wraps(func) diff --git a/src/calibre/ebooks/pdf/render/serialize.py b/src/calibre/ebooks/pdf/render/serialize.py index 5042702deb..9c5f8c6f21 100644 --- a/src/calibre/ebooks/pdf/render/serialize.py +++ b/src/calibre/ebooks/pdf/render/serialize.py @@ -15,7 +15,7 @@ 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, - GlyphIndex) + GlyphIndex, fmtnum) from calibre.ebooks.pdf.render.fonts import FontManager from calibre.ebooks.pdf.render.links import Links @@ -180,7 +180,7 @@ class Text(object): stream.write_line('BT ') serialize(Name(font_name), stream) stream.write(' %g Tf '%self.size) - stream.write(' '.join(map(type(u''), self.transform)) + ' Tm ') + stream.write(' '.join(map(fmtnum, self.transform)) + ' Tm ') if self.horizontal_scale != self.default_horizontal_scale: stream.write('%g Tz '%self.horizontal_scale) if self.word_spacing != self.default_word_spacing: @@ -331,7 +331,7 @@ class PDFStream(object): vals = [m.m11(), m.m12(), m.m21(), m.m22(), m.dx(), m.dy()] else: vals = args - cm = ' '.join(map(type(u''), vals)) + cm = ' '.join(map(fmtnum, vals)) self.current_page.write_line(cm + ' cm') def set_rgb_colorspace(self): @@ -355,7 +355,8 @@ class PDFStream(object): if i != 0: self.current_page.write_line() for x in op: - self.current_page.write(type(u'')(x) + ' ') + self.current_page.write( + (fmtnum(x) if isinstance(x, (int, long, float)) else x) + ' ') def draw_path(self, path, stroke=True, fill=False, fill_rule='winding'): if not path.ops: return @@ -394,7 +395,7 @@ class PDFStream(object): op = Dictionary({'Type':Name('ExtGState'), 'CA': opacity}) self.stroke_opacities[opacity] = self.objects.add(op) self.current_page.set_opacity(self.stroke_opacities[opacity]) - self.current_page.write_line(' '.join(map(type(u''), color[:3])) + ' SC') + self.current_page.write_line(' '.join(map(fmtnum, color[:3])) + ' SC') def set_fill_color(self, color): opacity = color.opacity @@ -402,7 +403,7 @@ class PDFStream(object): op = Dictionary({'Type':Name('ExtGState'), 'ca': opacity}) self.fill_opacities[opacity] = self.objects.add(op) self.current_page.set_opacity(self.fill_opacities[opacity]) - self.current_page.write_line(' '.join(map(type(u''), color[:3])) + ' sc') + self.current_page.write_line(' '.join(map(fmtnum, color[:3])) + ' sc') def end_page(self): pageref = self.current_page.end(self.objects, self.stream) @@ -424,7 +425,7 @@ class PDFStream(object): 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))) + self.current_page.write('%s Tm '%' '.join(map(fmtnum, transform))) for x, y, glyph_id in glyphs: self.current_page.write('%g %g Td '%(x, y)) serialize(GlyphIndex(glyph_id), self.current_page)