''' Revealer Do you have something to hide? Secret backup plug-in for the electrum wallet. Tiago Romagnani Silveira, 2017 ''' import os import random import qrcode import traceback from hashlib import sha256 from decimal import Decimal from typing import NamedTuple, Optional, Dict, Tuple from PyQt5.QtPrintSupport import QPrinter from electrum.plugin import BasePlugin, hook from electrum.i18n import _ from electrum.util import to_bytes, make_dir, InvalidPassword, UserCancelled, bh2u, bfh from electrum.gui.qt.util import * from electrum.gui.qt.qrtextedit import ScanQRTextEdit from electrum.gui.qt.main_window import StatusBarButton from .hmac_drbg import DRBG class VersionedSeed(NamedTuple): version: str seed: str checksum: str def get_ui_string_version_plus_seed(self): version, seed = self.version, self.seed assert isinstance(version, str) and len(version) == 1, version assert isinstance(seed, str) and len(seed) >= 32 ret = version + seed ret = ret.upper() return ' '.join(ret[i : i+4] for i in range(0, len(ret), 4)) class Plugin(BasePlugin): LATEST_VERSION = '1' KNOWN_VERSIONS = ('0', '1') assert LATEST_VERSION in KNOWN_VERSIONS MAX_PLAINTEXT_LEN = 189 # chars SIZE = (159, 97) def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) self.base_dir = os.path.join(config.electrum_path(), 'revealer') if self.config.get('calibration_h') is None: self.config.set_key('calibration_h', 0) if self.config.get('calibration_v') is None: self.config.set_key('calibration_v', 0) self.calibration_h = self.config.get('calibration_h') self.calibration_v = self.config.get('calibration_v') self.f_size = QSize(1014*2, 642*2) self.abstand_h = 21 self.abstand_v = 34 self.calibration_noise = int('10' * 128) self.rawnoise = False make_dir(self.base_dir) self.extension = False @hook def create_status_bar(self, parent): b = StatusBarButton(QIcon(':icons/revealer.png'), "Revealer "+_("secret backup utility"), partial(self.setup_dialog, parent)) parent.addPermanentWidget(b) def requires_settings(self): return True def settings_widget(self, window): return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window)) def password_dialog(self, msg=None, parent=None): from electrum.gui.qt.password_dialog import PasswordDialog parent = parent or self d = PasswordDialog(parent, msg) return d.run() def get_seed(self): password = None if self.wallet.has_keystore_encryption(): password = self.password_dialog(parent=self.d.parent()) if not password: raise UserCancelled() keystore = self.wallet.get_keystore() if not keystore or not keystore.has_seed(): return self.extension = bool(keystore.get_passphrase(password)) return keystore.get_seed(password) def setup_dialog(self, window): self.wallet = window.parent().wallet self.update_wallet_name(self.wallet) self.user_input = False self.d = WindowModalDialog(window, "Setup Dialog") self.d.setMinimumWidth(500) self.d.setMinimumHeight(210) self.d.setMaximumHeight(320) self.d.setContentsMargins(11,11,1,1) self.hbox = QHBoxLayout(self.d) vbox = QVBoxLayout() logo = QLabel() self.hbox.addWidget(logo) logo.setPixmap(QPixmap(':icons/revealer.png')) logo.setAlignment(Qt.AlignLeft) self.hbox.addSpacing(16) vbox.addWidget(WWLabel(""+_("Revealer Secret Backup Plugin")+"
" +_("To encrypt your backup, first we need to load some noise.")+"
")) vbox.addSpacing(7) bcreate = QPushButton(_("Create a new Revealer")) bcreate.setMaximumWidth(181) bcreate.setDefault(True) vbox.addWidget(bcreate, Qt.AlignCenter) self.load_noise = ScanQRTextEdit() self.load_noise.setTabChangesFocus(True) self.load_noise.textChanged.connect(self.on_edit) self.load_noise.setMaximumHeight(33) self.hbox.addLayout(vbox) vbox.addWidget(WWLabel(_("or type a existing revealer code below and click 'next':"))) vbox.addWidget(self.load_noise) vbox.addSpacing(3) self.next_button = QPushButton(_("Next"), self.d) self.next_button.setEnabled(False) vbox.addLayout(Buttons(self.next_button)) self.next_button.clicked.connect(self.d.close) self.next_button.clicked.connect(partial(self.cypherseed_dialog, window)) vbox.addWidget( QLabel("" + _("Warning") + ": " + _("Each revealer should be used only once.") +"
"+_("more information at https://revealer.cc/faq"))) def mk_digital(): try: self.make_digital(self.d) except Exception: traceback.print_exc(file=sys.stdout) else: self.cypherseed_dialog(window) bcreate.clicked.connect(mk_digital) return bool(self.d.exec_()) def get_noise(self): text = self.load_noise.text() return ''.join(text.split()).lower() def on_edit(self): txt = self.get_noise() versioned_seed = self.get_versioned_seed_from_user_input(txt) if versioned_seed: self.versioned_seed = versioned_seed self.user_input = bool(versioned_seed) self.next_button.setEnabled(bool(versioned_seed)) @classmethod def code_hashid(cls, txt: str) -> str: x = to_bytes(txt, 'utf8') hash = sha256(x).hexdigest() return hash[-3:].upper() @classmethod def get_versioned_seed_from_user_input(cls, txt: str) -> Optional[VersionedSeed]: if len(txt) < 34: return None try: int(txt, 16) except: return None version = txt[0] if version not in cls.KNOWN_VERSIONS: return None checksum = cls.code_hashid(txt[:-3]) if txt[-3:].upper() != checksum.upper(): return None return VersionedSeed(version=version.upper(), seed=txt[1:-3].upper(), checksum=checksum.upper()) def make_digital(self, dialog): self.make_rawnoise(True) self.bdone(dialog) self.d.close() def get_path_to_revealer_file(self, ext: str= '') -> str: version = self.versioned_seed.version code_id = self.versioned_seed.checksum filename = self.filename_prefix + version + "_" + code_id + ext path = os.path.join(self.base_dir, filename) return os.path.normcase(os.path.abspath(path)) def get_path_to_calibration_file(self): path = os.path.join(self.base_dir, 'calibration.pdf') return os.path.normcase(os.path.abspath(path)) def bcrypt(self, dialog): self.rawnoise = False version = self.versioned_seed.version code_id = self.versioned_seed.checksum dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id), "", self.get_path_to_revealer_file(), "", "
", "
", "", _("Always check you backups.")])) dialog.close() def ext_warning(self, dialog): dialog.show_message(''.join(["",_("Warning"), ": ", _("your seed extension will not be included in the encrypted backup.")])) dialog.close() def bdone(self, dialog): version = self.versioned_seed.version code_id = self.versioned_seed.checksum dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id), "
","", self.get_path_to_revealer_file(), ''])) def customtxt_limits(self): txt = self.text.text() self.max_chars.setVisible(False) self.char_count.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})") if len(txt)>0: self.ctext.setEnabled(True) if len(txt) > self.MAX_PLAINTEXT_LEN: self.text.setPlainText(txt[:self.MAX_PLAINTEXT_LEN]) self.max_chars.setVisible(True) def t(self): self.txt = self.text.text() self.seed_img(is_seed=False) def cypherseed_dialog(self, window): d = WindowModalDialog(window, "Encryption Dialog") d.setMinimumWidth(500) d.setMinimumHeight(210) d.setMaximumHeight(450) d.setContentsMargins(11, 11, 1, 1) self.c_dialog = d hbox = QHBoxLayout(d) self.vbox = QVBoxLayout() logo = QLabel() hbox.addWidget(logo) logo.setPixmap(QPixmap(':icons/revealer.png')) logo.setAlignment(Qt.AlignLeft) hbox.addSpacing(16) self.vbox.addWidget(WWLabel("" + _("Revealer Secret Backup Plugin") + "
" + _("Ready to encrypt for revealer {}") .format(self.versioned_seed.version+'_'+self.versioned_seed.checksum))) self.vbox.addSpacing(11) hbox.addLayout(self.vbox) grid = QGridLayout() self.vbox.addLayout(grid) cprint = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name)) cprint.setMaximumWidth(250) cprint.clicked.connect(partial(self.seed_img, True)) self.vbox.addWidget(cprint) self.vbox.addSpacing(1) self.vbox.addWidget(WWLabel(""+_("OR")+" "+_("type a custom alphanumerical secret below:"))) self.text = ScanQRTextEdit() self.text.setTabChangesFocus(True) self.text.setMaximumHeight(70) self.text.textChanged.connect(self.customtxt_limits) self.vbox.addWidget(self.text) self.char_count = WWLabel("") self.char_count.setAlignment(Qt.AlignRight) self.vbox.addWidget(self.char_count) self.max_chars = WWLabel("" + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN) +"") self.vbox.addWidget(self.max_chars) self.max_chars.setVisible(False) self.ctext = QPushButton(_("Encrypt custom secret")) self.ctext.clicked.connect(self.t) self.vbox.addWidget(self.ctext) self.ctext.setEnabled(False) self.vbox.addSpacing(11) self.vbox.addLayout(Buttons(CloseButton(d))) return bool(d.exec_()) def update_wallet_name(self, name): self.wallet_name = str(name) def seed_img(self, is_seed = True): if is_seed: try: cseed = self.get_seed() except UserCancelled: return except InvalidPassword as e: self.d.show_error(str(e)) return if not cseed: self.d.show_message(_("This wallet has no seed")) return txt = cseed.upper() else: txt = self.txt.upper() img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap.fill(Qt.white) painter = QPainter() painter.begin(bitmap) QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'SourceSansPro-Bold.otf') ) if len(txt) < 102 : fontsize = 15 linespace = 15 max_letters = 17 max_lines = 6 max_words = 3 else: fontsize = 12 linespace = 10 max_letters = 21 max_lines = 9 max_words = int(max_letters/4) font = QFont('Source Sans Pro', fontsize, QFont.Bold) font.setLetterSpacing(QFont.PercentageSpacing, 100) font.setPixelSize(fontsize) painter.setFont(font) seed_array = txt.split(' ') for n in range(max_lines): nwords = max_words temp_seed = seed_array[:nwords] while len(' '.join(map(str, temp_seed))) > max_letters: nwords = nwords - 1 temp_seed = seed_array[:nwords] painter.drawText(QRect(0, linespace*n , self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed))) del seed_array[:nwords] painter.end() img = bitmap.toImage() if (self.rawnoise == False): self.make_rawnoise() self.make_cypherseed(img, self.rawnoise, False, is_seed) return img def make_rawnoise(self, create_revealer=False): if not self.user_input: version = self.LATEST_VERSION hex_seed = bh2u(os.urandom(16)) checksum = self.code_hashid(version + hex_seed) self.versioned_seed = VersionedSeed(version=version.upper(), seed=hex_seed.upper(), checksum=checksum.upper()) assert self.versioned_seed w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format_Mono) noise_map = self.get_noise_map(self.versioned_seed) for (x,y), pixel in noise_map.items(): rawnoise.setPixel(x, y, pixel) self.rawnoise = rawnoise if create_revealer: self.make_revealer() @classmethod def get_noise_map(self, versioned_seed: VersionedSeed) -> Dict[Tuple[int, int], int]: """Returns a map from (x,y) coordinate to pixel value 0/1, to be used as noise.""" w, h = self.SIZE version = versioned_seed.version hex_seed = versioned_seed.seed checksum = versioned_seed.checksum noise_map = {} if version == '0': random.seed(int(hex_seed, 16)) for x in range(w): for y in range(h): noise_map[(x, y)] = random.randint(0, 1) elif version == '1': prng_seed = bfh(hex_seed + version + checksum) drbg = DRBG(prng_seed) num_noise_bytes = 1929 # ~ w*h noise_array = bin(int.from_bytes(drbg.generate(num_noise_bytes), 'big'))[2:] i = 0 for x in range(w): for y in range(h): noise_map[(x, y)] = int(noise_array[i]) i += 1 else: raise Exception(f"unexpected revealer version: {version}") return noise_map def make_calnoise(self): random.seed(self.calibration_noise) w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format_Mono) for x in range(w): for y in range(h): rawnoise.setPixel(x,y,random.randint(0, 1)) self.calnoise = self.pixelcode_2x2(rawnoise) def make_revealer(self): revealer = self.pixelcode_2x2(self.rawnoise) revealer.invertPixels() revealer = QBitmap.fromImage(revealer) revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio) revealer = self.overlay_marks(revealer) self.filename_prefix = 'revealer_' revealer.save(self.get_path_to_revealer_file('.png')) self.toPdf(QImage(revealer)) QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf'))) def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True): img = img.convertToFormat(QImage.Format_Mono) p = QPainter() p.begin(img) p.setCompositionMode(26) #xor p.drawImage(0, 0, rawnoise) p.end() cypherseed = self.pixelcode_2x2(img) cypherseed = QBitmap.fromImage(cypherseed) cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio) cypherseed = self.overlay_marks(cypherseed, True, calibration) if not is_seed: self.filename_prefix = 'custom_secret_' self.was = _('Custom secret') else: self.filename_prefix = self.wallet_name + '_seed_' self.was = self.wallet_name + ' ' + _('seed') if self.extension: self.ext_warning(self.c_dialog) if not calibration: self.toPdf(QImage(cypherseed)) QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf'))) cypherseed.save(self.get_path_to_revealer_file('.png')) self.bcrypt(self.c_dialog) return cypherseed def calibration(self): img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.MonoOnly) bitmap.fill(Qt.black) self.make_calnoise() img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True) self.calibration_pdf(img) QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_calibration_file())) return img def toPdf(self, image): printer = QPrinter() printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setResolution(600) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) printer.setPageMargins(0,0,0,0,6) painter = QPainter() painter.begin(printer) delta_h = round(image.width()/self.abstand_v) delta_v = round(image.height()/self.abstand_h) size_h = 2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))/2) size_v = 1284+((int(self.calibration_v)*1284/(1284-(delta_v*2)+int(self.calibration_v)))/2) image = image.scaled(size_h, size_v) painter.drawImage(553,533, image) wpath = QPainterPath() wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19) painter.setPen(QPen(Qt.black, 1)) painter.drawPath(wpath) painter.end() def calibration_pdf(self, image): printer = QPrinter() printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) printer.setResolution(600) printer.setOutputFormat(QPrinter.PdfFormat) printer.setOutputFileName(self.get_path_to_calibration_file()) printer.setPageMargins(0,0,0,0,6) painter = QPainter() painter.begin(printer) painter.drawImage(553,533, image) font = QFont('Source Sans Pro', 10, QFont.Bold) painter.setFont(font) painter.drawText(254,277, _("Calibration sheet")) font = QFont('Source Sans Pro', 7, QFont.Bold) painter.setFont(font) painter.drawText(600,2077, _("Instructions:")) font = QFont('Source Sans Pro', 7, QFont.Normal) painter.setFont(font) painter.drawText(700, 2177, _("1. Place this paper on a flat and well iluminated surface.")) painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left.")) painter.drawText(700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best " "match on the opposite sides. ")) painter.drawText(700, 2477, _("4. Type the numbers in the software")) painter.end() def pixelcode_2x2(self, img): result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32 ) white = qRgba(255,255,255,0) black = qRgba(0,0,0,255) for x in range(img.width()): for y in range(img.height()): c = img.pixel(QPoint(x,y)) colors = QColor(c).getRgbF() if colors[0]: result.setPixel(x*2+1,y*2+1, black) result.setPixel(x*2,y*2+1, white) result.setPixel(x*2+1,y*2, white) result.setPixel(x*2, y*2, black) else: result.setPixel(x*2+1,y*2+1, white) result.setPixel(x*2,y*2+1, black) result.setPixel(x*2+1,y*2, black) result.setPixel(x*2, y*2, white) return result def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): border_color = Qt.white base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32) base_img.fill(border_color) img = QImage(img) painter = QPainter() painter.begin(base_img) total_distance_h = round(base_img.width() / self.abstand_v) dist_v = round(total_distance_h) / 2 dist_h = round(total_distance_h) / 2 img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h))) painter.drawImage(total_distance_h, total_distance_h, img) #frame around image pen = QPen(Qt.black, 2) painter.setPen(pen) #horz painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h) painter.drawLine(0, base_img.height()-(total_distance_h), base_img.width(), base_img.height()-(total_distance_h)) #vert painter.drawLine(total_distance_h, 0, total_distance_h, base_img.height()) painter.drawLine(base_img.width()-(total_distance_h), 0, base_img.width()-(total_distance_h), base_img.height()) #border around img border_thick = 6 Rpath = QPainterPath() Rpath.addRect(QRectF((total_distance_h)+(border_thick/2), (total_distance_h)+(border_thick/2), base_img.width()-((total_distance_h)*2)-((border_thick)-1), (base_img.height()-((total_distance_h))*2)-((border_thick)-1))) pen = QPen(Qt.black, border_thick) pen.setJoinStyle (Qt.MiterJoin) painter.setPen(pen) painter.drawPath(Rpath) Bpath = QPainterPath() Bpath.addRect(QRectF((total_distance_h), (total_distance_h), base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2))) pen = QPen(Qt.black, 1) painter.setPen(pen) painter.drawPath(Bpath) pen = QPen(Qt.black, 1) painter.setPen(pen) painter.drawLine(0, base_img.height()/2, total_distance_h, base_img.height()/2) painter.drawLine(base_img.width()/2, 0, base_img.width()/2, total_distance_h) painter.drawLine(base_img.width()-total_distance_h, base_img.height()/2, base_img.width(), base_img.height()/2) painter.drawLine(base_img.width()/2, base_img.height(), base_img.width()/2, base_img.height() - total_distance_h) #print code f_size = 37 QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf')) font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold) font.setPixelSize(35) painter.setFont(font) if not calibration_sheet: if is_cseed: #its a secret painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11, QImage(':icons/electrumb.png').scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation)) painter.setPen(QPen(Qt.white, border_thick*8)) painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2, (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2, base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2 - 77, (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2) painter.setPen(QColor(0,0,0,255)) painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11, base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.version + '_'+self.versioned_seed.checksum) painter.end() else: # revealer painter.setPen(QPen(border_color, 17)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) painter.setPen(QPen(Qt.black, 2)) painter.drawLine(0, dist_v, base_img.width(), dist_v) painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) logo = QImage(':icons/revealer_c.png').scaledToWidth(1.3*(total_distance_h)) painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation) #frame around logo painter.setPen(QPen(Qt.black, border_thick)) painter.drawLine(total_distance_h+border_thick, total_distance_h+logo.height()+3*(border_thick/2), total_distance_h+logo.width()+border_thick, total_distance_h+logo.height()+3*(border_thick/2)) painter.drawLine(logo.width()+total_distance_h+3*(border_thick/2), total_distance_h+(border_thick), total_distance_h+logo.width()+3*(border_thick/2), total_distance_h+logo.height()+(border_thick)) #frame around code/qr qr_size = 179 painter.drawLine((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size, (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, (base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2) painter.drawLine((base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, base_img.width()/2 + (total_distance_h/2)-border_thick-(border_thick*8)/2-qr_size, ((base_img.height()-((total_distance_h)))-(border_thick/2)-2)) painter.setPen(QPen(Qt.white, border_thick * 8)) painter.drawLine( base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2, base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2) painter.setPen(QColor(0,0,0,255)) painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107, base_img.width()-total_distance_h - border_thick -93, base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed()) painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size, base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum) # draw qr code qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed() + self.versioned_seed.checksum) target = QRectF(base_img.width()-65-qr_size, base_img.height()-65-qr_size, qr_size, qr_size ) painter.drawImage(target, qr_qt) painter.setPen(QPen(Qt.black, 4)) painter.drawLine(base_img.width()-65-qr_size, base_img.height()-65-qr_size, base_img.width() - 65 - qr_size, (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 4 ) painter.drawLine(base_img.width()-65-qr_size, base_img.height()-65-qr_size, base_img.width() - 65, base_img.height()-65-qr_size ) painter.end() else: # calibration only painter.end() cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100, QImage.Format_ARGB32) cal_img.fill(Qt.white) cal_painter = QPainter() cal_painter.begin(cal_img) cal_painter.drawImage(0,0, base_img) #black lines in the middle of border top left only cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) cal_painter.drawLine(0, dist_v, base_img.width(), dist_v) cal_painter.drawLine(dist_h, 0, dist_h, base_img.height()) pen = QPen(Qt.black, 2, Qt.DashDotDotLine) cal_painter.setPen(pen) n=15 cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold)) for x in range(-n,n): #lines on bottom (vertical calibration) cal_painter.drawLine((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-13, x+2+base_img.height()-(dist_v), (((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)+13, x+2+base_img.height()-(dist_v)) num_pos = 9 if x > 9 : num_pos = 17 if x < 0 : num_pos = 20 if x < -9: num_pos = 27 cal_painter.drawText((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-num_pos, 50+base_img.height()-(dist_v), str(x)) #lines on the right (horizontal calibrations) cal_painter.drawLine(x+2+(base_img.width()-(dist_h)), ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)-13, x+2+(base_img.width()-(dist_h)), ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)+13) cal_painter.drawText(30+(base_img.width()-(dist_h)), ((base_img.height()/(2*n)) *(x))+ (base_img.height()/2)+13, str(x)) cal_painter.end() base_img = cal_img return base_img def paintQR(self, data): if not data: return qr = qrcode.QRCode() qr.add_data(data) matrix = qr.get_matrix() k = len(matrix) border_color = Qt.white base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32) base_img.fill(border_color) qrpainter = QPainter() qrpainter.begin(base_img) boxsize = 5 size = k * boxsize left = (base_img.width() - size)/2 top = (base_img.height() - size)/2 qrpainter.setBrush(Qt.black) qrpainter.setPen(Qt.black) for r in range(k): for c in range(k): if matrix[r][c]: qrpainter.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1) qrpainter.end() return base_img def calibration_dialog(self, window): d = WindowModalDialog(window, _("Revealer - Printer calibration settings")) d.setMinimumSize(100, 200) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(''.join(["
", _("If you have an old printer, or want optimal precision"),"
", _("print the calibration pdf and follow the instructions "), "
","
", ]))) self.calibration_h = self.config.get('calibration_h') self.calibration_v = self.config.get('calibration_v') cprint = QPushButton(_("Open calibration pdf")) cprint.clicked.connect(self.calibration) vbox.addWidget(cprint) vbox.addWidget(QLabel(_('Calibration values:'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel(_('Right side')), 0, 0) horizontal = QLineEdit() horizontal.setText(str(self.calibration_h)) grid.addWidget(horizontal, 0, 1) grid.addWidget(QLabel(_('Bottom')), 1, 0) vertical = QLineEdit() vertical.setText(str(self.calibration_v)) grid.addWidget(vertical, 1, 1) vbox.addStretch() vbox.addSpacing(13) vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return self.calibration_h = int(Decimal(horizontal.text())) self.config.set_key('calibration_h', self.calibration_h) self.calibration_v = int(Decimal(vertical.text())) self.config.set_key('calibration_v', self.calibration_v)