comparison mupdf-source/thirdparty/zint/backend/output.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /* output.c - Common routines for raster/vector
2
3 libzint - the open source barcode library
4 Copyright (C) 2020-2024 Robin Stuart <rstuart114@gmail.com>
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15 3. Neither the name of the project nor the names of its contributors
16 may be used to endorse or promote products derived from this software
17 without specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
23 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 SUCH DAMAGE.
30 */
31 /* SPDX-License-Identifier: BSD-3-Clause */
32
33 #include <assert.h>
34 #include <errno.h>
35 #include <math.h>
36 #ifdef _WIN32
37 #include <windows.h>
38 #include <direct.h>
39 #else
40 #include <sys/stat.h> /* mkdir(2) */
41 #endif
42 #include "common.h"
43 #include "output.h"
44
45 #define OUT_SSET_F (IS_NUM_F | IS_UHX_F | IS_LHX_F) /* SSET "0123456789ABCDEFabcdef" */
46
47 /* Helper to check an individual colour option is good */
48 static int out_check_colour(struct zint_symbol *symbol, const char *colour, const char *name) {
49 const char *comma1, *comma2, *comma3;
50 int val;
51
52 if ((comma1 = strchr(colour, ',')) == NULL) {
53 const int len = (int) strlen(colour);
54 if ((len != 6) && (len != 8)) {
55 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 880, "Malformed %s RGB colour (6 or 8 characters only)",
56 name);
57 }
58 if (not_sane(OUT_SSET_F, (unsigned char *) colour, len)) {
59 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 881,
60 "Malformed %1$s RGB colour '%2$s' (hexadecimal only)", name, colour);
61 }
62
63 return 0;
64 }
65
66 /* CMYK comma-separated percentages */
67 if ((comma2 = strchr(comma1 + 1, ',')) == NULL || (comma3 = strchr(comma2 + 1, ',')) == NULL
68 || strchr(comma3 + 1, ',') != NULL) {
69 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 882,
70 "Malformed %s CMYK colour (4 decimal numbers, comma-separated)", name);
71 }
72 if (comma1 - colour > 3 || comma2 - (comma1 + 1) > 3 || comma3 - (comma2 + 1) > 3 || strlen(comma3 + 1) > 3) {
73 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 883,
74 "Malformed %s CMYK colour (3 digit maximum per number)", name);
75 }
76
77 if ((val = to_int((const unsigned char *) colour, (int) (comma1 - colour))) == -1 || val > 100) {
78 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 884, "Malformed %s CMYK colour C (decimal 0 to 100 only)",
79 name);
80 }
81 if ((val = to_int((const unsigned char *) (comma1 + 1), (int) (comma2 - (comma1 + 1)))) == -1 || val > 100) {
82 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 885, "Malformed %s CMYK colour M (decimal 0 to 100 only)",
83 name);
84 }
85 if ((val = to_int((const unsigned char *) (comma2 + 1), (int) (comma3 - (comma2 + 1)))) == -1 || val > 100) {
86 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 886, "Malformed %s CMYK colour Y (decimal 0 to 100 only)",
87 name);
88 }
89 if ((val = to_int((const unsigned char *) (comma3 + 1), (int) strlen(comma3 + 1))) == -1 || val > 100) {
90 return errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 887, "Malformed %s CMYK colour K (decimal 0 to 100 only)",
91 name);
92 }
93
94 return 0;
95 }
96
97 /* Check colour options are good (`symbol->fgcolour`, `symbol->bgcolour`) */
98 INTERNAL int out_check_colour_options(struct zint_symbol *symbol) {
99
100 if (out_check_colour(symbol, symbol->fgcolour, "foreground") != 0) {
101 return ZINT_ERROR_INVALID_OPTION;
102 }
103 if (out_check_colour(symbol, symbol->bgcolour, "background") != 0) {
104 return ZINT_ERROR_INVALID_OPTION;
105 }
106
107 return 0;
108 }
109
110 /* Return RGB(A) from (well-formed) colour string. Returns 0 if RGB or converted CMYK, 1 if RGBA */
111 INTERNAL int out_colour_get_rgb(const char *colour, unsigned char *red, unsigned char *green, unsigned char *blue,
112 unsigned char *alpha) {
113 const char *comma1, *comma2, *comma3;
114 int black, val;
115
116 if ((comma1 = strchr(colour, ',')) == NULL) {
117 *red = 16 * ctoi(colour[0]) + ctoi(colour[1]);
118 *green = 16 * ctoi(colour[2]) + ctoi(colour[3]);
119 *blue = 16 * ctoi(colour[4]) + ctoi(colour[5]);
120 if (alpha) {
121 *alpha = colour[6] ? 16 * ctoi(colour[6]) + ctoi(colour[7]) : 0xFF;
122 return colour[6] ? 1 : 0;
123 }
124 return 0;
125 }
126 comma2 = strchr(comma1 + 1, ',');
127 comma3 = strchr(comma2 + 1, ',');
128
129 black = 100 - to_int((const unsigned char *) (comma3 + 1), (int) strlen(comma3 + 1));
130
131 val = 100 - to_int((const unsigned char *) colour, (int) (comma1 - colour)); /* Cyan */
132 *red = (int) round((0xFF * val * black) / 10000.0);
133
134 val = 100 - to_int((const unsigned char *) (comma1 + 1), (int) (comma2 - (comma1 + 1))); /* Magenta */
135 *green = (int) round((0xFF * val * black) / 10000.0);
136
137 val = 100 - to_int((const unsigned char *) (comma2 + 1), (int) (comma3 - (comma2 + 1))); /* Yellow */
138 *blue = (int) round((0xFF * val * black) / 10000.0);
139
140 if (alpha) {
141 *alpha = 0xFF;
142 }
143
144 return 0;
145 }
146
147 /* Return CMYK from (well-formed) colour string. Returns 0 if CMYK, 1 if converted RBG, 2 if converted RGBA */
148 INTERNAL int out_colour_get_cmyk(const char *colour, int *cyan, int *magenta, int *yellow, int *black,
149 unsigned char *rgb_alpha) {
150 const char *comma1;
151 unsigned char red, green, blue, alpha;
152 int have_alpha, k;
153
154 if ((comma1 = strchr(colour, ',')) != NULL) {
155 const char *const comma2 = strchr(comma1 + 1, ',');
156 const char *const comma3 = strchr(comma2 + 1, ',');
157 *cyan = to_int((const unsigned char *) colour, (int) (comma1 - colour));
158 *magenta = to_int((const unsigned char *) (comma1 + 1), (int) (comma2 - (comma1 + 1)));
159 *yellow = to_int((const unsigned char *) (comma2 + 1), (int) (comma3 - (comma2 + 1)));
160 *black = to_int((const unsigned char *) (comma3 + 1), (int) strlen(comma3 + 1));
161 if (rgb_alpha) {
162 *rgb_alpha = 0xFF;
163 }
164 return 0;
165 }
166 have_alpha = out_colour_get_rgb(colour, &red, &green, &blue, &alpha);
167
168 k = red;
169 if (green > k) {
170 k = green;
171 }
172 if (blue > k) {
173 k = blue;
174 }
175 if (k == 0) {
176 *cyan = *magenta = *yellow = 0;
177 *black = 100;
178 } else {
179 *cyan = (int) round((k - red) * 100.0 / k);
180 *magenta = (int) round((k - green) * 100.0 / k);
181 *yellow = (int) round((k - blue) * 100.0 / k);
182 *black = (int) round(((0xFF - k) * 100.0) / 0xFF);
183 }
184
185 if (rgb_alpha) {
186 *rgb_alpha = have_alpha ? alpha : 0xFF;
187 }
188
189 return 1 + have_alpha;
190 }
191
192 /* Convert internal colour chars "WCBMRYGK" to RGB */
193 INTERNAL int out_colour_char_to_rgb(const char ch, unsigned char *red, unsigned char *green, unsigned char *blue) {
194 static const char chars[] = "WCBMRYGK";
195 static const unsigned char colours[8][3] = {
196 { 0xff, 0xff, 0xff, }, /* White */
197 { 0, 0xff, 0xff, }, /* Cyan */
198 { 0, 0, 0xff, }, /* Blue */
199 { 0xff, 0, 0xff, }, /* Magenta */
200 { 0xff, 0, 0, }, /* Red */
201 { 0xff, 0xff, 0, }, /* Yellow */
202 { 0, 0xff, 0, }, /* Green */
203 { 0, 0, 0, }, /* Black */
204 };
205 int i = posn(chars, ch);
206 int ret = i != -1;
207
208 if (i == -1) {
209 i = 7; /* Black (zeroize) */
210 }
211 if (red) {
212 *red = colours[i][0];
213 }
214 if (green) {
215 *green = colours[i][1];
216 }
217 if (blue) {
218 *blue = colours[i][2];
219 }
220
221 return ret;
222 }
223
224 /* Return minimum quiet zones for each symbology */
225 static int out_quiet_zones(const struct zint_symbol *symbol, const int hide_text, const int comp_xoffset,
226 float *left, float *right, float *top, float *bottom) {
227 int done = 0;
228
229 *left = *right = *top = *bottom = 0.0f;
230
231 /* These always have quiet zones set (previously used whitespace_width) */
232 switch (symbol->symbology) {
233 case BARCODE_CODE16K:
234 /* BS EN 12323:2005 Section 4.5 (c) */
235 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
236 *left = 10.0f;
237 *right = 1.0f;
238 }
239 done = 1;
240 break;
241 case BARCODE_CODE49:
242 /* ANSI/AIM BC6-2000 Section 2.4 */
243 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
244 *left = 10.0f;
245 *right = 1.0f;
246 }
247 done = 1;
248 break;
249 case BARCODE_CODABLOCKF:
250 case BARCODE_HIBC_BLOCKF:
251 /* AIM ISS-X-24 Section 4.6.1 */
252 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
253 *left = *right = 10.0f;
254 }
255 done = 1;
256 break;
257 case BARCODE_ITF14:
258 /* GS1 General Specifications 21.0.1 Section 5.3.2.2 */
259 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
260 *left = *right = 10.0f;
261 }
262 done = 1;
263 break;
264 case BARCODE_EANX:
265 case BARCODE_EANX_CHK:
266 case BARCODE_EANX_CC:
267 case BARCODE_ISBNX:
268 /* GS1 General Specifications 21.0.1 Section 5.2.3.4 */
269 switch (ustrlen(symbol->text)) {
270 case 13: /* EAN-13/ISBN */
271 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
272 *left = comp_xoffset >= 10 ? 1.0f : 11.0f - comp_xoffset; /* Need at least 1X for CC-A/B */
273 *right = 7.0f - (comp_xoffset != 0);
274 } else if (!hide_text) {
275 *left = comp_xoffset >= 10 ? 1.0f : 11.0f - comp_xoffset; /* Need for outside left digit */
276 }
277 break;
278 case 16: /* EAN-13/ISBN + 2 digit add-on */
279 case 19: /* EAN-13/ISBN + 5 digit add-on */
280 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
281 *left = comp_xoffset >= 10 ? 1.0f : 11.0f - comp_xoffset; /* Need at least 1X for CC-A/B */
282 *right = 5.0f;
283 } else if (!hide_text) {
284 *left = comp_xoffset >= 10 ? 1.0f : 11.0f - comp_xoffset; /* Need for outside left digit */
285 }
286 break;
287 case 5: /* EAN-5 add-on */
288 case 2: /* EAN-2 add-on */
289 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
290 *right = 5.0f;
291 }
292 break;
293 case 8: /* EAN-8 */
294 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
295 *left = comp_xoffset >= 6 ? 1.0f : 7.0f - comp_xoffset; /* Need at least 1X for CC-A/B */
296 *right = 7.0f - (comp_xoffset != 0);
297 }
298 break;
299 default: /* EAN-8 + 2/5 digit add-on */
300 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
301 *left = comp_xoffset >= 6 ? 1.0f : 7.0f - comp_xoffset; /* Need at least 1X for CC-A/B */
302 *right = 5.0f;
303 }
304 break;
305 }
306 done = 1;
307 break;
308 case BARCODE_UPCA:
309 case BARCODE_UPCA_CHK:
310 case BARCODE_UPCA_CC:
311 /* GS1 General Specifications 21.0.1 Section 5.2.3.4 */
312 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
313 *left = comp_xoffset >= 8 ? 1.0f : 9.0f - comp_xoffset; /* Need at least 1X for CC-A/B */
314 if (ustrlen(symbol->text) > 12) { /* UPC-A + add-on */
315 *right = 5.0f;
316 } else {
317 *right = 9.0f - (comp_xoffset != 0);
318 }
319 } else if (!hide_text) {
320 *left = comp_xoffset >= 8 ? 1.0f : 9.0f - comp_xoffset; /* Need for outside left digit */
321 if (ustrlen(symbol->text) <= 12) { /* No add-on */
322 *right = 9.0f - (comp_xoffset != 0); /* Need for outside right digit */
323 }
324 }
325 done = 1;
326 break;
327 case BARCODE_UPCE:
328 case BARCODE_UPCE_CHK:
329 case BARCODE_UPCE_CC:
330 /* GS1 General Specifications 21.0.1 Section 5.2.3.4 */
331 if (!(symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
332 *left = comp_xoffset >= 8 ? 1.0f : 9.0f - comp_xoffset;
333 if (ustrlen(symbol->text) > 8) { /* UPC-E + add-on */
334 *right = 5.0f;
335 } else {
336 *right = 7.0f - (comp_xoffset != 0);
337 }
338 } else if (!hide_text) {
339 *left = comp_xoffset >= 8 ? 1.0f : 9.0f - comp_xoffset; /* Need for outside left digit */
340 if (ustrlen(symbol->text) <= 8) { /* No add-on */
341 *right = 7.0f - (comp_xoffset != 0); /* Need for outside right digit */
342 }
343 }
344 done = 1;
345 break;
346 }
347
348 if (done) {
349 return done;
350 }
351
352 /* Only do others if flag set */
353 if (!(symbol->output_options & BARCODE_QUIET_ZONES) || (symbol->output_options & BARCODE_NO_QUIET_ZONES)) {
354 return 0;
355 }
356
357 switch (symbol->symbology) {
358 case BARCODE_CODE11:
359 /* No known standard. Following ITF-14, set to 10X */
360 *left = *right = 10.0f;
361 done = 1;
362 break;
363
364 case BARCODE_DXFILMEDGE:
365 /* No known standard. Add a little horizontal space to make the detection easier. Tested with Zxing-CPP */
366 *left = *right = 1.8f;
367 done = 1;
368 break;
369
370 case BARCODE_C25INTER:
371 /* ISO/IEC 16390:2007 Section 4.4 10X */
372 *left = *right = 10.0f;
373 done = 1;
374 break;
375 case BARCODE_C25STANDARD:
376 case BARCODE_C25IATA:
377 case BARCODE_C25LOGIC:
378 case BARCODE_C25IND:
379 /* No known standards. Following C25INTER, set to 10X */
380 *left = *right = 10.0f;
381 done = 1;
382 break;
383
384 case BARCODE_CODE39:
385 case BARCODE_EXCODE39:
386 case BARCODE_LOGMARS:
387 case BARCODE_PZN:
388 case BARCODE_VIN:
389 case BARCODE_HIBC_39:
390 case BARCODE_CODE32:
391 /* ISO/IEC 16388:2007 Section 4.4 (d) */
392 *left = *right = 10.0f;
393 done = 1;
394 break;
395 case BARCODE_GS1_128: /* GS1-128 */
396 case BARCODE_EAN14:
397 /* GS1 General Specifications 21.0.1 Section 5.4.4.2 */
398 *left = *right = 10.0f;
399 done = 1;
400 break;
401 case BARCODE_GS1_128_CC:
402 /* GS1 General Specifications 21.0.1 Sections 5.11.2.1 (CC-A), 5.11.2.2 (CC-B) & 5.11.2.3 (CC-C) */
403 {
404 int comp_roffset = 0; /* Right offset of linear */
405 int min_qz; /* Minimum quiet zone - 1X for CC-A/B, 2X for CC-C */
406 int x;
407 for (x = symbol->width - 1; x >= 0 && !module_is_set(symbol, symbol->rows - 1, x); x--) {
408 comp_roffset++;
409 }
410 /* Determine if CC-C by counting initial start pattern */
411 for (x = 0; x < 8 && module_is_set(symbol, 0, x); x++);
412 min_qz = x == 8 ? 2 : 1;
413 *left = comp_xoffset >= 10 - min_qz ? min_qz : 10.0f - comp_xoffset;
414 *right = comp_roffset >= 10 - min_qz ? min_qz : 10.0f - comp_roffset;
415 }
416 done = 1;
417 break;
418 case BARCODE_CODABAR:
419 /* BS EN 798:1995 Section 4.4.1 (d) */
420 *left = *right = 10.0f;
421 done = 1;
422 break;
423 case BARCODE_CODE128:
424 case BARCODE_CODE128AB:
425 case BARCODE_HIBC_128:
426 case BARCODE_NVE18:
427 /* ISO/IEC 15417:2007 4.4.2 */
428 *left = *right = 10.0f;
429 done = 1;
430 break;
431 case BARCODE_DPLEIT:
432 case BARCODE_DPIDENT:
433 /* Using C25INTER values TODO: Find doc */
434 *left = *right = 10.0f;
435 done = 1;
436 break;
437
438 case BARCODE_CODE93:
439 /* ANSI/AIM BC5-1995 Section 2.4 */
440 *left = *right = 10.0f;
441 done = 1;
442 break;
443
444 case BARCODE_FLAT:
445 /* TODO: Find doc (application defined according to TEC-IT) */
446 break;
447
448 case BARCODE_DBAR_OMN: /* GS1 Databar Omnidirectional */
449 case BARCODE_DBAR_LTD: /* GS1 Databar Limited */
450 case BARCODE_DBAR_EXP: /* GS1 Databar Expanded */
451 case BARCODE_DBAR_STK: /* GS1 DataBar Stacked */
452 case BARCODE_DBAR_OMNSTK: /* GS1 DataBar Stacked Omnidirectional */
453 case BARCODE_DBAR_EXPSTK: /* GS1 Databar Expanded Stacked */
454 /* GS1 General Specifications 21.0.1 Section 5.5.1.1 - Quiet Zones: None required */
455 done = 1;
456 break;
457
458 /* GS1 General Specifications 21.0.1 Sections 5.11.2.1 (CC-A) & 5.11.2.2 (CC-B) require 1X either side
459 but this may be supplied by the positioning of the linear component */
460 case BARCODE_DBAR_OMN_CC:
461 case BARCODE_DBAR_LTD_CC:
462 /* Always have at least 1X to right of CC-A/B */
463 if (comp_xoffset > 1) { /* Exclude DBAR_LTD_CC with CC-A which always has 1X to left */
464 *left = 1.0f;
465 }
466 done = 1;
467 break;
468 case BARCODE_DBAR_STK_CC:
469 case BARCODE_DBAR_OMNSTK_CC:
470 /* Always have at least 1X to left of CC-A/B */
471 *right = 1.0f;
472 done = 1;
473 break;
474 case BARCODE_DBAR_EXP_CC:
475 case BARCODE_DBAR_EXPSTK_CC:
476 /* Always have at least 1X to left and right of CC-A/B */
477 done = 1;
478 break;
479
480 case BARCODE_TELEPEN:
481 case BARCODE_TELEPEN_NUM:
482 /* Appears to be ~10X from diagram in Telepen Barcode Symbology information and History */
483 /* TODO: Find better doc */
484 *left = *right = 10.0f;
485 done = 1;
486 break;
487
488 case BARCODE_POSTNET:
489 case BARCODE_PLANET:
490 /* USPS DMM 300 2006 (2011) 5.7 Barcode in Address Block
491 left/right 0.125" / 0.025" (X max) = 5, top/bottom 0.04" / 0.025" (X max) = 1.6 */
492 *left = *right = 5.0f;
493 *top = *bottom = 1.6f;
494 done = 1;
495 break;
496 case BARCODE_CEPNET:
497 /* CEPNet e Código Bidimensional Datamatrix 2D (26/05/2021) 3.8 Localização */
498 *left = *right = 10.0f;
499 *top = *bottom = 1.6f; /* As POSTNET (1.016mm == 0.025") */
500 done = 1;
501 break;
502
503 case BARCODE_MSI_PLESSEY:
504 /* TODO Find doc (TEC-IT says 12X so use that for the moment) */
505 *left = *right = 12.0f;
506 done = 1;
507 break;
508
509 case BARCODE_FIM:
510 /* USPS DMM 300 2006 (2011) 708.9.3 (top/bottom zero)
511 right 0.125" (min) / 0.03925" (X max) ~ 3.18, left 1.25" - 0.66725" (max width of barcode)
512 - 0.375 (max right) = 0.20775" / 0.03925" (X max) ~ 5.29 */
513 *right = 3.18471336f; /* 0.125 / 0.03925 */
514 *left = 5.29299355f; /* 0.20775 / 0.03925 */
515 done = 1;
516 break;
517 case BARCODE_PHARMA:
518 case BARCODE_PHARMA_TWO:
519 /* Laetus Pharmacode Guide 2.2 from 6mm depending on context, 6mm / 1mm (Pharma Two X) = 6 */
520 *left = *right = 6.0f;
521 done = 1;
522 break;
523 case BARCODE_PDF417:
524 case BARCODE_PDF417COMP:
525 case BARCODE_HIBC_PDF:
526 /* ISO/IEC 15438:2015 Section 5.8.3 */
527 *left = *right = *top = *bottom = 2.0f;
528 done = 1;
529 break;
530 case BARCODE_MICROPDF417:
531 case BARCODE_HIBC_MICPDF:
532 /* ISO/IEC 24728:2006 Section 5.8.3 */
533 *left = *right = *top = *bottom = 1.0f;
534 done = 1;
535 break;
536 case BARCODE_MAXICODE:
537 /* ISO/IEC 16023:2000 Section 4.11.5 */
538 *left = *right = *top = *bottom = 1.0f;
539 done = 1;
540 break;
541 case BARCODE_QRCODE:
542 case BARCODE_UPNQR:
543 case BARCODE_HIBC_QR:
544 /* ISO/IEC 18004:2015 Section 9.1 */
545 *left = *right = *top = *bottom = 4.0f;
546 done = 1;
547 break;
548 case BARCODE_DPD:
549 /* DPD Parcel Label Specification Version 2.4.1 Section 4.6.1.2, 5mm / 0.4mm (X max) = 12.5 */
550 *left = *right = 12.5f;
551 done = 1;
552 break;
553 case BARCODE_MICROQR:
554 /* ISO/IEC 18004:2015 Section 9.1 */
555 *left = *right = *top = *bottom = 2.0f;
556 done = 1;
557 break;
558 case BARCODE_RMQR:
559 /* ISO/IEC JTC1/SC31N000 Section 6.3.10 */
560 *left = *right = *top = *bottom = 2.0f;
561 done = 1;
562 break;
563 case BARCODE_AUSPOST:
564 case BARCODE_AUSREPLY:
565 case BARCODE_AUSROUTE:
566 case BARCODE_AUSREDIRECT:
567 /* Customer Barcode Technical Specifications (2012) left/right 6mm / 0.6mm = 10,
568 top/bottom 2mm / 0.6mm ~ 3.33 (X max) */
569 *left = *right = 10.0f;
570 *top = *bottom = 3.33333325f; /* 2.0 / 0.6 */
571 done = 1;
572 break;
573 case BARCODE_RM4SCC:
574 /* Royal Mail Know How User's Manual Appendix C: using CBC, same as MAILMARK_4S, 2mm all round,
575 use X max (25.4mm / 39) i.e. 20 bars per 25.4mm */
576 *left = *right = *top = *bottom = 3.07086611f; /* (2.0 * 39.0) / 25.4 */
577 done = 1;
578 break;
579 case BARCODE_DATAMATRIX:
580 case BARCODE_HIBC_DM:
581 /* ISO/IEC 16022:2006 Section 7.1 */
582 *left = *right = *top = *bottom = 1.0f;
583 done = 1;
584 break;
585 case BARCODE_JAPANPOST:
586 /* Japan Post Zip/Barcode Manual p.13 2mm all round, X 0.6mm, 2mm / 0.6mm ~ 3.33 */
587 *left = *right = *top = *bottom = 3.33333325f; /* 2.0 / 0.6 */
588 done = 1;
589 break;
590
591 case BARCODE_KOREAPOST:
592 /* TODO Find doc (TEC-IT uses 10X but says not exactly specified - do the same for the moment) */
593 *left = *right = 10.0f;
594 done = 1;
595 break;
596
597 case BARCODE_USPS_IMAIL:
598 /* USPS-B-3200 (2015) Section 2.3.2 left/right 0.125", top/bottom 0.026", use X max (1 / 39)
599 i.e. 20 bars per inch */
600 *left = *right = 4.875f; /* 0.125 * 39.0 */
601 *top = *bottom = 1.01400006f; /* 0.026 * 39.0 */
602 done = 1;
603 break;
604
605 case BARCODE_PLESSEY:
606 /* TODO Find doc (see MSI_PLESSEY) */
607 *left = *right = 12.0f;
608 done = 1;
609 break;
610
611 case BARCODE_KIX:
612 /* Handleiding KIX code brochure - same as RM4SCC/MAILMARK_4S */
613 *left = *right = *top = *bottom = 3.07086611f; /* (2.0 * 39.0) / 25.4 */
614 done = 1;
615 break;
616 case BARCODE_AZTEC:
617 case BARCODE_HIBC_AZTEC:
618 case BARCODE_AZRUNE:
619 /* ISO/IEC 24778:2008 Section 4.1 (c) & Annex A.1 (Rune) - no quiet zone required */
620 done = 1;
621 break;
622 case BARCODE_DAFT:
623 /* Generic so unlikely to be defined */
624 done = 1;
625 break;
626 case BARCODE_DOTCODE:
627 /* ISS DotCode Rev. 4.0 Section 4.1 (3) (c) */
628 *left = *right = *top = *bottom = 3.0f;
629 done = 1;
630 break;
631 case BARCODE_HANXIN:
632 /* ISO/IEC DIS 20830:2019 Section 4.2.8 (also Section 6.2) */
633 *left = *right = *top = *bottom = 3.0f;
634 done = 1;
635 break;
636 case BARCODE_MAILMARK_4S:
637 /* Royal Mail Mailmark Barcode Definition Document Section 3.5.2, 2mm all round, use X max (25.4mm / 39)
638 i.e. 20 bars per 25.4mm */
639 *left = *right = *top = *bottom = 3.07086611f; /* (2.0 * 39.0) / 25.4 */
640 done = 1;
641 break;
642 case BARCODE_UPU_S10:
643 /* Universal Postal Union S10 Section 8 */
644 *left = *right = 10.0f;
645 done = 1;
646 break;
647 case BARCODE_MAILMARK_2D:
648 /* Royal Mail Mailmark Barcode Definition Document, Section 2.4 */
649 *left = *right = *top = *bottom = 4.0f;
650 done = 1;
651 break;
652 case BARCODE_CHANNEL:
653 /* ANSI/AIM BC12-1998 Section 4.4 (c) */
654 *left = 1.0f;
655 *right = 2.0f;
656 done = 1;
657 break;
658
659 case BARCODE_CODEONE:
660 /* USS Code One AIM 1994 Section 2.2.4 No quiet zone required for Versions A to H */
661 if (symbol->option_2 == 9 || symbol->option_2 == 10) { /* Section 2.3.2 Versions S & T */
662 *left = *right = *bottom = 1.0f;
663 }
664 done = 1;
665 break;
666
667 case BARCODE_GRIDMATRIX:
668 /* AIMD014 (v 1.63) Section 7.1 */
669 *left = *right = *top = *bottom = 6.0f;
670 done = 1;
671 break;
672 case BARCODE_ULTRA:
673 /* AIMD/TSC15032-43 (v 0.99c) Section 9.2 */
674 *left = *right = *top = *bottom = 1.0f;
675 done = 1;
676 break;
677
678 case BARCODE_BC412:
679 /* SEMI T1-95 Table 4 */
680 *left = *right = 10.0f;
681 done = 1;
682 break;
683 }
684
685 return done; /* For self-checking */
686 }
687
688 #ifdef ZINT_TEST /* Wrapper for direct testing */
689 INTERNAL int out_quiet_zones_test(const struct zint_symbol *symbol, const int hide_text, const int comp_xoffset,
690 float *left, float *right, float *top, float *bottom) {
691 return out_quiet_zones(symbol, hide_text, comp_xoffset, left, right, top, bottom);
692 }
693 #endif
694
695 /* Set left (x), top (y), right and bottom offsets for whitespace, also right quiet zone */
696 INTERNAL void out_set_whitespace_offsets(const struct zint_symbol *symbol, const int hide_text,
697 const int comp_xoffset, float *p_xoffset, float *p_yoffset, float *p_roffset, float *p_boffset,
698 float *p_qz_right, const float scaler, int *p_xoffset_si, int *p_yoffset_si, int *p_roffset_si,
699 int *p_boffset_si, int *p_qz_right_si) {
700 float qz_left, qz_right, qz_top, qz_bottom;
701
702 out_quiet_zones(symbol, hide_text, comp_xoffset, &qz_left, &qz_right, &qz_top, &qz_bottom);
703
704 *p_xoffset = symbol->whitespace_width + qz_left;
705 *p_roffset = symbol->whitespace_width + qz_right;
706 if (symbol->output_options & BARCODE_BOX) {
707 *p_xoffset += symbol->border_width;
708 *p_roffset += symbol->border_width;
709 }
710
711 *p_yoffset = symbol->whitespace_height + qz_top;
712 *p_boffset = symbol->whitespace_height + qz_bottom;
713 if (symbol->output_options & (BARCODE_BOX | BARCODE_BIND | BARCODE_BIND_TOP)) {
714 *p_yoffset += symbol->border_width;
715 if (!(symbol->output_options & BARCODE_BIND_TOP)) { /* Trumps BARCODE_BOX & BARCODE_BIND */
716 *p_boffset += symbol->border_width;
717 }
718 }
719
720 if (p_qz_right) {
721 *p_qz_right = qz_right;
722 }
723
724 if (scaler) {
725 if (p_xoffset_si) {
726 *p_xoffset_si = (int) (*p_xoffset * scaler);
727 }
728 if (p_yoffset_si) {
729 *p_yoffset_si = (int) (*p_yoffset * scaler);
730 }
731 if (p_roffset_si) {
732 *p_roffset_si = (int) (*p_roffset * scaler);
733 }
734 if (p_boffset_si) {
735 *p_boffset_si = (int) (*p_boffset * scaler);
736 }
737 if (p_qz_right_si) {
738 *p_qz_right_si = (int) (qz_right * scaler);
739 }
740 }
741 }
742
743 /* Set composite offset and main width excluding add-on (for start of add-on calc) and add-on text, returning
744 EAN/UPC type */
745 INTERNAL int out_process_upcean(const struct zint_symbol *symbol, const int comp_xoffset, int *p_main_width,
746 unsigned char addon[6], int *p_addon_len, int *p_addon_gap) {
747 int main_width; /* Width of main linear symbol, excluding add-on */
748 int upceanflag; /* EAN/UPC type flag */
749 int i, j, latch;
750 const int text_length = (int) ustrlen(symbol->text);
751
752 latch = 0;
753 j = 0;
754 /* Isolate add-on text */
755 for (i = 6; i < text_length && j < 5; i++) {
756 if (latch == 1) {
757 /* Use dummy space-filled add-on if no hrt */
758 addon[j] = symbol->show_hrt ? symbol->text[i] : ' ';
759 j++;
760 } else if (symbol->text[i] == '+') {
761 latch = 1;
762 }
763 }
764 addon[j] = '\0';
765 if (latch) {
766 *p_addon_len = (int) ustrlen(addon);
767 if (symbol->symbology == BARCODE_UPCA || symbol->symbology == BARCODE_UPCA_CHK
768 || symbol->symbology == BARCODE_UPCA_CC) {
769 *p_addon_gap = symbol->option_2 >= 9 && symbol->option_2 <= 12 ? symbol->option_2 : 9;
770 } else {
771 *p_addon_gap = symbol->option_2 >= 7 && symbol->option_2 <= 12 ? symbol->option_2 : 7;
772 }
773 }
774
775 upceanflag = 0;
776 main_width = symbol->width;
777 if ((symbol->symbology == BARCODE_EANX) || (symbol->symbology == BARCODE_EANX_CHK)
778 || (symbol->symbology == BARCODE_EANX_CC) || (symbol->symbology == BARCODE_ISBNX)) {
779 switch (text_length) {
780 case 13: /* EAN-13 */
781 case 16: /* EAN-13 + EAN-2 */
782 case 19: /* EAN-13 + EAN-5 */
783 main_width = 95 + comp_xoffset; /* EAN-13 main symbol 95 modules wide */
784 upceanflag = 13;
785 break;
786 case 2:
787 /* EAN-2 can't have add-on or be composite */
788 upceanflag = 2;
789 break;
790 case 5:
791 /* EAN-5 can't have add-on or be composite */
792 upceanflag = 5;
793 break;
794 default:
795 main_width = 68 + comp_xoffset; /* EAN-8 main symbol 68 modules wide */
796 upceanflag = 8;
797 break;
798 }
799 } else if ((symbol->symbology == BARCODE_UPCA) || (symbol->symbology == BARCODE_UPCA_CHK)
800 || (symbol->symbology == BARCODE_UPCA_CC)) {
801 main_width = 95 + comp_xoffset; /* UPC-A main symbol 95 modules wide */
802 upceanflag = 12;
803 } else if ((symbol->symbology == BARCODE_UPCE) || (symbol->symbology == BARCODE_UPCE_CHK)
804 || (symbol->symbology == BARCODE_UPCE_CC)) {
805 main_width = 51 + comp_xoffset; /* UPC-E main symbol 51 modules wide */
806 upceanflag = 6;
807 }
808
809 *p_main_width = main_width;
810
811 return upceanflag;
812 }
813
814 /* Calculate large bar height i.e. linear bars with zero row height that respond to the symbol height.
815 If scaler `si` non-zero (raster), then large_bar_height if non-zero or else row heights will be rounded
816 to nearest pixel and symbol height adjusted */
817 INTERNAL float out_large_bar_height(struct zint_symbol *symbol, const int si, int *row_heights_si,
818 int *symbol_height_si) {
819 float fixed_height = 0.0f;
820 int zero_count = 0;
821 int round_rows = 0;
822 int i;
823 float large_bar_height = 0.0f; /* Not used if zero_count zero */
824
825 if (si) {
826 for (i = 0; i < symbol->rows; i++) {
827 if (symbol->row_height[i]) {
828 fixed_height += symbol->row_height[i];
829 if (!round_rows && !isfintf(symbol->row_height[i] * si)) {
830 round_rows = 1;
831 }
832 } else {
833 zero_count++;
834 }
835 }
836
837 if (zero_count) {
838 large_bar_height = stripf((symbol->height - fixed_height) / zero_count);
839 assert(large_bar_height >= 0.5f); /* Min row height as set by `set_height()` */
840 if (!isfintf(large_bar_height * si)) {
841 large_bar_height = stripf(roundf(large_bar_height * si) / si);
842 }
843 symbol->height = stripf(large_bar_height * zero_count + fixed_height);
844 /* Note should never happen that have both zero_count and round_rows */
845 } else {
846 if (round_rows) {
847 float total_height = 0.0f;
848 for (i = 0; i < symbol->rows; i++) {
849 if (!isfintf(symbol->row_height[i] * si)) {
850 symbol->row_height[i] = roundf(symbol->row_height[i] * si) / si;
851 }
852 total_height += symbol->row_height[i];
853 }
854 symbol->height = stripf(total_height);
855 }
856 }
857
858 if (row_heights_si) {
859 assert(symbol_height_si);
860 *symbol_height_si = 0;
861 for (i = 0; i < symbol->rows; i++) {
862 if (symbol->row_height[i]) {
863 row_heights_si[i] = (int) roundf(symbol->row_height[i] * si);
864 } else {
865 row_heights_si[i] = (int) roundf(large_bar_height * si);
866 }
867 *symbol_height_si += row_heights_si[i];
868 }
869 }
870 } else {
871 for (i = 0; i < symbol->rows; i++) {
872 if (symbol->row_height[i]) {
873 fixed_height += symbol->row_height[i];
874 } else {
875 zero_count++;
876 }
877 }
878 if (zero_count) {
879 large_bar_height = stripf((symbol->height - fixed_height) / zero_count);
880 assert(large_bar_height >= 0.5f); /* Min row height as set by `set_height()` */
881 symbol->height = stripf(large_bar_height * zero_count + fixed_height);
882 }
883 }
884
885 return large_bar_height;
886 }
887
888 #ifdef _WIN32
889 /* Convert UTF-8 to Windows wide chars. Ticket #288, props Marcel */
890 #define utf8_to_wide(u, w, r) \
891 { \
892 int lenW; /* Includes NUL terminator */ \
893 if ((lenW = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, u, -1, NULL, 0)) == 0) return r; \
894 w = (wchar_t *) z_alloca(sizeof(wchar_t) * lenW); \
895 if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, u, -1, w, lenW) == 0) return r; \
896 }
897
898 /* Do `fopen()` on Windows, assuming `filename` is UTF-8 encoded. Ticket #288, props Marcel */
899 INTERNAL FILE *out_win_fopen(const char *filename, const char *mode) {
900 wchar_t *filenameW, *modeW;
901
902 utf8_to_wide(filename, filenameW, NULL);
903 utf8_to_wide(mode, modeW, NULL);
904
905 return _wfopen(filenameW, modeW);
906 }
907 #endif
908
909 /* Make a directory; already existing dir okay */
910 /* Adapted from https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 and
911 https://nachtimwald.com/2019/07/10/recursive-create-directory-in-c-revisited/ */
912 static int out_maybe_mkdir(const char *path) {
913 #ifdef _WIN32
914 DWORD dwAttrib;
915 wchar_t *pathW;
916
917 /* Assumes `path` is UTF-8 encoded */
918 utf8_to_wide(path, pathW, 0);
919
920 /* Try to make the directory */
921 if (CreateDirectoryW(pathW, NULL) != 0) { /* Non-zero on success */
922 return 0;
923 }
924 /* If it fails for any reason but already exists, fail */
925 if (GetLastError() != ERROR_ALREADY_EXISTS) {
926 return -1;
927 }
928 /* Check if the existing path is a directory */
929 if ((dwAttrib = GetFileAttributesW(pathW)) == (DWORD) -1 || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) {
930 return -1;
931 }
932 #else
933 struct stat st;
934
935 /* Try to make the directory */
936 if (mkdir(path, 0777) == 0) {
937 return 0;
938 }
939 /* If it fails for any reason but already exists, fail */
940 if (errno != EEXIST) {
941 return -1;
942 }
943 /* Check if the existing path is a directory */
944 if (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
945 return -1;
946 }
947 #endif
948
949 return 0;
950 }
951
952 /* Create output file, creating sub-directories if necessary. Returns `fopen()` FILE pointer */
953 INTERNAL FILE *out_fopen(const char filename[256], const char *mode) {
954 FILE *outfile;
955
956 #ifdef _WIN32
957 if (!(outfile = out_win_fopen(filename, mode))) {
958 #else
959 if (!(outfile = fopen(filename, mode))) {
960 #endif
961 char dirname[256];
962 char *d;
963 #ifdef _WIN32
964 char *dirend = strrchr(filename, '\\');
965 if (!dirend) {
966 dirend = strrchr(filename, '/');
967 }
968 #else
969 char *dirend = strrchr(filename, '/');
970 #endif
971 if (!dirend) {
972 return outfile;
973 }
974
975 /* Adapted from https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 */
976 /* Remove filename, leaving directories */
977 memcpy(dirname, filename, dirend - filename);
978 dirname[dirend - filename] = '/';
979 dirname[dirend - filename + 1] = '\0';
980 #ifdef _WIN32
981 for (d = dirname; *d; d++) { /* Convert to Unix separators */
982 if (*d == '\\') {
983 *d = '/';
984 }
985 }
986 #endif
987 for (d = dirname + 1; *d; d++) { /* Ignore slash at start if any */
988 if (*d == '/' && *(d - 1) != '/') { /* Ignore double-slashes */
989 *d = '\0'; /* Temporarily truncate */
990 if (out_maybe_mkdir(dirname) != 0) {
991 return NULL;
992 }
993 *d = '/'; /* Restore */
994 }
995 }
996 #ifdef _WIN32
997 outfile = out_win_fopen(filename, mode);
998 #else
999 outfile = fopen(filename, mode);
1000 #endif
1001 }
1002
1003 return outfile;
1004 }
1005
1006 /* vim: set ts=4 sw=4 et : */