pdftohtml data urls: adding InMemoryFile utility class
diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt
index 34d9647..3516479 100644
--- a/utils/CMakeLists.txt
+++ b/utils/CMakeLists.txt
@@ -121,6 +121,7 @@
 
 # pdftohtml
 set(pdftohtml_SOURCES ${common_srcs}
+  InMemoryFile.cc
   pdftohtml.cc
   HtmlFonts.cc
   HtmlLinks.cc
diff --git a/utils/InMemoryFile.cc b/utils/InMemoryFile.cc
new file mode 100644
index 0000000..d4ed0f4
--- /dev/null
+++ b/utils/InMemoryFile.cc
@@ -0,0 +1,75 @@
+//========================================================================
+//
+// InMemoryFile.cc
+//
+// Represents a file in-memory with GNU's stdio wrappers.
+// NOTE as of this writing, open() depends on the glibc 'fopencookie'
+// extension and is not supported on other platforms. The
+// HAVE_IN_MEMORY_FILE macro is intended to reflect whether this class is
+// usable.
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright (C) 2018 Greg Knight <lyngvi@gmail.com>
+//
+//========================================================================
+
+#include "InMemoryFile.h"
+
+#include <string.h>
+#include <sstream>
+
+InMemoryFile::InMemoryFile()
+    : iohead(0)
+    , fptr(nullptr)
+{
+}
+
+ssize_t InMemoryFile::_read(char* buf, size_t sz)
+{
+    auto toRead = std::min<size_t>(data.size() - iohead, sz);
+    memcpy(&buf[0], &data[iohead], toRead);
+    iohead += toRead;
+    return toRead;
+}
+
+ssize_t InMemoryFile::_write(const char* buf, size_t sz)
+{
+    if (iohead + sz > data.size())
+        data.resize(iohead + sz);
+    memcpy(&data[iohead], buf, sz);
+    iohead += sz;
+    return sz;        
+}
+
+int InMemoryFile::_seek(off64_t* offset, int whence)
+{
+    switch (whence) {
+        case SEEK_SET: iohead  = (*offset); break;
+        case SEEK_CUR: iohead += (*offset); break;
+        case SEEK_END: iohead -= (*offset); break;
+    }
+    (*offset) = std::min<off64_t>(std::max<off64_t>(iohead, 0l), data.size());
+    iohead = static_cast<size_t>(*offset);
+    return 0;
+}
+
+FILE* InMemoryFile::open(const char* mode)
+{
+#if HAVE_IN_MEMORY_FILE_FOPENCOOKIE
+    if (fptr != nullptr) {
+        fprintf(stderr, "InMemoryFile: BUG: Why is this opened more than once?");
+        return nullptr; // maybe there's some legit reason for it, whoever comes up with one can remove this line
+    }
+    static cookie_io_functions_t methods = {
+        /* .read = */ [](void* self, char* buf, size_t sz) { return ((InMemoryFile*)self)->_read(buf, sz); },
+        /* .write = */ [](void* self, const char* buf, size_t sz) { return ((InMemoryFile*)self)->_write(buf, sz); },
+        /* .seek = */ [](void* self, off64_t* offset, int whence) { return ((InMemoryFile*)self)->_seek(offset, whence); },
+        /* .close = */ [](void* self) { ((InMemoryFile*)self)->fptr = nullptr; return 0; },
+    };
+    return fptr = fopencookie(this, mode, methods);
+#else
+    fprintf (stderr, "If you can read this, your platform does not support the features necessary to achieve your goals.");
+    return nullptr;
+#endif
+}
diff --git a/utils/InMemoryFile.h b/utils/InMemoryFile.h
new file mode 100644
index 0000000..6af7d50
--- /dev/null
+++ b/utils/InMemoryFile.h
@@ -0,0 +1,51 @@
+//========================================================================
+//
+// InMemoryFile.h
+//
+// Represents a file in-memory with GNU's stdio wrappers.
+// NOTE as of this writing, open() depends on the glibc 'fopencookie'
+// extension and is not supported on other platforms. The
+// HAVE_IN_MEMORY_FILE macro is intended to reflect whether this class is
+// usable.
+//
+// This file is licensed under the GPLv2 or later
+//
+// Copyright (C) 2018 Greg Knight <lyngvi@gmail.com>
+//
+//========================================================================
+
+#ifndef IN_MEMORY_FILE_H
+#define IN_MEMORY_FILE_H
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#if defined(__USE_GNU) && !defined(__ANDROID_API__)
+#  define HAVE_IN_MEMORY_FILE (1)
+#  define HAVE_IN_MEMORY_FILE_FOPENCOOKIE (1) // used internally
+#endif
+
+class InMemoryFile {
+private:
+    size_t iohead;
+    std::vector<char> data;
+    FILE *fptr;
+
+    ssize_t _read(char* buf, size_t sz);
+    ssize_t _write(const char* buf, size_t sz);
+    int _seek(off64_t* offset, int whence);
+
+public:
+    InMemoryFile();
+
+public:
+    /* Returns a file handle for this file. This is scoped to this object
+     * and must be fclosed() by the caller before destruction. */
+    FILE* open(const char* mode);
+
+    const std::vector<char>& getBuffer() const
+        { return data; }
+};
+
+#endif // IN_MEMORY_FILE_H