summaryrefslogtreecommitdiff
path: root/src/book.vala
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2017-11-12 13:21:19 +0100
committerJörg Frings-Fürst <debian@jff-webhosting.net>2017-11-12 13:21:19 +0100
commit99656b4fb34c8a8c6a174c7953a6b218ba6568c6 (patch)
treef511703bdeeff064dbc314281c6abce9b0bb5348 /src/book.vala
parent6c629d45711b613257d30e8d5a1527003d9c3f1f (diff)
parent35fd3ab8990210defe10f1614abe4ca1afe04c5d (diff)
Merge branch 'feature/upstream' into develop
Diffstat (limited to 'src/book.vala')
-rw-r--r--src/book.vala828
1 files changed, 540 insertions, 288 deletions
diff --git a/src/book.vala b/src/book.vala
index 4119cfc..d53b31a 100644
--- a/src/book.vala
+++ b/src/book.vala
@@ -9,31 +9,19 @@
* license.
*/
+public delegate void ProgressionCallback (double fraction);
+
public class Book
{
private List<Page> pages;
public uint n_pages { get { return pages.length (); } }
- private bool needs_saving_;
- public bool needs_saving
- {
- get { return needs_saving_; }
- set
- {
- if (needs_saving_ == value)
- return;
- needs_saving_ = value;
- needs_saving_changed ();
- }
- }
-
public signal void page_added (Page page);
public signal void page_removed (Page page);
public signal void reordered ();
public signal void cleared ();
- public signal void needs_saving_changed ();
- public signal void saving (int i);
+ public signal void changed ();
public Book ()
{
@@ -62,7 +50,7 @@ public class Book
private void page_changed_cb (Page page)
{
- needs_saving = true;
+ changed ();
}
public void append_page (Page page)
@@ -72,7 +60,7 @@ public class Book
pages.append (page);
page_added (page);
- needs_saving = true;
+ changed ();
}
public void move_page (Page page, uint location)
@@ -80,7 +68,7 @@ public class Book
pages.remove (page);
pages.insert (page, (int) location);
reordered ();
- needs_saving = true;
+ changed ();
}
public void reverse ()
@@ -91,7 +79,7 @@ public class Book
pages = (owned) new_pages;
reordered ();
- needs_saving = true;
+ changed ();
}
public void combine_sides ()
@@ -108,7 +96,7 @@ public class Book
pages = (owned) new_pages;
reordered ();
- needs_saving = true;
+ changed ();
}
public void combine_sides_reverse ()
@@ -124,7 +112,7 @@ public class Book
pages = (owned) new_pages;
reordered ();
- needs_saving = true;
+ changed ();
}
public void delete_page (Page page)
@@ -133,7 +121,7 @@ public class Book
page.crop_changed.disconnect (page_changed_cb);
pages.remove (page);
page_removed (page);
- needs_saving = true;
+ changed ();
}
public Page get_page (int page_number)
@@ -148,120 +136,440 @@ public class Book
return pages.index (page);
}
- private File make_indexed_file (string uri, int i)
+ public async void save_async (string t, int q, File f, ProgressionCallback? p, Cancellable? c) throws Error
{
- if (n_pages == 1)
- return File.new_for_uri (uri);
-
- /* Insert index before extension */
- var basename = Path.get_basename (uri);
- string prefix = uri, suffix = "";
- var extension_index = basename.last_index_of_char ('.');
- if (extension_index >= 0)
- {
- suffix = basename.slice (extension_index, basename.length);
- prefix = uri.slice (0, uri.length - suffix.length);
- }
- var width = n_pages.to_string().length;
- var number_format = "%%0%dd".printf (width);
- var filename = prefix + "-" + number_format.printf (i + 1) + suffix;
- return File.new_for_uri (filename);
+ var book_saver = new BookSaver ();
+ yield book_saver.save_async (this, t, q, f, p, c);
}
+}
- private void save_multi_file (string type, int quality, File file) throws Error
+private class BookSaver
+{
+ private uint n_pages;
+ private int quality;
+ private File file;
+ private unowned ProgressionCallback progression_callback;
+ private double progression;
+ private Mutex progression_mutex;
+ private Cancellable? cancellable;
+ private AsyncQueue<WriteTask> write_queue;
+ private ThreadPool<EncodeTask> encoder;
+ private SourceFunc save_async_callback;
+
+ /* save_async get called in the main thread to start saving. It
+ * distributes all encode tasks to other threads then yield so
+ * the ui can continue operating. The method then return once saving
+ * is completed, cancelled, or failed */
+ public async void save_async (Book book, string type, int quality, File file, ProgressionCallback? progression_callback, Cancellable? cancellable) throws Error
{
+ var timer = new Timer ();
+
+ this.n_pages = book.n_pages;
+ this.quality = quality;
+ this.file = file;
+ this.cancellable = cancellable;
+ this.save_async_callback = save_async.callback;
+ this.write_queue = new AsyncQueue<WriteTask> ();
+ this.progression = 0;
+ this.progression_mutex = Mutex ();
+
+ /* Configure a callback that monitor saving progression */
+ if (progression_callback == null)
+ this.progression_callback = (fraction) =>
+ {
+ debug ("Save progression: %f%%", fraction*100.0);
+ };
+ else
+ this.progression_callback = progression_callback;
+
+ /* Configure an encoder */
+ ThreadPoolFunc<EncodeTask>? encode_delegate = null;
+ switch (type)
+ {
+ case "jpeg":
+ encode_delegate = encode_jpeg;
+ break;
+ case "png":
+ encode_delegate = encode_png;
+ break;
+#if HAVE_WEBP
+ case "webp":
+ encode_delegate = encode_webp;
+ break;
+#endif
+ case "pdf":
+ encode_delegate = encode_pdf;
+ break;
+ }
+ encoder = new ThreadPool<EncodeTask>.with_owned_data (encode_delegate, (int) get_num_processors (), false);
+
+ /* Configure a writer */
+ ThreadFunc<Error?>? write_delegate = null;
+ switch (type)
+ {
+ case "jpeg":
+ case "png":
+#if HAVE_WEBP
+ case "webp":
+#endif
+ write_delegate = write_multifile;
+ break;
+ case "pdf":
+ write_delegate = write_pdf;
+ break;
+ }
+ var writer = new Thread<Error?> (null, write_delegate);
+
+ /* Issue encode tasks */
for (var i = 0; i < n_pages; i++)
{
- var page = get_page (i);
- page.save (type, quality, make_indexed_file (file.get_uri (), i));
- saving (i);
+ var encode_task = new EncodeTask ();
+ encode_task.number = i;
+ encode_task.page = book.get_page(i);
+ encoder.add ((owned) encode_task);
}
+
+ /* Waiting for saving to finish */
+ yield;
+
+ /* Any error from any thread ends up here */
+ var error = writer.join ();
+ if (error != null)
+ throw error;
+
+ timer.stop ();
+ debug ("Save time: %f seconds", timer.elapsed (null));
}
- private void save_ps_pdf_surface (Cairo.Surface surface, Gdk.Pixbuf image, double dpi)
+ /* Those methods are run in the encoder threads pool. It process
+ * one encode_task issued by save_async and reissue the result with
+ * a write_task */
+
+ private void encode_png (owned EncodeTask encode_task)
{
- var context = new Cairo.Context (surface);
- context.scale (72.0 / dpi, 72.0 / dpi);
- Gdk.cairo_set_source_pixbuf (context, image, 0, 0);
- context.get_source ().set_filter (Cairo.Filter.BEST);
- context.paint ();
+ var page = encode_task.page;
+ var icc_data = page.get_icc_data_encoded ();
+ var write_task = new WriteTask ();
+ var image = page.get_image (true);
+
+ string[] keys = { "x-dpi", "y-dpi", "icc-profile", null };
+ string[] values = { "%d".printf (page.dpi), "%d".printf (page.dpi), icc_data, null };
+ if (icc_data == null)
+ keys[2] = null;
+
+ try
+ {
+ image.save_to_bufferv (out write_task.data, "png", keys, values);
+ }
+ catch (Error error)
+ {
+ write_task.error = error;
+ }
+ write_task.number = encode_task.number;
+ write_queue.push ((owned) write_task);
+
+ update_progression ();
}
- private void save_ps (File file) throws Error
+ private void encode_jpeg (owned EncodeTask encode_task)
{
- var stream = file.replace (null, false, FileCreateFlags.NONE, null);
- var writer = new PsWriter (stream);
- var surface = writer.surface;
+ var page = encode_task.page;
+ var icc_data = page.get_icc_data_encoded ();
+ var write_task = new WriteTask ();
+ var image = page.get_image (true);
- for (var i = 0; i < n_pages; i++)
+ string[] keys = { "x-dpi", "y-dpi", "quality", "icc-profile", null };
+ string[] values = { "%d".printf (page.dpi), "%d".printf (page.dpi), "%d".printf (quality), icc_data, null };
+ if (icc_data == null)
+ keys[3] = null;
+
+ try
+ {
+ image.save_to_bufferv (out write_task.data, "jpeg", keys, values);
+ }
+ catch (Error error)
{
- var page = get_page (i);
- var image = page.get_image (true);
- var width = image.width * 72.0 / page.dpi;
- var height = image.height * 72.0 / page.dpi;
- surface.set_size (width, height);
- save_ps_pdf_surface (surface, image, page.dpi);
- surface.show_page ();
- saving (i);
+ write_task.error = error;
}
+ write_task.number = encode_task.number;
+ write_queue.push ((owned) write_task);
+
+ update_progression ();
}
- private uint8[]? compress_zlib (uint8[] data)
+#if HAVE_WEBP
+ private void encode_webp (owned EncodeTask encode_task)
{
- var stream = ZLib.DeflateStream (ZLib.Level.BEST_COMPRESSION);
- var out_data = new uint8[data.length];
-
- stream.next_in = data;
- stream.next_out = out_data;
- while (stream.avail_in > 0)
+ var page = encode_task.page;
+ var icc_data = page.get_icc_data_encoded ();
+ var write_task = new WriteTask ();
+ var image = page.get_image (true);
+ var webp_data = WebP.encode_rgb (image.get_pixels (),
+ image.get_width (),
+ image.get_height (),
+ image.get_rowstride (),
+ (float) quality);
+#if HAVE_COLORD
+ WebP.MuxError mux_error;
+ var mux = WebP.Mux.new_mux ();
+ uint8[] output;
+
+ mux_error = mux.set_image (webp_data, false);
+ debug ("mux.set_image: %s", mux_error.to_string ());
+
+ if (icc_data != null)
{
- if (stream.deflate (ZLib.Flush.FINISH) == ZLib.Status.STREAM_ERROR)
- break;
+ mux_error = mux.set_chunk ("ICCP", icc_data.data, false);
+ debug ("mux.set_chunk: %s", mux_error.to_string ());
+ if (mux_error != WebP.MuxError.OK)
+ warning ("icc profile data not saved with page %i", encode_task.number);
}
- if (stream.avail_in > 0)
- return null;
+ mux_error = mux.assemble (out output);
+ debug ("mux.assemble: %s", mux_error.to_string ());
+ if (mux_error != WebP.MuxError.OK)
+ write_task.error = new FileError.FAILED (_("Unable to encode page %i").printf (encode_task.number));
- var n_written = data.length - stream.avail_out;
- out_data.resize ((int) n_written);
+ write_task.data = (owned) output;
+#else
- return out_data;
- }
+ if (webp_data.length == 0)
+ write_task.error = new FileError.FAILED (_("Unable to encode page %i").printf (encode_task.number));
- private ByteArray jpeg_data;
+ write_task.data = (owned) webp_data;
+#endif
+ write_task.number = encode_task.number;
+ write_queue.push ((owned) write_task);
- private uint8[] compress_jpeg (Gdk.Pixbuf image, int quality, int dpi)
+ update_progression ();
+ }
+#endif
+
+ private void encode_pdf (owned EncodeTask encode_task)
{
- jpeg_data = new ByteArray ();
- string[] keys = { "quality", "density-unit", "x-density", "y-density", null };
- string[] values = { "%d".printf (quality), "dots-per-inch", "%d".printf (dpi), "%d".printf (dpi), null };
+ var page = encode_task.page;
+ var image = page.get_image (true);
+ var width = image.width;
+ var height = image.height;
+ unowned uint8[] pixels = image.get_pixels ();
+ int depth = 8;
+ string color_space = "DeviceRGB";
+ string? filter = null;
+ uint8[] data;
+
+ if (page.is_color)
+ {
+ depth = 8;
+ color_space = "DeviceRGB";
+ var data_length = height * width * 3;
+ data = new uint8[data_length];
+ for (var row = 0; row < height; row++)
+ {
+ var in_offset = row * image.rowstride;
+ var out_offset = row * width * 3;
+ for (var x = 0; x < width; x++)
+ {
+ var in_o = in_offset + x*3;
+ var out_o = out_offset + x*3;
+
+ data[out_o] = pixels[in_o];
+ data[out_o+1] = pixels[in_o+1];
+ data[out_o+2] = pixels[in_o+2];
+ }
+ }
+ }
+ else if (page.depth == 2)
+ {
+ int shift_count = 6;
+ depth = 2;
+ color_space = "DeviceGray";
+ var data_length = height * ((width * 2 + 7) / 8);
+ data = new uint8[data_length];
+ var offset = 0;
+ for (var row = 0; row < height; row++)
+ {
+ /* Pad to the next line */
+ if (shift_count != 6)
+ {
+ offset++;
+ shift_count = 6;
+ }
+
+ var in_offset = row * image.rowstride;
+ for (var x = 0; x < width; x++)
+ {
+ /* Clear byte */
+ if (shift_count == 6)
+ data[offset] = 0;
+
+ /* Set bits */
+ var p = pixels[in_offset + x*3];
+ if (p >= 192)
+ data[offset] |= 3 << shift_count;
+ else if (p >= 128)
+ data[offset] |= 2 << shift_count;
+ else if (p >= 64)
+ data[offset] |= 1 << shift_count;
+
+ /* Move to the next position */
+ if (shift_count == 0)
+ {
+ offset++;
+ shift_count = 6;
+ }
+ else
+ shift_count -= 2;
+ }
+ }
+ }
+ else if (page.depth == 1)
+ {
+ int mask = 0x80;
+
+ depth = 1;
+ color_space = "DeviceGray";
+ var data_length = height * ((width + 7) / 8);
+ data = new uint8[data_length];
+ var offset = 0;
+ for (var row = 0; row < height; row++)
+ {
+ /* Pad to the next line */
+ if (mask != 0x80)
+ {
+ offset++;
+ mask = 0x80;
+ }
+
+ var in_offset = row * image.rowstride;
+ for (var x = 0; x < width; x++)
+ {
+ /* Clear byte */
+ if (mask == 0x80)
+ data[offset] = 0;
+
+ /* Set bit */
+ if (pixels[in_offset+x*3] != 0)
+ data[offset] |= (uint8) mask;
+
+ /* Move to the next bit */
+ mask >>= 1;
+ if (mask == 0)
+ {
+ offset++;
+ mask = 0x80;
+ }
+ }
+ }
+ }
+ else
+ {
+ depth = 8;
+ color_space = "DeviceGray";
+ var data_length = height * width;
+ data = new uint8 [data_length];
+ for (var row = 0; row < height; row++)
+ {
+ var in_offset = row * image.rowstride;
+ var out_offset = row * width;
+ for (var x = 0; x < width; x++)
+ data[out_offset+x] = pixels[in_offset+x*3];
+ }
+ }
+
+ /* Compress data and use zlib compression if it is smaller than JPEG.
+ * zlib compression is slower in the worst case, so do JPEG first
+ * and stop zlib if it exceeds the JPEG size */
+ var write_task = new WriteTaskPDF ();
+ uint8[]? jpeg_data = null;
try
{
- image.save_to_callbackv (write_pixbuf_data, "jpeg", keys, values);
+ jpeg_data = compress_jpeg (image, quality, page.dpi);
}
- catch (Error e)
+ catch (Error error)
+ {
+ write_task.error = error;
+ }
+ var zlib_data = compress_zlib (data, jpeg_data.length);
+ if (zlib_data != null)
{
+ filter = "FlateDecode";
+ data = zlib_data;
+ }
+ else
+ {
+ filter = "DCTDecode";
+ data = jpeg_data;
}
- var data = (owned) jpeg_data.data;
- jpeg_data = null;
- return data;
+ write_task.number = encode_task.number;
+ write_task.data = data;
+ write_task.width = width;
+ write_task.height = height;
+ write_task.color_space = color_space;
+ write_task.depth = depth;
+ write_task.filter = filter;
+ write_task.dpi = page.dpi;
+ write_queue.push (write_task);
+
+ update_progression ();
}
- private bool write_pixbuf_data (uint8[] buf) throws Error
+ private Error? write_multifile ()
{
- jpeg_data.append (buf);
- return true;
+ for (var i=0; i < n_pages; i++)
+ {
+ if (cancellable.is_cancelled ())
+ {
+ finished_saving ();
+ return null;
+ }
+
+ var write_task = write_queue.pop ();
+ if (write_task.error != null)
+ {
+ finished_saving ();
+ return write_task.error;
+ }
+
+ var indexed_file = make_indexed_file (file.get_uri (), write_task.number, n_pages);
+ try
+ {
+ var stream = indexed_file.replace (null, false, FileCreateFlags.NONE);
+ stream.write_all (write_task.data, null);
+ }
+ catch (Error error)
+ {
+ finished_saving ();
+ return error;
+ }
+ }
+
+ update_progression ();
+ finished_saving ();
+ return null;
}
- private void save_pdf (File file, int quality) throws Error
+ /* Those methods are run in the writer thread. It receive all
+ * write_tasks sent to it by the encoder threads and write those to
+ * disk. */
+
+ private Error? write_pdf ()
{
/* Generate a random ID for this file */
var id = "";
for (var i = 0; i < 4; i++)
id += "%08x".printf (Random.next_int ());
- var stream = file.replace (null, false, FileCreateFlags.NONE, null);
+ FileOutputStream? stream = null;
+ try
+ {
+ stream = file.replace (null, false, FileCreateFlags.NONE, null);
+ }
+ catch (Error error)
+ {
+ finished_saving ();
+ return error;
+ }
var writer = new PDFWriter (stream);
/* Choose object numbers */
@@ -345,163 +653,41 @@ public class Book
writer.write_string (">>\n");
writer.write_string ("endobj\n");
- for (var i = 0; i < n_pages; i++)
+ /* Process each page in order */
+ var tasks_in_standby = new Queue<WriteTaskPDF> ();
+ for (int i = 0; i < n_pages; i++)
{
- var page = get_page (i);
- var image = page.get_image (true);
- var width = image.width;
- var height = image.height;
- unowned uint8[] pixels = image.get_pixels ();
- var page_width = width * 72.0 / page.dpi;
- var page_height = height * 72.0 / page.dpi;
-
- int depth = 8;
- string color_space = "DeviceRGB";
- string? filter = null;
- char[] width_buffer = new char[double.DTOSTR_BUF_SIZE];
- char[] height_buffer = new char[double.DTOSTR_BUF_SIZE];
- uint8[] data;
- if (page.is_color)
+ if (cancellable.is_cancelled ())
{
- depth = 8;
- color_space = "DeviceRGB";
- var data_length = height * width * 3;
- data = new uint8[data_length];
- for (var row = 0; row < height; row++)
- {
- var in_offset = row * image.rowstride;
- var out_offset = row * width * 3;
- for (var x = 0; x < width; x++)
- {
- var in_o = in_offset + x*3;
- var out_o = out_offset + x*3;
-
- data[out_o] = pixels[in_o];
- data[out_o+1] = pixels[in_o+1];
- data[out_o+2] = pixels[in_o+2];
- }
- }
+ finished_saving ();
+ return null;
}
- else if (page.depth == 2)
- {
- int shift_count = 6;
- depth = 2;
- color_space = "DeviceGray";
- var data_length = height * ((width * 2 + 7) / 8);
- data = new uint8[data_length];
- var offset = 0;
- for (var row = 0; row < height; row++)
- {
- /* Pad to the next line */
- if (shift_count != 6)
- {
- offset++;
- shift_count = 6;
- }
- var in_offset = row * image.rowstride;
- for (var x = 0; x < width; x++)
- {
- /* Clear byte */
- if (shift_count == 6)
- data[offset] = 0;
-
- /* Set bits */
- var p = pixels[in_offset + x*3];
- if (p >= 192)
- data[offset] |= 3 << shift_count;
- else if (p >= 128)
- data[offset] |= 2 << shift_count;
- else if (p >= 64)
- data[offset] |= 1 << shift_count;
-
- /* Move to the next position */
- if (shift_count == 0)
- {
- offset++;
- shift_count = 6;
- }
- else
- shift_count -= 2;
- }
- }
- }
- else if (page.depth == 1)
- {
- int mask = 0x80;
-
- depth = 1;
- color_space = "DeviceGray";
- var data_length = height * ((width + 7) / 8);
- data = new uint8[data_length];
- var offset = 0;
- for (var row = 0; row < height; row++)
- {
- /* Pad to the next line */
- if (mask != 0x80)
- {
- offset++;
- mask = 0x80;
- }
-
- var in_offset = row * image.rowstride;
- for (var x = 0; x < width; x++)
- {
- /* Clear byte */
- if (mask == 0x80)
- data[offset] = 0;
-
- /* Set bit */
- if (pixels[in_offset+x*3] != 0)
- data[offset] |= (uint8) mask;
-
- /* Move to the next bit */
- mask >>= 1;
- if (mask == 0)
- {
- offset++;
- mask = 0x80;
- }
- }
- }
- }
+ var write_task = tasks_in_standby.peek_head ();
+ if (write_task != null && write_task.number == i)
+ tasks_in_standby.pop_head ();
else
{
- depth = 8;
- color_space = "DeviceGray";
- var data_length = height * width;
- data = new uint8 [data_length];
- for (var row = 0; row < height; row++)
+ while (true)
{
- var in_offset = row * image.rowstride;
- var out_offset = row * width;
- for (var x = 0; x < width; x++)
- data[out_offset+x] = pixels[in_offset+x*3];
- }
- }
-
- /* Compress data */
- var compressed_data = compress_zlib (data);
- if (compressed_data != null)
- {
- /* Try if JPEG compression is better */
- if (depth > 1)
- {
- var jpeg_data = compress_jpeg (image, quality, page.dpi);
- if (jpeg_data.length < compressed_data.length)
+ write_task = (WriteTaskPDF) write_queue.pop ();
+ if (write_task.error != null)
{
- filter = "DCTDecode";
- data = jpeg_data;
+ finished_saving ();
+ return write_task.error;
}
- }
+ if (write_task.number == i)
+ break;
- if (filter == null)
- {
- filter = "FlateDecode";
- data = compressed_data;
+ tasks_in_standby.insert_sorted (write_task, (a, b) => {return a.number - b.number;});
}
}
+ var page_width = write_task.width * 72.0 / write_task.dpi;
+ var page_height = write_task.height * 72.0 / write_task.dpi;
+ var width_buffer = new char[double.DTOSTR_BUF_SIZE];
+ var height_buffer = new char[double.DTOSTR_BUF_SIZE];
+
/* Page */
writer.write_string ("\n");
writer.start_object (page_numbers[i]);
@@ -522,16 +708,16 @@ public class Book
writer.write_string ("<<\n");
writer.write_string ("/Type /XObject\n");
writer.write_string ("/Subtype /Image\n");
- writer.write_string ("/Width %d\n".printf (width));
- writer.write_string ("/Height %d\n".printf (height));
- writer.write_string ("/ColorSpace /%s\n".printf (color_space));
- writer.write_string ("/BitsPerComponent %d\n".printf (depth));
- writer.write_string ("/Length %d\n".printf (data.length));
- if (filter != null)
- writer.write_string ("/Filter /%s\n".printf (filter));
+ writer.write_string ("/Width %d\n".printf (write_task.width));
+ writer.write_string ("/Height %d\n".printf (write_task.height));
+ writer.write_string ("/ColorSpace /%s\n".printf (write_task.color_space));
+ writer.write_string ("/BitsPerComponent %d\n".printf (write_task.depth));
+ writer.write_string ("/Length %d\n".printf (write_task.data.length));
+ if (write_task.filter != null)
+ writer.write_string ("/Filter /%s\n".printf (write_task.filter));
writer.write_string (">>\n");
writer.write_string ("stream\n");
- writer.write (data);
+ writer.write (write_task.data);
writer.write_string ("\n");
writer.write_string ("endstream\n");
writer.write_string ("endobj\n");
@@ -541,7 +727,7 @@ public class Book
writer.start_object (struct_tree_root_number);
writer.write_string ("%u 0 obj\n".printf (struct_tree_root_number));
writer.write_string ("<<\n");
- writer.write_string ("/Type /StructTreeRoot\n");
+ writer.write_string ("/Type /StructTreeRoot\n");
writer.write_string (">>\n");
writer.write_string ("endobj\n");
@@ -558,8 +744,6 @@ public class Book
writer.write_string ("\n");
writer.write_string ("endstream\n");
writer.write_string ("endobj\n");
-
- saving (i);
}
/* Info */
@@ -576,10 +760,10 @@ public class Book
var xref_offset = writer.offset;
writer.write_string ("xref\n");
writer.write_string ("0 %zu\n".printf (writer.object_offsets.length + 1));
- writer.write_string ("%010zu 65535 f \n".printf (next_empty_object (writer, 0)));
+ writer.write_string ("%010zu 65535 f \n".printf (writer.next_empty_object (0)));
for (var i = 0; i < writer.object_offsets.length; i++)
if (writer.object_offsets[i] == 0)
- writer.write_string ("%010zu 65535 f \n".printf (next_empty_object (writer, i + 1)));
+ writer.write_string ("%010zu 65535 f \n".printf (writer.next_empty_object (i + 1)));
else
writer.write_string ("%010zu 00000 n \n".printf (writer.object_offsets[i]));
@@ -595,35 +779,102 @@ public class Book
writer.write_string ("startxref\n");
writer.write_string ("%zu\n".printf (xref_offset));
writer.write_string ("%%EOF\n");
+
+ update_progression ();
+ finished_saving ();
+ return null;
}
- static int next_empty_object (PDFWriter writer, int start)
+ /* update_progression is called once by page by encoder threads and
+ * once at the end by writer thread. */
+ private void update_progression ()
{
- for (var i = start; i < writer.object_offsets.length; i++)
- if (writer.object_offsets[i] == 0)
- return i + 1;
- return 0;
+ double step = 1.0 / (double)(n_pages+1);
+ progression_mutex.lock ();
+ progression += step;
+ progression_mutex.unlock ();
+ Idle.add (() =>
+ {
+ progression_callback (progression);
+ return false;
+ });
}
- public void save (string type, int quality, File file) throws Error
+ /* finished_saving is called by the writer thread when it's done,
+ * meaning there is nothing left to do or saving has been
+ * cancelled */
+ private void finished_saving ()
{
- switch (type)
+ /* At this point, any remaining encode_task ought to remain unprocessed */
+ ThreadPool.free ((owned) encoder, true, true);
+
+ /* Wake-up save_async method in main thread */
+ Idle.add ((owned)save_async_callback);
+ }
+
+ /* Utility methods */
+
+ private static uint8[]? compress_zlib (uint8[] data, uint max_size)
+ {
+ var stream = ZLib.DeflateStream (ZLib.Level.BEST_COMPRESSION);
+ var out_data = new uint8[max_size];
+
+ stream.next_in = data;
+ stream.next_out = out_data;
+ while (true)
{
- case "jpeg":
- case "png":
- case "tiff":
- save_multi_file (type, quality, file);
- break;
- case "ps":
- save_ps (file);
- break;
- case "pdf":
- save_pdf (file, quality);
- break;
+ /* Compression complete */
+ if (stream.avail_in == 0)
+ break;
+
+ /* Out of space */
+ if (stream.avail_out == 0)
+ return null;
+
+ if (stream.deflate (ZLib.Flush.FINISH) == ZLib.Status.STREAM_ERROR)
+ return null;
}
+
+ var n_written = out_data.length - stream.avail_out;
+ out_data.resize ((int) n_written);
+
+ return out_data;
+ }
+
+ private static uint8[] compress_jpeg (Gdk.Pixbuf image, int quality, int dpi) throws Error
+ {
+ uint8[] jpeg_data;
+ string[] keys = { "quality", "x-dpi", "y-dpi", null };
+ string[] values = { "%d".printf (quality), "%d".printf (dpi), "%d".printf (dpi), null };
+
+ image.save_to_bufferv (out jpeg_data, "jpeg", keys, values);
+ return jpeg_data;
}
}
+private class EncodeTask
+{
+ public int number;
+ public Page page;
+}
+
+private class WriteTask
+{
+ public int number;
+ public uint8[] data;
+ public Error error;
+}
+
+private class WriteTaskPDF : WriteTask
+{
+ public int width;
+ public int height;
+ public string color_space;
+ public int depth;
+ public string? filter;
+ public int dpi;
+}
+
private class PDFWriter
{
public size_t offset = 0;
@@ -666,31 +917,32 @@ private class PDFWriter
{
object_offsets[index - 1] = (uint)offset;
}
-}
-
-public class PsWriter
-{
- public Cairo.PsSurface surface;
- public FileOutputStream stream;
- public PsWriter (FileOutputStream stream)
+ public int next_empty_object (int start)
{
- this.stream = stream;
- surface = new Cairo.PsSurface.for_stream (write_cairo_data, 0, 0);
+ for (var i = start; i < object_offsets.length; i++)
+ if (object_offsets[i] == 0)
+ return i + 1;
+ return 0;
}
+}
- private Cairo.Status write_cairo_data (uint8[] data)
+public File make_indexed_file (string uri, uint i, uint n_pages)
+{
+ if (n_pages == 1)
+ return File.new_for_uri (uri);
+
+ /* Insert index before extension */
+ var basename = Path.get_basename (uri);
+ string prefix = uri, suffix = "";
+ var extension_index = basename.last_index_of_char ('.');
+ if (extension_index >= 0)
{
- try
- {
- stream.write_all (data, null, null);
- }
- catch (Error e)
- {
- warning ("Error writing data: %s", e.message);
- return Cairo.Status.WRITE_ERROR;
- }
-
- return Cairo.Status.SUCCESS;
+ suffix = basename.slice (extension_index, basename.length);
+ prefix = uri.slice (0, uri.length - suffix.length);
}
+ var width = n_pages.to_string().length;
+ var number_format = "%%0%dd".printf (width);
+ var filename = prefix + "-" + number_format.printf (i + 1) + suffix;
+ return File.new_for_uri (filename);
}