source: trunk/libdjvu/DjVuFile.cpp @ 269

Last change on this file since 269 was 206, checked in by Eugene Romanenko, 14 years ago

DJVU plugin: djvulibre updated to version 3.5.19

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