#include "mainwindow.h" #include "../common/diality_secure_code.h" #include "../common/diality_keys.h" #include "../common/diality_payload.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QImage bitMatrixToImage(const ZXing::BitMatrix& bm, int scale, int quietModules) { const int w = bm.width(); const int h = bm.height(); const int imgW = (w + 2 * quietModules) * scale; const int imgH = (h + 2 * quietModules) * scale; QImage img(imgW, imgH, QImage::Format_Grayscale8); img.fill(255); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { if (!bm.get(x, y)) continue; const int px = (x + quietModules) * scale; const int py = (y + quietModules) * scale; for (int dy = 0; dy < scale; ++dy) { uchar* row = img.scanLine(py + dy); for (int dx = 0; dx < scale; ++dx) { row[px + dx] = 0; } } } } return img; } static int computeScaleToFitMm(int modules, int quietModules, int printerDpi, double targetMm) { if (modules <= 0 || printerDpi <= 0 || targetMm <= 0.0) return 1; const double targetInches = targetMm / 25.4; const double targetPixels = targetInches * double(printerDpi); const int totalModules = modules + 2 * quietModules; const int scale = int(targetPixels / double(totalModules)); return (scale < 1) ? 1 : scale; } MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { diality::cryptoInit(); auto* central = new QWidget(this); auto* root = new QVBoxLayout(central); auto* keysRow = new QHBoxLayout(); m_keysStatus = new QLabel("Keys: not loaded", central); m_reloadKeysBtn = new QPushButton("Reload keys.json", central); keysRow->addWidget(m_keysStatus, 1); keysRow->addWidget(m_reloadKeysBtn, 0); m_fieldsEdit = new QLineEdit(central); m_fieldsEdit->setPlaceholderText("Enter: PID=CARTRIDGE-123|LOT=A24X7|EXP=20271231|SN=0000009876543210"); auto* settingsRow = new QHBoxLayout(); m_dpiEdit = new QLineEdit(central); m_dpiEdit->setPlaceholderText("Printer DPI (e.g., 300)"); m_dpiEdit->setText("300"); m_targetMmEdit = new QLineEdit(central); m_targetMmEdit->setPlaceholderText("Max size mm (e.g., 14.4)"); m_targetMmEdit->setText("14.4"); m_compressCheck = new QCheckBox("Compress if beneficial", central); m_compressCheck->setChecked(true); settingsRow->addWidget(new QLabel("DPI:", central)); settingsRow->addWidget(m_dpiEdit); settingsRow->addSpacing(12); settingsRow->addWidget(new QLabel("Max mm:", central)); settingsRow->addWidget(m_targetMmEdit); settingsRow->addSpacing(12); settingsRow->addWidget(m_compressCheck, 1); m_sizeStatus = new QLabel("Size: (not generated)", central); auto* btnRow = new QHBoxLayout(); auto* genBtn = new QPushButton("Generate + Render", central); m_saveBtn = new QPushButton("Save PNG...", central); m_saveBtn->setEnabled(false); btnRow->addWidget(genBtn); btnRow->addWidget(m_saveBtn); m_outText = new QTextEdit(central); m_outText->setReadOnly(true); m_outText->setPlaceholderText("Scanner text will appear here"); m_imageLabel = new QLabel(central); m_imageLabel->setMinimumSize(240, 240); m_imageLabel->setScaledContents(false); root->addLayout(keysRow); root->addWidget(m_fieldsEdit); root->addLayout(settingsRow); root->addWidget(m_sizeStatus); root->addLayout(btnRow); root->addWidget(m_outText); root->addWidget(m_imageLabel); setCentralWidget(central); connect(m_reloadKeysBtn, &QPushButton::clicked, this, &MainWindow::onReloadKeysClicked); connect(genBtn, &QPushButton::clicked, this, &MainWindow::onGenerateClicked); connect(m_saveBtn, &QPushButton::clicked, this, &MainWindow::onSaveClicked); onReloadKeysClicked(); } void MainWindow::onReloadKeysClicked() { QString err; QString path; diality::Keys k; if (!diality::loadPcKeys(&k, &path, &err)) { m_keysLoaded = false; m_keys = diality::Keys{}; m_keysPath.clear(); m_keysStatus->setText("Keys: NOT LOADED (" + err + "). Place keys.json next to the executable."); return; } m_keysLoaded = true; m_keys = k; m_keysPath = path; m_keysStatus->setText("Keys: loaded from " + m_keysPath); } bool MainWindow::ensureKeysLoaded() { if (m_keysLoaded) return true; QMessageBox::warning(this, "Keys", "keys.json is not loaded. Place keys.json next to the executable and click Reload."); return false; } void MainWindow::updateSizeStatus(int modules, int scalePx, int quietModules, double printedMm) { m_sizeStatus->setText(QString("Size: %1x%2 modules, quiet=%3, scale=%4 px/module, printed=%5 mm (target %6 mm)") .arg(modules).arg(modules).arg(quietModules).arg(scalePx) .arg(QString::number(printedMm, 'f', 2)) .arg(m_targetMmEdit->text().trimmed())); } void MainWindow::onGenerateClicked() { if (!ensureKeysLoaded()) return; QString partId; QString lot; QString exp; QString sn; QString perr; if (!diality::parseKeyValueInput(m_fieldsEdit->text(), &partId, &lot, &exp, &sn, &perr)) { QMessageBox::warning(this, "Input", perr); return; } bool compressed = false; QString derr; const bool enableCompression = m_compressCheck->isChecked(); const QByteArray payload = diality::buildTlvPayload(partId, lot, exp, sn, enableCompression, &compressed, &derr); if (payload.isEmpty()) { QMessageBox::critical(this, "Payload", derr.isEmpty() ? "Failed to build payload" : derr); return; } const QString secureText = diality::buildSecureDatamatrixText(payload, m_keys.encKey32, m_keys.signSk32, 0x01); if (secureText.isEmpty()) { QMessageBox::critical(this, "Generate", "Failed to build secure text (crypto error)"); return; } m_outText->setPlainText(secureText + (compressed ? "\n(compressed payload)" : "\n(uncompressed payload)")); ZXing::MultiFormatWriter writer(ZXing::BarcodeFormat::DataMatrix); writer.setMargin(0); const ZXing::BitMatrix bm = writer.encode(secureText.toStdWString(), 0, 0); const int quiet = 2; bool okDpi = false; const int dpi = m_dpiEdit->text().trimmed().toInt(&okDpi); bool okMm = false; const double targetMm = m_targetMmEdit->text().trimmed().toDouble(&okMm); const int modules = bm.width(); const int useDpi = okDpi ? dpi : 300; const double useMm = okMm ? targetMm : 14.4; const int scale = computeScaleToFitMm(modules, quiet, useDpi, useMm); const int totalModules = modules + 2 * quiet; const double printedInches = double(totalModules * scale) / double(useDpi); const double printedMm = printedInches * 25.4; updateSizeStatus(modules, scale, quiet, printedMm); if (printedMm > useMm + 0.01) { QMessageBox::warning(this, "Size", "Auto-fit could not meet the target size at this DPI. " "Try higher DPI or reduce payload."); } if (scale < 2) { QMessageBox::critical(this, "Readability", "Symbol is too dense to be reliably scanned at this size/DPI (scale < 2 px/module).\n\n" "Fix: increase DPI (e.g., 600), increase Max mm, or shorten fields."); return; } if (scale < 3) { QMessageBox::warning(this, "Readability", "Computed module size is very small (scale < 2 px/module). " "Scanner reliability may suffer. Consider higher DPI or smaller payload."); } m_lastImage = bitMatrixToImage(bm, scale, quiet); m_imageLabel->setPixmap(QPixmap::fromImage(m_lastImage)); m_saveBtn->setEnabled(true); } void MainWindow::onSaveClicked() { if (m_lastImage.isNull()) return; const QString path = QFileDialog::getSaveFileName(this, "Save Data Matrix", "diality_dm.png", "PNG (*.png)"); if (path.isEmpty()) return; QImageWriter w(path, "png"); if (!w.write(m_lastImage)) { QMessageBox::critical(this, "Save", w.errorString()); } }