source: trunk/libdjvu/ddjvuapi.cpp @ 426

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

DJVU plugin: djvulibre updated to version 3.5.22

File size: 98.0 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: ddjvuapi.cpp,v 1.91 2009/05/17 23:57:42 leonb Exp $ */
57
58#ifdef HAVE_CONFIG_H
59# include "config.h"
60#endif
61#if NEED_GNUG_PRAGMAS
62# pragma implementation "ddjvuapi.h"
63#endif
64
65#include <stdlib.h>
66#include <stdio.h>
67#include <string.h>
68#include <ctype.h>
69#include <locale.h>
70
71#ifdef HAVE_NAMESPACES
72namespace DJVU {
73  struct ddjvu_context_s;
74  struct ddjvu_job_s;
75  struct ddjvu_document_s;
76  struct ddjvu_page_s;
77  struct ddjvu_format_s;
78  struct ddjvu_message_p;
79  struct ddjvu_thumbnail_p;
80  struct ddjvu_runnablejob_s;
81  struct ddjvu_printjob_s;
82  struct ddjvu_savejob_s;
83}
84using namespace DJVU;
85# define DJVUNS DJVU::
86#else
87# define DJVUNS /**/
88#endif
89
90#include "GException.h"
91#include "GSmartPointer.h"
92#include "GThreads.h"
93#include "GContainer.h"
94#include "ByteStream.h"
95#include "IFFByteStream.h"
96#include "BSByteStream.h"
97#include "GString.h"
98#include "GBitmap.h"
99#include "GPixmap.h"
100#include "GScaler.h"
101#include "DjVuPort.h"
102#include "DataPool.h"
103#include "DjVuInfo.h"
104#include "IW44Image.h"
105#include "DjVuImage.h"
106#include "DjVuFileCache.h"
107#include "DjVuDocument.h"
108#include "DjVuDumpHelper.h"
109#include "DjVuMessageLite.h"
110#include "DjVuMessage.h"
111#include "DjVmNav.h"
112#include "DjVuText.h"
113#include "DjVuAnno.h"
114#include "DjVuToPS.h"
115#include "DjVmDir.h"
116#include "DjVmDir0.h"
117#include "DjVuNavDir.h"
118#include "DjVmDoc.h"
119
120
121#include "miniexp.h"
122#include "ddjvuapi.h"
123
124#if !defined(AUTOCONF) || HAVE_STDINT_H
125# include <stdint.h>
126#elif HAVE_INTTYPES_H
127# include <inttypes.h>
128#endif
129
130
131// ----------------------------------------
132// Private structures
133
134
135struct DJVUNS ddjvu_message_p : public GPEnabled
136{
137  GNativeString tmp1;
138  GNativeString tmp2;
139  ddjvu_message_t p;
140  ddjvu_message_p() { memset(&p, 0, sizeof(p)); }
141};
142
143struct DJVUNS ddjvu_thumbnail_p : public GPEnabled
144{
145  ddjvu_document_t *document;
146  int pagenum;
147  GTArray<char> data;
148  GP<DataPool> pool;
149  static void callback(void *);
150}; 
151
152
153// ----------------------------------------
154// Context, Jobs, Document, Pages
155
156
157struct DJVUNS ddjvu_context_s : public GPEnabled
158{
159  GMonitor monitor;
160  GP<DjVuFileCache> cache;
161  GPList<ddjvu_message_p> mlist;
162  GP<ddjvu_message_p> mpeeked;
163  int uniqueid;
164  ddjvu_message_callback_t callbackfun;
165  void *callbackarg;
166};
167
168struct DJVUNS ddjvu_job_s : public DjVuPort
169{
170  GMonitor monitor;
171  void *userdata;
172  GP<ddjvu_context_s> myctx;
173  GP<ddjvu_document_s> mydoc;
174  bool released;
175  ddjvu_job_s();
176  // virtual port functions:
177  virtual bool inherits(const GUTF8String&);
178  virtual bool notify_error(const DjVuPort*, const GUTF8String&); 
179  virtual bool notify_status(const DjVuPort*, const GUTF8String&);
180  // default implementation of virtual job functions:
181  virtual ddjvu_status_t status() {return DDJVU_JOB_NOTSTARTED;}
182  virtual void release() {}
183  virtual void stop() {}
184};
185
186struct DJVUNS ddjvu_document_s : public ddjvu_job_s
187{
188  GP<DjVuDocument> doc;
189  GPMap<int,DataPool> streams;
190  GMap<GUTF8String, int> names;
191  GPMap<int,ddjvu_thumbnail_p> thumbnails;
192  int streamid;
193  bool fileflag;
194  bool urlflag;
195  bool docinfoflag;
196  bool pageinfoflag;
197  minivar_t protect;
198  // virtual job functions:
199  virtual ddjvu_status_t status();
200  virtual void release();
201  // virtual port functions:
202  virtual bool inherits(const GUTF8String&);
203  virtual bool notify_error(const DjVuPort*, const GUTF8String&); 
204  virtual bool notify_status(const DjVuPort*, const GUTF8String&);
205  virtual void notify_doc_flags_changed(const DjVuDocument*, long, long);
206  virtual GP<DataPool> request_data(const DjVuPort*, const GURL&);
207  static void callback(void *);
208  bool want_pageinfo(void);
209};
210
211struct DJVUNS ddjvu_page_s : public ddjvu_job_s
212{
213  GP<DjVuImage> img;
214  ddjvu_job_t *job;
215  bool pageinfoflag;            // was the first m_pageinfo sent?
216  bool pagedoneflag;            // was the final m_pageinfo sent?
217  // virtual job functions:
218  virtual ddjvu_status_t status();
219  virtual void release();
220  // virtual port functions:
221  virtual bool inherits(const GUTF8String&);
222  virtual bool notify_error(const DjVuPort*, const GUTF8String&); 
223  virtual bool notify_status(const DjVuPort*, const GUTF8String&);
224  virtual void notify_file_flags_changed(const DjVuFile*, long, long);
225  virtual void notify_relayout(const class DjVuImage*);
226  virtual void notify_redisplay(const class DjVuImage*);
227  virtual void notify_chunk_done(const DjVuPort*, const GUTF8String &);
228  void sendmessages();
229};
230
231
232// ----------------------------------------
233// Helpers
234
235
236// Hack to increment counter
237static void 
238ref(GPEnabled *p)
239{
240  GPBase n(p);
241  char *gn = (char*)&n;
242  *(GPEnabled**)gn = 0;
243  n.assign(0);
244}
245
246// Hack to decrement counter
247static void 
248unref(GPEnabled *p)
249{
250  GPBase n;
251  char *gn = (char*)&n;
252  *(GPEnabled**)gn = p;
253  n.assign(0);
254}
255
256// Allocate strings
257static char *
258xstr(const char *s)
259{
260  int l = strlen(s);
261  char *p = (char*)malloc(l + 1);
262  if (p) 
263    {
264      strcpy(p, s);
265      p[l] = 0;
266    }
267  return p;
268}
269
270// Allocate strings
271static char *
272xstr(const GNativeString &n)
273{
274  return xstr( (const char*) n );
275}
276
277// Allocate strings
278static char *
279xstr(const GUTF8String &u)
280{
281  GNativeString n(u);
282  return xstr( n );
283}
284
285// Fill a message head
286static ddjvu_message_any_t
287xhead(ddjvu_message_tag_t tag,
288      ddjvu_context_t *context)
289{
290  ddjvu_message_any_t any;
291  any.tag = tag;
292  any.context = context;
293  any.document = 0;
294  any.page = 0;
295  any.job = 0;
296  return any;
297}
298static ddjvu_message_any_t
299xhead(ddjvu_message_tag_t tag,
300      ddjvu_job_t *job)
301{
302  ddjvu_message_any_t any;
303  any.tag = tag;
304  any.context = job->myctx;
305  any.document = job->mydoc;
306  any.page = 0;
307  any.job = job;
308  return any;
309}
310static ddjvu_message_any_t
311xhead(ddjvu_message_tag_t tag,
312      ddjvu_document_t *document)
313{
314  ddjvu_message_any_t any;
315  any.tag = tag;
316  any.context = document->myctx;
317  any.document = document;
318  any.page = 0;
319  any.job = document;
320  return any;
321}
322static ddjvu_message_any_t
323xhead(ddjvu_message_tag_t tag,
324      ddjvu_page_t *page)
325{
326  ddjvu_message_any_t any;
327  any.tag = tag;
328  any.context = page->myctx;
329  any.document = page->mydoc;
330  any.page = page;
331  any.job = page->job;
332  return any;
333}
334
335
336// ----------------------------------------
337// Context
338
339
340ddjvu_context_t *
341ddjvu_context_create(const char *programname)
342{
343  ddjvu_context_t *ctx = 0;
344  G_TRY
345    {
346      setlocale(LC_ALL,"");
347      if (programname)
348        djvu_programname(programname);
349      DjVuMessage::use_language();
350      DjVuMessageLite::create();
351      ctx = new ddjvu_context_s;
352      ref(ctx);
353      ctx->uniqueid = 0;
354      ctx->callbackfun = 0;
355      ctx->callbackarg = 0;
356      ctx->cache = DjVuFileCache::create();
357    }
358  G_CATCH_ALL
359    {
360      if (ctx)
361        unref(ctx);
362      ctx = 0;
363    }
364  G_ENDCATCH;
365  return ctx;
366}
367
368void 
369ddjvu_context_release(ddjvu_context_t *ctx)
370{
371  G_TRY
372    {
373      if (ctx)
374        unref(ctx);
375    }
376  G_CATCH_ALL
377    {
378    }
379  G_ENDCATCH;
380}
381
382
383// ----------------------------------------
384// Message helpers
385
386
387// post a new message
388static void
389msg_push(const ddjvu_message_any_t &head,
390         GP<ddjvu_message_p> msg = 0)
391{
392  ddjvu_context_t *ctx = head.context;
393  if (! msg) 
394    msg = new ddjvu_message_p;
395  msg->p.m_any = head; 
396  GMonitorLock lock(&ctx->monitor);
397  if ((head.document && head.document->released) ||
398      (head.page && head.page->released) ||
399      (head.job && head.job->released) )
400    return;
401  if (ctx->callbackfun) 
402    (*ctx->callbackfun)(ctx, ctx->callbackarg);
403  ctx->mlist.append(msg);
404  ctx->monitor.broadcast();
405}
406
407static void
408msg_push_nothrow(const ddjvu_message_any_t &head,
409                 GP<ddjvu_message_p> msg = 0)
410{
411  G_TRY
412    {
413      msg_push(head, msg);
414    }
415  G_CATCH_ALL
416    {
417    }
418  G_ENDCATCH;
419}
420
421// prepare error message from string
422static GP<ddjvu_message_p>
423msg_prep_error(GUTF8String message,
424               const char *function=0, 
425               const char *filename=0, 
426               int lineno=0)
427{
428  GP<ddjvu_message_p> p = new ddjvu_message_p;
429  p->p.m_error.message = 0;
430  p->p.m_error.function = function;
431  p->p.m_error.filename = filename;
432  p->p.m_error.lineno = lineno;
433  G_TRY
434    { 
435      p->tmp1 = DjVuMessageLite::LookUpUTF8(message);
436      p->p.m_error.message = (const char*)(p->tmp1);
437    }
438  G_CATCH_ALL
439    {
440    } 
441  G_ENDCATCH;
442  return p;
443}
444
445// prepare error message from exception
446static GP<ddjvu_message_p>
447msg_prep_error(const GException &ex,
448               const char *function=0, 
449               const char *filename=0, 
450               int lineno=0)
451{
452  GP<ddjvu_message_p> p = new ddjvu_message_p;
453  p->p.m_error.message = 0;
454  p->p.m_error.function = function;
455  p->p.m_error.filename = filename;
456  p->p.m_error.lineno = lineno;
457  G_TRY
458    { 
459      p->tmp1 = DjVuMessageLite::LookUpUTF8(ex.get_cause());
460      p->p.m_error.message = (const char*)(p->tmp1);
461      p->p.m_error.function = ex.get_function();
462      p->p.m_error.filename = ex.get_file();
463      p->p.m_error.lineno = ex.get_line();
464    }
465  G_CATCH_ALL
466    {
467    } 
468  G_ENDCATCH;
469  return p;
470}
471
472// prepare status message
473static GP<ddjvu_message_p>
474msg_prep_info(GUTF8String message)
475{
476  GP<ddjvu_message_p> p = new ddjvu_message_p;
477  p->tmp1 = DjVuMessageLite::LookUpUTF8(message); // i18n nonsense!
478  p->p.m_info.message = (const char*)(p->tmp1);
479  return p;
480}
481
482// ----------------------------------------
483
484
485#ifdef __GNUG__
486# define ERROR1(x, m) \
487    msg_push_nothrow(xhead(DDJVU_ERROR,x),\
488                     msg_prep_error(m,__func__,__FILE__,__LINE__))
489#else
490# define ERROR1(x, m) \
491    msg_push_nothrow(xhead(DDJVU_ERROR,x),\
492                     msg_prep_error(m,0,__FILE__,__LINE__))
493#endif
494
495
496// ----------------------------------------
497// Cache
498
499void
500ddjvu_cache_set_size(ddjvu_context_t *ctx,
501                     unsigned long cachesize)
502{
503  G_TRY
504    {
505      GMonitorLock lock(&ctx->monitor);
506      if (ctx->cache && cachesize>0)
507        ctx->cache->set_max_size(cachesize);
508    }
509  G_CATCH(ex) 
510    {
511      ERROR1(ctx, ex);
512    }
513  G_ENDCATCH;
514}
515
516DDJVUAPI unsigned long
517ddjvu_cache_get_size(ddjvu_context_t *ctx)
518{
519  G_TRY
520    {
521      GMonitorLock lock(&ctx->monitor);
522      if (ctx->cache)
523        return ctx->cache->get_max_size();
524    }
525  G_CATCH(ex) 
526    { 
527      ERROR1(ctx, ex);
528    }
529  G_ENDCATCH;
530  return 0;
531}
532
533void
534ddjvu_cache_clear(ddjvu_context_t *ctx)
535{
536  G_TRY
537    {
538      GMonitorLock lock(&ctx->monitor);
539      if (ctx->cache)
540      {
541        ctx->cache->clear();
542        return;
543      }
544    }
545  G_CATCH(ex)
546    {
547      ERROR1(ctx, ex);
548    }
549  G_ENDCATCH;
550 }
551
552
553// ----------------------------------------
554// Jobs
555
556ddjvu_job_s::ddjvu_job_s()
557  : userdata(0), released(false)
558{
559}
560
561bool
562ddjvu_job_s::inherits(const GUTF8String &classname)
563{
564  return (classname == "ddjvu_job_s") 
565    || DjVuPort::inherits(classname);
566}
567
568bool 
569ddjvu_job_s::notify_error(const DjVuPort *, const GUTF8String &m)
570{
571  msg_push(xhead(DDJVU_ERROR, this), msg_prep_error(m));
572  return true;
573}
574
575bool 
576ddjvu_job_s::notify_status(const DjVuPort *p, const GUTF8String &m)
577{
578  msg_push(xhead(DDJVU_INFO, this), msg_prep_info(m));
579  return true;
580}
581
582void
583ddjvu_job_release(ddjvu_job_t *job)
584{
585  G_TRY
586    {
587      if (!job)
588        return;
589      job->release();
590      job->userdata = 0;
591      job->released = true;
592      // clean all messages
593      ddjvu_context_t *ctx = job->myctx;
594      if (ctx)
595        {
596          GMonitorLock lock(&ctx->monitor);
597          GPosition p = ctx->mlist;
598          while (p) 
599            {
600              GPosition s = p; ++p;
601              if (ctx->mlist[s]->p.m_any.job == job ||
602                  ctx->mlist[s]->p.m_any.document == job ||
603                  ctx->mlist[s]->p.m_any.page == job )
604                ctx->mlist.del(s);
605            }
606          // cleanup pointers in current message as well.
607          if (ctx->mpeeked)
608            {
609              ddjvu_message_t *m = &ctx->mpeeked->p;
610              if (m->m_any.job == job)       
611                m->m_any.job = 0;
612              if (m->m_any.document == job)
613                m->m_any.document = 0;
614              if (m->m_any.page == job)
615                m->m_any.page = 0;
616            }
617        }
618      // decrement reference counter
619      unref(job);
620    }
621  G_CATCH_ALL
622    {
623    }
624  G_ENDCATCH;
625}
626
627ddjvu_status_t
628ddjvu_job_status(ddjvu_job_t *job)
629{
630  G_TRY
631    {
632      if (! job)
633        return DDJVU_JOB_NOTSTARTED;
634      return job->status();
635    }
636  G_CATCH(ex)
637    {
638      ERROR1(job, ex);
639    }
640  G_ENDCATCH;
641  return DDJVU_JOB_FAILED;
642}
643
644void
645ddjvu_job_stop(ddjvu_job_t *job)
646{
647  G_TRY
648    {
649      if (job)
650        job->stop();
651    }
652  G_CATCH(ex)
653    {
654      ERROR1(job, ex);
655    }
656  G_ENDCATCH;
657}
658
659void
660ddjvu_job_set_user_data(ddjvu_job_t *job, void *userdata)
661{
662  if (job)
663    job->userdata = userdata;
664}
665
666void *
667ddjvu_job_get_user_data(ddjvu_job_t *job)
668{
669  if (job)
670    return job->userdata;
671  return 0;
672}
673
674
675// ----------------------------------------
676// Message queue
677
678
679ddjvu_message_t *
680ddjvu_message_peek(ddjvu_context_t *ctx)
681{
682  G_TRY
683    {
684      GMonitorLock lock(&ctx->monitor);
685      if (ctx->mpeeked)
686        return &ctx->mpeeked->p;       
687      if (! ctx->mlist.size())
688        ctx->monitor.wait(0);
689      GPosition p = ctx->mlist;
690      if (! p)
691        return 0;
692      ctx->mpeeked = ctx->mlist[p];
693      ctx->mlist.del(p);
694      return &ctx->mpeeked->p;
695    }
696  G_CATCH_ALL
697    {
698    }
699  G_ENDCATCH;
700  return 0;
701}
702
703ddjvu_message_t *
704ddjvu_message_wait(ddjvu_context_t *ctx)
705{
706  G_TRY
707    {
708      GMonitorLock lock(&ctx->monitor);
709      if (ctx->mpeeked)
710        return &ctx->mpeeked->p;       
711      while (! ctx->mlist.size())
712        ctx->monitor.wait();
713      GPosition p = ctx->mlist;
714      if (! p)
715        return 0;
716      ctx->mpeeked = ctx->mlist[p];
717      ctx->mlist.del(p);
718      return &ctx->mpeeked->p;       
719    }
720  G_CATCH_ALL
721    {
722    }
723  G_ENDCATCH;
724  return 0;
725}
726
727void
728ddjvu_message_pop(ddjvu_context_t *ctx)
729{
730  G_TRY
731    {
732      GMonitorLock lock(&ctx->monitor);
733      ctx->mpeeked = 0;
734    }
735  G_CATCH_ALL
736    {
737    }
738  G_ENDCATCH;
739}
740
741void
742ddjvu_message_set_callback(ddjvu_context_t *ctx,
743                           ddjvu_message_callback_t callback,
744                           void *closure)
745{
746  GMonitorLock lock(&ctx->monitor);
747  ctx->callbackfun = callback;
748  ctx->callbackarg = closure;
749}
750
751
752// ----------------------------------------
753// Document callbacks
754
755
756void
757ddjvu_document_s::release()
758{
759  GPosition p;
760  GMonitorLock lock(&monitor);
761  doc = 0;
762  for (p=thumbnails; p; ++p)
763    {
764      ddjvu_thumbnail_p *thumb = thumbnails[p];
765      if (thumb->pool)
766        thumb->pool->del_trigger(ddjvu_thumbnail_p::callback, (void*)thumb);
767    }
768  for (p = streams; p; ++p)
769    {
770      GP<DataPool> pool = streams[p];
771      if (pool)
772        pool->del_trigger(callback, (void*)this);
773      if (pool && !pool->is_eof())
774        pool->stop();
775    }
776}
777
778ddjvu_status_t
779ddjvu_document_s::status()
780{
781  if (!doc)
782    return DDJVU_JOB_NOTSTARTED;
783  long flags = doc->get_doc_flags();
784  if (flags & DjVuDocument::DOC_INIT_OK)
785    return DDJVU_JOB_OK;
786  else if (flags & DjVuDocument::DOC_INIT_FAILED)
787    return DDJVU_JOB_FAILED;
788  return DDJVU_JOB_STARTED;
789}
790
791bool
792ddjvu_document_s::inherits(const GUTF8String &classname)
793{
794  return (classname == "ddjvu_document_s")
795    || ddjvu_job_s::inherits(classname);
796}
797
798bool 
799ddjvu_document_s::notify_error(const DjVuPort *, const GUTF8String &m)
800{
801  if (!doc) return false;
802  msg_push(xhead(DDJVU_ERROR, this), msg_prep_error(m));
803  return true;
804}
805 
806bool 
807ddjvu_document_s::notify_status(const DjVuPort *p, const GUTF8String &m)
808{
809  if (!doc) return false;
810  msg_push(xhead(DDJVU_INFO, this), msg_prep_info(m));
811  return true;
812}
813
814void 
815ddjvu_document_s::notify_doc_flags_changed(const DjVuDocument *, long, long)
816{
817  GMonitorLock lock(&monitor);
818  if (docinfoflag || !doc) return;
819  long flags = doc->get_doc_flags();
820  if ((flags & DjVuDocument::DOC_INIT_OK) ||
821      (flags & DjVuDocument::DOC_INIT_FAILED) )
822  {
823    msg_push(xhead(DDJVU_DOCINFO, this));
824    docinfoflag = true;
825  }
826}
827
828
829void 
830ddjvu_document_s::callback(void *arg)
831{
832  ddjvu_document_t *doc = (ddjvu_document_t *)arg;
833  if (doc && doc->pageinfoflag && !doc->fileflag) 
834    msg_push(xhead(DDJVU_PAGEINFO, doc));
835}
836
837
838GP<DataPool> 
839ddjvu_document_s::request_data(const DjVuPort *p, const GURL &url)
840{
841  // Note: the following line try to restore
842  //       the bytes stored in the djvu file
843  //       despite LT's i18n and gurl classes.
844  GUTF8String name = (const char*)url.fname(); 
845  GMonitorLock lock(&monitor);
846  GP<DataPool> pool;
847  if (names.contains(name))
848    {
849      int streamid = names[name];
850      return streams[streamid];
851    }
852  else if (fileflag)
853    {
854      if (doc && url.is_local_file_url())
855        return DataPool::create(url);
856    }
857  else if (doc)
858    {
859      // prepare pool
860      if (++streamid > 0)
861        streams[streamid] = pool = DataPool::create();
862      else
863        pool = streams[(streamid = 0)];
864      names[name] = streamid;
865      pool->add_trigger(-1, callback, (void*)this);
866      // build message
867      GP<ddjvu_message_p> p = new ddjvu_message_p;
868      p->p.m_newstream.streamid = streamid;
869      p->tmp1 = name;
870      p->p.m_newstream.name = (const char*)(p->tmp1);
871      p->p.m_newstream.url = 0;
872      if (urlflag)
873        {
874          // Should be urlencoded.
875          p->tmp2 = (const char*)url.get_string();
876          p->p.m_newstream.url = (const char*)(p->tmp2);
877        }
878      msg_push(xhead(DDJVU_NEWSTREAM, this), p);
879    }
880  return pool;
881}
882
883
884bool
885ddjvu_document_s::want_pageinfo()
886{
887  if (doc && docinfoflag && !pageinfoflag)
888    {
889      pageinfoflag = true;
890      int doctype = doc->get_doc_type();
891      if (doctype == DjVuDocument::BUNDLED ||
892          doctype == DjVuDocument::OLD_BUNDLED )
893        {
894          GP<DataPool> pool;
895          {
896            GMonitorLock lock(&monitor);
897            if (streams.contains(0))
898              pool = streams[0];
899          }
900          if (pool && doctype == DjVuDocument::BUNDLED)
901            {
902              GP<DjVmDir> dir = doc->get_djvm_dir();
903              if (dir)
904                for (int i=0; i<dir->get_files_num(); i++)
905                  {
906                    GP<DjVmDir::File> f = dir->pos_to_file(i);
907                    if (! pool->has_data(f->offset, f->size))
908                      pool->add_trigger(f->offset, f->size, callback, (void*)this );
909                  }
910            }
911          else if (pool && doctype == DjVuDocument::OLD_BUNDLED)
912            {
913              GP<DjVmDir0> dir = doc->get_djvm_dir0();
914              if (dir)
915                for (int i=0; i<dir->get_files_num(); i++)
916                  {
917                    GP<DjVmDir0::FileRec> f = dir->get_file(i);
918                    if (! pool->has_data(f->offset, f->size))
919                      pool->add_trigger(f->offset, f->size, callback, (void*)this );
920                  }
921            }
922        }
923    }
924  return pageinfoflag;
925}
926
927
928// ----------------------------------------
929// Documents
930
931
932ddjvu_document_t *
933ddjvu_document_create(ddjvu_context_t *ctx,
934                      const char *url,
935                      int cache)
936{
937  ddjvu_document_t *d = 0;
938  G_TRY
939    {
940      DjVuFileCache *xcache = ctx->cache;
941      if (! cache) xcache = 0;
942      d = new ddjvu_document_s;
943      ref(d);
944      GMonitorLock lock(&d->monitor);
945      d->streams[0] = DataPool::create();
946      d->streamid = -1;
947      d->fileflag = false;
948      d->docinfoflag = false;
949      d->pageinfoflag = false;
950      d->myctx = ctx;
951      d->mydoc = 0;
952      d->doc = DjVuDocument::create_noinit();
953      if (url)
954        {
955          GURL gurl = GUTF8String(url);
956          gurl.clear_djvu_cgi_arguments();
957          d->urlflag = true;
958          d->doc->start_init(gurl, d, xcache);
959        }
960      else
961        {
962          GUTF8String s;
963          s.format("ddjvu:///doc%d/index.djvu", ++(ctx->uniqueid));;
964          GURL gurl = s;
965          d->urlflag = false;
966          d->doc->start_init(gurl, d, xcache);
967        }
968    }
969  G_CATCH(ex)
970    {
971      if (d) 
972        unref(d);
973      d = 0;
974      ERROR1(ctx, ex);
975    }
976  G_ENDCATCH;
977  return d;
978}
979
980ddjvu_document_t *
981ddjvu_document_create_by_filename(ddjvu_context_t *ctx,
982                                  const char *filename,
983                                  int cache)
984{
985  ddjvu_document_t *d = 0;
986  G_TRY
987    {
988      DjVuFileCache *xcache = ctx->cache;
989      if (! cache) xcache = 0;
990      GURL gurl = GURL::Filename::UTF8(filename);
991      d = new ddjvu_document_s;
992      ref(d);
993      GMonitorLock lock(&d->monitor);
994      d->streamid = -1;
995      d->fileflag = true;
996      d->pageinfoflag = false;
997      d->urlflag = false;
998      d->docinfoflag = false;
999      d->myctx = ctx;
1000      d->mydoc = 0;
1001      d->doc = DjVuDocument::create_noinit();
1002      d->doc->start_init(gurl, d, xcache);
1003    }
1004  G_CATCH(ex)
1005    {
1006      if (d)
1007        unref(d);
1008      d = 0;
1009      ERROR1(ctx, ex);
1010    }
1011  G_ENDCATCH;
1012  return d;
1013}
1014
1015ddjvu_job_t *
1016ddjvu_document_job(ddjvu_document_t *document)
1017{
1018  return document;
1019}
1020
1021
1022// ----------------------------------------
1023// Streams
1024
1025
1026void
1027ddjvu_stream_write(ddjvu_document_t *doc,
1028                   int streamid,
1029                   const char *data,
1030                   unsigned long datalen )
1031{
1032  G_TRY
1033    {
1034      GP<DataPool> pool;
1035      { 
1036        GMonitorLock lock(&doc->monitor); 
1037        GPosition p = doc->streams.contains(streamid);
1038        if (p) pool = doc->streams[p];
1039      }
1040      if (! pool)
1041        G_THROW("Unknown stream ID");
1042      if (datalen > 0)
1043        pool->add_data(data, datalen);
1044    }
1045  G_CATCH(ex)
1046    {
1047      ERROR1(doc,ex);
1048    }
1049  G_ENDCATCH;
1050}
1051
1052void
1053ddjvu_stream_close(ddjvu_document_t *doc,
1054                   int streamid,
1055                   int stop )
1056{
1057  G_TRY
1058    {
1059      GP<DataPool> pool;
1060      { 
1061        GMonitorLock lock(&doc->monitor); 
1062        GPosition p = doc->streams.contains(streamid);
1063        if (p) pool = doc->streams[p];
1064      }
1065      if (! pool)
1066        G_THROW("Unknown stream ID");
1067      if (stop)
1068        pool->stop(true);
1069      pool->set_eof();
1070    }
1071  G_CATCH(ex)
1072    {
1073      ERROR1(doc, ex);
1074    }
1075  G_ENDCATCH;
1076}
1077
1078
1079// ----------------------------------------
1080// Document queries
1081
1082
1083ddjvu_document_type_t
1084ddjvu_document_get_type(ddjvu_document_t *document)
1085{
1086  G_TRY
1087    {
1088      DjVuDocument *doc = document->doc;
1089      if (doc)
1090        {
1091          switch (doc->get_doc_type())
1092            {
1093            case DjVuDocument::OLD_BUNDLED:
1094              return DDJVU_DOCTYPE_OLD_BUNDLED;
1095            case DjVuDocument::OLD_INDEXED:
1096              return DDJVU_DOCTYPE_OLD_INDEXED;
1097            case DjVuDocument::BUNDLED:
1098              return DDJVU_DOCTYPE_BUNDLED;
1099            case DjVuDocument::INDIRECT:
1100              return DDJVU_DOCTYPE_INDIRECT;
1101            case DjVuDocument::SINGLE_PAGE:
1102              return DDJVU_DOCTYPE_SINGLEPAGE;
1103            default:
1104              break;
1105            }
1106        }
1107    }
1108  G_CATCH(ex)
1109    {
1110      ERROR1(document,ex);
1111    }
1112  G_ENDCATCH;
1113  return DDJVU_DOCTYPE_UNKNOWN;
1114}
1115
1116
1117int
1118ddjvu_document_get_pagenum(ddjvu_document_t *document)
1119{
1120  G_TRY
1121    {
1122      DjVuDocument *doc = document->doc;
1123      if (doc)
1124        return doc->get_pages_num();
1125    }
1126  G_CATCH(ex)
1127    {
1128      ERROR1(document,ex);
1129    }
1130  G_ENDCATCH;
1131  return 1;
1132}
1133
1134
1135int
1136ddjvu_document_get_filenum(ddjvu_document_t *document)
1137{
1138  G_TRY
1139    {
1140      DjVuDocument *doc = document->doc;
1141      if (! (doc && doc->is_init_ok()))
1142        return 0;
1143      int doc_type = doc->get_doc_type();
1144      if (doc_type == DjVuDocument::BUNDLED ||
1145          doc_type == DjVuDocument::INDIRECT )
1146        {
1147          GP<DjVmDir> dir = doc->get_djvm_dir();
1148          return dir->get_files_num();
1149        }
1150      else if (doc_type == DjVuDocument::OLD_BUNDLED)
1151        {
1152          GP<DjVmDir0> dir0 = doc->get_djvm_dir0();
1153          return dir0->get_files_num();
1154        }
1155      else 
1156        return doc->get_pages_num();
1157    }
1158  G_CATCH(ex)
1159    {
1160      ERROR1(document,ex);
1161    }
1162  G_ENDCATCH;
1163  return 0;
1164}
1165
1166
1167#undef ddjvu_document_get_fileinfo
1168
1169extern "C" DDJVUAPI ddjvu_status_t
1170ddjvu_document_get_fileinfo(ddjvu_document_t *d, int f, ddjvu_fileinfo_t *i);
1171
1172ddjvu_status_t
1173ddjvu_document_get_fileinfo(ddjvu_document_t *d, int f, ddjvu_fileinfo_t *i)
1174{
1175  // for binary backward compatibility with ddjvuapi=17
1176  struct info17_s { char t; int p,s; const char *d, *n, *l; };
1177  return ddjvu_document_get_fileinfo_imp(d,f,i,sizeof(info17_s));
1178}
1179
1180ddjvu_status_t
1181ddjvu_document_get_fileinfo_imp(ddjvu_document_t *document, int fileno, 
1182                                ddjvu_fileinfo_t *info, 
1183                                unsigned int infosz )
1184{
1185  G_TRY
1186    {
1187      ddjvu_fileinfo_t myinfo;
1188      memset(info, 0, infosz);
1189      if (infosz > sizeof(myinfo))
1190        return DDJVU_JOB_FAILED;
1191      DjVuDocument *doc = document->doc;
1192      if (! doc)
1193        return DDJVU_JOB_NOTSTARTED;
1194      if (! doc->is_init_ok())
1195        return document->status();
1196      int type = doc->get_doc_type();
1197      if ( type == DjVuDocument::BUNDLED ||
1198           type == DjVuDocument::INDIRECT )
1199        {
1200          GP<DjVmDir> dir = doc->get_djvm_dir();
1201          GP<DjVmDir::File> file = dir->pos_to_file(fileno, &myinfo.pageno);
1202          if (! file)
1203            G_THROW("Illegal file number");
1204          myinfo.type = 'I';
1205          if (file->is_page())
1206            myinfo.type = 'P';
1207          else
1208            myinfo.pageno = -1;
1209          if (file->is_thumbnails())
1210            myinfo.type = 'T';
1211          if (file->is_shared_anno())
1212            myinfo.type = 'S';
1213          myinfo.size = file->size;
1214          myinfo.id = file->get_load_name();
1215          myinfo.name = file->get_save_name();
1216          myinfo.title = file->get_title();
1217          memcpy(info, &myinfo, infosz);
1218          return DDJVU_JOB_OK;
1219        }
1220      else if (type == DjVuDocument::OLD_BUNDLED)
1221        {
1222          GP<DjVmDir0> dir0 = doc->get_djvm_dir0();
1223          GP<DjVuNavDir> nav = doc->get_nav_dir();
1224          GP<DjVmDir0::FileRec> frec = dir0->get_file(fileno);
1225          if (! frec)
1226            G_THROW("Illegal file number");
1227          myinfo.size = frec->size;
1228          myinfo.id = (const char*) frec->name;
1229          myinfo.name = myinfo.title = myinfo.id;
1230          if (! nav)
1231            return DDJVU_JOB_STARTED;
1232          else if (nav->name_to_page(frec->name) >= 0)
1233            myinfo.type = 'P';
1234          else
1235            myinfo.type = 'I';
1236          memcpy(info, &myinfo, infosz);
1237          return DDJVU_JOB_OK;
1238        }
1239      else 
1240        {
1241          if (fileno<0 || fileno>=doc->get_pages_num())
1242            G_THROW("Illegal file number");
1243          myinfo.type = 'P';
1244          myinfo.pageno = fileno;
1245          myinfo.size = -1;
1246          GP<DjVuNavDir> nav = doc->get_nav_dir();
1247          myinfo.id = (nav) ? (const char *) nav->page_to_name(fileno) : 0;
1248          myinfo.name = myinfo.title = myinfo.id;
1249          GP<DjVuFile> file = doc->get_djvu_file(fileno, true);
1250          GP<DataPool> pool; 
1251          if (file) 
1252            pool = file->get_init_data_pool();
1253          if (pool)
1254            myinfo.size = pool->get_length();
1255          memcpy(info, &myinfo, infosz);
1256          return DDJVU_JOB_OK;
1257        }
1258    }
1259  G_CATCH(ex)
1260    {
1261      ERROR1(document,ex);
1262    }
1263  G_ENDCATCH;
1264  return DDJVU_JOB_FAILED;
1265}
1266
1267
1268int
1269ddjvu_document_search_pageno(ddjvu_document_t *document, const char *name)
1270{
1271  G_TRY
1272    {
1273      DjVuDocument *doc = document->doc;
1274      if (! (doc && doc->is_init_ok()))
1275        return -1;
1276      GP<DjVmDir> dir = doc->get_djvm_dir();
1277      if (! dir)
1278        return 0;
1279      GP<DjVmDir::File> file;
1280      if (! (file = dir->id_to_file(GUTF8String(name))))
1281        if (! (file = dir->name_to_file(GUTF8String(name))))
1282          if (! (file = dir->title_to_file(GUTF8String(name))))
1283            {
1284              char *edata=0;
1285              long int p = strtol(name, &edata, 10);
1286              if (edata!=name && !*edata && p>=1)
1287                file = dir->page_to_file(p-1);
1288            }
1289      if (file)
1290        {
1291          int pageno = -1;
1292          int fileno = dir->get_file_pos(file);
1293          if (dir->pos_to_file(fileno, &pageno))
1294            return pageno;
1295        }
1296    }
1297  G_CATCH(ex)
1298    {
1299      ERROR1(document,ex);
1300    }
1301  G_ENDCATCH;
1302  return -1;
1303}
1304
1305
1306
1307int 
1308ddjvu_document_check_pagedata(ddjvu_document_t *document, int pageno)
1309{
1310  G_TRY
1311    {
1312      document->want_pageinfo();
1313      DjVuDocument *doc = document->doc;
1314      if (doc && doc->is_init_ok())
1315        {
1316          bool dontcreate = false;
1317          if (doc->get_doc_type() == DjVuDocument::INDIRECT ||
1318              doc->get_doc_type() == DjVuDocument::OLD_INDEXED )
1319            {
1320              dontcreate = true;
1321              GURL url = doc->page_to_url(pageno);
1322              if (! url.is_empty())
1323                {
1324                  GUTF8String name = (const char*)url.fname();
1325                  GMonitorLock lock(&document->monitor);
1326                  if (document->names.contains(name))
1327                    dontcreate = false;
1328                }
1329            }
1330          GP<DjVuFile> file = doc->get_djvu_file(pageno, dontcreate);
1331          if (file && file->is_data_present())
1332            return 1;
1333        }
1334    }
1335  G_CATCH(ex)
1336    {
1337      ERROR1(document,ex);
1338    }
1339  G_ENDCATCH;
1340  return 0;
1341}
1342
1343
1344#undef ddjvu_document_get_pageinfo
1345
1346extern "C" DDJVUAPI ddjvu_status_t
1347ddjvu_document_get_pageinfo(ddjvu_document_t *d, int p, ddjvu_pageinfo_t *i);
1348
1349ddjvu_status_t
1350ddjvu_document_get_pageinfo(ddjvu_document_t *d, int p, ddjvu_pageinfo_t *i)
1351{
1352  // for binary backward compatibility with ddjvuapi<=17
1353  struct info17_s { int w; int h; int d; };
1354  return ddjvu_document_get_pageinfo_imp(d,p,i,sizeof(struct info17_s));
1355}
1356
1357ddjvu_status_t
1358ddjvu_document_get_pageinfo_imp(ddjvu_document_t *document, int pageno, 
1359                                ddjvu_pageinfo_t *pageinfo, 
1360                                unsigned int infosz)
1361{
1362  G_TRY
1363    {
1364      ddjvu_pageinfo_t myinfo;
1365      memset(pageinfo, 0, infosz);
1366      if (infosz > sizeof(myinfo))
1367        return DDJVU_JOB_FAILED;
1368      DjVuDocument *doc = document->doc;
1369      if (doc)
1370        {
1371          document->want_pageinfo();
1372          GP<DjVuFile> file = doc->get_djvu_file(pageno);
1373          if (! file || ! file->is_data_present() )
1374            return DDJVU_JOB_STARTED;
1375          const GP<ByteStream> pbs(file->get_djvu_bytestream(false, false));
1376          const GP<IFFByteStream> iff(IFFByteStream::create(pbs));
1377          GUTF8String chkid;
1378          if (iff->get_chunk(chkid))
1379            {
1380              if (chkid == "FORM:DJVU")
1381                {
1382                  while (iff->get_chunk(chkid) && chkid!="INFO")
1383                    iff->close_chunk();
1384                  if (chkid == "INFO")
1385                    {
1386                      GP<ByteStream> gbs = iff->get_bytestream();
1387                      GP<DjVuInfo> info=DjVuInfo::create();
1388                      info->decode(*gbs);
1389                      int rot = info->orientation;
1390                      myinfo.rotation = rot;
1391                      myinfo.width = (rot&1) ? info->height : info->width;
1392                      myinfo.height = (rot&1) ? info->width : info->height;
1393                      myinfo.dpi = info->dpi;
1394                      myinfo.version = info->version;
1395                      memcpy(pageinfo, &myinfo, infosz);
1396                      return DDJVU_JOB_OK;
1397                    }
1398                }
1399              else if (chkid == "FORM:BM44" || chkid == "FORM:PM44")
1400                {
1401                  while (iff->get_chunk(chkid) && 
1402                         chkid!="BM44" && chkid!="PM44")
1403                    iff->close_chunk();
1404                  if (chkid=="BM44" || chkid=="PM44")
1405                    {
1406                      GP<ByteStream> gbs = iff->get_bytestream();
1407                      if (gbs->read8() == 0)
1408                        {
1409                          gbs->read8();
1410                          unsigned char vhi = gbs->read8();
1411                          unsigned char vlo = gbs->read8();
1412                          unsigned char xhi = gbs->read8();
1413                          unsigned char xlo = gbs->read8();
1414                          unsigned char yhi = gbs->read8();
1415                          unsigned char ylo = gbs->read8();
1416                          myinfo.width = (xhi<<8)+xlo;
1417                          myinfo.height = (yhi<<8)+ylo;
1418                          myinfo.dpi = 100;
1419                          myinfo.rotation = 0;
1420                          myinfo.version = (vhi<<8)+vlo;
1421                          memcpy(pageinfo, &myinfo, infosz);
1422                        }
1423                    }
1424                }
1425            }
1426        }
1427    }
1428  G_CATCH(ex)
1429    {
1430      ERROR1(document, ex);
1431    }
1432  G_ENDCATCH;
1433  return DDJVU_JOB_FAILED;
1434}
1435
1436
1437static char *
1438get_file_dump(DjVuFile *file)
1439{
1440  DjVuDumpHelper dumper;
1441  GP<DataPool> pool = file->get_init_data_pool();
1442  GP<ByteStream> str = dumper.dump(pool);
1443  int size = str->size();
1444  char *buffer;
1445  if ((size = str->size()) > 0 && (buffer = (char*)malloc(size+1)))
1446    {
1447      str->seek(0);
1448      int len = str->readall(buffer, size);
1449      buffer[len] = 0;
1450      return buffer;
1451    }
1452  return 0;
1453}
1454
1455
1456char *
1457ddjvu_document_get_pagedump(ddjvu_document_t *document, int pageno)
1458{
1459  G_TRY
1460    {
1461      DjVuDocument *doc = document->doc;
1462      if (doc)
1463        {
1464          document->want_pageinfo();
1465          GP<DjVuFile> file = doc->get_djvu_file(pageno);
1466          if (file && file->is_data_present())
1467            return get_file_dump(file);
1468        }
1469    }
1470  G_CATCH(ex)
1471    {
1472      ERROR1(document, ex);
1473    }
1474  G_ENDCATCH;
1475  return 0;
1476}
1477
1478
1479char *
1480ddjvu_document_get_filedump(ddjvu_document_t *document, int fileno)
1481{
1482  G_TRY
1483    {
1484      DjVuDocument *doc = document->doc;
1485      document->want_pageinfo();
1486      if (doc)
1487        {
1488          GP<DjVuFile> file;
1489          int type = doc->get_doc_type();
1490          if ( type != DjVuDocument::BUNDLED &&
1491               type != DjVuDocument::INDIRECT )
1492            file = doc->get_djvu_file(fileno);
1493          else
1494            {
1495              GP<DjVmDir> dir = doc->get_djvm_dir();
1496              GP<DjVmDir::File> fdesc = dir->pos_to_file(fileno);
1497              if (fdesc)
1498                file = doc->get_djvu_file(fdesc->get_load_name());
1499            }
1500          if (file && file->is_data_present())
1501            return get_file_dump(file);
1502        }
1503    }
1504  G_CATCH(ex)
1505    {
1506      ERROR1(document, ex);
1507    }
1508  G_ENDCATCH;
1509  return 0;
1510}
1511
1512
1513
1514// ----------------------------------------
1515// Page
1516
1517static ddjvu_page_t *
1518ddjvu_page_create(ddjvu_document_t *document, ddjvu_job_t *job,
1519                  const char *pageid, int pageno)
1520{
1521  ddjvu_page_t *p = 0;
1522  G_TRY
1523    {
1524      DjVuDocument *doc = document->doc;
1525      if (! doc) return 0;
1526      p = new ddjvu_page_s;
1527      ref(p);
1528      GMonitorLock lock(&p->monitor);
1529      p->myctx = document->myctx;
1530      p->mydoc = document;
1531      p->pageinfoflag = false;
1532      p->pagedoneflag = false;
1533      if (! job)
1534        job = p;
1535      p->job = job;
1536      if (pageid)
1537        p->img = doc->get_page(GNativeString(pageid), false, job);
1538      else
1539        p->img = doc->get_page(pageno, false, job);
1540    }
1541  G_CATCH(ex)
1542    {
1543      if (p)
1544        unref(p);
1545      p = 0;
1546      ERROR1(document, ex);
1547    }
1548  G_ENDCATCH;
1549  return p;
1550}
1551
1552ddjvu_page_t *
1553ddjvu_page_create_by_pageno(ddjvu_document_t *document, int pageno)
1554{
1555  return ddjvu_page_create(document, 0, 0, pageno);
1556}
1557
1558ddjvu_page_t *
1559ddjvu_page_create_by_pageid(ddjvu_document_t *document, const char *pageid)
1560{
1561  return ddjvu_page_create(document, 0, pageid, 0);
1562}
1563
1564ddjvu_job_t *
1565ddjvu_page_job(ddjvu_page_t *page)
1566{
1567  return page;
1568}
1569
1570
1571// ----------------------------------------
1572// Page callbacks
1573
1574void
1575ddjvu_page_s::release()
1576{
1577  img = 0;
1578}
1579
1580ddjvu_status_t
1581ddjvu_page_s::status()
1582{
1583  if (! img)
1584    return DDJVU_JOB_NOTSTARTED;       
1585  DjVuFile *file = img->get_djvu_file();
1586  if (! file)
1587    return DDJVU_JOB_NOTSTARTED;
1588  else if (file->is_decode_stopped())
1589    return DDJVU_JOB_STOPPED;
1590  else if (file->is_decode_failed())
1591    return DDJVU_JOB_FAILED;
1592  else if (file->is_decode_ok())
1593    return DDJVU_JOB_OK;
1594  else if (file->is_decoding())
1595    return DDJVU_JOB_STARTED;
1596  return DDJVU_JOB_NOTSTARTED;
1597}
1598
1599bool
1600ddjvu_page_s::inherits(const GUTF8String &classname)
1601{
1602  return (classname == "ddjvu_page_s")
1603    || ddjvu_job_s::inherits(classname);
1604}
1605
1606bool 
1607ddjvu_page_s::notify_error(const DjVuPort *, const GUTF8String &m)
1608{
1609  if (!img) return false;
1610  msg_push(xhead(DDJVU_ERROR, this), msg_prep_error(m));
1611  return true;
1612}
1613 
1614bool 
1615ddjvu_page_s::notify_status(const DjVuPort *p, const GUTF8String &m)
1616{
1617  if (!img) return false;
1618  msg_push(xhead(DDJVU_INFO, this), msg_prep_info(m));
1619  return true;
1620}
1621
1622void 
1623ddjvu_page_s::notify_file_flags_changed(const DjVuFile *sender, long, long)
1624{
1625  GMonitorLock lock(&monitor);
1626  if (!img) return;
1627  DjVuFile *file = img->get_djvu_file();
1628  if (file==0 || file!=sender) return;
1629  long flags = file->get_flags();
1630  if ((flags & DjVuFile::DECODE_OK) ||
1631      (flags & DjVuFile::DECODE_FAILED) ||
1632      (flags & DjVuFile::DECODE_STOPPED) )
1633    {
1634      if (pagedoneflag) return;
1635      msg_push(xhead(DDJVU_PAGEINFO, this));
1636      pageinfoflag = pagedoneflag = true;
1637    }
1638}
1639
1640void 
1641ddjvu_page_s::notify_relayout(const DjVuImage *dimg)
1642{
1643  GMonitorLock lock(&monitor);
1644  if (img && !pageinfoflag)
1645    {
1646      msg_push(xhead(DDJVU_PAGEINFO, this));
1647      msg_push(xhead(DDJVU_RELAYOUT, this));
1648      pageinfoflag = true;
1649    }
1650}
1651
1652void 
1653ddjvu_page_s::notify_redisplay(const DjVuImage *dimg)
1654{
1655  GMonitorLock lock(&monitor);
1656  if (img && !pageinfoflag)
1657    {
1658      msg_push(xhead(DDJVU_PAGEINFO, this));
1659      msg_push(xhead(DDJVU_RELAYOUT, this));
1660      pageinfoflag = true;
1661    }
1662  if (img && pageinfoflag)
1663    msg_push(xhead(DDJVU_REDISPLAY, this));
1664}
1665
1666void 
1667ddjvu_page_s::notify_chunk_done(const DjVuPort*, const GUTF8String &name)
1668{
1669  GMonitorLock lock(&monitor);
1670  if (! img) return;
1671  GP<ddjvu_message_p> p = new ddjvu_message_p;
1672  p->tmp1 = name;
1673  p->p.m_chunk.chunkid = (const char*)(p->tmp1);
1674  msg_push(xhead(DDJVU_CHUNK,this), p);
1675}
1676
1677
1678// ----------------------------------------
1679// Page queries
1680
1681int
1682ddjvu_page_get_width(ddjvu_page_t *page)
1683{
1684  G_TRY
1685    {
1686      if (page && page->img)
1687        return page->img->get_width();
1688    }
1689  G_CATCH(ex)
1690    {
1691      ERROR1(page, ex);
1692    }
1693  G_ENDCATCH;
1694  return 0;
1695}
1696
1697int
1698ddjvu_page_get_height(ddjvu_page_t *page)
1699{
1700  G_TRY
1701    {
1702      if (page && page->img)
1703        return page->img->get_height();
1704    }
1705  G_CATCH(ex)
1706    {
1707      ERROR1(page, ex);
1708    }
1709  G_ENDCATCH;
1710  return 0;
1711}
1712
1713int
1714ddjvu_page_get_resolution(ddjvu_page_t *page)
1715{
1716  G_TRY
1717    {
1718      if (page && page->img)
1719        return page->img->get_dpi();
1720    }
1721  G_CATCH(ex)
1722    {
1723      ERROR1(page, ex);
1724    }
1725  G_ENDCATCH;
1726  return 0;
1727}
1728
1729double
1730ddjvu_page_get_gamma(ddjvu_page_t *page)
1731{
1732  G_TRY
1733    {
1734      if (page && page->img)
1735        return page->img->get_gamma();
1736    }
1737  G_CATCH(ex)
1738    {
1739      ERROR1(page, ex);
1740    }
1741  G_ENDCATCH;
1742  return 2.2;
1743}
1744
1745int
1746ddjvu_page_get_version(ddjvu_page_t *page)
1747{
1748  G_TRY
1749    {
1750      if (page && page->img)
1751        return page->img->get_version();
1752    }
1753  G_CATCH(ex)
1754    {
1755      ERROR1(page, ex);
1756    }
1757  G_ENDCATCH;
1758  return DJVUVERSION;
1759}
1760
1761int
1762ddjvu_code_get_version(void)
1763{
1764  return DJVUVERSION;
1765}
1766
1767ddjvu_page_type_t
1768ddjvu_page_get_type(ddjvu_page_t *page)
1769{
1770  G_TRY
1771    {
1772      if (! (page && page->img))
1773        return DDJVU_PAGETYPE_UNKNOWN;
1774      else if (page->img->is_legal_bilevel())
1775        return DDJVU_PAGETYPE_BITONAL;
1776      else if (page->img->is_legal_photo())
1777        return DDJVU_PAGETYPE_PHOTO;
1778      else if (page->img->is_legal_compound())
1779        return DDJVU_PAGETYPE_COMPOUND;
1780    }
1781  G_CATCH(ex)
1782    {
1783      ERROR1(page, ex);
1784    }
1785  G_ENDCATCH;
1786  return DDJVU_PAGETYPE_UNKNOWN;
1787}
1788
1789char *
1790ddjvu_page_get_short_description(ddjvu_page_t *page)
1791{
1792  G_TRY
1793    {
1794      if (page && page->img)
1795        {
1796          const char *desc = page->img->get_short_description();
1797          return xstr(DjVuMessageLite::LookUpUTF8(desc));
1798        }
1799    }
1800  G_CATCH(ex)
1801    {
1802      ERROR1(page, ex);
1803    }
1804  G_ENDCATCH;
1805  return 0;
1806}
1807
1808char *
1809ddjvu_page_get_long_description(ddjvu_page_t *page)
1810{
1811  G_TRY
1812    {
1813      if (page && page->img)
1814        {
1815          const char *desc = page->img->get_long_description();
1816          return xstr(DjVuMessageLite::LookUpUTF8(desc));
1817        }
1818    }
1819  G_CATCH(ex)
1820    {
1821      ERROR1(page, ex);
1822    }
1823  G_ENDCATCH;
1824  return 0;
1825}
1826
1827
1828// ----------------------------------------
1829// Rotations
1830
1831void
1832ddjvu_page_set_rotation(ddjvu_page_t *page,
1833                        ddjvu_page_rotation_t rot)
1834{
1835  G_TRY
1836    {
1837      switch(rot)
1838        {
1839        case DDJVU_ROTATE_0:
1840        case DDJVU_ROTATE_90:
1841        case DDJVU_ROTATE_180:
1842        case DDJVU_ROTATE_270:
1843          if (page && page->img && page->img->get_info())
1844            page->img->set_rotate((int)rot);
1845          break;
1846        default:
1847          G_THROW("Illegal ddjvu rotation code");
1848          break;
1849        }
1850    }
1851  G_CATCH(ex)
1852    {
1853      ERROR1(page, ex);
1854    }
1855  G_ENDCATCH;
1856}
1857
1858ddjvu_page_rotation_t
1859ddjvu_page_get_rotation(ddjvu_page_t *page)
1860{
1861  ddjvu_page_rotation_t rot = DDJVU_ROTATE_0;
1862  G_TRY
1863    {
1864      if (page && page->img)
1865        rot = (ddjvu_page_rotation_t)(page->img->get_rotate() & 3);
1866    }
1867  G_CATCH(ex)
1868    {
1869      ERROR1(page, ex);
1870    }
1871  G_ENDCATCH;
1872  return rot;
1873}
1874
1875ddjvu_page_rotation_t
1876ddjvu_page_get_initial_rotation(ddjvu_page_t *page)
1877{
1878  ddjvu_page_rotation_t rot = DDJVU_ROTATE_0;
1879  G_TRY
1880    {
1881      GP<DjVuInfo> info;
1882      if (page && page->img)
1883        info = page->img->get_info();
1884      if (info)
1885        rot = (ddjvu_page_rotation_t)(info->orientation & 3);
1886    }
1887  G_CATCH(ex)
1888    {
1889      ERROR1(page, ex);
1890    }
1891  G_ENDCATCH;
1892  return rot;
1893}
1894
1895
1896// ----------------------------------------
1897// Rectangles
1898
1899static void
1900rect2grect(const ddjvu_rect_t *r, GRect &g)
1901{
1902  g.xmin = r->x;
1903  g.ymin = r->y;
1904  g.xmax = r->x + r->w;
1905  g.ymax = r->y + r->h;
1906}
1907
1908static void
1909grect2rect(const GRect &g, ddjvu_rect_t *r)
1910{
1911  if (g.isempty())
1912    {
1913      r->x = r->y = 0;
1914      r->w = r->h = 0;
1915    }
1916  else
1917    {
1918      r->x = g.xmin;
1919      r->y = g.ymin;
1920      r->w = g.width();
1921      r->h = g.height();
1922    }
1923}
1924
1925ddjvu_rectmapper_t *
1926ddjvu_rectmapper_create(ddjvu_rect_t *input, ddjvu_rect_t *output)
1927{
1928  GRect ginput, goutput;
1929  rect2grect(input, ginput);
1930  rect2grect(output, goutput);
1931  GRectMapper *mapper = new GRectMapper;
1932  if (!ginput.isempty())
1933    mapper->set_input(ginput);
1934  if (!goutput.isempty())
1935    mapper->set_output(goutput);
1936  return (ddjvu_rectmapper_t*)mapper;
1937}
1938
1939void
1940ddjvu_rectmapper_modify(ddjvu_rectmapper_t *mapper,
1941                        int rotation, int mirrorx, int mirrory)
1942{
1943  GRectMapper *gmapper = (GRectMapper*)mapper;
1944  if (! gmapper) return;
1945  gmapper->rotate(rotation);
1946  if (mirrorx & 1)
1947    gmapper->mirrorx();
1948  if (mirrory & 1)
1949    gmapper->mirrory();
1950}
1951
1952void 
1953ddjvu_rectmapper_release(ddjvu_rectmapper_t *mapper)
1954{
1955  GRectMapper *gmapper = (GRectMapper*)mapper;
1956  if (! gmapper) return;
1957  delete gmapper;
1958}
1959
1960void 
1961ddjvu_map_point(ddjvu_rectmapper_t *mapper, int *x, int *y)
1962{
1963  GRectMapper *gmapper = (GRectMapper*)mapper;
1964  if (! gmapper) return;
1965  gmapper->map(*x,*y);
1966}
1967
1968void 
1969ddjvu_map_rect(ddjvu_rectmapper_t *mapper, ddjvu_rect_t *rect)
1970{
1971  GRectMapper *gmapper = (GRectMapper*)mapper;
1972  if (! gmapper) return;
1973  GRect grect;
1974  rect2grect(rect,grect);
1975  gmapper->map(grect);
1976  grect2rect(grect,rect);
1977}
1978
1979void 
1980ddjvu_unmap_point(ddjvu_rectmapper_t *mapper, int *x, int *y)
1981{
1982  GRectMapper *gmapper = (GRectMapper*)mapper;
1983  if (! gmapper) return;
1984  gmapper->unmap(*x,*y);
1985}
1986
1987void 
1988ddjvu_unmap_rect(ddjvu_rectmapper_t *mapper, ddjvu_rect_t *rect)
1989{
1990  GRectMapper *gmapper = (GRectMapper*)mapper;
1991  if (! gmapper) return;
1992  GRect grect;
1993  rect2grect(rect,grect);
1994  gmapper->unmap(grect);
1995  grect2rect(grect,rect);
1996}
1997
1998
1999// ----------------------------------------
2000// Render
2001
2002struct DJVUNS ddjvu_format_s
2003{
2004  ddjvu_format_style_t style;
2005  uint32_t rgb[3][256];
2006  uint32_t palette[6*6*6];
2007  uint32_t xorval;
2008  double gamma;
2009  char ditherbits;
2010  bool rtoptobottom;
2011  bool ytoptobottom;
2012};
2013
2014static ddjvu_format_t *
2015fmt_error(ddjvu_format_t *fmt)
2016{
2017  delete fmt;
2018  return 0;
2019}
2020
2021ddjvu_format_t *
2022ddjvu_format_create(ddjvu_format_style_t style,
2023                    int nargs, unsigned int *args)
2024{
2025  ddjvu_format_t *fmt = new ddjvu_format_s;
2026  memset(fmt, 0, sizeof(ddjvu_format_t));
2027  fmt->style = style; 
2028  fmt->rtoptobottom = false;
2029  fmt->ytoptobottom = false;
2030  fmt->gamma = 2.2;
2031  // Ditherbits
2032  fmt->ditherbits = 32;
2033  if (style==DDJVU_FORMAT_RGBMASK16)
2034    fmt->ditherbits = 16;
2035  else if (style==DDJVU_FORMAT_PALETTE8)
2036    fmt->ditherbits = 8;
2037  else if (style==DDJVU_FORMAT_MSBTOLSB || style==DDJVU_FORMAT_LSBTOMSB)
2038    fmt->ditherbits = 1;
2039  // Args
2040  switch(style)
2041    {
2042    case DDJVU_FORMAT_RGBMASK16:
2043    case DDJVU_FORMAT_RGBMASK32: 
2044      {
2045        if (sizeof(uint16_t)!=2 || sizeof(uint32_t)!=4)
2046          return fmt_error(fmt);
2047        if (!args || nargs<3 || nargs>4)
2048          return fmt_error(fmt);
2049        { // extra nesting for windows
2050          for (int j=0; j<3; j++)
2051          {
2052            int shift = 0;
2053            uint32_t mask = args[j];
2054            for (shift=0; shift<32 && !(mask & 1); shift++)
2055              mask >>= 1;
2056            if ((shift>=32) || (mask&(mask+1)))
2057              return fmt_error(fmt);
2058            for (int i=0; i<256; i++)
2059              fmt->rgb[j][i] = (mask & ((int)((i*mask+127.0)/255.0)))<<shift;
2060          }
2061        }
2062        if (nargs >= 4)
2063          fmt->xorval = args[3];
2064        break;
2065      }
2066    case DDJVU_FORMAT_PALETTE8:
2067      {
2068        if (nargs!=6*6*6 || !args)
2069          return fmt_error(fmt);
2070        { // extra nesting for windows
2071          for (int k=0; k<6*6*6; k++)
2072            fmt->palette[k] = args[k];
2073        }
2074        { // extra nesting for windows
2075          int j=0;
2076          for(int i=0; i<6; i++)
2077            for(; j < (i+1)*0x33 - 0x19 && j<256; j++)
2078            {
2079              fmt->rgb[0][j] = i * 6 * 6;
2080              fmt->rgb[1][j] = i * 6;
2081              fmt->rgb[2][j] = i;
2082            }
2083        }
2084        break;
2085      }
2086    case DDJVU_FORMAT_RGB24:
2087    case DDJVU_FORMAT_BGR24:
2088    case DDJVU_FORMAT_GREY8:
2089    case DDJVU_FORMAT_LSBTOMSB:
2090    case DDJVU_FORMAT_MSBTOLSB:
2091      if (!nargs) 
2092        break;
2093    default:
2094      return fmt_error(fmt);
2095    }
2096  return fmt;
2097}
2098
2099void
2100ddjvu_format_set_row_order(ddjvu_format_t *format, int top_to_bottom)
2101{
2102  format->rtoptobottom = !! top_to_bottom;
2103}
2104
2105void
2106ddjvu_format_set_y_direction(ddjvu_format_t *format, int top_to_bottom)
2107{
2108  format->ytoptobottom = !! top_to_bottom;
2109}
2110
2111void
2112ddjvu_format_set_ditherbits(ddjvu_format_t *format, int bits)
2113{
2114  if (bits>0 && bits<=64)
2115    format->ditherbits = bits;
2116}
2117
2118void
2119ddjvu_format_set_gamma(ddjvu_format_t *format, double gamma)
2120{
2121  if (gamma>=0.5 && gamma<=5.0)
2122    format->gamma = gamma;
2123}
2124
2125void
2126ddjvu_format_release(ddjvu_format_t *format)
2127{
2128  delete format;
2129}
2130
2131static void
2132fmt_convert_row(const GPixel *p, int w, 
2133                const ddjvu_format_t *fmt, char *buf)
2134{
2135  const uint32_t (*r)[256] = fmt->rgb;
2136  const uint32_t xorval = fmt->xorval;
2137  switch(fmt->style)
2138    {
2139    case DDJVU_FORMAT_BGR24:    /* truecolor 24 bits in BGR order */
2140      {
2141        memcpy(buf, (const char*)p, 3*w);
2142        break;
2143      }
2144    case DDJVU_FORMAT_RGB24:    /* truecolor 24 bits in RGB order */
2145      { 
2146        while (--w >= 0) { 
2147          buf[0]=p->r; buf[1]=p->g; buf[2]=p->b; 
2148          buf+=3; p+=1; 
2149        }
2150        break;
2151      }
2152    case DDJVU_FORMAT_RGBMASK16: /* truecolor 16 bits with masks */
2153      {
2154        uint16_t *b = (uint16_t*)buf;
2155        while (--w >= 0) {
2156          b[0]=(r[0][p->r]|r[1][p->g]|r[2][p->b])^xorval; 
2157          b+=1; p+=1; 
2158        }
2159        break;
2160      }
2161    case DDJVU_FORMAT_RGBMASK32: /* truecolor 32 bits with masks */
2162      {
2163        uint32_t *b = (uint32_t*)buf;
2164        while (--w >= 0) {
2165          b[0]=(r[0][p->r]|r[1][p->g]|r[2][p->b])^xorval; 
2166          b+=1; p+=1; 
2167        }
2168        break;
2169      }
2170    case DDJVU_FORMAT_GREY8:    /* greylevel 8 bits */
2171      {
2172        while (--w >= 0) { 
2173          buf[0]=(5*p->r + 9*p->g + 2*p->b)>>4; 
2174          buf+=1; p+=1; 
2175        }
2176        break;
2177      }
2178    case DDJVU_FORMAT_PALETTE8: /* paletized 8 bits (6x6x6 color cube) */
2179      {
2180        const uint32_t *u = fmt->palette;
2181        while (--w >= 0) {
2182          buf[0] = u[r[0][p->r]+r[1][p->g]+r[2][p->b]]; 
2183          buf+=1; p+=1; 
2184        }
2185        break;
2186      }
2187    case DDJVU_FORMAT_MSBTOLSB: /* packed bits, msb on the left */
2188      {
2189        unsigned char s=0, m=0x80;
2190        while (--w >= 0) {
2191          if ( 5*p->r + 9*p->g + 2*p->b < 0xc00 ) { s |= m; }
2192          if (! (m >>= 1)) { *buf++ = s; s=0; m=0x80; }
2193          p += 1;
2194        }
2195        if (m < 0x80) { *buf++ = s; }
2196        break;
2197      }
2198    case DDJVU_FORMAT_LSBTOMSB: /* packed bits, lsb on the left */
2199      {
2200        unsigned char s=0, m=0x1;
2201        while (--w >= 0) {
2202          if ( 5*p->r + 9*p->g + 2*p->b < 0xc00 ) { s |= m; }
2203          if (! (m <<= 1)) { *buf++ = s; s=0; m=0x1; }
2204          p += 1;
2205        }
2206        if (m > 0x1) { *buf++ = s; }
2207        break;
2208      }
2209    }
2210}
2211
2212static void
2213fmt_convert(GPixmap *pm, const ddjvu_format_t *fmt, char *buffer, int rowsize)
2214{
2215  int w = pm->columns();
2216  int h = pm->rows();
2217  // Loop on rows
2218  if (fmt->rtoptobottom)
2219    {
2220      for(int r=h-1; r>=0; r--, buffer+=rowsize)
2221        fmt_convert_row((*pm)[r], w, fmt, buffer);
2222    }
2223  else
2224    {
2225      for(int r=0; r<h; r++, buffer+=rowsize)
2226        fmt_convert_row((*pm)[r], w, fmt, buffer);
2227    }
2228}
2229
2230static void
2231fmt_convert_row(unsigned char *p, unsigned char *g, int w, 
2232                const ddjvu_format_t *fmt, char *buf)
2233{
2234  const uint32_t (*r)[256] = fmt->rgb;
2235  const uint32_t xorval = fmt->xorval;
2236  switch(fmt->style)
2237    {
2238    case DDJVU_FORMAT_BGR24:    /* truecolor 24 bits in BGR order */
2239    case DDJVU_FORMAT_RGB24:    /* truecolor 24 bits in RGB order */
2240      { 
2241        while (--w >= 0) { 
2242          buf[0]=buf[1]=buf[2]=g[*p];
2243          buf+=3; p+=1; 
2244        }
2245        break;
2246      }
2247    case DDJVU_FORMAT_RGBMASK16: /* truecolor 16 bits with masks */
2248      {
2249        uint16_t *b = (uint16_t*)buf;
2250        while (--w >= 0) {
2251          unsigned char x = g[*p];
2252          b[0]=(r[0][x]|r[1][x]|r[2][x])^xorval; 
2253          b+=1; p+=1; 
2254        }
2255        break;
2256      }
2257    case DDJVU_FORMAT_RGBMASK32: /* truecolor 32 bits with masks */
2258      {
2259        uint32_t *b = (uint32_t*)buf;
2260        while (--w >= 0) {
2261          unsigned char x = g[*p];
2262          b[0]=(r[0][x]|r[1][x]|r[2][x])^xorval; 
2263          b+=1; p+=1; 
2264        }
2265        break;
2266      }
2267    case DDJVU_FORMAT_GREY8:    /* greylevel 8 bits */
2268      {
2269        while (--w >= 0) { 
2270          buf[0]=g[*p];
2271          buf+=1; p+=1; 
2272        }
2273        break;
2274      }
2275    case DDJVU_FORMAT_PALETTE8: /* paletized 8 bits (6x6x6 color cube) */
2276      {
2277        const uint32_t *u = fmt->palette;
2278        while (--w >= 0) {
2279          buf[0] = u[g[*p]*(1+6+36)];
2280          buf+=1; p+=1; 
2281        }
2282        break;
2283      }
2284    case DDJVU_FORMAT_MSBTOLSB: /* packed bits, msb on the left */
2285      {
2286        unsigned char s=0, m=0x80;
2287        while (--w >= 0) {
2288          if (g[*p] < 0xc0) { s |= m; }
2289          if (! (m >>= 1)) { *buf++ = s; s=0; m=0x80; }
2290          p += 1;
2291        }
2292        if (m < 0x80) { *buf++ = s; }
2293        break;
2294      }
2295    case DDJVU_FORMAT_LSBTOMSB: /* packed bits, lsb on the left */
2296      {
2297        unsigned char s=0, m=0x1;
2298        while (--w >= 0) {
2299          if (g[*p] < 0xc0) { s |= m; }
2300          if (! (m <<= 1)) { *buf++ = s; s=0; m=0x1; }
2301          p += 1;
2302        }
2303        if (m > 0x1) { *buf++ = s; }
2304        break;
2305      }
2306    }
2307}
2308
2309static void
2310fmt_convert(GBitmap *bm, const ddjvu_format_t *fmt, char *buffer, int rowsize)
2311{
2312  int w = bm->columns();
2313  int h = bm->rows();
2314  int m = bm->get_grays();
2315  // Gray levels
2316  int i;
2317  unsigned char g[256];
2318  for (i=0; i<m; i++)
2319    g[i] = 255 - ( i * 255 + (m - 1)/2 ) / (m - 1);
2320  for (i=m; i<256; i++)
2321    g[i] = 0;
2322  // Loop on rows
2323  if (fmt->rtoptobottom)
2324    {
2325      for(int r=h-1; r>=0; r--, buffer+=rowsize)
2326        fmt_convert_row((*bm)[r], g, w, fmt, buffer);
2327    }
2328  else
2329    {
2330      for(int r=0; r<h; r++, buffer+=rowsize)
2331        fmt_convert_row((*bm)[r], g, w, fmt, buffer);
2332    }
2333}
2334
2335static void
2336fmt_dither(GPixmap *pm, const ddjvu_format_t *fmt, int x, int y)
2337{
2338  if (fmt->ditherbits < 8)
2339    return;
2340  else if (fmt->ditherbits < 15)
2341    pm->ordered_666_dither(x, y);
2342  else if (fmt->ditherbits < 24)
2343    pm->ordered_32k_dither(x, y);
2344}
2345
2346
2347// ----------------------------------------
2348
2349int
2350ddjvu_page_render(ddjvu_page_t *page,
2351                  const ddjvu_render_mode_t mode,
2352                  const ddjvu_rect_t *pagerect,
2353                  const ddjvu_rect_t *renderrect,
2354                  const ddjvu_format_t *pixelformat,
2355                  unsigned long rowsize,
2356                  char *imagebuffer )
2357{
2358  G_TRY
2359    {
2360      GP<GPixmap> pm;
2361      GP<GBitmap> bm;
2362      GRect prect, rrect;
2363      rect2grect(pagerect, prect);
2364      rect2grect(renderrect, rrect);
2365      if (pixelformat && pixelformat->ytoptobottom)
2366        {
2367          prect.ymin = renderrect->y + renderrect->h;
2368          prect.ymax = prect.ymin + pagerect->h;
2369          rrect.ymin = pagerect->y + pagerect->h;
2370          rrect.ymax = rrect.ymin + renderrect->h;
2371        }
2372
2373      DjVuImage *img = page->img;
2374      if (img) 
2375        {
2376          switch (mode)
2377            {
2378            case DDJVU_RENDER_COLOR:
2379              pm = img->get_pixmap(rrect, prect, pixelformat->gamma);
2380              if (! pm) 
2381                bm = img->get_bitmap(rrect, prect);
2382              break;
2383            case DDJVU_RENDER_BLACK:
2384              bm = img->get_bitmap(rrect, prect);
2385              if (! bm)
2386                pm = img->get_pixmap(rrect, prect, pixelformat->gamma);
2387              break;
2388            case DDJVU_RENDER_MASKONLY:
2389              bm = img->get_bitmap(rrect, prect);
2390              break;
2391            case DDJVU_RENDER_COLORONLY:
2392              pm = img->get_pixmap(rrect, prect, pixelformat->gamma);
2393              break;
2394            case DDJVU_RENDER_BACKGROUND:
2395              pm = img->get_bg_pixmap(rrect, prect, pixelformat->gamma);
2396              break;
2397            case DDJVU_RENDER_FOREGROUND:
2398              pm = img->get_fg_pixmap(rrect, prect, pixelformat->gamma);
2399              if (! pm) 
2400                bm = img->get_bitmap(rrect, prect);
2401              break;
2402            }
2403        }
2404      if (pm)
2405        {
2406          int dx = rrect.xmin - prect.xmin;
2407          int dy = rrect.ymin - prect.xmin;
2408          fmt_dither(pm, pixelformat, dx, dy);
2409          fmt_convert(pm, pixelformat, imagebuffer, rowsize);
2410          return 2;
2411        }
2412      else if (bm)
2413        {
2414          fmt_convert(bm, pixelformat, imagebuffer, rowsize);
2415          return 1;
2416        }
2417    }
2418  G_CATCH(ex)
2419    {
2420      ERROR1(page, ex);
2421    }
2422  G_ENDCATCH;
2423  return 0;
2424}
2425
2426
2427// ----------------------------------------
2428// Thumbnails
2429
2430void
2431ddjvu_thumbnail_p::callback(void *cldata)
2432{
2433  ddjvu_thumbnail_p *thumb = (ddjvu_thumbnail_p*)cldata;
2434  if (thumb->document)
2435    {
2436      GMonitorLock lock(&thumb->document->monitor);
2437      if (thumb->pool && thumb->pool->is_eof())
2438        {
2439          GP<DataPool> pool = thumb->pool;
2440          int size = pool->get_size();
2441          thumb->pool = 0;
2442          G_TRY
2443            {
2444              thumb->data.resize(0,size-1);
2445              pool->get_data( (void*)(char*)thumb->data, 0, size);
2446            }
2447          G_CATCH_ALL
2448            {
2449              thumb->data.empty();
2450            }
2451          G_ENDCATCH;
2452          if (thumb->document->doc)
2453            {
2454              GP<ddjvu_message_p> p = new ddjvu_message_p;
2455              p->p.m_thumbnail.pagenum = thumb->pagenum;
2456              msg_push(xhead(DDJVU_THUMBNAIL, thumb->document), p);
2457            } 
2458        }
2459    }
2460}
2461
2462ddjvu_status_t
2463ddjvu_thumbnail_status(ddjvu_document_t *document, int pagenum, int start)
2464{
2465  G_TRY
2466    {
2467      GP<ddjvu_thumbnail_p> thumb;
2468      DjVuDocument* doc = document->doc;
2469      if (doc)
2470        {
2471          GMonitorLock lock(&document->monitor);
2472          GPosition p = document->thumbnails.contains(pagenum);
2473          if (p)
2474            thumb = document->thumbnails[p];
2475        }
2476      if (!thumb && doc)
2477        {
2478          GP<DataPool> pool = doc->get_thumbnail(pagenum, !start);
2479          if (pool)
2480            {
2481              GMonitorLock lock(&document->monitor);
2482              thumb = new ddjvu_thumbnail_p;
2483              thumb->document = document;
2484              thumb->pagenum = pagenum;
2485              thumb->pool = pool;
2486              document->thumbnails[pagenum] = thumb;
2487            }
2488          if (thumb)
2489            pool->add_trigger(-1, ddjvu_thumbnail_p::callback, 
2490                              (void*)(ddjvu_thumbnail_p*)thumb);
2491        } 
2492      if (! thumb)
2493        return DDJVU_JOB_NOTSTARTED;       
2494      else if (thumb->pool)
2495        return DDJVU_JOB_STARTED;
2496      else if (thumb->data.size() > 0)
2497        return DDJVU_JOB_OK;
2498    }
2499  G_CATCH(ex)
2500    {
2501      ERROR1(document, ex);
2502    }
2503  G_ENDCATCH;
2504  return DDJVU_JOB_FAILED;
2505}
2506 
2507int
2508ddjvu_thumbnail_render(ddjvu_document_t *document, int pagenum, 
2509                       int *wptr, int *hptr,
2510                       const ddjvu_format_t *pixelformat,
2511                       unsigned long rowsize,
2512                       char *imagebuffer)
2513{
2514  G_TRY
2515    {
2516      GP<ddjvu_thumbnail_p> thumb;
2517      ddjvu_status_t status = ddjvu_thumbnail_status(document,pagenum,FALSE);
2518      if (status == DDJVU_JOB_OK)
2519        {
2520          GMonitorLock lock(&document->monitor);
2521          thumb = document->thumbnails[pagenum];
2522        }
2523      if (! (thumb && wptr && hptr))
2524        return FALSE;
2525      if (! (thumb->data.size() > 0))
2526        return FALSE;
2527      /* Decode wavelet data */
2528      int size = thumb->data.size();
2529      char *data = (char*)thumb->data;
2530      GP<IW44Image> iw = IW44Image::create_decode();
2531      iw->decode_chunk(ByteStream::create_static((void*)data, size));
2532      int w = iw->get_width();
2533      int h = iw->get_height();
2534      /* Restore aspect ratio */
2535      double dw = (double)w / *wptr;
2536      double dh = (double)h / *hptr;
2537      if (dw > dh) 
2538        *hptr = (int)(h / dw);
2539      else
2540        *wptr = (int)(w / dh);
2541      if (! imagebuffer)
2542        return TRUE;
2543      /* Render and scale image */
2544      GP<GPixmap> pm = iw->get_pixmap();
2545      double thumbgamma = document->doc->get_thumbnails_gamma();
2546      pm->color_correct(pixelformat->gamma / thumbgamma);
2547      GP<GPixmapScaler> scaler = GPixmapScaler::create(w, h, *wptr, *hptr);
2548      GP<GPixmap> scaledpm = GPixmap::create();
2549      GRect scaledrect(0, 0, *wptr, *hptr);
2550      scaler->scale(GRect(0, 0, w, h), *pm, scaledrect, *scaledpm);
2551      /* Convert */
2552      fmt_dither(scaledpm, pixelformat, 0, 0);
2553      fmt_convert(scaledpm, pixelformat, imagebuffer, rowsize);
2554      return TRUE;
2555    }
2556  G_CATCH(ex)
2557    {
2558      ERROR1(document, ex);
2559    }
2560  G_ENDCATCH;
2561  return FALSE;
2562}
2563
2564
2565// ----------------------------------------
2566// Threaded jobs
2567
2568struct DJVUNS ddjvu_runnablejob_s : public ddjvu_job_s
2569{
2570  bool mystop;
2571  int  myprogress;
2572  ddjvu_status_t mystatus;
2573  // methods
2574  ddjvu_runnablejob_s();
2575  ddjvu_status_t start();
2576  void progress(int p);
2577  // thread function
2578  virtual ddjvu_status_t run() = 0;
2579  // virtual port functions:
2580  virtual bool inherits(const GUTF8String&);
2581  virtual ddjvu_status_t status();
2582  virtual void stop();
2583private:
2584  static void cbstart(void*);
2585};
2586
2587ddjvu_runnablejob_s::ddjvu_runnablejob_s()
2588  : mystop(false), myprogress(-1),
2589    mystatus(DDJVU_JOB_NOTSTARTED) 
2590{
2591}
2592
2593void 
2594ddjvu_runnablejob_s::progress(int x)
2595{
2596  if ((mystatus>=DDJVU_JOB_OK) || (x>myprogress && x<100))
2597    {
2598      GMonitorLock lock(&monitor);
2599      GP<ddjvu_message_p> p = new ddjvu_message_p;
2600      p->p.m_progress.status = mystatus;
2601      p->p.m_progress.percent = myprogress = x;
2602      msg_push(xhead(DDJVU_PROGRESS,this),p);
2603    }
2604}
2605
2606ddjvu_status_t
2607ddjvu_runnablejob_s::start()
2608{
2609  GMonitorLock lock(&monitor);
2610  if (mystatus==DDJVU_JOB_NOTSTARTED && myctx)
2611    {
2612      GThread thr;
2613      thr.create(cbstart, (void*)this);
2614      monitor.wait();
2615    }
2616  return mystatus;
2617}
2618
2619void
2620ddjvu_runnablejob_s::cbstart(void *arg)
2621{
2622  GP<ddjvu_runnablejob_s> self = (ddjvu_runnablejob_s*)arg;
2623  {
2624    GMonitorLock lock(&self->monitor);
2625    self->mystatus = DDJVU_JOB_STARTED;
2626    self->monitor.signal();
2627  }
2628  ddjvu_status_t r;
2629  G_TRY
2630    {
2631      G_TRY
2632        {
2633          self->progress(0);
2634          r = self->run();
2635        }
2636      G_CATCH(ex)
2637        {
2638          ERROR1(self, ex);
2639          G_RETHROW;
2640        }
2641      G_ENDCATCH;
2642    }
2643  G_CATCH_ALL
2644    {
2645      r = DDJVU_JOB_FAILED;
2646      if (self && self->mystop)
2647        r = DDJVU_JOB_STOPPED;
2648    }
2649  G_ENDCATCH;
2650  {
2651    GMonitorLock lock(&self->monitor);
2652    self->mystatus = r;
2653  }
2654  if (self && self->mystatus> DDJVU_JOB_OK)
2655    self->progress(self->myprogress);
2656  else
2657    self->progress(100);
2658}
2659
2660bool 
2661ddjvu_runnablejob_s::inherits(const GUTF8String &classname)
2662{
2663  return (classname == "ddjvu_runnablejob_s") 
2664    || ddjvu_job_s::inherits(classname);
2665}
2666
2667ddjvu_status_t
2668ddjvu_runnablejob_s::status()
2669{
2670  return mystatus;
2671}
2672
2673void
2674ddjvu_runnablejob_s::stop()
2675{
2676  mystop = true;
2677}
2678
2679
2680// ----------------------------------------
2681// Printing
2682
2683struct DJVUNS ddjvu_printjob_s : public ddjvu_runnablejob_s
2684{
2685  DjVuToPS printer;
2686  GUTF8String pages;
2687  GP<ByteStream> obs;
2688  virtual ddjvu_status_t run();
2689  // virtual port functions:
2690  virtual bool inherits(const GUTF8String&);
2691  // progress
2692  static void cbrefresh(void*);
2693  static void cbprogress(double, void*);
2694  static void cbinfo(int, int, int, DjVuToPS::Stage, void*);
2695  double progress_low;
2696  double progress_high;
2697};
2698
2699bool 
2700ddjvu_printjob_s::inherits(const GUTF8String &classname)
2701{
2702  return (classname == "ddjvu_printjob_s") 
2703    || ddjvu_runnablejob_s::inherits(classname);
2704}
2705
2706ddjvu_status_t
2707ddjvu_printjob_s::run()
2708{
2709  mydoc->doc->wait_for_complete_init();
2710  progress_low = 0;
2711  progress_high = 1;
2712  printer.set_refresh_cb(cbrefresh, (void*)this);
2713  printer.set_dec_progress_cb(cbprogress, (void*)this);
2714  printer.set_prn_progress_cb(cbprogress, (void*)this);
2715  printer.set_info_cb(cbinfo, (void*)this);
2716  printer.print(*obs, mydoc->doc, pages);
2717  return DDJVU_JOB_OK;
2718}
2719
2720void
2721ddjvu_printjob_s::cbrefresh(void *data)
2722{
2723  ddjvu_printjob_s *self = (ddjvu_printjob_s*)data;
2724  if (self->mystop)
2725    {
2726      msg_push(xhead(DDJVU_INFO,self), msg_prep_info("Print job stopped"));
2727      G_THROW(DataPool::Stop);
2728    }
2729}
2730
2731void
2732ddjvu_printjob_s::cbprogress(double done, void *data)
2733{
2734  ddjvu_printjob_s *self = (ddjvu_printjob_s*)data;
2735  double &low = self->progress_low;
2736  double &high = self->progress_high;
2737  double progress = low;
2738  if (done >= 1)
2739    progress = high;
2740  else if (done >= 0)
2741    progress = low + done * (high-low);
2742  self->progress((int)(progress * 100));
2743  ddjvu_printjob_s::cbrefresh(data);
2744}
2745
2746void
2747ddjvu_printjob_s::cbinfo(int pnum, int pcnt, int ptot,
2748                         DjVuToPS::Stage stage, void *data)
2749{
2750  ddjvu_printjob_s *self = (ddjvu_printjob_s*)data;
2751  double &low = self->progress_low;
2752  double &high = self->progress_high;
2753  low = 0;
2754  high = 1;
2755  if (ptot > 0) 
2756    {
2757      double step = 1.0 / (double)ptot;
2758      low = (double)pcnt * step;
2759      if (stage != DjVuToPS::DECODING) 
2760        low += step / 2.0;
2761      high = low  + step / 2.0;
2762    }
2763  if (low < 0)
2764    low = 0;
2765  if (low > 1) 
2766    low = 1;
2767  if (high < low) 
2768    high = low;
2769  if (high > 1)
2770    high = 1;
2771  self->progress((int)(low * 100));
2772  ddjvu_printjob_s::cbrefresh(data);
2773}
2774
2775static void
2776complain(GUTF8String opt, const char *msg)
2777{
2778  GUTF8String message;
2779  if (opt.length() > 0)
2780    message = "Parsing \"" + opt + "\": " + msg;
2781  else
2782    message = msg;
2783  G_EMTHROW(GException((const char*)message));
2784}
2785
2786ddjvu_job_t *
2787ddjvu_document_print(ddjvu_document_t *document, FILE *output,
2788                     int optc, const char * const * optv)
2789{
2790  ddjvu_printjob_s *job = 0;
2791  G_TRY
2792    {
2793      job = new ddjvu_printjob_s;
2794      ref(job);
2795      job->myctx = document->myctx;
2796      job->mydoc = document;
2797      // parse options (see djvups(1))
2798      DjVuToPS::Options &options = job->printer.options;
2799      GUTF8String &pages = job->pages;
2800      while (optc>0)
2801        {
2802          // normalize
2803          GNativeString narg(optv[0]);
2804          GUTF8String uarg = narg;
2805          const char *s1 = (const char*)narg;
2806          if (s1[0] == '-') s1++;
2807          if (s1[0] == '-') s1++;
2808          // separate arguments
2809          const char *s2 = s1;
2810          while (*s2 && *s2 != '=') s2++;
2811          GUTF8String s( s1, s2-s1 );
2812          GUTF8String arg( s2[0] && s2[1] ? s2+1 : "" );
2813          // rumble!
2814          if (s == "page" || s == "pages")
2815            {
2816              if (pages.length())
2817                pages = pages + ",";
2818              pages = pages + arg;
2819            }
2820          else if (s == "format")
2821            {
2822              if (arg == "ps")
2823                options.set_format(DjVuToPS::Options::PS);
2824              else if (arg == "eps")
2825                options.set_format(DjVuToPS::Options::EPS);
2826              else
2827                complain(uarg,"Invalid format. Use \"ps\" or \"eps\".");
2828            }
2829          else if (s == "level")
2830            {
2831              int endpos;
2832              int lvl = arg.toLong(0, endpos);
2833              if (endpos != (int)arg.length() || lvl < 1 || lvl > 4)
2834                complain(uarg,"Invalid Postscript language level.");
2835              options.set_level(lvl);
2836            }
2837          else if (s == "orient" || s == "orientation")
2838            {
2839              if (arg == "a" || arg == "auto" )
2840                options.set_orientation(DjVuToPS::Options::AUTO);
2841              else if (arg == "l" || arg == "landscape" )
2842                options.set_orientation(DjVuToPS::Options::LANDSCAPE);
2843              else if (arg == "p" || arg == "portrait" )
2844                options.set_orientation(DjVuToPS::Options::PORTRAIT);
2845              else
2846                complain(uarg,"Invalid orientation. Use \"auto\", "
2847                         "\"landscape\" or \"portrait\".");
2848            }
2849          else if (s == "mode")
2850            {
2851              if (arg == "c" || arg == "color" )
2852                options.set_mode(DjVuToPS::Options::COLOR);
2853              else if (arg == "black" || arg == "bw")
2854                options.set_mode(DjVuToPS::Options::BW);
2855              else if (arg == "fore" || arg == "foreground")
2856                options.set_mode(DjVuToPS::Options::FORE);
2857              else if (arg == "back" || arg == "background" )
2858                options.set_mode(DjVuToPS::Options::BACK);
2859              else
2860                complain(uarg,"Invalid mode. Use \"color\", \"bw\", "
2861                         "\"foreground\", or \"background\".");
2862            }
2863          else if (s == "zoom")
2864            {
2865              if (arg == "auto" || arg == "fit" || arg == "fit_page")
2866                options.set_zoom(0);
2867              else if (arg == "1to1" || arg == "onetoone")
2868                options.set_zoom(100);               
2869              else 
2870                {
2871                  int endpos;
2872                  int z = arg.toLong(0,endpos);
2873                  if (endpos != (int)arg.length() || z < 25 || z > 2400)
2874                    complain(uarg,"Invalid zoom factor.");
2875                  options.set_zoom(z);
2876                }
2877            }
2878          else if (s == "color")
2879            {
2880              if (arg == "yes" || arg == "")
2881                options.set_color(true);
2882              else if (arg == "no")
2883                options.set_color(false);
2884              else
2885                complain(uarg,"Invalid argument. Use \"yes\" or \"no\".");
2886            }
2887          else if (s == "gray" || s == "grayscale")
2888            {
2889              if (arg.length())
2890                complain(uarg,"No argument was expected.");
2891              options.set_color(false);
2892            }
2893          else if (s == "srgb" || s == "colormatch")
2894            {
2895              if (arg == "yes" || arg == "")
2896                options.set_sRGB(true);
2897              else if (arg == "no")
2898                options.set_sRGB(false);
2899              else
2900                complain(uarg,"Invalid argument. Use \"yes\" or \"no\".");
2901            }
2902          else if (s == "gamma")
2903            {
2904              int endpos;
2905              double g = arg.toDouble(0,endpos);
2906              if (endpos != (int)arg.length() || g < 0.3 || g > 5.0)
2907                complain(uarg,"Invalid gamma factor. "
2908                              "Use a number in range 0.3 ... 5.0.");
2909              options.set_gamma(g);
2910            }
2911          else if (s == "copies")
2912            {
2913              int endpos;
2914              int n = arg.toLong(0, endpos);
2915              if (endpos != (int)arg.length() || n < 1 || n > 999999)
2916                complain(uarg,"Invalid number of copies.");
2917              options.set_copies(n);
2918            }
2919          else if (s == "frame")
2920            {
2921              if (arg == "yes" || arg == "")
2922                options.set_frame(true);
2923              else if (arg == "no")
2924                options.set_frame(false);
2925              else
2926                complain(uarg,"Invalid argument. Use \"yes\" or \"no\".");
2927            }
2928          else if (s == "cropmarks")
2929            {
2930              if (arg == "yes" || arg == "")
2931                options.set_cropmarks(true);
2932              else if (arg == "no")
2933                options.set_cropmarks(false);
2934              else
2935                complain(uarg,"Invalid argument. Use \"yes\" or \"no\".");
2936            }
2937          else if (s == "text")
2938            {
2939              if (arg == "yes" || arg == "")
2940                options.set_text(true);
2941              else if (arg == "no")
2942                options.set_text(false);
2943              else
2944                complain(uarg,"Invalid argument. Use \"yes\" or \"no\".");
2945            }
2946          else if (s == "booklet")
2947            {
2948              if (arg == "no")
2949                options.set_bookletmode(DjVuToPS::Options::OFF);
2950              else if (arg == "recto")
2951                options.set_bookletmode(DjVuToPS::Options::RECTO);
2952              else if (arg == "verso")
2953                options.set_bookletmode(DjVuToPS::Options::VERSO);
2954              else if (arg == "rectoverso" || arg=="yes" || arg=="")
2955                options.set_bookletmode(DjVuToPS::Options::RECTOVERSO);
2956              else 
2957                complain(uarg,"Invalid argument."
2958                         "Use \"no\", \"yes\", \"recto\", or \"verso\".");
2959            }
2960          else if (s == "bookletmax")
2961            {
2962              int endpos;
2963              int n = arg.toLong(0, endpos);
2964              if (endpos != (int)arg.length() || n < 0 || n > 999999)
2965                complain(uarg,"Invalid argument.");
2966              options.set_bookletmax(n);
2967            }
2968          else if (s == "bookletalign")
2969            {
2970              int endpos;
2971              int n = arg.toLong(0, endpos);
2972              if (endpos != (int)arg.length() || n < -720 || n > +720)
2973                complain(uarg,"Invalid argument.");
2974              options.set_bookletalign(n);
2975            }
2976          else if (s == "bookletfold")
2977            {
2978              int endpos = 0;
2979              int m = 250;
2980              int n = arg.toLong(0, endpos);
2981              if (endpos>0 && endpos<(int)arg.length() && arg[endpos]=='+')
2982                m = arg.toLong(endpos+1, endpos);
2983              if (endpos != (int)arg.length() || m<0 || m>720 || n<0 || n>9999 )
2984                complain(uarg,"Invalid argument.");
2985              options.set_bookletfold(n,m);
2986            }
2987          else
2988            {
2989              complain(uarg, "Unrecognized option.");
2990            }
2991          // Next option
2992          optc -= 1;
2993          optv += 1;
2994        }
2995      // go
2996      job->obs = ByteStream::create(output, "wb", false);
2997      job->start();
2998    }
2999  G_CATCH(ex)
3000    {
3001      if (job) 
3002        unref(job);
3003      job = 0;
3004      ERROR1(document, ex);
3005    }
3006  G_ENDCATCH;
3007  return job;
3008}
3009
3010
3011
3012// ----------------------------------------
3013// Saving
3014
3015struct DJVUNS ddjvu_savejob_s : public ddjvu_runnablejob_s
3016{
3017  GP<ByteStream> obs;
3018  GURL           odir; 
3019  GUTF8String    oname;
3020  GUTF8String    pages;
3021  GTArray<char>       comp_flags;
3022  GArray<GUTF8String> comp_ids;
3023  GPArray<DjVuFile>   comp_files;
3024  GMonitor monitor;
3025  // thread routine
3026  virtual ddjvu_status_t run();
3027  // virtual port functions:
3028  virtual bool inherits(const GUTF8String&);
3029  virtual void notify_file_flags_changed(const DjVuFile*, long, long);
3030  // helpers
3031  bool parse_pagespec(const char *s, int npages, bool *flags);
3032  void mark_included_files(DjVuFile *file);
3033};
3034
3035bool 
3036ddjvu_savejob_s::inherits(const GUTF8String &classname)
3037{
3038  return (classname == "ddjvu_savejob_s") 
3039    || ddjvu_runnablejob_s::inherits(classname);
3040}
3041
3042void
3043ddjvu_savejob_s::notify_file_flags_changed(const DjVuFile *file, 
3044                                           long mask, long)
3045{
3046  if (mask & (DjVuFile::ALL_DATA_PRESENT | DjVuFile::DATA_PRESENT |
3047              DjVuFile::DECODE_FAILED | DjVuFile::DECODE_STOPPED |
3048              DjVuFile::STOPPED | DjVuFile::DECODE_STOPPED ))
3049    {
3050      GMonitorLock lock(&monitor);
3051      monitor.signal();
3052    }
3053}
3054
3055bool
3056ddjvu_savejob_s::parse_pagespec(const char *s, int npages, bool *flags)
3057{
3058  int spec = 0;
3059  int both = 1;
3060  int start_page = 1;
3061  int end_page = npages;
3062  int pageno;
3063  char *p = (char*)s;
3064  while (*p)
3065    {
3066      spec = 0;
3067      while (*p==' ')
3068        p += 1;
3069      if (! *p)
3070        break;
3071      if (*p>='0' && *p<='9') {
3072        end_page = strtol(p, &p, 10);
3073        spec = 1;
3074      } else if (*p=='$') {
3075        spec = 1;
3076        end_page = npages;
3077        p += 1;
3078      } else if (both) {
3079        end_page = 1;
3080      } else {
3081        end_page = npages;
3082      }
3083      while (*p==' ')
3084        p += 1;
3085      if (both) {
3086        start_page = end_page;
3087        if (*p == '-') {
3088          p += 1;
3089          both = 0;
3090          continue;
3091        }
3092      }
3093      both = 1;
3094      while (*p==' ')
3095        p += 1;
3096      if (*p && *p != ',')
3097        return false;
3098      if (*p == ',')
3099        p += 1;
3100      if (! spec)
3101        return false;
3102      if (end_page < 0)
3103        end_page = 0;
3104      if (start_page < 0)
3105        start_page = 0;
3106      if (end_page > npages)
3107        end_page = npages;
3108      if (start_page > npages)
3109        start_page = npages;
3110      if (start_page <= end_page)
3111        for(pageno=start_page; pageno<=end_page; pageno++)
3112          flags[pageno-1] = true;
3113      else
3114        for(pageno=start_page; pageno>=end_page; pageno--)
3115          flags[pageno-1] = true;
3116    }
3117  if (!spec)
3118    return false;
3119  return true;
3120}
3121
3122void 
3123ddjvu_savejob_s::mark_included_files(DjVuFile *file)
3124{
3125  GP<DataPool> pool = file->get_init_data_pool();
3126  GP<ByteStream> str(pool->get_stream());
3127  GP<IFFByteStream> iff(IFFByteStream::create(str));
3128  GUTF8String chkid;
3129  if (!iff->get_chunk(chkid)) 
3130    return;
3131  while (iff->get_chunk(chkid))
3132    {
3133      if (chkid == "INCL")
3134        {
3135          GP<ByteStream> incl = iff->get_bytestream();
3136          GUTF8String fileid;
3137          char buffer[1024];
3138          int length;
3139          while((length=incl->read(buffer, 1024)))
3140            fileid += GUTF8String(buffer, length);
3141          for (int i=0; i<comp_ids.size(); i++)
3142            if (fileid == comp_ids[i] && !comp_flags[i])
3143              comp_flags[i] = 1;
3144        }
3145      iff->close_chunk();
3146    }
3147  iff->close_chunk();
3148  pool->clear_stream();
3149}
3150
3151ddjvu_status_t
3152ddjvu_savejob_s::run()
3153{
3154  DjVuDocument *doc = mydoc->doc;
3155  doc->wait_for_complete_init();
3156
3157  // Determine which pages to save
3158  int npages = doc->get_pages_num();
3159  GTArray<bool> page_flags(0, npages-1);
3160  if (!pages)
3161    {
3162      for (int pageno=0; pageno<npages; pageno++)
3163        page_flags[pageno] = true;
3164    }
3165  else
3166    {
3167      const char *s = pages;
3168      while (*s && *s!='=')
3169        s += 1;
3170      for (int pageno=0; pageno<npages; pageno++)
3171        page_flags[pageno] = false;
3172      if ((*s != '=') || !parse_pagespec(s+1, npages, (bool*)page_flags))
3173        complain(pages,"Illegal page specification");
3174      if (doc->get_doc_type()==DjVuDocument::OLD_BUNDLED ||
3175          doc->get_doc_type()==DjVuDocument::OLD_INDEXED )
3176        complain(pages,"Saving subsets of obsolete formats is not supported");
3177    }
3178 
3179  // Determine which component files to save
3180  int ncomps;
3181  if (doc->get_doc_type()==DjVuDocument::BUNDLED ||
3182      doc->get_doc_type()==DjVuDocument::INDIRECT)
3183    {
3184      GP<DjVmDir> dir = doc->get_djvm_dir();
3185      ncomps = dir->get_files_num();
3186      comp_ids.resize(ncomps - 1);
3187      comp_flags.resize(ncomps - 1);
3188      comp_files.resize(ncomps - 1);
3189      int pageno = 0;
3190      GPList<DjVmDir::File> flist = dir->get_files_list();
3191      GPosition pos=flist;
3192      for (int comp=0; comp<ncomps; ++pos, ++comp)
3193        {
3194          DjVmDir::File *file = flist[pos];
3195          comp_ids[comp] = file->get_load_name();
3196          comp_flags[comp] = 0;
3197          if (file->is_page() && page_flags[pageno++])
3198            comp_flags[comp] = 1;
3199        }
3200    }
3201  else
3202    {
3203      ncomps = npages;
3204      comp_flags.resize(ncomps - 1);
3205      comp_files.resize(ncomps - 1);
3206      for (int comp=0; comp<ncomps; ++comp)
3207        comp_flags[comp] = page_flags[comp];
3208    }
3209 
3210  // Download
3211  get_portcaster()->add_route(doc, this);
3212  while (!mystop)
3213    {
3214      int comp;
3215      int wanted = 0;
3216      int loaded = 0;
3217      int asked = 0;
3218      for (comp=0; comp<ncomps; comp++)
3219        {
3220          int flags = comp_flags[comp];
3221          if (flags > 2)
3222            loaded += 1;
3223          else if (flags < 2)
3224            continue;
3225          else if (!comp_files[comp]->is_data_present())
3226            asked += 1;
3227          else 
3228            {
3229              comp_flags[comp] += 1;
3230              mark_included_files(comp_files[comp]);
3231            } 
3232        }
3233      for (comp=0; comp<ncomps; comp++)
3234        if (comp_flags[comp] > 0)
3235          wanted += 1;
3236      progress(loaded * 100 / wanted);
3237      if (wanted == loaded)
3238        break;
3239      for (comp=0; comp<ncomps && asked < 2; comp++)
3240        if (comp_flags[comp] == 1)
3241          {
3242            if (comp_ids.size() > 0)
3243              comp_files[comp] = doc->get_djvu_file(comp_ids[comp]);
3244            else
3245              comp_files[comp] = doc->get_djvu_file(comp);
3246            comp_flags[comp] += 1;
3247            if (!comp_files[comp]->is_data_present())
3248              asked += 1;
3249          }
3250      GMonitorLock lock(&monitor);
3251      for (comp=0; comp<ncomps; comp++)
3252        if (comp_flags[comp] == 2)
3253          if (! comp_files[comp]->is_data_present())
3254            {
3255              monitor.wait();
3256              break;
3257            }
3258    }
3259  if (mystop)
3260    G_THROW(DataPool::Stop);
3261  // Saving!
3262  GP<DjVmDoc> djvm;
3263  if (! pages)
3264    {
3265      djvm = doc->get_djvm_doc();
3266    }
3267  else
3268    {
3269      djvm = DjVmDoc::create();
3270      GP<DjVmDir> dir = doc->get_djvm_dir();
3271      GPList<DjVmDir::File> flist = dir->get_files_list();
3272      GPosition pos=flist;
3273      int pageno = 0;
3274      for (int comp=0; comp<ncomps; ++pos, ++comp)
3275        {
3276          if (flist[pos]->is_page())
3277            pageno += 1;
3278          if (comp_flags[comp])
3279            {
3280              GP<DjVmDir::File> f = new DjVmDir::File(*flist[pos]);
3281              if (f->is_page() && f->get_save_name()==f->get_title())
3282                f->set_title(GUTF8String(pageno));
3283              GP<DjVuFile> file = comp_files[comp];
3284              GP<DataPool> data = file->get_init_data_pool();
3285              djvm->insert_file(f, data);
3286            }
3287        }
3288    }
3289  if (obs)
3290    djvm->write(obs);
3291  else if (odir.is_valid() && oname.length() > 0)
3292    djvm->expand(odir, oname);
3293  return DDJVU_JOB_OK;
3294}
3295
3296
3297ddjvu_job_t *
3298ddjvu_document_save(ddjvu_document_t *document, FILE *output, 
3299                    int optc, const char * const * optv)
3300{
3301  ddjvu_savejob_s *job = 0;
3302  G_TRY
3303    {
3304      job = new ddjvu_savejob_s;
3305      ref(job);
3306      job->myctx = document->myctx;
3307      job->mydoc = document;
3308      bool indirect = false;
3309      // parse options
3310      while (optc>0)
3311        {
3312          GNativeString narg(optv[0]);
3313          GUTF8String uarg = narg;
3314          const char *s1 = (const char*)narg;
3315          if (s1[0] == '-') s1++;
3316          if (s1[0] == '-') s1++;
3317          // separate arguments
3318          if (!strncmp(s1, "page=", 5) ||
3319              !strncmp(s1, "pages=", 6) )
3320            {
3321              if (job->pages.length())
3322                complain(uarg,"multiple page specifications");
3323              job->pages = uarg;
3324            }
3325          else if (!strncmp(s1, "indirect=", 9))
3326            {
3327              GURL oname = GURL::Filename::UTF8(s1 + 9);
3328              job->odir = oname.base();
3329              job->oname = oname.fname();
3330              indirect = true;
3331            }
3332          else
3333            {
3334              complain(uarg, "Unrecognized option.");
3335            }
3336          // next option
3337          optc -= 1;
3338          optv += 1;
3339        }
3340      // go
3341      if (!indirect)
3342        job->obs = ByteStream::create(output, "wb", false);
3343      else 
3344        job->obs = 0;
3345      job->start();
3346    }
3347  G_CATCH(ex)
3348    {
3349      if (job) 
3350        unref(job);
3351      job = 0;
3352      ERROR1(document, ex);
3353    }
3354  G_ENDCATCH;
3355  return job;
3356}
3357
3358
3359
3360
3361// ----------------------------------------
3362// S-Expressions (generic)
3363
3364static miniexp_t
3365miniexp_status(ddjvu_status_t status)
3366{
3367  if (status < DDJVU_JOB_OK)
3368    return miniexp_dummy;
3369  else if (status == DDJVU_JOB_STOPPED)
3370    return miniexp_symbol("stopped");
3371  else if (status > DDJVU_JOB_OK)
3372    return miniexp_symbol("failed");   
3373  return miniexp_nil;
3374}
3375
3376static void
3377miniexp_protect(ddjvu_document_t *document, miniexp_t expr)
3378{
3379  { // extra nesting for windows
3380    for(miniexp_t p=document->protect; miniexp_consp(p); p=miniexp_cdr(p))
3381      if (miniexp_car(p) == expr)
3382        return;
3383  }
3384  if (miniexp_consp(expr) || miniexp_objectp(expr))
3385    document->protect = miniexp_cons(expr, document->protect);
3386}
3387
3388void
3389ddjvu_miniexp_release(ddjvu_document_t *document, miniexp_t expr)
3390{
3391  miniexp_t q = miniexp_nil;
3392  miniexp_t p = document->protect;
3393  while (miniexp_consp(p))
3394    {
3395      if (miniexp_car(p) != expr)
3396        q = p;
3397      else if (q)
3398        miniexp_rplacd(q, miniexp_cdr(p));
3399      else
3400        document->protect = miniexp_cdr(p);
3401      p = miniexp_cdr(p);
3402    }
3403}
3404
3405
3406
3407// ----------------------------------------
3408// S-Expressions (outline)
3409
3410static miniexp_t
3411outline_sub(const GP<DjVmNav> &nav, int &pos, int count)
3412{
3413  GP<DjVmNav::DjVuBookMark> entry;
3414  minivar_t p,q,s;
3415  while (count > 0 && pos < nav->getBookMarkCount())
3416    {
3417      nav->getBookMark(entry, pos++);
3418      q = outline_sub(nav, pos, entry->count);
3419      s = miniexp_string((const char*)(entry->url));
3420      q = miniexp_cons(s, q);
3421      s = miniexp_string((const char*)(entry->displayname));
3422      q = miniexp_cons(s, q);
3423      p = miniexp_cons(q, p);
3424      count--;
3425    }
3426  return miniexp_reverse(p);
3427}
3428
3429miniexp_t
3430ddjvu_document_get_outline(ddjvu_document_t *document)
3431{
3432  G_TRY
3433    {
3434      ddjvu_status_t status = document->status();
3435      if (status != DDJVU_JOB_OK)
3436        return miniexp_status(status);
3437      DjVuDocument *doc = document->doc;
3438      if (doc)
3439        {
3440          GP<DjVmNav> nav = doc->get_djvm_nav();
3441          if (! nav) 
3442            return miniexp_nil;
3443          minivar_t result;
3444          int pos = 0;
3445          result = outline_sub(nav, pos, nav->getBookMarkCount());
3446          result = miniexp_cons(miniexp_symbol("bookmarks"), result);
3447          miniexp_protect(document, result);
3448          return result;
3449        }
3450    }
3451  G_CATCH(ex)
3452    {
3453      ERROR1(document, ex);
3454    }
3455  G_ENDCATCH;
3456  return miniexp_status(DDJVU_JOB_FAILED);
3457}
3458
3459
3460
3461
3462// ----------------------------------------
3463// S-Expressions (text)
3464
3465static struct zone_names_s {
3466  const char *name;
3467  DjVuTXT::ZoneType ztype;
3468  char separator;
3469} zone_names[] = {
3470  { "page",   DjVuTXT::PAGE,      0 },
3471  { "column", DjVuTXT::COLUMN,    DjVuTXT::end_of_column },
3472  { "region", DjVuTXT::REGION,    DjVuTXT::end_of_region },
3473  { "para",   DjVuTXT::PARAGRAPH, DjVuTXT::end_of_paragraph },
3474  { "line",   DjVuTXT::LINE,      DjVuTXT::end_of_line },
3475  { "word",   DjVuTXT::WORD,      ' ' },
3476  { "char",   DjVuTXT::CHARACTER, 0 },
3477  { 0, (DjVuTXT::ZoneType)0 ,0 }
3478};
3479
3480static miniexp_t
3481pagetext_sub(const GP<DjVuTXT> &txt, DjVuTXT::Zone &zone, 
3482             DjVuTXT::ZoneType detail)
3483{
3484  int zinfo;
3485  for (zinfo=0; zone_names[zinfo].name; zinfo++)
3486    if (zone.ztype == zone_names[zinfo].ztype)
3487      break;
3488  minivar_t p;
3489  minivar_t a;
3490  bool gather = zone.children.isempty();
3491  { // extra nesting for windows
3492    for (GPosition pos=zone.children; pos; ++pos)
3493      if (zone.children[pos].ztype > detail)
3494        gather = true;
3495  }
3496  if (gather)
3497    {
3498      const char *data = (const char*)(txt->textUTF8) + zone.text_start;
3499      int length = zone.text_length;
3500      if (length>0 && data[length-1]==zone_names[zinfo].separator)
3501        length -= 1;
3502      a = miniexp_substring(data, length);
3503      p = miniexp_cons(a, p);
3504    }
3505  else
3506    {
3507      for (GPosition pos=zone.children; pos; ++pos)
3508        {
3509          a = pagetext_sub(txt, zone.children[pos], detail);
3510          p = miniexp_cons(a, p);
3511        }
3512    }
3513  p = miniexp_reverse(p);
3514  const char *s = zone_names[zinfo].name;
3515  if (s)
3516    {
3517      p = miniexp_cons(miniexp_number(zone.rect.ymax), p);
3518      p = miniexp_cons(miniexp_number(zone.rect.xmax), p);
3519      p = miniexp_cons(miniexp_number(zone.rect.ymin), p);
3520      p = miniexp_cons(miniexp_number(zone.rect.xmin), p);
3521      p = miniexp_cons(miniexp_symbol(s), p);
3522      return p;
3523    }
3524  return miniexp_nil;
3525}
3526
3527miniexp_t
3528ddjvu_document_get_pagetext(ddjvu_document_t *document, int pageno,
3529                            const char *maxdetail)
3530{
3531  G_TRY
3532    {
3533      ddjvu_status_t status = document->status();
3534      if (status != DDJVU_JOB_OK)
3535        return miniexp_status(status);
3536      DjVuDocument *doc = document->doc;
3537      if (doc)
3538        {
3539          document->pageinfoflag = true;
3540          GP<DjVuFile> file = doc->get_djvu_file(pageno);
3541          if (! file || ! file->is_data_present() )
3542            return miniexp_dummy;
3543          GP<ByteStream> bs = file->get_text();
3544          if (! bs)
3545            return miniexp_nil;
3546          GP<DjVuText> text = DjVuText::create();
3547          text->decode(bs);
3548          GP<DjVuTXT> txt = text->txt;
3549          if (! txt)
3550            return miniexp_nil;
3551          minivar_t result;
3552          DjVuTXT::ZoneType detail = DjVuTXT::CHARACTER;
3553          { // extra nesting for windows
3554            for (int i=0; zone_names[i].name; i++)
3555              if (maxdetail && !strcmp(maxdetail, zone_names[i].name))
3556                detail = zone_names[i].ztype;
3557          }
3558          result = pagetext_sub(txt, txt->page_zone, detail);
3559          miniexp_protect(document, result);
3560          return result;
3561        }
3562    }
3563  G_CATCH(ex)
3564    {
3565      ERROR1(document, ex);
3566    }
3567  G_ENDCATCH;
3568  return miniexp_status(DDJVU_JOB_FAILED);
3569}
3570
3571
3572// ----------------------------------------
3573// S-Expressions (annotations)
3574
3575// The difficulty here lies with the syntax of strings in annotation chunks.
3576// - Early versions of djvu only had one possible escape
3577//   sequence (\") in annotation strings. All other characters
3578//   are accepted literally until reaching the closing double quote.
3579// - Current versions of djvu understand the usual backslash escapes.
3580//   All non printable ascii characters must however be escaped.
3581//   This is a subset of the miniexp syntax.
3582// We first check if strings in the annotation chunk obey the modern syntax.
3583// The compatibility mode is turned on if they contain non printable ascii
3584// characters or illegal backslash sequences. Function <anno_getc()> then
3585// creates the proper escapes on the fly.
3586
3587
3588static struct {
3589  const char *s;
3590  char buf[8];
3591  int  blen;
3592  int  state;
3593  bool compat;
3594  bool eof;
3595} anno_dat;
3596
3597
3598static bool
3599anno_compat(const char *s)
3600{
3601  int state = 0;
3602  bool compat = false;
3603  while (s && *s && !compat)
3604    {
3605      int i = (int)(unsigned char)*s++;
3606      switch(state)
3607        {
3608        case 0:
3609          if (i == '\"')
3610            state = '\"';
3611          break;
3612        case '\"':
3613          if (i == '\"')
3614            state = 0;
3615          else if (i == '\\')
3616            state = '\\';
3617          else if (isascii(i) && !isprint(i))
3618            compat = true;
3619          break;
3620        case '\\':
3621          if (!strchr("01234567abtnvfr\"\\",i))
3622            compat = true;
3623          state = '\"';
3624          break;
3625        }
3626    }
3627  return compat;
3628}
3629
3630
3631static int
3632anno_getc(void)
3633{
3634  if (anno_dat.blen>0)
3635    {
3636      anno_dat.blen--;
3637      char c = anno_dat.buf[0];
3638      { // extra nesting for windows
3639        for (int i=0; i<anno_dat.blen; i++)
3640          anno_dat.buf[i] = anno_dat.buf[i+1];
3641      }
3642      return c;
3643    }
3644  if (! *anno_dat.s)
3645    return EOF;
3646  int c = (int)(unsigned char)*anno_dat.s++;
3647  if (anno_dat.compat)
3648    {
3649      switch (anno_dat.state)
3650        {
3651        case 0:
3652          if (c == '\"') 
3653            anno_dat.state = '\"';
3654          break;
3655        case '\"':
3656          if (c == '\"') 
3657            anno_dat.state = 0;
3658          else if (c == '\\')
3659            anno_dat.state = '\\';
3660          else if (isascii(c) && !isprint(c))
3661            {
3662              sprintf(anno_dat.buf,"%03o", c);
3663              anno_dat.blen = strlen(anno_dat.buf);
3664              c = '\\';
3665            }
3666          break;
3667        case '\\':
3668          anno_dat.state = '\"';
3669          if (c != '\"')
3670            {
3671              sprintf(anno_dat.buf,"\\%03o", c);
3672              anno_dat.blen = strlen(anno_dat.buf);
3673              c = '\\';
3674            }
3675          break;
3676        }
3677    }
3678  return c;
3679}
3680
3681
3682static int
3683anno_ungetc(int c)
3684{
3685  if (c == EOF)
3686    return EOF;
3687  if (anno_dat.blen>=(int)sizeof(anno_dat.buf))
3688    return EOF;
3689  { // extra nesting for windows
3690    for (int i=anno_dat.blen; i>0; i--)
3691      anno_dat.buf[i] = anno_dat.buf[i-1];
3692  }
3693  anno_dat.blen += 1;
3694  anno_dat.buf[0] = c;
3695  return c;
3696}
3697
3698
3699static void
3700anno_sub(ByteStream *bs, miniexp_t &result)
3701{
3702  // Read bs
3703  GUTF8String raw;
3704  char buffer[1024];
3705  int length;
3706  while ((length=bs->read(buffer, sizeof(buffer))))
3707    raw += GUTF8String(buffer, length);
3708  // Prepare
3709  miniexp_t a;
3710  anno_dat.s = (const char*)raw;
3711  anno_dat.compat = anno_compat(anno_dat.s);
3712  anno_dat.blen = 0;
3713  anno_dat.state = 0;
3714  anno_dat.eof = false;
3715  int (*saved_getc)(void) = minilisp_getc;
3716  int (*saved_ungetc)(int) = minilisp_ungetc;
3717  // Process
3718  minilisp_getc = anno_getc;
3719  minilisp_ungetc = anno_ungetc;
3720  while (* anno_dat.s )
3721    if ((a = miniexp_read()) != miniexp_dummy)
3722      result = miniexp_cons(a, result);
3723  // Restore
3724  minilisp_getc = saved_getc;
3725  minilisp_ungetc = saved_ungetc;
3726}
3727
3728
3729static miniexp_t
3730get_bytestream_anno(GP<ByteStream> annobs)
3731{
3732  if (! (annobs && annobs->size()))
3733    return miniexp_nil;
3734  GP<IFFByteStream> iff = IFFByteStream::create(annobs);
3735  GUTF8String chkid;
3736  minivar_t result;
3737  while (iff->get_chunk(chkid))
3738    {
3739      GP<ByteStream> bs;
3740      if (chkid == "ANTa") 
3741        bs = iff->get_bytestream();
3742      else if (chkid == "ANTz")
3743        bs = BSByteStream::create(iff->get_bytestream());
3744      if (bs)
3745        anno_sub(bs, result);
3746      iff->close_chunk();
3747    }
3748  return miniexp_reverse(result);
3749}
3750
3751
3752static miniexp_t
3753get_file_anno(GP<DjVuFile> file)
3754{
3755  // Make sure all data is present
3756  if (! file || ! file->is_all_data_present())
3757    {
3758      if (file && file->is_data_present())
3759        {
3760          if (! file->are_incl_files_created())
3761            file->process_incl_chunks();
3762          if (! file->are_incl_files_created())
3763            {
3764              if (file->get_flags() & DjVuFile::STOPPED)
3765                return miniexp_status(DDJVU_JOB_STOPPED);
3766              return miniexp_status(DDJVU_JOB_FAILED);
3767            }
3768        }
3769      return miniexp_dummy;
3770    }
3771  // Access annotation data
3772  return get_bytestream_anno(file->get_merged_anno());
3773}
3774
3775
3776miniexp_t
3777ddjvu_document_get_pageanno(ddjvu_document_t *document, int pageno)
3778{
3779  G_TRY
3780    {
3781      ddjvu_status_t status = document->status();
3782      if (status != DDJVU_JOB_OK)
3783        return miniexp_status(status);
3784      DjVuDocument *doc = document->doc;
3785      if (doc)
3786        {
3787          document->pageinfoflag = true;
3788          minivar_t result = get_file_anno( doc->get_djvu_file(pageno) );
3789          if (miniexp_consp(result))
3790            miniexp_protect(document, result);
3791          return result;
3792        }
3793    }
3794  G_CATCH(ex)
3795    {
3796      ERROR1(document, ex);
3797    }
3798  G_ENDCATCH;
3799  return miniexp_status(DDJVU_JOB_FAILED);
3800}
3801
3802
3803miniexp_t
3804ddjvu_document_get_anno(ddjvu_document_t *document, int compat)
3805{
3806  G_TRY
3807    {
3808      ddjvu_status_t status = document->status();
3809      if (status != DDJVU_JOB_OK)
3810        return miniexp_status(status);
3811      DjVuDocument *doc = document->doc;
3812      if (doc)
3813        {
3814#if EXPERIMENTAL_DOCUMENT_ANNOTATIONS
3815          // not yet implemented
3816          GP<ByteStream> anno = doc->get_document_anno();
3817          if (anno)
3818            return get_bytestream_anno(anno);
3819#endif
3820          if (compat)
3821            {
3822              // look for shared annotations
3823              int doc_type = doc->get_doc_type();
3824              if (doc_type != DjVuDocument::BUNDLED &&
3825                  doc_type != DjVuDocument::INDIRECT )
3826                return miniexp_nil;
3827              GP<DjVmDir> dir = doc->get_djvm_dir();
3828              int filenum = dir->get_files_num();
3829              GP<DjVmDir::File> fdesc;
3830              for (int i=0; i<filenum; i++)
3831                {
3832                  GP<DjVmDir::File> f = dir->pos_to_file(i);
3833                  if (!f->is_shared_anno())
3834                    continue;
3835                  if (fdesc)
3836                    return miniexp_nil;
3837                  fdesc = f;
3838                }
3839              if (fdesc)
3840                {
3841                  GUTF8String id = fdesc->get_load_name();
3842                  return get_file_anno(doc->get_djvu_file(id));
3843                }
3844            }
3845          return miniexp_nil;
3846        }
3847    }
3848  G_CATCH(ex)
3849    {
3850      ERROR1(document, ex);
3851    }
3852  G_ENDCATCH;
3853  return miniexp_status(DDJVU_JOB_FAILED);
3854}
3855
3856
3857
3858
3859/* ------ helpers for annotations ---- */
3860
3861static const char *
3862simple_anno_sub(miniexp_t p, miniexp_t s, int i)
3863{
3864  const char *result = 0;
3865  while (miniexp_consp(p))
3866    {
3867      miniexp_t a = miniexp_car(p);
3868      p = miniexp_cdr(p);
3869      if (miniexp_car(a) == s)
3870        {
3871          miniexp_t q = miniexp_nth(i, a);
3872          if (miniexp_symbolp(q))
3873            result = miniexp_to_name(q);
3874        }
3875    }
3876  return result;
3877}
3878
3879const char *
3880ddjvu_anno_get_bgcolor(miniexp_t p)
3881{
3882  return simple_anno_sub(p, miniexp_symbol("background"), 1);
3883}
3884
3885const char *
3886ddjvu_anno_get_zoom(miniexp_t p)
3887{
3888  return simple_anno_sub(p, miniexp_symbol("zoom"), 1);
3889}
3890
3891const char *
3892ddjvu_anno_get_mode(miniexp_t p)
3893{
3894  return simple_anno_sub(p, miniexp_symbol("mode"), 1);
3895}
3896
3897const char *
3898ddjvu_anno_get_horizalign(miniexp_t p)
3899{
3900  return simple_anno_sub(p, miniexp_symbol("align"), 1);
3901}
3902
3903const char *
3904ddjvu_anno_get_vertalign(miniexp_t p)
3905{
3906  return simple_anno_sub(p, miniexp_symbol("align"), 2);
3907}
3908
3909miniexp_t *
3910ddjvu_anno_get_hyperlinks(miniexp_t annotations)
3911{
3912  miniexp_t p;
3913  miniexp_t s_maparea = miniexp_symbol("maparea");
3914  int i = 0;
3915  for (p = annotations; miniexp_consp(p); p = miniexp_cdr(p))
3916    if (miniexp_caar(p) == s_maparea)
3917      i += 1;
3918  miniexp_t *k = (miniexp_t*)malloc((1+i)*sizeof(miniexp_t));
3919  if (! k) return 0;
3920  i = 0;
3921  for (p = annotations; miniexp_consp(p); p = miniexp_cdr(p))
3922    if (miniexp_caar(p) == s_maparea)
3923      k[i++] = miniexp_car(p);
3924  k[i] = 0;
3925  return k;
3926}
3927
3928static void
3929metadata_sub(miniexp_t p, GMap<miniexp_t,miniexp_t> &m)
3930{
3931  miniexp_t s_metadata = miniexp_symbol("metadata");
3932  while (miniexp_consp(p))
3933    {
3934      if (miniexp_caar(p) == s_metadata)
3935        {
3936          miniexp_t q = miniexp_cdar(p);
3937          while (miniexp_consp(q))
3938            {
3939              miniexp_t a = miniexp_car(q);
3940              q = miniexp_cdr(q);
3941              if (miniexp_consp(a) && 
3942                  miniexp_symbolp(miniexp_car(a)) &&
3943                  miniexp_stringp(miniexp_cadr(a)) )
3944                {
3945                  m[miniexp_car(a)] = miniexp_cadr(a);
3946                }
3947            }
3948        }
3949      p = miniexp_cdr(p);
3950    }
3951}
3952
3953miniexp_t *
3954ddjvu_anno_get_metadata_keys(miniexp_t p)
3955{
3956  minivar_t l;
3957  GMap<miniexp_t,miniexp_t> m;
3958  metadata_sub(p, m);
3959  int i = m.size();
3960  miniexp_t *k = (miniexp_t*)malloc((1+i)*sizeof(miniexp_t));
3961  if (! k) return 0;
3962  i = 0;
3963  { // extra nesting for windows
3964    for (GPosition p=m; p; ++p)
3965      k[i++] = m.key(p);
3966  }
3967  k[i] = 0;
3968  return k;
3969}
3970
3971const char *
3972ddjvu_anno_get_metadata(miniexp_t p, miniexp_t key)
3973{
3974  GMap<miniexp_t,miniexp_t> m;
3975  metadata_sub(p, m);
3976  if (m.contains(key))
3977    return miniexp_to_str(m[key]);
3978  return 0;
3979}
3980
3981
3982// ----------------------------------------
3983// Backdoors
3984
3985GP<DjVuImage>
3986ddjvu_get_DjVuImage(ddjvu_page_t *page)
3987{
3988  return page->img;
3989}
3990
3991
3992GP<DjVuDocument>
3993ddjvu_get_DjVuDocument(ddjvu_document_t *document)
3994{
3995  return document->doc;
3996}
3997
3998
Note: See TracBrowser for help on using the repository browser.