1 ///
2 module barcode.svgdraw;
3 
4 import std..string : format, join;
5 import std.conv : text;
6 
7 import barcode.types;
8 
9 ///
10 interface BarCodeSvgDrawer
11 {
12     ///
13     string draw(BarCode bc);
14 }
15 
16 ///
17 class BaseBarCodeSvgDrawer : BarCodeSvgDrawer
18 {
19     ///
20     bool fixSizeMode = false;
21     ///
22     bool withBackground = true;
23 
24     ///
25     float borderX=50.0f, borderY=50.0f;
26     ///
27     float W=10.0f, H=10.0f;
28 
29     string bgColor = "#FFFFFF";
30     string fgColor = "#000000";
31 
32     ///
33     struct DrawData
34     {
35         ///
36         string svgpath;
37         ///
38         long w, h;
39     }
40 
41     DrawData buildPath(BarCode bc)
42     {
43         auto cW = W, cH = H;
44 
45         if (fixSizeMode)
46         {
47             cW /= bc.width;
48             cH /= bc.height;
49         }
50 
51         string[] paths;
52 
53         long start = -1;
54         float len = 0;
55 
56         foreach (size_t y; 0..bc.height)
57             foreach (size_t x; 0..bc.width)
58             {
59                 if (bc[x,y])
60                 {
61                     if (start == -1)
62                         start = x;
63                     len += cW;
64                 }
65 
66                 if ((!bc[x,y] || x == bc.width-1) && start != -1)
67                 {
68                     paths ~= "M%s,%sh%sv%sh-%sz"
69                         .format(start*cW+borderX,
70                                 y*cH+borderY, len, cH, len);
71                     start = -1;
72                     len = 0;
73                 }
74             }
75 
76         long w = cast(long)(bc.width * cW + borderX * 2);
77         long h = cast(long)(bc.height * cH + borderY * 2);
78 
79         return DrawData(paths.join(" "), w, h);
80     }
81 
82     ///
83     string draw(BarCode bc)
84     {
85         string bgStr;
86         if (withBackground)
87             bgStr = `<rect width="100%" height="100%" fill="`~bgColor~`" stroke-width="0"/>`;
88 
89         auto dd = buildPath(bc);
90 
91         return text(`
92             <?xml version="1.0" encoding="UTF-8"?>
93             <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
94             <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 `, dd.w, " ", dd.h, `">
95                 `, bgStr, `
96                 <path d="`, dd.svgpath, `" fill="`, fgColor, `" stroke-width="0"/>
97             </svg>`).flineOffset;
98     }
99 }
100 
101 class PseudoRasterBarCodeSvgDrawer : BaseBarCodeSvgDrawer
102 {
103     override DrawData buildPath(BarCode bc)
104     {
105         auto cW = W, cH = H;
106 
107         if (fixSizeMode)
108         {
109             cW /= bc.width;
110             cH /= bc.height;
111         }
112 
113         string[] paths;
114 
115         long start = -1;
116         float len = 0;
117 
118         foreach (size_t y; 0..bc.height)
119             foreach (size_t x; 0..bc.width)
120             {
121                 if (bc[x,y])
122                     paths ~= "M%s,%sh%sv%sh-%sz"
123                         .format(x*cW+borderX, y*cH+borderY, cW, cH, cW);
124             }
125 
126         long w = cast(long)(bc.width * cW + borderX * 2);
127         long h = cast(long)(bc.height * cH + borderY * 2);
128 
129         return DrawData(paths.join(" "), w, h);
130     }
131 }
132 
133 string flineOffset(string txt) @property @safe
134 {
135     import std..string;
136     import std.algorithm;
137     string[] res;
138     ptrdiff_t offset = -1;
139     foreach (ln; txt.splitLines.map!(a=>a.stripRight))
140     {
141         // skip empty lines
142         auto sln = ln.strip;
143         if (sln.length == 0)
144         {
145             if (res.length) res ~= "";
146             continue;
147         }
148 
149         if (offset == -1)
150             offset = ln.length - sln.length;
151 
152         res ~= ln[min(offset, ln.length - sln.length)..$];
153     }
154     return res.join("\n");
155 }
156 
157 @safe
158 unittest
159 {
160     enum txt = ("    \n            some  \n            text   \n  "~
161                  "   \n                here   \n\n            end  ").flineOffset;
162     enum exp = "some\ntext\n\n    here\n\nend";
163 
164     static assert(txt == exp);
165 }
166 
167 @safe
168 unittest
169 {
170     enum txt = `
171     some text
172    with
173   wrong formated
174       lines`.flineOffset;
175     enum exp = "some text\n" ~
176                "with\n" ~
177                "wrong formated\n" ~
178                "  lines";
179     static assert(txt == exp);
180 }