-
Notifications
You must be signed in to change notification settings - Fork 1
/
hovacui.c
3976 lines (3586 loc) · 103 KB
/
hovacui.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* hovacui.c
*
* view a pdf document, autozooming to text
*/
/*
* todo:
*
* - search: regular expression; config for pattern=string
* - configuration files specific for the framebuffer and x11, passed as
* additional arguments to hovacui()
* .config/hovacui/{framebuffer.conf,x11.conf}
* - merge narrow textarea boxes with others, minimizing size increase
* - merge boxes with the same (or very similar) horizontal coordinates
* - bookmarks, with field() for creating and list() for going to
* - commandline option for initial position: -p page,box,scrollx,scrolly;
* any part can be empty, even page; separate functions for parsing and
* printing a struct position, from readcachefile
* - maintain the position when changing mode: choose a character that is close
* to the center of the screen before changing mode; afterward, select the
* box that contains it, set scrollx,scrolly so that the character is in the
* center of the screen; then adjust the scroll
* - save an arbitrary selection/order of pages to file, not just a range;
* allow moving when chop() is active;
*
* - keystroke 'O' for setting the current page as page 1, like -O;
* same by a entry in the main menu; or field for changing the page offset
* - printf format string for page number label, with total pages
* - make minwidth depend on the size of the letters
* - list() yes/no to confirm exit; disabled by config option
* - allow reloading during search and save
* - fail search and save if the document change during them
* - man page: compare with fbpdf and jfbview
* - field() for executing a shell command
* - info(), based on list(): filename, number of pages, page size, etc.
* - rotate
* - lines of previous and next scroll: where the top or bottom of the screen
* were before the last scroll, or will be after scrolling up or down
* - stack of windows; a window returns WINDOW_PREVIOUS to go back
* - cache the textarea list of pages already scanned
* - config opt "nolabel" for no label at all: skip the label part from draw()
* - multiple files, list()-based window; return WINDOW_FILE+n to tell main()
* which file to switch to; and/or have a field in struct output for the new
* file index or name
* - optionally, show opening error as a window instead of fprintf
* - allow for a password (field() or commandline)
* - last two require document() to show a black screen if no document is open
* - non-expert mode: every unassigned key calls WINDOW_MENU
* - clip window title in list()
* - history of searches
* - include images (in pdfrects.c)
* - key to reset viewmode and fit direction to initial values
* - history of positions
* - order of rectangles for right-to-left and top-to-bottom scripts
* (generalize sorting functions in pdfrects.c)
* - i18n
* - annotations and links:
* some key switches to anchor navigation mode, where keyup/keydown or n/p
* move to the next anchor (annotation or link) in the displayed part of the
* current textbox if any, otherwise they scroll as usual; this requires
* storing the current anchor both for its attribute (the text or the target)
* and for moving to the next; use movetoselected() with afterscreen=FALSE
* and current!=CURRENT_UNUSED for searching in the visible part of the
* current textbox; if search fails scroll, but then search again for an
* annotation or link in the new position
* - detect file changes via inotify; this requires the file descriptor to be
* passed to cairodevice->input(); return KEY_FILECHANGE; the pdf file may
* not being fully written at that point, which results in a sequence of
* opening failures before being able to read it again; the current method
* using SIGHUP is better, if there is some way to send a signal when the
* file change is completed
* - make the margins (what determine the destination rectangle) customizable
* by field() or configuration file
* - alternative to fit=none: wrap the lines that are longer than the minwidth
* - next/previous match does not work with fit=none; do not fix, cannot work
* in general (see below); it can however be done by the same system of the
* next or previous anchor used for annotations and links
* - prefer showing white area outside bounding box than outside page
* - thread for page rendering, so that the ui remains responsive even on
* complex pages; the page and the ui render on different surfaces, that are
* then copied to the output
* - thread for progress indicator: tells that the program is still working,
* and nothing is shown because rendering or saving is in progress
*
* regressions:
* - open a long document, open gotopage window, replace file with a short
* document, go to a page the previous document has but the new does not
*/
/*
* how the document is mapped onto the screen
* ------------------------------------------
*
* position->textarea->rect[position->box]
* the current textbox: the block of text that is focused (drawn in blue)
*
* position->viewbox
* the same rectangle, possibly enlarged to ensure the minimal width
*
* output->dest
* the area of the screen to use (all of it but a thin margin)
*
* the cairo transformation matrix is initially set so that position->viewbox
* (rectangle in the document) is mapped onto output->dest (rectangle in the
* screen), fitted by width and aligned to the top
*
* position->scrolly
* the shift applied to the document;
* used to move the viewbox up and down
*
* shifting by the scroll is done last; since transformations work as if
* applied to the source in reverse order, this is like first scrolling the
* document and then mapping position->viewbox onto the top of output->dest
*
* the textbox is at the center of the viewbox and is therefore shown centered
* on the screen; but position->scrollx and position->scrolly are adjusted to
* avoid parts outside of the bounding box being displayed, wasting screen
* space; in this case, the textbox is moved out of the center of the screen;
* this happens for example for the textboxes at the border of the page
*
* all of this is for horizontal fitting mode: vertical fitting mode fits the
* viewbox by height, but is otherwise the same; in both modes, the scroll is
* relative to the origin of the current viewbox
*/
/*
* search
* ------
*
* output->search
* the last searched string;
* needed to highlight the matches when switching pages
*
* output->forward
* whether to move forward or backward between search matches
*
* output->found
* the list of rectangles of the search matches in the current page
*
* a match is located by scanning the document from the current textbox to the
* last of the page, then in the following pages, counting the number of pages
* until reaching the number of pages in the document
*
* positionscan()
* the document is scanned by a temporary struct position; this function
* initializes it as a copy of the current position, copies it back as the
* current position (only if a match is found) and deallocates it
*
* movetoselected()
* beforescreen
* inscreen
* afterscreen
* move to the first or next match; the three arguments tell whether to
* consider matches that are before, inside or after the visible part of
* the current textbox; beforescreen implies inscreen
*
* the visible part of the current textbox is considered when looking for
* the first match ('/') but skipped when looking for the next ('n'); what
* over the visible part of the current textbox is initially ignored, and
* considered after switching to the following boxes or pages; afterscreen
* is always true for searching; it is intended for next-or-scroll moves:
* go to the next match if in the screen, otherwise scroll
*
* output->current
* if not CURRENT_UNUSED, matches are navigated one by one instead of a
* screenful at time; when the current match is in the visible part of the
* current textbox, the next match is the one following it (no matter
* where it is); otherwise, the next match is the first starting from the
* top of the visible part of the current textbox; the box that is
* searched in is the current textbox if beforescreen==FALSE
*/
/*
* note: fit=none
*
* fit=none is mostly an hack to allow for arbitrary zooming and moving in the
* page like regular pdf viewers
*
* it is implemented in the current framework by allowing the viewbox to be
* narrower than the textbox, instead of always wider or equally wide; since
* the viewbox is mapped to the screen, this allows the screen to show only a
* part of the width of the textbox instead of its full width
*
* this requires toptextbox() to decrease the scroll: a zero scroll would just
* map the viewbox to the screen, but now the viewbox is somewhere in the
* middle of the textbox; while its upper left corner should be shown instead;
* the same for bottomtextbox()
*
* the current method for finding the next or previous search match cannot work
* in general: in the other fit modes, the next match in the current textbox is
* the first match below or on the right of the portion of the textbox that is
* currently shown; with fit=none, that portion may be the middle of the
* textbox; depending on how the next match is searched for, two matches in the
* lower left corner and the upper right corner may be each the next of the
* other, or none the next of the other
*/
/*
* note: reload
*
* a file is reloaded in the main loop whenever cairoui->reload=TRUE
*
* this field is set in:
*
* - document(), upon receiving keystroke 'r'
* - external(), upon receiving the external command "reload"
* - draw() or textarea() if a file change is detected (see below)
* - keyscript(), if the script requests a reload
*
* file changes are detected via poppler_document_get_id(), but this only works
* after trying to render the document; this is why detection is done in draw()
*
* this means that the document is automatically reloaded when switching into
* the virtual terminal and when moving in the document; it is not reloaded
* otherwise, in particular it is not reloaded when a window is active (unless
* it requests a refresh)
*
* since the newly opened document may have fewer pages than the previous,
* checking POPPLER_IS_PAGE(position->page) is always necessary before
* accessing the page (reading the textarea or drawing the page); if the page
* does not exist, *output->reload is set; it points to cairoui->reload
*
* output->nextfile
* when the script requests loading another document, it also gives the
* position in there; moving cannot be done before loading; the string
* containing the position is stored by keyscript() in output->nextfile
* and parsed by reloadpdf() after loading
*/
/*
* note: the minwidth
*
* the minwidth avoids textboxes being zoomed too much; without it, a narrow
* textbox such as a page number would render too large
*
* precisely, the minwidth is the minimal width of a textbox that is zoomed to
* fit the width of the destination box; a narrower texbox is mapped to a box
* that is proportionately narrower than the destination box
*
* for example, the default 400 means that a textbox in the document that is
* wide 400 pdf points is zoomed to fit to the width of the destination box,
* and that narrower boxes are zoomed the same and therefore take less than the
* width of the destination box
*
* in x11 the window (destination box) may be small or large; in a small window
* even a narrow box may need to be zoomed to the window width to be readable;
* for this reason, the minwidth is reduced in proportion to the window size;
* thanks to this multiplication, enlarging a small window the current textbox
* is enlarged, but at at some points the zoom stops increasing
*/
/*
* note: the page offset
*
* pages are numbered from 0 to numpages-1 in poppler but the page numbers as
* printed in them go from 1 up; prefaces, tables of content and other material
* may make page 1 not the first in the document; files that are cut from
* longer documents may begin with a page number larger than one
*
* this creates a user interface misaligment; for example, the expected result
* of jumping to page 20 is to see the page with "20" printed at the bottom,
* not "12" or "24", even if the page marked "12" or "24" is the twenty-th page
* of the document
*
* for this reason, pages in the user interface are as expected by the user;
* they start with page 1, not 0; this is the default, it can be changed by the
* -O and -F options
*
* internally, page numbers are always stored as poppler pages, from 0 to
* numpages-1; they are translated only when shown to the user or input from
* the user; the output structure contains an offset field that stores the
* difference; conversion is done by pagepdftoui() and pageuitopdf()
*
* a similar conversion is also done for internal references in
* movetonameddestination(), as destinations in pdf always use the
* 1-to-numpages numbering
*/
/*
* note: step-by-step operations
*
* some operations like searching and saving a range of pages may take long,
* and the interface looks dead meanwhile; this is avoided by turning the loop
* on pages into a page-by-page sequence of calls, returning to the main loop
* at each iteration to display progress and to check input for a request to
* abort (escape key)
*
* if the operation is done by a function, the window looks like this:
*
* int window(int c, struct cairoui *cairoui) {
* ...
*
* init;
*
* res = template(c, cairoui, ...);
* ...
* if (res == ... || c == ...) {
* pre;
* o = function(...);
* post;
* return WINDOW_DOCUMENT;
* }
* ...
* }
*
* its step-by-step version turns function() into a sort of window, called
* repeatedly until the operation completes; it is not called from the main
* loop but from this window, which acts as a passthrough to function() until
* finished; since function() is called from this window and not from the main
* loop, the data it needs is just passed to it rather than being stored in the
* output structure
*
* int window(int c, struct cairoui *cairoui) {
* ...
* static gboolean iterating = FALSE;
* static int res, savec;
* int currc;
*
* if (iterating) {
* currc = c;
* c = savec;
* if (currc == KEY_REFRESH)
* template(KEY_REFRESH, cairoui, ...)
* }
* else {
* init;
* res = template(c, cairoui, ...);
* }
* ...
* if (res == ... || c == ...) {
* if (! iterating) {
* pre;
* iterating = TRUE;
* savec = c;
* currc = KEY_INIT;
* }
* o = function(currc, cairoui, ...);
* if (! CAIROUI_OUT(o))
* return WINDOW_THIS;
* if (currc == KEY_FINISH)
* iterating = FALSE;
* post;
* return WINDOW_DOCUMENT;
* }
* ...
* }
*
* if c is not used in the original window after calling template(c, ...), the
* variables savec and currc and their assigments can be omitted and c used in
* place of currc; the call template(KEY_REFRESH, ...) is only present if the
* window is to be shown during iteration, and requires the window to return
* CAIROUI_REFRESH instead of WINDOW_THIS when redrawing is necessary; this may
* be the case for the first iteration to remove the existing labels
*
* function() is no longer a simple function but a window; it stores its data
* between calls; for example, savepdf() is (simplified):
*
* int savepdf(int c, struct cairoui *cairoui, ...) {
* static int page;
*
* if (c == KEY_INIT) {
* open file
* create cairo surface and context
* page = first;
* cairoui->timeout = 0;
* return CAIROUI_CHANGED;
* }
*
* if (c == '\033')
* return CAIROUI_LEAVE;
*
* if (c != KEY_FINISH) {
* save page
* page++
* cairoui->timeout = 0;
* return page <= last ? CAIROUI_CHANGED : CAIROUI_DONE;
* }
*
* deallocate surface and cairo context
* close file
* if (page <= last) {
* delete file
* return CAIROUI_LEAVE;
* }
* else {
* postsave
* return CAIROUI_DONE;
* }
* }
*
* always perform a single operation at time; in case of error do not jump to
* the final part, but return CAIROUI_FAIL and wait for the final call with
* KEY_FINISH to finalize (close the file, etc) and report the error; use a
* static variable to remember this condition and possibly the cause of error
*
* if something is printed in the help label, cairoui->redraw=TRUE removes the
* previous label; this requires redrawing the page, so it is best to avoid
* doing it at each step; a better choice is to do it only for the first
* iteration and to use labels that covers the previous during the steps;
* cairoui->timeout=0 is necessary when returning to the main loop between
* steps (that is, not at the end); it makes function() to be called again
* immediately after checking input and redrawing the labels
*
* the search() and next() windows use gotopagematch() as their step function,
* with its nsearched parameter as the key (0=init, -1=finish, otherwise>0);
* their nsearched static variable stores the number of pages searched so far;
* since the next() window is always iterating it does not need a boolean
* variabile to tell whether it is
*/
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include <poppler.h>
#include <cairo.h>
#include <cairo-pdf.h>
#include "pdfrects.h"
#include "cairoio.h"
#include "cairoui.h"
#include "hovacui.h"
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#undef clear
/*
* name of the program
*/
#define HOVACUI "hovacui"
/*
* output parameters
*/
struct output {
/* the cairo surface */
cairo_t *cr;
/* the destination rectangle */
PopplerRectangle dest;
/* the size of the whole screen */
double screenwidth;
double screenheight;
double screenaspect;
/* the pixel aspect */
double aspect;
/* night mode */
int night;
/* the minimal textbox-to-textbox distance */
int distance;
/* the minimal width; tells the maximal zoom */
int minwidth;
/* zoom to: 0=text, 1=boundingbox, 2=page */
int viewmode;
/* fit horizontally (1), vertically (2) or both (3) */
int fit;
/* sorting algorithm */
int order;
/* scroll distance, as a fraction of the screen size */
double scroll;
/* page offset */
int offset;
/* show the ui (menu and help) */
int ui;
/* apply the changes immediately from the ui */
int immediate;
/* draw draw the textbox and page box */
int drawbox;
/* show the page number when it changes */
int pagelabel;
/* reload, or load a new file */
int *reload;
/* position and name of the new file to load */
char *nextfile;
/* labels */
gboolean pagenumber;
gboolean totalpages;
gboolean showclock;
gboolean showmode;
gboolean showfit;
gboolean filename;
char help[80];
/* search */
char search[100];
char prevsearch[100];
gboolean forward;
GList *found;
int current;
/* selection */
GList *selection;
double texfudge;
/* pdf output */
char *pdfout;
int first, last;
char *postsave;
/* external script */
char *keys;
char *script;
cairo_rectangle_t *rectangle;
/* enable the cache file */
gboolean cachefile;
};
/*
* a position within a document
*/
struct position {
/* the poppler document */
char *filename;
PopplerDocument *doc;
gchar *permanent_id, *update_id;
/* the current page, its bounding box, the total number of pages */
int npage, totpages;
PopplerPage *page;
PopplerRectangle *boundingbox;
/* the text rectangle currently viewed, all of them in the page */
RectangleList *textarea;
int box;
PopplerRectangle *viewbox;
/* how much the viewbox is moved before being displayed */
double scrollx;
double scrolly;
};
/*
* the callback data structure
*/
struct callback {
struct output *output;
struct position *position;
struct position *previous;
};
#define POSITION(cairoui) (((struct callback *) (cairoui)->cb)->position)
#define PREVIOUS(cairoui) (((struct callback *) (cairoui)->cb)->previous)
#define OUTPUT(cairoui) (((struct callback *) (cairoui)->cb)->output)
/*
* initialize a position
*/
void initposition(struct position *position) {
position->npage = 0;
position->page = NULL;
position->boundingbox = NULL;
position->textarea = NULL;
position->box = 0;
position->viewbox = NULL;
position->scrollx = 0;
position->scrolly = 0;
}
/*
* copy a position
*/
void copyposition(struct position *destination, struct position *source) {
destination->npage = source->npage;
destination->box = source->box;
destination->scrollx = source->scrollx;
destination->scrolly = source->scrolly;
}
/*
* swap positions
*/
void swapposition(struct position *one, struct position *two) {
struct position swap;
copyposition(&swap, one);
copyposition(one, two);
copyposition(two, &swap);
}
/*
* current match
*/
#define CURRENT_UNUSED (-2)
#define CURRENT_NONE (-1)
void setcurrent(int *current, int value) {
if (*current != CURRENT_UNUSED)
*current = value;
}
/*
* convert between pdf internal page number and page number in the interface
*/
int pagepdftoui(struct output *output, int page) {
return page - output->offset + 2;
}
int pageuitopdf(struct output *output, int page) {
return page + output->offset - 2;
}
/*
* initial page number
*/
void initpage(struct position *position, int npage) {
position->npage = npage < 0 ? 0 :
npage >= position->totpages ? position->totpages - 1 : npage;
}
/*
* a rectangle as large as the whole page
*/
PopplerRectangle *pagerectangle(PopplerPage *page) {
PopplerRectangle *r;
r = poppler_rectangle_new();
r->x1 = 0;
r->y1 = 0;
poppler_page_get_size(page, &r->x2, &r->y2);
return r;
}
/*
* free a glist of rectangles
*/
void freeglistrectangles(GList *list) {
GList *l;
for (l = list; l != NULL; l = l->next)
poppler_rectangle_free((PopplerRectangle *) l->data);
g_list_free(list);
}
/*
* find the matches rectangles in the page
*/
int pagematch(struct position *position, struct output *output) {
freeglistrectangles(output->found);
output->found = output->search[0] == '\0' ?
NULL : poppler_page_find_text(position->page, output->search);
return 0;
}
/*
* read the current page without its textarea
*/
int readpageraw(struct position *position, struct output *output) {
g_clear_object(&position->page);
position->page =
poppler_document_get_page(position->doc, position->npage);
pagematch(position, output);
freeglistrectangles(output->selection);
output->selection = NULL;
setcurrent(&output->current, CURRENT_NONE);
return 0;
}
/*
* how much the page is fragmented in the boxes of its textarea
*/
double fragmented(struct position *position) {
RectangleList *rl;
int i;
double width, height, index;
rl = position->textarea;
width = position->boundingbox->x2 - position->boundingbox->x1;
height = position->boundingbox->y2 - position->boundingbox->y1;
index = 0;
for (i = 0; i < rl->num; i++) {
if (rectangle_width(&rl->rect[i]) < width / 6)
index++;
if (rectangle_height(&rl->rect[i]) < height / 6)
index++;
}
return index / rl->num;
}
/*
* how much the boxes of the text area overlap horizontally, if they do
*/
double interoverlap(struct position *position) {
RectangleList *ve, *ta;
int i, j;
double height, index;
ta = position->textarea;
ve = rectanglelist_vextents(ta);
height = rectanglelist_sumheight(ve);
// height = position->boundingbox->y2 - position->boundingbox->y1;
rectanglelist_free(ve);
index = 0;
for (i = 0; i < ta->num; i++)
for (j = 0; j < ta->num; j++)
if (! rectangle_htouch(&ta->rect[i], &ta->rect[j]))
index +=
(ta->rect[i].y2 - ta->rect[i].y1) /
height *
(ta->rect[j].y2 - ta->rect[j].y1) /
height;
return index;
}
/*
* determine the textarea of the current page
*/
int textarea(struct position *position, struct output *output) {
void (*order[])(RectangleList *, PopplerPage *) = {
rectanglelist_quicksort,
rectanglelist_twosort,
rectanglelist_charsort
};
double overlap, frag;
if (! POPPLER_IS_PAGE(position->page)) {
*output->reload = TRUE;
return -1;
}
rectanglelist_free(position->textarea);
poppler_rectangle_free(position->boundingbox);
switch (output->viewmode) {
case 0:
case 1:
position->textarea =
rectanglelist_textarea_distance(position->page,
output->distance);
if (position->textarea->num == 0) {
rectanglelist_free(position->textarea);
position->textarea = NULL;
position->boundingbox = NULL;
break;
}
position->boundingbox =
rectanglelist_joinall(position->textarea);
overlap = interoverlap(position);
frag = fragmented(position);
// ensureoutputfile(output);
// fprintf(output->outfile, "auto: %g %g\n", overlap, frag);
if (output->viewmode == 0 && (overlap < 0.8 || frag > 1.0)) {
rectanglelist_free(position->textarea);
position->textarea = NULL;
break;
}
order[output->order](position->textarea, position->page);
break;
case 2:
#if POPPLER_CHECK_VERSION(0, 90, 0)
position->boundingbox = poppler_rectangle_new();
poppler_page_get_bounding_box(position->page,
position->boundingbox);
#else
position->boundingbox =
rectanglelist_boundingbox(position->page);
#endif
position->textarea = NULL;
break;
case 3:
position->boundingbox = pagerectangle(position->page);
position->textarea = NULL;
break;
}
if (position->boundingbox == NULL)
position->boundingbox = pagerectangle(position->page);
if (position->textarea == NULL) {
position->textarea = rectanglelist_new(1);
rectanglelist_add(position->textarea, position->boundingbox);
}
return 0;
}
/*
* read the current page
*/
int readpage(struct position *position, struct output *output) {
readpageraw(position, output);
textarea(position, output);
return 0;
}
/*
* translate from textbox coordinates to screen coordinates and back
*/
double xdoctoscreen(struct output *output, double x) {
double xx = x, yy = 0.0;
cairo_user_to_device(output->cr, &xx, &yy);
return xx;
}
double xscreentodoc(struct output *output, double x) {
double xx = x, yy = 0.0;
cairo_device_to_user(output->cr, &xx, &yy);
return xx;
}
double ydoctoscreen(struct output *output, double y) {
double xx = 0.0, yy = y;
cairo_user_to_device(output->cr, &xx, &yy);
return yy;
}
double yscreentodoc(struct output *output, double y) {
double xx = 0.0, yy = y;
cairo_device_to_user(output->cr, &xx, &yy);
return yy;
}
/*
* translate distances from screen coordinates to textbox coordinates
*/
double xscreentodocdistance(struct output *output, double x) {
double xx = x, yy = 0.0;
cairo_device_to_user_distance(output->cr, &xx, &yy);
return xx;
}
double yscreentodocdistance(struct output *output, double y) {
double xx = 0.0, yy = y;
cairo_device_to_user_distance(output->cr, &xx, &yy);
return yy;
}
/*
* translate a rectangle from textbox coordindates to screen and back
*/
void rscreentodoc(struct output *output,
PopplerRectangle *dst, PopplerRectangle *src) {
dst->x1 = xscreentodoc(output, src->x1);
dst->y1 = yscreentodoc(output, src->y1);
dst->x2 = xscreentodoc(output, src->x2);
dst->y2 = yscreentodoc(output, src->y2);
}
/*
* size of destination rectangle translated to document coordinates
*/
double xdestsizetodoc(struct output *output) {
return xscreentodocdistance(output, output->dest.x2 - output->dest.x1);
}
double ydestsizetodoc(struct output *output) {
return yscreentodocdistance(output, output->dest.y2 - output->dest.y1);
}
/*
* change scrollx and scrolly to avoid space outside boundingbox being shown
*/
int adjustscroll(struct position *position, struct output *output) {
/* some space at the right of the bounding box is shown */
if (xdoctoscreen(output, position->boundingbox->x2 - position->scrollx)
< output->dest.x2)
position->scrollx = position->boundingbox->x2 -
xscreentodoc(output, output->dest.x2);
/* some space at the left of the bounding box is shown */
if (xdoctoscreen(output, position->boundingbox->x1 - position->scrollx)
> output->dest.x1)
position->scrollx = position->boundingbox->x1 -
xscreentodoc(output, output->dest.x1);
/* bounding box too narrow to fill the screen */
if (position->boundingbox->x2 - position->boundingbox->x1 <
xscreentodocdistance(output, output->dest.x2 - output->dest.x1))
position->scrollx =
(position->boundingbox->x1 +
position->boundingbox->x2) / 2 -
xscreentodoc(output,
(output->dest.x1 + output->dest.x2) / 2);
/* some space below the bounding box is shown */
if (ydoctoscreen(output, position->boundingbox->y2 - position->scrolly)
< output->dest.y2)
position->scrolly = position->boundingbox->y2 -
yscreentodoc(output, output->dest.y2);
/* some space over the bounding box is shown */
if (ydoctoscreen(output, position->boundingbox->y1 - position->scrolly)
> output->dest.y1)
position->scrolly = position->boundingbox->y1 -
yscreentodoc(output, output->dest.y1);
/* bounding box too short to fill the screen */
if (position->boundingbox->y2 - position->boundingbox->y1 <
yscreentodocdistance(output, output->dest.y2 - output->dest.y1))
position->scrolly =
(position->boundingbox->y1 +
position->boundingbox->y2) / 2 -
yscreentodoc(output,
(output->dest.y1 + output->dest.y2) / 2);
return 0;
}
/*
* adjust width of viewbox to the minimum allowed
*/
void adjustviewbox(struct position *position, struct output *output) {
double d, minwidth, minheight;
PopplerRectangle *viewbox;
int fitmode;
fitmode = output->fit;
viewbox = position->viewbox;
minwidth = xscreentodocdistance(output, output->minwidth *
(output->dest.x2 - output->dest.x1) / output->screenwidth);
minheight = yscreentodocdistance(output, output->minwidth *
(output->dest.y2 - output->dest.y1) / output->screenheight);
if (fitmode == 0 ||
(fitmode & 0x1 && viewbox->x2 - viewbox->x1 < minwidth)) {
d = minwidth - viewbox->x2 + viewbox->x1;
viewbox->x1 -= d / 2;
viewbox->x2 += d / 2;
}
if (fitmode == 0 ||
(fitmode & 0x2 && viewbox->y2 - viewbox->y1 < minheight)) {
d = minheight - viewbox->y2 + viewbox->y1;
viewbox->y1 -= d / 2;
viewbox->y2 += d / 2;
}
}
/*
* move to position
*/
void moveto(struct position *position, struct output *output) {
PopplerRectangle scaled;
cairo_identity_matrix(output->cr);
/* scale to match the screen aspect; also scale the destination
* rectangle in the opposite way, so that position->viewbox is still
* mapped to output->dest in spite of the scaling */
scaled = output->dest;
if (output->fit & 0x1) {
cairo_scale(output->cr, 1.0, output->aspect);
scaled.y1 = scaled.y1 / output->aspect;
scaled.y2 = scaled.y2 / output->aspect;
}
else {
cairo_scale(output->cr, 1.0 / output->aspect, 1.0);
scaled.x1 = scaled.x1 * output->aspect;
scaled.x2 = scaled.x2 * output->aspect;
}
poppler_rectangle_free(position->viewbox);
position->viewbox = poppler_rectangle_copy
(&position->textarea->rect[position->box]);
adjustviewbox(position, output);
rectangle_map_to_cairo(output->cr, &scaled, position->viewbox,
output->fit == 1, output->fit == 2, TRUE, TRUE, TRUE);
adjustscroll(position, output);
cairo_translate(output->cr, -position->scrollx, -position->scrolly);
}
/*
* go to the top of current textbox
*/
int toptextbox(struct position *position, struct output *output) {
(void) output;
position->scrollx = 0;
position->scrolly = 0;
moveto(position, output);
/* scrolling moves the origin of the viewbox; scrolling to zero places
* the top left corner of the viewbox at the top left corner of the
* screen; this is usually correct, as it makes the textbox centered on
* the screen (the textboxes at the border are then repositioned by
* adjustscroll() to avoid empty space being shown); but when fit=none,
* the viewbox may be smaller than the textbox; scrolling to zero would
* show the middle of the textbox instead of its upper left corner */
position->scrollx = MIN(0,
position->textarea->rect[position->box].x1 -
position->viewbox->x1);
position->scrolly = MIN(0,
position->textarea->rect[position->box].y1 -
position->viewbox->y1);
return 0;
}
/*