''' Revealer Do you have something to hide? Secret backup plug-in for the electrum wallet. Copyright: 2017 Tiago Romagnani Silveira 2023-2024 Soren Stoutner Distributed under the MIT software license, see the accompanying file LICENCE or http://www.opensource.org/licenses/mit-license.php ''' import os import random import traceback from decimal import Decimal from functools import partial import sys from typing import TYPE_CHECKING from PyQt6.QtPrintSupport import QPrinter from PyQt6.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize, QMarginsF from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, QColor, QDesktopServices, qRgba, QPainterPath, QPageSize, QPageLayout) from PyQt6.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit) from electrum.plugin import hook from electrum.i18n import _ from electrum.util import make_dir, InvalidPassword, UserCancelled from electrum.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path, internal_plugin_icon_path, WindowModalDialog, Buttons, CloseButton, OkButton) from electrum.gui.qt.qrtextedit import ScanQRTextEdit from electrum.gui.qt.main_window import StatusBarButton from electrum.gui.qt.util import read_QIcon_from_bytes, read_QPixmap_from_bytes from electrum.gui.common_qt.util import paintQR from .revealer import RevealerPlugin if TYPE_CHECKING: from electrum.gui.qt import ElectrumGui class Plugin(RevealerPlugin): MAX_PLAINTEXT_LEN = 189 # chars def __init__(self, parent, config, name): RevealerPlugin.__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 self._init_qt_received = False self.icon_bytes = self.read_file("revealer.png") @hook def load_wallet(self, wallet, window): if self._init_qt_received: # only need/want the first signal return self._init_qt_received = True # load custom fonts (note: here, and not in __init__, as it needs the QApplication to be created) QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'SourceSans3-Bold.otf')) QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf')) @hook def create_status_bar(self, sb): b = StatusBarButton( read_QIcon_from_bytes(self.icon_bytes), "Revealer "+_("Visual Cryptography Plugin"), partial(self.setup_dialog, sb), sb.height()) sb.addPermanentWidget(b) def requires_settings(self): return True def settings_dialog(self, window, wallet): return 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, "Revealer Visual Cryptography Plugin - Select Noise File") self.d.setContentsMargins(11,11,1,1) # Create an HBox layout. The logo will be on the left and the rest of the dialog on the right. hbox_layout = QHBoxLayout(self.d) # Create the logo label. logo_label = QLabel() # Set the logo label pixmap. logo_label.setPixmap(read_QPixmap_from_bytes(self.icon_bytes)) # Align the logo label to the top left. logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft) # Create a VBox layout for the main contents of the dialog. vbox_layout = QVBoxLayout() # Populate the HBox layout with spacing between the two columns. hbox_layout.addWidget(logo_label) hbox_layout.addSpacing(16) hbox_layout.addLayout(vbox_layout) # Create the labels. create_or_load_noise_file_label = QLabel(_("To encrypt a secret, you must first create or load a noise file.")) instructions_label = QLabel(_("Click the button above or type an existing revealer code in the box below.")) # Allow users to select text in the labels. create_or_load_noise_file_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) instructions_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) # Create the buttons. create_button = QPushButton(_("Create a new Revealer noise file")) self.next_button = QPushButton(_("Next"), self.d) # Calculate the desired width of the create button create_button_width = create_button.fontMetrics().boundingRect(create_button.text()).width() + 40 # Set the create button width. create_button.setMaximumWidth(create_button_width) # Set the create button to be the default. create_button.setDefault(True) # Initially disable the next button. self.next_button.setEnabled(False) # Define the create noise file function. def create_noise_file(): try: self.make_digital(self.d) except Exception: self.logger.exception('') else: self.cypherseed_dialog(window) # Handle clicks on the buttons. create_button.clicked.connect(create_noise_file) self.next_button.clicked.connect(self.d.close) self.next_button.clicked.connect(partial(self.cypherseed_dialog, window)) # Create the noise scan QR text edit. self.noise_scan_qr_textedit = ScanQRTextEdit(config=self.config) # Make tabs change focus from the text edit instead of inserting a tab into the field. self.noise_scan_qr_textedit.setTabChangesFocus(True) # Update the UI when the text changes. self.noise_scan_qr_textedit.textChanged.connect(self.on_edit) # Populate the VBox layout. vbox_layout.addWidget(create_or_load_noise_file_label) vbox_layout.addWidget(create_button, alignment=Qt.AlignmentFlag.AlignCenter) vbox_layout.addWidget(instructions_label) vbox_layout.addWidget(self.noise_scan_qr_textedit) vbox_layout.addLayout(Buttons(self.next_button)) # Add stretches to the end of the layouts to prevent the contents from spreading when the dialog is enlarged. hbox_layout.addStretch(1) vbox_layout.addStretch(1) return bool(self.d.exec()) def get_noise(self): # Get the text from the scan QR text edit. text = self.noise_scan_qr_textedit.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)) 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 your backups.")]), rich_text=True) dialog.close() def ext_warning(self, dialog): dialog.show_message(''.join(["",_("Warning"), ": ", _("your seed extension will not be included in the encrypted backup.")]), rich_text=True) 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(), '']), rich_text=True) def customtxt_limits(self): txt = self.custom_secret_scan_qr_textedit.text() self.custom_secret_character_count_label.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})") # Hide the custom secret maximum characters warning label. self.custom_secret_maximum_characters_warning_label.setVisible(False) # Update the status of the encrypt custom secret button. self.encrypt_custom_secret_button.setEnabled(len(txt)>0) # Check to make sure the length of the text has not exceeded the limit. if len(txt) > self.MAX_PLAINTEXT_LEN: # Truncate the text to the maximum limit. self.custom_secret_scan_qr_textedit.setPlainText(txt[:self.MAX_PLAINTEXT_LEN]) # Get the text cursor. textCursor = self.custom_secret_scan_qr_textedit.textCursor() # Move the cursor position to the end (setting the text above automatically moves the cursor to the beginning, which is undesirable) textCursor.movePosition(textCursor.MoveOperation.End) # Set the text cursor with the corrected position. self.custom_secret_scan_qr_textedit.setTextCursor(textCursor) # Display the custom secret maximum characters warning label. self.custom_secret_maximum_characters_warning_label.setVisible(True) def t(self): self.txt = self.custom_secret_scan_qr_textedit.text() self.seed_img(is_seed=False) def warn_old_revealer(self): if self.versioned_seed.version == '0': link = "https://revealer.cc/revealer-warning-and-upgrade/" self.d.show_warning(("{warning}: {ver0}
" "{url}
" "{risk}") .format(warning=_("Warning"), ver0=_("Revealers starting with 0 are not secure due to a vulnerability."), url=_("More info at: {}").format(f'{link}'), risk=_("Proceed at your own risk.")), rich_text=True) def cypherseed_dialog(self, window): self.warn_old_revealer() d = WindowModalDialog(window, "Revealer Visual Cryptography Plugin - Encryption Data") d.setContentsMargins(11, 11, 1, 1) self.c_dialog = d # Create an HBox layout. The logo will be on the left and the rest of the dialog on the right. hbox_layout = QHBoxLayout(d) # Create the logo label. logo_label = QLabel() # Set the logo label pixmap. logo_label.setPixmap(read_QPixmap_from_bytes(self.icon_bytes)) # Align the logo label to the top left. logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft) # Create a VBox layout for the main contents of the dialog. vbox_layout = QVBoxLayout() # Populate the HBox layout. hbox_layout.addWidget(logo_label) hbox_layout.addSpacing(16) hbox_layout.addLayout(vbox_layout) # Create the labels. ready_to_encrypt_label = QLabel(_("Ready to encrypt for revealer {}.").format(self.versioned_seed.version+'_'+self.versioned_seed.checksum)) instructions_label = QLabel(_("Click the button above to encrypt the seed or type a custom alphanumerical secret below.")) self.custom_secret_character_count_label = QLabel(f"(0/{self.MAX_PLAINTEXT_LEN})") self.custom_secret_maximum_characters_warning_label = QLabel("" + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN) +"") one_time_pad_warning_label = QLabel("" + _("Warning ") + ": " + _("each Revealer is a one-time-pad, use it for a single secret.")) # Allow users to select text in the labels. ready_to_encrypt_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) instructions_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) self.custom_secret_character_count_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) one_time_pad_warning_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) # Align the custom secret character count label to the right. self.custom_secret_character_count_label.setAlignment(Qt.AlignmentFlag.AlignRight) # Initially hide the custom secret character count label. self.custom_secret_maximum_characters_warning_label.setVisible(False) # Create the buttons. encrypt_seed_button = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name)) self.encrypt_custom_secret_button = QPushButton(_("Encrypt custom secret")) # Calculate the desired width of the buttons. encrypt_seed_button_width = encrypt_seed_button.fontMetrics().boundingRect(encrypt_seed_button.text()).width() + 40 encrypt_custom_secret_button_width = self.encrypt_custom_secret_button.fontMetrics().boundingRect(self.encrypt_custom_secret_button.text()).width() + 40 # Set the button widths. encrypt_seed_button.setMaximumWidth(encrypt_seed_button_width) self.encrypt_custom_secret_button.setMaximumWidth(encrypt_custom_secret_button_width) # Set the encrypt seed button to be the default. encrypt_seed_button.setDefault(True) # Initially disable the encrypt custom secret button. self.encrypt_custom_secret_button.setEnabled(False) # Handle clicks on the buttons. encrypt_seed_button.clicked.connect(partial(self.seed_img, True)) self.encrypt_custom_secret_button.clicked.connect(self.t) # Create the custom secret scan QR text edit. self.custom_secret_scan_qr_textedit = ScanQRTextEdit(config=self.config) # Make tabs change focus from the text edit instead of inserting a tab into the field. self.custom_secret_scan_qr_textedit.setTabChangesFocus(True) # Update the UI when the custom secret text changes. self.custom_secret_scan_qr_textedit.textChanged.connect(self.customtxt_limits) # Populate the VBox layout. vbox_layout.addWidget(ready_to_encrypt_label) vbox_layout.addWidget(encrypt_seed_button, alignment=Qt.AlignmentFlag.AlignCenter) vbox_layout.addWidget(instructions_label) vbox_layout.addWidget(self.custom_secret_scan_qr_textedit) vbox_layout.addWidget(self.custom_secret_character_count_label) vbox_layout.addWidget(self.custom_secret_maximum_characters_warning_label) vbox_layout.addWidget(self.encrypt_custom_secret_button, alignment=Qt.AlignmentFlag.AlignCenter) vbox_layout.addSpacing(40) vbox_layout.addWidget(one_time_pad_warning_label) vbox_layout.addLayout(Buttons(CloseButton(d))) # Add stretches to the end of the layouts to prevent the contents from spreading when the dialog is enlarged. hbox_layout.addStretch(1) vbox_layout.addStretch(1) 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.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.ImageConversionFlag.MonoOnly) bitmap.fill(Qt.GlobalColor.white) painter = QPainter() painter.begin(bitmap) 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 3', fontsize, QFont.Weight.Bold) font.setLetterSpacing(QFont.SpacingType.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.AlignmentFlag.AlignHCenter, ' '.join(map(str, temp_seed))) del seed_array[:nwords] painter.end() img = bitmap.toImage() if not self.rawnoise: 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: self.versioned_seed = self.gen_random_versioned_seed() assert self.versioned_seed w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format.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() def make_calnoise(self): random.seed(self.calibration_noise) w, h = self.SIZE rawnoise = QImage(w, h, QImage.Format.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.AspectRatioMode.KeepAspectRatio) revealer = self.overlay_marks(revealer) self.filename_prefix = 'revealer_' revealer.save(self.get_path_to_revealer_file('.png')) self.toPdf(QImage(revealer)) def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True): img = img.convertToFormat(QImage.Format.Format_Mono) p = QPainter() p.begin(img) p.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceXorDestination) #xor p.drawImage(0, 0, rawnoise) p.end() cypherseed = self.pixelcode_2x2(img) cypherseed = QBitmap.fromImage(cypherseed) cypherseed = cypherseed.scaled(self.f_size, Qt.AspectRatioMode.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)) 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.Format_Mono) bitmap = QBitmap.fromImage(img, Qt.ImageConversionFlag.MonoOnly) bitmap.fill(Qt.GlobalColor.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.setPageSize(QPageSize(QSizeF(210, 297), QPageSize.Unit.Millimeter)) printer.setResolution(600) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPageLayout.Unit.Millimeter) painter = QPainter() painter.begin(printer) delta_h = round(image.width()/self.abstand_v) delta_v = round(image.height()/self.abstand_h) size_h = round(2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))//2)) size_v = round(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.GlobalColor.black, 1)) painter.drawPath(wpath) painter.end() def calibration_pdf(self, image): printer = QPrinter() printer.setPageSize(QPageSize(QSizeF(210, 297), QPageSize.Unit.Millimeter)) printer.setResolution(600) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFileName(self.get_path_to_calibration_file()) printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPageLayout.Unit.Millimeter) painter = QPainter() painter.begin(printer) painter.drawImage(553,533, image) font = QFont('Source Sans 3', 10, QFont.Weight.Bold) painter.setFont(font) painter.drawText(254,277, _("Calibration sheet")) font = QFont('Source Sans 3', 7, QFont.Weight.Bold) painter.setFont(font) painter.drawText(600,2077, _("Instructions:")) font = QFont("", 7, QFont.Weight.Normal) painter.setFont(font) painter.drawText(700, 2177, _("1. Place this paper on a flat and well illuminated 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.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.GlobalColor.white base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format.Format_ARGB32) base_img.fill(border_color) img = QImage(img) painter = QPainter() painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) 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.GlobalColor.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.GlobalColor.black, border_thick) pen.setJoinStyle(Qt.PenJoinStyle.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.GlobalColor.black, 1) painter.setPen(pen) painter.drawPath(Bpath) pen = QPen(Qt.GlobalColor.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 font = QFont("DejaVu Sans Mono", f_size-11, QFont.Weight.Bold) font.setPixelSize(35) painter.setFont(font) if not calibration_sheet: if is_cseed: #its a secret painter.setPen(QPen(Qt.GlobalColor.black, 1, Qt.PenStyle.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(internal_plugin_icon_path(self.name, 'electrumb.png')).scaledToWidth(round(2.1*total_distance_h), Qt.TransformationMode.SmoothTransformation)) painter.setPen(QPen(Qt.GlobalColor.white, border_thick*8)) painter.drawLine(int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2), int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2), int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2 - 77), int(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.AlignmentFlag.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.GlobalColor.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(internal_plugin_icon_path(self.name, 'revealer_c.png')).scaledToWidth(round(1.3*(total_distance_h))) painter.drawImage(int(total_distance_h+border_thick), int(total_distance_h+border_thick), logo) #frame around logo painter.setPen(QPen(Qt.GlobalColor.black, border_thick)) painter.drawLine(int(total_distance_h+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2)), int(total_distance_h+logo.width()+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2))) painter.drawLine(int(logo.width()+total_distance_h+3*(border_thick/2)), int(total_distance_h+(border_thick)), int(total_distance_h+logo.width()+3*(border_thick/2)), int(total_distance_h+logo.height()+(border_thick))) #frame around code/qr qr_size = 179 painter.drawLine(int((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size), int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2), int((base_img.width()//2+(total_distance_h/2)-border_thick-(border_thick*8)//2)-qr_size), int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2)) painter.drawLine(int((base_img.width()//2+(total_distance_h/2)-border_thick-(border_thick*8)//2)-qr_size), int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2), int(base_img.width()//2 + (total_distance_h/2)-border_thick-(border_thick*8)//2-qr_size), int((base_img.height()-((total_distance_h)))-(border_thick/2)-2)) painter.setPen(QPen(Qt.GlobalColor.white, border_thick * 8)) painter.drawLine( int(base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2), int((base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2), int(base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size), int((base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2)) painter.setPen(QColor(0,0,0,255)) painter.drawText(QRect(int(((base_img.width()/2) +21)-qr_size), int(base_img.height()-107), int(base_img.width()-total_distance_h - border_thick -93), int(base_img.height()-total_distance_h - border_thick)), Qt.AlignmentFlag.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.AlignmentFlag.AlignRight, self.versioned_seed.checksum) # draw qr code qr_qt = 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.GlobalColor.black, 4)) painter.drawLine( int(base_img.width()-65-qr_size), int(base_img.height()-65-qr_size), int(base_img.width() - 65 - qr_size), int((base_img.height() - total_distance_h) - (border_thick * 8) - (border_thick / 2) - 4), ) painter.drawLine( int(base_img.width()-65-qr_size), int(base_img.height()-65-qr_size), int(base_img.width() - 65), int(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.Format_ARGB32) cal_img.fill(Qt.GlobalColor.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.GlobalColor.black, 1, Qt.PenStyle.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.GlobalColor.black, 2, Qt.PenStyle.DashDotDotLine) cal_painter.setPen(pen) n=15 cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Weight.Bold)) for x in range(-n,n): #lines on bottom (vertical calibration) cal_painter.drawLine(int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)-13), int(x+2+base_img.height()-(dist_v)), int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)+13), int(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(int((((base_img.width())/(n*2)) *(x)) + (base_img.width()//2)-num_pos), int(50+base_img.height()-(dist_v)), str(x)) #lines on the right (horizontal calibrations) cal_painter.drawLine(int(x+2+(base_img.width()-(dist_h))), int(((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()//2)-13), int(x+2+(base_img.width()-(dist_h))), int(((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()//2)+13)) cal_painter.drawText(int(30+(base_img.width()-(dist_h))), int(((base_img.height()/(2*n)) *(x))+ (base_img.height()//2)+13), str(x)) cal_painter.end() base_img = cal_img 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)