pdfium 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +9 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +68 -0
  8. data/Rakefile +62 -0
  9. data/ext/pdfium_ext/bookmark.cc +221 -0
  10. data/ext/pdfium_ext/buffer_file_write.hpp +27 -0
  11. data/ext/pdfium_ext/document.cc +268 -0
  12. data/ext/pdfium_ext/document.h +66 -0
  13. data/ext/pdfium_ext/document_wrapper.cc +63 -0
  14. data/ext/pdfium_ext/document_wrapper.h +56 -0
  15. data/ext/pdfium_ext/extconf.h +3 -0
  16. data/ext/pdfium_ext/extconf.rb +76 -0
  17. data/ext/pdfium_ext/image.cc +332 -0
  18. data/ext/pdfium_ext/page.cc +392 -0
  19. data/ext/pdfium_ext/page.h +5 -0
  20. data/ext/pdfium_ext/page_object_wrapper.cc +38 -0
  21. data/ext/pdfium_ext/page_object_wrapper.h +27 -0
  22. data/ext/pdfium_ext/page_wrapper.cc +86 -0
  23. data/ext/pdfium_ext/page_wrapper.h +37 -0
  24. data/ext/pdfium_ext/pdfium.cc +115 -0
  25. data/ext/pdfium_ext/pdfium.h +69 -0
  26. data/lib/pdfium.rb +15 -0
  27. data/lib/pdfium/bookmark_list.rb +28 -0
  28. data/lib/pdfium/bounding_box.rb +16 -0
  29. data/lib/pdfium/image_list.rb +21 -0
  30. data/lib/pdfium/page_list.rb +36 -0
  31. data/lib/pdfium/page_sizes.rb +7 -0
  32. data/lib/pdfium/version.rb +4 -0
  33. data/pdfium.gemspec +29 -0
  34. data/test/benchmark-docsplit.rb +41 -0
  35. data/test/bookmarks_list_spec.rb +26 -0
  36. data/test/bookmarks_spec.rb +34 -0
  37. data/test/debug.rb +24 -0
  38. data/test/document_spec.rb +49 -0
  39. data/test/image_list_spec.rb +18 -0
  40. data/test/image_spec.rb +53 -0
  41. data/test/page_list_spec.rb +24 -0
  42. data/test/page_spec.rb +91 -0
  43. data/test/pdfium_spec.rb +15 -0
  44. data/test/profile.rb +29 -0
  45. data/test/spec_helper.rb +31 -0
  46. metadata +202 -0
@@ -0,0 +1,66 @@
1
+ #ifndef __DOCUMENT_H__
2
+ #define __DOCUMENT_H__
3
+
4
+ #include "pdfium.h"
5
+
6
+ /* // https://redmine.ruby-lang.org/issues/6292 */
7
+
8
+ // Ruby will call dispose of the Page and DOCUMENT objects in whatever order it
9
+ // wishes to. This is problematic for PDFium. If a Document object is closed whie
10
+ // pages are still open, and then the Pages are closed later, it will segfault.
11
+ //
12
+ // To work around this, the Document keeps a reference to all open Pages. When a Page
13
+ // is deleted, it's destructor calls releasePage on the Document object.
14
+ //
15
+ // It does this so that it can keep track of all the Page objects that are in use
16
+ // and only release it's memory and close it's FPDF_DOCUMENT once all the pages
17
+ // are no longer used.
18
+ //
19
+ // It's reasonably safe to do so. Since we're only use the Document/Page classes
20
+ // from Ruby and control how they're called.
21
+ //
22
+ // A future improvement would be to write a custom smart pointer supervisor class
23
+ // to manage the interplay between the Document and Page objects
24
+ //
25
+ // Beware! As a side affect of the above, this class calls "delete this" on itself.
26
+ // Therefore it must be allocated on the heap (i.e. "new Document"),
27
+ // and not as part of an array (not new[]).
28
+ //
29
+
30
+ /* class Document { */
31
+
32
+ /* public: */
33
+ /* static void Initialize(); */
34
+
35
+ /* // an empty constructor. Ruby's allocate object doesn't have any arguments */
36
+ /* // so the Document allocation needs to function in the same manner */
37
+ /* Document(); */
38
+
39
+ /* bool initialize(const char* file); */
40
+
41
+ /* bool isValid(); */
42
+
43
+ /* int pageCount(); */
44
+
45
+ /* // Page* getPage(int page_index); */
46
+ /* void retain(Page* page); */
47
+ /* void release(Page* page); */
48
+
49
+ /* void markUnused(); */
50
+
51
+ /* FPDF_DOCUMENT pdfiumDocument(); */
52
+
53
+ /* ~Document(); */
54
+
55
+ /* private: */
56
+
57
+
58
+ /* std::unordered_set<Page*> _pages; */
59
+ /* bool _in_use; */
60
+ /* FPDF_DOCUMENT _document; */
61
+ /* void maybeKillSelf(); */
62
+
63
+ /* }; */
64
+
65
+
66
+ #endif // __DOCUMENT_H__
@@ -0,0 +1,63 @@
1
+ #include "document_wrapper.h"
2
+
3
+
4
+ DocumentWrapper::DocumentWrapper()
5
+ : document(0), _in_use(true)
6
+ { }
7
+
8
+ // Mark the Document object as no longer in use. At this
9
+ // point it may be freed once all Pages are also not
10
+ // in use
11
+ void
12
+ DocumentWrapper::markUnused(){
13
+ _in_use = false;
14
+ this->maybeKillSelf();
15
+ }
16
+
17
+
18
+ // a utility method to extract the reference to the FPDF_DOCUMENT from the Ruby/C++ wrapping
19
+ CPDF_Document*
20
+ RB2DOC(VALUE self) {
21
+ DocumentWrapper* doc;
22
+ Data_Get_Struct(self, DocumentWrapper, doc);
23
+ return doc->document;
24
+ }
25
+
26
+
27
+ // Retains a copy of the page, which will prevent
28
+ // the Document from being destroyed until the release()
29
+ // is called for the page
30
+ void
31
+ DocumentWrapper::retain(void *child){
32
+ _children.insert(child);
33
+ }
34
+
35
+ // Marks a page as no longer in use.
36
+ // Removes the page from the _pages set,
37
+ // If the page was the last one in the set and it's now empty,
38
+ // and the Document object is also no longer in use, then destroys the Document object
39
+ void
40
+ DocumentWrapper::release(void *child){
41
+ DEBUG_MSG("Release Doc Child: " << child);
42
+ _children.erase(child);
43
+ this->maybeKillSelf();
44
+ }
45
+
46
+
47
+ // Test if the Document is not in use and there are no pages
48
+ // that are still retained
49
+ void
50
+ DocumentWrapper::maybeKillSelf(){
51
+ DEBUG_MSG("Testing if killing Document: " << this);
52
+ if (_children.empty() && !_in_use){
53
+ DEBUG_MSG("Killing..");
54
+ delete this;
55
+ }
56
+ }
57
+
58
+
59
+ DocumentWrapper::~DocumentWrapper(){
60
+ if (document){ // the pdf might not have opened successfully
61
+ FPDF_CloseDocument(document);
62
+ }
63
+ }
@@ -0,0 +1,56 @@
1
+ #ifndef __DOCUMENT_WRAPPER_H__
2
+ #define __DOCUMENT_WRAPPER_H__
3
+ extern "C" {
4
+ #include "ruby.h"
5
+ }
6
+
7
+ #include "pdfium.h"
8
+ #include "fpdf_ext.h"
9
+ #include <unordered_set>
10
+ /*
11
+ +---------------------------------------------------------------------------------------------+
12
+ | |
13
+ | This is a lightweight wrapper that mediates between |
14
+ | CPDF_Document and all the other types that depend on it |
15
+ | |
16
+ | Ruby will dispose of the Document and it's child objects objects in whatever order it |
17
+ | wishes to. This is problematic for PDFium. For instance, if a Document object |
18
+ | is closed whie pages are still open, and then the Pages are closed later, it will segfault. |
19
+ | |
20
+ | To work around this, when a dependent object is created, it calls retain on |
21
+ | the DocumentWrapper. Then when Ruby garbage collects a dependent object, |
22
+ | it's destructor calls release on the Document object. |
23
+ | |
24
+ | When Ruby GC's the DocumentWrapper itself, it checks to see if any objects are still |
25
+ | retained. If there are, it does not delete itself until they are all removed. |
26
+ | |
27
+ | Beware! As a side affect of the above, this class calls "delete this" on itself. |
28
+ | Therefore it must be allocated on the heap. (i.e. "new DocumentWrapper"), |
29
+ | and not as part of an array (not new[]). |
30
+ +---------------------------------------------------------------------------------------------+
31
+ */
32
+
33
+ class DocumentWrapper {
34
+
35
+ public:
36
+ DocumentWrapper();
37
+
38
+ void retain(void *child);
39
+ void release(void *child);
40
+
41
+ void markUnused();
42
+
43
+ ~DocumentWrapper();
44
+
45
+ CPDF_Document *document;
46
+
47
+ private:
48
+
49
+ bool _in_use;
50
+ void maybeKillSelf();
51
+
52
+ std::unordered_set<void*> _children;
53
+
54
+ };
55
+
56
+ #endif // __DOCUMENT_WRAPPER_H__
@@ -0,0 +1,3 @@
1
+ #ifndef EXTCONF_H
2
+ #define EXTCONF_H
3
+ #endif
@@ -0,0 +1,76 @@
1
+ require "mkmf"
2
+ require 'rbconfig'
3
+
4
+ def existing(dirs)
5
+ dirs.select{|dir| Dir.exist?(dir) }
6
+ end
7
+
8
+ LIB_DIRS=[]
9
+ # if ENV['PDFIUM']
10
+ # LIB_DIRS = [ "#{ENV['PDFIUM']}/out/Debug/lib.target" ]
11
+ # HEADER_DIRS = [
12
+ # "#{ENV['PDFIUM']}/fpdfsdk/include",
13
+ # "#{ENV['PDFIUM']}/core/include",
14
+ # "#{ENV['PDFIUM']}"
15
+ # ]
16
+
17
+ # else
18
+ # LIB_DIRS = [
19
+ # "/usr/local/lib/pdfium",
20
+ # "/usr/lib/pdfium"
21
+ # ]
22
+
23
+ # HEADER_DIRS = [
24
+ # "/usr/include/pdfium",
25
+ # "/usr/local/include/pdfium",
26
+ # "/usr/local/include/pdfium/fpdfsdk/include",
27
+ # "/usr/local/include/pdfium/core/include"
28
+ # ]
29
+ # end
30
+
31
+ HEADER_DIRS=[
32
+ "/home/nas/pdfium/deb-package/pdfium/fpdfsdk/include",
33
+ "/home/nas/pdfium/deb-package/pdfium/core/include",
34
+ "/home/nas/pdfium/deb-package/pdfium"
35
+ ]
36
+
37
+ have_library('pthread')
38
+
39
+ DEBUG = ENV['DEBUG'] == '1'
40
+
41
+ $CPPFLAGS += " -Wall "
42
+ $CPPFLAGS += " -g" #if DEBUG
43
+
44
+ # The order that the libs are listed matters for Linux!
45
+ # to debug missing symbols you can run:
46
+ # for l in `ls /usr/lib/pdfium/*.a`; do echo $l; nm $l | grep '<missing symbol>'; done
47
+ # The listing with a "T" contains the symbol, the ones with a "U"
48
+ # depend on it. The "U" libs must come after the "T"
49
+ # LIBS=%w{
50
+ # javascript bigint freetype fpdfdoc fpdftext formfiller icudata icuuc
51
+ # icui18n v8_libbase v8_base v8_snapshot v8_libplatform jsapi pdfwindow fxedit
52
+ # fxcrt fxcodec fpdfdoc fdrm fxge fpdfapi freetype pdfium
53
+ # pthread freeimage
54
+ # }
55
+ LIBS=%w{pdfium freeimage}
56
+
57
+ dir_config("libs", existing(HEADER_DIRS), existing(LIB_DIRS))
58
+
59
+ LIBS.each do | lib |
60
+ have_library(lib) or abort "Didn't find library lib#{lib}"
61
+ end
62
+
63
+ if RUBY_PLATFORM =~ /darwin/
64
+ have_library('objc')
65
+ FRAMEWORKS = %w{AppKit CoreFoundation}
66
+ $LDFLAGS << FRAMEWORKS.map { |f| " -framework #{f}" }.join
67
+ else
68
+ $CPPFLAGS += " -fPIC"
69
+ end
70
+
71
+ $CPPFLAGS += " -std=c++11"
72
+ $defs.push "-DDEBUG=1" if DEBUG
73
+
74
+ create_header
75
+
76
+ create_makefile "pdfium_ext"
@@ -0,0 +1,332 @@
1
+ #include "pdfium.h"
2
+ #include <cstdint>
3
+ #include <cstring>
4
+
5
+ /////////////////////////////////////////////////////////////////////////
6
+ // The Image class
7
+ /////////////////////////////////////////////////////////////////////////
8
+ /*
9
+ * Document-class: PDFium::Image
10
+ *
11
+ * A Image can represent either a Page that
12
+ * has been rendered to a Image via Page#as_image
13
+ *
14
+ * Or an embedded image on a Page, obtained via Page#images
15
+ */
16
+
17
+
18
+ static void
19
+ image_gc_free(ImageWrapper *img) {
20
+ delete img;
21
+ }
22
+
23
+ static VALUE
24
+ image_allocate(VALUE klass) {
25
+ auto img = new ImageWrapper;
26
+ return Data_Wrap_Struct(klass, NULL, image_gc_free, img );
27
+ }
28
+
29
+ /*
30
+ * call-seq:
31
+ * Image.new -> Image
32
+ *
33
+ * Initializes an image
34
+ */
35
+ VALUE
36
+ image_initialize(int argc, VALUE *argv, VALUE self){
37
+
38
+ VALUE rb_page, rb_options;
39
+ rb_scan_args(argc,argv,"1:", &rb_page, &rb_options);
40
+
41
+ ImageWrapper *img;
42
+ Data_Get_Struct(self, ImageWrapper, img);
43
+
44
+ PageWrapper *pg;
45
+ Data_Get_Struct(rb_page, PageWrapper, pg);
46
+ img->wrap(pg);
47
+
48
+ if (NIL_P(rb_options)){
49
+ rb_options=rb_hash_new();
50
+ }
51
+ VALUE rb_width = RB::get_option(rb_options, "width");
52
+ if (!NIL_P(rb_width)){
53
+ rb_iv_set(self, "@width", rb_width);
54
+ }
55
+ VALUE rb_height = RB::get_option(rb_options, "height");
56
+ if (!NIL_P(rb_height)){
57
+ rb_iv_set(self, "@height", rb_height);
58
+ }
59
+
60
+ VALUE rb_bounds = RB::get_option(rb_options, "bounds");
61
+ VALUE rb_page_index = RB::get_option(rb_options, "index");
62
+ if (!NIL_P(rb_page_index)){
63
+ rb_iv_set(self, "@index", rb_page_index);
64
+ }
65
+ VALUE pg_object_index = RB::get_option(rb_options, "object_index");
66
+ if (NIL_P(rb_bounds) && NIL_P(pg_object_index)){
67
+ rb_raise(rb_eArgError, ":bounds or :object_index must be given");
68
+ }
69
+ if (!NIL_P(pg_object_index)){
70
+ img->page_object_index = FIX2INT(pg_object_index);
71
+ }
72
+ if (NIL_P(rb_bounds)){
73
+ CPDF_ImageObject *image = (CPDF_ImageObject*)pg->
74
+ page()->GetObjectByIndex(img->page_object_index);
75
+
76
+
77
+ VALUE bounds_args[4];
78
+ bounds_args[0] = rb_float_new( 0 );
79
+ bounds_args[1] = rb_float_new( image->m_pImage->GetPixelWidth() );
80
+ bounds_args[2] = rb_float_new( 0 );
81
+ bounds_args[3] = rb_float_new( image->m_pImage->GetPixelHeight() );
82
+ rb_bounds = rb_class_new_instance( 4, bounds_args, RB::BoundingBox() );
83
+ if (NIL_P(rb_height)){
84
+ rb_iv_set(self, "@height", INT2FIX(image->m_pImage->GetPixelHeight()) );
85
+ }
86
+ if (NIL_P(rb_width)){
87
+ rb_iv_set(self, "@width", INT2FIX(image->m_pImage->GetPixelWidth()) );
88
+ }
89
+
90
+ }
91
+ rb_iv_set(self, "@bounds", rb_bounds);
92
+
93
+ return Qnil;
94
+ }
95
+
96
+ FIBITMAP*
97
+ render_page(CPDF_Page *page, FREE_IMAGE_FORMAT format, int width, int height){
98
+ // Create bitmap. width, height, alpha 1=enabled,0=disabled
99
+ FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(width, height, FPDFBitmap_BGR, NULL, 0);
100
+
101
+ // fill all pixels with white for the background color
102
+ FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF);
103
+
104
+ // Render a page to a bitmap in RGBA format
105
+ // args are: *buffer, page, start_x, start_y, size_x, size_y, rotation, and flags
106
+ // flags are:
107
+ // 0 for normal display, or combination of flags defined below
108
+ // 0x01 Set if annotations are to be rendered
109
+ // 0x02 Set if using text rendering optimized for LCD display
110
+ // 0x04 Set if you don't want to use GDI+
111
+ FPDF_RenderPageBitmap(bitmap, page, 0, 0, width, height, 0, 0);
112
+
113
+ // The stride holds the width of one row in bytes. It may not be an exact
114
+ // multiple of the pixel width because the data may be packed to always end on a byte boundary
115
+ int stride = FPDFBitmap_GetStride(bitmap);
116
+
117
+ // Safety checks to make sure that the bitmap
118
+ // is properly sized and can be safely manipulated
119
+ if (stride < 0){
120
+ FPDFBitmap_Destroy(bitmap);
121
+ return NULL;
122
+ }
123
+ if (width > INT_MAX / height){
124
+ FPDFBitmap_Destroy(bitmap);
125
+ return NULL;
126
+ }
127
+ int out_len = stride * height;
128
+ if (out_len > INT_MAX / 3){
129
+ FPDFBitmap_Destroy(bitmap);
130
+ return NULL;
131
+ }
132
+
133
+ FIBITMAP *image = FreeImage_ConvertFromRawBits((BYTE*)FPDFBitmap_GetBuffer(bitmap),
134
+ width, height, stride, 24,
135
+ 0xFF0000, 0x00FF00, 0x0000FF, true);
136
+ if ( FIF_GIF == format ){
137
+ FIBITMAP *gif = FreeImage_ColorQuantize(image, FIQ_WUQUANT);
138
+ FreeImage_Unload(image);
139
+ return gif;
140
+ } else {
141
+ return image;
142
+ }
143
+ }
144
+
145
+
146
+ FIBITMAP*
147
+ render_image(ImageWrapper *image_wrapper, int index, FREE_IMAGE_FORMAT format, int width, int height){
148
+ CPDF_Page *page = image_wrapper->page_wrapper->page();
149
+ CPDF_ImageObject *image_obj = static_cast<CPDF_ImageObject*>(page->GetObjectByIndex(index));
150
+ CFX_DIBSource *dib = image_obj->m_pImage->LoadDIBSource();
151
+ FIBITMAP *bmp = FreeImage_Allocate(dib->GetWidth(), dib->GetHeight(), dib->GetBPP());
152
+ unsigned int byte_width = dib->GetWidth() * (dib->GetBPP()/8);
153
+ for (int row=0; row < dib->GetHeight(); row++ ){
154
+ auto dest_row = FreeImage_GetScanLine(bmp,dib->GetHeight()-row-1);
155
+ std::memcpy(dest_row, dib->GetScanline(row), byte_width );
156
+ }
157
+ return bmp;
158
+ }
159
+
160
+ FIBITMAP*
161
+ render_to_bitmap(VALUE self, FREE_IMAGE_FORMAT format){
162
+ int width = 0;
163
+ int height = 0;
164
+ VALUE rb_width = rb_iv_get(self, "@width");
165
+ if (T_FIXNUM == TYPE(rb_width)){
166
+ width = FIX2INT(rb_width);
167
+ }
168
+ VALUE rb_height = rb_iv_get(self, "@height");
169
+ if (T_FIXNUM == TYPE(rb_height)){
170
+ height = FIX2INT(rb_height);
171
+ }
172
+ // we must have at least one of width or height
173
+ if (!width && !height){
174
+ rb_raise(rb_eRuntimeError, "Both height and width must be set to a number");
175
+ }
176
+ ImageWrapper *img;
177
+ Data_Get_Struct(self, ImageWrapper, img);
178
+
179
+ if (-1 == img->page_object_index){
180
+ CPDF_Page *page = img->page_wrapper->page();
181
+ return render_page(page, format, width, height);
182
+ } else {
183
+ return render_image(img, img->page_object_index, format, width, height);
184
+ }
185
+ }
186
+
187
+
188
+ /*
189
+ * call-seq:
190
+ * as_science -> ImageScience instance
191
+ *
192
+ * Converts to an ImageScience bitmap and returns it.
193
+ * The ImageScience (https://github.com/seattlerb/image_science) library
194
+ * must be installed and required before calling this method or
195
+ * a NameError: uninitialized constant ImageScience exception will be raised.
196
+
197
+ === Example
198
+ pdf = PDFium::Document.new( "test.pdf" )
199
+ page = pdf.pages.first
200
+ page.images.each do | image |
201
+ image.as_science.cropped_thumbnail 100 do |thumb|
202
+ thumb.save "image-#{image.index}-cropped.png"
203
+ end
204
+ end
205
+
206
+ */
207
+ VALUE
208
+ image_as_science(VALUE self){
209
+ VALUE RBImageScience = rb_const_get(rb_cObject, rb_intern("ImageScience"));
210
+ FIBITMAP *image = render_to_bitmap(self, FIF_BMP);
211
+
212
+ VALUE instance = Data_Wrap_Struct(RBImageScience, NULL, NULL, image);
213
+ rb_iv_set(instance, "@file_type", INT2FIX(FIF_BMP));
214
+ return instance;
215
+ }
216
+
217
+ /*
218
+ * call-seq:
219
+ * save( file ) -> Boolean
220
+ *
221
+ * Save image to a file
222
+ */
223
+ VALUE
224
+ image_save(VALUE self, VALUE rb_file){
225
+ // figure out the desired format from the file extension
226
+ const char* file = StringValuePtr(rb_file);
227
+
228
+ FREE_IMAGE_FORMAT format = FreeImage_GetFIFFromFilename(file);
229
+ if((format == FIF_UNKNOWN) || !FreeImage_FIFSupportsWriting(format)) {
230
+ rb_raise(rb_eArgError, "Unable to write to a image of that type");
231
+ }
232
+
233
+ FIBITMAP *image = render_to_bitmap(self, format);
234
+
235
+ bool success = FreeImage_Save(format, image, file, 0);
236
+
237
+ // unload the image
238
+ FreeImage_Unload(image);
239
+
240
+ return success ? Qtrue : Qfalse;
241
+ }
242
+
243
+ /*
244
+ call-seq:
245
+ data(:format) -> Binary String
246
+
247
+ Returns the binary data for the image in the specified format.
248
+
249
+ Used in conjuction with Document.from_memory this can render be used to
250
+ render a PDF's pages completely in memory.
251
+
252
+ === Example rendering a PDF to AWS without hitting disk
253
+ # Assuming AWS::S3 is already authorized elsewhere
254
+ bucket = AWS::S3.new.buckets['my-pdfs']
255
+ pdf = PDFium::Document.from_memory bucket.objects['secrets.pdf'].read
256
+ pdf.pages.each do | page |
257
+ path = "secrets/page-#{page.number}.jpg"
258
+ bucket.objects[path].write page.as_image(height: 1000).data(:jpg)
259
+ page.images.each do | image |
260
+ path = "secrets/page-#{page.number}-image-#{image.index}.png"
261
+ bucket.objects[path].write image.data(:png)
262
+ end
263
+ end
264
+
265
+ */
266
+ static VALUE
267
+ image_data(VALUE self, VALUE rb_format)
268
+ {
269
+ VALUE path = RB::to_s(rb_format);
270
+ const char* type = StringValuePtr(path);
271
+
272
+ FREE_IMAGE_FORMAT format = FreeImage_GetFIFFromFilename(type);
273
+ if((format == FIF_UNKNOWN) || !FreeImage_FIFSupportsWriting(format)) {
274
+ rb_raise(rb_eArgError, "Unable to write to a image of that type");
275
+ }
276
+
277
+ FIBITMAP *image = render_to_bitmap(self, format);
278
+
279
+ FIMEMORY *mem = FreeImage_OpenMemory(); //mem_buffer, buf.st_size);
280
+
281
+ bool success = FreeImage_SaveToMemory(format, image, mem, 0);
282
+ if (!success){
283
+ FreeImage_Unload(image);
284
+ rb_raise(rb_eArgError, "Unable to save image to memory buffer");
285
+ }
286
+
287
+ long size = FreeImage_TellMemory(mem);
288
+
289
+ char *buffer = ALLOC_N(char, size);
290
+
291
+ FreeImage_SeekMemory(mem, 0L, SEEK_SET);
292
+
293
+ FreeImage_ReadMemory(buffer, size, 1, mem);
294
+
295
+ FreeImage_Unload(image);
296
+
297
+ FreeImage_CloseMemory(mem);
298
+ VALUE ret = rb_str_new(buffer, size);
299
+
300
+ xfree(buffer);
301
+ return ret;
302
+ }
303
+
304
+ VALUE
305
+ define_image_class(){
306
+ VALUE RB_PDFium = RB::PDFium();
307
+
308
+
309
+ VALUE RB_Image = rb_define_class_under(RB_PDFium, "Image", rb_cObject);
310
+ rb_define_alloc_func(RB_Image, image_allocate);
311
+ rb_define_private_method (RB_Image, "initialize", RUBY_METHOD_FUNC(image_initialize), -1);
312
+
313
+ /* Returns the bouding box of the image as a PDFium::BoundingBox */
314
+ rb_define_attr( RB_Image, "bounds", 1, 0 );
315
+
316
+ /* Returns the index of the image on the page.
317
+ * Note: The index method is only provided as a convience method.
318
+ * It has no relation to the position of images on the page.
319
+ * Do not depend on the top-left image being index 0, even if it often is. */
320
+ rb_define_attr( RB_Image, "index", 1, 0 );
321
+
322
+ /* Height of the image in pixels (Fixnum) */
323
+ rb_define_attr( RB_Image, "height", 1, 1 );
324
+
325
+ /* Width of the image in pixels (Fixnum) */
326
+ rb_define_attr( RB_Image, "width", 1, 1 );
327
+
328
+ rb_define_method( RB_Image, "save", RUBY_METHOD_FUNC(image_save), 1);
329
+ rb_define_method( RB_Image, "data", RUBY_METHOD_FUNC(image_data), 1);
330
+ rb_define_method( RB_Image, "as_science", RUBY_METHOD_FUNC(image_as_science),0);
331
+ return RB_Image;
332
+ }