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 }