1 /* Utility functions, mixins, constants and public imports of generally useful
2  * Phobos modules.  These are not meant to be part of the public API,
3  * at least for now.
4  *
5  * Copyright (C) 2010 David Simcha
6  *
7  * License:
8  *
9  * Boost Software License - Version 1.0 - August 17th, 2003
10  *
11  * Permission is hereby granted, free of charge, to any person or organization
12  * obtaining a copy of the software and accompanying documentation covered by
13  * this license (the "Software") to use, reproduce, display, distribute,
14  * execute, and transmit the Software, and to prepare derivative works of the
15  * Software, and to permit third-parties to whom the Software is furnished to
16  * do so, all subject to the following:
17  *
18  * The copyright notices in the Software and this entire statement, including
19  * the above license grant, this restriction and the following disclaimer,
20  * must be included in all copies of the Software, in whole or in part, and
21  * all derivative works of the Software, unless such copies or derivative
22  * works are solely in the form of machine-executable object code generated by
23  * a source language processor.
24  *
25  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27  * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
28  * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
29  * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
30  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
31  * DEALINGS IN THE SOFTWARE.
32  */
33 module plot2kill.util;
34 
35 public import std.conv, std.math, std.array, std.range, std.algorithm,
36     std.exception, std.traits, std.stdio, std..string, core.memory, std.path,
37     std.typecons, std.functional, std.system;
38 
39 private import etc.c.zlib;
40 
41 // For NoCopy but due to import system bugs importing the whole module works
42 // better.
43 import plot2kill.figure;
44 
45 version(Windows) {
46     // This should be available on all 32-bit versions of Windows.  It was
47     // standard since Windows 3.1.
48     immutable string defaultFont = "Arial";
49 } else version(Posix) {
50     // This is an X11 core font, so it should be on pretty much any
51     // non-embedded Posix system.
52     immutable string defaultFont = "Helvetica";
53 } else {
54     // Are non-ancient Macs Posix?  I hope so.
55     static assert(0, "Don't know what a sane default font is for this platform.");
56 }
57 
58 package double toSigFigs(double num, int nSigFigs)
59 in {
60     assert(nSigFigs > 0);
61 } body {
62     if(num == 0 || !isFinite(num)) {
63         return num;
64     }
65 
66     auto nZeros = to!int(log10(num)) - nSigFigs + 1;
67     auto divisor = pow(10.0, nZeros);
68     auto rounded = round(num / divisor);
69     if(rounded == 0) {
70         divisor /= 10;
71         rounded = round(num / divisor);
72     }
73 
74     return rounded * divisor;
75 }
76 
77 unittest {
78     assert(approxEqual(toSigFigs(0.001325356446, 1), 0.001));
79     assert(approxEqual(toSigFigs(PI, 3), 3.14));
80 }
81 
82 // TODO:  Refactor this messy mixin into a struct.  Unfortunately, though,
83 //        this refactoring will have tons of ripple effects in plot.d, which
84 //        is why I keep putting it off.
85 package enum toPixels = q{
86     double toPixelsX(double inUnits) {
87         immutable xRange = rightLim - leftLim;
88         
89         // This handles the case of plots that are infinitely small in one 
90         // direction.  Using <= instead of == to handle rounding error.
91         if(xRange <= 0) return leftMargin;
92 
93         immutable fract = (inUnits - leftLim) / xRange;
94         immutable ret = (fract * plotWidth) + leftMargin;
95         return ret;
96     }
97 
98     double toPixelsY(double inUnits) {
99         immutable yRange = upperLim - lowerLim;
100         
101         // This handles the case of plots that are infinitely small in one 
102         // direction.  Using <= instead of == to handle rounding error.
103         if(yRange <= 0) return topMargin;
104 
105         immutable fract = (upperLim - inUnits) / yRange;
106         immutable ret = (fract * plotHeight) + topMargin;
107         return ret;
108     }
109 };
110 
111 package enum drawErrorMixin = q{
112     void drawErrorBar(Pen pen, double x, double from, double to, double width) {
113         immutable xPixels = toPixelsX(x);
114         immutable fromPixels = toPixelsY(from);
115         immutable toPixels = toPixelsY(to);
116         immutable horizLeft = toPixelsX(x - width / 2);
117         immutable horizRight = toPixelsX(x + width / 2);
118 
119         form.drawClippedLine(pen, PlotPoint(xPixels, fromPixels),
120                              PlotPoint(xPixels, toPixels));
121         form.drawClippedLine(pen, PlotPoint(horizLeft, toPixels),
122                              PlotPoint(horizRight, toPixels));
123     }
124 };
125 
126 /*
127 Replicates the behavior of the deprecated std.path.getExt() by dropping 
128 the dot.
129 */
130 inout(char)[] extensionNoDot(inout(char)[] filename) {
131     auto ret = extension(filename);
132     if(ret.length > 0 && ret[0] == '.') {
133         ret = ret[1..$];
134     }
135 
136     return ret;
137 }
138 
139 /* Converts an array of doubles to strings, rounding off numbers very close
140  * to zero.
141  */
142 package string[] doublesToStrings(double[] arr) {
143     auto ret = new string[arr.length];
144     foreach(i, elem; arr) {
145         ret[i] = (abs(elem) > 1e-10) ? to!string(elem) : "0";
146     }
147     return ret;
148 }
149 
150 double[] toDoubleArray(R)(R range) {
151     static if(is(R == NoCopy)) {
152         return range.data;
153     } else {
154         double[] ret;
155         static if(std.range.hasLength!R) {{
156             ret.length = range.length;
157             size_t i = 0;
158             foreach(elem; range) {
159                 ret[i] = elem;
160                 i++;
161             }
162         }} else {
163             foreach(elem; range) {
164                 ret ~= elem;
165             }
166         }
167 
168         return ret;
169     }
170 }
171 
172 package bool nullOrInit(T)(T arg) {
173     static if(is(T == class)) {
174         return arg is null;
175     } else {
176         // Can't just check if arg == T.init because NaNs could be involved.
177         import core.stdc..string;
178         T init;
179         return memcmp(&arg, &init, T.sizeof) == 0;
180     }
181 }
182 
183 // For drawing columnar text if there's no support for real rotated text.
184 package string addNewLines(string input) {
185     if(input.empty) {
186         return null;
187     }
188 
189     string ret;
190     foreach(dchar elem; input) {
191         ret ~= elem;
192         ret ~= '\n';
193     }
194 
195     return ret[0..$ - 1];
196 }
197 
198 package struct PlotPoint {
199     double x;
200     double y;
201 }
202 
203 package struct PlotRect {
204     double x;
205     double y;
206     double width;
207     double height;
208 }
209 
210 void enforceSane(string file = __FILE__, int line = __LINE__)(PlotRect r) {
211     if(!(r.x >= 0 && r.y >= 0 && r.width >= 0 && r.height >= 0)) {
212         throw new Exception(text("Bad rectangle line ", line, " file ", file,
213             ":  ", r));
214     }
215 }
216 
217 package struct PlotSize {
218     double width;
219     double height;
220 }
221 
222 version(dfl) {
223     private {
224         static import core.stdc.stdlib;  // For malloc, free.
225 
226         // Used for writeBitmap.  This stuff was obtained from Wikipedia's
227         // documentation of the BMP file format.
228         immutable ubyte[2] magicNum = [0x42, 0x4D];
229         immutable ubyte[4] wasteSpace = [0, 0, 0, 0];
230         immutable ubyte[4] offset = [0x36, 0, 0, 0];
231         immutable ubyte[4] headerBytesLeft = [0x28, 0, 0, 0];
232         immutable ubyte[2] colorPlanes = [0x1, 0];
233         immutable ubyte[2] bitsPerPixel = [0x18, 0];
234         immutable ubyte[4] noCompression = [0, 0, 0, 0];
235         immutable ubyte[4] hRes = [0, 0, 0, 0];
236         immutable ubyte[4] vRes = hRes;
237         immutable ubyte[4] paletteColors = [0, 0, 0, 0];
238         immutable ubyte[4] importantColors = [0, 0, 0, 0];
239         immutable ubyte[1] zeroUbyte = [0];
240         enum headerSize = 54;
241 
242         // Since writing everything directly to a file is too slow, we write
243         // to this buffer and then output this buffer to a file in one go.
244         struct Buf {
245             ubyte[] arr;
246             size_t writeIndex;
247 
248             this(int size) {
249                 arr = (cast(ubyte*) core.stdc.stdlib.malloc(size))[0..size];
250             }
251 
252             @disable this(this) {}
253 
254             ~this() {
255                 core.stdc.stdlib.free(cast(void*) arr.ptr);
256             }
257 
258             void rawWrite(const ubyte[] writeThis) {
259                 assert(writeThis.length <= arr.length - writeIndex);
260                 arr[writeIndex..writeIndex + writeThis.length] = writeThis[];
261                 writeIndex += writeThis.length;
262             }
263         }
264     }
265 }
266 
267 // Write an array of pixels to a .bmp file.  Used to implement saving on DFL.
268 //
269 // BUGS:  Since for now this is only for saving on DFL, which is tied to
270 //        Windows, this function assumes it will be running on a little
271 //        endian platform.
272 //
273 //        Only supports 24-bit bitmaps.
274 void writeBitmap(Pixel)(Pixel[] pix, File handle, int width, int height) {
275     enforce(height > 0);
276     enforce(width > 0);
277     enforce(pix.length == width * height);
278 
279     // TODO:  Make this support stuff other than little endian.
280     static ubyte[] toUbyteArr(I)(ref I i) {
281         return (cast(ubyte*) &i)[0..I.sizeof];
282     }
283 
284     immutable rowSizeRaw = width * 3;
285     int rowSizeAligned = rowSizeRaw;  // Rows have to be 4-byte aligned.
286     int paddingBytes = 0;
287     while(rowSizeAligned % 4 != 0) {
288         rowSizeAligned++;
289         paddingBytes++;
290     }
291 
292     immutable int bitmapDataSize = rowSizeAligned * height;
293     immutable int fileSize = bitmapDataSize + headerSize;
294 
295     auto buf = Buf(fileSize);
296 
297     buf.rawWrite(magicNum[]);
298     buf.rawWrite(toUbyteArr(fileSize));
299     buf.rawWrite(wasteSpace[]);
300     buf.rawWrite(offset[]);
301     buf.rawWrite(headerBytesLeft[]);
302     buf.rawWrite(toUbyteArr(width));
303     buf.rawWrite(toUbyteArr(height));
304     buf.rawWrite(colorPlanes[]);
305     buf.rawWrite(bitsPerPixel[]);
306     buf.rawWrite(noCompression[]);
307     buf.rawWrite(toUbyteArr(bitmapDataSize));
308     buf.rawWrite(hRes[]);
309     buf.rawWrite(vRes[]);
310     buf.rawWrite(paletteColors[]);
311     buf.rawWrite(importantColors[]);
312 
313     // Start of bitmap data.
314     foreach(row; 0..height) {
315         auto rowData = pix[width * row..width * (row + 1)];
316         foreach(pixel; rowData) {
317             buf.rawWrite(toUbyteArr(pixel.b));
318             buf.rawWrite(toUbyteArr(pixel.g));
319             buf.rawWrite(toUbyteArr(pixel.r));
320         }
321 
322         foreach(i; 0..paddingBytes) {
323             buf.rawWrite(zeroUbyte[]);
324         }
325     }
326 
327     handle.rawWrite(buf.arr);
328     handle.flush();
329 }
330 
331 // Splits a stirng by a comma delimiter, but ignores escaped delimiters, and
332 // removes escape characters.
333 string[] splitEscape(string input) {
334     bool inEscape;
335     string[] ret;
336     string cur;
337 
338     foreach(i, char c; input) {
339         if(c == '\\' && !inEscape) {
340             inEscape = true;
341             continue;
342         }
343 
344         if(c == ',' && !inEscape) {
345             ret ~= cur.strip();
346             cur = null;
347             continue;
348         }
349 
350         cur ~= c;
351         inEscape = false;
352     }
353 
354     if(cur.length) ret ~= cur.strip();
355     return ret;
356 }
357 
358 unittest {
359     assert(splitEscape(r"Testing\, Separation, Using \\ backslashes") ==
360         [r"Testing, Separation", r"Using \ backslashes"]);
361 }
362 
363 // This contains the necessary machinations for writing gzip files.  It's
364 // kinda quick and dirty b/c it was only intended for writing svgz files.
365 package struct Gzip {
366 private:
367     gzFile gz;
368 
369 public:
370     this(string filename) {
371         gz = gzopen((filename ~ '\0').dup.ptr, "wb9\0".dup.ptr);
372     }
373 
374     void finish() {
375         int err = gzclose(gz);
376         enforce(err == 0, "gzip write unsuccessful.  (Error code "
377             ~ to!string(err));
378     }
379 
380     void addData(void[] buf) {
381         auto bytesWritten = gzwrite(gz, buf.ptr, cast(uint) buf.length);
382         enforce(bytesWritten == buf.length,
383             "gzip write unsuccessful.");
384     }
385 }