comparison mupdf-source/thirdparty/zint/backend/svg.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 /* svg.c - Scalable Vector Graphics */
2 /*
3 libzint - the open source barcode library
4 Copyright (C) 2009-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 <errno.h>
34 #include <math.h>
35 #include <stdio.h>
36
37 #include "common.h"
38 #include "filemem.h"
39 #include "output.h"
40 #include "fonts/normal_woff2.h"
41 #include "fonts/upcean_woff2.h"
42
43 /* Convert Ultracode rectangle colour to RGB */
44 static void svg_pick_colour(const int colour, char colour_code[7]) {
45 const int idx = colour >= 1 && colour <= 8 ? colour - 1 : 6 /*black*/;
46 static const char rgbs[8][7] = {
47 "00ffff", /* 0: Cyan (1) */
48 "0000ff", /* 1: Blue (2) */
49 "ff00ff", /* 2: Magenta (3) */
50 "ff0000", /* 3: Red (4) */
51 "ffff00", /* 4: Yellow (5) */
52 "00ff00", /* 5: Green (6) */
53 "000000", /* 6: Black (7) */
54 "ffffff", /* 7: White (8) */
55 };
56 strcpy(colour_code, rgbs[idx]);
57 }
58
59 /* Convert text to use HTML entity codes */
60 static void svg_make_html_friendly(const unsigned char *string, char *html_version) {
61
62 for (; *string; string++) {
63 switch (*string) {
64 case '>':
65 strcpy(html_version, "&gt;");
66 html_version += 4;
67 break;
68
69 case '<':
70 strcpy(html_version, "&lt;");
71 html_version += 4;
72 break;
73
74 case '&':
75 strcpy(html_version, "&amp;");
76 html_version += 5;
77 break;
78
79 case '"':
80 strcpy(html_version, "&quot;");
81 html_version += 6;
82 break;
83
84 case '\'':
85 strcpy(html_version, "&apos;");
86 html_version += 6;
87 break;
88
89 default:
90 *html_version++ = *string;
91 break;
92 }
93 }
94
95 *html_version = '\0';
96 }
97
98 /* Helper to output floating point attribute */
99 static void svg_put_fattrib(const char *prefix, const int dp, const float val, struct filemem *fmp) {
100 fm_putsf(prefix, dp, val, fmp);
101 fm_putc('"', fmp);
102 }
103
104 /* Helper to output opacity attribute attribute and close tag (maybe) */
105 static void svg_put_opacity_close(const unsigned char alpha, const float val, const int close, struct filemem *fmp) {
106 if (alpha != 0xff) {
107 svg_put_fattrib(" opacity=\"", 3, val, fmp);
108 }
109 if (close) {
110 fm_putc('/', fmp);
111 }
112 fm_puts(">\n", fmp);
113 }
114
115 INTERNAL int svg_plot(struct zint_symbol *symbol) {
116 static const char normal_font_family[] = "Arimo";
117 static const char upcean_font_family[] = "OCRB";
118 struct filemem fm;
119 struct filemem *const fmp = &fm;
120 float previous_diameter;
121 float radius, half_radius, half_sqrt3_radius;
122 int i;
123 char fgcolour_string[7];
124 char bgcolour_string[7];
125 unsigned char fgred, fggreen, fgblue, fg_alpha;
126 unsigned char bgred, bggreen, bgblue, bg_alpha;
127 float fg_alpha_opacity = 0.0f, bg_alpha_opacity = 0.0f; /* Suppress `-Wmaybe-uninitialized` */
128 int bold;
129
130 struct zint_vector_rect *rect;
131 struct zint_vector_hexagon *hex;
132 struct zint_vector_circle *circle;
133 struct zint_vector_string *string;
134
135 char colour_code[7];
136 int len, html_len;
137
138 const int upcean = is_upcean(symbol->symbology);
139 char *html_string;
140
141 (void) out_colour_get_rgb(symbol->fgcolour, &fgred, &fggreen, &fgblue, &fg_alpha);
142 if (fg_alpha != 0xff) {
143 fg_alpha_opacity = fg_alpha / 255.0f;
144 }
145 sprintf(fgcolour_string, "%02X%02X%02X", fgred, fggreen, fgblue);
146 (void) out_colour_get_rgb(symbol->bgcolour, &bgred, &bggreen, &bgblue, &bg_alpha);
147 if (bg_alpha != 0xff) {
148 bg_alpha_opacity = bg_alpha / 255.0f;
149 }
150 sprintf(bgcolour_string, "%02X%02X%02X", bgred, bggreen, bgblue);
151
152 len = (int) ustrlen(symbol->text);
153 html_len = len + 1;
154
155 for (i = 0; i < len; i++) {
156 switch (symbol->text[i]) {
157 case '>':
158 case '<':
159 case '"':
160 case '&':
161 case '\'':
162 html_len += 6;
163 break;
164 }
165 }
166 if (symbol->output_options & EANUPC_GUARD_WHITESPACE) {
167 html_len += 12; /* Allow for "<" & ">" */
168 }
169
170 html_string = (char *) z_alloca(html_len);
171
172 /* Check for no created vector set */
173 /* E-Mail Christian Schmitz 2019-09-10: reason unknown Ticket #164 */
174 if (symbol->vector == NULL) {
175 return errtxt(ZINT_ERROR_INVALID_DATA, symbol, 681, "Vector header NULL");
176 }
177 if (!fm_open(fmp, symbol, "w")) {
178 return errtxtf(ZINT_ERROR_FILE_ACCESS, symbol, 680, "Could not open SVG output file (%1$d: %2$s)", fmp->err,
179 strerror(fmp->err));
180 }
181
182 /* Start writing the header */
183 fm_puts("<?xml version=\"1.0\" standalone=\"no\"?>\n"
184 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
185 fmp);
186 fm_printf(fmp, "<svg width=\"%d\" height=\"%d\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n",
187 (int) ceilf(symbol->vector->width), (int) ceilf(symbol->vector->height));
188 fm_puts(" <desc>Zint Generated Symbol</desc>\n", fmp);
189 if ((symbol->output_options & EMBED_VECTOR_FONT) && symbol->vector->strings) {
190 /* Split into `puts()` rather than one very large `printf()` */
191 fm_printf(fmp, " <style>@font-face {font-family:\"%s\"; src:url(data:font/woff2;base64,",
192 upcean ? "OCRB" : "Arimo");
193 fm_puts(upcean ? upcean_woff2 : normal_woff2, fmp);
194 fm_puts(");}</style>\n", fmp);
195 }
196 fm_printf(fmp, " <g id=\"barcode\" fill=\"#%s\">\n", fgcolour_string);
197
198 if (bg_alpha != 0) {
199 fm_printf(fmp, " <rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" fill=\"#%s\"",
200 (int) ceilf(symbol->vector->width), (int) ceilf(symbol->vector->height), bgcolour_string);
201 svg_put_opacity_close(bg_alpha, bg_alpha_opacity, 1 /*close*/, fmp);
202 }
203
204 if (symbol->vector->rectangles) {
205 int current_colour = 0;
206 rect = symbol->vector->rectangles;
207 fm_puts(" <path d=\"", fmp);
208 while (rect) {
209 if (current_colour && rect->colour != current_colour) {
210 fm_putc('"', fmp);
211 if (current_colour != -1) {
212 svg_pick_colour(current_colour, colour_code);
213 fm_printf(fmp, " fill=\"#%s\"", colour_code);
214 }
215 svg_put_opacity_close(fg_alpha, fg_alpha_opacity, 1 /*close*/, fmp);
216 fm_puts(" <path d=\"", fmp);
217 }
218 current_colour = rect->colour;
219 fm_putsf("M", 2, rect->x, fmp);
220 fm_putsf(" ", 2, rect->y, fmp);
221 fm_putsf("h", 2, rect->width, fmp);
222 fm_putsf("v", 2, rect->height, fmp);
223 fm_putsf("h-", 2, rect->width, fmp);
224 fm_puts("Z", fmp);
225 rect = rect->next;
226 }
227 fm_putc('"', fmp);
228 if (current_colour != -1) {
229 svg_pick_colour(current_colour, colour_code);
230 fm_printf(fmp, " fill=\"#%s\"", colour_code);
231 }
232 svg_put_opacity_close(fg_alpha, fg_alpha_opacity, 1 /*close*/, fmp);
233 }
234
235 if (symbol->vector->hexagons) {
236 previous_diameter = radius = half_radius = half_sqrt3_radius = 0.0f;
237 hex = symbol->vector->hexagons;
238 fm_puts(" <path d=\"", fmp);
239 while (hex) {
240 if (previous_diameter != hex->diameter) {
241 previous_diameter = hex->diameter;
242 radius = 0.5f * previous_diameter;
243 half_radius = 0.25f * previous_diameter;
244 half_sqrt3_radius = 0.43301270189221932338f * previous_diameter;
245 }
246 if ((hex->rotation == 0) || (hex->rotation == 180)) {
247 fm_putsf("M", 2, hex->x, fmp);
248 fm_putsf(" ", 2, hex->y + radius, fmp);
249 fm_putsf("L", 2, hex->x + half_sqrt3_radius, fmp);
250 fm_putsf(" ", 2, hex->y + half_radius, fmp);
251 fm_putsf("L", 2, hex->x + half_sqrt3_radius, fmp);
252 fm_putsf(" ", 2, hex->y - half_radius, fmp);
253 fm_putsf("L", 2, hex->x, fmp);
254 fm_putsf(" ", 2, hex->y - radius, fmp);
255 fm_putsf("L", 2, hex->x - half_sqrt3_radius, fmp);
256 fm_putsf(" ", 2, hex->y - half_radius, fmp);
257 fm_putsf("L", 2, hex->x - half_sqrt3_radius, fmp);
258 fm_putsf(" ", 2, hex->y + half_radius, fmp);
259 } else {
260 fm_putsf("M", 2, hex->x - radius, fmp);
261 fm_putsf(" ", 2, hex->y, fmp);
262 fm_putsf("L", 2, hex->x - half_radius, fmp);
263 fm_putsf(" ", 2, hex->y + half_sqrt3_radius, fmp);
264 fm_putsf("L", 2, hex->x + half_radius, fmp);
265 fm_putsf(" ", 2, hex->y + half_sqrt3_radius, fmp);
266 fm_putsf("L", 2, hex->x + radius, fmp);
267 fm_putsf(" ", 2, hex->y, fmp);
268 fm_putsf("L", 2, hex->x + half_radius, fmp);
269 fm_putsf(" ", 2, hex->y - half_sqrt3_radius, fmp);
270 fm_putsf("L", 2, hex->x - half_radius, fmp);
271 fm_putsf(" ", 2, hex->y - half_sqrt3_radius, fmp);
272 }
273 fm_putc('Z', fmp);
274 hex = hex->next;
275 }
276 fm_putc('"', fmp);
277 svg_put_opacity_close(fg_alpha, fg_alpha_opacity, 1 /*close*/, fmp);
278 }
279
280 previous_diameter = radius = 0.0f;
281 circle = symbol->vector->circles;
282 while (circle) {
283 if (previous_diameter != circle->diameter) {
284 previous_diameter = circle->diameter;
285 radius = 0.5f * previous_diameter;
286 }
287 fm_puts(" <circle", fmp);
288 svg_put_fattrib(" cx=\"", 2, circle->x, fmp);
289 svg_put_fattrib(" cy=\"", 2, circle->y, fmp);
290 svg_put_fattrib(" r=\"", circle->width ? 3 : 2, radius, fmp);
291
292 if (circle->colour) { /* Legacy - no longer used */
293 if (circle->width) {
294 fm_printf(fmp, " stroke=\"#%s\"", bgcolour_string);
295 svg_put_fattrib(" stroke-width=\"", 3, circle->width, fmp);
296 fm_puts(" fill=\"none\"", fmp);
297 } else {
298 fm_printf(fmp, " fill=\"#%s\"", bgcolour_string);
299 }
300 /* This doesn't work how the user is likely to expect - more work needed! */
301 svg_put_opacity_close(bg_alpha, bg_alpha_opacity, 1 /*close*/, fmp);
302 } else {
303 if (circle->width) {
304 fm_printf(fmp, " stroke=\"#%s\"", fgcolour_string);
305 svg_put_fattrib(" stroke-width=\"", 3, circle->width, fmp);
306 fm_puts(" fill=\"none\"", fmp);
307 }
308 svg_put_opacity_close(fg_alpha, fg_alpha_opacity, 1 /*close*/, fmp);
309 }
310 circle = circle->next;
311 }
312
313 bold = (symbol->output_options & BOLD_TEXT) && !upcean;
314 string = symbol->vector->strings;
315 while (string) {
316 const char *const halign = string->halign == 2 ? "end" : string->halign == 1 ? "start" : "middle";
317 fm_puts(" <text", fmp);
318 svg_put_fattrib(" x=\"", 2, string->x, fmp);
319 svg_put_fattrib(" y=\"", 2, string->y, fmp);
320 fm_printf(fmp, " text-anchor=\"%s\"", halign);
321 if (upcean) {
322 fm_printf(fmp, " font-family=\"%s, monospace\"", upcean_font_family);
323 } else {
324 fm_printf(fmp, " font-family=\"%s, Arial, sans-serif\"", normal_font_family);
325 }
326 svg_put_fattrib(" font-size=\"", 1, string->fsize, fmp);
327 if (bold) {
328 fm_puts(" font-weight=\"bold\"", fmp);
329 }
330 if (string->rotation != 0) {
331 fm_printf(fmp, " transform=\"rotate(%d", string->rotation);
332 fm_putsf(",", 2, string->x, fmp);
333 fm_putsf(",", 2, string->y, fmp);
334 fm_puts(")\"", fmp);
335 }
336 svg_put_opacity_close(fg_alpha, fg_alpha_opacity, 0 /*close*/, fmp);
337 svg_make_html_friendly(string->text, html_string);
338 fm_printf(fmp, " %s\n", html_string);
339 fm_puts(" </text>\n", fmp);
340 string = string->next;
341 }
342
343 fm_puts(" </g>\n"
344 "</svg>\n", fmp);
345
346 if (fm_error(fmp)) {
347 errtxtf(0, symbol, 682, "Incomplete write to SVG output (%1$d: %2$s)", fmp->err, strerror(fmp->err));
348 (void) fm_close(fmp, symbol);
349 return ZINT_ERROR_FILE_WRITE;
350 }
351
352 if (!fm_close(fmp, symbol)) {
353 return errtxtf(ZINT_ERROR_FILE_WRITE, symbol, 684, "Failure on closing SVG output file (%1$d: %2$s)",
354 fmp->err, strerror(fmp->err));
355 }
356
357 return 0;
358 }
359
360 /* vim: set ts=4 sw=4 et : */