comparison mupdf-source/source/helpers/mu-office-lib/mu-office-lib.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2025 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 /**
24 * Mu Office Library
25 *
26 * Provided access to the core document, loading, displaying and
27 * editing routines
28 *
29 * Intended for use with native UI
30 */
31
32 #include "mupdf/fitz.h"
33 #include "mupdf/pdf.h"
34 #include "mupdf/helpers/mu-office-lib.h"
35 #include "mupdf/helpers/mu-threads.h"
36 #include "mupdf/memento.h"
37
38 #include <assert.h>
39
40 enum
41 {
42 MuError_OK = 0,
43 MuError_OOM = -1,
44 MuError_BadNull = -2,
45 MuError_Generic = -3,
46 MuError_NotImplemented = -4,
47 MuError_PasswordPending = -5,
48 };
49
50 enum {
51 LAYOUT_W = 450,
52 LAYOUT_H = 600,
53 LAYOUT_EM = 12
54 };
55
56 #ifdef DISABLE_MUTHREADS
57 #error "mu-office-lib requires threading to be enabled"
58 #endif
59
60 /*
61 If we are building as part of a smartoffice build, then we
62 should appeal to Pal_Mem_etc to get memory. If not, then
63 we should use malloc instead.
64
65 FIXME: Allow for something other than malloc/calloc/realloc/
66 free here.
67 */
68 #ifndef SMARTOFFICE_BUILD
69 void *Pal_Mem_calloc(unsigned int num, size_t size)
70 {
71 return calloc(num, size);
72 }
73
74 void *Pal_Mem_malloc(size_t size)
75 {
76 return malloc(size);
77 }
78
79 void *Pal_Mem_realloc(void *ptr, size_t size)
80 {
81 return realloc(ptr, size);
82 }
83
84 void Pal_Mem_free(void *address)
85 {
86 free(address);
87 }
88 #endif
89
90 /*
91 All MuPDF's allocations are redirected through the
92 following functions.
93 */
94 static void *muoffice_malloc(void *arg, size_t size)
95 {
96 return Pal_Mem_malloc(size);
97 }
98
99 static void *muoffice_realloc(void *arg, void *old, size_t size)
100 {
101 return Pal_Mem_realloc(old, size);
102 }
103
104 static void muoffice_free(void *arg, void *ptr)
105 {
106 Pal_Mem_free(ptr);
107 }
108
109 static fz_alloc_context muoffice_alloc =
110 {
111 /* user */
112 NULL,
113
114 /* void *(*malloc)(void *, size_t); */
115 muoffice_malloc,
116
117 /* void *(*realloc)(void *, void *, size_t); */
118 muoffice_realloc,
119
120 /* void (*free)(void *, void *); */
121 muoffice_free
122 };
123
124 /*
125 All MuPDF's locking is done using the following functions
126 */
127 static void muoffice_lock(void *user, int lock);
128
129 static void muoffice_unlock(void *user, int lock);
130
131 struct MuOfficeLib
132 {
133 fz_context *ctx;
134 mu_mutex mutexes[FZ_LOCK_MAX+1];
135 fz_locks_context locks;
136 };
137
138 /*
139 We add 1 extra lock which we use in this helper to protect
140 against accessing the fz_document from multiple threads
141 inadvertently when the caller is calling 'run' or
142 'runBackground'.
143 */
144 enum
145 {
146 DOCLOCK = FZ_LOCK_MAX
147 };
148
149 static void muoffice_lock(void *user, int lock)
150 {
151 MuOfficeLib *mu = (MuOfficeLib *)user;
152
153 mu_lock_mutex(&mu->mutexes[lock]);
154 }
155
156 static void muoffice_unlock(void *user, int lock)
157 {
158 MuOfficeLib *mu = (MuOfficeLib *)user;
159
160 mu_unlock_mutex(&mu->mutexes[lock]);
161 }
162
163 static void muoffice_doc_lock(MuOfficeLib *mu)
164 {
165 mu_lock_mutex(&mu->mutexes[DOCLOCK]);
166 }
167
168 static void muoffice_doc_unlock(MuOfficeLib *mu)
169 {
170 mu_unlock_mutex(&mu->mutexes[DOCLOCK]);
171 }
172
173 static void fin_muoffice_locks(MuOfficeLib *mu)
174 {
175 int i;
176
177 for (i = 0; i < FZ_LOCK_MAX+1; i++)
178 mu_destroy_mutex(&mu->mutexes[i]);
179 }
180
181 static fz_locks_context *init_muoffice_locks(MuOfficeLib *mu)
182 {
183 int i;
184 int failed = 0;
185
186 for (i = 0; i < FZ_LOCK_MAX+1; i++)
187 failed |= mu_create_mutex(&mu->mutexes[i]);
188
189 if (failed)
190 {
191 fin_muoffice_locks(mu);
192 return NULL;
193 }
194
195 mu->locks.user = mu;
196 mu->locks.lock = muoffice_lock;
197 mu->locks.unlock = muoffice_unlock;
198
199 return &mu->locks;
200 }
201
202 MuError MuOfficeLib_create(MuOfficeLib **pMu)
203 {
204 MuOfficeLib *mu;
205 fz_locks_context *locks;
206
207 if (pMu == NULL)
208 return MuOfficeDocErrorType_IllegalArgument;
209
210 mu = Pal_Mem_calloc(1, sizeof(MuOfficeLib));
211 if (mu == NULL)
212 return MuOfficeDocErrorType_OutOfMemory;
213
214 locks = init_muoffice_locks(mu);
215 if (locks == NULL)
216 goto Fail;
217
218 mu->ctx = fz_new_context(&muoffice_alloc, locks, FZ_STORE_DEFAULT);
219 if (mu->ctx == NULL)
220 goto Fail;
221
222 fz_try(mu->ctx)
223 fz_register_document_handlers(mu->ctx);
224 fz_catch(mu->ctx)
225 goto Fail;
226
227 *pMu = mu;
228
229 return MuOfficeDocErrorType_NoError;
230
231 Fail:
232 if (mu)
233 {
234 fin_muoffice_locks(mu);
235 Pal_Mem_free(mu);
236 }
237 return MuOfficeDocErrorType_OutOfMemory;
238 }
239
240 /**
241 * Destroy a MuOfficeLib instance
242 *
243 * @param mu the instance to destroy
244 */
245 void MuOfficeLib_destroy(MuOfficeLib *mu)
246 {
247 if (mu == NULL)
248 return;
249
250 fz_drop_context(mu->ctx);
251 fin_muoffice_locks(mu);
252
253 Pal_Mem_free(mu);
254 }
255
256 /**
257 * Perform MuPDF native operations on a given MuOfficeLib
258 * instance.
259 *
260 * The function is called with a fz_context value that can
261 * be safely used (i.e. the context is cloned/dropped
262 * appropriately around the call). The function should signal
263 * errors by fz_throw-ing.
264 *
265 * @param mu the MuOfficeLib instance.
266 * @param fn the function to call to run the operations.
267 * @param arg Opaque data pointer.
268 *
269 * @return error indication - 0 for success
270 */
271 MuError MuOfficeLib_run(MuOfficeLib *mu, void (*fn)(fz_context *ctx, void *arg), void *arg)
272 {
273 fz_context *ctx;
274 MuError err = MuError_OK;
275
276 if (mu == NULL)
277 return MuError_BadNull;
278 if (fn == NULL)
279 return err;
280
281 ctx = fz_clone_context(mu->ctx);
282 if (ctx == NULL)
283 return MuError_OOM;
284
285 fz_try(ctx)
286 fn(ctx, arg);
287 fz_catch(ctx)
288 err = MuError_Generic;
289
290 fz_drop_context(ctx);
291
292 return err;
293 }
294
295 /**
296 * Find the type of a file given its filename extension.
297 *
298 * @param path path to the file (in utf8)
299 *
300 * @return a valid MuOfficeDocType value, or MuOfficeDocType_Other
301 */
302 MuOfficeDocType MuOfficeLib_getDocTypeFromFileExtension(const char *path)
303 {
304 return /* FIXME */MuOfficeDocType_PDF;
305 }
306
307 /**
308 * Return a list of file extensions supported by Mu Office library.
309 *
310 * @return comma-delimited list of extensions, without the leading ".".
311 * The caller should free the returned pointer..
312 */
313 char * MuOfficeLib_getSupportedFileExtensions(void)
314 {
315 /* FIXME */
316 return NULL;
317 }
318
319 struct MuOfficeDoc
320 {
321 MuOfficeLib *mu;
322 fz_context *ctx;
323 MuOfficeLoadingProgressFn *progress;
324 MuOfficeLoadingErrorFn *error;
325 void *cookie;
326 char *path;
327 char *password;
328 mu_semaphore password_sem;
329 mu_thread thread;
330 int needs_password;
331 int aborted;
332 fz_document *doc;
333
334 MuOfficePage *pages;
335 };
336
337 struct MuOfficePage
338 {
339 MuOfficePage *next;
340 MuOfficeDoc *doc;
341 int pageNum;
342 void *cookie;
343 MuOfficePageUpdateFn *updateFn;
344 fz_page *page;
345 fz_display_list *list;
346 };
347
348 struct MuOfficeRender
349 {
350 MuOfficePage *page;
351 float zoom;
352 const MuOfficeBitmap *bitmap;
353 int area_valid;
354 MuOfficeRenderArea area;
355 MuOfficeRenderProgressFn *progress;
356 MuError error;
357 mu_thread thread;
358 void *cookie;
359 fz_cookie mu_cookie;
360 };
361
362 static void load_worker(void *arg)
363 {
364 MuOfficeDoc *doc = (MuOfficeDoc *)arg;
365 int numPages = 0;
366 fz_context *ctx = fz_clone_context(doc->ctx);
367 int err = 0;
368
369 if (ctx == NULL)
370 {
371 return;
372 }
373
374 muoffice_doc_lock(doc->mu);
375
376 fz_try(ctx)
377 {
378 doc->doc = fz_open_document(ctx, doc->path);
379 doc->needs_password = fz_needs_password(ctx, doc->doc);
380 }
381 fz_catch(ctx)
382 {
383 err = MuOfficeDocErrorType_UnsupportedDocumentType;
384 goto fail;
385 }
386
387 fz_try(ctx)
388 {
389 if (doc->needs_password && doc->error)
390 {
391 do
392 {
393 doc->error(doc->cookie, MuOfficeDocErrorType_PasswordRequest);
394 mu_wait_semaphore(&doc->password_sem);
395 if (doc->aborted)
396 break;
397 doc->needs_password = (fz_authenticate_password(ctx, doc->doc, doc->password) != 0);
398 Pal_Mem_free(doc->password);
399 doc->password = NULL;
400 }
401 while (doc->needs_password);
402 }
403
404 fz_layout_document(ctx, doc->doc, LAYOUT_W, LAYOUT_H, LAYOUT_EM);
405
406 numPages = fz_count_pages(ctx, doc->doc);
407 }
408 fz_catch(ctx)
409 err = MuOfficeDocErrorType_UnableToLoadDocument;
410
411 fail:
412 muoffice_doc_unlock(doc->mu);
413
414 if (err)
415 doc->error(doc->cookie, err);
416
417 if (doc->progress)
418 doc->progress(doc->cookie, numPages, 1);
419
420 fz_drop_context(ctx);
421 }
422
423 /**
424 * Load a document
425 *
426 * Call will return immediately, leaving the document loading
427 * in the background
428 *
429 * @param so a MuOfficeLib instance
430 * @param path path to the file to load (in utf8)
431 * @param progressFn callback for monitoring progress
432 * @param errorFn callback for monitoring errors
433 * @param cookie a pointer to pass back to the callbacks
434 * @param pDoc address for return of a MuOfficeDoc object
435 *
436 * @return error indication - 0 for success
437 *
438 * The progress callback may be called several times, with increasing
439 * values of pagesLoaded. Unless MuOfficeDoc_destroy is called,
440 * before loading completes, a call with "completed" set to true
441 * is guaranteed.
442 *
443 * Once MuOfficeDoc_destroy is called there will be no
444 * further callbacks.
445 *
446 * Alternatively, in a synchronous context, MuOfficeDoc_getNumPages
447 * can be called to wait for loading to complete and return the total
448 * number of pages. In this mode of use, progressFn can be NULL.
449 */
450 MuError MuOfficeLib_loadDocument( MuOfficeLib *mu,
451 const char *path,
452 MuOfficeLoadingProgressFn *progressFn,
453 MuOfficeLoadingErrorFn *errorFn,
454 void *cookie,
455 MuOfficeDoc **pDoc)
456 {
457 MuOfficeDoc *doc;
458 fz_context *ctx;
459
460 if (mu == NULL || pDoc == NULL)
461 return MuOfficeDocErrorType_IllegalArgument;
462
463 *pDoc = NULL;
464
465 doc = Pal_Mem_calloc(1, sizeof(*doc));
466 if (doc == NULL)
467 return MuOfficeDocErrorType_NoError;
468
469 ctx = mu->ctx;
470 doc->mu = mu;
471 doc->ctx = fz_clone_context(ctx);
472 doc->progress = progressFn;
473 doc->error = errorFn;
474 doc->cookie = cookie;
475 doc->path = fz_strdup(ctx, path);
476 if (mu_create_semaphore(&doc->password_sem))
477 goto fail;
478
479 if (mu_create_thread(&doc->thread, load_worker, doc))
480 goto fail;
481
482 *pDoc = doc;
483
484 return MuError_OK;
485 fail:
486 mu_destroy_semaphore(&doc->password_sem);
487 Pal_Mem_free(doc);
488
489 return MuError_OOM;
490 }
491
492 /**
493 * Provide the password for a document
494 *
495 * This function should be called to provide a password with a document
496 * error of MuOfficeError_PasswordRequired is received.
497 *
498 * If a password is requested again, this means the password was incorrect.
499 *
500 * @param doc the document object
501 * @param password the password (UTF8 encoded)
502 * @return error indication - 0 for success
503 */
504 int MuOfficeDoc_providePassword(MuOfficeDoc *doc, const char *password)
505 {
506 size_t len;
507
508 if (doc->password)
509 return MuError_PasswordPending;
510 if (!password)
511 password = "";
512
513 len = strlen(password);
514 doc->password = Pal_Mem_malloc(len+1);
515 strcpy(doc->password, password);
516 mu_trigger_semaphore(&doc->password_sem);
517
518 return MuError_OK;
519 }
520
521 /**
522 * Return the type of an open document
523 *
524 * @param doc the document object
525 *
526 * @return the document type
527 */
528 MuOfficeDocType MuOfficeDoc_docType(MuOfficeDoc *doc)
529 {
530 return /* FIXME */MuOfficeDocType_PDF;
531 }
532
533 static void
534 ensure_doc_loaded(MuOfficeDoc *doc)
535 {
536 if (doc == NULL)
537 return;
538
539 mu_destroy_thread(&doc->thread);
540 }
541
542 /**
543 * Return the number of pages of a document
544 *
545 * This function waits for document loading to complete before returning
546 * the result. It may block the calling thread for a significant period of
547 * time. To avoid blocking, this call should be avoided in favour of using
548 * the MuOfficeLib_loadDocument callbacks to monitor loading.
549 *
550 * If background loading fails, the associated error will be returned
551 * from this call.
552 *
553 * @param doc the document
554 * @param pNumPages address for return of the number of pages
555 *
556 * @return error indication - 0 for success
557 */
558 MuError MuOfficeDoc_getNumPages(MuOfficeDoc *doc, int *pNumPages)
559 {
560 fz_context *ctx;
561 MuError err = MuError_OK;
562
563 if (doc == NULL)
564 {
565 *pNumPages = 0;
566 return MuError_BadNull;
567 }
568
569 ensure_doc_loaded(doc);
570
571 ctx = doc->ctx;
572
573 fz_try(ctx)
574 {
575 *pNumPages = fz_count_pages(ctx, doc->doc);
576 }
577 fz_catch(ctx)
578 {
579 err = MuError_Generic;
580 }
581
582 return err;
583 }
584
585 /**
586 * Determine if the document has been modified
587 *
588 * @param doc the document
589 *
590 * @return modified flag
591 */
592 int MuOfficeDoc_hasBeenModified(MuOfficeDoc *doc)
593 {
594 fz_context *ctx;
595 pdf_document *pdoc;
596 int modified = 0;
597
598 if (doc == NULL)
599 return 0;
600
601 ensure_doc_loaded(doc);
602
603 ctx = doc->ctx;
604 pdoc = pdf_specifics(ctx, doc->doc);
605
606 if (pdoc == NULL)
607 return 0;
608
609 fz_try(ctx)
610 modified = pdf_has_unsaved_changes(ctx, pdoc);
611 fz_catch(ctx)
612 modified = 0;
613
614 return modified;
615 }
616
617 /**
618 * Start a save operation
619 *
620 * @param doc the document
621 * @param path path of the file to which to save
622 * @param resultFn callback used to report completion
623 * @param cookie a pointer to pass to the callback
624 *
625 * @return error indication - 0 for success
626 */
627 MuError MuOfficeDoc_save( MuOfficeDoc *doc,
628 const char *path,
629 MuOfficeSaveResultFn *resultFn,
630 void *cookie)
631 {
632 return MuError_NotImplemented; /* FIXME */
633 }
634
635 /**
636 * Stop a document loading. The document is not destroyed, but
637 * no further content will be read from the file.
638 *
639 * @param doc the MuOfficeDoc object
640 */
641 void MuOfficeDoc_abortLoad(MuOfficeDoc *doc)
642 {
643 fz_context *ctx;
644
645 if (doc == NULL)
646 return;
647
648 ctx = doc->ctx;
649 doc->aborted = 1;
650 mu_trigger_semaphore(&doc->password_sem);
651 }
652
653 /**
654 * Destroy a MuOfficeDoc object. Loading of the document is shutdown
655 * and no further callbacks will be issued for the specified object.
656 *
657 * @param doc the MuOfficeDoc object
658 */
659 void MuOfficeDoc_destroy(MuOfficeDoc *doc)
660 {
661 MuOfficeDoc_abortLoad(doc);
662 mu_destroy_thread(&doc->thread);
663 mu_destroy_semaphore(&doc->password_sem);
664
665 fz_drop_document(doc->ctx, doc->doc);
666 fz_drop_context(doc->ctx);
667 Pal_Mem_free(doc->path);
668 Pal_Mem_free(doc);
669 }
670
671 /**
672 * Get a page of a document
673 *
674 * @param doc the document object
675 * @param pageNumber the number of the page to load (lying in the
676 * range 0 to one less than the number of pages)
677 * @param updateFn Function to be called back when the page updates
678 * @param cookie Opaque value to pass for any updates
679 * @param pPage Address for return of the page object
680 *
681 * @return error indication - 0 for success
682 */
683 MuError MuOfficeDoc_getPage( MuOfficeDoc *doc,
684 int pageNumber,
685 MuOfficePageUpdateFn *updateFn,
686 void *cookie,
687 MuOfficePage **pPage)
688 {
689 MuOfficePage *page;
690 MuError err = MuError_OK;
691 fz_context *ctx;
692
693 if (!doc)
694 return MuError_BadNull;
695 if (!pPage)
696 return MuError_OK;
697
698 *pPage = NULL;
699
700 ensure_doc_loaded(doc);
701 ctx = doc->ctx;
702
703 page = Pal_Mem_calloc(1, sizeof(*page));
704 if (page == NULL)
705 return MuError_OOM;
706
707 muoffice_doc_lock(doc->mu);
708
709 fz_try(ctx)
710 {
711 page->doc = doc;
712 page->pageNum = pageNumber;
713 page->cookie = cookie;
714 page->updateFn = updateFn;
715 page->page = fz_load_page(doc->ctx, doc->doc, pageNumber);
716 page->next = doc->pages;
717 doc->pages = page;
718 *pPage = page;
719 }
720 fz_catch(ctx)
721 {
722 Pal_Mem_free(page);
723 err = MuError_Generic;
724 }
725
726 muoffice_doc_unlock(doc->mu);
727
728 return err;
729 }
730
731 /**
732 * Perform MuPDF native operations on a given document.
733 *
734 * The function is called with fz_context and fz_document
735 * values that can be safely used (i.e. the context is
736 * cloned/dropped appropriately around the function, and
737 * locking is used to ensure that no other threads are
738 * simultaneously using the document). Functions can
739 * signal errors by fz_throw-ing.
740 *
741 * Due to the locking, it is best to ensure that as little
742 * time is taken here as possible (i.e. if you fetch some
743 * data and then spend a long time processing it, it is
744 * probably best to fetch the data using MuOfficeDoc_run
745 * and then process it outside). This avoids potentially
746 * blocking the UI.
747 *
748 * @param doc the document object.
749 * @param fn the function to call with fz_context/fz_document
750 * values.
751 * @param arg Opaque data pointer.
752 *
753 * @return error indication - 0 for success
754 */
755 MuError MuOfficeDoc_run(MuOfficeDoc *doc, void (*fn)(fz_context *ctx, fz_document *doc, void *arg), void *arg)
756 {
757 fz_context *ctx;
758 MuError err = MuError_OK;
759
760 if (doc == NULL)
761 return MuError_BadNull;
762 if (fn == NULL)
763 return err;
764
765 ensure_doc_loaded(doc);
766
767 ctx = fz_clone_context(doc->mu->ctx);
768 if (ctx == NULL)
769 return MuError_OOM;
770
771 muoffice_doc_lock(doc->mu);
772
773 fz_try(ctx)
774 fn(ctx, doc->doc, arg);
775 fz_catch(ctx)
776 err = MuError_Generic;
777
778 muoffice_doc_unlock(doc->mu);
779
780 fz_drop_context(ctx);
781
782 return err;
783 }
784
785 /**
786 * Destroy a page object
787 *
788 * Note this does not delete or remove the page from the document.
789 * It simply destroys the page object which is merely a reference
790 * to the page.
791 *
792 * @param page the page object
793 */
794 void MuOfficePage_destroy(MuOfficePage *page)
795 {
796 MuOfficeDoc *doc;
797 MuOfficePage **ptr;
798
799 if (!page)
800 return;
801
802 /* Unlink page from doc */
803 doc = page->doc;
804 ptr = &doc->pages;
805 while (*ptr && *ptr != page)
806 ptr = &(*ptr)->next;
807 assert(*ptr);
808 *ptr = page->next;
809
810 fz_drop_page(doc->ctx, page->page);
811 fz_drop_display_list(doc->ctx, page->list);
812 fz_free(doc->ctx, page);
813 }
814
815 /**
816 * Get the size of a page in pixels
817 *
818 * This returns the size of the page in pixels. Pages can be rendered
819 * with a zoom factor. The returned value is the size of bitmap
820 * appropriate for rendering with a zoom of 1.0 and corresponds to
821 * 90 dpi. The returned values are not necessarily whole numbers.
822 *
823 * @param page the page object
824 * @param pWidth address for return of the width
825 * @param pHeight address for return of the height
826 *
827 * @return error indication - 0 for success
828 */
829 MuError MuOfficePage_getSize( MuOfficePage *page,
830 float *pWidth,
831 float *pHeight)
832 {
833 MuOfficeDoc *doc;
834 fz_rect rect;
835
836 if (!page)
837 return MuError_BadNull;
838 doc = page->doc;
839 if (!doc)
840 return MuError_BadNull;
841
842 rect = fz_bound_page(doc->ctx, page->page);
843
844 /* MuPDF measures in points (72ths of an inch). This API wants
845 * 90ths of an inch, so adjust. */
846
847 if (pWidth)
848 *pWidth = 90 * (rect.x1 - rect.x0) / 72;
849 if (pHeight)
850 *pHeight = 90 * (rect.y1 - rect.y0) / 72;
851
852 return MuError_OK;
853 }
854
855 /**
856 * Return the zoom factors necessary to render at to a given
857 * size in pixels. (deprecated)
858 *
859 * @param page the page object
860 * @param width the desired width
861 * @param height the desired height
862 * @param pXZoom Address for return of zoom necessary to fit width
863 * @param pYZoom Address for return of zoom necessary to fit height
864 *
865 * @return error indication - 0 for success
866 */
867 MuError MuOfficePage_calculateZoom( MuOfficePage *page,
868 int width,
869 int height,
870 float *pXZoom,
871 float *pYZoom)
872 {
873 MuOfficeDoc *doc;
874 fz_rect rect;
875 float w, h;
876
877 if (!page)
878 return MuError_BadNull;
879 doc = page->doc;
880 if (!doc)
881 return MuError_BadNull;
882
883 rect = fz_bound_page(doc->ctx, page->page);
884
885 /* MuPDF measures in points (72ths of an inch). This API wants
886 * 90ths of an inch, so adjust. */
887 w = 90 * (rect.x1 - rect.x0) / 72;
888 h = 90 * (rect.y1 - rect.y0) / 72;
889
890 if (pXZoom)
891 *pXZoom = width/w;
892 if (pYZoom)
893 *pYZoom = height/h;
894
895 return MuError_OK;
896 }
897
898 /**
899 * Get the size of a page in pixels for a specified zoom factor
900 * (deprecated)
901 *
902 * This returns the size of bitmap that should be used to display
903 * the entire page at the given zoom factor. A zoom of 1.0
904 * corresponds to 90 dpi.
905 *
906 * @param page the page object
907 * @param zoom the zoom factor
908 * @param pWidth address for return of the width
909 * @param pHeight address for return of the height
910 *
911 * @return error indication - 0 for success
912 */
913 MuError MuOfficePage_getSizeForZoom( MuOfficePage *page,
914 float zoom,
915 int *pWidth,
916 int *pHeight)
917 {
918 MuOfficeDoc *doc;
919 fz_rect rect;
920 float w, h;
921
922 if (!page)
923 return MuError_BadNull;
924 doc = page->doc;
925 if (!doc)
926 return MuError_BadNull;
927
928 rect = fz_bound_page(doc->ctx, page->page);
929
930 /* MuPDF measures in points (72ths of an inch). This API wants
931 * 90ths of an inch, so adjust. */
932 w = 90 * (rect.x1 - rect.x0) / 72;
933 h = 90 * (rect.y1 - rect.y0) / 72;
934
935 if (pWidth)
936 *pWidth = (int)(w * zoom + 0.5f);
937 if (pHeight)
938 *pHeight = (int)(h * zoom + 0.5f);
939
940 return MuError_OK;
941 }
942
943 /**
944 * Perform MuPDF native operations on a given page.
945 *
946 * The function is called with fz_context and fz_page
947 * values that can be safely used (i.e. the context is
948 * cloned/dropped appropriately around the function, and
949 * locking is used to ensure that no other threads are
950 * simultaneously using the document). Functions can
951 * signal errors by fz_throw-ing.
952 *
953 * Due to the locking, it is best to ensure that as little
954 * time is taken here as possible (i.e. if you fetch some
955 * data and then spend a long time processing it, it is
956 * probably best to fetch the data using MuOfficePage_run
957 * and then process it outside). This avoids potentially
958 * blocking the UI.
959 *
960 * @param page the page object.
961 * @param fn the function to call with fz_context/fz_document
962 * values.
963 * @param arg Opaque data pointer.
964 *
965 * @return error indication - 0 for success
966 */
967 MuError MuOfficePage_run(MuOfficePage *page, void (*fn)(fz_context *ctx, fz_page *page, void *arg), void *arg)
968 {
969 fz_context *ctx;
970 MuError err = MuError_OK;
971
972 if (page == NULL)
973 return MuError_BadNull;
974 if (fn == NULL)
975 return err;
976
977 ctx = fz_clone_context(page->doc->mu->ctx);
978 if (ctx == NULL)
979 return MuError_OOM;
980
981 muoffice_doc_lock(page->doc->mu);
982
983 fz_try(ctx)
984 fn(ctx, page->page, arg);
985 fz_catch(ctx)
986 err = MuError_Generic;
987
988 muoffice_doc_unlock(page->doc->mu);
989
990 fz_drop_context(ctx);
991
992 return err;
993 }
994
995 static void render_worker(void *arg)
996 {
997 MuOfficeRender *render = (MuOfficeRender *)arg;
998 MuOfficePage *page = render->page;
999 fz_context *ctx = fz_clone_context(page->doc->ctx);
1000 int err = 0;
1001 fz_pixmap *pixmap = NULL;
1002 fz_device *dev = NULL;
1003 float scalex;
1004 float scaley;
1005 fz_rect page_bounds;
1006 int locked = 0;
1007
1008 if (ctx == NULL)
1009 return;
1010
1011 fz_var(pixmap);
1012 fz_var(dev);
1013 fz_var(locked);
1014
1015 fz_try(ctx)
1016 {
1017 if (page->list == NULL)
1018 {
1019 muoffice_doc_lock(page->doc->mu);
1020 locked = 1;
1021 page->list = fz_new_display_list_from_page(ctx, page->page);
1022 locked = 0;
1023 muoffice_doc_unlock(page->doc->mu);
1024 }
1025 /* Make a pixmap from the bitmap */
1026 if (!render->area_valid)
1027 {
1028 render->area.renderArea.x = 0;
1029 render->area.renderArea.y = 0;
1030 render->area.renderArea.width = render->bitmap->width;
1031 render->area.renderArea.height = render->bitmap->height;
1032 }
1033 pixmap = fz_new_pixmap_with_data(ctx,
1034 fz_device_rgb(ctx),
1035 render->area.renderArea.width,
1036 render->area.renderArea.height,
1037 NULL,
1038 1,
1039 render->bitmap->lineSkip,
1040 ((unsigned char *)render->bitmap->memptr) +
1041 render->bitmap->lineSkip * ((int)render->area.renderArea.x + (int)render->area.origin.x) +
1042 4 * ((int)render->area.renderArea.y + (int)render->area.origin.y));
1043 /* Be a bit clever with the scaling to make sure we get
1044 * integer width/heights. First calculate the target
1045 * width/height. */
1046 page_bounds = fz_bound_page(ctx, render->page->page);
1047 scalex = (int)(90 * render->zoom * (page_bounds.x1 - page_bounds.x0) / 72 + 0.5f);
1048 scaley = (int)(90 * render->zoom * (page_bounds.y1 - page_bounds.y0) / 72 + 0.5f);
1049 /* Now calculate the actual scale factors required */
1050 scalex /= (page_bounds.x1 - page_bounds.x0);
1051 scaley /= (page_bounds.y1 - page_bounds.y0);
1052 /* Render the list */
1053 fz_clear_pixmap_with_value(ctx, pixmap, 0xFF);
1054 dev = fz_new_draw_device(ctx, fz_post_scale(fz_translate(-page_bounds.x0, -page_bounds.y0), scalex, scaley), pixmap);
1055 fz_run_display_list(ctx, page->list, dev, fz_identity, fz_infinite_rect, NULL);
1056 fz_close_device(ctx, dev);
1057 }
1058 fz_always(ctx)
1059 {
1060 fz_drop_pixmap(ctx, pixmap);
1061 fz_drop_device(ctx, dev);
1062 }
1063 fz_catch(ctx)
1064 {
1065 if (locked)
1066 muoffice_doc_unlock(page->doc->mu);
1067 err = MuError_Generic;
1068 goto fail;
1069 }
1070
1071 fail:
1072 if (render->progress)
1073 render->progress(render->cookie, err);
1074 render->error = err;
1075
1076 fz_drop_context(ctx);
1077 }
1078
1079 /**
1080 * Schedule the rendering of an area of document page to
1081 * an area of a bitmap.
1082 *
1083 * The alignment between page and bitmap is defined by specifying
1084 * document's origin within the bitmap, possibly either positive or
1085 * negative. A render object is returned via which the process can
1086 * be monitored or terminated.
1087 *
1088 * The progress function is called exactly once per render in either
1089 * the success or failure case.
1090 *
1091 * Note that, since a render object represents a running thread that
1092 * needs access to the page, document, and library objects, it is important
1093 * to call MuOfficeRender_destroy, not only before using or deallocating
1094 * the bitmap, but also before calling MuOfficePage_destroy, etc..
1095 *
1096 * @param page the page to render
1097 * @param zoom the zoom factor
1098 * @param bitmap the bitmap
1099 * @param area area to render
1100 * @param progressFn the progress callback function
1101 * @param cookie a pointer to pass to the callback function
1102 * @param pRender Address for return of the render object
1103 *
1104 * @return error indication - 0 for success
1105 */
1106 MuError MuOfficePage_render( MuOfficePage *page,
1107 float zoom,
1108 const MuOfficeBitmap *bitmap,
1109 const MuOfficeRenderArea *area,
1110 MuOfficeRenderProgressFn *progressFn,
1111 void *cookie,
1112 MuOfficeRender **pRender)
1113 {
1114 MuOfficeRender *render;
1115 MuOfficeDoc *doc;
1116 fz_context *ctx;
1117
1118 if (!pRender)
1119 return MuError_BadNull;
1120 *pRender = NULL;
1121 if (!page)
1122 return MuError_BadNull;
1123 doc = page->doc;
1124 ctx = doc->ctx;
1125
1126 render = Pal_Mem_calloc(1, sizeof(*render));
1127 if (render == NULL)
1128 return MuError_OOM;
1129
1130 render->page = page;
1131 render->zoom = zoom;
1132 render->bitmap = bitmap;
1133 if (area)
1134 {
1135 render->area = *area;
1136 render->area_valid = 1;
1137 }
1138 else
1139 {
1140 render->area_valid = 0;
1141 }
1142 render->progress = progressFn;
1143 render->cookie = cookie;
1144
1145 if (mu_create_thread(&render->thread, render_worker, render))
1146 {
1147 Pal_Mem_free(render);
1148 return MuError_OOM;
1149 }
1150
1151 *pRender = render;
1152
1153 return MuError_OK;
1154 }
1155
1156 /**
1157 * Destroy a render
1158 *
1159 * This call destroys a MuOfficeRender object, aborting any current
1160 * render.
1161 *
1162 * This call is intended to support an app dealing with a user quickly
1163 * flicking through document pages. A render may be scheduled but, before
1164 * completion, be found not to be needed. In that case the bitmap will
1165 * need to be reused, which requires any existing render to be aborted.
1166 * The call to MuOfficeRender_destroy will cut short the render and
1167 * allow the bitmap to be reused immediately.
1168 *
1169 * @note If an active render thread is destroyed, it will be aborted.
1170 * While fast, this is not an instant operation. For maximum
1171 * responsiveness, it is best to 'abort' as soon as you realise you
1172 * don't need the render, and to destroy when you get the callback.
1173 *
1174 * @param render The render object
1175 */
1176 void MuOfficeRender_destroy(MuOfficeRender *render)
1177 {
1178 if (!render)
1179 return;
1180
1181 MuOfficeRender_abort(render);
1182 mu_destroy_thread(&render->thread);
1183 Pal_Mem_free(render);
1184 }
1185
1186 /**
1187 * Abort a render
1188 *
1189 * This call aborts any rendering currently underway. The 'render
1190 * complete' callback (if any) given when the render was created will
1191 * still be called. If a render has completed, this call will have no
1192 * effect.
1193 *
1194 * This call will not block to wait for the render thread to stop, but
1195 * will cause it to stop as soon as it can in the background.
1196 *
1197 * @note It is important not to start any new render to the same bitmap
1198 * until the callback comes in (or until waitUntilComplete returns), as
1199 * otherwise you can have multiple renders drawing to the same bitmap
1200 * with unpredictable consequences.
1201 *
1202 * @param render The render object to abort
1203 */
1204 void MuOfficeRender_abort(MuOfficeRender *render)
1205 {
1206 if (render)
1207 render->mu_cookie.abort = 1;
1208 }
1209
1210 /**
1211 * Wait for a render to complete.
1212 *
1213 * This call will not return until rendering is complete, so on return
1214 * the bitmap will contain the page image (assuming the render didn't
1215 * run into an error condition) and will not be used further by any
1216 * background processing. Any error during rendering will be returned
1217 * from this function.
1218 *
1219 * This call may block the calling thread for a significant period of
1220 * time. To avoid blocking, supply a progress-monitoring callback
1221 * function to MuOfficePage_render.
1222 *
1223 * @param render The render object to destroy
1224 * @return render error condition - 0 for no error.
1225 */
1226 MuError MuOfficeRender_waitUntilComplete(MuOfficeRender *render)
1227 {
1228 if (!render)
1229 return MuError_OK;
1230
1231 mu_destroy_thread(&render->thread);
1232
1233 return render->error;
1234 }