source: trunk/libdjvu/ddjvuapi.cpp @ 269

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

DJVU plugin: djvulibre updated to version 3.5.19

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