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