summaryrefslogtreecommitdiffstats
path: root/kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp')
-rw-r--r--kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp2831
1 files changed, 2831 insertions, 0 deletions
diff --git a/kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp b/kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp
new file mode 100644
index 00000000..73e3a9c2
--- /dev/null
+++ b/kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp
@@ -0,0 +1,2831 @@
+//C- -*- C++ -*-
+//C- -------------------------------------------------------------------
+//C- DjVuLibre-3.5
+//C- Copyright (c) 2002 Leon Bottou and Yann Le Cun.
+//C- Copyright (c) 2001 AT&T
+//C-
+//C- This software is subject to, and may be distributed under, the
+//C- GNU General Public License, Version 2. The license should have
+//C- accompanied the software or you may obtain a copy of the license
+//C- from the Free Software Foundation at http://www.fsf.org .
+//C-
+//C- This program is distributed in the hope that it will be useful,
+//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
+//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+//C- GNU General Public License for more details.
+//C-
+//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
+//C- distributed by Lizardtech Software. On July 19th 2002, Lizardtech
+//C- Software authorized us to replace the original DjVu(r) Reference
+//C- Library notice by the following text (see doc/lizard2002.djvu):
+//C-
+//C- ------------------------------------------------------------------
+//C- | DjVu (r) Reference Library (v. 3.5)
+//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
+//C- | The DjVu Reference Library is protected by U.S. Pat. No.
+//C- | 6,058,214 and patents pending.
+//C- |
+//C- | This software is subject to, and may be distributed under, the
+//C- | GNU General Public License, Version 2. The license should have
+//C- | accompanied the software or you may obtain a copy of the license
+//C- | from the Free Software Foundation at http://www.fsf.org .
+//C- |
+//C- | The computer code originally released by LizardTech under this
+//C- | license and unmodified by other parties is deemed "the LIZARDTECH
+//C- | ORIGINAL CODE." Subject to any third party intellectual property
+//C- | claims, LizardTech grants recipient a worldwide, royalty-free,
+//C- | non-exclusive license to make, use, sell, or otherwise dispose of
+//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the
+//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
+//C- | General Public License. This grant only confers the right to
+//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
+//C- | the extent such infringement is reasonably necessary to enable
+//C- | recipient to make, have made, practice, sell, or otherwise dispose
+//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
+//C- | any greater extent that may be necessary to utilize further
+//C- | modifications or combinations.
+//C- |
+//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
+//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
+//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+//C- +------------------------------------------------------------------
+//
+// $Id: DjVuFile.cpp,v 1.11 2003/11/07 22:08:20 leonb Exp $
+// $Name: release_3_5_15 $
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#if NEED_GNUG_PRAGMAS
+# pragma implementation
+#endif
+
+#include "DjVuFile.h"
+#include "IFFByteStream.h"
+#include "GOS.h"
+#include "MMRDecoder.h"
+#ifdef NEED_JPEG_DECODER
+#include "JPEGDecoder.h"
+#endif
+#include "DjVuAnno.h"
+#include "DjVuText.h"
+#include "DataPool.h"
+#include "JB2Image.h"
+#include "IW44Image.h"
+#include "DjVuNavDir.h"
+#ifndef NEED_DECODER_ONLY
+#include "BSByteStream.h"
+#endif // NEED_DECODER_ONLY
+
+#include "debug.h"
+
+
+#ifdef HAVE_NAMESPACES
+namespace DJVU {
+# ifdef NOT_DEFINED // Just to fool emacs c++ mode
+}
+#endif
+#endif
+
+
+#define STRINGIFY(x) STRINGIFY_(x)
+#define STRINGIFY_(x) #x
+
+
+#define REPORT_EOF(x) \
+ {G_TRY{G_THROW( ByteStream::EndOfFile );}G_CATCH(ex){report_error(ex,(x));}G_ENDCATCH;}
+
+static GP<GPixmap> (*djvu_decode_codec)(ByteStream &bs)=0;
+
+class ProgressByteStream : public ByteStream
+{
+public:
+ ProgressByteStream(const GP<ByteStream> & xstr) : str(xstr),
+ last_call_pos(0) {}
+ virtual ~ProgressByteStream() {}
+
+ virtual size_t read(void *buffer, size_t size)
+ {
+ int rc=0;
+ // G_TRY {} CATCH; block here is merely to avoid egcs internal error
+ G_TRY {
+ int cur_pos=str->tell();
+ if (progress_cb && (last_call_pos/256!=cur_pos/256))
+ {
+ progress_cb(cur_pos, progress_cl_data);
+ last_call_pos=cur_pos;
+ }
+ rc=str->read(buffer, size);
+ } G_CATCH_ALL {
+ G_RETHROW;
+ } G_ENDCATCH;
+ return rc;
+ }
+ virtual size_t write(const void *buffer, size_t size)
+ {
+ return str->write(buffer, size);
+ }
+ virtual int seek(long offset, int whence = SEEK_SET, bool nothrow=false)
+ {
+ return str->seek(offset, whence);
+ }
+ virtual long tell(void ) const { return str->tell(); }
+
+ void set_progress_cb(void (* xprogress_cb)(int, void *),
+ void * xprogress_cl_data)
+ {
+ progress_cb=xprogress_cb;
+ progress_cl_data=xprogress_cl_data;
+ }
+private:
+ GP<ByteStream> str;
+ void * progress_cl_data;
+ void (* progress_cb)(int pos, void *);
+ int last_call_pos;
+
+ // Cancel C++ default stuff
+ ProgressByteStream & operator=(const ProgressByteStream &);
+};
+
+
+DjVuFile::DjVuFile()
+: file_size(0), recover_errors(ABORT), verbose_eof(false), chunks_number(-1),
+initialized(false)
+{
+}
+
+void
+DjVuFile::check() const
+{
+ if (!initialized)
+ G_THROW( ERR_MSG("DjVuFile.not_init") );
+}
+
+GP<DjVuFile>
+DjVuFile::create(
+ const GP<ByteStream> & str, const ErrorRecoveryAction recover_errors,
+ const bool verbose_eof )
+{
+ DjVuFile *file=new DjVuFile();
+ GP<DjVuFile> retval=file;
+ file->set_recover_errors(recover_errors);
+ file->set_verbose_eof(verbose_eof);
+ file->init(str);
+ return retval;
+}
+
+void
+DjVuFile::init(const GP<ByteStream> & str)
+{
+ DEBUG_MSG("DjVuFile::DjVuFile(): ByteStream constructor\n");
+ DEBUG_MAKE_INDENT(3);
+
+ if (initialized)
+ G_THROW( ERR_MSG("DjVuFile.2nd_init") );
+ if (!get_count())
+ G_THROW( ERR_MSG("DjVuFile.not_secured") );
+
+ file_size=0;
+ decode_thread=0;
+
+ // Read the data from the stream
+ data_pool=DataPool::create(str);
+
+ // Construct some dummy URL
+ GUTF8String buffer;
+ buffer.format("djvufile:/%p.djvu", this);
+ DEBUG_MSG("DjVuFile::DjVuFile(): url is "<<(const char *)buffer<<"\n");
+ url=GURL::UTF8(buffer);
+
+ // Set it here because trigger will call other DjVuFile's functions
+ initialized=true;
+
+ // Add (basically - call) the trigger
+ data_pool->add_trigger(-1, static_trigger_cb, this);
+}
+
+GP<DjVuFile>
+DjVuFile::create(
+ const GURL & xurl, GP<DjVuPort> port,
+ const ErrorRecoveryAction recover_errors, const bool verbose_eof )
+{
+ DjVuFile *file=new DjVuFile();
+ GP<DjVuFile> retval=file;
+ file->set_recover_errors(recover_errors);
+ file->set_verbose_eof(verbose_eof);
+ file->init(xurl,port);
+ return retval;
+}
+
+void
+DjVuFile::init(const GURL & xurl, GP<DjVuPort> port)
+{
+ DEBUG_MSG("DjVuFile::init(): url='" << xurl << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ if (initialized)
+ G_THROW( ERR_MSG("DjVuFile.2nd_init") );
+ if (!get_count())
+ G_THROW( ERR_MSG("DjVuFile.not_secured") );
+ if (xurl.is_empty())
+ G_THROW( ERR_MSG("DjVuFile.empty_URL") );
+
+ url = xurl;
+ DEBUG_MSG("DjVuFile::DjVuFile(): url is "<<(const char *)url<<"\n");
+ file_size=0;
+ decode_thread=0;
+
+ DjVuPortcaster * pcaster=get_portcaster();
+
+ // We need it 'cause we're waiting for our own termination in stop_decode()
+ pcaster->add_route(this, this);
+ if (!port)
+ port = simple_port = new DjVuSimplePort();
+ pcaster->add_route(this, port);
+
+ // Set it here because trigger will call other DjVuFile's functions
+ initialized=true;
+
+ if (!(data_pool=DataPool::create(pcaster->request_data(this, url))))
+ G_THROW( ERR_MSG("DjVuFile.no_data") "\t"+url.get_string());
+ data_pool->add_trigger(-1, static_trigger_cb, this);
+}
+
+DjVuFile::~DjVuFile(void)
+{
+ DEBUG_MSG("DjVuFile::~DjVuFile(): destroying...\n");
+ DEBUG_MAKE_INDENT(3);
+
+ // No more messages. They may result in adding this file to a cache
+ // which will be very-very bad as we're being destroyed
+ get_portcaster()->del_port(this);
+
+ // Unregister the trigger (we don't want it to be called and attempt
+ // to access the destroyed object)
+ if (data_pool)
+ data_pool->del_trigger(static_trigger_cb, this);
+
+ // We don't have to wait for decoding to finish here. It's already
+ // finished (we know it because there is a "life saver" in the
+ // thread function) -- but we need to delete it
+ delete decode_thread; decode_thread=0;
+}
+
+void
+DjVuFile::reset(void)
+{
+ flags.enter();
+ info = 0;
+ anno = 0;
+ text = 0;
+ meta = 0;
+ bg44 = 0;
+ fgbc = 0;
+ fgjb = 0;
+ fgjd = 0;
+ fgpm = 0;
+ dir = 0;
+ description = "";
+ mimetype = "";
+ flags=(flags&(ALL_DATA_PRESENT|DECODE_STOPPED|DECODE_FAILED));
+ flags.leave();
+}
+
+unsigned int
+DjVuFile::get_memory_usage(void) const
+{
+ unsigned int size=sizeof(*this);
+ if (info) size+=info->get_memory_usage();
+ if (bg44) size+=bg44->get_memory_usage();
+ if (fgjb) size+=fgjb->get_memory_usage();
+ if (fgpm) size+=fgpm->get_memory_usage();
+ if (fgbc) size+=fgbc->size()*sizeof(int);
+ if (anno) size+=anno->size();
+ if (meta) size+=meta->size();
+ if (dir) size+=dir->get_memory_usage();
+ return size;
+}
+
+GPList<DjVuFile>
+DjVuFile::get_included_files(bool only_created)
+{
+ check();
+ if (!only_created && !are_incl_files_created())
+ process_incl_chunks();
+
+ GCriticalSectionLock lock(&inc_files_lock);
+ GPList<DjVuFile> list=inc_files_list; // Get a copy when locked
+ return list;
+}
+
+void
+DjVuFile::wait_for_chunk(void)
+// Will return after a chunk has been decoded
+{
+ check();
+ DEBUG_MSG("DjVuFile::wait_for_chunk() called\n");
+ DEBUG_MAKE_INDENT(3);
+ chunk_mon.enter();
+ chunk_mon.wait();
+ chunk_mon.leave();
+}
+
+bool
+DjVuFile::wait_for_finish(bool self)
+// if self==TRUE, will block until decoding of this file is over
+// if self==FALSE, will block until decoding of a child (direct
+// or indirect) is over.
+// Will return FALSE if there is nothing to wait for. TRUE otherwise
+{
+ DEBUG_MSG("DjVuFile::wait_for_finish(): self=" << self <<"\n");
+ DEBUG_MAKE_INDENT(3);
+
+ check();
+
+ if (self)
+ {
+ // It's best to check for self termination using flags. The reason
+ // is that finish_mon is updated in a DjVuPort function, which
+ // will not be called if the object is being destroyed
+ GMonitorLock lock(&flags);
+ if (is_decoding())
+ {
+ while(is_decoding()) flags.wait();
+ DEBUG_MSG("got it\n");
+ return 1;
+ }
+ } else
+ {
+ // By locking the monitor, we guarantee that situation doesn't change
+ // between the moments when we check for pending finish events
+ // and when we actually run wait(). If we don't lock, the last child
+ // may terminate in between, and we'll wait forever.
+ //
+ // Locking is required by GMonitor interface too, btw.
+ GMonitorLock lock(&finish_mon);
+ GP<DjVuFile> file;
+ {
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;++pos)
+ {
+ GP<DjVuFile> & f=inc_files_list[pos];
+ if (f->is_decoding())
+ {
+ file=f; break;
+ }
+ }
+ }
+ if (file)
+ {
+ finish_mon.wait();
+ DEBUG_MSG("got it\n");
+ return 1;
+ }
+ }
+ DEBUG_MSG("nothing to wait for\n");
+ return 0;
+}
+
+void
+DjVuFile::notify_chunk_done(const DjVuPort *, const GUTF8String &)
+{
+ check();
+ chunk_mon.enter();
+ chunk_mon.broadcast();
+ chunk_mon.leave();
+}
+
+void
+DjVuFile::notify_file_flags_changed(const DjVuFile * src,
+ long set_mask, long clr_mask)
+{
+ check();
+ if (set_mask & (DECODE_OK | DECODE_FAILED | DECODE_STOPPED))
+ {
+ // Signal threads waiting for file termination
+ finish_mon.enter();
+ finish_mon.broadcast();
+ finish_mon.leave();
+
+ // In case a thread is still waiting for a chunk
+ chunk_mon.enter();
+ chunk_mon.broadcast();
+ chunk_mon.leave();
+ }
+
+ if ((set_mask & ALL_DATA_PRESENT) && src!=this &&
+ are_incl_files_created() && is_data_present())
+ {
+ if (src!=this && are_incl_files_created() && is_data_present())
+ {
+ // Check if all children have data
+ bool all=true;
+ {
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;++pos)
+ if (!inc_files_list[pos]->is_all_data_present())
+ {
+ all=false;
+ break;
+ }
+ }
+ if (all)
+ {
+ DEBUG_MSG("Just got ALL data for '" << url << "'\n");
+ flags|=ALL_DATA_PRESENT;
+ get_portcaster()->notify_file_flags_changed(this, ALL_DATA_PRESENT, 0);
+ }
+ }
+ }
+}
+
+void
+DjVuFile::static_decode_func(void * cl_data)
+{
+ DjVuFile * th=(DjVuFile *) cl_data;
+
+ /* Please do not undo this life saver. If you do then try to resolve the
+ following conflict first:
+ 1. Decoding starts and there is only one external reference
+ to the DjVuFile.
+ 2. Decoding proceeds and calls DjVuPortcaster::notify_error(),
+ which creates inside a temporary GP<DjVuFile>.
+ 3. While notify_error() is running, the only external reference
+ is lost, but the DjVuFile is still alive (remember the
+ temporary GP<>?)
+ 4. The notify_error() returns, the temporary GP<> gets destroyed
+ and the DjVuFile is attempting to destroy right in the middle
+ of the decoding thread. This is either a dead block (waiting
+ for the termination of the decoding from the ~DjVuFile() called
+ from the decoding thread) or coredump. */
+ GP<DjVuFile> life_saver=th;
+ th->decode_life_saver=0;
+ G_TRY {
+ th->decode_func();
+ } G_CATCH_ALL {
+ } G_ENDCATCH;
+}
+
+void
+DjVuFile::decode_func(void)
+{
+ check();
+ DEBUG_MSG("DjVuFile::decode_func() called, url='" << url << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ DjVuPortcaster * pcaster=get_portcaster();
+
+ G_TRY {
+ const GP<ByteStream> decode_stream(decode_data_pool->get_stream());
+ ProgressByteStream *pstr=new ProgressByteStream(decode_stream);
+ const GP<ByteStream> gpstr(pstr);
+ pstr->set_progress_cb(progress_cb, this);
+
+ decode(gpstr);
+
+ // Wait for all child files to finish
+ while(wait_for_finish(0))
+ continue;
+
+ DEBUG_MSG("waiting for children termination\n");
+ // Check for termination status
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;++pos)
+ {
+ GP<DjVuFile> & f=inc_files_list[pos];
+ if (f->is_decode_failed())
+ G_THROW( ERR_MSG("DjVuFile.decode_fail") );
+ if (f->is_decode_stopped())
+ G_THROW( DataPool::Stop );
+ if (!f->is_decode_ok())
+ {
+ DEBUG_MSG("this_url='" << url << "'\n");
+ DEBUG_MSG("incl_url='" << f->get_url() << "'\n");
+ DEBUG_MSG("decoding=" << f->is_decoding() << "\n");
+ DEBUG_MSG("status='" << f->get_flags() << "\n");
+ G_THROW( ERR_MSG("DjVuFile.not_finished") );
+ }
+ }
+ } G_CATCH(exc) {
+ G_TRY {
+ if (!exc.cmp_cause(DataPool::Stop))
+ {
+ flags.enter();
+ flags=flags & ~DECODING | DECODE_STOPPED;
+ flags.leave();
+ pcaster->notify_status(this, GUTF8String(ERR_MSG("DjVuFile.stopped"))
+ + GUTF8String("\t") + GUTF8String(url));
+ pcaster->notify_file_flags_changed(this, DECODE_STOPPED, DECODING);
+ } else
+ {
+ flags.enter();
+ flags=flags & ~DECODING | DECODE_FAILED;
+ flags.leave();
+ pcaster->notify_status(this, GUTF8String(ERR_MSG("DjVuFile.failed"))
+ + GUTF8String("\t") + GUTF8String(url));
+ pcaster->notify_error(this, exc.get_cause());
+ pcaster->notify_file_flags_changed(this, DECODE_FAILED, DECODING);
+ }
+ } G_CATCH_ALL
+ {
+ DEBUG_MSG("******* Oops. Almost missed an exception\n");
+ } G_ENDCATCH;
+ } G_ENDCATCH;
+
+ decode_data_pool->clear_stream();
+ G_TRY {
+ if (flags.test_and_modify(DECODING, 0, DECODE_OK | INCL_FILES_CREATED, DECODING))
+ pcaster->notify_file_flags_changed(this, DECODE_OK | INCL_FILES_CREATED,
+ DECODING);
+ } G_CATCH_ALL {} G_ENDCATCH;
+ DEBUG_MSG("decoding thread for url='" << url << "' ended\n");
+}
+
+GP<DjVuFile>
+DjVuFile::process_incl_chunk(ByteStream & str, int file_num)
+{
+ check();
+ DEBUG_MSG("DjVuFile::process_incl_chunk(): processing INCL chunk...\n");
+ DEBUG_MAKE_INDENT(3);
+
+ DjVuPortcaster * pcaster=get_portcaster();
+
+ GUTF8String incl_str;
+ char buffer[1024];
+ int length;
+ while((length=str.read(buffer, 1024)))
+ incl_str+=GUTF8String(buffer, length);
+
+ // Eat '\n' in the beginning and at the end
+ while(incl_str.length() && incl_str[0]=='\n')
+ {
+ incl_str=incl_str.substr(1,(unsigned int)(-1));
+ }
+ while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
+ {
+ incl_str.setat(incl_str.length()-1, 0);
+ }
+
+ if (incl_str.length()>0)
+ {
+ if (strchr(incl_str, '/'))
+ G_THROW( ERR_MSG("DjVuFile.malformed") );
+
+ DEBUG_MSG("incl_str='" << incl_str << "'\n");
+
+ GURL incl_url=pcaster->id_to_url(this, incl_str);
+ if (incl_url.is_empty()) // Fallback. Should never be used.
+ incl_url=GURL::UTF8(incl_str,url.base());
+
+ // Now see if there is already a file with this *name* created
+ {
+ GCriticalSectionLock lock(&inc_files_lock);
+ GPosition pos;
+ for(pos=inc_files_list;pos;++pos)
+ {
+ if (inc_files_list[pos]->url.fname()==incl_url.fname())
+ break;
+ }
+ if (pos)
+ return inc_files_list[pos];
+ }
+
+ // No. We have to request a new file
+ GP<DjVuFile> file;
+ G_TRY
+ {
+ file=pcaster->id_to_file(this, incl_str);
+ }
+ G_CATCH(ex)
+ {
+ unlink_file(incl_str);
+ // In order to keep compatibility with the previous
+ // release of the DjVu plugin, we will not interrupt
+ // decoding here. We will just report the error.
+ // NOTE, that it's now the responsibility of the
+ // decoder to resolve all chunk dependencies, and
+ // abort decoding if necessary.
+
+ // G_EXTHROW(ex); /* commented out */
+
+ get_portcaster()->notify_error(this,ex.get_cause());
+ return 0;
+ }
+ G_ENDCATCH;
+ if (!file)
+ {
+ G_THROW( ERR_MSG("DjVuFile.no_create") "\t"+incl_str);
+ }
+ if (recover_errors!=ABORT)
+ file->set_recover_errors(recover_errors);
+ if (verbose_eof)
+ file->set_verbose_eof(verbose_eof);
+ pcaster->add_route(file, this);
+
+ // We may have been stopped. Make sure the child will be stopped too.
+ if (flags & STOPPED)
+ file->stop(false);
+ if (flags & BLOCKED_STOPPED)
+ file->stop(true);
+
+ // Lock the list again and check if the file has already been
+ // added by someone else
+ {
+ GCriticalSectionLock lock(&inc_files_lock);
+ GPosition pos;
+ for(pos=inc_files_list;pos;++pos)
+ {
+ if (inc_files_list[pos]->url.fname()==incl_url.fname())
+ break;
+ }
+ if (pos)
+ {
+ file=inc_files_list[pos];
+ } else if (file_num<0 || !(pos=inc_files_list.nth(file_num)))
+ {
+ inc_files_list.append(file);
+ } else
+ {
+ inc_files_list.insert_before(pos, file);
+ }
+ }
+ return file;
+ }
+ return 0;
+}
+
+
+void
+DjVuFile::report_error(const GException &ex,bool throw_errors)
+{
+ data_pool->clear_stream();
+ if((!verbose_eof)|| (ex.cmp_cause(ByteStream::EndOfFile)))
+ {
+ if(throw_errors)
+ {
+ G_EXTHROW(ex);
+ }else
+ {
+ get_portcaster()->notify_error(this,ex.get_cause());
+ }
+ }else
+ {
+ GURL url=get_url();
+ GUTF8String url_str=url.get_string();
+// if (url.is_local_file_url())
+// url_str=url.filename();
+
+ GUTF8String msg = GUTF8String( ERR_MSG("DjVuFile.EOF") "\t") + url;
+ if(throw_errors)
+ {
+ G_EXTHROW(ex, msg);
+ }else
+ {
+ get_portcaster()->notify_error(this,msg);
+ }
+ }
+}
+
+void
+DjVuFile::process_incl_chunks(void)
+// This function may block for data
+// NOTE: It may be called again when INCL_FILES_CREATED is set.
+// It happens in insert_file() when it has modified the data
+// and wants to create the actual file
+{
+ DEBUG_MSG("DjVuFile::process_incl_chunks(void)\n");
+ DEBUG_MAKE_INDENT(3);
+ check();
+
+ int incl_cnt=0;
+
+ const GP<ByteStream> str(data_pool->get_stream());
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (iff.get_chunk(chkid))
+ {
+ int chunks=0;
+ int last_chunk=0;
+ G_TRY
+ {
+ int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
+ int chksize;
+ for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
+ {
+ chunks++;
+ if (chkid=="INCL")
+ {
+ G_TRY
+ {
+ process_incl_chunk(*iff.get_bytestream(), incl_cnt++);
+ }
+ G_CATCH(ex);
+ {
+ report_error(ex,(recover_errors <= SKIP_PAGES));
+ }
+ G_ENDCATCH;
+ }else if(chkid=="FAKE")
+ {
+ set_needs_compression(true);
+ set_can_compress(true);
+ }else if(chkid=="BGjp")
+ {
+ set_can_compress(true);
+ }else if(chkid=="Smmr")
+ {
+ set_can_compress(true);
+ }
+ iff.seek_close_chunk();
+ }
+ if (chunks_number < 0) chunks_number=last_chunk;
+ }
+ G_CATCH(ex)
+ {
+ if (chunks_number < 0)
+ chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
+ report_error(ex,(recover_errors <= SKIP_PAGES));
+ }
+ G_ENDCATCH;
+ }
+ flags|=INCL_FILES_CREATED;
+ data_pool->clear_stream();
+}
+
+GP<JB2Dict>
+DjVuFile::static_get_fgjd(void *arg)
+{
+ DjVuFile *file = (DjVuFile*)arg;
+ return file->get_fgjd(1);
+}
+
+GP<JB2Dict>
+DjVuFile::get_fgjd(int block)
+{
+ check();
+
+ // Simplest case
+ if (fgjd)
+ return fgjd;
+ // Check wether included files
+ chunk_mon.enter();
+ G_TRY {
+ for(;;)
+ {
+ int active = 0;
+ GPList<DjVuFile> incs = get_included_files();
+ for (GPosition pos=incs.firstpos(); pos; ++pos)
+ {
+ GP<DjVuFile> file = incs[pos];
+ if (file->is_decoding())
+ active = 1;
+ GP<JB2Dict> fgjd = file->get_fgjd();
+ if (fgjd)
+ {
+ chunk_mon.leave();
+ return fgjd;
+ }
+ }
+ // Exit if non-blocking mode
+ if (! block)
+ break;
+ // Exit if there is no decoding activity
+ if (! active)
+ break;
+ // Wait until a new chunk gets decoded
+ wait_for_chunk();
+ }
+ } G_CATCH_ALL {
+ chunk_mon.leave();
+ G_RETHROW;
+ } G_ENDCATCH;
+ chunk_mon.leave();
+ if (is_decode_stopped()) G_THROW( DataPool::Stop );
+ return 0;
+}
+
+int
+DjVuFile::get_dpi(int w, int h)
+{
+ int dpi=0, red=1;
+ if (info)
+ {
+ for(red=1; red<=12; red++)
+ if ((info->width+red-1)/red==w)
+ if ((info->height+red-1)/red==h)
+ break;
+ if (red>12)
+ G_THROW( ERR_MSG("DjVuFile.corrupt_BG44") );
+ dpi=info->dpi;
+ }
+ return (dpi ? dpi : 300)/red;
+}
+
+static inline bool
+is_info(const GUTF8String &chkid)
+{
+ return (chkid=="INFO");
+}
+
+static inline bool
+is_annotation(const GUTF8String &chkid)
+{
+ return (chkid=="ANTa" ||
+ chkid=="ANTz" ||
+ chkid=="FORM:ANNO" );
+}
+
+static inline bool
+is_text(const GUTF8String &chkid)
+{
+ return (chkid=="TXTa" || chkid=="TXTz");
+}
+
+static inline bool
+is_meta(const GUTF8String &chkid)
+{
+ return (chkid=="METa" || chkid=="METz");
+}
+
+
+GUTF8String
+DjVuFile::decode_chunk( const GUTF8String &id, const GP<ByteStream> &gbs,
+ bool djvi, bool djvu, bool iw44)
+{
+ DEBUG_MSG("DjVuFile::decode_chunk()\n");
+ ByteStream &bs=*gbs;
+ check();
+
+ // If this object is referenced by only one GP<> pointer, this
+ // pointer should be the "life_saver" created by the decoding thread.
+ // If it is the only GP<> pointer, then nobody is interested in the
+ // results of the decoding and we can abort now with #DataPool::Stop#
+ if (get_count()==1)
+ G_THROW( DataPool::Stop );
+
+ GUTF8String desc = ERR_MSG("DjVuFile.unrecog_chunk");
+ GUTF8String chkid = id;
+ DEBUG_MSG("DjVuFile::decode_chunk() : decoding " << id << "\n");
+
+ // INFO (information chunk for djvu page)
+ if (is_info(chkid) && (djvu || djvi))
+ {
+ if (info)
+ G_THROW( ERR_MSG("DjVuFile.corrupt_dupl") );
+ if (djvi)
+ G_THROW( ERR_MSG("DjVuFile.corrupt_INFO") );
+ // DjVuInfo::decode no longer throws version exceptions
+ GP<DjVuInfo> xinfo=DjVuInfo::create();
+ xinfo->decode(bs);
+ info = xinfo;
+ desc.format( ERR_MSG("DjVuFile.page_info") );
+ // Consistency checks (previously in DjVuInfo::decode)
+ if (info->width<0 || info->height<0)
+ G_THROW( ERR_MSG("DjVuFile.corrupt_zero") );
+ if (info->version >= DJVUVERSION_TOO_NEW)
+ G_THROW( ERR_MSG("DjVuFile.new_version") "\t" STRINGIFY(DJVUVERSION_TOO_NEW) );
+ if(info->compressable)
+ set_can_compress(true);
+ }
+
+ // INCL (inclusion chunk)
+ else if (chkid == "INCL" && (djvi || djvu || iw44))
+ {
+ GP<DjVuFile> file=process_incl_chunk(bs);
+ if (file)
+ {
+ int decode_was_already_started = 1;
+ {
+ GMonitorLock lock(&file->flags);
+ // Start decoding
+ if(file->resume_decode())
+ {
+ decode_was_already_started = 0;
+ }
+ }
+ // Send file notifications if previously started
+ if (decode_was_already_started)
+ {
+ // May send duplicate notifications...
+ if (file->is_decode_ok())
+ get_portcaster()->notify_file_flags_changed(file, DECODE_OK, 0);
+ else if (file->is_decode_failed())
+ get_portcaster()->notify_file_flags_changed(file, DECODE_FAILED, 0);
+ }
+ desc.format( ERR_MSG("DjVuFile.indir_chunk1") "\t" + file->get_url().fname() );
+ } else
+ desc.format( ERR_MSG("DjVuFile.indir_chunk2") );
+ }
+
+ // Djbz (JB2 Dictionary)
+ else if (chkid == "Djbz" && (djvu || djvi))
+ {
+ if (this->fgjd)
+ G_THROW( ERR_MSG("DjVuFile.dupl_Dxxx") );
+ if (this->fgjd)
+ G_THROW( ERR_MSG("DjVuFile.Dxxx_after_Sxxx") );
+ GP<JB2Dict> fgjd = JB2Dict::create();
+ fgjd->decode(gbs);
+ this->fgjd = fgjd;
+ desc.format( ERR_MSG("DjVuFile.shape_dict") "\t%d", fgjd->get_shape_count() );
+ }
+
+ // Sjbz (JB2 encoded mask)
+ else if (chkid=="Sjbz" && (djvu || djvi))
+ {
+ if (this->fgjb)
+ G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
+ GP<JB2Image> fgjb=JB2Image::create();
+ // ---- begin hack
+ if (info && info->version <=18)
+ fgjb->reproduce_old_bug = true;
+ // ---- end hack
+ fgjb->decode(gbs, static_get_fgjd, (void*)this);
+ this->fgjb = fgjb;
+ desc.format( ERR_MSG("DjVuFile.fg_mask") "\t%d\t%d\t%d",
+ fgjb->get_width(), fgjb->get_height(),
+ get_dpi(fgjb->get_width(), fgjb->get_height()));
+ }
+
+ // Smmr (MMR-G4 encoded mask)
+ else if (chkid=="Smmr" && (djvu || djvi))
+ {
+ if (this->fgjb)
+ G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
+ set_can_compress(true);
+ this->fgjb = MMRDecoder::decode(gbs);
+ desc.format( ERR_MSG("DjVuFile.G4_mask") "\t%d\t%d\t%d",
+ fgjb->get_width(), fgjb->get_height(),
+ get_dpi(fgjb->get_width(), fgjb->get_height()));
+ }
+
+ // BG44 (background wavelets)
+ else if (chkid == "BG44" && (djvu || djvi))
+ {
+ if (!bg44)
+ {
+ if (bgpm)
+ G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
+ // First chunk
+ GP<IW44Image> bg44=IW44Image::create_decode(IW44Image::COLOR);
+ bg44->decode_chunk(gbs);
+ this->bg44 = bg44;
+ desc.format( ERR_MSG("DjVuFile.IW44_bg1") "\t%d\t%d\t%d",
+ bg44->get_width(), bg44->get_height(),
+ get_dpi(bg44->get_width(), bg44->get_height()));
+ }
+ else
+ {
+ // Refinement chunks
+ GP<IW44Image> bg44 = this->bg44;
+ bg44->decode_chunk(gbs);
+ desc.format( ERR_MSG("DjVuFile.IW44_bg2") "\t%d\t%d",
+ bg44->get_serial(), get_dpi(bg44->get_width(), bg44->get_height()));
+ }
+ }
+
+ // FG44 (foreground wavelets)
+ else if (chkid == "FG44" && (djvu || djvu))
+ {
+ if (fgpm || fgbc)
+ G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
+ GP<IW44Image> gfg44=IW44Image::create_decode(IW44Image::COLOR);
+ IW44Image &fg44=*gfg44;
+ fg44.decode_chunk(gbs);
+ fgpm=fg44.get_pixmap();
+ desc.format( ERR_MSG("DjVuFile.IW44_fg") "\t%d\t%d\t%d",
+ fg44.get_width(), fg44.get_height(),
+ get_dpi(fg44.get_width(), fg44.get_height()));
+ }
+
+ // LINK (background LINK)
+ else if (chkid == "LINK" && (djvu || djvi))
+ {
+ if (bg44 || bgpm)
+ G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
+ if(djvu_decode_codec)
+ {
+ set_modified(true);
+ set_can_compress(true);
+ set_needs_compression(true);
+ this->bgpm = djvu_decode_codec(bs);
+ desc.format( ERR_MSG("DjVuFile.color_import1") "\t%d\t%d\t%d",
+ bgpm->columns(), bgpm->rows(),
+ get_dpi(bgpm->columns(), bgpm->rows()));
+ }else
+ {
+ desc.format( ERR_MSG("DjVuFile.color_import2") );
+ }
+ }
+
+ // BGjp (background JPEG)
+ else if (chkid == "BGjp" && (djvu || djvi))
+ {
+ if (bg44 || bgpm)
+ G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
+ set_can_compress(true);
+#ifdef NEED_JPEG_DECODER
+ this->bgpm = JPEGDecoder::decode(bs);
+ desc.format( ERR_MSG("DjVuFile.JPEG_bg1") "\t%d\t%d\t%d",
+ bgpm->columns(), bgpm->rows(),
+ get_dpi(bgpm->columns(), bgpm->rows()));
+#else
+ desc.format( ERR_MSG("DjVuFile.JPEG_bg2") );
+#endif
+ }
+
+ // FGjp (foreground JPEG)
+ else if (chkid == "FGjp" && (djvu || djvi))
+ {
+ if (fgpm || fgbc)
+ G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
+#ifdef NEED_JPEG_DECODER
+ this->fgpm = JPEGDecoder::decode(bs);
+ desc.format( ERR_MSG("DjVuFile.JPEG_fg1") "\t%d\t%d\t%d",
+ fgpm->columns(), fgpm->rows(),
+ get_dpi(fgpm->columns(), fgpm->rows()));
+#else
+ desc.format( ERR_MSG("DjVuFile.JPEG_fg2") );
+#endif
+ }
+
+ // BG2k (background JPEG-2000) Note: JPEG2K bitstream not finalized.
+ else if (chkid == "BG2k" && (djvu || djvi))
+ {
+ if (bg44)
+ G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
+ desc.format( ERR_MSG("DjVuFile.JPEG2K_bg") );
+ }
+
+ // FG2k (foreground JPEG-2000) Note: JPEG2K bitstream not finalized.
+ else if (chkid == "FG2k" && (djvu || djvi))
+ {
+ if (fgpm || fgbc)
+ G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
+ desc.format( ERR_MSG("DjVuFile.JPEG2K_fg") );
+ }
+
+ // FGbz (foreground color vector)
+ else if (chkid == "FGbz" && (djvu || djvi))
+ {
+ if (fgpm || fgbc)
+ G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
+ GP<DjVuPalette> fgbc = DjVuPalette::create();
+ fgbc->decode(gbs);
+ this->fgbc = fgbc;
+ desc.format( ERR_MSG("DjVuFile.JB2_fg") "\t%d\t%d",
+ fgbc->size(), fgbc->colordata.size());
+ }
+
+ // BM44/PM44 (IW44 data)
+ else if ((chkid == "PM44" || chkid=="BM44") && iw44)
+ {
+ if (!bg44)
+ {
+ // First chunk
+ GP<IW44Image> bg44 = IW44Image::create_decode(IW44Image::COLOR);
+ bg44->decode_chunk(gbs);
+ GP<DjVuInfo> info = DjVuInfo::create();
+ info->width = bg44->get_width();
+ info->height = bg44->get_height();
+ info->dpi = 100;
+ this->bg44 = bg44;
+ this->info = info;
+ desc.format( ERR_MSG("DjVuFile.IW44_data1") "\t%d\t%d\t%d",
+ bg44->get_width(), bg44->get_height(),
+ get_dpi(bg44->get_width(), bg44->get_height()));
+ }
+ else
+ {
+ // Refinement chunks
+ GP<IW44Image> bg44 = this->bg44;
+ bg44->decode_chunk(gbs);
+ desc.format( ERR_MSG("DjVuFile.IW44_data2") "\t%d\t%d",
+ bg44->get_serial(),
+ get_dpi(bg44->get_width(), bg44->get_height()));
+ }
+ }
+
+ // NDIR (obsolete navigation chunk)
+ else if (chkid == "NDIR")
+ {
+ GP<DjVuNavDir> dir=DjVuNavDir::create(url);
+ dir->decode(bs);
+ this->dir=dir;
+ desc.format( ERR_MSG("DjVuFile.nav_dir") );
+ }
+
+ // FORM:ANNO (obsolete) (must be before other annotations)
+ else if (chkid == "FORM:ANNO")
+ {
+ const GP<ByteStream> gachunk(ByteStream::create());
+ ByteStream &achunk=*gachunk;
+ achunk.copy(bs);
+ achunk.seek(0);
+ GCriticalSectionLock lock(&anno_lock);
+ if (! anno)
+ {
+ anno=ByteStream::create();
+ }
+ anno->seek(0,SEEK_END);
+ if (anno->tell())
+ {
+ anno->write((void*)"", 1);
+ }
+ // Copy data
+ anno->copy(achunk);
+ desc.format( ERR_MSG("DjVuFile.anno1") );
+ }
+
+ // ANTa/ANTx/TXTa/TXTz annotations
+ else if (is_annotation(chkid)) // but not FORM:ANNO
+ {
+ const GP<ByteStream> gachunk(ByteStream::create());
+ ByteStream &achunk=*gachunk;
+ achunk.copy(bs);
+ achunk.seek(0);
+ GCriticalSectionLock lock(&anno_lock);
+ if (! anno)
+ {
+ anno = ByteStream::create();
+ }
+ anno->seek(0,SEEK_END);
+ if (anno->tell() & 1)
+ {
+ anno->write((const void*)"", 1);
+ }
+ // Recreate chunk header
+ const GP<IFFByteStream> giffout(IFFByteStream::create(anno));
+ IFFByteStream &iffout=*giffout;
+ iffout.put_chunk(id);
+ iffout.copy(achunk);
+ iffout.close_chunk();
+ desc.format( ERR_MSG("DjVuFile.anno2") );
+ }
+ else if (is_text(chkid))
+ {
+ const GP<ByteStream> gachunk(ByteStream::create());
+ ByteStream &achunk=*gachunk;
+ achunk.copy(bs);
+ achunk.seek(0);
+ GCriticalSectionLock lock(&text_lock);
+ if (! text)
+ {
+ text = ByteStream::create();
+ }
+ text->seek(0,SEEK_END);
+ if (text->tell())
+ {
+ text->write((const void*)"", 1);
+ }
+ // Recreate chunk header
+ const GP<IFFByteStream> giffout(IFFByteStream::create(text));
+ IFFByteStream &iffout=*giffout;
+ iffout.put_chunk(id);
+ iffout.copy(achunk);
+ iffout.close_chunk();
+ desc.format( ERR_MSG("DjVuFile.text") );
+ }
+ else if (is_meta(chkid))
+ {
+ const GP<ByteStream> gachunk(ByteStream::create());
+ ByteStream &achunk=*gachunk;
+ achunk.copy(bs);
+ achunk.seek(0);
+ GCriticalSectionLock lock(&text_lock);
+ if (! meta)
+ {
+ meta = ByteStream::create();
+ }
+ meta->seek(0,SEEK_END);
+ if (meta->tell())
+ {
+ meta->write((const void*)"", 1);
+ }
+ // Recreate chunk header
+ const GP<IFFByteStream> giffout(IFFByteStream::create(meta));
+ IFFByteStream &iffout=*giffout;
+ iffout.put_chunk(id);
+ iffout.copy(achunk);
+ iffout.close_chunk();
+// desc.format( ERR_MSG("DjVuFile.text") );
+ }
+
+ // Return description
+ return desc;
+}
+
+void
+DjVuFile::set_decode_codec(GP<GPixmap> (*codec)(ByteStream &bs))
+{
+ djvu_decode_codec=codec;
+}
+
+void
+DjVuFile::decode(const GP<ByteStream> &gbs)
+{
+ check();
+ DEBUG_MSG("DjVuFile::decode(), url='" << url << "'\n");
+ DEBUG_MAKE_INDENT(3);
+ DjVuPortcaster * pcaster=get_portcaster();
+
+ // Get form chunk
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(gbs));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ REPORT_EOF(true)
+
+ // Check file format
+ bool djvi = (chkid=="FORM:DJVI")?true:false;
+ bool djvu = (chkid=="FORM:DJVU")?true:false;
+ bool iw44 = ((chkid=="FORM:PM44") || (chkid=="FORM:BM44"));
+ if (djvi || djvu)
+ mimetype = "image/x.djvu";
+ else if (iw44)
+ mimetype = "image/x-iw44";
+ else
+ G_THROW( ERR_MSG("DjVuFile.unexp_image") );
+
+ // Process chunks
+ int size_so_far=iff.tell();
+ int chunks=0;
+ int last_chunk=0;
+ G_TRY
+ {
+ int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
+ int chksize;
+ for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
+ {
+ chunks++;
+
+ // Decode and get chunk description
+ GUTF8String str = decode_chunk(chkid, iff.get_bytestream(), djvi, djvu, iw44);
+ // Add parameters to the chunk description to give the size and chunk id
+ GUTF8String desc;
+ desc.format("\t%5.1f\t%s", chksize/1024.0, (const char*)chkid);
+ // Append the whole thing to the growing file description
+ description = description + str + desc + "\n";
+
+ pcaster->notify_chunk_done(this, chkid);
+ // Close chunk
+ iff.seek_close_chunk();
+ // Record file size
+ size_so_far=iff.tell();
+ }
+ if (chunks_number < 0) chunks_number=last_chunk;
+ }
+ G_CATCH(ex)
+ {
+ if(!ex.cmp_cause(ByteStream::EndOfFile))
+ {
+ if (chunks_number < 0)
+ chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
+ report_error(ex,(recover_errors <= SKIP_PAGES));
+ }else
+ {
+ report_error(ex,true);
+ }
+ }
+ G_ENDCATCH;
+
+ // Record file size
+ file_size=size_so_far;
+ // Close form chunk
+ iff.close_chunk();
+ // Close BG44 codec
+ if (bg44)
+ bg44->close_codec();
+
+ // Complete description
+ if (djvu && !info)
+ G_THROW( ERR_MSG("DjVuFile.corrupt_missing_info") );
+ if (iw44 && !info)
+ G_THROW( ERR_MSG("DjVuFile.corrupt_missing_IW44") );
+ if (info)
+ {
+ GUTF8String desc;
+ if (djvu || djvi)
+ desc.format( ERR_MSG("DjVuFile.djvu_header") "\t%d\t%d\t%d\t%d",
+ info->width, info->height,
+ info->dpi, info->version);
+ else if (iw44)
+ desc.format( ERR_MSG("DjVuFile.IW44_header") "\t%d\t%d\t%d",
+ info->width, info->height, info->dpi);
+ description=desc + "\n" + description;
+ int rawsize=info->width*info->height*3;
+ desc.format( ERR_MSG("DjVuFile.ratio") "\t%0.1f\t%0.1f",
+ (double)rawsize/file_size, file_size/1024.0 );
+ description=description+desc;
+ }
+}
+
+void
+DjVuFile::start_decode(void)
+{
+ check();
+ DEBUG_MSG("DjVuFile::start_decode(), url='" << url << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ GThread * thread_to_delete=0;
+ flags.enter();
+ G_TRY {
+ if (!(flags & DONT_START_DECODE) && !is_decoding())
+ {
+ if (flags & DECODE_STOPPED) reset();
+ flags&=~(DECODE_OK | DECODE_STOPPED | DECODE_FAILED);
+ flags|=DECODING;
+
+ // Don't delete the thread while you're owning the flags lock
+ // Beware of deadlock!
+ thread_to_delete=decode_thread; decode_thread=0;
+
+ // We want to create it right here to be able to stop the
+ // decoding thread even before its function is called (it starts)
+ decode_data_pool=DataPool::create(data_pool);
+ decode_life_saver=this;
+
+ decode_thread=new GThread();
+ decode_thread->create(static_decode_func, this);
+ }
+ }
+ G_CATCH_ALL
+ {
+ flags&=~DECODING;
+ flags|=DECODE_FAILED;
+ flags.leave();
+ get_portcaster()->notify_file_flags_changed(this, DECODE_FAILED, DECODING);
+ delete thread_to_delete;
+ G_RETHROW;
+ }
+ G_ENDCATCH;
+ flags.leave();
+ delete thread_to_delete;
+}
+
+bool
+DjVuFile::resume_decode(const bool sync)
+{
+ bool retval=false;
+ {
+ GMonitorLock lock(&flags);
+ if( !is_decoding() && !is_decode_ok() && !is_decode_failed() )
+ {
+ start_decode();
+ retval=true;
+ }
+ }
+ if(sync)
+ {
+ wait_for_finish();
+ }
+ return retval;
+}
+
+void
+DjVuFile::stop_decode(bool sync)
+{
+ check();
+
+ DEBUG_MSG("DjVuFile::stop_decode(), url='" << url <<
+ "', sync=" << (int) sync << "\n");
+ DEBUG_MAKE_INDENT(3);
+
+ G_TRY
+ {
+ flags|=DONT_START_DECODE;
+
+ // Don't stop SYNCHRONOUSLY from the thread where the decoding is going!!!
+ {
+ // First - ask every included child to stop in async mode
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;++pos)
+ inc_files_list[pos]->stop_decode(0);
+
+// if (decode_data_pool) decode_data_pool->stop();
+ }
+
+ if (sync)
+ {
+ while(1)
+ {
+ GP<DjVuFile> file;
+ {
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;++pos)
+ {
+ GP<DjVuFile> & f=inc_files_list[pos];
+ if (f->is_decoding())
+ {
+ file=f; break;
+ }
+ }
+ }
+ if (!file) break;
+
+ file->stop_decode(1);
+ }
+
+ wait_for_finish(1); // Wait for self termination
+
+ // Don't delete the thread here. Until GPBase::preserve() is
+ // reimplemented somehow at the GThread level.
+ // delete decode_thread; decode_thread=0;
+ }
+ flags&=~(DONT_START_DECODE);
+ } G_CATCH_ALL {
+ flags&=~(DONT_START_DECODE);
+ G_RETHROW;
+ } G_ENDCATCH;
+}
+
+void
+DjVuFile::stop(bool only_blocked)
+// This is a one-way function. There is no way to undo the stop()
+// command.
+{
+ DEBUG_MSG("DjVuFile::stop(): Stopping everything\n");
+ DEBUG_MAKE_INDENT(3);
+
+ flags|=only_blocked ? BLOCKED_STOPPED : STOPPED;
+ if (data_pool) data_pool->stop(only_blocked);
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;++pos)
+ inc_files_list[pos]->stop(only_blocked);
+}
+
+GP<DjVuNavDir>
+DjVuFile::find_ndir(GMap<GURL, void *> & map)
+{
+ check();
+
+ DEBUG_MSG("DjVuFile::find_ndir(): looking for NDIR in '" << url << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ if (dir) return dir;
+
+ if (!map.contains(url))
+ {
+ map[url]=0;
+
+ GPList<DjVuFile> list=get_included_files(false);
+ for(GPosition pos=list;pos;++pos)
+ {
+ GP<DjVuNavDir> d=list[pos]->find_ndir(map);
+ if (d) return d;
+ }
+ }
+ return 0;
+}
+
+GP<DjVuNavDir>
+DjVuFile::find_ndir(void)
+{
+ GMap<GURL, void *> map;
+ return find_ndir(map);
+}
+
+GP<DjVuNavDir>
+DjVuFile::decode_ndir(GMap<GURL, void *> & map)
+{
+ check();
+
+ DEBUG_MSG("DjVuFile::decode_ndir(): decoding for NDIR in '" << url << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ if (dir) return dir;
+
+ if (!map.contains(url))
+ {
+ map[url]=0;
+
+ const GP<ByteStream> str(data_pool->get_stream());
+
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ REPORT_EOF(true)
+
+ int chunks=0;
+ int last_chunk=0;
+ G_TRY
+ {
+ int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
+ int chksize;
+ for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
+ {
+ chunks++;
+ if (chkid=="NDIR")
+ {
+ GP<DjVuNavDir> d=DjVuNavDir::create(url);
+ d->decode(*iff.get_bytestream());
+ dir=d;
+ break;
+ }
+ iff.seek_close_chunk();
+ }
+ if ((!dir)&&(chunks_number < 0)) chunks_number=last_chunk;
+ }
+ G_CATCH(ex)
+ {
+ if(!ex.cmp_cause(ByteStream::EndOfFile))
+ {
+ if (chunks_number < 0)
+ chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
+ report_error(ex,(recover_errors<=SKIP_PAGES));
+ }else
+ {
+ report_error(ex,true);
+ }
+ }
+ G_ENDCATCH;
+
+ data_pool->clear_stream();
+ if (dir) return dir;
+
+ GPList<DjVuFile> list=get_included_files(false);
+ for(GPosition pos=list;pos;++pos)
+ {
+ GP<DjVuNavDir> d=list[pos]->decode_ndir(map);
+ if (d) return d;
+ }
+ data_pool->clear_stream();
+ }
+ return 0;
+}
+
+GP<DjVuNavDir>
+DjVuFile::decode_ndir(void)
+{
+ GMap<GURL, void *> map;
+ return decode_ndir(map);
+}
+
+void
+DjVuFile::get_merged_anno(const GP<DjVuFile> & file,
+ const GP<ByteStream> &gstr_out, const GList<GURL> & ignore_list,
+ int level, int & max_level, GMap<GURL, void *> & map)
+{
+ DEBUG_MSG("DjVuFile::get_merged_anno()\n");
+ GURL url=file->get_url();
+ if (!map.contains(url))
+ {
+ ByteStream &str_out=*gstr_out;
+ map[url]=0;
+
+ // Do the included files first (To make sure that they have
+ // less precedence)
+ // Depending on if we have all data present, we will
+ // either create all included files or will use only
+ // those that have already been created
+ GPList<DjVuFile> list=file->get_included_files(!file->is_data_present());
+ for(GPosition pos=list;pos;++pos)
+ get_merged_anno(list[pos], gstr_out, ignore_list, level+1, max_level, map);
+
+ // Now process the DjVuFile's own annotations
+ if (!ignore_list.contains(file->get_url()))
+ {
+ if (!file->is_data_present() ||
+ file->is_modified() && file->anno)
+ {
+ // Process the decoded (?) anno
+ GCriticalSectionLock lock(&file->anno_lock);
+ if (file->anno && file->anno->size())
+ {
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ file->anno->seek(0);
+ str_out.copy(*file->anno);
+ }
+ } else if (file->is_data_present())
+ {
+ // Copy all annotations chunks, but do NOT modify
+ // this->anno (to avoid correlation with DjVuFile::decode())
+ const GP<ByteStream> str(file->data_pool->get_stream());
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ GUTF8String chkid;
+ if (iff.get_chunk(chkid))
+ while(iff.get_chunk(chkid))
+ {
+ if (chkid=="FORM:ANNO")
+ {
+ if (max_level<level)
+ max_level=level;
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ str_out.copy(*iff.get_bytestream());
+ }
+ else if (is_annotation(chkid)) // but not FORM:ANNO
+ {
+ if (max_level<level)
+ max_level=level;
+ if (str_out.tell()&&chkid != "ANTz")
+ {
+ str_out.write((void *) "", 1);
+ }
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff.get_bytestream());
+ iff_out.close_chunk();
+ }
+ iff.close_chunk();
+ }
+ file->data_pool->clear_stream();
+ }
+ }
+ }
+}
+
+GP<ByteStream>
+DjVuFile::get_merged_anno(const GList<GURL> & ignore_list,
+ int * max_level_ptr)
+ // Will do the same thing as get_merged_anno(int *), but will
+ // ignore DjVuFiles with URLs from the ignore_list
+{
+ DEBUG_MSG("DjVuFile::get_merged_anno()\n");
+ GP<ByteStream> gstr(ByteStream::create());
+ GMap<GURL, void *> map;
+ int max_level=0;
+ get_merged_anno(this, gstr, ignore_list, 0, max_level, map);
+ if (max_level_ptr)
+ *max_level_ptr=max_level;
+ ByteStream &str=*gstr;
+ if (!str.tell())
+ {
+ gstr=0;
+ }else
+ {
+ str.seek(0);
+ }
+ return gstr;
+}
+
+GP<ByteStream>
+DjVuFile::get_merged_anno(int * max_level_ptr)
+// Will go down the DjVuFile's hierarchy and decode all DjVuAnno even
+// when the DjVuFile is not fully decoded yet. To avoid correlations
+// with DjVuFile::decode(), we do not modify DjVuFile::anno data.
+//
+// Files deeper in the hierarchy have less influence on the
+// results. It means, for example, that the if annotations are
+// specified in the top level page file and in a shared file,
+// the top level page file settings will take precedence.
+//
+// NOTE! This function guarantees correct results only if the
+// DjVuFile has all data
+{
+ GList<GURL> ignore_list;
+ return get_merged_anno(ignore_list, max_level_ptr);
+}
+
+
+// [LB->BCR] The following six functions get_anno, get_text, get_meta
+// contain the same code in triplicate!!!
+
+void
+DjVuFile::get_anno(
+ const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
+{
+ DEBUG_MSG("DjVuFile::get_anno()\n");
+ ByteStream &str_out=*gstr_out;
+ if (!file->is_data_present() ||
+ file->is_modified() && file->anno)
+ {
+ // Process the decoded (?) anno
+ GCriticalSectionLock lock(&file->anno_lock);
+ if (file->anno && file->anno->size())
+ {
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ file->anno->seek(0);
+ str_out.copy(*file->anno);
+ }
+ } else if (file->is_data_present())
+ {
+ // Copy all anno chunks, but do NOT modify
+ // DjVuFile::anno (to avoid correlation with DjVuFile::decode())
+ const GP<ByteStream> str=file->data_pool->get_stream();
+ const GP<IFFByteStream> giff=IFFByteStream::create(str);
+ IFFByteStream &iff=*giff;
+ GUTF8String chkid;
+ if (iff.get_chunk(chkid))
+ {
+ while(iff.get_chunk(chkid))
+ {
+ if (is_annotation(chkid))
+ {
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff.get_bytestream());
+ iff_out.close_chunk();
+ }
+ iff.close_chunk();
+ }
+ }
+ file->data_pool->clear_stream();
+ }
+}
+
+void
+DjVuFile::get_text(
+ const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
+{
+ DEBUG_MSG("DjVuFile::get_text()\n");
+ ByteStream &str_out=*gstr_out;
+ if (!file->is_data_present() ||
+ file->is_modified() && file->text)
+ {
+ // Process the decoded (?) text
+ GCriticalSectionLock lock(&file->text_lock);
+ if (file->text && file->text->size())
+ {
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ file->text->seek(0);
+ str_out.copy(*file->text);
+ }
+ } else if (file->is_data_present())
+ {
+ // Copy all text chunks, but do NOT modify
+ // DjVuFile::text (to avoid correlation with DjVuFile::decode())
+ const GP<ByteStream> str=file->data_pool->get_stream();
+ const GP<IFFByteStream> giff=IFFByteStream::create(str);
+ IFFByteStream &iff=*giff;
+ GUTF8String chkid;
+ if (iff.get_chunk(chkid))
+ {
+ while(iff.get_chunk(chkid))
+ {
+ if (is_text(chkid))
+ {
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff.get_bytestream());
+ iff_out.close_chunk();
+ }
+ iff.close_chunk();
+ }
+ }
+ file->data_pool->clear_stream();
+ }
+}
+
+void
+DjVuFile::get_meta(
+ const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
+{
+ DEBUG_MSG("DjVuFile::get_meta()\n");
+ ByteStream &str_out=*gstr_out;
+ if (!file->is_data_present() ||
+ file->is_modified() && file->meta)
+ {
+ // Process the decoded (?) meta
+ GCriticalSectionLock lock(&file->meta_lock);
+ if (file->meta && file->meta->size())
+ {
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ file->meta->seek(0);
+ str_out.copy(*file->meta);
+ }
+ } else if (file->is_data_present())
+ {
+ // Copy all meta chunks, but do NOT modify
+ // DjVuFile::meta (to avoid correlation with DjVuFile::decode())
+ const GP<ByteStream> str=file->data_pool->get_stream();
+ const GP<IFFByteStream> giff=IFFByteStream::create(str);
+ IFFByteStream &iff=*giff;
+ GUTF8String chkid;
+ if (iff.get_chunk(chkid))
+ {
+ while(iff.get_chunk(chkid))
+ {
+ if (is_meta(chkid))
+ {
+ if (str_out.tell())
+ {
+ str_out.write((void *) "", 1);
+ }
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff.get_bytestream());
+ iff_out.close_chunk();
+ }
+ iff.close_chunk();
+ }
+ }
+ file->data_pool->clear_stream();
+ }
+}
+
+GP<ByteStream>
+DjVuFile::get_anno(void)
+{
+ DEBUG_MSG("DjVuFile::get_text(void)\n");
+ GP<ByteStream> gstr(ByteStream::create());
+ get_anno(this, gstr);
+ ByteStream &str=*gstr;
+ if (!str.tell())
+ {
+ gstr=0;
+ }else
+ {
+ str.seek(0);
+ }
+ return gstr;
+}
+
+GP<ByteStream>
+DjVuFile::get_text(void)
+{
+ DEBUG_MSG("DjVuFile::get_text(void)\n");
+ GP<ByteStream> gstr(ByteStream::create());
+ get_text(this, gstr);
+ ByteStream &str=*gstr;
+ if (!str.tell())
+ {
+ gstr=0;
+ }else
+ {
+ str.seek(0);
+ }
+ return gstr;
+}
+
+GP<ByteStream>
+DjVuFile::get_meta(void)
+{
+ DEBUG_MSG("DjVuFile::get_meta(void)\n");
+ GP<ByteStream> gstr(ByteStream::create());
+ get_meta(this, gstr);
+ ByteStream &str=*gstr;
+ if (!str.tell())
+ {
+ gstr=0;
+ }else
+ {
+ str.seek(0);
+ }
+ return gstr;
+}
+
+void
+DjVuFile::static_trigger_cb(void * cl_data)
+{
+ DjVuFile * th=(DjVuFile *) cl_data;
+ G_TRY {
+ GP<DjVuPort> port=DjVuPort::get_portcaster()->is_port_alive(th);
+ if (port && port->inherits("DjVuFile"))
+ ((DjVuFile *) (DjVuPort *) port)->trigger_cb();
+ } G_CATCH(exc) {
+ G_TRY {
+ get_portcaster()->notify_error(th, exc.get_cause());
+ } G_CATCH_ALL {} G_ENDCATCH;
+ } G_ENDCATCH;
+}
+
+void
+DjVuFile::trigger_cb(void)
+{
+ GP<DjVuFile> life_saver=this;
+
+ DEBUG_MSG("DjVuFile::trigger_cb(): got data for '" << url << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ file_size=data_pool->get_length();
+ flags|=DATA_PRESENT;
+ get_portcaster()->notify_file_flags_changed(this, DATA_PRESENT, 0);
+
+ if (!are_incl_files_created())
+ process_incl_chunks();
+
+ bool all=true;
+ inc_files_lock.lock();
+ GPList<DjVuFile> files_list=inc_files_list;
+ inc_files_lock.unlock();
+ for(GPosition pos=files_list;pos&&(all=files_list[pos]->is_all_data_present());++pos)
+ EMPTY_LOOP;
+ if (all)
+ {
+ DEBUG_MSG("DjVuFile::trigger_cb(): We have ALL data for '" << url << "'\n");
+ flags|=ALL_DATA_PRESENT;
+ get_portcaster()->notify_file_flags_changed(this, ALL_DATA_PRESENT, 0);
+ }
+}
+
+void
+DjVuFile::progress_cb(int pos, void * cl_data)
+{
+ DEBUG_MSG("DjVuFile::progress_cb() called\n");
+ DEBUG_MAKE_INDENT(3);
+
+ DjVuFile * th=(DjVuFile *) cl_data;
+
+ int length=th->decode_data_pool->get_length();
+ if (length>0)
+ {
+ float progress=(float) pos/length;
+ DEBUG_MSG("progress=" << progress << "\n");
+ get_portcaster()->notify_decode_progress(th, progress);
+ } else
+ {
+ DEBUG_MSG("DataPool size is still unknown => ignoring\n");
+ }
+}
+
+//*****************************************************************************
+//******************************** Utilities **********************************
+//*****************************************************************************
+
+void
+DjVuFile::move(GMap<GURL, void *> & map, const GURL & dir_url)
+// This function may block for data.
+{
+ if (!map.contains(url))
+ {
+ map[url]=0;
+
+ url=GURL::UTF8(url.name(),dir_url);
+
+
+ // Leave the lock here!
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;++pos)
+ inc_files_list[pos]->move(map, dir_url);
+ }
+}
+
+void
+DjVuFile::move(const GURL & dir_url)
+// This function may block for data.
+{
+ check();
+ DEBUG_MSG("DjVuFile::move(): dir_url='" << dir_url << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ GMap<GURL, void *> map;
+ move(map, dir_url);
+}
+
+void
+DjVuFile::set_name(const GUTF8String &name)
+{
+ DEBUG_MSG("DjVuFile::set_name(): name='" << name << "'\n");
+ DEBUG_MAKE_INDENT(3);
+ url=GURL::UTF8(name,url.base());
+}
+
+//*****************************************************************************
+//****************************** Data routines ********************************
+//*****************************************************************************
+
+int
+DjVuFile::get_chunks_number(void)
+{
+ if(chunks_number < 0)
+ {
+ const GP<ByteStream> str(data_pool->get_stream());
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ REPORT_EOF(true)
+
+ int chunks=0;
+ int last_chunk=0;
+ G_TRY
+ {
+ int chksize;
+ for(;(chksize=iff.get_chunk(chkid));last_chunk=chunks)
+ {
+ chunks++;
+ iff.seek_close_chunk();
+ }
+ chunks_number=last_chunk;
+ }
+ G_CATCH(ex)
+ {
+ chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
+ report_error(ex,(recover_errors<=SKIP_PAGES));
+ }
+ G_ENDCATCH;
+ data_pool->clear_stream();
+ }
+ return chunks_number;
+}
+
+GUTF8String
+DjVuFile::get_chunk_name(int chunk_num)
+{
+ if(chunk_num < 0)
+ {
+ G_THROW( ERR_MSG("DjVuFile.illegal_chunk") );
+ }
+ if((chunks_number >= 0)&&(chunk_num > chunks_number))
+ {
+ G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
+ }
+ check();
+
+ GUTF8String name;
+ const GP<ByteStream> str(data_pool->get_stream());
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ REPORT_EOF(true)
+
+ int chunks=0;
+ int last_chunk=0;
+ G_TRY
+ {
+ int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
+ int chksize;
+ for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
+ {
+ if (chunks++==chunk_num) { name=chkid; break; }
+ iff.seek_close_chunk();
+ }
+ }
+ G_CATCH(ex)
+ {
+ if (chunks_number < 0)
+ chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
+ report_error(ex,(recover_errors <= SKIP_PAGES));
+ }
+ G_ENDCATCH;
+ if (!name.length())
+ {
+ if (chunks_number < 0) chunks_number=chunks;
+ G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
+ }
+ return name;
+}
+
+bool
+DjVuFile::contains_chunk(const GUTF8String &chunk_name)
+{
+ check();
+ DEBUG_MSG("DjVuFile::contains_chunk(): url='" << url << "', chunk_name='" <<
+ chunk_name << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ bool contains=0;
+ const GP<ByteStream> str(data_pool->get_stream());
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ REPORT_EOF((recover_errors<=SKIP_PAGES))
+
+ int chunks=0;
+ int last_chunk=0;
+ G_TRY
+ {
+ int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
+ int chksize;
+ for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
+ {
+ chunks++;
+ if (chkid==chunk_name) { contains=1; break; }
+ iff.seek_close_chunk();
+ }
+ if (!contains &&(chunks_number < 0)) chunks_number=last_chunk;
+ }
+ G_CATCH(ex)
+ {
+ if (chunks_number < 0)
+ chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
+ report_error(ex,(recover_errors <= SKIP_PAGES));
+ }
+ G_ENDCATCH;
+ data_pool->clear_stream();
+ return contains;
+}
+
+bool
+DjVuFile::contains_anno(void)
+{
+ const GP<ByteStream> str(data_pool->get_stream());
+
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ G_THROW( ByteStream::EndOfFile );
+
+ while(iff.get_chunk(chkid))
+ {
+ if (is_annotation(chkid))
+ return true;
+ iff.close_chunk();
+ }
+
+ data_pool->clear_stream();
+ return false;
+}
+
+bool
+DjVuFile::contains_text(void)
+{
+ const GP<ByteStream> str(data_pool->get_stream());
+
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ G_THROW( ByteStream::EndOfFile );
+
+ while(iff.get_chunk(chkid))
+ {
+ if (is_text(chkid))
+ return true;
+ iff.close_chunk();
+ }
+
+ data_pool->clear_stream();
+ return false;
+}
+
+bool
+DjVuFile::contains_meta(void)
+{
+ const GP<ByteStream> str(data_pool->get_stream());
+
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ G_THROW( ByteStream::EndOfFile );
+
+ while(iff.get_chunk(chkid))
+ {
+ if (is_meta(chkid))
+ return true;
+ iff.close_chunk();
+ }
+
+ data_pool->clear_stream();
+ return false;
+}
+
+//*****************************************************************************
+//****************************** Save routines ********************************
+//*****************************************************************************
+
+static void
+copy_chunks(const GP<ByteStream> &from, IFFByteStream &ostr)
+{
+ from->seek(0);
+ const GP<IFFByteStream> giff(IFFByteStream::create(from));
+ IFFByteStream &iff=*giff;
+ GUTF8String chkid;
+ int chksize;
+ while ((chksize=iff.get_chunk(chkid)))
+ {
+ ostr.put_chunk(chkid);
+ int ochksize=ostr.copy(*iff.get_bytestream());
+ ostr.close_chunk();
+ iff.seek_close_chunk();
+ if(ochksize != chksize)
+ {
+ G_THROW( ByteStream::EndOfFile );
+ }
+ }
+}
+
+
+void
+DjVuFile::add_djvu_data(IFFByteStream & ostr, GMap<GURL, void *> & map,
+ const bool included_too, const bool no_ndir)
+{
+ check();
+ if (map.contains(url)) return;
+ bool top_level = !map.size();
+ map[url]=0;
+ bool processed_annotation = false;
+ bool processed_text = false;
+ bool processed_meta = false;
+
+ const GP<ByteStream> str(data_pool->get_stream());
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff(IFFByteStream::create(str));
+ IFFByteStream &iff=*giff;
+ if (!iff.get_chunk(chkid))
+ REPORT_EOF(true)
+
+ // Open toplevel form
+ if (top_level)
+ ostr.put_chunk(chkid);
+ // Process chunks
+ int chunks=0;
+ int last_chunk=0;
+ G_TRY
+ {
+ int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
+ int chksize;
+ for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
+ {
+ chunks++;
+ if (is_info(chkid) && info)
+ {
+ ostr.put_chunk(chkid);
+ info->encode(*ostr.get_bytestream());
+ ostr.close_chunk();
+ }
+ else if (chkid=="INCL" && included_too)
+ {
+ GP<DjVuFile> file = process_incl_chunk(*iff.get_bytestream());
+ if (file)
+ {
+ if(recover_errors!=ABORT)
+ file->set_recover_errors(recover_errors);
+ if(verbose_eof)
+ file->set_verbose_eof(verbose_eof);
+ file->add_djvu_data(ostr, map, included_too, no_ndir);
+ }
+ }
+ else if (is_annotation(chkid) && anno && anno->size())
+ {
+ if (!processed_annotation)
+ {
+ processed_annotation = true;
+ GCriticalSectionLock lock(&anno_lock);
+ copy_chunks(anno, ostr);
+ }
+ }
+ else if (is_text(chkid) && text && text->size())
+ {
+ if (!processed_text)
+ {
+ processed_text = true;
+ GCriticalSectionLock lock(&text_lock);
+ copy_chunks(text, ostr);
+ }
+ }
+ else if (is_meta(chkid) && meta && meta->size())
+ {
+ if (!processed_meta)
+ {
+ processed_meta = true;
+ GCriticalSectionLock lock(&meta_lock);
+ copy_chunks(meta, ostr);
+ }
+ }
+ else if (chkid!="NDIR"||!(no_ndir || dir))
+ { // Copy NDIR chunks, but never generate new ones.
+ ostr.put_chunk(chkid);
+ ostr.copy(*iff.get_bytestream());
+ ostr.close_chunk();
+ }
+ iff.seek_close_chunk();
+ }
+ if (chunks_number < 0) chunks_number=last_chunk;
+ }
+ G_CATCH(ex)
+ {
+ if(!ex.cmp_cause(ByteStream::EndOfFile))
+ {
+ if (chunks_number < 0)
+ chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
+ report_error(ex,(recover_errors<=SKIP_PAGES));
+ }else
+ {
+ report_error(ex,true);
+ }
+ }
+ G_ENDCATCH;
+
+ // Otherwise, writes annotation at the end (annotations could be big)
+ if (!processed_annotation && anno && anno->size())
+ {
+ processed_annotation = true;
+ GCriticalSectionLock lock(&anno_lock);
+ copy_chunks(anno, ostr);
+ }
+ if (!processed_text && text && text->size())
+ {
+ processed_text = true;
+ GCriticalSectionLock lock(&text_lock);
+ copy_chunks(text, ostr);
+ }
+ if (!processed_meta && meta && meta->size())
+ {
+ processed_meta = true;
+ GCriticalSectionLock lock(&meta_lock);
+ copy_chunks(meta, ostr);
+ }
+ // Close iff
+ if (top_level)
+ ostr.close_chunk();
+
+ data_pool->clear_stream();
+}
+
+GP<ByteStream>
+DjVuFile::get_djvu_bytestream(const bool included_too, const bool no_ndir)
+{
+ check();
+ DEBUG_MSG("DjVuFile::get_djvu_bytestream(): creating DjVu raw file\n");
+ DEBUG_MAKE_INDENT(3);
+ const GP<ByteStream> pbs(ByteStream::create());
+ const GP<IFFByteStream> giff=IFFByteStream::create(pbs);
+ IFFByteStream &iff=*giff;
+ GMap<GURL, void *> map;
+ add_djvu_data(iff, map, included_too, no_ndir);
+ iff.flush();
+ pbs->seek(0, SEEK_SET);
+ return pbs;
+}
+
+GP<DataPool>
+DjVuFile::get_djvu_data(const bool included_too, const bool no_ndir)
+{
+ const GP<ByteStream> pbs = get_djvu_bytestream(included_too, no_ndir);
+ return DataPool::create(pbs);
+}
+
+void
+DjVuFile::merge_anno(ByteStream &out)
+{
+ // Reuse get_merged_anno(), which is better than the previous
+ // implementation due to three things:
+ // 1. It works even before the file is completely decoded
+ // 2. It merges annotations taking into account where a child DjVuFile
+ // is included.
+ // 3. It handles loops in DjVuFile's hierarchy
+
+ const GP<ByteStream> str(get_merged_anno());
+ if (str)
+ {
+ str->seek(0);
+ if (out.tell())
+ {
+ out.write((void *) "", 1);
+ }
+ out.copy(*str);
+ }
+}
+
+void
+DjVuFile::get_text(ByteStream &out)
+{
+ const GP<ByteStream> str(get_text());
+ if (str)
+ {
+ str->seek(0);
+ if (out.tell())
+ {
+ out.write((void *) "", 1);
+ }
+ out.copy(*str);
+ }
+}
+
+void
+DjVuFile::get_meta(ByteStream &out)
+{
+ const GP<ByteStream> str(get_meta());
+ if (str)
+ {
+ str->seek(0);
+ if (out.tell())
+ {
+ out.write((void *) "", 1);
+ }
+ out.copy(*str);
+ }
+}
+
+
+
+//****************************************************************************
+//******************************* Modifying **********************************
+//****************************************************************************
+
+void
+DjVuFile::remove_anno(void)
+{
+ DEBUG_MSG("DjVuFile::remove_anno()\n");
+ const GP<ByteStream> str_in(data_pool->get_stream());
+ const GP<ByteStream> gstr_out(ByteStream::create());
+
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
+ IFFByteStream &iff_in=*giff_in;
+ if (!iff_in.get_chunk(chkid))
+ G_THROW( ByteStream::EndOfFile );
+
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+ iff_out.put_chunk(chkid);
+
+ while(iff_in.get_chunk(chkid))
+ {
+ if (!is_annotation(chkid))
+ {
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff_in.get_bytestream());
+ iff_out.close_chunk();
+ }
+ iff_in.close_chunk();
+ }
+
+ iff_out.close_chunk();
+
+ gstr_out->seek(0, SEEK_SET);
+ data_pool=DataPool::create(gstr_out);
+ chunks_number=-1;
+
+ anno=0;
+
+ flags|=MODIFIED;
+ data_pool->clear_stream();
+}
+
+void
+DjVuFile::remove_text(void)
+{
+ DEBUG_MSG("DjVuFile::remove_text()\n");
+ const GP<ByteStream> str_in(data_pool->get_stream());
+ const GP<ByteStream> gstr_out(ByteStream::create());
+
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
+ IFFByteStream &iff_in=*giff_in;
+ if (!iff_in.get_chunk(chkid))
+ G_THROW( ByteStream::EndOfFile );
+
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+ iff_out.put_chunk(chkid);
+
+ while(iff_in.get_chunk(chkid))
+ {
+ if (!is_text(chkid))
+ {
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff_in.get_bytestream());
+ iff_out.close_chunk();
+ }
+ iff_in.close_chunk();
+ }
+
+ iff_out.close_chunk();
+
+ gstr_out->seek(0, SEEK_SET);
+ data_pool=DataPool::create(gstr_out);
+ chunks_number=-1;
+
+ text=0;
+
+ flags|=MODIFIED;
+ data_pool->clear_stream();
+}
+
+void
+DjVuFile::remove_meta(void)
+{
+ DEBUG_MSG("DjVuFile::remove_meta()\n");
+ const GP<ByteStream> str_in(data_pool->get_stream());
+ const GP<ByteStream> gstr_out(ByteStream::create());
+
+ GUTF8String chkid;
+ const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
+ IFFByteStream &iff_in=*giff_in;
+ if (!iff_in.get_chunk(chkid))
+ G_THROW( ByteStream::EndOfFile );
+
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+ iff_out.put_chunk(chkid);
+
+ while(iff_in.get_chunk(chkid))
+ {
+ if (!is_meta(chkid))
+ {
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff_in.get_bytestream());
+ iff_out.close_chunk();
+ }
+ iff_in.close_chunk();
+ }
+
+ iff_out.close_chunk();
+
+ gstr_out->seek(0, SEEK_SET);
+ data_pool=DataPool::create(gstr_out);
+ chunks_number=-1;
+
+ meta=0;
+
+ flags|=MODIFIED;
+ data_pool->clear_stream();
+}
+
+void
+DjVuFile::rebuild_data_pool(void)
+{
+ data_pool=get_djvu_data(false,false);
+ chunks_number=1;
+ flags|=MODIFIED;
+}
+
+// Do NOT comment this function out. It's used by DjVuDocEditor to convert
+// old-style DjVu documents to BUNDLED format.
+
+GP<DataPool>
+DjVuFile::unlink_file(const GP<DataPool> & data, const GUTF8String &name)
+// Will process contents of data[] and remove any INCL chunk
+// containing 'name'
+{
+ DEBUG_MSG("DjVuFile::unlink_file()\n");
+ const GP<ByteStream> gstr_out(ByteStream::create());
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+
+ const GP<ByteStream> str_in(data->get_stream());
+ const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
+ IFFByteStream &iff_in=*giff_in;
+
+ int chksize;
+ GUTF8String chkid;
+ if (!iff_in.get_chunk(chkid)) return data;
+
+ iff_out.put_chunk(chkid);
+
+ while((chksize=iff_in.get_chunk(chkid)))
+ {
+ if (chkid=="INCL")
+ {
+ GUTF8String incl_str;
+ char buffer[1024];
+ int length;
+ while((length=iff_in.read(buffer, 1024)))
+ incl_str+=GUTF8String(buffer, length);
+
+ // Eat '\n' in the beginning and at the end
+ while(incl_str.length() && incl_str[0]=='\n')
+ {
+ incl_str=incl_str.substr(1,(unsigned int)(-1));
+ }
+ while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
+ {
+ incl_str.setat(incl_str.length()-1, 0);
+ }
+ if (incl_str!=name)
+ {
+ iff_out.put_chunk(chkid);
+ iff_out.get_bytestream()->writestring(incl_str);
+ iff_out.close_chunk();
+ }
+ } else
+ {
+ iff_out.put_chunk(chkid);
+ char buffer[1024];
+ int length;
+ for(const GP<ByteStream> gbs(iff_out.get_bytestream());
+ (length=iff_in.read(buffer, 1024));)
+ {
+ gbs->writall(buffer, length);
+ }
+ iff_out.close_chunk();
+ }
+ iff_in.close_chunk();
+ }
+ iff_out.close_chunk();
+ iff_out.flush();
+ gstr_out->seek(0, SEEK_SET);
+ data->clear_stream();
+ return DataPool::create(gstr_out);
+}
+
+#ifndef NEED_DECODER_ONLY
+void
+DjVuFile::insert_file(const GUTF8String &id, int chunk_num)
+{
+ DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "', chunk_num="
+ << chunk_num << "\n");
+ DEBUG_MAKE_INDENT(3);
+
+ // First: create new data
+ const GP<ByteStream> str_in(data_pool->get_stream());
+ const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
+ IFFByteStream &iff_in=*giff_in;
+
+ const GP<ByteStream> gstr_out(ByteStream::create());
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+
+ int chunk_cnt=0;
+ bool done=false;
+ GUTF8String chkid;
+ if (iff_in.get_chunk(chkid))
+ {
+ iff_out.put_chunk(chkid);
+ int chksize;
+ while((chksize=iff_in.get_chunk(chkid)))
+ {
+ if (chunk_cnt++==chunk_num)
+ {
+ iff_out.put_chunk("INCL");
+ iff_out.get_bytestream()->writestring(id);
+ iff_out.close_chunk();
+ done=true;
+ }
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff_in.get_bytestream());
+ iff_out.close_chunk();
+ iff_in.close_chunk();
+ }
+ if (!done)
+ {
+ iff_out.put_chunk("INCL");
+ iff_out.get_bytestream()->writestring(id);
+ iff_out.close_chunk();
+ }
+ iff_out.close_chunk();
+ }
+ gstr_out->seek(0, SEEK_SET);
+ data_pool=DataPool::create(gstr_out);
+ chunks_number=-1;
+
+ // Second: create missing DjVuFiles
+ process_incl_chunks();
+
+ flags|=MODIFIED;
+ data_pool->clear_stream();
+}
+#endif
+
+void
+DjVuFile::unlink_file(const GUTF8String &id)
+{
+ DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "'\n");
+ DEBUG_MAKE_INDENT(3);
+
+ // Remove the file from the list of included files
+ {
+ GURL url=DjVuPort::get_portcaster()->id_to_url(this, id);
+ if (url.is_empty()) url=GURL::UTF8(id,this->url.base());
+ GCriticalSectionLock lock(&inc_files_lock);
+ for(GPosition pos=inc_files_list;pos;)
+ if (inc_files_list[pos]->get_url()==url)
+ {
+ GPosition this_pos=pos;
+ ++pos;
+ inc_files_list.del(this_pos);
+ } else ++pos;
+ }
+
+ // And update the data.
+ const GP<ByteStream> str_in(data_pool->get_stream());
+ const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
+ IFFByteStream &iff_in=*giff_in;
+
+ const GP<ByteStream> gstr_out(ByteStream::create());
+ const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
+ IFFByteStream &iff_out=*giff_out;
+
+ GUTF8String chkid;
+ if (iff_in.get_chunk(chkid))
+ {
+ iff_out.put_chunk(chkid);
+ int chksize;
+ while((chksize=iff_in.get_chunk(chkid)))
+ {
+ if (chkid!="INCL")
+ {
+ iff_out.put_chunk(chkid);
+ iff_out.copy(*iff_in.get_bytestream());
+ iff_out.close_chunk();
+ } else
+ {
+ GUTF8String incl_str;
+ char buffer[1024];
+ int length;
+ while((length=iff_in.read(buffer, 1024)))
+ incl_str+=GUTF8String(buffer, length);
+
+ // Eat '\n' in the beginning and at the end
+ while(incl_str.length() && incl_str[0]=='\n')
+ {
+ incl_str=incl_str.substr(1,(unsigned int)(-1));
+ }
+ while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
+ incl_str.setat(incl_str.length()-1, 0);
+ if (incl_str!=id)
+ {
+ iff_out.put_chunk("INCL");
+ iff_out.get_bytestream()->writestring(incl_str);
+ iff_out.close_chunk();
+ }
+ }
+ iff_in.close_chunk();
+ }
+ iff_out.close_chunk();
+ }
+
+ gstr_out->seek(0, SEEK_SET);
+ data_pool=DataPool::create(gstr_out);
+ chunks_number=-1;
+
+ flags|=MODIFIED;
+}
+
+void
+DjVuFile::change_info(GP<DjVuInfo> xinfo,const bool do_reset)
+{
+ DEBUG_MSG("DjVuFile::change_text()\n");
+ // Mark this as modified
+ set_modified(true);
+ if(do_reset)
+ reset();
+ info=xinfo;
+}
+
+#ifndef NEED_DECODER_ONLY
+void
+DjVuFile::change_text(GP<DjVuTXT> txt,const bool do_reset)
+{
+ DEBUG_MSG("DjVuFile::change_text()\n");
+ GP<DjVuText> gtext_c=DjVuText::create();
+ DjVuText &text_c=*gtext_c;
+ if(contains_text())
+ {
+ const GP<ByteStream> file_text(get_text());
+ if(file_text)
+ {
+ text_c.decode(file_text);
+ }
+ }
+ GCriticalSectionLock lock(&text_lock);
+ // Mark this as modified
+ set_modified(true);
+ if(do_reset)
+ reset();
+ text_c.txt = txt;
+ text=ByteStream::create();
+ text_c.encode(text);
+}
+
+void
+DjVuFile::change_meta(const GUTF8String &xmeta,const bool do_reset)
+{
+ DEBUG_MSG("DjVuFile::change_meta()\n");
+ // Mark this as modified
+ set_modified(true);
+ if(contains_meta())
+ {
+ (void)get_meta();
+ }
+ if(do_reset)
+ reset();
+ GCriticalSectionLock lock(&meta_lock);
+ meta=ByteStream::create();
+ if(xmeta.length())
+ {
+ const GP<IFFByteStream> giff=IFFByteStream::create(meta);
+ IFFByteStream &iff=*giff;
+ iff.put_chunk("METz");
+ {
+ GP<ByteStream> gbsiff=BSByteStream::create(iff.get_bytestream(),50);
+ gbsiff->writestring(xmeta);
+ }
+ iff.close_chunk();
+ }
+}
+#endif
+
+
+#ifdef HAVE_NAMESPACES
+}
+# ifndef NOT_USING_DJVU_NAMESPACE
+using namespace DJVU;
+# endif
+#endif