diff --git a/data/common/fb2read b/data/common/fb2read
index da36129bdd..bbcdee4e75 100644
Binary files a/data/common/fb2read and b/data/common/fb2read differ
diff --git a/programs/develop/cedit/BUILD.SH b/programs/develop/cedit/BUILD.SH
index e7e610bf4a..87bfae436b 100644
--- a/programs/develop/cedit/BUILD.SH
+++ b/programs/develop/cedit/BUILD.SH
@@ -1,3 +1,3 @@
#SHS
-/kolibrios/develop/oberon07/compiler.kex ./src/cedit.ob07 kosexe -out /tmp0/1/cedit -stk 1 -nochk a
+/kolibrios/develop/oberon07/compiler.kex ./src/cedit.ob07 kosexe -out /tmp0/1/cedit.kex -stk 1 -nochk a
exit
\ No newline at end of file
diff --git a/programs/develop/cedit/RUN.SH b/programs/develop/cedit/RUN.SH
index 4c92a8decc..fa89aeba44 100644
--- a/programs/develop/cedit/RUN.SH
+++ b/programs/develop/cedit/RUN.SH
@@ -1,3 +1,3 @@
#SHS
-/tmp0/1/cedit
+/tmp0/1/cedit.kex
exit
\ No newline at end of file
diff --git a/programs/other/fb2reader/BUILD.SH b/programs/other/fb2reader/BUILD.SH
new file mode 100644
index 0000000000..013b251d90
--- /dev/null
+++ b/programs/other/fb2reader/BUILD.SH
@@ -0,0 +1,3 @@
+#SHS
+/kolibrios/develop/oberon07/compiler.kex ./src/fb2read.ob07 kosexe -out /tmp0/1/fb2read.kex -stk 1 -nochk a -upper
+exit
\ No newline at end of file
diff --git a/programs/other/fb2reader/FB2READ.INI b/programs/other/fb2reader/FB2READ.INI
new file mode 100644
index 0000000000..08fe8d4298
--- /dev/null
+++ b/programs/other/fb2reader/FB2READ.INI
@@ -0,0 +1,16 @@
+[Paths]
+history=/sys/settings/fb2_hist.dat
+browser=/sys/network/webview
+default=/sys
+font=/sys/fonts/tahoma.kf
+picture=
+[Files]
+files=fb2|asm|txt|ini
+[Flags]
+picture=off
+[Colors]
+back=240,240,199
+text=0,0,0
+italic=80,80,80
+link=0,0,255
+visited=128,0,128
diff --git a/programs/other/fb2reader/RUN.SH b/programs/other/fb2reader/RUN.SH
new file mode 100644
index 0000000000..a4cfd9ecb5
--- /dev/null
+++ b/programs/other/fb2reader/RUN.SH
@@ -0,0 +1,3 @@
+#SHS
+/tmp0/1/fb2read.kex
+exit
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/API.ob07 b/programs/other/fb2reader/SRC/API.ob07
new file mode 100644
index 0000000000..4151eb2368
--- /dev/null
+++ b/programs/other/fb2reader/SRC/API.ob07
@@ -0,0 +1,290 @@
+(*
+ BSD 2-Clause License
+
+ Copyright (c) 2018, 2020-2022, Anton Krotov
+ All rights reserved.
+*)
+
+MODULE API;
+
+IMPORT SYSTEM, K := KOSAPI;
+
+
+CONST
+
+ eol* = 0DX + 0AX;
+ BIT_DEPTH* = 32;
+
+ MAX_SIZE = 16 * 400H;
+ HEAP_SIZE = 1 * 100000H;
+
+ _new = 1;
+ _dispose = 2;
+
+ SizeOfHeader = 36;
+
+
+TYPE
+
+ CRITICAL_SECTION = ARRAY 2 OF INTEGER;
+
+
+VAR
+
+ heap, endheap: INTEGER;
+ pockets: ARRAY MAX_SIZE DIV 32 + 1 OF INTEGER;
+
+ CriticalSection: CRITICAL_SECTION;
+
+ multi: BOOLEAN;
+
+ base*: INTEGER;
+
+
+PROCEDURE [stdcall] zeromem* (dwords: INTEGER; adr: INTEGER);
+BEGIN
+ SYSTEM.CODE(
+ 0FCH, (* cld *)
+ 031H, 0C0H, (* xor eax, eax *)
+ 057H, (* push edi *)
+ 08BH, 07DH, 00CH, (* mov edi, dword [ebp + 12] *)
+ 08BH, 04DH, 008H, (* mov ecx, dword [ebp + 8] *)
+ 0F3H, 0ABH, (* rep stosd *)
+ 05FH (* pop edi *)
+ )
+END zeromem;
+
+
+PROCEDURE mem_commit* (adr, size: INTEGER);
+VAR
+ tmp: INTEGER;
+BEGIN
+ FOR tmp := adr TO adr + size - 1 BY 4096 DO
+ SYSTEM.PUT(tmp, 0)
+ END
+END mem_commit;
+
+
+PROCEDURE switch_task;
+BEGIN
+ K.sysfunc2(68, 1)
+END switch_task;
+
+
+PROCEDURE futex_create (ptr: INTEGER): INTEGER;
+ RETURN K.sysfunc3(77, 0, ptr)
+END futex_create;
+
+
+PROCEDURE futex_wait (futex, value, timeout: INTEGER);
+BEGIN
+ K.sysfunc5(77, 2, futex, value, timeout)
+END futex_wait;
+
+
+PROCEDURE futex_wake (futex, number: INTEGER);
+BEGIN
+ K.sysfunc4(77, 3, futex, number)
+END futex_wake;
+
+
+PROCEDURE EnterCriticalSection* (VAR CriticalSection: CRITICAL_SECTION);
+BEGIN
+ switch_task;
+ futex_wait(CriticalSection[0], 1, 10000);
+ CriticalSection[1] := 1
+END EnterCriticalSection;
+
+
+PROCEDURE LeaveCriticalSection* (VAR CriticalSection: CRITICAL_SECTION);
+BEGIN
+ CriticalSection[1] := 0;
+ futex_wake(CriticalSection[0], 1)
+END LeaveCriticalSection;
+
+
+PROCEDURE InitializeCriticalSection* (VAR CriticalSection: CRITICAL_SECTION);
+BEGIN
+ CriticalSection[0] := futex_create(SYSTEM.ADR(CriticalSection[1]));
+ CriticalSection[1] := 0
+END InitializeCriticalSection;
+
+
+PROCEDURE __NEW (size: INTEGER): INTEGER;
+VAR
+ res, idx, temp: INTEGER;
+BEGIN
+ IF size <= MAX_SIZE THEN
+ idx := ASR(size, 5);
+ res := pockets[idx];
+ IF res # 0 THEN
+ SYSTEM.GET(res, pockets[idx]);
+ SYSTEM.PUT(res, size);
+ INC(res, 4)
+ ELSE
+ temp := 0;
+ IF heap + size >= endheap THEN
+ IF K.sysfunc2(18, 16) > ASR(HEAP_SIZE, 10) THEN
+ temp := K.sysfunc3(68, 12, HEAP_SIZE)
+ ELSE
+ temp := 0
+ END;
+ IF temp # 0 THEN
+ mem_commit(temp, HEAP_SIZE);
+ heap := temp;
+ endheap := heap + HEAP_SIZE
+ ELSE
+ temp := -1
+ END
+ END;
+ IF (heap # 0) & (temp # -1) THEN
+ SYSTEM.PUT(heap, size);
+ res := heap + 4;
+ heap := heap + size
+ ELSE
+ res := 0
+ END
+ END
+ ELSE
+ IF K.sysfunc2(18, 16) > ASR(size, 10) THEN
+ res := K.sysfunc3(68, 12, size);
+ IF res # 0 THEN
+ mem_commit(res, size);
+ SYSTEM.PUT(res, size);
+ INC(res, 4)
+ END
+ ELSE
+ res := 0
+ END
+ END;
+ IF (res # 0) & (size <= MAX_SIZE) THEN
+ zeromem(ASR(size, 2) - 1, res)
+ END
+ RETURN res
+END __NEW;
+
+
+PROCEDURE __DISPOSE (ptr: INTEGER): INTEGER;
+VAR
+ size, idx: INTEGER;
+BEGIN
+ DEC(ptr, 4);
+ SYSTEM.GET(ptr, size);
+ IF size <= MAX_SIZE THEN
+ idx := ASR(size, 5);
+ SYSTEM.PUT(ptr, pockets[idx]);
+ pockets[idx] := ptr
+ ELSE
+ size := K.sysfunc3(68, 13, ptr)
+ END
+ RETURN 0
+END __DISPOSE;
+
+
+PROCEDURE NEW_DISPOSE (func, arg: INTEGER): INTEGER;
+VAR
+ res: INTEGER;
+
+BEGIN
+ IF multi THEN
+ EnterCriticalSection(CriticalSection)
+ END;
+
+ IF func = _new THEN
+ res := __NEW(arg)
+ ELSIF func = _dispose THEN
+ res := __DISPOSE(arg)
+ END;
+
+ IF multi THEN
+ LeaveCriticalSection(CriticalSection)
+ END
+
+ RETURN res
+END NEW_DISPOSE;
+
+
+PROCEDURE _NEW* (size: INTEGER): INTEGER;
+ RETURN NEW_DISPOSE(_new, size)
+END _NEW;
+
+
+PROCEDURE _DISPOSE* (ptr: INTEGER): INTEGER;
+ RETURN NEW_DISPOSE(_dispose, ptr)
+END _DISPOSE;
+
+
+PROCEDURE exit* (p1: INTEGER);
+BEGIN
+ K.sysfunc1(-1)
+END exit;
+
+
+PROCEDURE exit_thread* (p1: INTEGER);
+BEGIN
+ K.sysfunc1(-1)
+END exit_thread;
+
+
+PROCEDURE OutStr (pchar: INTEGER);
+VAR
+ c: CHAR;
+BEGIN
+ IF pchar # 0 THEN
+ REPEAT
+ SYSTEM.GET(pchar, c);
+ IF c # 0X THEN
+ K.OutChar(c)
+ END;
+ INC(pchar)
+ UNTIL c = 0X
+ END
+END OutStr;
+
+
+PROCEDURE DebugMsg* (lpText, lpCaption: INTEGER);
+BEGIN
+ IF lpCaption # 0 THEN
+ K.OutLn;
+ OutStr(lpCaption);
+ K.OutChar(":");
+ K.OutLn
+ END;
+ OutStr(lpText);
+ IF lpCaption # 0 THEN
+ K.OutLn
+ END
+END DebugMsg;
+
+
+PROCEDURE init* (import_, code: INTEGER);
+BEGIN
+ multi := FALSE;
+ base := code - SizeOfHeader;
+ K.sysfunc2(68, 11);
+ InitializeCriticalSection(CriticalSection);
+ K._init(import_)
+END init;
+
+
+PROCEDURE SetMultiThr* (value: BOOLEAN);
+BEGIN
+ multi := value
+END SetMultiThr;
+
+
+PROCEDURE GetTickCount* (): INTEGER;
+ RETURN K.sysfunc2(26, 9) * 10
+END GetTickCount;
+
+
+PROCEDURE dllentry* (hinstDLL, fdwReason, lpvReserved: INTEGER): INTEGER;
+ RETURN 0
+END dllentry;
+
+
+PROCEDURE sofinit*;
+END sofinit;
+
+
+END API.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/ColorDlg.ob07 b/programs/other/fb2reader/SRC/ColorDlg.ob07
new file mode 100644
index 0000000000..0091ef2572
--- /dev/null
+++ b/programs/other/fb2reader/SRC/ColorDlg.ob07
@@ -0,0 +1,87 @@
+(*
+ Copyright 2016, 2022 Anton Krotov
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*)
+
+MODULE ColorDlg;
+
+IMPORT sys := SYSTEM, KOSAPI;
+
+TYPE
+
+ DRAW_WINDOW = PROCEDURE;
+
+ TDialog = RECORD
+ type,
+ procinfo,
+ com_area_name,
+ com_area,
+ start_path: INTEGER;
+ draw_window: DRAW_WINDOW;
+ status*,
+ X, Y,
+ color_type,
+ color*: INTEGER;
+
+ procinf: ARRAY 1024 OF CHAR;
+ s_com_area_name: ARRAY 32 OF CHAR
+ END;
+
+ Dialog* = POINTER TO TDialog;
+
+
+PROCEDURE [stdcall, "Proc_lib.obj", ""] ColorDialog_start (cd: Dialog); END;
+PROCEDURE [stdcall, "Proc_lib.obj", ""] ColorDialog_init (cd: Dialog); END;
+
+PROCEDURE Show*(cd: Dialog);
+BEGIN
+ IF cd # NIL THEN
+ cd.X := 0;
+ cd.Y := 0;
+ ColorDialog_start(cd)
+ END
+END Show;
+
+PROCEDURE Create*(draw_window: DRAW_WINDOW): Dialog;
+VAR res: Dialog;
+BEGIN
+ NEW(res);
+ IF res # NIL THEN
+ res.s_com_area_name := "FFFFFFFF_color_dlg";
+ res.com_area := 0;
+ res.type := 0;
+ res.color_type := 0;
+ res.procinfo := sys.ADR(res.procinf[0]);
+ res.com_area_name := sys.ADR(res.s_com_area_name[0]);
+ res.start_path := sys.SADR("/sys/colrdial");
+ res.draw_window := draw_window;
+ res.status := 0;
+ res.X := 0;
+ res.Y := 0;
+ res.color := 0;
+ ColorDialog_init(res)
+ END
+ RETURN res
+END Create;
+
+PROCEDURE Destroy*(VAR cd: Dialog);
+BEGIN
+ IF cd # NIL THEN
+ DISPOSE(cd)
+ END
+END Destroy;
+
+
+END ColorDlg.
diff --git a/programs/other/fb2reader/SRC/Conv.ob07 b/programs/other/fb2reader/SRC/Conv.ob07
new file mode 100644
index 0000000000..46111e1381
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Conv.ob07
@@ -0,0 +1,84 @@
+(*
+ Copyright 2016 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Conv;
+
+IMPORT sys := SYSTEM, Encode;
+
+VAR table: ARRAY 65536 OF CHAR;
+
+PROCEDURE GetUtf8 (str: INTEGER; VAR val, idx: INTEGER);
+VAR ch: CHAR;
+BEGIN
+ sys.GET(str + idx, ch); INC(idx);
+ IF ch < 80X THEN
+ val := ORD(ch)
+ ELSIF ch < 0E0X THEN
+ val := ORD(ch) - 192;
+ sys.GET(str + idx, ch); INC(idx);
+ val := val * 64 + ORD(ch) - 128
+ ELSE
+ val := ORD(ch) - 224;
+ sys.GET(str + idx, ch); INC(idx); val := val * 64 + ORD(ch) - 128;
+ sys.GET(str + idx, ch); INC(idx); val := val * 64 + ORD(ch) - 128
+ END
+END GetUtf8;
+
+PROCEDURE convert*(adr, adr2: INTEGER; len: INTEGER);
+VAR val, idx: INTEGER;
+BEGIN
+ idx := 0;
+ WHILE len > 0 DO
+ GetUtf8(adr, val, idx);
+ IF (0 <= val) & (val < LEN(table)) THEN
+ sys.PUT(adr2, table[val])
+ ELSE
+ sys.PUT(adr2, "?")
+ END;
+ INC(adr2);
+ DEC(len)
+ END
+END convert;
+
+PROCEDURE utf8to1251(code: INTEGER): CHAR;
+VAR res: CHAR; i: INTEGER;
+BEGIN
+ res := "?";
+ i := 0;
+ WHILE i <= 255 DO
+ IF Encode.W1251[i].code = code THEN
+ res := CHR(i);
+ i := 255
+ END;
+ INC(i)
+ END
+ RETURN res
+END utf8to1251;
+
+PROCEDURE main;
+VAR i: INTEGER;
+BEGIN
+ FOR i := 0 TO LEN(table) - 1 DO
+ table[i] := utf8to1251(i)
+ END
+END main;
+
+BEGIN
+ main
+END Conv.
diff --git a/programs/other/fb2reader/SRC/Cursor.ob07 b/programs/other/fb2reader/SRC/Cursor.ob07
new file mode 100644
index 0000000000..581c1fda51
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Cursor.ob07
@@ -0,0 +1,364 @@
+(*
+ Copyright 2016 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Cursor;
+
+
+IMPORT sys := SYSTEM;
+
+
+PROCEDURE [stdcall] cur;
+BEGIN
+sys.CODE(
+000H, 000H, 002H, 000H, 001H, 000H, 020H, 020H, 000H, 000H,
+005H, 000H, 000H, 000H, 0A8H, 00CH, 000H, 000H, 016H, 000H,
+000H, 000H, 028H, 000H, 000H, 000H, 020H, 000H, 000H, 000H,
+040H, 000H, 000H, 000H, 001H, 000H, 018H, 000H, 000H, 000H,
+000H, 000H, 080H, 00CH, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 033H, 033H, 033H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 055H, 055H, 055H, 0BBH, 0BBH, 0BBH, 0BBH, 0BBH, 0BBH,
+0BBH, 0BBH, 0BBH, 0AAH, 0AAH, 0AAH, 033H, 033H, 033H, 0AAH,
+0AAH, 0AAH, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 033H, 033H, 033H, 0BBH, 0BBH, 0BBH,
+0EEH, 0EEH, 0EEH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0EEH,
+0EEH, 0EEH, 0AAH, 0AAH, 0AAH, 0EEH, 0EEH, 0EEH, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 033H, 033H, 033H,
+0BBH, 0BBH, 0BBH, 0EEH, 0EEH, 0EEH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0EEH, 0EEH,
+0EEH, 0FFH, 0FFH, 0FFH, 0AAH, 0AAH, 0AAH, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+033H, 033H, 033H, 0BBH, 0BBH, 0BBH, 0EEH, 0EEH, 0EEH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0CCH, 0CCH, 0CCH, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 055H, 055H, 055H, 0EEH,
+0EEH, 0EEH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0E5H, 0E5H, 0E5H, 088H,
+088H, 088H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 055H, 055H, 055H, 0EEH, 0EEH, 0EEH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0E5H, 0E5H, 0E5H, 088H, 088H, 088H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 033H, 033H, 033H, 0DDH, 0DDH,
+0DDH, 0FFH, 0FFH, 0FFH, 0DDH, 0DDH, 0DDH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0EEH, 0EEH,
+0EEH, 0BBH, 0BBH, 0BBH, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 033H, 033H,
+033H, 0DDH, 0DDH, 0DDH, 0FFH, 0FFH, 0FFH, 0E5H, 0E5H, 0E5H,
+000H, 000H, 000H, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0CCH, 0CCH, 0CCH,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 033H, 033H, 033H, 0DDH, 0DDH, 0DDH,
+0FFH, 0FFH, 0FFH, 0E5H, 0E5H, 0E5H, 000H, 000H, 000H, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0CCH, 0CCH, 0CCH, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+055H, 055H, 055H, 0FFH, 0FFH, 0FFH, 0BBH, 0BBH, 0BBH, 000H,
+000H, 000H, 033H, 033H, 033H, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0CCH,
+0CCH, 0CCH, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 055H,
+055H, 055H, 022H, 022H, 022H, 000H, 000H, 000H, 033H, 033H,
+033H, 0FFH, 0FFH, 0FFH, 0EEH, 0EEH, 0EEH, 088H, 088H, 088H,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 088H, 088H, 088H, 0E5H,
+0E5H, 0E5H, 044H, 044H, 044H, 0E5H, 0E5H, 0E5H, 033H, 033H,
+033H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 033H, 033H, 033H, 0FFH, 0FFH, 0FFH,
+0E5H, 0E5H, 0E5H, 044H, 044H, 044H, 0F4H, 0F4H, 0F4H, 0E5H,
+0E5H, 0E5H, 044H, 044H, 044H, 0E5H, 0E5H, 0E5H, 000H, 000H,
+000H, 033H, 033H, 033H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+033H, 033H, 033H, 0FFH, 0FFH, 0FFH, 0E5H, 0E5H, 0E5H, 000H,
+000H, 000H, 0AAH, 0AAH, 0AAH, 022H, 022H, 022H, 000H, 000H,
+000H, 033H, 033H, 033H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 033H, 033H, 033H, 0FFH,
+0FFH, 0FFH, 0E5H, 0E5H, 0E5H, 000H, 000H, 000H, 033H, 033H,
+033H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 033H, 033H, 033H, 0FFH, 0FFH, 0FFH, 0E5H, 0E5H,
+0E5H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 033H, 033H,
+033H, 0FFH, 0FFH, 0FFH, 0E5H, 0E5H, 0E5H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 044H, 044H, 044H, 0EEH, 0EEH, 0EEH,
+0E5H, 0E5H, 0E5H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 044H, 044H, 044H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H, 000H,
+000H, 000H, 000H, 000H, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH,
+0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0FFH, 0F8H, 027H, 0FFH, 0FFH,
+0F8H, 007H, 0FFH, 0FFH, 0F0H, 007H, 0FFH, 0FFH, 0E0H, 003H,
+0FFH, 0FFH, 0C0H, 003H, 0FFH, 0FFH, 0C0H, 001H, 0FFH, 0FFH,
+0C0H, 001H, 0FFH, 0FFH, 080H, 001H, 0FFH, 0FFH, 000H, 001H,
+0FFH, 0FFH, 000H, 001H, 0FFH, 0FFH, 000H, 001H, 0FFH, 0FFH,
+090H, 001H, 0FFH, 0FFH, 0F0H, 003H, 0FFH, 0FFH, 0F0H, 00FH,
+0FFH, 0FFH, 0F0H, 07FH, 0FFH, 0FFH, 0F0H, 0FFH, 0FFH, 0FFH,
+0F0H, 0FFH, 0FFH, 0FFH, 0F0H, 0FFH, 0FFH, 0FFH, 0F9H, 0FFH,
+0FFH, 0FFH)
+END cur;
+
+
+PROCEDURE GetCursor* (): INTEGER;
+ RETURN sys.ADR(cur) + 3
+END GetCursor;
+
+
+END Cursor.
diff --git a/programs/other/fb2reader/SRC/DOM.ob07 b/programs/other/fb2reader/SRC/DOM.ob07
new file mode 100644
index 0000000000..45ba0bcea5
--- /dev/null
+++ b/programs/other/fb2reader/SRC/DOM.ob07
@@ -0,0 +1,1756 @@
+(*
+ Copyright 2016-2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE DOM;
+
+IMPORT XML, SU := SysUtils, S := Strings, Font, Window, G := Graph, LibImg,
+ RF := ReadFile, File, Write, Read, Ini, K := KOSAPI, sys := SYSTEM,
+ V := Vector, Cursor, box_lib, tables, Search;
+
+
+CONST
+
+ BACK_COLOR* = 0;
+ TEXT_COLOR* = 1;
+ ITALIC_COLOR* = 2;
+ LINK_COLOR* = 3;
+ VISITED_COLOR* = 4;
+ CLICKED_COLOR* = 5;
+
+ CellPadding = 5;
+
+
+TYPE
+
+ TSettings* = RECORD
+
+ Colors* : ARRAY 6 OF INTEGER;
+ FontSize* : INTEGER;
+ TwoCol* : BOOLEAN;
+ b_pict* : BOOLEAN;
+
+ PADDING* : RECORD Left, Right, Top*, Bottom, ColInter, LRpc*, CInt*: INTEGER END;
+
+ PARAGRAPH*,
+ EPIGRAPH*,
+ LEVEL,
+ SUB,
+ SUP,
+ InterLin*,
+ Picture*,
+ SpaceW: INTEGER
+
+ END;
+
+ StackItem = POINTER TO TStackItem;
+
+ TStackItem = RECORD (XML.DESC_ELEMENT)
+
+ body : XML.TAG;
+ Ycur : INTEGER;
+ d : REAL
+
+ END;
+
+
+VAR
+
+ Settings* : TSettings;
+ Canvas_X, Canvas_Y: INTEGER;
+ ColLeft : Window.TRect;
+ ColRight : Window.TRect;
+
+ Ymin, Ymax, Ycur : INTEGER;
+
+ X, Y, W, LineH, W1, W2: INTEGER;
+
+ epigraph : INTEGER;
+ sup, sub : INTEGER;
+ ref_depth : INTEGER;
+ align : INTEGER;
+ code : INTEGER;
+ strong : INTEGER;
+ italic : INTEGER;
+ strike : INTEGER;
+ refer : INTEGER;
+
+ Text : ARRAY 100000 OF XML.TEXT;
+ TextCount : INTEGER;
+
+ Lines: INTEGER;
+
+ description, contents, mainbody, body, ref, cover, clickRef, hoverRef: XML.TAG;
+
+ MainBody: BOOLEAN;
+
+ f_stk, b_stk, vis_ref: XML.LIST;
+
+ FilePath, FileName: S.STRING;
+
+ done, last, resized, loaded*, mouseDown: BOOLEAN;
+
+ Stack*: ARRAY 1000000 OF CHAR;
+
+ Ycont: INTEGER;
+
+ history: File.FS;
+
+ references: V.VECTOR;
+ cursor: INTEGER;
+
+ fsize2, chksum: INTEGER;
+
+ sb: box_lib.scrollbar;
+ urlstr* : S.STRING;
+ DrawStatus, DrawToolbar: PROCEDURE;
+
+
+PROCEDURE PushRef(ref: XML.TAG);
+VAR item: StackItem;
+BEGIN
+ NEW(item);
+ item.body := ref;
+ XML.AddItem(vis_ref, item);
+END PushRef;
+
+
+PROCEDURE Push(VAR stk: XML.LIST);
+VAR item: StackItem;
+BEGIN
+ NEW(item);
+ item.body := body;
+ item.Ycur := Ycur;
+ XML.AddItem(stk, item);
+ IF body = contents THEN
+ Ycont := Ycur
+ END
+END Push;
+
+
+PROCEDURE Pop(VAR stk: XML.LIST);
+VAR item : StackItem;
+BEGIN
+ item := stk.last(StackItem);
+ IF item # NIL THEN
+ body := item.body;
+ Ymin := body.Ymin;
+ Ymax := body.Ymax;
+ Ycur := item.Ycur;
+ XML.DelLastItem(stk)
+ END
+END Pop;
+
+
+PROCEDURE Clear(VAR stk: XML.LIST);
+BEGIN
+ REPEAT
+ XML.DelLastItem(stk)
+ UNTIL stk.last = NIL
+END Clear;
+
+
+PROCEDURE AddToLine(text: XML.TEXT);
+BEGIN
+ Text[TextCount] := text;
+ INC(TextCount)
+END AddToLine;
+
+
+PROCEDURE Epigraph(): INTEGER;
+ RETURN ORD(epigraph > 0) * Settings.EPIGRAPH
+END Epigraph;
+
+
+PROCEDURE SpaceWidth(): INTEGER;
+VAR Result: INTEGER;
+BEGIN
+ IF code > 0 THEN
+ Result := Font.MonoWidth()
+ ELSE
+ Result := Settings.SpaceW
+ END
+ RETURN Result
+END SpaceWidth;
+
+
+PROCEDURE Trim;
+VAR n: INTEGER;
+BEGIN
+ IF TextCount > 0 THEN
+ n := TextCount - 1;
+ WHILE (n >= 0) & (Text[n] IS XML.SPACE) DO
+ Text[n].width := -1;
+ DEC(n)
+ END;
+ TextCount := n + 1
+ END
+END Trim;
+
+
+PROCEDURE Align;
+VAR
+ i, n, sp, d, quo, rem, x: INTEGER;
+ text: XML.TEXT;
+BEGIN
+ IF (TextCount > 0) & (code = 0) & (align # 3) THEN
+ sp := 0;
+ Trim;
+ n := TextCount - 1;
+ IF n >= 0 THEN
+ d := W - Text[n].X - Text[n].width
+ END;
+ IF align = 1 THEN
+ x := (d + Text[0].X) DIV 2
+ ELSIF align = 2 THEN
+ x := d + Text[0].X
+ ELSIF align = 0 THEN
+ x := Text[0].X;
+ FOR i := 0 TO n DO
+ IF Text[i] IS XML.SPACE THEN
+ INC(sp)
+ END
+ END;
+ IF sp > 0 THEN
+ quo := d DIV sp;
+ rem := d MOD sp;
+ FOR i := 0 TO n DO
+ IF Text[i] IS XML.SPACE THEN
+ text := Text[i];
+ text.width := text.width + quo + ORD(rem > 0);
+ DEC(rem)
+ END
+ END
+ END
+ END;
+ FOR i := 0 TO n DO
+ text := Text[i];
+ text.X := x;
+ INC(x, text.width)
+ END
+ END
+END Align;
+
+
+PROCEDURE NewLine;
+BEGIN
+ IF align # 0 THEN
+ Align
+ END;
+ X := Epigraph();
+ INC(Y, LineH);
+ TextCount := 0
+END NewLine;
+
+
+PROCEDURE Sup(open: BOOLEAN);
+BEGIN
+ IF open THEN
+ IF sup = 0 THEN
+ DEC(Y, Settings.SUP)
+ END;
+ INC(sup)
+ ELSE
+ DEC(sup);
+ IF sup = 0 THEN
+ INC(Y, Settings.SUP)
+ END
+ END
+END Sup;
+
+
+PROCEDURE Sub(open: BOOLEAN);
+BEGIN
+ IF open THEN
+ IF sub = 0 THEN
+ INC(Y, Settings.SUB)
+ END;
+ INC(sub)
+ ELSE
+ DEC(sub);
+ IF sub = 0 THEN
+ DEC(Y, Settings.SUB)
+ END
+ END
+END Sub;
+
+
+PROCEDURE Split(word: XML.WORD);
+VAR
+ i, n, max, len: INTEGER;
+ c: CHAR;
+ rem: XML.WORD;
+BEGIN
+ WHILE Font.TextWidth(word.value, max) <= W DO
+ INC(max)
+ END;
+ DEC(max);
+ IF max = 0 THEN
+ max := 1
+ END;
+ i := 0;
+ n := 0;
+ len := word.value.last - word.value.first + 1;
+ WHILE (n <= max) & (i < len) DO
+ c := S.GetChar(word.value, i);
+ INC(n);
+ IF (80X <= c) & (c <= 0BFX) THEN
+ DEC(n)
+ END;
+ INC(i)
+ END;
+ IF n > max THEN
+ DEC(i);
+ rem := XML.CreateWord();
+ rem^ := word^;
+ rem.value.first := word.value.first + i;
+ word.next := rem;
+ word.value.last := rem.value.first - 1;
+ word.length := S.Utf8Length(word.value);
+ word.width := Font.TextWidth(word.value, word.length)
+ END
+END Split;
+
+
+PROCEDURE Depth(tag: XML.ELEMENT): INTEGER;
+VAR n: INTEGER;
+BEGIN
+ n := 0;
+ WHILE tag # NIL DO
+ IF tag(XML.TAG).value = XML.tag_section THEN
+ INC(n)
+ END;
+ tag := tag.parent
+ END
+ RETURN n
+END Depth;
+
+
+PROCEDURE shift(tag: XML.TAG; shx, shy: INTEGER);
+VAR cur: XML.ELEMENT; t: XML.TAG;
+BEGIN
+ cur := tag.child.first;
+ WHILE cur # NIL DO
+ IF cur IS XML.TAG THEN
+ t := cur(XML.TAG);
+ INC(t.X, shx);
+ INC(t.Ymin, shy);
+ INC(t.Ymax, shy);
+ shift(t, shx, shy)
+ ELSIF cur IS XML.TEXT THEN
+ INC(cur(XML.TEXT).X, shx);
+ INC(cur(XML.TEXT).Y, shy)
+ END;
+ cur := cur.next
+ END
+END shift;
+
+
+PROCEDURE getspan(td: XML.TAG; span: S.STRING): INTEGER;
+VAR res: INTEGER;
+ attr_value: S.CHARS;
+ err: BOOLEAN;
+BEGIN
+ IF XML.GetAttr(td, span, attr_value) THEN
+ res := S.CharsToInt(attr_value, err);
+ IF err OR (res <= 0) THEN
+ res := 1
+ END
+ ELSE
+ res := 1
+ END
+ RETURN res
+END getspan;
+
+
+PROCEDURE td(t: tables.Table; tag: XML.TAG);
+BEGIN
+ tag.cell := t.cells.count;
+ tables.td(t, getspan(tag, "colspan"), getspan(tag, "rowspan"))
+END td;
+
+
+PROCEDURE tr(t: tables.Table; tag: XML.TAG);
+VAR
+ cur : XML.ELEMENT;
+ cell : XML.TAG;
+BEGIN
+ tables.tr(t);
+ cur := tag.child.first;
+ WHILE cur # NIL DO
+ IF cur IS XML.TAG THEN
+ cell := cur(XML.TAG);
+ IF (cell.value = XML.tag_td) OR (cell.value = XML.tag_th) THEN
+ cell.table := t;
+ td(t, cell)
+ END
+ END;
+ cur := cur.next
+ END
+END tr;
+
+
+PROCEDURE table(t: tables.Table; tag: XML.TAG; open: BOOLEAN);
+VAR
+ cur : XML.ELEMENT;
+ row : XML.TAG;
+BEGIN
+ IF open THEN
+ tables.table(t, W, TRUE);
+ cur := tag.child.first;
+ WHILE cur # NIL DO
+ IF cur IS XML.TAG THEN
+ row := cur(XML.TAG);
+ IF row.value = XML.tag_tr THEN
+ row.table := t;
+ tr(t, row)
+ END
+ END;
+ cur := cur.next
+ END;
+ tables.table(t, W, FALSE)
+ END
+END table;
+
+
+PROCEDURE layout(body: XML.ELEMENT);
+VAR
+ cur : XML.ELEMENT;
+ tag : XML.TAG;
+ word : XML.WORD;
+ text : XML.TEXT;
+ tag_value : INTEGER;
+ _align : INTEGER;
+ title : XML.ELEMENT;
+ width : INTEGER;
+ height1 : INTEGER;
+ height2 : INTEGER;
+
+
+ PROCEDURE Image (VAR tag: XML.TAG);
+ VAR
+ note : BOOLEAN;
+ img : XML.TAG;
+ URL : INTEGER;
+ chars : S.CHARS;
+ sizeY : INTEGER;
+ FName : S.STRING;
+ path : S.STRING;
+ BEGIN
+ IF tag.img # 0 THEN
+ LibImg.img_destroy(tag.img)
+ END;
+ img := XML.GetRef(tag, note, URL);
+ IF img # NIL THEN
+ IF img.child.first IS XML.WORD THEN
+ chars := img.child.first(XML.WORD).value;
+ tag.img := LibImg.GetImg(chars.first, chars.last - chars.first + 1, W, sizeY);
+ IF tag.img # 0 THEN
+ INC(Y, (sizeY DIV LineH) * LineH);
+ NewLine;
+ tag.Ymax := Y - Y MOD LineH
+ END
+ END
+ ELSIF URL # 0 THEN
+ S.PtrToString(URL, FName);
+ tag.img := LibImg.LoadFromFile(FName, W, sizeY);
+ IF tag.img = 0 THEN
+ path := FilePath;
+ IF FName[0] # "/" THEN
+ S.Append(path, "/")
+ END;
+ S.Append(path, FName);
+ tag.img := LibImg.LoadFromFile(path, W, sizeY);
+ END;
+ IF tag.img # 0 THEN
+ INC(Y, (sizeY DIV LineH) * LineH);
+ NewLine;
+ tag.Ymax := Y - Y MOD LineH
+ END
+ END
+ END Image;
+
+
+BEGIN
+ cur := body;
+ WHILE cur # NIL DO
+ IF cur IS XML.TAG THEN
+ tag := cur(XML.TAG);
+ tag_value := tag.value;
+ CASE tag_value OF
+ |XML.tag_p, XML.tag_v:
+ Trim;
+ IF TextCount > 0 THEN
+ NewLine
+ END;
+ X := Settings.PARAGRAPH + Epigraph()
+ |XML.tag_epigraph:
+ NewLine;
+ INC(epigraph)
+ |XML.tag_contents_item:
+ INC(ref_depth);
+ Settings.EPIGRAPH := Settings.LEVEL * Depth(tag);
+ _align := align;
+ align := 3
+ |XML.tag_title:
+ INC(strong);
+ Font.Bold(TRUE);
+ _align := align;
+ align := 1;
+ IF MainBody THEN
+ tag.value := XML.tag_contents_item;
+ title := XML.Copy(tag);
+ XML.AddChild(contents, title);
+ title.parent := tag.parent;
+ tag.value := XML.tag_title
+ END
+ |XML.tag_subtitle:
+ NewLine;
+ _align := align;
+ align := 1
+ |XML.tag_text_author, XML.tag_date:
+ _align := align;
+ align := 2
+ |XML.tag_section, XML.tag_body, XML.tag_empty_line, XML.tag_poem, XML.tag_stanza, XML.tag_annotation, XML.tag_cite:
+ NewLine
+ |XML.tag_a:
+ INC(ref_depth);
+ IF XML.IsNote(tag) THEN
+ Sup(TRUE)
+ END
+ |XML.tag_sup:
+ Sup(TRUE)
+ |XML.tag_sub:
+ Sub(TRUE)
+ |XML.tag_code:
+ Font.sysfont(TRUE);
+ INC(code)
+ |XML.tag_image:
+ tag.X := 0;
+ NewLine;
+ NewLine
+ |XML.tag_coverpage:
+ cover := tag
+
+ |XML.tag_table:
+ NewLine;
+ tables.destroy(tag.table);
+ NEW(tag.table);
+ table(tag.table, tag, TRUE)
+ |XML.tag_td, XML.tag_th:
+ IF tag_value = XML.tag_th THEN
+ INC(strong);
+ Font.Bold(TRUE);
+ END;
+ SU.ErrorIf(tag.parent(XML.TAG).value # XML.tag_tr, 21);
+ NewLine; DEC(Y, LineH);
+ tag.Width := tables.get_width(tag.table, tag.cell);
+ tag.X := tables.get_x(tag.table, tag.cell);
+ width := W;
+ W := tag.Width - 2 * CellPadding;
+ IF W <= 0 THEN
+ W := 1
+ END
+ |XML.tag_tr:
+ SU.ErrorIf(tag.parent(XML.TAG).value # XML.tag_table, 20)
+
+ |XML.tag_strong:
+ INC(strong);
+ Font.Bold(TRUE)
+ ELSE
+ END;
+
+ tag.Ymin := Y - Y MOD LineH;
+ layout(tag.child.first);
+ tag.Ymax := Y - Y MOD LineH;
+
+ CASE tag_value OF
+ |XML.tag_epigraph:
+ NewLine;
+ DEC(epigraph)
+ |XML.tag_subtitle:
+ NewLine;
+ NewLine;
+ align := _align
+ |XML.tag_title, XML.tag_text_author, XML.tag_date:
+ DEC(strong);
+ Font.Bold(strong > 0);
+ NewLine;
+ align := _align
+ |XML.tag_contents_item:
+ DEC(ref_depth);
+ align := _align;
+ |XML.tag_section, XML.tag_poem, XML.tag_v, XML.tag_p, XML.tag_annotation, XML.tag_cite:
+ NewLine
+ |XML.tag_a:
+ DEC(ref_depth);
+ IF XML.IsNote(tag) THEN
+ Sup(FALSE)
+ END
+ |XML.tag_sup:
+ Sup(FALSE)
+ |XML.tag_sub:
+ Sub(FALSE)
+ |XML.tag_code:
+ DEC(code);
+ Font.sysfont(code > 0)
+ |XML.tag_image:
+ Image(tag)
+
+ |XML.tag_table:
+ Y := tag.Ymin + tables.get_table_height(tag.table);
+ tag.Ymax := Y - Y MOD LineH;
+ NewLine;
+ |XML.tag_td, XML.tag_th:
+ IF tag_value = XML.tag_th THEN
+ DEC(strong);
+ Font.Bold(strong > 0)
+ END;
+ W := width;
+ NewLine;
+ Y := tag.Ymin + Settings.SUP; //!!!
+ height1 := tables.get_height(tag.table, tag.cell);
+ height2 := tag.Ymax - tag.Ymin + LineH;
+ IF height2 > height1 THEN
+ tables.set_height(tag.table, tag.cell, height2)
+ END;
+ INC(tag.Ymin, tables.get_y(tag.table, tag.cell));
+ INC(tag.Ymax, tables.get_height(tag.table, tag.cell));
+ shift(tag, tag.X + CellPadding, tables.get_y(tag.table, tag.cell));
+
+ |XML.tag_strong:
+ DEC(strong);
+ Font.Bold(strong > 0)
+ ELSE
+ END
+ ELSIF cur IS XML.WORD THEN
+ word := cur(XML.WORD);
+ word.length := S.Utf8Length(word.value);
+ word.width := Font.TextWidth(word.value, word.length);
+ IF W - X < word.width THEN
+ Align;
+ NewLine
+ END;
+ IF W < word.width THEN
+ Split(word)
+ END
+ ELSIF cur IS XML.SPACE THEN
+ IF W - X < SpaceWidth() THEN
+ cur(XML.SPACE).width := 0
+ ELSE
+ cur(XML.SPACE).width := SpaceWidth()
+ END
+ END;
+ IF cur IS XML.TEXT THEN
+ IF ref_depth > 0 THEN
+ V.push(references, cur)
+ END;
+ text := cur(XML.TEXT);
+ text.X := X;
+ text.Y := Y;
+ INC(X, text.width);
+ AddToLine(text)
+ END;
+ cur := cur.next
+ END
+END layout;
+
+
+PROCEDURE layout2(body: XML.ELEMENT);
+VAR
+ color : INTEGER;
+ cur : XML.ELEMENT;
+ text : XML.TEXT;
+ tag : XML.TAG;
+ y, y0 : INTEGER;
+ value : INTEGER;
+
+ PROCEDURE DrawText(Col: Window.TRect; min, max, y0, y: INTEGER; right: BOOLEAN; VAR text: XML.TEXT);
+ VAR word: XML.WORD;
+ BEGIN
+ IF (min <= y0) & (y0 <= max) THEN
+ Font.sysfont(code > 0);
+ IF text IS XML.WORD THEN
+ word := text(XML.WORD);
+ Font.Text(Col, word.X, y - Col.Height * ORD(right), word.value.first, word.length);
+ END;
+ Font.StrikeText(Col, text.X, y - Col.Height * ORD(right), text.width)
+ END
+ END DrawText;
+
+ PROCEDURE Image(VAR tag: XML.TAG);
+ VAR sizeX, sizeY, img, y: INTEGER;
+ BEGIN
+ IF tag.img # 0 THEN
+ y := Ycur;
+ LibImg.GetInf(tag.img, sizeX, sizeY, img);
+ IF (y <= tag.Ymax) & (tag.Ymin <= y + ColLeft.Height) THEN
+ G.Image(ColLeft.Left + tag.X, tag.Ymin - y + ColLeft.Top, sizeX, sizeY, img, ColLeft.Top, ColLeft.Top + ColLeft.Height - 1)
+ END;
+ IF Settings.TwoCol THEN
+ y := Ycur + ColLeft.Height;
+ IF (y <= tag.Ymax) & (tag.Ymin <= y + ColRight.Height) THEN
+ G.Image(ColRight.Left + tag.X, tag.Ymin - y + ColLeft.Top, sizeX, sizeY, img, ColRight.Top, ColRight.Top + ColRight.Height - 1)
+ END
+ END
+ END
+ END Image;
+
+ PROCEDURE td(VAR tag: XML.TAG);
+ VAR x1, y1, x2, y2, cl: INTEGER;
+ BEGIN
+ x1 := tag.X + ColLeft.Left;
+ y1 := tag.Ymin - Ycur + ColLeft.Top;
+ x2 := x1 + tag.Width;
+ y2 := y1 + tables.get_height(tag.table, tag.cell);
+ cl := G.GetColor();
+ G.SetColor(Settings.Colors[TEXT_COLOR]);
+ G.Rect(x1, y1, x2, y2);
+ IF Settings.TwoCol THEN
+ x1 := x1 - ColLeft.Left + ColRight.Left;
+ x2 := x2 - ColLeft.Left + ColRight.Left;
+ y1 := y1 - ColLeft.Height;
+ y2 := y2 - ColLeft.Height;
+ G.Rect(x1, y1, x2, y2)
+ END;
+ G.SetColor(cl)
+ END td;
+
+BEGIN
+ cur := body;
+ WHILE cur # NIL DO
+ IF cur IS XML.TAG THEN
+ tag := cur(XML.TAG);
+ IF (tag.value = XML.tag_td) OR (tag.value = XML.tag_th) THEN
+ tag.Ymax := tag.Ymin + tables.get_height(tag.table, tag.cell)
+ END;
+
+ IF (tag.Ymin < Ycur + LineH * Lines * (ORD(Settings.TwoCol) + 1)) & (tag.Ymax >= Ycur) OR (tag.value = XML.tag_tr) THEN
+
+ value := tag.value;
+ CASE value OF
+ |XML.tag_a:
+ INC(refer);
+ color := Font.Font.color;
+ IF tag.Clicked THEN
+ Font.SetFontColor(Settings.Colors[CLICKED_COLOR])
+ ELSE
+ IF tag.Visited THEN
+ Font.SetFontColor(Settings.Colors[VISITED_COLOR])
+ ELSE
+ Font.SetFontColor(Settings.Colors[LINK_COLOR])
+ END
+ END
+ |XML.tag_contents_item:
+ IF tag.Clicked THEN
+ INC(refer);
+ color := Font.Font.color;
+ Font.SetFontColor(Settings.Colors[CLICKED_COLOR])
+ ELSIF tag.Visited THEN
+ INC(refer);
+ color := Font.Font.color;
+ Font.SetFontColor(Settings.Colors[VISITED_COLOR])
+ END
+ |XML.tag_title, XML.tag_strong, XML.tag_th:
+ INC(strong);
+ Font.Bold(TRUE)
+ |XML.tag_strikethrough:
+ INC(strike);
+ Font.Strike(TRUE)
+ |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
+ INC(italic);
+ Font.Italic(TRUE, refer = 0)
+ |XML.tag_image:
+ Image(tag)
+ |XML.tag_code:
+ INC(code)
+ ELSE
+ END;
+ layout2(tag.child.first);
+ CASE value OF
+ |XML.tag_a:
+ DEC(refer);
+ Font.SetFontColor(color)
+ |XML.tag_contents_item:
+ IF tag.Clicked OR tag.Visited THEN
+ DEC(refer);
+ Font.SetFontColor(color)
+ END
+ |XML.tag_title, XML.tag_strong:
+ DEC(strong);
+ Font.Bold(strong > 0)
+ |XML.tag_strikethrough:
+ DEC(strike);
+ Font.Strike(strike > 0)
+ |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
+ DEC(italic);
+ Font.Italic(italic > 0, refer = 0)
+ |XML.tag_td:
+ td(tag)
+ |XML.tag_th:
+ DEC(strong);
+ Font.Bold(strong > 0);
+ td(tag)
+ |XML.tag_code:
+ DEC(code)
+ ELSE
+ END
+
+ END
+ ELSIF cur IS XML.TEXT THEN
+ text := cur(XML.TEXT);
+ y := text.Y - Ycur;
+ y0 := y - y MOD LineH;
+ DrawText(ColLeft, 0, ColLeft.Height - LineH, y0, y, FALSE, text);
+ IF Settings.TwoCol THEN
+ DrawText(ColRight, ColLeft.Height, ColLeft.Height + ColRight.Height - LineH, y0, y, TRUE, text)
+ END
+ END;
+ cur := cur.next
+ END
+END layout2;
+
+
+PROCEDURE DrawProgress(progress_color: INTEGER);
+VAR max_X, max_Y: INTEGER;
+BEGIN
+ max_X := G.Buffer.Width - 1;
+ max_Y := G.Buffer.Height - 1;
+ G.SetColor(0);
+ G.HLine(0, max_X, 0);
+ G.HLine(0, max_X, max_Y);
+ G.VLine(0, 0, max_Y);
+ sb.max_area := (Ymax - Ymin) DIV LineH + 50;
+ sb.cur_area := 50;
+ sb.position := (Ycur - Ymin) DIV LineH;
+ box_lib.scrollbar_v_draw(sb)
+END DrawProgress;
+
+
+PROCEDURE Draw*;
+VAR back, max_X, max_Y: INTEGER;
+BEGIN
+ back := Settings.Colors[BACK_COLOR];
+ max_X := G.Buffer.Width - 1;
+ max_Y := G.Buffer.Height - 1;
+ G.Copy(G.Buffer3, G.Buffer, 0, G.Buffer.Height, 0);
+ Font.SetFontColor(Settings.Colors[TEXT_COLOR]);
+ IF ((body = description) OR (body = contents)) & Settings.TwoCol THEN
+ Settings.TwoCol := FALSE;
+ layout2(body.child.first);
+ Settings.TwoCol := TRUE;
+ Search.draw(body, ColLeft, ColRight, Ycur, LineH, FALSE)
+ ELSE
+ layout2(body.child.first);
+ Search.draw(body, ColLeft, ColRight, Ycur, LineH, Settings.TwoCol)
+ END;
+ G.Copy(G.Buffer3, G.Buffer, 0, ColLeft.Top + 1, 0);
+ G.Copy(G.Buffer3, G.Buffer, max_Y - ColLeft.Top, ColLeft.Top + 1, max_Y - ColLeft.Top);
+ DrawProgress(0);
+ G.Draw(Canvas_X, Canvas_Y);
+ DrawToolbar;
+ DrawStatus
+END Draw;
+
+
+PROCEDURE BackEnabled* (): BOOLEAN;
+ RETURN b_stk.first # NIL
+END BackEnabled;
+
+
+PROCEDURE FrwEnabled* (): BOOLEAN;
+ RETURN f_stk.first # NIL
+END FrwEnabled;
+
+
+PROCEDURE ContentsEnabled* (): BOOLEAN;
+ RETURN (contents # NIL) (*& (body # contents)*)
+END ContentsEnabled;
+
+
+PROCEDURE DescrEnabled* (): BOOLEAN;
+ RETURN (description # NIL) (*& (body # description)*)
+END DescrEnabled;
+
+
+PROCEDURE Back*;
+BEGIN
+ IF b_stk.first # NIL THEN
+ Push(f_stk);
+ Pop(b_stk)
+ END
+END Back;
+
+
+PROCEDURE Forward*;
+BEGIN
+ IF f_stk.first # NIL THEN
+ Push(b_stk);
+ Pop(f_stk)
+ END
+END Forward;
+
+
+PROCEDURE Contents*;
+BEGIN
+ IF (contents # NIL) & (body # contents) THEN
+ Push(b_stk);
+ Clear(f_stk);
+ body := contents;
+ Ycur := Ycont;
+ Ymin := 0;
+ Ymax := body.Ymax
+ END
+END Contents;
+
+
+PROCEDURE Descr*;
+BEGIN
+ IF (description # NIL) & (body # description) THEN
+ Push(b_stk);
+ Clear(f_stk);
+ body := description;
+ Ycur := 0;
+ Ymin := 0;
+ Ymax := body.Ymax
+ END
+END Descr;
+
+
+PROCEDURE Up*;
+BEGIN
+ DEC(Ycur, LineH);
+ SU.MinMax(Ycur, Ymin, Ymax)
+END Up;
+
+
+PROCEDURE Down*;
+BEGIN
+ INC(Ycur, LineH);
+ SU.MinMax(Ycur, Ymin, Ymax)
+END Down;
+
+
+PROCEDURE PageUp*;
+VAR i: INTEGER;
+BEGIN
+ FOR i := 1 TO Lines * (ORD(Settings.TwoCol) + 1) DO
+ Up
+ END
+END PageUp;
+
+
+PROCEDURE PageDown*;
+VAR i: INTEGER;
+BEGIN
+ FOR i := 1 TO Lines * (ORD(Settings.TwoCol) + 1) DO
+ Down
+ END
+END PageDown;
+
+
+PROCEDURE Home*;
+BEGIN
+ IF Ycur # Ymin THEN
+ Push(b_stk);
+ Clear(f_stk);
+ Ycur := Ymin
+ END
+END Home;
+
+
+PROCEDURE End*;
+BEGIN
+ IF Ycur # Ymax THEN
+ Push(b_stk);
+ Clear(f_stk);
+ Ycur := Ymax
+ END
+END End;
+
+
+PROCEDURE ScrollBar*;
+BEGIN
+ Ycur := sb.position * LineH + Ymin
+END ScrollBar;
+
+
+PROCEDURE GetBody(tag: XML.TAG): XML.TAG;
+BEGIN
+ WHILE (tag # NIL) & (tag.value # XML.tag_body) DO
+ tag := tag.parent(XML.TAG)
+ END
+ RETURN tag
+END GetBody;
+
+
+PROCEDURE layout3(Body: XML.ELEMENT; X, Y: INTEGER);
+VAR
+ ptr : V.ANYPTR;
+ text : XML.TEXT;
+ sect : XML.TAG;
+ y : INTEGER;
+ i : INTEGER;
+BEGIN
+ i := 0;
+ WHILE i < references.count DO
+ ptr := V.get(references, i);
+ text := ptr(XML.TEXT);
+ y := text.Y - Ycur;
+ IF (y <= Y) & (Y <= y + Font.FontH()) & (text.X <= X) & (X <= text.X + text.width) THEN
+ sect := text.parent(XML.TAG);
+ IF Body = contents THEN
+ WHILE (sect # NIL) & (sect.value # XML.tag_contents_item) DO
+ sect := sect.parent(XML.TAG)
+ END
+ ELSE
+ WHILE (sect # NIL) & (sect # Body) DO
+ IF sect.value = XML.tag_contents_item THEN
+ sect := NIL
+ ELSE
+ sect := sect.parent(XML.TAG)
+ END
+ END
+ END;
+
+ IF sect # NIL THEN
+ sect := text.parent(XML.TAG);
+ WHILE sect # NIL DO
+ IF (sect.value = XML.tag_contents_item) & (Body = contents) OR (sect.value = XML.tag_a) THEN
+ ref := sect;
+ sect := NIL;
+ i := references.count
+ ELSE
+ sect := sect.parent(XML.TAG)
+ END
+ END
+ END
+ END;
+ INC(i)
+ END
+END layout3;
+
+
+PROCEDURE MouseDown;
+BEGIN
+ IF ~mouseDown THEN
+ mouseDown := TRUE;
+ clickRef := ref;
+ ref.Clicked := TRUE;
+ Draw
+ END
+END MouseDown;
+
+
+PROCEDURE MouseUp;
+VAR
+ note : BOOLEAN;
+ URL : INTEGER;
+ redraw: BOOLEAN;
+BEGIN
+ redraw := FALSE;
+ mouseDown := FALSE;
+ IF (ref # NIL) & (clickRef = ref) & ref.Clicked THEN
+ redraw := TRUE;
+ ref.Clicked := FALSE;
+ note := FALSE;
+ URL := 0;
+ IF ref.value = XML.tag_a THEN
+ ref := XML.GetRef(ref, note, URL)
+ ELSE
+ ref := ref.parent(XML.TAG)
+ END;
+ IF ref # NIL THEN
+ Push(b_stk);
+ Clear(f_stk);
+ Ycur := ref.Ymin;
+ IF note THEN
+ body := ref
+ ELSE
+ body := GetBody(ref)
+ END;
+ Ymax := body.Ymax;
+ Ymin := body.Ymin;
+
+ IF ~clickRef.Visited THEN
+ clickRef.Visited := TRUE;
+ PushRef(clickRef)
+ END
+ ELSIF URL # 0 THEN
+ SU.Run(Ini.Browser, URL);
+ IF ~clickRef.Visited THEN
+ clickRef.Visited := TRUE;
+ PushRef(clickRef)
+ END
+ END;
+
+ END;
+ IF clickRef # NIL THEN
+ clickRef.Clicked := FALSE;
+ clickRef := NIL;
+ redraw := TRUE
+ END;
+ IF hoverRef # NIL THEN
+ hoverRef.Clicked := FALSE;
+ hoverRef := NIL;
+ redraw := TRUE
+ END;
+ IF redraw THEN
+ Draw
+ END
+END MouseUp;
+
+
+PROCEDURE Click*(X, Y: INTEGER; clicked: BOOLEAN);
+VAR
+ note : BOOLEAN;
+ URL : INTEGER;
+ urlchars: S.CHARS;
+ urlstr1 : S.STRING;
+BEGIN
+ DEC(Y, Settings.PADDING.Top);
+ DEC(X, Settings.PADDING.Left);
+ IF (0 <= Y) & (Y <= Lines * LineH) THEN
+ ref := NIL;
+ layout3(body, X, Y);
+ IF (ref = NIL) & Settings.TwoCol THEN
+ layout3(body, X - ColLeft.Width - Settings.PADDING.ColInter, Y + Lines * LineH);
+ END;
+ hoverRef := ref;
+ IF clicked THEN
+ MouseDown
+ ELSE
+ MouseUp
+ END;
+ IF ref # NIL THEN
+ SU.SetCursor(cursor);
+ note := FALSE;
+ URL := 0;
+ IF ref.value = XML.tag_a THEN
+ ref := XML.GetRef(ref, note, URL)
+ END;
+ IF URL # 0 THEN
+ S.PtrToString(URL, urlstr1);
+ S.StrToChars(urlstr1, urlchars)
+ END
+ ELSE
+ SU.SetCursor(0);
+ urlstr1 := ""
+ END;
+ IF urlstr1 # urlstr THEN
+ urlstr := urlstr1;
+ DrawStatus
+ END
+ ELSE
+ SU.SetCursor(0);
+ urlstr := "";
+ ref := NIL;
+ DrawStatus
+ END
+END Click;
+
+
+PROCEDURE Scroll*(value: INTEGER);
+BEGIN
+ value := 2 * value;
+ WHILE value > 0 DO
+ Down;
+ DEC(value)
+ ELSIF value < 0 DO
+ Up;
+ INC(value)
+ END
+END Scroll;
+
+
+PROCEDURE main(fb: XML.ELEMENT; Contents: BOOLEAN);
+VAR
+ cur: XML.ELEMENT;
+ tag: XML.TAG;
+ par, epi: INTEGER;
+
+
+ PROCEDURE lout(body: XML.ELEMENT);
+ BEGIN
+ TextCount := 0;
+ X := 0;
+ Y := Settings.SUP;
+ layout(body(XML.TAG).child.first);
+ body(XML.TAG).Ymax := Y - Settings.SUP
+ END lout;
+
+ PROCEDURE lout_one_col(body: XML.ELEMENT);
+ BEGIN
+ IF body # NIL THEN
+ IF Settings.TwoCol THEN
+ W := W2;
+ Settings.TwoCol := FALSE;
+ lout(body);
+ Settings.TwoCol := TRUE;
+ W := W1
+ ELSE
+ lout(body)
+ END
+ END
+ END lout_one_col;
+
+BEGIN
+ TextCount := 0;
+ sup := 0;
+ sub := 0;
+ epigraph := 0;
+ align := 0;
+ code := 0;
+ strong := 0;
+ italic := 0;
+ strike := 0;
+ refer := 0;
+ SU.ErrorIf(fb = NIL, 11);
+ MainBody := FALSE;
+ description := NIL;
+ mainbody := NIL;
+ cover := NIL;
+ cur := fb;
+ cur := cur(XML.TAG).child.first;
+ WHILE (cur # NIL) & (mainbody = NIL) DO
+ IF cur IS XML.TAG THEN
+ tag := cur(XML.TAG);
+ IF tag.value = XML.tag_description THEN
+ description := tag
+ ELSIF tag.value = XML.tag_body THEN
+ mainbody := tag
+ END
+ END;
+ cur := cur.next
+ END;
+ SU.ErrorIf(mainbody = NIL, 12);
+
+ WHILE cur # NIL DO
+ IF (cur IS XML.TAG) & (cur(XML.TAG).value = XML.tag_body) THEN
+ lout(cur)
+ END;
+ cur := cur.next
+ END;
+
+ IF Contents THEN
+ contents := XML.CreateTag();
+ MainBody := TRUE;
+ END;
+ lout(mainbody);
+ IF Contents & (contents.child.first = NIL) THEN
+ DISPOSE(contents)
+ END;
+ MainBody := FALSE;
+ epigraph := 1;
+ par := Settings.PARAGRAPH;
+ epi := Settings.EPIGRAPH;
+ Settings.PARAGRAPH := 0;
+ Settings.EPIGRAPH := 0;
+ lout_one_col(contents);
+ Settings.EPIGRAPH := epi;
+ Settings.PARAGRAPH := par;
+ epigraph := 0;
+ lout_one_col(description);
+ body := mainbody;
+ Ymax := body.Ymax;
+ Ycur := 0;
+ Ymin := 0;
+ Ycont := 0
+END main;
+
+
+PROCEDURE Find* (d: INTEGER);
+VAR
+ y, min, max: INTEGER;
+
+BEGIN
+ Search.fnext(body, y, d);
+ IF y >= 0 THEN
+ DEC(y, y MOD LineH);
+ min := Ycur;
+ IF Settings.TwoCol THEN
+ max := min + ColLeft.Height + ColRight.Height - LineH
+ ELSE
+ max := min + ColLeft.Height - LineH
+ END;
+
+ IF (y < min) OR (y > max) THEN
+ Ycur := MAX(y - ColLeft.Height DIV 2, 0)
+ END;
+
+ DEC(Ycur, Ycur MOD LineH)
+ END
+END Find;
+
+
+PROCEDURE OpenSearch*;
+BEGIN
+ Search.open(Find)
+END OpenSearch;
+
+
+PROCEDURE CloseSearch*;
+BEGIN
+ Search.close
+END CloseSearch;
+
+
+PROCEDURE found* (): BOOLEAN;
+ RETURN Search.found(body)
+END found;
+
+
+PROCEDURE FontSizeChange(fs: INTEGER);
+BEGIN
+ Settings.SUP := fs DIV 4;
+ Settings.SUB := fs DIV 4;
+ Settings.SpaceW := fs DIV 2;
+ Settings.LEVEL := Settings.PARAGRAPH;
+ Settings.PADDING.Bottom := Settings.PADDING.Top;
+ Settings.PADDING.Left := G.Buffer.Width * Settings.PADDING.LRpc DIV 100;
+ IF Settings.PADDING.Left = 0 THEN
+ Settings.PADDING.Left := 1
+ END;
+ Settings.PADDING.Right := Settings.PADDING.Left;
+ Settings.PADDING.ColInter := G.Buffer.Width * Settings.PADDING.CInt DIV 100;
+
+ LineH := Font.FontH() + Settings.SUP + Settings.SUB + Settings.InterLin;
+ Window.InitRect(
+ ColLeft, Settings.PADDING.Left, Settings.PADDING.Top,
+ G.Buffer.Width - Settings.PADDING.Left - Settings.PADDING.Right,
+ G.Buffer.Height - Settings.PADDING.Top - Settings.PADDING.Bottom);
+ IF Settings.TwoCol THEN
+ ColLeft.Width := (ColLeft.Width - Settings.PADDING.ColInter) DIV 2;
+ ColRight := ColLeft;
+ ColRight.Left := ColLeft.Left + ColLeft.Width + Settings.PADDING.ColInter
+ END;
+ W := ColLeft.Width;
+ Lines := ColLeft.Height DIV LineH;
+ ColLeft.Height := Lines * LineH;
+ ColRight.Height := ColLeft.Height;
+END FontSizeChange;
+
+
+PROCEDURE Resize*(Width, Height: INTEGER);
+VAR d: REAL; resize: BOOLEAN; sizeX, sizeY, data: INTEGER;
+
+ PROCEDURE stk1(stk: XML.LIST);
+ VAR cur: StackItem;
+ BEGIN
+ cur := stk.first(StackItem);
+ WHILE cur # NIL DO
+ cur.d := FLT(cur.Ycur - cur.body.Ymin) / FLT(cur.body.Ymax - cur.body.Ymin);
+ cur := cur.next(StackItem)
+ END
+ END stk1;
+
+ PROCEDURE stk2(stk: XML.LIST);
+ VAR cur: StackItem;
+ BEGIN
+ cur := stk.first(StackItem);
+ WHILE cur # NIL DO
+ cur.Ycur := FLOOR(FLT(cur.body.Ymax - cur.body.Ymin) * cur.d) + cur.body.Ymin;
+ cur.Ycur := cur.Ycur - cur.Ycur MOD LineH;
+ SU.MinMax(cur.Ycur, cur.body.Ymin, cur.body.Ymax);
+ cur := cur.next(StackItem)
+ END
+ END stk2;
+
+BEGIN
+ resize := (Width # G.Buffer.Width) OR resized;
+ G.Resize(Width, Height);
+ G.SetColor(Settings.Colors[BACK_COLOR]);
+ IF (Settings.Picture # 0) & Settings.b_pict THEN
+ LibImg.GetInf(Settings.Picture, sizeX, sizeY, data);
+ G.BackImage(sizeX, sizeY, data);
+ ELSE
+ G.Clear;
+ G.Copy(G.Buffer, G.Buffer3, 0, G.Buffer.Height, 0)
+ END;
+
+ IF Font.FontH() # 0 THEN
+ FontSizeChange(Font.FontH());
+ ELSE
+ FontSizeChange(Settings.FontSize);
+ END;
+
+ ColLeft.Width := G.Buffer.Width - Settings.PADDING.Left - Settings.PADDING.Right;
+ IF Settings.TwoCol THEN
+ ColLeft.Width := (ColLeft.Width - Settings.PADDING.ColInter) DIV 2;
+ ColRight.Width := ColLeft.Width;
+ ColRight.Left := ColLeft.Left + ColLeft.Width + Settings.PADDING.ColInter
+ END;
+ ColLeft.Height := G.Buffer.Height - Settings.PADDING.Top - Settings.PADDING.Bottom;
+ Lines := ColLeft.Height DIV LineH;
+ ColLeft.Height := Lines * LineH;
+ ColRight.Height := ColLeft.Height;
+
+ IF done & resize THEN
+ resized := FALSE;
+ Push(b_stk);
+ stk1(b_stk);
+ stk1(f_stk);
+ IF contents # NIL THEN
+ d := FLT(Ycont) / FLT(contents.Ymax)
+ END;
+ W := ColLeft.Width;
+ W2 := ColLeft.Width + ColRight.Width + Settings.PADDING.ColInter;
+ W1 := W;
+ main(XML.FB, FALSE);
+ Search.resize;
+ stk2(b_stk);
+ stk2(f_stk);
+ IF contents # NIL THEN
+ Ycont := FLOOR(FLT(contents.Ymax) * d);
+ Ycont := Ycont - Ycont MOD LineH;
+ SU.MinMax(Ycont, 0, contents.Ymax)
+ END;
+ Pop(b_stk);
+ END
+END Resize;
+
+
+PROCEDURE SetColors*;
+BEGIN
+ Settings.Colors[BACK_COLOR] := Ini.GetColor("back", Settings.Colors[BACK_COLOR]);
+ Settings.Colors[TEXT_COLOR] := Ini.GetColor("text", Settings.Colors[TEXT_COLOR]);
+ Settings.Colors[ITALIC_COLOR] := Ini.GetColor("italic", Settings.Colors[ITALIC_COLOR]);
+ Settings.Colors[LINK_COLOR] := Ini.GetColor("link", Settings.Colors[LINK_COLOR]);
+ Settings.Colors[VISITED_COLOR] := Ini.GetColor("visited", Settings.Colors[LINK_COLOR]);
+END SetColors;
+
+
+PROCEDURE Resized(set1, set2: TSettings): BOOLEAN;
+ RETURN (set1.FontSize # set2.FontSize) OR (set1.TwoCol # set2.TwoCol) OR
+ (set1.PARAGRAPH # set2.PARAGRAPH) OR (set1.EPIGRAPH # set2.EPIGRAPH) OR
+ (set1.PADDING.LRpc # set2.PADDING.LRpc) OR (set1.PADDING.CInt # set2.PADDING.CInt)
+ OR (set1.InterLin # set2.InterLin)
+END Resized;
+
+
+PROCEDURE SetSettings*(NewSet: TSettings);
+BEGIN
+ resized := Resized(Settings, NewSet) OR resized;
+ Settings := NewSet;
+ Font.Init(Settings.Colors[ITALIC_COLOR], Settings.Colors[TEXT_COLOR], Settings.FontSize);
+ Resize(G.Buffer.Width, G.Buffer.Height)
+END SetSettings;
+
+
+PROCEDURE Init*(Left, Top, Width, Height: INTEGER);
+BEGIN
+ G.Resize(Width, Height);
+ Canvas_X := Left;
+ Canvas_Y := Top
+END Init;
+
+
+PROCEDURE Start;
+BEGIN
+ XML.Open(FileName);
+ main(XML.FB, TRUE);
+ done := TRUE;
+ SU.Halt
+END Start;
+
+
+PROCEDURE CleanHistory*(fname: S.STRING);
+VAR F: File.FS; pos, pos2, fsize, size, buf, buf2: INTEGER; c: CHAR;
+BEGIN
+ F := File.Open(fname);
+ IF F # NIL THEN
+ fsize := File.Seek(F, 0, 2);
+ pos := File.Seek(F, 0, 0);
+ buf := K.malloc(fsize + 1024);
+ buf2 := K.malloc(fsize + 1024);
+ pos := File.Read(F, buf, fsize);
+ File.Close(F);
+ pos := 0;
+ pos2 := 0;
+ WHILE pos < fsize DO
+ sys.GET(buf + pos, size);
+ sys.GET(buf + pos + 4, c);
+ IF c = 0X THEN
+ sys.MOVE(buf + pos, buf2 + pos2, size);
+ pos2 := pos2 + size
+ END;
+ pos := pos + size
+ END;
+ F := File.Create(fname);
+ pos := File.Write(F, buf2, pos2);
+ File.Close(F);
+ buf := K.free(buf);
+ buf2 := K.free(buf2)
+ END
+END CleanHistory;
+
+
+PROCEDURE Save;
+VAR history: File.FS; win_size_x, win_size_y, size, pos: INTEGER;
+
+ PROCEDURE WriteInt(history: File.FS; x: INTEGER);
+ BEGIN
+ IF Write.Int(history, x) THEN END
+ END WriteInt;
+
+ PROCEDURE WriteStk(history: File.FS; VAR stk: XML.LIST; links: BOOLEAN);
+ VAR
+ cur: StackItem;
+ BEGIN
+ WriteInt(history, XML.ListCount(stk));
+ cur := stk.first(StackItem);
+ WHILE cur # NIL DO
+ WriteInt(history, cur.body.num);
+ IF ~links THEN
+ WriteInt(history, cur.Ycur)
+ END;
+ cur := cur.next(StackItem)
+ END
+ END WriteStk;
+
+BEGIN
+ Ini.Save(Settings.Colors, Settings.b_pict);
+ history := File.Open(Ini.History);
+ IF history = NIL THEN
+ history := File.Create(Ini.History)
+ ELSE
+ pos := File.Seek(history, 0 , 2)
+ END;
+ size := 1 + 18*4 + 1 + 8*(XML.ListCount(b_stk) + XML.ListCount(f_stk)) + 4*XML.ListCount(vis_ref) + 12;
+ WriteInt(history, size);
+ IF Write.Char(history, 0X) THEN END;
+ WriteInt(history, fsize2);
+ WriteInt(history, chksum);
+ SU.GetWindowSize(win_size_x, win_size_y);
+ WriteInt(history, win_size_x);
+ WriteInt(history, win_size_y);
+ WriteInt(history, Settings.PADDING.LRpc);
+ WriteInt(history, Settings.PADDING.Top);
+ WriteInt(history, Settings.PADDING.CInt);
+ WriteInt(history, Settings.PARAGRAPH);
+ WriteInt(history, Settings.EPIGRAPH);
+ WriteInt(history, Settings.InterLin);
+
+ IF Write.Boolean(history, Settings.TwoCol) THEN END;
+
+ WriteInt(history, Settings.FontSize);
+ WriteInt(history, body.num);
+ WriteInt(history, Ymin);
+ WriteInt(history, Ymax);
+ WriteInt(history, Ycur);
+ WriteInt(history, Ycont);
+
+ WriteStk(history, b_stk, FALSE);
+ WriteStk(history, f_stk, FALSE);
+ WriteStk(history, vis_ref, TRUE);
+
+ WriteInt(history, size);
+
+ File.Close(history);
+ CleanHistory(Ini.History)
+END Save;
+
+
+PROCEDURE ReadInt(VAR x: INTEGER);
+BEGIN
+ IF Read.Int(history, x) THEN END
+END ReadInt;
+
+
+PROCEDURE Load;
+VAR body_num, ycur, size, pos: INTEGER;
+
+ PROCEDURE ReadStk(VAR stk: XML.LIST);
+ VAR n, num: INTEGER;
+ BEGIN
+ ReadInt(n);
+ WHILE n > 0 DO
+ ReadInt(num);
+ body := XML.GetTagByNum(num);
+ ReadInt(Ycur);
+ Push(stk);
+ DEC(n)
+ END
+ END ReadStk;
+
+ PROCEDURE ReadRef;
+ VAR
+ n, num: INTEGER;
+ ref: XML.TAG;
+ BEGIN
+ ReadInt(n);
+ WHILE n > 0 DO
+ ReadInt(num);
+ ref := XML.GetTagByNum(num);
+ IF ref # NIL THEN
+ PushRef(ref);
+ ref.Visited := TRUE
+ END;
+ DEC(n)
+ END
+ END ReadRef;
+
+BEGIN
+ ReadInt(Settings.PADDING.LRpc);
+ ReadInt(Settings.PADDING.Top);
+ ReadInt(Settings.PADDING.CInt);
+ ReadInt(Settings.PARAGRAPH);
+ ReadInt(Settings.EPIGRAPH);
+ ReadInt(Settings.InterLin);
+ IF Read.Boolean(history, Settings.TwoCol) THEN END;
+ ReadInt(Settings.FontSize);
+
+ SetSettings(Settings);
+
+ ReadInt(body_num);
+ ReadInt(Ymin);
+ ReadInt(Ymax);
+ ReadInt(ycur);
+ ReadInt(Ycont);
+
+ ReadStk(b_stk);
+ ReadStk(f_stk);
+ ReadRef;
+
+ ReadInt(size);
+ pos := File.Seek(history, -size, 1);
+ pos := File.Seek(history, 4, 1);
+ IF Write.Char(history, 1X) THEN END;
+
+ Ycur := ycur;
+ body := XML.GetTagByNum(body_num);
+ File.Close(history)
+END Load;
+
+
+PROCEDURE GetWinSize*(hist_fn: S.STRING; VAR win_size_x, win_size_y: INTEGER);
+VAR c: CHAR; size, pos, x, y, fsize, _chksum: INTEGER; found: BOOLEAN;
+BEGIN
+ fsize2 := RF.FileSize(hist_fn);
+ chksum := RF.ChkSum(hist_fn);
+ found := FALSE;
+ history := File.Open(Ini.History);
+ pos := File.Seek(history, -4, 2);
+ last := FALSE;
+ WHILE pos >= 0 DO
+ IF Read.Int(history, size) THEN
+ pos := File.Seek(history, -size + 4, 1);
+ END;
+ IF Read.Char(history, c) THEN END;
+ ReadInt(fsize);
+ ReadInt(_chksum);
+ IF (c = 0X) & (fsize = fsize2) & (_chksum = chksum) THEN
+ found := TRUE;
+ IF Read.Int(history, x) & Read.Int(history, y) THEN
+ win_size_x := x;
+ win_size_y := y;
+ ELSE
+ found := FALSE
+ END;
+ pos := -1
+ ELSE
+ IF ~last THEN
+ last := TRUE;
+ ReadInt(x);
+ ReadInt(y);
+ ReadInt(Settings.PADDING.LRpc);
+ ReadInt(Settings.PADDING.Top);
+ ReadInt(Settings.PADDING.CInt);
+ ReadInt(Settings.PARAGRAPH);
+ ReadInt(Settings.EPIGRAPH);
+ ReadInt(Settings.InterLin);
+ IF Read.Boolean(history, Settings.TwoCol) THEN END;
+ ReadInt(Settings.FontSize);
+ END;
+ pos := File.Seek(history, pos - 8, 0)
+ END
+ END;
+ IF ~found THEN
+ File.Close(history)
+ END
+END GetWinSize;
+
+
+PROCEDURE Open*(FName: S.STRING; DrawWindow, _DrawStatus, _DrawToolbar: SU.ENTRY);
+VAR PID, event: INTEGER;
+BEGIN
+ DrawStatus := _DrawStatus;
+ DrawToolbar := _DrawToolbar;
+ cursor := SU.LoadCursor(Cursor.GetCursor());
+ references := V.create(1024);
+ ref_depth := 0;
+ done := FALSE;
+ loaded := FALSE;
+ FilePath := FName;
+ FileName := FName;
+ S.GetPath(FilePath);
+ W := ColLeft.Width;
+ W1 := W;
+ W2 := ColLeft.Width + ColRight.Width + Settings.PADDING.ColInter;
+ Lines := ColLeft.Height DIV LineH;
+ ColLeft.Height := Lines * LineH;
+ PID := SU.NewThread(Start, Stack);
+ WHILE ~SU.IsTerminated(PID) DO
+ event := SU.CheckEvent();
+ IF event = 3 THEN
+ SU.TerminateThreadId(PID);
+ SU.Halt
+ END;
+ G.Progress(RF.Progress());
+ G.Draw(Canvas_X, Canvas_Y);
+ DrawWindow;
+ SU.Pause(30)
+ END;
+ IF ~done THEN
+ SU.Halt
+ END;
+ loaded := TRUE;
+ resized := TRUE;
+ IF history # NIL THEN
+ Load
+ ELSE
+ SetSettings(Settings)
+ END
+END Open;
+
+
+PROCEDURE Close*;
+BEGIN
+ SU.DelCursor(cursor);
+ Save;
+ SU.Halt
+END Close;
+
+
+PROCEDURE SetScrollBar*(_sb: box_lib.scrollbar);
+BEGIN
+ sb := _sb
+END SetScrollBar;
+
+
+PROCEDURE Set_b_pict*(b_pict: BOOLEAN);
+BEGIN
+ Settings.b_pict := b_pict
+END Set_b_pict;
+
+
+BEGIN
+ clickRef := NIL;
+ hoverRef := NIL;
+ mouseDown := FALSE
+END DOM.
diff --git a/programs/other/fb2reader/SRC/FB2READ.ob07 b/programs/other/fb2reader/SRC/FB2READ.ob07
new file mode 100644
index 0000000000..8e19474be1
--- /dev/null
+++ b/programs/other/fb2reader/SRC/FB2READ.ob07
@@ -0,0 +1,366 @@
+(*
+ Copyright 2016-2023 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE FB2READ;
+
+IMPORT
+
+ DOM, SU := SysUtils, S := Strings, W := Window, Settings, OpenDlg,
+ G := Graph, SelEnc, Ini, File, box_lib, Font, SearchForm, Toolbar;
+
+
+CONST
+
+ CLOSE = 1;
+ BACK = 16;
+ FORWARD = 17;
+ CONTENTS = 18;
+ DESCR = 19;
+ SETTINGS = 20;
+ SEARCH = 21;
+
+ KEY_DOWN_CODE = 177;
+ KEY_UP_CODE = 178;
+ KEY_PG_DOWN_CODE = 183;
+ KEY_PG_UP_CODE = 184;
+ KEY_HOME_CODE = 180;
+ KEY_END_CODE = 181;
+ KEY_F2_CODE = 51;
+ KEY_F3_CODE = 52;
+ KEY_F4_CODE = 53;
+ KEY_F10_CODE = 49;
+
+ TOOLBAR_LEFT = 5;
+ TOOLBAR_TOP = 6;
+ BUTTON_HEIGHT = 24;
+
+ CANVAS_LEFT = 1;
+ CANVAS_TOP = 2 * TOOLBAR_TOP + Toolbar.BtnSize;
+
+ WINDOW_BEVEL = 4;
+
+ SCROLLBAR_WIDTH = 20;
+ STATUSBAR_HEIGHT = 18;
+
+VAR
+
+ Window : W.TWindow;
+ toolbar : Toolbar.tToolbar;
+ SkinHeight : INTEGER;
+ Open : OpenDlg.Dialog;
+ FileName : S.STRING;
+ sb : box_lib.scrollbar;
+
+
+PROCEDURE ToolbarEnable;
+BEGIN
+ Toolbar.enable(toolbar, BACK, DOM.BackEnabled());
+ Toolbar.enable(toolbar, FORWARD, DOM.FrwEnabled());
+ Toolbar.enable(toolbar, CONTENTS, DOM.ContentsEnabled());
+ Toolbar.enable(toolbar, DESCR, DOM.DescrEnabled());
+END ToolbarEnable;
+
+
+PROCEDURE ToolBar;
+BEGIN
+ sb := box_lib.kolibri_scrollbar(sb, (G.Buffer.Width + CANVAS_LEFT) * 65536 + SCROLLBAR_WIDTH + 1,
+ CANVAS_TOP * 65536 + G.Buffer.Height, SCROLLBAR_WIDTH, sb.max_area, sb.cur_area, sb.position, SU.lightColor, SU.btnColor, 0, 0);
+ box_lib.scrollbar_v_draw(sb);
+ ToolbarEnable;
+ Toolbar.draw(toolbar);
+END ToolBar;
+
+
+PROCEDURE Resize;
+VAR Width, Height: INTEGER;
+BEGIN
+ SU.GetWindowPos(Window.Left, Window.Top);
+ SU.GetWindowSize(Width, Height);
+ IF (Window.Width # Width) OR (Window.Height # Height) OR (SkinHeight # SU.SkinHeight()) THEN
+ SU.MinMax(Width, 640, 65535);
+ SU.MinMax(Height, 400, 65535);
+ Window.dWidth := Width - Window.Width;
+ Window.dHeight := Height - Window.Height;
+ Window.Width := Width;
+ Window.Height := Height;
+ SU.SetWindowSize(Width, Height);
+ DOM.Resize(G.Buffer.Width + Window.dWidth, G.Buffer.Height + Window.dHeight + (SkinHeight - SU.SkinHeight()));
+ SkinHeight := SU.SkinHeight()
+ END
+END Resize;
+
+
+PROCEDURE DrawStatus;
+BEGIN
+ SU.DrawRect(0, Window.Height - SkinHeight - WINDOW_BEVEL - STATUSBAR_HEIGHT + 1, Window.Width - 2 * WINDOW_BEVEL - 1, STATUSBAR_HEIGHT, SU.winColor);
+ IF DOM.urlstr # "" THEN
+ SU.OutText(CANVAS_LEFT, Window.Height - SkinHeight - WINDOW_BEVEL - STATUSBAR_HEIGHT + 2, DOM.urlstr,
+ MIN(LENGTH(DOM.urlstr), (Window.Width - 2 * WINDOW_BEVEL - 1 - CANVAS_LEFT * 2) DIV 8), SU.textColor)
+ ELSIF DOM.found() THEN
+ SU.OutText(CANVAS_LEFT, Window.Height - SkinHeight - WINDOW_BEVEL - STATUSBAR_HEIGHT + 2,
+ "F2 - first | F3 - next | F4 - prev. | F10 - exit", 48, SU.textColor)
+ END
+END DrawStatus;
+
+
+PROCEDURE DrawWindow;
+BEGIN
+ SU.GetSystemColors;
+ SU.WindowRedrawStatus(1);
+ IF Window.Created THEN
+ Resize
+ ELSE
+ Window.Created := TRUE
+ END;
+ SU.DefineAndDrawWindow(Window.Left, Window.Top, Window.Width, Window.Height,
+ SU.winColor, LSL(ORD({0, 1, 2}), 4) + 4 - ORD(DOM.loaded), Window.Caption);
+ SU.DrawRect(0, 0, Window.Width - 2 * WINDOW_BEVEL - 1, CANVAS_TOP, SU.winColor);
+ SU.DrawRect(0, Window.Height - SkinHeight - WINDOW_BEVEL - STATUSBAR_HEIGHT + 1, Window.Width - 2 * WINDOW_BEVEL - 1, STATUSBAR_HEIGHT, SU.winColor);
+ SU.DrawRect(0, 0, CANVAS_LEFT, Window.Height - SkinHeight - WINDOW_BEVEL, SU.winColor);
+ SU.DrawRect(Window.Width - 2 * WINDOW_BEVEL - CANVAS_LEFT - 1 - SCROLLBAR_WIDTH - 2, 0, CANVAS_LEFT + SCROLLBAR_WIDTH + 2, Window.Height - SkinHeight - WINDOW_BEVEL, SU.winColor);
+ IF DOM.loaded THEN
+ ToolBar;
+ DOM.Draw;
+ DrawStatus
+ END;
+ SU.WindowRedrawStatus(2)
+END DrawWindow;
+
+
+PROCEDURE ConvMousePos(VAR X, Y: INTEGER);
+BEGIN
+ X := X - Window.Left - WINDOW_BEVEL - 1;
+ Y := Y - Window.Top - SkinHeight
+END ConvMousePos;
+
+
+PROCEDURE DrawToolbar;
+BEGIN
+ ToolbarEnable;
+ Toolbar.drawIcons(toolbar)
+END DrawToolbar;
+
+
+PROCEDURE ButtonClick;
+BEGIN
+ CASE SU.GetButtonCode() OF
+ |0 :
+ |CLOSE : SearchForm.close(FALSE);
+ Settings.Close;
+ DOM.Close
+ |BACK : DOM.Back
+ |FORWARD : DOM.Forward
+ |CONTENTS : DOM.Contents
+ |DESCR : DOM.Descr
+ |SEARCH : DOM.OpenSearch
+ |SETTINGS : Settings.Open
+ END;
+ DOM.Draw;
+ DrawStatus
+END ButtonClick;
+
+
+PROCEDURE KeyDown;
+BEGIN
+ CASE SU.GetKeyCode() OF
+ |KEY_DOWN_CODE : DOM.Down
+ |KEY_UP_CODE : DOM.Up
+ |KEY_PG_DOWN_CODE : DOM.PageDown
+ |KEY_PG_UP_CODE : DOM.PageUp
+ |KEY_HOME_CODE : DOM.Home
+ |KEY_END_CODE : DOM.End
+ |KEY_F2_CODE : DOM.Find(0)
+ |KEY_F3_CODE : DOM.Find(1)
+ |KEY_F4_CODE : DOM.Find(-1)
+ |KEY_F10_CODE : DOM.CloseSearch
+ ELSE
+ END;
+ DOM.Draw;
+ DrawStatus
+END KeyDown;
+
+
+PROCEDURE CanvasIsClicked(X, Y: INTEGER): BOOLEAN;
+ RETURN
+ (CANVAS_LEFT <= X) & (X < CANVAS_LEFT + G.Buffer.Width) &
+ (CANVAS_TOP <= Y) & (Y < CANVAS_TOP + G.Buffer.Height)
+END CanvasIsClicked;
+
+
+PROCEDURE MouseEvent;
+
+VAR
+
+ mouse_status : SET;
+ X, Y : INTEGER;
+ scroll : INTEGER;
+
+BEGIN
+ SU.MousePos(X, Y);
+ mouse_status := SU.MouseStatus();
+ scroll := SU.MouseVScroll();
+ IF SU.L_BUTTON IN mouse_status THEN
+ ConvMousePos(X, Y);
+ IF CanvasIsClicked(X, Y) THEN
+ X := X - CANVAS_LEFT;
+ Y := Y - CANVAS_TOP;
+ DOM.Click(X, Y, TRUE)
+ END
+ ELSIF scroll # 0 THEN
+ DOM.Scroll(scroll);
+ DOM.Draw
+ ELSE
+ ConvMousePos(X, Y);
+ IF CanvasIsClicked(X, Y) THEN
+ X := X - CANVAS_LEFT;
+ Y := Y - CANVAS_TOP;
+ DOM.Click(X, Y, FALSE)
+ END
+ END
+END MouseEvent;
+
+
+PROCEDURE Empty;
+END Empty;
+
+
+PROCEDURE OpenFile;
+BEGIN
+ Open := OpenDlg.Create(Empty, 0, Ini.Default, Ini.Files);
+ OpenDlg.Show(Open, 500, 400);
+ WHILE Open.status = 2 DO
+ SU.Pause(30)
+ END;
+ IF Open.status = 0 THEN
+ SU.Halt
+ END;
+ COPY(Open.FilePath, FileName);
+ OpenDlg.Destroy(Open)
+END OpenFile;
+
+
+PROCEDURE IsFB2(FileName: S.STRING): BOOLEAN;
+VAR temp: S.STRING;
+BEGIN
+ temp := FileName;
+ S.Reverse(temp);
+ temp[4] := 0X;
+ S.UCase(temp)
+ RETURN temp = "2BF."
+END IsFB2;
+
+
+PROCEDURE main(title: ARRAY OF CHAR);
+VAR WinW, X1, Y1, X2, Y2, scr_pos: INTEGER; Win2: W.TWindow; resize: BOOLEAN; FilePath: S.STRING; defpath: BOOLEAN;
+BEGIN
+ SkinHeight := SU.SkinHeight();
+ sb := box_lib.kolibri_new_scrollbar(10 * 65536 + 200, 10 * 65536 + 30, 25, 15, 10, 0, 0, 0, 0, 0);
+ DOM.SetScrollBar(sb);
+ defpath := TRUE;
+ SU.GetParam(FileName);
+ IF FileName = "" THEN
+ OpenFile
+ END;
+
+ IF FileName[0] = "!" THEN
+ FileName[0] := "/";
+ defpath := FALSE
+ END;
+
+ IF defpath THEN
+ FilePath := FileName;
+ S.GetPath(FilePath);
+ Ini.SetDefaultPath(FilePath);
+ DOM.SetColors;
+ DOM.Set_b_pict(Ini.b_pict);
+ Ini.Save(DOM.Settings.Colors, DOM.Settings.b_pict)
+ END;
+
+ IF ~IsFB2(FileName) THEN
+ SelEnc.Show(FileName)
+ END;
+
+ SU.SetEventsMask({0, 1, 2, 5, 31});
+ SU.GetScreenArea(X1, Y1, X2, Y2);
+ WinW := (X2 - X1) DIV 2;
+ W.InitWindow(Window, WinW DIV 2, Y1, WinW, Y2 - Y1, title);
+ Settings.Default;
+ DOM.GetWinSize(FileName, Window.Width, Window.Height);
+
+ Win2 := Window;
+ resize := FALSE;
+ IF Win2.Width > X2 - X1 THEN
+ Win2.Width := X2 - X1;
+ resize := TRUE
+ END;
+
+ IF Win2.Height > Y2 - Y1 THEN
+ Win2.Height := Y2 - Y1;
+ resize := TRUE
+ END;
+
+ DOM.Init(CANVAS_LEFT, CANVAS_TOP,
+ Window.Width - 2 * CANVAS_LEFT - 2 * WINDOW_BEVEL - 1 - SCROLLBAR_WIDTH - 2,
+ Window.Height - SkinHeight - CANVAS_TOP - WINDOW_BEVEL - STATUSBAR_HEIGHT + 1);
+ DOM.SetColors;
+ DOM.Set_b_pict(Ini.b_pict);
+ Window := Win2;
+ G.Resize2(Window.Width - 2 * CANVAS_LEFT - 2 * WINDOW_BEVEL - 1 - SCROLLBAR_WIDTH, Window.Height - SkinHeight - CANVAS_TOP - WINDOW_BEVEL + 1 - STATUSBAR_HEIGHT);
+ S.Append(Window.Caption, " - ");
+ S.Append(Window.Caption, FileName);
+
+ Toolbar.create(toolbar, TOOLBAR_LEFT, TOOLBAR_TOP);
+ Toolbar.add(toolbar, BACK, 30, "");
+ Toolbar.add(toolbar, FORWARD, 31, "");
+ Toolbar.delimiter(toolbar);
+ Toolbar.add(toolbar, CONTENTS, 3, "");
+ Toolbar.delimiter(toolbar);
+ Toolbar.add(toolbar, SEARCH, 49, "");
+ Toolbar.delimiter(toolbar);
+ Toolbar.add(toolbar, DESCR, 66, "");
+ Toolbar.delimiter(toolbar);
+ Toolbar.add(toolbar, SETTINGS, 60, "");
+
+ DOM.Open(FileName, DrawWindow, DrawStatus, DrawToolbar);
+ IF resize THEN
+ DOM.Resize(Window.Width - 2 * CANVAS_LEFT - 2 * WINDOW_BEVEL - 1 - SCROLLBAR_WIDTH, Window.Height - SkinHeight - CANVAS_TOP - WINDOW_BEVEL + 1 - STATUSBAR_HEIGHT)
+ END;
+
+ DrawWindow;
+ scr_pos := sb.position;
+ WHILE TRUE DO
+ CASE SU.WaitForEvent() OF
+ |1 : DrawWindow
+ |2 : KeyDown
+ |3 : ButtonClick
+ |6 : box_lib.scrollbar_v_mouse(sb);
+ IF sb.position # scr_pos THEN
+ DOM.ScrollBar;
+ DOM.Draw;
+ scr_pos := sb.position;
+ END;
+ MouseEvent
+ END
+ END
+END main;
+
+
+BEGIN
+ main("FB2 Reader v0.97")
+END FB2READ.
diff --git a/programs/other/fb2reader/SRC/File.ob07 b/programs/other/fb2reader/SRC/File.ob07
new file mode 100644
index 0000000000..b906546776
--- /dev/null
+++ b/programs/other/fb2reader/SRC/File.ob07
@@ -0,0 +1,255 @@
+(*
+ Copyright 2016, 2019 Anton Krotov
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*)
+
+MODULE File;
+
+IMPORT sys := SYSTEM, KOSAPI;
+
+CONST
+
+ SEEK_BEG* = 0; SEEK_CUR* = 1; SEEK_END* = 2;
+
+TYPE
+
+ FNAME* = ARRAY 520 OF CHAR;
+
+ FS* = POINTER TO rFS;
+
+ rFS* = RECORD
+ subfunc*, pos*, hpos*, bytes*, buffer*: INTEGER;
+ name*: FNAME
+ END;
+
+ FD* = POINTER TO rFD;
+
+ rFD* = RECORD
+ attr*: INTEGER;
+ ntyp*: CHAR;
+ reserved: ARRAY 3 OF CHAR;
+ time_create*, date_create*,
+ time_access*, date_access*,
+ time_modif*, date_modif*,
+ size*, hsize*: INTEGER;
+ name*: FNAME
+ END;
+
+PROCEDURE [stdcall] f_68_27(file_name: INTEGER; VAR size: INTEGER): INTEGER;
+BEGIN
+ sys.CODE(053H); (* push ebx *)
+ sys.CODE(06AH, 044H); (* push 68 *)
+ sys.CODE(058H); (* pop eax *)
+ sys.CODE(06AH, 01BH); (* push 27 *)
+ sys.CODE(05BH); (* pop ebx *)
+ sys.CODE(08BH, 04DH, 008H); (* mov ecx, [ebp + 08h] *)
+ sys.CODE(0CDH, 040H); (* int 40h *)
+ sys.CODE(08BH, 04DH, 00CH); (* mov ecx, [ebp + 0Ch] *)
+ sys.CODE(089H, 011H); (* mov [ecx], edx *)
+ sys.CODE(05BH); (* pop ebx *)
+ sys.CODE(0C9H); (* leave *)
+ sys.CODE(0C2H, 008H, 000H); (* ret 08h *)
+ RETURN 0
+END f_68_27;
+
+PROCEDURE Load*(FName: ARRAY OF CHAR; VAR size: INTEGER): INTEGER;
+ RETURN f_68_27(sys.ADR(FName[0]), size)
+END Load;
+
+PROCEDURE GetFileInfo*(FName: ARRAY OF CHAR; VAR Info: rFD): BOOLEAN;
+VAR res2: INTEGER; fs: rFS;
+BEGIN
+ fs.subfunc := 5;
+ fs.pos := 0;
+ fs.hpos := 0;
+ fs.bytes := 0;
+ fs.buffer := sys.ADR(Info);
+ COPY(FName, fs.name)
+ RETURN KOSAPI.sysfunc22(70, sys.ADR(fs), res2) = 0
+END GetFileInfo;
+
+PROCEDURE Exists*(FName: ARRAY OF CHAR): BOOLEAN;
+VAR fd: rFD;
+BEGIN
+ RETURN GetFileInfo(FName, fd) & ~(4 IN BITS(fd.attr))
+END Exists;
+
+PROCEDURE Close*(VAR F: FS);
+BEGIN
+ IF F # NIL THEN
+ DISPOSE(F)
+ END
+END Close;
+
+PROCEDURE Open*(FName: ARRAY OF CHAR): FS;
+VAR F: FS;
+BEGIN
+ IF Exists(FName) THEN
+ NEW(F);
+ IF F # NIL THEN
+ F.subfunc := 0;
+ F.pos := 0;
+ F.hpos := 0;
+ F.bytes := 0;
+ F.buffer := 0;
+ COPY(FName, F.name)
+ END
+ ELSE
+ F := NIL
+ END
+ RETURN F
+END Open;
+
+PROCEDURE Delete*(FName: ARRAY OF CHAR): BOOLEAN;
+VAR F: FS; res, res2: INTEGER;
+BEGIN
+ IF Exists(FName) THEN
+ NEW(F);
+ IF F # NIL THEN
+ F.subfunc := 8;
+ F.pos := 0;
+ F.hpos := 0;
+ F.bytes := 0;
+ F.buffer := 0;
+ COPY(FName, F.name);
+ res := KOSAPI.sysfunc22(70, sys.ADR(F^), res2);
+ DISPOSE(F)
+ ELSE
+ res := -1
+ END
+ ELSE
+ res := -1
+ END
+ RETURN res = 0
+END Delete;
+
+PROCEDURE Seek*(F: FS; Offset, Origin: INTEGER): INTEGER;
+VAR res: INTEGER; fd: rFD;
+BEGIN
+ IF (F # NIL) & GetFileInfo(F.name, fd) & (BITS(fd.attr) * {4} = {}) THEN
+ CASE Origin OF
+ |SEEK_BEG: F.pos := Offset
+ |SEEK_CUR: INC(F.pos, Offset)
+ |SEEK_END: F.pos := fd.size + Offset
+ ELSE
+ END;
+ res := F.pos
+ ELSE
+ res := -1
+ END
+ RETURN res
+END Seek;
+
+PROCEDURE Read*(F: FS; Buffer, Count: INTEGER): INTEGER;
+VAR res, res2: INTEGER;
+BEGIN
+ IF F # NIL THEN
+ F.subfunc := 0;
+ F.bytes := Count;
+ F.buffer := Buffer;
+ res := KOSAPI.sysfunc22(70, sys.ADR(F^), res2);
+ IF res2 > 0 THEN
+ INC(F.pos, res2)
+ END
+ ELSE
+ res2 := 0
+ END
+ RETURN res2
+END Read;
+
+PROCEDURE Write*(F: FS; Buffer, Count: INTEGER): INTEGER;
+VAR res, res2: INTEGER;
+BEGIN
+ IF F # NIL THEN
+ F.subfunc := 3;
+ F.bytes := Count;
+ F.buffer := Buffer;
+ res := KOSAPI.sysfunc22(70, sys.ADR(F^), res2);
+ IF res2 > 0 THEN
+ INC(F.pos, res2)
+ END
+ ELSE
+ res2 := 0
+ END
+ RETURN res2
+END Write;
+
+PROCEDURE Create*(FName: ARRAY OF CHAR): FS;
+VAR F: FS; res2: INTEGER;
+BEGIN
+ NEW(F);
+ IF F # NIL THEN
+ F.subfunc := 2;
+ F.pos := 0;
+ F.hpos := 0;
+ F.bytes := 0;
+ F.buffer := 0;
+ COPY(FName, F.name);
+ IF KOSAPI.sysfunc22(70, sys.ADR(F^), res2) # 0 THEN
+ DISPOSE(F)
+ END
+ END
+ RETURN F
+END Create;
+
+PROCEDURE DirExists*(FName: ARRAY OF CHAR): BOOLEAN;
+VAR fd: rFD;
+BEGIN
+ RETURN GetFileInfo(FName, fd) & (4 IN BITS(fd.attr))
+END DirExists;
+
+PROCEDURE CreateDir*(DirName: ARRAY OF CHAR): BOOLEAN;
+VAR F: FS; res, res2: INTEGER;
+BEGIN
+ NEW(F);
+ IF F # NIL THEN
+ F.subfunc := 9;
+ F.pos := 0;
+ F.hpos := 0;
+ F.bytes := 0;
+ F.buffer := 0;
+ COPY(DirName, F.name);
+ res := KOSAPI.sysfunc22(70, sys.ADR(F^), res2);
+ DISPOSE(F)
+ ELSE
+ res := -1
+ END
+ RETURN res = 0
+END CreateDir;
+
+PROCEDURE DeleteDir*(DirName: ARRAY OF CHAR): BOOLEAN;
+VAR F: FS; res, res2: INTEGER;
+BEGIN
+ IF DirExists(DirName) THEN
+ NEW(F);
+ IF F # NIL THEN
+ F.subfunc := 8;
+ F.pos := 0;
+ F.hpos := 0;
+ F.bytes := 0;
+ F.buffer := 0;
+ COPY(DirName, F.name);
+ res := KOSAPI.sysfunc22(70, sys.ADR(F^), res2);
+ DISPOSE(F)
+ ELSE
+ res := -1
+ END
+ ELSE
+ res := -1
+ END
+ RETURN res = 0
+END DeleteDir;
+
+END File.
diff --git a/programs/other/fb2reader/SRC/Font.ob07 b/programs/other/fb2reader/SRC/Font.ob07
new file mode 100644
index 0000000000..3513bda6b2
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Font.ob07
@@ -0,0 +1,176 @@
+(*
+ Copyright 2016, 2018, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Font;
+
+IMPORT W := Window, S := Strings, G := Graph, sys := SYSTEM, K := KOSAPI, Conv, Ini, KF := kfonts;
+
+VAR
+
+ kf_font, kf_loaded, kf_enabled: BOOLEAN;
+ cp1251buf: ARRAY 102400 OF CHAR;
+
+ KFont*: KF.TFont;
+
+ Font*: RECORD
+ color* : INTEGER;
+ size : INTEGER;
+ bold : BOOLEAN;
+ italic : BOOLEAN;
+ strike : BOOLEAN
+ END;
+
+ ItalicColor, NormalColor: INTEGER;
+
+
+PROCEDURE KFText(X, Y: INTEGER; first, quantity: INTEGER; canvas: G.PBuffer);
+BEGIN
+ KF.TextOut(KFont, canvas.adr - 8, X, Y, first, quantity, Font.color, ORD(Font.bold) + ORD(Font.italic) * 2 + ORD(Font.strike) * 8)
+END KFText;
+
+
+PROCEDURE sysfont*(sf: BOOLEAN);
+BEGIN
+ kf_font := ~sf & kf_enabled;
+END sysfont;
+
+
+PROCEDURE params*(): INTEGER;
+ RETURN Font.size + 0 + LSL(3, 16) + LSL(ORD(Font.bold) + ORD(Font.italic) * 2 + 128, 24)
+END params;
+
+
+PROCEDURE SetFontColor*(color: INTEGER);
+BEGIN
+ Font.color := color
+END SetFontColor;
+
+
+PROCEDURE Bold*(bold: BOOLEAN);
+BEGIN
+ Font.bold := bold
+END Bold;
+
+
+PROCEDURE Italic*(italic, notLink: BOOLEAN);
+BEGIN
+ Font.italic := italic;
+ IF italic THEN
+ IF notLink THEN
+ SetFontColor(ItalicColor)
+ END
+ ELSE
+ IF notLink THEN
+ SetFontColor(NormalColor)
+ END
+ END
+END Italic;
+
+
+PROCEDURE Strike*(strike: BOOLEAN);
+BEGIN
+ Font.strike := strike
+END Strike;
+
+
+PROCEDURE FontW(): INTEGER;
+ RETURN ASR(Font.size, 1)
+END FontW;
+
+
+PROCEDURE FontH*(): INTEGER;
+VAR res: INTEGER;
+BEGIN
+ IF kf_font THEN
+ res := KF.TextHeight(KFont)
+ ELSE
+ res := Font.size
+ END
+ RETURN res
+END FontH;
+
+
+PROCEDURE TextWidth*(text: S.CHARS; length: INTEGER): INTEGER;
+VAR res: INTEGER;
+BEGIN
+ IF kf_font THEN
+ Conv.convert(text.first, sys.ADR(cp1251buf[0]), length);
+ res := KF.TextWidth(KFont, sys.ADR(cp1251buf[0]), length, ORD(Font.bold) + ORD(Font.italic) * 2)
+ ELSE
+ res := length * FontW()
+ END
+ RETURN res
+END TextWidth;
+
+
+PROCEDURE MonoWidth*(): INTEGER;
+ RETURN FontW()
+END MonoWidth;
+
+
+PROCEDURE StrikeText*(Rect: W.TRect; X, Y: INTEGER; width: INTEGER);
+VAR y: INTEGER;
+BEGIN
+ IF Font.strike THEN
+ y := Y + FontH() DIV 2;
+// X := X + ORD(Font.italic & kf_font) * ((KF.TextHeight(KFont) DIV 2) DIV 3);
+ G.SetColor(Font.color);
+ G.HLine(X + Rect.Left, X + Rect.Left + width, y + Rect.Top);
+ IF Font.size >= 28 THEN
+ INC(y);
+ G.HLine(X + Rect.Left, X + Rect.Left + width, y + Rect.Top);
+ END
+ END
+END StrikeText;
+
+
+PROCEDURE Text*(Rect: W.TRect; X, Y: INTEGER; adr: INTEGER; length: INTEGER);
+BEGIN
+ IF kf_font THEN
+ Conv.convert(adr, sys.ADR(cp1251buf[0]), length);
+ KFText(X + Rect.Left, Y + Rect.Top, sys.ADR(cp1251buf[0]), length, G.Buffer)
+ ELSE
+ G.SetColor(Font.color);
+ G.TextOut(X + Rect.Left, Y + Rect.Top, adr, length, Font.size, params())
+ END
+END Text;
+
+
+PROCEDURE Init*(italic, normal, fs: INTEGER);
+BEGIN
+ ItalicColor := italic;
+ NormalColor := normal;
+ IF KF.SetSize(KFont, fs) THEN
+ Font.size := KF.TextHeight(KFont);
+ kf_font := TRUE;
+ kf_enabled := TRUE
+ ELSE
+ Font.size := fs;
+ kf_font := FALSE;
+ kf_enabled := FALSE
+ END
+END Init;
+
+
+BEGIN
+ KFont := KF.LoadFont(Ini.Font);
+ kf_loaded := KFont # NIL;
+ kf_font := kf_loaded;
+ kf_enabled := kf_loaded
+END Font.
diff --git a/programs/other/fb2reader/SRC/Graph.ob07 b/programs/other/fb2reader/SRC/Graph.ob07
new file mode 100644
index 0000000000..db3a7728bb
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Graph.ob07
@@ -0,0 +1,310 @@
+(*
+ Copyright 2016-2020, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Graph;
+
+IMPORT K := KOSAPI, sys := SYSTEM, SU := SysUtils, LibImg;
+
+
+TYPE
+
+ TBuffer = RECORD Width*, Height*, adr*, Color: INTEGER END;
+ PBuffer* = POINTER TO TBuffer;
+
+
+VAR
+
+ Buffer*, Buffer2, Buffer3*: PBuffer;
+
+
+PROCEDURE [stdcall-, "rasterworks.obj", ""] drawText (canvas, x, y, string, charQuantity, fontColor, params: INTEGER): INTEGER; END;
+
+PROCEDURE Destroy*(VAR Buffer: PBuffer);
+BEGIN
+ IF Buffer # NIL THEN
+ IF Buffer.adr # 0 THEN
+ DEC(Buffer.adr, 8);
+ Buffer.adr := K.free(Buffer.adr)
+ END;
+ DISPOSE(Buffer)
+ END
+END Destroy;
+
+
+PROCEDURE Create*(Width, Height: INTEGER): PBuffer;
+VAR res: PBuffer;
+BEGIN
+ NEW(res);
+ res.adr := K.malloc(Width * Height * 4 + 8);
+ sys.PUT(res.adr, Width);
+ sys.PUT(res.adr + 4, Height);
+ res.Width := Width;
+ res.Height := Height;
+ INC(res.adr, 8);
+ RETURN res
+END Create;
+
+
+PROCEDURE getRGB* (color: INTEGER; VAR r, g, b: BYTE);
+BEGIN
+ b := color MOD 256;
+ g := color DIV 256 MOD 256;
+ r := color DIV 65536 MOD 256
+END getRGB;
+
+
+PROCEDURE Fill*(Buffer: PBuffer; Color: INTEGER);
+VAR p, n, i: INTEGER;
+BEGIN
+ p := Buffer.adr;
+ n := Buffer.Width * Buffer.Height;
+ FOR i := 1 TO n DO
+ sys.PUT(p, Color);
+ INC(p, 4)
+ END
+END Fill;
+
+
+PROCEDURE HLine*(X1, X2, Y: INTEGER);
+VAR
+ p1, p2, i, color: INTEGER;
+
+BEGIN
+ IF X1 <= X2 THEN
+ SU.MinMax(Y, 0, Buffer.Height - 1);
+ color := Buffer.Color;
+ p1 := Buffer.adr + 4 * (Y * Buffer.Width + X1);
+ p2 := p1 + (X2 - X1) * 4;
+ FOR i := p1 TO p2 BY 4 DO
+ sys.PUT(i, color)
+ END
+ END
+END HLine;
+
+
+PROCEDURE HLineNotXOR (X1, X2, Y, color: INTEGER);
+VAR
+ p1, p2, i: INTEGER;
+ pix: SET;
+
+BEGIN
+ IF X1 <= X2 THEN
+ SU.MinMax(Y, 0, Buffer.Height - 1);
+ p1 := Buffer.adr + 4 * (Y * Buffer.Width + X1);
+ p2 := p1 + (X2 - X1) * 4;
+ FOR i := p1 TO p2 BY 4 DO
+ sys.GET(i, pix);
+ pix := (-pix) / BITS(color) - {24..31};
+ sys.PUT(i, pix)
+ END
+ END
+END HLineNotXOR;
+
+
+PROCEDURE VLine*(X, Y1, Y2: INTEGER);
+VAR p1, p2, line_size, color: INTEGER;
+BEGIN
+ ASSERT(Y1 <= Y2);
+ SU.MinMax(Y1, 0, Buffer.Height - 1);
+ SU.MinMax(Y2, 0, Buffer.Height - 1);
+ color := Buffer.Color;
+ line_size := Buffer.Width * 4;
+ p1 := Buffer.adr + line_size * Y1 + 4 * X;
+ p2 := p1 + (Y2 - Y1) * line_size;
+ WHILE p1 <= p2 DO
+ sys.PUT(p1, color);
+ p1 := p1 + line_size
+ END
+END VLine;
+
+
+PROCEDURE Box(X1, Y1, X2, Y2: INTEGER);
+VAR y: INTEGER;
+BEGIN
+ FOR y := Y1 TO Y2 DO
+ HLine(X1, X2, y)
+ END
+END Box;
+
+
+PROCEDURE BoxNotXOR* (X1, Y1, X2, Y2, color: INTEGER);
+VAR y: INTEGER;
+BEGIN
+ FOR y := Y1 TO Y2 DO
+ HLineNotXOR(X1, X2, y, color)
+ END
+END BoxNotXOR;
+
+
+PROCEDURE SetColor*(color: INTEGER);
+BEGIN
+ Buffer.Color := color
+END SetColor;
+
+
+PROCEDURE GetColor*(): INTEGER;
+ RETURN Buffer.Color
+END GetColor;
+
+
+PROCEDURE TextOut*(X, Y: INTEGER; Text: INTEGER; length: INTEGER; size, params: INTEGER);
+BEGIN
+ drawText(Buffer.adr - 8, X, Y, Text, length, 0FF000000H + Buffer.Color, params)
+END TextOut;
+
+
+PROCEDURE Resize2*(Width, Height: INTEGER);
+BEGIN
+ Buffer2.Width := Width;
+ Buffer2.Height := Height;
+END Resize2;
+
+
+PROCEDURE Image* (X, Y, sizeX, sizeY, ptr, Ymin, Ymax: INTEGER);
+VAR
+ y: INTEGER;
+BEGIN
+ ASSERT(sizeX <= Buffer.Width);
+ FOR y := 0 TO sizeY - 1 DO
+ IF (Ymin <= Y) & (Y < Ymax) THEN
+ sys.MOVE(ptr + sizeX*4*y, Buffer.adr + (Buffer.Width*Y + X)*4, sizeX*4)
+ END;
+ INC(Y)
+ END
+END Image;
+
+
+PROCEDURE Image2(Buffer: PBuffer; X, Y, sizeX, sizeY, ptr: INTEGER);
+VAR x, y, pix, left: INTEGER;
+BEGIN
+ left := X;
+ FOR y := 0 TO sizeY - 1 DO
+ X := left;
+ FOR x := 0 TO sizeX - 1 DO
+ sys.GET32(ptr + (y*sizeX + x)*4, pix);
+ IF (X < Buffer.Width) & (Y < Buffer.Height) THEN
+ sys.PUT32(Buffer.adr + (Buffer.Width*Y + X)*4, pix)
+ END;
+ INC(X)
+ END;
+ INC(Y)
+ END
+END Image2;
+
+
+PROCEDURE BackImage*(sizeX, sizeY, ptr: INTEGER);
+VAR x, y: INTEGER;
+BEGIN
+ IF ptr # 0 THEN
+ y := 0;
+ WHILE y < Buffer3.Height DO
+ x := 0;
+ WHILE x < Buffer3.Width DO
+ Image2(Buffer3, x, y, sizeX, sizeY, ptr);
+ INC(x, sizeX)
+ END;
+ INC(y, sizeY)
+ END
+ END
+END BackImage;
+
+
+PROCEDURE Copy*(src, dst: PBuffer; y_src, lines, y_dst: INTEGER);
+BEGIN
+ sys.MOVE(src.adr + y_src * src.Width * 4, dst.adr + y_dst * dst.Width * 4, lines * dst.Width * 4)
+END Copy;
+
+
+PROCEDURE Clear*;
+VAR p, color: INTEGER;
+BEGIN
+ color := Buffer.Color;
+ FOR p := Buffer.adr TO Buffer.adr + Buffer.Width * Buffer.Height * 4 - 4 BY 4 DO
+ sys.PUT(p, color)
+ END
+END Clear;
+
+
+PROCEDURE Draw*(X, Y: INTEGER);
+BEGIN
+ K.sysfunc7(65, Buffer.adr, Buffer.Width * 65536 + Buffer.Height, X * 65536 + Y, 32, 0, 0)
+END Draw;
+
+
+PROCEDURE Rect*(X1, Y1, X2, Y2: INTEGER);
+BEGIN
+ VLine(X1, Y1, Y2);
+ VLine(X2, Y1, Y2);
+ HLine(X1, X2, Y1);
+ HLine(X1, X2, Y2)
+END Rect;
+
+
+PROCEDURE Progress*(value: REAL);
+VAR W4, W2, H2: INTEGER;
+BEGIN
+ W4 := Buffer2.Width DIV 4;
+ W2 := Buffer2.Width DIV 2;
+ H2 := Buffer2.Height DIV 2;
+ SetColor(0FFFFFFH);
+ Clear;
+ SetColor(0);
+ Rect(W4, H2 - 50, 3 * W4, H2 + 30);
+ TextOut(W2 - 10 * 8 DIV 2, H2 - 50 + 15, sys.SADR("Loading..."), 10, 1, 16 + 0 + LSL(3, 16) + LSL(128, 24));
+ SetColor(000000FFH);
+ Box(W4 + 10, H2, W4 + 10 + FLOOR( FLT(W2 - 20) * value ), H2 + 15);
+END Progress;
+
+
+PROCEDURE Resize3(Buffer: PBuffer; Width, Height: INTEGER);
+BEGIN
+ IF Buffer.adr # 0 THEN
+ DEC(Buffer.adr, 8)
+ END;
+ Buffer.adr := K.realloc(Buffer.adr, Width * Height * 4 + 8);
+ SU.MemError(Buffer.adr = 0);
+ sys.PUT(Buffer.adr, Width);
+ sys.PUT(Buffer.adr + 4, Height);
+ INC(Buffer.adr, 8);
+ Buffer.Width := Width;
+ Buffer.Height := Height
+END Resize3;
+
+
+PROCEDURE Resize*(Width, Height: INTEGER);
+BEGIN
+ Resize3(Buffer, Width, Height);
+ Resize3(Buffer3, Width, Height);
+END Resize;
+
+
+PROCEDURE Init;
+VAR Width, Height: INTEGER;
+BEGIN
+ NEW(Buffer);
+ NEW(Buffer2);
+ NEW(Buffer3);
+ SU.GetScreenSize(Width, Height);
+ Resize(Width, Height)
+END Init;
+
+
+BEGIN
+ Init
+END Graph.
diff --git a/programs/other/fb2reader/SRC/Icons.ob07 b/programs/other/fb2reader/SRC/Icons.ob07
new file mode 100644
index 0000000000..eafb5191dd
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Icons.ob07
@@ -0,0 +1,106 @@
+(*
+ Copyright 2021, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Icons;
+
+IMPORT
+ LibImg, K := SysUtils, Graph, File, KOSAPI, SYSTEM;
+
+CONST
+ fileName = "/sys/Icons16.png";
+ SIZE* = 18;
+
+VAR
+ source: INTEGER;
+
+(*
+PROCEDURE copy (src, dst: INTEGER);
+VAR
+ src_width, src_height,
+ dst_width, dst_height,
+ src_data, dst_data: INTEGER;
+BEGIN
+ LibImg.GetInf(src, src_width, src_height, src_data);
+ LibImg.GetInf(dst, dst_width, dst_height, dst_data);
+ ASSERT(src_width = dst_width);
+ ASSERT(src_height = dst_height);
+ SYSTEM.MOVE(src_data, dst_data, src_width*src_height*4)
+END copy;
+*)
+
+
+PROCEDURE load (): INTEGER;
+VAR
+ height: INTEGER;
+BEGIN
+ RETURN LibImg.LoadFromFile(fileName, SIZE, height)
+END load;
+
+
+PROCEDURE draw* (icons, n, x, y: INTEGER);
+VAR
+ width, height, data: INTEGER;
+BEGIN
+ LibImg.GetInf(icons, width, height, data);
+ KOSAPI.sysfunc7(65, data + SIZE*SIZE*4*n, SIZE*65536 + SIZE, x*65536 + y, 32, 0, 0)
+END draw;
+
+
+PROCEDURE iconsBackColor (icons: INTEGER; BackColor: INTEGER);
+VAR
+ width, height, data, x, y, pix: INTEGER;
+ b, g, r, gr: BYTE;
+BEGIN
+ LibImg.GetInf(icons, width, height, data);
+ FOR y := 0 TO height - 1 DO
+ FOR x := 0 TO width - 1 DO
+ SYSTEM.GET32(data, pix);
+ Graph.getRGB(pix, r, g, b);
+ gr := (r + g + b) DIV 3;
+ IF BackColor = -1 THEN
+ pix := gr + 256*gr + 65536*gr
+ ELSIF gr = 255 THEN
+ pix := BackColor
+ END;
+ SYSTEM.PUT32(data, pix);
+ INC(data, 4)
+ END
+ END
+END iconsBackColor;
+
+
+PROCEDURE get* (VAR icons, grayIcons: INTEGER; BackColor: INTEGER);
+BEGIN
+ IF source = 0 THEN
+ source := load();
+ icons := load();
+ grayIcons := load();
+ iconsBackColor(grayIcons, -1);
+ iconsBackColor(grayIcons, BackColor);
+ iconsBackColor(icons, BackColor)
+ (*ELSE
+ copy(source, icons);
+ copy(source, grayIcons)*)
+ END;
+END get;
+
+
+BEGIN
+ source := 0
+END Icons.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/Ini.ob07 b/programs/other/fb2reader/SRC/Ini.ob07
new file mode 100644
index 0000000000..44fd745914
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Ini.ob07
@@ -0,0 +1,149 @@
+(*
+ Copyright 2016, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Ini;
+
+IMPORT KOSAPI, sys := SYSTEM, S := Strings, File;
+
+
+CONST
+
+ IniFileName = "/sys/settings/fb2read.ini";
+
+
+VAR
+
+ History*, Browser*, Default*, Font*, Files*, Picture* : S.STRING;
+ b_pict*: BOOLEAN;
+ buffer: ARRAY 5000 OF CHAR;
+
+
+PROCEDURE [stdcall, "libini.obj", "ini_enum_keys"] enum_keys (f_name, sec_name: S.STRING; callback: INTEGER); END;
+PROCEDURE [stdcall, "libini.obj", "ini_get_color"] get_color (f_name, sec_name, key_name: S.STRING; def_val: INTEGER): INTEGER; END;
+
+PROCEDURE Save* (Colors: ARRAY OF INTEGER; b_pict: BOOLEAN);
+VAR F: File.FS; pos: INTEGER;
+
+ PROCEDURE WriteStr(str: S.STRING; VAR pos: INTEGER);
+ BEGIN
+ sys.MOVE(sys.ADR(str[0]), pos, LENGTH(str));
+ pos := pos + LENGTH(str)
+ END WriteStr;
+
+ PROCEDURE WriteLn (VAR pos: INTEGER);
+ BEGIN
+ WriteStr(0DX, pos);
+ WriteStr(0AX, pos)
+ END WriteLn;
+
+ PROCEDURE GetRGB(color: INTEGER; VAR r, g, b: INTEGER);
+ BEGIN
+ b := ORD(BITS(color) * {0..7});
+ g := ORD(BITS(LSR(color, 8)) * {0..7});
+ r := ORD(BITS(LSR(color, 16)) * {0..7})
+ END GetRGB;
+
+ PROCEDURE WriteColor(color: INTEGER; VAR pos: INTEGER);
+ VAR r, g, b: INTEGER; s: S.STRING;
+ BEGIN
+ GetRGB(color, r, g, b);
+ S.IntToString(r, s); WriteStr(s, pos); WriteStr(",", pos);
+ S.IntToString(g, s); WriteStr(s, pos); WriteStr(",", pos);
+ S.IntToString(b, s); WriteStr(s, pos);
+ END WriteColor;
+
+BEGIN
+ pos := sys.ADR(buffer[0]);
+ F := File.Create(IniFileName);
+ WriteStr("[Paths]", pos); WriteLn(pos);
+ WriteStr("history=", pos); WriteStr(History, pos); WriteLn(pos);
+ WriteStr("browser=", pos); WriteStr(Browser, pos); WriteLn(pos);
+ WriteStr("default=", pos); WriteStr(Default, pos); WriteLn(pos);
+ WriteStr("font=", pos); WriteStr(Font, pos); WriteLn(pos);
+ WriteStr("picture=", pos); WriteStr(Picture, pos); WriteLn(pos);
+ WriteStr("[Files]", pos); WriteLn(pos);
+ WriteStr("files=", pos); WriteStr(Files, pos); WriteLn(pos);
+ WriteStr("[Flags]", pos); WriteLn(pos);
+ WriteStr("picture=", pos);
+ IF b_pict THEN
+ WriteStr("on", pos)
+ ELSE
+ WriteStr("off", pos)
+ END;
+ WriteLn(pos);
+ WriteStr("[Colors]", pos); WriteLn(pos);
+ WriteStr("back=", pos); WriteColor(Colors[0], pos); WriteLn(pos);
+ WriteStr("text=", pos); WriteColor(Colors[1], pos); WriteLn(pos);
+ WriteStr("italic=", pos); WriteColor(Colors[2], pos); WriteLn(pos);
+ WriteStr("link=", pos); WriteColor(Colors[3], pos); WriteLn(pos);
+ WriteStr("visited=", pos); WriteColor(Colors[4], pos); WriteLn(pos);
+ pos := File.Write(F, sys.ADR(buffer[0]), pos - sys.ADR(buffer[0]));
+ File.Close(F)
+END Save;
+
+
+PROCEDURE [stdcall] callback(f_name, sec_name, key_name, key_value: S.STRING): INTEGER;
+BEGIN
+ IF sec_name = "Paths" THEN
+ IF key_name = "history" THEN
+ History := key_value
+ ELSIF key_name = "browser" THEN
+ Browser := key_value
+ ELSIF key_name = "default" THEN
+ Default := key_value
+ ELSIF key_name = "font" THEN
+ Font := key_value
+ ELSIF key_name = "picture" THEN
+ Picture := key_value
+ END
+ ELSIF sec_name = "Files" THEN
+ IF key_name = "files" THEN
+ Files := key_value
+ END
+ ELSIF sec_name = "Flags" THEN
+ IF key_name = "picture" THEN
+ b_pict := key_value = "on"
+ END
+ END
+ RETURN 1
+END callback;
+
+
+PROCEDURE GetColor*(key: S.STRING; def: INTEGER): INTEGER;
+ RETURN get_color(IniFileName, "Colors", key, def)
+END GetColor;
+
+
+PROCEDURE SetDefaultPath*(Path: S.STRING);
+BEGIN
+ Default := Path;
+END SetDefaultPath;
+
+
+PROCEDURE SetPicturePath*(Path: S.STRING);
+BEGIN
+ Picture := Path;
+END SetPicturePath;
+
+
+BEGIN
+ enum_keys(IniFileName, "Paths", sys.ADR(callback));
+ enum_keys(IniFileName, "Files", sys.ADR(callback));
+ enum_keys(IniFileName, "Flags", sys.ADR(callback));
+END Ini.
diff --git a/programs/other/fb2reader/SRC/KOSAPI.ob07 b/programs/other/fb2reader/SRC/KOSAPI.ob07
new file mode 100644
index 0000000000..57fed1dc17
--- /dev/null
+++ b/programs/other/fb2reader/SRC/KOSAPI.ob07
@@ -0,0 +1,436 @@
+(*
+ BSD 2-Clause License
+
+ Copyright (c) 2018-2019, 2022 Anton Krotov
+ All rights reserved.
+*)
+
+MODULE KOSAPI;
+
+IMPORT SYSTEM;
+
+
+TYPE
+
+ STRING = ARRAY 1024 OF CHAR;
+
+
+VAR
+
+ DLL_INIT: PROCEDURE [stdcall] (entry: INTEGER);
+
+
+PROCEDURE [stdcall-] sysfunc1* (arg1: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 0CDH, 040H, (* int 64 *)
+ 0C9H, (* leave *)
+ 0C2H, 004H, 000H (* ret 4 *)
+ )
+ RETURN 0
+END sysfunc1;
+
+
+PROCEDURE [stdcall-] sysfunc2* (arg1, arg2: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 0CDH, 040H, (* int 64 *)
+ 05BH, (* pop ebx *)
+ 0C9H, (* leave *)
+ 0C2H, 008H, 000H (* ret 8 *)
+ )
+ RETURN 0
+END sysfunc2;
+
+
+PROCEDURE [stdcall-] sysfunc3* (arg1, arg2, arg3: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 08BH, 04DH, 010H, (* mov ecx, dword [ebp + 16] *)
+ 0CDH, 040H, (* int 64 *)
+ 05BH, (* pop ebx *)
+ 0C9H, (* leave *)
+ 0C2H, 00CH, 000H (* ret 12 *)
+ )
+ RETURN 0
+END sysfunc3;
+
+
+PROCEDURE [stdcall-] sysfunc4* (arg1, arg2, arg3, arg4: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 08BH, 04DH, 010H, (* mov ecx, dword [ebp + 16] *)
+ 08BH, 055H, 014H, (* mov edx, dword [ebp + 20] *)
+ 0CDH, 040H, (* int 64 *)
+ 05BH, (* pop ebx *)
+ 0C9H, (* leave *)
+ 0C2H, 010H, 000H (* ret 16 *)
+ )
+ RETURN 0
+END sysfunc4;
+
+
+PROCEDURE [stdcall-] sysfunc5* (arg1, arg2, arg3, arg4, arg5: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 056H, (* push esi *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 08BH, 04DH, 010H, (* mov ecx, dword [ebp + 16] *)
+ 08BH, 055H, 014H, (* mov edx, dword [ebp + 20] *)
+ 08BH, 075H, 018H, (* mov esi, dword [ebp + 24] *)
+ 0CDH, 040H, (* int 64 *)
+ 05EH, (* pop esi *)
+ 05BH, (* pop ebx *)
+ 0C9H, (* leave *)
+ 0C2H, 014H, 000H (* ret 20 *)
+ )
+ RETURN 0
+END sysfunc5;
+
+
+PROCEDURE [stdcall-] sysfunc6* (arg1, arg2, arg3, arg4, arg5, arg6: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 056H, (* push esi *)
+ 057H, (* push edi *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 08BH, 04DH, 010H, (* mov ecx, dword [ebp + 16] *)
+ 08BH, 055H, 014H, (* mov edx, dword [ebp + 20] *)
+ 08BH, 075H, 018H, (* mov esi, dword [ebp + 24] *)
+ 08BH, 07DH, 01CH, (* mov edi, dword [ebp + 28] *)
+ 0CDH, 040H, (* int 64 *)
+ 05FH, (* pop edi *)
+ 05EH, (* pop esi *)
+ 05BH, (* pop ebx *)
+ 0C9H, (* leave *)
+ 0C2H, 018H, 000H (* ret 24 *)
+ )
+ RETURN 0
+END sysfunc6;
+
+
+PROCEDURE [stdcall-] sysfunc7* (arg1, arg2, arg3, arg4, arg5, arg6, arg7: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 056H, (* push esi *)
+ 057H, (* push edi *)
+ 055H, (* push ebp *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 08BH, 04DH, 010H, (* mov ecx, dword [ebp + 16] *)
+ 08BH, 055H, 014H, (* mov edx, dword [ebp + 20] *)
+ 08BH, 075H, 018H, (* mov esi, dword [ebp + 24] *)
+ 08BH, 07DH, 01CH, (* mov edi, dword [ebp + 28] *)
+ 08BH, 06DH, 020H, (* mov ebp, dword [ebp + 32] *)
+ 0CDH, 040H, (* int 64 *)
+ 05DH, (* pop ebp *)
+ 05FH, (* pop edi *)
+ 05EH, (* pop esi *)
+ 05BH, (* pop ebx *)
+ 0C9H, (* leave *)
+ 0C2H, 01CH, 000H (* ret 28 *)
+ )
+ RETURN 0
+END sysfunc7;
+
+
+PROCEDURE [stdcall-] sysfunc22* (arg1, arg2: INTEGER; VAR res2: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 0CDH, 040H, (* int 64 *)
+ 08BH, 04DH, 010H, (* mov ecx, dword [ebp + 16] *)
+ 089H, 019H, (* mov dword [ecx], ebx *)
+ 05BH, (* pop ebx *)
+ 0C9H, (* leave *)
+ 0C2H, 00CH, 000H (* ret 12 *)
+ )
+ RETURN 0
+END sysfunc22;
+
+
+PROCEDURE mem_commit (adr, size: INTEGER);
+VAR
+ tmp: INTEGER;
+BEGIN
+ FOR tmp := adr TO adr + size - 1 BY 4096 DO
+ SYSTEM.PUT(tmp, 0)
+ END
+END mem_commit;
+
+
+PROCEDURE [stdcall] malloc* (size: INTEGER): INTEGER;
+VAR
+ ptr: INTEGER;
+BEGIN
+ SYSTEM.CODE(060H); (* pusha *)
+ IF sysfunc2(18, 16) > ASR(size, 10) THEN
+ ptr := sysfunc3(68, 12, size);
+ IF ptr # 0 THEN
+ mem_commit(ptr, size)
+ END
+ ELSE
+ ptr := 0
+ END;
+ SYSTEM.CODE(061H) (* popa *)
+ RETURN ptr
+END malloc;
+
+
+PROCEDURE [stdcall] free* (ptr: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(060H); (* pusha *)
+ IF ptr # 0 THEN
+ ptr := sysfunc3(68, 13, ptr)
+ END;
+ SYSTEM.CODE(061H) (* popa *)
+ RETURN 0
+END free;
+
+
+PROCEDURE [stdcall] realloc* (ptr, size: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(060H); (* pusha *)
+ ptr := sysfunc4(68, 20, size, ptr);
+ SYSTEM.CODE(061H) (* popa *)
+ RETURN ptr
+END realloc;
+
+
+PROCEDURE AppAdr (): INTEGER;
+VAR
+ buf: ARRAY 1024 OF CHAR;
+ a: INTEGER;
+BEGIN
+ a := sysfunc3(9, SYSTEM.ADR(buf), -1);
+ SYSTEM.GET(SYSTEM.ADR(buf) + 22, a)
+ RETURN a
+END AppAdr;
+
+
+PROCEDURE GetCommandLine* (): INTEGER;
+VAR
+ param: INTEGER;
+BEGIN
+ SYSTEM.GET(28 + AppAdr(), param)
+ RETURN param
+END GetCommandLine;
+
+
+PROCEDURE GetName* (): INTEGER;
+VAR
+ name: INTEGER;
+BEGIN
+ SYSTEM.GET(32 + AppAdr(), name)
+ RETURN name
+END GetName;
+
+
+PROCEDURE [stdcall] dll_init2 (arg1, arg2, arg3, arg4, arg5: INTEGER);
+BEGIN
+ SYSTEM.CODE(
+ 060H, (* pusha *)
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 08BH, 05DH, 00CH, (* mov ebx, dword [ebp + 12] *)
+ 08BH, 04DH, 010H, (* mov ecx, dword [ebp + 16] *)
+ 08BH, 055H, 014H, (* mov edx, dword [ebp + 20] *)
+ 08BH, 075H, 018H, (* mov esi, dword [ebp + 24] *)
+ 0FFH, 0D6H, (* call esi *)
+ 061H, (* popa *)
+ 0C9H, (* leave *)
+ 0C2H, 014H, 000H (* ret 20 *)
+ )
+END dll_init2;
+
+
+PROCEDURE GetProcAdr* (name: ARRAY OF CHAR; lib: INTEGER): INTEGER;
+VAR
+ cur, procname, adr: INTEGER;
+
+ PROCEDURE streq (str1, str2: INTEGER): BOOLEAN;
+ VAR
+ c1, c2: CHAR;
+ BEGIN
+ REPEAT
+ SYSTEM.GET(str1, c1);
+ SYSTEM.GET(str2, c2);
+ INC(str1);
+ INC(str2)
+ UNTIL (c1 # c2) OR (c1 = 0X)
+
+ RETURN c1 = c2
+ END streq;
+
+BEGIN
+ adr := 0;
+ IF (lib # 0) & (name # "") THEN
+ cur := lib;
+ REPEAT
+ SYSTEM.GET(cur, procname);
+ INC(cur, 8)
+ UNTIL (procname = 0) OR streq(procname, SYSTEM.ADR(name[0]));
+ IF procname # 0 THEN
+ SYSTEM.GET(cur - 4, adr)
+ END
+ END
+
+ RETURN adr
+END GetProcAdr;
+
+
+PROCEDURE init (dll: INTEGER);
+VAR
+ lib_init: INTEGER;
+BEGIN
+ lib_init := GetProcAdr("lib_init", dll);
+ IF lib_init # 0 THEN
+ DLL_INIT(lib_init)
+ END;
+ lib_init := GetProcAdr("START", dll);
+ IF lib_init # 0 THEN
+ DLL_INIT(lib_init)
+ END
+END init;
+
+
+PROCEDURE OutChar* (c: CHAR);
+BEGIN
+ sysfunc3(63, 1, ORD(c))
+END OutChar;
+
+
+PROCEDURE OutLn*;
+BEGIN
+ OutChar(0DX);
+ OutChar(0AX)
+END OutLn;
+
+
+PROCEDURE OutString (s: ARRAY OF CHAR);
+VAR
+ i: INTEGER;
+BEGIN
+ i := 0;
+ WHILE (i < LEN(s)) & (s[i] # 0X) DO
+ OutChar(s[i]);
+ INC(i)
+ END
+END OutString;
+
+
+PROCEDURE imp_error (lib, proc: STRING);
+BEGIN
+ OutString("import error: ");
+ IF proc = "" THEN
+ OutString("can't load '")
+ ELSE
+ OutString("not found '"); OutString(proc); OutString("' in '")
+ END;
+ OutString(lib);
+ OutString("'" + 0DX + 0AX)
+END imp_error;
+
+
+PROCEDURE GetStr (adr, i: INTEGER; VAR str: STRING);
+VAR
+ c: CHAR;
+BEGIN
+ REPEAT
+ SYSTEM.GET(adr, c); INC(adr);
+ str[i] := c; INC(i)
+ UNTIL c = 0X
+END GetStr;
+
+
+PROCEDURE [stdcall-] dll_Load* (import_table: INTEGER): INTEGER;
+CONST
+ path = "/sys/lib/";
+VAR
+ imp, lib, exp, proc, pathLen: INTEGER;
+ procname, libname: STRING;
+BEGIN
+ SYSTEM.CODE(060H); (* pusha *)
+ libname := path;
+ pathLen := LENGTH(libname);
+
+ SYSTEM.GET(import_table, imp);
+ WHILE imp # 0 DO
+ SYSTEM.GET(import_table + 4, lib);
+ GetStr(lib, pathLen, libname);
+ exp := sysfunc3(68, 19, SYSTEM.ADR(libname[0]));
+ IF exp = 0 THEN
+ imp_error(libname, "")
+ ELSE
+ REPEAT
+ SYSTEM.GET(imp, proc);
+ IF proc # 0 THEN
+ GetStr(proc, 0, procname);
+ proc := GetProcAdr(procname, exp);
+ IF proc # 0 THEN
+ SYSTEM.PUT(imp, proc)
+ ELSE
+ proc := 1;
+ imp_error(libname, procname)
+ END;
+ INC(imp, 4)
+ END
+ UNTIL proc = 0;
+ init(exp)
+ END;
+ INC(import_table, 8);
+ SYSTEM.GET(import_table, imp);
+ END;
+
+ SYSTEM.CODE(061H) (* popa *)
+ RETURN 0
+END dll_Load;
+
+
+PROCEDURE [stdcall] dll_Init (entry: INTEGER);
+BEGIN
+ SYSTEM.CODE(060H); (* pusha *)
+ IF entry # 0 THEN
+ dll_init2(SYSTEM.ADR(malloc), SYSTEM.ADR(free), SYSTEM.ADR(realloc), SYSTEM.ADR(dll_Load), entry)
+ END;
+ SYSTEM.CODE(061H); (* popa *)
+END dll_Init;
+
+
+PROCEDURE LoadLib* (name: ARRAY OF CHAR): INTEGER;
+VAR
+ Lib: INTEGER;
+BEGIN
+ DLL_INIT := dll_Init;
+ Lib := sysfunc3(68, 19, SYSTEM.ADR(name[0]));
+ IF Lib # 0 THEN
+ init(Lib)
+ END
+ RETURN Lib
+END LoadLib;
+
+
+PROCEDURE _init* (import_table: INTEGER);
+BEGIN
+ DLL_INIT := dll_Init;
+ dll_Load(import_table)
+END _init;
+
+
+END KOSAPI.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/LISTS.ob07 b/programs/other/fb2reader/SRC/LISTS.ob07
new file mode 100644
index 0000000000..89e4d802a2
--- /dev/null
+++ b/programs/other/fb2reader/SRC/LISTS.ob07
@@ -0,0 +1,135 @@
+(*
+ Copyright 2018, 2020 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE LISTS;
+
+
+TYPE
+
+ LIST* = POINTER TO rLIST;
+
+ ITEM* = POINTER TO rITEM;
+
+ rITEM* = RECORD
+
+ prev*, next*: ITEM;
+
+ destroy*: PROCEDURE (VAR item: ITEM)
+
+ END;
+
+ rLIST* = RECORD
+
+ first*, last*: ITEM
+
+ END;
+
+
+PROCEDURE push* (list: LIST; item: ITEM);
+BEGIN
+ ASSERT(list # NIL);
+ ASSERT(item # NIL);
+
+ IF list.first = NIL THEN
+ list.first := item;
+ list.last := item;
+ item.prev := NIL;
+ item.next := NIL
+ ELSE
+ ASSERT(list.last # NIL);
+ item.prev := list.last;
+ list.last.next := item;
+ item.next := NIL;
+ list.last := item
+ END
+END push;
+
+
+PROCEDURE get* (list: LIST; n: INTEGER): ITEM;
+VAR
+ cur: ITEM;
+
+BEGIN
+ cur := list.first;
+ WHILE (cur # NIL) & (n > 0) DO
+ cur := cur.next;
+ DEC(n)
+ END
+
+ RETURN cur
+END get;
+
+
+PROCEDURE idx* (list: LIST; item: ITEM): INTEGER;
+VAR
+ cur: ITEM;
+ n: INTEGER;
+
+BEGIN
+ ASSERT(item # NIL);
+ n := 0;
+ cur := list.first;
+ WHILE (cur # NIL) & (cur # item) DO
+ cur := cur.next;
+ INC(n)
+ END;
+
+ IF cur = NIL THEN
+ n := -1
+ END
+
+ RETURN n
+END idx;
+
+
+PROCEDURE create* (list: LIST): LIST;
+BEGIN
+ IF list = NIL THEN
+ NEW(list)
+ END;
+
+ list.first := NIL;
+ list.last := NIL
+
+ RETURN list
+END create;
+
+
+PROCEDURE destroy* (VAR list: LIST);
+VAR
+ item, next: ITEM;
+
+BEGIN
+ IF list # NIL THEN
+ item := list.first;
+ WHILE item # NIL DO
+ next := item.next;
+ IF item.destroy # NIL THEN
+ item.destroy(item)
+ ELSE
+ DISPOSE(item)
+ END;
+ item := next
+ END;
+ DISPOSE(list)
+ END
+END destroy;
+
+
+END LISTS.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/Libimg.ob07 b/programs/other/fb2reader/SRC/Libimg.ob07
new file mode 100644
index 0000000000..ecc2b79c9f
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Libimg.ob07
@@ -0,0 +1,81 @@
+(*
+ Copyright 2016, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE LibImg;
+
+IMPORT sys := SYSTEM, KOSAPI, File, S := Strings;
+
+
+PROCEDURE [stdcall, "Libimg.obj", ""] img_decode (data, size, options: INTEGER): INTEGER; END;
+PROCEDURE [stdcall, "Libimg.obj", ""] img_to_rgb2 (data, data_rgb: INTEGER); END;
+PROCEDURE [stdcall, "Libimg.obj", ""] img_scale (src, crop_x, crop_y, crop_width, crop_height, dst, scale, inter, param1, param2: INTEGER): INTEGER; END;
+PROCEDURE [stdcall, "Libimg.obj", ""] img_destroy* (img: INTEGER); END;
+PROCEDURE [stdcall, "Libimg.obj", ""] img_convert (src, dst, dst_type, flags, param: INTEGER): INTEGER; END;
+
+
+PROCEDURE GetInf* (img: INTEGER; VAR sizeX, sizeY, data: INTEGER);
+BEGIN
+ sys.GET(img + 4, sizeX);
+ sys.GET(img + 8, sizeY);
+ sys.GET(img + 24, data)
+END GetInf;
+
+
+PROCEDURE GetImg* (ptr, size, Width: INTEGER; VAR sizeY: INTEGER): INTEGER;
+VAR
+ image_data, dst, x, y, type: INTEGER;
+BEGIN
+ image_data := img_decode(ptr, size, 0);
+ IF image_data # 0 THEN
+ sys.GET(image_data + 4, x);
+ sys.GET(image_data + 8, y);
+ sys.GET(image_data + 20, type);
+ IF type # 3 THEN
+ dst := img_convert(image_data, 0, 3, 0, 0);
+ img_destroy(image_data);
+ image_data := dst
+ END;
+ IF (x > Width) & (image_data # 0) THEN
+ dst := img_scale(image_data, 0, 0, x, y, 0, 3, 1, Width, (y * Width) DIV x);
+ img_destroy(image_data);
+ image_data := dst
+ END;
+ IF image_data # 0 THEN
+ sys.GET(image_data + 8, sizeY)
+ END
+ END
+ RETURN image_data
+END GetImg;
+
+
+PROCEDURE LoadFromFile* (fileName: S.STRING; width: INTEGER; VAR height: INTEGER): INTEGER;
+VAR
+ size, res, ptr: INTEGER;
+BEGIN
+ res := 0;
+ ptr := File.Load(fileName, size);
+ IF ptr # 0 THEN
+ res := GetImg(ptr, size, width, height);
+ ptr := KOSAPI.free(ptr)
+ END
+ RETURN res
+END LoadFromFile;
+
+
+END LibImg.
diff --git a/programs/other/fb2reader/SRC/OpenDlg.ob07 b/programs/other/fb2reader/SRC/OpenDlg.ob07
new file mode 100644
index 0000000000..b5af638204
--- /dev/null
+++ b/programs/other/fb2reader/SRC/OpenDlg.ob07
@@ -0,0 +1,134 @@
+(*
+ Copyright 2016, 2022 Anton Krotov
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*)
+
+MODULE OpenDlg;
+
+IMPORT sys := SYSTEM, KOSAPI, S := Strings;
+
+TYPE
+
+ DRAW_WINDOW = PROCEDURE;
+
+ TDialog = RECORD
+ type,
+ procinfo,
+ com_area_name,
+ com_area,
+ opendir_path,
+ dir_default_path,
+ start_path: INTEGER;
+ draw_window: DRAW_WINDOW;
+ status*,
+ openfile_path,
+ filename_area: INTEGER;
+ filter_area:
+ POINTER TO RECORD
+ size: INTEGER;
+ filter: ARRAY 4096 OF CHAR
+ END;
+ X, Y: INTEGER;
+
+ procinf: ARRAY 1024 OF CHAR;
+ s_com_area_name: ARRAY 32 OF CHAR;
+ s_opendir_path,
+ s_dir_default_path,
+ FilePath*,
+ FileName*: ARRAY 4096 OF CHAR
+ END;
+
+ Dialog* = POINTER TO TDialog;
+
+
+PROCEDURE [stdcall, "Proc_lib.obj", ""] OpenDialog_start (od: Dialog); END;
+PROCEDURE [stdcall, "Proc_lib.obj", ""] OpenDialog_init (od: Dialog); END;
+
+PROCEDURE Show*(od: Dialog; Width, Height: INTEGER);
+BEGIN
+ IF od # NIL THEN
+ od.X := Width;
+ od.Y := Height;
+ OpenDialog_start(od)
+ END
+END Show;
+
+PROCEDURE Create*(draw_window: DRAW_WINDOW; type: INTEGER; def_path, filter: ARRAY OF CHAR): Dialog;
+VAR res: Dialog; n, i: INTEGER;
+
+ PROCEDURE replace(VAR str: ARRAY OF CHAR; c1, c2: CHAR);
+ VAR i: INTEGER;
+ BEGIN
+ i := LENGTH(str) - 1;
+ WHILE i >= 0 DO
+ IF str[i] = c1 THEN
+ str[i] := c2
+ END;
+ DEC(i)
+ END
+ END replace;
+
+BEGIN
+ NEW(res);
+ IF res # NIL THEN
+ NEW(res.filter_area);
+ IF res.filter_area # NIL THEN
+ res.s_com_area_name := "FFFFFFFF_open_dialog";
+ res.com_area := 0;
+ res.type := type;
+ res.draw_window := draw_window;
+ COPY(def_path, res.s_dir_default_path);
+ COPY(filter, res.filter_area.filter);
+
+ n := LENGTH(res.filter_area.filter);
+ FOR i := 0 TO 3 DO
+ res.filter_area.filter[n + i] := "|"
+ END;
+ res.filter_area.filter[n + 4] := 0X;
+
+ res.X := 0;
+ res.Y := 0;
+ res.s_opendir_path := res.s_dir_default_path;
+ res.FilePath := "";
+ res.FileName := "";
+ res.status := 0;
+ res.filter_area.size := LENGTH(res.filter_area.filter);
+ res.procinfo := sys.ADR(res.procinf[0]);
+ res.com_area_name := sys.ADR(res.s_com_area_name[0]);
+ res.start_path := sys.SADR("/sys/File managers/opendial");
+ res.opendir_path := sys.ADR(res.s_opendir_path[0]);
+ res.dir_default_path := sys.ADR(res.s_dir_default_path[0]);
+ res.openfile_path := sys.ADR(res.FilePath[0]);
+ res.filename_area := sys.ADR(res.FileName[0]);
+
+ replace(res.filter_area.filter, "|", 0X);
+ OpenDialog_init(res)
+ ELSE
+ DISPOSE(res)
+ END
+ END
+ RETURN res
+END Create;
+
+PROCEDURE Destroy*(VAR od: Dialog);
+BEGIN
+ IF od # NIL THEN
+ DISPOSE(od.filter_area);
+ DISPOSE(od)
+ END
+END Destroy;
+
+
+END OpenDlg.
diff --git a/programs/other/fb2reader/SRC/RTL.ob07 b/programs/other/fb2reader/SRC/RTL.ob07
new file mode 100644
index 0000000000..0818bca97d
--- /dev/null
+++ b/programs/other/fb2reader/SRC/RTL.ob07
@@ -0,0 +1,543 @@
+(*
+ BSD 2-Clause License
+
+ Copyright (c) 2018-2021, Anton Krotov
+ All rights reserved.
+*)
+
+MODULE RTL;
+
+IMPORT SYSTEM, API;
+
+
+CONST
+
+ minint = ROR(1, 1);
+
+ WORD = API.BIT_DEPTH DIV 8;
+
+
+VAR
+
+ name: INTEGER;
+ types: INTEGER;
+
+
+PROCEDURE [stdcall] _move* (bytes, dest, source: INTEGER);
+BEGIN
+ SYSTEM.CODE(
+ 08BH, 045H, 008H, (* mov eax, dword [ebp + 8] *)
+ 085H, 0C0H, (* test eax, eax *)
+ 07EH, 019H, (* jle L *)
+ 0FCH, (* cld *)
+ 057H, (* push edi *)
+ 056H, (* push esi *)
+ 08BH, 075H, 010H, (* mov esi, dword [ebp + 16] *)
+ 08BH, 07DH, 00CH, (* mov edi, dword [ebp + 12] *)
+ 089H, 0C1H, (* mov ecx, eax *)
+ 0C1H, 0E9H, 002H, (* shr ecx, 2 *)
+ 0F3H, 0A5H, (* rep movsd *)
+ 089H, 0C1H, (* mov ecx, eax *)
+ 083H, 0E1H, 003H, (* and ecx, 3 *)
+ 0F3H, 0A4H, (* rep movsb *)
+ 05EH, (* pop esi *)
+ 05FH (* pop edi *)
+ (* L: *)
+ )
+END _move;
+
+
+PROCEDURE [stdcall] _arrcpy* (base_size, len_dst, dst, len_src, src: INTEGER): BOOLEAN;
+VAR
+ res: BOOLEAN;
+
+BEGIN
+ IF len_src > len_dst THEN
+ res := FALSE
+ ELSE
+ _move(len_src * base_size, dst, src);
+ res := TRUE
+ END
+
+ RETURN res
+END _arrcpy;
+
+
+PROCEDURE [stdcall] _strcpy* (chr_size, len_src, src, len_dst, dst: INTEGER);
+BEGIN
+ _move(MIN(len_dst, len_src) * chr_size, dst, src)
+END _strcpy;
+
+
+PROCEDURE [stdcall] _rot* (Len, Ptr: INTEGER);
+BEGIN
+ SYSTEM.CODE(
+ 08BH, 04DH, 008H, (* mov ecx, dword [ebp + 8] *) (* ecx <- Len *)
+ 08BH, 045H, 00CH, (* mov eax, dword [ebp + 12] *) (* eax <- Ptr *)
+ 049H, (* dec ecx *)
+ 053H, (* push ebx *)
+ 08BH, 018H, (* mov ebx, dword [eax] *)
+ (* L: *)
+ 08BH, 050H, 004H, (* mov edx, dword [eax + 4] *)
+ 089H, 010H, (* mov dword [eax], edx *)
+ 083H, 0C0H, 004H, (* add eax, 4 *)
+ 049H, (* dec ecx *)
+ 075H, 0F5H, (* jnz L *)
+ 089H, 018H, (* mov dword [eax], ebx *)
+ 05BH, (* pop ebx *)
+ 05DH, (* pop ebp *)
+ 0C2H, 008H, 000H (* ret 8 *)
+ )
+END _rot;
+
+
+PROCEDURE [stdcall] _set* (b, a: INTEGER); (* {a..b} -> eax *)
+BEGIN
+ SYSTEM.CODE(
+ 08BH, 04DH, 008H, (* mov ecx, dword [ebp + 8] *) (* ecx <- b *)
+ 08BH, 045H, 00CH, (* mov eax, dword [ebp + 12] *) (* eax <- a *)
+ 039H, 0C8H, (* cmp eax, ecx *)
+ 07FH, 033H, (* jg L1 *)
+ 083H, 0F8H, 01FH, (* cmp eax, 31 *)
+ 07FH, 02EH, (* jg L1 *)
+ 085H, 0C9H, (* test ecx, ecx *)
+ 07CH, 02AH, (* jl L1 *)
+ 083H, 0F9H, 01FH, (* cmp ecx, 31 *)
+ 07EH, 005H, (* jle L3 *)
+ 0B9H, 01FH, 000H, 000H, 000H, (* mov ecx, 31 *)
+ (* L3: *)
+ 085H, 0C0H, (* test eax, eax *)
+ 07DH, 002H, (* jge L2 *)
+ 031H, 0C0H, (* xor eax, eax *)
+ (* L2: *)
+ 089H, 0CAH, (* mov edx, ecx *)
+ 029H, 0C2H, (* sub edx, eax *)
+ 0B8H, 000H, 000H, 000H, 080H, (* mov eax, 0x80000000 *)
+ 087H, 0CAH, (* xchg edx, ecx *)
+ 0D3H, 0F8H, (* sar eax, cl *)
+ 087H, 0CAH, (* xchg edx, ecx *)
+ 083H, 0E9H, 01FH, (* sub ecx, 31 *)
+ 0F7H, 0D9H, (* neg ecx *)
+ 0D3H, 0E8H, (* shr eax, cl *)
+ 05DH, (* pop ebp *)
+ 0C2H, 008H, 000H, (* ret 8 *)
+ (* L1: *)
+ 031H, 0C0H, (* xor eax, eax *)
+ 05DH, (* pop ebp *)
+ 0C2H, 008H, 000H (* ret 8 *)
+ )
+END _set;
+
+
+PROCEDURE [stdcall] _set1* (a: INTEGER); (* {a} -> eax *)
+BEGIN
+ SYSTEM.CODE(
+ 031H, 0C0H, (* xor eax, eax *)
+ 08BH, 04DH, 008H, (* mov ecx, dword [ebp + 8] *) (* ecx <- a *)
+ 083H, 0F9H, 01FH, (* cmp ecx, 31 *)
+ 077H, 003H, (* ja L *)
+ 00FH, 0ABH, 0C8H (* bts eax, ecx *)
+ (* L: *)
+ )
+END _set1;
+
+
+PROCEDURE [stdcall] _divmod* (y, x: INTEGER); (* (x div y) -> eax; (x mod y) -> edx *)
+BEGIN
+ SYSTEM.CODE(
+ 053H, (* push ebx *)
+ 08BH, 045H, 00CH, (* mov eax, dword [ebp + 12] *) (* eax <- x *)
+ 031H, 0D2H, (* xor edx, edx *)
+ 085H, 0C0H, (* test eax, eax *)
+ 074H, 018H, (* je L2 *)
+ 07FH, 002H, (* jg L1 *)
+ 0F7H, 0D2H, (* not edx *)
+ (* L1: *)
+ 089H, 0C3H, (* mov ebx, eax *)
+ 08BH, 04DH, 008H, (* mov ecx, dword [ebp + 8] *) (* ecx <- y *)
+ 0F7H, 0F9H, (* idiv ecx *)
+ 085H, 0D2H, (* test edx, edx *)
+ 074H, 009H, (* je L2 *)
+ 031H, 0CBH, (* xor ebx, ecx *)
+ 085H, 0DBH, (* test ebx, ebx *)
+ 07DH, 003H, (* jge L2 *)
+ 048H, (* dec eax *)
+ 001H, 0CAH, (* add edx, ecx *)
+ (* L2: *)
+ 05BH (* pop ebx *)
+ )
+END _divmod;
+
+
+PROCEDURE [stdcall] _new* (t, size: INTEGER; VAR ptr: INTEGER);
+BEGIN
+ ptr := API._NEW(size);
+ IF ptr # 0 THEN
+ SYSTEM.PUT(ptr, t);
+ INC(ptr, WORD)
+ END
+END _new;
+
+
+PROCEDURE [stdcall] _dispose* (VAR ptr: INTEGER);
+BEGIN
+ IF ptr # 0 THEN
+ ptr := API._DISPOSE(ptr - WORD)
+ END
+END _dispose;
+
+
+PROCEDURE [stdcall] _length* (len, str: INTEGER);
+BEGIN
+ SYSTEM.CODE(
+ 08BH, 045H, 00CH, (* mov eax, dword [ebp + 0Ch] *)
+ 08BH, 04DH, 008H, (* mov ecx, dword [ebp + 08h] *)
+ 048H, (* dec eax *)
+ (* L1: *)
+ 040H, (* inc eax *)
+ 080H, 038H, 000H, (* cmp byte [eax], 0 *)
+ 074H, 003H, (* jz L2 *)
+ 0E2H, 0F8H, (* loop L1 *)
+ 040H, (* inc eax *)
+ (* L2: *)
+ 02BH, 045H, 00CH (* sub eax, dword [ebp + 0Ch] *)
+ )
+END _length;
+
+
+PROCEDURE [stdcall] _lengthw* (len, str: INTEGER);
+BEGIN
+ SYSTEM.CODE(
+ 08BH, 045H, 00CH, (* mov eax, dword [ebp + 0Ch] *)
+ 08BH, 04DH, 008H, (* mov ecx, dword [ebp + 08h] *)
+ 048H, (* dec eax *)
+ 048H, (* dec eax *)
+ (* L1: *)
+ 040H, (* inc eax *)
+ 040H, (* inc eax *)
+ 066H, 083H, 038H, 000H, (* cmp word [eax], 0 *)
+ 074H, 004H, (* jz L2 *)
+ 0E2H, 0F6H, (* loop L1 *)
+ 040H, (* inc eax *)
+ 040H, (* inc eax *)
+ (* L2: *)
+ 02BH, 045H, 00CH, (* sub eax, dword [ebp + 0Ch] *)
+ 0D1H, 0E8H (* shr eax, 1 *)
+ )
+END _lengthw;
+
+
+PROCEDURE [stdcall] strncmp (a, b, n: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 056H, (* push esi *)
+ 057H, (* push edi *)
+ 053H, (* push ebx *)
+ 08BH, 075H, 008H, (* mov esi, dword[ebp + 8]; esi <- a *)
+ 08BH, 07DH, 00CH, (* mov edi, dword[ebp + 12]; edi <- b *)
+ 08BH, 05DH, 010H, (* mov ebx, dword[ebp + 16]; ebx <- n *)
+ 031H, 0C9H, (* xor ecx, ecx *)
+ 031H, 0D2H, (* xor edx, edx *)
+ 0B8H,
+ 000H, 000H, 000H, 080H, (* mov eax, minint *)
+ (* L1: *)
+ 085H, 0DBH, (* test ebx, ebx *)
+ 07EH, 017H, (* jle L3 *)
+ 08AH, 00EH, (* mov cl, byte[esi] *)
+ 08AH, 017H, (* mov dl, byte[edi] *)
+ 046H, (* inc esi *)
+ 047H, (* inc edi *)
+ 04BH, (* dec ebx *)
+ 039H, 0D1H, (* cmp ecx, edx *)
+ 074H, 006H, (* je L2 *)
+ 089H, 0C8H, (* mov eax, ecx *)
+ 029H, 0D0H, (* sub eax, edx *)
+ 0EBH, 006H, (* jmp L3 *)
+ (* L2: *)
+ 085H, 0C9H, (* test ecx, ecx *)
+ 075H, 0E7H, (* jne L1 *)
+ 031H, 0C0H, (* xor eax, eax *)
+ (* L3: *)
+ 05BH, (* pop ebx *)
+ 05FH, (* pop edi *)
+ 05EH, (* pop esi *)
+ 05DH, (* pop ebp *)
+ 0C2H, 00CH, 000H (* ret 12 *)
+ )
+ RETURN 0
+END strncmp;
+
+
+PROCEDURE [stdcall] strncmpw (a, b, n: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.CODE(
+ 056H, (* push esi *)
+ 057H, (* push edi *)
+ 053H, (* push ebx *)
+ 08BH, 075H, 008H, (* mov esi, dword[ebp + 8]; esi <- a *)
+ 08BH, 07DH, 00CH, (* mov edi, dword[ebp + 12]; edi <- b *)
+ 08BH, 05DH, 010H, (* mov ebx, dword[ebp + 16]; ebx <- n *)
+ 031H, 0C9H, (* xor ecx, ecx *)
+ 031H, 0D2H, (* xor edx, edx *)
+ 0B8H,
+ 000H, 000H, 000H, 080H, (* mov eax, minint *)
+ (* L1: *)
+ 085H, 0DBH, (* test ebx, ebx *)
+ 07EH, 01BH, (* jle L3 *)
+ 066H, 08BH, 00EH, (* mov cx, word[esi] *)
+ 066H, 08BH, 017H, (* mov dx, word[edi] *)
+ 046H, (* inc esi *)
+ 046H, (* inc esi *)
+ 047H, (* inc edi *)
+ 047H, (* inc edi *)
+ 04BH, (* dec ebx *)
+ 039H, 0D1H, (* cmp ecx, edx *)
+ 074H, 006H, (* je L2 *)
+ 089H, 0C8H, (* mov eax, ecx *)
+ 029H, 0D0H, (* sub eax, edx *)
+ 0EBH, 006H, (* jmp L3 *)
+ (* L2: *)
+ 085H, 0C9H, (* test ecx, ecx *)
+ 075H, 0E3H, (* jne L1 *)
+ 031H, 0C0H, (* xor eax, eax *)
+ (* L3: *)
+ 05BH, (* pop ebx *)
+ 05FH, (* pop edi *)
+ 05EH, (* pop esi *)
+ 05DH, (* pop ebp *)
+ 0C2H, 00CH, 000H (* ret 12 *)
+ )
+ RETURN 0
+END strncmpw;
+
+
+PROCEDURE [stdcall] _strcmp* (op, len2, str2, len1, str1: INTEGER): BOOLEAN;
+VAR
+ res: INTEGER;
+ bRes: BOOLEAN;
+ c: CHAR;
+
+BEGIN
+ res := strncmp(str1, str2, MIN(len1, len2));
+ IF res = minint THEN
+ IF len1 > len2 THEN
+ SYSTEM.GET(str1 + len2, c);
+ res := ORD(c)
+ ELSIF len1 < len2 THEN
+ SYSTEM.GET(str2 + len1, c);
+ res := -ORD(c)
+ ELSE
+ res := 0
+ END
+ END;
+
+ CASE op OF
+ |0: bRes := res = 0
+ |1: bRes := res # 0
+ |2: bRes := res < 0
+ |3: bRes := res <= 0
+ |4: bRes := res > 0
+ |5: bRes := res >= 0
+ END
+
+ RETURN bRes
+END _strcmp;
+
+
+PROCEDURE [stdcall] _strcmpw* (op, len2, str2, len1, str1: INTEGER): BOOLEAN;
+VAR
+ res: INTEGER;
+ bRes: BOOLEAN;
+ c: WCHAR;
+
+BEGIN
+ res := strncmpw(str1, str2, MIN(len1, len2));
+ IF res = minint THEN
+ IF len1 > len2 THEN
+ SYSTEM.GET(str1 + len2 * 2, c);
+ res := ORD(c)
+ ELSIF len1 < len2 THEN
+ SYSTEM.GET(str2 + len1 * 2, c);
+ res := -ORD(c)
+ ELSE
+ res := 0
+ END
+ END;
+
+ CASE op OF
+ |0: bRes := res = 0
+ |1: bRes := res # 0
+ |2: bRes := res < 0
+ |3: bRes := res <= 0
+ |4: bRes := res > 0
+ |5: bRes := res >= 0
+ END
+
+ RETURN bRes
+END _strcmpw;
+
+
+PROCEDURE PCharToStr (pchar: INTEGER; VAR s: ARRAY OF CHAR);
+VAR
+ c: CHAR;
+ i: INTEGER;
+
+BEGIN
+ i := 0;
+ REPEAT
+ SYSTEM.GET(pchar, c);
+ s[i] := c;
+ INC(pchar);
+ INC(i)
+ UNTIL c = 0X
+END PCharToStr;
+
+
+PROCEDURE IntToStr (x: INTEGER; VAR str: ARRAY OF CHAR);
+VAR
+ i, a: INTEGER;
+
+BEGIN
+ i := 0;
+ a := x;
+ REPEAT
+ INC(i);
+ a := a DIV 10
+ UNTIL a = 0;
+
+ str[i] := 0X;
+
+ REPEAT
+ DEC(i);
+ str[i] := CHR(x MOD 10 + ORD("0"));
+ x := x DIV 10
+ UNTIL x = 0
+END IntToStr;
+
+
+PROCEDURE append (VAR s1: ARRAY OF CHAR; s2: ARRAY OF CHAR);
+VAR
+ n1, n2: INTEGER;
+
+BEGIN
+ n1 := LENGTH(s1);
+ n2 := LENGTH(s2);
+
+ ASSERT(n1 + n2 < LEN(s1));
+
+ SYSTEM.MOVE(SYSTEM.ADR(s2[0]), SYSTEM.ADR(s1[n1]), n2);
+ s1[n1 + n2] := 0X
+END append;
+
+
+PROCEDURE [stdcall] _error* (modnum, _module, err, line: INTEGER);
+VAR
+ s, temp: ARRAY 1024 OF CHAR;
+
+BEGIN
+ CASE err OF
+ | 1: s := "assertion failure"
+ | 2: s := "NIL dereference"
+ | 3: s := "bad divisor"
+ | 4: s := "NIL procedure call"
+ | 5: s := "type guard error"
+ | 6: s := "index out of range"
+ | 7: s := "invalid CASE"
+ | 8: s := "array assignment error"
+ | 9: s := "CHR out of range"
+ |10: s := "WCHR out of range"
+ |11: s := "BYTE out of range"
+ END;
+
+ append(s, API.eol + "module: "); PCharToStr(_module, temp); append(s, temp);
+ append(s, API.eol + "line: "); IntToStr(line, temp); append(s, temp);
+
+ API.DebugMsg(SYSTEM.ADR(s[0]), name);
+
+ API.exit_thread(0)
+END _error;
+
+
+PROCEDURE [stdcall] _isrec* (t0, t1, r: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.GET(t0 + t1 + types, t0)
+ RETURN t0 MOD 2
+END _isrec;
+
+
+PROCEDURE [stdcall] _is* (t0, p: INTEGER): INTEGER;
+BEGIN
+ IF p # 0 THEN
+ SYSTEM.GET(p - WORD, p);
+ SYSTEM.GET(t0 + p + types, p)
+ END
+
+ RETURN p MOD 2
+END _is;
+
+
+PROCEDURE [stdcall] _guardrec* (t0, t1: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.GET(t0 + t1 + types, t0)
+ RETURN t0 MOD 2
+END _guardrec;
+
+
+PROCEDURE [stdcall] _guard* (t0, p: INTEGER): INTEGER;
+BEGIN
+ SYSTEM.GET(p, p);
+ IF p # 0 THEN
+ SYSTEM.GET(p - WORD, p);
+ SYSTEM.GET(t0 + p + types, p)
+ ELSE
+ p := 1
+ END
+
+ RETURN p MOD 2
+END _guard;
+
+
+PROCEDURE [stdcall] _dllentry* (hinstDLL, fdwReason, lpvReserved: INTEGER): INTEGER;
+ RETURN API.dllentry(hinstDLL, fdwReason, lpvReserved)
+END _dllentry;
+
+
+PROCEDURE [stdcall] _sofinit*;
+BEGIN
+ API.sofinit
+END _sofinit;
+
+
+PROCEDURE [stdcall] _exit* (code: INTEGER);
+BEGIN
+ API.exit(code)
+END _exit;
+
+
+PROCEDURE [stdcall] _init* (modname: INTEGER; tcount, _types: INTEGER; code, param: INTEGER);
+VAR
+ t0, t1, i, j: INTEGER;
+
+BEGIN
+ SYSTEM.CODE(09BH, 0DBH, 0E3H); (* finit *)
+ API.init(param, code);
+
+ types := API._NEW(tcount * tcount + SYSTEM.SIZE(INTEGER));
+ ASSERT(types # 0);
+ FOR i := 0 TO tcount - 1 DO
+ FOR j := 0 TO tcount - 1 DO
+ t0 := i; t1 := j;
+
+ WHILE (t1 # 0) & (t1 # t0) DO
+ SYSTEM.GET(_types + t1 * WORD, t1)
+ END;
+
+ SYSTEM.PUT8(i * tcount + j + types, ORD(t0 = t1))
+ END
+ END;
+
+ name := modname
+END _init;
+
+
+END RTL.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/Read.ob07 b/programs/other/fb2reader/SRC/Read.ob07
new file mode 100644
index 0000000000..cf6c0fc922
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Read.ob07
@@ -0,0 +1,42 @@
+(*
+ Copyright 2016 Anton Krotov
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*)
+
+MODULE Read;
+
+IMPORT File, sys := SYSTEM;
+
+PROCEDURE Char*(F: File.FS; VAR x: CHAR): BOOLEAN;
+ RETURN File.Read(F, sys.ADR(x), sys.SIZE(CHAR)) = sys.SIZE(CHAR)
+END Char;
+
+PROCEDURE Int*(F: File.FS; VAR x: INTEGER): BOOLEAN;
+ RETURN File.Read(F, sys.ADR(x), sys.SIZE(INTEGER)) = sys.SIZE(INTEGER)
+END Int;
+
+PROCEDURE Real*(F: File.FS; VAR x: REAL): BOOLEAN;
+ RETURN File.Read(F, sys.ADR(x), sys.SIZE(REAL)) = sys.SIZE(REAL)
+END Real;
+
+PROCEDURE Boolean*(F: File.FS; VAR x: BOOLEAN): BOOLEAN;
+ RETURN File.Read(F, sys.ADR(x), sys.SIZE(BOOLEAN)) = sys.SIZE(BOOLEAN)
+END Boolean;
+
+PROCEDURE Set*(F: File.FS; VAR x: SET): BOOLEAN;
+ RETURN File.Read(F, sys.ADR(x), sys.SIZE(SET)) = sys.SIZE(SET)
+END Set;
+
+END Read.
diff --git a/programs/other/fb2reader/SRC/ReadFile.ob07 b/programs/other/fb2reader/SRC/ReadFile.ob07
new file mode 100644
index 0000000000..81ccc562d4
--- /dev/null
+++ b/programs/other/fb2reader/SRC/ReadFile.ob07
@@ -0,0 +1,159 @@
+(*
+ Copyright 2016, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE ReadFile;
+
+IMPORT sys := SYSTEM, K := KOSAPI, S := Strings, File, SU := SysUtils, Encode;
+
+
+VAR
+
+ Mem, Pos, Size, FSize*: INTEGER;
+
+ Error*: BOOLEAN;
+
+
+PROCEDURE Adr*(): INTEGER;
+ RETURN Mem + Pos
+END Adr;
+
+
+PROCEDURE Next*(VAR ch: CHAR);
+BEGIN
+ INC(Pos);
+ sys.GET(Mem + Pos, ch)
+END Next;
+
+
+PROCEDURE Progress*(): REAL;
+VAR res: REAL;
+BEGIN
+ res := FLT(Pos) / FLT(Size);
+ IF res < 0.0 THEN
+ res := 0.0
+ END;
+ IF res > 1.0 THEN
+ res := 1.0
+ END
+ RETURN res
+END Progress;
+
+
+PROCEDURE Load*(FileName: S.STRING);
+VAR F: File.FS; pos, FileSize: INTEGER;
+BEGIN
+ Error := TRUE;
+ Mem := 0;
+ F := File.Open(FileName);
+ SU.ErrorIf(F = NIL, 1);
+ FileSize := File.Seek(F, 0, File.SEEK_END);
+ Size := FileSize;
+ SU.ErrorIf(FileSize <= 0, 1);
+ pos := File.Seek(F, 0, File.SEEK_BEG);
+ SU.ErrorIf(pos # 0, 1);
+ Mem := K.malloc(FileSize + 1024);
+ SU.MemError(Mem = 0);
+ pos := File.Read(F, Mem, FileSize);
+ SU.ErrorIf(pos # FileSize, 1);
+ sys.PUT(Mem + FileSize, 0X);
+ File.Close(F);
+ Pos := -1;
+ Error := FALSE;
+ FSize := FileSize
+END Load;
+
+
+PROCEDURE Free*;
+BEGIN
+ IF Mem # 0 THEN
+ Mem := K.free(Mem)
+ END
+END Free;
+
+
+PROCEDURE Conv*(cp: Encode.CP);
+VAR m, nov, mem2, k: INTEGER; c: CHAR;
+BEGIN
+ m := Mem;
+ k := 0;
+ REPEAT
+ sys.GET(m, c); INC(m);
+ k := k + cp[ORD(c)].len
+ UNTIL c = 0X;
+ nov := K.malloc(k + 1024);
+ SU.MemError(nov = 0);
+ Size := k;
+ mem2 := nov;
+ m := Mem;
+ REPEAT
+ sys.GET(m, c); INC(m);
+ sys.MOVE(sys.ADR(cp[ORD(c)].utf8), nov, cp[ORD(c)].len);
+ nov := nov + cp[ORD(c)].len
+ UNTIL c = 0X;
+ Pos := -1;
+ Mem := K.free(Mem);
+ Mem := mem2;
+END Conv;
+
+
+PROCEDURE SeekBeg*;
+BEGIN
+ Pos := -1
+END SeekBeg;
+
+
+PROCEDURE Int*(): INTEGER;
+VAR i: INTEGER;
+BEGIN
+ sys.GET(Mem + Pos, i)
+ RETURN i
+END Int;
+
+
+PROCEDURE FileSize*(name: S.STRING): INTEGER;
+VAR F: File.FS; res: INTEGER;
+BEGIN
+ F := File.Open(name);
+ res := File.Seek(F, 0, 2);
+ File.Close(F)
+ RETURN res
+END FileSize;
+
+
+PROCEDURE ChkSum* (name: S.STRING): INTEGER;
+VAR
+ ptr, size, res: INTEGER;
+ b: BYTE;
+BEGIN
+ res := 0;
+ ptr := File.Load(name, size);
+ IF ptr # 0 THEN
+ WHILE size > 0 DO
+ sys.GET(ptr, b);
+ INC(res, b);
+ INC(ptr);
+ DEC(size)
+ END;
+ ptr := K.free(ptr)
+ END
+ RETURN res
+END ChkSum;
+
+
+END ReadFile.
diff --git a/programs/other/fb2reader/SRC/Search.ob07 b/programs/other/fb2reader/SRC/Search.ob07
new file mode 100644
index 0000000000..c4f4de1c6c
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Search.ob07
@@ -0,0 +1,645 @@
+(*
+ Copyright 2020, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Search;
+
+IMPORT
+
+ XML, G := Graph, Window, Font, S := Strings, LISTS, SYSTEM,
+ SU := SysUtils, K := KOSAPI, SearchForm;
+
+
+TYPE
+
+ STRING* = SearchForm.STRING;
+
+ PFind = PROCEDURE (d: INTEGER);
+
+ TRect = POINTER TO RECORD (LISTS.ITEM)
+
+ x1, y1, x2, y2: INTEGER
+
+ END;
+
+ TPos = POINTER TO RECORD (LISTS.ITEM)
+
+ n, first, last: INTEGER;
+ RectList: LISTS.LIST
+
+ END;
+
+ TextIdx = POINTER TO RECORD
+
+ cnt, offs: ARRAY 256 OF INTEGER;
+ table: INTEGER;
+ data, size: INTEGER
+
+ END;
+
+ Text = POINTER TO RECORD (LISTS.ITEM)
+
+ case: BOOLEAN;
+ idx0, idx1: TextIdx;
+ str0, str1: STRING;
+ PosList: LISTS.LIST;
+ curPos: TPos;
+ found: INTEGER;
+ body: XML.TAG
+
+ END;
+
+
+VAR
+
+ TextList: LISTS.LIST;
+ Body: XML.TAG;
+ Find: PFind;
+
+
+PROCEDURE SelText (Col: Window.TRect; min, max, Ycur, LineH: INTEGER; right: BOOLEAN; rect: TRect; cur: BOOLEAN);
+VAR
+ y, y0, color: INTEGER;
+
+BEGIN
+ y := rect.y1 - Ycur;
+ y0 := y - y MOD LineH;
+ IF (min <= y0) & (y0 <= max) THEN
+ IF cur THEN
+ color := 0FF0000H
+ ELSE
+ color := 0
+ END;
+ 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)
+ END
+END SelText;
+
+
+PROCEDURE draw* (body: XML.TAG; ColLeft, ColRight: Window.TRect; Ycur, LineH: INTEGER; TwoCol: BOOLEAN);
+VAR
+ rect: TRect;
+ pos, cur: TPos;
+
+BEGIN
+ Body := body;
+ IF body.text # NIL THEN
+ pos := body.text(Text).PosList.first(TPos);
+ cur := body.text(Text).curPos
+ ELSE
+ pos := NIL;
+ cur := NIL
+ END;
+ WHILE pos # NIL DO
+ rect := pos.RectList.first(TRect);
+ WHILE rect # NIL DO
+ SelText(ColLeft, 0, ColLeft.Height - LineH, Ycur, LineH, FALSE, rect, pos = cur);
+ IF TwoCol THEN
+ SelText(ColRight, ColLeft.Height, ColLeft.Height + ColRight.Height - LineH, Ycur, LineH, TRUE, rect, pos = cur)
+ END;
+ rect := rect.next(TRect)
+ END;
+ pos := pos.next(TPos)
+ END
+END draw;
+
+
+PROCEDURE getc_utf8 (VAR text, size, code: INTEGER);
+VAR
+ c: BYTE;
+ n, k: INTEGER;
+ end: BOOLEAN;
+
+BEGIN
+ ASSERT(size > 0);
+ code := 0;
+ end := FALSE;
+ REPEAT
+ SYSTEM.GET(text, c);
+ INC(text);
+ DEC(size);
+ CASE c OF
+ | 0..127:
+ code := c;
+ end := TRUE
+
+ |128..191:
+ code := code * 64 + c MOD 64;
+ DEC(n);
+ end := n <= 0
+
+ |192..255:
+ k := LSL(c, 24);
+ n := -2;
+ REPEAT
+ k := ROR(k, -1);
+ INC(n)
+ UNTIL ~ODD(k);
+ k := LSL(c, n + 25);
+ code := LSR(k, n + 25)
+
+ END
+ UNTIL (size = 0) OR end
+END getc_utf8;
+
+
+PROCEDURE textlen (body: XML.ELEMENT; VAR length: INTEGER);
+VAR
+ cur: XML.ELEMENT;
+
+BEGIN
+ cur := body;
+ WHILE (cur # NIL) DO
+ IF cur IS XML.TAG THEN
+ textlen(cur(XML.TAG).child.first, length)
+ ELSIF cur IS XML.WORD THEN
+ INC(length, cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1)
+ ELSIF cur IS XML.SPACE THEN
+ INC(length)
+ END;
+ cur := cur.next
+ END
+END textlen;
+
+
+PROCEDURE puttext (body: XML.ELEMENT; VAR buf: INTEGER);
+VAR
+ cur: XML.ELEMENT;
+ len: INTEGER;
+
+BEGIN
+ cur := body;
+ WHILE (cur # NIL) DO
+ IF cur IS XML.TAG THEN
+ puttext(cur(XML.TAG).child.first, buf)
+ ELSIF cur IS XML.WORD THEN
+ len := cur(XML.WORD).value.last - cur(XML.WORD).value.first + 1;
+ SYSTEM.MOVE(cur(XML.WORD).value.first, buf, len);
+ INC(buf, len)
+ ELSIF cur IS XML.SPACE THEN
+ SYSTEM.PUT(buf, 20X);
+ INC(buf)
+ END;
+ cur := cur.next
+ END
+END puttext;
+
+
+PROCEDURE cap (code: INTEGER): INTEGER;
+BEGIN
+ CASE code OF
+ |61H..7AH, 430H..44FH:
+ DEC(code, 32)
+ |451H..45FH:
+ DEC(code, 80)
+ |491H:
+ code := 490H
+ ELSE
+ END
+ RETURN code
+END cap;
+
+
+PROCEDURE UpCase (s1, s2, length: INTEGER);
+VAR
+ code, n: INTEGER;
+ u: S.UTF8;
+
+BEGIN
+ WHILE length > 0 DO
+ getc_utf8(s1, length, code);
+ S.utf8(cap(code), u);
+ n := LENGTH(u);
+ SYSTEM.MOVE(SYSTEM.ADR(u[0]), s2, n);
+ INC(s2, n)
+ END
+END UpCase;
+
+
+PROCEDURE create (body: XML.ELEMENT);
+VAR
+ length, buf, buf1, temp: INTEGER;
+ text: Text;
+ xml: XML.ELEMENT;
+
+
+ PROCEDURE index (idx: TextIdx; buf, length: INTEGER);
+ VAR
+ i: INTEGER;
+ c: CHAR;
+ offs, temp: INTEGER;
+
+ BEGIN
+ idx.data := buf;
+ idx.size := length;
+
+ FOR i := 0 TO 255 DO
+ idx.offs[i] := 0;
+ idx.cnt[i] := 0
+ END;
+
+ i := length;
+
+ WHILE i > 0 DO
+ SYSTEM.GET(buf, c);
+ INC(idx.offs[ORD(c)]);
+ DEC(i);
+ INC(buf)
+ END;
+
+ offs := 0;
+
+ FOR i := 0 TO 255 DO
+ temp := offs;
+ INC(offs, idx.offs[i]);
+ idx.offs[i] := temp * 4
+ END;
+
+ idx.table := K.malloc(offs * 4);
+ SU.MemError(idx.table = 0);
+
+ i := length;
+ buf := idx.data;
+
+ WHILE i > 0 DO
+ SYSTEM.GET(buf, c);
+ SYSTEM.PUT(idx.table + idx.offs[ORD(c)] + idx.cnt[ORD(c)] * 4, length - i);
+ INC(idx.cnt[ORD(c)]);
+ DEC(i);
+ INC(buf)
+ END
+ END index;
+
+
+BEGIN
+ NEW(text);
+ text.body := body(XML.TAG);
+ text.PosList := LISTS.create(NIL);
+
+ xml := body;
+ body := body(XML.TAG).child.first;
+ textlen(body, length);
+ buf := K.malloc(length);
+ SU.MemError(buf = 0);
+ temp := buf;
+ puttext(body, temp);
+
+ NEW(text.idx0);
+ index(text.idx0, buf, length);
+
+ buf1 := K.malloc(length);
+ SU.MemError(buf1 = 0);
+
+ UpCase(buf, buf1, length);
+
+ NEW(text.idx1);
+ index(text.idx1, buf1, text.idx0.size);
+
+ text.case := FALSE;
+
+ text.str0 := "";
+ text.str1 := "";
+ xml(XML.TAG).text := text;
+ LISTS.push(TextList, text)
+END create;
+
+
+PROCEDURE select (body: XML.ELEMENT; VAR pos: TPos; VAR curpos, strong, italic, code: INTEGER);
+VAR
+ cur : XML.ELEMENT;
+ word : XML.WORD;
+ space : XML.SPACE;
+
+ tag_value, len, wbeg, wend, selbeg, selend,
+ a, b, z, x, w: INTEGER;
+
+
+ PROCEDURE New (RectList: LISTS.LIST; x1, y1, x2, y2: INTEGER);
+ VAR rect: TRect;
+ BEGIN
+ NEW(rect);
+ rect.x1 := x1; rect.y1 := y1;
+ rect.x2 := x2; rect.y2 := y2;
+ LISTS.push(RectList, rect)
+ END New;
+
+
+BEGIN
+ cur := body;
+ WHILE (cur # NIL) & (pos # NIL) DO
+ selbeg := pos.first;
+ selend := pos.last;
+ IF cur IS XML.TAG THEN
+ tag_value := cur(XML.TAG).value;
+
+ CASE tag_value OF
+ |XML.tag_title, XML.tag_strong, XML.tag_th:
+ INC(strong);
+ Font.Bold(TRUE)
+ |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
+ INC(italic);
+ Font.Italic(TRUE, FALSE)
+ |XML.tag_code:
+ Font.sysfont(TRUE);
+ INC(code)
+ ELSE
+ END;
+
+ select(cur(XML.TAG).child.first, pos, curpos, strong, italic, code);
+
+ CASE tag_value OF
+ |XML.tag_title, XML.tag_strong, XML.tag_th, XML.tag_text_author, XML.tag_date:
+ DEC(strong);
+ Font.Bold(strong > 0)
+ |XML.tag_epigraph, XML.tag_cite, XML.tag_emphasis:
+ DEC(italic);
+ Font.Italic(italic > 0, FALSE)
+ |XML.tag_code:
+ DEC(code);
+ Font.sysfont(code > 0)
+ ELSE
+ END;
+
+ IF pos # NIL THEN
+ selbeg := pos.first;
+ selend := pos.last
+ END
+ ELSIF cur IS XML.WORD THEN
+ word := cur(XML.WORD);
+ len := word.value.last - word.value.first + 1;
+ wbeg := curpos;
+ wend := curpos + len - 1;
+ INC(curpos, len);
+
+ a := MAX(wbeg, selbeg);
+ b := MIN(wend, selend);
+
+ IF b >= a THEN
+ x := word.width;
+ IF (a = wbeg) & (b = wend) THEN
+ New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
+ ELSIF (a = selbeg) & (b = wend) THEN
+ z := selbeg - wbeg;
+ INC(word.value.first, z);
+ word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
+ INC(word.X, x - word.width);
+ New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
+ DEC(word.value.first, z);
+ DEC(word.X, x - word.width)
+ ELSIF (a = wbeg) & (b = selend) THEN
+ z := wend - selend;
+ DEC(word.value.last, z);
+ word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
+ New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
+ INC(word.value.last, z)
+ ELSIF (a = selbeg) & (b = selend) THEN
+ z := selbeg - wbeg;
+ w := wend - selend;
+ INC(word.value.first, z);
+ INC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
+ DEC(word.value.last, w);
+ word.width := Font.TextWidth(word.value, S.Utf8Length(word.value));
+ New(pos.RectList, word.X, word.Y, word.X + word.width, word.Y + Font.FontH());
+ INC(word.value.last, w);
+ DEC(word.X, x - Font.TextWidth(word.value, S.Utf8Length(word.value)));
+ DEC(word.value.first, z)
+ END;
+ word.width := x
+ END
+ ELSIF cur IS XML.SPACE THEN
+ IF (selbeg <= curpos) & (curpos <= selend) THEN
+ space := cur(XML.SPACE);
+ New(pos.RectList, space.X, space.Y, space.X + space.width, space.Y + Font.FontH())
+ END;
+ len := 1;
+ INC(curpos)
+ END;
+ IF curpos > selend THEN
+ IF pos # NIL THEN
+ pos := pos.next(TPos);
+ END;
+ IF cur IS XML.TEXT THEN
+ DEC(curpos, len)
+ ELSE (* tag *)
+ cur := cur.next
+ END
+ ELSE
+ cur := cur.next
+ END
+ END
+END select;
+
+
+PROCEDURE streq (s1, s2, n: INTEGER): BOOLEAN;
+VAR
+ c1, c2: CHAR;
+
+BEGIN
+ REPEAT
+ SYSTEM.GET(s1, c1); INC(s1);
+ SYSTEM.GET(s2, c2); INC(s2);
+ DEC(n)
+ UNTIL (n = 0) OR (c1 # c2)
+
+ RETURN c1 = c2
+END streq;
+
+
+PROCEDURE destroy (VAR item: LISTS.ITEM);
+BEGIN
+ LISTS.destroy(item(TPos).RectList);
+ DISPOSE(item)
+END destroy;
+
+
+PROCEDURE find (body: XML.TAG; str: STRING);
+VAR
+ c: CHAR;
+ offs, i, pos, strong, italic, code: INTEGER;
+ posItem: TPos;
+ text: Text;
+ pstr, slen: INTEGER;
+ idx: TextIdx;
+
+BEGIN
+ text := body.text(Text);
+ text.found := 0;
+ LISTS.destroy(text.PosList);
+ text.PosList := LISTS.create(NIL);
+
+ text.str0 := str;
+ UpCase(SYSTEM.ADR(str[0]), SYSTEM.ADR(text.str1[0]), LENGTH(str));
+
+ IF text.case THEN
+ idx := text.idx0;
+ pstr := SYSTEM.ADR(text.str0[0])
+ ELSE
+ idx := text.idx1;
+ pstr := SYSTEM.ADR(text.str1[0])
+ END;
+
+ slen := LENGTH(str);
+
+ SYSTEM.GET(pstr, c);
+ offs := idx.offs[ORD(c)];
+ i := idx.cnt[ORD(c)];
+ WHILE i > 0 DO
+ SYSTEM.GET(idx.table + offs, pos);
+ INC(offs, 4);
+ IF (pos + slen <= idx.size) & streq(pstr, idx.data + pos, slen) THEN
+ NEW(posItem);
+ posItem.n := text.found;
+ posItem.first := pos;
+ posItem.last := pos + slen - 1;
+ posItem.RectList := LISTS.create(NIL);
+ posItem.destroy := destroy;
+ LISTS.push(text.PosList, posItem);
+ INC(text.found)
+ END;
+ DEC(i)
+ END;
+ posItem := text.PosList.first(TPos);
+ pos := 0; strong := 0; italic := 0; code := 0;
+ select(body.child.first, posItem, pos, strong, italic, code);
+ text.curPos := NIL
+END find;
+
+
+PROCEDURE ffirst (body: XML.TAG);
+VAR
+ text: Text;
+
+BEGIN
+ text := body.text(Text);
+ IF text.str0 # "" THEN
+ find(body, text.str0);
+ text.curPos := text.PosList.first(TPos)
+ END
+END ffirst;
+
+
+PROCEDURE found* (body: XML.TAG): BOOLEAN;
+ RETURN (body # NIL) & (body.text # NIL) & (body.text(Text).found # 0)
+END found;
+
+
+PROCEDURE fnext* (body: XML.TAG; VAR y: INTEGER; d: INTEGER);
+VAR
+ text: Text;
+ rect: TRect;
+ cur: LISTS.ITEM;
+
+BEGIN
+ text := body.text(Text);
+ IF (text # NIL) & (text.found # 0) THEN
+ cur := text.curPos;
+ CASE d OF
+ |1:
+ IF cur.next # NIL THEN
+ cur := cur.next
+ ELSE
+ cur := text.PosList.first
+ END
+
+ |-1:
+ IF cur.prev # NIL THEN
+ cur := cur.prev
+ ELSE
+ cur := text.PosList.last
+ END
+
+ |0:
+ cur := text.PosList.first
+
+ END;
+ text.curPos := cur(TPos);
+ rect := text.curPos.RectList.first(TRect);
+ IF rect # NIL THEN
+ y := rect.y1
+ END
+ ELSE
+ y := -1
+ END
+END fnext;
+
+
+PROCEDURE open* (_find: PFind);
+BEGIN
+ Find := _find;
+ SearchForm.open
+END open;
+
+
+PROCEDURE close*;
+VAR
+ text: Text;
+ body: XML.TAG;
+
+BEGIN
+ body := Body;
+ text := body.text(Text);
+ IF text # NIL THEN
+ LISTS.destroy(text.PosList);
+ text.PosList := LISTS.create(NIL);
+ text.found := 0;
+ text.curPos := NIL
+ END
+END close;
+
+
+PROCEDURE resize*;
+VAR
+ n: INTEGER;
+ text: Text;
+ item: LISTS.ITEM;
+
+BEGIN
+ text := TextList.first(Text);
+ WHILE text # NIL DO
+ IF text.found # 0 THEN
+ n := text.curPos.n;
+ find(text.body, text.str0);
+ item := LISTS.get(text.PosList, n);
+ text.curPos := item(TPos)
+ END;
+ text := text.next(Text)
+ END
+END resize;
+
+
+PROCEDURE callback (case: BOOLEAN; str: STRING): BOOLEAN;
+VAR
+ body: XML.TAG;
+
+BEGIN
+ body := Body;
+ IF body.text = NIL THEN
+ create(body)
+ END;
+ body.text(Text).case := case;
+ body.text(Text).str0 := str;
+ ffirst(body);
+ Find(0)
+
+ RETURN body.text(Text).found # 0
+END callback;
+
+
+BEGIN
+ TextList := LISTS.create(NIL);
+ SearchForm.init(callback)
+END Search.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/SearchForm.ob07 b/programs/other/fb2reader/SRC/SearchForm.ob07
new file mode 100644
index 0000000000..ddafaa2212
--- /dev/null
+++ b/programs/other/fb2reader/SRC/SearchForm.ob07
@@ -0,0 +1,199 @@
+(*
+ Copyright 2020-2021 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE SearchForm;
+
+IMPORT
+
+ SYSTEM, SU := SysUtils, W := Window, box_lib, K := KOSAPI, Encode, S := Strings;
+
+
+CONST
+
+ BTN_CLOSE = 1;
+ BTN_FIND = 19;
+ BTN_CANCEL = 20;
+
+ BtnH = 25;
+ BtnW = 80;
+
+ WINDOW_BEVEL = 4;
+
+ MAXCHARS = 2000;
+
+
+TYPE
+
+ STRING* = ARRAY MAXCHARS OF CHAR;
+
+ PROC = PROCEDURE (case: BOOLEAN; str: STRING): BOOLEAN;
+
+
+VAR
+
+ PID, Slot: INTEGER;
+ Stack: ARRAY 1000000 OF CHAR;
+ Window: W.TWindow;
+ str: STRING;
+
+ callback: PROC;
+ case: box_lib.checkbox;
+ text: box_lib.edit_box;
+
+
+PROCEDURE DrawText (x, y: INTEGER; text: ARRAY OF CHAR);
+VAR
+ L: INTEGER;
+BEGIN
+ L := LENGTH(text);
+ SU.Box(x, y, L*SU.FontW, SU.FontH, SU.winColor, SU.winColor);
+ SU.OutText(x, y, text, L, SU.textColor)
+END DrawText;
+
+
+PROCEDURE buttons;
+BEGIN
+ SU.CreateButton(BTN_FIND, 5, 80, BtnW, BtnH, SU.btnColor, "find");
+ SU.CreateButton(BTN_CANCEL, 5 - BtnW + text.width, 80, BtnW, BtnH, SU.btnColor, "cancel");
+ box_lib.check_box_draw2(case); DrawText(25, 50, "match case");
+ box_lib.edit_box_draw(text)
+END buttons;
+
+
+PROCEDURE DrawWindow;
+BEGIN
+ SU.GetSystemColors;
+ SU.WindowRedrawStatus(1);
+ SU.DefineAndDrawWindow(Window.Left, Window.Top, Window.Width, Window.Height,
+ SU.winColor, LSL(ORD({0, 1}), 4) + 4, Window.Caption);
+ buttons;
+ SU.WindowRedrawStatus(2)
+END DrawWindow;
+
+
+PROCEDURE close* (ok: BOOLEAN);
+VAR
+ pid, i, j, k, n: INTEGER;
+ found: BOOLEAN;
+ str0: STRING;
+ u: S.UTF8;
+
+BEGIN
+ found := TRUE;
+ box_lib.edit_box_get_value(text, str);
+
+ IF ok THEN
+ IF str # "" THEN
+ j := 0;
+ i := 0;
+ WHILE str[i] # 0X DO
+ u := Encode.CP866[ORD(str[i])].utf8;
+ n := Encode.CP866[ORD(str[i])].len;
+ FOR k := 0 TO n - 1 DO
+ str0[j] := u[k];
+ INC(j)
+ END;
+ INC(i)
+ END;
+ found := callback(box_lib.check_box_get_value(case), str0)
+ ELSE
+ found := FALSE
+ END
+ END;
+
+ IF found THEN
+ pid := PID;
+ PID := 0;
+ IF pid # 0 THEN
+ SU.TerminateThreadId(pid)
+ END
+ ELSE
+ IF str # "" THEN
+ DrawText(5 + BtnW + 10, 80 + 4, "not found")
+ END
+ END
+END close;
+
+
+PROCEDURE ButtonClick;
+BEGIN
+ CASE SU.GetButtonCode() OF
+ |0 :
+ |BTN_CLOSE, BTN_CANCEL : close(FALSE)
+ |BTN_FIND : close(TRUE)
+ END;
+ buttons
+END ButtonClick;
+
+
+PROCEDURE show;
+VAR
+ scrWidth, scrHeight, key: INTEGER;
+
+BEGIN
+ SU.SetEventsMask({0, 1, 2, 5, 30, 31});
+ W.InitWindow(Window, 0, 0, 320, 140, "Search");
+ SU.GetScreenSize(scrWidth, scrHeight);
+ Window.Left := (scrWidth - Window.Width) DIV 2;
+ Window.Top := (scrHeight - Window.Height) DIV 2;
+
+ DrawWindow;
+ WHILE TRUE DO
+ CASE SU.WaitForEvent() OF
+ |1: DrawWindow
+ |2: key := K.sysfunc1(2);
+ IF key DIV 65536 = 28 THEN
+ close(TRUE)
+ ELSIF key DIV 65536 = 1 THEN
+ close(FALSE)
+ ELSE
+ box_lib.edit_box_key_safe(text, key)
+ END
+ |3: ButtonClick
+ |6:
+ box_lib.check_box_mouse2(case);
+ box_lib.edit_box_mouse(text)
+ ELSE
+ END
+ END
+END show;
+
+
+PROCEDURE open*;
+BEGIN
+ IF PID = 0 THEN
+ PID := SU.NewThread(show, Stack);
+ Slot := SU.GetThreadSlot(PID)
+ ELSE
+ SU.FocusWindow(Slot)
+ END
+END open;
+
+
+PROCEDURE init* (proc: PROC);
+BEGIN
+ callback := proc;
+ PID := 0;
+ case := box_lib.kolibri_new_check_box(5, 50, 16, 16, SYSTEM.SADR(""), 14 * 8 + 5);
+ text := box_lib.kolibri_new_edit_box(5, 10, 300, MAXCHARS DIV 3);
+ text.flags := 4002H;
+END init;
+
+
+END SearchForm.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/SelEnc.ob07 b/programs/other/fb2reader/SRC/SelEnc.ob07
new file mode 100644
index 0000000000..6f0d8377f6
--- /dev/null
+++ b/programs/other/fb2reader/SRC/SelEnc.ob07
@@ -0,0 +1,163 @@
+(*
+ Copyright 2016, 2018, 2020-2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE SelEnc;
+
+IMPORT
+
+ SU := SysUtils, W := Window, OpenDlg, S := Strings, TXT := Txt2FB2, SYSTEM, K := KOSAPI, Settings, File;
+
+
+CONST
+
+ BtnH = 30;
+ BtnW = 150;
+ BtnX = 5;
+ BtnY = 10;
+ BtnInter = 10;
+
+ tempfile* = "/tmp0/1/~temp.fb2";
+
+
+VAR
+
+ Window : W.TWindow;
+ ENCODING* : INTEGER;
+ FileName : S.STRING;
+
+
+PROCEDURE Buttons;
+VAR
+ Y : INTEGER;
+
+BEGIN
+ Y := BtnY;
+ SU.CreateButton(TXT.AUTO, BtnX, Y, BtnW, BtnH, SU.btnColor, "AUTO" ); INC(Y, BtnH + BtnInter);
+ SU.CreateButton(TXT.CP866, BtnX, Y, BtnW, BtnH, SU.btnColor, "CP-866" ); INC(Y, BtnH + BtnInter);
+ SU.CreateButton(TXT.CP1251, BtnX, Y, BtnW, BtnH, SU.btnColor, "CP-1251"); INC(Y, BtnH + BtnInter);
+ SU.CreateButton(TXT.CP1252, BtnX, Y, BtnW, BtnH, SU.btnColor, "CP-1252"); INC(Y, BtnH + BtnInter);
+ SU.CreateButton(TXT.CP1250, BtnX, Y, BtnW, BtnH, SU.btnColor, "CP-1250"); INC(Y, BtnH + BtnInter);
+ SU.CreateButton(TXT.UTF8, BtnX, Y, BtnW, BtnH, SU.btnColor, "UTF-8" )
+END Buttons;
+
+
+PROCEDURE DrawWindow;
+BEGIN
+ SU.GetSystemColors;
+ SU.WindowRedrawStatus(1);
+ SU.DefineAndDrawWindow(Window.Left, Window.Top, Window.Width, Window.Height,
+ SU.winColor, LSL(ORD({0, 1}), 4) + 4, Window.Caption);
+ Buttons;
+ SU.WindowRedrawStatus(2)
+END DrawWindow;
+
+
+PROCEDURE auto (fname: S.STRING): INTEGER;
+VAR
+ enc, data, size, ptr: INTEGER;
+
+
+ PROCEDURE SearchPair (ptr, size: INTEGER; chr1, chr2: BYTE): BOOLEAN;
+ VAR
+ c, c0: BYTE;
+ res: BOOLEAN;
+
+ BEGIN
+ c := 0;
+ res := FALSE;
+ WHILE (size > 0) & ~res DO
+ c0 := c;
+ SYSTEM.GET(ptr, c);
+ IF (c = chr2) & (c0 = chr1) THEN
+ res := TRUE
+ END;
+ INC(ptr);
+ DEC(size)
+ END
+
+ RETURN res
+ END SearchPair;
+
+
+BEGIN
+ data := File.Load(fname, size);
+ SU.ErrorIf(data = 0, 1);
+ ptr := data;
+
+ IF SearchPair(ptr, size, 208, 190) THEN
+ enc := TXT.UTF8
+ ELSE
+ IF SearchPair(ptr, size, 239, 240) OR SearchPair(ptr, size, 241, 242) THEN
+ enc := TXT.CP1251
+ ELSE
+ enc := TXT.CP866
+ END
+ END;
+
+ data := K.free(data)
+
+ RETURN enc
+END auto;
+
+
+PROCEDURE ButtonClick;
+VAR
+ btn_code: INTEGER;
+ program, file: S.STRING;
+
+BEGIN
+ btn_code := SU.GetButtonCode();
+ IF btn_code = TXT.AUTO THEN
+ ENCODING := auto(FileName)
+ ELSE
+ ENCODING := btn_code
+ END;
+ TXT.convert(FileName, tempfile, ENCODING);
+ S.PtrToString(K.GetName(), program);
+ file := tempfile;
+ file[0] := "!";
+ SU.Run(program, SYSTEM.ADR(file));
+ SU.Halt
+END ButtonClick;
+
+
+PROCEDURE Show*(FName: S.STRING);
+VAR
+ X1, Y1, X2, Y2: INTEGER;
+
+BEGIN
+ FileName := FName;
+ SU.SetEventsMask({0, 2, 31});
+ SU.GetScreenArea(X1, Y1, X2, Y2);
+ W.InitWindow(Window, 0, 0, BtnX * 2 + BtnW + 10, (BtnH + BtnInter) * 6 + BtnY * 2 + SU.SkinHeight() - 5, "Encoding");
+ Window.Left := (X2 - X1 - Window.Width) DIV 2;
+ Window.Top := (Y2 - Y1 - Window.Height) DIV 2;
+ DrawWindow;
+ WHILE TRUE DO
+ CASE SU.WaitForEvent() OF
+ |1 : DrawWindow
+ |3 : ButtonClick
+ END
+ END
+END Show;
+
+
+BEGIN
+ ENCODING := 0
+END SelEnc.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/Settings.ob07 b/programs/other/fb2reader/SRC/Settings.ob07
new file mode 100644
index 0000000000..57a0c567e2
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Settings.ob07
@@ -0,0 +1,420 @@
+(*
+ Copyright 2016, 2018, 2020-2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Settings;
+
+IMPORT SU := SysUtils, W := Window, C := ColorDlg, DOM, S := Strings,
+ Font, KF := kfonts, OD := OpenDlg, LibImg, G := Graph, Ini, box_lib, sys := SYSTEM;
+
+
+CONST
+
+ DAY = 19;
+ NIGHT = 20;
+ APPLY = 21;
+ CANCEL = 22;
+ BACK_PICTURE = 23;
+
+ C100 = 100;
+
+ CHANGE_BACK_COLOR = DOM.BACK_COLOR + C100;
+ CHANGE_TEXT_COLOR = DOM.TEXT_COLOR + C100;
+ CHANGE_ITALIC_COLOR = DOM.ITALIC_COLOR + C100;
+ CHANGE_LINK_COLOR = DOM.LINK_COLOR + C100;
+ CHANGE_VISITED_COLOR = DOM.VISITED_COLOR + C100;
+
+ MAX_LRpc = 25;
+ MAX_Top = 120;
+ MAX_PARAGRAPH = 120;
+ MAX_EPIGRAPH = 120;
+ MAX_CInt = 25;
+ MAX_InterLin = 50;
+ MAX_FONT_SIZE = 40;
+ MIN_FONT_SIZE = 10;
+
+ BtnH* = 25;
+ BoxW = 50;
+ TextLeft = 20;
+ BtnW* = 80;
+
+
+VAR
+
+ Window : W.TWindow;
+ PID : INTEGER;
+ Slot : INTEGER;
+ Color : C.Dialog;
+ Data : DOM.TSettings;
+
+ String : S.STRING;
+
+ sb : ARRAY 7 OF box_lib.scrollbar;
+ check1 : box_lib.checkbox;
+ check2 : box_lib.checkbox;
+ OpenPict : OD.Dialog;
+ picture : INTEGER;
+
+ picture_path : S.STRING;
+
+
+PROCEDURE Close*;
+VAR pid: INTEGER;
+BEGIN
+ IF PID # 0 THEN
+ pid := PID;
+ PID := 0;
+ IF (picture # 0) & (picture # Data.Picture) THEN
+ LibImg.img_destroy(picture)
+ END;
+ C.Destroy(Color);
+ OD.Destroy(OpenPict);
+ SU.TerminateThreadId(pid)
+ END
+END Close;
+
+
+PROCEDURE ClearWindow;
+BEGIN
+ SU.Box(0, 0, Window.Width - 10, Window.Height - SU.SkinHeight() - 5, SU.winColor, SU.winColor)
+END ClearWindow;
+
+
+PROCEDURE OutText (x, y: INTEGER; text: ARRAY OF CHAR);
+BEGIN
+ SU.OutText(x, y, text, LENGTH(text), SU.textColor)
+END OutText;
+
+
+PROCEDURE PlusMinus(x, y, max, min: INTEGER; _sb: box_lib.scrollbar);
+VAR range, Y: INTEGER;
+ sysfont: ARRAY 20 OF CHAR;
+BEGIN
+ sysfont := "System font only";
+ S.IntToString(_sb.position + min, String);
+ IF _sb = sb[0] THEN
+ Y := y - (BtnH + 10) * 2 + 26;
+ SU.Box(TextLeft + 230, Y, SU.FontW * LENGTH(sysfont), SU.FontH, SU.winColor, SU.winColor);
+ IF ~KF.Enabled(Font.KFont, _sb.position + min) THEN
+ OutText(TextLeft + 230, Y, sysfont)
+ END
+ END;
+ SU.Box(x + 25, y + 6, SU.FontW * 4, SU.FontH, SU.winColor, SU.winColor);
+ OutText(x + 25 + (35 - SU.FontW * LENGTH(String)) DIV 2, y + 6, String);
+ x := x + 60 - 25;
+ range := max - min;
+ _sb := box_lib.kolibri_scrollbar(_sb, (x + 30) * 65536 + 196, y * 65536 + 22 + 2, 22, range + range DIV 10, range DIV 10,
+ _sb.position, SU.lightColor, SU.btnColor, 0, 2)
+END PlusMinus;
+
+
+PROCEDURE Buttons;
+
+VAR
+
+ X, Y, TextY : INTEGER;
+ WinW, WinH, SkinH : INTEGER;
+ i : INTEGER;
+ Rect : W.TRect;
+
+BEGIN
+ Rect.Left := 10;
+ Rect.Top := 85;
+ Rect.Width := 210;
+ Rect.Height := 255;
+ SU.Box(Rect.Left, Rect.Top, Rect.Width, Rect.Height, SU.winColor, SU.borderColor);
+ SU.Box(Rect.Left + 230, Rect.Top, Rect.Width + 170, Rect.Height, SU.winColor, SU.borderColor);
+
+ WinW := Window.Width;
+ WinH := Window.Height;
+ SkinH := SU.SkinHeight();
+ X := 125;
+ Y := 10;
+ IF picture = 0 THEN
+ OutText(TextLeft + 20, Y + 6, "back picture (none)")
+ ELSE
+ OutText(TextLeft + 20, Y + 6, "back picture")
+ END;
+ SU.CreateButton(BACK_PICTURE, X + 75, Y, 30, BtnH, SU.btnColor, "...");
+
+ Y := 10 + (BtnH + 10);
+
+ OutText(TextLeft + 20, Y + 6, "two columns");
+
+ Y := Y + (BtnH + 10) * 2 - 20;
+
+ TextY := Y;
+ FOR i := 0 TO 4 DO
+ SU.Box(X, Y, BoxW, BtnH, Data.Colors[i], 0);
+ SU.CreateButton(i + C100, X + BoxW + 5, Y, 30, BtnH, SU.btnColor, "...");
+ Y := Y + BtnH + 10;
+ END;
+
+ X := 20; Y := TextY + 6;
+
+ OutText(TextLeft, Y, "back"); OutText(TextLeft + 230, Y, "font size"); PlusMinus(TextLeft + 330, Y - 6, MAX_FONT_SIZE, MIN_FONT_SIZE, sb[0]); Y := Y + BtnH + 10;
+ OutText(TextLeft, Y, "text"); OutText(TextLeft + 230, Y, "left & right %"); PlusMinus(TextLeft + 330, Y - 6, MAX_LRpc, 0, sb[1]); Y := Y + BtnH + 10;
+ OutText(TextLeft, Y, "italic"); OutText(TextLeft + 230, Y, "col. spacing %"); PlusMinus(TextLeft + 330, Y - 6, MAX_CInt, 0, sb[2]); Y := Y + BtnH + 10;
+ OutText(TextLeft, Y, "link"); OutText(TextLeft + 230, Y, "top & bottom"); PlusMinus(TextLeft + 330, Y - 6, MAX_Top, 0, sb[3]); Y := Y + BtnH + 10;
+ OutText(TextLeft, Y, "visited"); OutText(TextLeft + 230, Y, "paragraph"); PlusMinus(TextLeft + 330, Y - 6, MAX_PARAGRAPH, 0, sb[4]); Y := Y + BtnH + 10;
+
+ OutText(TextLeft + 230, Y, "epigraph"); PlusMinus(TextLeft + 330, Y - 6, MAX_EPIGRAPH, 0, sb[5]); Y := Y + BtnH + 10;
+ OutText(TextLeft + 230, Y, "line spacing"); PlusMinus(TextLeft + 330, Y - 6, MAX_InterLin, 0, sb[6]);
+
+ Y := Y - 6;
+
+ SU.CreateButton(DAY, (Rect.Width - (BtnW + 5 + BtnW)) DIV 2 + Rect.Left, Y, 80, BtnH, SU.btnColor, "Day" );
+ SU.CreateButton(NIGHT, (Rect.Width - (BtnW + 5 + BtnW)) DIV 2 + Rect.Left + 5 + BtnW, Y, 80, BtnH, SU.btnColor, "Night" );
+
+ SU.CreateButton(APPLY, (WinW - (BtnW + 5 + BtnW) - 10) DIV 2, WinH - BtnH - SkinH - 10, 80, BtnH, SU.btnColor, "Apply" );
+ SU.CreateButton(CANCEL, (WinW - (BtnW + 5 + BtnW) - 10) DIV 2 + 5 + BtnW, WinH - BtnH - SkinH - 10, 80, BtnH, SU.btnColor, "Cancel");
+
+ FOR i := 0 TO LEN(sb) - 1 DO
+ box_lib.scrollbar_h_draw(sb[i])
+ END;
+ box_lib.check_box_draw2(check1);
+ box_lib.check_box_draw2(check2);
+
+END Buttons;
+
+
+PROCEDURE DrawWindow;
+BEGIN
+ SU.GetSystemColors;
+ SU.WindowRedrawStatus(1);
+ SU.DefineAndDrawWindow(Window.Left, Window.Top, Window.Width, Window.Height,
+ SU.winColor, LSL(ORD({0, 1}), 4) + 4, Window.Caption);
+ Buttons;
+ SU.WindowRedrawStatus(2)
+END DrawWindow;
+
+
+PROCEDURE SelColor(Color: C.Dialog; Default: INTEGER): INTEGER;
+VAR Result: INTEGER;
+BEGIN
+ Result := Default;
+ IF Color # NIL THEN
+ C.Show(Color);
+ WHILE Color.status = 2 DO
+ SU.Pause(20)
+ END;
+ IF Color.status = 1 THEN
+ Result := Color.color
+ END
+ END
+ RETURN Result
+END SelColor;
+
+
+PROCEDURE ChangeColor(idx: INTEGER);
+BEGIN
+ Data.Colors[idx] := SelColor(Color, Data.Colors[idx])
+END ChangeColor;
+
+
+PROCEDURE Day;
+BEGIN
+ Data.Colors[DOM.BACK_COLOR] := 0F0F0C7H;
+ Data.Colors[DOM.TEXT_COLOR] := 0000000H;
+ Data.Colors[DOM.ITALIC_COLOR] := 0505050H;
+ Data.Colors[DOM.LINK_COLOR] := 00000FFH;
+ Data.Colors[DOM.VISITED_COLOR] := 0800080H;
+ Data.Colors[DOM.CLICKED_COLOR] := 0FF0000H;
+END Day;
+
+
+PROCEDURE Night;
+BEGIN
+ Data.Colors[DOM.BACK_COLOR] := 0000000H;
+ Data.Colors[DOM.TEXT_COLOR] := 0AFAFAFH;
+ Data.Colors[DOM.ITALIC_COLOR] := 07F7F7FH;
+ Data.Colors[DOM.LINK_COLOR] := 000A0D0H;
+ Data.Colors[DOM.VISITED_COLOR] := 0C000C0H;
+ Data.Colors[DOM.CLICKED_COLOR] := 0FF0000H;
+END Night;
+
+
+PROCEDURE Apply;
+BEGIN
+ Data.FontSize := sb[0].position + MIN_FONT_SIZE;
+ Data.PADDING.LRpc := sb[1].position;
+ Data.PADDING.CInt := sb[2].position;
+ Data.PADDING.Top := sb[3].position;
+ Data.PARAGRAPH := sb[4].position;
+ Data.EPIGRAPH := sb[5].position;
+ Data.InterLin := sb[6].position;
+ IF Data.Picture # picture THEN
+ IF Data.Picture # 0 THEN
+ LibImg.img_destroy(Data.Picture)
+ END;
+ Data.Picture := picture;
+ Ini.SetPicturePath(picture_path)
+ END;
+ picture := 0;
+ DOM.SetSettings(Data);
+ Close
+END Apply;
+
+
+PROCEDURE LoadPicture(file_path: S.STRING);
+VAR ysize, img: INTEGER;
+BEGIN
+ img := LibImg.LoadFromFile(file_path, 10240000, ysize);
+ IF img # 0 THEN
+ IF (picture # 0) & (picture # Data.Picture) THEN
+ LibImg.img_destroy(picture)
+ END;
+ picture := img;
+ picture_path := file_path
+ END
+END LoadPicture;
+
+
+PROCEDURE OpenPicture;
+BEGIN
+ IF OpenPict # NIL THEN
+ OD.Show(OpenPict, 500, 400);
+ WHILE OpenPict.status = 2 DO
+ SU.Pause(30)
+ END;
+ IF OpenPict.status = 1 THEN
+ COPY(OpenPict.FilePath, picture_path);
+ LoadPicture(picture_path)
+ END
+ END
+END OpenPicture;
+
+
+PROCEDURE ButtonClick;
+BEGIN
+ CASE SU.GetButtonCode() OF
+ |0 :
+ |1 : Close
+ |BACK_PICTURE : OpenPicture
+ |DAY : Day
+ |NIGHT : Night
+ |APPLY : Apply
+ |CANCEL : Close
+
+ |CHANGE_BACK_COLOR : ChangeColor(DOM.BACK_COLOR)
+ |CHANGE_TEXT_COLOR : ChangeColor(DOM.TEXT_COLOR)
+ |CHANGE_ITALIC_COLOR : ChangeColor(DOM.ITALIC_COLOR)
+ |CHANGE_LINK_COLOR : ChangeColor(DOM.LINK_COLOR)
+ |CHANGE_VISITED_COLOR : ChangeColor(DOM.VISITED_COLOR)
+
+ END;
+ ClearWindow;
+ Buttons
+END ButtonClick;
+
+
+PROCEDURE Default*;
+BEGIN
+ Day;
+ Data.FontSize := 16;
+ Data.TwoCol := FALSE;
+ Data.PADDING.Top := 15;
+ Data.PADDING.LRpc := 3;
+ Data.PADDING.CInt := 6;
+ Data.PARAGRAPH := 30;
+ Data.EPIGRAPH := 100;
+ Data.InterLin := 0;
+ Data.Picture := picture;
+ DOM.SetSettings(Data)
+END Default;
+
+
+PROCEDURE Show;
+VAR i, scrWidth, scrHeight: INTEGER;
+BEGIN
+ SU.SetEventsMask({0, 2, 5, 30, 31});
+ W.InitWindow(Window, 0, 0, 640, 420, "Settings");
+ SU.GetScreenSize(scrWidth, scrHeight);
+ Window.Left := (scrWidth - Window.Width) DIV 2;
+ Window.Top := (scrHeight - Window.Height) DIV 2;
+ Color := C.Create(DrawWindow);
+ OpenPict := OD.Create(DrawWindow, 0, "/sys", "JPG|PNG|BMP|GIF");
+ Data := DOM.Settings;
+ picture := Data.Picture;
+ DrawWindow;
+ WHILE TRUE DO
+ CASE SU.WaitForEvent() OF
+ |1 : DrawWindow
+ |3 : ButtonClick
+ |6 : FOR i := 0 TO LEN(sb) - 1 DO
+ box_lib.scrollbar_h_mouse(sb[i])
+ END;
+ box_lib.check_box_mouse2(check1);
+ box_lib.check_box_mouse2(check2);
+ PlusMinus(TextLeft + 330, sb[0].y_h DIV 65536, MAX_FONT_SIZE, MIN_FONT_SIZE, sb[0]);
+ PlusMinus(TextLeft + 330, sb[1].y_h DIV 65536, MAX_LRpc, 0, sb[1]);
+ PlusMinus(TextLeft + 330, sb[2].y_h DIV 65536, MAX_CInt, 0, sb[2]);
+ PlusMinus(TextLeft + 330, sb[3].y_h DIV 65536, MAX_Top, 0, sb[3]);
+ PlusMinus(TextLeft + 330, sb[4].y_h DIV 65536, MAX_PARAGRAPH, 0, sb[4]);
+ PlusMinus(TextLeft + 330, sb[5].y_h DIV 65536, MAX_EPIGRAPH, 0, sb[5]);
+ PlusMinus(TextLeft + 330, sb[6].y_h DIV 65536, MAX_InterLin, 0, sb[6]);
+ Data.TwoCol := box_lib.check_box_get_value(check1);
+ Data.b_pict := box_lib.check_box_get_value(check2);
+ END
+ END
+END Show;
+
+
+PROCEDURE Open*;
+BEGIN
+ IF PID = 0 THEN
+ Data := DOM.Settings;
+ box_lib.check_box_set_value(check1, Data.TwoCol);
+ box_lib.check_box_set_value(check2, Data.b_pict);
+ PID := SU.NewThread(Show, DOM.Stack);
+ Slot := SU.GetThreadSlot(PID);
+ sb[0].position := Data.FontSize - MIN_FONT_SIZE;
+ sb[1].position := Data.PADDING.LRpc;
+ sb[2].position := Data.PADDING.CInt;
+ sb[3].position := Data.PADDING.Top;
+ sb[4].position := Data.PARAGRAPH;
+ sb[5].position := Data.EPIGRAPH;
+ sb[6].position := Data.InterLin;
+ ELSE
+ SU.FocusWindow(Slot)
+ END
+END Open;
+
+
+PROCEDURE main;
+VAR i: INTEGER;
+ bpicture, twocol: ARRAY 20 OF CHAR;
+BEGIN
+ PID := 0;
+ FOR i := 0 TO LEN(sb) - 1 DO
+ sb[i] := box_lib.kolibri_new_scrollbar(10 * 65536 + 200, 10 * 65536 + 22 + 2, 22, 15, 10, 0, 0, 0, 0, 2)
+ END;
+ bpicture := "back picture";
+ twocol := "two columns";
+ check2 := box_lib.kolibri_new_check_box(TextLeft, 10 + 5, 16, 16, sys.SADR(""), LENGTH(bpicture) * 8 + 5);
+ check1 := box_lib.kolibri_new_check_box(TextLeft, 10 + (BtnH + 10) + 5, 16, 16, sys.SADR(""), LENGTH(twocol) * 8 + 5);
+ picture := 0;
+ IF Ini.Picture # "" THEN
+ LoadPicture(Ini.Picture)
+ END
+END main;
+
+
+BEGIN
+ main
+END Settings.
diff --git a/programs/other/fb2reader/SRC/Strings.ob07 b/programs/other/fb2reader/SRC/Strings.ob07
new file mode 100644
index 0000000000..e42437a7f6
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Strings.ob07
@@ -0,0 +1,414 @@
+(*
+ Copyright 2016, 2019, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Strings;
+
+IMPORT sys := SYSTEM, KOSAPI;
+
+
+TYPE
+
+ STRING* = ARRAY 1024 OF CHAR;
+
+ UTF8* = ARRAY 8 OF CHAR;
+
+ CHARS* = RECORD first*, last* : INTEGER END;
+
+
+VAR
+
+ CS: BOOLEAN;
+
+
+PROCEDURE [ccall, "base64.obj", ""] base64_decode (inp, outp: INTEGER; Len: INTEGER): INTEGER; END;
+PROCEDURE [stdcall, "rasterworks.obj", ""] countUTF8Z (string, byteQuantity: INTEGER): INTEGER; END;
+
+PROCEDURE DelLeft(VAR s: STRING; count: INTEGER);
+VAR i, max: INTEGER;
+BEGIN
+ max := LENGTH(s) - count - 1;
+ IF max >= 0 THEN
+ FOR i := 0 TO max DO
+ s[i] := s[i + count]
+ END
+ END
+END DelLeft;
+
+
+PROCEDURE Trim*(VAR s: STRING; ch: CHAR);
+VAR i, n: INTEGER;
+BEGIN
+ i := 0;
+ WHILE s[i] = ch DO
+ INC(i)
+ END;
+ DelLeft(s, i);
+ n := LENGTH(s) - 1;
+ IF n >= 0 THEN
+ i := n;
+ WHILE s[i] = ch DO
+ DEC(i)
+ END;
+ IF n # i THEN
+ s[i + 1] := 0X
+ END
+ END
+END Trim;
+
+
+PROCEDURE GetChar*(chars: CHARS; i: INTEGER): CHAR;
+VAR c: CHAR;
+BEGIN
+ ASSERT(chars.first + i <= chars.last);
+ sys.GET(chars.first + i, c)
+ RETURN c
+END GetChar;
+
+
+PROCEDURE Reverse*(VAR s: ARRAY OF CHAR);
+VAR i, j: INTEGER; c: CHAR;
+BEGIN
+ i := 0;
+ j := LENGTH(s) - 1;
+ WHILE i < j DO
+ c := s[i];
+ s[i] := s[j];
+ s[j] := c;
+ INC(i);
+ DEC(j)
+ END
+END Reverse;
+
+
+PROCEDURE IntToString*(x: INTEGER; VAR s: STRING);
+VAR n, i: INTEGER;
+BEGIN
+ i := 0;
+ REPEAT
+ n := x MOD 10;
+ x := x DIV 10;
+ s[i] := CHR(ORD("0") + n);
+ INC(i)
+ UNTIL x = 0;
+ s[i] := 0X;
+ Reverse(s)
+END IntToString;
+
+
+PROCEDURE isdigit(c: CHAR): BOOLEAN;
+ RETURN ("0" <= c) & (c <= "9")
+END isdigit;
+
+
+PROCEDURE CharsToInt*(s: CHARS; VAR err: BOOLEAN): INTEGER;
+VAR n, i, res, len: INTEGER; c: CHAR;
+BEGIN
+ res := 0;
+ len := s.last - s.first + 1;
+ err := len <= 0;
+ FOR i := 0 TO s.last - s.first DO
+ c := GetChar(s, i);
+ IF isdigit(c) THEN
+ n := ORD(c) - ORD("0");
+ res := res * 10 + n
+ ELSE
+ err := TRUE
+ END
+ END
+ RETURN res
+END CharsToInt;
+
+
+PROCEDURE Append*(VAR str1: STRING; str2: STRING);
+VAR
+ len1, len2 : INTEGER;
+ i, j : INTEGER;
+BEGIN
+ len1 := LENGTH(str1);
+ len2 := LENGTH(str2);
+ ASSERT(len1 + len2 < LEN(str1));
+ j := len1;
+ FOR i := 0 TO len2 - 1 DO
+ str1[j] := str2[i];
+ INC(j)
+ END;
+ str1[j] := 0X
+END Append;
+
+
+PROCEDURE GetPath*(VAR S: STRING);
+VAR i, j: INTEGER;
+BEGIN
+ j := 0;
+ i := LENGTH(S) - 1;
+ WHILE i >= 0 DO
+ IF S[i] = "/" THEN
+ j := i;
+ i := 0
+ END;
+ DEC(i)
+ END;
+ S[j] := 0X
+END GetPath;
+
+
+PROCEDURE PutChar*(chars: CHARS; i: INTEGER; c: CHAR);
+BEGIN
+ ASSERT(chars.first + i <= chars.last);
+ sys.PUT(chars.first + i, c)
+END PutChar;
+
+
+PROCEDURE StrToChars*(str: ARRAY OF CHAR; VAR chars: CHARS);
+BEGIN
+ ASSERT(str # "");
+ chars.first := sys.ADR(str[0]);
+ chars.last := sys.ADR(str[LENGTH(str) - 1])
+END StrToChars;
+
+
+PROCEDURE PtrToString*(ptr: INTEGER; VAR S: STRING);
+VAR i: INTEGER; c: CHAR;
+BEGIN
+ i := 0;
+ REPEAT
+ sys.GET(ptr, c);
+ S[i] := c;
+ INC(i);
+ INC(ptr)
+ UNTIL (c = 0X) OR (i = LEN(S));
+ S[i - 1] := 0X
+END PtrToString;
+
+
+PROCEDURE CharsEq*(chars1, chars2: CHARS): BOOLEAN;
+VAR
+ pos, len2 : INTEGER;
+ c1, c2 : CHAR;
+ Result : BOOLEAN;
+
+ PROCEDURE CAP(VAR c: CHAR);
+ BEGIN
+ IF ~CS & ("a" <= c) & (c <= "z") THEN
+ c := CHR(ORD(c) - 32)
+ END
+ END CAP;
+
+BEGIN
+ pos := chars1.last - chars1.first;
+ len2 := chars2.last - chars2.first;
+ IF pos = len2 THEN
+ REPEAT
+ c1 := GetChar(chars1, pos);
+ c2 := GetChar(chars2, pos);
+ CAP(c1);
+ CAP(c2);
+ DEC(pos)
+ UNTIL (c1 # c2) OR (pos = -1);
+ Result := c1 = c2
+ ELSE
+ Result := FALSE
+ END
+ RETURN Result
+END CharsEq;
+
+
+PROCEDURE CharsEqStr*(chars: CHARS; str: STRING): BOOLEAN;
+VAR
+ chars2: CHARS;
+BEGIN
+ StrToChars(str, chars2)
+ RETURN CharsEq(chars, chars2)
+END CharsEqStr;
+
+
+PROCEDURE SetCS*(value: BOOLEAN);
+BEGIN
+ CS := value
+END SetCS;
+
+
+PROCEDURE Utf8Length*(chars: CHARS): INTEGER;
+ RETURN countUTF8Z(chars.first, chars.last - chars.first + 1)
+END Utf8Length;
+
+
+PROCEDURE Replace*(VAR chars: CHARS; str1, str2: ARRAY OF CHAR);
+VAR
+ temp: CHARS;
+ s : CHARS;
+ len1: INTEGER;
+ len2: INTEGER;
+ diff: INTEGER;
+
+ PROCEDURE Put(first, last, len1, len2, diff: INTEGER; str2: ARRAY OF CHAR);
+ VAR i: INTEGER; c: CHAR;
+ BEGIN
+ sys.MOVE(sys.ADR(str2[0]), first, len2);
+ FOR i := first + len1 TO last DO
+ sys.GET(i, c);
+ sys.PUT(i - diff, c);
+ END
+ END Put;
+
+BEGIN
+ len1 := LENGTH(str1);
+ len2 := LENGTH(str2);
+ diff := len1 - len2;
+ ASSERT(diff >= 0);
+ ASSERT(len1 > 0);
+ StrToChars(str1, s);
+ temp := chars;
+ temp.last := temp.first + len1 - 1;
+ WHILE temp.last <= chars.last DO
+ IF CharsEq(temp, s) THEN
+ Put(temp.first, chars.last, len1, len2, diff, str2);
+ chars.last := chars.last - diff;
+ temp.first := temp.first + len2;
+ temp.last := temp.first + len1 - 1
+ ELSE
+ INC(temp.first);
+ INC(temp.last)
+ END
+ END
+END Replace;
+
+
+PROCEDURE utf8*(code: INTEGER; VAR uchar: UTF8);
+BEGIN
+ uchar[0] := 0X;
+ IF code < 80H THEN
+ uchar[0] := CHR(code);
+ uchar[1] := 0X
+ ELSIF code < 800H THEN
+ uchar[1] := CHR(ORD(BITS(code) * {0..5}) + 80H);
+ uchar[0] := CHR(ASR(code, 6) + 0C0H);
+ uchar[2] := 0X
+ ELSIF code < 10000H THEN
+ uchar[2] := CHR(ORD(BITS(code) * {0..5}) + 80H);
+ code := ASR(code, 6);
+ uchar[1] := CHR(ORD(BITS(code) * {0..5}) + 80H);
+ uchar[0] := CHR(ASR(code, 6) + 0E0H);
+ uchar[3] := 0X
+(*
+ ELSIF code < 200000H THEN
+ ELSIF code < 4000000H THEN
+ ELSE *)
+ END
+END utf8;
+
+
+PROCEDURE EntOct*(VAR chars: CHARS): BOOLEAN;
+VAR
+ i : INTEGER;
+ c : CHAR;
+ amp : BOOLEAN;
+ oct : BOOLEAN;
+ val : INTEGER;
+ exit : BOOLEAN;
+ str : STRING;
+ str2 : STRING;
+ uchar : UTF8;
+ res : BOOLEAN;
+
+BEGIN
+ i := 0;
+ amp := FALSE;
+ oct := FALSE;
+ res := FALSE;
+ WHILE i <= chars.last - chars.first DO
+ c := GetChar(chars, i);
+ CASE c OF
+ |"&":
+ amp := TRUE;
+ oct := FALSE
+ |"#":
+ oct := amp;
+ amp := FALSE
+ |"0".."9":
+ IF oct THEN
+ val := 0;
+ str := "";
+ str2[1] := 0X;
+ exit := FALSE;
+ REPEAT
+ val := val * 10 + ORD(c) - ORD("0");
+ str2[0] := c;
+ Append(str, str2);
+ INC(i);
+ IF i <= chars.last - chars.first THEN
+ c := GetChar(chars, i)
+ ELSE
+ exit := TRUE
+ END
+ UNTIL ~isdigit(c) OR exit;
+ IF c = ";" THEN
+ str2[0] := c;
+ Append(str, str2);
+ utf8(val, uchar);
+ Replace(chars, str, uchar);
+ res := TRUE;
+ i := chars.last - chars.first
+ ELSE
+ IF ~exit THEN
+ DEC(i);
+ amp := FALSE;
+ oct := FALSE
+ END
+ END
+ ELSE
+ amp := FALSE
+ END
+ ELSE
+ amp := FALSE;
+ oct := FALSE
+ END;
+ INC(i)
+ END
+ RETURN res
+END EntOct;
+
+
+PROCEDURE UCase*(VAR s: STRING);
+VAR i, n: INTEGER; c: CHAR;
+BEGIN
+ n := LENGTH(s) - 1;
+ FOR i := 0 TO n DO
+ c := s[i];
+ IF ("a" <= c) & (c <= "z") OR (0A0X <= c) & (c <= 0AFX) THEN
+ c := CHR(ORD(c) - 32)
+ ELSIF (0E0X <= c) & (c <= 0EFX) THEN
+ c := CHR(ORD(c) - 50H)
+ ELSIF (c = 0F1X) OR (c = 0F3X) OR (c = 0F5X) OR (c = 0F7X) THEN
+ c := CHR(ORD(c) - 1)
+ END;
+ s[i] := c
+ END
+END UCase;
+
+
+PROCEDURE Base64* (VAR chars: CHARS);
+BEGIN
+ chars.last := chars.first + base64_decode(chars.first, chars.first, chars.last - chars.first + 1) - 1
+END Base64;
+
+
+BEGIN
+ CS := TRUE
+END Strings.
diff --git a/programs/other/fb2reader/SRC/SysUtils.ob07 b/programs/other/fb2reader/SRC/SysUtils.ob07
new file mode 100644
index 0000000000..ba4ed391e0
--- /dev/null
+++ b/programs/other/fb2reader/SRC/SysUtils.ob07
@@ -0,0 +1,365 @@
+(*
+ Copyright 2016, 2019, 2021, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE SysUtils;
+
+IMPORT K := KOSAPI, sys := SYSTEM, S := Strings;
+
+
+CONST
+
+ L_BUTTON* = 0;
+
+ FontH* = 16;
+ FontW* = 8;
+
+
+TYPE
+
+ ENTRY* = PROCEDURE;
+
+
+VAR
+ (*darkColor*,*) lightColor*,
+ winColor*, textColor*, btnColor*, btnTextColor*,
+ borderColor*: INTEGER;
+
+
+PROCEDURE GetParam*(VAR Param: S.STRING);
+VAR
+ adr : INTEGER;
+ c : CHAR;
+ i, max : INTEGER;
+BEGIN
+ adr := K.GetCommandLine();
+ i := 0;
+ max := LEN(Param) - 1;
+ REPEAT
+ sys.GET(adr, c);
+ INC(adr);
+ Param[i] := c;
+ INC(i)
+ UNTIL (c = 0X) OR (i = max);
+ Param[i] := 0X;
+ S.Trim(Param, 20X);
+ S.Trim(Param, 22X)
+END GetParam;
+
+
+PROCEDURE Halt*;
+BEGIN
+ K.sysfunc1(-1)
+END Halt;
+
+
+PROCEDURE Run*(program: S.STRING; param: INTEGER);
+TYPE
+
+ info_struct = RECORD
+ subfunc : INTEGER;
+ flags : INTEGER;
+ param : INTEGER;
+ rsrvd1 : INTEGER;
+ rsrvd2 : INTEGER;
+ fname : ARRAY 1024 OF CHAR
+ END;
+
+VAR
+ info: info_struct;
+
+BEGIN
+ info.subfunc := 7;
+ info.flags := 0;
+ info.param := param;
+ info.rsrvd1 := 0;
+ info.rsrvd2 := 0;
+ COPY(program, info.fname);
+ K.sysfunc2(70, sys.ADR(info))
+END Run;
+
+
+PROCEDURE ErrorIf*(condition: BOOLEAN; code: INTEGER);
+VAR str, str2: S.STRING;
+BEGIN
+ IF condition THEN
+ str := "'FB2 ERROR: ";
+ S.IntToString(code, str2);
+ S.Append(str, str2);
+ S.Append(str, "' -E");
+ Run("/sys/@notify", sys.ADR(str[0]));
+ Halt
+ END
+END ErrorIf;
+
+
+PROCEDURE MemError*(err: BOOLEAN);
+BEGIN
+ ErrorIf(err, 13)
+END MemError;
+
+
+PROCEDURE MinMax*(VAR value: INTEGER; min, max: INTEGER);
+BEGIN
+ value := MIN(MAX(value, min), max)
+END MinMax;
+
+
+PROCEDURE MousePos*(VAR X, Y: INTEGER);
+VAR res: INTEGER;
+BEGIN
+ res := K.sysfunc2(37, 0);
+ X := LSR(res, 16);
+ Y := ORD(BITS(res) * {0..15});
+END MousePos;
+
+
+PROCEDURE MouseVScroll*(): INTEGER;
+ RETURN ASR(LSL(K.sysfunc2(37, 7), 16), 16)
+END MouseVScroll;
+
+
+PROCEDURE MouseStatus*(): SET;
+ RETURN BITS(K.sysfunc2(37, 2))
+END MouseStatus;
+
+
+PROCEDURE WindowRedrawStatus*(status: INTEGER);
+BEGIN
+ K.sysfunc2(12, status)
+END WindowRedrawStatus;
+
+
+PROCEDURE DefineAndDrawWindow*(Left, Top, Width, Height, Color, Style: INTEGER; Caption: ARRAY OF CHAR);
+BEGIN
+ K.sysfunc6(0, LSL(Left, 16) + Width, LSL(Top, 16) + Height, Color + LSL(Style, 24), 0, sys.ADR(Caption[0]))
+END DefineAndDrawWindow;
+
+
+PROCEDURE WaitForEvent*(): INTEGER;
+ RETURN K.sysfunc1(10)
+END WaitForEvent;
+
+
+PROCEDURE CheckEvent*(): INTEGER;
+ RETURN K.sysfunc1(11)
+END CheckEvent;
+
+
+PROCEDURE SetEventsMask*(mask: SET);
+BEGIN
+ K.sysfunc2(40, ORD(mask))
+END SetEventsMask;
+
+
+PROCEDURE GetKeyCode*(): INTEGER;
+ RETURN LSR(LSL(K.sysfunc1(2), 16), 24)
+END GetKeyCode;
+
+
+PROCEDURE GetButtonCode*(): INTEGER;
+VAR res, button_code: INTEGER;
+BEGIN
+ res := K.sysfunc1(17);
+ IF ORD(BITS(res) * {0..7}) = 0 THEN
+ button_code := LSR(res, 8)
+ ELSE
+ button_code := 0
+ END
+ RETURN button_code
+END GetButtonCode;
+
+
+PROCEDURE OutText*(X, Y: INTEGER; Text: ARRAY OF CHAR; length: INTEGER; color: INTEGER);
+BEGIN
+ K.sysfunc6(4, LSL(X, 16) + Y, LSL(3 * 16, 24) + color, sys.ADR(Text[0]), length, 0)
+END OutText;
+
+
+PROCEDURE GetWindowPos*(VAR Left, Top: INTEGER);
+VAR info: ARRAY 1024 OF CHAR;
+BEGIN
+ K.sysfunc3(9, sys.ADR(info[0]), -1);
+ sys.GET(sys.ADR(info[34]), Left);
+ sys.GET(sys.ADR(info[38]), Top)
+END GetWindowPos;
+
+
+PROCEDURE GetWindowSize*(VAR Width, Height: INTEGER);
+VAR info: ARRAY 1024 OF CHAR;
+BEGIN
+ K.sysfunc3(9, sys.ADR(info[0]), -1);
+ sys.GET(sys.ADR(info[42]), Width);
+ sys.GET(sys.ADR(info[46]), Height)
+END GetWindowSize;
+
+
+PROCEDURE SetWindowSize*(Width, Height: INTEGER);
+BEGIN
+ K.sysfunc5(67, -1, -1, Width, Height)
+END SetWindowSize;
+
+
+PROCEDURE GetScreenSize*(VAR Width, Height: INTEGER);
+VAR res: INTEGER;
+BEGIN
+ res := K.sysfunc1(14);
+ Width := LSR(res, 16) + 1;
+ Height := ORD(BITS(res) * {0..15}) + 1
+END GetScreenSize;
+
+
+PROCEDURE GetScreenArea*(VAR X1, Y1, X2, Y2: INTEGER);
+VAR eax, ebx: INTEGER;
+BEGIN
+ eax := K.sysfunc22(48, 5, ebx);
+ X1 := LSR(eax, 16);
+ Y1 := LSR(ebx, 16);
+ X2 := ORD(BITS(eax) * {0..15});
+ Y2 := ORD(BITS(ebx) * {0..15})
+END GetScreenArea;
+
+
+PROCEDURE SkinHeight*(): INTEGER;
+ RETURN K.sysfunc2(48, 4)
+END SkinHeight;
+
+
+PROCEDURE DrawRect*(Left, Top, Width, Height, Color: INTEGER);
+BEGIN
+ K.sysfunc4(13, LSL(Left, 16) + Width, LSL(Top, 16) + Height, Color)
+END DrawRect;
+
+
+PROCEDURE NewThread*(eip: ENTRY; stack: ARRAY OF CHAR): INTEGER;
+VAR entry: INTEGER;
+BEGIN
+ sys.GET(sys.ADR(eip), entry)
+ RETURN K.sysfunc4(51, 1, entry, sys.ADR(stack[0]) + LEN(stack))
+END NewThread;
+
+
+PROCEDURE Pause*(time: INTEGER);
+BEGIN
+ K.sysfunc2(5, time)
+END Pause;
+
+
+PROCEDURE GetThreadSlot*(PID: INTEGER): INTEGER;
+ RETURN K.sysfunc3(18, 21, PID)
+END GetThreadSlot;
+
+
+PROCEDURE TerminateThreadId*(PID: INTEGER);
+BEGIN
+ K.sysfunc3(18, 18, PID)
+END TerminateThreadId;
+
+
+PROCEDURE IsTerminated*(PID: INTEGER): BOOLEAN;
+ RETURN GetThreadSlot(PID) = 0
+END IsTerminated;
+
+
+PROCEDURE FocusWindow*(Slot: INTEGER);
+BEGIN
+ K.sysfunc3(18, 3, Slot)
+END FocusWindow;
+
+
+PROCEDURE CreateButton*(id, Left, Top, Width, Height, Color: INTEGER; Caption: ARRAY OF CHAR);
+VAR
+ X, Y, len: INTEGER;
+
+BEGIN
+ len := LENGTH(Caption);
+ K.sysfunc5(8, LSL(Left, 16) + Width, LSL(Top, 16) + Height, id, btnColor);
+ X := Left + (Width - FontW * len) DIV 2;
+ Y := Top + (Height - FontH) DIV 2 + 1;
+ OutText(X, Y, Caption, len, btnTextColor)
+END CreateButton;
+
+
+PROCEDURE DrawLine* (x1, y1, x2, y2: INTEGER; color: INTEGER);
+BEGIN
+ K.sysfunc4(38, x1*65536 + x2, y1*65536 + y2, color)
+END DrawLine;
+
+
+PROCEDURE Box*(Left, Top, Width, Height, BrushColor, PenColor: INTEGER);
+BEGIN
+ K.sysfunc4(13, LSL(Left, 16) + Width, LSL(Top, 16) + Height, BrushColor);
+ DrawLine(Left, Top, Left + Width, Top, PenColor);
+ DrawLine(Left + Width, Top, Left + Width, Top + Height, PenColor);
+ DrawLine(Left + Width, Top + Height, Left, Top + Height, PenColor);
+ DrawLine(Left, Top + Height, Left, Top, PenColor);
+END Box;
+
+
+PROCEDURE LoadCursor*(cursor: INTEGER): INTEGER;
+ RETURN K.sysfunc4(37, 4, cursor, 1)
+END LoadCursor;
+
+
+PROCEDURE SetCursor*(handle: INTEGER);
+BEGIN
+ K.sysfunc3(37, 5, handle)
+END SetCursor;
+
+
+PROCEDURE DelCursor*(handle: INTEGER);
+BEGIN
+ K.sysfunc3(37, 6, handle)
+END DelCursor;
+
+
+PROCEDURE DrawImage* (data, sizeX, sizeY, x, y: INTEGER);
+BEGIN
+ K.sysfunc4(7, data, sizeX*65536 + sizeY, x*65536 + y)
+END DrawImage;
+
+
+PROCEDURE DrawText69* (x, y, color: INTEGER; text: ARRAY OF CHAR);
+BEGIN
+ K.sysfunc6(4, x*65536 + y, color + LSL(080H, 24), sys.ADR(text[0]), 0, 0)
+END DrawText69;
+
+
+PROCEDURE PutPixel* (x, y, color: INTEGER);
+BEGIN
+ K.sysfunc5(1, x, y, color, 0)
+END PutPixel;
+
+
+PROCEDURE GetSystemColors*;
+VAR
+ buf: ARRAY 10 OF INTEGER;
+BEGIN
+ ASSERT(LEN(buf) >= 10);
+ K.sysfunc4(48, 3, sys.ADR(buf[0]), 40);
+ (*darkColor := buf[2];*)
+ lightColor := buf[3];
+ winColor := buf[5];
+ textColor := buf[8];
+ btnColor := buf[6];
+ btnTextColor := buf[7];
+ borderColor := buf[9];
+END GetSystemColors;
+
+
+END SysUtils.
diff --git a/programs/other/fb2reader/SRC/Toolbar.ob07 b/programs/other/fb2reader/SRC/Toolbar.ob07
new file mode 100644
index 0000000000..162f79324d
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Toolbar.ob07
@@ -0,0 +1,167 @@
+(*
+ Copyright 2021, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Toolbar;
+
+IMPORT
+ Icons, K := SysUtils;
+
+CONST
+ max = 14;
+
+ BtnSize* = 26;
+ BtnInter = 5;
+ DelimSize = 7;
+ IconPad = (BtnSize - Icons.SIZE) DIV 2;
+
+TYPE
+ tButtonText = ARRAY 4 OF CHAR;
+
+ tButton = RECORD
+ btn, icon, x: INTEGER;
+ text: tButtonText;
+ enabled: BOOLEAN
+ END;
+
+ tToolbar* = RECORD
+ buttons: ARRAY max OF tButton;
+ x, y, cnt, width: INTEGER;
+ icons, grayIcons: INTEGER;
+ colors: RECORD back, text, disText, light, shadow, window: INTEGER END
+ END;
+
+
+PROCEDURE drawIcons* (toolbar: tToolbar);
+VAR
+ i, icons, color: INTEGER;
+ button: tButton;
+BEGIN
+ i := 0;
+ WHILE i < toolbar.cnt DO
+ button := toolbar.buttons[i];
+ IF button.btn # 0 THEN
+ IF button.enabled THEN
+ icons := toolbar.icons;
+ color := toolbar.colors.text
+ ELSE
+ icons := toolbar.grayIcons;
+ color := toolbar.colors.disText
+ END;
+ IF button.icon # -1 THEN
+ Icons.draw(icons, button.icon, button.x + IconPad, toolbar.y + IconPad)
+ ELSE
+ K.DrawRect(button.x + 1, toolbar.y + 1, BtnSize - 1, BtnSize - 1, toolbar.colors.back);
+ K.DrawText69(button.x + (BtnSize - LENGTH(button.text)*6) DIV 2, toolbar.y + (BtnSize - 9) DIV 2 + 2, color, button.text)
+ END
+ END;
+ INC(i)
+ END
+END drawIcons;
+
+
+PROCEDURE setColors (VAR toolbar: tToolbar);
+BEGIN
+ toolbar.colors.back := 0F2EFECH;
+ toolbar.colors.text := 00000FFH;
+ toolbar.colors.disText := 0808080H;
+ toolbar.colors.light := 0FEFEFEH;
+ toolbar.colors.shadow := 09F9C9AH;
+ toolbar.colors.window := K.winColor
+END setColors;
+
+
+PROCEDURE draw* (VAR toolbar: tToolbar);
+VAR
+ i, x, y, btn: INTEGER;
+ button: tButton;
+BEGIN
+ setColors(toolbar);
+ Icons.get(toolbar.icons, toolbar.grayIcons, toolbar.colors.back);
+ i := 0;
+ WHILE i < toolbar.cnt DO
+ button := toolbar.buttons[i];
+ btn := button.btn;
+ IF btn # 0 THEN
+ x := button.x;
+ y := toolbar.y;
+ K.DrawRect(x + 1, y + 1, BtnSize, BtnSize - 1, toolbar.colors.back);
+ K.DrawLine(x + 1, y + BtnSize, x + BtnSize - 1, y + BtnSize, toolbar.colors.shadow);
+ K.DrawLine(x + 1, y, x + BtnSize - 1, y, toolbar.colors.light);
+ K.DrawLine(x, y + 1, x, y + BtnSize - 1, toolbar.colors.light);
+ K.PutPixel(x + BtnSize, y + 1, toolbar.colors.light);
+ K.PutPixel(x, y + BtnSize - 1, toolbar.colors.shadow);
+ K.PutPixel(x + BtnSize, y + BtnSize - 1, toolbar.colors.shadow);
+ K.CreateButton(btn + ORD({30}), x, y, BtnSize, BtnSize, 0, "")
+ END;
+ INC(i)
+ END;
+ drawIcons(toolbar)
+END draw;
+
+
+PROCEDURE enable* (VAR toolbar: tToolbar; btn: INTEGER; value: BOOLEAN);
+VAR
+ i: INTEGER;
+BEGIN
+ i := 0;
+ WHILE (i < toolbar.cnt) & (toolbar.buttons[i].btn # btn) DO
+ INC(i)
+ END;
+ IF i < toolbar.cnt THEN
+ toolbar.buttons[i].enabled := value
+ END
+END enable;
+
+
+PROCEDURE add* (VAR toolbar: tToolbar; btn, icon: INTEGER; text: tButtonText);
+VAR
+ button: tButton;
+BEGIN
+ ASSERT(toolbar.cnt < max);
+ button.btn := btn;
+ button.icon := icon;
+ button.x := toolbar.width + toolbar.x;
+ button.text := text;
+ button.enabled := TRUE;
+ toolbar.buttons[toolbar.cnt] := button;
+ INC(toolbar.cnt);
+ IF btn # 0 THEN
+ INC(toolbar.width, BtnSize + BtnInter)
+ ELSE
+ INC(toolbar.width, DelimSize)
+ END
+END add;
+
+
+PROCEDURE delimiter* (VAR toolbar: tToolbar);
+BEGIN
+ add(toolbar, 0, 0, "")
+END delimiter;
+
+
+PROCEDURE create* (VAR toolbar: tToolbar; x, y: INTEGER);
+BEGIN
+ toolbar.x := x;
+ toolbar.y := y;
+ toolbar.cnt := 0;
+ toolbar.width := 0
+END create;
+
+
+END Toolbar.
\ No newline at end of file
diff --git a/programs/other/fb2reader/SRC/Txt2fb2.ob07 b/programs/other/fb2reader/SRC/Txt2fb2.ob07
new file mode 100644
index 0000000000..649d9f96e6
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Txt2fb2.ob07
@@ -0,0 +1,129 @@
+(*
+ Copyright 2016, 2020 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Txt2FB2;
+
+IMPORT File, sys := SYSTEM, K := KOSAPI, S := Strings, SU := SysUtils;
+
+
+CONST
+
+ AUTO* = 15;
+ CP866* = 16;
+ CP1251* = 17;
+ CP1252* = 18;
+ CP1250* = 19;
+ UTF8* = 20;
+
+
+VAR F: File.FS; ch: CHAR; pos, mem, mem2, pos2: INTEGER;
+
+
+PROCEDURE getch;
+BEGIN
+ sys.GET(mem + pos, ch);
+ INC(pos)
+END getch;
+
+
+PROCEDURE WriteStr(s: ARRAY OF CHAR);
+BEGIN
+ sys.MOVE(sys.ADR(s[0]), mem2 + pos2, LENGTH(s));
+ pos2 := pos2 + LENGTH(s)
+END WriteStr;
+
+
+PROCEDURE WriteChar(ch: CHAR);
+BEGIN
+ sys.PUT(mem2 + pos2, ch);
+ INC(pos2)
+END WriteChar;
+
+
+PROCEDURE convert*(in, out: S.STRING; encoding: INTEGER);
+CONST buf_size = 1024*16;
+VAR n, size: INTEGER; CR: BOOLEAN;
+BEGIN
+ F := File.Open(in);
+ size := File.Seek(F, 0, 2);
+ n := File.Seek(F, 0, 0);
+ mem := K.malloc(size + 1024);
+ SU.MemError(mem = 0);
+ n := File.Read(F, mem, size);
+ File.Close(F);
+ pos := 0;
+ F := File.Create(out);
+ mem2 := K.malloc(buf_size);
+ SU.MemError(mem2 = 0);
+ pos2 := 0;
+ WriteStr("");
+ WriteChar(0DX);
+ WriteChar(0AX);
+ WriteStr("");
+ WHILE pos < size DO
+ IF pos2 > buf_size - 32 THEN
+ n := File.Write(F, mem2, pos2);
+ pos2 := 0
+ END;
+ getch;
+ IF ch = "<" THEN
+ WriteStr("<")
+ ELSIF ch = ">" THEN
+ WriteStr(">")
+ ELSIF ch = "&" THEN
+ WriteStr("&")
+ ELSIF ch = "'" THEN
+ WriteStr("'")
+ ELSIF ch = 22X THEN
+ WriteStr(""")
+ ELSIF ch = 0DX THEN
+ WriteStr("")
+ ELSIF ch = 0AX THEN
+ IF ~CR THEN
+ WriteStr("")
+ END
+ ELSIF ch = 0X THEN
+ WriteChar(20X)
+ ELSE
+ WriteChar(ch)
+ END;
+ CR := ch = 0DX
+ END;
+
+ WriteStr("");
+ n := File.Write(F, mem2, pos2);
+ File.Close(F);
+ mem := K.free(mem);
+ mem2 := K.free(mem2)
+END convert;
+
+
+END Txt2FB2.
diff --git a/programs/other/fb2reader/SRC/Vector.ob07 b/programs/other/fb2reader/SRC/Vector.ob07
new file mode 100644
index 0000000000..a2fa7f632e
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Vector.ob07
@@ -0,0 +1,105 @@
+(*
+ Copyright 2016 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Vector;
+
+
+IMPORT sys := SYSTEM, K := KOSAPI;
+
+
+TYPE
+
+ DESC_VECTOR = RECORD
+
+ data : INTEGER;
+ count* : INTEGER;
+ size : INTEGER
+
+ END;
+
+ VECTOR* = POINTER TO DESC_VECTOR;
+
+ ANYREC* = RECORD END;
+
+ ANYPTR* = POINTER TO ANYREC;
+
+ DESTRUCTOR* = PROCEDURE (VAR ptr: ANYPTR);
+
+
+PROCEDURE push* (vector: VECTOR; value: ANYPTR);
+BEGIN
+ IF vector.count = vector.size THEN
+ vector.data := K.realloc(vector.data, (vector.size + 1024) * 4);
+ vector.size := vector.size + 1024
+ END;
+ sys.PUT(vector.data + vector.count * 4, value);
+ INC(vector.count)
+END push;
+
+
+PROCEDURE get* (vector: VECTOR; idx: INTEGER): ANYPTR;
+VAR res: ANYPTR;
+BEGIN
+ ASSERT( (0 <= idx) & (idx < vector.count) );
+ sys.GET(vector.data + idx * 4, res)
+ RETURN res
+END get;
+
+
+PROCEDURE put* (vector: VECTOR; idx: INTEGER; value: ANYPTR);
+BEGIN
+ ASSERT( (0 <= idx) & (idx < vector.count) );
+ sys.PUT(vector.data + idx * 4, value)
+END put;
+
+
+PROCEDURE create* (size: INTEGER): VECTOR;
+VAR vector: VECTOR;
+BEGIN
+ NEW(vector);
+ vector.data := K.malloc(4 * size);
+ vector.size := size;
+ vector.count := 0
+ RETURN vector
+END create;
+
+
+PROCEDURE def_destructor (VAR any: ANYPTR);
+BEGIN
+ DISPOSE(any)
+END def_destructor;
+
+
+PROCEDURE destroy* (VAR vector: VECTOR; destructor: DESTRUCTOR);
+VAR i: INTEGER;
+ any: ANYPTR;
+BEGIN
+ IF destructor = NIL THEN
+ destructor := def_destructor
+ END;
+ FOR i := 0 TO vector.count - 1 DO
+ any := get(vector, i);
+ destructor(any)
+ END;
+ vector.data := K.free(vector.data);
+ DISPOSE(vector)
+END destroy;
+
+
+END Vector.
diff --git a/programs/other/fb2reader/SRC/Window.ob07 b/programs/other/fb2reader/SRC/Window.ob07
new file mode 100644
index 0000000000..c2c71e9093
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Window.ob07
@@ -0,0 +1,58 @@
+(*
+ Copyright 2016, 2021 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Window;
+
+IMPORT S := Strings;
+
+TYPE
+
+ TRect* = RECORD
+ Left*, Top*, Width*, Height* : INTEGER
+ END;
+
+ TWindow* = RECORD (TRect)
+ Caption* : S.STRING;
+ Created* : BOOLEAN;
+ dWidth*, dHeight* : INTEGER
+ END;
+
+PROCEDURE InitWindow*(VAR Window: TWindow; Left, Top, Width, Height: INTEGER; Caption: ARRAY OF CHAR);
+BEGIN
+ Window.Left := Left;
+ Window.Top := Top;
+ Window.Width := Width;
+ Window.Height := Height;
+ Window.Created := FALSE;
+ Window.dWidth := 0;
+ Window.dHeight := 0;
+ COPY(Caption, Window.Caption)
+END InitWindow;
+
+
+PROCEDURE InitRect*(VAR Rect: TRect; Left, Top, Width, Height: INTEGER);
+BEGIN
+ Rect.Left := Left;
+ Rect.Top := Top;
+ Rect.Width := Width;
+ Rect.Height := Height
+END InitRect;
+
+
+END Window.
diff --git a/programs/other/fb2reader/SRC/Write.ob07 b/programs/other/fb2reader/SRC/Write.ob07
new file mode 100644
index 0000000000..e12cb07fa4
--- /dev/null
+++ b/programs/other/fb2reader/SRC/Write.ob07
@@ -0,0 +1,42 @@
+(*
+ Copyright 2016 Anton Krotov
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
+*)
+
+MODULE Write;
+
+IMPORT File, sys := SYSTEM;
+
+PROCEDURE Char*(F: File.FS; x: CHAR): BOOLEAN;
+ RETURN File.Write(F, sys.ADR(x), sys.SIZE(CHAR)) = sys.SIZE(CHAR)
+END Char;
+
+PROCEDURE Int*(F: File.FS; x: INTEGER): BOOLEAN;
+ RETURN File.Write(F, sys.ADR(x), sys.SIZE(INTEGER)) = sys.SIZE(INTEGER)
+END Int;
+
+PROCEDURE Real*(F: File.FS; x: REAL): BOOLEAN;
+ RETURN File.Write(F, sys.ADR(x), sys.SIZE(REAL)) = sys.SIZE(REAL)
+END Real;
+
+PROCEDURE Boolean*(F: File.FS; x: BOOLEAN): BOOLEAN;
+ RETURN File.Write(F, sys.ADR(x), sys.SIZE(BOOLEAN)) = sys.SIZE(BOOLEAN)
+END Boolean;
+
+PROCEDURE Set*(F: File.FS; x: SET): BOOLEAN;
+ RETURN File.Write(F, sys.ADR(x), sys.SIZE(SET)) = sys.SIZE(SET)
+END Set;
+
+END Write.
diff --git a/programs/other/fb2reader/SRC/XML.ob07 b/programs/other/fb2reader/SRC/XML.ob07
new file mode 100644
index 0000000000..c063c727dd
--- /dev/null
+++ b/programs/other/fb2reader/SRC/XML.ob07
@@ -0,0 +1,755 @@
+(*
+ Copyright 2016, 2020, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE XML;
+
+IMPORT SU := SysUtils, RF := ReadFile, S := Strings, Encode, V := Vector, tables, LISTS;
+
+
+CONST
+
+ tag_p* = 1;
+ tag_v* = 2;
+ tag_section* = 3;
+ tag_stanza* = 4;
+ tag_empty_line* = 5;
+ tag_subtitle* = 6;
+ tag_date* = 7;
+ tag_text_author* = 8;
+ tag_a* = 9;
+ tag_sub* = 10;
+ tag_sup* = 11;
+ tag_code* = 12;
+ tag_poem* = 13;
+ tag_title* = 14;
+ tag_FictionBook* = 15;
+ tag_body* = 16;
+ tag_strikethrough* = 17;
+ tag_strong* = 18;
+ tag_cite* = 19;
+ tag_epigraph* = 20;
+ tag_emphasis* = 21;
+ tag_image* = 22;
+ tag_binary* = 23;
+ tag_coverpage* = 24;
+ tag_description* = 25;
+ tag_xml* = 26;
+ tag_annotation* = 27;
+ tag_contents_item* = 28;
+ tag_table* = 29;
+ tag_tr* = 30;
+ tag_td* = 31;
+ tag_th* = 32;
+ tag_unknown* = -1;
+
+
+TYPE
+
+ ELEMENT* = POINTER TO DESC_ELEMENT;
+
+ TEXT* = POINTER TO DESC_TEXT;
+
+ SPACE* = POINTER TO DESC_SPACE;
+
+ WORD* = POINTER TO DESC_WORD;
+
+ TAG* = POINTER TO DESC_TAG;
+
+ ATTR* = POINTER TO DESC_ATTR;
+
+ TAG_ID = POINTER TO DESC_TAG_ID;
+
+
+ LIST* = RECORD first*, last* : ELEMENT END;
+
+ DESC_ELEMENT* = RECORD (V.ANYREC)
+ parent*, next* : ELEMENT
+ END;
+
+ DESC_TEXT = RECORD (DESC_ELEMENT)
+ X*, Y* : INTEGER;
+ width* : INTEGER
+ END;
+
+ DESC_SPACE = RECORD (DESC_TEXT)
+
+ END;
+
+ DESC_WORD = RECORD (DESC_TEXT)
+ length* : INTEGER;
+ value* : S.CHARS
+ END;
+
+ DESC_TAG = RECORD (DESC_ELEMENT)
+ name* : S.CHARS;
+ value* : INTEGER;
+ child* : LIST;
+ attr* : LIST;
+ Ymin* : INTEGER;
+ Ymax* : INTEGER;
+ X* : INTEGER;
+ Width* : INTEGER;
+ Clicked* : BOOLEAN;
+ Visited* : BOOLEAN;
+ img* : INTEGER;
+ num* : INTEGER;
+ cell* : INTEGER;
+ table* : tables.Table;
+ text* : LISTS.ITEM
+ END;
+
+ DESC_ATTR = RECORD (DESC_ELEMENT)
+ name : S.CHARS;
+ value : S.CHARS
+ END;
+
+ DESC_TAG_ID = RECORD (DESC_ELEMENT)
+ tag : TAG;
+ id : S.CHARS
+ END;
+
+
+VAR
+ ch: CHAR; binary: BOOLEAN;
+
+ Root, Current, Header, FB*: ELEMENT;
+
+ Tag_id: LIST;
+
+ tire1, tire2, nbsp, ellipsis, apo,
+ quot1, quot2, quot3, quot4, quot5, quot6, quot7,
+ number, bullet, euro,
+ dash1, dash2: S.UTF8;
+
+ num: INTEGER;
+ Tags: V.VECTOR;
+
+
+PROCEDURE GetTagByNum*(n: INTEGER): TAG;
+VAR ptr: V.ANYPTR;
+BEGIN
+ ptr := V.get(Tags, n)
+ RETURN ptr(TAG)
+END GetTagByNum;
+
+
+PROCEDURE ListCount*(list: LIST): INTEGER;
+VAR cur: ELEMENT; res: INTEGER;
+BEGIN
+ res := 0;
+ cur := list.first;
+ WHILE cur # NIL DO
+ INC(res);
+ cur := cur.next
+ END
+ RETURN res
+END ListCount;
+
+
+PROCEDURE GetTagByID(id: S.CHARS): TAG;
+VAR
+ cur : TAG_ID;
+ Result : TAG;
+BEGIN
+ Result := NIL;
+ cur := Tag_id.first(TAG_ID);
+ WHILE cur # NIL DO
+ IF S.CharsEq(id, cur.id) THEN
+ Result := cur.tag;
+ cur := NIL
+ ELSE
+ cur := cur.next(TAG_ID)
+ END
+ END
+ RETURN Result
+END GetTagByID;
+
+
+PROCEDURE GetAttr*(tag: TAG; attr_name: S.STRING; VAR attr_value: S.CHARS): BOOLEAN;
+VAR attr: ELEMENT;
+ found: BOOLEAN;
+BEGIN
+ found := FALSE;
+ attr := tag.attr.first;
+ WHILE ~found & (attr # NIL) DO
+ IF S.CharsEqStr(attr(ATTR).name, attr_name) THEN
+ attr_value := attr(ATTR).value;
+ INC(attr_value.first);
+ DEC(attr_value.last);
+ found := TRUE
+ ELSE
+ attr := attr.next
+ END
+ END
+ RETURN found
+END GetAttr;
+
+
+PROCEDURE IsHref(attr_name: S.CHARS): BOOLEAN;
+VAR chars: S.CHARS;
+BEGIN
+ chars := attr_name;
+ chars.first := chars.last - 4
+ RETURN S.CharsEqStr(chars, ":href")
+END IsHref;
+
+
+PROCEDURE GetRef*(tag: TAG; VAR note: BOOLEAN; VAR URL: INTEGER): TAG;
+VAR
+ attr : ATTR;
+ chars : S.CHARS;
+ Result : TAG;
+BEGIN
+ Result := NIL;
+ note := FALSE;
+ URL := 0;
+ attr := tag.attr.first(ATTR);
+ WHILE attr # NIL DO
+ IF IsHref(attr.name) THEN
+ chars := attr.value;
+ INC(chars.first);
+ IF S.GetChar(chars, 0) = "#" THEN
+ DEC(chars.last);
+ INC(chars.first);
+ Result := GetTagByID(chars)
+ ELSE
+ S.PutChar(chars, chars.last - chars.first, 0X);
+ URL := chars.first
+ END
+ ELSIF S.CharsEqStr(attr.name, "type") THEN
+ chars := attr.value;
+ INC(chars.first);
+ DEC(chars.last);
+ note := S.CharsEqStr(chars, "note")
+ END;
+ attr := attr.next(ATTR)
+ END
+ RETURN Result
+END GetRef;
+
+
+PROCEDURE IsNote*(tag: TAG): BOOLEAN;
+VAR
+ res : TAG;
+ note : BOOLEAN;
+ URL : INTEGER;
+BEGIN
+ res := GetRef(tag, note, URL)
+ RETURN note
+END IsNote;
+
+
+PROCEDURE CreateTag*(): TAG;
+VAR tag: TAG;
+BEGIN
+ NEW(tag);
+ tag.Visited := FALSE;
+ SU.MemError(tag = NIL);
+ INC(num);
+ tag.num := num;
+ V.push(Tags, tag)
+ RETURN tag
+END CreateTag;
+
+
+PROCEDURE CreateWord*(): WORD;
+VAR word: WORD;
+BEGIN
+ NEW(word);
+ SU.MemError(word = NIL)
+ RETURN word
+END CreateWord;
+
+
+PROCEDURE CreateSpace(): SPACE;
+VAR space: SPACE;
+BEGIN
+ NEW(space);
+ SU.MemError(space = NIL)
+ RETURN space
+END CreateSpace;
+
+
+PROCEDURE CreateAttr(): ATTR;
+VAR attr: ATTR;
+BEGIN
+ NEW(attr);
+ SU.MemError(attr = NIL)
+ RETURN attr
+END CreateAttr;
+
+
+PROCEDURE AddItem*(VAR list: LIST; item: ELEMENT);
+BEGIN
+ IF list.first = NIL THEN
+ list.first := item
+ ELSE
+ list.last.next := item
+ END;
+ list.last := item
+END AddItem;
+
+
+PROCEDURE DelLastItem*(VAR list: LIST);
+VAR cur: ELEMENT;
+BEGIN
+ IF list.first = list.last THEN
+ IF list.last # NIL THEN
+ DISPOSE(list.last)
+ END;
+ list.first := NIL
+ ELSE
+ cur := list.first;
+ WHILE cur.next # list.last DO
+ cur := cur.next
+ END;
+ DISPOSE(list.last);
+ cur.next := NIL;
+ list.last := cur
+ END
+END DelLastItem;
+
+
+PROCEDURE AddChild*(tag: TAG; child: ELEMENT);
+BEGIN
+ AddItem(tag.child, child);
+ child.parent := tag
+END AddChild;
+
+
+PROCEDURE AddAttr(tag: TAG; attr: ATTR);
+BEGIN
+ AddItem(tag.attr, attr);
+ attr.parent := tag
+END AddAttr;
+
+
+PROCEDURE Copy*(node: ELEMENT): ELEMENT;
+VAR
+ space : SPACE;
+ word : WORD;
+ tag : TAG;
+ cur : ELEMENT;
+ num : INTEGER;
+
+ Result : ELEMENT;
+BEGIN
+ IF node IS TAG THEN
+ tag := CreateTag();
+ num := tag.num;
+ tag^ := node(TAG)^;
+ tag.num := num;
+ tag.child.first := NIL;
+ tag.child.last := NIL;
+ cur := node(TAG).child.first;
+ WHILE cur # NIL DO
+ AddChild(tag, Copy(cur));
+ cur := cur.next
+ END;
+ Result := tag
+ ELSIF node IS WORD THEN
+ word := CreateWord();
+ word^ := node(WORD)^;
+ Result := word
+ ELSIF node IS SPACE THEN
+ space := CreateSpace();
+ space^ := node(SPACE)^;
+ Result := space
+ END;
+ Result.next := NIL
+ RETURN Result
+END Copy;
+
+
+PROCEDURE IsIdentChar(): BOOLEAN;
+ RETURN ("A" <= ch) & (ch <= "Z") OR
+ ("a" <= ch) & (ch <= "z") OR
+ ("0" <= ch) & (ch <= "9") OR
+ (ch = "?") OR (ch = "!") OR
+ (ch = ":") OR (ch = "_") OR
+ (ch = "-")
+END IsIdentChar;
+
+
+PROCEDURE Space(): BOOLEAN;
+ RETURN (ch # 0X) & (ch <= 20X)
+END Space;
+
+
+PROCEDURE Ident(VAR id: S.CHARS);
+BEGIN
+ id.first := RF.Adr();
+ WHILE IsIdentChar() DO
+ RF.Next(ch)
+ END;
+ id.last := RF.Adr() - 1
+END Ident;
+
+
+PROCEDURE Skip;
+BEGIN
+ WHILE Space() DO
+ RF.Next(ch)
+ END
+END Skip;
+
+
+PROCEDURE String(VAR str: S.CHARS);
+VAR quot: CHAR;
+BEGIN
+ SU.ErrorIf((ch # "'") & (ch # 22X), 1);
+ str.first := RF.Adr();
+ quot := ch;
+ REPEAT
+ RF.Next(ch)
+ UNTIL (ch = quot) OR (ch = 0X);
+ SU.ErrorIf(ch = 0X, 2);
+ str.last := RF.Adr();
+ RF.Next(ch)
+END String;
+
+
+PROCEDURE SetTagValue(tag: TAG);
+VAR
+ value : INTEGER;
+ name : S.CHARS;
+BEGIN
+ name := tag.name;
+ IF S.CharsEqStr(name, "p") THEN
+ value := tag_p
+ ELSIF S.CharsEqStr(name, "v") THEN
+ value := tag_v
+ ELSIF S.CharsEqStr(name, "section") THEN
+ value := tag_section
+ ELSIF S.CharsEqStr(name, "stanza") THEN
+ value := tag_stanza
+ ELSIF S.CharsEqStr(name, "empty-line") THEN
+ value := tag_empty_line
+ ELSIF S.CharsEqStr(name, "subtitle") THEN
+ value := tag_subtitle
+ ELSIF S.CharsEqStr(name, "date") THEN
+ value := tag_date
+ ELSIF S.CharsEqStr(name, "text-author") THEN
+ value := tag_text_author
+ ELSIF S.CharsEqStr(name, "a") THEN
+ value := tag_a
+ ELSIF S.CharsEqStr(name, "sub") THEN
+ value := tag_sub
+ ELSIF S.CharsEqStr(name, "sup") THEN
+ value := tag_sup
+ ELSIF S.CharsEqStr(name, "code") THEN
+ value := tag_code
+ ELSIF S.CharsEqStr(name, "poem") THEN
+ value := tag_poem
+ ELSIF S.CharsEqStr(name, "title") THEN
+ value := tag_title
+ ELSIF S.CharsEqStr(name, "FictionBook") THEN
+ value := tag_FictionBook;
+ FB := tag
+ ELSIF S.CharsEqStr(name, "body") THEN
+ value := tag_body
+ ELSIF S.CharsEqStr(name, "strikethrough") THEN
+ value := tag_strikethrough
+ ELSIF S.CharsEqStr(name, "strong") THEN
+ value := tag_strong
+ ELSIF S.CharsEqStr(name, "cite") THEN
+ value := tag_cite
+ ELSIF S.CharsEqStr(name, "epigraph") THEN
+ value := tag_epigraph
+ ELSIF S.CharsEqStr(name, "emphasis") THEN
+ value := tag_emphasis
+ ELSIF S.CharsEqStr(name, "image") THEN
+ value := tag_image
+ ELSIF S.CharsEqStr(name, "binary") THEN
+ binary := TRUE;
+ value := tag_binary
+ ELSIF S.CharsEqStr(name, "coverpage") THEN
+ value := tag_coverpage
+ ELSIF S.CharsEqStr(name, "description") THEN
+ value := tag_description
+ ELSIF S.CharsEqStr(name, "annotation") THEN
+ value := tag_annotation
+ ELSIF S.CharsEqStr(name, "table") THEN
+ value := tag_table
+ ELSIF S.CharsEqStr(name, "tr") THEN
+ value := tag_tr
+ ELSIF S.CharsEqStr(name, "td") THEN
+ value := tag_td
+ ELSIF S.CharsEqStr(name, "th") THEN
+ value := tag_th
+ ELSIF S.CharsEqStr(name, "?xml") THEN
+ value := tag_xml;
+ Header := tag
+ ELSE
+ value := tag_unknown
+ END;
+ tag.value := value
+END SetTagValue;
+
+
+PROCEDURE ReadTag;
+VAR tag: TAG; name: S.CHARS; attr: ATTR; tag_id: TAG_ID;
+BEGIN
+ RF.Next(ch);
+ Skip;
+ IF ch = "/" THEN
+ RF.Next(ch);
+ Skip;
+ SU.ErrorIf(~IsIdentChar(), 3);
+ Ident(name);
+ Skip;
+ SU.ErrorIf(ch # ">", 4);
+ RF.Next(ch);
+ tag := Current(TAG);
+ SU.ErrorIf(~S.CharsEq(tag.name, name), 5);
+ IF tag.value = tag_binary THEN
+ binary := FALSE;
+ IF tag.child.first IS WORD THEN
+ S.Base64(tag.child.first(WORD).value)
+ END
+ END;
+ Current := Current.parent
+ ELSE
+ tag := CreateTag();
+ AddChild(Current(TAG), tag);
+ Current := tag;
+ SU.ErrorIf(~IsIdentChar(), 6);
+ Ident(tag.name);
+ SetTagValue(tag);
+ WHILE Space() DO
+ Skip;
+ IF IsIdentChar() THEN
+ attr := CreateAttr();
+ Ident(attr.name);
+ Skip;
+ SU.ErrorIf(ch # "=", 7);
+ RF.Next(ch);
+ Skip;
+ String(attr.value);
+ AddAttr(Current(TAG), attr);
+ IF S.CharsEqStr(attr.name, "id") THEN
+ NEW(tag_id);
+ SU.MemError(tag_id = NIL);
+ tag_id.tag := Current(TAG);
+ tag_id.id := attr.value;
+ INC(tag_id.id.first);
+ DEC(tag_id.id.last);
+ AddItem(Tag_id, tag_id)
+ END
+ END
+ END;
+ IF ch = "/" THEN
+ RF.Next(ch);
+ IF Current(TAG).value = tag_binary THEN
+ binary := FALSE
+ END;
+ Current := Current.parent
+ ELSIF ch = "?" THEN
+ RF.Next(ch);
+ SU.ErrorIf(Current(TAG).value # tag_xml, 8);
+ Current := Current.parent
+ END;
+ SU.ErrorIf(ch # ">", 9);
+ RF.Next(ch)
+ END
+END ReadTag;
+
+
+PROCEDURE ReadSpace;
+VAR space: SPACE;
+BEGIN
+ space := CreateSpace();
+ AddChild(Current(TAG), space);
+ RF.Next(ch)
+END ReadSpace;
+
+
+PROCEDURE ReadWord;
+VAR word: WORD; chars: S.CHARS; repl: BOOLEAN;
+BEGIN
+ word := CreateWord();
+ word.value.first := RF.Adr();
+ repl := FALSE;
+ WHILE ((ch > 20X) OR binary) & (ch # 0X) & (ch # "<") DO
+ repl := repl OR (ch = "&") OR (ch = 0C2X) OR (ch >= 0E0X) & (ch < 0F0X);
+ RF.Next(ch)
+ END;
+ word.value.last := RF.Adr() - 1;
+ IF repl THEN
+ chars := word.value;
+ S.Replace(chars, "&", "&");
+ S.Replace(chars, "<", "<");
+ S.Replace(chars, ">", ">");
+ S.Replace(chars, """, 22X);
+ S.Replace(chars, "'", "'");
+ WHILE S.EntOct(chars) DO END;
+ S.Replace(chars, tire1, "--");
+ S.Replace(chars, tire2, "--");
+ S.Replace(chars, nbsp, " ");
+ S.Replace(chars, ellipsis, "...");
+ S.Replace(chars, quot1, 22X);
+ S.Replace(chars, quot2, 22X);
+ S.Replace(chars, quot3, 22X);
+ S.Replace(chars, quot4, "'");
+ S.Replace(chars, quot5, ",");
+ S.Replace(chars, quot6, "<");
+ S.Replace(chars, quot7, ">");
+ S.Replace(chars, number, "No.");
+ S.Replace(chars, apo, "'");
+ S.Replace(chars, dash1, "-");
+ S.Replace(chars, dash2, "-");
+ S.Replace(chars, bullet, "*");
+ S.Replace(chars, euro, "EUR");
+ word.value := chars
+ END;
+ AddChild(Current(TAG), word)
+END ReadWord;
+
+
+PROCEDURE Comment(): BOOLEAN;
+CONST com = 2D2D213CH;
+VAR res: BOOLEAN;
+BEGIN
+ res := FALSE;
+ IF RF.Int() = com THEN
+ RF.Next(ch);
+ RF.Next(ch);
+ RF.Next(ch);
+ RF.Next(ch);
+
+ REPEAT
+ RF.Next(ch);
+ IF ch = "-" THEN
+ RF.Next(ch);
+ WHILE (ch = "-") & ~res DO
+ RF.Next(ch);
+ IF ch = ">" THEN
+ RF.Next(ch);
+ res := TRUE
+ END
+ END
+ END
+ UNTIL (ch = 0X) OR res
+
+ END
+ RETURN res
+END Comment;
+
+
+PROCEDURE Prolog;
+VAR attr: ATTR; chars: S.CHARS;
+BEGIN
+ RF.Next(ch);
+ IF ch = 0EFX THEN
+ RF.Next(ch);
+ SU.ErrorIf(ch # 0BBX, 16);
+ RF.Next(ch);
+ SU.ErrorIf(ch # 0BFX, 16);
+ RF.Next(ch)
+ END;
+ Skip;
+ IF ch = "<" THEN
+ ReadTag
+ END;
+
+ SU.ErrorIf(Header = NIL, 15);
+
+ attr := Header(TAG).attr.first(ATTR);
+ WHILE attr # NIL DO
+ IF S.CharsEqStr(attr.name, "encoding") THEN
+ chars := attr.value;
+ INC(chars.first);
+ DEC(chars.last);
+ S.SetCS(FALSE);
+ IF S.CharsEqStr(chars, "windows-1250") THEN
+ RF.Conv(Encode.W1250)
+ ELSIF S.CharsEqStr(chars, "windows-1251") THEN
+ RF.Conv(Encode.W1251)
+ ELSIF S.CharsEqStr(chars, "windows-1252") THEN
+ RF.Conv(Encode.W1252)
+ ELSIF S.CharsEqStr(chars, "cp866" ) THEN
+ RF.Conv(Encode.CP866)
+ ELSIF S.CharsEqStr(chars, "utf-8" ) THEN
+ RF.SeekBeg
+ ELSE
+ SU.ErrorIf(TRUE, 14)
+ END;
+ S.SetCS(TRUE)
+ END;
+ attr := attr.next(ATTR)
+ END
+END Prolog;
+
+
+PROCEDURE Parse;
+BEGIN
+ Prolog;
+ binary := FALSE;
+ RF.Next(ch);
+ WHILE ch = "<" DO
+ IF ~Comment() THEN
+ ReadTag
+ END
+ ELSIF Space() & ~binary DO
+ ReadSpace
+ ELSIF (ch # 0X) DO
+ ReadWord
+ END
+END Parse;
+
+
+PROCEDURE Open*(FileName: S.STRING);
+BEGIN
+ Root := CreateTag();
+ Current := Root;
+ Header := NIL;
+ FB := NIL;
+ num := 0;
+ RF.Load(FileName);
+ Parse;
+ SU.ErrorIf(Current # Root, 10)
+END Open;
+
+
+PROCEDURE Init;
+BEGIN
+ S.utf8(8212, tire1);
+ S.utf8(8211, tire2);
+ S.utf8( 160, nbsp);
+ S.utf8(8230, ellipsis);
+ S.utf8(8217, apo);
+ S.utf8(8220, quot1);
+ S.utf8(8221, quot2);
+ S.utf8(8222, quot3);
+ S.utf8(8216, quot4);
+ S.utf8(8218, quot5);
+ S.utf8(8249, quot6);
+ S.utf8(8250, quot7);
+ S.utf8(8470, number);
+ S.utf8(8208, dash1);
+ S.utf8(8209, dash2);
+ S.utf8(8226, bullet);
+ S.utf8(8364, euro);
+ Tags := V.create(1024)
+END Init;
+
+
+BEGIN
+ Init
+END XML.
diff --git a/programs/other/fb2reader/SRC/box_lib.ob07 b/programs/other/fb2reader/SRC/box_lib.ob07
new file mode 100644
index 0000000000..f9683d250f
--- /dev/null
+++ b/programs/other/fb2reader/SRC/box_lib.ob07
@@ -0,0 +1,236 @@
+(*
+ Copyright 2016, 2017, 2020, 2022 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE box_lib;
+
+IMPORT sys := SYSTEM, KOSAPI;
+
+
+CONST
+
+ CHECKBOX_IS_SET* = 1;
+
+
+TYPE
+
+ checkbox* = POINTER TO RECORD
+
+ left_s: INTEGER;
+ top_s: INTEGER;
+ ch_text_margin*: INTEGER;
+ color: INTEGER;
+ border_color: INTEGER;
+ text_color: INTEGER;
+ text: INTEGER;
+ flags*: SET;
+
+ (* Users can use members above this *)
+ size_of_str: INTEGER
+
+ END;
+
+
+ scrollbar* = POINTER TO RECORD
+
+ x_w: INTEGER;
+ y_h*: INTEGER;
+ btn_height: INTEGER;
+ typ: INTEGER;
+ max_area*: INTEGER;
+ cur_area*: INTEGER;
+ position*: INTEGER;
+ back_color: INTEGER;
+ front_color: INTEGER;
+ line_color: INTEGER;
+ redraw: INTEGER;
+
+ delta: WCHAR;
+ delta2: WCHAR;
+ r_size_x: WCHAR;
+ r_start_x: WCHAR;
+ r_size_y: WCHAR;
+ r_start_y: WCHAR;
+
+ m_pos: INTEGER;
+ m_pos2: INTEGER;
+ m_keys: INTEGER;
+ run_size: INTEGER;
+ position2: INTEGER;
+ work_size: INTEGER;
+ all_redraw: INTEGER;
+ ar_offset: INTEGER
+
+ END;
+
+ edit_box* = POINTER TO RECORD
+ width*,
+ left,
+ top,
+ color,
+ shift_color,
+ focus_border_color,
+ blur_border_color,
+ text_color*,
+ max: INTEGER;
+ text*: INTEGER;
+ mouse_variable: INTEGER;
+ flags*,
+
+ size,
+ pos: INTEGER;
+ (* The following struct members are not used by the users of API *)
+ offset, cl_curs_x, cl_curs_y, shift, shift_old, height, char_width: INTEGER
+ END;
+
+
+PROCEDURE [stdcall, "box_lib.obj", ""] check_box_draw2* (cb: checkbox); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] check_box_mouse2* (cb: checkbox); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] init_checkbox2 (cb: checkbox); END;
+
+PROCEDURE [stdcall, "box_lib.obj", ""] scrollbar_h_draw* (sb: scrollbar); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] scrollbar_h_mouse* (sb: scrollbar); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] scrollbar_v_draw* (sb: scrollbar); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] scrollbar_v_mouse* (sb: scrollbar); END;
+
+PROCEDURE [stdcall, "box_lib.obj", ""] edit_box_draw* (eb: edit_box); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] edit_box_key_safe* (eb: edit_box; key: INTEGER); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] edit_box_mouse* (eb: edit_box); END;
+PROCEDURE [stdcall, "box_lib.obj", ""] edit_box_set_text* (eb: edit_box; text: INTEGER); END;
+
+
+PROCEDURE edit_box_get_value* (text: edit_box; VAR str: ARRAY OF CHAR);
+VAR
+ ptr, max, i: INTEGER;
+
+BEGIN
+ ptr := text.text;
+ max := text.max;
+ ASSERT(max < LEN(str));
+ i := 0;
+ REPEAT
+ sys.GET(ptr, str[i]);
+ INC(i);
+ INC(ptr)
+ UNTIL (str[i - 1] = 0X) OR (i = max);
+ str[i] := 0X
+END edit_box_get_value;
+
+
+PROCEDURE memset(adr: INTEGER; c: CHAR; n: INTEGER);
+BEGIN
+ WHILE n > 0 DO
+ sys.PUT(adr, c);
+ INC(adr);
+ DEC(n)
+ END
+END memset;
+
+
+PROCEDURE check_box_set_value* (cb: checkbox; value: BOOLEAN);
+BEGIN
+ IF cb # NIL THEN
+ IF value THEN
+ INCL(cb.flags, CHECKBOX_IS_SET)
+ ELSE
+ EXCL(cb.flags, CHECKBOX_IS_SET)
+ END
+ END
+END check_box_set_value;
+
+
+PROCEDURE check_box_get_value* (cb: checkbox): BOOLEAN;
+VAR res: BOOLEAN;
+BEGIN
+ res := FALSE;
+ IF cb # NIL THEN
+ res := CHECKBOX_IS_SET IN cb.flags
+ END
+ RETURN res
+END check_box_get_value;
+
+
+PROCEDURE kolibri_new_check_box* (tlx, tly, sizex, sizey, label_text, text_margin: INTEGER): checkbox;
+VAR new_checkbox: checkbox;
+BEGIN
+ NEW(new_checkbox);
+ new_checkbox.left_s := tlx * 65536 + sizex;
+ new_checkbox.top_s := tly * 65536 + sizey;
+ new_checkbox.ch_text_margin := text_margin;
+ new_checkbox.color := 80808080H;
+ new_checkbox.border_color := 0000FF00H;
+ new_checkbox.text_color := 00000000H;
+ new_checkbox.text := label_text;
+ new_checkbox.flags := {3};
+ init_checkbox2(new_checkbox)
+ RETURN new_checkbox
+END kolibri_new_check_box;
+
+
+PROCEDURE kolibri_scrollbar*(sb: scrollbar; x_w, y_h, btn_height, max_area, cur_area, position, back_color, front_color, line_color, typ: INTEGER): scrollbar;
+BEGIN
+ memset(sys.ADR(sb^), 0X, sys.SIZE(scrollbar));
+ sb.x_w := x_w;
+ sb.y_h := y_h;
+ sb.btn_height := btn_height;
+ sb.typ := typ;
+ sb.max_area := max_area;
+ sb.cur_area := cur_area;
+ sb.position := position;
+ sb.line_color := line_color;
+ sb.back_color := back_color;
+ sb.front_color := front_color;
+ sb.ar_offset := 1;
+ sb.all_redraw := 1
+ RETURN sb
+END kolibri_scrollbar;
+
+
+PROCEDURE kolibri_new_scrollbar*(x_w, y_h, btn_height, max_area, cur_area, position, back_color, front_color, line_color, typ: INTEGER): scrollbar;
+VAR sb: scrollbar;
+BEGIN
+ NEW(sb);
+ RETURN kolibri_scrollbar(sb, x_w, y_h, btn_height, max_area, cur_area, position, back_color, front_color, line_color, typ)
+END kolibri_new_scrollbar;
+
+
+PROCEDURE kolibri_new_edit_box* (tlx, tly, width, max_chars: INTEGER): edit_box;
+VAR
+ new_textbox: edit_box;
+
+BEGIN
+ NEW(new_textbox);
+
+ new_textbox.width := width;
+ new_textbox.left := tlx;
+ new_textbox.top := tly;
+ new_textbox.color := 0FFFFFFH;
+ new_textbox.shift_color := 06A9480H;
+ new_textbox.focus_border_color := 0;
+ new_textbox.blur_border_color := 06A9480H;
+ new_textbox.text_color := 30000000H;
+ new_textbox.max := max_chars;
+ new_textbox.text := KOSAPI.malloc(max_chars + 2);
+ new_textbox.mouse_variable := 0;
+ new_textbox.flags := 0
+
+ RETURN new_textbox
+END kolibri_new_edit_box;
+
+
+END box_lib.
diff --git a/programs/other/fb2reader/SRC/encode.ob07 b/programs/other/fb2reader/SRC/encode.ob07
new file mode 100644
index 0000000000..fa2621d28b
--- /dev/null
+++ b/programs/other/fb2reader/SRC/encode.ob07
@@ -0,0 +1,149 @@
+(*
+ Copyright 2016 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE Encode;
+
+IMPORT S := Strings;
+
+TYPE
+
+ CP* = ARRAY 256 OF RECORD code*, len*: INTEGER; utf8*: S.UTF8 END;
+
+
+VAR
+
+ W1250*, W1251*, W1252*, CP866*: CP;
+
+
+PROCEDURE InitCP(VAR cp: CP);
+VAR i: INTEGER;
+BEGIN
+ FOR i := 0H TO 7FH DO
+ cp[i].code := i
+ END;
+ FOR i := 0H TO 0FFH DO
+ S.utf8(cp[i].code, cp[i].utf8);
+ cp[i].len := LENGTH(cp[i].utf8)
+ END
+END InitCP;
+
+
+PROCEDURE Init8(VAR cp: CP; VAR n: INTEGER; a, b, c, d, e, f, g, h: INTEGER);
+BEGIN
+ cp[n].code := a; INC(n);
+ cp[n].code := b; INC(n);
+ cp[n].code := c; INC(n);
+ cp[n].code := d; INC(n);
+ cp[n].code := e; INC(n);
+ cp[n].code := f; INC(n);
+ cp[n].code := g; INC(n);
+ cp[n].code := h; INC(n);
+END Init8;
+
+
+PROCEDURE InitW1250(VAR cp: CP);
+VAR n: INTEGER;
+BEGIN
+ n := 80H;
+ Init8(cp, n, 20ACH, 20H, 201AH, 20H, 201EH, 2026H, 2020H, 2021H);
+ Init8(cp, n, 20H, 2030H, 0160H, 2039H, 015AH, 0164H, 017DH, 0179H);
+ Init8(cp, n, 20H, 2018H, 2019H, 201CH, 201DH, 2022H, 2013H, 2014H);
+ Init8(cp, n, 20H, 2122H, 0161H, 203AH, 015BH, 0165H, 017EH, 017AH);
+ Init8(cp, n, 00A0H, 02C7H, 02D8H, 0141H, 00A4H, 0104H, 00A6H, 00A7H);
+ Init8(cp, n, 00A8H, 00A9H, 015EH, 00ABH, 00ACH, 00ADH, 00AEH, 017BH);
+ Init8(cp, n, 00B0H, 00B1H, 02DBH, 0142H, 00B4H, 00B5H, 00B6H, 00B7H);
+ Init8(cp, n, 00B8H, 0105H, 015FH, 00BBH, 013DH, 02DDH, 013EH, 017CH);
+ Init8(cp, n, 0154H, 00C1H, 00C2H, 0102H, 00C4H, 0139H, 0106H, 00C7H);
+ Init8(cp, n, 010CH, 00C9H, 0118H, 00CBH, 011AH, 00CDH, 00CEH, 010EH);
+ Init8(cp, n, 0110H, 0143H, 0147H, 00D3H, 00D4H, 0150H, 00D6H, 00D7H);
+ Init8(cp, n, 0158H, 016EH, 00DAH, 0170H, 00DCH, 00DDH, 0162H, 00DFH);
+ Init8(cp, n, 0155H, 00E1H, 00E2H, 0103H, 00E4H, 013AH, 0107H, 00E7H);
+ Init8(cp, n, 010DH, 00E9H, 0119H, 00EBH, 011BH, 00EDH, 00EEH, 010FH);
+ Init8(cp, n, 0111H, 0144H, 0148H, 00F3H, 00F4H, 0151H, 00F6H, 00F7H);
+ Init8(cp, n, 0159H, 016FH, 00FAH, 0171H, 00FCH, 00FDH, 0163H, 02D9H);
+ InitCP(cp)
+END InitW1250;
+
+
+PROCEDURE InitW1251(VAR cp: CP);
+VAR n, i: INTEGER;
+BEGIN
+ n := 80H;
+ Init8(cp, n, 0402H, 0403H, 201AH, 0453H, 201EH, 2026H, 2020H, 2021H);
+ Init8(cp, n, 20ACH, 2030H, 0409H, 2039H, 040AH, 040CH, 040BH, 040FH);
+ Init8(cp, n, 0452H, 2018H, 2019H, 201CH, 201DH, 2022H, 2013H, 2014H);
+ Init8(cp, n, 20H, 2122H, 0459H, 203AH, 045AH, 045CH, 045BH, 045FH);
+ Init8(cp, n, 00A0H, 040EH, 045EH, 0408H, 00A4H, 0490H, 00A6H, 00A7H);
+ Init8(cp, n, 0401H, 00A9H, 0404H, 00ABH, 00ACH, 00ADH, 00AEH, 0407H);
+ Init8(cp, n, 00B0H, 00B1H, 0406H, 0456H, 0491H, 00B5H, 00B6H, 00B7H);
+ Init8(cp, n, 0451H, 2116H, 0454H, 00BBH, 0458H, 0405H, 0455H, 0457H);
+ FOR i := 0410H TO 044FH DO
+ cp[i - 350H].code := i
+ END;
+ InitCP(cp)
+END InitW1251;
+
+
+PROCEDURE InitW1252(VAR cp: CP);
+VAR n, i: INTEGER;
+BEGIN
+ n := 80H;
+ Init8(cp, n, 20ACH, 20H, 201AH, 0192H, 201EH, 2026H, 2020H, 2021H);
+ Init8(cp, n, 02C6H, 2030H, 0160H, 2039H, 0152H, 20H, 017DH, 20H);
+ Init8(cp, n, 20H, 2018H, 2019H, 201CH, 201DH, 2022H, 2013H, 2014H);
+ Init8(cp, n, 02DCH, 2122H, 0161H, 203AH, 0153H, 20H, 017EH, 0178H);
+ FOR i := 0A0H TO 0FFH DO
+ cp[i].code := i
+ END;
+ InitCP(cp)
+END InitW1252;
+
+
+PROCEDURE InitCP866(VAR cp: CP);
+VAR n, i: INTEGER;
+BEGIN
+ FOR i := 0410H TO 043FH DO
+ cp[i - 0410H + 80H].code := i
+ END;
+ FOR i := 0440H TO 044FH DO
+ cp[i - 0440H + 0E0H].code := i
+ END;
+
+ n := 0B0H;
+ Init8(cp, n, 2591H, 2592H, 2593H, 2502H, 2524H, 2561H, 2562H, 2556H);
+ Init8(cp, n, 2555H, 2563H, 2551H, 2557H, 255DH, 255CH, 255BH, 2510H);
+ Init8(cp, n, 2514H, 2534H, 252CH, 251CH, 2500H, 253CH, 255EH, 255FH);
+ Init8(cp, n, 255AH, 2554H, 2569H, 2566H, 2560H, 2550H, 256CH, 2567H);
+ Init8(cp, n, 2568H, 2564H, 2565H, 2559H, 2558H, 2552H, 2553H, 256BH);
+ Init8(cp, n, 256AH, 2518H, 250CH, 2588H, 2584H, 258CH, 2590H, 2580H);
+
+ n := 0F0H;
+ Init8(cp, n, 0401H, 0451H, 0404H, 0454H, 0407H, 0457H, 040EH, 045EH);
+ Init8(cp, n, 00B0H, 2219H, 00B7H, 221AH, 2116H, 00A4H, 25A0H, 00A0H);
+
+ InitCP(cp)
+END InitCP866;
+
+
+BEGIN
+ InitW1250(W1250);
+ InitW1251(W1251);
+ InitW1252(W1252);
+ InitCP866(CP866);
+END Encode.
diff --git a/programs/other/fb2reader/SRC/kfonts.ob07 b/programs/other/fb2reader/SRC/kfonts.ob07
new file mode 100644
index 0000000000..01a2f0c40b
--- /dev/null
+++ b/programs/other/fb2reader/SRC/kfonts.ob07
@@ -0,0 +1,466 @@
+(*
+ Copyright 2018-2020 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE kfonts;
+
+IMPORT File, sys := SYSTEM, LISTS, KOSAPI, S := Strings;
+
+
+CONST
+
+ MIN_FONT_SIZE = 8;
+ MAX_FONT_SIZE = 46;
+
+ bold* = 1;
+ //italic* = 2;
+ underline* = 4;
+ strike_through* = 8;
+ //smoothing* = 16;
+ //bpp32* = 32;
+
+
+TYPE
+
+ FNAME = ARRAY 2048 OF CHAR;
+
+ FILE = RECORD
+
+ name: FNAME;
+ data, size, pos: INTEGER
+
+ END;
+
+ PIX = POINTER TO RECORD (LISTS.ITEM)
+
+ x, y: INTEGER
+
+ END;
+
+ FONT = POINTER TO RECORD
+
+ chars,
+ smooth: ARRAY 256 OF LISTS.LIST;
+ width: ARRAY 256 OF INTEGER;
+ height: INTEGER;
+ file: FILE
+
+ END;
+
+ TFont* = FONT;
+
+
+PROCEDURE getch (VAR F: FILE): CHAR;
+VAR
+ ch: CHAR;
+BEGIN
+ IF (F.pos >= 0) & (F.pos < F.size) THEN
+ sys.GET(F.data + F.pos, ch);
+ INC(F.pos)
+ ELSE
+ ch := 0X
+ END
+ RETURN ch
+END getch;
+
+
+PROCEDURE getint (VAR F: FILE): INTEGER;
+VAR
+ i: INTEGER;
+BEGIN
+ IF (F.pos >= 0) & (F.pos < F.size) THEN
+ sys.GET(F.data + F.pos, i);
+ INC(F.pos, 4)
+ ELSE
+ i := 0
+ END
+ RETURN i
+END getint;
+
+
+PROCEDURE getpix (list: LISTS.LIST; x, y: INTEGER): BOOLEAN;
+VAR
+ pix: PIX;
+ res: BOOLEAN;
+
+BEGIN
+ res := FALSE;
+ pix := list.first(PIX);
+ WHILE pix # NIL DO
+ IF (pix.x = x) & (pix.y = y) THEN
+ res := TRUE;
+ pix := NIL
+ ELSE
+ pix := pix.next(PIX)
+ END
+ END
+
+ RETURN res
+END getpix;
+
+
+PROCEDURE process (font: FONT; n: INTEGER);
+VAR
+ xsize, ysize, size, ch_size, xmax: INTEGER;
+ ptr: INTEGER; i, c: INTEGER;
+ s: SET; x, y: INTEGER;
+ eoc: BOOLEAN;
+
+ pix: PIX; chr, smooth: LISTS.LIST;
+BEGIN
+ font.file.pos := n * 4;
+ ptr := getint(font.file) + 156;
+ font.file.pos := ptr;
+ size := getint(font.file);
+ INC(font.file.pos, size - 6);
+ xsize := ORD(getch(font.file));
+ ysize := ORD(getch(font.file));
+ ch_size := (size - 6) DIV 256;
+
+ INC(ptr, 4);
+
+ font.height := ysize;
+
+ FOR c := 0 TO 255 DO
+ chr := font.chars[c];
+ smooth := font.smooth[c];
+ font.file.pos := ptr + c * ch_size;
+
+ x := 0; y := 0; eoc := FALSE;
+ xmax := 0;
+
+ eoc := (xsize = 0) OR (ysize = 0);
+
+ WHILE ~eoc DO
+
+ s := BITS(getint(font.file));
+ i := 0;
+
+ WHILE i <= 31 DO
+ IF i IN s THEN
+ NEW(pix);
+ IF x > xmax THEN
+ xmax := x
+ END;
+ pix.x := x;
+ pix.y := y;
+ LISTS.push(chr, pix)
+ END;
+ INC(x);
+ IF x = xsize THEN
+ x := 0;
+ INC(y);
+ IF y = ysize THEN
+ eoc := TRUE;
+ i := 31
+ END
+ END;
+ INC(i)
+ END
+
+ END;
+
+ FOR x := 0 TO xsize - 2 DO
+ FOR y := 0 TO ysize - 2 DO
+ IF getpix(chr, x, y) & getpix(chr, x + 1, y + 1) &
+ ~getpix(chr, x + 1, y) & ~getpix(chr, x, y + 1) THEN
+
+ IF ~getpix(smooth, x + 1, y) THEN
+ NEW(pix);
+ pix.x := x + 1;
+ pix.y := y;
+ LISTS.push(smooth, pix);
+ END;
+
+ IF ~getpix(smooth, x, y + 1) THEN
+ NEW(pix);
+ pix.x := x;
+ pix.y := y + 1;
+ LISTS.push(smooth, pix)
+ END
+ END
+ END
+ END;
+
+ FOR x := 1 TO xsize - 1 DO
+ FOR y := 0 TO ysize - 2 DO
+ IF getpix(chr, x, y) & getpix(chr, x - 1, y + 1) &
+ ~getpix(chr, x - 1, y) & ~getpix(chr, x, y + 1) THEN
+
+ IF ~getpix(smooth, x - 1, y) THEN
+ NEW(pix);
+ pix.x := x - 1;
+ pix.y := y;
+ LISTS.push(smooth, pix);
+ END;
+
+ IF ~getpix(smooth, x, y + 1) THEN
+ NEW(pix);
+ pix.x := x;
+ pix.y := y + 1;
+ LISTS.push(smooth, pix)
+ END
+ END
+ END
+ END;
+
+ IF xmax = 0 THEN
+ xmax := xsize DIV 3
+ END;
+
+ font.width[c] := xmax
+
+ END
+
+END process;
+
+
+PROCEDURE getrgb(color: INTEGER; VAR r, g, b: INTEGER);
+BEGIN
+ b := ORD(BITS(color) * {0..7});
+ g := ORD(BITS(LSR(color, 8)) * {0..7});
+ r := ORD(BITS(LSR(color, 16)) * {0..7})
+END getrgb;
+
+
+PROCEDURE rgb(r, g, b: INTEGER): INTEGER;
+ RETURN b + LSL(g, 8) + LSL(r, 16)
+END rgb;
+
+
+PROCEDURE OutChar (font: FONT; canvas: INTEGER; x, y: INTEGER; c: CHAR; color: INTEGER);
+VAR
+ xsize, ysize: INTEGER;
+ pix: PIX;
+ bkcolor: INTEGER;
+ r0, b0, g0, r, g, b: INTEGER;
+ ptr: INTEGER;
+BEGIN
+ sys.GET(canvas, xsize);
+ sys.GET(canvas, ysize);
+ INC(canvas, 8);
+ getrgb(color, r0, g0, b0);
+
+ pix := font.chars[ORD(c)].first(PIX);
+ WHILE pix # NIL DO
+ sys.PUT(canvas + ((pix.y + y) * xsize + (pix.x + x)) * 4, color);
+ pix := pix.next(PIX)
+ END;
+
+ pix := font.smooth[ORD(c)].first(PIX);
+ WHILE pix # NIL DO
+ ptr := canvas + ((pix.y + y) * xsize + (pix.x + x)) * 4;
+ sys.GET(ptr, bkcolor);
+ getrgb(bkcolor, r, g, b);
+
+ r := (r * 7 + r0 * 2) DIV 9;
+ g := (g * 7 + g0 * 2) DIV 9;
+ b := (b * 7 + b0 * 2) DIV 9;
+
+ sys.PUT(ptr, rgb(r, g, b));
+ pix := pix.next(PIX)
+ END
+
+END OutChar;
+
+
+PROCEDURE TextHeight* (font: FONT): INTEGER;
+VAR
+ res: INTEGER;
+
+BEGIN
+ IF font # NIL THEN
+ res := font.height
+ ELSE
+ res := 0
+ END
+
+ RETURN res
+END TextHeight;
+
+
+
+PROCEDURE TextOut* (font: FONT; canvas: INTEGER; x, y: INTEGER; text: INTEGER; length: INTEGER; color: INTEGER; flags: INTEGER);
+VAR
+ c: CHAR;
+ x1: INTEGER;
+
+BEGIN
+ IF font # NIL THEN
+ x1 := x;
+ WHILE length > 0 DO
+ sys.GET(text, c);
+ INC(text);
+ DEC(length);
+ OutChar(font, canvas, x, y, c, color);
+ IF BITS(bold) * BITS(flags) = BITS(bold) THEN
+ INC(x);
+ OutChar(font, canvas, x, y, c, color)
+ END;
+ INC(x, font.width[ORD(c)])
+ END;
+ IF length = -1 THEN
+ sys.GET(text, c);
+ INC(text);
+ WHILE c # 0X DO
+ OutChar(font, canvas, x, y, c, color);
+ IF BITS(bold) * BITS(flags) = BITS(bold) THEN
+ INC(x);
+ OutChar(font, canvas, x, y, c, color)
+ END;
+ INC(x, font.width[ORD(c)]);
+ sys.GET(text, c);
+ INC(text)
+ END
+ END
+ END
+END TextOut;
+
+
+PROCEDURE TextWidth* (font: FONT; text: INTEGER; length: INTEGER; flags: INTEGER): INTEGER;
+VAR
+ c: CHAR;
+ res: INTEGER;
+
+BEGIN
+ res := 0;
+
+ IF font # NIL THEN
+ WHILE length > 0 DO
+ sys.GET(text, c);
+ INC(text);
+ DEC(length);
+ IF BITS(bold) * BITS(flags) = BITS(bold) THEN
+ INC(res)
+ END;
+ INC(res, font.width[ORD(c)])
+ END;
+ IF length = -1 THEN
+ sys.GET(text, c);
+ INC(text);
+ WHILE c # 0X DO
+ IF BITS(bold) * BITS(flags) = BITS(bold) THEN
+ INC(res)
+ END;
+ INC(res, font.width[ORD(c)]);
+ sys.GET(text, c);
+ INC(text)
+ END
+ END
+ END
+
+ RETURN res
+END TextWidth;
+
+
+PROCEDURE Enabled*(font: FONT; size: INTEGER): BOOLEAN;
+VAR
+ offset, temp: INTEGER;
+
+BEGIN
+ offset := -1;
+ IF (MIN_FONT_SIZE <= size) & (size <= MAX_FONT_SIZE) & (font # NIL) THEN
+ temp := font.file.data + (size - 8) * 4;
+ IF (font.file.data <= temp) & (temp <= font.file.size + font.file.data - 4) THEN
+ sys.GET(temp, offset)
+ END
+ END
+ RETURN offset # -1
+END Enabled;
+
+
+PROCEDURE LoadFont* (fname: ARRAY OF CHAR): FONT;
+VAR
+ font: FONT;
+ c: INTEGER;
+ ptr: INTEGER;
+
+BEGIN
+ NEW(font);
+ IF font # NIL THEN
+ font.file.data := File.Load(fname, font.file.size);
+ IF font.file.data # 0 THEN
+ ptr := KOSAPI.malloc(font.file.size + 4096);
+ IF ptr # 0 THEN
+
+ sys.MOVE(font.file.data, ptr, font.file.size);
+ font.file.data := KOSAPI.sysfunc3(68, 13, font.file.data);
+ font.file.data := ptr;
+
+ font.file.pos := 0;
+ COPY(fname, font.file.name);
+
+ FOR c := 0 TO 255 DO
+ font.chars[c] := LISTS.create(NIL);
+ font.smooth[c] := LISTS.create(NIL);
+ font.width[c] := 0;
+ font.height := 0
+ END
+
+ ELSE
+ font.file.data := KOSAPI.sysfunc3(68, 13, font.file.data);
+ DISPOSE(font)
+ END
+
+ ELSE
+ DISPOSE(font)
+ END
+ END
+
+ RETURN font
+END LoadFont;
+
+
+PROCEDURE Destroy* (VAR font: FONT);
+VAR
+ c: INTEGER;
+
+BEGIN
+ IF font # NIL THEN
+ FOR c := 0 TO 255 DO
+ LISTS.destroy(font.chars[c]);
+ LISTS.destroy(font.smooth[c]);
+ END;
+ IF font.file.data # 0 THEN
+ font.file.data := KOSAPI.sysfunc3(68, 13, font.file.data)
+ END;
+ DISPOSE(font)
+ END
+END Destroy;
+
+
+PROCEDURE SetSize* (VAR font: FONT; size: INTEGER): BOOLEAN;
+VAR
+ res: BOOLEAN;
+ fname: FNAME;
+
+BEGIN
+ IF Enabled(font, size) THEN
+ fname := font.file.name;
+ Destroy(font);
+ font := LoadFont(fname);
+ process(font, size - 8);
+ res := TRUE
+ ELSE
+ res := FALSE
+ END
+ RETURN res
+END SetSize;
+
+
+END kfonts.
diff --git a/programs/other/fb2reader/SRC/tables.ob07 b/programs/other/fb2reader/SRC/tables.ob07
new file mode 100644
index 0000000000..4b6641acd7
--- /dev/null
+++ b/programs/other/fb2reader/SRC/tables.ob07
@@ -0,0 +1,256 @@
+(*
+ Copyright 2016 Anton Krotov
+
+ This file is part of fb2read.
+
+ fb2read is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ fb2read is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with fb2read. If not, see .
+*)
+
+MODULE tables;
+
+IMPORT V := Vector;
+
+
+TYPE
+
+
+ Int = POINTER TO RECORD (V.ANYREC) value: INTEGER END;
+
+ Cell = POINTER TO RECORD (V.ANYREC) top, bottom, left, right, colspan, rowspan: INTEGER END;
+
+ Tab = POINTER TO RECORD (V.ANYREC) col, row: INTEGER END;
+
+ Table* = POINTER TO RECORD
+
+ tab : V.VECTOR;
+ h_lines : V.VECTOR;
+ v_lines : V.VECTOR;
+ cells* : V.VECTOR;
+
+ tab_x, tab_y, max_length: INTEGER
+
+ END;
+
+
+PROCEDURE GetCell (t: Table; cell: INTEGER): Cell;
+VAR any: V.ANYPTR;
+BEGIN
+ any := V.get(t.cells, cell)
+ RETURN any(Cell)
+END GetCell;
+
+
+PROCEDURE GetInt (v: V.VECTOR; idx: INTEGER): INTEGER;
+VAR any: V.ANYPTR;
+BEGIN
+ any := V.get(v, idx)
+ RETURN any(Int).value
+END GetInt;
+
+
+PROCEDURE PutInt (v: V.VECTOR; idx, value: INTEGER);
+VAR any: V.ANYPTR;
+BEGIN
+ any := V.get(v, idx);
+ any(Int).value := value
+END PutInt;
+
+
+PROCEDURE PushInt (v: V.VECTOR; value: INTEGER);
+VAR int: Int;
+BEGIN
+ NEW(int);
+ int.value := value;
+ V.push(v, int)
+END PushInt;
+
+
+PROCEDURE get_tab_xy (t: Table; x, y: INTEGER): BOOLEAN;
+VAR i: INTEGER;
+ tab: Tab; any: V.ANYPTR;
+ res: BOOLEAN;
+BEGIN
+ res := FALSE;
+ i := 0;
+ WHILE (i < t.tab.count) & ~res DO
+ any := V.get(t.tab, i);
+ tab := any(Tab);
+ res := (tab.col = x) & (tab.row = y);
+ INC(i)
+ END
+ RETURN res
+END get_tab_xy;
+
+
+PROCEDURE set_tab_xy (t: Table; x, y: INTEGER);
+VAR tab: Tab;
+BEGIN
+ NEW(tab);
+ tab.col := x;
+ tab.row := y;
+ V.push(t.tab, tab)
+END set_tab_xy;
+
+
+PROCEDURE tr* (t: Table);
+BEGIN
+ INC(t.tab_y);
+ WHILE t.h_lines.count < t.tab_y + 10 DO
+ PushInt(t.h_lines, 0)
+ END;
+ t.tab_x := 0;
+ WHILE get_tab_xy(t, t.tab_x, t.tab_y) DO
+ INC(t.tab_x);
+ WHILE t.v_lines.count < t.tab_x + 10 DO
+ PushInt(t.v_lines, 0)
+ END
+ END
+END tr;
+
+
+PROCEDURE td* (t: Table; colspan, rowspan: INTEGER);
+VAR i, j: INTEGER; _cell: Cell;
+BEGIN
+ FOR i := t.tab_x TO t.tab_x + colspan - 1 DO
+ FOR j := t.tab_y TO t.tab_y + rowspan - 1 DO
+ set_tab_xy(t, i, j);
+ IF i > t.max_length THEN
+ t.max_length := i
+ END
+ END
+ END;
+ NEW(_cell);
+ _cell.left := t.tab_x;
+ _cell.top := t.tab_y;
+ _cell.right := t.tab_x + colspan;
+ WHILE t.v_lines.count < _cell.right + 10 DO
+ PushInt(t.v_lines, 0)
+ END;
+ _cell.bottom := t.tab_y + rowspan;
+ WHILE t.h_lines.count < _cell.bottom + 10 DO
+ PushInt(t.h_lines, 0)
+ END;
+ _cell.colspan := colspan;
+ _cell.rowspan := rowspan;
+ V.push(t.cells, _cell);
+ WHILE get_tab_xy(t, t.tab_x, t.tab_y) DO
+ INC(t.tab_x);
+ WHILE t.v_lines.count < t.tab_x + 10 DO
+ PushInt(t.v_lines, 0)
+ END
+ END
+END td;
+
+
+PROCEDURE set_width* (t: Table; cell, width: INTEGER);
+VAR left, right, old_width, d_width, i: INTEGER; _cell: Cell;
+BEGIN
+ _cell := GetCell(t, cell);
+ right := GetInt(t.v_lines, _cell.right);
+ left := GetInt(t.v_lines, _cell.left);
+ old_width := right - left;
+ d_width := width - old_width;
+ PutInt(t.v_lines, _cell.right, left + width);
+ FOR i := _cell.right + 1 TO t.v_lines.count - 1 DO
+ PutInt(t.v_lines, i, GetInt(t.v_lines, i) + d_width)
+ END
+END set_width;
+
+
+PROCEDURE set_height* (t: Table; cell, height: INTEGER);
+VAR top, bottom, old_height, d_height, i: INTEGER; _cell: Cell;
+BEGIN
+ _cell := GetCell(t, cell);
+ top := GetInt(t.h_lines, _cell.top);
+ bottom := GetInt(t.h_lines, _cell.bottom);
+ old_height := bottom - top;
+ d_height := height - old_height;
+ PutInt(t.h_lines, _cell.bottom, top + height);
+ FOR i := _cell.bottom + 1 TO t.h_lines.count - 1 DO
+ PutInt(t.h_lines, i, GetInt(t.h_lines, i) + d_height)
+ END
+END set_height;
+
+
+PROCEDURE get_height* (t: Table; cell: INTEGER): INTEGER;
+VAR _cell: Cell;
+BEGIN
+ _cell := GetCell(t, cell)
+ RETURN GetInt(t.h_lines, _cell.bottom) - GetInt(t.h_lines, _cell.top)
+END get_height;
+
+
+PROCEDURE get_width* (t: Table; cell: INTEGER): INTEGER;
+VAR _cell: Cell;
+BEGIN
+ _cell := GetCell(t, cell)
+ RETURN GetInt(t.v_lines, _cell.right) - GetInt(t.v_lines, _cell.left)
+END get_width;
+
+
+PROCEDURE get_x* (t: Table; cell: INTEGER): INTEGER;
+VAR _cell: Cell;
+BEGIN
+ _cell := GetCell(t, cell)
+ RETURN GetInt(t.v_lines, _cell.left)
+END get_x;
+
+
+PROCEDURE get_y* (t: Table; cell: INTEGER): INTEGER;
+VAR _cell: Cell;
+BEGIN
+ _cell := GetCell(t, cell)
+ RETURN GetInt(t.h_lines, _cell.top)
+END get_y;
+
+
+PROCEDURE get_table_height* (t: Table): INTEGER;
+ RETURN GetInt(t.h_lines, t.tab_y + 1)
+END get_table_height;
+
+
+PROCEDURE table* (t: Table; tab_width: INTEGER; open: BOOLEAN);
+VAR i, width: INTEGER; _cell: Cell;
+BEGIN
+ IF open THEN
+ t.cells := V.create(1024);
+ t.v_lines := V.create(1024);
+ t.h_lines := V.create(1024);
+ t.tab := V.create(1024);
+ t.tab_x := 0;
+ t.tab_y := -1;
+ t.max_length := 0;
+ ELSE
+ width := tab_width DIV (t.max_length + 1);
+ FOR i := 0 TO t.cells.count - 1 DO
+ _cell := GetCell(t, i);
+ set_width(t, i, width * _cell.colspan)
+ END
+ END
+END table;
+
+
+PROCEDURE destroy* (t: Table);
+BEGIN
+ IF t # NIL THEN
+ V.destroy(t.tab, NIL);
+ V.destroy(t.h_lines, NIL);
+ V.destroy(t.v_lines, NIL);
+ V.destroy(t.cells, NIL);
+ DISPOSE(t)
+ END
+END destroy;
+
+
+END tables.