source: trunk/libdjvu/DjVuFile.cpp @ 15

Last change on this file since 15 was 15, checked in by Eugene Romanenko, 15 years ago

needed libs update

File size: 73.7 KB
Line 
1//C-  -*- C++ -*-
2//C- -------------------------------------------------------------------
3//C- DjVuLibre-3.5
4//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
5//C- Copyright (c) 2001  AT&T
6//C-
7//C- This software is subject to, and may be distributed under, the
8//C- GNU General Public License, Version 2. The license should have
9//C- accompanied the software or you may obtain a copy of the license
10//C- from the Free Software Foundation at http://www.fsf.org .
11//C-
12//C- This program is distributed in the hope that it will be useful,
13//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
14//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15//C- GNU General Public License for more details.
16//C-
17//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
18//C- distributed by Lizardtech Software.  On July 19th 2002, Lizardtech
19//C- Software authorized us to replace the original DjVu(r) Reference
20//C- Library notice by the following text (see doc/lizard2002.djvu):
21//C-
22//C-  ------------------------------------------------------------------
23//C- | DjVu (r) Reference Library (v. 3.5)
24//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
25//C- | The DjVu Reference Library is protected by U.S. Pat. No.
26//C- | 6,058,214 and patents pending.
27//C- |
28//C- | This software is subject to, and may be distributed under, the
29//C- | GNU General Public License, Version 2. The license should have
30//C- | accompanied the software or you may obtain a copy of the license
31//C- | from the Free Software Foundation at http://www.fsf.org .
32//C- |
33//C- | The computer code originally released by LizardTech under this
34//C- | license and unmodified by other parties is deemed "the LIZARDTECH
35//C- | ORIGINAL CODE."  Subject to any third party intellectual property
36//C- | claims, LizardTech grants recipient a worldwide, royalty-free,
37//C- | non-exclusive license to make, use, sell, or otherwise dispose of
38//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the
39//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
40//C- | General Public License.   This grant only confers the right to
41//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
42//C- | the extent such infringement is reasonably necessary to enable
43//C- | recipient to make, have made, practice, sell, or otherwise dispose
44//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
45//C- | any greater extent that may be necessary to utilize further
46//C- | modifications or combinations.
47//C- |
48//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
49//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
50//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
51//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
52//C- +------------------------------------------------------------------
53//
54// $Id: DjVuFile.cpp,v 1.11 2003/11/07 22:08:20 leonb Exp $
55// $Name: release_3_5_16 $
56
57#ifdef HAVE_CONFIG_H
58# include "config.h"
59#endif
60#if NEED_GNUG_PRAGMAS
61# pragma implementation
62#endif
63
64#include "DjVuFile.h"
65#include "IFFByteStream.h"
66#include "GOS.h"
67#include "MMRDecoder.h"
68#ifdef NEED_JPEG_DECODER
69#include "JPEGDecoder.h"
70#endif
71#include "DjVuAnno.h"
72#include "DjVuText.h"
73#include "DataPool.h"
74#include "JB2Image.h"
75#include "IW44Image.h"
76#include "DjVuNavDir.h"
77#ifndef NEED_DECODER_ONLY
78#include "BSByteStream.h"
79#endif // NEED_DECODER_ONLY
80
81#include "debug.h"
82
83
84#ifdef HAVE_NAMESPACES
85namespace DJVU {
86# ifdef NOT_DEFINED // Just to fool emacs c++ mode
87}
88#endif
89#endif
90
91
92#define STRINGIFY(x) STRINGIFY_(x)
93#define STRINGIFY_(x) #x
94
95
96#define REPORT_EOF(x) \
97  {G_TRY{G_THROW( ByteStream::EndOfFile );}G_CATCH(ex){report_error(ex,(x));}G_ENDCATCH;}
98
99static GP<GPixmap> (*djvu_decode_codec)(ByteStream &bs)=0;
100
101class ProgressByteStream : public ByteStream
102{
103public:
104  ProgressByteStream(const GP<ByteStream> & xstr) : str(xstr),
105    last_call_pos(0) {}
106  virtual ~ProgressByteStream() {}
107
108  virtual size_t read(void *buffer, size_t size)
109  {
110    int rc=0;
111           // G_TRY {} CATCH; block here is merely to avoid egcs internal error
112    G_TRY {
113      int cur_pos=str->tell();
114      if (progress_cb && (last_call_pos/256!=cur_pos/256))
115      {
116        progress_cb(cur_pos, progress_cl_data);
117        last_call_pos=cur_pos;
118      }
119      rc=str->read(buffer, size);
120    } G_CATCH_ALL {
121      G_RETHROW;
122    } G_ENDCATCH;
123    return rc;
124  }
125  virtual size_t write(const void *buffer, size_t size)
126  {
127    return str->write(buffer, size);
128  }
129  virtual int seek(long offset, int whence = SEEK_SET, bool nothrow=false)
130  {
131    return str->seek(offset, whence);
132  }
133  virtual long tell(void ) const { return str->tell(); }
134
135  void          set_progress_cb(void (* xprogress_cb)(int, void *),
136  void * xprogress_cl_data)
137  {
138    progress_cb=xprogress_cb;
139    progress_cl_data=xprogress_cl_data;
140  }
141private:
142  GP<ByteStream> str;
143  void          * progress_cl_data;
144  void          (* progress_cb)(int pos, void *);
145  int           last_call_pos;
146 
147  // Cancel C++ default stuff
148  ProgressByteStream & operator=(const ProgressByteStream &);
149};
150
151
152DjVuFile::DjVuFile()
153: file_size(0), recover_errors(ABORT), verbose_eof(false), chunks_number(-1),
154initialized(false)
155{
156}
157
158void
159DjVuFile::check() const
160{
161  if (!initialized)
162    G_THROW( ERR_MSG("DjVuFile.not_init") );
163}
164
165GP<DjVuFile>
166DjVuFile::create(
167  const GP<ByteStream> & str, const ErrorRecoveryAction recover_errors,
168  const bool verbose_eof )
169{
170  DjVuFile *file=new DjVuFile();
171  GP<DjVuFile> retval=file;
172  file->set_recover_errors(recover_errors);
173  file->set_verbose_eof(verbose_eof);
174  file->init(str);
175  return retval;
176}
177
178void 
179DjVuFile::init(const GP<ByteStream> & str)
180{
181  DEBUG_MSG("DjVuFile::DjVuFile(): ByteStream constructor\n");
182  DEBUG_MAKE_INDENT(3);
183 
184  if (initialized)
185    G_THROW( ERR_MSG("DjVuFile.2nd_init") );
186  if (!get_count())
187    G_THROW( ERR_MSG("DjVuFile.not_secured") );
188 
189  file_size=0;
190  decode_thread=0;
191 
192  // Read the data from the stream
193  data_pool=DataPool::create(str);
194 
195  // Construct some dummy URL
196  GUTF8String buffer;
197  buffer.format("djvufile:/%p.djvu", this);
198  DEBUG_MSG("DjVuFile::DjVuFile(): url is "<<(const char *)buffer<<"\n");
199  url=GURL::UTF8(buffer);
200 
201  // Set it here because trigger will call other DjVuFile's functions
202  initialized=true;
203 
204  // Add (basically - call) the trigger
205  data_pool->add_trigger(-1, static_trigger_cb, this);
206}
207
208GP<DjVuFile>
209DjVuFile::create(
210  const GURL & xurl, GP<DjVuPort> port, 
211  const ErrorRecoveryAction recover_errors, const bool verbose_eof ) 
212{
213  DjVuFile *file=new DjVuFile();
214  GP<DjVuFile> retval=file;
215  file->set_recover_errors(recover_errors);
216  file->set_verbose_eof(verbose_eof);
217  file->init(xurl,port);
218  return retval;
219}
220
221void
222DjVuFile::init(const GURL & xurl, GP<DjVuPort> port) 
223{
224  DEBUG_MSG("DjVuFile::init(): url='" << xurl << "'\n");
225  DEBUG_MAKE_INDENT(3);
226 
227  if (initialized)
228    G_THROW( ERR_MSG("DjVuFile.2nd_init") );
229  if (!get_count())
230    G_THROW( ERR_MSG("DjVuFile.not_secured") );
231  if (xurl.is_empty())
232    G_THROW( ERR_MSG("DjVuFile.empty_URL") );
233 
234  url = xurl;
235  DEBUG_MSG("DjVuFile::DjVuFile(): url is "<<(const char *)url<<"\n");
236  file_size=0;
237  decode_thread=0;
238 
239  DjVuPortcaster * pcaster=get_portcaster();
240 
241  // We need it 'cause we're waiting for our own termination in stop_decode()
242  pcaster->add_route(this, this);
243  if (!port)
244    port = simple_port = new DjVuSimplePort();
245  pcaster->add_route(this, port);
246 
247  // Set it here because trigger will call other DjVuFile's functions
248  initialized=true;
249 
250  if (!(data_pool=DataPool::create(pcaster->request_data(this, url))))
251    G_THROW( ERR_MSG("DjVuFile.no_data") "\t"+url.get_string());
252  data_pool->add_trigger(-1, static_trigger_cb, this);
253}
254
255DjVuFile::~DjVuFile(void)
256{
257  DEBUG_MSG("DjVuFile::~DjVuFile(): destroying...\n");
258  DEBUG_MAKE_INDENT(3);
259 
260  // No more messages. They may result in adding this file to a cache
261  // which will be very-very bad as we're being destroyed
262  get_portcaster()->del_port(this);
263 
264  // Unregister the trigger (we don't want it to be called and attempt
265  // to access the destroyed object)
266  if (data_pool)
267    data_pool->del_trigger(static_trigger_cb, this);
268 
269  // We don't have to wait for decoding to finish here. It's already
270  // finished (we know it because there is a "life saver" in the
271  // thread function)  -- but we need to delete it
272  delete decode_thread; decode_thread=0;
273}
274
275void
276DjVuFile::reset(void)
277{
278   flags.enter();
279   info = 0; 
280   anno = 0; 
281   text = 0; 
282   meta = 0; 
283   bg44 = 0; 
284   fgbc = 0;
285   fgjb = 0; 
286   fgjd = 0;
287   fgpm = 0;
288   dir  = 0; 
289   description = ""; 
290   mimetype = "";
291   flags=(flags&(ALL_DATA_PRESENT|DECODE_STOPPED|DECODE_FAILED));
292   flags.leave();
293}
294
295unsigned int
296DjVuFile::get_memory_usage(void) const
297{
298   unsigned int size=sizeof(*this);
299   if (info) size+=info->get_memory_usage();
300   if (bg44) size+=bg44->get_memory_usage();
301   if (fgjb) size+=fgjb->get_memory_usage();
302   if (fgpm) size+=fgpm->get_memory_usage();
303   if (fgbc) size+=fgbc->size()*sizeof(int);
304   if (anno) size+=anno->size();
305   if (meta) size+=meta->size();
306   if (dir) size+=dir->get_memory_usage();
307   return size;
308}
309
310GPList<DjVuFile>
311DjVuFile::get_included_files(bool only_created)
312{
313  check();
314  if (!only_created && !are_incl_files_created())
315    process_incl_chunks();
316 
317  GCriticalSectionLock lock(&inc_files_lock);
318  GPList<DjVuFile> list=inc_files_list; // Get a copy when locked
319  return list;
320}
321
322void
323DjVuFile::wait_for_chunk(void)
324// Will return after a chunk has been decoded
325{
326  check();
327  DEBUG_MSG("DjVuFile::wait_for_chunk() called\n");
328  DEBUG_MAKE_INDENT(3);
329  chunk_mon.enter();
330  chunk_mon.wait();
331  chunk_mon.leave();
332}
333
334bool
335DjVuFile::wait_for_finish(bool self)
336// if self==TRUE, will block until decoding of this file is over
337// if self==FALSE, will block until decoding of a child (direct
338// or indirect) is over.
339// Will return FALSE if there is nothing to wait for. TRUE otherwise
340{
341  DEBUG_MSG("DjVuFile::wait_for_finish():  self=" << self <<"\n");
342  DEBUG_MAKE_INDENT(3);
343 
344  check();
345 
346  if (self)
347  {
348    // It's best to check for self termination using flags. The reason
349    // is that finish_mon is updated in a DjVuPort function, which
350    // will not be called if the object is being destroyed
351    GMonitorLock lock(&flags);
352    if (is_decoding())
353    {
354      while(is_decoding()) flags.wait();
355      DEBUG_MSG("got it\n");
356      return 1;
357    }
358  } else
359  {
360    // By locking the monitor, we guarantee that situation doesn't change
361    // between the moments when we check for pending finish events
362    // and when we actually run wait(). If we don't lock, the last child
363    // may terminate in between, and we'll wait forever.
364    //
365    // Locking is required by GMonitor interface too, btw.
366    GMonitorLock lock(&finish_mon);
367    GP<DjVuFile> file;
368    {
369      GCriticalSectionLock lock(&inc_files_lock);
370      for(GPosition pos=inc_files_list;pos;++pos)
371      {
372        GP<DjVuFile> & f=inc_files_list[pos];
373        if (f->is_decoding())
374        {
375          file=f; break;
376        }
377      }
378    }
379    if (file)
380    {
381      finish_mon.wait();
382      DEBUG_MSG("got it\n");
383      return 1;
384    }
385  }
386  DEBUG_MSG("nothing to wait for\n");
387  return 0;
388}
389
390void
391DjVuFile::notify_chunk_done(const DjVuPort *, const GUTF8String &)
392{
393  check();
394  chunk_mon.enter();
395  chunk_mon.broadcast();
396  chunk_mon.leave();
397}
398
399void
400DjVuFile::notify_file_flags_changed(const DjVuFile * src,
401                                    long set_mask, long clr_mask)
402{
403  check();
404  if (set_mask & (DECODE_OK | DECODE_FAILED | DECODE_STOPPED))
405  {
406    // Signal threads waiting for file termination
407    finish_mon.enter();
408    finish_mon.broadcast();
409    finish_mon.leave();
410   
411    // In case a thread is still waiting for a chunk
412    chunk_mon.enter();
413    chunk_mon.broadcast();
414    chunk_mon.leave();
415  }
416 
417  if ((set_mask & ALL_DATA_PRESENT) && src!=this &&
418    are_incl_files_created() && is_data_present())
419  {
420    if (src!=this && are_incl_files_created() && is_data_present())
421    {
422      // Check if all children have data
423      bool all=true;
424      {
425        GCriticalSectionLock lock(&inc_files_lock);
426        for(GPosition pos=inc_files_list;pos;++pos)
427          if (!inc_files_list[pos]->is_all_data_present())
428          {
429            all=false;
430            break;
431          }
432      }
433      if (all)
434      {
435        DEBUG_MSG("Just got ALL data for '" << url << "'\n");
436        flags|=ALL_DATA_PRESENT;
437        get_portcaster()->notify_file_flags_changed(this, ALL_DATA_PRESENT, 0);
438      }
439    }
440  }
441}
442
443void
444DjVuFile::static_decode_func(void * cl_data)
445{
446  DjVuFile * th=(DjVuFile *) cl_data;
447 
448  /* Please do not undo this life saver. If you do then try to resolve the
449  following conflict first:
450  1. Decoding starts and there is only one external reference
451  to the DjVuFile.
452  2. Decoding proceeds and calls DjVuPortcaster::notify_error(),
453  which creates inside a temporary GP<DjVuFile>.
454  3. While notify_error() is running, the only external reference
455  is lost, but the DjVuFile is still alive (remember the
456  temporary GP<>?)
457  4. The notify_error() returns, the temporary GP<> gets destroyed
458  and the DjVuFile is attempting to destroy right in the middle
459  of the decoding thread. This is either a dead block (waiting
460  for the termination of the decoding from the ~DjVuFile() called
461  from the decoding thread) or coredump. */
462  GP<DjVuFile> life_saver=th;
463  th->decode_life_saver=0;
464  G_TRY {
465    th->decode_func();
466  } G_CATCH_ALL {
467  } G_ENDCATCH;
468}
469
470void
471DjVuFile::decode_func(void)
472{
473  check();
474  DEBUG_MSG("DjVuFile::decode_func() called, url='" << url << "'\n");
475  DEBUG_MAKE_INDENT(3);
476 
477  DjVuPortcaster * pcaster=get_portcaster();
478 
479  G_TRY {
480    const GP<ByteStream> decode_stream(decode_data_pool->get_stream());
481    ProgressByteStream *pstr=new ProgressByteStream(decode_stream);
482    const GP<ByteStream> gpstr(pstr);
483    pstr->set_progress_cb(progress_cb, this);
484   
485    decode(gpstr);
486   
487    // Wait for all child files to finish
488    while(wait_for_finish(0))
489        continue;
490   
491    DEBUG_MSG("waiting for children termination\n");
492    // Check for termination status
493    GCriticalSectionLock lock(&inc_files_lock);
494    for(GPosition pos=inc_files_list;pos;++pos)
495    {
496      GP<DjVuFile> & f=inc_files_list[pos];
497      if (f->is_decode_failed())
498        G_THROW( ERR_MSG("DjVuFile.decode_fail") );
499      if (f->is_decode_stopped())
500        G_THROW( DataPool::Stop );
501      if (!f->is_decode_ok())
502      {
503        DEBUG_MSG("this_url='" << url << "'\n");
504        DEBUG_MSG("incl_url='" << f->get_url() << "'\n");
505        DEBUG_MSG("decoding=" << f->is_decoding() << "\n");
506        DEBUG_MSG("status='" << f->get_flags() << "\n");
507        G_THROW( ERR_MSG("DjVuFile.not_finished") );
508      }
509    }
510  } G_CATCH(exc) {
511    G_TRY {
512      if (!exc.cmp_cause(DataPool::Stop))
513      {
514        flags.enter();
515        flags=flags & ~DECODING | DECODE_STOPPED;
516        flags.leave();
517        pcaster->notify_status(this, GUTF8String(ERR_MSG("DjVuFile.stopped"))
518                               + GUTF8String("\t") + GUTF8String(url));
519        pcaster->notify_file_flags_changed(this, DECODE_STOPPED, DECODING);
520      } else
521      {
522        flags.enter();
523        flags=flags & ~DECODING | DECODE_FAILED;
524        flags.leave();
525        pcaster->notify_status(this, GUTF8String(ERR_MSG("DjVuFile.failed"))
526                               + GUTF8String("\t") + GUTF8String(url));
527        pcaster->notify_error(this, exc.get_cause());
528        pcaster->notify_file_flags_changed(this, DECODE_FAILED, DECODING);
529      }
530    } G_CATCH_ALL
531    {
532      DEBUG_MSG("******* Oops. Almost missed an exception\n");
533    } G_ENDCATCH;
534  } G_ENDCATCH;
535 
536  decode_data_pool->clear_stream();
537  G_TRY {
538    if (flags.test_and_modify(DECODING, 0, DECODE_OK | INCL_FILES_CREATED, DECODING))
539      pcaster->notify_file_flags_changed(this, DECODE_OK | INCL_FILES_CREATED, 
540                                         DECODING);
541  } G_CATCH_ALL {} G_ENDCATCH;
542  DEBUG_MSG("decoding thread for url='" << url << "' ended\n");
543}
544
545GP<DjVuFile>
546DjVuFile::process_incl_chunk(ByteStream & str, int file_num)
547{
548  check();
549  DEBUG_MSG("DjVuFile::process_incl_chunk(): processing INCL chunk...\n");
550  DEBUG_MAKE_INDENT(3);
551 
552  DjVuPortcaster * pcaster=get_portcaster();
553 
554  GUTF8String incl_str;
555  char buffer[1024];
556  int length;
557  while((length=str.read(buffer, 1024)))
558    incl_str+=GUTF8String(buffer, length);
559 
560  // Eat '\n' in the beginning and at the end
561  while(incl_str.length() && incl_str[0]=='\n')
562  {
563    incl_str=incl_str.substr(1,(unsigned int)(-1));
564  }
565  while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
566  {
567    incl_str.setat(incl_str.length()-1, 0);
568  }
569 
570  if (incl_str.length()>0)
571  {
572    if (strchr(incl_str, '/'))
573      G_THROW( ERR_MSG("DjVuFile.malformed") );
574   
575    DEBUG_MSG("incl_str='" << incl_str << "'\n");
576   
577    GURL incl_url=pcaster->id_to_url(this, incl_str);
578    if (incl_url.is_empty())    // Fallback. Should never be used.
579      incl_url=GURL::UTF8(incl_str,url.base());
580   
581    // Now see if there is already a file with this *name* created
582    {
583      GCriticalSectionLock lock(&inc_files_lock);
584      GPosition pos;
585      for(pos=inc_files_list;pos;++pos)
586      {
587        if (inc_files_list[pos]->url.fname()==incl_url.fname())
588           break;
589      }
590      if (pos)
591        return inc_files_list[pos];
592    }
593   
594    // No. We have to request a new file
595    GP<DjVuFile> file;
596    G_TRY
597    {
598      file=pcaster->id_to_file(this, incl_str);
599    }
600    G_CATCH(ex)
601    {
602      unlink_file(incl_str);
603      // In order to keep compatibility with the previous
604      // release of the DjVu plugin, we will not interrupt
605      // decoding here. We will just report the error.
606      // NOTE, that it's now the responsibility of the
607      // decoder to resolve all chunk dependencies, and
608      // abort decoding if necessary.
609     
610      // G_EXTHROW(ex); /* commented out */
611     
612      get_portcaster()->notify_error(this,ex.get_cause());
613      return 0;
614    }
615    G_ENDCATCH;
616    if (!file)
617    {
618      G_THROW( ERR_MSG("DjVuFile.no_create") "\t"+incl_str);
619    }
620    if (recover_errors!=ABORT)
621      file->set_recover_errors(recover_errors);
622    if (verbose_eof)
623      file->set_verbose_eof(verbose_eof);
624    pcaster->add_route(file, this);
625   
626    // We may have been stopped. Make sure the child will be stopped too.
627    if (flags & STOPPED)
628      file->stop(false);
629    if (flags & BLOCKED_STOPPED)
630      file->stop(true);
631   
632    // Lock the list again and check if the file has already been
633    // added by someone else
634    {
635      GCriticalSectionLock lock(&inc_files_lock);
636      GPosition pos;
637      for(pos=inc_files_list;pos;++pos)
638      {
639        if (inc_files_list[pos]->url.fname()==incl_url.fname())
640          break;
641      }
642      if (pos)
643      {
644        file=inc_files_list[pos];
645      } else if (file_num<0 || !(pos=inc_files_list.nth(file_num)))
646      {
647        inc_files_list.append(file);
648      } else 
649      {
650        inc_files_list.insert_before(pos, file);
651      }
652    }
653    return file;
654  }
655  return 0;
656}
657
658
659void
660DjVuFile::report_error(const GException &ex,bool throw_errors)
661{
662  data_pool->clear_stream();
663  if((!verbose_eof)|| (ex.cmp_cause(ByteStream::EndOfFile)))
664  {
665    if(throw_errors)
666    {
667      G_EXTHROW(ex);
668    }else
669    {
670      get_portcaster()->notify_error(this,ex.get_cause());
671    }
672  }else
673  {
674    GURL url=get_url();
675    GUTF8String url_str=url.get_string();
676//    if (url.is_local_file_url())
677//      url_str=url.filename();
678   
679    GUTF8String msg = GUTF8String( ERR_MSG("DjVuFile.EOF") "\t") + url;
680    if(throw_errors)
681    {
682      G_EXTHROW(ex, msg);
683    }else
684    {
685      get_portcaster()->notify_error(this,msg);
686    }
687  }
688}
689
690void
691DjVuFile::process_incl_chunks(void)
692// This function may block for data
693// NOTE: It may be called again when INCL_FILES_CREATED is set.
694// It happens in insert_file() when it has modified the data
695// and wants to create the actual file
696{
697  DEBUG_MSG("DjVuFile::process_incl_chunks(void)\n");
698  DEBUG_MAKE_INDENT(3);
699  check();
700 
701  int incl_cnt=0;
702 
703  const GP<ByteStream> str(data_pool->get_stream());
704  GUTF8String chkid;
705  const GP<IFFByteStream> giff(IFFByteStream::create(str));
706  IFFByteStream &iff=*giff;
707  if (iff.get_chunk(chkid))
708  {
709    int chunks=0;
710    int last_chunk=0;
711    G_TRY
712    {
713      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
714      int chksize;
715      for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
716      {
717        chunks++;
718        if (chkid=="INCL")
719        {
720          G_TRY
721          {
722            process_incl_chunk(*iff.get_bytestream(), incl_cnt++);
723          }
724          G_CATCH(ex);
725          {
726            report_error(ex,(recover_errors <= SKIP_PAGES));
727          }
728          G_ENDCATCH;
729        }else if(chkid=="FAKE")
730        {
731          set_needs_compression(true);
732          set_can_compress(true);
733        }else if(chkid=="BGjp")
734        {
735          set_can_compress(true);
736        }else if(chkid=="Smmr")
737        {
738          set_can_compress(true);
739        }
740        iff.seek_close_chunk();
741      }
742      if (chunks_number < 0) chunks_number=last_chunk;
743    }
744    G_CATCH(ex)
745    {   
746      if (chunks_number < 0)
747        chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
748      report_error(ex,(recover_errors <= SKIP_PAGES));
749    }
750    G_ENDCATCH;
751  }
752  flags|=INCL_FILES_CREATED;
753  data_pool->clear_stream();
754}
755
756GP<JB2Dict>
757DjVuFile::static_get_fgjd(void *arg)
758{
759  DjVuFile *file = (DjVuFile*)arg;
760  return file->get_fgjd(1);
761}
762
763GP<JB2Dict>
764DjVuFile::get_fgjd(int block)
765{
766  check();
767 
768  // Simplest case
769  if (fgjd)
770    return fgjd;
771  // Check wether included files
772  chunk_mon.enter();
773  G_TRY {
774    for(;;)
775    {
776      int active = 0;
777      GPList<DjVuFile> incs = get_included_files();
778      for (GPosition pos=incs.firstpos(); pos; ++pos)
779      {
780        GP<DjVuFile> file = incs[pos];
781        if (file->is_decoding())
782          active = 1;
783        GP<JB2Dict> fgjd = file->get_fgjd();
784        if (fgjd)
785        {
786          chunk_mon.leave();
787          return fgjd;
788        }
789      }
790      // Exit if non-blocking mode
791      if (! block)
792        break;
793      // Exit if there is no decoding activity
794      if (! active)
795        break;
796      // Wait until a new chunk gets decoded
797      wait_for_chunk();
798    }
799  } G_CATCH_ALL {
800    chunk_mon.leave();
801    G_RETHROW;
802  } G_ENDCATCH;
803  chunk_mon.leave();
804  if (is_decode_stopped()) G_THROW( DataPool::Stop );
805  return 0;
806}
807
808int
809DjVuFile::get_dpi(int w, int h)
810{
811  int dpi=0, red=1;
812  if (info)
813  {
814    for(red=1; red<=12; red++)
815      if ((info->width+red-1)/red==w)
816        if ((info->height+red-1)/red==h)
817          break;
818    if (red>12)
819      G_THROW( ERR_MSG("DjVuFile.corrupt_BG44") );
820    dpi=info->dpi;
821  }
822  return (dpi ? dpi : 300)/red;
823}
824
825static inline bool
826is_info(const GUTF8String &chkid)
827{
828  return (chkid=="INFO");
829}
830
831static inline bool
832is_annotation(const GUTF8String &chkid)
833{
834  return (chkid=="ANTa" ||
835    chkid=="ANTz" ||
836    chkid=="FORM:ANNO" ); 
837}
838
839static inline bool
840is_text(const GUTF8String &chkid)
841{
842  return (chkid=="TXTa" || chkid=="TXTz");
843}
844
845static inline bool
846is_meta(const GUTF8String &chkid)
847{
848  return (chkid=="METa" || chkid=="METz");
849}
850
851
852GUTF8String
853DjVuFile::decode_chunk( const GUTF8String &id, const GP<ByteStream> &gbs,
854  bool djvi, bool djvu, bool iw44)
855{
856  DEBUG_MSG("DjVuFile::decode_chunk()\n");
857  ByteStream &bs=*gbs;
858  check();
859 
860  // If this object is referenced by only one GP<> pointer, this
861  // pointer should be the "life_saver" created by the decoding thread.
862  // If it is the only GP<> pointer, then nobody is interested in the
863  // results of the decoding and we can abort now with #DataPool::Stop#
864  if (get_count()==1)
865    G_THROW( DataPool::Stop );
866 
867  GUTF8String desc = ERR_MSG("DjVuFile.unrecog_chunk");
868  GUTF8String chkid = id;
869  DEBUG_MSG("DjVuFile::decode_chunk() : decoding " << id << "\n");
870 
871  // INFO  (information chunk for djvu page)
872  if (is_info(chkid) && (djvu || djvi))
873  {
874    if (info)
875      G_THROW( ERR_MSG("DjVuFile.corrupt_dupl") );
876    if (djvi)
877      G_THROW( ERR_MSG("DjVuFile.corrupt_INFO") );
878    // DjVuInfo::decode no longer throws version exceptions
879    GP<DjVuInfo> xinfo=DjVuInfo::create();
880    xinfo->decode(bs);
881    info = xinfo;
882    desc.format( ERR_MSG("DjVuFile.page_info") );
883    // Consistency checks (previously in DjVuInfo::decode)
884    if (info->width<0 || info->height<0)
885      G_THROW( ERR_MSG("DjVuFile.corrupt_zero") );
886    if (info->version >= DJVUVERSION_TOO_NEW)
887      G_THROW( ERR_MSG("DjVuFile.new_version") "\t" STRINGIFY(DJVUVERSION_TOO_NEW) );
888    if(info->compressable)
889      set_can_compress(true);
890  }
891 
892  // INCL (inclusion chunk)
893  else if (chkid == "INCL" && (djvi || djvu || iw44))
894  {
895    GP<DjVuFile> file=process_incl_chunk(bs);
896    if (file)
897    {
898      int decode_was_already_started = 1;
899      {
900        GMonitorLock lock(&file->flags);
901          // Start decoding
902        if(file->resume_decode())
903        {
904          decode_was_already_started = 0;
905        }
906      }
907      // Send file notifications if previously started
908      if (decode_was_already_started)
909      {
910        // May send duplicate notifications...
911        if (file->is_decode_ok())
912          get_portcaster()->notify_file_flags_changed(file, DECODE_OK, 0);
913        else if (file->is_decode_failed())
914          get_portcaster()->notify_file_flags_changed(file, DECODE_FAILED, 0);
915      }
916      desc.format( ERR_MSG("DjVuFile.indir_chunk1") "\t" + file->get_url().fname() );
917    } else
918      desc.format( ERR_MSG("DjVuFile.indir_chunk2") );
919  }
920 
921  // Djbz (JB2 Dictionary)
922  else if (chkid == "Djbz" && (djvu || djvi))
923  {
924    if (this->fgjd)
925      G_THROW( ERR_MSG("DjVuFile.dupl_Dxxx") );
926    if (this->fgjd)
927      G_THROW( ERR_MSG("DjVuFile.Dxxx_after_Sxxx") );
928    GP<JB2Dict> fgjd = JB2Dict::create();
929    fgjd->decode(gbs);
930    this->fgjd = fgjd;
931    desc.format( ERR_MSG("DjVuFile.shape_dict") "\t%d", fgjd->get_shape_count() );
932  } 
933 
934  // Sjbz (JB2 encoded mask)
935  else if (chkid=="Sjbz" && (djvu || djvi))
936  {
937    if (this->fgjb)
938      G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
939    GP<JB2Image> fgjb=JB2Image::create();
940    // ---- begin hack
941    if (info && info->version <=18)
942      fgjb->reproduce_old_bug = true;
943    // ---- end hack
944    fgjb->decode(gbs, static_get_fgjd, (void*)this);
945    this->fgjb = fgjb;
946    desc.format( ERR_MSG("DjVuFile.fg_mask") "\t%d\t%d\t%d",
947      fgjb->get_width(), fgjb->get_height(),
948      get_dpi(fgjb->get_width(), fgjb->get_height()));
949  }
950 
951  // Smmr (MMR-G4 encoded mask)
952  else if (chkid=="Smmr" && (djvu || djvi))
953  {
954    if (this->fgjb)
955      G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
956    set_can_compress(true);
957    this->fgjb = MMRDecoder::decode(gbs);
958    desc.format( ERR_MSG("DjVuFile.G4_mask") "\t%d\t%d\t%d",
959      fgjb->get_width(), fgjb->get_height(),
960      get_dpi(fgjb->get_width(), fgjb->get_height()));
961  }
962 
963  // BG44 (background wavelets)
964  else if (chkid == "BG44" && (djvu || djvi))
965  {
966    if (!bg44)
967    {
968      if (bgpm)
969        G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
970      // First chunk
971      GP<IW44Image> bg44=IW44Image::create_decode(IW44Image::COLOR);
972      bg44->decode_chunk(gbs);
973      this->bg44 = bg44;
974      desc.format( ERR_MSG("DjVuFile.IW44_bg1") "\t%d\t%d\t%d",
975                      bg44->get_width(), bg44->get_height(),
976          get_dpi(bg44->get_width(), bg44->get_height()));
977    } 
978    else
979    {
980      // Refinement chunks
981      GP<IW44Image> bg44 = this->bg44;
982      bg44->decode_chunk(gbs);
983      desc.format( ERR_MSG("DjVuFile.IW44_bg2") "\t%d\t%d",
984                      bg44->get_serial(), get_dpi(bg44->get_width(), bg44->get_height()));
985    }
986  }
987 
988  // FG44 (foreground wavelets)
989  else if (chkid == "FG44" && (djvu || djvu))
990  {
991    if (fgpm || fgbc)
992      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
993    GP<IW44Image> gfg44=IW44Image::create_decode(IW44Image::COLOR);
994    IW44Image &fg44=*gfg44;
995    fg44.decode_chunk(gbs);
996    fgpm=fg44.get_pixmap();
997    desc.format( ERR_MSG("DjVuFile.IW44_fg") "\t%d\t%d\t%d",
998      fg44.get_width(), fg44.get_height(),
999      get_dpi(fg44.get_width(), fg44.get_height()));
1000  } 
1001 
1002  // LINK (background LINK)
1003  else if (chkid == "LINK" && (djvu || djvi))
1004  {
1005    if (bg44 || bgpm)
1006      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
1007    if(djvu_decode_codec)
1008    {
1009      set_modified(true);
1010      set_can_compress(true);
1011      set_needs_compression(true);
1012      this->bgpm = djvu_decode_codec(bs);
1013      desc.format( ERR_MSG("DjVuFile.color_import1") "\t%d\t%d\t%d",
1014        bgpm->columns(), bgpm->rows(),
1015        get_dpi(bgpm->columns(), bgpm->rows()));
1016    }else
1017    {
1018      desc.format( ERR_MSG("DjVuFile.color_import2") );
1019    }
1020  } 
1021 
1022  // BGjp (background JPEG)
1023  else if (chkid == "BGjp" && (djvu || djvi))
1024  {
1025    if (bg44 || bgpm)
1026      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
1027    set_can_compress(true);
1028#ifdef NEED_JPEG_DECODER
1029    this->bgpm = JPEGDecoder::decode(bs);
1030    desc.format( ERR_MSG("DjVuFile.JPEG_bg1") "\t%d\t%d\t%d",
1031      bgpm->columns(), bgpm->rows(),
1032      get_dpi(bgpm->columns(), bgpm->rows()));
1033#else
1034    desc.format( ERR_MSG("DjVuFile.JPEG_bg2") );
1035#endif
1036  } 
1037 
1038  // FGjp (foreground JPEG)
1039  else if (chkid == "FGjp" && (djvu || djvi))
1040  {
1041    if (fgpm || fgbc)
1042      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
1043#ifdef NEED_JPEG_DECODER
1044    this->fgpm = JPEGDecoder::decode(bs);
1045    desc.format( ERR_MSG("DjVuFile.JPEG_fg1") "\t%d\t%d\t%d",
1046      fgpm->columns(), fgpm->rows(),
1047      get_dpi(fgpm->columns(), fgpm->rows()));
1048#else
1049    desc.format( ERR_MSG("DjVuFile.JPEG_fg2") );
1050#endif
1051  } 
1052 
1053  // BG2k (background JPEG-2000) Note: JPEG2K bitstream not finalized.
1054  else if (chkid == "BG2k" && (djvu || djvi))
1055  {
1056    if (bg44)
1057      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
1058    desc.format( ERR_MSG("DjVuFile.JPEG2K_bg") );
1059  } 
1060 
1061  // FG2k (foreground JPEG-2000) Note: JPEG2K bitstream not finalized.
1062  else if (chkid == "FG2k" && (djvu || djvi))
1063  {
1064    if (fgpm || fgbc)
1065      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
1066    desc.format( ERR_MSG("DjVuFile.JPEG2K_fg") );
1067  } 
1068 
1069  // FGbz (foreground color vector)
1070  else if (chkid == "FGbz" && (djvu || djvi))
1071  {
1072    if (fgpm || fgbc)
1073      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
1074    GP<DjVuPalette> fgbc = DjVuPalette::create();
1075    fgbc->decode(gbs);
1076    this->fgbc = fgbc;
1077    desc.format( ERR_MSG("DjVuFile.JB2_fg") "\t%d\t%d",
1078      fgbc->size(), fgbc->colordata.size());
1079  }
1080 
1081  // BM44/PM44 (IW44 data)
1082  else if ((chkid == "PM44" || chkid=="BM44") && iw44)
1083  {
1084    if (!bg44)
1085    {
1086      // First chunk
1087      GP<IW44Image> bg44 = IW44Image::create_decode(IW44Image::COLOR);
1088      bg44->decode_chunk(gbs);
1089      GP<DjVuInfo> info = DjVuInfo::create();
1090      info->width = bg44->get_width();
1091      info->height = bg44->get_height();
1092      info->dpi = 100;
1093      this->bg44 = bg44;
1094      this->info = info;
1095      desc.format( ERR_MSG("DjVuFile.IW44_data1") "\t%d\t%d\t%d",
1096                   bg44->get_width(), bg44->get_height(),
1097                   get_dpi(bg44->get_width(), bg44->get_height()));
1098    } 
1099    else
1100    {
1101      // Refinement chunks
1102      GP<IW44Image> bg44 = this->bg44;
1103      bg44->decode_chunk(gbs);
1104      desc.format( ERR_MSG("DjVuFile.IW44_data2") "\t%d\t%d",
1105                   bg44->get_serial(),
1106                   get_dpi(bg44->get_width(), bg44->get_height()));
1107    }
1108  }
1109 
1110  // NDIR (obsolete navigation chunk)
1111  else if (chkid == "NDIR")
1112  {
1113    GP<DjVuNavDir> dir=DjVuNavDir::create(url);
1114    dir->decode(bs);
1115    this->dir=dir;
1116    desc.format( ERR_MSG("DjVuFile.nav_dir") );
1117  }
1118 
1119  // FORM:ANNO (obsolete) (must be before other annotations)
1120  else if (chkid == "FORM:ANNO") 
1121    {
1122      const GP<ByteStream> gachunk(ByteStream::create());
1123      ByteStream &achunk=*gachunk;
1124      achunk.copy(bs);
1125      achunk.seek(0);
1126      GCriticalSectionLock lock(&anno_lock);
1127      if (! anno)
1128      {
1129        anno=ByteStream::create();
1130      }
1131      anno->seek(0,SEEK_END);
1132      if (anno->tell())
1133      {
1134        anno->write((void*)"", 1);
1135      }
1136      // Copy data
1137      anno->copy(achunk);
1138      desc.format( ERR_MSG("DjVuFile.anno1") );
1139    }
1140 
1141  // ANTa/ANTx/TXTa/TXTz annotations
1142  else if (is_annotation(chkid))  // but not FORM:ANNO
1143    {
1144      const GP<ByteStream> gachunk(ByteStream::create());
1145      ByteStream &achunk=*gachunk;
1146      achunk.copy(bs);
1147      achunk.seek(0);
1148      GCriticalSectionLock lock(&anno_lock);
1149      if (! anno)
1150      {
1151        anno = ByteStream::create();
1152      }
1153      anno->seek(0,SEEK_END);
1154      if (anno->tell() & 1)
1155      {
1156        anno->write((const void*)"", 1);
1157      }
1158      // Recreate chunk header
1159      const GP<IFFByteStream> giffout(IFFByteStream::create(anno));
1160      IFFByteStream &iffout=*giffout;
1161      iffout.put_chunk(id);
1162      iffout.copy(achunk);
1163      iffout.close_chunk();
1164      desc.format( ERR_MSG("DjVuFile.anno2") );
1165    }
1166  else if (is_text(chkid))
1167    {
1168      const GP<ByteStream> gachunk(ByteStream::create());
1169      ByteStream &achunk=*gachunk;
1170      achunk.copy(bs);
1171      achunk.seek(0);
1172      GCriticalSectionLock lock(&text_lock);
1173      if (! text)
1174      {
1175        text = ByteStream::create();
1176      }
1177      text->seek(0,SEEK_END);
1178      if (text->tell())
1179      {
1180        text->write((const void*)"", 1);
1181      }
1182      // Recreate chunk header
1183      const GP<IFFByteStream> giffout(IFFByteStream::create(text));
1184      IFFByteStream &iffout=*giffout;
1185      iffout.put_chunk(id);
1186      iffout.copy(achunk);
1187      iffout.close_chunk();
1188      desc.format( ERR_MSG("DjVuFile.text") );
1189    }
1190  else if (is_meta(chkid))
1191    {
1192      const GP<ByteStream> gachunk(ByteStream::create());
1193      ByteStream &achunk=*gachunk;
1194      achunk.copy(bs);
1195      achunk.seek(0);
1196      GCriticalSectionLock lock(&text_lock);
1197      if (! meta)
1198      {
1199        meta = ByteStream::create();
1200      }
1201      meta->seek(0,SEEK_END);
1202      if (meta->tell())
1203      {
1204        meta->write((const void*)"", 1);
1205      }
1206      // Recreate chunk header
1207      const GP<IFFByteStream> giffout(IFFByteStream::create(meta));
1208      IFFByteStream &iffout=*giffout;
1209      iffout.put_chunk(id);
1210      iffout.copy(achunk);
1211      iffout.close_chunk();
1212//      desc.format( ERR_MSG("DjVuFile.text") );
1213    }
1214
1215  // Return description
1216  return desc;
1217}
1218
1219void
1220DjVuFile::set_decode_codec(GP<GPixmap> (*codec)(ByteStream &bs))
1221{
1222  djvu_decode_codec=codec;
1223}
1224
1225void
1226DjVuFile::decode(const GP<ByteStream> &gbs)
1227{
1228  check();
1229  DEBUG_MSG("DjVuFile::decode(), url='" << url << "'\n");
1230  DEBUG_MAKE_INDENT(3);
1231  DjVuPortcaster * pcaster=get_portcaster();
1232 
1233  // Get form chunk
1234  GUTF8String chkid;
1235  const GP<IFFByteStream> giff(IFFByteStream::create(gbs));
1236  IFFByteStream &iff=*giff;
1237  if (!iff.get_chunk(chkid)) 
1238    REPORT_EOF(true)
1239   
1240    // Check file format
1241  bool djvi = (chkid=="FORM:DJVI")?true:false;
1242  bool djvu = (chkid=="FORM:DJVU")?true:false;
1243  bool iw44 = ((chkid=="FORM:PM44") || (chkid=="FORM:BM44"));
1244  if (djvi || djvu)
1245    mimetype = "image/x.djvu";
1246  else if (iw44)
1247    mimetype = "image/x-iw44";
1248  else
1249    G_THROW( ERR_MSG("DjVuFile.unexp_image") );
1250 
1251  // Process chunks
1252  int size_so_far=iff.tell();
1253  int chunks=0;
1254  int last_chunk=0;
1255  G_TRY
1256  {
1257    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
1258    int chksize;
1259    for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
1260    {
1261      chunks++;
1262
1263      // Decode and get chunk description
1264      GUTF8String str = decode_chunk(chkid, iff.get_bytestream(), djvi, djvu, iw44);
1265      // Add parameters to the chunk description to give the size and chunk id
1266      GUTF8String desc;
1267      desc.format("\t%5.1f\t%s", chksize/1024.0, (const char*)chkid);
1268      // Append the whole thing to the growing file description
1269      description = description + str + desc + "\n";
1270
1271      pcaster->notify_chunk_done(this, chkid);
1272      // Close chunk
1273      iff.seek_close_chunk();
1274      // Record file size
1275      size_so_far=iff.tell();
1276    }
1277    if (chunks_number < 0) chunks_number=last_chunk;
1278  }
1279  G_CATCH(ex)
1280  {
1281    if(!ex.cmp_cause(ByteStream::EndOfFile))
1282    {
1283      if (chunks_number < 0)
1284        chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
1285      report_error(ex,(recover_errors <= SKIP_PAGES));
1286    }else
1287    {
1288      report_error(ex,true);
1289    }
1290  }
1291  G_ENDCATCH;
1292 
1293  // Record file size
1294  file_size=size_so_far;
1295  // Close form chunk
1296  iff.close_chunk();
1297  // Close BG44 codec
1298  if (bg44) 
1299    bg44->close_codec();
1300 
1301  // Complete description
1302  if (djvu && !info)
1303    G_THROW( ERR_MSG("DjVuFile.corrupt_missing_info") );
1304  if (iw44 && !info)
1305    G_THROW( ERR_MSG("DjVuFile.corrupt_missing_IW44") );
1306  if (info)
1307  {
1308    GUTF8String desc;
1309    if (djvu || djvi)
1310      desc.format( ERR_MSG("DjVuFile.djvu_header") "\t%d\t%d\t%d\t%d", 
1311        info->width, info->height,
1312        info->dpi, info->version);
1313    else if (iw44)
1314      desc.format( ERR_MSG("DjVuFile.IW44_header") "\t%d\t%d\t%d", 
1315        info->width, info->height, info->dpi);
1316    description=desc + "\n" + description;
1317    int rawsize=info->width*info->height*3;
1318    desc.format( ERR_MSG("DjVuFile.ratio") "\t%0.1f\t%0.1f",
1319      (double)rawsize/file_size, file_size/1024.0 );
1320    description=description+desc;
1321  }
1322}
1323
1324void
1325DjVuFile::start_decode(void)
1326{
1327  check();
1328  DEBUG_MSG("DjVuFile::start_decode(), url='" << url << "'\n");
1329  DEBUG_MAKE_INDENT(3);
1330 
1331  GThread * thread_to_delete=0;
1332  flags.enter();
1333  G_TRY {
1334    if (!(flags & DONT_START_DECODE) && !is_decoding())
1335    {
1336      if (flags & DECODE_STOPPED) reset();
1337      flags&=~(DECODE_OK | DECODE_STOPPED | DECODE_FAILED);
1338      flags|=DECODING;
1339     
1340      // Don't delete the thread while you're owning the flags lock
1341      // Beware of deadlock!
1342      thread_to_delete=decode_thread; decode_thread=0;
1343     
1344      // We want to create it right here to be able to stop the
1345      // decoding thread even before its function is called (it starts)
1346      decode_data_pool=DataPool::create(data_pool);
1347      decode_life_saver=this;
1348     
1349      decode_thread=new GThread();
1350      decode_thread->create(static_decode_func, this);
1351    }
1352  }
1353  G_CATCH_ALL
1354  {
1355    flags&=~DECODING;
1356    flags|=DECODE_FAILED;
1357    flags.leave();
1358    get_portcaster()->notify_file_flags_changed(this, DECODE_FAILED, DECODING);
1359    delete thread_to_delete;
1360    G_RETHROW;
1361  }
1362  G_ENDCATCH;
1363  flags.leave();
1364  delete thread_to_delete;
1365}
1366
1367bool
1368DjVuFile::resume_decode(const bool sync)
1369{
1370  bool retval=false;
1371  {
1372    GMonitorLock lock(&flags);
1373    if( !is_decoding() && !is_decode_ok() && !is_decode_failed() )
1374    {
1375      start_decode();
1376      retval=true;
1377    }
1378  }
1379  if(sync)
1380  {
1381    wait_for_finish();
1382  }
1383  return retval;
1384}
1385
1386void
1387DjVuFile::stop_decode(bool sync)
1388{
1389  check();
1390 
1391  DEBUG_MSG("DjVuFile::stop_decode(), url='" << url <<
1392    "', sync=" << (int) sync << "\n");
1393  DEBUG_MAKE_INDENT(3);
1394 
1395  G_TRY
1396  {
1397    flags|=DONT_START_DECODE;
1398   
1399    // Don't stop SYNCHRONOUSLY from the thread where the decoding is going!!!
1400    {
1401      // First - ask every included child to stop in async mode
1402      GCriticalSectionLock lock(&inc_files_lock);
1403      for(GPosition pos=inc_files_list;pos;++pos)
1404        inc_files_list[pos]->stop_decode(0);
1405     
1406//      if (decode_data_pool) decode_data_pool->stop();
1407    }
1408   
1409    if (sync)
1410    {
1411      while(1)
1412      {
1413        GP<DjVuFile> file;
1414        {
1415          GCriticalSectionLock lock(&inc_files_lock);
1416          for(GPosition pos=inc_files_list;pos;++pos)
1417          {
1418            GP<DjVuFile> & f=inc_files_list[pos];
1419            if (f->is_decoding())
1420            {
1421              file=f; break;
1422            }
1423          }
1424        }
1425        if (!file) break;
1426       
1427        file->stop_decode(1);
1428      }
1429     
1430      wait_for_finish(1);       // Wait for self termination
1431     
1432      // Don't delete the thread here. Until GPBase::preserve() is
1433      // reimplemented somehow at the GThread level.
1434      // delete decode_thread; decode_thread=0;
1435    }
1436    flags&=~(DONT_START_DECODE);
1437  } G_CATCH_ALL {
1438    flags&=~(DONT_START_DECODE);
1439    G_RETHROW;
1440  } G_ENDCATCH;
1441}
1442
1443void
1444DjVuFile::stop(bool only_blocked)
1445// This is a one-way function. There is no way to undo the stop()
1446// command.
1447{
1448  DEBUG_MSG("DjVuFile::stop(): Stopping everything\n");
1449  DEBUG_MAKE_INDENT(3);
1450 
1451  flags|=only_blocked ? BLOCKED_STOPPED : STOPPED;
1452  if (data_pool) data_pool->stop(only_blocked);
1453  GCriticalSectionLock lock(&inc_files_lock);
1454  for(GPosition pos=inc_files_list;pos;++pos)
1455    inc_files_list[pos]->stop(only_blocked);
1456}
1457
1458GP<DjVuNavDir>
1459DjVuFile::find_ndir(GMap<GURL, void *> & map)
1460{
1461  check();
1462 
1463  DEBUG_MSG("DjVuFile::find_ndir(): looking for NDIR in '" << url << "'\n");
1464  DEBUG_MAKE_INDENT(3);
1465 
1466  if (dir) return dir;
1467 
1468  if (!map.contains(url))
1469  {
1470    map[url]=0;
1471   
1472    GPList<DjVuFile> list=get_included_files(false);
1473    for(GPosition pos=list;pos;++pos)
1474    {
1475      GP<DjVuNavDir> d=list[pos]->find_ndir(map);
1476      if (d) return d;
1477    }
1478  }
1479  return 0;
1480}
1481
1482GP<DjVuNavDir>
1483DjVuFile::find_ndir(void)
1484{
1485  GMap<GURL, void *> map;
1486  return find_ndir(map);
1487}
1488
1489GP<DjVuNavDir>
1490DjVuFile::decode_ndir(GMap<GURL, void *> & map)
1491{
1492  check();
1493 
1494  DEBUG_MSG("DjVuFile::decode_ndir(): decoding for NDIR in '" << url << "'\n");
1495  DEBUG_MAKE_INDENT(3);
1496 
1497  if (dir) return dir;
1498 
1499  if (!map.contains(url))
1500  {
1501    map[url]=0;
1502   
1503    const GP<ByteStream> str(data_pool->get_stream());
1504   
1505    GUTF8String chkid;
1506    const GP<IFFByteStream> giff(IFFByteStream::create(str));
1507    IFFByteStream &iff=*giff;
1508    if (!iff.get_chunk(chkid)) 
1509      REPORT_EOF(true)
1510     
1511    int chunks=0;
1512    int last_chunk=0;
1513    G_TRY
1514    {
1515      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
1516      int chksize;
1517      for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
1518      {
1519        chunks++;
1520        if (chkid=="NDIR")
1521        {
1522          GP<DjVuNavDir> d=DjVuNavDir::create(url);
1523          d->decode(*iff.get_bytestream());
1524          dir=d;
1525          break;
1526        }
1527        iff.seek_close_chunk();
1528      }
1529      if ((!dir)&&(chunks_number < 0)) chunks_number=last_chunk;
1530    }
1531    G_CATCH(ex)
1532    {
1533       if(!ex.cmp_cause(ByteStream::EndOfFile))
1534       {
1535          if (chunks_number < 0)
1536             chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
1537          report_error(ex,(recover_errors<=SKIP_PAGES));
1538       }else
1539       {
1540          report_error(ex,true);
1541       }
1542    }
1543    G_ENDCATCH;
1544   
1545    data_pool->clear_stream();
1546    if (dir) return dir;
1547   
1548    GPList<DjVuFile> list=get_included_files(false);
1549    for(GPosition pos=list;pos;++pos)
1550    {
1551      GP<DjVuNavDir> d=list[pos]->decode_ndir(map);
1552      if (d) return d;
1553    }
1554    data_pool->clear_stream();
1555  }
1556  return 0;
1557}
1558
1559GP<DjVuNavDir>
1560DjVuFile::decode_ndir(void)
1561{
1562  GMap<GURL, void *> map;
1563  return decode_ndir(map);
1564}
1565
1566void
1567DjVuFile::get_merged_anno(const GP<DjVuFile> & file,
1568  const GP<ByteStream> &gstr_out, const GList<GURL> & ignore_list,
1569  int level, int & max_level, GMap<GURL, void *> & map)
1570{
1571  DEBUG_MSG("DjVuFile::get_merged_anno()\n");
1572  GURL url=file->get_url();
1573  if (!map.contains(url))
1574  {
1575    ByteStream &str_out=*gstr_out;
1576    map[url]=0;
1577   
1578    // Do the included files first (To make sure that they have
1579    // less precedence)
1580    // Depending on if we have all data present, we will
1581    // either create all included files or will use only
1582    // those that have already been created
1583    GPList<DjVuFile> list=file->get_included_files(!file->is_data_present());
1584    for(GPosition pos=list;pos;++pos)
1585      get_merged_anno(list[pos], gstr_out, ignore_list, level+1, max_level, map);
1586   
1587    // Now process the DjVuFile's own annotations
1588    if (!ignore_list.contains(file->get_url()))
1589    {
1590      if (!file->is_data_present() ||
1591        file->is_modified() && file->anno)
1592      {
1593               // Process the decoded (?) anno
1594        GCriticalSectionLock lock(&file->anno_lock);
1595        if (file->anno && file->anno->size())
1596        {
1597          if (str_out.tell())
1598          {
1599            str_out.write((void *) "", 1);
1600          }
1601          file->anno->seek(0);
1602          str_out.copy(*file->anno);
1603        }
1604      } else if (file->is_data_present())
1605      {
1606               // Copy all annotations chunks, but do NOT modify
1607               // this->anno (to avoid correlation with DjVuFile::decode())
1608        const GP<ByteStream> str(file->data_pool->get_stream());
1609        const GP<IFFByteStream> giff(IFFByteStream::create(str));
1610        IFFByteStream &iff=*giff;
1611        GUTF8String chkid;
1612        if (iff.get_chunk(chkid))
1613          while(iff.get_chunk(chkid))
1614          {
1615            if (chkid=="FORM:ANNO")
1616            {
1617              if (max_level<level)
1618                max_level=level;
1619              if (str_out.tell())
1620              {
1621                str_out.write((void *) "", 1);
1622              }
1623              str_out.copy(*iff.get_bytestream());
1624            } 
1625            else if (is_annotation(chkid)) // but not FORM:ANNO
1626            {
1627              if (max_level<level)
1628                max_level=level;
1629              if (str_out.tell()&&chkid != "ANTz")
1630              {
1631                str_out.write((void *) "", 1);
1632              }
1633              const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1634              IFFByteStream &iff_out=*giff_out;
1635              iff_out.put_chunk(chkid);
1636              iff_out.copy(*iff.get_bytestream());
1637              iff_out.close_chunk();
1638            }
1639            iff.close_chunk();
1640          }
1641        file->data_pool->clear_stream();
1642      }
1643    }
1644  }
1645}
1646
1647GP<ByteStream>
1648DjVuFile::get_merged_anno(const GList<GURL> & ignore_list,
1649                          int * max_level_ptr)
1650                          // Will do the same thing as get_merged_anno(int *), but will
1651                          // ignore DjVuFiles with URLs from the ignore_list
1652{
1653  DEBUG_MSG("DjVuFile::get_merged_anno()\n");
1654  GP<ByteStream> gstr(ByteStream::create());
1655  GMap<GURL, void *> map;
1656  int max_level=0;
1657  get_merged_anno(this, gstr, ignore_list, 0, max_level, map);
1658  if (max_level_ptr)
1659    *max_level_ptr=max_level;
1660  ByteStream &str=*gstr;
1661  if (!str.tell()) 
1662  {
1663    gstr=0;
1664  }else
1665  {
1666    str.seek(0);
1667  }
1668  return gstr;
1669}
1670
1671GP<ByteStream>
1672DjVuFile::get_merged_anno(int * max_level_ptr)
1673// Will go down the DjVuFile's hierarchy and decode all DjVuAnno even
1674// when the DjVuFile is not fully decoded yet. To avoid correlations
1675// with DjVuFile::decode(), we do not modify DjVuFile::anno data.
1676//
1677// Files deeper in the hierarchy have less influence on the
1678// results. It means, for example, that the if annotations are
1679// specified in the top level page file and in a shared file,
1680// the top level page file settings will take precedence.
1681//
1682// NOTE! This function guarantees correct results only if the
1683// DjVuFile has all data
1684{
1685  GList<GURL> ignore_list;
1686  return get_merged_anno(ignore_list, max_level_ptr);
1687}
1688
1689
1690// [LB->BCR] The following six functions get_anno, get_text, get_meta
1691// contain the same code in triplicate!!!
1692
1693void
1694DjVuFile::get_anno(
1695  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
1696{
1697  DEBUG_MSG("DjVuFile::get_anno()\n");
1698  ByteStream &str_out=*gstr_out;
1699  if (!file->is_data_present() ||
1700    file->is_modified() && file->anno)
1701  {
1702    // Process the decoded (?) anno
1703    GCriticalSectionLock lock(&file->anno_lock);
1704    if (file->anno && file->anno->size())
1705    {
1706      if (str_out.tell())
1707      {
1708        str_out.write((void *) "", 1);
1709      }
1710      file->anno->seek(0);
1711      str_out.copy(*file->anno);
1712    }
1713  } else if (file->is_data_present())
1714  {
1715               // Copy all anno chunks, but do NOT modify
1716               // DjVuFile::anno (to avoid correlation with DjVuFile::decode())
1717    const GP<ByteStream> str=file->data_pool->get_stream();
1718    const GP<IFFByteStream> giff=IFFByteStream::create(str);
1719    IFFByteStream &iff=*giff;
1720    GUTF8String chkid;
1721    if (iff.get_chunk(chkid))
1722    {
1723      while(iff.get_chunk(chkid))
1724      {
1725        if (is_annotation(chkid))
1726        {
1727          if (str_out.tell())
1728          {
1729            str_out.write((void *) "", 1);
1730          }
1731          const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1732          IFFByteStream &iff_out=*giff_out;
1733          iff_out.put_chunk(chkid);
1734          iff_out.copy(*iff.get_bytestream());
1735          iff_out.close_chunk();
1736        }
1737        iff.close_chunk();
1738      }
1739    }
1740    file->data_pool->clear_stream();
1741  }
1742}
1743
1744void
1745DjVuFile::get_text(
1746  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
1747{
1748  DEBUG_MSG("DjVuFile::get_text()\n");
1749  ByteStream &str_out=*gstr_out;
1750  if (!file->is_data_present() ||
1751    file->is_modified() && file->text)
1752  {
1753    // Process the decoded (?) text
1754    GCriticalSectionLock lock(&file->text_lock);
1755    if (file->text && file->text->size())
1756    {
1757      if (str_out.tell())
1758      {
1759        str_out.write((void *) "", 1);
1760      }
1761      file->text->seek(0);
1762      str_out.copy(*file->text);
1763    }
1764  } else if (file->is_data_present())
1765  {
1766               // Copy all text chunks, but do NOT modify
1767               // DjVuFile::text (to avoid correlation with DjVuFile::decode())
1768    const GP<ByteStream> str=file->data_pool->get_stream();
1769    const GP<IFFByteStream> giff=IFFByteStream::create(str);
1770    IFFByteStream &iff=*giff;
1771    GUTF8String chkid;
1772    if (iff.get_chunk(chkid))
1773    {
1774      while(iff.get_chunk(chkid))
1775      {
1776        if (is_text(chkid))
1777        {
1778          if (str_out.tell())
1779          {
1780            str_out.write((void *) "", 1);
1781          }
1782          const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1783          IFFByteStream &iff_out=*giff_out;
1784          iff_out.put_chunk(chkid);
1785          iff_out.copy(*iff.get_bytestream());
1786          iff_out.close_chunk();
1787        }
1788        iff.close_chunk();
1789      }
1790    }
1791    file->data_pool->clear_stream();
1792  }
1793}
1794
1795void
1796DjVuFile::get_meta(
1797  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
1798{
1799  DEBUG_MSG("DjVuFile::get_meta()\n");
1800  ByteStream &str_out=*gstr_out;
1801  if (!file->is_data_present() ||
1802    file->is_modified() && file->meta)
1803  {
1804    // Process the decoded (?) meta
1805    GCriticalSectionLock lock(&file->meta_lock);
1806    if (file->meta && file->meta->size())
1807    {
1808      if (str_out.tell())
1809      {
1810        str_out.write((void *) "", 1);
1811      }
1812      file->meta->seek(0);
1813      str_out.copy(*file->meta);
1814    }
1815  } else if (file->is_data_present())
1816  {
1817               // Copy all meta chunks, but do NOT modify
1818               // DjVuFile::meta (to avoid correlation with DjVuFile::decode())
1819    const GP<ByteStream> str=file->data_pool->get_stream();
1820    const GP<IFFByteStream> giff=IFFByteStream::create(str);
1821    IFFByteStream &iff=*giff;
1822    GUTF8String chkid;
1823    if (iff.get_chunk(chkid))
1824    {
1825      while(iff.get_chunk(chkid))
1826      {
1827        if (is_meta(chkid))
1828        {
1829          if (str_out.tell())
1830          {
1831            str_out.write((void *) "", 1);
1832          }
1833          const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1834          IFFByteStream &iff_out=*giff_out;
1835          iff_out.put_chunk(chkid);
1836          iff_out.copy(*iff.get_bytestream());
1837          iff_out.close_chunk();
1838        }
1839        iff.close_chunk();
1840      }
1841    }
1842    file->data_pool->clear_stream();
1843  }
1844}
1845
1846GP<ByteStream>
1847DjVuFile::get_anno(void)
1848{
1849  DEBUG_MSG("DjVuFile::get_text(void)\n");
1850  GP<ByteStream> gstr(ByteStream::create());
1851  get_anno(this, gstr);
1852  ByteStream &str=*gstr;
1853  if (!str.tell())
1854  { 
1855    gstr=0;
1856  }else
1857  {
1858    str.seek(0);
1859  }
1860  return gstr;
1861}
1862
1863GP<ByteStream>
1864DjVuFile::get_text(void)
1865{
1866  DEBUG_MSG("DjVuFile::get_text(void)\n");
1867  GP<ByteStream> gstr(ByteStream::create());
1868  get_text(this, gstr);
1869  ByteStream &str=*gstr;
1870  if (!str.tell())
1871  { 
1872    gstr=0;
1873  }else
1874  {
1875    str.seek(0);
1876  }
1877  return gstr;
1878}
1879
1880GP<ByteStream>
1881DjVuFile::get_meta(void)
1882{
1883  DEBUG_MSG("DjVuFile::get_meta(void)\n");
1884  GP<ByteStream> gstr(ByteStream::create());
1885  get_meta(this, gstr);
1886  ByteStream &str=*gstr;
1887  if (!str.tell())
1888  { 
1889    gstr=0;
1890  }else
1891  {
1892    str.seek(0);
1893  }
1894  return gstr;
1895}
1896
1897void
1898DjVuFile::static_trigger_cb(void * cl_data)
1899{
1900  DjVuFile * th=(DjVuFile *) cl_data;
1901  G_TRY {
1902    GP<DjVuPort> port=DjVuPort::get_portcaster()->is_port_alive(th);
1903    if (port && port->inherits("DjVuFile"))
1904      ((DjVuFile *) (DjVuPort *) port)->trigger_cb();
1905  } G_CATCH(exc) {
1906    G_TRY {
1907      get_portcaster()->notify_error(th, exc.get_cause());
1908    } G_CATCH_ALL {} G_ENDCATCH;
1909  } G_ENDCATCH;
1910}
1911
1912void
1913DjVuFile::trigger_cb(void)
1914{
1915  GP<DjVuFile> life_saver=this;
1916 
1917  DEBUG_MSG("DjVuFile::trigger_cb(): got data for '" << url << "'\n");
1918  DEBUG_MAKE_INDENT(3);
1919 
1920  file_size=data_pool->get_length();
1921  flags|=DATA_PRESENT;
1922  get_portcaster()->notify_file_flags_changed(this, DATA_PRESENT, 0);
1923 
1924  if (!are_incl_files_created())
1925    process_incl_chunks();
1926 
1927  bool all=true;
1928  inc_files_lock.lock();
1929  GPList<DjVuFile> files_list=inc_files_list;
1930  inc_files_lock.unlock();
1931  for(GPosition pos=files_list;pos&&(all=files_list[pos]->is_all_data_present());++pos)
1932    EMPTY_LOOP;
1933  if (all)
1934  {
1935    DEBUG_MSG("DjVuFile::trigger_cb(): We have ALL data for '" << url << "'\n");
1936    flags|=ALL_DATA_PRESENT;
1937    get_portcaster()->notify_file_flags_changed(this, ALL_DATA_PRESENT, 0);
1938  }
1939}
1940
1941void
1942DjVuFile::progress_cb(int pos, void * cl_data)
1943{
1944  DEBUG_MSG("DjVuFile::progress_cb() called\n");
1945  DEBUG_MAKE_INDENT(3);
1946 
1947  DjVuFile * th=(DjVuFile *) cl_data;
1948 
1949  int length=th->decode_data_pool->get_length();
1950  if (length>0)
1951  {
1952    float progress=(float) pos/length;
1953    DEBUG_MSG("progress=" << progress << "\n");
1954    get_portcaster()->notify_decode_progress(th, progress);
1955  } else
1956  {
1957    DEBUG_MSG("DataPool size is still unknown => ignoring\n");
1958  }
1959}
1960
1961//*****************************************************************************
1962//******************************** Utilities **********************************
1963//*****************************************************************************
1964
1965void
1966DjVuFile::move(GMap<GURL, void *> & map, const GURL & dir_url)
1967// This function may block for data.
1968{
1969  if (!map.contains(url))
1970  {
1971    map[url]=0;
1972   
1973    url=GURL::UTF8(url.name(),dir_url);
1974   
1975   
1976    // Leave the lock here!
1977    GCriticalSectionLock lock(&inc_files_lock);
1978    for(GPosition pos=inc_files_list;pos;++pos)
1979      inc_files_list[pos]->move(map, dir_url);
1980  }
1981}
1982
1983void
1984DjVuFile::move(const GURL & dir_url)
1985// This function may block for data.
1986{
1987  check();
1988  DEBUG_MSG("DjVuFile::move(): dir_url='" << dir_url << "'\n");
1989  DEBUG_MAKE_INDENT(3);
1990 
1991  GMap<GURL, void *> map;
1992  move(map, dir_url);
1993}
1994
1995void
1996DjVuFile::set_name(const GUTF8String &name)
1997{
1998  DEBUG_MSG("DjVuFile::set_name(): name='" << name << "'\n");
1999  DEBUG_MAKE_INDENT(3);
2000  url=GURL::UTF8(name,url.base());
2001}
2002
2003//*****************************************************************************
2004//****************************** Data routines ********************************
2005//*****************************************************************************
2006
2007int
2008DjVuFile::get_chunks_number(void)
2009{
2010  if(chunks_number < 0)
2011  {
2012    const GP<ByteStream> str(data_pool->get_stream());
2013    GUTF8String chkid;
2014    const GP<IFFByteStream> giff(IFFByteStream::create(str));
2015    IFFByteStream &iff=*giff;
2016    if (!iff.get_chunk(chkid))
2017      REPORT_EOF(true)
2018     
2019      int chunks=0;
2020    int last_chunk=0;
2021    G_TRY
2022    {
2023      int chksize;
2024      for(;(chksize=iff.get_chunk(chkid));last_chunk=chunks)
2025      {
2026        chunks++;
2027        iff.seek_close_chunk();
2028      }
2029      chunks_number=last_chunk;
2030    }
2031    G_CATCH(ex)
2032    {
2033      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2034      report_error(ex,(recover_errors<=SKIP_PAGES));
2035    }
2036    G_ENDCATCH;
2037    data_pool->clear_stream();
2038  }
2039  return chunks_number;
2040}
2041
2042GUTF8String
2043DjVuFile::get_chunk_name(int chunk_num)
2044{
2045  if(chunk_num < 0)
2046  {
2047    G_THROW( ERR_MSG("DjVuFile.illegal_chunk") );
2048  }
2049  if((chunks_number >= 0)&&(chunk_num > chunks_number))
2050  {
2051    G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
2052  }
2053  check();
2054 
2055  GUTF8String name;
2056  const GP<ByteStream> str(data_pool->get_stream());
2057  GUTF8String chkid;
2058  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2059  IFFByteStream &iff=*giff;
2060  if (!iff.get_chunk(chkid)) 
2061    REPORT_EOF(true)
2062   
2063    int chunks=0;
2064  int last_chunk=0;
2065  G_TRY
2066  {
2067    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
2068    int chksize;
2069    for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
2070    {
2071      if (chunks++==chunk_num) { name=chkid; break; }
2072      iff.seek_close_chunk();
2073    }
2074  }
2075  G_CATCH(ex)
2076  {
2077    if (chunks_number < 0)
2078      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2079    report_error(ex,(recover_errors <= SKIP_PAGES));
2080  }
2081  G_ENDCATCH;
2082  if (!name.length())
2083  {
2084    if (chunks_number < 0) chunks_number=chunks;
2085    G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
2086  }
2087  return name;
2088}
2089
2090bool
2091DjVuFile::contains_chunk(const GUTF8String &chunk_name)
2092{
2093  check();
2094  DEBUG_MSG("DjVuFile::contains_chunk(): url='" << url << "', chunk_name='" <<
2095    chunk_name << "'\n");
2096  DEBUG_MAKE_INDENT(3);
2097 
2098  bool contains=0;
2099  const GP<ByteStream> str(data_pool->get_stream());
2100  GUTF8String chkid;
2101  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2102  IFFByteStream &iff=*giff;
2103  if (!iff.get_chunk(chkid)) 
2104    REPORT_EOF((recover_errors<=SKIP_PAGES))
2105   
2106    int chunks=0;
2107  int last_chunk=0;
2108  G_TRY
2109  {
2110    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
2111    int chksize;
2112    for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
2113    {
2114      chunks++;
2115      if (chkid==chunk_name) { contains=1; break; }
2116      iff.seek_close_chunk();
2117    }
2118    if (!contains &&(chunks_number < 0)) chunks_number=last_chunk;
2119  }
2120  G_CATCH(ex)
2121  {
2122    if (chunks_number < 0)
2123      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2124    report_error(ex,(recover_errors <= SKIP_PAGES));
2125  }
2126  G_ENDCATCH;
2127  data_pool->clear_stream();
2128  return contains;
2129}
2130
2131bool
2132DjVuFile::contains_anno(void)
2133{
2134  const GP<ByteStream> str(data_pool->get_stream());
2135 
2136  GUTF8String chkid;
2137  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2138  IFFByteStream &iff=*giff;
2139  if (!iff.get_chunk(chkid))
2140    G_THROW( ByteStream::EndOfFile );
2141 
2142  while(iff.get_chunk(chkid))
2143  {
2144    if (is_annotation(chkid))
2145      return true;
2146    iff.close_chunk();
2147  }
2148 
2149  data_pool->clear_stream();
2150  return false;
2151}
2152
2153bool
2154DjVuFile::contains_text(void)
2155{
2156  const GP<ByteStream> str(data_pool->get_stream());
2157 
2158  GUTF8String chkid;
2159  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2160  IFFByteStream &iff=*giff;
2161  if (!iff.get_chunk(chkid))
2162    G_THROW( ByteStream::EndOfFile );
2163 
2164  while(iff.get_chunk(chkid))
2165  {
2166    if (is_text(chkid))
2167      return true;
2168    iff.close_chunk();
2169  }
2170 
2171  data_pool->clear_stream();
2172  return false;
2173}
2174
2175bool
2176DjVuFile::contains_meta(void)
2177{
2178  const GP<ByteStream> str(data_pool->get_stream());
2179 
2180  GUTF8String chkid;
2181  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2182  IFFByteStream &iff=*giff;
2183  if (!iff.get_chunk(chkid))
2184    G_THROW( ByteStream::EndOfFile );
2185 
2186  while(iff.get_chunk(chkid))
2187  {
2188    if (is_meta(chkid))
2189      return true;
2190    iff.close_chunk();
2191  }
2192 
2193  data_pool->clear_stream();
2194  return false;
2195}
2196
2197//*****************************************************************************
2198//****************************** Save routines ********************************
2199//*****************************************************************************
2200
2201static void
2202copy_chunks(const GP<ByteStream> &from, IFFByteStream &ostr)
2203{
2204  from->seek(0);
2205  const GP<IFFByteStream> giff(IFFByteStream::create(from));
2206  IFFByteStream &iff=*giff;
2207  GUTF8String chkid;
2208  int chksize;
2209  while ((chksize=iff.get_chunk(chkid)))
2210  {
2211    ostr.put_chunk(chkid);
2212    int ochksize=ostr.copy(*iff.get_bytestream());
2213    ostr.close_chunk();
2214    iff.seek_close_chunk();
2215    if(ochksize != chksize)
2216    {
2217      G_THROW( ByteStream::EndOfFile );
2218    }
2219  }
2220}
2221
2222
2223void
2224DjVuFile::add_djvu_data(IFFByteStream & ostr, GMap<GURL, void *> & map,
2225                        const bool included_too, const bool no_ndir)
2226{
2227  check();
2228  if (map.contains(url)) return;
2229  bool top_level = !map.size();
2230  map[url]=0;
2231  bool processed_annotation = false;
2232  bool processed_text = false;
2233  bool processed_meta = false;
2234 
2235  const GP<ByteStream> str(data_pool->get_stream());
2236  GUTF8String chkid;
2237  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2238  IFFByteStream &iff=*giff;
2239  if (!iff.get_chunk(chkid)) 
2240    REPORT_EOF(true)
2241   
2242    // Open toplevel form
2243    if (top_level) 
2244      ostr.put_chunk(chkid);
2245    // Process chunks
2246    int chunks=0;
2247    int last_chunk=0;
2248    G_TRY
2249    {
2250      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
2251      int chksize;
2252      for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
2253      {
2254        chunks++;
2255        if (is_info(chkid) && info)
2256        {
2257          ostr.put_chunk(chkid);
2258          info->encode(*ostr.get_bytestream());
2259          ostr.close_chunk();
2260        }
2261        else if (chkid=="INCL" && included_too)
2262        {
2263          GP<DjVuFile> file = process_incl_chunk(*iff.get_bytestream());
2264          if (file)
2265          {
2266            if(recover_errors!=ABORT)
2267              file->set_recover_errors(recover_errors);
2268            if(verbose_eof)
2269              file->set_verbose_eof(verbose_eof);
2270            file->add_djvu_data(ostr, map, included_too, no_ndir);
2271          }
2272        } 
2273        else if (is_annotation(chkid) && anno && anno->size())
2274        {
2275          if (!processed_annotation)
2276          {
2277            processed_annotation = true;
2278            GCriticalSectionLock lock(&anno_lock);
2279            copy_chunks(anno, ostr);
2280          }
2281        }
2282        else if (is_text(chkid) && text && text->size())
2283        {
2284          if (!processed_text)
2285          {
2286            processed_text = true;
2287            GCriticalSectionLock lock(&text_lock);
2288            copy_chunks(text, ostr);
2289          }
2290        }
2291        else if (is_meta(chkid) && meta && meta->size())
2292        {
2293          if (!processed_meta)
2294          {
2295            processed_meta = true;
2296            GCriticalSectionLock lock(&meta_lock);
2297            copy_chunks(meta, ostr);
2298          }
2299        }
2300        else if (chkid!="NDIR"||!(no_ndir || dir))
2301        {  // Copy NDIR chunks, but never generate new ones.
2302          ostr.put_chunk(chkid);
2303          ostr.copy(*iff.get_bytestream());
2304          ostr.close_chunk();
2305        }
2306        iff.seek_close_chunk();
2307      }
2308      if (chunks_number < 0) chunks_number=last_chunk;
2309    }
2310    G_CATCH(ex)
2311    {
2312      if(!ex.cmp_cause(ByteStream::EndOfFile))
2313      {
2314        if (chunks_number < 0)
2315          chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2316        report_error(ex,(recover_errors<=SKIP_PAGES));
2317      }else
2318      {
2319        report_error(ex,true);
2320      }
2321    }
2322    G_ENDCATCH;
2323   
2324    // Otherwise, writes annotation at the end (annotations could be big)
2325    if (!processed_annotation && anno && anno->size())
2326    {
2327      processed_annotation = true;
2328      GCriticalSectionLock lock(&anno_lock);
2329      copy_chunks(anno, ostr);
2330    }
2331    if (!processed_text && text && text->size())
2332    {
2333      processed_text = true;
2334      GCriticalSectionLock lock(&text_lock);
2335      copy_chunks(text, ostr);
2336    }
2337    if (!processed_meta && meta && meta->size())
2338    {
2339      processed_meta = true;
2340      GCriticalSectionLock lock(&meta_lock);
2341      copy_chunks(meta, ostr);
2342    }
2343    // Close iff
2344    if (top_level) 
2345      ostr.close_chunk();
2346
2347  data_pool->clear_stream();
2348}
2349
2350GP<ByteStream> 
2351DjVuFile::get_djvu_bytestream(const bool included_too, const bool no_ndir)
2352{
2353   check();
2354   DEBUG_MSG("DjVuFile::get_djvu_bytestream(): creating DjVu raw file\n");
2355   DEBUG_MAKE_INDENT(3);
2356   const GP<ByteStream> pbs(ByteStream::create());
2357   const GP<IFFByteStream> giff=IFFByteStream::create(pbs);
2358   IFFByteStream &iff=*giff;
2359   GMap<GURL, void *> map;
2360   add_djvu_data(iff, map, included_too, no_ndir);
2361   iff.flush();
2362   pbs->seek(0, SEEK_SET);
2363   return pbs;
2364}
2365
2366GP<DataPool>
2367DjVuFile::get_djvu_data(const bool included_too, const bool no_ndir)
2368{
2369  const GP<ByteStream> pbs = get_djvu_bytestream(included_too, no_ndir);
2370  return DataPool::create(pbs);
2371}
2372
2373void
2374DjVuFile::merge_anno(ByteStream &out)
2375{
2376  // Reuse get_merged_anno(), which is better than the previous
2377  // implementation due to three things:
2378  //  1. It works even before the file is completely decoded
2379  //  2. It merges annotations taking into account where a child DjVuFile
2380  //     is included.
2381  //  3. It handles loops in DjVuFile's hierarchy
2382 
2383  const GP<ByteStream> str(get_merged_anno());
2384  if (str)
2385  {
2386    str->seek(0);
2387    if (out.tell())
2388    {
2389      out.write((void *) "", 1);
2390    }
2391    out.copy(*str);
2392  }
2393}
2394
2395void
2396DjVuFile::get_text(ByteStream &out)
2397{
2398  const GP<ByteStream> str(get_text());
2399  if (str)
2400  {
2401    str->seek(0);
2402    if (out.tell())
2403    {
2404      out.write((void *) "", 1);
2405    }
2406    out.copy(*str);
2407  }
2408}
2409
2410void
2411DjVuFile::get_meta(ByteStream &out)
2412{
2413  const GP<ByteStream> str(get_meta());
2414  if (str)
2415  {
2416    str->seek(0);
2417    if (out.tell())
2418    {
2419      out.write((void *) "", 1);
2420    }
2421    out.copy(*str);
2422  }
2423}
2424
2425
2426
2427//****************************************************************************
2428//******************************* Modifying **********************************
2429//****************************************************************************
2430
2431void
2432DjVuFile::remove_anno(void)
2433{
2434  DEBUG_MSG("DjVuFile::remove_anno()\n");
2435  const GP<ByteStream> str_in(data_pool->get_stream());
2436  const GP<ByteStream> gstr_out(ByteStream::create());
2437 
2438  GUTF8String chkid;
2439  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2440  IFFByteStream &iff_in=*giff_in;
2441  if (!iff_in.get_chunk(chkid))
2442    G_THROW( ByteStream::EndOfFile );
2443 
2444  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2445  IFFByteStream &iff_out=*giff_out;
2446  iff_out.put_chunk(chkid);
2447 
2448  while(iff_in.get_chunk(chkid))
2449  {
2450    if (!is_annotation(chkid))
2451    {
2452      iff_out.put_chunk(chkid);
2453      iff_out.copy(*iff_in.get_bytestream());
2454      iff_out.close_chunk();
2455    }
2456    iff_in.close_chunk();
2457  }
2458 
2459  iff_out.close_chunk();
2460 
2461  gstr_out->seek(0, SEEK_SET);
2462  data_pool=DataPool::create(gstr_out);
2463  chunks_number=-1;
2464 
2465  anno=0;
2466 
2467  flags|=MODIFIED;
2468  data_pool->clear_stream();
2469}
2470
2471void
2472DjVuFile::remove_text(void)
2473{
2474  DEBUG_MSG("DjVuFile::remove_text()\n");
2475  const GP<ByteStream> str_in(data_pool->get_stream());
2476  const GP<ByteStream> gstr_out(ByteStream::create());
2477 
2478  GUTF8String chkid;
2479  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2480  IFFByteStream &iff_in=*giff_in;
2481  if (!iff_in.get_chunk(chkid))
2482    G_THROW( ByteStream::EndOfFile );
2483 
2484  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2485  IFFByteStream &iff_out=*giff_out;
2486  iff_out.put_chunk(chkid);
2487 
2488  while(iff_in.get_chunk(chkid))
2489  {
2490    if (!is_text(chkid))
2491    {
2492      iff_out.put_chunk(chkid);
2493      iff_out.copy(*iff_in.get_bytestream());
2494      iff_out.close_chunk();
2495    }
2496    iff_in.close_chunk();
2497  }
2498 
2499  iff_out.close_chunk();
2500 
2501  gstr_out->seek(0, SEEK_SET);
2502  data_pool=DataPool::create(gstr_out);
2503  chunks_number=-1;
2504 
2505  text=0;
2506 
2507  flags|=MODIFIED;
2508  data_pool->clear_stream();
2509}
2510
2511void
2512DjVuFile::remove_meta(void)
2513{
2514  DEBUG_MSG("DjVuFile::remove_meta()\n");
2515  const GP<ByteStream> str_in(data_pool->get_stream());
2516  const GP<ByteStream> gstr_out(ByteStream::create());
2517 
2518  GUTF8String chkid;
2519  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2520  IFFByteStream &iff_in=*giff_in;
2521  if (!iff_in.get_chunk(chkid))
2522    G_THROW( ByteStream::EndOfFile );
2523 
2524  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2525  IFFByteStream &iff_out=*giff_out;
2526  iff_out.put_chunk(chkid);
2527 
2528  while(iff_in.get_chunk(chkid))
2529  {
2530    if (!is_meta(chkid))
2531    {
2532      iff_out.put_chunk(chkid);
2533      iff_out.copy(*iff_in.get_bytestream());
2534      iff_out.close_chunk();
2535    }
2536    iff_in.close_chunk();
2537  }
2538 
2539  iff_out.close_chunk();
2540 
2541  gstr_out->seek(0, SEEK_SET);
2542  data_pool=DataPool::create(gstr_out);
2543  chunks_number=-1;
2544 
2545  meta=0;
2546 
2547  flags|=MODIFIED;
2548  data_pool->clear_stream();
2549}
2550
2551void
2552DjVuFile::rebuild_data_pool(void)
2553{
2554  data_pool=get_djvu_data(false,false);
2555  chunks_number=1;
2556  flags|=MODIFIED;
2557}
2558
2559// Do NOT comment this function out. It's used by DjVuDocEditor to convert
2560// old-style DjVu documents to BUNDLED format.
2561
2562GP<DataPool>
2563DjVuFile::unlink_file(const GP<DataPool> & data, const GUTF8String &name)
2564// Will process contents of data[] and remove any INCL chunk
2565// containing 'name'
2566{
2567  DEBUG_MSG("DjVuFile::unlink_file()\n");
2568  const GP<ByteStream> gstr_out(ByteStream::create());
2569  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2570  IFFByteStream &iff_out=*giff_out;
2571 
2572  const GP<ByteStream> str_in(data->get_stream());
2573  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2574  IFFByteStream &iff_in=*giff_in;
2575 
2576  int chksize;
2577  GUTF8String chkid;
2578  if (!iff_in.get_chunk(chkid)) return data;
2579 
2580  iff_out.put_chunk(chkid);
2581 
2582  while((chksize=iff_in.get_chunk(chkid)))
2583  {
2584    if (chkid=="INCL")
2585    {
2586      GUTF8String incl_str;
2587      char buffer[1024];
2588      int length;
2589      while((length=iff_in.read(buffer, 1024)))
2590        incl_str+=GUTF8String(buffer, length);
2591     
2592      // Eat '\n' in the beginning and at the end
2593      while(incl_str.length() && incl_str[0]=='\n')
2594      {
2595        incl_str=incl_str.substr(1,(unsigned int)(-1));
2596      }
2597      while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
2598      {
2599        incl_str.setat(incl_str.length()-1, 0);
2600      }
2601      if (incl_str!=name)
2602      {
2603        iff_out.put_chunk(chkid);
2604        iff_out.get_bytestream()->writestring(incl_str);
2605        iff_out.close_chunk();
2606      }
2607    } else
2608    {
2609      iff_out.put_chunk(chkid);
2610      char buffer[1024];
2611      int length;
2612      for(const GP<ByteStream> gbs(iff_out.get_bytestream());
2613        (length=iff_in.read(buffer, 1024));)
2614      {
2615        gbs->writall(buffer, length);
2616      }
2617      iff_out.close_chunk();
2618    }
2619    iff_in.close_chunk();
2620  }
2621  iff_out.close_chunk();
2622  iff_out.flush();
2623  gstr_out->seek(0, SEEK_SET);
2624  data->clear_stream();
2625  return DataPool::create(gstr_out);
2626}
2627
2628#ifndef NEED_DECODER_ONLY
2629void
2630DjVuFile::insert_file(const GUTF8String &id, int chunk_num)
2631{
2632  DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "', chunk_num="
2633    << chunk_num << "\n");
2634  DEBUG_MAKE_INDENT(3);
2635 
2636  // First: create new data
2637  const GP<ByteStream> str_in(data_pool->get_stream());
2638  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2639  IFFByteStream &iff_in=*giff_in;
2640 
2641  const GP<ByteStream> gstr_out(ByteStream::create());
2642  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2643  IFFByteStream &iff_out=*giff_out;
2644 
2645  int chunk_cnt=0;
2646  bool done=false;
2647  GUTF8String chkid;
2648  if (iff_in.get_chunk(chkid))
2649  {
2650    iff_out.put_chunk(chkid);
2651    int chksize;
2652    while((chksize=iff_in.get_chunk(chkid)))
2653    {
2654      if (chunk_cnt++==chunk_num)
2655      {
2656        iff_out.put_chunk("INCL");
2657        iff_out.get_bytestream()->writestring(id);
2658        iff_out.close_chunk();
2659        done=true;
2660      }
2661      iff_out.put_chunk(chkid);
2662      iff_out.copy(*iff_in.get_bytestream());
2663      iff_out.close_chunk();
2664      iff_in.close_chunk();
2665    }
2666    if (!done)
2667    {
2668      iff_out.put_chunk("INCL");
2669      iff_out.get_bytestream()->writestring(id);
2670      iff_out.close_chunk();
2671    }
2672    iff_out.close_chunk();
2673  }
2674  gstr_out->seek(0, SEEK_SET);
2675  data_pool=DataPool::create(gstr_out);
2676  chunks_number=-1;
2677 
2678  // Second: create missing DjVuFiles
2679  process_incl_chunks();
2680 
2681  flags|=MODIFIED;
2682  data_pool->clear_stream();
2683}
2684#endif
2685
2686void
2687DjVuFile::unlink_file(const GUTF8String &id)
2688{
2689  DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "'\n");
2690  DEBUG_MAKE_INDENT(3);
2691 
2692  // Remove the file from the list of included files
2693  {
2694    GURL url=DjVuPort::get_portcaster()->id_to_url(this, id);
2695    if (url.is_empty()) url=GURL::UTF8(id,this->url.base());
2696    GCriticalSectionLock lock(&inc_files_lock);
2697    for(GPosition pos=inc_files_list;pos;)
2698      if (inc_files_list[pos]->get_url()==url)
2699      {
2700        GPosition this_pos=pos;
2701        ++pos;
2702        inc_files_list.del(this_pos);
2703      } else ++pos;
2704  }
2705 
2706  // And update the data.
2707  const GP<ByteStream> str_in(data_pool->get_stream());
2708  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2709  IFFByteStream &iff_in=*giff_in;
2710 
2711  const GP<ByteStream> gstr_out(ByteStream::create());
2712  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2713  IFFByteStream &iff_out=*giff_out;
2714 
2715  GUTF8String chkid;
2716  if (iff_in.get_chunk(chkid))
2717  {
2718    iff_out.put_chunk(chkid);
2719    int chksize;
2720    while((chksize=iff_in.get_chunk(chkid)))
2721    {
2722      if (chkid!="INCL")
2723      {
2724        iff_out.put_chunk(chkid);
2725        iff_out.copy(*iff_in.get_bytestream());
2726        iff_out.close_chunk();
2727      } else
2728      {
2729        GUTF8String incl_str;
2730        char buffer[1024];
2731        int length;
2732        while((length=iff_in.read(buffer, 1024)))
2733          incl_str+=GUTF8String(buffer, length);
2734       
2735               // Eat '\n' in the beginning and at the end
2736        while(incl_str.length() && incl_str[0]=='\n')
2737        {
2738          incl_str=incl_str.substr(1,(unsigned int)(-1));
2739        }
2740        while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
2741          incl_str.setat(incl_str.length()-1, 0);
2742        if (incl_str!=id)
2743        {
2744          iff_out.put_chunk("INCL");
2745          iff_out.get_bytestream()->writestring(incl_str);
2746          iff_out.close_chunk();
2747        }
2748      }
2749      iff_in.close_chunk();
2750    }
2751    iff_out.close_chunk();
2752  }
2753 
2754  gstr_out->seek(0, SEEK_SET);
2755  data_pool=DataPool::create(gstr_out);
2756  chunks_number=-1;
2757 
2758  flags|=MODIFIED;
2759}
2760
2761void
2762DjVuFile::change_info(GP<DjVuInfo> xinfo,const bool do_reset)
2763{
2764  DEBUG_MSG("DjVuFile::change_text()\n");
2765  // Mark this as modified
2766  set_modified(true);
2767  if(do_reset)
2768    reset();
2769  info=xinfo;
2770}
2771
2772#ifndef NEED_DECODER_ONLY
2773void
2774DjVuFile::change_text(GP<DjVuTXT> txt,const bool do_reset)
2775{
2776  DEBUG_MSG("DjVuFile::change_text()\n");
2777  GP<DjVuText> gtext_c=DjVuText::create();
2778  DjVuText &text_c=*gtext_c;
2779  if(contains_text())
2780  {
2781    const GP<ByteStream> file_text(get_text());
2782    if(file_text)
2783    {
2784      text_c.decode(file_text);
2785    }
2786  }
2787  GCriticalSectionLock lock(&text_lock);
2788  // Mark this as modified
2789  set_modified(true);
2790  if(do_reset)
2791    reset();
2792  text_c.txt = txt;
2793  text=ByteStream::create();
2794  text_c.encode(text);
2795}
2796
2797void
2798DjVuFile::change_meta(const GUTF8String &xmeta,const bool do_reset)
2799{
2800  DEBUG_MSG("DjVuFile::change_meta()\n");
2801  // Mark this as modified
2802  set_modified(true);
2803  if(contains_meta())
2804  {
2805    (void)get_meta();
2806  }
2807  if(do_reset)
2808    reset();
2809  GCriticalSectionLock lock(&meta_lock);
2810  meta=ByteStream::create();
2811  if(xmeta.length())
2812  {
2813    const GP<IFFByteStream> giff=IFFByteStream::create(meta);
2814    IFFByteStream &iff=*giff;
2815    iff.put_chunk("METz");
2816    {
2817      GP<ByteStream> gbsiff=BSByteStream::create(iff.get_bytestream(),50);
2818      gbsiff->writestring(xmeta);
2819    }
2820    iff.close_chunk();
2821  }
2822}
2823#endif
2824
2825
2826#ifdef HAVE_NAMESPACES
2827}
2828# ifndef NOT_USING_DJVU_NAMESPACE
2829using namespace DJVU;
2830# endif
2831#endif
Note: See TracBrowser for help on using the repository browser.