diff --git a/ChangeLog b/ChangeLog index 73bab8de..43f3d3b5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,17 @@ -Version 2.16.0.9 (dev) not released yet (2025-12-19) +Version 2.16.0.9 (dev) not released yet (2025-12-29) ==================================================== -No changes yet +**Incompatible changes** +------------------------ +- New Qt Backend method `save_as_memfile()` to save file to memory + +Changes +------- +- backend_qt: add new method `save_as_memfile()` to save as + `BARCODE_MEMORY_FILE` and use in GUI for pasting to clipboard instead of + creating temporary file +- CLI: allow "tiff" as filetype (saved as ".tif"); + add `ZINT_TEST`-only "--test" option to do various internal tests Version 2.16.0 (2025-12-19) diff --git a/backend/tests/tools/bwipp_dump.ps.tar.xz b/backend/tests/tools/bwipp_dump.ps.tar.xz index b24d8430..dde9655d 100644 Binary files a/backend/tests/tools/bwipp_dump.ps.tar.xz and b/backend/tests/tools/bwipp_dump.ps.tar.xz differ diff --git a/backend_qt/qzint.cpp b/backend_qt/qzint.cpp index 7f3d9e57..b66b75c7 100644 --- a/backend_qt/qzint.cpp +++ b/backend_qt/qzint.cpp @@ -967,6 +967,33 @@ namespace Zint { return true; } + bool QZint::save_to_memfile(const QString& filename, QByteArray& data) { + if (resetSymbol()) { + m_zintSymbol->output_options |= BARCODE_MEMORY_FILE; + cpy_bytearray_left(m_zintSymbol->outfile, filename.toUtf8(), ARRAY_SIZE(m_zintSymbol->outfile) - 1); + if (m_segs.empty()) { + QByteArray bstr = m_text.toUtf8(); + m_error = ZBarcode_Encode_and_Print(m_zintSymbol, (unsigned char *) bstr.data(), bstr.length(), + m_rotate_angle); + } else { + struct zint_seg segs[maxSegs]; + std::vector bstrs; + int seg_count = convertSegs(segs, bstrs); + m_error = ZBarcode_Encode_Segs_and_Print(m_zintSymbol, segs, seg_count, m_rotate_angle); + } + } + if (m_error >= ZINT_ERROR) { + m_lastError = m_zintSymbol->errtxt; + m_encodedWidth = m_encodedRows = 0; + m_encodedHeight = m_vectorWidth = m_vectorHeight = 0.0f; + emit errored(); + return false; + } + data = QByteArray((const char *) m_zintSymbol->memfile, m_zintSymbol->memfile_size); + + return true; + } + /* Convert `zint_vector_rect->colour` to Qt color */ Qt::GlobalColor QZint::colourToQtColor(int colour) { switch (colour) { diff --git a/backend_qt/qzint.h b/backend_qt/qzint.h index f7e77043..08768061 100644 --- a/backend_qt/qzint.h +++ b/backend_qt/qzint.h @@ -278,10 +278,10 @@ public: bool takesGS1AIData(int symbology = 0) const; - /* Error or warning returned by Zint on `render()` or `save_to_file()` */ + /* Error or warning returned by Zint on `render()`, `save_to_file()` or `save_to_memfile()` */ int getError() const; - /* Error message returned by Zint on `render()` or `save_to_file()` */ + /* Error message returned by Zint on `render()`, `save_to_file()` or `save_to_memfile()` */ const QString& lastError() const; // `symbol->errtxt` /* Whether `lastError()` set */ @@ -291,6 +291,10 @@ public: /* Encode and print barcode to file `filename`. Only sets `getError()` on error, not on warning */ bool save_to_file(const QString& filename); // `ZBarcode_Print()` + /* Encode and print barcode to memory file `filename` (only the extension is used, to determine output format). + Only sets `getError()` on error, not on warning */ + bool save_to_memfile(const QString& filename, QByteArray& data); // `ZBarcode_Print()` + BARCODE_MEMORY_FILE + /* Encode and display barcode in `paintRect` using `painter`. Note: legacy argument `mode` is not used */ void render(QPainter& painter, const QRectF& paintRect, AspectRatioMode mode = IgnoreAspectRatio); diff --git a/frontend/main.c b/frontend/main.c index f5dcf7cc..404724d3 100644 --- a/frontend/main.c +++ b/frontend/main.c @@ -360,11 +360,7 @@ static int validate_float(const char source[], const int allow_neg, float *const return 0; } if (val2 && int_len + fract_len > 7) { - if (val) { - cpy_str(errbuf, 64, "7 significant digits maximum"); - } else { - cpy_str(errbuf, 64, "fractional part must be 7 digits maximum"); - } + cpy_str(errbuf, 64, "7 significant digits maximum"); return 0; } *p_val = val + val2 * fract_muls[fract_len - 1]; @@ -681,14 +677,20 @@ static int supported_filetype(const char *const filetype, const int no_png, int static const char filetypes[][4] = { "bmp", "emf", "eps", "gif", "pcx", "png", "svg", "tif", "txt", }; - char lc_filetype[4]; + char lc_filetype[5]; int i; + const int len = (int) strlen(filetype); if (png_refused) { *png_refused = 0; } /* Disallow != 3, except for "tiff" */ - if (strlen(filetype) != 3 && strcmp(filetype, "tiff") != 0 && strcmp(filetype, "TIFF") != 0) { + if (len != 3) { + if (len == 4) { + ncpy_str(lc_filetype, ARRAY_SIZE(lc_filetype), filetype, 4); + to_lower(lc_filetype); + return strcmp(lc_filetype, "tiff") == 0; + } return 0; } ncpy_str(lc_filetype, ARRAY_SIZE(lc_filetype), filetype, 3); @@ -709,12 +711,10 @@ static int supported_filetype(const char *const filetype, const int no_png, int return 0; } -/* Get file extension, excluding those of 4 or more letters */ +/* Get file extension, excluding those of more than 4 letters */ static char *get_extension(const char *const file) { - char *dot; - - dot = strrchr(file, '.'); - if (dot && strlen(file) - (dot - file) <= 4) { /* Only recognize up to 3 letter extensions */ + char *const dot = strrchr(file, '.'); + if (dot && strlen(file) - (dot - file) <= 5) { /* Only recognize up to 4 letter extensions */ return dot + 1; } return NULL; @@ -756,16 +756,12 @@ static int is_raster(const char *const filetype, const int no_png) { int i; char lc_filetype[4]; - if (filetype == NULL) { + if (filetype == NULL || !supported_filetype(filetype, no_png, NULL)) { return 0; } cpy_str(lc_filetype, ARRAY_SIZE(lc_filetype), filetype); to_lower(lc_filetype); - if (no_png && strcmp(lc_filetype, "png") == 0) { - return 0; - } - for (i = 0; i < ARRAY_SIZE(raster_filetypes); i++) { if (strcmp(lc_filetype, raster_filetypes[i]) == 0) { return 1; @@ -2371,9 +2367,238 @@ int main(int argc, char **argv) { } #ifdef ZINT_TEST + +static void test_validate_int(void) { + /* s/\/\*[ 0-9]*\*\//\=printf("\/\*%3d*\/", line(".") - line("'<")): */ + static const struct { const char *source; int len; int val; int ret; } data[] = { + /* 0*/ { "", -1, 0, 1 }, /* Empty allowed */ + /* 1*/ { "1", -1, 1, 1 }, + /* 2*/ { "123456789", -1, 123456789, 1 }, + /* 3*/ { "1234567890", -1, -1, 0 }, + /* 4*/ { "0", -1, 0, 1 }, + /* 5*/ { "+1", -1, -1, 0 }, + /* 6*/ { "-1", -1, -1, 0 }, + /* 7*/ { "1.2", -1, -1, 0 }, + }; + int i; + for (i = 0; i < ARRAY_SIZE(data); i++) { + int val = -1; + const int ret = validate_int(data[i].source, data[i].len, &val); + if (ret != data[i].ret) { + fprintf(stderr, "%d: ret %d != %d\n", i, ret, data[i].ret); + assert(0); + } + if (val != data[i].val) { + fprintf(stderr, "%d: val %d != %d\n", i, val, data[i].val); + assert(0); + } + } +} + +static void test_validate_float(void) { + /* s/\/\*[ 0-9]*\*\//\=printf("\/\*%3d*\/", line(".") - line("'<")): */ + static const struct { const char *source; int allow_neg; float val; const char* errbuf; int ret; } data[] = { + /* 0*/ { "", 0, 0.0f, "", 1 }, /* Empty allowed */ + /* 1*/ { "1234567", 0, 1234567.0f, "", 1 }, + /* 2*/ { "1234567890", 0, -1.0f, "integer part must be 7 digits maximum", 0 }, + /* 3*/ { "12345678", 0, -1.0f, "integer part must be 7 digits maximum", 0 }, + /* 4*/ { "123+", 0, -1.0f, "integer part must be digits only", 0 }, + /* 5*/ { "+1234567", 0, 1234567.0f, "", 1 }, + /* 6*/ { "-1234567", 0, -1.0f, "negative value not permitted", 0 }, + /* 7*/ { "-1234567", 1, -1234567.0f, "", 1 }, + /* 8*/ { "1234.567", 0, 1234.567f, "", 1 }, + /* 9*/ { "1234567.0", 0, 1234567.0f, "", 1 }, + /* 10*/ { "1234567.00000", 0, 1234567.0f, "", 1 }, + /* 11*/ { "1234567.", 0, 1234567.0f, "", 1 }, + /* 12*/ { "1.234567", 0, 1.234567f, "", 1 }, + /* 13*/ { ".1234567", 0, 0.1234567f, "", 1 }, + /* 14*/ { "0.1234567", 0, 0.1234567f, "", 1 }, + /* 15*/ { "-0.1234567", 1, -0.1234567f, "", 1 }, + /* 16*/ { "0.12345678", 0, -1.0f, "fractional part must be 7 digits maximum", 0 }, + /* 17*/ { "0.123.4", 0, -1.0f, "fractional part must be digits only", 0 }, + /* 18*/ { "1234.5678", 0, -1.0f, "7 significant digits maximum", 0 }, + /* 19*/ { "1234.5670", 0, 1234.567f, "", 1 }, + /* 20*/ { "1234.56700", 0, 1234.567f, "", 1 }, + /* 21*/ { "0.", 0, 0.0f, "", 1 }, + /* 22*/ { "-0", 1, 0.0f, "", 1 }, + /* 23*/ { ".0", 0, 0.0f, "", 1 }, + /* 24*/ { ".", 0, 0.0f, "", 1 }, + /* 25*/ { "+.", 0, 0.0f, "", 1 }, + /* 26*/ { "-.", 1, 0.0f, "", 1 }, + }; + int i; + for (i = 0; i < ARRAY_SIZE(data); i++) { + char errbuf[64] = {0}; + float val = -1.0f; + const int ret = validate_float(data[i].source, data[i].allow_neg, &val, errbuf); + if (ret != data[i].ret) { + fprintf(stderr, "%d: ret %d != %d (%s)\n", i, ret, data[i].ret, errbuf); + assert(0); + } + if (val != data[i].val) { + fprintf(stderr, "%d: val %g != %g\n", i, val, data[i].val); + assert(0); + } + if (strcmp(errbuf, data[i].errbuf) != 0) { + fprintf(stderr, "%d: errbuf \"%s\" != \"%s\"\n", i, errbuf, data[i].errbuf); + assert(0); + } + } +} + +static void test_to_lower(void) { + /* s/\/\*[ 0-9]*\*\//\=printf("\/\*%3d*\/", line(".") - line("'<")): */ + static const struct { const char *source; const char *expected; } data[] = { + /* 0*/ { "", "" }, + /* 1*/ { "ABCEFGHIJKLMNOPQRSTUVWXYZ", "abcefghijklmnopqrstuvwxyz" }, + /* 2*/ { ".A[B`Ca~b\177c;\200", ".a[b`ca~b\177c;\200" }, + /* 3*/ { "é", "é" }, + }; + int i; + for (i = 0; i < ARRAY_SIZE(data); i++) { + char buf[128]; + assert((int) strlen(data[i].source) < ARRAY_SIZE(buf)); + strcpy(buf, data[i].source); + to_lower(buf); + if (strcmp(buf, data[i].expected) != 0) { + fprintf(stderr, "%d: \"%s\" != \"%s\"\n", i, buf, data[i].expected); + assert(0); + } + } +} + +static void test_supported_filetype(void) { + /* s/\/\*[ 0-9]*\*\//\=printf("\/\*%3d*\/", line(".") - line("'<")): */ + static const struct { const char *filetype; int no_png; int png_refused; int ret; } data[] = { + /* 0*/ { "bMp", 0, 0, 1 }, + /* 1*/ { "Txt", 1, 0, 1 }, + /* 2*/ { "png", 0, 0, 1 }, + /* 3*/ { "png", 1, 1, 0 }, + /* 4*/ { "gif", 0, 0, 1 }, + /* 5*/ { "giff", 0, 0, 0 }, + /* 6*/ { "tif", 0, 0, 1 }, + /* 7*/ { "Tif", 0, 0, 1 }, + /* 8*/ { "tiff", 0, 0, 1 }, + /* 9*/ { "TIF", 0, 0, 1 }, + /* 10*/ { "TIFF", 0, 0, 1 }, + /* 11*/ { "tIFF", 0, 0, 1 }, + /* 12*/ { "tifff", 0, 0, 0 }, + }; + int i; + for (i = 0; i < ARRAY_SIZE(data); i++) { + int png_refused = -1; + const int ret = supported_filetype(data[i].filetype, data[i].no_png, &png_refused); + if (ret != data[i].ret) { + fprintf(stderr, "%d: %d != %d\n", i, ret, data[i].ret); + assert(0); + } + if (png_refused != data[i].png_refused) { + fprintf(stderr, "%d: %d != %d\n", i, png_refused, data[i].png_refused); + assert(0); + } + } +} + +static void test_get_extension(void) { + /* s/\/\*[ 0-9]*\*\//\=printf("\/\*%3d*\/", line(".") - line("'<")): */ + static const struct { const char *file; const char *ret; } data[] = { + /* 0*/ { "Gosh.bMp", "bMp" }, + /* 1*/ { "Gosh.BMPP", "BMPP" }, + /* 2*/ { "Gosh.tif", "tif" }, + /* 3*/ { "Gosh.TIFF", "TIFF" }, + /* 4*/ { "Gosh.a", "a" }, + /* 5*/ { "Gosh.as", "as" }, + /* 6*/ { "Gosh.asd", "asd" }, + /* 7*/ { "Gosh.asdf", "asdf" }, + /* 8*/ { "Gosh.asdfg", NULL }, + }; + int i; + for (i = 0; i < ARRAY_SIZE(data); i++) { + char *ret = get_extension(data[i].file); + if (ret == NULL && data[i].ret != NULL) { + fprintf(stderr, "%d: != \"%s\"\n", i, data[i].ret); + assert(0); + } + if (ret != NULL && data[i].ret == NULL) { + fprintf(stderr, "%d: \"%s\" != \n", i, ret); + assert(0); + } + if (ret && data[i].ret && strcmp(ret, data[i].ret) != 0) { + fprintf(stderr, "%d: \"%s\" != \"%s\"\n", i, ret, data[i].ret); + assert(0); + } + } +} + +static void test_set_extension(void) { + /* s/\/\*[ 0-9]*\*\//\=printf("\/\*%3d*\/", line(".") - line("'<")): */ + static const struct { const char *file; const char *filetype; const char *expected; } data[] = { + /* 0*/ { "Gosh.bMp", "bMp", "Gosh.bMp" }, + /* 1*/ { "Gosh", "bMp", "Gosh.bMp" }, + /* 2*/ { "Gosh", "tif", "Gosh.tif" }, + /* 3*/ { "Gosh", "tiff", "Gosh.tif" }, + /* 4*/ { "Gosh", "tiFf", "Gosh.tiF" }, + /* 5*/ { "Gosh.a", "gif", "Gosh.gif" }, + /* 6*/ { "Gosh.as", "gif", "Gosh.gif" }, + /* 7*/ { "Gosh.asd", "gif", "Gosh.gif" }, + /* 8*/ { "Gosh.asdf", "gif", "Gosh.gif" }, + /* 9*/ { "Gosh.asdfg", "gif", "Gosh.asdfg.gif" }, + /* 10*/ { "123456789012345678901234567890123456789012345678901234567890123412345678901234567890123456789012345678901234567890123456789012341234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123", "png", "12345678901234567890123456789012345678901234567890123456789012341234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123412345678901234567890123456789012345678901234567890123456789.png" }, + }; + int i; + for (i = 0; i < ARRAY_SIZE(data); i++) { + char file[256]; + assert((int) strlen(data[i].file) < ARRAY_SIZE(file)); + strcpy(file, data[i].file); + set_extension(file, data[i].filetype); + if (strcmp(file, data[i].expected) != 0) { + fprintf(stderr, "%d: \"%s\" != \"%s\"\n", i, file, data[i].expected); + assert(0); + } + } +} + +static void test_is_raster(void) { + /* s/\/\*[ 0-9]*\*\//\=printf("\/\*%3d*\/", line(".") - line("'<")): */ + static const struct { const char *filetype; int no_png; int ret; } data[] = { + /* 0*/ { NULL, 0, 0 }, + /* 1*/ { "bMp", 0, 1 }, + /* 2*/ { "BMPP", 0, 0 }, + /* 3*/ { "PNG", 0, 1 }, + /* 4*/ { "PNG", 1, 0 }, + /* 5*/ { "emf", 0, 0 }, + /* 6*/ { "eps", 0, 0 }, + /* 7*/ { "svg", 0, 0 }, + /* 8*/ { "txt", 0, 0 }, + /* 9*/ { "gif", 0, 1 }, + /* 10*/ { "PcX", 0, 1 }, + /* 11*/ { "tif", 0, 1 }, + /* 12*/ { "tiff", 0, 1 }, + /* 13*/ { "TIFF", 0, 1 }, + /* 14*/ { "tIFF", 0, 1 }, + /* 15*/ { "tifff", 0, 0 }, + }; + int i; + for (i = 0; i < ARRAY_SIZE(data); i++) { + const int ret = is_raster(data[i].filetype, data[i].no_png); + if (ret != data[i].ret) { + fprintf(stderr, "%d: ret %d != %d\n", i, ret, data[i].ret); + assert(0); + } + } +} + static void test(void) { (void)get_barcode_name(NULL, 1 /*test*/); + test_validate_int(); + test_validate_float(); + test_to_lower(); + test_supported_filetype(); + test_get_extension(); + test_set_extension(); + test_is_raster(); } -#endif + +#endif /* ZINT_TEST */ /* vim: set ts=4 sw=4 et : */ diff --git a/frontend/tests/test_args.c b/frontend/tests/test_args.c index b642842e..7fd51005 100644 --- a/frontend/tests/test_args.c +++ b/frontend/tests/test_args.c @@ -531,9 +531,9 @@ static void test_input(const testCtx *const p_ctx) { /* 17*/ { BARCODE_CODE128, 1, -1, 0, "gif", NULL, "123\n456\n", TEST_INPUT_LONG "~.gif", 2, TEST_INPUT_LONG "1.gif\000" TEST_INPUT_LONG "2.gif" }, /* 18*/ { BARCODE_CODE128, 0, -1, 0, "svg", NULL, "123", TEST_INPUT_LONG "1.gif", 1, TEST_INPUT_LONG "1.svg" }, /* 19*/ { BARCODE_CODE128, 1, -1, 0, "svg", NULL, "123\n", TEST_INPUT_LONG "1.gif", 1, TEST_INPUT_LONG "1.svg" }, - /* 20*/ { BARCODE_CODE128, 1, -1, 0, "gif", NULL, "123\n", "test_batch.jpeg", 1, "test_batch.jpeg.gif" }, + /* 20*/ { BARCODE_CODE128, 1, -1, 0, "gif", NULL, "123\n", "test_batch.jpeg", 1, "test_batch.gif" }, /* 21*/ { BARCODE_CODE128, 1, -1, 0, "gif", NULL, "123\n", "test_batch.jpg", 1, "test_batch.gif" }, - /* 22*/ { BARCODE_CODE128, 1, -1, 0, "emf", NULL, "123\n", "test_batch.jpeg", 1, "test_batch.jpeg.emf" }, + /* 22*/ { BARCODE_CODE128, 1, -1, 0, "emf", NULL, "123\n", "test_batch.jpegg", 1, "test_batch.jpegg.emf" }, /* 23*/ { BARCODE_CODE128, 1, -1, 0, "emf", NULL, "123\n", "test_batch.jpg", 1, "test_batch.emf" }, /* 24*/ { BARCODE_CODE128, 1, -1, 0, "eps", NULL, "123\n", "test_batch.ps", 1, "test_batch.eps" }, /* 25*/ { BARCODE_CODE128, 1, -1, 1, "gif", NULL, "1234567890123456789012345678901\n1234567890123456789012345678902\n", TEST_MIRRORED_DIR_LONG, 2, TEST_MIRRORED_DIR_LONG "1234567890123456789012345678901.gif\000" TEST_MIRRORED_DIR_LONG "1234567890123456789012345678902.gif" }, diff --git a/frontend_qt/mainwindow.cpp b/frontend_qt/mainwindow.cpp index 7852bd04..5056ce0d 100644 --- a/frontend_qt/mainwindow.cpp +++ b/frontend_qt/mainwindow.cpp @@ -1518,8 +1518,10 @@ void MainWindow::copy_to_clipboard_emf() void MainWindow::copy_to_clipboard_eps() { +#ifdef MAINWINDOW_COPY_EPS // TODO: try other possibles application/eps, application/x-eps, image/eps, image/x-eps copy_to_clipboard(QSL(".zint.eps"), QSL("EPS"), "application/postscript"); +#endif } void MainWindow::copy_to_clipboard_gif() @@ -1529,8 +1531,10 @@ void MainWindow::copy_to_clipboard_gif() void MainWindow::copy_to_clipboard_pcx() { +#ifdef MAINWINDOW_COPY_PCX // TODO: try other mime types in various apps copy_to_clipboard(QSL(".zint.pcx"), QSL("PCX"), "image/x-pcx"); +#endif } void MainWindow::copy_to_clipboard_png() @@ -3584,30 +3588,22 @@ void MainWindow::copy_to_clipboard(const QString &filename, const QString& name, { QClipboard *clipboard = QGuiApplication::clipboard(); - if (!m_bc.bc.save_to_file(filename)) { + QByteArray data; + if (!m_bc.bc.save_to_memfile(filename, data)) { return; } QMimeData *mdata = new QMimeData; if (mimeType) { - QFile file(filename); - if (!file.open(QIODevice::ReadOnly)) { - delete mdata; - } else { - mdata->setData(mimeType, file.readAll()); - file.close(); - clipboard->setMimeData(mdata, QClipboard::Clipboard); - /*: %1 is format (BMP/EMF etc) */ - statusBar->showMessage(tr("Copied to clipboard as %1").arg(name), 0 /*No timeout*/); - } + mdata->setData(mimeType, data); } else { - mdata->setImageData(QImage(filename)); - clipboard->setMimeData(mdata, QClipboard::Clipboard); - /*: %1 is format (BMP/EMF etc) */ - statusBar->showMessage(tr("Copied to clipboard as %1").arg(name), 0 /*No timeout*/); + QImage img; + img.loadFromData(data); + mdata->setImageData(img); } - - QFile::remove(filename); + clipboard->setMimeData(mdata, QClipboard::Clipboard); + /*: %1 is format (BMP/EMF etc) */ + statusBar->showMessage(tr("Copied to clipboard as %1").arg(name), 0 /*No timeout*/); } void MainWindow::errtxtBar_clear()