#include "ItemCommon.h" #include "DlgQrcodeFormat.h" #include "DlgBarcodeFormat.h" #include #include "Matrix/dm200.h" #include "BarCode/barcode.h" #include #include /** * @brief 将libqrencode的QRcode结构渲染成QImage * * 避免依赖文件系统或PostScript输出,直接在内存中生成可用于UI显示或打印的图像。 * 使用2D像素数组而非向量,支持任意缩放因子。 */ static QImage renderQRCode(const QRcode *qrcode, int moduleSize = 4, QRgb foreground = 0xff000000, QRgb background = 0xffffffff) { if (!qrcode || qrcode->width <= 0) { return QImage(); } int margin = 1; int imageSize = (qrcode->width + margin * 2) * moduleSize; QImage image(imageSize, imageSize, QImage::Format_ARGB32); image.fill(background); const unsigned char *data = qrcode->data; for (int y = 0; y < qrcode->width; ++y) { for (int x = 0; x < qrcode->width; ++x) { bool moduleOn = (data[y * qrcode->width + x] & 0x01) != 0; QRgb color = moduleOn ? foreground : background; int px = (margin + x) * moduleSize; int py = (margin + y) * moduleSize; for (int dy = 0; dy < moduleSize; ++dy) { for (int dx = 0; dx < moduleSize; ++dx) { image.setPixel(px + dx, py + dy, color); } } } } return image; } /** * @brief 将DataMatrix位图网格渲染成QImage * * iec16022ecc200返回原始位数组,需转换为可视化图像以供UI或打印使用。 * 与renderQRCode采用相同的像素化策略,确保两种编码方式的输出一致性。 */ static QImage renderDataMatrix(const unsigned char *grid, int cols, int rows, int moduleSize = 1, QRgb foreground = 0xff000000, QRgb background = 0xffffffff) { if (!grid || cols <= 0 || rows <= 0) { return QImage(); } int width = cols * moduleSize; int height = rows * moduleSize; QImage image(width, height, QImage::Format_ARGB32); image.fill(background); for (int y = 0; y < rows; ++y) { for (int x = 0; x < cols; ++x) { bool bit = (grid[y * cols + x] & 0x01) != 0; QRgb color = bit ? foreground : background; int px = x * moduleSize; int py = y * moduleSize; for (int dy = 0; dy < moduleSize; ++dy) { for (int dx = 0; dx < moduleSize; ++dx) { image.setPixel(px + dx, py + dy, color); } } } } return image; } static QRecLevel qrCodeLevelFromEnum(QRCodeLevel level) { switch (level) { case QR_CODE_LEVEL_M: return QR_ECLEVEL_M; case QR_CODE_LEVEL_Q: return QR_ECLEVEL_Q; case QR_CODE_LEVEL_H: return QR_ECLEVEL_H; default: return QR_ECLEVEL_L; } } /** * @brief 根据格式配置生成二维码或DataMatrix图像 * * 支持两种编码方式: * - DataMatrix:适合小容量数据和有限空间环境(如工业标签) * - QR Code:高容量、高可靠性,广泛应用于商业和物流 * * 直接在内存中渲染,避免对dlgQrCode或外部库的依赖。 * 颜色反转机制(INK_TYPE_WHITE)用于支持黑底白码的特殊打印场景。 */ static QImage generateQRCodeImage(const QString &content, const DlgQrcodeFormatVM &vm) { if (content.isEmpty()) { return QImage(); } QRecLevel level = qrCodeLevelFromEnum(vm.level); // 适配黑底白码场景(逆色):打印机某些模式下需要反转前景/背景色 QRgb foreground = vm.inkType == INK_TYPE_WHITE ? 0x00ffffff : 0xff000000; QRgb background = vm.inkType == INK_TYPE_WHITE ? 0xff000000 : 0x00ffffff; QByteArray bytes = content.toUtf8(); // DataMatrix:调用者指定尺寸约束(sizeType),库返回实际分配的网格 if (vm.type == QR_CODE_TYPE_DATA_MATRIX) { short cols = 0; short rows = 0; short sizeType = static_cast(vm.size); // iec16022ecc200会根据sizeType调整输出尺寸,支持灵活的条码宽高比 unsigned char *grid = iec16022ecc200(&cols, &rows, reinterpret_cast(bytes.data()), &sizeType); if (!grid || cols <= 0 || rows <= 0) { if (grid) { free(grid); } return QImage(); } QImage image = renderDataMatrix(grid, cols, rows, 1, foreground, background); free(grid); return image; } // QR Code:版本0表示自动选择版本(1-40),确保内容总是被编码 QRcode *qrcode = QRcode_encodeString(bytes.constData(), 0, level, QR_MODE_8, 1); if (!qrcode) { return QImage(); } QImage image = renderQRCode(qrcode, 1, foreground, background); QRcode_free(qrcode); return image; } /** * @brief 将Barcode_Item的编码数据(partial)渲染成QImage * * partial字段存储压缩的条形码宽度数据(高4位=空白宽度,低3位=黑条宽度), * 需展开为像素数组。barWidth参数控制每个模块的物理宽度(用于放大)。 * * 这种直接渲染方式避免了PostScript生成和文件I/O的开销。 */ static QImage renderBarcodeImage(const Barcode_Item *bc, int barHeight = 12, int barWidth = 1, QRgb foreground = 0xff000000, QRgb background = 0xffffffff) { if (!bc || !bc->partial) { return QImage(); } // 约束:barHeight和barWidth必须为正值。默认值支持小尺寸标签的视觉清晰度 if (barHeight <= 0) { barHeight = 12; } if (barWidth <= 0) { barWidth = 1; } // xoffset=8提供左边距(4像素),用于对齐打印布局和改善美观度 int xoffset = 8; int len = static_cast(strlen(bc->partial)); // 计算条形码总宽度:每字节的低4位表示条纹宽度单位数之和 int bcWidth1 = 0; for (int i = 0; i < len; ++i) { bcWidth1 += (bc->partial[i] & 0x0f); } // 最终宽度 = 左边距 + 条纹单位总数 * 每单位像素数 int imageWidth = (xoffset >> 1) + bcWidth1 * barWidth; if (imageWidth <= 0 || barHeight <= 0) { return QImage(); } QImage image(imageWidth, barHeight, QImage::Format_ARGB32); image.fill(background); for (int y = 0; y < barHeight; ++y) { int xpos = xoffset >> 1; for (int n = 0; n < len; ++n) { // partial数据编码方案:奇数索引=黑条,偶数索引=空白 // 低3位存储实际宽度,需乘以barWidth得到像素宽度 int run = (bc->partial[n] & 0x07) * barWidth; if (n & 1) { // 奇数索引:绘制黑条(前景色) for (int k = 0; k < run; ++k) { if (xpos >= 0 && xpos < imageWidth) { image.setPixel(xpos, y, foreground); } ++xpos; } } else { // 偶数索引:跳过空白区域(已由背景色填充) xpos += run; } } } return image; } static int barcodeTypeFromString(const QString &type) { QString t = type.trimmed().toUpper(); if (t == "BARCODE_128") return BARCODE_128; if (t == "BARCODE_93") return BARCODE_93; if (t == "BARCODE_ISBN") return BARCODE_ISBN; if (t == "BARCODE_EAN") return BARCODE_EAN; if (t == "BARCODE_UPC") return BARCODE_UPC; if (t == "BARCODE_39") return BARCODE_39; if (t == "BARCODE_CBR") return BARCODE_CBR; if (t == "BARCODE_PLS") return BARCODE_PLS; if (t == "BARCODE_MSI") return BARCODE_MSI; return BARCODE_128; } static QString barcodeTypeNameFromIndex(int index) { switch (index) { case 0: return QLatin1String("BARCODE_128"); case 1: return QLatin1String("BARCODE_93"); case 2: return QLatin1String("BARCODE_ISBN"); case 3: return QLatin1String("BARCODE_EAN"); case 4: return QLatin1String("BARCODE_UPC"); case 5: return QLatin1String("BARCODE_39"); case 6: return QLatin1String("BARCODE_CBR"); case 7: return QLatin1String("BARCODE_PLS"); case 8: return QLatin1String("BARCODE_MSI"); default: return QLatin1String("BARCODE_128"); } } static QImage generateQRCodeImage(const QString &content, int qrType) { DlgQrcodeFormatVM vm; vm.type = qrType == 0 ? QR_CODE_TYPE_DATA_MATRIX : QR_CODE_TYPE_QRCODE; return generateQRCodeImage(content, vm); } /** * @brief 根据格式配置生成条形码图像 * * 支持多种条形码标准(128、93、EAN等),直接渲染到QImage,无需文件操作。 * 避免对QBarCode类的依赖,实现自包含的编码流程。 */ static QImage generateBarCodeImage(const QString &content, const DlgBarcodeFormatVM &vm) { if (content.isEmpty()) { return QImage(); } // UTF-8编码确保国际字符被正确处理,与打印驱动兼容 QByteArray bytes = content.toUtf8(); // Barcode_Create内部用strdup复制字符串,无需手动生命周期管理 struct Barcode_Item *bc = Barcode_Create(bytes.data()); if (!bc) { return QImage(); } int encodingFlag = barcodeTypeFromString(vm.type); // BARCODE_OUT_PS: 预期PostScript格式输出(internal flag),BARCODE_OUT_NOHEADERS: 不生成PS头 int flags = encodingFlag | BARCODE_OUT_PS | BARCODE_OUT_NOHEADERS; // 传递缩放倍数到库,影响条纹宽度和字体大小的计算(510/60为库内部单位转换) int width = qMax(1, vm.scale) * 510; int height = qMax(1, vm.scale) * 60; if (Barcode_Position(bc, width, height, 0, 0, 0.0) < 0 || Barcode_Encode(bc, flags) < 0) { Barcode_Delete(bc); return QImage(); } // 黑底白码支持(INK_TYPE_WHITE表示反向模式) QRgb foreground = vm.inkType == INK_TYPE_WHITE ? 0x00ffffff : 0xff000000; QRgb background = vm.inkType == INK_TYPE_WHITE ? 0xff000000 : 0x00ffffff; QImage image = renderBarcodeImage(bc, vm.height, vm.width, foreground, background); Barcode_Delete(bc); // 可选文本叠加:某些应用场景(如物流标签)需要条码旁显示人类可读的内容 if (vm.txtVisible) { QPainter painter(&image); painter.setPen(foreground); QFont font = painter.font(); // 字体大小自适应:防止文本溢出图像,同时保持可读性 font.setPixelSize(qMin(12, image.height())); painter.setFont(font); painter.drawText(image.rect().adjusted(1, 1, -1, -1), Qt::AlignBottom | Qt::AlignHCenter, content); } // 最终放大:scale>1时对整个图像进行像素放大(用于低DPI打印环保模式) if (vm.scale > 1) { image = image.scaled(image.width() * vm.scale, image.height() * vm.scale, Qt::KeepAspectRatio, Qt::FastTransformation); } return image; } static QImage generateBarCodeImage(const QString &content, int barCodeType) { DlgBarcodeFormatVM vm; vm.type = barcodeTypeNameFromIndex(barCodeType); return generateBarCodeImage(content, vm); } QString generateTxtFromDateItemModel(const DateItemModel &model, const QDateTime &time) { QString result = model.datetimePattern; // 应用偏移量 QDateTime applyDateTime = time; if(model.monthsOffset != 0) { applyDateTime = time.addMonths(model.monthsOffset); } else if(model.daysOffset != 0) { applyDateTime = time.addDays(model.daysOffset); } // 使用正则表达式匹配并替换各种格式化模式 // 匹配四位数年份 yyyy int i = result.indexOf("yyyy"); if(i != -1) { int year = applyDateTime.date().year(); QString yearStr = model.yearsMap.contains(year) ? model.yearsMap[year] : QString::number(year); result.replace(i, 4, yearStr); } // 匹配两位数年份 yy i = result.indexOf("YY"); if(i != -1) { QString yearStr = QString::number(applyDateTime.date().year()).right(2); result.replace(i, 2, yearStr); } // 匹配两位数月份 MM i = result.indexOf("MM"); if(i != -1) { result.replace(i, 2, model.monthsMap[applyDateTime.date().month() - 1]); } // 匹配两位数日期 dd i = result.indexOf("dd"); if(i != -1) { result.replace(i, 2, model.daysMap[applyDateTime.date().day() - 1]); } // 匹配两位数小时 HH i = result.indexOf("HH"); if(i != -1) { result.replace(i, 2, model.hoursMap[applyDateTime.time().hour()]); } // 匹配两位数分钟 mm QRegExp minutePattern("mm"); if (result.contains(minutePattern)) { int minute = applyDateTime.time().minute(); result.replace(minutePattern, QString::number(minute).rightJustified(2, '0')); } // 匹配两位数秒钟 ss QRegExp secondPattern("ss"); if (result.contains(secondPattern)) { int second = applyDateTime.time().second(); result.replace(secondPattern, QString::number(second).rightJustified(2, '0')); } // 格式化周几 w i = result.indexOf("w"); if (i != -1) { result.replace(i, 1, model.weeksMap[applyDateTime.date().dayOfWeek() - 1]); } // 格式化年中周数 c QRegExp weekNumberPattern("c"); if (result.contains(weekNumberPattern)) { result.replace(weekNumberPattern, QString::number(applyDateTime.date().weekNumber())); } return result; } QString generateTxtFromBarcodeViewModel(const BarcodeViewModel &vm, const std::array counterValues, const QDateTime &time) { QString txt = vm.contentPattern; auto pos = vm.contentPattern.indexOf("{t}"); if(pos != -1) { txt.replace(pos, 3, generateTxtFromDateItemModel(vm.dateViewModel, time)); } pos = vm.contentPattern.indexOf("{c}"); if(pos != -1) { auto cntValue = vm.counterViewModel.counterType == COUNTER_TYPE_1 ? counterValues[0] : counterValues[1]; auto valueStr = QString("%1").arg(cntValue, 10, QChar('0'));; valueStr = valueStr.right(vm.counterViewModel.digitalNum); txt.replace(pos, 3, valueStr); } return txt; } /** * @brief 统一的条码/二维码生成入口 * * @details * 目的:将二维码和条形码编码逻辑独立出来,避免对UI对话框类(dlgQrCode、QBarCode)的依赖, * 实现在数据层直接进行编码,支持后端打印任务、异步处理等场景。 * * 多态format参数设计的理由: * - 支持便利调用(int):向后兼容现有代码路径,使用库默认配置 * - 支持细粒度控制(DlgQrcodeFormatVM/DlgBarcodeFormatVM):UI配置直通生成函数 * - 避免序列化开销:QVariant作为类型安全的通用容器 * * @param content 已格式化的文本内容(由调用者负责模板替换如{t}、{c}) * @param type 条码类型(BARCODE_TYPE_QRCODE 或 BARCODE_TYPE_BARCODE) * @param format 配置参数容器,支持以下类型: * - BARCODE_TYPE_QRCODE: DlgQrcodeFormatVM(完整配置)或 int(0=DataMatrix, 1=QRCode,使用默认值) * - BARCODE_TYPE_BARCODE: DlgBarcodeFormatVM(完整配置)或 int(条形码类型索引,使用默认值) * @return QImage 生成的图像;失败返回空QImage */ QImage generateBarcodeImage(const QString &content, BarcodeType type, const QVariant &format) { if (content.isEmpty()) { return QImage(); } if (type == BARCODE_TYPE_QRCODE) { // 优先级:完整VM配置 > 简化int配置 > 默认配置 // 原因:支持从UI直接传递结构体,同时保留向后兼容的快速路径 if (format.canConvert()) { DlgQrcodeFormatVM vm = format.value(); return generateQRCodeImage(content, vm); } if (format.isValid() && format.canConvert()) { return generateQRCodeImage(content, format.toInt()); } return generateQRCodeImage(content, static_cast(QR_CODE_TYPE_QRCODE)); } if (type == BARCODE_TYPE_BARCODE) { if (format.canConvert()) { DlgBarcodeFormatVM vm = format.value(); return generateBarCodeImage(content, vm); } if (format.isValid() && format.canConvert()) { return generateBarCodeImage(content, format.toInt()); } return generateBarCodeImage(content, DlgBarcodeFormatVM()); } return QImage(); } /** * @brief 将 QImage 中所有等于 bg 的边缘像素裁掉,仅保留有效内容的最小外接矩形 * * 复刻自 PicTextItem 的 clipImage:用四个方向扫描确定 left/top/right/bottom 边界, * 然后调用 QImage::copy 输出裁剪后的图像。这样字符串如 "aaa" 不会保留 "j" 才会用到的下行空白, * 节省后续打印或合成时的画布空间。 */ static QImage clipTxtImage(const QImage &img, QRgb bg) { if (img.isNull()) { return img; } QRect clipRect; for (int x = 0; x < img.width(); ++x) { for (int y = 0; y < img.height(); ++y) { if (img.pixel(x, y) != bg) { clipRect.setLeft(x); goto top; } } } // 整张图都是背景色,直接返回原图(无可裁剪内容) return img; top: for (int y = 0; y < img.height(); ++y) { for (int x = 0; x < img.width(); ++x) { if (img.pixel(x, y) != bg) { clipRect.setTop(y); goto right; } } } right: for (int x = img.width() - 1; x >= 0; --x) { for (int y = 0; y < img.height(); ++y) { if (img.pixel(x, y) != bg) { clipRect.setRight(x + 1); goto bottom; } } } bottom: for (int y = img.height() - 1; y >= 0; --y) { for (int x = 0; x < img.width(); ++x) { if (img.pixel(x, y) != bg) { clipRect.setBottom(y + 1); goto end; } } } end: return img.copy(clipRect); } QImage txtToImage(const QString &txt, const QFont &font, const QColor &fgColor, const QColor &bgColor) { if (txt.isEmpty()) { return QImage(); } // 用 QFontMetrics 计算给定字体下文本的水平推进宽度和单行高度, // 这样初始画布刚好能容纳整段文本,避免后续裁剪时出现截断 QFontMetrics fm(font); int width = fm.horizontalAdvance(txt); int height = fm.height(); if (width <= 0 || height <= 0) { return QImage(); } QImage img(width, height, QImage::Format_ARGB32); QRgb bgRgb = bgColor.rgba(); img.fill(bgRgb); QPainter painter; painter.begin(&img); painter.setFont(font); painter.setPen(fgColor); painter.drawText(img.rect(), txt); painter.end(); // 按背景色裁剪,使输出图像紧贴文字像素的最小包围盒(详见函数注释) return clipTxtImage(img, bgRgb); }