source: trunk/libdjvu/DjVuFile.cpp @ 199

Last change on this file since 199 was 17, checked in by Eugene Romanenko, 16 years ago

update makefiles, remove absolute paths, update djvulibre to version 3.5.17

File size: 76.5 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.12 2005/12/24 12:45:01 leonb Exp $
55// $Name:  $
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" 
888               STRINGIFY(DJVUVERSION_TOO_NEW) );
889  }
890 
891  // INCL (inclusion chunk)
892  else if (chkid == "INCL" && (djvi || djvu || iw44))
893  {
894    GP<DjVuFile> file=process_incl_chunk(bs);
895    if (file)
896    {
897      int decode_was_already_started = 1;
898      {
899        GMonitorLock lock(&file->flags);
900          // Start decoding
901        if(file->resume_decode())
902        {
903          decode_was_already_started = 0;
904        }
905      }
906      // Send file notifications if previously started
907      if (decode_was_already_started)
908      {
909        // May send duplicate notifications...
910        if (file->is_decode_ok())
911          get_portcaster()->notify_file_flags_changed(file, DECODE_OK, 0);
912        else if (file->is_decode_failed())
913          get_portcaster()->notify_file_flags_changed(file, DECODE_FAILED, 0);
914      }
915      desc.format( ERR_MSG("DjVuFile.indir_chunk1") "\t" + file->get_url().fname() );
916    } else
917      desc.format( ERR_MSG("DjVuFile.indir_chunk2") );
918  }
919 
920  // Djbz (JB2 Dictionary)
921  else if (chkid == "Djbz" && (djvu || djvi))
922  {
923    if (this->fgjd)
924      G_THROW( ERR_MSG("DjVuFile.dupl_Dxxx") );
925    if (this->fgjd)
926      G_THROW( ERR_MSG("DjVuFile.Dxxx_after_Sxxx") );
927    GP<JB2Dict> fgjd = JB2Dict::create();
928    fgjd->decode(gbs);
929    this->fgjd = fgjd;
930    desc.format( ERR_MSG("DjVuFile.shape_dict") "\t%d", fgjd->get_shape_count() );
931  } 
932 
933  // Sjbz (JB2 encoded mask)
934  else if (chkid=="Sjbz" && (djvu || djvi))
935  {
936    if (this->fgjb)
937      G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
938    GP<JB2Image> fgjb=JB2Image::create();
939    // ---- begin hack
940    if (info && info->version <=18)
941      fgjb->reproduce_old_bug = true;
942    // ---- end hack
943    fgjb->decode(gbs, static_get_fgjd, (void*)this);
944    this->fgjb = fgjb;
945    desc.format( ERR_MSG("DjVuFile.fg_mask") "\t%d\t%d\t%d",
946      fgjb->get_width(), fgjb->get_height(),
947      get_dpi(fgjb->get_width(), fgjb->get_height()));
948  }
949 
950  // Smmr (MMR-G4 encoded mask)
951  else if (chkid=="Smmr" && (djvu || djvi))
952  {
953    if (this->fgjb)
954      G_THROW( ERR_MSG("DjVuFile.dupl_Sxxx") );
955    set_can_compress(true);
956    this->fgjb = MMRDecoder::decode(gbs);
957    desc.format( ERR_MSG("DjVuFile.G4_mask") "\t%d\t%d\t%d",
958      fgjb->get_width(), fgjb->get_height(),
959      get_dpi(fgjb->get_width(), fgjb->get_height()));
960  }
961 
962  // BG44 (background wavelets)
963  else if (chkid == "BG44" && (djvu || djvi))
964  {
965    if (!bg44)
966    {
967      if (bgpm)
968        G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
969      // First chunk
970      GP<IW44Image> bg44=IW44Image::create_decode(IW44Image::COLOR);
971      bg44->decode_chunk(gbs);
972      this->bg44 = bg44;
973      desc.format( ERR_MSG("DjVuFile.IW44_bg1") "\t%d\t%d\t%d",
974                      bg44->get_width(), bg44->get_height(),
975          get_dpi(bg44->get_width(), bg44->get_height()));
976    } 
977    else
978    {
979      // Refinement chunks
980      GP<IW44Image> bg44 = this->bg44;
981      bg44->decode_chunk(gbs);
982      desc.format( ERR_MSG("DjVuFile.IW44_bg2") "\t%d\t%d",
983                      bg44->get_serial(), get_dpi(bg44->get_width(), bg44->get_height()));
984    }
985  }
986 
987  // FG44 (foreground wavelets)
988  else if (chkid == "FG44" && (djvu || djvu))
989  {
990    if (fgpm || fgbc)
991      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
992    GP<IW44Image> gfg44=IW44Image::create_decode(IW44Image::COLOR);
993    IW44Image &fg44=*gfg44;
994    fg44.decode_chunk(gbs);
995    fgpm=fg44.get_pixmap();
996    desc.format( ERR_MSG("DjVuFile.IW44_fg") "\t%d\t%d\t%d",
997      fg44.get_width(), fg44.get_height(),
998      get_dpi(fg44.get_width(), fg44.get_height()));
999  } 
1000 
1001  // LINK (background LINK)
1002  else if (chkid == "LINK" && (djvu || djvi))
1003  {
1004    if (bg44 || bgpm)
1005      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
1006    if(djvu_decode_codec)
1007    {
1008      set_modified(true);
1009      set_can_compress(true);
1010      set_needs_compression(true);
1011      this->bgpm = djvu_decode_codec(bs);
1012      desc.format( ERR_MSG("DjVuFile.color_import1") "\t%d\t%d\t%d",
1013        bgpm->columns(), bgpm->rows(),
1014        get_dpi(bgpm->columns(), bgpm->rows()));
1015    }else
1016    {
1017      desc.format( ERR_MSG("DjVuFile.color_import2") );
1018    }
1019  } 
1020 
1021  // BGjp (background JPEG)
1022  else if (chkid == "BGjp" && (djvu || djvi))
1023  {
1024    if (bg44 || bgpm)
1025      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
1026    set_can_compress(true);
1027#ifdef NEED_JPEG_DECODER
1028    this->bgpm = JPEGDecoder::decode(bs);
1029    desc.format( ERR_MSG("DjVuFile.JPEG_bg1") "\t%d\t%d\t%d",
1030      bgpm->columns(), bgpm->rows(),
1031      get_dpi(bgpm->columns(), bgpm->rows()));
1032#else
1033    desc.format( ERR_MSG("DjVuFile.JPEG_bg2") );
1034#endif
1035  } 
1036 
1037  // FGjp (foreground JPEG)
1038  else if (chkid == "FGjp" && (djvu || djvi))
1039  {
1040    if (fgpm || fgbc)
1041      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
1042#ifdef NEED_JPEG_DECODER
1043    this->fgpm = JPEGDecoder::decode(bs);
1044    desc.format( ERR_MSG("DjVuFile.JPEG_fg1") "\t%d\t%d\t%d",
1045      fgpm->columns(), fgpm->rows(),
1046      get_dpi(fgpm->columns(), fgpm->rows()));
1047#else
1048    desc.format( ERR_MSG("DjVuFile.JPEG_fg2") );
1049#endif
1050  } 
1051 
1052  // BG2k (background JPEG-2000) Note: JPEG2K bitstream not finalized.
1053  else if (chkid == "BG2k" && (djvu || djvi))
1054  {
1055    if (bg44)
1056      G_THROW( ERR_MSG("DjVuFile.dupl_backgrnd") );
1057    desc.format( ERR_MSG("DjVuFile.JPEG2K_bg") );
1058  } 
1059 
1060  // FG2k (foreground JPEG-2000) Note: JPEG2K bitstream not finalized.
1061  else if (chkid == "FG2k" && (djvu || djvi))
1062  {
1063    if (fgpm || fgbc)
1064      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
1065    desc.format( ERR_MSG("DjVuFile.JPEG2K_fg") );
1066  } 
1067 
1068  // FGbz (foreground color vector)
1069  else if (chkid == "FGbz" && (djvu || djvi))
1070  {
1071    if (fgpm || fgbc)
1072      G_THROW( ERR_MSG("DjVuFile.dupl_foregrnd") );
1073    GP<DjVuPalette> fgbc = DjVuPalette::create();
1074    fgbc->decode(gbs);
1075    this->fgbc = fgbc;
1076    desc.format( ERR_MSG("DjVuFile.JB2_fg") "\t%d\t%d",
1077      fgbc->size(), fgbc->colordata.size());
1078  }
1079 
1080  // BM44/PM44 (IW44 data)
1081  else if ((chkid == "PM44" || chkid=="BM44") && iw44)
1082  {
1083    if (!bg44)
1084    {
1085      // First chunk
1086      GP<IW44Image> bg44 = IW44Image::create_decode(IW44Image::COLOR);
1087      bg44->decode_chunk(gbs);
1088      GP<DjVuInfo> info = DjVuInfo::create();
1089      info->width = bg44->get_width();
1090      info->height = bg44->get_height();
1091      info->dpi = 100;
1092      this->bg44 = bg44;
1093      this->info = info;
1094      desc.format( ERR_MSG("DjVuFile.IW44_data1") "\t%d\t%d\t%d",
1095                   bg44->get_width(), bg44->get_height(),
1096                   get_dpi(bg44->get_width(), bg44->get_height()));
1097    } 
1098    else
1099    {
1100      // Refinement chunks
1101      GP<IW44Image> bg44 = this->bg44;
1102      bg44->decode_chunk(gbs);
1103      desc.format( ERR_MSG("DjVuFile.IW44_data2") "\t%d\t%d",
1104                   bg44->get_serial(),
1105                   get_dpi(bg44->get_width(), bg44->get_height()));
1106    }
1107  }
1108 
1109  // NDIR (obsolete navigation chunk)
1110  else if (chkid == "NDIR")
1111  {
1112    GP<DjVuNavDir> dir=DjVuNavDir::create(url);
1113    dir->decode(bs);
1114    this->dir=dir;
1115    desc.format( ERR_MSG("DjVuFile.nav_dir") );
1116  }
1117 
1118  // FORM:ANNO (obsolete) (must be before other annotations)
1119  else if (chkid == "FORM:ANNO") 
1120    {
1121      const GP<ByteStream> gachunk(ByteStream::create());
1122      ByteStream &achunk=*gachunk;
1123      achunk.copy(bs);
1124      achunk.seek(0);
1125      GCriticalSectionLock lock(&anno_lock);
1126      if (! anno)
1127      {
1128        anno=ByteStream::create();
1129      }
1130      anno->seek(0,SEEK_END);
1131      if (anno->tell())
1132      {
1133        anno->write((void*)"", 1);
1134      }
1135      // Copy data
1136      anno->copy(achunk);
1137      desc.format( ERR_MSG("DjVuFile.anno1") );
1138    }
1139 
1140  // ANTa/ANTx/TXTa/TXTz annotations
1141  else if (is_annotation(chkid))  // but not FORM:ANNO
1142    {
1143      const GP<ByteStream> gachunk(ByteStream::create());
1144      ByteStream &achunk=*gachunk;
1145      achunk.copy(bs);
1146      achunk.seek(0);
1147      GCriticalSectionLock lock(&anno_lock);
1148      if (! anno)
1149      {
1150        anno = ByteStream::create();
1151      }
1152      anno->seek(0,SEEK_END);
1153      if (anno->tell() & 1)
1154      {
1155        anno->write((const void*)"", 1);
1156      }
1157      // Recreate chunk header
1158      const GP<IFFByteStream> giffout(IFFByteStream::create(anno));
1159      IFFByteStream &iffout=*giffout;
1160      iffout.put_chunk(id);
1161      iffout.copy(achunk);
1162      iffout.close_chunk();
1163      desc.format( ERR_MSG("DjVuFile.anno2") );
1164    }
1165  else if (is_text(chkid))
1166    {
1167      const GP<ByteStream> gachunk(ByteStream::create());
1168      ByteStream &achunk=*gachunk;
1169      achunk.copy(bs);
1170      achunk.seek(0);
1171      GCriticalSectionLock lock(&text_lock);
1172      if (! text)
1173      {
1174        text = ByteStream::create();
1175      }
1176      text->seek(0,SEEK_END);
1177      if (text->tell())
1178      {
1179        text->write((const void*)"", 1);
1180      }
1181      // Recreate chunk header
1182      const GP<IFFByteStream> giffout(IFFByteStream::create(text));
1183      IFFByteStream &iffout=*giffout;
1184      iffout.put_chunk(id);
1185      iffout.copy(achunk);
1186      iffout.close_chunk();
1187      desc.format( ERR_MSG("DjVuFile.text") );
1188    }
1189  else if (is_meta(chkid))
1190    {
1191      const GP<ByteStream> gachunk(ByteStream::create());
1192      ByteStream &achunk=*gachunk;
1193      achunk.copy(bs);
1194      achunk.seek(0);
1195      GCriticalSectionLock lock(&text_lock);
1196      if (! meta)
1197      {
1198        meta = ByteStream::create();
1199      }
1200      meta->seek(0,SEEK_END);
1201      if (meta->tell())
1202      {
1203        meta->write((const void*)"", 1);
1204      }
1205      // Recreate chunk header
1206      const GP<IFFByteStream> giffout(IFFByteStream::create(meta));
1207      IFFByteStream &iffout=*giffout;
1208      iffout.put_chunk(id);
1209      iffout.copy(achunk);
1210      iffout.close_chunk();
1211//      desc.format( ERR_MSG("DjVuFile.text") );
1212    }
1213
1214  // Return description
1215  return desc;
1216}
1217
1218void
1219DjVuFile::set_decode_codec(GP<GPixmap> (*codec)(ByteStream &bs))
1220{
1221  djvu_decode_codec=codec;
1222}
1223
1224void
1225DjVuFile::decode(const GP<ByteStream> &gbs)
1226{
1227  check();
1228  DEBUG_MSG("DjVuFile::decode(), url='" << url << "'\n");
1229  DEBUG_MAKE_INDENT(3);
1230  DjVuPortcaster * pcaster=get_portcaster();
1231 
1232  // Get form chunk
1233  GUTF8String chkid;
1234  const GP<IFFByteStream> giff(IFFByteStream::create(gbs));
1235  IFFByteStream &iff=*giff;
1236  if (!iff.get_chunk(chkid)) 
1237    REPORT_EOF(true)
1238   
1239    // Check file format
1240  bool djvi = (chkid=="FORM:DJVI")?true:false;
1241  bool djvu = (chkid=="FORM:DJVU")?true:false;
1242  bool iw44 = ((chkid=="FORM:PM44") || (chkid=="FORM:BM44"));
1243  if (djvi || djvu)
1244    mimetype = "image/x.djvu";
1245  else if (iw44)
1246    mimetype = "image/x-iw44";
1247  else
1248    G_THROW( ERR_MSG("DjVuFile.unexp_image") );
1249 
1250  // Process chunks
1251  int size_so_far=iff.tell();
1252  int chunks=0;
1253  int last_chunk=0;
1254  G_TRY
1255  {
1256    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
1257    int chksize;
1258    for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
1259    {
1260      chunks++;
1261
1262      // Decode and get chunk description
1263      GUTF8String str = decode_chunk(chkid, iff.get_bytestream(), djvi, djvu, iw44);
1264      // Add parameters to the chunk description to give the size and chunk id
1265      GUTF8String desc;
1266      desc.format("\t%5.1f\t%s", chksize/1024.0, (const char*)chkid);
1267      // Append the whole thing to the growing file description
1268      description = description + str + desc + "\n";
1269
1270      pcaster->notify_chunk_done(this, chkid);
1271      // Close chunk
1272      iff.seek_close_chunk();
1273      // Record file size
1274      size_so_far=iff.tell();
1275    }
1276    if (chunks_number < 0) chunks_number=last_chunk;
1277  }
1278  G_CATCH(ex)
1279  {
1280    if(!ex.cmp_cause(ByteStream::EndOfFile))
1281    {
1282      if (chunks_number < 0)
1283        chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
1284      report_error(ex,(recover_errors <= SKIP_PAGES));
1285    }else
1286    {
1287      report_error(ex,true);
1288    }
1289  }
1290  G_ENDCATCH;
1291 
1292  // Record file size
1293  file_size=size_so_far;
1294  // Close form chunk
1295  iff.close_chunk();
1296  // Close BG44 codec
1297  if (bg44) 
1298    bg44->close_codec();
1299 
1300  // Complete description
1301  if (djvu && !info)
1302    G_THROW( ERR_MSG("DjVuFile.corrupt_missing_info") );
1303  if (iw44 && !info)
1304    G_THROW( ERR_MSG("DjVuFile.corrupt_missing_IW44") );
1305  if (info)
1306  {
1307    GUTF8String desc;
1308    if (djvu || djvi)
1309      desc.format( ERR_MSG("DjVuFile.djvu_header") "\t%d\t%d\t%d\t%d", 
1310        info->width, info->height,
1311        info->dpi, info->version);
1312    else if (iw44)
1313      desc.format( ERR_MSG("DjVuFile.IW44_header") "\t%d\t%d\t%d", 
1314        info->width, info->height, info->dpi);
1315    description=desc + "\n" + description;
1316    int rawsize=info->width*info->height*3;
1317    desc.format( ERR_MSG("DjVuFile.ratio") "\t%0.1f\t%0.1f",
1318      (double)rawsize/file_size, file_size/1024.0 );
1319    description=description+desc;
1320  }
1321}
1322
1323void
1324DjVuFile::start_decode(void)
1325{
1326  check();
1327  DEBUG_MSG("DjVuFile::start_decode(), url='" << url << "'\n");
1328  DEBUG_MAKE_INDENT(3);
1329 
1330  GThread * thread_to_delete=0;
1331  flags.enter();
1332  G_TRY {
1333    if (!(flags & DONT_START_DECODE) && !is_decoding())
1334    {
1335      if (flags & DECODE_STOPPED) reset();
1336      flags&=~(DECODE_OK | DECODE_STOPPED | DECODE_FAILED);
1337      flags|=DECODING;
1338     
1339      // Don't delete the thread while you're owning the flags lock
1340      // Beware of deadlock!
1341      thread_to_delete=decode_thread; decode_thread=0;
1342     
1343      // We want to create it right here to be able to stop the
1344      // decoding thread even before its function is called (it starts)
1345      decode_data_pool=DataPool::create(data_pool);
1346      decode_life_saver=this;
1347     
1348      decode_thread=new GThread();
1349      decode_thread->create(static_decode_func, this);
1350    }
1351  }
1352  G_CATCH_ALL
1353  {
1354    flags&=~DECODING;
1355    flags|=DECODE_FAILED;
1356    flags.leave();
1357    get_portcaster()->notify_file_flags_changed(this, DECODE_FAILED, DECODING);
1358    delete thread_to_delete;
1359    G_RETHROW;
1360  }
1361  G_ENDCATCH;
1362  flags.leave();
1363  delete thread_to_delete;
1364}
1365
1366bool
1367DjVuFile::resume_decode(const bool sync)
1368{
1369  bool retval=false;
1370  {
1371    GMonitorLock lock(&flags);
1372    if( !is_decoding() && !is_decode_ok() && !is_decode_failed() )
1373    {
1374      start_decode();
1375      retval=true;
1376    }
1377  }
1378  if(sync)
1379  {
1380    wait_for_finish();
1381  }
1382  return retval;
1383}
1384
1385void
1386DjVuFile::stop_decode(bool sync)
1387{
1388  check();
1389 
1390  DEBUG_MSG("DjVuFile::stop_decode(), url='" << url <<
1391    "', sync=" << (int) sync << "\n");
1392  DEBUG_MAKE_INDENT(3);
1393 
1394  G_TRY
1395  {
1396    flags|=DONT_START_DECODE;
1397   
1398    // Don't stop SYNCHRONOUSLY from the thread where the decoding is going!!!
1399    {
1400      // First - ask every included child to stop in async mode
1401      GCriticalSectionLock lock(&inc_files_lock);
1402      for(GPosition pos=inc_files_list;pos;++pos)
1403        inc_files_list[pos]->stop_decode(0);
1404     
1405//      if (decode_data_pool) decode_data_pool->stop();
1406    }
1407   
1408    if (sync)
1409    {
1410      while(1)
1411      {
1412        GP<DjVuFile> file;
1413        {
1414          GCriticalSectionLock lock(&inc_files_lock);
1415          for(GPosition pos=inc_files_list;pos;++pos)
1416          {
1417            GP<DjVuFile> & f=inc_files_list[pos];
1418            if (f->is_decoding())
1419            {
1420              file=f; break;
1421            }
1422          }
1423        }
1424        if (!file) break;
1425       
1426        file->stop_decode(1);
1427      }
1428     
1429      wait_for_finish(1);       // Wait for self termination
1430     
1431      // Don't delete the thread here. Until GPBase::preserve() is
1432      // reimplemented somehow at the GThread level.
1433      // delete decode_thread; decode_thread=0;
1434    }
1435    flags&=~(DONT_START_DECODE);
1436  } G_CATCH_ALL {
1437    flags&=~(DONT_START_DECODE);
1438    G_RETHROW;
1439  } G_ENDCATCH;
1440}
1441
1442void
1443DjVuFile::stop(bool only_blocked)
1444// This is a one-way function. There is no way to undo the stop()
1445// command.
1446{
1447  DEBUG_MSG("DjVuFile::stop(): Stopping everything\n");
1448  DEBUG_MAKE_INDENT(3);
1449 
1450  flags|=only_blocked ? BLOCKED_STOPPED : STOPPED;
1451  if (data_pool) data_pool->stop(only_blocked);
1452  GCriticalSectionLock lock(&inc_files_lock);
1453  for(GPosition pos=inc_files_list;pos;++pos)
1454    inc_files_list[pos]->stop(only_blocked);
1455}
1456
1457GP<DjVuNavDir>
1458DjVuFile::find_ndir(GMap<GURL, void *> & map)
1459{
1460  check();
1461 
1462  DEBUG_MSG("DjVuFile::find_ndir(): looking for NDIR in '" << url << "'\n");
1463  DEBUG_MAKE_INDENT(3);
1464 
1465  if (dir) return dir;
1466 
1467  if (!map.contains(url))
1468  {
1469    map[url]=0;
1470   
1471    GPList<DjVuFile> list=get_included_files(false);
1472    for(GPosition pos=list;pos;++pos)
1473    {
1474      GP<DjVuNavDir> d=list[pos]->find_ndir(map);
1475      if (d) return d;
1476    }
1477  }
1478  return 0;
1479}
1480
1481GP<DjVuNavDir>
1482DjVuFile::find_ndir(void)
1483{
1484  GMap<GURL, void *> map;
1485  return find_ndir(map);
1486}
1487
1488GP<DjVuNavDir>
1489DjVuFile::decode_ndir(GMap<GURL, void *> & map)
1490{
1491  check();
1492 
1493  DEBUG_MSG("DjVuFile::decode_ndir(): decoding for NDIR in '" << url << "'\n");
1494  DEBUG_MAKE_INDENT(3);
1495 
1496  if (dir) return dir;
1497 
1498  if (!map.contains(url))
1499  {
1500    map[url]=0;
1501   
1502    const GP<ByteStream> str(data_pool->get_stream());
1503   
1504    GUTF8String chkid;
1505    const GP<IFFByteStream> giff(IFFByteStream::create(str));
1506    IFFByteStream &iff=*giff;
1507    if (!iff.get_chunk(chkid)) 
1508      REPORT_EOF(true)
1509     
1510    int chunks=0;
1511    int last_chunk=0;
1512    G_TRY
1513    {
1514      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
1515      int chksize;
1516      for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
1517      {
1518        chunks++;
1519        if (chkid=="NDIR")
1520        {
1521          GP<DjVuNavDir> d=DjVuNavDir::create(url);
1522          d->decode(*iff.get_bytestream());
1523          dir=d;
1524          break;
1525        }
1526        iff.seek_close_chunk();
1527      }
1528      if ((!dir)&&(chunks_number < 0)) chunks_number=last_chunk;
1529    }
1530    G_CATCH(ex)
1531    {
1532       if(!ex.cmp_cause(ByteStream::EndOfFile))
1533       {
1534          if (chunks_number < 0)
1535             chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
1536          report_error(ex,(recover_errors<=SKIP_PAGES));
1537       }else
1538       {
1539          report_error(ex,true);
1540       }
1541    }
1542    G_ENDCATCH;
1543   
1544    data_pool->clear_stream();
1545    if (dir) return dir;
1546   
1547    GPList<DjVuFile> list=get_included_files(false);
1548    for(GPosition pos=list;pos;++pos)
1549    {
1550      GP<DjVuNavDir> d=list[pos]->decode_ndir(map);
1551      if (d) return d;
1552    }
1553    data_pool->clear_stream();
1554  }
1555  return 0;
1556}
1557
1558GP<DjVuNavDir>
1559DjVuFile::decode_ndir(void)
1560{
1561  GMap<GURL, void *> map;
1562  return decode_ndir(map);
1563}
1564
1565void
1566DjVuFile::get_merged_anno(const GP<DjVuFile> & file,
1567  const GP<ByteStream> &gstr_out, const GList<GURL> & ignore_list,
1568  int level, int & max_level, GMap<GURL, void *> & map)
1569{
1570  DEBUG_MSG("DjVuFile::get_merged_anno()\n");
1571  GURL url=file->get_url();
1572  if (!map.contains(url))
1573  {
1574    ByteStream &str_out=*gstr_out;
1575    map[url]=0;
1576   
1577    // Do the included files first (To make sure that they have
1578    // less precedence)
1579    // Depending on if we have all data present, we will
1580    // either create all included files or will use only
1581    // those that have already been created
1582    GPList<DjVuFile> list=file->get_included_files(!file->is_data_present());
1583    for(GPosition pos=list;pos;++pos)
1584      get_merged_anno(list[pos], gstr_out, ignore_list, level+1, max_level, map);
1585   
1586    // Now process the DjVuFile's own annotations
1587    if (!ignore_list.contains(file->get_url()))
1588    {
1589      if (!file->is_data_present() ||
1590        file->is_modified() && file->anno)
1591      {
1592               // Process the decoded (?) anno
1593        GCriticalSectionLock lock(&file->anno_lock);
1594        if (file->anno && file->anno->size())
1595        {
1596          if (str_out.tell())
1597          {
1598            str_out.write((void *) "", 1);
1599          }
1600          file->anno->seek(0);
1601          str_out.copy(*file->anno);
1602        }
1603      } else if (file->is_data_present())
1604      {
1605               // Copy all annotations chunks, but do NOT modify
1606               // this->anno (to avoid correlation with DjVuFile::decode())
1607        const GP<ByteStream> str(file->data_pool->get_stream());
1608        const GP<IFFByteStream> giff(IFFByteStream::create(str));
1609        IFFByteStream &iff=*giff;
1610        GUTF8String chkid;
1611        if (iff.get_chunk(chkid))
1612          while(iff.get_chunk(chkid))
1613          {
1614            if (chkid=="FORM:ANNO")
1615            {
1616              if (max_level<level)
1617                max_level=level;
1618              if (str_out.tell())
1619              {
1620                str_out.write((void *) "", 1);
1621              }
1622              str_out.copy(*iff.get_bytestream());
1623            } 
1624            else if (is_annotation(chkid)) // but not FORM:ANNO
1625            {
1626              if (max_level<level)
1627                max_level=level;
1628              if (str_out.tell()&&chkid != "ANTz")
1629              {
1630                str_out.write((void *) "", 1);
1631              }
1632              const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1633              IFFByteStream &iff_out=*giff_out;
1634              iff_out.put_chunk(chkid);
1635              iff_out.copy(*iff.get_bytestream());
1636              iff_out.close_chunk();
1637            }
1638            iff.close_chunk();
1639          }
1640        file->data_pool->clear_stream();
1641      }
1642    }
1643  }
1644}
1645
1646GP<ByteStream>
1647DjVuFile::get_merged_anno(const GList<GURL> & ignore_list,
1648                          int * max_level_ptr)
1649                          // Will do the same thing as get_merged_anno(int *), but will
1650                          // ignore DjVuFiles with URLs from the ignore_list
1651{
1652  DEBUG_MSG("DjVuFile::get_merged_anno()\n");
1653  GP<ByteStream> gstr(ByteStream::create());
1654  GMap<GURL, void *> map;
1655  int max_level=0;
1656  get_merged_anno(this, gstr, ignore_list, 0, max_level, map);
1657  if (max_level_ptr)
1658    *max_level_ptr=max_level;
1659  ByteStream &str=*gstr;
1660  if (!str.tell()) 
1661  {
1662    gstr=0;
1663  }else
1664  {
1665    str.seek(0);
1666  }
1667  return gstr;
1668}
1669
1670GP<ByteStream>
1671DjVuFile::get_merged_anno(int * max_level_ptr)
1672// Will go down the DjVuFile's hierarchy and decode all DjVuAnno even
1673// when the DjVuFile is not fully decoded yet. To avoid correlations
1674// with DjVuFile::decode(), we do not modify DjVuFile::anno data.
1675//
1676// Files deeper in the hierarchy have less influence on the
1677// results. It means, for example, that the if annotations are
1678// specified in the top level page file and in a shared file,
1679// the top level page file settings will take precedence.
1680//
1681// NOTE! This function guarantees correct results only if the
1682// DjVuFile has all data
1683{
1684  GList<GURL> ignore_list;
1685  return get_merged_anno(ignore_list, max_level_ptr);
1686}
1687
1688
1689// [LB->BCR] The following six functions get_anno, get_text, get_meta
1690// contain the same code in triplicate!!!
1691
1692void
1693DjVuFile::get_anno(
1694  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
1695{
1696  DEBUG_MSG("DjVuFile::get_anno()\n");
1697  ByteStream &str_out=*gstr_out;
1698  if (!file->is_data_present() ||
1699    file->is_modified() && file->anno)
1700  {
1701    // Process the decoded (?) anno
1702    GCriticalSectionLock lock(&file->anno_lock);
1703    if (file->anno && file->anno->size())
1704    {
1705      if (str_out.tell())
1706      {
1707        str_out.write((void *) "", 1);
1708      }
1709      file->anno->seek(0);
1710      str_out.copy(*file->anno);
1711    }
1712  } else if (file->is_data_present())
1713  {
1714               // Copy all anno chunks, but do NOT modify
1715               // DjVuFile::anno (to avoid correlation with DjVuFile::decode())
1716    const GP<ByteStream> str=file->data_pool->get_stream();
1717    const GP<IFFByteStream> giff=IFFByteStream::create(str);
1718    IFFByteStream &iff=*giff;
1719    GUTF8String chkid;
1720    if (iff.get_chunk(chkid))
1721    {
1722      while(iff.get_chunk(chkid))
1723      {
1724        if (is_annotation(chkid))
1725        {
1726          if (str_out.tell())
1727          {
1728            str_out.write((void *) "", 1);
1729          }
1730          const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1731          IFFByteStream &iff_out=*giff_out;
1732          iff_out.put_chunk(chkid);
1733          iff_out.copy(*iff.get_bytestream());
1734          iff_out.close_chunk();
1735        }
1736        iff.close_chunk();
1737      }
1738    }
1739    file->data_pool->clear_stream();
1740  }
1741}
1742
1743void
1744DjVuFile::get_text(
1745  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
1746{
1747  DEBUG_MSG("DjVuFile::get_text()\n");
1748  ByteStream &str_out=*gstr_out;
1749  if (!file->is_data_present() ||
1750    file->is_modified() && file->text)
1751  {
1752    // Process the decoded (?) text
1753    GCriticalSectionLock lock(&file->text_lock);
1754    if (file->text && file->text->size())
1755    {
1756      if (str_out.tell())
1757      {
1758        str_out.write((void *) "", 1);
1759      }
1760      file->text->seek(0);
1761      str_out.copy(*file->text);
1762    }
1763  } else if (file->is_data_present())
1764  {
1765               // Copy all text chunks, but do NOT modify
1766               // DjVuFile::text (to avoid correlation with DjVuFile::decode())
1767    const GP<ByteStream> str=file->data_pool->get_stream();
1768    const GP<IFFByteStream> giff=IFFByteStream::create(str);
1769    IFFByteStream &iff=*giff;
1770    GUTF8String chkid;
1771    if (iff.get_chunk(chkid))
1772    {
1773      while(iff.get_chunk(chkid))
1774      {
1775        if (is_text(chkid))
1776        {
1777          if (str_out.tell())
1778          {
1779            str_out.write((void *) "", 1);
1780          }
1781          const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1782          IFFByteStream &iff_out=*giff_out;
1783          iff_out.put_chunk(chkid);
1784          iff_out.copy(*iff.get_bytestream());
1785          iff_out.close_chunk();
1786        }
1787        iff.close_chunk();
1788      }
1789    }
1790    file->data_pool->clear_stream();
1791  }
1792}
1793
1794void
1795DjVuFile::get_meta(
1796  const GP<DjVuFile> & file, const GP<ByteStream> &gstr_out)
1797{
1798  DEBUG_MSG("DjVuFile::get_meta()\n");
1799  ByteStream &str_out=*gstr_out;
1800  if (!file->is_data_present() ||
1801    file->is_modified() && file->meta)
1802  {
1803    // Process the decoded (?) meta
1804    GCriticalSectionLock lock(&file->meta_lock);
1805    if (file->meta && file->meta->size())
1806    {
1807      if (str_out.tell())
1808      {
1809        str_out.write((void *) "", 1);
1810      }
1811      file->meta->seek(0);
1812      str_out.copy(*file->meta);
1813    }
1814  } else if (file->is_data_present())
1815  {
1816               // Copy all meta chunks, but do NOT modify
1817               // DjVuFile::meta (to avoid correlation with DjVuFile::decode())
1818    const GP<ByteStream> str=file->data_pool->get_stream();
1819    const GP<IFFByteStream> giff=IFFByteStream::create(str);
1820    IFFByteStream &iff=*giff;
1821    GUTF8String chkid;
1822    if (iff.get_chunk(chkid))
1823    {
1824      while(iff.get_chunk(chkid))
1825      {
1826        if (is_meta(chkid))
1827        {
1828          if (str_out.tell())
1829          {
1830            str_out.write((void *) "", 1);
1831          }
1832          const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
1833          IFFByteStream &iff_out=*giff_out;
1834          iff_out.put_chunk(chkid);
1835          iff_out.copy(*iff.get_bytestream());
1836          iff_out.close_chunk();
1837        }
1838        iff.close_chunk();
1839      }
1840    }
1841    file->data_pool->clear_stream();
1842  }
1843}
1844
1845GP<ByteStream>
1846DjVuFile::get_anno(void)
1847{
1848  DEBUG_MSG("DjVuFile::get_text(void)\n");
1849  GP<ByteStream> gstr(ByteStream::create());
1850  get_anno(this, gstr);
1851  ByteStream &str=*gstr;
1852  if (!str.tell())
1853  { 
1854    gstr=0;
1855  }else
1856  {
1857    str.seek(0);
1858  }
1859  return gstr;
1860}
1861
1862GP<ByteStream>
1863DjVuFile::get_text(void)
1864{
1865  DEBUG_MSG("DjVuFile::get_text(void)\n");
1866  GP<ByteStream> gstr(ByteStream::create());
1867  get_text(this, gstr);
1868  ByteStream &str=*gstr;
1869  if (!str.tell())
1870  { 
1871    gstr=0;
1872  }else
1873  {
1874    str.seek(0);
1875  }
1876  return gstr;
1877}
1878
1879GP<ByteStream>
1880DjVuFile::get_meta(void)
1881{
1882  DEBUG_MSG("DjVuFile::get_meta(void)\n");
1883  GP<ByteStream> gstr(ByteStream::create());
1884  get_meta(this, gstr);
1885  ByteStream &str=*gstr;
1886  if (!str.tell())
1887  { 
1888    gstr=0;
1889  }else
1890  {
1891    str.seek(0);
1892  }
1893  return gstr;
1894}
1895
1896void
1897DjVuFile::static_trigger_cb(void * cl_data)
1898{
1899  DjVuFile * th=(DjVuFile *) cl_data;
1900  G_TRY {
1901    GP<DjVuPort> port=DjVuPort::get_portcaster()->is_port_alive(th);
1902    if (port && port->inherits("DjVuFile"))
1903      ((DjVuFile *) (DjVuPort *) port)->trigger_cb();
1904  } G_CATCH(exc) {
1905    G_TRY {
1906      get_portcaster()->notify_error(th, exc.get_cause());
1907    } G_CATCH_ALL {} G_ENDCATCH;
1908  } G_ENDCATCH;
1909}
1910
1911void
1912DjVuFile::trigger_cb(void)
1913{
1914  GP<DjVuFile> life_saver=this;
1915 
1916  DEBUG_MSG("DjVuFile::trigger_cb(): got data for '" << url << "'\n");
1917  DEBUG_MAKE_INDENT(3);
1918 
1919  file_size=data_pool->get_length();
1920  flags|=DATA_PRESENT;
1921  get_portcaster()->notify_file_flags_changed(this, DATA_PRESENT, 0);
1922 
1923  if (!are_incl_files_created())
1924    process_incl_chunks();
1925 
1926  bool all=true;
1927  inc_files_lock.lock();
1928  GPList<DjVuFile> files_list=inc_files_list;
1929  inc_files_lock.unlock();
1930  for(GPosition pos=files_list;pos&&(all=files_list[pos]->is_all_data_present());++pos)
1931    EMPTY_LOOP;
1932  if (all)
1933  {
1934    DEBUG_MSG("DjVuFile::trigger_cb(): We have ALL data for '" << url << "'\n");
1935    flags|=ALL_DATA_PRESENT;
1936    get_portcaster()->notify_file_flags_changed(this, ALL_DATA_PRESENT, 0);
1937  }
1938}
1939
1940void
1941DjVuFile::progress_cb(int pos, void * cl_data)
1942{
1943  DEBUG_MSG("DjVuFile::progress_cb() called\n");
1944  DEBUG_MAKE_INDENT(3);
1945 
1946  DjVuFile * th=(DjVuFile *) cl_data;
1947 
1948  int length=th->decode_data_pool->get_length();
1949  if (length>0)
1950  {
1951    float progress=(float) pos/length;
1952    DEBUG_MSG("progress=" << progress << "\n");
1953    get_portcaster()->notify_decode_progress(th, progress);
1954  } else
1955  {
1956    DEBUG_MSG("DataPool size is still unknown => ignoring\n");
1957  }
1958}
1959
1960//*****************************************************************************
1961//******************************** Utilities **********************************
1962//*****************************************************************************
1963
1964void
1965DjVuFile::move(GMap<GURL, void *> & map, const GURL & dir_url)
1966// This function may block for data.
1967{
1968  if (!map.contains(url))
1969  {
1970    map[url]=0;
1971   
1972    url=GURL::UTF8(url.name(),dir_url);
1973   
1974   
1975    // Leave the lock here!
1976    GCriticalSectionLock lock(&inc_files_lock);
1977    for(GPosition pos=inc_files_list;pos;++pos)
1978      inc_files_list[pos]->move(map, dir_url);
1979  }
1980}
1981
1982void
1983DjVuFile::move(const GURL & dir_url)
1984// This function may block for data.
1985{
1986  check();
1987  DEBUG_MSG("DjVuFile::move(): dir_url='" << dir_url << "'\n");
1988  DEBUG_MAKE_INDENT(3);
1989 
1990  GMap<GURL, void *> map;
1991  move(map, dir_url);
1992}
1993
1994void
1995DjVuFile::set_name(const GUTF8String &name)
1996{
1997  DEBUG_MSG("DjVuFile::set_name(): name='" << name << "'\n");
1998  DEBUG_MAKE_INDENT(3);
1999  url=GURL::UTF8(name,url.base());
2000}
2001
2002//*****************************************************************************
2003//****************************** Data routines ********************************
2004//*****************************************************************************
2005
2006int
2007DjVuFile::get_chunks_number(void)
2008{
2009  if(chunks_number < 0)
2010  {
2011    const GP<ByteStream> str(data_pool->get_stream());
2012    GUTF8String chkid;
2013    const GP<IFFByteStream> giff(IFFByteStream::create(str));
2014    IFFByteStream &iff=*giff;
2015    if (!iff.get_chunk(chkid))
2016      REPORT_EOF(true)
2017     
2018      int chunks=0;
2019    int last_chunk=0;
2020    G_TRY
2021    {
2022      int chksize;
2023      for(;(chksize=iff.get_chunk(chkid));last_chunk=chunks)
2024      {
2025        chunks++;
2026        iff.seek_close_chunk();
2027      }
2028      chunks_number=last_chunk;
2029    }
2030    G_CATCH(ex)
2031    {
2032      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2033      report_error(ex,(recover_errors<=SKIP_PAGES));
2034    }
2035    G_ENDCATCH;
2036    data_pool->clear_stream();
2037  }
2038  return chunks_number;
2039}
2040
2041GUTF8String
2042DjVuFile::get_chunk_name(int chunk_num)
2043{
2044  if(chunk_num < 0)
2045  {
2046    G_THROW( ERR_MSG("DjVuFile.illegal_chunk") );
2047  }
2048  if((chunks_number >= 0)&&(chunk_num > chunks_number))
2049  {
2050    G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
2051  }
2052  check();
2053 
2054  GUTF8String name;
2055  const GP<ByteStream> str(data_pool->get_stream());
2056  GUTF8String chkid;
2057  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2058  IFFByteStream &iff=*giff;
2059  if (!iff.get_chunk(chkid)) 
2060    REPORT_EOF(true)
2061   
2062    int chunks=0;
2063  int last_chunk=0;
2064  G_TRY
2065  {
2066    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
2067    int chksize;
2068    for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
2069    {
2070      if (chunks++==chunk_num) { name=chkid; break; }
2071      iff.seek_close_chunk();
2072    }
2073  }
2074  G_CATCH(ex)
2075  {
2076    if (chunks_number < 0)
2077      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2078    report_error(ex,(recover_errors <= SKIP_PAGES));
2079  }
2080  G_ENDCATCH;
2081  if (!name.length())
2082  {
2083    if (chunks_number < 0) chunks_number=chunks;
2084    G_THROW( ERR_MSG("DjVuFile.missing_chunk") );
2085  }
2086  return name;
2087}
2088
2089bool
2090DjVuFile::contains_chunk(const GUTF8String &chunk_name)
2091{
2092  check();
2093  DEBUG_MSG("DjVuFile::contains_chunk(): url='" << url << "', chunk_name='" <<
2094    chunk_name << "'\n");
2095  DEBUG_MAKE_INDENT(3);
2096 
2097  bool contains=0;
2098  const GP<ByteStream> str(data_pool->get_stream());
2099  GUTF8String chkid;
2100  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2101  IFFByteStream &iff=*giff;
2102  if (!iff.get_chunk(chkid)) 
2103    REPORT_EOF((recover_errors<=SKIP_PAGES))
2104   
2105    int chunks=0;
2106  int last_chunk=0;
2107  G_TRY
2108  {
2109    int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
2110    int chksize;
2111    for(;(chunks_left--)&&(chksize=iff.get_chunk(chkid));last_chunk=chunks)
2112    {
2113      chunks++;
2114      if (chkid==chunk_name) { contains=1; break; }
2115      iff.seek_close_chunk();
2116    }
2117    if (!contains &&(chunks_number < 0)) chunks_number=last_chunk;
2118  }
2119  G_CATCH(ex)
2120  {
2121    if (chunks_number < 0)
2122      chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2123    report_error(ex,(recover_errors <= SKIP_PAGES));
2124  }
2125  G_ENDCATCH;
2126  data_pool->clear_stream();
2127  return contains;
2128}
2129
2130bool
2131DjVuFile::contains_anno(void)
2132{
2133  const GP<ByteStream> str(data_pool->get_stream());
2134 
2135  GUTF8String chkid;
2136  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2137  IFFByteStream &iff=*giff;
2138  if (!iff.get_chunk(chkid))
2139    G_THROW( ByteStream::EndOfFile );
2140 
2141  while(iff.get_chunk(chkid))
2142  {
2143    if (is_annotation(chkid))
2144      return true;
2145    iff.close_chunk();
2146  }
2147 
2148  data_pool->clear_stream();
2149  return false;
2150}
2151
2152bool
2153DjVuFile::contains_text(void)
2154{
2155  const GP<ByteStream> str(data_pool->get_stream());
2156 
2157  GUTF8String chkid;
2158  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2159  IFFByteStream &iff=*giff;
2160  if (!iff.get_chunk(chkid))
2161    G_THROW( ByteStream::EndOfFile );
2162 
2163  while(iff.get_chunk(chkid))
2164  {
2165    if (is_text(chkid))
2166      return true;
2167    iff.close_chunk();
2168  }
2169 
2170  data_pool->clear_stream();
2171  return false;
2172}
2173
2174bool
2175DjVuFile::contains_meta(void)
2176{
2177  const GP<ByteStream> str(data_pool->get_stream());
2178 
2179  GUTF8String chkid;
2180  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2181  IFFByteStream &iff=*giff;
2182  if (!iff.get_chunk(chkid))
2183    G_THROW( ByteStream::EndOfFile );
2184 
2185  while(iff.get_chunk(chkid))
2186  {
2187    if (is_meta(chkid))
2188      return true;
2189    iff.close_chunk();
2190  }
2191 
2192  data_pool->clear_stream();
2193  return false;
2194}
2195
2196//*****************************************************************************
2197//****************************** Save routines ********************************
2198//*****************************************************************************
2199
2200static void
2201copy_chunks(const GP<ByteStream> &from, IFFByteStream &ostr)
2202{
2203  from->seek(0);
2204  const GP<IFFByteStream> giff(IFFByteStream::create(from));
2205  IFFByteStream &iff=*giff;
2206  GUTF8String chkid;
2207  int chksize;
2208  while ((chksize=iff.get_chunk(chkid)))
2209  {
2210    ostr.put_chunk(chkid);
2211    int ochksize=ostr.copy(*iff.get_bytestream());
2212    ostr.close_chunk();
2213    iff.seek_close_chunk();
2214    if(ochksize != chksize)
2215    {
2216      G_THROW( ByteStream::EndOfFile );
2217    }
2218  }
2219}
2220
2221
2222void
2223DjVuFile::add_djvu_data(IFFByteStream & ostr, GMap<GURL, void *> & map,
2224                        const bool included_too, const bool no_ndir)
2225{
2226  check();
2227  if (map.contains(url)) return;
2228  bool top_level = !map.size();
2229  map[url]=0;
2230  bool processed_annotation = false;
2231  bool processed_text = false;
2232  bool processed_meta = false;
2233 
2234  const GP<ByteStream> str(data_pool->get_stream());
2235  GUTF8String chkid;
2236  const GP<IFFByteStream> giff(IFFByteStream::create(str));
2237  IFFByteStream &iff=*giff;
2238  if (!iff.get_chunk(chkid)) 
2239    REPORT_EOF(true)
2240   
2241    // Open toplevel form
2242    if (top_level) 
2243      ostr.put_chunk(chkid);
2244    // Process chunks
2245    int chunks=0;
2246    int last_chunk=0;
2247    G_TRY
2248    {
2249      int chunks_left=(recover_errors>SKIP_PAGES)?chunks_number:(-1);
2250      int chksize;
2251      for(;(chunks_left--)&&(chksize = iff.get_chunk(chkid));last_chunk=chunks)
2252      {
2253        chunks++;
2254        if (is_info(chkid) && info)
2255        {
2256          ostr.put_chunk(chkid);
2257          info->encode(*ostr.get_bytestream());
2258          ostr.close_chunk();
2259        }
2260        else if (chkid=="INCL" && included_too)
2261        {
2262          GP<DjVuFile> file = process_incl_chunk(*iff.get_bytestream());
2263          if (file)
2264          {
2265            if(recover_errors!=ABORT)
2266              file->set_recover_errors(recover_errors);
2267            if(verbose_eof)
2268              file->set_verbose_eof(verbose_eof);
2269            file->add_djvu_data(ostr, map, included_too, no_ndir);
2270          }
2271        } 
2272        else if (is_annotation(chkid) && anno && anno->size())
2273        {
2274          if (!processed_annotation)
2275          {
2276            processed_annotation = true;
2277            GCriticalSectionLock lock(&anno_lock);
2278            copy_chunks(anno, ostr);
2279          }
2280        }
2281        else if (is_text(chkid) && text && text->size())
2282        {
2283          if (!processed_text)
2284          {
2285            processed_text = true;
2286            GCriticalSectionLock lock(&text_lock);
2287            copy_chunks(text, ostr);
2288          }
2289        }
2290        else if (is_meta(chkid) && meta && meta->size())
2291        {
2292          if (!processed_meta)
2293          {
2294            processed_meta = true;
2295            GCriticalSectionLock lock(&meta_lock);
2296            copy_chunks(meta, ostr);
2297          }
2298        }
2299        else if (chkid!="NDIR"||!(no_ndir || dir))
2300        {  // Copy NDIR chunks, but never generate new ones.
2301          ostr.put_chunk(chkid);
2302          ostr.copy(*iff.get_bytestream());
2303          ostr.close_chunk();
2304        }
2305        iff.seek_close_chunk();
2306      }
2307      if (chunks_number < 0) chunks_number=last_chunk;
2308    }
2309    G_CATCH(ex)
2310    {
2311      if(!ex.cmp_cause(ByteStream::EndOfFile))
2312      {
2313        if (chunks_number < 0)
2314          chunks_number=(recover_errors>SKIP_CHUNKS)?chunks:last_chunk;
2315        report_error(ex,(recover_errors<=SKIP_PAGES));
2316      }else
2317      {
2318        report_error(ex,true);
2319      }
2320    }
2321    G_ENDCATCH;
2322   
2323    // Otherwise, writes annotation at the end (annotations could be big)
2324    if (!processed_annotation && anno && anno->size())
2325    {
2326      processed_annotation = true;
2327      GCriticalSectionLock lock(&anno_lock);
2328      copy_chunks(anno, ostr);
2329    }
2330    if (!processed_text && text && text->size())
2331    {
2332      processed_text = true;
2333      GCriticalSectionLock lock(&text_lock);
2334      copy_chunks(text, ostr);
2335    }
2336    if (!processed_meta && meta && meta->size())
2337    {
2338      processed_meta = true;
2339      GCriticalSectionLock lock(&meta_lock);
2340      copy_chunks(meta, ostr);
2341    }
2342    // Close iff
2343    if (top_level) 
2344      ostr.close_chunk();
2345
2346  data_pool->clear_stream();
2347}
2348
2349GP<ByteStream> 
2350DjVuFile::get_djvu_bytestream(const bool included_too, const bool no_ndir)
2351{
2352   check();
2353   DEBUG_MSG("DjVuFile::get_djvu_bytestream(): creating DjVu raw file\n");
2354   DEBUG_MAKE_INDENT(3);
2355   const GP<ByteStream> pbs(ByteStream::create());
2356   const GP<IFFByteStream> giff=IFFByteStream::create(pbs);
2357   IFFByteStream &iff=*giff;
2358   GMap<GURL, void *> map;
2359   add_djvu_data(iff, map, included_too, no_ndir);
2360   iff.flush();
2361   pbs->seek(0, SEEK_SET);
2362   return pbs;
2363}
2364
2365GP<DataPool>
2366DjVuFile::get_djvu_data(const bool included_too, const bool no_ndir)
2367{
2368  const GP<ByteStream> pbs = get_djvu_bytestream(included_too, no_ndir);
2369  return DataPool::create(pbs);
2370}
2371
2372void
2373DjVuFile::merge_anno(ByteStream &out)
2374{
2375  // Reuse get_merged_anno(), which is better than the previous
2376  // implementation due to three things:
2377  //  1. It works even before the file is completely decoded
2378  //  2. It merges annotations taking into account where a child DjVuFile
2379  //     is included.
2380  //  3. It handles loops in DjVuFile's hierarchy
2381 
2382  const GP<ByteStream> str(get_merged_anno());
2383  if (str)
2384  {
2385    str->seek(0);
2386    if (out.tell())
2387    {
2388      out.write((void *) "", 1);
2389    }
2390    out.copy(*str);
2391  }
2392}
2393
2394void
2395DjVuFile::get_text(ByteStream &out)
2396{
2397  const GP<ByteStream> str(get_text());
2398  if (str)
2399  {
2400    str->seek(0);
2401    if (out.tell())
2402    {
2403      out.write((void *) "", 1);
2404    }
2405    out.copy(*str);
2406  }
2407}
2408
2409void
2410DjVuFile::get_meta(ByteStream &out)
2411{
2412  const GP<ByteStream> str(get_meta());
2413  if (str)
2414  {
2415    str->seek(0);
2416    if (out.tell())
2417    {
2418      out.write((void *) "", 1);
2419    }
2420    out.copy(*str);
2421  }
2422}
2423
2424
2425
2426//****************************************************************************
2427//******************************* Modifying **********************************
2428//****************************************************************************
2429
2430void
2431DjVuFile::remove_anno(void)
2432{
2433  DEBUG_MSG("DjVuFile::remove_anno()\n");
2434  const GP<ByteStream> str_in(data_pool->get_stream());
2435  const GP<ByteStream> gstr_out(ByteStream::create());
2436 
2437  GUTF8String chkid;
2438  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2439  IFFByteStream &iff_in=*giff_in;
2440  if (!iff_in.get_chunk(chkid))
2441    G_THROW( ByteStream::EndOfFile );
2442 
2443  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2444  IFFByteStream &iff_out=*giff_out;
2445  iff_out.put_chunk(chkid);
2446 
2447  while(iff_in.get_chunk(chkid))
2448  {
2449    if (!is_annotation(chkid))
2450    {
2451      iff_out.put_chunk(chkid);
2452      iff_out.copy(*iff_in.get_bytestream());
2453      iff_out.close_chunk();
2454    }
2455    iff_in.close_chunk();
2456  }
2457 
2458  iff_out.close_chunk();
2459 
2460  gstr_out->seek(0, SEEK_SET);
2461  data_pool=DataPool::create(gstr_out);
2462  chunks_number=-1;
2463 
2464  anno=0;
2465 
2466  flags|=MODIFIED;
2467  data_pool->clear_stream();
2468}
2469
2470void
2471DjVuFile::remove_text(void)
2472{
2473  DEBUG_MSG("DjVuFile::remove_text()\n");
2474  const GP<ByteStream> str_in(data_pool->get_stream());
2475  const GP<ByteStream> gstr_out(ByteStream::create());
2476 
2477  GUTF8String chkid;
2478  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2479  IFFByteStream &iff_in=*giff_in;
2480  if (!iff_in.get_chunk(chkid))
2481    G_THROW( ByteStream::EndOfFile );
2482 
2483  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2484  IFFByteStream &iff_out=*giff_out;
2485  iff_out.put_chunk(chkid);
2486 
2487  while(iff_in.get_chunk(chkid))
2488  {
2489    if (!is_text(chkid))
2490    {
2491      iff_out.put_chunk(chkid);
2492      iff_out.copy(*iff_in.get_bytestream());
2493      iff_out.close_chunk();
2494    }
2495    iff_in.close_chunk();
2496  }
2497 
2498  iff_out.close_chunk();
2499 
2500  gstr_out->seek(0, SEEK_SET);
2501  data_pool=DataPool::create(gstr_out);
2502  chunks_number=-1;
2503 
2504  text=0;
2505 
2506  flags|=MODIFIED;
2507  data_pool->clear_stream();
2508}
2509
2510void
2511DjVuFile::remove_meta(void)
2512{
2513  DEBUG_MSG("DjVuFile::remove_meta()\n");
2514  const GP<ByteStream> str_in(data_pool->get_stream());
2515  const GP<ByteStream> gstr_out(ByteStream::create());
2516 
2517  GUTF8String chkid;
2518  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2519  IFFByteStream &iff_in=*giff_in;
2520  if (!iff_in.get_chunk(chkid))
2521    G_THROW( ByteStream::EndOfFile );
2522 
2523  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2524  IFFByteStream &iff_out=*giff_out;
2525  iff_out.put_chunk(chkid);
2526 
2527  while(iff_in.get_chunk(chkid))
2528  {
2529    if (!is_meta(chkid))
2530    {
2531      iff_out.put_chunk(chkid);
2532      iff_out.copy(*iff_in.get_bytestream());
2533      iff_out.close_chunk();
2534    }
2535    iff_in.close_chunk();
2536  }
2537 
2538  iff_out.close_chunk();
2539 
2540  gstr_out->seek(0, SEEK_SET);
2541  data_pool=DataPool::create(gstr_out);
2542  chunks_number=-1;
2543 
2544  meta=0;
2545 
2546  flags|=MODIFIED;
2547  data_pool->clear_stream();
2548}
2549
2550void
2551DjVuFile::rebuild_data_pool(void)
2552{
2553  data_pool=get_djvu_data(false,false);
2554  chunks_number=1;
2555  flags|=MODIFIED;
2556}
2557
2558// Do NOT comment this function out. It's used by DjVuDocEditor to convert
2559// old-style DjVu documents to BUNDLED format.
2560
2561GP<DataPool>
2562DjVuFile::unlink_file(const GP<DataPool> & data, const GUTF8String &name)
2563// Will process contents of data[] and remove any INCL chunk
2564// containing 'name'
2565{
2566  DEBUG_MSG("DjVuFile::unlink_file()\n");
2567  const GP<ByteStream> gstr_out(ByteStream::create());
2568  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2569  IFFByteStream &iff_out=*giff_out;
2570 
2571  const GP<ByteStream> str_in(data->get_stream());
2572  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2573  IFFByteStream &iff_in=*giff_in;
2574 
2575  int chksize;
2576  GUTF8String chkid;
2577  if (!iff_in.get_chunk(chkid)) return data;
2578 
2579  iff_out.put_chunk(chkid);
2580 
2581  while((chksize=iff_in.get_chunk(chkid)))
2582  {
2583    if (chkid=="INCL")
2584    {
2585      GUTF8String incl_str;
2586      char buffer[1024];
2587      int length;
2588      while((length=iff_in.read(buffer, 1024)))
2589        incl_str+=GUTF8String(buffer, length);
2590     
2591      // Eat '\n' in the beginning and at the end
2592      while(incl_str.length() && incl_str[0]=='\n')
2593      {
2594        incl_str=incl_str.substr(1,(unsigned int)(-1));
2595      }
2596      while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
2597      {
2598        incl_str.setat(incl_str.length()-1, 0);
2599      }
2600      if (incl_str!=name)
2601      {
2602        iff_out.put_chunk(chkid);
2603        iff_out.get_bytestream()->writestring(incl_str);
2604        iff_out.close_chunk();
2605      }
2606    } else
2607    {
2608      iff_out.put_chunk(chkid);
2609      char buffer[1024];
2610      int length;
2611      for(const GP<ByteStream> gbs(iff_out.get_bytestream());
2612        (length=iff_in.read(buffer, 1024));)
2613      {
2614        gbs->writall(buffer, length);
2615      }
2616      iff_out.close_chunk();
2617    }
2618    iff_in.close_chunk();
2619  }
2620  iff_out.close_chunk();
2621  iff_out.flush();
2622  gstr_out->seek(0, SEEK_SET);
2623  data->clear_stream();
2624  return DataPool::create(gstr_out);
2625}
2626
2627#ifndef NEED_DECODER_ONLY
2628void
2629DjVuFile::insert_file(const GUTF8String &id, int chunk_num)
2630{
2631  DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "', chunk_num="
2632    << chunk_num << "\n");
2633  DEBUG_MAKE_INDENT(3);
2634 
2635  // First: create new data
2636  const GP<ByteStream> str_in(data_pool->get_stream());
2637  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2638  IFFByteStream &iff_in=*giff_in;
2639 
2640  const GP<ByteStream> gstr_out(ByteStream::create());
2641  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2642  IFFByteStream &iff_out=*giff_out;
2643 
2644  int chunk_cnt=0;
2645  bool done=false;
2646  GUTF8String chkid;
2647  if (iff_in.get_chunk(chkid))
2648  {
2649    iff_out.put_chunk(chkid);
2650    int chksize;
2651    while((chksize=iff_in.get_chunk(chkid)))
2652    {
2653      if (chunk_cnt++==chunk_num)
2654      {
2655        iff_out.put_chunk("INCL");
2656        iff_out.get_bytestream()->writestring(id);
2657        iff_out.close_chunk();
2658        done=true;
2659      }
2660      iff_out.put_chunk(chkid);
2661      iff_out.copy(*iff_in.get_bytestream());
2662      iff_out.close_chunk();
2663      iff_in.close_chunk();
2664    }
2665    if (!done)
2666    {
2667      iff_out.put_chunk("INCL");
2668      iff_out.get_bytestream()->writestring(id);
2669      iff_out.close_chunk();
2670    }
2671    iff_out.close_chunk();
2672  }
2673  gstr_out->seek(0, SEEK_SET);
2674  data_pool=DataPool::create(gstr_out);
2675  chunks_number=-1;
2676 
2677  // Second: create missing DjVuFiles
2678  process_incl_chunks();
2679 
2680  flags|=MODIFIED;
2681  data_pool->clear_stream();
2682}
2683#endif
2684
2685void
2686DjVuFile::unlink_file(const GUTF8String &id)
2687{
2688  DEBUG_MSG("DjVuFile::insert_file(): id='" << id << "'\n");
2689  DEBUG_MAKE_INDENT(3);
2690 
2691  // Remove the file from the list of included files
2692  {
2693    GURL url=DjVuPort::get_portcaster()->id_to_url(this, id);
2694    if (url.is_empty()) url=GURL::UTF8(id,this->url.base());
2695    GCriticalSectionLock lock(&inc_files_lock);
2696    for(GPosition pos=inc_files_list;pos;)
2697      if (inc_files_list[pos]->get_url()==url)
2698      {
2699        GPosition this_pos=pos;
2700        ++pos;
2701        inc_files_list.del(this_pos);
2702      } else ++pos;
2703  }
2704 
2705  // And update the data.
2706  const GP<ByteStream> str_in(data_pool->get_stream());
2707  const GP<IFFByteStream> giff_in(IFFByteStream::create(str_in));
2708  IFFByteStream &iff_in=*giff_in;
2709 
2710  const GP<ByteStream> gstr_out(ByteStream::create());
2711  const GP<IFFByteStream> giff_out(IFFByteStream::create(gstr_out));
2712  IFFByteStream &iff_out=*giff_out;
2713 
2714  GUTF8String chkid;
2715  if (iff_in.get_chunk(chkid))
2716  {
2717    iff_out.put_chunk(chkid);
2718    int chksize;
2719    while((chksize=iff_in.get_chunk(chkid)))
2720    {
2721      if (chkid!="INCL")
2722      {
2723        iff_out.put_chunk(chkid);
2724        iff_out.copy(*iff_in.get_bytestream());
2725        iff_out.close_chunk();
2726      } else
2727      {
2728        GUTF8String incl_str;
2729        char buffer[1024];
2730        int length;
2731        while((length=iff_in.read(buffer, 1024)))
2732          incl_str+=GUTF8String(buffer, length);
2733       
2734               // Eat '\n' in the beginning and at the end
2735        while(incl_str.length() && incl_str[0]=='\n')
2736        {
2737          incl_str=incl_str.substr(1,(unsigned int)(-1));
2738        }
2739        while(incl_str.length()>0 && incl_str[(int)incl_str.length()-1]=='\n')
2740          incl_str.setat(incl_str.length()-1, 0);
2741        if (incl_str!=id)
2742        {
2743          iff_out.put_chunk("INCL");
2744          iff_out.get_bytestream()->writestring(incl_str);
2745          iff_out.close_chunk();
2746        }
2747      }
2748      iff_in.close_chunk();
2749    }
2750    iff_out.close_chunk();
2751  }
2752 
2753  gstr_out->seek(0, SEEK_SET);
2754  data_pool=DataPool::create(gstr_out);
2755  chunks_number=-1;
2756 
2757  flags|=MODIFIED;
2758}
2759
2760void
2761DjVuFile::change_info(GP<DjVuInfo> xinfo,const bool do_reset)
2762{
2763  DEBUG_MSG("DjVuFile::change_text()\n");
2764  // Mark this as modified
2765  set_modified(true);
2766  if(do_reset)
2767    reset();
2768  info=xinfo;
2769}
2770
2771#ifndef NEED_DECODER_ONLY
2772void
2773DjVuFile::change_text(GP<DjVuTXT> txt,const bool do_reset)
2774{
2775  DEBUG_MSG("DjVuFile::change_text()\n");
2776  GP<DjVuText> gtext_c=DjVuText::create();
2777  DjVuText &text_c=*gtext_c;
2778  if(contains_text())
2779  {
2780    const GP<ByteStream> file_text(get_text());
2781    if(file_text)
2782    {
2783      text_c.decode(file_text);
2784    }
2785  }
2786  GCriticalSectionLock lock(&text_lock);
2787  // Mark this as modified
2788  set_modified(true);
2789  if(do_reset)
2790    reset();
2791  text_c.txt = txt;
2792  text=ByteStream::create();
2793  text_c.encode(text);
2794}
2795
2796void
2797DjVuFile::change_meta(const GUTF8String &xmeta,const bool do_reset)
2798{
2799  DEBUG_MSG("DjVuFile::change_meta()\n");
2800  // Mark this as modified
2801  set_modified(true);
2802  if(contains_meta())
2803  {
2804    (void)get_meta();
2805  }
2806  if(do_reset)
2807    reset();
2808  GCriticalSectionLock lock(&meta_lock);
2809  meta=ByteStream::create();
2810  if(xmeta.length())
2811  {
2812    const GP<IFFByteStream> giff=IFFByteStream::create(meta);
2813    IFFByteStream &iff=*giff;
2814    iff.put_chunk("METz");
2815    {
2816      GP<ByteStream> gbsiff=BSByteStream::create(iff.get_bytestream(),50);
2817      gbsiff->writestring(xmeta);
2818    }
2819    iff.close_chunk();
2820  }
2821}
2822#endif
2823
2824
2825#ifdef HAVE_NAMESPACES
2826}
2827# ifndef NOT_USING_DJVU_NAMESPACE
2828using namespace DJVU;
2829# endif
2830#endif
Note: See TracBrowser for help on using the repository browser.