1 module gen_bindings;
2 
3 import std;
4 
5 alias writeText = std.file.write;
6 
7 void main(string[] args)
8 {
9     void writeUsage()
10     {
11         stdout.writefln("\nUsage: %s [options] <blend2d_repo_path>", args[0].baseName);
12     }
13 
14     if (args.length < 2)
15     {
16         writeUsage;
17         return;
18     }
19 
20     string dirBlend2d = args[1];
21     if (!dirBlend2d.exists || !dirBlend2d.isDir)
22     {
23         stderr.writeln("<blend2d_repo_path> is expected to be an existing directory: ", dirBlend2d);
24         writeUsage;
25         return;
26     }
27 
28     auto gen = new GenBindings(dirBlend2d);
29     gen.bindStatic;
30     gen.bindDynamic;
31     gen.save;
32 }
33 
34 class GenBindings
35 {
36     string dApi = "api.d";
37     string versionStatic = "version(BindBlend2D_Static) ";
38     string[] skipFnList = [
39         "blDefaultApproximationOptions",
40         "blFormatInfo",
41         "blMatrix2DMapPointDArrayFuncs",
42         "blPixelConverterConvert",
43         "blRuntimeAllocImpl",
44         "blRuntimeAllocAlignedImpl",
45         "blRuntimeFreeImpl"
46     ];
47 
48     string cwd;
49     string dirBlend2dSrc;
50     string dirBlend2dSrcBlend2d;
51     string dFile;
52     string[] symbols;
53     string fileSymbols;
54     string fileBindStatic;
55     string fileBindDynamic;
56     Appender!(string[]) bufBindStatic;
57     Appender!(string[]) bufBindDynamic;
58     Appender!(string[]) bufBindSymbol;
59 
60     this(string dirBlend2d)
61     {
62         cwd = getcwd();
63         dirBlend2dSrc = absolutePath(chainPath(dirBlend2d, "src").array, cwd);
64         dirBlend2dSrcBlend2d = absolutePath(chainPath(dirBlend2dSrc, "blend2d").array, cwd);
65         fileSymbols = chainPath(cwd, "symbols.txt").array;
66         string dirBindBC = chainPath(cwd, "../source/bindbc/blend2d").array;
67         fileBindStatic = chainPath(dirBindBC, "bindstatic.d").array;
68         fileBindDynamic = chainPath(dirBindBC, "binddynamic.d").array;
69 
70         bufBindStatic = appender!(string[]);
71         bufBindDynamic = appender!(string[]);
72         bufBindSymbol = appender!(string[]);
73 
74         initialize;
75     }
76 
77     void initialize()
78     {
79         dFile = genDFile();
80         symbols = readText(fileSymbols).splitLines;
81     }
82 
83     string genDFile()
84     {
85         chdir(dirBlend2dSrc);
86         scope(exit) chdir(cwd);
87 
88         auto tmpFile = chainPath(dirBlend2dSrcBlend2d, "blend2d-api.h").array;
89         append(tmpFile, "// The file is generated automatically");
90         scope(exit) tmpFile.remove;
91 
92         auto re = ctRegex!(`#include "(.+)"`);
93         foreach (line; readText("blend2d.h").splitLines)
94         {
95             auto m = matchFirst(line, re);
96             if (m.empty)
97                 continue;
98 
99             append(tmpFile, "\n\n//****** " ~ m[1] ~ " ******//\n\n");
100             append(tmpFile, readText(m[1]));
101         }
102 
103         chdir(dirBlend2dSrcBlend2d);
104 
105         auto preprocessedFile = "blend2d-api.c";
106         auto gccOut = File(preprocessedFile, "w");
107         auto pidGcc = spawnProcess(["gcc", "-E", "-P", tmpFile], std.stdio.stdin, gccOut);
108         enforce(pidGcc.wait() == 0, "gcc preprocessor failed on " ~ tmpFile);
109         gccOut.close();
110         scope(exit) remove(preprocessedFile);
111 
112         string[] txt;
113         txt ~= "#include <stdarg.h>";
114         txt ~= "#include <stdint.h>";
115         auto lines = readText(preprocessedFile).splitLines;
116         foreach (i, line; lines)
117         {
118             if (indexOf(line, "typedef struct BL") != 0)
119                 continue;
120             txt ~= lines[i..$];
121             break;
122         }
123         writeText(preprocessedFile, txt.join("\n"));
124 
125         auto pidDStep = spawnProcess(["dstep",
126             "--output=" ~ dApi,
127             "--collision-action=ignore",
128             "--single-line-function-signatures=true",
129             "--space-after-function-name=false",
130             "--comments=false",
131             preprocessedFile]);
132         enforce(pidDStep.wait() == 0, "dstep failed on " ~ preprocessedFile);
133         scope(exit) remove(dApi);
134 
135         return readText(dApi);
136     }
137 
138     void bindStatic()
139     {
140         bufBindStatic.put(q"EOL
141 module bindbc.blend2d.bindstatic;
142 
143 version(BindBC_Static) version = BindBlend2D_Static;
144 
145 import core.stdc.stdint;
146 import core.stdc.stdarg;
147 
148 auto BL_MAKE_VERSION(uint MAJOR, uint MINOR, uint PATCH)
149 {
150     return (MAJOR << 16) | (MINOR << 8) | PATCH;
151 }
152 
153 enum BL_VERSION = BL_MAKE_VERSION(0, 8, 0);
154 
155 private
156 enum expandEnum(EnumType, string fqnEnumType = EnumType.stringof) = (){
157     string expandEnum = "enum {";
158     foreach(m;__traits(allMembers, EnumType)) {
159         expandEnum ~= m ~ " = " ~ fqnEnumType ~ "." ~ m ~ ",";
160     }
161     expandEnum  ~= "}";
162     return expandEnum;
163 }();
164 
165 struct BLObjectImpl;
166 
167 EOL");
168 
169         string[] enums;
170         auto re = ctRegex!(r"^enum ([^;{]+)$");
171         foreach (line; dFile.splitLines)
172         {
173             auto m = matchFirst(line, re);
174             if (!m.empty)
175                 enums ~= "mixin(expandEnum!" ~ m[1] ~ ");";
176         }
177         enums ~= "";
178         bufBindStatic.put(enums.join("\n"));
179 
180         auto toBeCommentedOut = [
181             ctRegex!(r"^(struct .+;)", "m"),
182             ctRegex!(r"^(union .+;)", "m"),
183             ctRegex!(r"^(.* _Pragma\(.+;)", "m"),
184             ctRegex!(r"^(BLResult blResultFromWinError.+;)", "m"),
185             ctRegex!(r"^(BLResult blResultFromPosixError.+;)", "m"),
186         ];
187 
188         bufBindStatic.put("");
189         bufBindStatic.put("//****** " ~ dApi ~ " ******//");
190         bufBindStatic.put("");
191 
192         foreach (ref rx; toBeCommentedOut)
193             dFile = replaceAll(dFile, rx, "//$&");
194 
195         foreach (ref fn; symbols)
196             dFile = replaceAll(dFile, regex(r"^(.* " ~ fn ~ r"\()", "m"), versionStatic ~ "$&");
197         bufBindStatic.put(dFile.splitLines);
198 
199         bufBindStatic.put("");
200         bufBindStatic.put(versionStatic ~ "version(Windows) BLResult blResultFromWinError(uint e);");
201         bufBindStatic.put(versionStatic ~ "version(Posix) BLResult blResultFromPosixError(int e);");
202     }
203 
204     void bindDynamic()
205     {
206         bufBindDynamic.put(q"EOL
207 module bindbc.blend2d.binddynamic;
208 
209 version(BindBlend2D_Static) {}
210 else version = BindBlend2D_Dynamic;
211 
212 version(BindBlend2D_Dynamic):
213 
214 import core.stdc.stdint;
215 import core.stdc.stdarg;
216 
217 import bindbc.loader;
218 import bindbc.blend2d.types;
219 import bindbc.blend2d.bindstatic;
220 
221 extern(C) @nogc nothrow alias pblDefaultApproximationOptions = const BLApproximationOptions;
222 __gshared pblDefaultApproximationOptions blDefaultApproximationOptions;
223 
224 extern(C) @nogc nothrow alias pblFormatInfo = const(BLFormatInfo)[BL_FORMAT_RESERVED_COUNT];
225 __gshared pblFormatInfo blFormatInfo;
226 
227 extern(C) @nogc nothrow alias pblMatrix2DMapPointDArrayFuncs = BLMapPointDArrayFunc[BL_MATRIX2D_TYPE_MAX_VALUE + 1];
228 __gshared pblMatrix2DMapPointDArrayFuncs blMatrix2DMapPointDArrayFuncs;
229 
230 extern(C) @nogc nothrow alias pblPixelConverterConvert = BLResult function(const(BLPixelConverterCore)* self, void* dstData, intptr_t dstStride, const(void)* srcData, intptr_t srcStride, uint w, uint h, const(BLPixelConverterOptions)* options);
231 __gshared pblPixelConverterConvert blPixelConverterConvert;
232 
233 version(Windows) {
234     extern(C) @nogc nothrow alias pblResultFromWinError = BLResult function(uint e);
235     __gshared pblResultFromWinError blResultFromWinError;
236 }
237 version(Posix) {
238     extern(C) @nogc nothrow alias pblResultFromPosixError = BLResult function(int e);
239     __gshared pblResultFromPosixError blResultFromPosixError;
240 }
241 
242 EOL");
243 
244         auto fns = symbols.filter!(a => skipFnList.find(a).empty);
245         auto re_fns = regex(" (" ~ fns.join("|") ~ r")\(");
246         foreach (line; bufBindStatic.data)
247         {
248             const m = line.matchFirst(re_fns);
249             if (m.empty)
250                 continue;
251 
252             string fn = m[1];
253             string fn_def = line.replaceFirst(fn, "function")[versionStatic.length .. $];
254             bufBindDynamic.put("extern(C) @nogc nothrow alias p" ~ fn ~ " = " ~ fn_def);
255             bufBindDynamic.put("__gshared p" ~ fn ~ " " ~ fn ~ ";");
256             bufBindDynamic.put("");
257             bufBindSymbol.put("    lib.bindSymbol_stdcall(" ~ fn ~ ", \"" ~ fn ~ "\");");
258         }
259 
260         bufBindDynamic.put(q"EOL
261 private {
262     SharedLib lib;
263     Blend2DSupport loadedVersion;
264 }
265 
266 @nogc nothrow:
267 
268 void unloadGLFW()
269 {
270     if(lib != invalidHandle) {
271         lib.unload();
272     }
273 }
274 
275 Blend2DSupport loadedBlend2DVersion() @safe
276 {
277     return loadedVersion;
278 }
279 
280 bool isBlend2DLoaded() @safe
281 {
282     return lib != invalidHandle;
283 }
284 
285 Blend2DSupport loadBlend2D()
286 {
287     version(Windows) {
288         const(char)[][1] libNames = ["blend2d.dll"];
289     }
290     else version(OSX) {
291         const(char)[][1] libNames = [
292             "blend2d.dylib"
293         ];
294     }
295     else version(Posix) {
296         const(char)[][1] libNames = [
297             "blend2d.so"
298         ];
299     }
300     else static assert(0, "bindbc-blend2d is not yet supported on this platform.");
301 
302     Blend2DSupport ret;
303     foreach(name; libNames) {
304         ret = loadBlend2D(name.ptr);
305         if(ret != Blend2DSupport.noLibrary) break;
306     }
307     return ret;
308 }
309 
310 Blend2DSupport loadBlend2D(const(char)* libName)
311 {
312     lib = load(libName);
313     if(lib == invalidHandle) {
314         return Blend2DSupport.noLibrary;
315     }
316 
317     auto errCount = errorCount();
318     loadedVersion = Blend2DSupport.badLibrary;
319 
320     lib.bindSymbol(cast(void**)&blDefaultApproximationOptions, "blDefaultApproximationOptions");
321     lib.bindSymbol(cast(void**)&blFormatInfo, "blFormatInfo");
322     lib.bindSymbol(cast(void**)&blMatrix2DMapPointDArrayFuncs, "blMatrix2DMapPointDArrayFuncs");
323 
324     lib.bindSymbol_stdcall(blPixelConverterConvert, "blPixelConverterConvert");
325 
326     version(Windows) lib.bindSymbol_stdcall(blResultFromWinError, "blResultFromWinError");
327     version(Posix) lib.bindSymbol_stdcall(blResultFromPosixError, "blResultFromPosixError");
328 
329 EOL");
330 
331         bufBindDynamic.put(bufBindSymbol.data.join("\n"));
332 
333         bufBindDynamic.put(q"EOL
334 
335     if(errorCount() != errCount) return Blend2DSupport.badLibrary;
336     else loadedVersion = Blend2DSupport.bl00;
337 
338     return loadedVersion;
339 }
340 EOL");
341 
342     }
343 
344     void save()
345     {
346         writeText(fileBindStatic, bufBindStatic.data.join("\n"));
347         writeText(fileBindDynamic, bufBindDynamic.data.join("\n"));
348     }
349 }