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