source: trunk/libdjvu/DjVuFile.cpp @ 280

Last change on this file since 280 was 280, checked in by rbri, 11 years ago

DJVU plugin: djvulibre updated to version 3.5.22

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