Subversion Repositories Kolibri OS

Rev

Rev 9896 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
9896 akron1 1
(*
9898 akron1 2
    Copyright 2020, 2022, 2023 Anton Krotov
9896 akron1 3
 
4
    This file is part of fb2read.
5
 
6
    fb2read is free software: you can redistribute it and/or modify
7
    it under the terms of the GNU General Public License as published by
8
    the Free Software Foundation, either version 3 of the License, or
9
    (at your option) any later version.
10
 
11
    fb2read is distributed in the hope that it will be useful,
12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
    GNU General Public License for more details.
15
 
16
    You should have received a copy of the GNU General Public License
17
    along with fb2read. If not, see .
18
*)
19
 
20
MODULE Search;
21
 
22
IMPORT
23
 
24
    XML, G := Graph, Window, Font, S := Strings, LISTS, SYSTEM,
9898 akron1 25
    SU := SysUtils, K := KOSAPI, SearchForm, Encoding;
9896 akron1 26
 
27
 
28
TYPE
29
 
30
    STRING* = SearchForm.STRING;
31
 
32
    PFind = PROCEDURE (d: INTEGER);
33
 
34
    TRect = POINTER TO RECORD (LISTS.ITEM)
35
 
36
        x1, y1, x2, y2: INTEGER
37
 
38
    END;
39
 
40
    TPos = POINTER TO RECORD (LISTS.ITEM)
41
 
42
        n, first, last: INTEGER;
43
        RectList: LISTS.LIST
44
 
45
    END;
46
 
47
    TextIdx = POINTER TO RECORD
48
 
49
        cnt, offs: ARRAY 256 OF INTEGER;
50
        table: INTEGER;
51
        data, size: INTEGER
52
 
53
    END;
54
 
55
    Text = POINTER TO RECORD (LISTS.ITEM)
56
 
57
        case: BOOLEAN;
58
        idx0, idx1: TextIdx;
59
        str0, str1: STRING;
60
        PosList: LISTS.LIST;
61
        curPos: TPos;
62
        found: INTEGER;
63
        body: XML.TAG
64
 
65
    END;
66
 
67
 
68
VAR
69
 
70
    TextList: LISTS.LIST;
71
    Body: XML.TAG;
72
    Find: PFind;
73
 
74
 
9898 akron1 75
PROCEDURE SelText (Col: Window.tRect; min, max, Ycur, LineH: INTEGER; right: BOOLEAN; rect: TRect; cur: BOOLEAN);
9896 akron1 76
VAR
77
    y, y0, color: INTEGER;
78
 
79
BEGIN
80
    y  := rect.y1 - Ycur;
81
    y0 := y - y MOD LineH;
82
    IF (min <= y0) & (y0 <= max) THEN
83
        IF cur THEN
84
            color := 0FF0000H
85
        ELSE
86
            color := 0
87
        END;
9898 akron1 88
        G.BoxNotXOR(Col.left + rect.x1 + 1, Col.top + y - Col.height * ORD(right), Col.left + rect.x2, Col.top + y - Col.height * ORD(right) + Font.FontH(), color)
9896 akron1 89
    END
90
END SelText;
91
 
92
 
9898 akron1 93
PROCEDURE draw* (body: XML.TAG; ColLeft, ColRight: Window.tRect; Ycur, LineH: INTEGER; TwoCol: BOOLEAN);
9896 akron1 94
VAR
95
    rect: TRect;
96
    pos, cur: TPos;
97
 
98
BEGIN
99
    Body := body;
100
    IF body.text # NIL THEN
101
        pos := body.text(Text).PosList.first(TPos);
102
        cur := body.text(Text).curPos
103
    ELSE
104
        pos := NIL;
105
        cur := NIL
106
    END;
107
    WHILE pos # NIL DO
108
        rect := pos.RectList.first(TRect);
109
        WHILE rect # NIL DO
9898 akron1 110
            SelText(ColLeft, 0, ColLeft.height - LineH, Ycur, LineH, FALSE, rect, pos = cur);
9896 akron1 111
            IF TwoCol THEN
9898 akron1 112
                SelText(ColRight, ColLeft.height, ColLeft.height + ColRight.height - LineH, Ycur, LineH, TRUE, rect, pos = cur)
9896 akron1 113
            END;
114
            rect := rect.next(TRect)
115
        END;
116
        pos := pos.next(TPos)
117
    END
118
END draw;
119
 
120
 
121
PROCEDURE textlen (body: XML.ELEMENT; VAR length: INTEGER);
122
VAR
123
    cur: XML.ELEMENT;
124
 
125
BEGIN
126
    cur := body;
127
    WHILE (cur # NIL) DO
128
        IF cur IS XML.TAG THEN
129
            textlen(cur(XML.TAG).child.first, length)
130
        ELSIF cur IS XML.WORD THEN
131
            INC(length, cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1)
132
        ELSIF cur IS XML.SPACE THEN
133
            INC(length)
134
        END;
135
        cur := cur.next
136
    END
137
END textlen;
138
 
139
 
140
PROCEDURE puttext (body: XML.ELEMENT; VAR buf: INTEGER);
141
VAR
142
    cur: XML.ELEMENT;
143
    len: INTEGER;
144
 
145
BEGIN
146
    cur := body;
147
    WHILE (cur # NIL) DO
148
        IF cur IS XML.TAG THEN
149
            puttext(cur(XML.TAG).child.first, buf)
150
        ELSIF cur IS XML.WORD THEN
151
            len := cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1;
152
            SYSTEM.MOVE(cur(XML.WORD).value.first, buf, len);
153
            INC(buf, len)
154
        ELSIF cur IS XML.SPACE THEN
155
            SYSTEM.PUT(buf, 20X);
156
            INC(buf)
157
        END;
158
        cur := cur.next
159
    END
160
END puttext;
161
 
162
 
163
PROCEDURE cap (code: INTEGER): INTEGER;
164
BEGIN
165
    CASE code OF
166
    |61H..7AH, 430H..44FH:
167
        DEC(code, 32)
168
    |451H..45FH:
169
        DEC(code, 80)
170
    |491H:
171
        code := 490H
172
    ELSE
173
    END
174
    RETURN code
175
END cap;
176
 
177
 
9898 akron1 178
PROCEDURE upcase (src, dst, length: INTEGER);
9896 akron1 179
VAR
9898 akron1 180
    n: INTEGER;
181
    u: Encoding.tUtf8;
9896 akron1 182
BEGIN
183
    WHILE length > 0 DO
9898 akron1 184
        Encoding.utf8(cap(Encoding.getUtf8Char(src, length)), u);
9896 akron1 185
        n := LENGTH(u);
9898 akron1 186
        SYSTEM.MOVE(SYSTEM.ADR(u[0]), dst, n);
187
        INC(dst, n)
9896 akron1 188
    END
9898 akron1 189
END upcase;
9896 akron1 190
 
191
 
192
PROCEDURE create (body: XML.ELEMENT);
193
VAR
194
    length, buf, buf1, temp: INTEGER;
195
    text: Text;
196
    xml: XML.ELEMENT;
197
 
198
 
199
    PROCEDURE index (idx: TextIdx; buf, length: INTEGER);
200
    VAR
201
        i: INTEGER;
202
        c: CHAR;
203
        offs, temp: INTEGER;
204
 
205
    BEGIN
206
        idx.data := buf;
207
        idx.size := length;
208
 
209
        FOR i := 0 TO 255 DO
210
            idx.offs[i] := 0;
211
            idx.cnt[i] := 0
212
        END;
213
 
214
        i := length;
215
 
216
        WHILE i > 0 DO
217
            SYSTEM.GET(buf, c);
218
            INC(idx.offs[ORD(c)]);
219
            DEC(i);
220
            INC(buf)
221
        END;
222
 
223
        offs := 0;
224
 
225
        FOR i := 0 TO 255 DO
226
            temp := offs;
227
            INC(offs, idx.offs[i]);
228
            idx.offs[i] := temp * 4
229
        END;
230
 
231
        idx.table := K.malloc(offs * 4);
232
        SU.MemError(idx.table = 0);
233
 
234
        i := length;
235
        buf := idx.data;
236
 
237
        WHILE i > 0 DO
238
            SYSTEM.GET(buf, c);
239
            SYSTEM.PUT(idx.table + idx.offs[ORD(c)] + idx.cnt[ORD(c)] * 4, length - i);
240
            INC(idx.cnt[ORD(c)]);
241
            DEC(i);
242
            INC(buf)
243
        END
244
    END index;
245
 
246
 
247
BEGIN
248
    NEW(text);
249
    text.body := body(XML.TAG);
250
    text.PosList := LISTS.create(NIL);
251
 
252
    xml := body;
253
    body := body(XML.TAG).child.first;
254
    textlen(body, length);
255
    buf := K.malloc(length);
256
    SU.MemError(buf = 0);
257
    temp := buf;
258
    puttext(body, temp);
259
 
260
    NEW(text.idx0);
261
    index(text.idx0, buf, length);
262
 
263
    buf1 := K.malloc(length);
264
    SU.MemError(buf1 = 0);
265
 
9898 akron1 266
    upcase(buf, buf1, length);
9896 akron1 267
 
268
    NEW(text.idx1);
269
    index(text.idx1, buf1, text.idx0.size);
270
 
271
    text.case := FALSE;
272
 
273
    text.str0 := "";
274
    text.str1 := "";
275
    xml(XML.TAG).text := text;
276
    LISTS.push(TextList, text)
277
END create;
278
 
279
 
280
PROCEDURE select (body: XML.ELEMENT; VAR pos: TPos; VAR curpos, strong, italic, code: INTEGER);
281
VAR
282
    cur   : XML.ELEMENT;
283
    word  : XML.WORD;
284
    space : XML.SPACE;
285
 
286
    tag_value, len, wbeg, wend, selbeg, selend,
287
    a, b, z, x, w: INTEGER;
288
 
289
 
290
    PROCEDURE New (RectList: LISTS.LIST; x1, y1, x2, y2: INTEGER);
291
    VAR rect: TRect;
292
    BEGIN
293
        NEW(rect);
294
        rect.x1 := x1; rect.y1 := y1;
295
        rect.x2 := x2; rect.y2 := y2;
296
        LISTS.push(RectList, rect)
297
    END New;
298
 
299
 
300
BEGIN
301
    cur := body;
302
    WHILE (cur # NIL) & (pos # NIL) DO
303
        selbeg := pos.first;
304
        selend := pos.last;
305
        IF cur IS XML.TAG THEN
306
            tag_value := cur(XML.TAG).value;
307
 
308
            CASE tag_value OF
309
            |XML.tag_title, XML.tag_strong, XML.tag_th:
310
                INC(strong);
311
                Font.Bold(TRUE)
312
            |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
313
                INC(italic);
314
                Font.Italic(TRUE, FALSE)
315
            |XML.tag_code:
316
                Font.sysfont(TRUE);
317
                INC(code)
318
            ELSE
319
            END;
320
 
321
            select(cur(XML.TAG).child.first, pos, curpos, strong, italic, code);
322
 
323
            CASE tag_value OF
324
            |XML.tag_title, XML.tag_strong, XML.tag_th, XML.tag_text_author, XML.tag_date:
325
                DEC(strong);
326
                Font.Bold(strong > 0)
327
            |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
328
                DEC(italic);
329
                Font.Italic(italic > 0, FALSE)
330
            |XML.tag_code:
331
                DEC(code);
332
                Font.sysfont(code > 0)
333
            ELSE
334
            END;
335
 
336
            IF pos # NIL THEN
337
                selbeg := pos.first;
338
                selend := pos.last
339
            END
340
        ELSIF cur IS XML.WORD THEN
341
            word := cur(XML.WORD);
342
            len := word.value.last - word.value.first + 1;
343
            wbeg := curpos;
344
            wend := curpos + len - 1;
345
            INC(curpos, len);
346
 
347
            a := MAX(wbeg, selbeg);
348
            b := MIN(wend, selend);
349
 
350
            IF b >= a THEN
351
                x := word.width;
352
                IF (a = wbeg) & (b = wend) THEN
353
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
354
                ELSIF (a = selbeg) & (b = wend) THEN
355
                    z := selbeg - wbeg;
356
                    INC(word.value.first, z);
357
                    word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
358
                    INC(word.X, x - word.width);
359
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
360
                    DEC(word.value.first, z);
361
                    DEC(word.X, x - word.width)
362
                ELSIF (a = wbeg) & (b = selend) THEN
363
                    z := wend - selend;
364
                    DEC(word.value.last, z);
365
                    word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
366
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
367
                    INC(word.value.last, z)
368
                ELSIF (a = selbeg) & (b = selend) THEN
369
                    z := selbeg - wbeg;
370
                    w := wend - selend;
371
                    INC(word.value.first, z);
372
                    INC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
373
                    DEC(word.value.last, w);
374
                    word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
375
                    New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
376
                    INC(word.value.last, w);
377
                    DEC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
378
                    DEC(word.value.first, z)
379
                END;
380
                word.width := x
381
            END
382
        ELSIF cur IS XML.SPACE THEN
383
            IF (selbeg <= curpos) & (curpos <= selend) THEN
384
                space := cur(XML.SPACE);
385
                New(pos.RectList, space.X, space.Y, space.X + space.width, space.Y + Font.FontH())
386
            END;
387
            len := 1;
388
            INC(curpos)
389
        END;
390
        IF curpos > selend THEN
391
            IF pos # NIL THEN
392
                pos := pos.next(TPos);
393
            END;
394
            IF cur IS XML.TEXT THEN
395
                DEC(curpos, len)
396
            ELSE  (* tag *)
397
                cur := cur.next
398
            END
399
        ELSE
400
            cur := cur.next
401
        END
402
    END
403
END select;
404
 
405
 
406
PROCEDURE streq (s1, s2, n: INTEGER): BOOLEAN;
407
VAR
408
    c1, c2: CHAR;
409
 
410
BEGIN
411
    REPEAT
412
        SYSTEM.GET(s1, c1); INC(s1);
413
        SYSTEM.GET(s2, c2); INC(s2);
414
        DEC(n)
415
    UNTIL (n = 0) OR (c1 # c2)
416
 
417
    RETURN c1 = c2
418
END streq;
419
 
420
 
421
PROCEDURE destroy (VAR item: LISTS.ITEM);
422
BEGIN
423
    LISTS.destroy(item(TPos).RectList);
424
    DISPOSE(item)
425
END destroy;
426
 
427
 
428
PROCEDURE find (body: XML.TAG; str: STRING);
429
VAR
430
    c: CHAR;
431
    offs, i, pos, strong, italic, code: INTEGER;
432
    posItem: TPos;
433
    text: Text;
434
    pstr, slen: INTEGER;
435
    idx: TextIdx;
436
 
437
BEGIN
438
    text := body.text(Text);
439
    text.found := 0;
440
    LISTS.destroy(text.PosList);
441
    text.PosList := LISTS.create(NIL);
442
 
443
    text.str0 := str;
9898 akron1 444
    upcase(SYSTEM.ADR(str[0]), SYSTEM.ADR(text.str1[0]), LENGTH(str));
9896 akron1 445
 
446
    IF text.case THEN
447
        idx := text.idx0;
448
        pstr := SYSTEM.ADR(text.str0[0])
449
    ELSE
450
        idx := text.idx1;
451
        pstr := SYSTEM.ADR(text.str1[0])
452
    END;
453
 
454
    slen := LENGTH(str);
455
 
456
    SYSTEM.GET(pstr, c);
457
    offs := idx.offs[ORD(c)];
458
    i := idx.cnt[ORD(c)];
459
    WHILE i > 0 DO
460
        SYSTEM.GET(idx.table + offs, pos);
461
        INC(offs, 4);
462
        IF (pos + slen <= idx.size) & streq(pstr, idx.data + pos, slen) THEN
463
            NEW(posItem);
464
            posItem.n := text.found;
465
            posItem.first := pos;
466
            posItem.last := pos + slen - 1;
467
            posItem.RectList := LISTS.create(NIL);
468
            posItem.destroy := destroy;
469
            LISTS.push(text.PosList, posItem);
470
            INC(text.found)
471
        END;
472
        DEC(i)
473
    END;
474
    posItem := text.PosList.first(TPos);
475
    pos := 0; strong := 0; italic := 0; code := 0;
476
    select(body.child.first, posItem, pos, strong, italic, code);
477
    text.curPos := NIL
478
END find;
479
 
480
 
481
PROCEDURE ffirst (body: XML.TAG);
482
VAR
483
    text: Text;
484
 
485
BEGIN
486
    text := body.text(Text);
487
    IF text.str0 # "" THEN
488
        find(body, text.str0);
489
        text.curPos := text.PosList.first(TPos)
490
    END
491
END ffirst;
492
 
493
 
494
PROCEDURE found* (body: XML.TAG): BOOLEAN;
495
    RETURN (body # NIL) & (body.text # NIL) & (body.text(Text).found # 0)
496
END found;
497
 
498
 
499
PROCEDURE fnext* (body: XML.TAG; VAR y: INTEGER; d: INTEGER);
500
VAR
501
    text: Text;
502
    rect: TRect;
503
    cur:  LISTS.ITEM;
504
 
505
BEGIN
506
    text := body.text(Text);
507
    IF (text # NIL) & (text.found # 0) THEN
508
        cur := text.curPos;
509
        CASE d OF
510
        |1:
511
            IF cur.next # NIL THEN
512
                cur := cur.next
513
            ELSE
514
                cur := text.PosList.first
515
            END
516
 
517
        |-1:
518
            IF cur.prev # NIL THEN
519
                cur := cur.prev
520
            ELSE
521
                cur := text.PosList.last
522
            END
523
 
524
        |0:
525
            cur := text.PosList.first
526
 
527
        END;
528
        text.curPos := cur(TPos);
529
        rect := text.curPos.RectList.first(TRect);
530
        IF rect # NIL THEN
531
            y := rect.y1
532
        END
533
    ELSE
534
        y := -1
535
    END
536
END fnext;
537
 
538
 
539
PROCEDURE open* (_find: PFind);
540
BEGIN
541
    Find := _find;
542
    SearchForm.open
543
END open;
544
 
545
 
546
PROCEDURE close*;
547
VAR
548
    text: Text;
549
    body: XML.TAG;
9898 akron1 550
    nullptr: INTEGER;
9896 akron1 551
 
552
BEGIN
553
    body := Body;
554
    text := body.text(Text);
555
    IF text # NIL THEN
556
        LISTS.destroy(text.PosList);
557
        text.PosList := LISTS.create(NIL);
558
        text.found := 0;
9898 akron1 559
        text.curPos := NIL;
560
        nullptr := K.free(text.idx0.table);
561
        nullptr := K.free(text.idx0.data);
562
        nullptr := K.free(text.idx1.table);
563
        nullptr := K.free(text.idx1.data);
564
        DISPOSE(text.idx0);
565
        DISPOSE(text.idx1);
566
        DISPOSE(text);
567
        body.text := NIL
9896 akron1 568
    END
569
END close;
570
 
571
 
572
PROCEDURE resize*;
573
VAR
574
    n: INTEGER;
575
    text: Text;
576
    item: LISTS.ITEM;
577
 
578
BEGIN
579
    text := TextList.first(Text);
580
    WHILE text # NIL DO
581
        IF text.found # 0 THEN
582
            n := text.curPos.n;
583
            find(text.body, text.str0);
584
            item := LISTS.get(text.PosList, n);
585
            text.curPos := item(TPos)
586
        END;
587
        text := text.next(Text)
588
    END
589
END resize;
590
 
591
 
592
PROCEDURE callback (case: BOOLEAN; str: STRING): BOOLEAN;
593
VAR
594
    body: XML.TAG;
595
 
596
BEGIN
597
    body := Body;
598
    IF body.text = NIL THEN
599
        create(body)
600
    END;
601
    body.text(Text).case := case;
602
    body.text(Text).str0 := str;
603
    ffirst(body);
604
    Find(0)
605
 
606
    RETURN body.text(Text).found # 0
607
END callback;
608
 
609
 
610
BEGIN
611
    TextList := LISTS.create(NIL);
612
    SearchForm.init(callback)
613
END Search.