Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64f89ca01e |
No files matched your search
@@ -1,55 +0,0 @@
|
||||
# Continuous integration
|
||||
|
||||
## `Build C-- programs` (`workflows/build.yaml`)
|
||||
|
||||
Runs on every push to `main` and on every pull request. It compiles
|
||||
every C-- program in the repository and fails if any target stops
|
||||
building, so a change that breaks compilation is caught before merge.
|
||||
|
||||
The compiler is not committed here. The job clones [KolibriOS/cmm], builds
|
||||
it with `Makefile.lin32` and puts the result on `PATH` together with its
|
||||
`c--.ini`, which is where the compiler looks for it. Building the tip of
|
||||
the compiler on every run is deliberate: a compiler change that breaks a
|
||||
program is caught here, before it is bumped into the OS tree.
|
||||
|
||||
The heavy lifting is in [`build.sh`](../build.sh):
|
||||
|
||||
* it walks every `Tupfile.lua` and reads the declared c-- rule, so the
|
||||
set of built programs stays in sync with the real `tup` build — add a
|
||||
program with a `Tupfile.lua` and CI picks it up automatically;
|
||||
* `tup.foreach_rule("*.c", …)` directories (`examples/`, `misc/`) build
|
||||
every source file, single-`tup.rule` directories build their one target;
|
||||
* `/D=AUTOBUILD` and the `/D=LANG_*` define are applied exactly as the
|
||||
`Tupfile.lua` requests;
|
||||
* a target is considered built only if c-- exits 0 **and** produced a
|
||||
non-empty `.com`; a crash or hang (the known heap-layout-dependent c--
|
||||
flakiness) is caught by a per-compile timeout and retried up to 4 times
|
||||
before being reported as a failure.
|
||||
|
||||
Language variants `LANG_ENG`, `LANG_RUS` and `LANG_SPA` build in parallel.
|
||||
|
||||
Built programs are collected under `dist/` named as KolibriOS ships them —
|
||||
lowercase and without the `.com` extension — and published per language
|
||||
(`cmm-programs-LANG_ENG` / `cmm-programs-LANG_RUS`), downloadable from the
|
||||
run's **Summary** page for 14 days. They are not packed with `kpack`; the
|
||||
OS build does that itself when it assembles an image.
|
||||
|
||||
### Running it locally
|
||||
|
||||
```sh
|
||||
# c-- on PATH:
|
||||
./build.sh LANG_ENG
|
||||
|
||||
# or point CMM at a compiler built from KolibriOS/cmm:
|
||||
CMM=/path/to/c--.exe sh build.sh LANG_ENG
|
||||
```
|
||||
|
||||
### Runner
|
||||
|
||||
The job uses the `kolibri-toolchain` runner, the same self-hosted runner
|
||||
label as the main [kolibrios] build. It needs a working `g++` and `make`
|
||||
to build the compiler, and must be able to run the resulting 32-bit i386
|
||||
binary (any x86-64 Linux with IA32 support).
|
||||
|
||||
[kolibrios]: https://git.kolibrios.org/KolibriOS/kolibrios
|
||||
[KolibriOS/cmm]: https://git.kolibrios.org/KolibriOS/cmm
|
||||
@@ -1,67 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# SPDX-FileCopyrightText: 2026 KolibriOS team
|
||||
|
||||
# Auto-build for cmm_apps: compile every C-- program on push and PR,
|
||||
# and fail if any program stops compiling. The compiler is built from
|
||||
# KolibriOS/cmm, so a compiler change that breaks a program shows up
|
||||
# here before it is bumped into the OS.
|
||||
|
||||
name: 'Build C-- programs'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: 'Build (${{ matrix.lang }})'
|
||||
runs-on: kolibri-toolchain
|
||||
# build each language variant in parallel; don't cancel the others on a failure
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
lang: [LANG_ENG, LANG_RUS, LANG_SPA]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
# Cloned and installed outside the workspace: the compiler repo carries a
|
||||
# Tupfile.lua of its own, and build.sh compiles every Tupfile.lua it finds
|
||||
# under the checkout. Paths carry the matrix key so the two language jobs
|
||||
# never share a directory on the runner.
|
||||
- name: Build and install C--
|
||||
env:
|
||||
BIN: /tmp/cmm-bin-${{ matrix.lang }}
|
||||
SRC: /tmp/cmm-src-${{ matrix.lang }}
|
||||
run: |
|
||||
set -eu
|
||||
rm -rf "$SRC" "$BIN"
|
||||
git clone --depth 1 https://git.kolibrios.org/KolibriOS/cmm.git "$SRC"
|
||||
make -C "$SRC" -f Makefile.lin32
|
||||
mkdir -p "$BIN"
|
||||
install -m 755 "$SRC/c--" "$BIN/c--"
|
||||
# the compiler looks for c--.ini in the current directory and then in
|
||||
# each PATH entry - never beside its own executable - so the config
|
||||
# has to go into a PATH directory or the build silently uses defaults
|
||||
install -m 644 "$SRC/c--.ini" "$BIN/c--.ini"
|
||||
echo "$BIN" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Build all C-- programs
|
||||
run: sh build.sh ${{ matrix.lang }}
|
||||
|
||||
# publish the compiled .com files so they can be downloaded from the
|
||||
# run's Summary page; upload even on failure to keep partial output
|
||||
- name: Upload built programs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cmm-programs-${{ matrix.lang }}
|
||||
path: dist
|
||||
retention-days: 14
|
||||
@@ -1,3 +0,0 @@
|
||||
# CI build output collected by build.sh
|
||||
/dist/
|
||||
.vm/
|
||||
+14
-15
@@ -1,5 +1,6 @@
|
||||
#define MEMSIZE 1024*60
|
||||
|
||||
#include "..\lib\strings.h"
|
||||
#include "..\lib\mem.h"
|
||||
#include "..\lib\gui.h"
|
||||
#include "..\lib\obj\proc_lib.h"
|
||||
#include "..\lib\patterns\simple_open_dialog.h"
|
||||
@@ -15,7 +16,7 @@
|
||||
#define WINW 570
|
||||
#define WINH 300
|
||||
#define CONX 28 //content X pos
|
||||
#define CONY 25 //content Y pos
|
||||
#define CONY 25 //content X pos
|
||||
|
||||
char default_dir[] = "/sys";
|
||||
od_filter filter2;
|
||||
@@ -36,15 +37,15 @@ od_filter filter2;
|
||||
?define CONTENT_HEADER_TEXT "KolibriOS работает в режиме дискеты"
|
||||
?define DESCRIPTION_TEXT "Многие приложения сейчас недоступны.
|
||||
|
||||
Причина в том, что не смонтирован дополнительный системный
|
||||
каталог. Но вы можете найти и задать его вручную. Его название
|
||||
'kolibrios'. Вы также можете загрузить latest-dirstr.7z через
|
||||
Причина в том, что не смонтирован дополнительный системный
|
||||
каталог. Но вы можете найти и задать его вручную. Его название
|
||||
'kolibrios'. Вы также можете загрузить latest-dirstr.7z через
|
||||
WebView и найти его внутри."
|
||||
?define MANUALLY_BUTTON_TEXT "Указать папку /kolibrios/..."
|
||||
?define OPEN_ANYWAY_BUTTON_TEXT "Запустить APP+ (некоторые программы будут недоступны)"
|
||||
#else
|
||||
?define WINDOW_TITLE_TEXT "Warning!"
|
||||
?define CONTENT_HEADER_TEXT "KolibriOS is running in floppy mode"
|
||||
?define CONTENT_HEADER_TEXT "KolibriOS is in a Floppy-mode now"
|
||||
?define DESCRIPTION_TEXT "A lot of apps aren't available.
|
||||
|
||||
The reason is that additional system directory is not mounted.
|
||||
@@ -52,7 +53,7 @@ But you can find and set it manually. Its name is 'kolibrios'.
|
||||
You can also download latest-dirstr.7z via WebView and find
|
||||
it inside."
|
||||
?define MANUALLY_BUTTON_TEXT "Choose /kolibrios/ folder..."
|
||||
?define OPEN_ANYWAY_BUTTON_TEXT "Open APP+ anyway (some programs won't be available)"
|
||||
?define OPEN_ANYWAY_BUTTON_TEXT "Open APP+ anyway (some programs won't be available) "
|
||||
#endif
|
||||
|
||||
//===================================================//
|
||||
@@ -105,7 +106,7 @@ void draw_window()
|
||||
WriteTextB(CONX+27+2,CONY+2,0x81,MixColors(sc.work, 0xB92234,220),CONTENT_HEADER_TEXT);
|
||||
WriteTextB(CONX+27,CONY,0x81,0xB92234,CONTENT_HEADER_TEXT);
|
||||
WriteTextLines(CONX,CONY+42,0x90,sc.work_text,DESCRIPTION_TEXT,20);
|
||||
DrawButtons();
|
||||
DrawButtons();
|
||||
}
|
||||
|
||||
void DrawButtons()
|
||||
@@ -116,7 +117,7 @@ void DrawButtons()
|
||||
|
||||
void CheckKosMounted()
|
||||
{
|
||||
if (dir_exists("/kolibrios"))
|
||||
if (dir_exists("/kolibrios"))
|
||||
{
|
||||
if (file_exists(APP_PLUS_INI_PATH)) EventOpenApp();
|
||||
ExitProcess();
|
||||
@@ -139,13 +140,13 @@ void EventManualSearch()
|
||||
OpenDialog_start stdcall (#o_dialog);
|
||||
if (o_dialog.status) SetAdditionalSystemDirectory("kolibrios", #openfile_path+1);
|
||||
pause(3);
|
||||
CheckKosMounted();
|
||||
CheckKosMounted();
|
||||
}
|
||||
|
||||
void EventOpenApp()
|
||||
{
|
||||
if (RunProgram("/sys/syspanel", APP_PLUS_INI_PATH) < 0) {
|
||||
notify("'App+ cannot start because\n/sys/syspanel does not exist' -E");
|
||||
notify("'App+ can not be started because\n/sys/syspanel does not exists' -E");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,10 +154,8 @@ void EventButton(dword id)
|
||||
{
|
||||
if (id==CLOSE_BTN) ExitProcess();
|
||||
else if (id==BTN_MANUAL_SEARCH) EventManualSearch();
|
||||
else if (id==BTN_OPEN_ANYWAY) {
|
||||
EventOpenApp();
|
||||
ExitProcess();
|
||||
}
|
||||
else if (id==BTN_OPEN_ANYWAY) { EventOpenApp(); ExitProcess(); }
|
||||
}
|
||||
|
||||
|
||||
stop:
|
||||
+2
-4
@@ -89,7 +89,7 @@ checkbox tbMenuButton = { MENU_BUTTON };
|
||||
checkbox dkFsize = { FSIZE };
|
||||
checkbox dkAshow = { ASHOW };
|
||||
|
||||
int dkLocation=0;
|
||||
int dkLocation;
|
||||
checkbox tbRunApplButton;
|
||||
checkbox tbClnDeskButton;
|
||||
|
||||
@@ -174,7 +174,6 @@ void DrawWindowContent()
|
||||
dword frame_y;
|
||||
word win_center_x = Form.cwidth / 2 + 20;
|
||||
incn y;
|
||||
int i;
|
||||
|
||||
SetDisabledMode();
|
||||
|
||||
@@ -199,8 +198,7 @@ void DrawWindowContent()
|
||||
//DOCKY
|
||||
frame_y = calc(y.inc(20));
|
||||
DefineButton(22, y.inc(18), PIMG_W-1, PIMG_H-1, 200 + BT_HIDE, 0);
|
||||
if (!dkLocation) i=2; else i=dkLocation;
|
||||
DrawPanelsImage(y.n, i+1);
|
||||
DrawPanelsImage(y.n, dkLocation+1);
|
||||
WriteTextWithBg(68, y.inc(7), 0xD0, sc.work_text, CHANGE_POS, sc.work);
|
||||
dkFsize.draw(22, y.inc(35));
|
||||
dkAshow.draw(win_center_x, y.n);
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
if tup.getconfig("NO_CMM") ~= "" then return end
|
||||
if tup.getconfig("LANG") == "ru_RU"
|
||||
then C_LANG = "LANG_RUS"
|
||||
else C_LANG = "LANG_ENG" -- this includes default case without config
|
||||
end
|
||||
tup.rule("bbench.c", "c-- /D=$(C_LANG) /OPATH=%o %f" .. tup.getconfig("KPACK_CMD"), "bbench.com")
|
||||
-616
@@ -1,616 +0,0 @@
|
||||
//========================================================//
|
||||
// //
|
||||
// BirdBench - a modular system benchmark //
|
||||
// The window is a launcher: tick the tests you want, //
|
||||
// press Run - results open as an HTML report in //
|
||||
// WebView (clearer there than in the tiny window). //
|
||||
// //
|
||||
// Add a test : edit tests_cpu.h / tests_gpu.h / ... //
|
||||
// Add a rival : edit cpudb.h //
|
||||
// //
|
||||
//========================================================//
|
||||
|
||||
#define MEMSIZE 4096*1536 // ~6 MB heap (2+2 MB work buffers + canvas)
|
||||
|
||||
#include "../lib/kolibri.h" // must be first (sets entry point)
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/strings.h"
|
||||
#include "../lib/fs.h"
|
||||
#include "../lib/io.h"
|
||||
#include "../lib/gui.h"
|
||||
#include "../lib/system.h"
|
||||
#include "../lib/gui/checkbox.h"
|
||||
#include "../lib/gui/menu.h"
|
||||
|
||||
#include "bbench.h"
|
||||
#include "tests_cpu.h"
|
||||
#include "tests_gpu.h"
|
||||
#include "tests_disk.h"
|
||||
#include "cpudb.h"
|
||||
|
||||
//---------------- layout ----------------//
|
||||
#define PAD 16
|
||||
#define COL2_X 170 // second column (Graphics)
|
||||
#define COL3_X 328 // third column (Disk)
|
||||
#define HEADER_H 42
|
||||
#define ROW_H 22
|
||||
#define WIN_W GCV_W
|
||||
#define WIN_H GCV_H + HEADER_H
|
||||
|
||||
#define BUTTON_Y WIN_H - 62
|
||||
#define BUTTON_W 70
|
||||
#define BUTTON_H 24
|
||||
|
||||
#define STATUSBAR_Y WIN_H - 25
|
||||
|
||||
#define BAR_FULL 3000 // score that fills a full HTML bar
|
||||
|
||||
#define BTN_RUN 30
|
||||
#define BTN_ALL 33
|
||||
#define BTN_NONE 34
|
||||
#define BTN_DISK_DROP 39 // disk dropdown toggle
|
||||
#define MAX_DISKS 20
|
||||
#define BTN_SECT_BASE 50 // 50..52 section masters
|
||||
#define BTN_CHECK_BASE 60 // 60.. per-test checkboxes
|
||||
|
||||
checkbox cb; // reused as a renderer for every checkbox
|
||||
|
||||
byte t_enabled[MAX_TESTS];
|
||||
dword sums[SECT_NUM];
|
||||
dword cnts[SECT_NUM];
|
||||
|
||||
char sysinfo[] = "CPU: %d MHz RAM: %d MB Screen: %dx%d@%db";
|
||||
char revinfo[] = " BB Revision: %d";
|
||||
|
||||
//---------------- disk list (LMENU dropdown) ----------------//
|
||||
char disk_store[MAX_DISKS*24];
|
||||
dword disk_dirp[MAX_DISKS];
|
||||
int disk_count = 0;
|
||||
int disk_sel = 0;
|
||||
byte disk_menu_open = 0; // an LMENU popup is currently open
|
||||
char disk_menu[MAX_DISKS*26]; // "\n"-separated item list for /sys/develop/menu
|
||||
dword disk_drop_x, disk_drop_y;
|
||||
char probe_buf[16];
|
||||
|
||||
int DiskWritable(dword dir)
|
||||
{
|
||||
char pf[80];
|
||||
dword r;
|
||||
strcpy(#pf, dir); strcat(#pf, "/bbprobe.tmp");
|
||||
r = FileWrite(#pf, #probe_buf, 16);
|
||||
if (r==0) dk_delete(#pf);
|
||||
if (r==0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AddDisk(dword dir)
|
||||
{
|
||||
dword slot;
|
||||
if (disk_count >= MAX_DISKS) return;
|
||||
slot = disk_count * 24; slot = slot + #disk_store;
|
||||
strcpy(slot, dir);
|
||||
disk_dirp[disk_count] = slot;
|
||||
disk_count++;
|
||||
}
|
||||
|
||||
void TryDisk(dword dir) { if (DiskWritable(dir)) AddDisk(dir); }
|
||||
|
||||
void ScanDisks() // enumerate real mounts under "/", keep writable
|
||||
{
|
||||
int i, j;
|
||||
dword nm, l;
|
||||
char base[48];
|
||||
char dig[2];
|
||||
byte c0, c1, skip;
|
||||
disk_count = 0;
|
||||
dig[1] = 0;
|
||||
io.dir.load("/", DIR_ONLYREAL);
|
||||
for (i=0; i<io.dir.count; i++) {
|
||||
nm = io.dir.position(i);
|
||||
c0 = DSBYTE[nm]; c1 = DSBYTE[nm+1];
|
||||
skip = 0;
|
||||
l = strlen(nm);
|
||||
if (l > 12) skip = 1; // path slots are fixed 24 bytes
|
||||
if (c0=='c') && (c1=='d') skip = 1; // CD (read-only)
|
||||
if (c0=='f') && (c1=='d') skip = 1; // floppy
|
||||
if (c0=='r') && (c1=='d') skip = 1; // ramdisk /rd (system)
|
||||
if (!skip) {
|
||||
// whole-disk FS (rare: unpartitioned media)
|
||||
strcpy(#base, "/"); strcat(#base, nm);
|
||||
TryDisk(#base);
|
||||
// probe partitions /1../9 - the writable one is NOT always #1
|
||||
// (e.g. #1 = EFI/NTFS-reserved, data partition = #2 or #3)
|
||||
for (j=1; j<=9; j++) {
|
||||
dig[0] = j + '0';
|
||||
strcpy(#base, "/"); strcat(#base, nm);
|
||||
strcat(#base, "/"); strcat(#base, #dig);
|
||||
TryDisk(#base);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (disk_count < 1) AddDisk("/tmp0/1");
|
||||
disk_sel = 0;
|
||||
disk_dir = disk_dirp[0];
|
||||
disk_menu[0] = 0; // "\n"-joined paths for the LMENU app
|
||||
for (i=0; i<disk_count; i++) {
|
||||
strcat(#disk_menu, disk_dirp[i]);
|
||||
if (i < disk_count-1) strcat(#disk_menu, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
//---------------- helpers ----------------//
|
||||
void bench_exit() // single exit point: never leave bbtst.tmp behind
|
||||
{
|
||||
Disk_Cleanup();
|
||||
ExitProcess();
|
||||
}
|
||||
|
||||
void FlatButton(dword x, id, bg, tc, label)
|
||||
{
|
||||
DefineButton(x, BUTTON_Y, BUTTON_W, BUTTON_H - 1, id, bg);
|
||||
WriteText(-strlen(label)*8 + BUTTON_W / 2 + x,
|
||||
BUTTON_H - 16 / 2 + BUTTON_Y, 0x90, tc, label);
|
||||
}
|
||||
|
||||
//---------------- checkbox model ----------------//
|
||||
byte SectionAllOn(dword sect)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<t_count; i++) if (t_sect[i]==sect) && (!t_enabled[i]) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ToggleSection(dword sect)
|
||||
{
|
||||
int i;
|
||||
byte v;
|
||||
v = SectionAllOn(sect); v = v ^ 1;
|
||||
for (i=0; i<t_count; i++) if (t_sect[i]==sect) t_enabled[i] = v;
|
||||
}
|
||||
|
||||
void SetAll(byte v)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<t_count; i++) t_enabled[i] = v;
|
||||
}
|
||||
|
||||
//========================================================//
|
||||
// HTML export //
|
||||
//========================================================//
|
||||
#define ICON_CPU 48
|
||||
#define ICON_GPU 52
|
||||
#define ICON_DISK 50
|
||||
|
||||
#define EBUF_SIZE 16384 // report buffer; a full 48-test run needs ~6 KB
|
||||
#define BAR_W 26 // bar width in characters, same for every row
|
||||
|
||||
#define COL_NAME 20 // same columns for test and compare rows, so
|
||||
#define COL_SCORE 8 // numbers and bars line up down the whole page
|
||||
#define COL_INFO 16
|
||||
|
||||
// one copy of each colour; html_head repeats them for browsers that honour
|
||||
// <style> - WebView reads bg= straight off the tag
|
||||
char c_bar1[] = "#2c7be5"; char c_trk1[] = "#cdd2da";
|
||||
char c_bar2[] = "#4a90e2"; char c_trk2[] = "#e4e7ec";
|
||||
char c_cmp [] = "#8fb8e8";
|
||||
|
||||
dword ebuf = 0;
|
||||
dword epos = 0;
|
||||
char etmp[176];
|
||||
char epath[64];
|
||||
|
||||
void AppendBcd(dword dst, byte b) // BCD byte -> 2 ASCII digits
|
||||
{
|
||||
dword hi, lo;
|
||||
hi = b >> 4; hi = hi & 0xF;
|
||||
lo = b & 0xF;
|
||||
DSBYTE[dst] = hi + '0';
|
||||
DSBYTE[dst+1] = lo + '0';
|
||||
}
|
||||
|
||||
void BuildExportPath() // unique name from date + time
|
||||
{
|
||||
dword dt, tm, adr, v;
|
||||
EAX = 29; $int 0x40; dt = EAX; // fn 29: 0x00DDMMYY (BCD)
|
||||
EAX = 3; $int 0x40; tm = EAX; // fn 3: 0x00SSMMHH (BCD)
|
||||
strcpy(#epath, "/tmp0/1/bb_");
|
||||
adr = #epath; adr = adr + strlen(#epath);
|
||||
v = dt; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // YY
|
||||
v = dt >> 8; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // MM
|
||||
v = dt >> 16; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // DD
|
||||
DSBYTE[adr] = '_'; adr = adr + 1;
|
||||
v = tm; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // HH
|
||||
v = tm >> 8; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // MM
|
||||
v = tm >> 16; v = v & 0xFF; AppendBcd(adr, v); adr = adr + 2; // SS
|
||||
DSBYTE[adr] = 0;
|
||||
strcat(#epath, ".htm");
|
||||
}
|
||||
|
||||
void Hput(dword s)
|
||||
{
|
||||
dword l = strlen(s);
|
||||
if (epos + l >= EBUF_SIZE) return; // truncate the report, never the heap
|
||||
strcpy(ebuf+epos, s);
|
||||
epos = epos + l;
|
||||
}
|
||||
|
||||
void Hfield(dword s, dword width)
|
||||
{
|
||||
dword l = strlen(s);
|
||||
Hput(s);
|
||||
while (l < width) { Hput(" "); l++; }
|
||||
}
|
||||
|
||||
// one bar for test rows (max = BAR_FULL) and compare rows (max = fastest CPU)
|
||||
void Hbar(dword val, max, fillcol, trackcol)
|
||||
{
|
||||
dword filled, track, i;
|
||||
if (max < 1) max = 1;
|
||||
filled = muldiv(val, BAR_W, max); if (filled > BAR_W) filled = BAR_W;
|
||||
track = BAR_W - filled;
|
||||
Hput("<font bg="); Hput(fillcol); Hput(">");
|
||||
for (i=0; i<filled; i++) Hput(" ");
|
||||
Hput("</font><font bg="); Hput(trackcol); Hput(">");
|
||||
for (i=0; i<track; i++) Hput(" ");
|
||||
Hput("</font>\n");
|
||||
}
|
||||
|
||||
void ExportSection(dword sect, title, icon)
|
||||
{
|
||||
int i, r;
|
||||
dword whole, frac, barcol, trkcol;
|
||||
if (!SectionRan(sect)) return; // no completed test here -> no header at all
|
||||
if (sect==SECT_DISK) // show which disk was actually tested
|
||||
sprintf(#etmp, "<h3><kosicon n=%d><b>%s</b> (%s): %d</h3><blockquote>\n", icon, title, disk_dir, sect_score[sect]);
|
||||
else
|
||||
sprintf(#etmp, "<h3><kosicon n=%d><b>%s</b>: %d</h3><blockquote>\n", icon, title, sect_score[sect]);
|
||||
Hput(#etmp);
|
||||
r = 0;
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_sect[i]==sect) {
|
||||
if (t_done[i]==2) { // not applicable here
|
||||
Hfield(t_name[i], COL_NAME);
|
||||
Hfield("skipped", COL_SCORE);
|
||||
Hput("\n");
|
||||
}
|
||||
if (t_done[i]==1) {
|
||||
whole = t_raw[i] / 100; frac = t_raw[i] % 100;
|
||||
if (r & 1) { barcol = #c_bar2; trkcol = #c_trk2; }
|
||||
else { barcol = #c_bar1; trkcol = #c_trk1; }
|
||||
Hfield(t_name[i], COL_NAME);
|
||||
sprintf(#etmp, "%d", t_score[i]); Hfield(#etmp, COL_SCORE);
|
||||
if (frac < 10) sprintf(#etmp, "%d.0%d %s", whole, frac, t_unit[i]);
|
||||
else sprintf(#etmp, "%d.%d %s", whole, frac, t_unit[i]);
|
||||
Hfield(#etmp, COL_INFO);
|
||||
Hbar(t_score[i], BAR_FULL, barcol, trkcol);
|
||||
r++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Hput("</blockquote>\n");
|
||||
}
|
||||
|
||||
void CompareRow(dword name, mhz, score, maxscore, fillcol, trkcol)
|
||||
{
|
||||
char sb[20];
|
||||
Hfield(name, COL_NAME);
|
||||
sprintf(#sb, "%d", score); Hfield(#sb, COL_SCORE);
|
||||
sprintf(#sb, "%d MHz", mhz); Hfield(#sb, COL_INFO);
|
||||
Hbar(score, maxscore, fillcol, trkcol);
|
||||
}
|
||||
|
||||
#ifdef BB_CALIB
|
||||
// Ready-to-paste block: without it calibration means transcribing every
|
||||
// number by hand, so nobody does it and the REF_ values stay guesses.
|
||||
void ExportCalib()
|
||||
{
|
||||
int i;
|
||||
Hput("<h3><b>Calibration</b></h3><blockquote>\n");
|
||||
Hput("This machine's metrics. Put each one in the matching REF_ define to\n");
|
||||
Hput("score it 1000 everywhere, then bump BB_REVISION and re-measure cpudb.\n\n");
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_done[i]==1) {
|
||||
Hfield(t_name[i], COL_NAME);
|
||||
sprintf(#etmp, "%d", t_raw[i]); Hput(#etmp);
|
||||
Hput("\n");
|
||||
}
|
||||
}
|
||||
if (SectionRan(SECT_CPU)) {
|
||||
sprintf(#etmp, "\ncpudb_add(\"CPU name here\", %d, %d, %d);\n",
|
||||
sys_cpu_mhz, sect_score[SECT_CPU], BB_REVISION);
|
||||
Hput(#etmp);
|
||||
}
|
||||
Hput("</blockquote>\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
void ExportCompare()
|
||||
{
|
||||
int i;
|
||||
dword mx, thisc;
|
||||
thisc = sect_score[SECT_CPU];
|
||||
Hput("<h3><kosicon n=47><b>CPU compare</b></h3><blockquote>\n");
|
||||
mx = thisc;
|
||||
for (i=0; i<cpudb_count; i++) if (cpudb_score[i] > mx) mx = cpudb_score[i];
|
||||
if (mx < 1) mx = 1;
|
||||
CompareRow("This PC", sys_cpu_mhz, thisc, mx, #c_bar1, #c_trk1);
|
||||
for (i=0; i<cpudb_count; i++) {
|
||||
if (i < 8) CompareRow(cpudb_name[i], cpudb_mhz[i], cpudb_score[i], mx, #c_cmp, #c_trk2);
|
||||
}
|
||||
Hput("</blockquote>\n");
|
||||
}
|
||||
|
||||
// styles for Chrome/Firefox (WebView ignores <style>, uses bg= directly)
|
||||
char html_head[] = "</title><style>h3{margin:0}
|
||||
font{margin-bottom:1px;display:inline-block;}
|
||||
font[bg='#2c7be5']{background:#2c7be5}font[bg='#4a90e2']{background:#4a90e2}
|
||||
font[bg='#8fb8e8']{background:#8fb8e8}font[bg='#cdd2da']{background:#cdd2da}
|
||||
font[bg='#e4e7ec']{background:#e4e7ec}
|
||||
</style><body bgcolor=#ffffff><pre><h2>BirdBench results</h2>";
|
||||
|
||||
void ExportHTML()
|
||||
{
|
||||
dword sp, fname;
|
||||
if (!ebuf) ebuf = malloc(EBUF_SIZE);
|
||||
if (!ebuf) return;
|
||||
BuildExportPath(); // need the name for <title>
|
||||
sp = strrchr(#epath, '/');
|
||||
fname = #epath + sp; // -> "bb_YYMMDD_HHMMSS.htm"
|
||||
epos = 0;
|
||||
DSBYTE[ebuf] = 0;
|
||||
Hput("<html><title>"); Hput(fname);
|
||||
Hput(#html_head);
|
||||
Hput("<font color=#86868b>");
|
||||
sprintf(#etmp, #sysinfo, sys_cpu_mhz, sys_ram_mb, screen.w, screen.h, sys_bpp);
|
||||
Hput(#etmp);
|
||||
sprintf(#etmp, #revinfo, BB_REVISION);
|
||||
Hput(#etmp);
|
||||
Hput("</font>\n\n");
|
||||
ExportSection(SECT_CPU, "CPU", ICON_CPU); // each self-guards on t_done
|
||||
ExportSection(SECT_GPU, "Graphics", ICON_GPU);
|
||||
ExportSection(SECT_DISK, "Disk", ICON_DISK);
|
||||
if (SectionRan(SECT_CPU)) ExportCompare();
|
||||
#ifdef BB_CALIB
|
||||
ExportCalib();
|
||||
#endif
|
||||
Hput("</pre></body></html>");
|
||||
FileWrite(#epath, ebuf, epos);
|
||||
RunProgram("/sys/network/webview", #epath);
|
||||
}
|
||||
|
||||
//========================================================//
|
||||
// the panel //
|
||||
//========================================================//
|
||||
void DrawDiskButton(dword x, y) // combobox trigger (Eolite style) -> opens LMENU
|
||||
{
|
||||
#define DISK_BW 100
|
||||
#define DISK_BH 18
|
||||
dword ax;
|
||||
disk_drop_x = x; disk_drop_y = y;
|
||||
ax = x + DISK_BW - DISK_BH; // arrow-cell left
|
||||
// sunken field with a light background
|
||||
DrawRectangle3D(x, y, DISK_BW-1, DISK_BH, sc.dark, sc.light);
|
||||
DrawBar(x+1, y+1, DISK_BW-2, DISK_BH-1, sc.light);
|
||||
WriteText(x+5, y+3, 0x90, sc.work_text, disk_dirp[disk_sel]);
|
||||
// raised arrow button on the right
|
||||
DrawRectangle3D(x-1, y-1, DISK_BW+1, DISK_BH+2, sc.line, sc.line);
|
||||
DrawRectangle3D(ax-1, y-1, DISK_BH+1, DISK_BH+2, sc.line, sc.line);
|
||||
DrawRectangle3D(ax, y, DISK_BH-1, DISK_BH, sc.light, sc.dark);
|
||||
DrawBar(ax+1, y+1, DISK_BH-2, DISK_BH-1, sc.work);
|
||||
WriteText(ax+6, y+6, 0x80, sc.work_text, "\x19"); // CP866 down arrow
|
||||
DefineHiddenButton(x, y, DISK_BW-1, DISK_BH, BTN_DISK_DROP);
|
||||
}
|
||||
|
||||
dword DrawSectionChecks(dword sect, title, x, y)
|
||||
{
|
||||
int i;
|
||||
cb.disabled = 0;
|
||||
cb.text = title;
|
||||
cb.checked = SectionAllOn(sect);
|
||||
cb.id = BTN_SECT_BASE + sect;
|
||||
cb.draw(x, y);
|
||||
y = y + 30; // bigger gap after the master (the first)
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_sect[i]==sect) {
|
||||
cb.disabled = 0;
|
||||
cb.text = t_name[i];
|
||||
cb.checked = t_enabled[i];
|
||||
cb.id = BTN_CHECK_BASE + i;
|
||||
cb.draw(x, y); // aligned with the master (no extra indent)
|
||||
y = y + ROW_H;
|
||||
}
|
||||
}
|
||||
DrawBar(x-1, HEADER_H+32, math.min(140, WIN_W-x-10), 1, sc.light); // line under the top (master) checkboxes
|
||||
DrawBar(x-1, HEADER_H+33, math.min(140, WIN_W-x-10), 1, sc.dark); // line under the top (master) checkboxes
|
||||
if (sect==SECT_DISK) { // dropdown under bottom checkbox
|
||||
DrawDiskButton(x, y+2); y = y + 24;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
void DrawPanel()
|
||||
{
|
||||
char line[96];
|
||||
// Icons
|
||||
draw_icon_32(67, 6, sc.work, 37); // CPU chip, centered over column 1
|
||||
draw_icon_32(225, 6, sc.work, 27); // Graphics card, over column 2
|
||||
draw_icon_32(362, 6, sc.work, 50); // Disk, over column 3
|
||||
// Main Checkboxes
|
||||
DrawSectionChecks(SECT_CPU, "CPU", PAD, HEADER_H + 10);
|
||||
DrawSectionChecks(SECT_GPU, "Graphics", COL2_X, HEADER_H + 10);
|
||||
DrawSectionChecks(SECT_DISK, "Disk", COL3_X, HEADER_H + 10);
|
||||
// Buttons
|
||||
FlatButton(PAD, BTN_ALL, sc.work, sc.work_text, "All");
|
||||
FlatButton(PAD + BUTTON_W + 9, BTN_NONE, sc.work, sc.work_text, "None");
|
||||
FlatButton(WIN_W - BUTTON_W - PAD, BTN_RUN, 0x4A90E2, 0xFFFFFF, "Run");
|
||||
// Foter
|
||||
DrawBar(0, STATUSBAR_Y, WIN_W, 1, sc.light);
|
||||
DrawBar(0, STATUSBAR_Y+1, WIN_W, 1, sc.dark);
|
||||
sprintf(#line, #sysinfo, sys_cpu_mhz, sys_ram_mb, screen.w, screen.h, sys_bpp);
|
||||
WriteText(PAD, STATUSBAR_Y+7, 0x90, sc.work_text, #line);
|
||||
// Disk open
|
||||
}
|
||||
|
||||
proc_info Form; // global: menu.h reads Form.left/top for LMENU
|
||||
|
||||
void draw_window()
|
||||
{
|
||||
dword wx, wy;
|
||||
sc.get();
|
||||
RefreshScreen();
|
||||
wx = 0; if (screen.w > WIN_W) { wx = screen.w - WIN_W; wx = wx/2; }
|
||||
wy = 0; if (screen.h > WIN_H) { wy = screen.h - WIN_H; wy = wy/2; }
|
||||
DefineAndDrawWindow(wx, wy, WIN_W+9, WIN_H+skin_h+4, 0x34, sc.work, "BirdBench", 0);
|
||||
GetProcessInfo(#Form, SelfInfo);
|
||||
DrawPanel();
|
||||
}
|
||||
|
||||
//---------------- run flow ----------------//
|
||||
char bench_caption[] = "This Bird is Benching";
|
||||
|
||||
void DrawRunScreen() // once per run: clear the body + draw the mascot
|
||||
{
|
||||
dword tx;
|
||||
DrawBar(0, 0, WIN_W, STATUSBAR_Y, sc.work);
|
||||
draw_icon_32(WIN_W-32/2, 96, sc.work, 121);
|
||||
tx = strlen(#bench_caption)*8; // 0x90 = 8x16 font
|
||||
tx = WIN_W - tx; tx = tx/2;
|
||||
WriteText(tx, 140, 0x90, sc.work_text, #bench_caption);
|
||||
}
|
||||
|
||||
void DrawRunStatus(dword name, idx, total, sect)
|
||||
{
|
||||
char msg[80];
|
||||
dword icon;
|
||||
icon = ICON_CPU;
|
||||
if (sect==SECT_GPU) icon = ICON_GPU;
|
||||
if (sect==SECT_DISK) icon = ICON_DISK;
|
||||
DrawBar(0, 0, WIN_W, HEADER_H, sc.work); // header only
|
||||
draw_icon_16w(PAD, 11, icon); // section icon, left of text
|
||||
sprintf(#msg, "Running %s (%d/%d)", name, idx, total);
|
||||
WriteText(PAD+24, 13, 0x90, sc.work_text, #msg);
|
||||
}
|
||||
|
||||
// drain events queued while a test was running; returns 1 if ESC = abort
|
||||
byte AbortRequested()
|
||||
{
|
||||
dword ev, id;
|
||||
byte stop = 0;
|
||||
loop() {
|
||||
ev = CheckEvent();
|
||||
if (!ev) break;
|
||||
if (ev==evButton) {
|
||||
id = GetButtonID();
|
||||
if (id==1) bench_exit(); // window X pressed mid-run
|
||||
}
|
||||
if (ev==evKey) {
|
||||
GetKeys();
|
||||
if (key_scancode==SCAN_CODE_ESC) stop = 1;
|
||||
}
|
||||
if (ev==evReDraw) { draw_window(); DrawRunScreen(); } // keep the run look
|
||||
}
|
||||
return stop;
|
||||
}
|
||||
|
||||
void RunSelected()
|
||||
{
|
||||
int i, idx, total;
|
||||
dword s, v;
|
||||
byte aborted;
|
||||
total = 0;
|
||||
for (i=0; i<t_count; i++) if (t_enabled[i]) total++;
|
||||
if (total < 1) return;
|
||||
for (s=0; s<SECT_NUM; s++) { sums[s] = 0; cnts[s] = 0; }
|
||||
for (i=0; i<t_count; i++) t_done[i] = 0;
|
||||
// occlusion skews fn13/fn7/fn38/fn4 (the kernel only draws visible
|
||||
// parts) - keep the window on top for the duration of the run
|
||||
SetWindowLayerBehaviour(-1, ZPOS_ALWAYS_TOP);
|
||||
DrawRunScreen(); // the mascot, once per run
|
||||
aborted = 0;
|
||||
idx = 0;
|
||||
for (i=0; i<t_count; i++) {
|
||||
if (t_enabled[i]) {
|
||||
if (AbortRequested()) { aborted = 1; break; }
|
||||
idx++;
|
||||
DrawRunStatus(t_name[i], idx, total, t_sect[i]);
|
||||
RunOne(i);
|
||||
if (t_done[i]==1) { // skipped carry no score
|
||||
s = t_sect[i];
|
||||
v = t_score[i]; if (v < 1) v = 1;
|
||||
sums[s] = sums[s] + ilog2_16(v); // geometric mean accumulator
|
||||
cnts[s] = cnts[s] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Disk_Cleanup(); // remove the 8 MB temp file
|
||||
SetWindowLayerBehaviour(-1, ZPOS_NORMAL);
|
||||
// section score = geomean of its tests (robust to single-test outliers)
|
||||
for (s=0; s<SECT_NUM; s++) {
|
||||
if (cnts[s] > 0) { v = sums[s] / cnts[s]; sect_score[s] = iexp2_16(v); }
|
||||
else sect_score[s] = 0;
|
||||
}
|
||||
draw_window();
|
||||
if (!aborted) ExportHTML(); // auto-open the report (ESC = silent abort)
|
||||
}
|
||||
|
||||
//---------------- events ----------------//
|
||||
void HandleButton(dword id)
|
||||
{
|
||||
if (id==1) bench_exit();
|
||||
if (id==BTN_DISK_DROP) { // open the LMENU popup below the combobox
|
||||
open_lmenu(disk_drop_x, disk_drop_y+DISK_BH+1, MENU_TOP_LEFT, disk_sel+1, #disk_menu);
|
||||
disk_menu_open = 1;
|
||||
return;
|
||||
}
|
||||
if (id==BTN_RUN) RunSelected();
|
||||
if (id==BTN_ALL) { SetAll(1); draw_window(); }
|
||||
if (id==BTN_NONE) { SetAll(0); draw_window(); }
|
||||
if (id>=BTN_SECT_BASE) && (id<BTN_SECT_BASE+SECT_NUM) {
|
||||
ToggleSection(id - BTN_SECT_BASE);
|
||||
draw_window();
|
||||
}
|
||||
if (id>=BTN_CHECK_BASE) && (id<BTN_CHECK_BASE+MAX_TESTS) {
|
||||
t_enabled[id-BTN_CHECK_BASE] ^= 1;
|
||||
draw_window();
|
||||
}
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
int i;
|
||||
dword click;
|
||||
GetSysInfo();
|
||||
if (!BenchAllocBuffers()) {
|
||||
notify("'BirdBench\nNot enough memory for the work buffers!' -E");
|
||||
ExitProcess();
|
||||
}
|
||||
cpudb_init();
|
||||
Register_CPU();
|
||||
Register_GPU();
|
||||
Register_DISK();
|
||||
ScanDisks();
|
||||
for (i=0; i<t_count; i++) t_enabled[i] = 1;
|
||||
|
||||
loop() switch(WaitEvent())
|
||||
{
|
||||
case evButton:
|
||||
HandleButton(GetButtonID());
|
||||
break;
|
||||
case evKey:
|
||||
GetKeys();
|
||||
if (key_scancode==SCAN_CODE_ESC) bench_exit();
|
||||
if (key_scancode==SCAN_CODE_ENTER) RunSelected();
|
||||
break;
|
||||
case evReDraw:
|
||||
if (disk_menu_open) { // an LMENU popup was opened
|
||||
click = get_menu_click();
|
||||
if (click) { // user picked a disk (1-based)
|
||||
disk_sel = click - 1;
|
||||
disk_dir = disk_dirp[disk_sel];
|
||||
}
|
||||
if (!menu_process_id) disk_menu_open = 0; // popup closed
|
||||
}
|
||||
draw_window();
|
||||
break;
|
||||
}
|
||||
}
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
#ifndef INCLUDE_BENCH_H
|
||||
#define INCLUDE_BENCH_H
|
||||
//========================================================//
|
||||
// KolibriMark - benchmark engine //
|
||||
// Test registry, timing, scoring, system info. //
|
||||
// (No UI here - see kbench.c) //
|
||||
//========================================================//
|
||||
|
||||
#define SECT_CPU 0
|
||||
#define SECT_GPU 1
|
||||
#define SECT_DISK 2
|
||||
#define SECT_NUM 3
|
||||
|
||||
#define MAX_TESTS 48
|
||||
|
||||
//#define BB_CALIB // uncomment to add the calibration block to the report
|
||||
|
||||
#define BB_REVISION 1 // version of the tests + REF_ values, shown in the report
|
||||
#define BB_SKIP 0xFFFFFFFF // test cannot run here: no score, out of the geomean
|
||||
|
||||
// ---- shared work buffers ----
|
||||
#define GCV_X 0 // graphics canvas = window body (WIN_W/H tied to these)
|
||||
#define GCV_Y 42
|
||||
#define GCV_W 455 // wide enough for 3 columns of checkboxes
|
||||
#define GCV_H 240
|
||||
|
||||
#define CPY_BYTES 2097152 // 2 MB: copy/latency working set (> retro-era caches)
|
||||
|
||||
// buf_a: copy source, hash input, disk-write source (content never matters)
|
||||
// buf_b: copy dest, sieve + pointer-chase workspace (each test re-inits it)
|
||||
dword buf_a, buf_b, buf_canvas;
|
||||
|
||||
// ---- parallel-array registry (modular: RegisterTest appends) ----
|
||||
dword t_name [MAX_TESTS]; // -> name string
|
||||
dword t_unit [MAX_TESTS]; // -> unit string, e.g. "MB/s"
|
||||
dword t_fn [MAX_TESTS]; // -> test function (returns metric x100)
|
||||
dword t_ref [MAX_TESTS]; // reference metric x100 that scores 1000
|
||||
dword t_raw [MAX_TESTS]; // last measured metric x100
|
||||
dword t_score[MAX_TESTS]; // last computed score
|
||||
byte t_sect [MAX_TESTS];
|
||||
byte t_done [MAX_TESTS]; // 0 = not run, 1 = has a result, 2 = skipped
|
||||
int t_count = 0;
|
||||
|
||||
dword sect_score[SECT_NUM]; // aggregate score per section
|
||||
|
||||
void RegisterTest(dword sect, name, unit, ref, fn)
|
||||
{
|
||||
t_sect [t_count] = sect;
|
||||
t_name [t_count] = name;
|
||||
t_unit [t_count] = unit;
|
||||
t_ref [t_count] = ref;
|
||||
t_fn [t_count] = fn;
|
||||
t_raw [t_count] = 0;
|
||||
t_score[t_count] = 0;
|
||||
t_done [t_count] = 0;
|
||||
t_count++;
|
||||
}
|
||||
|
||||
byte SectionRan(dword sect) // true if any test here produced a result
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<t_count; i++) if (t_sect[i]==sect) && (t_done[i]==1) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- 64-bit-safe a*b/c ----
|
||||
dword muldiv(dword a, b, c)
|
||||
{
|
||||
if (c<1) c = 1;
|
||||
EAX = a;
|
||||
ECX = b;
|
||||
$mul ecx
|
||||
ECX = c;
|
||||
$div ecx
|
||||
}
|
||||
|
||||
// ---- integer log2 / exp2 in 16.16 fixed point ----
|
||||
// Used for the geometric mean of section scores: with an arithmetic mean
|
||||
// one outlier test (VRAM write spans 2 orders of magnitude between WC and
|
||||
// non-WC machines) would dominate the whole section.
|
||||
// log2(1+f) ~ f + 0.343*f*(1-f), error < 0.5% after the exp2 round-trip.
|
||||
dword pw2tab[32]; // 2^n table (C-- has no variable shifts)
|
||||
|
||||
dword ilog2_16(dword s) // log2(s) in 16.16, s >= 1
|
||||
{
|
||||
dword e, m, x, c;
|
||||
if (s < 2) return 0;
|
||||
e = 0; m = s;
|
||||
while (m > 1) { m = m / 2; e++; }
|
||||
x = muldiv(s, 65536, pw2tab[e]);
|
||||
x = x - 65536; // fractional mantissa, 16.16
|
||||
c = 65536 - x;
|
||||
c = muldiv(x, c, 65536);
|
||||
c = muldiv(c, 22479, 65536); // 0.343 in 16.16
|
||||
x = x + c;
|
||||
e = e << 16;
|
||||
return e + x;
|
||||
}
|
||||
|
||||
dword iexp2_16(dword v) // 2^(v/65536), inverse of ilog2_16
|
||||
{
|
||||
dword e, f, c, m;
|
||||
e = v >> 16;
|
||||
f = v & 0xFFFF;
|
||||
c = 65536 - f;
|
||||
c = muldiv(f, c, 65536);
|
||||
c = muldiv(c, 22479, 65536);
|
||||
f = f - c;
|
||||
m = 65536 + f; // 2^frac in 16.16
|
||||
if (e > 30) return 0xFFFFFFFF;
|
||||
return muldiv(m, pw2tab[e], 65536);
|
||||
}
|
||||
|
||||
// ---- timing (system fn 26.9: 1/100 s counter) ----
|
||||
dword bench_t0;
|
||||
void BenchBegin() { bench_t0 = GetStartTime(); }
|
||||
dword BenchTicks() // elapsed 1/100 s, min 1
|
||||
{
|
||||
dword e = GetStartTime() - bench_t0;
|
||||
if (e < 1) e = 1;
|
||||
return e;
|
||||
}
|
||||
// count = amount processed in DISPLAY units; returns units/sec x100
|
||||
dword PerSecX100(dword count)
|
||||
{
|
||||
return muldiv(count, 10000, BenchTicks());
|
||||
}
|
||||
|
||||
// ---- call a registered test by pointer, return its metric x100 ----
|
||||
dword CallFn(dword fn)
|
||||
{
|
||||
ESI = fn;
|
||||
$call esi
|
||||
}
|
||||
|
||||
// ---- run one registered test ----
|
||||
void RunOne(int i)
|
||||
{
|
||||
dword raw = CallFn(t_fn[i]);
|
||||
if (raw == BB_SKIP) {
|
||||
t_raw[i] = 0;
|
||||
t_score[i] = 0;
|
||||
t_done[i] = 2;
|
||||
return;
|
||||
}
|
||||
t_raw[i] = raw;
|
||||
t_score[i] = muldiv(raw, 1000, t_ref[i]);
|
||||
t_done[i] = 1;
|
||||
}
|
||||
|
||||
// ---- allocate shared buffers + seed with data ----
|
||||
byte BenchAllocBuffers() // 0 = out of memory
|
||||
{
|
||||
dword i, n;
|
||||
buf_a = malloc(CPY_BYTES);
|
||||
buf_b = malloc(CPY_BYTES);
|
||||
buf_canvas = malloc(GCV_W*GCV_H*3);
|
||||
if (!buf_a) || (!buf_b) || (!buf_canvas) return 0;
|
||||
i = 0;
|
||||
while (i < CPY_BYTES) {
|
||||
ESDWORD[buf_a+i] = i;
|
||||
i += 4;
|
||||
}
|
||||
n = GCV_W*GCV_H; n = n*3;
|
||||
for (i=0; i<n; i++) DSBYTE[buf_canvas+i] = i; // gradient BBGGRR
|
||||
pw2tab[0] = 1;
|
||||
for (i=1; i<32; i++) { n = pw2tab[i-1]; pw2tab[i] = n + n; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
//======================= system info =======================//
|
||||
dword sys_cpu_mhz;
|
||||
dword sys_ram_mb;
|
||||
dword sys_bpp; // current video mode bits per pixel
|
||||
|
||||
// re-read screen resolution + bpp (they change on a mode switch)
|
||||
void RefreshScreen()
|
||||
{
|
||||
dword d;
|
||||
EAX = 14; $int 0x40 // screen size: (w-1)<<16 + (h-1)
|
||||
d = EAX;
|
||||
screen.h = d & 0xFFFF; screen.h++;
|
||||
screen.w = d >> 16; screen.w++;
|
||||
EAX = 61; EBX = 2; $int 0x40 // fn 61.2 = bits per pixel
|
||||
sys_bpp = EAX;
|
||||
}
|
||||
|
||||
// RAM + CPU clock (fn 18.5). No CPUID/RDTSC -> fully 386-compatible (no
|
||||
// "CPU required: Pentium"). The processor is just labelled "CPU" in the UI.
|
||||
void GetSysInfo()
|
||||
{
|
||||
dword hz;
|
||||
sys_ram_mb = GetTotalRAM() / 1024;
|
||||
EAX = 18; EBX = 5; $int 0x40; // fn 18.5 = CPU clock rate, Hz
|
||||
hz = EAX;
|
||||
sys_cpu_mhz = hz / 1000000;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,5 +0,0 @@
|
||||
@del *.
|
||||
@..\c--\c--.exe bbench.c
|
||||
@rename *.com *.
|
||||
@del warning.txt
|
||||
if not exist bbench ( @pause )
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef INCLUDE_CPUDB_H
|
||||
#define INCLUDE_CPUDB_H
|
||||
//========================================================//
|
||||
// CPU comparison database -- EDIT ME //
|
||||
// //
|
||||
// One line per reference CPU in cpudb_init(), copied straight //
|
||||
// from the Calibration block of a run on that machine. Entries //
|
||||
// measured under another BB_REVISION are hidden. //
|
||||
// The values below are placeholders - replace them. //
|
||||
//========================================================//
|
||||
|
||||
#define CPUDB_MAX 24
|
||||
dword cpudb_name [CPUDB_MAX];
|
||||
dword cpudb_mhz [CPUDB_MAX];
|
||||
dword cpudb_score[CPUDB_MAX];
|
||||
int cpudb_count = 0;
|
||||
|
||||
// entries from another revision are dropped: their numbers mean something else
|
||||
void cpudb_add(dword name, mhz, score, rev)
|
||||
{
|
||||
if (cpudb_count >= CPUDB_MAX) return;
|
||||
if (rev != BB_REVISION) return;
|
||||
cpudb_name [cpudb_count] = name;
|
||||
cpudb_mhz [cpudb_count] = mhz;
|
||||
cpudb_score[cpudb_count] = score;
|
||||
cpudb_count++;
|
||||
}
|
||||
|
||||
void cpudb_init()
|
||||
{
|
||||
cpudb_add("Intel Core i5-2400", 3100, 1600, 1);
|
||||
cpudb_add("AMD Athlon XP 2500+", 1833, 360, 1);
|
||||
cpudb_add("Intel Pentium 4", 2400, 300, 1);
|
||||
cpudb_add("Intel Pentium III", 1000, 180, 1);
|
||||
cpudb_add("Intel Atom N270", 1600, 420, 1);
|
||||
cpudb_add("VIA C7", 1200, 150, 1);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,124 +0,0 @@
|
||||
# BirdBench
|
||||
|
||||
A small, modular system benchmark for KolibriOS, styled after Geekbench.
|
||||
Single-threaded (KolibriOS exposes no SMP to apps). 386-compatible
|
||||
(no CPUID/RDTSC — the MHz value comes from sysfn 18.5).
|
||||
|
||||
## Sections
|
||||
|
||||
* **CPU** — Integer, Floating Point, Memory Copy, Memory Latency,
|
||||
Prime Sieve, Hash (FNV-1a).
|
||||
Integer/Float run **4 independent chains** so superscalar CPUs (P5+) can
|
||||
pair/overlap them — a strictly serial chain would only measure latency.
|
||||
Float has no division on purpose: one fdiv (15–40+ cycles) would dominate
|
||||
the chain and the test would degenerate into an fdiv-latency meter.
|
||||
Memory Copy is a **forward** `rep movsd` over 2 MB (lib `memmov` copies
|
||||
backward when dst>src, which never hits the fast-string path on modern
|
||||
CPUs). Memory Latency is a serial pointer chase over a 2 MB random cycle
|
||||
(Sattolo shuffle) — defeats prefetch and cache, measures dependent-load
|
||||
latency; `100 / (Macc/s) = ns` per access.
|
||||
* **Graphics** — Fill (f13), Blit (f7), VRAM Read (f36), VRAM Write (gs:),
|
||||
Lines (f38), Text (f4).
|
||||
Fill/Blit = writes through the driver's blitter (may be HW-accelerated),
|
||||
VRAM Read (f36) = read-back, VRAM Write (gs:) = raw CPU `rep stosd` straight
|
||||
into the LFB via the GS selector — this one exposes write-combining (MTRR/PAT):
|
||||
no WC and it collapses ~10-50x. The report shows the current video mode —
|
||||
switch modes with the vidmode picker and re-run to compare VESA-LFB vs a
|
||||
native driver vs KMS at the same resolution.
|
||||
Before writing, the GS segment limit is checked with `lsl` — on banked-VGA
|
||||
modes (no LFB) the test skips instead of #GP-faulting. Color counters are
|
||||
masked to 24 bits: fn13 treats bit 31 as the gradient-fill flag and fn38
|
||||
treats bit 24 as the inversed-line flag — unmasked they silently switch the
|
||||
kernel to a different (slower) operation mid-test.
|
||||
Text draws **both kernel fonts** (6×9 and 8×16) and forces **subpixel font
|
||||
smoothing** for the duration (fn 48.9 saves the user's setting, fn 48.10
|
||||
sets/restores it) — the heaviest glyph path, so machines are compared on
|
||||
equal footing regardless of their smoothing setting.
|
||||
* **Disk** — Sequential write / read (2 MB ops) + Random write / read
|
||||
(4 KB blocks at fixed-seed random offsets in an 8 MB file, via fn 70
|
||||
positioned I/O) + **File System** (create 10 files + 10 folders, delete
|
||||
them all — metadata op/s, fn 70.2/70.9/70.8; names are pre-built so no
|
||||
sprintf lands in the timed loop). Folder support is probed first: not every
|
||||
writable FS driver implements fn 70.9 (exFAT has no CreateFolder at all) —
|
||||
there the test degrades to files-only instead of scoring 0. Pick the target
|
||||
disk in the UI — the report's Disk header shows which disk was tested. All
|
||||
disk rates are MiB/s.
|
||||
Temp files are deleted when the run finishes.
|
||||
NB: KolibriOS apps can't drop the FS cache, so on a real disk the random
|
||||
numbers reflect the FS small-op path (not raw seek/IOPS), and on the RAM
|
||||
disk random ≈ sequential (no seek penalty).
|
||||
|
||||
The window is a **launcher**: tick the tests you want (per-test checkboxes,
|
||||
per-section master checkbox, or All / None), pick a disk target, press **Run**.
|
||||
Only ticked tests run; **Esc aborts** between tests. During a run the window
|
||||
is forced always-on-top: the kernel only draws the visible parts of a window,
|
||||
so an occluded window would inflate the Graphics scores. When a run finishes,
|
||||
an HTML report is written to `/tmp0/1/bb_YYMMDD_HHMMSS.htm` (unique name) and
|
||||
**auto-opened in WebView**.
|
||||
|
||||
The report (not the window) shows the results: per-section score, one row per
|
||||
test with a colored bar (alternating shades) plus a gray/light-gray track, and
|
||||
a **CPU compare** chart of your score vs the CPUs in `cpudb.h` (with a MHz
|
||||
column; the compare bars line up with the test bars). Results are only
|
||||
in the web page — clearer there than in the small window.
|
||||
|
||||
Each test reports a raw throughput (MB/s, MPix/s, ...) and a *score*
|
||||
(1000 = the reference machine defined by the `REF_*` constants in the
|
||||
module). The section score is the **geometric mean** of its ticked tests'
|
||||
scores — a single outlier test (VRAM Write spans two orders of magnitude
|
||||
between WC and non-WC machines) can't dominate the section the way it
|
||||
would with an arithmetic mean.
|
||||
|
||||
## How to add a test
|
||||
|
||||
1. Open the section module (`tests_cpu.h`, `tests_gpu.h` or `tests_disk.h`).
|
||||
2. Write a function returning the metric ×100 (two decimals):
|
||||
|
||||
```c
|
||||
dword t_cpu_mytest()
|
||||
{
|
||||
dword c = 0;
|
||||
BenchBegin();
|
||||
do { /* one unit of work */ c++; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // units/sec ×100
|
||||
}
|
||||
```
|
||||
|
||||
`c` counts how many *display units* you processed (1 MB, 1 Mpixel,
|
||||
1 Kline, ...). The framework turns that into a per-second rate.
|
||||
3. Register it and give it a reference value:
|
||||
|
||||
```c
|
||||
#define REF_MYTEST 5000 // 50.00 units/s -> score 1000
|
||||
RegisterTest(SECT_CPU, "My Test", "u/s", REF_MYTEST, #t_cpu_mytest);
|
||||
```
|
||||
|
||||
That's it — it shows up in the list automatically.
|
||||
|
||||
## How to add a comparison CPU
|
||||
|
||||
Edit `cpudb.h`, add a line to `cpudb_init()`:
|
||||
|
||||
```c
|
||||
cpudb_add("Intel Core 2 Duo E8400", 3000, 520); // name, MHz, measured CPU score
|
||||
```
|
||||
|
||||
NB: any change to the test definitions invalidates previously measured
|
||||
db entries — re-measure them with the same binary you compare against.
|
||||
|
||||
## Build
|
||||
|
||||
Run `build.bat` (needs `../c--/c--.exe`). Output: `bbench`.
|
||||
|
||||
## Notes / calibration
|
||||
|
||||
* The `REF_*` constants are placeholders. Pick a machine as your baseline,
|
||||
run it, and set each `REF_*` to that machine's raw metric ×100 so it
|
||||
scores ~1000. Everything else scales relative to it.
|
||||
* All "MB" are MiB (2^20) across CPU, VRAM Write and Disk tests.
|
||||
* Random-I/O offsets and the latency chain use **fixed seeds** — every run
|
||||
and every machine gets the same access pattern (reproducibility).
|
||||
* On machines with a large L3 the 2 MB working set of Memory Copy/Latency
|
||||
measures the cache, not RAM — unavoidable at sizes that still fit
|
||||
retro-era boxes; scores stay comparable within an era.
|
||||
* Needs ~6 MB RAM (see `MEMSIZE`).
|
||||
@@ -1,188 +0,0 @@
|
||||
#ifndef INCLUDE_TESTS_CPU_H
|
||||
#define INCLUDE_TESTS_CPU_H
|
||||
//========================================================//
|
||||
// CPU test module. //
|
||||
// Each test returns its metric x100 (two decimals). //
|
||||
// To add a test: write t_cpu_xxx() then RegisterTest(). //
|
||||
//========================================================//
|
||||
|
||||
// reference metric x100 that should score 1000 (calibration)
|
||||
#define REF_INT 5000 // 50.00 MOps/s
|
||||
#define REF_FLOAT 2000 // 20.00 MFLOP/s
|
||||
#define REF_MEM 80000 // 800.00 MB/s
|
||||
#define REF_LAT 1000 // 10.00 Macc/s (= 100 ns per access)
|
||||
#define REF_SIEVE 3000 // 30.00 Mcell/s
|
||||
#define REF_HASH 10000 // 100.00 MB/s
|
||||
|
||||
#define CPY_MB 2 // CPY_BYTES in whole MiB (see bbench.h)
|
||||
#define LAT_N 524288 // nodes in the 2 MB pointer chain
|
||||
|
||||
dword cpu_sink;
|
||||
float cpu_fsink;
|
||||
dword lat_rnd;
|
||||
|
||||
// forward rep movsd block copy. NB: lib memmov() copies BACKWARD when
|
||||
// dst>src (std; rep movsd), which never hits the fast-string path on
|
||||
// modern CPUs and would understate them - so we do our own, forward.
|
||||
inline fastcall copy_fwd(EDI, ESI, ECX) // dst, src, bytes (mult of 4)
|
||||
{
|
||||
asm {
|
||||
CLD
|
||||
SHR ECX, 2
|
||||
REP MOVSD
|
||||
}
|
||||
}
|
||||
|
||||
void sieve1m(dword buf)
|
||||
{
|
||||
dword i, j;
|
||||
EDI = buf;
|
||||
EAX = 0;
|
||||
ECX = 262144; // 1 MB / 4
|
||||
asm {
|
||||
CLD
|
||||
REP STOSD
|
||||
}
|
||||
for (i=2; i*i<1000000; i++) {
|
||||
if (DSBYTE[buf+i]==0) {
|
||||
j = i*i;
|
||||
while (j<1000000) { DSBYTE[buf+j]=1; j += i; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dword hash1mb(dword buf)
|
||||
{
|
||||
dword i, h;
|
||||
h = 2166136261;
|
||||
for (i=0; i<1048576; i++) {
|
||||
h = h ^ DSBYTE[buf+i];
|
||||
h = h * 16777619;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
// build one random cycle of byte-offsets over buf (Sattolo shuffle):
|
||||
// following p = [buf+p] visits every node in random order - defeats
|
||||
// both the prefetcher and the cache -> pure dependent-load latency.
|
||||
void lat_build(dword buf)
|
||||
{
|
||||
dword i, j, t, ai, aj;
|
||||
for (i=0; i<LAT_N; i++) { ai = i*4; ESDWORD[buf+ai] = ai; }
|
||||
lat_rnd = 0x1A2B3C4D; // fixed seed: same chain on every machine
|
||||
i = LAT_N;
|
||||
while (i > 1) {
|
||||
i = i - 1;
|
||||
lat_rnd = lat_rnd * 1103515245; lat_rnd = lat_rnd + 12345;
|
||||
j = lat_rnd >> 8; j = j % i;
|
||||
ai = i*4; ai = ai + buf;
|
||||
aj = j*4; aj = aj + buf;
|
||||
t = ESDWORD[ai]; ESDWORD[ai] = ESDWORD[aj]; ESDWORD[aj] = t;
|
||||
}
|
||||
}
|
||||
|
||||
//--- Integer: 4 independent ALU chains, ~1e6 ops per unit ---
|
||||
// Interleaved so superscalar CPUs (P5+) can overlap them; a strictly
|
||||
// serial chain would only measure latency and hide pipelining.
|
||||
dword t_cpu_int()
|
||||
{
|
||||
dword c=0, i, x1, x2, x3, x4;
|
||||
BenchBegin();
|
||||
do {
|
||||
x1 = 0x12345678; x2 = 0x9E3779B9; x3 = 0x01234567; x4 = 0xABCDEF01;
|
||||
for (i=0; i<50000; i++) {
|
||||
x1 = x1 + i; x2 = x2 + i; x3 = x3 + i; x4 = x4 + i;
|
||||
x1 = x1 ^ 0x55AA55AA; x2 = x2 ^ 0x33CC33CC; x3 = x3 ^ 0x0F0F0F0F; x4 = x4 ^ 0x5A5A5A5A;
|
||||
x1 = x1 * 3; x2 = x2 * 3; x3 = x3 * 3; x4 = x4 * 3;
|
||||
x1 = x1 - i; x2 = x2 - i; x3 = x3 - i; x4 = x4 - i;
|
||||
x1 = x1 + x1; x2 = x2 + x2; x3 = x3 + x3; x4 = x4 + x4;
|
||||
}
|
||||
cpu_sink = x1; cpu_sink = cpu_sink ^ x2;
|
||||
cpu_sink = cpu_sink ^ x3; cpu_sink = cpu_sink ^ x4;
|
||||
c++; // 50000 * 20 = 1e6 ops
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MOps/s
|
||||
}
|
||||
|
||||
//--- Floating point: 4 independent mul/add chains, ~1e6 flops per unit ---
|
||||
// No division: one fdiv (15-40+ cycles) would dominate the whole chain
|
||||
// and the test would just measure fdiv latency.
|
||||
dword t_cpu_float()
|
||||
{
|
||||
dword c=0, i;
|
||||
float a1, a2, a3, a4, b, k;
|
||||
BenchBegin();
|
||||
do {
|
||||
a1 = 1.0; a2 = 1.1; a3 = 1.2; a4 = 1.3;
|
||||
b = 1.0000151; k = 0.9999847;
|
||||
for (i=0; i<62500; i++) {
|
||||
a1 = a1 * b; a2 = a2 * b; a3 = a3 * b; a4 = a4 * b;
|
||||
a1 = a1 + k; a2 = a2 + k; a3 = a3 + k; a4 = a4 + k;
|
||||
a1 = a1 * k; a2 = a2 * k; a3 = a3 * k; a4 = a4 * k;
|
||||
a1 = a1 - k; a2 = a2 - k; a3 = a3 - k; a4 = a4 - k;
|
||||
}
|
||||
cpu_fsink = a1; cpu_fsink = cpu_fsink + a2;
|
||||
cpu_fsink = cpu_fsink + a3; cpu_fsink = cpu_fsink + a4;
|
||||
c++; // 62500 * 16 = 1e6 flops
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MFLOP/s
|
||||
}
|
||||
|
||||
//--- Memory bandwidth: 2 MB forward copy per unit ---
|
||||
dword t_cpu_mem()
|
||||
{
|
||||
dword c=0;
|
||||
BenchBegin();
|
||||
do { copy_fwd(buf_b, buf_a, CPY_BYTES); c += CPY_MB; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
//--- Memory latency: serial pointer chase over 2 MB, 1e6 hops per unit ---
|
||||
dword t_cpu_latency()
|
||||
{
|
||||
dword c=0, k, p;
|
||||
lat_build(buf_b);
|
||||
BenchBegin();
|
||||
do {
|
||||
p = 0;
|
||||
for (k=0; k<250000; k++) {
|
||||
p = ESDWORD[buf_b+p];
|
||||
p = ESDWORD[buf_b+p];
|
||||
p = ESDWORD[buf_b+p];
|
||||
p = ESDWORD[buf_b+p];
|
||||
}
|
||||
cpu_sink = p;
|
||||
c++; // 250000 * 4 = 1e6 dependent loads
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // Macc/s (100/result = ns per access)
|
||||
}
|
||||
|
||||
//--- Prime sieve: 1e6 cells per unit ---
|
||||
dword t_cpu_sieve()
|
||||
{
|
||||
dword c=0;
|
||||
BenchBegin();
|
||||
do { sieve1m(buf_b); c++; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // Mcell/s
|
||||
}
|
||||
|
||||
//--- Hash throughput (FNV-1a): 1 MB per unit ---
|
||||
dword t_cpu_hash()
|
||||
{
|
||||
dword c=0;
|
||||
BenchBegin();
|
||||
do { cpu_sink = hash1mb(buf_a); c++; } while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
void Register_CPU()
|
||||
{
|
||||
RegisterTest(SECT_CPU, "Integer", "MOps/s", REF_INT, #t_cpu_int);
|
||||
RegisterTest(SECT_CPU, "Floating Point", "MFLOP/s", REF_FLOAT, #t_cpu_float);
|
||||
RegisterTest(SECT_CPU, "Memory Copy", "MB/s", REF_MEM, #t_cpu_mem);
|
||||
RegisterTest(SECT_CPU, "Memory Latency", "Macc/s", REF_LAT, #t_cpu_latency);
|
||||
RegisterTest(SECT_CPU, "Prime Sieve", "Mcell/s", REF_SIEVE, #t_cpu_sieve);
|
||||
RegisterTest(SECT_CPU, "Hash FNV-1a", "MB/s", REF_HASH, #t_cpu_hash);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,311 +0,0 @@
|
||||
#ifndef INCLUDE_TESTS_DISK_H
|
||||
#define INCLUDE_TESTS_DISK_H
|
||||
//========================================================//
|
||||
// Disk test module. //
|
||||
// Writes/reads a temp file via system fn 70. //
|
||||
// Target directory = disk_dir (set from the UI disk //
|
||||
// selector); falls back to the RAM disk /tmp0/1. //
|
||||
// NB: sequential rewrite of one file mostly measures the //
|
||||
// FS/cache throughput, not raw platter speed. //
|
||||
//========================================================//
|
||||
|
||||
#define REF_DWRITE 5000 // 50.00 MB/s
|
||||
#define REF_DREAD 5000
|
||||
#define REF_RWRITE 1000 // 10.00 MB/s (random 4K is much slower)
|
||||
#define REF_RREAD 1000
|
||||
#define REF_FS 50000 // 500.00 op/s (create/delete metadata ops)
|
||||
#define FS_N 10 // files + folders created per unit
|
||||
#define DISK_CHUNK CPY_BYTES // 2 MB per op (reuses buf_a - content irrelevant)
|
||||
#define DISK_MB 2
|
||||
#define RND_FILE 8388608 // 8 MB test file for random ops
|
||||
#define RND_BLK 4096 // 4 KB random block
|
||||
#define RND_NBLK 2048 // RND_FILE / RND_BLK
|
||||
|
||||
dword buf_disk = 0;
|
||||
dword disk_dir = 0; // -> selected target dir (set by the UI)
|
||||
dword rnd_state; // LCG state for random offsets
|
||||
char dk_op[32];
|
||||
char dk_file[80];
|
||||
|
||||
dword FileWrite(dword name, data, size)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 2; // subfn 2 = create/rewrite
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX; // 0 = ok
|
||||
}
|
||||
|
||||
dword FileRead(dword name, data, size)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 0; // subfn 0 = read
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword dk_delete(dword name)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 8; // subfn 8 = delete
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = 0;
|
||||
ESDWORD[#dk_op+16] = 0;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword dk_mkdir(dword name)
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 9; // subfn 9 = create folder
|
||||
ESDWORD[#dk_op+4] = 0;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = 0;
|
||||
ESDWORD[#dk_op+16] = 0;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword FileWriteAt(dword name, data, size, pos) // fn70.3 = write at position
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 3;
|
||||
ESDWORD[#dk_op+4] = pos;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
dword FileReadAt(dword name, data, size, pos) // fn70.0 = read at position
|
||||
{
|
||||
ESDWORD[#dk_op+0] = 0;
|
||||
ESDWORD[#dk_op+4] = pos;
|
||||
ESDWORD[#dk_op+8] = 0;
|
||||
ESDWORD[#dk_op+12] = size;
|
||||
ESDWORD[#dk_op+16] = data;
|
||||
DSBYTE [#dk_op+20] = 0;
|
||||
ESDWORD[#dk_op+21] = name;
|
||||
EAX = 70;
|
||||
EBX = #dk_op;
|
||||
$int 0x40;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
void DiskPath() // dk_file = <disk_dir>/bbtst.tmp
|
||||
{
|
||||
if (disk_dir) strcpy(#dk_file, disk_dir);
|
||||
else strcpy(#dk_file, "/tmp0/1");
|
||||
strcat(#dk_file, "/bbtst.tmp");
|
||||
}
|
||||
|
||||
// create/fill an 8 MB file so random 4K offsets are valid and physically written
|
||||
void EnsureRandFile()
|
||||
{
|
||||
dword pos;
|
||||
FileWrite(#dk_file, buf_disk, DISK_CHUNK); // create
|
||||
pos = DISK_CHUNK;
|
||||
while (pos < RND_FILE) {
|
||||
FileWriteAt(#dk_file, buf_disk, DISK_CHUNK, pos);
|
||||
pos += DISK_CHUNK;
|
||||
}
|
||||
}
|
||||
|
||||
// remove the temp file (called by the UI when a run finishes)
|
||||
void Disk_Cleanup()
|
||||
{
|
||||
DiskPath();
|
||||
dk_delete(#dk_file);
|
||||
}
|
||||
|
||||
dword t_disk_write()
|
||||
{
|
||||
dword c=0, r;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
BenchBegin();
|
||||
do {
|
||||
r = FileWrite(#dk_file, buf_disk, DISK_CHUNK);
|
||||
if (r) return BB_SKIP; // write error: no result, not a slow one
|
||||
c += DISK_MB;
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
dword t_disk_read()
|
||||
{
|
||||
dword c=0, r;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
// Seq Write may be unticked - create the test file if it is missing
|
||||
r = FileRead(#dk_file, buf_disk, RND_BLK);
|
||||
if (r!=0) && (r!=6) FileWrite(#dk_file, buf_disk, DISK_CHUNK);
|
||||
BenchBegin();
|
||||
do {
|
||||
r = FileRead(#dk_file, buf_disk, DISK_CHUNK);
|
||||
if (r!=0) && (r!=6) return BB_SKIP; // 6 = EOF (still ok)
|
||||
c += DISK_MB;
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // MB/s
|
||||
}
|
||||
|
||||
// random 4K write at random offsets in the 8 MB file
|
||||
dword t_disk_rwrite()
|
||||
{
|
||||
dword c=0, pos, r;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
EnsureRandFile();
|
||||
rnd_state = 0x12345678; // fixed seed: same offsets on every run
|
||||
BenchBegin();
|
||||
do {
|
||||
rnd_state = rnd_state * 1103515245; rnd_state = rnd_state + 12345;
|
||||
r = rnd_state >> 8; r = r % RND_NBLK;
|
||||
pos = r * RND_BLK;
|
||||
if (FileWriteAt(#dk_file, buf_disk, RND_BLK, pos)) return BB_SKIP;
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
r = muldiv(c, 40000, BenchTicks()); // KiB/s x100 (c * 4 KiB)
|
||||
return r / 1024; // -> MB/s x100 (MiB, same as Seq)
|
||||
}
|
||||
|
||||
// random 4K read at random offsets (NB: hits the FS cache - can't be dropped)
|
||||
dword t_disk_rread()
|
||||
{
|
||||
dword c=0, pos, r, res;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
DiskPath();
|
||||
EnsureRandFile();
|
||||
rnd_state = 0x12345678; // fixed seed: same offsets on every run
|
||||
BenchBegin();
|
||||
do {
|
||||
rnd_state = rnd_state * 1103515245; rnd_state = rnd_state + 12345;
|
||||
r = rnd_state >> 8; r = r % RND_NBLK;
|
||||
pos = r * RND_BLK;
|
||||
res = FileReadAt(#dk_file, buf_disk, RND_BLK, pos);
|
||||
if (res!=0) && (res!=6) return BB_SKIP;
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
r = muldiv(c, 40000, BenchTicks()); // KiB/s x100 (c * 4 KiB)
|
||||
return r / 1024; // -> MB/s x100 (MiB, same as Seq)
|
||||
}
|
||||
|
||||
//---- File system: create/delete files and folders (metadata ops) ----
|
||||
char fs_fn[FS_N*64]; // pre-built file names (no sprintf in the loop)
|
||||
char fs_dn[FS_N*64]; // pre-built folder names
|
||||
|
||||
// names via strcpy/strcat only - the same primitives DiskWritable/DiskPath
|
||||
// use successfully on every disk (no sprintf/itoa in the path building).
|
||||
// No trailing slash anywhere: folder path is "<dir>/bbd_0".
|
||||
void fs_names_build()
|
||||
{
|
||||
dword i, p, dir;
|
||||
char dig[2];
|
||||
dir = disk_dir;
|
||||
if (!dir) dir = "/tmp0/1";
|
||||
dig[1] = 0;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
dig[0] = i + '0';
|
||||
p = i*64; p = p + #fs_fn;
|
||||
strcpy(p, dir); strcat(p, "/bbf_"); strcat(p, #dig); strcat(p, ".tmp");
|
||||
p = i*64; p = p + #fs_dn;
|
||||
strcpy(p, dir); strcat(p, "/bbd_"); strcat(p, #dig);
|
||||
}
|
||||
}
|
||||
|
||||
void fs_wipe() // best-effort removal (also leftovers of a crash)
|
||||
{
|
||||
dword i, p;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
p = i*64; p = p + #fs_fn; dk_delete(p);
|
||||
p = i*64; p = p + #fs_dn; dk_delete(p);
|
||||
}
|
||||
}
|
||||
|
||||
// one unit = create FS_N files (512 B) + FS_N folders, then delete them all.
|
||||
// BOTH phases are probed before timing and degrade independently: not every
|
||||
// writable FS driver implements every fn70 op (e.g. exFAT has no
|
||||
// CreateFolder at all). 0 only when neither files nor folders work.
|
||||
dword t_disk_fs()
|
||||
{
|
||||
dword c=0, i, p, r;
|
||||
byte files_ok, dirs_ok, ok;
|
||||
if (!buf_disk) buf_disk = buf_a;
|
||||
fs_names_build();
|
||||
fs_wipe();
|
||||
files_ok = 0; // probe file create+delete
|
||||
r = FileWrite(#fs_fn, buf_disk, 512);
|
||||
if (r==0) {
|
||||
r = dk_delete(#fs_fn);
|
||||
if (r==0) files_ok = 1;
|
||||
}
|
||||
dirs_ok = 0; // probe folder create+delete
|
||||
r = dk_mkdir(#fs_dn);
|
||||
if (r==0) {
|
||||
r = dk_delete(#fs_dn);
|
||||
if (r==0) dirs_ok = 1;
|
||||
}
|
||||
if (!files_ok) && (!dirs_ok) return BB_SKIP;
|
||||
BenchBegin();
|
||||
do {
|
||||
if (files_ok) {
|
||||
ok = 1;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
p = i*64; p = p + #fs_fn;
|
||||
if (FileWrite(p, buf_disk, 512)) ok = 0;
|
||||
}
|
||||
for (i=0; i<FS_N; i++) { p = i*64; p = p + #fs_fn; dk_delete(p); }
|
||||
if (ok) c += 20; // 2 * FS_N file operations
|
||||
else files_ok = 0; // driver refused mid-run - stop trying
|
||||
}
|
||||
if (dirs_ok) {
|
||||
ok = 1;
|
||||
for (i=0; i<FS_N; i++) {
|
||||
p = i*64; p = p + #fs_dn;
|
||||
if (dk_mkdir(p)) ok = 0;
|
||||
}
|
||||
for (i=0; i<FS_N; i++) { p = i*64; p = p + #fs_dn; dk_delete(p); }
|
||||
if (ok) c += 20; // 2 * FS_N folder operations
|
||||
else dirs_ok = 0;
|
||||
}
|
||||
if (!files_ok) && (!dirs_ok) break;
|
||||
} while (BenchTicks() < 100);
|
||||
fs_wipe(); // belt and braces: no leftovers
|
||||
return PerSecX100(c); // op/s
|
||||
}
|
||||
|
||||
void Register_DISK()
|
||||
{
|
||||
RegisterTest(SECT_DISK, "Seq Write", "MB/s", REF_DWRITE, #t_disk_write);
|
||||
RegisterTest(SECT_DISK, "Seq Read", "MB/s", REF_DREAD, #t_disk_read);
|
||||
RegisterTest(SECT_DISK, "Rand Write", "MB/s", REF_RWRITE, #t_disk_rwrite);
|
||||
RegisterTest(SECT_DISK, "Rand Read", "MB/s", REF_RREAD, #t_disk_rread);
|
||||
RegisterTest(SECT_DISK, "File System", "op/s", REF_FS, #t_disk_fs);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,204 +0,0 @@
|
||||
#ifndef INCLUDE_TESTS_GPU_H
|
||||
#define INCLUDE_TESTS_GPU_H
|
||||
//========================================================//
|
||||
// Graphics (2D) test module. //
|
||||
// Tests draw into the window canvas (GCV_* region). //
|
||||
//========================================================//
|
||||
|
||||
#define REF_FILL 20000 // 200.00 MPix/s
|
||||
#define REF_BLIT 10000 // 100.00 MPix/s
|
||||
#define REF_READ 5000 // 50.00 MPix/s (VRAM read is slow)
|
||||
#define REF_VWRITE 100000 // 1000.00 MB/s (direct LFB write, WC-dependent)
|
||||
#define REF_LINE 50000 // 500.00 Kline/s
|
||||
#define REF_TEXT 30000 // 300.00 Kchar/s
|
||||
|
||||
char gpu_text_str[] = "BirdBench benchmark 0123456789ab";
|
||||
|
||||
void DrawLine38(dword x1,y1,x2,y2,color)
|
||||
{
|
||||
EAX = 38;
|
||||
EBX = x1<<16+x2;
|
||||
ECX = y1<<16+y2;
|
||||
EDX = color;
|
||||
$int 0x40;
|
||||
}
|
||||
|
||||
//--- Fill rate: solid bars (fn 13) ---
|
||||
dword t_gpu_fill()
|
||||
{
|
||||
dword c=0, col=0, mp;
|
||||
BenchBegin();
|
||||
do {
|
||||
DrawBar(GCV_X, GCV_Y, GCV_W, GCV_H, col);
|
||||
col += 0x0A0C0E;
|
||||
col = col & 0xFFFFFF; // bit 31 = gradient-fill flag (fn13 0x80RRGGBB)!
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
mp = muldiv(c, GCV_W*GCV_H, 1000000); // total mega-pixels
|
||||
return PerSecX100(mp); // MPix/s
|
||||
}
|
||||
|
||||
//--- Blit rate: 24bpp image (fn 7) ---
|
||||
dword t_gpu_blit()
|
||||
{
|
||||
dword c=0, mp;
|
||||
BenchBegin();
|
||||
do { PutImage(GCV_X, GCV_Y, GCV_W, GCV_H, buf_canvas); c++; }
|
||||
while (BenchTicks() < 100);
|
||||
mp = muldiv(c, GCV_W*GCV_H, 1000000);
|
||||
return PerSecX100(mp); // MPix/s
|
||||
}
|
||||
|
||||
//--- Line rate: 1000 lines (fn 38) per unit ---
|
||||
dword t_gpu_lines()
|
||||
{
|
||||
dword c=0, i, col=0x3388CC;
|
||||
dword x1,y1,x2,y2;
|
||||
BenchBegin();
|
||||
do {
|
||||
for (i=0; i<1000; i++) {
|
||||
x1 = i*7; x1 = x1 % GCV_W; x1 = x1 + GCV_X;
|
||||
y1 = i*13; y1 = y1 % GCV_H; y1 = y1 + GCV_Y;
|
||||
x2 = i*29; x2 = x2 % GCV_W; x2 = x2 + GCV_X;
|
||||
y2 = i*23; y2 = y2 % GCV_H; y2 = y2 + GCV_Y;
|
||||
DrawLine38(x1,y1,x2,y2,col);
|
||||
col += 0x0A1420;
|
||||
col = col & 0xFFFFFF; // bit 24 = inversed-line flag (fn38 0x01xxxxxx)!
|
||||
}
|
||||
c++; // 1000 lines = 1 Kline
|
||||
} while (BenchTicks() < 100);
|
||||
return PerSecX100(c); // Kline/s
|
||||
}
|
||||
|
||||
//--- Text rate: WriteText (fn 4), both kernel fonts (6x9 + 8x16) ---
|
||||
// Subpixel smoothing is forced for the duration (the heaviest glyph
|
||||
// path), then the user's setting is restored.
|
||||
dword t_gpu_text()
|
||||
{
|
||||
dword c=0, i, y, len, kc, sm;
|
||||
len = strlen(#gpu_text_str);
|
||||
EAX = 48; EBX = 9; $int 0x40; // SSF_GET_FONT_SMOOTH
|
||||
sm = EAX;
|
||||
EAX = 48; EBX = 10; ECX = 2; $int 0x40; // SSF_SET_FONT_SMOOTH: subpixel
|
||||
BenchBegin();
|
||||
do {
|
||||
for (i=0; i<25; i++) {
|
||||
y = i*11; y = y % GCV_H; y = y + GCV_Y;
|
||||
WriteText(GCV_X, y, 0x80, 0x101010, #gpu_text_str); // 6x9
|
||||
}
|
||||
for (i=0; i<25; i++) {
|
||||
y = i*17; y = y % GCV_H; y = y + GCV_Y;
|
||||
WriteText(GCV_X, y, 0x90, 0x101010, #gpu_text_str); // 8x16
|
||||
}
|
||||
c++; // 50 * len chars
|
||||
} while (BenchTicks() < 100);
|
||||
EAX = 48; EBX = 10; ECX = sm; $int 0x40; // restore user's smoothing
|
||||
kc = muldiv(c, 50*len, 1000); // total kilo-chars
|
||||
return PerSecX100(kc); // Kchar/s
|
||||
}
|
||||
|
||||
//--- VRAM read-back: read screen area to RAM (fn 36) ---
|
||||
dword t_gpu_read()
|
||||
{
|
||||
dword c=0, mp, sx, sy;
|
||||
proc_info p;
|
||||
GetProcessInfo(#p, SelfInfo); // fn 36 uses absolute screen coords
|
||||
sx = p.left; sx = sx + 5; sx = sx + GCV_X;
|
||||
sy = p.top; sy = sy + skin_h; sy = sy + GCV_Y;
|
||||
BenchBegin();
|
||||
do {
|
||||
CopyScreen(buf_canvas, sx, sy, GCV_W, GCV_H); // 24bpp -> buf
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
mp = muldiv(c, GCV_W*GCV_H, 1000000);
|
||||
return PerSecX100(mp); // MPix/s
|
||||
}
|
||||
|
||||
//--- direct VRAM write: rep stosd into the LFB via the GS selector ---
|
||||
// The kernel gives apps a writable GS whose base = LFBAddress (see kernel.asm:
|
||||
// "Set base of graphic segment to linear address of LFB"). rep stosd needs
|
||||
// ES:EDI, so we copy GS into ES for the duration, then restore it. No window
|
||||
// clipping here - we fill only our own on-screen rectangle, scanline by scanline.
|
||||
// GS segment limit via LSL (unprivileged). If the video mode has no LFB
|
||||
// (banked VGA), the selector window is tiny and writing past it would #GP.
|
||||
dword gs_limit()
|
||||
{
|
||||
asm {
|
||||
XOR EAX, EAX
|
||||
MOV AX, GS
|
||||
LSL EAX, EAX
|
||||
}
|
||||
}
|
||||
|
||||
inline fastcall vram_fill_gs(EDI, ESI, EDX, ECX) // ofs, dwords/row, row-advance, rows
|
||||
{
|
||||
asm {
|
||||
push es
|
||||
push ebx
|
||||
mov ebx, ecx // ebx = row count
|
||||
mov ax, gs
|
||||
mov es, ax // es -> LFB
|
||||
cld
|
||||
kvw1:
|
||||
mov ecx, esi // dwords per scanline
|
||||
mov eax, 0x00336699 // fill pattern
|
||||
rep stosd
|
||||
add edi, edx // -> next scanline
|
||||
dec ebx
|
||||
jnz kvw1
|
||||
pop ebx
|
||||
pop es
|
||||
}
|
||||
}
|
||||
|
||||
dword t_gpu_vramwrite()
|
||||
{
|
||||
dword c=0, sx, sy, cols, rows, pitch, bpp, bpp_b, off, rowbytes, rowdw, rowadv, bpf, mb, t, lim;
|
||||
proc_info p;
|
||||
GetProcessInfo(#p, SelfInfo);
|
||||
EAX = 61; EBX = 3; $int 0x40; pitch = EAX; // bytes per scanline
|
||||
EAX = 61; EBX = 2; $int 0x40; bpp = EAX; // bits per pixel
|
||||
bpp_b = bpp + 7; bpp_b = bpp_b / 8;
|
||||
if (bpp_b < 1) bpp_b = 4;
|
||||
sx = p.left; sx = sx + 5; sx = sx + GCV_X;
|
||||
sy = p.top; sy = sy + skin_h; sy = sy + GCV_Y;
|
||||
// clamp to the screen - writing past the LFB would #GP (small screens!)
|
||||
if (sx >= screen.w) return BB_SKIP;
|
||||
if (sy >= screen.h) return BB_SKIP;
|
||||
cols = GCV_W; t = sx + cols; if (t > screen.w) cols = screen.w - sx;
|
||||
rows = GCV_H; t = sy + rows; if (t > screen.h) rows = screen.h - sy;
|
||||
rowbytes = cols * bpp_b;
|
||||
if (pitch < rowbytes) pitch = rowbytes;
|
||||
// stosd moves whole dwords: trim the row to a multiple of 4 so rowadv and
|
||||
// the reported byte count match what was actually written (at 24/16 bpp
|
||||
// rowbytes is not divisible by 4 and every row would drift by 1-2 bytes)
|
||||
rowdw = rowbytes / 4;
|
||||
rowbytes = rowdw * 4;
|
||||
rowadv = pitch - rowbytes;
|
||||
t = sx * bpp_b;
|
||||
off = sy * pitch; off = off + t;
|
||||
// last byte we will touch must be inside the GS segment (no-LFB safety)
|
||||
t = rows - 1; t = t * pitch; t = t + off; t = t + rowbytes; t = t - 1;
|
||||
lim = gs_limit();
|
||||
if (t > lim) return BB_SKIP;
|
||||
BenchBegin();
|
||||
do {
|
||||
vram_fill_gs(off, rowdw, rowadv, rows);
|
||||
c++;
|
||||
} while (BenchTicks() < 100);
|
||||
bpf = rowbytes * rows; // bytes per frame
|
||||
mb = muldiv(c, bpf, 1048576); // total MB written (MiB, like the disk tests)
|
||||
return PerSecX100(mb); // MB/s
|
||||
}
|
||||
|
||||
void Register_GPU()
|
||||
{
|
||||
RegisterTest(SECT_GPU, "Fill Rate", "MPix/s", REF_FILL, #t_gpu_fill);
|
||||
RegisterTest(SECT_GPU, "Blit (f7)", "MPix/s", REF_BLIT, #t_gpu_blit);
|
||||
RegisterTest(SECT_GPU, "VRAM Read (f36)","MPix/s", REF_READ, #t_gpu_read);
|
||||
RegisterTest(SECT_GPU, "VRAM Write (gs)","MB/s", REF_VWRITE, #t_gpu_vramwrite);
|
||||
RegisterTest(SECT_GPU, "Lines (f38)", "Kline/s", REF_LINE, #t_gpu_lines);
|
||||
RegisterTest(SECT_GPU, "Text (f4)", "Kchar/s", REF_TEXT, #t_gpu_text);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,285 @@
|
||||
#include "TWB\colors.h"
|
||||
#include "TWB\anchors.h"
|
||||
#include "TWB\parse_tag.h"
|
||||
#include "TWB\special.h"
|
||||
#include "TWB\tag_list.h"
|
||||
#define DEFAULT_BG_COL 0xffFBE5E4; //- peach; 0xffEBE8E9 - more grey
|
||||
dword link_color_default;
|
||||
dword link_color_active;
|
||||
#include "TWB\links.h"
|
||||
|
||||
#define BODY_MARGIN 6
|
||||
#define BASIC_CHAR_W 8
|
||||
#define BASIC_LINE_H 18
|
||||
|
||||
struct STYLE {
|
||||
bool
|
||||
b, u, s, h,
|
||||
font,
|
||||
pre,
|
||||
blq,
|
||||
button,
|
||||
image,
|
||||
nav, header;
|
||||
LIST tag_list;
|
||||
dword title;
|
||||
dword cur_line_h;
|
||||
void reset();
|
||||
};
|
||||
|
||||
void STYLE::reset()
|
||||
{
|
||||
b = u = s = h = font = pre = blq =
|
||||
title = button = image = nav = header = false;
|
||||
cur_line_h = NULL;
|
||||
tag_list.reset();
|
||||
}
|
||||
|
||||
struct TWebBrowser {
|
||||
llist list;
|
||||
STYLE style;
|
||||
dword draw_y, draw_x, draw_w, left_gap;
|
||||
dword o_bufpointer;
|
||||
int cur_encoding, custom_encoding;
|
||||
bool link, t_html, t_body;
|
||||
dword bufpointer;
|
||||
dword bufsize;
|
||||
dword is_html;
|
||||
collection img_url;
|
||||
char header[150];
|
||||
char linebuf[500];
|
||||
char redirect[URL_SIZE];
|
||||
|
||||
bool secondrun;
|
||||
|
||||
void SetStyle();
|
||||
void RenderTextbuf();
|
||||
void RenderLine();
|
||||
bool RenderImage();
|
||||
|
||||
void SetPageDefaults();
|
||||
void ParseHtml();
|
||||
void Reparse();
|
||||
void NewLine();
|
||||
void ChangeEncoding();
|
||||
void AddCharToTheLine();
|
||||
void DrawPage();
|
||||
|
||||
void tag_a();
|
||||
void tag_p();
|
||||
void tag_img();
|
||||
void tag_kosicon();
|
||||
void tag_div();
|
||||
void tag_h1234_caption();
|
||||
void tag_ol_ul_dt();
|
||||
void tag_li();
|
||||
void tag_q();
|
||||
void tag_hr();
|
||||
void tag_code();
|
||||
void tag_meta_xml();
|
||||
void tag_body();
|
||||
void tag_iframe();
|
||||
void tag_title();
|
||||
void tag_font();
|
||||
void tag_table_reset();
|
||||
void tag_table();
|
||||
void tag_td();
|
||||
void tag_tr();
|
||||
void reset_font_style();
|
||||
};
|
||||
|
||||
#include "TWB\render.h"
|
||||
#include "TWB\set_style.h"
|
||||
//============================================================================================
|
||||
void TWebBrowser::SetPageDefaults()
|
||||
{
|
||||
t_html = t_body = link = false;
|
||||
style.reset();
|
||||
reset_font_style();
|
||||
link_color_default = 0x0000FF;
|
||||
link_color_active = 0xFF0000;
|
||||
links.clear();
|
||||
anchors.clear();
|
||||
img_url.drop();
|
||||
text_colors.drop();
|
||||
text_colors.add(0);
|
||||
if (secondrun) {
|
||||
canvas.Init(list.x, list.y, list.w, math.max(list.visible, list.count)+200);
|
||||
canvas.Fill(0, bg_colors.get(0));
|
||||
}
|
||||
bg_colors.drop();
|
||||
bg_colors.add(DEFAULT_BG_COL);
|
||||
header = NULL;
|
||||
draw_y = BODY_MARGIN;
|
||||
draw_x = left_gap = BODY_MARGIN;
|
||||
draw_w = list.w - BODY_MARGIN - BODY_MARGIN;
|
||||
linebuf = 0;
|
||||
redirect = '\0';
|
||||
tag_table_reset();
|
||||
is_html = true;
|
||||
if (!strstri(bufpointer, "<body")) {
|
||||
t_body = true;
|
||||
if (!strstri(bufpointer, "<html")) && (!strstri(bufpointer, "<head")) && (!strstri(bufpointer, "<title"))
|
||||
&& (!strstr(bufpointer, "<?xml")) && (!strstr(bufpointer, "<xml")) {
|
||||
style.pre = true; //show linebreaks for a plaint text
|
||||
is_html = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
//============================================================================================
|
||||
void TWebBrowser::Reparse()
|
||||
{
|
||||
ParseHtml(bufpointer, bufsize);
|
||||
}
|
||||
//============================================================================================
|
||||
void TWebBrowser::ParseHtml(dword _bufpointer, _bufsize){
|
||||
int tab_len;
|
||||
dword bufpos;
|
||||
bufsize = _bufsize;
|
||||
|
||||
if (bufpointer == _bufpointer) {
|
||||
custom_encoding = cur_encoding;
|
||||
} else {
|
||||
bufpointer = malloc(bufsize);
|
||||
memmov(bufpointer, _bufpointer, bufsize);
|
||||
|
||||
//hold original buffer
|
||||
o_bufpointer = malloc(bufsize);
|
||||
memmov(o_bufpointer, bufpointer, bufsize);
|
||||
|
||||
cur_encoding = CH_CP866;
|
||||
if (custom_encoding != -1) {
|
||||
cur_encoding = custom_encoding;
|
||||
bufpointer = ChangeCharset(cur_encoding, CH_CP866, bufpointer);
|
||||
bufsize = strlen(bufpointer);
|
||||
}
|
||||
}
|
||||
|
||||
table.cols.drop();
|
||||
secondrun = false;
|
||||
|
||||
_PARSE_START_:
|
||||
|
||||
SetPageDefaults();
|
||||
for (bufpos=bufpointer; bufpos < bufpointer+bufsize; bufpos++;)
|
||||
{
|
||||
if (style.pre) {
|
||||
if (ESBYTE[bufpos] == 0x0a) {
|
||||
RenderTextbuf();
|
||||
NewLine();
|
||||
continue;
|
||||
}
|
||||
if (ESBYTE[bufpos] == 0x09) {
|
||||
tab_len = draw_x - left_gap / list.font_w + strlen(#linebuf) % 4;
|
||||
if (!tab_len) tab_len = 4; else tab_len = 4 - tab_len;
|
||||
while (tab_len) {chrcat(#linebuf,' '); tab_len--;}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (ESBYTE[bufpos] == '&') {
|
||||
bufpos = GetUnicodeSymbol(#linebuf, sizeof(TWebBrowser.linebuf)-1, bufpos+1, bufpointer+bufsize);
|
||||
continue;
|
||||
}
|
||||
if (ESBYTE[bufpos] == '<') && (is_html) {
|
||||
if (strchr("!/?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", ESBYTE[bufpos+1])) {
|
||||
bufpos++;
|
||||
if (tag.parse(#bufpos, bufpointer + bufsize)) {
|
||||
|
||||
/*
|
||||
if (secondrun)
|
||||
{
|
||||
if (streq(#tag.name, "tr")) debugln(" ");
|
||||
if (!tag.opened) {
|
||||
debugch('/');
|
||||
} else {
|
||||
debugch(' ');
|
||||
}
|
||||
debugln(sprintf(#param, "%s x%i y%i %s",
|
||||
#tag.name, draw_x, draw_y, #linebuf));
|
||||
}
|
||||
//*/
|
||||
RenderTextbuf();
|
||||
if (debug_mode) { debugch('<'); if(!tag.opened)debugch('/'); debug(#tag.name); debugln(">"); }
|
||||
$push cur_encoding
|
||||
SetStyle();
|
||||
$pop eax
|
||||
// The thing is that UTF if longer than other encodings.
|
||||
// So if encoding was changed from UTF to DOS than $bufpos position got wrong,
|
||||
// and we have to start parse from the very beginning
|
||||
if (EAX != cur_encoding) && (cur_encoding == CH_UTF8) {
|
||||
ParseHtml(bufpointer, strlen(bufpointer));
|
||||
return;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
AddCharToTheLine(ESBYTE[bufpos]);
|
||||
}
|
||||
|
||||
RenderTextbuf();
|
||||
list.count = draw_y + style.cur_line_h;
|
||||
list.CheckDoesValuesOkey();
|
||||
anchors.current = NULL;
|
||||
|
||||
if (!secondrun) {
|
||||
secondrun = true;
|
||||
goto _PARSE_START_;
|
||||
}
|
||||
if (!header) {
|
||||
strncpy(#header, #version, sizeof(TWebBrowser.header)-1);
|
||||
DrawTitle(#header);
|
||||
}
|
||||
}
|
||||
//============================================================================================
|
||||
void TWebBrowser::AddCharToTheLine(unsigned char _char)
|
||||
{
|
||||
dword line_len = strlen(#linebuf);
|
||||
if (_char<=15) _char=' ';
|
||||
if (!style.pre) && (_char == ' ')
|
||||
{
|
||||
if (linebuf[line_len-1]==' ') return; //no double spaces
|
||||
if (draw_x==left_gap) && (!linebuf) return; //no paces at the beginning of the line
|
||||
if (link) && (line_len==0) return;
|
||||
}
|
||||
if (line_len < sizeof(TWebBrowser.linebuf)) {
|
||||
chrcat(#linebuf+line_len, _char);
|
||||
} else {
|
||||
RenderTextbuf();
|
||||
}
|
||||
}
|
||||
//============================================================================================
|
||||
void TWebBrowser::ChangeEncoding(int _new_encoding)
|
||||
{
|
||||
if (cur_encoding == _new_encoding) return;
|
||||
cur_encoding = _new_encoding;
|
||||
bufpointer = ChangeCharset(cur_encoding, CH_CP866, bufpointer);
|
||||
if (header) {
|
||||
ChangeCharset(cur_encoding, CH_CP866, #header);
|
||||
DrawTitle(#header);
|
||||
}
|
||||
}
|
||||
//============================================================================================
|
||||
scroll_bar scroll_wv = { 15,NULL,NULL,NULL,
|
||||
0,2,NULL,0,0,0xeeeeee,0xBBBbbb,0xeeeeee};
|
||||
|
||||
void TWebBrowser::DrawPage()
|
||||
{
|
||||
if (list.w!=canvas.bufw) Reparse();
|
||||
canvas.Show(list.first, list.h);
|
||||
|
||||
scroll_wv.max_area = list.count;
|
||||
scroll_wv.cur_area = list.visible;
|
||||
scroll_wv.position = list.first;
|
||||
scroll_wv.all_redraw = 0;
|
||||
scroll_wv.start_x = list.x + list.w;
|
||||
scroll_wv.start_y = list.y;
|
||||
scroll_wv.size_y = list.h;
|
||||
|
||||
if (list.count <= list.visible) {
|
||||
DrawBar(scroll_wv.start_x, scroll_wv.start_y, scroll_wv.size_x,
|
||||
scroll_wv.size_y, bg_colors.get(0) & 0x00FFFFFF);
|
||||
} else {
|
||||
scrollbar_v_draw(#scroll_wv);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
struct _anchors {
|
||||
char current[256];
|
||||
collection anchor_name;
|
||||
collection anchor_position;
|
||||
void add();
|
||||
int get_pos_by_name();
|
||||
void clear();
|
||||
void take_anchor_from();
|
||||
} anchors=0;
|
||||
|
||||
void _anchors::add(dword _name, _pos)
|
||||
{
|
||||
anchor_name.add(_name);
|
||||
anchor_position.add(itoa(_pos));
|
||||
}
|
||||
|
||||
int _anchors::get_pos_by_name(dword _get_name)
|
||||
{
|
||||
dword pos_name = anchor_name.get_pos_by_name(_get_name);
|
||||
if (ESBYTE[_get_name]==NULL) return 0;
|
||||
if (pos_name==-1) {
|
||||
return -1;
|
||||
} else {
|
||||
return atoi(anchor_position.get(pos_name));
|
||||
}
|
||||
}
|
||||
|
||||
void _anchors::clear()
|
||||
{
|
||||
anchor_name.drop();
|
||||
anchor_position.drop();
|
||||
}
|
||||
|
||||
void _anchors::take_anchor_from(dword _URL)
|
||||
{
|
||||
int anchor_pos;
|
||||
if (!current) && (_URL) && (anchor_pos = strrchr(_URL, '#')) {
|
||||
strncpy(#current, _URL+anchor_pos-1, sizeof(_anchors.current)-1);
|
||||
ESBYTE[_URL+anchor_pos-1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
collection_int text_colors;
|
||||
collection_int bg_colors;
|
||||
|
||||
struct WEB_COLORS {
|
||||
char *name;
|
||||
dword rgb;
|
||||
} web_colors[] = {
|
||||
"aliceblue", 0xF0F8FF, "antiquewhite", 0xFAEBD7, "aqua", 0x00FFFF, "aquamarine", 0x7FFFD4,
|
||||
"azure", 0xF0FFFF, "beige", 0xF5F5DC, "bisque", 0xFFE4C4, "black", 0x000000,
|
||||
"blanchedalmond", 0xFFEBCD, "blue", 0x0000FF, "blueviolet", 0x8A2BE2, "brown", 0xA52A2A,
|
||||
"burlywood", 0xDEB887, "cadetblue", 0x5F9EA0, "chartreuse", 0x7FFF00, "chocolate", 0xD2691E,
|
||||
"coral", 0xFF7F50, "cornflowerblue", 0x6495ED, "cornsilk", 0xFFF8DC, "crimson", 0xDC143C,
|
||||
"cyan", 0x00FFFF, "darkblue", 0x00008B, "darkcyan", 0x008B8B, "darkgoldenrod", 0xB8860B,
|
||||
"darkgray", 0xA9A9A9, "darkgreen", 0x006400, "darkkhaki", 0xBDB76B, "darkmagenta", 0x8B008B,
|
||||
"darkolivegreen", 0x556B2F, "darkorange", 0xFF8C00, "darkorchid", 0x9932CC, "darkred", 0x8B0000,
|
||||
"darksalmon", 0xE9967A, "darkseagreen", 0x8FBC8F, "darkslateblue", 0x483D8B, "darkslategray", 0x2F4F4F,
|
||||
"darkturquoise", 0x00CED1, "darkviolet", 0x9400D3, "deeppink", 0xFF1493, "deepskyblue", 0x00BFFF,
|
||||
"dimgray", 0x696969, "dodgerblue", 0x1E90FF, "firebrick", 0xB22222, "floralwhite", 0xFFFAF0,
|
||||
"forestgreen", 0x228B22, "fuchsia", 0xFF00FF, "gainsboro", 0xDCDCDC, "ghostwhite", 0xF8F8FF,
|
||||
"gold", 0xFFD700, "goldenrod", 0xDAA520, "gray", 0x808080, "green", 0x008000,
|
||||
"greenyellow", 0xADFF2F, "honeydew", 0xF0FFF0, "hotpink", 0xFF69B4, "indianred", 0xCD5C5C,
|
||||
"indigo", 0x4B0082, "ivory", 0xFFFFF0, "khaki", 0xF0E68C, "lavender", 0xE6E6FA,
|
||||
"lavenderblush", 0xFFF0F5, "lawngreen", 0x7CFC00, "lemonchiffon", 0xFFFACD, "lightblue", 0xADD8E6,
|
||||
"lightcoral", 0xF08080, "lightcyan", 0xE0FFFF, "lightgreen", 0x90EE90, "lightgrey", 0xD3D3D3,
|
||||
"lightpink", 0xFFB6C1, "lightsalmon", 0xFFA07A, "lightseagreen", 0x20B2AA, "lightskyblue", 0x87CEFA,
|
||||
"lightslategray", 0x778899, "lightsteelblue", 0xB0C4DE, "lightyellow", 0xFFFFE0, "lime", 0x00FF00,
|
||||
"limegreen", 0x32CD32, "linen", 0xFAF0E6, "magenta", 0xFF00FF, "maroon", 0x800000,
|
||||
"mediumaquamarine", 0x66CDAA, "mediumblue", 0x0000CD, "mediumorchid", 0xBA55D3, "mediumpurple", 0x9370DB,
|
||||
"mediumseagreen", 0x3CB371, "mediumslateblue", 0x7B68EE, "mediumspringgreen",0x00FA9A, "mediumturquoise", 0x48D1CC,
|
||||
"mediumvioletred", 0xC71585, "midnightblue", 0x191970, "mintcream", 0xF5FFFA, "mistyrose", 0xFFE4E1,
|
||||
"moccasin", 0xFFE4B5, "navajowhite", 0xFFDEAD, "navy", 0x000080, "oldlace", 0xFDF5E6,
|
||||
"olive", 0x808000, "olivedrab", 0x6B8E23, "orange", 0xFFA500, "orangered", 0xFF4500,
|
||||
"orchid", 0xDA70D6, "palegoldenrod", 0xEEE8AA, "palegreen", 0x98FB98, "paleturquoise", 0xAFEEEE,
|
||||
"palevioletred", 0xDB7093, "papayawhip", 0xFFEFD5, "peachpuff", 0xFFDAB9, "peru", 0xCD853F,
|
||||
"pink", 0xFFC0CB, "plum", 0xDDA0DD, "powderblue", 0xB0E0E6, "purple", 0x800080,
|
||||
"red", 0xFF0000, "rosybrown", 0xBC8F8F, "royalblue", 0x4169E1, "saddlebrown", 0x8B4513,
|
||||
"salmon", 0xFA8072, "sandybrown", 0xF4A460, "seagreen", 0x2E8B57, "seashell", 0xFFF5EE,
|
||||
"sienna", 0xA0522D, "silver", 0xC0C0C0, "skyblue", 0x87CEEB, "slateblue", 0x6A5ACD,
|
||||
"slategray", 0x708090, "snow", 0xFFFAFA, "springgreen", 0x00FF7F, "steelblue", 0x4682B4,
|
||||
"tan", 0xD2B48C, "teal", 0x008080, "thistle", 0xD8BFD8, "tomato", 0xFF6347,
|
||||
"turquoise", 0x40E0D0, "violet", 0xEE82EE, "wheat", 0xF5DEB3, "white", 0xFFFFFF,
|
||||
"whitesmoke", 0xF5F5F5, "yellow", 0xFFFF00, "yellowgreen", 0x9ACD32, 0};
|
||||
|
||||
dword GetColor(char* color_str)
|
||||
{
|
||||
int ii;
|
||||
dword textlen=0;
|
||||
dword color=0;
|
||||
|
||||
strlwr(color_str);
|
||||
if (ESBYTE[color_str] == '#') {
|
||||
color_str++;
|
||||
} else {
|
||||
for (ii=0; web_colors[ii].name!=0; ii++)
|
||||
{
|
||||
if (streq(color_str, web_colors[ii].name)) return web_colors[ii].rgb;
|
||||
}
|
||||
}
|
||||
|
||||
textlen = strlen(color_str);
|
||||
if (textlen==6) || (textlen==3)
|
||||
{
|
||||
FOR (ii=0; ii<textlen; ii++)
|
||||
{
|
||||
BL = ESBYTE[color_str+ii];
|
||||
IF ((BL>='0') && (BL<='9')) BL -= '0';
|
||||
//IF ((BL>='A') && (BL<='F')) BL -= 'A'-10;
|
||||
IF ((BL>='a') && (BL<='f')) BL -= 'a'-10;
|
||||
color = color*0x10 + BL;
|
||||
if (textlen==3) color = color*0x10 + BL; //#abc ->> #aabbcc
|
||||
}
|
||||
return color;
|
||||
}
|
||||
return text_colors.get(0);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
CustomCursor CursorPointer;
|
||||
dword CursorFile = FROM "TWB/pointer.cur";
|
||||
#include "..\lib\collection.h"
|
||||
|
||||
struct PAGE_LINKS {
|
||||
collection unic_links;
|
||||
collection element_links;
|
||||
|
||||
collection_int x;
|
||||
collection_int y;
|
||||
collection_int w;
|
||||
collection_int h;
|
||||
collection_int id;
|
||||
collection_int underline_h;
|
||||
|
||||
signed int active;
|
||||
dword active_url;
|
||||
bool hover();
|
||||
void add_link();
|
||||
void add_text();
|
||||
void clear();
|
||||
void draw_underline();
|
||||
} links;
|
||||
|
||||
void PAGE_LINKS::add_link(dword lpath)
|
||||
{
|
||||
unic_links.add(lpath);
|
||||
}
|
||||
|
||||
void PAGE_LINKS::add_text(dword _x, _y, _w, _h, _underline_h)
|
||||
{
|
||||
x.add(_x);
|
||||
y.add(_y);
|
||||
w.add(_w);
|
||||
h.add(_h);
|
||||
underline_h.add(_underline_h);
|
||||
element_links.add(unic_links.get_last());
|
||||
id.add(unic_links.count);
|
||||
}
|
||||
|
||||
void PAGE_LINKS::clear()
|
||||
{
|
||||
x.drop();
|
||||
y.drop();
|
||||
w.drop();
|
||||
h.drop();
|
||||
underline_h.drop();
|
||||
id.drop();
|
||||
|
||||
element_links.drop();
|
||||
unic_links.drop();
|
||||
|
||||
active = -1;
|
||||
active_url = 0;
|
||||
CursorPointer.Restore();
|
||||
}
|
||||
|
||||
void PAGE_LINKS::draw_underline(signed _id, dword list_first, list_y, color)
|
||||
{
|
||||
int i;
|
||||
if (_id == -1) return;
|
||||
for (i=0; i<id.count; i++)
|
||||
{
|
||||
if (id.get(i) - id.get(_id) == 0)
|
||||
&& (y.get(i) + h.get(i) - list_first > list_y) {
|
||||
DrawBar(x.get(i), y.get(i) + h.get(i) - list_first,
|
||||
w.get(i), underline_h.get(i), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PAGE_LINKS::hover(dword list_y, list_first)
|
||||
{
|
||||
int i;
|
||||
int mx = mouse.x;
|
||||
int my = mouse.y + list_first;
|
||||
if (!id.count) return false;
|
||||
|
||||
//Here we check is any link hovered
|
||||
for (i=0; i<id.count; i++)
|
||||
{
|
||||
if (mx>x.get(i)) && (my>y.get(i))
|
||||
&& (mx<x.get(i)+w.get(i)) && (my<y.get(i)+h.get(i))
|
||||
&& (my>list_y+list_first)
|
||||
{
|
||||
if (active!=i) {
|
||||
CursorPointer.Load(#CursorFile);
|
||||
CursorPointer.Set();
|
||||
|
||||
draw_underline(active, list_first, list_y, link_color_default);
|
||||
draw_underline(i, list_first, list_y, DEFAULT_BG_COL);
|
||||
|
||||
active_url = element_links.get(i);
|
||||
active = i;
|
||||
DrawStatusBar(active_url);
|
||||
}
|
||||
if (mouse.lkm) && (mouse.down) {
|
||||
DrawRectangle(x.get(active), -list_first + y.get(active),
|
||||
w.get(active), h.get(active), 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (active_url) {
|
||||
CursorPointer.Restore();
|
||||
draw_underline(active, list_first, list_y, link_color_default);
|
||||
active_url = 0;
|
||||
active = -1;
|
||||
DrawStatusBar(NULL);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
struct _DOM {
|
||||
collection tag;
|
||||
collection tagparam;
|
||||
collection_int parent;
|
||||
} dom;
|
||||
*/
|
||||
|
||||
struct _tag
|
||||
{
|
||||
char name[32];
|
||||
char prior[32];
|
||||
bool opened;
|
||||
collection attributes;
|
||||
collection values;
|
||||
dword value;
|
||||
dword number;
|
||||
bool is();
|
||||
bool parse();
|
||||
dword get_next_param();
|
||||
dword get_value_of();
|
||||
signed get_number_of();
|
||||
} tag=0;
|
||||
|
||||
bool _tag::is(dword _text)
|
||||
{
|
||||
return streq(#name, _text);
|
||||
}
|
||||
|
||||
bool _tag::parse(dword _bufpos, bufend)
|
||||
{
|
||||
bool retok = true;
|
||||
dword bufpos = ESDWORD[_bufpos];
|
||||
dword params, paramsend;
|
||||
|
||||
dword closepos;
|
||||
dword whitepos;
|
||||
dword openpos;
|
||||
|
||||
if (name) strcpy(#prior, #name); else prior = '\0';
|
||||
name = '\0';
|
||||
attributes.drop();
|
||||
values.drop();
|
||||
|
||||
if (!strncmp(bufpos,"!--",3))
|
||||
{
|
||||
bufpos+=3;
|
||||
//STRSTR
|
||||
while (strncmp(bufpos,"-->",3)!=0) && (bufpos < bufend)
|
||||
{
|
||||
bufpos++;
|
||||
}
|
||||
bufpos+=2;
|
||||
retok = false;
|
||||
goto _RET;
|
||||
}
|
||||
|
||||
if (ESBYTE[bufpos] == '/') {
|
||||
opened = false;
|
||||
bufpos++;
|
||||
} else {
|
||||
opened = true;
|
||||
}
|
||||
|
||||
closepos = strchr(bufpos, '>');
|
||||
whitepos = strchrw(bufpos, bufend-bufpos);
|
||||
|
||||
if (!whitepos) || (whitepos > closepos) {
|
||||
//no param
|
||||
strncpy(#name, bufpos, math.min(closepos - bufpos, sizeof(tag.name)-1));
|
||||
bufpos = closepos;
|
||||
} else {
|
||||
//we have param
|
||||
while (chrlnum(whitepos, '\"', closepos - whitepos)%2) { //alt="Next>>"
|
||||
if (!openpos = strchr(closepos+1, '<')) break;
|
||||
if (openpos < strchr(closepos+1, '>')) break;
|
||||
if (!closepos = EAX) {closepos = bufend;break;}
|
||||
}
|
||||
strncpy(#name, bufpos, math.min(whitepos - bufpos, sizeof(tag.name)-1));
|
||||
bufpos = closepos;
|
||||
|
||||
params = malloc(closepos - whitepos + 1);
|
||||
strncpy(params, whitepos, closepos - whitepos);
|
||||
paramsend = params + closepos - whitepos;
|
||||
while (paramsend = get_next_param(params, paramsend-1));
|
||||
free(params);
|
||||
}
|
||||
|
||||
if (name) {
|
||||
strlwr(#name);
|
||||
// ignore text inside the next tags
|
||||
if (is("script")) || (is("style")) || (is("binary")) || (is("select")) {
|
||||
strcpy(#prior, #name);
|
||||
sprintf(#name, "</%s>", #prior);
|
||||
if (strstri(bufpos, #name)) bufpos = EAX-1;
|
||||
retok = false;
|
||||
} else {
|
||||
if (name[strlen(#name)-1]=='/') name[strlen(#name)-1]=NULL; //for <br/>
|
||||
}
|
||||
} else {
|
||||
retok = false;
|
||||
}
|
||||
|
||||
_RET:
|
||||
ESDWORD[_bufpos] = bufpos;
|
||||
return retok;
|
||||
}
|
||||
|
||||
dword _tag::get_next_param(dword ps, pe)
|
||||
{
|
||||
// "ps" - param start
|
||||
// "pe" - param end
|
||||
// "q" - quote char
|
||||
char q = NULL;
|
||||
dword initial_pe = pe;
|
||||
dword fixeq;
|
||||
dword val;
|
||||
dword attr;
|
||||
|
||||
if (ESBYTE[pe] == '/') pe--;
|
||||
while (pe>ps) && (__isWhite(ESBYTE[pe])) pe--;
|
||||
|
||||
if (ESBYTE[pe] == '"') || (ESBYTE[pe] == '\'')
|
||||
{
|
||||
//remove quote
|
||||
q = ESBYTE[pe];
|
||||
ESBYTE[pe] = '\0';
|
||||
pe--;
|
||||
|
||||
//find VAL start and copy
|
||||
pe = strrchr(ps, q) + ps;
|
||||
val = pe;
|
||||
if (pe>ps) pe--;
|
||||
ESBYTE[pe] = '\0';
|
||||
|
||||
//find ATTR end
|
||||
while (pe > ps) && (ESBYTE[pe] != '=') pe--;
|
||||
ESBYTE[pe] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
//find VAL start and copy
|
||||
while (pe > ps) && (ESBYTE[pe] != '=') pe--;
|
||||
val = pe+1;
|
||||
ESBYTE[pe] = '\0';
|
||||
//already have ATTR end
|
||||
}
|
||||
|
||||
if (pe > ps) pe--;
|
||||
|
||||
//Fix case: "src ="
|
||||
while (pe>ps) && (__isWhite(ESBYTE[pe])) {
|
||||
ESBYTE[pe] = '\0';
|
||||
pe--;
|
||||
}
|
||||
|
||||
//find ATTR start and copy
|
||||
while (pe>ps) && (!__isWhite(ESBYTE[pe])) pe--;
|
||||
attr = pe + 1;
|
||||
ESBYTE[pe] = '\0';
|
||||
|
||||
// Fix case: src=./images/logo?sid=e8ece8b38b
|
||||
// Exchange '=' and '\0' position.
|
||||
// attr: src=./images/logo?sid => src
|
||||
// val: e8ece8b38b => ./images/logo?sid=e8ece8b38b
|
||||
fixeq = strchr(attr,'=');
|
||||
if (!q) && (fixeq) {
|
||||
ESBYTE[val-1] >< ESBYTE[fixeq];
|
||||
val = fixeq+1;
|
||||
}
|
||||
strlwr(attr);
|
||||
strrtrim(val);
|
||||
//Fix case: " img.png"
|
||||
while(__isWhite(ESBYTE[val])) && (val<initial_pe) val++;
|
||||
|
||||
attributes.add(attr);
|
||||
values.add(val);
|
||||
|
||||
if (pe==ps) return NULL;
|
||||
return pe;
|
||||
}
|
||||
|
||||
dword _tag::get_value_of(dword _attr_name)
|
||||
{
|
||||
int pos = attributes.get_pos_by_name(_attr_name);
|
||||
if (pos == -1) {
|
||||
value = 0;
|
||||
} else {
|
||||
value = values.get(pos);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
signed _tag::get_number_of(dword _attr_name)
|
||||
{
|
||||
if (get_value_of(_attr_name)) {
|
||||
number = atoi(value);
|
||||
} else {
|
||||
number = 0;
|
||||
}
|
||||
return number;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,191 @@
|
||||
CANVAS canvas;
|
||||
|
||||
void TWebBrowser::RenderLine(dword _line)
|
||||
{
|
||||
unsigned px; //paint x coordinate
|
||||
unsigned pw; //paint y coordinate
|
||||
dword pc; //paint color
|
||||
int zoom;
|
||||
|
||||
if (style.title)
|
||||
{
|
||||
strncpy(#header, _line, sizeof(TWebBrowser.header)-1);
|
||||
strncat(#header, " - ", sizeof(TWebBrowser.header)-1);
|
||||
strncat(#header, #version, sizeof(TWebBrowser.header)-1);
|
||||
}
|
||||
else if (t_html) && (!t_body) {
|
||||
//
|
||||
}
|
||||
else if (ESBYTE[_line])
|
||||
{
|
||||
pw = strlen(_line) * list.font_w;
|
||||
zoom = list.font_w / BASIC_CHAR_W;
|
||||
|
||||
//there is some shit happens!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
if (pw > draw_w + BODY_MARGIN) {
|
||||
//debugln("shit");
|
||||
}
|
||||
|
||||
if (debug_mode) {
|
||||
canvas.DrawBar(draw_x, draw_y, pw, list.item_h, 0xCCCccc);
|
||||
debugln(_line);
|
||||
}
|
||||
|
||||
style.cur_line_h = math.max(style.cur_line_h, list.item_h);
|
||||
|
||||
if (!secondrun) goto _SKIP_DRAW;
|
||||
if (bg_colors.get_last() - bg_colors.get(0)) {
|
||||
canvas.DrawBar(draw_x, draw_y, pw, list.item_h, bg_colors.get_last());
|
||||
}
|
||||
|
||||
if (style.image) {
|
||||
canvas.DrawBar(draw_x, draw_y, pw, list.item_h-1, 0xF9DBCB);
|
||||
}
|
||||
if (style.button) {
|
||||
canvas.DrawBar(draw_x, draw_y, pw, list.item_h - calc(zoom*2), 0xCCCccc);
|
||||
canvas.DrawBar(draw_x, draw_y + list.item_h - calc(zoom*2), pw, zoom, 0x999999);
|
||||
}
|
||||
|
||||
pc = text_colors.get_last();
|
||||
if (link) && (pc == text_colors.get(0)) pc = link_color_default;
|
||||
|
||||
canvas.WriteText(draw_x, draw_y+1, list.font_type, pc, _line, NULL);
|
||||
if (style.b) canvas.WriteText(draw_x+1, draw_y+1, list.font_type, pc, _line, NULL);
|
||||
if (style.s) canvas.DrawBar(draw_x, list.item_h / 2 - zoom + draw_y, pw, zoom, pc);
|
||||
if (style.u) canvas.DrawBar(draw_x, draw_y + list.font_h, pw, zoom, pc);
|
||||
if (link) {
|
||||
if (ESBYTE[_line]==' ') && (ESBYTE[_line+1]==NULL) {} else {
|
||||
canvas.DrawBar(draw_x, draw_y + list.font_h, pw, zoom, link_color_default);
|
||||
links.add_text(draw_x, draw_y + list.y, pw, list.font_h, zoom);
|
||||
}
|
||||
}
|
||||
_SKIP_DRAW:
|
||||
draw_x += pw;
|
||||
}
|
||||
ESBYTE[_line] = NULL;
|
||||
}
|
||||
|
||||
void TWebBrowser::RenderTextbuf()
|
||||
{
|
||||
dword lbp = #linebuf;
|
||||
int br; //break position
|
||||
char nul = '\0';
|
||||
unsigned int len;
|
||||
|
||||
//Do we need a line break?
|
||||
|
||||
if (!linebuf[0]) return;
|
||||
|
||||
while (len = strlen(lbp)) && (len * list.font_w + draw_x - left_gap >= draw_w) {
|
||||
//Yes, we do. Lets calculate where...
|
||||
br = len;
|
||||
|
||||
//debugln(" \\n");
|
||||
|
||||
//Is a new line fits in the maximum line width?
|
||||
if (br * list.font_w + draw_x - left_gap >= draw_w) {
|
||||
br = draw_w - draw_x / list.font_w;
|
||||
if (br < 0) br = 0;
|
||||
while(br) {
|
||||
if (ESBYTE[lbp + br]==' ') {
|
||||
if (br < len) br++;
|
||||
break;
|
||||
}
|
||||
br--;
|
||||
}
|
||||
}
|
||||
//Maybe a new line is too big for the whole new line? Then we have to split it
|
||||
if (!br) && (len * list.font_w >= draw_w) {
|
||||
//debugln("bigbr");
|
||||
//debugval("draw_x", draw_x);
|
||||
//debugval("draw_w", draw_w);
|
||||
br = draw_w - draw_x / list.font_w;
|
||||
//debugval("cut into 1", br * list.font_w);
|
||||
if (br < 0) {
|
||||
NewLine();
|
||||
br = draw_w - draw_x / list.font_w;
|
||||
//debugval("cut into 2", br * list.font_w);
|
||||
}
|
||||
}
|
||||
|
||||
if (br) {
|
||||
ESBYTE[lbp + br] >< nul;
|
||||
RenderLine(lbp);
|
||||
ESBYTE[lbp + br] >< nul;
|
||||
lbp += br;
|
||||
while (ESBYTE[lbp]==' ') && (ESBYTE[lbp]) lbp++;
|
||||
if (ESBYTE[lbp]) NewLine();
|
||||
} else {
|
||||
NewLine();
|
||||
RenderLine(lbp);
|
||||
}
|
||||
}
|
||||
RenderLine(lbp);
|
||||
}
|
||||
|
||||
void TWebBrowser::NewLine()
|
||||
{
|
||||
static bool empty_line = true;
|
||||
|
||||
if (draw_x==left_gap) && (draw_y==BODY_MARGIN) return;
|
||||
if (t_html) && (!t_body) return;
|
||||
|
||||
if (draw_x == style.tag_list.level * 5 * list.font_w + left_gap) {
|
||||
if (!empty_line) {
|
||||
empty_line=true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
empty_line = false;
|
||||
}
|
||||
|
||||
draw_y += style.cur_line_h;
|
||||
style.cur_line_h = list.item_h;
|
||||
if (style.blq) draw_x = 6 * list.font_w; else draw_x = 0; //left_gap += style.tag_list.level * 5 * list.font_w ???
|
||||
draw_x += style.tag_list.level * 5 * list.font_w + left_gap;
|
||||
}
|
||||
|
||||
bool TWebBrowser::RenderImage(dword cur_img)
|
||||
{
|
||||
int img_x, img_y, img_w, img_h;
|
||||
dword imgbuf[44];
|
||||
|
||||
if (!cur_img) || (!buf_data) return false;
|
||||
|
||||
img_h = ESDWORD[cur_img+8];
|
||||
img_w = ESDWORD[cur_img+4];
|
||||
|
||||
if (img_w + draw_x - left_gap >= draw_w) NewLine();
|
||||
img_y = draw_y;
|
||||
if (img_h < list.item_h) img_y += list.item_h - img_h / 2 - 1; else img_y -= 2;
|
||||
style.cur_line_h = math.max(style.cur_line_h, img_h);
|
||||
|
||||
img_w = math.min(img_w, canvas.bufw - draw_x);
|
||||
|
||||
if (link) links.add_text(draw_x + list.x, img_y + list.y, img_w, img_h, 0);
|
||||
|
||||
if (img_y + img_h >= canvas.bufh) canvas.IncreaseBufSize();
|
||||
|
||||
if (secondrun)
|
||||
{
|
||||
if (ESDWORD[cur_img+20] != IMAGE_BPP32) {
|
||||
img_convert stdcall(cur_img, 0, IMAGE_BPP32, 0, 0);
|
||||
$push eax
|
||||
img_destroy stdcall(cur_img);
|
||||
$pop eax
|
||||
cur_img = EAX;
|
||||
if (!EAX) return false;
|
||||
}
|
||||
imgbuf[04] = canvas.bufw;
|
||||
imgbuf[08] = canvas.bufh;
|
||||
imgbuf[20] = IMAGE_BPP32;
|
||||
imgbuf[24] = buf_data+8;
|
||||
img_blend stdcall(#imgbuf, cur_img, draw_x, img_y, 0, 0, img_w, img_h);
|
||||
img_destroy stdcall(cur_img);
|
||||
}
|
||||
|
||||
draw_x += img_w;
|
||||
if (draw_x - left_gap >= draw_w) NewLine();
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
void TWebBrowser::SetStyle()
|
||||
{
|
||||
if (tag.get_value_of("name")) || (tag.get_value_of("id")) {
|
||||
anchors.add(tag.value, draw_y);
|
||||
if (anchors.current) && (streq(tag.value, #anchors.current+1)) {
|
||||
list.first = draw_y;
|
||||
anchors.current = NULL;
|
||||
}
|
||||
}
|
||||
if (tag.is("a")) { tag_a(); return; }
|
||||
if (tag.is("p")) { tag_p(); return; }
|
||||
if (tag.is("img")) { tag_img(); return; }
|
||||
if (tag.is("kosicon")) { tag_kosicon(); return; }
|
||||
if (tag.is("div")) { tag_div(); return; }
|
||||
if (tag.is("br")) { /*draw_x++;*/NewLine(); return; }
|
||||
if (tag.is("nav")) { style.nav = tag.opened; return; }
|
||||
if (tag.is("header")) { NewLine(); return; }
|
||||
if (tag.is("article")) { NewLine(); return; }
|
||||
if (tag.is("footer")) { NewLine(); return; }
|
||||
if (tag.is("figure")) { NewLine(); return; }
|
||||
if (tag.is("w:p")) { NewLine(); return; }
|
||||
if (tag.is("b")) { style.b = tag.opened; return; }
|
||||
if (tag.is("strong")) { style.b = tag.opened; return; }
|
||||
if (tag.is("big")) { style.b = tag.opened; return; }
|
||||
if (tag.is("w:b")) { style.b = tag.opened; return; }
|
||||
if (tag.is("u")) { style.u = tag.opened; return; }
|
||||
if (tag.is("ins")) { style.u = tag.opened; return; }
|
||||
if (tag.is("s")) { style.s = tag.opened; return; }
|
||||
if (tag.is("strike")) { style.s = tag.opened; return; }
|
||||
if (tag.is("del")) { style.s = tag.opened; return; }
|
||||
if (tag.is("pre")) { style.pre = tag.opened; return; }
|
||||
if (tag.is("blockquote")) { style.blq = tag.opened; return; }
|
||||
if (tag.is("button")) { style.button = tag.opened; return; }
|
||||
if (tag.is("dl")) { if (tag.opened) NewLine(); return; }
|
||||
if (tag.is("w:r")) { if (!tag.opened) style.b = false; return; }
|
||||
if (tag.is("h1")) { tag_h1234_caption(); return; }
|
||||
if (tag.is("h2")) { tag_h1234_caption(); return; }
|
||||
if (tag.is("h3")) { tag_h1234_caption(); return; }
|
||||
if (tag.is("h4")) { tag_h1234_caption(); return; }
|
||||
if (tag.is("font")) { tag_font(); return; }
|
||||
if (tag.is("dt")) { tag_ol_ul_dt(); return; }
|
||||
if (tag.is("dd")) { tag_ol_ul_dt(); return; }
|
||||
if (tag.is("ul")) { tag_ol_ul_dt(); return; }
|
||||
if (tag.is("ol")) { tag_ol_ul_dt(); return; }
|
||||
if (tag.is("li")) { tag_li(); return; }
|
||||
if (tag.is("q")) { tag_q(); return; }
|
||||
if (tag.is("hr")) { tag_hr(); return; }
|
||||
if (tag.is("meta")) { tag_meta_xml(); return; }
|
||||
if (tag.is("?xml")) { tag_meta_xml(); return; }
|
||||
if (tag.is("code")) { tag_code(); return; }
|
||||
if (tag.is("iframe")) { tag_iframe(); return; }
|
||||
if (tag.is("caption")) { tag_h1234_caption(); return; }
|
||||
if (tag.is("title")) { tag_title(); return; }
|
||||
if (tag.is("body")) { tag_body(); return; }
|
||||
if (tag.is("html")) { t_html = tag.opened; return; }
|
||||
if (tag.is("table")) { tag_table(); return; }
|
||||
if (tag.is("tr")) { tag_table(); return; }
|
||||
if (tag.is("th")) { tag_table(); return; }
|
||||
if (tag.is("td")) { tag_table(); return; }
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_p()
|
||||
{
|
||||
IF (tag.prior[0] == 'h') || (streq(#tag.prior,"td")) return;
|
||||
IF (!tag.opened) && (streq(#tag.prior,"p")) return;
|
||||
NewLine();
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_title()
|
||||
{
|
||||
style.title = tag.opened;
|
||||
if (!tag.opened) DrawTitle(#header);
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_font()
|
||||
{
|
||||
style.font = tag.opened;
|
||||
if (tag.opened)
|
||||
{
|
||||
if (tag.get_value_of("bg")) {
|
||||
bg_colors.add(GetColor(tag.value));
|
||||
} else {
|
||||
bg_colors.add(bg_colors.get_last());
|
||||
}
|
||||
if (tag.get_value_of("color")) {
|
||||
text_colors.add(GetColor(tag.value));
|
||||
} else {
|
||||
text_colors.add(text_colors.get_last());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (bg_colors.count>1) text_colors.pop();
|
||||
if (bg_colors.count>1) bg_colors.pop(); //never pop the last color
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_div()
|
||||
{
|
||||
//if (streq(#tag.prior,"div")) && (tag.opened) return;
|
||||
if (streq(#tag.prior,"td")) return;
|
||||
if (streq(#tag.prior,"div")) return;
|
||||
if (!tag.opened) && (style.font) text_colors.pop();
|
||||
NewLine();
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_iframe()
|
||||
{
|
||||
if (tag.get_value_of("src")) {
|
||||
NewLine();
|
||||
strcpy(#linebuf, "IFRAME: ");
|
||||
RenderTextbuf();
|
||||
link=true;
|
||||
links.add_link(tag.value);
|
||||
strncpy(#linebuf, tag.value, sizeof(TWebBrowser.linebuf)-1);
|
||||
RenderTextbuf();
|
||||
link=false;
|
||||
NewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_a()
|
||||
{
|
||||
if (!secondrun) return;
|
||||
if (tag.opened)
|
||||
{
|
||||
if (tag.get_value_of("href")) && (!strstr(tag.value,"javascript:"))
|
||||
{
|
||||
link = true;
|
||||
links.add_link(tag.value);
|
||||
}
|
||||
} else {
|
||||
link = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_meta_xml()
|
||||
{
|
||||
if (secondrun) return;
|
||||
if (custom_encoding == -1) if (tag.get_value_of("charset"))
|
||||
|| (tag.get_value_of("content")) || (tag.get_value_of("encoding"))
|
||||
{
|
||||
EDX = strrchr(tag.value, '=') + tag.value; //search in content=
|
||||
if (ESBYTE[EDX] == '"') EDX++;
|
||||
strlwr(EDX);
|
||||
EAX = get_encoding_type_by_name(EDX);
|
||||
if (EAX!=-1) ChangeEncoding(EAX);
|
||||
}
|
||||
if (streq(tag.get_value_of("http-equiv"), "refresh")) && (tag.get_value_of("content")) {
|
||||
if (tag.value = strstri(tag.value, "url")) strcpy(#redirect, tag.value);
|
||||
}
|
||||
}
|
||||
|
||||
signed int get_encoding_type_by_name(dword name)
|
||||
{
|
||||
EDX = name;
|
||||
if (streqrp(EDX,"utf-8")) || (streqrp(EDX,"utf8")) return CH_UTF8;
|
||||
else if (streqrp(EDX,"windows-1251")) || (streqrp(EDX,"windows1251")) return CH_CP1251;
|
||||
else if (streqrp(EDX,"dos")) || (streqrp(EDX,"cp-866")) return CH_CP866;
|
||||
else if (streqrp(EDX,"iso-8859-5")) || (streqrp(EDX,"iso8859-5")) return CH_ISO8859_5;
|
||||
else if (streqrp(EDX,"koi8-r")) || (streqrp(EDX,"koi8-u")) return CH_KOI8;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_code()
|
||||
{
|
||||
if (style.pre = tag.opened) {
|
||||
bg_colors.add(0xe4ffcb);
|
||||
} else {
|
||||
if (bg_colors.count>1) bg_colors.pop(); //never pop the last color
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_ol_ul_dt()
|
||||
{
|
||||
char type = ESBYTE[#tag.name];
|
||||
switch(type)
|
||||
{
|
||||
case 'd':
|
||||
if (ESBYTE[#tag.name+1]=='d') style.tag_list.upd_level(tag.opened, type);
|
||||
if (ESBYTE[#tag.name+1]=='t') style.tag_list.upd_level(false, 'd');
|
||||
if (tag.opened) NewLine();
|
||||
break;
|
||||
case 'u':
|
||||
case 'o':
|
||||
if (style.nav) && (style.tag_list.level) NewLine();
|
||||
style.tag_list.upd_level(tag.opened, type);
|
||||
if (!tag.opened) && (!style.pre) NewLine();
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_li()
|
||||
{
|
||||
//if (style.nav) return;
|
||||
if (tag.opened) {
|
||||
if (!style.tag_list.level) style.tag_list.upd_level(1, 'u');
|
||||
if (!style.pre) NewLine();
|
||||
if (style.tag_list.order_type() == 'u') {
|
||||
strcpy(#linebuf, "\31 ");
|
||||
draw_x = style.tag_list.level * 5 - 2 * list.font_w + left_gap;
|
||||
}
|
||||
if (style.tag_list.order_type() == 'o') {
|
||||
sprintf(#linebuf, "%i. ", style.tag_list.inc_counter());
|
||||
draw_x = style.tag_list.level * 5 - 1 - strlen(#linebuf) * list.font_w + left_gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_hr()
|
||||
{
|
||||
dword hrcol = 0x00777777;
|
||||
dword hr_width = draw_w-BODY_MARGIN-BODY_MARGIN;
|
||||
dword hr_size = 1;
|
||||
if (tag.get_value_of("color")) {
|
||||
hrcol = GetColor(tag.value);
|
||||
}
|
||||
if (tag.get_value_of("width")) && (!strchr(tag.value, '%')) {
|
||||
hr_width = math.min(hr_width, tag.get_number_of("width"));
|
||||
}
|
||||
if (tag.get_number_of("size")) {
|
||||
hr_size = math.min(500, tag.number);
|
||||
}
|
||||
if (draw_x != left_gap) NewLine();
|
||||
if (secondrun) canvas.DrawBar(left_gap, style.cur_line_h / 2 + draw_y - 1, hr_width, hr_size, hrcol);
|
||||
draw_y += hr_size - 3;
|
||||
draw_x++;
|
||||
NewLine();
|
||||
return;
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_body()
|
||||
{
|
||||
t_body = tag.opened;
|
||||
if (tag.get_value_of("link")) link_color_default = GetColor(tag.value);
|
||||
if (tag.get_value_of("alink")) link_color_active = GetColor(tag.value);
|
||||
if (tag.get_value_of("text")) text_colors.set(0, GetColor(tag.value));
|
||||
if (tag.get_value_of("bgcolor")) bg_colors.set(0, GetColor(tag.value));
|
||||
// Autodetecting encoding if no encoding was set
|
||||
if (tag.opened) && (custom_encoding==-1) && (cur_encoding == CH_CP866) {
|
||||
if (strstr(bufpointer, "\208\190")) ChangeEncoding(CH_UTF8);
|
||||
else if (chrnum(bufpointer, '\246')>5) ChangeEncoding(CH_CP1251);
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_q()
|
||||
{
|
||||
chrncat(#linebuf, '\"', sizeof(TWebBrowser.linebuf));
|
||||
}
|
||||
|
||||
void TWebBrowser::reset_font_style()
|
||||
{
|
||||
list.SetFont(BASIC_CHAR_W, 14, 10011000b);
|
||||
style.cur_line_h = list.item_h = list.font_h + 5;
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_h1234_caption()
|
||||
{
|
||||
if (ESBYTE[#tag.name+1]=='4') {
|
||||
NewLine();
|
||||
NewLine();
|
||||
style.h = tag.opened;
|
||||
style.b = tag.opened;
|
||||
} else {
|
||||
style.h = tag.opened;
|
||||
if (tag.opened) {
|
||||
if (!style.pre) {
|
||||
NewLine();
|
||||
NewLine();
|
||||
}
|
||||
if (tag.is("h1")) {
|
||||
list.SetFont(BASIC_CHAR_W*2, 14+13, 10011001b);
|
||||
style.b = true;
|
||||
} else if (tag.is("h2")) {
|
||||
list.SetFont(BASIC_CHAR_W*2, 14+13, 10011001b);
|
||||
} else {
|
||||
list.SetFont(6*2, 9+8, 10001001b);
|
||||
}
|
||||
style.cur_line_h = list.item_h = list.font_h + 3;
|
||||
} else {
|
||||
if (tag.is("h1")) style.b = false;
|
||||
NewLine();
|
||||
reset_font_style();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_kosicon()
|
||||
{
|
||||
dword imgbuf[44];
|
||||
dword maxicon;
|
||||
dword shared_i18 = memopen("ICONS18", NULL, SHM_READ);
|
||||
maxicon = EDX / 18 / 18 / 4;
|
||||
if (shared_i18) && (tag.get_number_of("n")) {
|
||||
if (tag.number < maxicon) {
|
||||
if (draw_x + 18 > canvas.bufw) NewLine();
|
||||
canvas.DrawImage(draw_x, draw_y-1, 18, 18, 18*18*4*tag.number+shared_i18);
|
||||
draw_x += 22;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_img()
|
||||
{
|
||||
char img_path[4096]=0;
|
||||
dword base64img;
|
||||
|
||||
if (!tag.get_value_of("data-large-image"))
|
||||
if (!tag.get_value_of("data-src"))
|
||||
if (!tag.get_value_of("src")) return;
|
||||
|
||||
if (streqrp(tag.value, "data:")) {
|
||||
if (!strstr(tag.value, "base64,")) goto NOIMG;
|
||||
EDX = EAX+7;
|
||||
if (ESBYTE[EDX]==' ') EDX++;
|
||||
base64img = malloc(strlen(EDX));
|
||||
base64_decode stdcall (EDX, base64img, strlen(EDX));
|
||||
img_decode stdcall (base64img, EAX, 0);
|
||||
$push eax
|
||||
free(base64img);
|
||||
$pop eax
|
||||
if (EAX) goto IMGOK; else goto NOIMG;
|
||||
}
|
||||
|
||||
if (!strcmp(tag.value + strrchr(tag.value, '.'), "svg")) goto NOIMG;
|
||||
if (!strcmp(tag.value + strrchr(tag.value, '.'), "webp")) goto NOIMG;
|
||||
|
||||
strlcpy(#img_path, tag.value, sizeof(img_path)-1);
|
||||
replace_char(#img_path, ' ', '\0', sizeof(img_path)-1);
|
||||
get_absolute_url(#img_path, history.current());
|
||||
|
||||
if (check_is_the_adress_local(#img_path)) {
|
||||
img_from_file stdcall(#img_path);
|
||||
if (EAX) goto IMGOK; else goto NOIMG;
|
||||
}
|
||||
|
||||
if (cache.has(#img_path)) {
|
||||
img_decode stdcall (cache.current_buf, cache.current_size, 0);
|
||||
if (EAX) goto IMGOK; else goto NOIMG;
|
||||
} else {
|
||||
if (img_url.get_pos_by_name(#img_path)==-1) img_url.add(#img_path);
|
||||
goto NOIMG;
|
||||
}
|
||||
|
||||
IMGOK:
|
||||
if (RenderImage(EAX)) return;
|
||||
|
||||
NOIMG:
|
||||
if (tag.get_value_of("title")) || (tag.get_value_of("alt")) {
|
||||
strncpy(#img_path, tag.value, sizeof(TWebBrowser.linebuf)-3);
|
||||
sprintf(#linebuf, "[%s]", #img_path);
|
||||
} else {
|
||||
if (streqrp(#img_path, "data:")) img_path=0;
|
||||
replace_char(#img_path, '?', NULL, strlen(#img_path));
|
||||
img_path[sizeof(TWebBrowser.linebuf)-3] = '\0'; //prevent overflow in sprintf
|
||||
sprintf(#linebuf, "[%s]", #img_path+strrchr(#img_path, '/'));
|
||||
linebuf[50]= NULL;
|
||||
}
|
||||
text_colors.add(0x9A6F29);
|
||||
style.image = true;
|
||||
RenderTextbuf();
|
||||
style.image = false;
|
||||
text_colors.pop();
|
||||
}
|
||||
|
||||
struct TABLE {
|
||||
int depth;
|
||||
int margin;
|
||||
collection_int cols;
|
||||
} table;
|
||||
|
||||
unsigned table_id;
|
||||
unsigned colcount;
|
||||
unsigned tr_pos;
|
||||
unsigned td_pos;
|
||||
unsigned row_start_y;
|
||||
unsigned tallest_cell_in_row;
|
||||
unsigned cur_cell_w;
|
||||
|
||||
void TWebBrowser::tag_table_reset()
|
||||
{
|
||||
table_id = 0;
|
||||
colcount = 0;
|
||||
tr_pos = 0;
|
||||
td_pos = 0;
|
||||
table.depth = 0;
|
||||
cur_cell_w = 0;
|
||||
}
|
||||
|
||||
void TWebBrowser::tag_table()
|
||||
{
|
||||
if (!tag.opened) {
|
||||
if (style.font) tag_font();
|
||||
if (link) tag_a();
|
||||
style.b = false;
|
||||
}
|
||||
if (tag.is("table")) {
|
||||
if(tag.opened) {
|
||||
table.depth++;
|
||||
if (table.depth==1) {
|
||||
table_id++;
|
||||
colcount = 0;
|
||||
td_pos = 0;
|
||||
row_start_y = draw_y;
|
||||
if (tag.get_number_of("width")) {
|
||||
if (strchr(tag.value, '%')) tag.number = list.w * tag.number / 100;
|
||||
cur_cell_w = math.min(tag.number,list.w);
|
||||
} else {
|
||||
cur_cell_w = list.w;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (table.depth>0) {
|
||||
table.depth--;
|
||||
}
|
||||
if (table.depth==0) {
|
||||
draw_x = left_gap = style.tag_list.level * 5 * list.font_w + BODY_MARGIN;
|
||||
draw_w = list.w;
|
||||
if (td_pos) draw_y = math.max(draw_y, tallest_cell_in_row);
|
||||
draw_y = math.max(draw_y+style.cur_line_h, tallest_cell_in_row);
|
||||
row_start_y = tallest_cell_in_row = draw_y;
|
||||
style.cur_line_h = list.item_h;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (table.depth>1) {
|
||||
if (tag.is("tr")) && (tag.opened) NewLine();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!secondrun) {
|
||||
if (tag.is("tr")) {
|
||||
if (tag.opened) {
|
||||
table.cols.add(1);
|
||||
}
|
||||
colcount = 0;
|
||||
}
|
||||
if (tag.opened) if (tag.is("td")) || (tag.is("th")) {
|
||||
if (!table.cols.count) { table.cols.add(1); colcount = 0;}
|
||||
colcount++;
|
||||
if (colcount) table.cols.set(table.cols.count-1, colcount);
|
||||
//if (tag.get_number_of("colspan")) colcount += tag.number-1;
|
||||
}
|
||||
} else {
|
||||
if (tag.is("tr")) {
|
||||
if (tag.opened) {
|
||||
if (td_pos) draw_y = math.max(draw_y, tallest_cell_in_row);
|
||||
_TR_FIX:
|
||||
if (draw_x==left_gap) && (draw_y==BODY_MARGIN) {
|
||||
row_start_y = tallest_cell_in_row = draw_y;
|
||||
} else {
|
||||
row_start_y = tallest_cell_in_row = draw_y = draw_y + style.cur_line_h;
|
||||
}
|
||||
style.cur_line_h = list.item_h;
|
||||
tr_pos++;
|
||||
td_pos = 0;
|
||||
} else {
|
||||
draw_y = math.max(draw_y, tallest_cell_in_row);
|
||||
td_pos = 0;
|
||||
}
|
||||
draw_x = left_gap = style.tag_list.level * 5 * list.font_w + BODY_MARGIN;
|
||||
draw_w = list.w;
|
||||
}
|
||||
if (tag.is("td")) || (tag.is("th")) {
|
||||
|
||||
if (!tr_pos) goto _TR_FIX;
|
||||
|
||||
tallest_cell_in_row = math.max(draw_y+style.cur_line_h-list.item_h+1, tallest_cell_in_row);
|
||||
style.cur_line_h = list.item_h;
|
||||
if (tag.opened)
|
||||
{
|
||||
if (!td_pos) {
|
||||
table.margin = list.w - cur_cell_w / 2 + BODY_MARGIN;
|
||||
draw_x = left_gap = table.margin;
|
||||
draw_w = cur_cell_w - BODY_MARGIN;
|
||||
} else {
|
||||
draw_x = left_gap = left_gap + draw_w;
|
||||
draw_w = cur_cell_w - left_gap + table.margin - BODY_MARGIN;
|
||||
}
|
||||
|
||||
if (EAX = table.cols.get(tr_pos-1)-td_pos) {
|
||||
draw_w /= EAX;
|
||||
} else {
|
||||
draw_y = row_start_y;
|
||||
draw_x = left_gap = table.margin;
|
||||
}
|
||||
if (table.cols.get(tr_pos-1)-td_pos>1) && (tag.get_number_of("width")) {
|
||||
if (strchr(tag.value, '%')) {
|
||||
tag.number = cur_cell_w * tag.number / 100; //
|
||||
}
|
||||
if (tag.number < list.w - table.margin - left_gap) draw_w = tag.number;
|
||||
|
||||
}
|
||||
draw_y = row_start_y;
|
||||
td_pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (draw_x > cur_cell_w) {
|
||||
draw_x = left_gap = table.margin;
|
||||
draw_w = cur_cell_w - table.margin - 23 - left_gap;
|
||||
table.depth = 0;
|
||||
NewLine();
|
||||
if (debug_mode) {
|
||||
debugln("anomaly draw_x");
|
||||
canvas.DrawBar(0, draw_y, 20, 20, 0xFF0000);
|
||||
}
|
||||
}
|
||||
//if (left_gap + draw_w > list.w) {
|
||||
// draw_w = list.w - left_gap;
|
||||
// if (debug_mode) debugln("anomaly draw_W");
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
char *unicode_symbols[]={
|
||||
"quot","\"",
|
||||
"amp", "&",
|
||||
"lt", "<",
|
||||
"gt", ">",
|
||||
"#183","\31", "middot", "\31",
|
||||
"#149","-",
|
||||
"#151","-",
|
||||
"#160"," ", "nbsp", "\t", "emsp", " ",
|
||||
"#169","(c)", "copy", "(c)",
|
||||
"#171","<<", "laquo","<<",
|
||||
"#174","(r)", "reg", "(r)",
|
||||
"#187",">>", "raquo",">>",
|
||||
"hellip", "...",
|
||||
|
||||
"trade", "[TM]",
|
||||
"bdquo", ",,",
|
||||
|
||||
"minus", "-",
|
||||
"ndash", "-",
|
||||
"mdash", "-", //--
|
||||
|
||||
"rsquo", "'", "apos", "'",
|
||||
"sect", "#",
|
||||
|
||||
"ensp", " ",
|
||||
"emsp13", " ",
|
||||
"emsp14", " ",
|
||||
"numsp", " ",
|
||||
"puncsp", " ",
|
||||
"thinsp", " ",
|
||||
|
||||
"#1028", "\242",
|
||||
"#1030", "I",
|
||||
"#1031", "\244",
|
||||
|
||||
"#8211", "-",
|
||||
"#8212", "-",
|
||||
"#8217", "'",
|
||||
"#8220", "\"",
|
||||
"#8222", "\"", "ldquo", "\"",
|
||||
"#8221", "\"", "rdquo", "\"",
|
||||
"#8470", "N",
|
||||
"#8722", "-",
|
||||
"#9642", "-", //square in the middle of the line
|
||||
"#9658", ">",
|
||||
"#9660", "v",
|
||||
"#10094", "<",
|
||||
"#10095", ">",
|
||||
"#65122", "+",
|
||||
|
||||
"#8594", "->",
|
||||
"#8627", "->",
|
||||
|
||||
"uarr", "^",
|
||||
"darr", "v",
|
||||
"rarr", "->",
|
||||
"larr", "<-",
|
||||
|
||||
"bull", "\31",
|
||||
"percnt","%",
|
||||
|
||||
"#xfeff", "",
|
||||
|
||||
"times", "x",
|
||||
"lowast","*",
|
||||
|
||||
"#128154","<3",
|
||||
"#128545",":(",
|
||||
|
||||
0};
|
||||
|
||||
|
||||
unsigned char unicode_chars[] = "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяЁёhг\243i\105\244\0";
|
||||
|
||||
bool GetUnicodeSymbol(dword _line, line_size, bufpos, buf_end)
|
||||
{
|
||||
int i;
|
||||
int code;
|
||||
char special_code[10];
|
||||
bool white_end = false;
|
||||
dword bufstart = bufpos;
|
||||
|
||||
for (i=0; i<9; i++, bufpos++)
|
||||
{
|
||||
if (__isWhite(ESBYTE[bufpos])) {bufpos--; break;}
|
||||
if (ESBYTE[bufpos] == ';') || (bufpos >= buf_end) break;
|
||||
if (!strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#", ESBYTE[bufpos])) {bufpos--; break;}
|
||||
special_code[i] = ESBYTE[bufpos];
|
||||
}
|
||||
special_code[i] = '\0';
|
||||
|
||||
for (i=0; unicode_symbols[i]!=0; i+=2;)
|
||||
{
|
||||
if (!strcmp(#special_code, unicode_symbols[i]))
|
||||
{
|
||||
strncat(_line, unicode_symbols[i+1], line_size);
|
||||
return bufpos;
|
||||
}
|
||||
}
|
||||
|
||||
if (special_code[0]=='#')
|
||||
{
|
||||
code = atoi(#special_code + 1);
|
||||
if (code>=0) && (code<=255) {
|
||||
chrncat(_line, code, line_size); //NOT ALL ASCII CODES IN KOLIBRI ARE COMPATABLE WITH STANDARDS
|
||||
return bufpos;
|
||||
}
|
||||
if (code>=1040) && (code<=1040+72) {
|
||||
chrncat(_line, unicode_chars[code-1040], line_size);
|
||||
return bufpos;
|
||||
}
|
||||
}
|
||||
|
||||
chrncat(_line, '&', line_size);
|
||||
return bufstart;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
struct LIST
|
||||
{
|
||||
int level;
|
||||
char ordered[5];
|
||||
int counter[5];
|
||||
void reset();
|
||||
void upd_level();
|
||||
int inc_counter();
|
||||
char order_type();
|
||||
};
|
||||
|
||||
void LIST::reset()
|
||||
{
|
||||
level = 0;
|
||||
}
|
||||
|
||||
void LIST::upd_level(int direction, char type)
|
||||
{
|
||||
if (direction == 1) && (level<5) {
|
||||
level++;
|
||||
counter[level] = 0;
|
||||
ordered[level] = type;
|
||||
}
|
||||
if (direction == 0) && (level>0) {
|
||||
level--;
|
||||
}
|
||||
}
|
||||
|
||||
int LIST::inc_counter()
|
||||
{
|
||||
counter[level]++;
|
||||
return counter[level];
|
||||
}
|
||||
|
||||
char LIST::order_type()
|
||||
{
|
||||
return ordered[level];
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
if tup.getconfig("NO_CMM") ~= "" then return end
|
||||
if tup.getconfig("LANG") == "ru_RU"
|
||||
then C_LANG = "LANG_RUS"
|
||||
elseif tup.getconfig("LANG") == "es_ES"
|
||||
then C_LANG = "LANG_SPA"
|
||||
else C_LANG = "LANG_ENG" -- this includes default case without config
|
||||
end
|
||||
tup.rule("WebView.c", "c-- /D=$(C_LANG) /OPATH=%o %f" .. tup.getconfig("KPACK_CMD"), "WebView.com")
|
||||
+391
-1347
File diff suppressed because it is too large.
Load diff
@@ -6,3 +6,12 @@
|
||||
@del warning.txt
|
||||
|
||||
if not exist WebView ( @pause )
|
||||
|
||||
if exist WebView (
|
||||
"C:\Program Files (x86)\WinImage\winimage.exe" "C:\Users\leency\Desktop\kolibri.img" /H /Q /I WebView
|
||||
d:
|
||||
cd "D:\Kolibri\Infrastructure\QEMU"
|
||||
call "C:\Users\leency\Desktop\qemu.bat"
|
||||
) else (
|
||||
pause
|
||||
)
|
||||
+1
-1
@@ -1 +1 @@
|
||||
c-- -D=LANG_ENG WebView.c
|
||||
../c--/c--.elf -D=LANG_ENG WebView.c
|
||||
+12
-194
@@ -6,17 +6,19 @@ struct _cache
|
||||
{
|
||||
dword current_buf;
|
||||
dword current_size;
|
||||
dword current_type;
|
||||
dword current_charset;
|
||||
collection url;
|
||||
collection_int data;
|
||||
collection_int size;
|
||||
collection_int type;
|
||||
collection_int charset;
|
||||
void add();
|
||||
bool has();
|
||||
void clear();
|
||||
} cache;
|
||||
|
||||
void _cache::add(dword _url, _data, _size, _charset)
|
||||
void _cache::add(dword _url, _data, _size, _type, _charset)
|
||||
{
|
||||
dword data_pointer;
|
||||
data_pointer = malloc(_size);
|
||||
@@ -25,6 +27,7 @@ void _cache::add(dword _url, _data, _size, _charset)
|
||||
|
||||
url.add(_url);
|
||||
size.add(_size);
|
||||
type.add(_type);
|
||||
charset.add(_charset);
|
||||
|
||||
current_buf = data_pointer;
|
||||
@@ -34,211 +37,26 @@ void _cache::add(dword _url, _data, _size, _charset)
|
||||
bool _cache::has(dword _link)
|
||||
{
|
||||
int pos;
|
||||
if (!url.count) return false;
|
||||
pos = url.get_pos_by_name(_link);
|
||||
IF (pos != -1) {
|
||||
if (pos != -1) {
|
||||
current_buf = data.get(pos);
|
||||
current_size = size.get(pos);
|
||||
current_type = type.get(pos);
|
||||
current_charset = charset.get(pos);
|
||||
RETURN true;
|
||||
return true;
|
||||
}
|
||||
RETURN false;
|
||||
return false;
|
||||
}
|
||||
|
||||
void _cache::clear()
|
||||
{
|
||||
int i;
|
||||
FOR (i=0; i<data.count; i++) free(data.get(i));
|
||||
for (i=0; i<data.count; i++) free(data.get(i));
|
||||
url.drop();
|
||||
data.drop();
|
||||
size.drop();
|
||||
charset.drop();
|
||||
type.drop();
|
||||
current_buf = NULL;
|
||||
current_size = NULL;
|
||||
current_charset = NULL;
|
||||
}
|
||||
|
||||
|
||||
// Image and stylesheet cache stored on the RAM disk, index is kept in _list_db.ini
|
||||
// as "path=url" (the url may contain '=', the path never does): sheets in [css],
|
||||
// pictures in [img].
|
||||
#define IMG_CACHE_DIR "/tmp0/1/Cache"
|
||||
#define IMG_CACHE_DB "/tmp0/1/Cache/_list_db.ini"
|
||||
#define IMG_CACHE_LIMIT 8388608
|
||||
#define IMG_CACHE_BIG 262144 // a bigger picture is not kept once its page is laid out
|
||||
|
||||
// Plain functions on the global instance, not methods: C-- miscompiles a member
|
||||
// collection's method called from a method of the owner.
|
||||
struct _cache2
|
||||
{
|
||||
collection url;
|
||||
collection path;
|
||||
collection_int fsize; // 0 => entry was evicted
|
||||
collection_int css; // 1 => a stylesheet: evicted only after every picture
|
||||
dword total_size;
|
||||
dword cur;
|
||||
} cache2;
|
||||
|
||||
bool cache2_is_contain(dword _url)
|
||||
{
|
||||
dword i, sz, u;
|
||||
FOR (i = 0; i < cache2.url.count; i++) {
|
||||
sz = cache2.fsize.get(i);
|
||||
IF (!sz) CONTINUE;
|
||||
u = cache2.url.get(i);
|
||||
IF (streq(u, _url)) {
|
||||
cache2.cur = i;
|
||||
RETURN true;
|
||||
}
|
||||
}
|
||||
cache2.cur = -1;
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// Register a downloaded file. Returns false and leaves the collections
|
||||
// untouched if the file is missing or empty (download failed, disk full).
|
||||
bool cache2_add(dword _url, _path, _css)
|
||||
{
|
||||
dword sz;
|
||||
IF (cache2_is_contain(_url)) RETURN true;
|
||||
sz = get_file_size(_path);
|
||||
IF (!sz) {
|
||||
DeleteFile(_path);
|
||||
RETURN false;
|
||||
}
|
||||
cache2.url.add(_url);
|
||||
cache2.path.add(_path);
|
||||
cache2.fsize.add(sz);
|
||||
cache2.css.add(_css);
|
||||
cache2.total_size += sz;
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
// Evict one entry: delete the file and mark the slot as free.
|
||||
void cache2_evict(dword _pos)
|
||||
{
|
||||
dword sz, p;
|
||||
IF (_pos >= cache2.url.count) RETURN;
|
||||
sz = cache2.fsize.get(_pos);
|
||||
IF (!sz) RETURN;
|
||||
p = cache2.path.get(_pos);
|
||||
DeleteFile(p);
|
||||
cache2.total_size -= sz;
|
||||
cache2.fsize.set(_pos, 0);
|
||||
}
|
||||
|
||||
// Keep the cache under IMG_CACHE_LIMIT by evicting the oldest entries: pictures
|
||||
// first, stylesheets only when the pictures alone are not enough. A page shown
|
||||
// again (tab switch) is restyled from here: without its sheets it is unreadable,
|
||||
// a missing picture is only a placeholder.
|
||||
void cache2_trim()
|
||||
{
|
||||
dword i, c;
|
||||
FOR (i = 0; i < cache2.url.count; i++) {
|
||||
IF (cache2.total_size <= IMG_CACHE_LIMIT) RETURN;
|
||||
c = cache2.css.get(i);
|
||||
IF (!c) cache2_evict(i);
|
||||
}
|
||||
FOR (i = 0; i < cache2.url.count; i++) {
|
||||
IF (cache2.total_size <= IMG_CACHE_LIMIT) RETURN;
|
||||
cache2_evict(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Pictures over IMG_CACHE_BIG are dropped once the page is laid out: its nodes hold
|
||||
// the decoded copies, and a few photos would push every stylesheet out otherwise.
|
||||
void cache2_drop_big()
|
||||
{
|
||||
dword i, c, sz;
|
||||
FOR (i = 0; i < cache2.url.count; i++) {
|
||||
c = cache2.css.get(i);
|
||||
IF (c) CONTINUE;
|
||||
sz = cache2.fsize.get(i);
|
||||
IF (sz > IMG_CACHE_BIG) cache2_evict(i);
|
||||
}
|
||||
}
|
||||
|
||||
void cache2_clear()
|
||||
{
|
||||
dword i;
|
||||
FOR (i = 0; i < cache2.url.count; i++) cache2_evict(i);
|
||||
cache2.url.drop();
|
||||
cache2.path.drop();
|
||||
cache2.fsize.drop();
|
||||
cache2.css.drop();
|
||||
cache2.total_size = 0;
|
||||
}
|
||||
|
||||
// Delete "wv_*" files in the cache dir that are not in the index
|
||||
// (left over after a crash or another WebView instance).
|
||||
void cache2_remove_orphans()
|
||||
{
|
||||
dword dirbuf, fcount, i, name;
|
||||
int pos;
|
||||
char fpath[600];
|
||||
IF (GetDir(#dirbuf, #fcount, IMG_CACHE_DIR, DIRS_ONLYREAL)) RETURN;
|
||||
FOR (i = 0; i < fcount; i++) {
|
||||
name = i * 304 + dirbuf + 32 + 40;
|
||||
IF (strncmp(name, "wv_", 3)) CONTINUE;
|
||||
sprintf(#fpath, "%s/%s", IMG_CACHE_DIR, name);
|
||||
pos = cache2.path.get_pos_by_name(#fpath);
|
||||
IF (pos == -1) DeleteFile(#fpath);
|
||||
}
|
||||
free(dirbuf);
|
||||
}
|
||||
|
||||
// the live entries of one kind as "path=url" lines
|
||||
void cache2_save_part(dword _buf, _css)
|
||||
{
|
||||
dword i, sz, c, p;
|
||||
for (i = 0; i < cache2.url.count; i++) {
|
||||
sz = cache2.fsize.get(i);
|
||||
IF (!sz) CONTINUE;
|
||||
c = cache2.css.get(i);
|
||||
IF (c != _css) CONTINUE;
|
||||
p = cache2.path.get(i);
|
||||
strcat(_buf, p);
|
||||
strcat(_buf, "=");
|
||||
p = cache2.url.get(i);
|
||||
strcat(_buf, p);
|
||||
strcat(_buf, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void cache2_save()
|
||||
{
|
||||
dword buf, buf_size;
|
||||
buf_size = cache2.url.data_size + cache2.path.data_size + cache2.url.count + cache2.url.count + 13;
|
||||
buf = malloc(buf_size);
|
||||
strcpy(buf, "[css]\n");
|
||||
cache2_save_part(buf, 1);
|
||||
strcat(buf, "[img]\n");
|
||||
cache2_save_part(buf, 0);
|
||||
CreateDir(IMG_CACHE_DIR);
|
||||
CreateFile(strlen(buf), buf, IMG_CACHE_DB);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
byte _cache2_load_css(dword key_value, key_name, sec_name, f_name)
|
||||
{
|
||||
// key_name = path, key_value = url; add() drops entries whose file is gone
|
||||
cache2_add(key_value, key_name, 1);
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
byte _cache2_load_sections(dword key_value, key_name, sec_name, f_name)
|
||||
{
|
||||
cache2_add(key_value, key_name, 0);
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
void cache2_load()
|
||||
{
|
||||
CreateDir(IMG_CACHE_DIR);
|
||||
ini_enum_keys stdcall (IMG_CACHE_DB, "css", #_cache2_load_css);
|
||||
ini_enum_keys stdcall (IMG_CACHE_DB, "img", #_cache2_load_sections);
|
||||
// another window (Ctrl+N, -new) keeps its index in memory until it exits, and its
|
||||
// dl children may be writing wv_* files right now: only the sole instance cleans up
|
||||
IF (GetProcessesCount(#webview_shared) == 1) cache2_remove_orphans();
|
||||
cache2_drop_big(); // an index written before the limit may still list big pictures
|
||||
cache2_trim();
|
||||
}
|
||||
}
|
||||
+29
-103
@@ -6,6 +6,7 @@
|
||||
//===================================================//
|
||||
char buildin_page_error[] = FROM "res/page_not_found_ru.htm";
|
||||
char buildin_page_home[] = FROM "res/homepage_ru.htm";
|
||||
char buildin_page_help[] = FROM "res/help_ru.htm";
|
||||
char accept_language[]= "Accept-Language: ru\n";
|
||||
char rmb_menu[] =
|
||||
"Back|Ctrl+<
|
||||
@@ -15,11 +16,9 @@ Forward|Ctrl+>
|
||||
Редактировать исходник|F2";
|
||||
char main_menu[] =
|
||||
"Открыть файл...|Ctrl+O
|
||||
Открыть в NetSurf
|
||||
-
|
||||
Мелкий шрифт|Ctrl-
|
||||
Крупный шрифт|Ctrl+
|
||||
Новое окно|Ctrl+N
|
||||
-
|
||||
История|Ctrl+H
|
||||
Менеджер загрузок|Ctrl+J
|
||||
Настройки|Ctrl+E
|
||||
Очистить кэш|Ctrl+F5
|
||||
@@ -30,80 +29,16 @@ char link_menu[] =
|
||||
-
|
||||
Копировать ссылку
|
||||
Скачать содержимое ссылки";
|
||||
char img_menu[] =
|
||||
"Открыть картинку
|
||||
Сохранить картинку
|
||||
Копировать адрес картинки";
|
||||
char sel_menu[] = "Копировать|Ctrl+C";
|
||||
#define T_FIND "Поиск:"
|
||||
#define T_FIND_NOTHING "не найдено"
|
||||
#define T_TEXT_COPIED "'Текст скопирован в буфер обмена'O"
|
||||
#define T_NO_FREE_TAB "'Нет свободных вкладок. Закройте одну из вкладок.' -tW"
|
||||
#define T_FORM_NO_SECRET "'Форма с паролем отправляется только на адрес http://' -tW"
|
||||
char loading_text[] = "Загрузка...";
|
||||
|
||||
char update_url[] = "http://builds.kolibrios.org/ci/latest/ru_RU/data/programs/cmm/browser/WebView.com";
|
||||
char update_param[] = "-e http://builds.kolibrios.org/ru_RU/data/programs/cmm/browser/WebView.com";
|
||||
char update_download_error[] = "'WebView\nОшибка при получении обновлений!' -tE";
|
||||
char update_ok[] = "'WebView\nБраузер был успешно обновлен!' -tO";
|
||||
char update_is_current[] = "'WebView\nВы уже используете последнюю версию.' -tI";
|
||||
char update_can_not_copy[] = "'WebView\nНе могу переместить новую версию из папки Downloads на Ramdisk. Возможно, не достаточно места.' -tE";
|
||||
char clear_cache_ok[] = "'WebView\nКэш очищен.' -tI";
|
||||
#define T_RENDERING "Рендеринг страницы..."
|
||||
#define T_STATUS_CONNECTING "Соединение с %s..."
|
||||
#define T_STATUS_SENDING "Отправка формы на %s..."
|
||||
#define T_STATUS_LOADING "Загрузка страницы: %i КБ"
|
||||
#define T_STATUS_LOADING_OF "Загрузка страницы: %i из %i КБ"
|
||||
#define T_STATUS_IMAGES "Загрузка картинок: %i/%i..."
|
||||
#define T_DONE_IN_SEC "Готово: %i сек (%i милисек)"
|
||||
#define T_PAGE_INCOMPLETE "Связь оборвалась: страница загружена не полностью"
|
||||
#else
|
||||
#ifdef LANG_SPA
|
||||
//===================================================//
|
||||
// //
|
||||
// SPANISH //
|
||||
// //
|
||||
//===================================================//
|
||||
char buildin_page_error[] = FROM "res/page_not_found_es.htm";
|
||||
char buildin_page_home[] = FROM "res/homepage_es.htm";
|
||||
char accept_language[]= "Accept-Language: es\n";
|
||||
char rmb_menu[] =
|
||||
"Volver|Ctrl+<
|
||||
Adelante|Ctrl+>
|
||||
-
|
||||
Ver fuente|Ctrl+U
|
||||
Editar fuente|F2";
|
||||
char main_menu[] =
|
||||
"Abrir archivo local...|Ctrl+O
|
||||
Abrir en NetSurf
|
||||
-
|
||||
Reducir letra|Ctrl-
|
||||
Aumentar letra|Ctrl+
|
||||
-
|
||||
Gestor de descargas|Ctrl+J
|
||||
Ajustes|Ctrl+E
|
||||
Limpiar cache|Ctrl+F5
|
||||
Actualizar navegador";
|
||||
char link_menu[] =
|
||||
"Abrir en pestana nueva
|
||||
Abrir en ventana nueva
|
||||
-
|
||||
Copiar enlace
|
||||
Descargar destino del enlace";
|
||||
char img_menu[] =
|
||||
"Abrir imagen
|
||||
Guardar imagen
|
||||
Copiar URL de la imagen";
|
||||
char sel_menu[] = "Copiar|Ctrl+C";
|
||||
#define T_FIND "Buscar:"
|
||||
#define T_FIND_NOTHING "no encontrado"
|
||||
#define T_TEXT_COPIED "'Texto copiado al portapapeles'O"
|
||||
#define T_NO_FREE_TAB "'No hay pestanas libres. Cierre una de ellas primero.' -tW"
|
||||
#define T_FORM_NO_SECRET "'Un formulario con clave solo puede enviarse a una direccion http://' -tW"
|
||||
char update_url[] = "http://builds.kolibrios.org/ci/latest/es_ES/data/programs/cmm/browser/WebView.com";
|
||||
char clear_cache_ok[] = "'WebView\nLa cache ha sido limpiada.' -tI";
|
||||
#define T_RENDERING "Renderizando..."
|
||||
#define T_STATUS_CONNECTING "Conectando con %s..."
|
||||
#define T_STATUS_SENDING "Enviando el formulario a %s..."
|
||||
#define T_STATUS_LOADING "Cargando: %i KB"
|
||||
#define T_STATUS_LOADING_OF "Cargando: %i de %i KB"
|
||||
#define T_STATUS_IMAGES "Descargando imagenes: %i/%i..."
|
||||
#define T_DONE_IN_SEC "Listo en %i seg (%i milisec)"
|
||||
#define T_PAGE_INCOMPLETE "Conexion perdida: falta parte de la pagina"
|
||||
#else
|
||||
//===================================================//
|
||||
// //
|
||||
@@ -112,6 +47,7 @@ char clear_cache_ok[] = "'WebView\nLa cache ha sido limpiada.' -tI";
|
||||
//===================================================//
|
||||
char buildin_page_error[] = FROM "res/page_not_found_en.htm";
|
||||
char buildin_page_home[] = FROM "res/homepage_en.htm";
|
||||
char buildin_page_help[] = FROM "res/help_en.htm";
|
||||
char accept_language[]= "Accept-Language: en\n";
|
||||
char rmb_menu[] =
|
||||
"Back|Ctrl+<
|
||||
@@ -121,11 +57,9 @@ View source|Ctrl+U
|
||||
Edit source|F2";
|
||||
char main_menu[] =
|
||||
"Open local file...|Ctrl+O
|
||||
Open in NetSurf
|
||||
-
|
||||
Smaller font|Ctrl-
|
||||
Larger font|Ctrl+
|
||||
New window|Ctrl+N
|
||||
-
|
||||
History|Ctrl+H
|
||||
Download Manager|Ctrl+J
|
||||
Settings|Ctrl+E
|
||||
Clear cache|Ctrl+F5
|
||||
@@ -136,27 +70,15 @@ Open in new window
|
||||
-
|
||||
Copy link
|
||||
Download link contents";
|
||||
char img_menu[] =
|
||||
"Open image
|
||||
Save image
|
||||
Copy image URL";
|
||||
char sel_menu[] = "Copy|Ctrl+C";
|
||||
#define T_FIND "Find:"
|
||||
#define T_FIND_NOTHING "not found"
|
||||
#define T_TEXT_COPIED "'Text copied to clipboard'O"
|
||||
#define T_NO_FREE_TAB "'No free tab. Close one of the tabs first.' -tW"
|
||||
#define T_FORM_NO_SECRET "'A form with a password can only be sent to an http:// address.' -tW"
|
||||
char update_url[] = "http://builds.kolibrios.org/ci/latest/en_US/data/programs/cmm/browser/WebView.com";
|
||||
char loading_text[] = "Loading...";
|
||||
char update_param[] = "-e http://builds.kolibrios.org/en_US/data/programs/cmm/browser/WebView.com";
|
||||
char update_download_error[] = "'WebView\nError receiving update information!' -tE";
|
||||
char update_ok[] = "'WebView\nThe browser has been updated!' -tO";
|
||||
char update_is_current[] = "'WebView\nThe browser is up to date.' -tI";
|
||||
char update_can_not_copy[] = "'WebView\nError copying a new version from Downloads folder!\nProbably too litle space on Ramdisk.' -tE";
|
||||
char clear_cache_ok[] = "'WebView\nThe cache has been cleared.' -tI";
|
||||
#define T_RENDERING "Rendering..."
|
||||
#define T_STATUS_CONNECTING "Connecting to %s..."
|
||||
#define T_STATUS_SENDING "Sending the form to %s..."
|
||||
#define T_STATUS_LOADING "Loading the page: %i KB"
|
||||
#define T_STATUS_LOADING_OF "Loading the page: %i of %i KB"
|
||||
#define T_STATUS_IMAGES "Downloading images: %i/%i..."
|
||||
#define T_DONE_IN_SEC "Done in %i sec (%i milisec)"
|
||||
#define T_PAGE_INCOMPLETE "Connection lost: the page is incomplete"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//===================================================//
|
||||
@@ -165,7 +87,11 @@ char clear_cache_ok[] = "'WebView\nThe cache has been cleared.' -tI";
|
||||
// //
|
||||
//===================================================//
|
||||
|
||||
char buildin_page_test[] = FROM "res/test.htm";
|
||||
|
||||
#define URL_SERVICE_HISTORY "WebView:history"
|
||||
#define URL_SERVICE_HOMEPAGE "WebView:home"
|
||||
#define URL_SERVICE_TEST "WebView:test"
|
||||
|
||||
char webview_shared[] = "WEBVIEW";
|
||||
|
||||
@@ -174,19 +100,19 @@ enum {
|
||||
ENCODINGS=700,
|
||||
BACK_BUTTON=800,
|
||||
FORWARD_BUTTON, VIEW_SOURCE, EDIT_SOURCE,
|
||||
REFRESH_BUTTON, CHANGE_ENCODING, SANDWICH_BUTTON, DOWNLOADS_FOLDER,
|
||||
OPEN_FILE, OPEN_IN_NETSURF, FONT_SMALLER, FONT_LARGER, DOWNLOAD_MANAGER,
|
||||
REFRESH_BUTTON, GOTOURL_BUTTON, CHANGE_ENCODING,
|
||||
GO_BUTTON, SEARCH_BUTTON, SANDWICH_BUTTON,
|
||||
OPEN_FILE, NEW_WINDOW, VIEW_HISTORY, DOWNLOAD_MANAGER,
|
||||
OPEN_SETTINGS, CLEAR_CACHE, UPDATE_BROWSER, IN_NEW_TAB, IN_NEW_WINDOW,
|
||||
COPY_LINK_URL, DOWNLOAD_LINK_CT, IMG_OPEN, IMG_SAVE, IMG_COPY_URL,
|
||||
COPY_SELECTION,
|
||||
FIND_PREV, FIND_NEXT, FIND_CLOSE,
|
||||
TAB_ID,
|
||||
COPY_LINK_URL, DOWNLOAD_LINK_CT, TAB_ID,
|
||||
TAB_CLOSE_ID = 900
|
||||
};
|
||||
|
||||
char editbox_icons[] = FROM "res/editbox_icons.raw";
|
||||
|
||||
#define WIN_W 850
|
||||
#define WIN_H 920
|
||||
|
||||
#define DEFAULT_URL URL_SERVICE_HOMEPAGE
|
||||
|
||||
char version[]="WebView 4.0";
|
||||
char version[]="WebView 3.95";
|
||||
@@ -1,207 +0,0 @@
|
||||
// Cookies, kept in memory for the session. Domain, Path, Expires, Max-Age and Secure
|
||||
// are ignored: a cookie goes back only to the host that set it, an empty value removes it.
|
||||
|
||||
#define COOKIE_JAR_SIZE 16384
|
||||
#define COOKIE_REQUEST_SIZE 4096
|
||||
|
||||
// "host name=value\n" lines
|
||||
char cookie_jar[16384];
|
||||
// the extra request headers for http_get(): Accept-Language plus Cookie
|
||||
char cookie_request[4096];
|
||||
char cookie_host[256];
|
||||
|
||||
// "http://Host:8080/path?q" -> "host:8080" into cookie_host
|
||||
void cookie_get_host(dword _url)
|
||||
{
|
||||
dword s, d, e, c;
|
||||
s = _url;
|
||||
IF (!strncmp(s, "http://", 7)) s += 7;
|
||||
ELSE IF (!strncmp(s, "https://", 8)) s += 8;
|
||||
d = #cookie_host;
|
||||
e = #cookie_host;
|
||||
e += 255;
|
||||
WHILE (DSBYTE[s]) {
|
||||
IF (d >= e) BREAK;
|
||||
c = DSBYTE[s];
|
||||
IF (c == '/') || (c == '?') || (c == '#') BREAK;
|
||||
IF (c >= 'A') && (c <= 'Z') c += 32;
|
||||
DSBYTE[d] = c;
|
||||
d++;
|
||||
s++;
|
||||
}
|
||||
DSBYTE[d] = 0;
|
||||
}
|
||||
|
||||
// the "name=value" of a jar line of cookie_host, 0 for a line of another host
|
||||
dword cookie_of_host(dword _line)
|
||||
{
|
||||
dword p, q;
|
||||
p = _line;
|
||||
q = #cookie_host;
|
||||
WHILE (DSBYTE[q]) {
|
||||
IF (DSBYTE[p] != DSBYTE[q]) RETURN 0;
|
||||
p++;
|
||||
q++;
|
||||
}
|
||||
IF (DSBYTE[p] != ' ') RETURN 0;
|
||||
RETURN p + 1;
|
||||
}
|
||||
|
||||
// the end of a jar line: its line feed or the end of the jar
|
||||
dword cookie_line_end(dword _line)
|
||||
{
|
||||
WHILE (DSBYTE[_line]) && (DSBYTE[_line] != 10) _line++;
|
||||
RETURN _line;
|
||||
}
|
||||
|
||||
// the jar line of cookie_host with this name, or 0
|
||||
dword cookie_find(dword _name, _name_len)
|
||||
{
|
||||
dword line, p, n;
|
||||
line = #cookie_jar;
|
||||
WHILE (DSBYTE[line]) {
|
||||
p = cookie_of_host(line);
|
||||
IF (p) && (!strncmp(p, _name, _name_len)) {
|
||||
n = p + _name_len;
|
||||
IF (DSBYTE[n] == '=') RETURN line;
|
||||
}
|
||||
line = cookie_line_end(line);
|
||||
IF (DSBYTE[line] == 10) line++;
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// removes one jar line
|
||||
void cookie_remove(dword _line)
|
||||
{
|
||||
dword next;
|
||||
next = cookie_line_end(_line);
|
||||
IF (DSBYTE[next] == 10) next++;
|
||||
strcpy(_line, next);
|
||||
}
|
||||
|
||||
// copies _len bytes to _dst, returns the end
|
||||
dword cookie_copy(dword _dst, _src, _len)
|
||||
{
|
||||
WHILE (_len) {
|
||||
DSBYTE[_dst] = DSBYTE[_src];
|
||||
_dst++;
|
||||
_src++;
|
||||
_len--;
|
||||
}
|
||||
DSBYTE[_dst] = 0;
|
||||
RETURN _dst;
|
||||
}
|
||||
|
||||
void cookie_set(dword _name, _name_len, _value, _value_len)
|
||||
{
|
||||
dword line, need, used, d;
|
||||
line = cookie_find(_name, _name_len);
|
||||
IF (line) cookie_remove(line);
|
||||
IF (!_value_len) return;
|
||||
need = strlen(#cookie_host);
|
||||
need += _name_len;
|
||||
need += _value_len;
|
||||
need += 3;
|
||||
IF (need >= 2048) return;
|
||||
// no room: the oldest cookies go first
|
||||
loop() {
|
||||
used = strlen(#cookie_jar);
|
||||
used += need;
|
||||
IF (used < COOKIE_JAR_SIZE) BREAK;
|
||||
cookie_remove(#cookie_jar);
|
||||
}
|
||||
d = strlen(#cookie_jar);
|
||||
d += #cookie_jar;
|
||||
d = cookie_copy(d, #cookie_host, strlen(#cookie_host));
|
||||
DSBYTE[d] = ' ';
|
||||
d++;
|
||||
d = cookie_copy(d, _name, _name_len);
|
||||
DSBYTE[d] = '=';
|
||||
d++;
|
||||
d = cookie_copy(d, _value, _value_len);
|
||||
DSBYTE[d] = 10;
|
||||
d++;
|
||||
DSBYTE[d] = 0;
|
||||
}
|
||||
|
||||
// Takes every "set-cookie:" line of the finished transfer. libHTTP keeps the raw
|
||||
// header after http_msg with the field names in lower case.
|
||||
void cookies_save(dword _url, _transfer)
|
||||
{
|
||||
dword p, e, n, v, ve, c;
|
||||
IF (!_transfer) return;
|
||||
cookie_get_host(_url);
|
||||
IF (!cookie_host[0]) return;
|
||||
p = _transfer;
|
||||
p += 44; // http_msg.http_header
|
||||
e = DSDWORD[_transfer + 28];
|
||||
e += p; // + http_msg.header_length
|
||||
while (p < e) {
|
||||
IF (!DSBYTE[p]) break;
|
||||
if (!strncmp(p, "set-cookie:", 11)) {
|
||||
n = p + 11;
|
||||
WHILE (DSBYTE[n] == ' ') || (DSBYTE[n] == 9) n++;
|
||||
v = n;
|
||||
loop() {
|
||||
c = DSBYTE[v];
|
||||
IF (c == '=') || (c == ';') || (c == 13) || (c == 10) || (c == 0) BREAK;
|
||||
v++;
|
||||
}
|
||||
IF (DSBYTE[v] == '=') && (v > n) {
|
||||
ve = v + 1;
|
||||
loop() {
|
||||
c = DSBYTE[ve];
|
||||
IF (c == ';') || (c == 13) || (c == 10) || (c == 0) BREAK;
|
||||
ve++;
|
||||
}
|
||||
c = v - n;
|
||||
v++;
|
||||
ve -= v;
|
||||
cookie_set(n, c, v, ve);
|
||||
}
|
||||
}
|
||||
WHILE (p < e) && (DSBYTE[p]) && (DSBYTE[p] != 10) p++;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
// Accept-Language plus "Cookie: a=1; b=2" for the host of _url into cookie_request
|
||||
dword cookies_request(dword _url)
|
||||
{
|
||||
dword line, p, d, e, len, start;
|
||||
strcpy(#cookie_request, #accept_language);
|
||||
cookie_get_host(_url);
|
||||
IF (!cookie_host[0]) return #cookie_request;
|
||||
d = strlen(#cookie_request);
|
||||
d += #cookie_request;
|
||||
e = #cookie_request;
|
||||
e += COOKIE_REQUEST_SIZE;
|
||||
e -= 16;
|
||||
start = d;
|
||||
line = #cookie_jar;
|
||||
while (DSBYTE[line]) {
|
||||
p = cookie_of_host(line);
|
||||
line = cookie_line_end(line);
|
||||
IF (p) {
|
||||
len = line - p;
|
||||
len += d;
|
||||
len += 2;
|
||||
IF (len < e) {
|
||||
IF (d == start) d = cookie_copy(d, "Cookie: ", 8);
|
||||
ELSE d = cookie_copy(d, "; ", 2);
|
||||
len = line - p;
|
||||
d = cookie_copy(d, p, len);
|
||||
}
|
||||
}
|
||||
IF (DSBYTE[line] == 10) line++;
|
||||
}
|
||||
IF (d != start) {
|
||||
DSBYTE[d] = 13;
|
||||
d++;
|
||||
DSBYTE[d] = 10;
|
||||
d++;
|
||||
DSBYTE[d] = 0;
|
||||
}
|
||||
RETURN #cookie_request;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
# WebView engine
|
||||
|
||||
A DOM -> style -> layout -> paint pipeline (it replaced the streaming two-pass renderer TWB):
|
||||
tables, text selection, find-in-page, link handling and CSS all work from one data structure.
|
||||
|
||||
## Architecture (browser/engine)
|
||||
|
||||
| stage | file | output |
|
||||
|---------|------------|------------------------------------------------------------|
|
||||
| parse | dom.h | node arena (element/text nodes, parent/child/next links), string pool |
|
||||
| style | style.h | computed style stored in each element node (UA defaults, presentational attributes, `style=""`, `<style>`) |
|
||||
| layout | layout.h | flat list of fragments `{node, kind, x, y, w, h, text, style, link}` in document order; proper table algorithm (grid, min/max content widths, colspan/rowspan) |
|
||||
| paint | paint.h | only fragments intersecting the viewport are drawn into a viewport-sized canvas; selection and search highlights are overlays |
|
||||
| interact| hit.h | hit-testing, link hover/click, text selection + copy, find-in-page, image context menu |
|
||||
|
||||
Nodes and fragments are addressed by index into growable arenas, so `realloc` is safe.
|
||||
Tag names are mapped to `tag_id` once; everything else switches on the id.
|
||||
|
||||
## Status (September 2026)
|
||||
|
||||
The engine is complete and TWB is deleted: DOM, styles + CSS subset (`<style>`,
|
||||
`<link rel=stylesheet>`, `style=""`), block/inline/table/flex layout, floats, viewport
|
||||
painting, links, selection (drag, Ctrl+A, Ctrl+C), find bar (Ctrl+F, Enter / Shift+Enter,
|
||||
Esc), image context menu (open / save to Downloads / copy URL).
|
||||
|
||||
The parser repairs sloppy markup the way browsers do: an implied `<tr>` for a cell placed
|
||||
straight in `<table>` or after `</tr>`, an implied `<td>` for content placed straight in
|
||||
`<tr>`/`<table>`. `<center>` and `align=center` centre child tables, CSS `text-align` does not.
|
||||
|
||||
Lengths: `px`, `%`, `em`/`rem` (14px), `pt`, `vw` (as a percentage); `ch` in `width` /
|
||||
`max-width` is the glyph width of the element's font (8, 12 or 16px).
|
||||
|
||||
Test pages: `tests/basic.htm` (inline styles, lists, pre, CSS), `tests/tables.htm`
|
||||
(borders, colspan/rowspan, percent widths, nested and layout tables), `links.htm`.
|
||||
Checked in the QEMU VM against those, `tests/acid_0.1.htm`, WebView:home and menuetos.net.
|
||||
|
||||
Known limitations / next steps:
|
||||
- no horizontal scrolling: content wider than the window (nowrap, huge fixed widths) is cut
|
||||
- CSS: no `:hover`/`:focus`/`:not()`, no shorthand `border` colors; `position: absolute`
|
||||
is parsed but off (`css_abspos`), such boxes stay in the flow
|
||||
- one font family (monospace 8x16, 6x9x2, 8x16x2): headings and `font-size` map to those three
|
||||
- `select` shows only its first option, forms are not submitted
|
||||
- table cells with `rowspan` are stretched but never re-laid out for the extra height
|
||||
- markup content placed in a table outside cells gets its own cell instead of being moved
|
||||
before the table
|
||||
|
||||
## Testing
|
||||
|
||||
`.vm/vm.py` boots the test image (`vm.py start|reset|put|get`), `.vm/rfb.py` drives it over
|
||||
VNC (`shot`, `click`, `drag`, `key`, `type`, `url`). Build with `/D=TESTBUILD` to get
|
||||
`/hd0/1/dom.txt`, `dom2.txt` (after layout) and `scroll.txt` dumps.
|
||||
|
||||
## C-- notes
|
||||
|
||||
No operator precedence (left to right): use temporaries. No function pointers:
|
||||
dispatch with `switch (tag_id)`. Recursion is fine, DOM depth is capped at 64.
|
||||
@@ -1,68 +0,0 @@
|
||||
|
||||
// the named colors, most used on real sites first (named_color scans them): the
|
||||
// names, each ended by "|", and in the same order their RGB
|
||||
char web_color_names[] = "white|black|red|gray|grey|silver|blue|green|"
|
||||
"orange|yellow|navy|purple|aqua|brown|cyan|darkblue|"
|
||||
"darkgray|darkgreen|darkgrey|darkorange|darkred|fuchsia|ivory|lightblue|"
|
||||
"lightgray|lightgreen|lightgrey|lightyellow|lime|maroon|olive|pink|"
|
||||
"teal|violet|";
|
||||
dword web_color_rgb[] = {
|
||||
0xFFFFFF, 0x000000, 0xFF0000, 0x808080, 0x808080, 0xC0C0C0, 0x0000FF, 0x008000,
|
||||
0xFFA500, 0xFFFF00, 0x000080, 0x800080, 0x00FFFF, 0xA52A2A, 0x00FFFF, 0x00008B,
|
||||
0xA9A9A9, 0x006400, 0xA9A9A9, 0xFF8C00, 0x8B0000, 0xFF00FF, 0xFFFFF0, 0xADD8E6,
|
||||
0xD3D3D3, 0x90EE90, 0xD3D3D3, 0xFFFFE0, 0x00FF00, 0x800000, 0x808000, 0xFFC0CB,
|
||||
0x008080, 0xEE82EE
|
||||
};
|
||||
|
||||
// the RGB of the color named _s, 0xFFFFFFFF (NO_COLOR) for none
|
||||
dword named_color(dword _s)
|
||||
{
|
||||
int i;
|
||||
i = css_kw(_s, #web_color_names); // style.h: 1, 2... or 0
|
||||
IF (!i) RETURN 0xFFFFFFFF;
|
||||
i--;
|
||||
RETURN web_color_rgb[i];
|
||||
}
|
||||
|
||||
|
||||
// a hex digit's value, -1 for anything else
|
||||
int color_hex_digit(dword _c)
|
||||
{
|
||||
IF (_c >= '0') && (_c <= '9') RETURN _c - '0';
|
||||
IF (_c >= 'a') && (_c <= 'f') RETURN _c - 87;
|
||||
RETURN -1;
|
||||
}
|
||||
|
||||
// 0xFFFFFFFF (NO_COLOR, dom.h is included later) for a name not in the table or
|
||||
// a broken hex value: the caller keeps the inherited color instead of black
|
||||
dword GetColor(char* color_str)
|
||||
{
|
||||
int ii;
|
||||
int d;
|
||||
dword c;
|
||||
dword textlen=0;
|
||||
dword color=0;
|
||||
|
||||
strlwr(color_str);
|
||||
IF (DSBYTE[color_str] == '#') {
|
||||
color_str++;
|
||||
} ELSE {
|
||||
c = named_color(color_str);
|
||||
IF (c != 0xFFFFFFFF) RETURN c;
|
||||
}
|
||||
|
||||
textlen = strlen(color_str);
|
||||
IF (textlen==6) || (textlen==3)
|
||||
{
|
||||
FOR (ii=0; ii<textlen; ii++)
|
||||
{
|
||||
c = DSBYTE[color_str+ii];
|
||||
d = color_hex_digit(c);
|
||||
IF (d < 0) RETURN 0xFFFFFFFF;
|
||||
color = color*0x10 + d;
|
||||
IF (textlen==3) color = color*0x10 + d; //#abc ->> #aabbcc
|
||||
}
|
||||
RETURN color;
|
||||
}
|
||||
RETURN 0xFFFFFFFF;
|
||||
}
|
||||
@@ -1,915 +0,0 @@
|
||||
// DOM: string pool, node arena, tag table and the HTML tree builder.
|
||||
//
|
||||
// Nodes live in one growable arena and are addressed by index (1-based, 0 = none),
|
||||
// so realloc never invalidates a reference. Every node is NODE_SIZE bytes; the
|
||||
// N_* / S_* / L_* constants are byte offsets inside a node (dom_n(idx) gives the address).
|
||||
// `dom` is a global: a tab switch copies the whole struct in and out (see tabs.h).
|
||||
|
||||
//===================================================//
|
||||
// string pool //
|
||||
//===================================================//
|
||||
|
||||
struct POOL
|
||||
{
|
||||
dword buf, cap, used;
|
||||
dword add(); // copy a string, return its offset
|
||||
dword addn(); // copy n bytes + NUL, return offset
|
||||
void addc(); // append one byte (open string, terminate with addc(0))
|
||||
dword get(); // offset -> pointer
|
||||
void reset();
|
||||
void release();
|
||||
} dpool; // strings of the current document (saved per tab together with dom)
|
||||
|
||||
void POOL::reset()
|
||||
{
|
||||
dword p;
|
||||
IF (!buf) { cap = 65536; buf = malloc(cap); }
|
||||
p = buf;
|
||||
DSBYTE[p] = 0;
|
||||
used = 1; // offset 0 is the empty string
|
||||
}
|
||||
|
||||
void POOL::release()
|
||||
{
|
||||
free(buf);
|
||||
buf = cap = used = 0;
|
||||
}
|
||||
|
||||
void POOL::addc(dword c)
|
||||
{
|
||||
dword p;
|
||||
IF (!buf) reset();
|
||||
IF (used + 2 >= cap) {
|
||||
cap += cap;
|
||||
buf = realloc(buf, cap);
|
||||
}
|
||||
p = buf + used;
|
||||
DSBYTE[p] = c;
|
||||
used++;
|
||||
}
|
||||
|
||||
dword POOL::addn(dword s, len)
|
||||
{
|
||||
dword off, p;
|
||||
IF (!buf) reset();
|
||||
WHILE (used + len + 2 >= cap) {
|
||||
cap += cap;
|
||||
buf = realloc(buf, cap);
|
||||
}
|
||||
off = used;
|
||||
p = buf + used;
|
||||
IF (len) memmov(p, s, len);
|
||||
used += len;
|
||||
p = buf + used;
|
||||
DSBYTE[p] = 0;
|
||||
used++;
|
||||
RETURN off;
|
||||
}
|
||||
|
||||
dword POOL::add(dword s)
|
||||
{
|
||||
RETURN addn(s, strlen(s));
|
||||
}
|
||||
|
||||
dword POOL::get(dword off)
|
||||
{
|
||||
dword p = buf;
|
||||
RETURN p + off;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// tags //
|
||||
//===================================================//
|
||||
|
||||
// tag flags
|
||||
#define TF_BLOCK 1 // block-level: closes an open <p>, starts on a new line
|
||||
#define TF_VOID 2 // no closing tag, no children
|
||||
#define TF_RAW 4 // raw text content (script/style/textarea)
|
||||
#define TF_HIDDEN 8 // never rendered
|
||||
#define TF_INHEAD 16 // may stand in <head>: does not close it
|
||||
#define TF_INTABLE 32 // table structure (and script, style, form): gets no implied cell
|
||||
|
||||
// the T_* tags in the order of the enum below: their names, each ended by "|", and
|
||||
// their TF_* flags. tag_split() (DOM::build) makes the names NUL-ended strings and
|
||||
// points tag_ptr[] at them: no table of 113 pointers in the program file (-130 B)
|
||||
char tag_names[] = "|#text|#root|a|abbr|address|article|aside|b|big|blockquote|body|"
|
||||
"br|button|caption|center|cite|code|col|colgroup|dd|del|dfn|div|"
|
||||
"dl|dt|em|fieldset|figcaption|figure|font|footer|form|h1|h2|h3|"
|
||||
"h4|h5|h6|head|header|hr|html|i|iframe|img|input|ins|"
|
||||
"kbd|kosicon|label|li|link|main|meta|nav|noscript|ol|option|p|"
|
||||
"pre|q|s|script|section|select|small|span|strike|strong|style|sub|"
|
||||
"sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|"
|
||||
"ul|var|base|area|param|source|wbr|embed|object|svg|video|audio|"
|
||||
"canvas|template|summary|details|menu|dir|listing|xmp|plaintext|marquee|frameset|frame|"
|
||||
"noframes|map|time|mark|unknown|";
|
||||
byte tag_flagv[] = {
|
||||
0, 0, TF_BLOCK, 0, 0, TF_BLOCK, TF_BLOCK, TF_BLOCK,
|
||||
0, 0, TF_BLOCK, TF_BLOCK, TF_VOID, 0, TF_BLOCK+TF_INTABLE, TF_BLOCK,
|
||||
0, 0, TF_VOID+TF_HIDDEN+TF_INTABLE, TF_HIDDEN+TF_INTABLE, TF_BLOCK, 0, 0, TF_BLOCK,
|
||||
TF_BLOCK, TF_BLOCK, 0, TF_BLOCK, TF_BLOCK, TF_BLOCK, 0, TF_BLOCK,
|
||||
TF_BLOCK+TF_INTABLE, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK+TF_HIDDEN+TF_INHEAD,
|
||||
TF_BLOCK, TF_BLOCK+TF_VOID, TF_BLOCK+TF_INHEAD, 0, TF_BLOCK, TF_VOID, TF_VOID, 0,
|
||||
0, TF_VOID, 0, TF_BLOCK, TF_VOID+TF_HIDDEN+TF_INHEAD, TF_BLOCK, TF_VOID+TF_HIDDEN+TF_INHEAD, TF_BLOCK,
|
||||
TF_INHEAD, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK, 0, 0, TF_RAW+TF_HIDDEN+TF_INHEAD+TF_INTABLE,
|
||||
TF_BLOCK, 0, 0, 0, 0, 0, TF_RAW+TF_HIDDEN+TF_INHEAD+TF_INTABLE, 0,
|
||||
0, TF_BLOCK+TF_INTABLE, TF_BLOCK+TF_INTABLE, TF_BLOCK+TF_INTABLE, TF_RAW, TF_BLOCK+TF_INTABLE, TF_BLOCK+TF_INTABLE, TF_BLOCK+TF_INTABLE,
|
||||
TF_BLOCK+TF_HIDDEN+TF_INHEAD, TF_BLOCK+TF_INTABLE, 0, 0, TF_BLOCK, 0, TF_VOID+TF_HIDDEN+TF_INHEAD, TF_VOID+TF_HIDDEN,
|
||||
TF_VOID+TF_HIDDEN, TF_VOID+TF_HIDDEN, TF_VOID, TF_VOID+TF_HIDDEN, TF_HIDDEN, TF_HIDDEN, TF_BLOCK, TF_HIDDEN,
|
||||
TF_HIDDEN, TF_HIDDEN, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK, TF_BLOCK,
|
||||
TF_BLOCK, TF_BLOCK, TF_HIDDEN, TF_VOID+TF_HIDDEN, TF_BLOCK, 0, 0, 0,
|
||||
0
|
||||
};
|
||||
dword tag_ptr[114]; // T_* -> its name
|
||||
|
||||
|
||||
// The enum must list the names in exactly the same order as tag_names[].
|
||||
enum {
|
||||
T_NONE, T_TEXT, T_ROOT,
|
||||
T_A, T_ABBR, T_ADDRESS, T_ARTICLE, T_ASIDE, T_B, T_BIG, T_BLOCKQUOTE, T_BODY, T_BR, T_BUTTON,
|
||||
T_CAPTION, T_CENTER, T_CITE, T_CODE, T_COL, T_COLGROUP, T_DD, T_DEL, T_DFN, T_DIV, T_DL, T_DT,
|
||||
T_EM, T_FIELDSET, T_FIGCAPTION, T_FIGURE, T_FONT, T_FOOTER, T_FORM,
|
||||
T_H1, T_H2, T_H3, T_H4, T_H5, T_H6, T_HEAD, T_HEADER, T_HR, T_HTML, T_I, T_IFRAME, T_IMG,
|
||||
T_INPUT, T_INS, T_KBD, T_KOSICON, T_LABEL, T_LI, T_LINK, T_MAIN, T_META, T_NAV, T_NOSCRIPT,
|
||||
T_OL, T_OPTION, T_P, T_PRE, T_Q, T_S, T_SCRIPT, T_SECTION, T_SELECT, T_SMALL, T_SPAN,
|
||||
T_STRIKE, T_STRONG, T_STYLE, T_SUB, T_SUP, T_TABLE, T_TBODY, T_TD, T_TEXTAREA, T_TFOOT,
|
||||
T_TH, T_THEAD, T_TITLE, T_TR, T_TT, T_U, T_UL, T_VAR,
|
||||
T_BASE, T_AREA, T_PARAM, T_SOURCE, T_WBR, T_EMBED, T_OBJECT, T_SVG, T_VIDEO, T_AUDIO,
|
||||
T_CANVAS, T_TEMPLATE, T_SUMMARY, T_DETAILS, T_MENU, T_DIR, T_LISTING, T_XMP, T_PLAINTEXT,
|
||||
T_MARQUEE, T_FRAMESET, T_FRAME, T_NOFRAMES, T_MAP, T_TIME, T_MARK,
|
||||
T_UNKNOWN
|
||||
};
|
||||
|
||||
void tag_split()
|
||||
{
|
||||
dword p, e;
|
||||
int i;
|
||||
IF (tag_ptr[0]) RETURN; // index 0: a constant index is a byte offset in C--
|
||||
p = #tag_names;
|
||||
e = sizeof(tag_names) + p;
|
||||
i = 0;
|
||||
tag_ptr[0] = p;
|
||||
FOR (; p < e; p++) {
|
||||
IF (DSBYTE[p] == '|') { DSBYTE[p] = 0; i++; tag_ptr[i] = p + 1; }
|
||||
}
|
||||
}
|
||||
|
||||
dword tag_id_by_name(dword _name)
|
||||
{
|
||||
int i;
|
||||
FOR (i = 3; i < T_UNKNOWN; i++) {
|
||||
IF (streq(_name, tag_ptr[i])) RETURN i;
|
||||
}
|
||||
RETURN T_UNKNOWN;
|
||||
}
|
||||
|
||||
dword tag_flags(dword _id)
|
||||
{
|
||||
dword f;
|
||||
f = tag_flagv[_id]; // a byte: widened here, not left in AL
|
||||
RETURN f;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// nodes //
|
||||
//===================================================//
|
||||
|
||||
#define NODE_SIZE 440
|
||||
#define BODY_MARGIN 6 // UA margin of <body> (the page margin)
|
||||
|
||||
// tree
|
||||
#define N_TYPE 0 // T_TEXT or element tag id (T_ROOT for the document)
|
||||
#define N_PARENT 4
|
||||
#define N_FIRST 8
|
||||
#define N_LAST 12
|
||||
#define N_NEXT 16
|
||||
#define N_ATTRS 20 // pool offset of "name\0value\0...\0" (0 = none)
|
||||
#define N_TEXT 24 // text node: pool offset of the text
|
||||
#define N_LEN 28 // text node: length in bytes
|
||||
#define N_IMG 32 // img: decoded libimg image (0 = none yet)
|
||||
#define S_BGSIZE 36 // background-size: width + height * 65536, a side px, BGS_PCT + n%, 0 = auto; 0 = as the picture is
|
||||
// computed style
|
||||
#define S_DISPLAY 40
|
||||
#define S_FLAGS 44
|
||||
#define S_COLOR 48
|
||||
#define S_BG 52 // NO_COLOR = transparent
|
||||
#define S_FONT 56 // FONT_*
|
||||
#define S_ALIGN 60 // ALIGN_*
|
||||
#define S_WIDTH 64 // px > 0, 0 auto, < 0 percent (-50 = 50%)
|
||||
#define S_HEIGHT 68
|
||||
#define S_MTOP 72 // margins in px
|
||||
#define S_MBOT 76
|
||||
#define S_PTOP 80 // padding: px, < 0 percent of the container width; a table keeps its cellpadding here
|
||||
#define S_BORDT 84 // border width of the top side (the others are below)
|
||||
#define S_COLSPAN 88
|
||||
#define S_ROWSPAN 92
|
||||
#define S_LINK 96 // nearest <a href> ancestor (node index) or 0
|
||||
#define S_VALIGN 100
|
||||
#define S_INDENT 104 // extra left indent for children (blockquote, lists)
|
||||
#define S_LISTDEP 108 // list nesting depth of <li>
|
||||
#define S_LISTTYP 112 // 'u' / 'o' / 'n' (list-style: none)
|
||||
#define S_SPACING 116 // table cellspacing
|
||||
#define S_LINKCOL 120 // link color in effect
|
||||
#define S_MLEFT 124
|
||||
#define S_MRIGHT 128
|
||||
// layout results
|
||||
#define L_X 132
|
||||
#define L_Y 136
|
||||
#define L_W 140
|
||||
#define L_H 144
|
||||
#define L_MINW 148 // cached min/max content width (tables), -1 = not computed
|
||||
#define L_MAXW 152
|
||||
#define L_ORDER 156 // li: ordinal number
|
||||
#define S_FLOAT 160 // 0 none, 1 left, 2 right
|
||||
#define S_POS 164 // POS_STATIC / POS_RELATIVE / POS_ABSOLUTE
|
||||
#define S_TOP 168 // offsets of a positioned box: px, POS_PCT + n = n%, POS_AUTO when not given
|
||||
#define S_LEFT 172
|
||||
#define S_RIGHT 176
|
||||
#define S_BOTTOM 180
|
||||
#define S_MAXW 184 // max-width: px > 0, 0 none, < 0 percent
|
||||
#define S_MAUTO 188 // margin auto: 1 left, 2 right (3 = centred)
|
||||
#define S_FLEX 192 // flex container: FLEX_* bits
|
||||
#define S_JUST 196 // justify-content: FJ_*
|
||||
#define S_GROW 200 // flex-grow in tenths (1.5 -> 15), | GROW_NOSHRINK
|
||||
#define GROW_NOSHRINK 0x40000000 // flex-shrink: 0 - an overflowing row does not squeeze the item
|
||||
#define S_BASIS 204 // flex-basis: px, < 0 percent, POS_AUTO = auto
|
||||
#define S_PRIGHT 208
|
||||
#define S_PBOT 212
|
||||
#define S_PLEFT 216
|
||||
#define S_LH 220 // line-height: 0 normal, > 0 px, < 0 multiple of the font size in tenths (-15 = 1.5)
|
||||
#define S_BGIMG 224 // background-image: index + 1 into bg_urls, 0 = none
|
||||
#define S_BGREP 228 // background-repeat / -position: BGR_* bits
|
||||
#define S_BGX 232 // background-position in px (CSS sprites: -112px -16px)
|
||||
#define S_BGY 236
|
||||
#define S_MINW 240 // min-width: px > 0, 0 none, < 0 percent
|
||||
#define S_BORDR 244 // border widths of the other sides
|
||||
#define S_BORDB 248
|
||||
#define S_BORDL 252
|
||||
#define S_BCOLT 256 // border color per side, NO_COLOR = the grey the frames are drawn in
|
||||
#define S_BCOLR 260
|
||||
#define S_BCOLB 264
|
||||
#define S_BCOLL 268
|
||||
#define S_MINH 272 // min-height in px, 0 none
|
||||
#define S_MAXH 276 // max-height in px (0 is a real value: a collapsed menu), POS_AUTO none
|
||||
#define S_ORDER 280 // flex item "order", signed
|
||||
#define S_GRIDN 284 // grid-template-columns: number of tracks, 0 none; < 0 "repeat(auto-fill, ...)" of tracks at least that many px wide
|
||||
#define S_GRIDT 288 // the tracks, GRID_TRACKS of them: px > 0, 0 auto, < 0 fr in tenths (-10 = 1fr), GT_PCT + n = n%, GT_CAP + px
|
||||
#define GRID_TRACKS 8
|
||||
#define GT_PCT 0x20000000
|
||||
#define GT_CAP 0x10000000 // GT_CAP + px: "minmax(0, 96rem)", at most that wide, narrower when the row is short of room
|
||||
#define N_TAG 320 // T_UNKNOWN: pool offset of the real tag name ("overflow-menu"), 0 = none
|
||||
#define N_OPEN 324 // <details> the user opened (1) or closed (2); 0: as its "open" attribute says
|
||||
#define S_Z 328 // z-index of a positioned box, POS_AUTO = auto
|
||||
#define S_TX 332 // transform: translate(): px, or POS_PCT + n = n% of the box's own size
|
||||
#define S_TY 336
|
||||
#define S_HPCT 340 // height in percent of the containing block (a definite height: layout.h lay_def_h), 0 = none
|
||||
#define S_WFRAC 344 // hundredths of a width in percent (16.66% -> S_WIDTH -16, 66 here), or px * WF_PX of calc(n% + px)
|
||||
#define WF_PX 100
|
||||
#define S_FPX 348 // 1em of the element (its font-size) in tenths of px, CSS_EM_PX*10 on the page; inherited
|
||||
#define S_MLPCT 352 // margin-left in percent of the container (signed), 0 = none: layout_block resolves it into S_MLEFT
|
||||
// the empty boxes of "::before" / "::after" { content: '' } (style.h css_pseudo_decl): no
|
||||
// node of their own, only what makes them tall. PS_* from the start of each: PSF_* flags,
|
||||
// padding-top and -bottom (as S_PTOP), height in px. Not inherited.
|
||||
#define S_PSB 356
|
||||
#define S_PSA 372
|
||||
#define PS_FLAGS 0
|
||||
#define PS_PT 4
|
||||
#define PS_PB 8
|
||||
#define PS_H 12
|
||||
#define S_ASPECT 388 // aspect-ratio: the height per 1000 of the width, 0 = none
|
||||
#define S_MHPCT 392 // max-height in percent of the containing block's height, 0 = none (S_MAXH then)
|
||||
#define S_OBJPOS 396 // object-position of a picture cut to its box: x% + y% * 256 (0..100 each)
|
||||
#define N_IMGW 400 // img: the size N_IMG had when decoded (and sized by width=/height=): the
|
||||
#define N_IMGH 404 // layout works with it, the painter scales N_IMG in place to what it gets
|
||||
#define S_MRPCT 408 // margin-right in percent like S_MLPCT; past style_init[]: style_compute() clears it
|
||||
#define S_AREAS 412 // grid-template-areas: its place + 1 in style.h css_areas, 0 none; cleared like S_MRPCT
|
||||
#define S_GAREA 416 // grid-area: css_hash() of the name of the item's area, 0 none; cleared like S_MRPCT
|
||||
#define S_OUTW 420 // outline: its width in px, 0 = none; cleared like S_MRPCT
|
||||
#define S_OUTC 424 // outline-color, NO_COLOR = the text colour (currentColor)
|
||||
#define S_OUTOFF 428 // outline-offset in px
|
||||
#define S_GCOL 432 // grid-column-start: the line the item starts at (1, 2...), 0 = auto; cleared like S_MRPCT
|
||||
#define S_BGA 436 // a translucent background: alpha << 24 + its own RGB (S_BG is it blended over the backdrop), 0 = opaque or none; cleared like S_MRPCT
|
||||
#define OBJPOS_CENTER 12850 // 50% 50%, the initial value
|
||||
#define GRID_ALL 1000 // S_COLSPAN of "grid-column: 1 / -1": the whole row
|
||||
|
||||
#define FLEX_ON 1
|
||||
#define FLEX_COL 2
|
||||
#define FLEX_WRAP 4
|
||||
#define FLEX_ACENTER 8 // align-items; none of the three = stretch
|
||||
|
||||
#define PSF_CONTENT 1 // content: "" - the box is made (text would be a box we do not draw)
|
||||
#define PSF_BLOCK 2 // display: block, table, flex...: its height is in the flow
|
||||
#define PSF_OUT 4 // position: absolute / fixed, float: out of the flow
|
||||
#define FLEX_AEND 16
|
||||
#define FLEX_ASTART 32
|
||||
#define FLEX_GRID 64 // display:grid: the children go into the columns of grid-template-columns
|
||||
#define FLEX_AREAS 128 // grid-template-areas: items are placed by name (S_AREAS, S_GAREA); an area over several rows stacks them
|
||||
enum { FJ_START, FJ_END, FJ_CENTER, FJ_BETWEEN, FJ_AROUND };
|
||||
|
||||
#define POS_STATIC 0
|
||||
#define POS_RELATIVE 1
|
||||
#define POS_ABSOLUTE 2 // position:fixed too (with SF_FIXED)
|
||||
#define POS_STICKY 3 // placed like static; a containing block like relative, its offsets are not moves
|
||||
#define POS_AUTO 0x40000000
|
||||
#define POS_PCT 0x20000000 // + n: a percentage offset (n may be negative), see pos_len()
|
||||
// POS_PCT + n + px * POS_PX: n% and px more, "calc(50% - 50vw)" (n -500..499, px +-200000)
|
||||
#define POS_PX 1000
|
||||
#define POS_PX_MAX 200000
|
||||
|
||||
enum { D_INLINE, D_BLOCK, D_NONE, D_LIST_ITEM, D_TABLE, D_ROW, D_CELL, D_ROWGROUP, D_CAPTION };
|
||||
enum { FONT_NORMAL, FONT_BIG, FONT_MEDIUM, FONT_SMALL };
|
||||
enum { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT };
|
||||
enum { VALIGN_TOP, VALIGN_MIDDLE, VALIGN_BOTTOM, VALIGN_SUPER, VALIGN_SUB }; // SUPER/SUB: text raised / lowered in its line (<sup>, <sub>)
|
||||
|
||||
#define SF_BOLD 1
|
||||
#define SF_UNDER 2
|
||||
#define SF_STRIKE 4
|
||||
#define SF_PRE 8
|
||||
#define SF_NOWRAP 16
|
||||
#define SF_BUTTON 32
|
||||
#define SF_SRONLY 64 // hidden for the eye only (sr-only, parked off-screen): display:none, but its words still name a link (layout.h link_label_text); not inherited
|
||||
#define SF_ITALIC 128 // no italic font: paint.h shears the drawn run (cv_slant)
|
||||
#define SF_VMID 256 // vertical-align: middle set on the element itself (S_VALIGN is inherited, for the cells); not inherited
|
||||
#define SF_COLORSET 1024 // a stylesheet rule set the color on this very node (not inherited)
|
||||
#define SF_INLBLOCK 4096 // display:inline-block (S_DISPLAY stays D_INLINE), not inherited
|
||||
#define SF_CLEAR 16384 // clear: left/right/both - starts below the floats, not inherited
|
||||
#define SF_BREAKANY 8192 // overflow-wrap:anywhere / word-break: a long word may split, even for min-content
|
||||
#define SF_CLIP 32768 // overflow:hidden with a height: the box is exactly that tall, the rest is cut; not inherited
|
||||
#define SF_TRANSP 65536 // color:transparent - text takes room but is not drawn
|
||||
#define SF_BORDERBOX 131072 // box-sizing:border-box - padding and border fit inside width, not inherited
|
||||
#define SF_OWNLINE 262144 // a form control the page made display:block - it takes a line of its own, not inherited
|
||||
#define SF_FOLDED 1048576 // display:none that came from "grid-template-rows: 0fr": "1fr" opens it again, not inherited
|
||||
#define SF_NOAPPEAR 2097152 // appearance:none - a checkbox / radio the page draws itself, not inherited
|
||||
#define SF_CONTENTS 16777216 // display:contents - no box of its own: its children are laid out in its place, not inherited
|
||||
#define SF_CLEARFIX 8388608 // "x:after { clear: both }" matched: the box holds its floats, not inherited
|
||||
#define SF_CUTX 512 // overflow(-x): hidden/clip - a nowrap line past the right edge is cut, not inherited
|
||||
#define SF_SCROLLX 33554432 // overflow(-x): auto/scroll - no scrollbar here, so a nowrap line wraps instead, not inherited
|
||||
#define SF_FIXED 67108864 // position:fixed: placed against the window, not the positioned ancestors; not inherited
|
||||
#define SF_ABSINL 134217728 // an absolute box that was inline: its static position is the pen in the line, not inherited
|
||||
#define SF_INVIS 2048 // visibility:hidden - inherited: the box keeps its room, its frags are not drawn (an absolute one is display:none)
|
||||
#define SF_UNSEEN 268435456 // opacity:0 - not drawn (an absolute box is left out), not inherited
|
||||
#define SF_SCALE0 536870912 // transform: scale(0) - the same, kept apart: a later "transform" must not undo opacity
|
||||
#define SF_CLIP0 4194304 // clip: rect() with nothing inside - an absolute box cut down to nothing (sr-only); not inherited
|
||||
#define SF_MAXCONT 524288 // width: max-content - an absolute box as wide as its content, whatever room its containing block has; a block in the flow as wide as its content, up to the room; not inherited
|
||||
#define SF_TINDENT 1073741824 // text-indent: -9999px - the text of a box that shows a picture instead (a logo): takes room, not drawn; inherited
|
||||
#define SF_INHERIT_MASK 1073817791 // BOLD+UNDER+STRIKE+PRE+NOWRAP+BUTTON+ITALIC+INVIS+BREAKANY+TRANSP+TINDENT (1+2+4+8+16+32+128+2048+8192+65536+1073741824)
|
||||
|
||||
// clear a flag without bitwise NOT (C-- has none)
|
||||
void flag_clear(dword _addr, _flag)
|
||||
{
|
||||
IF (DSDWORD[_addr] & _flag) DSDWORD[_addr] -= _flag;
|
||||
}
|
||||
|
||||
// flag_clear() of the S_FLAGS / S_FLEX of a node: shorter at the many call sites
|
||||
void sf_clear(dword _node, _flag) { flag_clear(_node + S_FLAGS, _flag); }
|
||||
void flex_clear(dword _node, _flag) { flag_clear(_node + S_FLEX, _flag); }
|
||||
|
||||
#define NO_COLOR 0xFFFFFFFF
|
||||
#define CLEAR_COLOR 0xFFFFFFFE // a border side that keeps its width but is not drawn
|
||||
|
||||
#define MAX_DEPTH 64
|
||||
|
||||
// builder state (transient, only used while build() runs)
|
||||
dword dom_stack[65]; // MAX_DEPTH + 1
|
||||
int depth;
|
||||
dword text_start; // pool offset where the pending text node starts, 0 = none
|
||||
bool text_space; // last appended char was a collapsible space
|
||||
bool pre_cr; // the last character in <pre> was CR: a LF right after it is the same line end
|
||||
int pre_level;
|
||||
bool pre_skip_lf; // a newline right after <pre> is not content (HTML spec)
|
||||
|
||||
struct DOM
|
||||
{
|
||||
dword nodes; // arena
|
||||
dword nodes_size; // bytes allocated
|
||||
dword count; // nodes used (index of the next free node)
|
||||
dword root;
|
||||
dword body;
|
||||
dword title; // pool offset of the <title> text or 0
|
||||
dword pool_mark; // dpool.used after parsing: layout strings live above it
|
||||
|
||||
dword new_node();
|
||||
void append();
|
||||
dword scope_find();
|
||||
void pop_to();
|
||||
void open_element();
|
||||
void close_element();
|
||||
void flush_text();
|
||||
void add_char();
|
||||
dword store_attrs();
|
||||
void build();
|
||||
void reset();
|
||||
void release();
|
||||
dword find_by_id();
|
||||
dword first_of_type();
|
||||
void dump(); // TESTBUILD only; C-- checks members even in inactive #ifdef
|
||||
} dom;
|
||||
|
||||
// The node accessors are plain functions, not DOM methods: a method call pushes the
|
||||
// address of dom as well, and these are called everywhere
|
||||
dword dom_n(dword idx)
|
||||
{
|
||||
dword a = dom.nodes;
|
||||
idx *= NODE_SIZE;
|
||||
RETURN idx + a;
|
||||
}
|
||||
|
||||
dword dom_ntype(dword idx) { dword a = dom_n(idx); RETURN DSDWORD[a + N_TYPE]; }
|
||||
dword dom_nfirst(dword idx) { dword a = dom_n(idx); RETURN DSDWORD[a + N_FIRST]; }
|
||||
dword dom_nnext(dword idx) { dword a = dom_n(idx); RETURN DSDWORD[a + N_NEXT]; }
|
||||
dword dom_nparent(dword idx) { dword a = dom_n(idx); RETURN DSDWORD[a + N_PARENT]; }
|
||||
|
||||
void DOM::reset()
|
||||
{
|
||||
dword p;
|
||||
IF (!nodes) {
|
||||
nodes_size = NODE_SIZE * 1024;
|
||||
nodes = malloc(nodes_size);
|
||||
}
|
||||
dpool.reset();
|
||||
count = 1; // 0 = null node
|
||||
body = title = pool_mark = 0;
|
||||
depth = 0;
|
||||
text_start = 0;
|
||||
text_space = false;
|
||||
pre_level = 0;
|
||||
pre_cr = false;
|
||||
pre_skip_lf = false;
|
||||
root = new_node(T_ROOT, 0);
|
||||
p = root;
|
||||
dom_stack[0] = p;
|
||||
}
|
||||
|
||||
void DOM::release()
|
||||
{
|
||||
dword i, node;
|
||||
FOR (i = 1; i < count; i++) {
|
||||
node = dom_n(i);
|
||||
node = DSDWORD[node + N_IMG];
|
||||
IF (node) && (node != -1) img_destroy stdcall (node);
|
||||
}
|
||||
free(nodes);
|
||||
nodes = nodes_size = count = 0;
|
||||
dpool.release();
|
||||
}
|
||||
|
||||
dword DOM::new_node(dword _type, _parent)
|
||||
{
|
||||
dword node, idx, need;
|
||||
need = count + 2;
|
||||
need *= NODE_SIZE;
|
||||
IF (need >= nodes_size) {
|
||||
nodes_size += nodes_size;
|
||||
nodes = realloc(nodes, nodes_size);
|
||||
}
|
||||
idx = count;
|
||||
count++;
|
||||
node = dom_n(idx);
|
||||
MEMSETD(node, NODE_SIZE / 4, 0);
|
||||
DSDWORD[node + N_TYPE] = _type;
|
||||
DSDWORD[node + N_PARENT] = _parent;
|
||||
DSDWORD[node + S_BG] = NO_COLOR;
|
||||
DSDWORD[node + L_MINW] = -1;
|
||||
IF (_parent) append(_parent, idx);
|
||||
RETURN idx;
|
||||
}
|
||||
|
||||
void DOM::append(dword _parent, _child)
|
||||
{
|
||||
dword p = dom_n(_parent);
|
||||
dword last = DSDWORD[p + N_LAST];
|
||||
dword a;
|
||||
IF (last) {
|
||||
a = dom_n(last);
|
||||
DSDWORD[a + N_NEXT] = _child;
|
||||
} ELSE {
|
||||
DSDWORD[p + N_FIRST] = _child;
|
||||
}
|
||||
DSDWORD[p + N_LAST] = _child;
|
||||
a = dom_n(_child);
|
||||
DSDWORD[a + N_PARENT] = _parent;
|
||||
}
|
||||
|
||||
// attribute value or 0
|
||||
dword dom_attr(dword _idx, _name)
|
||||
{
|
||||
dword p = dom_n(_idx);
|
||||
dword s;
|
||||
p = DSDWORD[p + N_ATTRS];
|
||||
IF (!p) RETURN 0;
|
||||
s = dpool.get(p);
|
||||
WHILE (DSBYTE[s]) {
|
||||
IF (streq(s, _name)) RETURN s + strlen(s) + 1;
|
||||
s += strlen(s) + 1; // skip name
|
||||
s += strlen(s) + 1; // skip value
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
dword dom_ntext(dword _idx)
|
||||
{
|
||||
dword a = dom_n(_idx);
|
||||
a = DSDWORD[a + N_TEXT];
|
||||
RETURN dpool.get(a);
|
||||
}
|
||||
|
||||
// scan the stack from the top: depth of the first element with tag _tg; -1 when one of the
|
||||
// stop tags is met first (they bound the "scope", like <table> for <p>). No open element
|
||||
// is T_NONE: scope_find(_tg, T_NONE, T_NONE, T_NONE) searches the whole stack.
|
||||
dword DOM::scope_find(dword _tg, _stop1, _stop2, _stop3)
|
||||
{
|
||||
int i;
|
||||
dword t, s;
|
||||
FOR (i = depth; i > 0; i--) {
|
||||
s = dom_stack[i];
|
||||
t = dom_ntype(s);
|
||||
IF (t == _tg) RETURN i;
|
||||
IF (t == _stop1) || (t == _stop2) || (t == _stop3) RETURN -1;
|
||||
}
|
||||
RETURN -1;
|
||||
}
|
||||
|
||||
// close everything above and including stack level _level
|
||||
void DOM::pop_to(dword _level)
|
||||
{
|
||||
int i;
|
||||
dword t, s;
|
||||
FOR (i = depth; i >= _level; i--) {
|
||||
s = dom_stack[i];
|
||||
t = dom_ntype(s);
|
||||
IF (t == T_PRE) || (t == T_TEXTAREA) || (t == T_LISTING) pre_level--;
|
||||
}
|
||||
IF (_level > 0) depth = _level - 1;
|
||||
}
|
||||
|
||||
void DOM::open_element(dword _tg, _attrs)
|
||||
{
|
||||
dword idx, lvl, parent, a;
|
||||
int d;
|
||||
dword flags = tag_flags(_tg);
|
||||
|
||||
flush_text();
|
||||
|
||||
// implicit closing rules
|
||||
// anything that does not belong to <head> closes it (a missing </head> is common)
|
||||
IF (flags & TF_INHEAD == 0) {
|
||||
lvl = scope_find(T_HEAD, T_NONE, T_NONE, T_NONE);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
}
|
||||
// (<option> is a block only for the text flow: inside <p>...<select> it must not
|
||||
// close the <p> and the <select> with it)
|
||||
IF (flags & TF_BLOCK) && (_tg != T_OPTION) {
|
||||
lvl = scope_find(T_P, T_TD, T_TH, T_TABLE);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
}
|
||||
// content straight inside <tr>/<table> ("<tr><h3>News</h3>...") would be lost:
|
||||
// give it an implied cell (the <td> rule below adds the row when needed)
|
||||
IF (flags & TF_INTABLE == 0) {
|
||||
a = dom_ntype(dom_stack[depth]);
|
||||
IF (a == T_TR) || (a == T_TABLE) || (a == T_TBODY) || (a == T_THEAD) || (a == T_TFOOT) open_element(T_TD, 0);
|
||||
}
|
||||
switch (_tg) {
|
||||
CASE T_LI:
|
||||
lvl = scope_find(T_LI, T_UL, T_OL, T_TABLE);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
break;
|
||||
CASE T_DT:
|
||||
CASE T_DD:
|
||||
lvl = scope_find(T_DT, T_DL, T_TABLE, T_TD);
|
||||
IF (lvl == -1) lvl = scope_find(T_DD, T_DL, T_TABLE, T_TD);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
break;
|
||||
CASE T_TD:
|
||||
CASE T_TH:
|
||||
lvl = scope_find(T_TD, T_TR, T_TABLE, T_NONE);
|
||||
IF (lvl == -1) lvl = scope_find(T_TH, T_TR, T_TABLE, T_NONE);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
// a cell straight inside <table>/<tbody> (no <tr>, or after a </tr>) gets an implied row
|
||||
a = dom_ntype(dom_stack[depth]);
|
||||
IF (a == T_TABLE) || (a == T_TBODY) || (a == T_THEAD) || (a == T_TFOOT) open_element(T_TR, 0);
|
||||
break;
|
||||
case T_TR:
|
||||
lvl = scope_find(T_TR, T_TABLE, T_NONE, T_NONE);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
break;
|
||||
case T_THEAD:
|
||||
case T_TBODY:
|
||||
case T_TFOOT:
|
||||
lvl = scope_find(T_THEAD, T_TABLE, T_NONE, T_NONE);
|
||||
IF (lvl == -1) lvl = scope_find(T_TBODY, T_TABLE, T_NONE, T_NONE);
|
||||
IF (lvl == -1) lvl = scope_find(T_TFOOT, T_TABLE, T_NONE, T_NONE);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
BREAK;
|
||||
case T_OPTION:
|
||||
lvl = scope_find(T_OPTION, T_SELECT, T_NONE, T_NONE);
|
||||
IF (lvl != -1) pop_to(lvl);
|
||||
BREAK;
|
||||
case T_BODY:
|
||||
IF (body) return; // a second <body>: ignore
|
||||
BREAK;
|
||||
case T_HTML:
|
||||
IF (depth) return;
|
||||
}
|
||||
|
||||
d = depth;
|
||||
parent = dom_stack[d];
|
||||
idx = new_node(_tg, parent);
|
||||
a = dom_n(idx);
|
||||
DSDWORD[a + N_ATTRS] = _attrs;
|
||||
IF (_tg == T_BODY) body = idx;
|
||||
|
||||
IF (flags & TF_VOID) { text_space = false; RETURN; } // "a <img> b" keeps both spaces
|
||||
IF (depth >= MAX_DEPTH) RETURN; // too deep: treat as void
|
||||
depth++;
|
||||
d = depth;
|
||||
dom_stack[d] = idx;
|
||||
IF (_tg == T_PRE) || (_tg == T_TEXTAREA) || (_tg == T_LISTING) { pre_level++; pre_skip_lf = true; }
|
||||
}
|
||||
|
||||
// A closing tag only acts on an element in its scope: table parts never reach past
|
||||
// the nearest <table> (a stray </tr> must not close the outer table), list items stay
|
||||
// inside their list, everything else stops at a table cell boundary.
|
||||
void DOM::close_element(dword _tg)
|
||||
{
|
||||
dword lvl;
|
||||
flush_text();
|
||||
IF (_tg == T_BODY) || (_tg == T_HTML) return; // keep collecting trailing content
|
||||
SWITCH (_tg) {
|
||||
CASE T_TABLE:
|
||||
lvl = scope_find(_tg, T_NONE, T_NONE, T_NONE);
|
||||
BREAK;
|
||||
CASE T_TR:
|
||||
CASE T_TD:
|
||||
CASE T_TH:
|
||||
CASE T_THEAD:
|
||||
CASE T_TBODY:
|
||||
CASE T_TFOOT:
|
||||
CASE T_CAPTION:
|
||||
lvl = scope_find(_tg, T_TABLE, T_NONE, T_NONE);
|
||||
BREAK;
|
||||
CASE T_LI:
|
||||
lvl = scope_find(_tg, T_UL, T_OL, T_TABLE);
|
||||
BREAK;
|
||||
CASE T_DT:
|
||||
CASE T_DD:
|
||||
lvl = scope_find(_tg, T_DL, T_TABLE, T_NONE);
|
||||
BREAK;
|
||||
default:
|
||||
lvl = scope_find(_tg, T_TD, T_TH, T_TABLE);
|
||||
}
|
||||
IF (lvl == -1) RETURN;
|
||||
pop_to(lvl);
|
||||
}
|
||||
|
||||
void DOM::flush_text()
|
||||
{
|
||||
dword idx, node, len, parent;
|
||||
int d;
|
||||
IF (!text_start) RETURN;
|
||||
len = dpool.used - text_start;
|
||||
dpool.addc(0);
|
||||
IF (len) {
|
||||
d = depth;
|
||||
parent = dom_stack[d];
|
||||
idx = new_node(T_TEXT, parent);
|
||||
node = dom_n(idx);
|
||||
DSDWORD[node + N_TEXT] = text_start;
|
||||
DSDWORD[node + N_LEN] = len;
|
||||
}
|
||||
text_start = 0;
|
||||
}
|
||||
|
||||
void DOM::add_char(dword c)
|
||||
{
|
||||
IF (c == 0xFF) c = 1; // CP866 NBSP (U+00A0 after the charset conversion)
|
||||
IF (c == 1) {
|
||||
// : a space that never collapses and never breaks the line (layout splits
|
||||
// words at ' ' only), in <pre> and outside alike. Stored as CP866 NBSP.
|
||||
IF (!text_start) text_start = dpool.used;
|
||||
dpool.addc(0xFF);
|
||||
text_space = false;
|
||||
return;
|
||||
}
|
||||
IF (!pre_level) {
|
||||
IF (c == 9) || (c == 10) || (c == 13) || (c == 32) {
|
||||
IF (text_space) return;
|
||||
text_space = true;
|
||||
c = ' ';
|
||||
} ELSE {
|
||||
text_space = false;
|
||||
}
|
||||
} ELSE {
|
||||
// line ends in <pre>: CRLF, LF, and a lone CR too (old Mac text: the code
|
||||
// blocks of board.flatassembler.net have CR only)
|
||||
IF (c == 10) && (pre_cr) { pre_cr = false; return; }
|
||||
pre_cr = false;
|
||||
IF (c == 13) { pre_cr = true; c = 10; }
|
||||
IF (pre_skip_lf) { pre_skip_lf = false; IF (c == 10) RETURN; }
|
||||
IF (c == 9) { add_char(' '); add_char(' '); add_char(' '); c = ' '; }
|
||||
}
|
||||
IF (c < 9) c = ' ';
|
||||
IF (!text_start) text_start = dpool.used;
|
||||
dpool.addc(c);
|
||||
}
|
||||
|
||||
// Copies the attributes collected by the tag tokenizer into the pool: "name\0value\0...\0"
|
||||
// "&", ">", " " ... inside an attribute value (value="Next >", href with &)
|
||||
void decode_entities(dword _src, _dst, _dstsize)
|
||||
{
|
||||
char ent[32];
|
||||
dword p = _src, end, e, i;
|
||||
dword o = _dst, lim = _dst + _dstsize - 1;
|
||||
end = _src + strlen(_src);
|
||||
WHILE (DSBYTE[p]) && (o < lim) {
|
||||
IF (DSBYTE[p] == '&') {
|
||||
ent[0] = 0;
|
||||
e = GetUnicodeSymbol(#ent, sizeof(ent) - 1, p + 1, end);
|
||||
FOR (i = 0; ent[i]; i++) { IF (o < lim) { DSBYTE[o] = ent[i]; o++; } }
|
||||
p = e + 1;
|
||||
CONTINUE;
|
||||
}
|
||||
DSBYTE[o] = DSBYTE[p];
|
||||
o++; p++;
|
||||
}
|
||||
DSBYTE[o] = 0;
|
||||
}
|
||||
|
||||
dword DOM::store_attrs()
|
||||
{
|
||||
dword i, off, name, val;
|
||||
char dec[2048];
|
||||
IF (!tag.attributes.count) return 0;
|
||||
off = 0;
|
||||
for (i = 0; i < tag.attributes.count; i++) {
|
||||
name = tag.attributes.get(i);
|
||||
val = tag.values.get(i);
|
||||
IF (!val) val = "";
|
||||
EAX = dpool.add(name);
|
||||
IF (!off) off = EAX;
|
||||
IF (strchr(val, '&')) { decode_entities(val, #dec, sizeof(dec)); val = #dec; }
|
||||
dpool.add(val);
|
||||
}
|
||||
dpool.addc(0);
|
||||
RETURN off;
|
||||
}
|
||||
|
||||
void DOM::build(dword _buf, _size)
|
||||
{
|
||||
dword bufpos;
|
||||
dword bufend = _buf + _size;
|
||||
dword tagid, attrs, endpos, flags, rawlen;
|
||||
char ent[32];
|
||||
char closer[40];
|
||||
int i;
|
||||
|
||||
reset();
|
||||
tag_split();
|
||||
|
||||
for (bufpos = _buf; bufpos < bufend; bufpos++) {
|
||||
if (DSBYTE[bufpos] == '<')
|
||||
&& (strchr("!/?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", DSBYTE[bufpos+1])) {
|
||||
bufpos++;
|
||||
IF (!tag.parse(#bufpos, bufend)) continue;
|
||||
IF (tag.name[0] == '!') || (tag.name[0] == '?') continue;
|
||||
tagid = tag_id_by_name(#tag.name);
|
||||
IF (!tag.opened) {
|
||||
IF (tagid == T_BR) { open_element(T_BR, 0); continue; }
|
||||
close_element(tagid);
|
||||
continue;
|
||||
}
|
||||
flush_text(); // the pending text node must be closed before attributes go into the pool
|
||||
attrs = store_attrs();
|
||||
open_element(tagid, attrs);
|
||||
// a custom element keeps its name for type selectors: Gitea's
|
||||
// "overflow-menu .overflow-menu-items { display: flex }" is the tab bar
|
||||
IF (tagid == T_UNKNOWN) {
|
||||
i = count - 1;
|
||||
endpos = dom_n(i);
|
||||
IF (DSDWORD[endpos + N_TYPE] == T_UNKNOWN) {
|
||||
rawlen = dpool.add(#tag.name);
|
||||
DSDWORD[endpos + N_TAG] = rawlen;
|
||||
}
|
||||
}
|
||||
flags = tag_flags(tagid);
|
||||
IF (tag.selfclosed) && (flags & TF_VOID == 0) close_element(tagid);
|
||||
|
||||
if (flags & TF_RAW) {
|
||||
// raw text up to the matching closing tag
|
||||
sprintf(#closer, "</%s", #tag.name);
|
||||
endpos = strstri(bufpos + 1, #closer); // returns the position AFTER the match
|
||||
IF (endpos) endpos -= strlen(#closer); ELSE endpos = bufend;
|
||||
rawlen = endpos - bufpos - 1;
|
||||
IF (tagid == T_TEXTAREA) || (tagid == T_STYLE) {
|
||||
text_start = dpool.addn(bufpos + 1, rawlen);
|
||||
dpool.used--; // flush_text() writes the terminator itself
|
||||
flush_text();
|
||||
}
|
||||
bufpos = endpos - 1;
|
||||
}
|
||||
CONTINUE;
|
||||
}
|
||||
IF (DSBYTE[bufpos] == '&') {
|
||||
ent[0] = 0;
|
||||
endpos = GetUnicodeSymbol(#ent, sizeof(ent) - 1, bufpos + 1, bufend);
|
||||
FOR (i = 0; ent[i]; i++) add_char(ent[i]);
|
||||
bufpos = endpos;
|
||||
CONTINUE;
|
||||
}
|
||||
add_char(DSBYTE[bufpos]);
|
||||
}
|
||||
flush_text();
|
||||
depth = 0;
|
||||
pool_mark = dpool.used;
|
||||
|
||||
// <title>
|
||||
IF (i = first_of_type(T_TITLE)) {
|
||||
i = dom_nfirst(i);
|
||||
IF (i) && (dom_ntype(i) == T_TEXT) { attrs = dom_n(i); title = DSDWORD[attrs + N_TEXT]; }
|
||||
}
|
||||
}
|
||||
|
||||
dword DOM::first_of_type(dword _tg)
|
||||
{
|
||||
dword i;
|
||||
FOR (i = 1; i < count; i++) IF (dom_ntype(i) == _tg) RETURN i;
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// element with id="..." or name="..." (anchors)
|
||||
dword DOM::find_by_id(dword _id)
|
||||
{
|
||||
dword i, v;
|
||||
FOR (i = 1; i < count; i++) {
|
||||
IF (dom_ntype(i) == T_TEXT) CONTINUE;
|
||||
v = dom_n(i);
|
||||
IF (!DSDWORD[v + N_ATTRS]) CONTINUE;
|
||||
v = dom_attr(i, "id");
|
||||
IF (v) && (streq(v, _id)) RETURN i;
|
||||
v = dom_attr(i, "name");
|
||||
IF (v) && (streq(v, _id)) RETURN i;
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
#ifdef TESTBUILD
|
||||
// debug: write the tree as indented text
|
||||
dword dump_out, dump_end, dump_max;
|
||||
|
||||
void dump_str(dword _s)
|
||||
{
|
||||
dword l = strlen(_s);
|
||||
dword lim = dump_out + dump_max;
|
||||
IF (dump_end + l + 1 >= lim) RETURN;
|
||||
strcpy(dump_end, _s);
|
||||
dump_end += l;
|
||||
}
|
||||
|
||||
void dom_dump_node(dword _idx, _level)
|
||||
{
|
||||
dword node, child, s;
|
||||
char tmp[128];
|
||||
int i;
|
||||
node = dom_n(_idx);
|
||||
FOR (i = 0; i < _level; i++) dump_str(" ");
|
||||
IF (DSDWORD[node + N_TYPE] == T_TEXT) {
|
||||
dump_str("\"");
|
||||
s = dom_ntext(_idx);
|
||||
strncpy(#tmp, s, 60); tmp[60] = 0;
|
||||
dump_str(#tmp);
|
||||
dump_str("\"\n");
|
||||
} ELSE {
|
||||
dump_str("<");
|
||||
s = DSDWORD[node + N_TYPE];
|
||||
dump_str(tag_ptr[s]);
|
||||
IF (DSDWORD[node + N_ATTRS]) {
|
||||
s = dpool.get(DSDWORD[node + N_ATTRS]);
|
||||
WHILE (DSBYTE[s]) {
|
||||
dump_str(" "); dump_str(s); s += strlen(s) + 1;
|
||||
dump_str("="); strncpy(#tmp, s, 40); tmp[40] = 0; dump_str(#tmp); s += strlen(s) + 1;
|
||||
}
|
||||
}
|
||||
sprintf(#tmp, "> n=%i d=%i pos=%i mt=%i mb=%i h=%i xywh=%i,%i,%i,%i\n", _idx, DSDWORD[node + S_DISPLAY], DSDWORD[node + S_POS],
|
||||
DSDWORD[node + S_MTOP], DSDWORD[node + S_MBOT], DSDWORD[node + S_HEIGHT],
|
||||
DSDWORD[node + L_X], DSDWORD[node + L_Y], DSDWORD[node + L_W], DSDWORD[node + L_H]);
|
||||
dump_str(#tmp);
|
||||
child = DSDWORD[node + N_FIRST];
|
||||
WHILE (child) {
|
||||
dom_dump_node(child, _level + 1);
|
||||
child = dom_nnext(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DOM::dump(dword _path)
|
||||
{
|
||||
char tmp[64];
|
||||
dump_max = 300000;
|
||||
dump_out = malloc(dump_max);
|
||||
dump_end = dump_out;
|
||||
DSBYTE[dump_out] = 0;
|
||||
sprintf(#tmp, "nodes=%i pool=%i\n", count, dpool.used);
|
||||
dump_str(#tmp);
|
||||
dom_dump_node(root, 0);
|
||||
CreateFile(dump_end - dump_out, dump_out, _path);
|
||||
free(dump_out);
|
||||
}
|
||||
#endif
|
||||
@@ -1,972 +0,0 @@
|
||||
// Engine glue: the per-tab browser object (TWebBrowser), page loading pipeline
|
||||
// (encoding -> DOM -> style -> layout -> paint), anchors, image lookup, tab state.
|
||||
//
|
||||
// Include order matters: the browser must already provide cache2, show_images,
|
||||
// history, DrawStatusBar(), DrawHoverStatus() and version[].
|
||||
|
||||
// URL relative links of the page resolve against: <base href> or the page address.
|
||||
// Looked up on every call: the DOM changes with the tab, and the text lives in the
|
||||
// global buffer, so use the result before the next call.
|
||||
char page_base_buf[URL_SIZE+1];
|
||||
char abs_base_tmp[URL_SIZE+1];
|
||||
dword page_base();
|
||||
#include "engine\colors.h"
|
||||
#include "engine\special.h"
|
||||
#include "engine\tag.h"
|
||||
#include "engine\dom.h"
|
||||
bool form_is_on(dword _idx); // form.h
|
||||
#include "engine\style.h"
|
||||
#include "engine\layout.h"
|
||||
bool form_frag_text(dword _f, _strp, _lenp, _caretp); // form.h
|
||||
void form_reset();
|
||||
int form_frag_frame(dword _f);
|
||||
void form_paint_check(dword _f, int _x, _y);
|
||||
#include "engine\paint.h"
|
||||
void engine_repaint();
|
||||
#include "engine\hit.h"
|
||||
|
||||
#define BASIC_LINE_H 19
|
||||
|
||||
scroll_bar scroll_wv = { 15,NULL,NULL,NULL,
|
||||
0,2,NULL,0,0,0xeeeeee,0xBBBbbb,0xeeeeee};
|
||||
|
||||
//===================================================//
|
||||
// encoding //
|
||||
//===================================================//
|
||||
|
||||
dword page_base()
|
||||
{
|
||||
dword i, v;
|
||||
// not strlcpy(..., history.current(), ...): the method call clobbers the
|
||||
// registers of the fastcall strlcpy and the copy lands on history itself
|
||||
v = history.current();
|
||||
strlcpy(#page_base_buf, v, URL_SIZE);
|
||||
i = dom.first_of_type(T_BASE);
|
||||
IF (!i) RETURN #page_base_buf;
|
||||
v = dom_attr(i, "href");
|
||||
IF (!v) RETURN #page_base_buf;
|
||||
IF (!DSBYTE[v]) RETURN #page_base_buf;
|
||||
// the base itself may be relative to the page
|
||||
strlcpy(#abs_base_tmp, v, URL_SIZE);
|
||||
get_absolute_url(#abs_base_tmp, #page_base_buf);
|
||||
strlcpy(#page_base_buf, #abs_base_tmp, URL_SIZE);
|
||||
RETURN #page_base_buf;
|
||||
}
|
||||
|
||||
// the charsets a page may name, a prefix is enough ("koi8-ru"), and their CH_*
|
||||
char enc_names[] = "utf-8|utf8|windows-1251|windows1251|cp1251|dos|cp-866|cp866|iso-8859-5|iso8859-5|koi8-|";
|
||||
byte enc_ids[] = { CH_UTF8, CH_UTF8, CH_CP1251, CH_CP1251, CH_CP1251, CH_CP866, CH_CP866, CH_CP866,
|
||||
CH_ISO8859_5, CH_ISO8859_5, CH_KOI8 };
|
||||
|
||||
signed int get_encoding_type_by_name(dword name)
|
||||
{
|
||||
dword p, e;
|
||||
int i, n;
|
||||
p = #enc_names;
|
||||
FOR (i = 0; DSBYTE[p]; i++) {
|
||||
e = strchr(p, '|');
|
||||
n = e - p;
|
||||
IF (!strncmp(name, p, n)) { n = enc_ids[i]; RETURN n; }
|
||||
p = e + 1;
|
||||
}
|
||||
RETURN -1;
|
||||
}
|
||||
|
||||
// <meta charset=...> / <meta content="...; charset=..."> in the head, else heuristics
|
||||
int detect_encoding(dword _buf, _size)
|
||||
{
|
||||
char name[32];
|
||||
dword p, e, n;
|
||||
signed enc;
|
||||
dword limit = _buf + math.min(_size, 4096);
|
||||
p = strstri(_buf, "charset="); // points right after the match
|
||||
if (p) && (p < limit) {
|
||||
IF (DSBYTE[p] == '"') || (DSBYTE[p] == '\'') p++;
|
||||
e = p;
|
||||
WHILE (DSBYTE[e]) && (!__isWhite(DSBYTE[e])) && (DSBYTE[e] != '"') && (DSBYTE[e] != '\'')
|
||||
&& (DSBYTE[e] != ';') && (DSBYTE[e] != '>') && (DSBYTE[e] != '/') e++;
|
||||
n = math.min(e - p, 30);
|
||||
strncpy(#name, p, n); name[n] = 0;
|
||||
strlwr(#name);
|
||||
enc = get_encoding_type_by_name(#name);
|
||||
IF (enc != -1) RETURN enc;
|
||||
}
|
||||
IF (strstr(_buf, "\208\190")) RETURN CH_UTF8;
|
||||
IF (chrnum(_buf, '\246') > 5) RETURN CH_CP1251;
|
||||
RETURN CH_CP866;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// meta refresh //
|
||||
//===================================================//
|
||||
|
||||
int meta_refresh_secs; // the wait the page asks for, filled by the call below
|
||||
|
||||
// <meta http-equiv="refresh" content="0; url=..."> of the document into _out
|
||||
// (a buffer of _size bytes), 0 when there is none. The address is left as written.
|
||||
dword meta_refresh_url(dword _out, _size)
|
||||
{
|
||||
dword i, v, p, e, c;
|
||||
IF (!dom.nodes) return 0;
|
||||
for (i = 1; i < dom.count; i++) {
|
||||
IF (dom_ntype(i) != T_META) continue;
|
||||
v = dom_attr(i, "http-equiv");
|
||||
IF (!v) continue;
|
||||
IF (strcmpi(v, "refresh")) continue;
|
||||
v = dom_attr(i, "content");
|
||||
IF (!v) continue;
|
||||
p = css_skip_sp(v);
|
||||
e = strstri(p, "url="); // points right after the match
|
||||
IF (!e) CONTINUE;
|
||||
meta_refresh_secs = atoi(p);
|
||||
e = css_skip_sp(e);
|
||||
c = DSBYTE[e];
|
||||
IF (c == '"') || (c == 0x27) e++;
|
||||
strlcpy(_out, e, _size - 1);
|
||||
p = _out;
|
||||
WHILE (DSBYTE[p]) {
|
||||
c = DSBYTE[p];
|
||||
IF (c == '"') || (c == 0x27) || (c == ' ') { DSBYTE[p] = 0; BREAK; }
|
||||
p++;
|
||||
}
|
||||
IF (DSBYTE[_out]) RETURN _out;
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// anchors //
|
||||
//===================================================//
|
||||
|
||||
struct _anchors {
|
||||
char current[256];
|
||||
void clear();
|
||||
int get_pos_by_name();
|
||||
void take_anchor_from();
|
||||
} anchors;
|
||||
|
||||
void _anchors::clear()
|
||||
{
|
||||
current[0] = 0;
|
||||
}
|
||||
|
||||
// page y of the element with id/name, -1 if none, 0 for an empty name
|
||||
int _anchors::get_pos_by_name(dword _get_name)
|
||||
{
|
||||
dword idx, node;
|
||||
IF (DSBYTE[_get_name] == 0) RETURN 0;
|
||||
idx = dom.find_by_id(_get_name);
|
||||
IF (!idx) RETURN -1;
|
||||
node = dom_n(idx);
|
||||
RETURN DSDWORD[node + L_Y];
|
||||
}
|
||||
|
||||
// cut "#anchor" off the url and remember it
|
||||
void _anchors::take_anchor_from(dword _URL)
|
||||
{
|
||||
int anchor_pos;
|
||||
IF (!current[0]) && (_URL) && (anchor_pos = strrchr(_URL, '#')) {
|
||||
strncpy(#current, _URL + anchor_pos - 1, sizeof(_anchors.current) - 1);
|
||||
DSBYTE[_URL + anchor_pos - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// browser object //
|
||||
//===================================================//
|
||||
|
||||
#ifdef TESTBUILD
|
||||
void frags_dump(dword _path)
|
||||
{
|
||||
char tmp[300];
|
||||
dword i, f, p, l;
|
||||
dump_max = 300000;
|
||||
dump_out = malloc(dump_max);
|
||||
dump_end = dump_out;
|
||||
DSBYTE[dump_out] = 0;
|
||||
FOR (i = 0; i < frags.count; i++) {
|
||||
f = frags.f(i);
|
||||
sprintf(#tmp, "%i n=%i k=%i c=%i bg=%i xywh=%i,%i,%i,%i t=%i l=%i", i, DSDWORD[f + F_NODE], DSDWORD[f + F_KIND], DSDWORD[f + F_COLOR], DSDWORD[f + F_BG],
|
||||
DSDWORD[f + F_X], DSDWORD[f + F_Y], DSDWORD[f + F_W], DSDWORD[f + F_H], DSDWORD[f + F_TEXT], DSDWORD[f + F_LEN]);
|
||||
dump_str(#tmp);
|
||||
IF (DSDWORD[f + F_KIND] == FR_TEXT) {
|
||||
l = DSDWORD[f + F_LEN];
|
||||
IF (l > 200) l = 200;
|
||||
p = dpool.get(DSDWORD[f + F_TEXT]);
|
||||
strcpy(#tmp, " \"");
|
||||
strncat(#tmp, p, l);
|
||||
strcat(#tmp, "\"");
|
||||
dump_str(#tmp);
|
||||
}
|
||||
dump_str("
|
||||
");
|
||||
}
|
||||
CreateFile(dump_end - dump_out, dump_out, _path);
|
||||
free(dump_out);
|
||||
}
|
||||
#endif
|
||||
|
||||
// a new page waits for its stylesheets: DrawPage() keeps showing the previous one
|
||||
bool page_draw_pending;
|
||||
|
||||
struct TWebBrowser {
|
||||
llist list;
|
||||
dword o_bufpointer; // page source as received (for view-source / re-decode)
|
||||
dword bufpointer; // page source converted to cp866
|
||||
dword bufsize;
|
||||
int cur_encoding, custom_encoding;
|
||||
collection img_url; // images that are not cached yet (filled during layout)
|
||||
char header[150];
|
||||
bool built;
|
||||
|
||||
void ParseHtml();
|
||||
void Reparse();
|
||||
void DrawPage();
|
||||
void SetTitle();
|
||||
void Relayout();
|
||||
void Restyle();
|
||||
};
|
||||
|
||||
TWebBrowser WB1;
|
||||
|
||||
#ifdef TESTBUILD
|
||||
// the image addresses waiting for the downloader, one per line
|
||||
void img_urls_dump()
|
||||
{
|
||||
dword out, u, i;
|
||||
IF (!WB1.img_url.count) RETURN;
|
||||
out = malloc(65536);
|
||||
DSBYTE[out] = 0;
|
||||
FOR (i = 0; i < WB1.img_url.count; i++) {
|
||||
u = WB1.img_url.get(i);
|
||||
IF (strlen(out) + strlen(u) > 65000) BREAK;
|
||||
strcat(out, u);
|
||||
strcat(out, "
|
||||
");
|
||||
}
|
||||
u = strlen(out);
|
||||
CreateFile(u, out, "/hd0/1/imgs.txt");
|
||||
free(out);
|
||||
}
|
||||
#endif
|
||||
|
||||
char find_query[128]; // active search (empty = none)
|
||||
|
||||
void TWebBrowser::SetTitle()
|
||||
{
|
||||
dword t;
|
||||
IF (dom.title) {
|
||||
t = dpool.get(dom.title);
|
||||
strncpy(#header, t, sizeof(TWebBrowser.header) - 1);
|
||||
strrtrim(#header);
|
||||
IF (header[0]) {
|
||||
strncat(#header, " - ", sizeof(TWebBrowser.header) - 1);
|
||||
strncat(#header, #version, sizeof(TWebBrowser.header) - 1);
|
||||
}
|
||||
} ELSE header[0] = 0;
|
||||
IF (!header[0]) strncpy(#header, #version, sizeof(TWebBrowser.header) - 1);
|
||||
DrawTitle(#header);
|
||||
}
|
||||
|
||||
// styles are already computed: lay the page out for the current width
|
||||
void TWebBrowser::Relayout()
|
||||
{
|
||||
dword i, node;
|
||||
int y;
|
||||
IF (!built) return;
|
||||
paint_valid = false; // the canvas holds the old layout: nothing to move on a scroll
|
||||
img_url.drop();
|
||||
// images that could not be found earlier may have arrived since
|
||||
FOR (i = 1; i < dom.count; i++) {
|
||||
node = dom_n(i);
|
||||
IF (DSDWORD[node + N_IMG] == -1) DSDWORD[node + N_IMG] = 0;
|
||||
}
|
||||
// @media (max-width: 1175px) and the like: another window width, other rules
|
||||
IF (css_media_w) && (css_view_w != list.w) {
|
||||
css_view_w = list.w;
|
||||
css_view_h = list.h;
|
||||
style_all();
|
||||
} ELSE IF (css_media_h) && (css_view_h != list.h) {
|
||||
css_view_w = list.w;
|
||||
css_view_h = list.h;
|
||||
style_all();
|
||||
}
|
||||
lay_view_h = list.h; // the window: what position:fixed and bottom:0 are placed against
|
||||
page_h = layout_page(list.w);
|
||||
list.count = page_h;
|
||||
list.CheckDoesValuesOkey();
|
||||
page_text_build();
|
||||
sel_active = false;
|
||||
IF (find_query[0]) find_run(#find_query); ELSE find_clear();
|
||||
IF (anchors.current[0]) {
|
||||
y = anchors.get_pos_by_name(#anchors.current + 1);
|
||||
IF (y >= 0) { list.first = y; list.CheckDoesValuesOkey(); }
|
||||
anchors.current[0] = 0;
|
||||
}
|
||||
#ifdef TESTBUILD
|
||||
frags_dump("/hd0/1/frags.txt");
|
||||
dom.dump("/hd0/1/dom2.txt");
|
||||
img_urls_dump();
|
||||
#endif
|
||||
}
|
||||
|
||||
// a checkbox or radio was clicked and the page styles ":checked" (a spoiler, a slide
|
||||
// show): styles again from the parsed rules, a new layout, the page stays where it was
|
||||
void TWebBrowser::Restyle()
|
||||
{
|
||||
int first;
|
||||
IF (!built) RETURN;
|
||||
first = list.first;
|
||||
style_tree();
|
||||
Relayout();
|
||||
list.first = first;
|
||||
list.CheckDoesValuesOkey();
|
||||
}
|
||||
|
||||
// full pipeline for a new document
|
||||
void TWebBrowser::ParseHtml(dword _bufpointer, _bufsize)
|
||||
{
|
||||
dword p;
|
||||
IF (_bufpointer != o_bufpointer) {
|
||||
free(o_bufpointer);
|
||||
o_bufpointer = malloc(_bufsize + 1);
|
||||
memmov(o_bufpointer, _bufpointer, _bufsize);
|
||||
p = o_bufpointer + _bufsize;
|
||||
DSBYTE[p] = 0;
|
||||
}
|
||||
free(bufpointer); // free(0) does nothing
|
||||
bufsize = strlen(o_bufpointer);
|
||||
bufpointer = malloc(bufsize + 1);
|
||||
memmov(bufpointer, o_bufpointer, bufsize + 1);
|
||||
|
||||
IF (custom_encoding != -1) cur_encoding = custom_encoding;
|
||||
ELSE cur_encoding = detect_encoding(bufpointer, bufsize);
|
||||
IF (cur_encoding != CH_CP866) {
|
||||
ChangeCharset(cur_encoding, CH_CP866, bufpointer);
|
||||
bufsize = strlen(bufpointer);
|
||||
}
|
||||
|
||||
dom.release();
|
||||
form_reset();
|
||||
#ifdef TESTBUILD
|
||||
CreateFile(bufsize, bufpointer, "/hd0/1/src.htm");
|
||||
#endif
|
||||
dom.build(bufpointer, bufsize);
|
||||
#ifdef TESTBUILD
|
||||
dom.dump("/hd0/1/dom.txt");
|
||||
#endif
|
||||
css_view_w = list.w;
|
||||
css_view_h = list.h;
|
||||
style_all();
|
||||
#ifdef TESTBUILD
|
||||
CreateFile(6, "styled", "/hd0/1/step.txt");
|
||||
#endif
|
||||
built = true;
|
||||
links.clear();
|
||||
SetTitle();
|
||||
Relayout();
|
||||
#ifdef TESTBUILD
|
||||
CreateFile(6, "layout", "/hd0/1/step.txt");
|
||||
dom.dump("/hd0/1/dom2.txt");
|
||||
frags_dump("/hd0/1/frags.txt");
|
||||
#endif
|
||||
}
|
||||
|
||||
void TWebBrowser::Reparse()
|
||||
{
|
||||
// F12 / Ctrl+Tab on a tab that is still loading: there is no document yet
|
||||
IF (!dom.nodes) || (dom.count <= 1) RETURN;
|
||||
css_view_w = list.w;
|
||||
css_view_h = list.h;
|
||||
style_all(); // a linked stylesheet may have arrived
|
||||
Relayout();
|
||||
#ifdef TESTBUILD
|
||||
frags_dump("/hd0/1/frags.txt");
|
||||
dom.dump("/hd0/1/dom2.txt");
|
||||
img_urls_dump();
|
||||
#endif
|
||||
}
|
||||
|
||||
// DrawPage() for a page that only scrolled (wheel, scroll bar, keys): the canvas moves
|
||||
// and only the band that came into view is painted (paint.h paint_scroll)
|
||||
bool page_scrolling;
|
||||
|
||||
void TWebBrowser::DrawPage()
|
||||
{
|
||||
dword h;
|
||||
IF (!built) return;
|
||||
h = list.h + PAINT_MARGIN + PAINT_MARGIN;
|
||||
IF (canvas.bufw != list.w) || (canvas.bufh != h) || (canvas.bufx != list.x) || (canvas.bufy != list.y) {
|
||||
page_draw_pending = false; // the viewport changed: an old picture does not fit
|
||||
paint_init(list.x, list.y, list.w, list.h);
|
||||
IF (lay_page_w != list.w) Relayout();
|
||||
} ELSE IF (page_draw_pending) {
|
||||
// the canvas still holds the previous page: show it again (window redraw,
|
||||
// wheel, hover...) instead of painting the new page without its stylesheets
|
||||
canvas.Show(PAINT_MARGIN, list.h);
|
||||
return;
|
||||
}
|
||||
IF (page_scrolling) paint_scroll(list.first, list.h);
|
||||
ELSE paint_page(list.first, list.h);
|
||||
#ifdef TESTBUILD
|
||||
sprintf(#param, "page_h=%i count=%i visible=%i first=%i h=%i frags=%i w=%i", page_h, list.count, list.visible, list.first, list.h, frags.count, list.w);
|
||||
CreateFile(strlen(#param), #param, "/hd0/1/scroll.txt");
|
||||
#endif
|
||||
|
||||
scroll_wv.max_area = list.count;
|
||||
scroll_wv.cur_area = list.visible;
|
||||
scroll_wv.position = list.first;
|
||||
scroll_wv.all_redraw = 0;
|
||||
scroll_wv.start_x = list.x + list.w;
|
||||
scroll_wv.start_y = list.y;
|
||||
scroll_wv.size_y = list.h;
|
||||
|
||||
IF (list.count <= list.visible) {
|
||||
DrawBar(scroll_wv.start_x, scroll_wv.start_y, scroll_wv.size_x,
|
||||
scroll_wv.size_y, body_bg & 0x00FFFFFF);
|
||||
} ELSE {
|
||||
scrollbar_v_draw(#scroll_wv);
|
||||
}
|
||||
}
|
||||
|
||||
void engine_repaint()
|
||||
{
|
||||
WB1.DrawPage();
|
||||
}
|
||||
|
||||
// the page scrolled and nothing else changed
|
||||
void engine_scroll_page()
|
||||
{
|
||||
page_scrolling = true;
|
||||
WB1.DrawPage();
|
||||
page_scrolling = false;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// images //
|
||||
//===================================================//
|
||||
|
||||
// width= / height= (and CSS) scale the picture, a missing one keeps the aspect ratio;
|
||||
// a picture wider than the page is scaled down to it to save memory
|
||||
// _w x _h no taller than the window, into img_cap_w x img_cap_h
|
||||
int img_cap_w, img_cap_h;
|
||||
void image_cap_h(int _w, _h)
|
||||
{
|
||||
int k;
|
||||
img_cap_w = _w;
|
||||
img_cap_h = _h;
|
||||
IF (css_view_h <= 0) RETURN;
|
||||
IF (_h <= css_view_h) RETURN;
|
||||
k = _w * css_view_h;
|
||||
k /= _h;
|
||||
IF (k < 1) k = 1;
|
||||
img_cap_w = k;
|
||||
img_cap_h = css_view_h;
|
||||
}
|
||||
|
||||
dword image_apply_size(dword _idx, _img)
|
||||
{
|
||||
dword node = dom_n(_idx);
|
||||
int iw = DSDWORD[_img + 4], ih = DSDWORD[_img + 8];
|
||||
int w = DSDWORD[node + S_WIDTH], h = DSDWORD[node + S_HEIGHT];
|
||||
int maxw;
|
||||
IF (w < 0) w = 0; // percent: not supported, keep the intrinsic size
|
||||
IF (h < 0) h = 0;
|
||||
IF (!iw) || (!ih) return _img;
|
||||
IF (!w) && (!h) w = iw;
|
||||
IF (!w) { w = iw * h; w /= ih; }
|
||||
IF (!h) { h = ih * w; h /= iw; }
|
||||
maxw = css_view_w;
|
||||
// "width: auto; height: 100%" is scaled to the height of its box and cut by the box
|
||||
// around (layout.h img_cover): capped by the window height, a width of four windows
|
||||
IF (!DSDWORD[node + S_WIDTH]) && (DSDWORD[node + S_HPCT]) {
|
||||
image_cap_h(w, h);
|
||||
w = img_cap_w;
|
||||
h = img_cap_h;
|
||||
maxw = css_view_w * 4;
|
||||
}
|
||||
IF (maxw > 0) && (w > maxw) { h = h * maxw; h /= w; w = maxw; }
|
||||
IF (w < 1) w = 1;
|
||||
IF (h < 1) h = 1;
|
||||
RETURN img_scaled(_img, iw, ih, w, h);
|
||||
}
|
||||
|
||||
// Where the picture of an <img> is. Lazy loaders keep the real address in a data-*
|
||||
// attribute and put a blank placeholder into src; responsive pages may give only
|
||||
// srcset. image_from_srcset says the value is a srcset list: the caller keeps the
|
||||
// first address of it (up to the first space).
|
||||
bool image_from_srcset;
|
||||
|
||||
dword image_src_attr(dword _idx)
|
||||
{
|
||||
dword v, s;
|
||||
image_from_srcset = false;
|
||||
v = dom_attr(_idx, "data-src");
|
||||
IF (!v) v = dom_attr(_idx, "data-lazy-src");
|
||||
IF (!v) v = dom_attr(_idx, "data-original");
|
||||
IF (v) && (DSBYTE[v]) return v;
|
||||
v = dom_attr(_idx, "src");
|
||||
IF (v) && (DSBYTE[v]) && (strncmp(v, "data:", 5)) RETURN v; // a real address
|
||||
s = dom_attr(_idx, "srcset");
|
||||
IF (!s) s = dom_attr(_idx, "data-srcset");
|
||||
IF (s) && (DSBYTE[s]) { image_from_srcset = true; RETURN s; }
|
||||
IF (v) && (DSBYTE[v]) RETURN v; // an inline data: picture
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// a srcset value copied to a buffer: keep its first address only
|
||||
void image_srcset_first(dword _buf)
|
||||
{
|
||||
dword p;
|
||||
IF (!image_from_srcset) RETURN;
|
||||
p = css_skip_sp(_buf);
|
||||
IF (p != _buf) strcpy(_buf, p);
|
||||
p = strchr(_buf, ' ');
|
||||
IF (p) DSBYTE[p] = 0;
|
||||
p = _buf + strlen(_buf);
|
||||
IF (p > _buf) && (DSBYTE[p-1] == ',') DSBYTE[p-1] = 0;
|
||||
}
|
||||
|
||||
// the _n (up to 7) chars at _p, lowercased, are _lit
|
||||
bool image_ext_is(dword _p, _n, _lit)
|
||||
{
|
||||
char ext[8];
|
||||
strncpy(#ext, _p, _n);
|
||||
ext[_n] = 0;
|
||||
strlwr(#ext);
|
||||
IF (streq(#ext, _lit)) RETURN true;
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// a picture libimg cannot decode, by the extension of its address (query aside):
|
||||
// there is no point in downloading it. (streqrp() compares the start of a string,
|
||||
// so it never caught "x.svg" and every SVG icon went to the downloader.)
|
||||
// IMG_SVG / IMG_WEBP, 0 for a picture libimg knows
|
||||
enum { IMG_SVG = 1, IMG_WEBP };
|
||||
int image_unsupported(dword _url)
|
||||
{
|
||||
dword e, n;
|
||||
IF (!strncmp(_url, "data:", 5)) return 0;
|
||||
e = strchr(_url, '?');
|
||||
IF (!e) e = strchr(_url, '#');
|
||||
IF (!e) e = _url + strlen(_url);
|
||||
n = e - _url;
|
||||
IF (n < 5) RETURN 0;
|
||||
n = e - 5;
|
||||
IF (image_ext_is(n, 5, ".webp")) RETURN IMG_WEBP;
|
||||
IF (image_ext_is(n, 5, ".svgz")) RETURN IMG_SVG;
|
||||
n++;
|
||||
IF (image_ext_is(n, 4, ".svg")) RETURN IMG_SVG;
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// SVG and WebP are not in libimg: such a picture is asked from a converter, wsrv.nl by
|
||||
// default, which renders it to PNG (JPEG for WebP, mostly photos) at the size the page
|
||||
// wants. Plain http, so no https proxy is needed. The address (ini image_converter) gets
|
||||
// the escaped picture address and wsrv.nl parameters appended; empty: these pictures
|
||||
// are skipped, as before
|
||||
char image_converter[256]; // settings.h: "http://wsrv.nl/?url=" by default
|
||||
char image_proxy_buf[4001]; // URL_SIZE + 1
|
||||
char image_hex[] = "0123456789ABCDEF";
|
||||
|
||||
// _url escaped as a query value into _dst; 0 when it does not fit before _end
|
||||
dword image_proxy_esc(dword _dst, _url, _end)
|
||||
{
|
||||
dword c, h;
|
||||
while (DSBYTE[_url]) {
|
||||
IF (_dst >= _end) return 0;
|
||||
c = DSBYTE[_url];
|
||||
IF (c <= ' ') || (c >= 0x7F) || (c == '%') || (c == '&') || (c == '+') || (c == '#') || (c == '?') || (c == '=') {
|
||||
DSBYTE[_dst] = '%';
|
||||
h = c >> 4 + #image_hex;
|
||||
DSBYTE[_dst + 1] = DSBYTE[h];
|
||||
h = c & 15 + #image_hex;
|
||||
DSBYTE[_dst + 2] = DSBYTE[h];
|
||||
_dst += 3;
|
||||
} ELSE {
|
||||
DSBYTE[_dst] = c;
|
||||
_dst++;
|
||||
}
|
||||
_url++;
|
||||
}
|
||||
DSBYTE[_dst] = 0;
|
||||
RETURN _dst;
|
||||
}
|
||||
|
||||
// a width/height of the page for the picture: 0 when not given (or a percent),
|
||||
// at most _max (the page width) when there is one
|
||||
int image_want_dim(int _v, _max)
|
||||
{
|
||||
IF (_v <= 0) RETURN 0;
|
||||
IF (_max <= 0) RETURN _v;
|
||||
IF (_v > _max) RETURN _max;
|
||||
RETURN _v;
|
||||
}
|
||||
|
||||
// both sides of an SVG given (debian.org: width="512" and "height: 60px" of the CSS):
|
||||
// a browser fits the drawing into that box keeping its proportions (preserveAspectRatio
|
||||
// "meet"), a raster picture is stretched. wsrv.nl fits it (users.svg comes 80x60) and
|
||||
// image_fitted tells engine_get_image() not to stretch it to the box again. A picture
|
||||
// of the box size with transparent edges (fit=contain) was no good: max-width shrank
|
||||
// the whole box, and the icon in it, to a few pixels
|
||||
bool image_fitted;
|
||||
void image_proxy_box(dword _d, _w, _h)
|
||||
{
|
||||
sprintf(_d, "&output=png&w=%d&h=%d&fit=inside", _w, _h);
|
||||
image_fitted = true;
|
||||
}
|
||||
|
||||
// the output format and the size of the <img> _idx (0: none) at _d. One side is enough
|
||||
// otherwise: wsrv.nl keeps the aspect ratio, image_apply_size() does the rest
|
||||
void image_proxy_tail(dword _d, _kind, _idx)
|
||||
{
|
||||
dword node, fmt;
|
||||
int w, h;
|
||||
IF (_kind == IMG_WEBP) fmt = "jpg"; ELSE fmt = "png";
|
||||
IF (!_idx) { sprintf(_d, "&output=%s", fmt); return; }
|
||||
node = dom_n(_idx);
|
||||
w = DSDWORD[node + S_WIDTH];
|
||||
w = image_want_dim(w, css_view_w);
|
||||
h = DSDWORD[node + S_HEIGHT];
|
||||
h = image_want_dim(h, 0);
|
||||
IF (_kind == IMG_SVG) && (w) && (h) { image_proxy_box(_d, w, h); RETURN; }
|
||||
IF (w) { sprintf(_d, "&output=%s&w=%d", fmt, w); RETURN; }
|
||||
IF (h) { sprintf(_d, "&output=%s&h=%d", fmt, h); RETURN; }
|
||||
sprintf(_d, "&output=%s", fmt);
|
||||
}
|
||||
|
||||
// the converter address of the absolute picture address _url (in image_proxy_buf), 0
|
||||
// when it is too long. _idx: the <img> whose size is asked for, 0 for a background
|
||||
dword image_proxy_url(dword _url, _idx, _kind)
|
||||
{
|
||||
dword d, e;
|
||||
strcpy(#image_proxy_buf, #image_converter);
|
||||
d = strlen(#image_proxy_buf) + #image_proxy_buf;
|
||||
e = #image_proxy_buf + 3940;
|
||||
d = image_proxy_esc(d, _url, e);
|
||||
IF (!d) RETURN 0;
|
||||
image_proxy_tail(d, _kind, _idx);
|
||||
RETURN #image_proxy_buf;
|
||||
}
|
||||
|
||||
// "photo.jpg.webp": libimg has no WebP, but such an address is mostly a converted
|
||||
// copy with the original beside it (the ichef pictures of bbc.com, the WebP plugins
|
||||
// of WordPress): ask for "photo.jpg" instead. The query stays.
|
||||
void image_webp_twin(dword _buf)
|
||||
{
|
||||
dword e, q, k;
|
||||
e = strchr(_buf, '?');
|
||||
IF (!e) e = _buf + strlen(_buf);
|
||||
k = e - _buf;
|
||||
IF (k < 10) return;
|
||||
q = e - 5;
|
||||
IF (!image_ext_is(q, 5, ".webp")) RETURN;
|
||||
k = q - 4;
|
||||
IF (image_ext_is(k, 4, ".jpg")) || (image_ext_is(k, 4, ".png")) || (image_ext_is(k, 4, ".gif")) {
|
||||
strcpy(q, e);
|
||||
RETURN;
|
||||
}
|
||||
k = q - 5;
|
||||
IF (image_ext_is(k, 5, ".jpeg")) strcpy(q, e);
|
||||
}
|
||||
|
||||
// an inline "data:" picture: only base64 is decoded ("data:image/svg+xml,<svg..."
|
||||
// is not a picture libimg knows), 0 otherwise
|
||||
dword image_from_data(dword _v)
|
||||
{
|
||||
dword p, b64, n, img;
|
||||
p = strstr(_v, "base64,");
|
||||
IF (!p) RETURN 0;
|
||||
p += 7;
|
||||
IF (DSBYTE[p] == ' ') p++;
|
||||
n = strlen(p);
|
||||
b64 = malloc(n + 1);
|
||||
base64_decode stdcall (p, b64, n);
|
||||
img_decode stdcall (b64, EAX, 0);
|
||||
img = EAX;
|
||||
free(b64);
|
||||
RETURN img;
|
||||
}
|
||||
|
||||
// a big-endian 16-bit number
|
||||
int image_be16(dword _p)
|
||||
{
|
||||
int v;
|
||||
v = DSBYTE[_p];
|
||||
v <<= 8;
|
||||
v += DSBYTE[_p + 1];
|
||||
RETURN v;
|
||||
}
|
||||
|
||||
// width and height from the header of a PNG, GIF or JPEG file; false for anything else
|
||||
int image_dim_w, image_dim_h;
|
||||
bool image_dims(dword _data, _size)
|
||||
{
|
||||
dword p, e;
|
||||
int m, len;
|
||||
image_dim_w = image_dim_h = 0;
|
||||
IF (_size < 32) return false;
|
||||
IF (DSBYTE[_data + 1] == 'P') && (DSBYTE[_data + 2] == 'N') && (DSBYTE[_data + 3] == 'G') {
|
||||
// IHDR: big-endian width and height at 16 and 20
|
||||
image_dim_w = image_be16(_data + 18);
|
||||
image_dim_h = image_be16(_data + 22);
|
||||
IF (DSBYTE[_data + 16]) || (DSBYTE[_data + 17]) image_dim_w = 65535;
|
||||
IF (DSBYTE[_data + 20]) || (DSBYTE[_data + 21]) image_dim_h = 65535;
|
||||
return true;
|
||||
}
|
||||
IF (DSBYTE[_data] == 'G') && (DSBYTE[_data + 1] == 'I') && (DSBYTE[_data + 2] == 'F') {
|
||||
// little-endian at 6 and 8
|
||||
m = DSBYTE[_data + 7];
|
||||
m <<= 8;
|
||||
m += DSBYTE[_data + 6];
|
||||
image_dim_w = m;
|
||||
m = DSBYTE[_data + 9];
|
||||
m <<= 8;
|
||||
m += DSBYTE[_data + 8];
|
||||
image_dim_h = m;
|
||||
return true;
|
||||
}
|
||||
IF (DSBYTE[_data] != 0xFF) || (DSBYTE[_data + 1] != 0xD8) return false;
|
||||
// JPEG: walk the segments up to a start of frame (SOF0..SOF15 but DHT, JPG, DAC)
|
||||
p = _data + 2;
|
||||
e = _data + _size;
|
||||
e -= 9;
|
||||
while (p < e) {
|
||||
IF (DSBYTE[p] != 0xFF) return false;
|
||||
m = DSBYTE[p + 1];
|
||||
IF (m == 0xFF) { p++; CONTINUE; }
|
||||
IF (m >= 0xC0) && (m <= 0xCF) && (m != 0xC4) && (m != 0xC8) && (m != 0xCC) {
|
||||
image_dim_h = image_be16(p + 5);
|
||||
image_dim_w = image_be16(p + 7);
|
||||
RETURN true;
|
||||
}
|
||||
IF (m == 0xD9) || (m == 0xDA) RETURN false;
|
||||
len = image_be16(p + 2);
|
||||
p += 2;
|
||||
p += len;
|
||||
}
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// A picture too big to decode with the memory there is: a 6600x3712 photo on
|
||||
// nasa.gov is 98 MB in 32bpp, and libimg needs the 24bpp copy beside it for a while.
|
||||
// Such a one is left out (its [alt] shows) instead of taking the browser down.
|
||||
bool image_too_big(dword _data, _size)
|
||||
{
|
||||
dword px, need, free_kb;
|
||||
IF (!image_dims(_data, _size)) RETURN false;
|
||||
IF (image_dim_w > 16384) || (image_dim_h > 16384) RETURN true;
|
||||
px = image_dim_w * image_dim_h;
|
||||
// 24bpp decode + 32bpp copy + the scaled one: 8 bytes a pixel, in KB
|
||||
need = px / 128;
|
||||
free_kb = GetFreeRAM();
|
||||
free_kb >>= 1;
|
||||
IF (need > free_kb) RETURN true;
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// Pictures that came but cannot be shown: libimg does not decode them, or they are too
|
||||
// big for the memory there is. Their file leaves the cache, and they are not asked for
|
||||
// again when a later layout (a resize, a tab switch) meets them: the next GetShown() or
|
||||
// GetMissing() fetched them once more only to leave them out again, a big photo each
|
||||
// time. EventClearCache() (Ctrl+F5, the menu) forgets them.
|
||||
collection image_failed;
|
||||
|
||||
// a decoded picture for an absolute address: a local file, the download cache, or
|
||||
// 0 after queueing the address into WB1.img_url for the downloader
|
||||
dword image_fetch(dword _path)
|
||||
{
|
||||
dword img = 0, data = 0, size = 0, p;
|
||||
int pos;
|
||||
bool big;
|
||||
IF (check_is_the_adress_local(_path)) {
|
||||
img_from_file stdcall (_path);
|
||||
return EAX;
|
||||
}
|
||||
IF (image_failed.get_pos_by_name(_path) != -1) return 0;
|
||||
IF (cache2_is_contain(_path)) {
|
||||
pos = cache2.cur;
|
||||
p = cache2.path.get(pos);
|
||||
read_file(p, #data, #size);
|
||||
// not "if (size) && (!image_too_big())": c-- then pushed the size as 0
|
||||
IF (size) {
|
||||
big = image_too_big(data, size);
|
||||
IF (!big) {
|
||||
img_decode stdcall (data, size, 0);
|
||||
img = EAX;
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
IF (img) RETURN img;
|
||||
cache2_evict(pos);
|
||||
IF (size) {
|
||||
image_failed.add(_path);
|
||||
RETURN 0;
|
||||
}
|
||||
// the file of the entry is gone (another window trimmed it): fetch it again
|
||||
}
|
||||
IF (WB1.img_url.get_pos_by_name(_path) == -1) WB1.img_url.add(_path);
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// the canvas blends 32bpp only: convert anything else, 0 on failure
|
||||
dword image_to_32bpp(dword _img)
|
||||
{
|
||||
IF (DSDWORD[_img + 20] == IMAGE_BPP32) RETURN _img;
|
||||
img_convert stdcall (_img, 0, IMAGE_BPP32, 0, 0);
|
||||
$push eax
|
||||
img_destroy stdcall (_img);
|
||||
$pop eax
|
||||
RETURN EAX;
|
||||
}
|
||||
|
||||
// The picture at address _v (a data: one, or relative to the page) as a 32bpp image; 0
|
||||
// when it cannot be shown or is not there yet (a remote one is queued for the downloader).
|
||||
// _src: the address comes from an <img>, a srcset list or a ".jpg.webp" name maybe;
|
||||
// _idx: that <img> (its size goes to wsrv.nl), 0 for a background
|
||||
dword image_load(dword _v, _src, _idx)
|
||||
{
|
||||
dword img, u;
|
||||
int kind;
|
||||
char path[URL_SIZE + 1]; // get_absolute_url() writes up to URL_SIZE + 1 bytes
|
||||
image_fitted = false;
|
||||
IF (!strncmp(_v, "data:", 5)) {
|
||||
img = image_from_data(_v);
|
||||
} else {
|
||||
strlcpy(#path, _v, sizeof(path) - 1);
|
||||
IF (_src) { image_srcset_first(#path); image_webp_twin(#path); }
|
||||
kind = image_unsupported(#path);
|
||||
get_absolute_url(#path, page_base());
|
||||
IF (kind) {
|
||||
// wsrv.nl cannot reach a local file
|
||||
IF (!image_converter[0]) RETURN 0;
|
||||
IF (check_is_the_adress_local(#path)) RETURN 0;
|
||||
u = image_proxy_url(#path, _idx, kind);
|
||||
IF (!u) RETURN 0;
|
||||
img = image_fetch(u);
|
||||
} ELSE {
|
||||
img = image_fetch(#path);
|
||||
}
|
||||
}
|
||||
IF (!img) RETURN 0;
|
||||
RETURN image_to_32bpp(img);
|
||||
}
|
||||
|
||||
// the background-image of a box, kept in its N_IMG slot. An <img> holds its own picture
|
||||
// there, so it gets no background (a lazy-load spinner behind it is not drawn)
|
||||
dword engine_get_bgimage(dword _idx)
|
||||
{
|
||||
dword node = dom_n(_idx);
|
||||
dword img = DSDWORD[node + N_IMG];
|
||||
dword k = DSDWORD[node + S_BGIMG];
|
||||
dword v;
|
||||
IF (dom_ntype(_idx) == T_IMG) RETURN 0;
|
||||
IF (img == -1) RETURN 0;
|
||||
IF (img) RETURN img;
|
||||
IF (!k) || (!show_images) RETURN 0;
|
||||
k--;
|
||||
v = bg_urls.get(k);
|
||||
IF (v) {
|
||||
img = image_load(v, false, 0);
|
||||
IF (img) { DSDWORD[node + N_IMG] = img; RETURN img; }
|
||||
}
|
||||
DSDWORD[node + N_IMG] = -1;
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// returns a 32bpp libimg image for the <img> node, or 0. Missing remote images are
|
||||
// queued into WB1.img_url for the downloader.
|
||||
dword engine_get_image(dword _idx)
|
||||
{
|
||||
dword node = dom_n(_idx);
|
||||
dword img = DSDWORD[node + N_IMG];
|
||||
dword v;
|
||||
|
||||
IF (img == -1) return 0;
|
||||
IF (img) return img;
|
||||
IF (!show_images) RETURN 0;
|
||||
|
||||
v = image_src_attr(_idx);
|
||||
IF (!v) { DSDWORD[node + N_IMG] = -1; RETURN 0; }
|
||||
img = image_load(v, true, _idx);
|
||||
IF (!img) { DSDWORD[node + N_IMG] = -1; RETURN 0; }
|
||||
IF (!image_fitted) img = image_apply_size(_idx, img);
|
||||
DSDWORD[node + N_IMG] = img;
|
||||
DSDWORD[node + N_IMGW] = DSDWORD[img + 4];
|
||||
DSDWORD[node + N_IMGH] = DSDWORD[img + 8];
|
||||
RETURN img;
|
||||
}
|
||||
|
||||
// absolute url of an <img> node into a caller buffer of URL_SIZE bytes
|
||||
dword image_url(dword _idx, _out)
|
||||
{
|
||||
dword v;
|
||||
v = image_src_attr(_idx);
|
||||
IF (!v) RETURN 0;
|
||||
strlcpy(_out, v, URL_SIZE - 1);
|
||||
image_srcset_first(_out);
|
||||
image_webp_twin(_out);
|
||||
get_absolute_url(_out, page_base());
|
||||
RETURN _out;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// tabs //
|
||||
//===================================================//
|
||||
|
||||
// tab slots 0..TABS_MAX, shared with tabs.h (tabdata[], tabstory[])
|
||||
#define TABS_MAX 5
|
||||
DOM tabdom[TABS_MAX+1];
|
||||
POOL tabpool[TABS_MAX+1];
|
||||
|
||||
void engine_save_tab(int _i)
|
||||
{
|
||||
tabdom[_i] = dom;
|
||||
tabpool[_i] = dpool;
|
||||
}
|
||||
|
||||
void engine_restore_tab(int _i)
|
||||
{
|
||||
dom = tabdom[_i];
|
||||
dpool = tabpool[_i];
|
||||
// a tab without a document gets no Relayout(): the fragments of the tab shown
|
||||
// before would stay and a selection would read text through the empty pool
|
||||
IF (!dom.nodes) frags.reset();
|
||||
form_reset(); // typed values are kept for the active document only
|
||||
links.clear();
|
||||
sel_active = false;
|
||||
find_clear();
|
||||
// no style_all() here: both callers (tabs.h) follow with Reparse(), which restyles
|
||||
DrawTitle(#WB1.header);
|
||||
}
|
||||
|
||||
// a fresh tab starts with an empty document
|
||||
void engine_new_tab()
|
||||
{
|
||||
MEMSETD(#dom, sizeof(DOM) / 4, 0);
|
||||
MEMSETD(#dpool, sizeof(POOL) / 4, 0);
|
||||
WB1.bufpointer = 0;
|
||||
WB1.o_bufpointer = 0;
|
||||
WB1.bufsize = 0;
|
||||
WB1.built = false;
|
||||
WB1.header[0] = 0;
|
||||
MEMSETD(#WB1.img_url, sizeof(collection) / 4, 0);
|
||||
frags.reset(); // the old page's fragments point into a document that no longer exists
|
||||
form_reset();
|
||||
links.clear();
|
||||
sel_active = false;
|
||||
find_clear();
|
||||
}
|
||||
|
||||
// a closed tab: drop its document (the active tab's state is kept)
|
||||
void engine_free_tab(int _i, _active)
|
||||
{
|
||||
engine_save_tab(_active);
|
||||
dom = tabdom[_i];
|
||||
dpool = tabpool[_i];
|
||||
dom.release(); // without nodes it only releases dpool
|
||||
dom = tabdom[_active];
|
||||
dpool = tabpool[_active];
|
||||
}
|
||||
|
||||
#include "engine\form.h"
|
||||
@@ -1,829 +0,0 @@
|
||||
// Forms: text fields the user types into, checkboxes and radio buttons, and sending
|
||||
// a form by GET or POST (search boxes). Values the user changed live here, keyed by DOM node
|
||||
// index: paint draws them over the control's fragment, so typing needs no relayout.
|
||||
// They belong to the document of the active tab and go away with it.
|
||||
//
|
||||
// Included at the end of engine.h: it uses WB1 (page encoding) and page_base().
|
||||
|
||||
#define FORM_FIELDS 32
|
||||
#define FORM_VAL 256
|
||||
#define FORM_POST_MAX 1024 // body of a POST form
|
||||
|
||||
dword fv_node[32]; // FORM_FIELDS
|
||||
char fv_text[8192]; // FORM_FIELDS * FORM_VAL, cp866 like the page text
|
||||
int fv_count;
|
||||
dword form_focus; // text field with the caret, 0 = none
|
||||
char form_show[264]; // FORM_VAL + 8: what paint draws for a changed field
|
||||
|
||||
// control kinds
|
||||
enum { FC_NONE, FC_TEXT, FC_SECRET, FC_CHECK, FC_RADIO, FC_SUBMIT, FC_BUTTON, FC_HIDDEN, FC_SELECT, FC_AREA };
|
||||
|
||||
// the kind of each <input type> named in form_kind()'s list, in the same order.
|
||||
// "file": no file picker here, so it is a button that never sends anything
|
||||
byte fc_of_input[] = { FC_CHECK, FC_RADIO, FC_SUBMIT, FC_SUBMIT, FC_HIDDEN, FC_SECRET, FC_BUTTON, FC_BUTTON, FC_BUTTON };
|
||||
|
||||
// result of form_submit_url()
|
||||
enum { FS_NONE, FS_OK, FS_POST };
|
||||
|
||||
void form_reset()
|
||||
{
|
||||
fv_count = 0;
|
||||
form_focus = 0;
|
||||
}
|
||||
|
||||
dword fv_slot(int _i)
|
||||
{
|
||||
dword p;
|
||||
p = _i * FORM_VAL;
|
||||
p += #fv_text;
|
||||
RETURN p;
|
||||
}
|
||||
|
||||
// changed value of a control, or 0 when the user has not touched it
|
||||
dword fv_find(dword _idx)
|
||||
{
|
||||
int i;
|
||||
FOR (i = 0; i < fv_count; i++) {
|
||||
IF (fv_node[i] == _idx) RETURN fv_slot(i);
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// lowercased copy of an attribute into a buffer of 16 bytes ("" when missing)
|
||||
dword attr_lower(dword _idx, _name, _buf)
|
||||
{
|
||||
dword v;
|
||||
v = dom_attr(_idx, _name);
|
||||
IF (!v) { DSBYTE[_buf] = 0; RETURN _buf; }
|
||||
strlcpy(_buf, v, 15);
|
||||
strlwr(_buf);
|
||||
RETURN _buf;
|
||||
}
|
||||
|
||||
int form_kind(dword _idx)
|
||||
{
|
||||
char t[16];
|
||||
dword tg;
|
||||
tg = dom_ntype(_idx);
|
||||
IF (tg == T_SELECT) return FC_SELECT;
|
||||
IF (tg == T_TEXTAREA) RETURN FC_AREA;
|
||||
IF (tg == T_BUTTON) {
|
||||
attr_lower(_idx, "type", #t);
|
||||
IF (!t[0]) RETURN FC_SUBMIT; // a <button> submits unless told otherwise
|
||||
IF (streq(#t, "submit")) RETURN FC_SUBMIT;
|
||||
RETURN FC_BUTTON;
|
||||
}
|
||||
IF (tg != T_INPUT) RETURN FC_NONE;
|
||||
attr_lower(_idx, "type", #t);
|
||||
tg = css_kw(#t, "checkbox|radio|submit|image|hidden|password|button|reset|file");
|
||||
IF (tg) RETURN fc_of_input[tg - 1];
|
||||
RETURN FC_TEXT;
|
||||
}
|
||||
|
||||
// the slot of a control, made from its markup on first use (0 when all are taken).
|
||||
// Text fields start with value="", checkboxes and radios with "1" when checked.
|
||||
dword fv_get(dword _idx)
|
||||
{
|
||||
dword s, v;
|
||||
int kind;
|
||||
s = fv_find(_idx);
|
||||
IF (s) return s;
|
||||
IF (fv_count >= FORM_FIELDS) return 0;
|
||||
fv_node[fv_count] = _idx;
|
||||
s = fv_slot(fv_count);
|
||||
fv_count++;
|
||||
kind = form_kind(_idx);
|
||||
IF (kind == FC_CHECK) || (kind == FC_RADIO) {
|
||||
IF (dom_attr(_idx, "checked")) strcpy(s, "1"); ELSE strcpy(s, "0");
|
||||
RETURN s;
|
||||
}
|
||||
v = dom_attr(_idx, "value");
|
||||
IF (v) strlcpy(s, v, FORM_VAL - 1); ELSE DSBYTE[s] = 0;
|
||||
RETURN s;
|
||||
}
|
||||
|
||||
// a checkbox or radio is on: as the user left it, else as the markup says
|
||||
bool form_is_on(dword _idx)
|
||||
{
|
||||
dword s;
|
||||
s = fv_find(_idx);
|
||||
IF (s) {
|
||||
IF (DSBYTE[s] == '1') RETURN true;
|
||||
RETURN false;
|
||||
}
|
||||
IF (dom_attr(_idx, "checked")) RETURN true;
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// the control a <label> clicks: for="id", else the first input inside it
|
||||
dword form_label_target(dword _lab)
|
||||
{
|
||||
dword v, i, p;
|
||||
v = dom_attr(_lab, "for");
|
||||
IF (v) {
|
||||
i = dom.find_by_id(v);
|
||||
IF (!i) RETURN 0;
|
||||
IF (dom_ntype(i) != T_INPUT) RETURN 0;
|
||||
RETURN i;
|
||||
}
|
||||
// the subtree of the label follows it in the node array
|
||||
FOR (i = _lab + 1; i < dom.count; i++) {
|
||||
p = dom_nparent(i);
|
||||
WHILE (p) && (p != _lab) p = dom_nparent(p);
|
||||
IF (!p) RETURN 0;
|
||||
IF (dom_ntype(i) == T_INPUT) RETURN i;
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// the <form> a control belongs to: an ancestor, else form="id", else the nearest
|
||||
// form before it when that one was cut short by table markup: the table around the
|
||||
// form holds the control too. A control after a form that was simply closed has none.
|
||||
dword form_owner(dword _idx)
|
||||
{
|
||||
dword p, v;
|
||||
int i;
|
||||
p = dom_nparent(_idx);
|
||||
WHILE (p) {
|
||||
IF (dom_ntype(p) == T_FORM) return p;
|
||||
p = dom_nparent(p);
|
||||
}
|
||||
v = dom_attr(_idx, "form");
|
||||
IF (v) {
|
||||
p = dom.find_by_id(v);
|
||||
IF (p) && (dom_ntype(p) == T_FORM) return p;
|
||||
}
|
||||
for (i = _idx - 1; i > 0; i--) {
|
||||
IF (dom_ntype(i) != T_FORM) CONTINUE;
|
||||
p = dom_nparent(i);
|
||||
WHILE (p) {
|
||||
IF (dom_ntype(p) == T_TABLE) BREAK;
|
||||
// a form inside a cell was closed normally: controls in other cells are not its
|
||||
v = dom_ntype(p);
|
||||
IF (v == T_TD) || (v == T_TH) RETURN 0;
|
||||
p = dom_nparent(p);
|
||||
}
|
||||
IF (!p) RETURN 0;
|
||||
v = dom_nparent(_idx);
|
||||
WHILE (v) {
|
||||
IF (v == p) RETURN i;
|
||||
v = dom_nparent(v);
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// paint and input //
|
||||
//===================================================//
|
||||
|
||||
// Asked by paint.h for every text fragment: a control the user changed is drawn from
|
||||
// its value. Sets the string, its length and the caret column (-1 = no caret).
|
||||
bool form_frag_text(dword _f, _strp, _lenp, _caretp)
|
||||
{
|
||||
dword idx, s, v, len, k;
|
||||
int kind, n, room;
|
||||
DSDWORD[_caretp] = -1;
|
||||
IF (!fv_count) return false;
|
||||
idx = DSDWORD[_f + F_NODE];
|
||||
IF (dom_ntype(idx) != T_INPUT) return false;
|
||||
s = fv_find(idx);
|
||||
IF (!s) return false;
|
||||
len = DSDWORD[_f + F_LEN];
|
||||
IF (len >= FORM_VAL) return false;
|
||||
kind = form_kind(idx);
|
||||
IF (kind != FC_TEXT) && (kind != FC_SECRET) return false;
|
||||
IF (len < 3) return false;
|
||||
n = strlen(s);
|
||||
// an empty field shows its placeholder until it gets the caret
|
||||
IF (!n) && (idx != form_focus) {
|
||||
v = dom_attr(idx, "value");
|
||||
IF (!v) return false;
|
||||
IF (!DSBYTE[v]) return false; // value="": the placeholder as well
|
||||
}
|
||||
// " " + the end of the value, one column left for the caret
|
||||
room = len - 2;
|
||||
IF (n > room) {
|
||||
k = n - room;
|
||||
s += k;
|
||||
n = room;
|
||||
}
|
||||
form_show[0] = ' ';
|
||||
memmov(#form_show + 1, s, n);
|
||||
IF (kind == FC_SECRET) {
|
||||
FOR (k = 1; k <= n; k++) form_show[k] = '*';
|
||||
}
|
||||
FOR (k = n + 1; k < len; k++) form_show[k] = ' ';
|
||||
form_show[len] = 0;
|
||||
DSDWORD[_strp] = #form_show;
|
||||
DSDWORD[_lenp] = len;
|
||||
IF (idx == form_focus) DSDWORD[_caretp] = n + 1;
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
// the frame paint.h draws around a fragment: 0 none, 1 a text field, 2 the field with the caret
|
||||
int form_frag_frame(dword _f)
|
||||
{
|
||||
dword idx;
|
||||
int kind;
|
||||
IF (!dom.nodes) RETURN 0;
|
||||
idx = DSDWORD[_f + F_NODE];
|
||||
IF (dom_ntype(idx) != T_INPUT) RETURN 0;
|
||||
kind = form_kind(idx);
|
||||
IF (kind != FC_TEXT) && (kind != FC_SECRET) RETURN 0;
|
||||
IF (idx == form_focus) RETURN 2;
|
||||
RETURN 1;
|
||||
}
|
||||
|
||||
#define CHECK_FRAME 0x767676 // the frame of an unchecked box
|
||||
#define CHECK_ON 0x0075FF // a checked box, the ring and dot of a chosen radio
|
||||
#define CHECK_OFF_DIS 0xC6C6C6 // the frame of a disabled one
|
||||
|
||||
// a filled circle of diameter _d with its top-left corner at (_x, _y)
|
||||
void cv_disc(int _x, _y, _d, dword _color)
|
||||
{
|
||||
int r, c, a, b, lim;
|
||||
lim = _d * _d;
|
||||
for (r = 0; r < _d; r++) {
|
||||
a = r + r;
|
||||
a += 1;
|
||||
a -= _d;
|
||||
a *= a;
|
||||
FOR (c = 0; c < _d; c++) {
|
||||
b = c + c;
|
||||
b += 1;
|
||||
b -= _d;
|
||||
b *= b;
|
||||
b += a;
|
||||
IF (b <= lim) BREAK;
|
||||
}
|
||||
b = _d - c;
|
||||
b -= c;
|
||||
IF (b > 0) cv_bar(_x + c, _y + r, b, 1, _color);
|
||||
}
|
||||
}
|
||||
|
||||
// the tick of a checked box, 2px thick, in a 13px box at (_x, _y)
|
||||
void cv_tick(int _x, _y, dword _color)
|
||||
{
|
||||
int i, x, y;
|
||||
// squares at (3,6) (4,7) (5,8), then up to (9,4)
|
||||
FOR (i = 0; i < 7; i++) {
|
||||
x = _x + 3;
|
||||
x += i;
|
||||
IF (i < 3) y = i + 6; ELSE y = 10 - i;
|
||||
y += _y;
|
||||
cv_bar(x, y, 2, 2, _color);
|
||||
}
|
||||
}
|
||||
|
||||
// A checkbox or radio fragment (layout.h inline_check) in the state the form holds.
|
||||
// "appearance: none" leaves the drawing to the page: its background.
|
||||
void form_paint_check(dword _f, int _x, _y)
|
||||
{
|
||||
dword idx, node, bg, frame;
|
||||
int w, h, kind;
|
||||
bool on;
|
||||
idx = DSDWORD[_f + F_NODE];
|
||||
w = DSDWORD[_f + F_W];
|
||||
h = DSDWORD[_f + F_H];
|
||||
node = dom_n(idx);
|
||||
IF (DSDWORD[node + S_FLAGS] & SF_NOAPPEAR) {
|
||||
bg = DSDWORD[_f + F_BG];
|
||||
IF (bg == NO_COLOR) return;
|
||||
cv_bar(_x, _y, w, h, bg);
|
||||
return;
|
||||
}
|
||||
kind = form_kind(idx);
|
||||
on = form_is_on(idx);
|
||||
IF (dom_attr(idx, "disabled")) frame = CHECK_OFF_DIS; ELSE frame = CHECK_FRAME;
|
||||
IF (kind == FC_RADIO) {
|
||||
IF (on) {
|
||||
cv_disc(_x, _y, 13, CHECK_ON);
|
||||
cv_disc(_x + 2, _y + 2, 9, 0xFFFFFF);
|
||||
cv_disc(_x + 3, _y + 3, 7, CHECK_ON);
|
||||
RETURN;
|
||||
}
|
||||
cv_disc(_x, _y, 13, frame);
|
||||
cv_disc(_x + 1, _y + 1, 11, 0xFFFFFF);
|
||||
RETURN;
|
||||
}
|
||||
IF (on) {
|
||||
cv_bar(_x, _y, 13, 13, CHECK_ON);
|
||||
cv_tick(_x, _y, 0xFFFFFF);
|
||||
RETURN;
|
||||
}
|
||||
cv_bar(_x, _y, 13, 13, 0xFFFFFF);
|
||||
cv_rect(_x, _y, 13, 13, 1, frame);
|
||||
}
|
||||
|
||||
// the form control under a page point, or 0. Text on top that is not part of a
|
||||
// control hides what is under it; boxes (button padding) do not.
|
||||
dword form_control_at(int _x, _y)
|
||||
{
|
||||
dword f, kind, idx, t;
|
||||
int i, j, k, fx, fy, fw, fh;
|
||||
IF (!dom.nodes) return 0;
|
||||
// the one painted last is on top (layout.h lay_paint_sort)
|
||||
for (j = frags.count - 1; j >= 0; j--) {
|
||||
i = frag_in_paint_order(j);
|
||||
f = frags.f(i);
|
||||
kind = DSDWORD[f + F_KIND];
|
||||
IF (!kind) continue; // cut away by an overflow
|
||||
IF (kind == FR_HLINE) continue;
|
||||
fx = DSDWORD[f + F_X]; fy = DSDWORD[f + F_Y];
|
||||
fw = DSDWORD[f + F_W]; fh = DSDWORD[f + F_H];
|
||||
IF (_x < fx) || (_x >= fx + fw) || (_y < fy) || (_y >= fy + fh) continue;
|
||||
idx = DSDWORD[f + F_NODE];
|
||||
FOR (k = 0; k < 6; k++) { // "<button><span>Go</span></button>"
|
||||
IF (!idx) BREAK;
|
||||
t = dom_ntype(idx);
|
||||
IF (t == T_INPUT) || (t == T_BUTTON) || (t == T_SELECT) || (t == T_TEXTAREA) RETURN idx;
|
||||
// a click on a label is a click on its control, hidden or not
|
||||
// ("Which to choose?" on kolibrios.org, the pictures of a slide show)
|
||||
IF (t == T_LABEL) {
|
||||
t = form_label_target(idx);
|
||||
IF (t) RETURN t;
|
||||
BREAK;
|
||||
}
|
||||
IF (t == T_SUMMARY) RETURN idx; // opens its <details>: form_click
|
||||
IF (t == T_FORM) || (t == T_BODY) || (t == T_A) BREAK;
|
||||
idx = dom_nparent(idx);
|
||||
}
|
||||
IF (kind == FR_TEXT) || (kind == FR_IMG) || (kind == FR_ICON) RETURN 0;
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// the form of control _ctrl has a password field
|
||||
bool form_has_secret(dword _ctrl)
|
||||
{
|
||||
dword form, i;
|
||||
form = form_owner(_ctrl);
|
||||
IF (!form) RETURN false;
|
||||
FOR (i = 1; i < dom.count; i++) {
|
||||
IF (form_kind(i) != FC_SECRET) CONTINUE;
|
||||
IF (form_owner(i) == form) RETURN true;
|
||||
}
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// a pointer (finger) over this control: buttons, checkboxes, radios
|
||||
bool form_is_clickable(dword _idx)
|
||||
{
|
||||
int kind;
|
||||
IF (dom_ntype(_idx) == T_SUMMARY) RETURN true;
|
||||
kind = form_kind(_idx);
|
||||
IF (kind == FC_SUBMIT) || (kind == FC_CHECK) || (kind == FC_RADIO) RETURN true;
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// is there a control under the page point that wants the finger cursor?
|
||||
bool form_pointer_at(int _x, _y)
|
||||
{
|
||||
dword c;
|
||||
c = form_control_at(_x, _y);
|
||||
IF (!c) RETURN false;
|
||||
RETURN form_is_clickable(c);
|
||||
}
|
||||
|
||||
// click on control _idx: FA_NONE, FA_REPAINT, FA_RESTYLE or FA_SUBMIT
|
||||
enum { FA_NONE, FA_REPAINT, FA_SUBMIT, FA_RESTYLE };
|
||||
|
||||
// a click on a <summary> opens or closes its <details> (style.h details_is_open)
|
||||
int details_toggle(dword _sum)
|
||||
{
|
||||
dword d, dn;
|
||||
d = dom_nparent(_sum);
|
||||
IF (!d) RETURN FA_NONE;
|
||||
IF (dom_ntype(d) != T_DETAILS) RETURN FA_NONE;
|
||||
dn = dom_n(d);
|
||||
IF (details_is_open(d)) DSDWORD[dn + N_OPEN] = 2; ELSE DSDWORD[dn + N_OPEN] = 1;
|
||||
RETURN FA_RESTYLE;
|
||||
}
|
||||
|
||||
// a checkbox or radio changed: the page has to be styled again when it asks for ":checked"
|
||||
int form_toggled()
|
||||
{
|
||||
IF (css_uses_checked) RETURN FA_RESTYLE;
|
||||
RETURN FA_REPAINT;
|
||||
}
|
||||
int form_click(dword _idx)
|
||||
{
|
||||
dword s, s2, name, form, nm2, i;
|
||||
int kind;
|
||||
dword had_focus;
|
||||
had_focus = form_focus;
|
||||
form_focus = 0;
|
||||
IF (dom_ntype(_idx) == T_SUMMARY) return details_toggle(_idx);
|
||||
kind = form_kind(_idx);
|
||||
IF (kind == FC_CHECK) || (kind == FC_RADIO) {
|
||||
IF (dom_attr(_idx, "disabled")) return FA_NONE;
|
||||
}
|
||||
IF (kind == FC_TEXT) || (kind == FC_SECRET) {
|
||||
IF (!fv_get(_idx)) return FA_NONE;
|
||||
form_focus = _idx;
|
||||
return FA_REPAINT;
|
||||
}
|
||||
IF (kind == FC_CHECK) {
|
||||
s = fv_get(_idx);
|
||||
IF (!s) return FA_NONE;
|
||||
IF (DSBYTE[s] == '1') DSBYTE[s] = '0'; ELSE DSBYTE[s] = '1';
|
||||
return form_toggled();
|
||||
}
|
||||
if (kind == FC_RADIO) {
|
||||
s = fv_get(_idx);
|
||||
IF (!s) return FA_NONE;
|
||||
DSBYTE[s] = '1';
|
||||
// the other radios of the group in this form go off
|
||||
name = dom_attr(_idx, "name");
|
||||
IF (!name) return form_toggled();
|
||||
form = form_owner(_idx);
|
||||
for (i = 1; i < dom.count; i++) {
|
||||
IF (i == _idx) CONTINUE;
|
||||
IF (form_kind(i) != FC_RADIO) CONTINUE;
|
||||
nm2 = dom_attr(i, "name");
|
||||
IF (!nm2) CONTINUE;
|
||||
IF (!streq(nm2, name)) CONTINUE;
|
||||
IF (form_owner(i) != form) CONTINUE;
|
||||
// no slot per radio (a poll would use up FORM_FIELDS): only one that is on
|
||||
// - a stored value, or checked in the markup - has to be turned off
|
||||
s2 = fv_find(i);
|
||||
IF (!s2) && (dom_attr(i, "checked")) s2 = fv_get(i);
|
||||
IF (s2) DSBYTE[s2] = '0';
|
||||
}
|
||||
RETURN form_toggled();
|
||||
}
|
||||
IF (kind == FC_SUBMIT) RETURN FA_SUBMIT;
|
||||
IF (had_focus) RETURN FA_REPAINT;
|
||||
RETURN FA_NONE;
|
||||
}
|
||||
|
||||
// a key for the focused field; false when it is not the field's (the browser takes it)
|
||||
bool form_key(dword _ascii, _scancode)
|
||||
{
|
||||
dword s;
|
||||
int n;
|
||||
s = fv_find(form_focus);
|
||||
IF (!s) RETURN false;
|
||||
n = strlen(s);
|
||||
IF (_scancode == SCAN_CODE_BS) {
|
||||
IF (n) DSBYTE[s + n - 1] = 0;
|
||||
RETURN true;
|
||||
}
|
||||
IF (_ascii < 32) || (_ascii == 127) RETURN false;
|
||||
IF (n >= FORM_VAL - 1) RETURN true;
|
||||
DSBYTE[s + n] = _ascii;
|
||||
DSBYTE[s + n + 1] = 0;
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
// append text (cp866) to the focused field: a paste. Line breaks become spaces.
|
||||
void form_insert(dword _text)
|
||||
{
|
||||
dword s, p;
|
||||
int n;
|
||||
s = fv_find(form_focus);
|
||||
IF (!s) RETURN;
|
||||
n = strlen(s);
|
||||
p = _text;
|
||||
WHILE (DSBYTE[p]) && (n < FORM_VAL - 1) {
|
||||
IF (DSBYTE[p] == 13) { p++; CONTINUE; }
|
||||
IF (DSBYTE[p] == 10) || (DSBYTE[p] == 9) DSBYTE[s + n] = ' '; ELSE DSBYTE[s + n] = DSBYTE[p];
|
||||
n++;
|
||||
p++;
|
||||
}
|
||||
DSBYTE[s + n] = 0;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// building the query //
|
||||
//===================================================//
|
||||
|
||||
char fq_hex[] = "0123456789ABCDEF";
|
||||
/* replaced by iconv.obj in fq_high()
|
||||
// KOI8-R codes of the lowercase letters a..ya in alphabet order (uppercase: +32)
|
||||
char fq_koi[] = "\193\194\215\199\196\197\214\218\201\202\203\204\205\206\207\208\210\211\212\213\198\200\195\222\219\221\223\217\216\220\192\209";
|
||||
*/
|
||||
dword fq_end, fq_limit;
|
||||
dword fq_cd; // iconv descriptor cp866 -> page encoding, -1 when none
|
||||
char fq_src[4];
|
||||
char fq_dst[8];
|
||||
int fq_enc;
|
||||
bool fq_first;
|
||||
|
||||
void fq_put(dword _c)
|
||||
{
|
||||
IF (fq_end >= fq_limit) RETURN;
|
||||
DSBYTE[fq_end] = _c;
|
||||
fq_end++;
|
||||
}
|
||||
|
||||
// one byte percent-encoded
|
||||
void fq_hexbyte(dword _c)
|
||||
{
|
||||
dword h, l;
|
||||
IF (fq_end + 3 > fq_limit) RETURN;
|
||||
_c &= 0xFF;
|
||||
h = _c / 16;
|
||||
l = _c & 15;
|
||||
DSBYTE[fq_end] = '%';
|
||||
DSBYTE[fq_end + 1] = fq_hex[h];
|
||||
DSBYTE[fq_end + 2] = fq_hex[l];
|
||||
fq_end += 3;
|
||||
}
|
||||
|
||||
// a cp866 byte from 0x80 up in the page encoding, percent-encoded.
|
||||
// One character per iconv call: a character the page encoding lacks
|
||||
// becomes '?' instead of cutting the rest of the field
|
||||
void fq_high(dword _c)
|
||||
{
|
||||
dword inp, in_len, outp, out_len, n, i, p;
|
||||
IF (fq_enc == CH_CP866) { fq_hexbyte(_c); return; }
|
||||
IF (fq_cd == -1) { fq_put('?'); return; }
|
||||
DSBYTE[#fq_src] = _c;
|
||||
inp = #fq_src;
|
||||
in_len = 1;
|
||||
outp = #fq_dst;
|
||||
out_len = 8;
|
||||
iconv cdecl (fq_cd, #inp, #in_len, #outp, #out_len);
|
||||
IF (EAX != 0) { fq_put('?'); RETURN; }
|
||||
// Kolibri iconv does not move outp: only out_len tells how much was written,
|
||||
// and a character missing in the page encoding is skipped (0 bytes)
|
||||
n = 8 - out_len;
|
||||
IF (!n) { fq_put('?'); RETURN; }
|
||||
FOR (i = 0; i < n; i++) {
|
||||
p = #fq_dst + i;
|
||||
fq_hexbyte(DSBYTE[p]);
|
||||
}
|
||||
}
|
||||
|
||||
/* replaced by iconv.obj in fq_high() above
|
||||
// Unicode code point of a cp866 byte from 0x80 up ('?' for the pseudographics)
|
||||
dword cp866_uni(dword _c)
|
||||
{
|
||||
if (_c >= 0x80) && (_c <= 0xAF) return _c + 0x390; // А..п
|
||||
if (_c >= 0xE0) && (_c <= 0xEF) return _c + 0x360; // р..я
|
||||
switch (_c) {
|
||||
case 0xF0: return 0x401;
|
||||
case 0xF1: return 0x451;
|
||||
case 0xF2: return 0x404;
|
||||
case 0xF3: return 0x454;
|
||||
case 0xF4: return 0x407;
|
||||
case 0xF5: return 0x457;
|
||||
case 0xF6: return 0x40E;
|
||||
case 0xF7: return 0x45E;
|
||||
case 0xF8: return 0xB0;
|
||||
case 0xFA: return 0xB7;
|
||||
case 0xFC: return 0x2116;
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
|
||||
// a cp866 byte from 0x80 up in the page encoding, percent-encoded
|
||||
void fq_high(dword _c)
|
||||
{
|
||||
dword u, b, L;
|
||||
if (fq_enc == CH_CP866) { fq_hexbyte(_c); return; }
|
||||
if (fq_enc == CH_UTF8) {
|
||||
u = cp866_uni(_c);
|
||||
if (u < 0x80) { fq_hexbyte(u); return; }
|
||||
if (u < 0x800) {
|
||||
b = u / 64; b |= 0xC0; fq_hexbyte(b);
|
||||
b = u & 63; b |= 0x80; fq_hexbyte(b);
|
||||
return;
|
||||
}
|
||||
b = u / 4096; b |= 0xE0; fq_hexbyte(b);
|
||||
b = u / 64; b &= 63; b |= 0x80; fq_hexbyte(b);
|
||||
b = u & 63; b |= 0x80; fq_hexbyte(b);
|
||||
return;
|
||||
}
|
||||
if (fq_enc == CH_CP1251) {
|
||||
if (_c >= 0x80) && (_c <= 0xAF) { fq_hexbyte(_c + 0x40); return; }
|
||||
if (_c >= 0xE0) && (_c <= 0xEF) { fq_hexbyte(_c + 0x10); return; }
|
||||
if (_c == 0xF0) { fq_hexbyte(0xA8); return; }
|
||||
if (_c == 0xF1) { fq_hexbyte(0xB8); return; }
|
||||
if (_c == 0xFC) { fq_hexbyte(0xB9); return; }
|
||||
fq_put('?');
|
||||
return;
|
||||
}
|
||||
if (fq_enc == CH_ISO8859_5) {
|
||||
if (_c >= 0x80) && (_c <= 0xAF) { fq_hexbyte(_c + 0x30); return; }
|
||||
if (_c >= 0xE0) && (_c <= 0xEF) { fq_hexbyte(_c); return; }
|
||||
if (_c == 0xF0) { fq_hexbyte(0xA1); return; }
|
||||
if (_c == 0xF1) { fq_hexbyte(0xF1); return; }
|
||||
fq_put('?');
|
||||
return;
|
||||
}
|
||||
if (fq_enc == CH_KOI8) {
|
||||
if (_c == 0xF0) { fq_hexbyte(0xB3); return; }
|
||||
if (_c == 0xF1) { fq_hexbyte(0xA3); return; }
|
||||
// letter number 0..31 and case
|
||||
if (_c >= 0x80) && (_c <= 0x9F) { L = _c - 0x80; b = fq_koi[L]; b += 32; fq_hexbyte(b); return; }
|
||||
if (_c >= 0xA0) && (_c <= 0xAF) { L = _c - 0xA0; b = fq_koi[L]; fq_hexbyte(b); return; }
|
||||
if (_c >= 0xE0) && (_c <= 0xEF) { L = _c - 0xD0; b = fq_koi[L]; fq_hexbyte(b); return; }
|
||||
}
|
||||
fq_put('?');
|
||||
}
|
||||
*/
|
||||
|
||||
// application/x-www-form-urlencoded: space is '+', letters, digits and "*-._" as is
|
||||
void fq_text(dword _s)
|
||||
{
|
||||
dword c;
|
||||
while (DSBYTE[_s]) {
|
||||
c = DSBYTE[_s];
|
||||
_s++;
|
||||
IF (c == ' ') || (c == 0xFF) { fq_put('+'); continue; }
|
||||
IF (c >= 0x80) { fq_high(c); continue; }
|
||||
IF (c >= 'a') && (c <= 'z') { fq_put(c); CONTINUE; }
|
||||
IF (c >= 'A') && (c <= 'Z') { fq_put(c); CONTINUE; }
|
||||
IF (c >= '0') && (c <= '9') { fq_put(c); CONTINUE; }
|
||||
IF (c == '*') || (c == '-') || (c == '.') || (c == '_') { fq_put(c); CONTINUE; }
|
||||
IF (c == 10) && (fq_end + 3 <= fq_limit) { fq_hexbyte(13); } // line breaks go as CR LF
|
||||
IF (c == 13) CONTINUE;
|
||||
fq_hexbyte(c);
|
||||
}
|
||||
}
|
||||
|
||||
void fq_pair(dword _name, _value)
|
||||
{
|
||||
IF (!fq_first) fq_put('&');
|
||||
fq_first = false;
|
||||
fq_text(_name);
|
||||
fq_put('=');
|
||||
fq_text(_value);
|
||||
}
|
||||
|
||||
// text of an element's text children (an <option>, a <textarea>) into _buf
|
||||
dword node_text(dword _idx, _buf, _size)
|
||||
{
|
||||
dword c, p, n, d;
|
||||
int room;
|
||||
n = 0;
|
||||
DSBYTE[_buf] = 0;
|
||||
c = dom_nfirst(_idx);
|
||||
WHILE (c) {
|
||||
IF (dom_ntype(c) == T_TEXT) {
|
||||
p = dom_ntext(c);
|
||||
room = _size - n;
|
||||
room--;
|
||||
IF (room < 1) BREAK;
|
||||
d = _buf + n;
|
||||
strlcpy(d, p, room);
|
||||
n = strlen(_buf);
|
||||
}
|
||||
c = dom_nnext(c);
|
||||
}
|
||||
RETURN _buf;
|
||||
}
|
||||
|
||||
// the button Enter in a field stands for: the first submit button of the form
|
||||
dword form_first_submit(dword _form)
|
||||
{
|
||||
dword i;
|
||||
FOR (i = 1; i < dom.count; i++) {
|
||||
IF (form_kind(i) != FC_SUBMIT) CONTINUE; // only <input> and <button> are
|
||||
IF (form_owner(i) == _form) RETURN i;
|
||||
}
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
char fq_val[1024];
|
||||
|
||||
// does this form go by POST? (formmethod of the pressed button wins)
|
||||
bool method_is_post(dword _form, _submitter);
|
||||
|
||||
// an attribute of the pressed button (0 when there is none)
|
||||
dword submitter_attr(dword _sub, _name)
|
||||
{
|
||||
IF (!_sub) RETURN 0;
|
||||
RETURN dom_attr(_sub, _name);
|
||||
}
|
||||
|
||||
// add control _i (owned by the form being sent) to the query
|
||||
void form_add_control(dword _i, _submitter)
|
||||
{
|
||||
dword name, v, s, opt;
|
||||
int kind;
|
||||
IF (dom_attr(_i, "disabled")) return;
|
||||
name = dom_attr(_i, "name");
|
||||
IF (!name) return;
|
||||
IF (!DSBYTE[name]) return;
|
||||
kind = form_kind(_i);
|
||||
switch (kind) {
|
||||
CASE FC_TEXT:
|
||||
CASE FC_SECRET:
|
||||
CASE FC_HIDDEN:
|
||||
s = fv_find(_i);
|
||||
IF (s) { fq_pair(name, s); return; }
|
||||
v = dom_attr(_i, "value");
|
||||
IF (!v) v = "";
|
||||
fq_pair(name, v);
|
||||
return;
|
||||
CASE FC_CHECK:
|
||||
CASE FC_RADIO:
|
||||
s = fv_find(_i);
|
||||
IF (s) {
|
||||
IF (DSBYTE[s] != '1') return;
|
||||
} ELSE {
|
||||
IF (!dom_attr(_i, "checked")) return;
|
||||
}
|
||||
v = dom_attr(_i, "value");
|
||||
IF (!v) v = "on";
|
||||
fq_pair(name, v);
|
||||
return;
|
||||
case FC_SUBMIT:
|
||||
IF (_i != _submitter) return;
|
||||
v = dom_attr(_i, "value");
|
||||
IF (!v) v = "";
|
||||
fq_pair(name, v);
|
||||
return;
|
||||
case FC_SELECT:
|
||||
IF (dom_attr(_i, "multiple")) RETURN;
|
||||
opt = select_option(_i);
|
||||
IF (!opt) RETURN;
|
||||
v = dom_attr(opt, "value");
|
||||
IF (!v) {
|
||||
node_text(opt, #fq_val, sizeof(fq_val));
|
||||
v = css_skip_sp(#fq_val);
|
||||
strrtrim(v);
|
||||
}
|
||||
fq_pair(name, v);
|
||||
RETURN;
|
||||
case FC_AREA:
|
||||
node_text(_i, #fq_val, sizeof(fq_val));
|
||||
fq_pair(name, #fq_val);
|
||||
RETURN;
|
||||
}
|
||||
}
|
||||
|
||||
bool method_is_post(dword _form, _submitter)
|
||||
{
|
||||
char m[16];
|
||||
dword v;
|
||||
v = submitter_attr(_submitter, "formmethod");
|
||||
IF (v) attr_lower(_submitter, "formmethod", #m); ELSE attr_lower(_form, "method", #m);
|
||||
IF (streq(#m, "post")) RETURN true;
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
// The form of control _ctrl, ready to send: the address into _out (URL_SIZE+1 bytes)
|
||||
// and, for a POST form, the body into _body (FORM_POST_MAX bytes; empty otherwise).
|
||||
// _submitter: the button pressed, 0 for Enter in a field.
|
||||
// Returns FS_OK (send _out by GET), FS_POST (send the body to _out) or FS_NONE.
|
||||
int form_submit_url(dword _ctrl, _submitter, _out, _body)
|
||||
{
|
||||
dword form, v, p, i, t;
|
||||
bool post;
|
||||
DSBYTE[_body] = 0;
|
||||
form = form_owner(_ctrl);
|
||||
IF (!form) return FS_NONE;
|
||||
IF (!_submitter) _submitter = form_first_submit(form);
|
||||
post = method_is_post(form, _submitter);
|
||||
|
||||
v = submitter_attr(_submitter, "formaction");
|
||||
IF (!v) v = dom_attr(form, "action");
|
||||
IF (v) && (DSBYTE[v]) {
|
||||
strlcpy(_out, v, URL_SIZE - 1);
|
||||
p = page_base();
|
||||
get_absolute_url(_out, p);
|
||||
} ELSE {
|
||||
p = history.current();
|
||||
strlcpy(_out, p, URL_SIZE - 1);
|
||||
}
|
||||
p = strchr(_out, '#');
|
||||
IF (p) DSBYTE[p] = 0;
|
||||
IF (post) {
|
||||
// the body is sent separately: the action keeps its own query
|
||||
fq_end = _body;
|
||||
fq_limit = _body + FORM_POST_MAX - 1;
|
||||
} ELSE {
|
||||
// a GET form replaces the query of the action
|
||||
p = strchr(_out, '?');
|
||||
IF (p) DSBYTE[p] = 0;
|
||||
fq_end = strlen(_out);
|
||||
fq_end += _out;
|
||||
fq_limit = _out + URL_SIZE - 1;
|
||||
}
|
||||
fq_enc = WB1.cur_encoding;
|
||||
fq_cd = -1;
|
||||
IF (fq_enc != CH_CP866) {
|
||||
// one expression: after "p = const; p += x;" c-- pushed the stale const
|
||||
p = fq_enc * 10 + #charsets;
|
||||
iconv_open cdecl (p, CH_CP866*10+#charsets);
|
||||
fq_cd = EAX;
|
||||
}
|
||||
fq_first = true;
|
||||
IF (!post) fq_put('?');
|
||||
FOR (i = 1; i < dom.count; i++) {
|
||||
t = dom_ntype(i);
|
||||
IF (t != T_INPUT) && (t != T_BUTTON) && (t != T_SELECT) && (t != T_TEXTAREA) CONTINUE;
|
||||
IF (form_owner(i) != form) CONTINUE;
|
||||
form_add_control(i, _submitter);
|
||||
}
|
||||
DSBYTE[fq_end] = 0;
|
||||
IF (post) RETURN FS_POST;
|
||||
RETURN FS_OK;
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
// Interaction over the fragment list: hit-testing, link hover, text selection,
|
||||
// copy, find-in-page. All coordinates here are page coordinates.
|
||||
|
||||
//===================================================//
|
||||
// hit testing //
|
||||
//===================================================//
|
||||
|
||||
// topmost text/image/icon fragment at page point, or -1
|
||||
signed frag_at(int _x, _y)
|
||||
{
|
||||
dword f, kind;
|
||||
int i, k, fx, fy, fw, fh;
|
||||
IF (!dom.nodes) return -1; // no document (a fresh tab waiting for its page)
|
||||
// the one painted last is on top (layout.h lay_paint_sort)
|
||||
for (k = frags.count - 1; k >= 0; k--) {
|
||||
i = frag_in_paint_order(k);
|
||||
f = frags.f(i);
|
||||
kind = DSDWORD[f + F_KIND];
|
||||
IF (kind != FR_TEXT) && (kind != FR_IMG) && (kind != FR_ICON) CONTINUE;
|
||||
IF (DSDWORD[f + F_FLAGS] & SF_INVIS) CONTINUE;
|
||||
fx = DSDWORD[f + F_X]; fy = DSDWORD[f + F_Y];
|
||||
fw = DSDWORD[f + F_W]; fh = DSDWORD[f + F_H];
|
||||
IF (_x >= fx) && (_x < fx + fw) && (_y >= fy) && (_y < fy + fh) RETURN i;
|
||||
}
|
||||
RETURN -1;
|
||||
}
|
||||
|
||||
// href (or src for iframe) of a link node
|
||||
dword link_href(dword _node)
|
||||
{
|
||||
dword v;
|
||||
IF (!_node) RETURN 0;
|
||||
v = dom_attr(_node, "href");
|
||||
IF (!v) v = dom_attr(_node, "src");
|
||||
RETURN v;
|
||||
}
|
||||
|
||||
// <img> node under the point or 0
|
||||
dword image_at(int _x, _y)
|
||||
{
|
||||
signed i = frag_at(_x, _y);
|
||||
dword f;
|
||||
IF (i == -1) RETURN 0;
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_IMG) RETURN 0;
|
||||
RETURN DSDWORD[f + F_NODE];
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// links //
|
||||
//===================================================//
|
||||
|
||||
CustomCursor CursorPointer;
|
||||
dword CursorFile = FROM "engine/pointer.cur";
|
||||
dword cursor_finger; // the kernel handle of CursorFile, 0 = not loaded yet
|
||||
|
||||
// show the finger cursor. It is loaded once and kept: CursorPointer.Restore() forgets
|
||||
// the handle without deleting the cursor, and a Load() per hovered link or button made
|
||||
// a new kernel cursor every time, freed only when WebView exits
|
||||
void finger_show()
|
||||
{
|
||||
IF (!cursor_finger) {
|
||||
CursorPointer.Load(#CursorFile);
|
||||
cursor_finger = CursorPointer.CursorPointer;
|
||||
}
|
||||
CursorPointer.CursorPointer = cursor_finger;
|
||||
CursorPointer.Set();
|
||||
}
|
||||
|
||||
struct PAGE_LINKS {
|
||||
dword active_url; // href of the hovered link (raw, may be relative)
|
||||
dword active_node;
|
||||
bool hover();
|
||||
void clear();
|
||||
} links;
|
||||
|
||||
void PAGE_LINKS::clear()
|
||||
{
|
||||
active_url = 0;
|
||||
active_node = 0;
|
||||
hover_link = 0;
|
||||
CursorPointer.Restore();
|
||||
}
|
||||
|
||||
// called on mouse move over the page; returns true when a link is under the pointer.
|
||||
// _px/_py: page coordinates. Repaints when the hovered link changes.
|
||||
bool PAGE_LINKS::hover(int _px, _py)
|
||||
{
|
||||
signed i = frag_at(_px, _py);
|
||||
dword f, lnk = 0;
|
||||
IF (i != -1) {
|
||||
f = frags.f(i);
|
||||
lnk = DSDWORD[f + F_LINK];
|
||||
}
|
||||
IF (lnk == active_node) { IF (lnk) RETURN true; RETURN false; }
|
||||
active_node = lnk;
|
||||
hover_link = lnk;
|
||||
IF (lnk) {
|
||||
active_url = link_href(lnk);
|
||||
finger_show();
|
||||
DrawHoverStatus(active_url);
|
||||
} ELSE {
|
||||
active_url = 0;
|
||||
CursorPointer.Restore();
|
||||
DrawHoverStatus(NULL);
|
||||
}
|
||||
engine_repaint();
|
||||
IF (lnk) RETURN true;
|
||||
RETURN false;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// selection //
|
||||
//===================================================//
|
||||
|
||||
dword sel_anchor_f, sel_anchor_c, sel_focus_f, sel_focus_c;
|
||||
bool sel_dragging;
|
||||
|
||||
// nearest text position to a page point: fragment index and character
|
||||
void sel_locate(int _x, _y, dword _fp, _cp)
|
||||
{
|
||||
dword f, best = -1, bestc = 0, before = -1, first = -1;
|
||||
int i, fx, fy, fw, fh, cw, c, d, bestd = 0x7FFFFFFF;
|
||||
for (i = 0; i < frags.count; i++) {
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_TEXT) continue;
|
||||
fx = DSDWORD[f + F_X]; fy = DSDWORD[f + F_Y];
|
||||
fw = DSDWORD[f + F_W]; fh = DSDWORD[f + F_H];
|
||||
IF (first == -1) first = i;
|
||||
IF (fy + fh <= _y) before = i;
|
||||
IF (_y < fy) || (_y >= fy + fh) continue;
|
||||
// on this line
|
||||
cw = font_w(DSDWORD[f + F_FONT]);
|
||||
IF (_x < fx) d = fx - _x; ELSE IF (_x >= fx + fw) d = _x - fx - fw + 1; ELSE d = 0;
|
||||
IF (d < bestd) {
|
||||
bestd = d; best = i;
|
||||
IF (_x < fx) bestc = 0;
|
||||
ELSE IF (_x >= fx + fw) bestc = DSDWORD[f + F_LEN];
|
||||
ELSE { c = _x - fx; c += cw / 2; c /= cw; bestc = c; }
|
||||
}
|
||||
}
|
||||
IF (best == -1) {
|
||||
IF (before != -1) { best = before; f = frags.f(best); bestc = DSDWORD[f + F_LEN]; }
|
||||
ELSE IF (first != -1) { best = first; bestc = 0; }
|
||||
}
|
||||
DSDWORD[_fp] = best;
|
||||
DSDWORD[_cp] = bestc;
|
||||
}
|
||||
|
||||
void sel_normalize()
|
||||
{
|
||||
IF (sel_anchor_f == -1) || (sel_focus_f == -1) { sel_active = false; RETURN; }
|
||||
IF (sel_anchor_f < sel_focus_f) || (sel_anchor_f == sel_focus_f) && (sel_anchor_c <= sel_focus_c) {
|
||||
sel_f1 = sel_anchor_f; sel_c1 = sel_anchor_c; sel_f2 = sel_focus_f; sel_c2 = sel_focus_c;
|
||||
} ELSE {
|
||||
sel_f1 = sel_focus_f; sel_c1 = sel_focus_c; sel_f2 = sel_anchor_f; sel_c2 = sel_anchor_c;
|
||||
}
|
||||
sel_active = true;
|
||||
IF (sel_f1 == sel_f2) && (sel_c1 == sel_c2) sel_active = false;
|
||||
}
|
||||
|
||||
void sel_clear()
|
||||
{
|
||||
bool was = sel_active;
|
||||
sel_active = false;
|
||||
sel_dragging = false;
|
||||
IF (was) engine_repaint();
|
||||
}
|
||||
|
||||
void sel_begin(int _x, _y)
|
||||
{
|
||||
sel_locate(_x, _y, #sel_anchor_f, #sel_anchor_c);
|
||||
sel_focus_f = sel_anchor_f; sel_focus_c = sel_anchor_c;
|
||||
sel_dragging = true;
|
||||
IF (sel_active) { sel_active = false; engine_repaint(); }
|
||||
}
|
||||
|
||||
void sel_update(int _x, _y)
|
||||
{
|
||||
dword of = sel_focus_f, oc = sel_focus_c;
|
||||
IF (!sel_dragging) RETURN;
|
||||
sel_locate(_x, _y, #sel_focus_f, #sel_focus_c);
|
||||
IF (of == sel_focus_f) && (oc == sel_focus_c) RETURN;
|
||||
sel_normalize();
|
||||
engine_repaint();
|
||||
}
|
||||
|
||||
void sel_end()
|
||||
{
|
||||
sel_dragging = false;
|
||||
}
|
||||
|
||||
void sel_all()
|
||||
{
|
||||
dword f;
|
||||
int i, last = -1, first = -1;
|
||||
FOR (i = 0; i < frags.count; i++) {
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_TEXT) CONTINUE;
|
||||
IF (first == -1) first = i;
|
||||
last = i;
|
||||
}
|
||||
IF (first == -1) RETURN;
|
||||
sel_anchor_f = first; sel_anchor_c = 0;
|
||||
sel_focus_f = last; f = frags.f(last); sel_focus_c = DSDWORD[f + F_LEN];
|
||||
sel_normalize();
|
||||
engine_repaint();
|
||||
}
|
||||
|
||||
// text of the selection into a malloc'ed buffer (caller frees); 0 if none
|
||||
dword sel_text()
|
||||
{
|
||||
dword f, buf, p, s;
|
||||
dword size = 0, c1, c2, line = -1;
|
||||
int i;
|
||||
IF (!sel_active) RETURN 0;
|
||||
FOR (i = sel_f1; i <= sel_f2; i++) {
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_TEXT) CONTINUE;
|
||||
size += DSDWORD[f + F_LEN] + 2;
|
||||
}
|
||||
buf = malloc(size + 1);
|
||||
p = buf;
|
||||
for (i = sel_f1; i <= sel_f2; i++) {
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_TEXT) CONTINUE;
|
||||
IF (!sel_range(i, DSDWORD[f + F_LEN], #c1, #c2)) CONTINUE;
|
||||
IF (line != -1) && (DSDWORD[f + F_LINE] != line) { DSBYTE[p] = 10; p++; }
|
||||
line = DSDWORD[f + F_LINE];
|
||||
s = dpool.get(DSDWORD[f + F_TEXT]);
|
||||
memmov(p, s + c1, c2 - c1);
|
||||
p += c2 - c1;
|
||||
}
|
||||
DSBYTE[p] = 0;
|
||||
// (CP866 0xFF) goes out as a plain space
|
||||
FOR (p = buf; DSBYTE[p]; p++) IF (DSBYTE[p] == 0xFF) DSBYTE[p] = ' ';
|
||||
RETURN buf;
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// find in page //
|
||||
//===================================================//
|
||||
|
||||
dword page_text; // lowercase text of all runs, '\n' between lines
|
||||
dword page_text_len;
|
||||
int find_matches;
|
||||
|
||||
byte lower_cp866(byte c)
|
||||
{
|
||||
IF (c >= 'A') && (c <= 'Z') RETURN c + 32;
|
||||
IF (c >= 0x80) && (c <= 0x8F) RETURN c + 32; // А-П -> а-п
|
||||
IF (c >= 0x90) && (c <= 0x9F) RETURN c + 80; // Р-Я -> р-я
|
||||
IF (c == 0xF0) RETURN 0xF1; // Ё -> ё
|
||||
RETURN c;
|
||||
}
|
||||
|
||||
// build page_text and F_TOFF after every layout
|
||||
void page_text_build()
|
||||
{
|
||||
dword f, s, line = -1, k;
|
||||
int i;
|
||||
byte c;
|
||||
page_text_len = 0;
|
||||
FOR (i = 0; i < frags.count; i++) {
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_TEXT) CONTINUE;
|
||||
page_text_len += DSDWORD[f + F_LEN] + 1;
|
||||
}
|
||||
free(page_text);
|
||||
page_text = malloc(page_text_len + 1);
|
||||
page_text_len = 0;
|
||||
for (i = 0; i < frags.count; i++) {
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_TEXT) continue;
|
||||
IF (line != -1) && (DSDWORD[f + F_LINE] != line) { DSBYTE[page_text + page_text_len] = 10; page_text_len++; }
|
||||
line = DSDWORD[f + F_LINE];
|
||||
DSDWORD[f + F_TOFF] = page_text_len;
|
||||
s = dpool.get(DSDWORD[f + F_TEXT]);
|
||||
FOR (k = 0; k < DSDWORD[f + F_LEN]; k++) {
|
||||
c = DSBYTE[s + k];
|
||||
// matches a typed space. Not "if (c == 0xFF) c = ' ';" before the call:
|
||||
// c-- then passed the stale 0xFF still in EAX to lower_cp866()
|
||||
IF (c == 0xFF) DSBYTE[page_text + page_text_len] = ' ';
|
||||
ELSE DSBYTE[page_text + page_text_len] = lower_cp866(c);
|
||||
page_text_len++;
|
||||
}
|
||||
}
|
||||
DSBYTE[page_text + page_text_len] = 0;
|
||||
}
|
||||
|
||||
void find_clear()
|
||||
{
|
||||
hl_frag.drop(); hl_start.drop(); hl_len.drop(); hl_match.drop();
|
||||
find_matches = 0;
|
||||
find_current = -1;
|
||||
}
|
||||
|
||||
// map a match [off, off+len) in page_text to highlight entries
|
||||
void find_add_match(dword _off, _len, _m)
|
||||
{
|
||||
dword f, toff, flen, a, b;
|
||||
int i;
|
||||
for (i = 0; i < frags.count; i++) {
|
||||
f = frags.f(i);
|
||||
IF (DSDWORD[f + F_KIND] != FR_TEXT) continue;
|
||||
toff = DSDWORD[f + F_TOFF];
|
||||
flen = DSDWORD[f + F_LEN];
|
||||
IF (toff + flen <= _off) CONTINUE;
|
||||
IF (toff >= _off + _len) BREAK;
|
||||
a = _off; IF (a < toff) a = toff;
|
||||
b = _off + _len; IF (b > toff + flen) b = toff + flen;
|
||||
IF (b <= a) CONTINUE;
|
||||
hl_frag.add(i); hl_start.add(a - toff); hl_len.add(b - a); hl_match.add(_m);
|
||||
}
|
||||
}
|
||||
|
||||
// search; returns number of matches. Highlights are ready for painting.
|
||||
int find_run(dword _query)
|
||||
{
|
||||
dword p, qlen, i;
|
||||
char q[128];
|
||||
find_clear();
|
||||
IF (!page_text) return 0;
|
||||
strncpy(#q, _query, sizeof(q) - 1); q[sizeof(q) - 1] = 0;
|
||||
qlen = strlen(#q);
|
||||
IF (!qlen) return 0;
|
||||
FOR (i = 0; i < qlen; i++) q[i] = lower_cp866(q[i]);
|
||||
p = page_text;
|
||||
WHILE (p = strstr(p, #q)) {
|
||||
find_add_match(p - page_text, qlen, find_matches);
|
||||
find_matches++;
|
||||
IF (find_matches >= 500) BREAK;
|
||||
p++;
|
||||
}
|
||||
IF (find_matches) find_current = 0;
|
||||
RETURN find_matches;
|
||||
}
|
||||
|
||||
// page y of the current match (or -1)
|
||||
int find_current_y()
|
||||
{
|
||||
int i;
|
||||
dword f;
|
||||
IF (find_current < 0) RETURN -1;
|
||||
FOR (i = 0; i < hl_frag.count; i++) {
|
||||
IF (hl_match.get(i) == find_current) {
|
||||
f = frags.f(hl_frag.get(i));
|
||||
RETURN DSDWORD[f + F_Y];
|
||||
}
|
||||
}
|
||||
RETURN -1;
|
||||
}
|
||||
|
||||
void find_next(int _dir)
|
||||
{
|
||||
IF (!find_matches) RETURN;
|
||||
find_current += _dir;
|
||||
IF (find_current >= find_matches) find_current = 0;
|
||||
IF (find_current < 0) find_current = find_matches - 1;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,877 +0,0 @@
|
||||
// Paint: draws the fragments that intersect the viewport into a viewport-sized canvas.
|
||||
// The canvas has PAINT_MARGIN extra rows above and below the visible area so that
|
||||
// text runs cut by the viewport edge are still drawn by the kernel at a valid y.
|
||||
|
||||
#define PAINT_MARGIN 40
|
||||
#define SEL_BG 0xB4D5FE
|
||||
#define FIND_BG 0xFFF176
|
||||
#define FIND_CUR_BG 0xFFB74D
|
||||
|
||||
CANVAS canvas;
|
||||
|
||||
dword hover_link; // <a> node under the mouse (painted in the active link colour)
|
||||
dword page_h; // document height in px (result of the last layout)
|
||||
|
||||
// selection: ordered range [f1:c1, f2:c2] over text fragments
|
||||
bool sel_active;
|
||||
dword sel_f1, sel_c1, sel_f2, sel_c2;
|
||||
|
||||
// find highlights: parallel arrays (fragment, first char, char count, match number)
|
||||
collection_int hl_frag, hl_start, hl_len, hl_match;
|
||||
int find_current = -1;
|
||||
|
||||
// libimg Image structure used as the blend target (canvas)
|
||||
char canvas_img[44];
|
||||
|
||||
// ---- clipped primitives ---------------------------------------------------------
|
||||
// CANVAS (draw_buf.h) grows its buffer by 50 MB when a bar reaches the last row
|
||||
// (y + h >= bufh) or text comes within 30 rows of it: a leftover of the old engine
|
||||
// that painted whole pages. Our canvas is viewport-sized with PAINT_MARGIN rows of
|
||||
// slack below the visible area, so those rows can simply be left alone.
|
||||
|
||||
void cv_bar(int _x, _y, _w, _h, dword _color)
|
||||
{
|
||||
int x2 = _x + _w, y2 = _y + _h;
|
||||
IF (_x < 0) _x = 0;
|
||||
IF (_y < 0) _y = 0;
|
||||
IF (x2 > canvas.bufw) x2 = canvas.bufw;
|
||||
IF (y2 >= canvas.bufh) y2 = canvas.bufh - 1;
|
||||
IF (x2 <= _x) || (y2 <= _y) RETURN;
|
||||
// opaque, as CANVAS::Fill makes it: img_blend weighs the picture's alpha against
|
||||
// the canvas one, over alpha 0 a half-transparent edge came out solid (the jagged
|
||||
// hummingbird of kolibrios.org on its white box)
|
||||
_color |= 0xFF000000;
|
||||
canvas.DrawBar(_x, _y, x2 - _x, y2 - _y, _color);
|
||||
}
|
||||
|
||||
// a bar of the color _argb (alpha << 24 + RGB) mixed into what the canvas holds
|
||||
void cv_blend_bar(int _x, _y, _w, _h, dword _argb)
|
||||
{
|
||||
int x2, y2, y;
|
||||
dword a, na, p, e, row, n, stride, d, s, v, k, sr, sg, sb;
|
||||
x2 = _x + _w;
|
||||
y2 = _y + _h;
|
||||
IF (_x < 0) _x = 0;
|
||||
IF (_y < 0) _y = 0;
|
||||
IF (x2 > canvas.bufw) x2 = canvas.bufw;
|
||||
IF (y2 >= canvas.bufh) y2 = canvas.bufh - 1;
|
||||
IF (x2 <= _x) || (y2 <= _y) return;
|
||||
a = _argb >> 24;
|
||||
na = 255 - a;
|
||||
sr = _argb >> 16; sr &= 0xFF; sr *= a;
|
||||
sg = _argb >> 8; sg &= 0xFF; sg *= a;
|
||||
sb = _argb & 0xFF; sb *= a;
|
||||
stride = canvas.bufw * 4;
|
||||
row = _y * stride;
|
||||
row += buf_data;
|
||||
row += 8;
|
||||
k = _x * 4;
|
||||
row += k;
|
||||
n = x2 - _x;
|
||||
n *= 4;
|
||||
// a full-window backdrop is blended on every band painted: no division per pixel,
|
||||
// "x / 255" is (x + 1 + (x >> 8)) >> 8, exact up to 255 * 255
|
||||
for (y = _y; y < y2; y++) {
|
||||
p = row;
|
||||
e = row + n;
|
||||
while (p < e) {
|
||||
d = DSDWORD[p];
|
||||
s = d >> 16 & 0xFF; s *= na; s += sr;
|
||||
k = s >> 8; s += k; s++; s >>= 8;
|
||||
v = s << 16;
|
||||
s = d >> 8 & 0xFF; s *= na; s += sg;
|
||||
k = s >> 8; s += k; s++; s >>= 8;
|
||||
s <<= 8;
|
||||
v |= s;
|
||||
s = d & 0xFF; s *= na; s += sb;
|
||||
k = s >> 8; s += k; s++; s >>= 8;
|
||||
v |= s;
|
||||
v |= 0xFF000000;
|
||||
DSDWORD[p] = v;
|
||||
p += 4;
|
||||
}
|
||||
row += stride;
|
||||
}
|
||||
}
|
||||
|
||||
// a background box: a translucent one of a positioned box (F_P) is mixed into what is
|
||||
// painted under it, as a browser does; any other is a plain bar of its blended color
|
||||
void paint_box(dword _f, int _x, _y, _w, _h)
|
||||
{
|
||||
dword c, n;
|
||||
c = DSDWORD[_f + F_BG];
|
||||
if (c != NO_COLOR) {
|
||||
n = dom_n(DSDWORD[_f + F_NODE]);
|
||||
n = DSDWORD[n + S_BGA];
|
||||
IF (n) && (DSDWORD[_f + F_P]) cv_blend_bar(_x, _y, _w, _h, n);
|
||||
ELSE cv_bar(_x, _y, _w, _h, c);
|
||||
}
|
||||
paint_bg_image(DSDWORD[_f + F_NODE], _x, _y, _w, _h);
|
||||
}
|
||||
|
||||
// canvas rows [_y1, _y2) saved by cv_rows_keep(), put back by cv_rows_back(). One
|
||||
// buffer for all, it only grows: no malloc per text run on every repaint
|
||||
dword cv_keep_buf, cv_keep_cap;
|
||||
int cv_keep_y, cv_keep_n;
|
||||
|
||||
void cv_rows_keep(int _y1, _y2)
|
||||
{
|
||||
dword a;
|
||||
IF (_y1 < 0) _y1 = 0;
|
||||
IF (_y2 > canvas.bufh) _y2 = canvas.bufh;
|
||||
cv_keep_n = _y2 - _y1;
|
||||
IF (cv_keep_n <= 0) { cv_keep_n = 0; return; }
|
||||
cv_keep_n *= canvas.bufw;
|
||||
cv_keep_n *= 4;
|
||||
cv_keep_y = _y1;
|
||||
if (cv_keep_n > cv_keep_cap) {
|
||||
IF (cv_keep_buf) free(cv_keep_buf);
|
||||
cv_keep_cap = 0;
|
||||
cv_keep_buf = malloc(cv_keep_n);
|
||||
IF (!cv_keep_buf) { cv_keep_n = 0; return; }
|
||||
cv_keep_cap = cv_keep_n;
|
||||
}
|
||||
a = _y1 * canvas.bufw;
|
||||
a *= 4;
|
||||
a += buf_data;
|
||||
a += 8;
|
||||
memmov(cv_keep_buf, a, cv_keep_n);
|
||||
}
|
||||
|
||||
void cv_rows_back()
|
||||
{
|
||||
dword a;
|
||||
IF (!cv_keep_n) return;
|
||||
a = cv_keep_y * canvas.bufw;
|
||||
a *= 4;
|
||||
a += buf_data;
|
||||
a += 8;
|
||||
memmov(a, cv_keep_buf, cv_keep_n);
|
||||
cv_keep_n = 0;
|
||||
}
|
||||
|
||||
void cv_rect(int _x, _y, _w, _h, _t, dword _color)
|
||||
{
|
||||
cv_bar(_x, _y, _w, _t, _color);
|
||||
cv_bar(_x, _y + _h - _t, _w, _t, _color);
|
||||
cv_bar(_x, _y, _t, _h, _color);
|
||||
cv_bar(_x + _w - _t, _y, _t, _h, _color);
|
||||
}
|
||||
|
||||
// the frame of a box: each side has its own width and colour ("border-bottom: 2px
|
||||
// solid #c00" underlines a card, the sides stay away). A node with no width of its
|
||||
// own (a table frame the layout asked for) gets the 1px line in _fallback.
|
||||
void paint_border(dword _idx, int _x, _y, _w, _h, dword _fallback, _open)
|
||||
{
|
||||
dword node = dom_n(_idx);
|
||||
int t, r, b, l;
|
||||
dword c;
|
||||
t = DSDWORD[node + S_BORDT];
|
||||
r = DSDWORD[node + S_BORDR];
|
||||
b = DSDWORD[node + S_BORDB];
|
||||
l = DSDWORD[node + S_BORDL];
|
||||
IF (!t) && (!r) && (!b) && (!l) {
|
||||
cv_rect(_x, _y, _w, _h, 1, _fallback);
|
||||
return;
|
||||
}
|
||||
// an inline element broken over lines: no side where the line breaks it
|
||||
c = _open & FRAME_NO_LEFT;
|
||||
IF (c) l = 0;
|
||||
c = _open & FRAME_NO_RIGHT;
|
||||
IF (c) r = 0;
|
||||
c = _open & FRAME_NO_BOTTOM;
|
||||
IF (c) b = 0;
|
||||
IF (t) {
|
||||
c = DSDWORD[node + S_BCOLT]; IF (c == NO_COLOR) c = _fallback;
|
||||
IF (c != CLEAR_COLOR) cv_bar(_x, _y, _w, t, c);
|
||||
}
|
||||
IF (b) {
|
||||
c = DSDWORD[node + S_BCOLB]; IF (c == NO_COLOR) c = _fallback;
|
||||
IF (c != CLEAR_COLOR) cv_bar(_x, _y + _h - b, _w, b, c);
|
||||
}
|
||||
IF (l) {
|
||||
c = DSDWORD[node + S_BCOLL]; IF (c == NO_COLOR) c = _fallback;
|
||||
IF (c != CLEAR_COLOR) cv_bar(_x, _y, l, _h, c);
|
||||
}
|
||||
IF (r) {
|
||||
c = DSDWORD[node + S_BCOLR]; IF (c == NO_COLOR) c = _fallback;
|
||||
IF (c != CLEAR_COLOR) cv_bar(_x + _w - r, _y, r, _h, c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// the chars of _str (_len) up to a cell cv_text() draws itself: (0xFF),
|
||||
// in the 8x16 font (_big) the down arrow 0x19, which only the 6x9 font has,
|
||||
// and in the 6x9 font the long dash 0x12, which only the 8x16 font has
|
||||
int cv_text_run(dword _str, _len, _big)
|
||||
{
|
||||
int n;
|
||||
dword c;
|
||||
n = 0;
|
||||
WHILE (n < _len) {
|
||||
c = DSBYTE[_str + n];
|
||||
IF (c == 0xFF) RETURN n;
|
||||
IF (c == 0x19) && (_big) RETURN n;
|
||||
IF (c == 0x12) && (!_big) RETURN n;
|
||||
n++;
|
||||
}
|
||||
RETURN n;
|
||||
}
|
||||
|
||||
// iconv.obj turns en and em dashes into 0x12: a full-width dash in the 8x16 font,
|
||||
// but an up-down arrow in the 6x9 one. There it is a line across the cell at _x, _y
|
||||
// on the row of "-" (row 4 of 9, zoomed with the font; the zoom comes from the width:
|
||||
// the height of the doubled font is 17, not 18)
|
||||
void cv_dash(int _x, _y, dword _font, _color)
|
||||
{
|
||||
int w, h, k;
|
||||
w = font_w(_font);
|
||||
h = w / 6;
|
||||
k = h * 4;
|
||||
_y += k;
|
||||
cv_bar(_x, _y, w, h, _color);
|
||||
}
|
||||
|
||||
// the arrow of a <select>: 0x19 of the 6x9 font at the same zoom, in the middle of
|
||||
// the cell of the 8x16 font _font at _x, _y
|
||||
char cv_arrow_str[2];
|
||||
void cv_arrow(int _x, _y, dword _font, _color)
|
||||
{
|
||||
dword t;
|
||||
int d;
|
||||
t = font_types[_font];
|
||||
t -= 0x10;
|
||||
d = font_w(_font);
|
||||
d /= 8;
|
||||
_x -= d; // 2px left of the middle (at zoom 1): off the right edge of the box
|
||||
d *= 4;
|
||||
_y += d;
|
||||
DSBYTE[#cv_arrow_str] = 0x19;
|
||||
canvas.WriteText(_x, _y, t, _color, #cv_arrow_str, 1);
|
||||
}
|
||||
|
||||
// text run: the kernel draws into the buffer, we only clip the length on the right
|
||||
void cv_text(int _x, _y, dword _font, _color, _str, _len)
|
||||
{
|
||||
int fw = font_w(_font), fh = font_h(_font), room, k;
|
||||
dword big;
|
||||
IF (_len <= 0) return;
|
||||
IF (_y < 0) || (_y + fh > canvas.bufh) return;
|
||||
IF (_y + 30 >= canvas.bufh) return; // WriteText would skip each run there anyway
|
||||
IF (_x < 0) {
|
||||
room = 0 - _x; room /= fw; room++;
|
||||
IF (room >= _len) return;
|
||||
_str += room; _len -= room; room *= fw; _x += room;
|
||||
}
|
||||
room = canvas.bufw - _x; room /= fw;
|
||||
IF (room <= 0) return;
|
||||
IF (_len > room) _len = room;
|
||||
// is stored as CP866 0xFF, which the system font draws as "_":
|
||||
// draw the pieces between them and leave a blank cell for each
|
||||
big = font_types[_font] & 0x10;
|
||||
while (_len > 0) {
|
||||
room = cv_text_run(_str, _len, big);
|
||||
IF (room) canvas.WriteText(_x, _y, font_types[_font], _color, _str, room);
|
||||
IF (room < _len) {
|
||||
// the NBSP cell, the arrow of a <select> drawn in the small font,
|
||||
// or a long dash drawn as a line
|
||||
k = room * fw;
|
||||
k += _x;
|
||||
IF (DSBYTE[_str + room] == 0x19) cv_arrow(k, _y, _font, _color);
|
||||
IF (DSBYTE[_str + room] == 0x12) cv_dash(k, _y, _font, _color);
|
||||
room++;
|
||||
}
|
||||
_str += room;
|
||||
_len -= room;
|
||||
room *= fw;
|
||||
_x += room;
|
||||
}
|
||||
}
|
||||
|
||||
// The kernel fonts have no italic: the run is sheared after it is drawn, in 1px steps
|
||||
// (one for the plain fonts, two for the doubled). A row is copied from k pixels to its
|
||||
// left, so the run needs k columns on each side and stays upright without them.
|
||||
void cv_slant(int _x, _y, _w, _h)
|
||||
{
|
||||
int r, k, n;
|
||||
dword p;
|
||||
n = _h / 16; // 9 and 14: one step, two bands; 17 and 27: two steps, three bands
|
||||
n++;
|
||||
IF (_x < n) || (_y < 0) return;
|
||||
IF (_x + _w + n >= canvas.bufw) return;
|
||||
for (r = 0; r < _h; r++) {
|
||||
k = n + 1; // the row's band, counted from the top
|
||||
k *= r;
|
||||
k /= _h;
|
||||
k = n - k; // the bands lean n, n-1 ... 1, 0
|
||||
IF (!k) BREAK;
|
||||
p = _y + r;
|
||||
IF (p >= canvas.bufh) RETURN;
|
||||
p = p * canvas.bufw;
|
||||
p += _x;
|
||||
p *= 4;
|
||||
p += buf_data + 8;
|
||||
k *= 4;
|
||||
memmov(p, p - k, _w * 4 + k);
|
||||
}
|
||||
}
|
||||
|
||||
// part of a 32bpp image: _w x _h pixels from (_sx, _sy) of the picture to (_x, _y)
|
||||
void cv_image_part(int _x, _y, _sx, _sy, _w, _h, dword _img)
|
||||
{
|
||||
int k;
|
||||
IF (_sx < 0) { _x -= _sx; _w += _sx; _sx = 0; }
|
||||
IF (_sy < 0) { _y -= _sy; _h += _sy; _sy = 0; }
|
||||
IF (_x < 0) { _sx -= _x; _w += _x; _x = 0; }
|
||||
IF (_y < 0) { _sy -= _y; _h += _y; _y = 0; }
|
||||
k = canvas.bufw - _x; IF (_w > k) _w = k;
|
||||
k = canvas.bufh - _y; IF (_h > k) _h = k;
|
||||
// never past the picture: img_blend reads it unchecked (a picture cut to its box
|
||||
// by layout.h img_cover can be a row lower than the box after scaling, and a page
|
||||
// fault in libimg killed WebView while scrolling habr.com)
|
||||
k = DSDWORD[_img + 4];
|
||||
k -= _sx;
|
||||
IF (_w > k) _w = k;
|
||||
k = DSDWORD[_img + 8];
|
||||
k -= _sy;
|
||||
IF (_h > k) _h = k;
|
||||
IF (_w <= 0) || (_h <= 0) RETURN;
|
||||
IF (DSDWORD[_img + 20] != IMAGE_BPP32) RETURN;
|
||||
DSDWORD[#canvas_img + 4] = canvas.bufw;
|
||||
DSDWORD[#canvas_img + 8] = canvas.bufh;
|
||||
DSDWORD[#canvas_img + 20] = IMAGE_BPP32;
|
||||
DSDWORD[#canvas_img + 24] = buf_data + 8;
|
||||
img_blend stdcall (#canvas_img, _img, _x, _y, _sx, _sy, _w, _h);
|
||||
}
|
||||
|
||||
// _img (_iw x _ih) scaled to _w x _h: the copy, _img destroyed; _img itself when it
|
||||
// has that size already, is too big to scale or scaling fails
|
||||
dword img_scaled(dword _img, int _iw, _ih, _w, _h)
|
||||
{
|
||||
dword scaled;
|
||||
IF (_w == _iw) && (_h == _ih) RETURN _img;
|
||||
IF (_w > 4000) || (_h > 4000) RETURN _img;
|
||||
img_scale stdcall (_img, 0, 0, _iw, _ih, 0, LIBIMG_SCALE_STRETCH, LIBIMG_INTER_DEFAULT, _w, _h);
|
||||
scaled = EAX;
|
||||
IF (!scaled) RETURN _img;
|
||||
img_destroy stdcall (_img);
|
||||
RETURN scaled;
|
||||
}
|
||||
|
||||
// background-size contain / cover: the picture of the node scaled to the box once, the
|
||||
// scaled copy replaces the original (a later resize scales it again)
|
||||
// _img (_iw x _ih) scaled to _w x _h, the copy replaces the picture of _node (N_IMG).
|
||||
dword img_rescale(dword _node, _img, int _iw, _ih, _w, _h)
|
||||
{
|
||||
dword scaled;
|
||||
scaled = img_scaled(_img, _iw, _ih, _w, _h);
|
||||
DSDWORD[_node + N_IMG] = scaled;
|
||||
RETURN scaled;
|
||||
}
|
||||
|
||||
dword bg_image_fit(dword _node, _img, int _w, _h, dword _rep)
|
||||
{
|
||||
int iw, ih, w, h;
|
||||
iw = DSDWORD[_img + 4];
|
||||
ih = DSDWORD[_img + 8];
|
||||
// the height for the box width, then whichever side decides
|
||||
h = ih * _w;
|
||||
h /= iw;
|
||||
w = _w;
|
||||
IF (_rep & BGR_CONTAIN) {
|
||||
IF (h > _h) {
|
||||
h = _h;
|
||||
w = iw * _h;
|
||||
w /= ih;
|
||||
}
|
||||
} ELSE {
|
||||
IF (h < _h) {
|
||||
h = _h;
|
||||
w = iw * _h;
|
||||
w /= ih;
|
||||
}
|
||||
}
|
||||
IF (w < 1) w = 1;
|
||||
IF (h < 1) h = 1;
|
||||
RETURN img_rescale(_node, _img, iw, ih, w, h);
|
||||
}
|
||||
|
||||
// a side of background-size (style.h css_bg_side) in px of a box side _box long
|
||||
int bg_side_px(int _v, _box)
|
||||
{
|
||||
int k;
|
||||
k = _v & BGS_PCT;
|
||||
IF (!k) RETURN _v;
|
||||
k = _v & 0x3FFF;
|
||||
k *= _box;
|
||||
k /= 100;
|
||||
k = math.max(k, 1);
|
||||
RETURN k;
|
||||
}
|
||||
|
||||
// background-size in px ("146px": slashdot.org's logo, an SVG 432px wide) or in percent
|
||||
// of the box _bw x _bh: the picture scaled to it once, a side given as auto keeps the
|
||||
// aspect ratio
|
||||
dword bg_image_sized(dword _node, _img, int _bw, _bh)
|
||||
{
|
||||
int iw, ih, w, h;
|
||||
dword s;
|
||||
s = DSDWORD[_node + S_BGSIZE];
|
||||
w = s & 0xFFFF;
|
||||
h = s >> 16;
|
||||
w = bg_side_px(w, _bw);
|
||||
h = bg_side_px(h, _bh);
|
||||
iw = DSDWORD[_img + 4];
|
||||
ih = DSDWORD[_img + 8];
|
||||
IF (!w) { w = iw * h; w /= ih; }
|
||||
IF (!h) { h = ih * w; h /= iw; }
|
||||
IF (w < 1) w = 1;
|
||||
IF (h < 1) h = 1;
|
||||
RETURN img_rescale(_node, _img, iw, ih, w, h);
|
||||
}
|
||||
|
||||
// the width fragment _f of <img> _node is drawn at: F_IMGW, or the picture's own size
|
||||
int img_frag_w(dword _f, _node)
|
||||
{
|
||||
int w;
|
||||
w = DSDWORD[_f + F_IMGW];
|
||||
IF (w) RETURN w;
|
||||
RETURN DSDWORD[_node + N_IMGW];
|
||||
}
|
||||
|
||||
// an <img> laid out at another width (layout.h F_IMGW: a width in percent, max-width):
|
||||
// scaled to it once, keeping the aspect ratio; the copy replaces the original. Laid
|
||||
// out at its own size again (the window got wider) it is scaled back: the layout
|
||||
// measures the picture by N_IMGW, not by the copy a narrower window left
|
||||
dword img_fit_scale(dword _f, _img)
|
||||
{
|
||||
dword node;
|
||||
int iw, ih, w, h, nw;
|
||||
node = DSDWORD[_f + F_NODE];
|
||||
node = dom_n(node);
|
||||
w = img_frag_w(_f, node);
|
||||
iw = DSDWORD[_img + 4];
|
||||
IF (w <= 0) || (w == iw) RETURN _img;
|
||||
nw = DSDWORD[node + N_IMGW];
|
||||
IF (nw <= 0) RETURN _img;
|
||||
h = DSDWORD[node + N_IMGH];
|
||||
h *= w;
|
||||
h /= nw;
|
||||
IF (h < 1) RETURN _img;
|
||||
ih = DSDWORD[_img + 8];
|
||||
RETURN img_rescale(node, _img, iw, ih, w, h);
|
||||
}
|
||||
|
||||
// A small tile that repeats is made into a bigger one (64px at least in each repeated
|
||||
// direction) for one paint: a 4x4 dot pattern over the window would be ~56000 cells to
|
||||
// blit otherwise. A new image the caller destroys, or _img itself. It must not replace
|
||||
// the picture of the node: a sheet that arrives later may say no-repeat (the forum
|
||||
// icons of board.kolibrios.org came out as 2x2 books)
|
||||
dword bg_tile_grow(dword _img, _rep)
|
||||
{
|
||||
int iw, ih, w, h, x, y, sx, sy;
|
||||
dword big, src, dst, row, k;
|
||||
IF (_rep & BGR_CONTAIN) return _img;
|
||||
IF (_rep & BGR_COVER) return _img;
|
||||
iw = DSDWORD[_img + 4];
|
||||
ih = DSDWORD[_img + 8];
|
||||
w = iw;
|
||||
h = ih;
|
||||
IF (_rep & BGR_X) WHILE (w < 64) w += iw;
|
||||
IF (_rep & BGR_Y) WHILE (h < 64) h += ih;
|
||||
IF (w == iw) && (h == ih) return _img;
|
||||
img_create stdcall (w, h, IMAGE_BPP32);
|
||||
big = EAX;
|
||||
IF (!big) return _img;
|
||||
src = DSDWORD[_img + 24];
|
||||
dst = DSDWORD[big + 24];
|
||||
sy = 0;
|
||||
FOR (y = 0; y < h; y++) {
|
||||
row = sy * iw;
|
||||
row *= 4;
|
||||
row += src;
|
||||
sx = 0;
|
||||
FOR (x = 0; x < w; x++) {
|
||||
k = sx * 4;
|
||||
k += row;
|
||||
DSDWORD[dst] = DSDWORD[k];
|
||||
dst += 4;
|
||||
sx++;
|
||||
IF (sx >= iw) sx = 0;
|
||||
}
|
||||
sy++;
|
||||
IF (sy >= ih) sy = 0;
|
||||
}
|
||||
RETURN big;
|
||||
}
|
||||
|
||||
// background-image of box _idx inside (_x, _y, _w, _h): placed by BGR_* bits, tiled
|
||||
// in the repeated directions, clipped to the box
|
||||
void paint_bg_image(dword _idx, int _x, _y, _w, _h)
|
||||
{
|
||||
dword node = dom_n(_idx);
|
||||
dword img, rep, tile;
|
||||
int iw, ih, x0, y0, tx, ty, dx, dy, sx, sy, tw, th, k, n;
|
||||
n = 0;
|
||||
IF (!DSDWORD[node + S_BGIMG]) return;
|
||||
// a strip with a CSS height keeps its picture to that height, even when our bigger
|
||||
// font made the box taller
|
||||
k = DSDWORD[node + S_HEIGHT];
|
||||
IF (k > 0) && (_h > k) _h = k;
|
||||
IF (_w <= 0) || (_h <= 0) return;
|
||||
img = engine_get_bgimage(_idx);
|
||||
IF (!img) return;
|
||||
iw = DSDWORD[img + 4];
|
||||
ih = DSDWORD[img + 8];
|
||||
IF (iw <= 0) || (ih <= 0) return;
|
||||
rep = DSDWORD[node + S_BGREP];
|
||||
IF (rep & BGR_CONTAIN) || (rep & BGR_COVER) {
|
||||
img = bg_image_fit(node, img, _w, _h, rep);
|
||||
iw = DSDWORD[img + 4];
|
||||
ih = DSDWORD[img + 8];
|
||||
} ELSE IF (DSDWORD[node + S_BGSIZE]) {
|
||||
img = bg_image_sized(node, img, _w, _h);
|
||||
iw = DSDWORD[img + 4];
|
||||
ih = DSDWORD[img + 8];
|
||||
}
|
||||
x0 = _x; y0 = _y;
|
||||
IF (rep & BGR_RIGHT) { x0 = _x + _w; x0 -= iw; }
|
||||
IF (rep & BGR_CX) { k = _w - iw; k /= 2; x0 = _x + k; }
|
||||
IF (rep & BGR_BOTTOM) { y0 = _y + _h; y0 -= ih; }
|
||||
IF (rep & BGR_CY) { k = _h - ih; k /= 2; y0 = _y + k; }
|
||||
k = DSDWORD[node + S_BGX]; x0 += k; // pixel offsets: a sprite shows its own cell
|
||||
k = DSDWORD[node + S_BGY]; y0 += k;
|
||||
// after the placement: the bigger tile repeats in the phase of the small one
|
||||
tile = bg_tile_grow(img, rep);
|
||||
iw = DSDWORD[tile + 4];
|
||||
ih = DSDWORD[tile + 8];
|
||||
// a repeated direction starts with the tile that covers the box edge, or the canvas
|
||||
// edge when the box begins before it (a big negative offset too)
|
||||
IF (rep & BGR_X) {
|
||||
WHILE (x0 > _x) x0 -= iw;
|
||||
k = math.max(_x, 0);
|
||||
k -= x0;
|
||||
IF (k >= iw) { k /= iw; k *= iw; x0 += k; }
|
||||
}
|
||||
IF (rep & BGR_Y) {
|
||||
WHILE (y0 > _y) y0 -= ih;
|
||||
k = math.max(_y, 0);
|
||||
k -= y0;
|
||||
IF (k >= ih) { k /= ih; k *= ih; y0 += k; }
|
||||
}
|
||||
ty = y0;
|
||||
while (ty < _y + _h) && (ty < canvas.bufh) {
|
||||
tx = x0;
|
||||
while (tx < _x + _w) && (tx < canvas.bufw) {
|
||||
dx = tx; sx = 0; tw = iw;
|
||||
IF (dx < _x) { sx = _x - dx; tw -= sx; dx = _x; }
|
||||
k = _x + _w - dx; IF (tw > k) tw = k;
|
||||
dy = ty; sy = 0; th = ih;
|
||||
IF (dy < _y) { sy = _y - dy; th -= sy; dy = _y; }
|
||||
k = _y + _h - dy; IF (th > k) th = k;
|
||||
IF (tw > 0) && (th > 0) cv_image_part(dx, dy, sx, sy, tw, th, tile);
|
||||
n++;
|
||||
IF (n > 4000) BREAK; // a safety net: the tiles are 64px and clipped to the canvas
|
||||
IF (!(rep & BGR_X)) BREAK;
|
||||
tx += iw;
|
||||
}
|
||||
IF (n > 4000) BREAK;
|
||||
IF (!(rep & BGR_Y)) BREAK;
|
||||
ty += ih;
|
||||
}
|
||||
IF (tile != img) img_destroy stdcall (tile);
|
||||
}
|
||||
|
||||
// the shared ICONS18 area, mapped once: every memopen() maps it again and it is never closed
|
||||
dword icons18_buf;
|
||||
dword icons18_max;
|
||||
|
||||
// raw 18x18 32bpp icon from the shared ICONS18 area
|
||||
void cv_icon(int _x, _y, dword _n)
|
||||
{
|
||||
dword base, src, dst;
|
||||
int i, j, p, q;
|
||||
IF (!icons18_buf) {
|
||||
icons18_buf = memopen("ICONS18", NULL, SHM_READ);
|
||||
icons18_max = EDX / 1296; // 18*18*4
|
||||
}
|
||||
IF (!icons18_buf) return;
|
||||
IF (_n >= icons18_max) return;
|
||||
base = _n * 1296 + icons18_buf;
|
||||
for (j = 0; j < 18; j++) {
|
||||
p = _y + j;
|
||||
IF (p < 0) || (p >= canvas.bufh) CONTINUE;
|
||||
FOR (i = 0; i < 18; i++) {
|
||||
q = _x + i;
|
||||
IF (q < 0) || (q >= canvas.bufw) CONTINUE;
|
||||
// the source is addressed by pixel: a clipped pixel or row must still be skipped
|
||||
src = j * 18; src += i; src *= 4; src += base;
|
||||
dst = p * canvas.bufw; dst += q; dst *= 4; dst += buf_data + 8;
|
||||
DSDWORD[dst] = DSDWORD[src];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- overlays ---------------------------------------------------------------------
|
||||
|
||||
// selection range on fragment _i: returns true and sets c1/c2 (chars) when the fragment is selected
|
||||
bool sel_range(dword _i, _len, dword _c1p, _c2p)
|
||||
{
|
||||
dword c1 = 0, c2 = _len;
|
||||
IF (!sel_active) RETURN false;
|
||||
IF (_i < sel_f1) || (_i > sel_f2) RETURN false;
|
||||
IF (_i == sel_f1) c1 = sel_c1;
|
||||
IF (_i == sel_f2) c2 = sel_c2;
|
||||
IF (c2 > _len) c2 = _len;
|
||||
IF (c1 >= c2) RETURN false;
|
||||
DSDWORD[_c1p] = c1;
|
||||
DSDWORD[_c2p] = c2;
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
void paint_text_frag(dword _i, _f, int _x, _y)
|
||||
{
|
||||
dword font = DSDWORD[_f + F_FONT];
|
||||
dword color = DSDWORD[_f + F_COLOR];
|
||||
dword flags = DSDWORD[_f + F_FLAGS];
|
||||
dword len = DSDWORD[_f + F_LEN];
|
||||
dword str = dpool.get(DSDWORD[_f + F_TEXT]);
|
||||
int w = DSDWORD[_f + F_W], h = DSDWORD[_f + F_H];
|
||||
int fw = font_w(font), fh = font_h(font), zoom, ty, k;
|
||||
int caret, mid, pt, pb, pl;
|
||||
dword c1, c2, bg, fnode;
|
||||
|
||||
form_frag_text(_f, #str, #len, #caret); // a form field shows what was typed into it
|
||||
|
||||
zoom = fw / 8;
|
||||
IF (zoom < 1) zoom = 1;
|
||||
ty = _y + h - fh - 2; // text sits at the bottom of the line box
|
||||
|
||||
bg = DSDWORD[_f + F_BG];
|
||||
k = form_frag_frame(_f);
|
||||
// a text field is always black on white, whatever the page colours are
|
||||
IF (k) { bg = 0xFFFFFF; color = 0; }
|
||||
// a field the page made taller than a line (layout.h control_fit): its text in the
|
||||
// middle of what the padding leaves (a percentage padding is taken as none here)
|
||||
pl = 0;
|
||||
if (k) {
|
||||
fnode = dom_n(DSDWORD[_f + F_NODE]);
|
||||
pt = DSDWORD[fnode + S_PTOP];
|
||||
IF (pt < 0) pt = 0;
|
||||
pb = DSDWORD[fnode + S_PBOT];
|
||||
IF (pb < 0) pb = 0;
|
||||
pl = DSDWORD[fnode + S_PLEFT];
|
||||
IF (pl < 0) pl = 0;
|
||||
mid = h - pt;
|
||||
mid -= pb;
|
||||
mid -= fh;
|
||||
IF (mid < 0) mid = 0;
|
||||
c1 = pt | pb;
|
||||
IF (c1) || (mid > 8) { mid /= 2; mid += pt; ty = _y + mid; }
|
||||
}
|
||||
IF (bg != NO_COLOR) cv_bar(_x, _y, w, h, bg);
|
||||
IF (k == 1) cv_rect(_x, _y, w, h, 2, FIELD_BORDER);
|
||||
IF (k == 2) cv_rect(_x - 1, _y - 1, w + 2, h + 2, 2, FOCUS_RING);
|
||||
// the value of a field starts with a space: the text after it begins inside the
|
||||
// frame and the page's padding; with no padding half of the space is the inset
|
||||
IF (k) {
|
||||
IF (pl) { pl += 2; pl -= fw; _x += pl; }
|
||||
ELSE { k = fw / 2; _x -= k; }
|
||||
}
|
||||
IF (flags & SF_TRANSP) return; // color:transparent
|
||||
IF (flags & SF_TINDENT) return; // text-indent: -9999px, a logo picture stands for it
|
||||
if (flags & SF_BUTTON) {
|
||||
// the system colours of a button: the frame all around it, a light line under
|
||||
// its top edge, a dark one above its bottom edge, the label a pixel higher
|
||||
cv_bar(_x, _y, w, h, sc.work);
|
||||
cv_rect(_x, _y, w, h, zoom, sc.line);
|
||||
k = w - zoom - zoom;
|
||||
cv_bar(_x + zoom, _y + zoom, k, zoom, sc.light);
|
||||
cv_bar(_x + zoom, _y + h - zoom - zoom, k, zoom, sc.dark);
|
||||
color = sc.work_text;
|
||||
ty -= zoom;
|
||||
// a button the page made taller (layout.h control_fit): the label in the middle
|
||||
mid = h - fh;
|
||||
IF (mid > 8) { mid /= 2; ty = _y + mid; }
|
||||
// the label sits in the middle: the space of the value around it can be cut off,
|
||||
// then its end (a button the page made narrower, layout.h control_fit)
|
||||
k = len * fw;
|
||||
k = w - k;
|
||||
IF (k < 0) && (DSBYTE[str] == ' ') { str++; len--; k += fw; }
|
||||
IF (k < 0) { len = w / fw; k = len * fw; k = w - k; }
|
||||
IF (k > 0) { k /= 2; _x += k; }
|
||||
}
|
||||
// find highlights
|
||||
for (k = 0; k < hl_frag.count; k++) {
|
||||
IF (hl_frag.get(k) != _i) CONTINUE;
|
||||
c1 = hl_start.get(k); c2 = hl_len.get(k);
|
||||
IF (hl_match.get(k) == find_current) bg = FIND_CUR_BG; ELSE bg = FIND_BG;
|
||||
c1 *= fw; c2 *= fw;
|
||||
cv_bar(_x + c1, _y, c2, h, bg);
|
||||
}
|
||||
// selection
|
||||
IF (sel_range(_i, len, #c1, #c2)) {
|
||||
k = c2 - c1; k *= fw; c1 *= fw;
|
||||
cv_bar(_x + c1, _y, k, h, SEL_BG);
|
||||
}
|
||||
IF (DSDWORD[_f + F_LINK]) && (DSDWORD[_f + F_LINK] == hover_link) color = body_link_active;
|
||||
|
||||
// a run cut at the top by an overflow (layout.h frag_clip): its glyphs start above
|
||||
// _y, the rows there are kept as they are
|
||||
IF (DSDWORD[_f + F_IMGY]) cv_rows_keep(ty, _y);
|
||||
cv_text(_x, ty, font, color, str, len);
|
||||
IF (flags & SF_BOLD) cv_text(_x + 1, ty, font, color, str, len);
|
||||
IF (flags & SF_ITALIC) { k = len * fw; cv_slant(_x, ty, k, fh); }
|
||||
IF (flags & SF_UNDER) cv_bar(_x, ty + fh, w, zoom, color);
|
||||
IF (flags & SF_STRIKE) { k = fh / 2; cv_bar(_x, ty + k, w, zoom, color); }
|
||||
IF (caret >= 0) { k = caret * fw; cv_bar(_x + k, ty, zoom + 1, fh, color); }
|
||||
cv_rows_back();
|
||||
}
|
||||
|
||||
// ---- page -------------------------------------------------------------------------
|
||||
|
||||
// What the canvas shows: the page painted from paint_shown on. paint_valid false:
|
||||
// nothing the next scroll could move (a new canvas, a new layout)
|
||||
bool paint_valid;
|
||||
int paint_shown;
|
||||
|
||||
// the canvas of a band painted by paint_band(), kept for the next scroll
|
||||
dword paint_band_buf;
|
||||
dword paint_band_size;
|
||||
|
||||
// (re)create the canvas for the viewport rectangle
|
||||
void paint_init(dword _x, _y, _w, _h)
|
||||
{
|
||||
paint_valid = false;
|
||||
canvas.Init(_x, _y, _w, _h + PAINT_MARGIN + PAINT_MARGIN);
|
||||
}
|
||||
|
||||
// the document window [scroll, scroll + view_h) into the canvas, PAINT_MARGIN rows of
|
||||
// it above and below
|
||||
void paint_area(int _scroll, _view_h)
|
||||
{
|
||||
dword i, k, f, kind, img;
|
||||
int fx, fy, fw, fh, top, bottom;
|
||||
|
||||
canvas.Fill(0, body_bg);
|
||||
top = _scroll - PAINT_MARGIN;
|
||||
bottom = _scroll + _view_h + PAINT_MARGIN;
|
||||
|
||||
// in paint order: positioned boxes over the flow (layout.h lay_paint_sort)
|
||||
for (k = 0; k < frags.count; k++) {
|
||||
i = frag_in_paint_order(k);
|
||||
f = frags.f(i);
|
||||
fy = DSDWORD[f + F_Y];
|
||||
fh = DSDWORD[f + F_H];
|
||||
IF (fy + fh < top) continue;
|
||||
IF (fy > bottom) continue;
|
||||
IF (DSDWORD[f + F_FLAGS] & SF_INVIS) continue; // visibility:hidden
|
||||
fx = DSDWORD[f + F_X];
|
||||
fw = DSDWORD[f + F_W];
|
||||
fy = fy - _scroll + PAINT_MARGIN;
|
||||
kind = DSDWORD[f + F_KIND];
|
||||
switch (kind) {
|
||||
CASE FR_BOX:
|
||||
paint_box(f, fx, fy, fw, fh);
|
||||
break;
|
||||
CASE FR_RECT:
|
||||
paint_border(DSDWORD[f + F_NODE], fx, fy, fw, fh, DSDWORD[f + F_COLOR], DSDWORD[f + F_TEXT]);
|
||||
break;
|
||||
CASE FR_HLINE:
|
||||
cv_bar(fx, fy, fw, fh, DSDWORD[f + F_COLOR]);
|
||||
break;
|
||||
case FR_TEXT:
|
||||
paint_text_frag(i, f, fx, fy);
|
||||
BREAK;
|
||||
case FR_IMG:
|
||||
img = engine_get_image(DSDWORD[f + F_NODE]);
|
||||
IF (img) img = img_fit_scale(f, img);
|
||||
// a picture cut at its left or top edge by an overflow (layout.h frag_clip)
|
||||
IF (img) cv_image_part(fx, fy, DSDWORD[f + F_TEXT], DSDWORD[f + F_IMGY], fw, fh, img);
|
||||
BREAK;
|
||||
case FR_ICON:
|
||||
IF (DSDWORD[f + F_TEXT] == ICON_FORM) form_paint_check(f, fx, fy);
|
||||
ELSE cv_icon(fx, fy, DSDWORD[f + F_TEXT]);
|
||||
BREAK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the document window [scroll, scroll + view_h) and show it
|
||||
void paint_page(int _scroll, _view_h)
|
||||
{
|
||||
IF (canvas.BufIsInvalid()) RETURN;
|
||||
paint_area(_scroll, _view_h);
|
||||
paint_valid = true;
|
||||
paint_shown = _scroll;
|
||||
canvas.Show(PAINT_MARGIN, _view_h);
|
||||
}
|
||||
|
||||
// The band [_y, _y + _h) of the page into the canvas shown from _scroll. It is painted
|
||||
// into a canvas of its own with the margins of a whole paint: a text run or a picture
|
||||
// cut by the band edge comes out whole there (cv_text skips a run past the canvas edge),
|
||||
// and a half-transparent one is not blended twice over what the canvas holds.
|
||||
// false: no memory for it
|
||||
bool paint_band(int _y, _h, _scroll)
|
||||
{
|
||||
dword main_buf, main_h, row, size, src, dst;
|
||||
int k;
|
||||
row = canvas.bufw * 4;
|
||||
k = _h + PAINT_MARGIN + PAINT_MARGIN;
|
||||
size = k * row + 8;
|
||||
IF (size > paint_band_size) {
|
||||
IF (paint_band_buf) free(paint_band_buf);
|
||||
paint_band_size = 0;
|
||||
paint_band_buf = malloc(size);
|
||||
IF (!paint_band_buf) return false;
|
||||
paint_band_size = size;
|
||||
}
|
||||
main_buf = buf_data;
|
||||
main_h = canvas.bufh;
|
||||
buf_data = paint_band_buf;
|
||||
canvas.bufh = k;
|
||||
DSDWORD[buf_data] = canvas.bufw;
|
||||
DSDWORD[buf_data + 4] = k;
|
||||
paint_area(_y, _h);
|
||||
buf_data = main_buf;
|
||||
canvas.bufh = main_h;
|
||||
// its rows without the margins, to their place in the canvas
|
||||
src = PAINT_MARGIN * row + paint_band_buf + 8;
|
||||
k = _y - _scroll + PAINT_MARGIN;
|
||||
dst = k * row + buf_data + 8;
|
||||
size = _h * row;
|
||||
memmov(dst, src, size);
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
// The page scrolled from paint_shown to _scroll: the rows that stay in view move in
|
||||
// the canvas, only the band that came into view is painted. Whatever else changes the
|
||||
// page paints it whole (paint_page); a new layout says so by paint_valid.
|
||||
void paint_scroll(int _scroll, _view_h)
|
||||
{
|
||||
int d, n, y;
|
||||
dword row, src, dst, size;
|
||||
IF (canvas.BufIsInvalid()) return;
|
||||
IF (!paint_valid) { paint_page(_scroll, _view_h); return; }
|
||||
d = _scroll - paint_shown;
|
||||
IF (!d) { canvas.Show(PAINT_MARGIN, _view_h); return; }
|
||||
n = d;
|
||||
IF (n < 0) n = 0 - n;
|
||||
IF (n >= _view_h) { paint_page(_scroll, _view_h); return; }
|
||||
// only the visible rows are sure: the margins miss runs cut by the canvas edge
|
||||
row = canvas.bufw * 4;
|
||||
size = _view_h - n * row;
|
||||
IF (d > 0) {
|
||||
src = PAINT_MARGIN + n * row + buf_data + 8;
|
||||
dst = PAINT_MARGIN * row + buf_data + 8;
|
||||
y = _scroll + _view_h - n;
|
||||
} ELSE {
|
||||
src = PAINT_MARGIN * row + buf_data + 8;
|
||||
dst = PAINT_MARGIN + n * row + buf_data + 8;
|
||||
y = _scroll;
|
||||
}
|
||||
memmov(dst, src, size);
|
||||
IF (!paint_band(y, n, _scroll)) { paint_page(_scroll, _view_h); RETURN; }
|
||||
paint_shown = _scroll;
|
||||
canvas.Show(PAINT_MARGIN, _view_h);
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 766 B |
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -1,199 +0,0 @@
|
||||
// Tag tokenizer: parses one "<name attr=value ...>" at a time into name + attribute list.
|
||||
// Derived from TWB/parse_tag.h; unlike the old version it does not skip the contents
|
||||
// of <script>/<style>: the DOM builder decides what to do with raw text elements.
|
||||
|
||||
struct _tag
|
||||
{
|
||||
char name[32];
|
||||
bool opened;
|
||||
bool selfclosed;
|
||||
collection attributes;
|
||||
collection values;
|
||||
bool parse();
|
||||
dword get_next_param();
|
||||
} tag;
|
||||
|
||||
// _bufpos: pointer to a dword holding the position right after '<'; on return it holds
|
||||
// the position of the closing '>' (the caller continues from the next byte).
|
||||
// Returns false for comments and malformed tags.
|
||||
bool _tag::parse(dword _bufpos, bufend)
|
||||
{
|
||||
bool retok = true;
|
||||
dword bufpos = DSDWORD[_bufpos];
|
||||
dword params, paramsend;
|
||||
dword closepos;
|
||||
dword whitepos;
|
||||
dword openpos;
|
||||
char lastch;
|
||||
|
||||
name = '\0';
|
||||
selfclosed = false;
|
||||
attributes.drop();
|
||||
values.drop();
|
||||
|
||||
IF (!strncmp(bufpos,"!--",3))
|
||||
{
|
||||
bufpos+=3;
|
||||
WHILE (strncmp(bufpos,"-->",3)!=0) && (bufpos < bufend) bufpos++;
|
||||
bufpos+=2;
|
||||
retok = false;
|
||||
goto _RET;
|
||||
}
|
||||
|
||||
IF (DSBYTE[bufpos] == '/') {
|
||||
opened = false;
|
||||
bufpos++;
|
||||
} ELSE {
|
||||
opened = true;
|
||||
}
|
||||
|
||||
closepos = strchr(bufpos, '>');
|
||||
IF (!closepos) { closepos = bufend - 1; }
|
||||
whitepos = strchrw(bufpos, bufend-bufpos);
|
||||
|
||||
IF (!whitepos) || (whitepos > closepos) {
|
||||
//no param
|
||||
strncpy(#name, bufpos, math.min(closepos - bufpos, sizeof(tag.name)-1));
|
||||
bufpos = closepos;
|
||||
} else {
|
||||
//we have params; a '>' inside a quoted value does not close the tag: alt="Next>>"
|
||||
WHILE (chrlnum(whitepos, '\"', closepos - whitepos)%2) || (chrlnum(whitepos, '\'', closepos - whitepos)%2) {
|
||||
IF (!openpos = strchr(closepos+1, '<')) BREAK;
|
||||
IF (openpos < strchr(closepos+1, '>')) BREAK;
|
||||
IF (!closepos = EAX) {closepos = bufend;BREAK;}
|
||||
}
|
||||
strncpy(#name, bufpos, math.min(whitepos - bufpos, sizeof(tag.name)-1));
|
||||
bufpos = closepos;
|
||||
|
||||
params = malloc(closepos - whitepos + 1);
|
||||
strncpy(params, whitepos, closepos - whitepos);
|
||||
paramsend = params + closepos - whitepos;
|
||||
// a '/' right before '>' only closes the tag when it stands on its own:
|
||||
// in <a href=https://pouet.net/> it belongs to the unquoted value
|
||||
IF (DSBYTE[paramsend-1] == '/') {
|
||||
lastch = DSBYTE[paramsend-2];
|
||||
IF (__isWhite(lastch)) || (lastch == '"') || (lastch == '\'') selfclosed = true;
|
||||
}
|
||||
WHILE (paramsend = get_next_param(params, paramsend-1));
|
||||
free(params);
|
||||
}
|
||||
|
||||
IF (name) {
|
||||
strlwr(#name);
|
||||
IF (name[strlen(#name)-1]=='/') { //for <br/>
|
||||
name[strlen(#name)-1]=NULL;
|
||||
selfclosed = true;
|
||||
}
|
||||
IF (!name) retok = false;
|
||||
} ELSE {
|
||||
retok = false;
|
||||
}
|
||||
|
||||
_RET:
|
||||
DSDWORD[_bufpos] = bufpos;
|
||||
RETURN retok;
|
||||
}
|
||||
|
||||
dword _tag::get_next_param(dword ps, pe)
|
||||
{
|
||||
// "ps" - param start
|
||||
// "pe" - param end
|
||||
// "q" - quote char
|
||||
char q = NULL;
|
||||
char pch;
|
||||
dword initial_pe = pe;
|
||||
dword fixeq;
|
||||
dword val;
|
||||
dword attr;
|
||||
|
||||
IF (DSBYTE[pe] == '/') {
|
||||
// same rule as above: only a standalone slash is the self-closing one
|
||||
pch = DSBYTE[pe-1];
|
||||
IF (__isWhite(pch)) || (pch == '"') || (pch == '\'') pe--;
|
||||
}
|
||||
WHILE (pe>ps) && (__isWhite(DSBYTE[pe])) pe--;
|
||||
IF (pe <= ps) return NULL;
|
||||
|
||||
// a bare word without '=' (checked, disabled, nowrap): attribute with an empty value
|
||||
val = pe;
|
||||
WHILE (val > ps) && (!__isWhite(DSBYTE[val])) && (DSBYTE[val] != '=') && (DSBYTE[val] != '"') && (DSBYTE[val] != '\'') val--;
|
||||
IF (DSBYTE[val] != '=') && (DSBYTE[val] != '"') && (DSBYTE[val] != '\'') {
|
||||
IF (__isWhite(DSBYTE[val])) val++;
|
||||
DSBYTE[pe+1] = 0;
|
||||
strlwr(val);
|
||||
attributes.add(val);
|
||||
values.add("");
|
||||
pe = val - 1;
|
||||
IF (val > ps) DSBYTE[pe] = 0; // the previous value ends before this word
|
||||
IF (pe <= ps) return NULL;
|
||||
return pe;
|
||||
}
|
||||
|
||||
IF (DSBYTE[pe] == '"') || (DSBYTE[pe] == '\'')
|
||||
{
|
||||
//remove quote
|
||||
q = DSBYTE[pe];
|
||||
DSBYTE[pe] = '\0';
|
||||
pe--;
|
||||
|
||||
// the opening quote is the nearest one to the left: the value cannot hold it.
|
||||
// strrchr(ps, q) scanned from the start of the tag instead and on GitHub
|
||||
// sometimes stopped short, giving placeholder="spellcheck="false" type="email"
|
||||
fixeq = pe;
|
||||
WHILE (fixeq > ps) && (DSBYTE[fixeq] != q) fixeq--;
|
||||
IF (DSBYTE[fixeq] == q) {
|
||||
val = fixeq + 1;
|
||||
} ELSE {
|
||||
val = fixeq;
|
||||
}
|
||||
pe = fixeq;
|
||||
DSBYTE[pe] = '\0';
|
||||
|
||||
//find ATTR end
|
||||
WHILE (pe > ps) && (DSBYTE[pe] != '=') pe--;
|
||||
DSBYTE[pe] = '\0';
|
||||
}
|
||||
ELSE
|
||||
{
|
||||
//find VAL start and copy
|
||||
WHILE (pe > ps) && (DSBYTE[pe] != '=') pe--;
|
||||
val = pe+1;
|
||||
DSBYTE[pe] = '\0';
|
||||
//already have ATTR end
|
||||
}
|
||||
|
||||
IF (pe > ps) pe--;
|
||||
|
||||
//Fix case: "src ="
|
||||
WHILE (pe>ps) && (__isWhite(DSBYTE[pe])) {
|
||||
DSBYTE[pe] = '\0';
|
||||
pe--;
|
||||
}
|
||||
|
||||
//find ATTR start and copy
|
||||
WHILE (pe>ps) && (!__isWhite(DSBYTE[pe])) pe--;
|
||||
attr = pe + 1;
|
||||
DSBYTE[pe] = '\0';
|
||||
|
||||
// Fix case: src=./images/logo?sid=e8ece8b38b
|
||||
// Exchange '=' and '\0' position.
|
||||
// attr: src=./images/logo?sid => src
|
||||
// val: e8ece8b38b => ./images/logo?sid=e8ece8b38b
|
||||
fixeq = strchr(attr,'=');
|
||||
IF (!q) && (fixeq) {
|
||||
DSBYTE[val-1] >< DSBYTE[fixeq];
|
||||
val = fixeq+1;
|
||||
}
|
||||
strlwr(attr);
|
||||
strrtrim(val);
|
||||
//Fix case: " img.png"
|
||||
WHILE(__isWhite(DSBYTE[val])) && (val<initial_pe) val++;
|
||||
|
||||
IF (DSBYTE[attr]) {
|
||||
attributes.add(attr);
|
||||
values.add(val);
|
||||
}
|
||||
|
||||
IF (pe==ps) RETURN NULL;
|
||||
RETURN pe;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifdef LANG_RUS
|
||||
#define HISTORY_HEADER "<html><title>История</title><body bgcolor=#fff><h3>Посещенные страницы</h3><br>"
|
||||
#else
|
||||
#define HISTORY_HEADER "<html><title>History</title><body bgcolor=#fff><h3>Visited pages</h3><br>"
|
||||
#endif
|
||||
|
||||
|
||||
ShowHistory()
|
||||
{
|
||||
int i;
|
||||
dword history_pointer = malloc(history.items.data_size+256);
|
||||
strcat(history_pointer, HISTORY_HEADER);
|
||||
|
||||
for (i=0; i<history.items.count-1; i++) //if (cache.type.get(i) == PAGE)
|
||||
{
|
||||
strcat(history_pointer, "<kosicon n=3><a href='");
|
||||
strcat(history_pointer, history.items.get(i));
|
||||
strcat(history_pointer, "'>");
|
||||
strcat(history_pointer, history.items.get(i));
|
||||
strcat(history_pointer, "</a><br>");
|
||||
}
|
||||
|
||||
/*
|
||||
strcat(history_pointer, "<br><b>Cached images</b><br>");
|
||||
for (i=1; i<cache.url.count; i++) if (cache.type.get(i) == IMG)
|
||||
{
|
||||
strcat(history_pointer, cache.url.get(i));
|
||||
strcat(history_pointer, " <img src='");
|
||||
strcat(history_pointer, cache.url.get(i));
|
||||
strcat(history_pointer, "'><br>");
|
||||
}
|
||||
*/
|
||||
|
||||
LoadInternalPage(history_pointer, strlen(history_pointer));
|
||||
free(history_pointer);
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
// Pictures and stylesheets of a page are fetched by threads of WebView itself, one per
|
||||
// file. They used to be "dl -i" processes: a program loaded from disk for every picture,
|
||||
// a 256 byte limit on the command line (longer urls were skipped) and no cookies.
|
||||
// libHTTP's get() waits for the name lookup and the connection, so a thread does it and
|
||||
// the window stays live. The thread writes the file to the path it was given and exits;
|
||||
// the window sees it gone (GetProcessSlot works for a thread id too) and takes the file,
|
||||
// the way it did with dl.
|
||||
//
|
||||
// A thread must not touch what the window thread writes: no sprintf into globals, no
|
||||
// CreateFile (it fills the shared f70), no "http" struct, no cookie buffers. What it
|
||||
// needs is copied into the memory of its job before it starts.
|
||||
|
||||
// not defined: every file is fetched by a "dl -i" process, as before (WebView.c ImgStart)
|
||||
#define IMG_THREADS
|
||||
|
||||
#define IMG_JOBS 64 // >= IMG_PARALLEL + IMG_ORPHANS (60): every slot and every orphan has a job
|
||||
#define IMGJ_STACK 32768 // the thread's stack, at the start of the job memory
|
||||
#define IMGJ_URL 32768 // the address asked for (URL_SIZE + 1)
|
||||
#define IMGJ_GET 36772 // what libHTTP gets: https in the proxy form (URL_SIZE + 256 + 1)
|
||||
#define IMGJ_HDR 41032 // request headers: Accept-Language and the cookies of the host
|
||||
#define IMGJ_PATH 45128 // the file to write (256)
|
||||
#define IMGJ_SIZE 45384
|
||||
|
||||
dword img_job_mem[IMG_JOBS]; // allocated when the job is first used, then kept
|
||||
dword img_job_tid[IMG_JOBS]; // its thread, 0 = never started
|
||||
dword img_job_cancel[IMG_JOBS]; // set by the window: stop, write nothing
|
||||
dword img_job_phase[IMG_JOBS]; // what the thread does now: JP_* (ImgJobsDump)
|
||||
dword img_job_bytes[IMG_JOBS]; // received so far
|
||||
dword img_job_flags[IMG_JOBS]; // http_msg.flags of the transfer
|
||||
enum { JP_START = 1, JP_GET, JP_RECV, JP_SAVE, JP_END };
|
||||
#ifdef TESTBUILD
|
||||
bool img_job_dump = false; // tests: a job slower than 10 s writes /hd0/1/jobs.txt
|
||||
bool img_job_dumped;
|
||||
#endif
|
||||
|
||||
// the address libHTTP gets: https goes through the proxy. _get holds URL_SIZE + 257
|
||||
// bytes (IMGJ_GET, WebView.c http_url_buf); the page load uses it too (GetUrl)
|
||||
bool img_job_url(dword _url, _get)
|
||||
{
|
||||
IF (!strncmp(_url, "http://", 7)) { strlcpy(_get, _url, URL_SIZE); RETURN true; }
|
||||
IF (strncmp(_url, "https://", 8)) RETURN false;
|
||||
IF (!proxy_address[0]) RETURN false;
|
||||
strcpy(_get, #proxy_address);
|
||||
strcat_proxy_site(_get, _url, URL_SIZE + 257);
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
// one GET received to the end: the finished transfer, 0 when it failed or was cancelled
|
||||
dword img_job_get(int _j, dword _get, _hdr)
|
||||
{
|
||||
dword t, r;
|
||||
img_job_phase[_j] = JP_GET;
|
||||
http_get stdcall (_get, 0, 0, _hdr);
|
||||
t = EAX;
|
||||
IF (!t) RETURN 0;
|
||||
img_job_phase[_j] = JP_RECV;
|
||||
loop() {
|
||||
IF (img_job_cancel[_j]) BREAK;
|
||||
http_receive stdcall (t);
|
||||
r = EAX;
|
||||
EDI = t;
|
||||
img_job_bytes[_j] = EDI.http_msg.content_received;
|
||||
EDI = t;
|
||||
img_job_flags[_j] = EDI.http_msg.flags;
|
||||
IF (!r) RETURN t;
|
||||
WaitEventTimeout(2); // EVM_STACK: wakes up when data comes
|
||||
}
|
||||
http_free stdcall (t);
|
||||
RETURN 0;
|
||||
}
|
||||
|
||||
// a header value up to the end of its line
|
||||
void img_job_line(dword _dst, _src, _size)
|
||||
{
|
||||
dword e, c;
|
||||
e = _dst + _size - 1;
|
||||
WHILE (_dst < e) {
|
||||
c = DSBYTE[_src];
|
||||
IF (!c) || (c == 13) || (c == 10) BREAK;
|
||||
DSBYTE[_dst] = c;
|
||||
_dst++;
|
||||
_src++;
|
||||
}
|
||||
DSBYTE[_dst] = 0;
|
||||
}
|
||||
|
||||
// a 3xx answer: its Location made absolute against _url into _url (_buf is scratch)
|
||||
bool img_job_redirect(dword _t, _url, _buf, _hops)
|
||||
{
|
||||
dword p;
|
||||
IF (_hops > 5) RETURN false;
|
||||
http_find_header_field stdcall (_t, "location");
|
||||
p = EAX;
|
||||
IF (!p) RETURN false;
|
||||
img_job_line(_buf, p, URL_SIZE);
|
||||
get_absolute_url(_buf, _url);
|
||||
strlcpy(_url, _buf, URL_SIZE);
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
// write the file with a request block of our own: CreateFile() fills the shared f70
|
||||
void img_job_save(dword _path, _data, _size)
|
||||
{
|
||||
char blk[28];
|
||||
dword b;
|
||||
b = #blk;
|
||||
DSDWORD[b] = 2;
|
||||
DSDWORD[b+4] = 0;
|
||||
DSDWORD[b+8] = 0;
|
||||
DSDWORD[b+12] = _size;
|
||||
DSDWORD[b+16] = _data;
|
||||
DSBYTE[b+20] = 0;
|
||||
DSDWORD[b+21] = _path;
|
||||
EBX = b;
|
||||
EAX = 70;
|
||||
$int 0x40
|
||||
}
|
||||
|
||||
// the work of the thread: the file of job _j, following up to 5 redirects. Only a
|
||||
// complete 2xx answer is written: an error page must not be stored as the picture, and
|
||||
// http_receive() also ends with 0 on a timeout or a dropped connection - a picture or
|
||||
// a stylesheet cut there would stay in the cache as it is.
|
||||
void img_job_run(int _j)
|
||||
{
|
||||
dword mem, ju, jg, jh, t, code, p, n, all;
|
||||
int hops;
|
||||
bool ok;
|
||||
mem = img_job_mem[_j];
|
||||
ju = mem + IMGJ_URL;
|
||||
jg = mem + IMGJ_GET;
|
||||
jh = mem + IMGJ_HDR;
|
||||
hops = 0;
|
||||
loop() {
|
||||
IF (!img_job_url(ju, jg)) return;
|
||||
t = img_job_get(_j, jg, jh);
|
||||
IF (!t) return;
|
||||
EDI = t;
|
||||
code = EDI.http_msg.status;
|
||||
IF (code < 300) || (code >= 400) BREAK;
|
||||
hops++;
|
||||
ok = img_job_redirect(t, ju, jg, hops);
|
||||
http_free stdcall (t);
|
||||
IF (!ok) RETURN;
|
||||
jh = #accept_language; // the cookies were for the first host only
|
||||
}
|
||||
EDI = t;
|
||||
p = EDI.http_msg.content_ptr;
|
||||
n = EDI.http_msg.content_received;
|
||||
EDI = t;
|
||||
all = EDI.http_msg.flags & HTTP_GOT_ALL_DATA;
|
||||
IF (code >= 200) && (code < 300) && (p) && (n) && (all) {
|
||||
IF (!img_job_cancel[_j]) {
|
||||
img_job_phase[_j] = JP_SAVE;
|
||||
jh = mem + IMGJ_PATH;
|
||||
img_job_save(jh, p, n);
|
||||
}
|
||||
}
|
||||
http_free stdcall (t); // it frees content_ptr as well
|
||||
}
|
||||
|
||||
// the thread of one job: the job whose memory holds this stack
|
||||
void img_worker()
|
||||
{
|
||||
char mark[4];
|
||||
dword here, m;
|
||||
int j;
|
||||
here = #mark;
|
||||
FOR (j = 0; j < IMG_JOBS; j++) {
|
||||
m = img_job_mem[j];
|
||||
IF (!m) CONTINUE;
|
||||
IF (here < m) CONTINUE;
|
||||
m += IMGJ_STACK;
|
||||
IF (here >= m) CONTINUE;
|
||||
SetEventMask(EVM_STACK);
|
||||
img_job_run(j);
|
||||
img_job_phase[j] = JP_END;
|
||||
BREAK;
|
||||
}
|
||||
ExitProcess(); // ends this thread only
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
// the window thread side //
|
||||
//===================================================//
|
||||
|
||||
dword ImgJobMem(int _j)
|
||||
{
|
||||
dword m;
|
||||
m = img_job_mem[_j];
|
||||
IF (m) RETURN m;
|
||||
m = malloc(IMGJ_SIZE);
|
||||
img_job_mem[_j] = m;
|
||||
RETURN m;
|
||||
}
|
||||
|
||||
// start a thread fetching _url into the file _path: its id, 0 when it could not start
|
||||
int ImgJobStart(dword _url, _path)
|
||||
{
|
||||
int j, tid;
|
||||
dword m, p, h;
|
||||
FOR (j = 0; j < IMG_JOBS; j++) {
|
||||
IF (!img_job_tid[j]) BREAK;
|
||||
IF (!GetProcessSlot(img_job_tid[j])) BREAK;
|
||||
}
|
||||
IF (j >= IMG_JOBS) return 0;
|
||||
m = ImgJobMem(j);
|
||||
IF (!m) return 0;
|
||||
p = m + IMGJ_URL;
|
||||
strlcpy(p, _url, URL_SIZE);
|
||||
h = cookies_request(_url);
|
||||
p = m + IMGJ_HDR;
|
||||
strlcpy(p, h, 4095);
|
||||
p = m + IMGJ_PATH;
|
||||
strlcpy(p, _path, 255);
|
||||
img_job_cancel[j] = 0;
|
||||
img_job_phase[j] = JP_START;
|
||||
img_job_bytes[j] = 0;
|
||||
img_job_flags[j] = 0;
|
||||
p = m + IMGJ_STACK;
|
||||
p -= 16;
|
||||
tid = CreateThread(#img_worker, p);
|
||||
IF (tid <= 0) RETURN 0;
|
||||
img_job_tid[j] = tid;
|
||||
RETURN tid;
|
||||
}
|
||||
|
||||
// the window gives up on thread _tid: it stops at its next read and writes nothing
|
||||
void ImgJobCancel(int _tid)
|
||||
{
|
||||
int j;
|
||||
FOR (j = 0; j < IMG_JOBS; j++) {
|
||||
IF (img_job_tid[j] == _tid) img_job_cancel[j] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TESTBUILD
|
||||
// the jobs whose thread still runs, into /hd0/1/jobs.txt: where a slow page waits
|
||||
void ImgJobsDump()
|
||||
{
|
||||
dword out, p;
|
||||
int j;
|
||||
char line[512];
|
||||
out = malloc(IMG_JOBS * 512 + 16);
|
||||
IF (!out) RETURN;
|
||||
DSBYTE[out] = 0;
|
||||
FOR (j = 0; j < IMG_JOBS; j++) {
|
||||
IF (!img_job_tid[j]) CONTINUE;
|
||||
IF (!GetProcessSlot(img_job_tid[j])) CONTINUE;
|
||||
p = img_job_mem[j];
|
||||
p += IMGJ_URL;
|
||||
sprintf(#line, "job %i tid %i phase %i bytes %i flags %a cancel %i ", j, img_job_tid[j], img_job_phase[j], img_job_bytes[j], img_job_flags[j], img_job_cancel[j]);
|
||||
strcat(out, #line);
|
||||
strlcpy(#line, p, 300);
|
||||
strcat(out, #line);
|
||||
strcat(out, "\n");
|
||||
}
|
||||
p = strlen(out);
|
||||
CreateFile(p, out, "/hd0/1/jobs.txt");
|
||||
free(out);
|
||||
}
|
||||
#endif
|
||||
|
||||
// leaving WebView: the threads outlive the window thread, they must not go on
|
||||
void ImgJobsCancelAll()
|
||||
{
|
||||
int j;
|
||||
FOR (j = 0; j < IMG_JOBS; j++) img_job_cancel[j] = 1;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1 @@
|
||||
№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№ЇЇЇ╠╠╠мммМММммм╠╠╠ЇЇЇ№№№№№№№№№№№№№№№№№№№№№№№№▄▄▄МММММММММММММММММММММ▄▄▄№№№╠╠╠№№№№№№№№№№№№▄▄▄ММММММ╠╠╠ЇЇЇ№№№ЇЇЇ╠╠╠ММММММ╠╠╠МММ№№№№№№№№№ЇЇЇММММММ▄▄▄№№№№№№№№№№№№№№№ЇЇЇМММММММММ№№№№№№№№№╠╠╠МММ╠╠╠№№№№№№№№№№№№№№№№№№╠╠╠МММММММММ№№№№№№№№№мммМММЇЇЇ№№№№№№№№№№№№№№№╠╠╠ММММММММММММ№№№№№№№№№МММммм№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№мммМММЇЇЇ№№№№№№№№№№№№№№№№№№№№№ЇЇЇММММММ№№№№№№№№№╠╠╠МММ╠╠╠№№№№№№№№№№№№№№№№№№№№№╠╠╠МММ╠╠╠№№№№№№№№№ЇЇЇММММММ▄▄▄№№№№№№№№№№№№№№№▄▄▄ММММММЇЇЇ№№№№№№№№№№№№▄▄▄ММММММ╠╠╠ЇЇЇ№№№ЇЇЇ╠╠╠ММММММ▄▄▄№№№№№№№№№№№№№№№№№№▄▄▄МММММММММММММММММММММ▄▄▄№№№№№№№№№№№№№№№№№№№№№№№№ЇЇЇ╠╠╠мммМММммм╠╠╠ЇЇЇ№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№▄▄▄№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№\L╘№№№№№№№№№№№№\L╘№№№№№№№№№№№№№№№№№№№№№№№№№№№\L╘\L╘\L╘№№№№№№\L╘\L╘\L╘№№№№№№№№№№№№№№№№№№№№№\L╘\L╘\L╘\L╘\L╘\L╘\L╘\L╘\L╘4$д№№№№№№№№№№№№№№№№№№№№№\L╘\L╘\L╘\L╘\L╘\L╘\L╘4$д№№№№№№№№№№№№№№№№№№№№№№№№№№№\L╘\L╘\L╘\L╘\L╘4$д№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№\L╘\L╘\L╘\L╘\L╘4$д№№№№№№№№№№№№№№№№№№№№№№№№№№№\L╘\L╘\L╘\L╘\L╘\L╘\L╘\L╘№№№№№№№№№№№№№№№№№№№№№\L╘\L╘\L╘\L╘4$д4$д\L╘\L╘\L╘4$д№№№№№№№№№№№№№№№№№№№№№\L╘\L╘4$д№№№№№№4$д\L╘4$д№№№№№№№№№№№№№№№№№№№№№№№№№№№4$д№№№№№№№№№№№№4$д№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№№▄▄▄
|
||||
@@ -0,0 +1,19 @@
|
||||
<html><title>WebView Help</title><body>
|
||||
|
||||
<h1>WebView Text-Based Browser</h1>
|
||||
|
||||
It is free and open-source. If you have any suggestions or want to help improving it please visit its topic at <a href="//board.kolibrios.org/viewtopic.php?f=40&t=1075">board.kolibrios.org</a><pre>
|
||||
|
||||
<b>Shortcut keys</b>
|
||||
|
||||
<font bg=C7CEE4>[CTRL + N or CTRL + T]</font> New window
|
||||
<font bg=C7CEE4>[CTRL + R or F5]</font> Refresh the current page
|
||||
<font bg=C7CEE4>[CTRL + O]</font> Start OpenDialog to open local file
|
||||
<font bg=C7CEE4>[CTRL + U]</font> View a web page's source code
|
||||
<font bg=C7CEE4>[CTRL + H]</font> Open browser history
|
||||
<font bg=C7CEE4>[CTRL + J]</font> Display the file downloader
|
||||
<font bg=C7CEE4>[CTRL + W]</font> Exit browser
|
||||
<font bg=C7CEE4>[CTRL + ENTER]</font> Submit a web search
|
||||
<font bg=C7CEE4>[CTRL + LEFT]</font> Back a page.
|
||||
<font bg=C7CEE4>[CTRL + RIGHT]</font> Forward a page.
|
||||
<font bg=C7CEE4>[F6]</font> Moves a text cursor to the omnibox
|
||||
@@ -0,0 +1,17 @@
|
||||
<html>
|
||||
<meta charset="cp-866">
|
||||
<title>Справка WebView</title>
|
||||
<body><pre><h1>Справка WebView</h1>
|
||||
<b>Клавиши быстрого вызова</b>
|
||||
|
||||
<font bg=C7CEE4>[CTRL + N или CTRL + T]</font> Новое окно
|
||||
<font bg=C7CEE4>[CTRL + R или F5]</font> Перезагрузить страницу
|
||||
<font bg=C7CEE4>[CTRL + O]</font> Вызвать диалог выбора файла
|
||||
<font bg=C7CEE4>[CTRL + U]</font> Смотреть исходный код страницы
|
||||
<font bg=C7CEE4>[CTRL + H]</font> Смотреть историю посещенных страниц
|
||||
<font bg=C7CEE4>[CTRL + J]</font> Открыть загрузчик файлов
|
||||
<font bg=C7CEE4>[CTRL + W]</font> Закрыть браузер
|
||||
<font bg=C7CEE4>[CTRL + ENTER]</font> Поиск в интернете с помощью Google
|
||||
<font bg=C7CEE4>[CTRL + ВЛЕВО]</font> Аналогично кнопке "Назад" в браузере
|
||||
<font bg=C7CEE4>[CTRL + ВПРАВО]</font> Аналогично кнопке "Вперед"
|
||||
<font bg=C7CEE4>[F6]</font> Перемещает текстовый курсор в омнибокс
|
||||
@@ -0,0 +1,121 @@
|
||||
enum { TAG, OPTION_VALUE, TEXT, COMMENT, INLINE_COMMENT, CODE };
|
||||
|
||||
|
||||
|
||||
char* C_HL_keywords[] = {
|
||||
"switch", "if", "while", "for", "break", "continue", "return", "else",
|
||||
"union", "typedef", "static", "class", "case", "#include",
|
||||
"volatile", "register", "sizeof", "typedef", "union", "goto", "const", "auto",
|
||||
"#define", "#endif", "#error", "#ifdef", "#ifndef", "#undef", "#if", "#else",
|
||||
"inline",
|
||||
|
||||
"int ", "dword ", "long ", "double ", "float ", "char ", "unsigned ", "signed ",
|
||||
"void ", "bool ", "enum ", "byte ", "word ", "struct ", "NULL", "loop", "stdcall ",
|
||||
":void ", ":int ", ":bool ", ":dword ", NULL
|
||||
};
|
||||
|
||||
dword ShowCodeSource(dword _bufpointer, _bufsize)
|
||||
{
|
||||
dword new_buf, new_buf_start, i;
|
||||
int mode = CODE;
|
||||
|
||||
char spstr[64];
|
||||
dword keylen;
|
||||
dword keyn;
|
||||
dword keycolor;
|
||||
|
||||
new_buf = malloc(_bufsize*10);
|
||||
new_buf_start = new_buf;
|
||||
sprintf(new_buf,"<html><head><title>%s</title><body><pre>",#current_path);
|
||||
new_buf += strlen(new_buf);
|
||||
for (i=_bufpointer; i<_bufpointer+_bufsize; i++)
|
||||
{
|
||||
if ('<' == ESBYTE[i]) {
|
||||
strcpy(new_buf, "<");
|
||||
new_buf+=4;
|
||||
continue;
|
||||
}
|
||||
if ('>' == ESBYTE[i]) {
|
||||
strcpy(new_buf, ">");
|
||||
new_buf+=4;
|
||||
continue;
|
||||
}
|
||||
if (ESBYTE[i] >= '0') && (ESBYTE[i] <= '9') && (CODE == mode) {
|
||||
strcpy(new_buf, "<font color=#CF00FF>?</font>");
|
||||
ESBYTE[new_buf+20] = ESBYTE[i];
|
||||
new_buf+=28;
|
||||
if (ESBYTE[i+1] == 'x') {
|
||||
strcpy(new_buf, "<font color=#CF00FF>x</font>");
|
||||
new_buf+=28;
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CODE == mode) && ('\"' == ESBYTE[i]) {
|
||||
mode = TEXT;
|
||||
strcpy(new_buf, "<font color=#080>\"");
|
||||
new_buf+=18;
|
||||
continue;
|
||||
}
|
||||
if (TEXT == mode) && ('\"' == ESBYTE[i]) {
|
||||
mode = CODE;
|
||||
strcpy(new_buf, "\"</font>");
|
||||
new_buf+=8;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! strncmp(i, "//", 2) ) && (mode == CODE) {
|
||||
mode = INLINE_COMMENT;
|
||||
strcpy(new_buf, "<font color=#999>//");
|
||||
new_buf+=19;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (INLINE_COMMENT == mode) {
|
||||
if (13 == ESBYTE[i]) {
|
||||
mode = CODE;
|
||||
strcpy(new_buf, "\13</font>");
|
||||
new_buf+=8;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (! strncmp(i, "/*", 2) ) {
|
||||
mode = COMMENT;
|
||||
strcpy(new_buf, "<font color=#999>/*");
|
||||
new_buf+=19;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (! strncmp(i, "*/", 2) ) {
|
||||
mode = CODE;
|
||||
strcpy(new_buf, "*/</font>");
|
||||
new_buf+=9;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CODE == mode) for (keyn=0; C_HL_keywords[keyn]!=NULL; keyn++)
|
||||
{
|
||||
keylen = strlen(C_HL_keywords[keyn]);
|
||||
if (! strncmp(i, C_HL_keywords[keyn], keylen) ) {
|
||||
|
||||
if (keyn<31) keycolor="#f00"; else keycolor="#00f";
|
||||
sprintf(#spstr, "<font color=%s>%s</font>", keycolor, C_HL_keywords[keyn]);
|
||||
strcpy(new_buf, #spstr);
|
||||
|
||||
new_buf += keylen + 24;
|
||||
i += keylen-1;
|
||||
goto _CONTINUE;
|
||||
}
|
||||
}
|
||||
ESBYTE[new_buf] = ESBYTE[i];
|
||||
new_buf++;
|
||||
_CONTINUE:
|
||||
}
|
||||
ESBYTE[new_buf] = 0;
|
||||
LoadInternalPage(new_buf_start, new_buf - new_buf_start);
|
||||
free(new_buf_start);
|
||||
}
|
||||
+15
-13
@@ -1,7 +1,7 @@
|
||||
<title>New tab</title>
|
||||
<html><head><title>New tab</title></head>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<center><table width=750><td width=20><td width=220 valign=top><pre style="margin-top:0">
|
||||
<table><tr><td width=20><td width=220><pre>
|
||||
_____________________
|
||||
|# : : #|
|
||||
| : WebView : |
|
||||
@@ -14,21 +14,23 @@
|
||||
| || | | |
|
||||
\_____||__|________|__|<font color=#DDD>lc</font>
|
||||
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 2.0 <font bg=#47D018> compatible
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 1.0 <font bg=#47D018> compatible
|
||||
<font bg=#3CE7FF> </font></font></font></font>
|
||||
<td>
|
||||
|
||||
<kosicon n=6><a href="//ftp.kolibrios.org/users/Leency/test_files/showroom.htm">Showroom</a>
|
||||
<kosicon n=73><a href=//builds.kolibrios.org/ci/latest/>Nightly builds</a>
|
||||
</pre>
|
||||
<kosicon n=58><a href=//kolibrios.org>Homepage</a>
|
||||
<kosicon n=50><a href="//builds.kolibrios.org">Night-builds</a>
|
||||
<kosicon n=50><a href="//ftp.kolibrios.org">FTP Server</a>
|
||||
<pre>
|
||||
|
||||
<td><pre>
|
||||
<kosicon n=58><a href=//kolibrios.org>Homepage</a> <kosicon n=17><a href="//apps.kolibrios.org">Kolibri App Storage</a> <kosicon n=5><a href="//ftp.kolibrios.org/users/Leency/index.htm">Leency's</a>
|
||||
|
||||
|
||||
<pre>By the way,
|
||||
<font color="#555">• You can check for browser updates from the main menu.
|
||||
By the way,
|
||||
<font color="#555555">• You can check for browser updates from the main menu.
|
||||
• To run a web search, type text in the address box and press Ctrl+Enter.
|
||||
• Pressing F6 moves the text cursor to the omnibox.
|
||||
• You can manually change the encoding of a page by clicking on the label in the bottom right corner.
|
||||
• Need better web support? Use Netsurf or WebKit.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +42,7 @@
|
||||
' |
|
||||
() .-.,="``"=. - o -
|
||||
'=/_ \ |
|
||||
* | '=.<a href=//ftp.kolibrios.org/users/Leency/test_files/links.htm> </a> |
|
||||
* | '=._ |
|
||||
\ `=./`, '
|
||||
. '=.__.=' `=' *
|
||||
+ +
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<title>Pestana nueva</title>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<center><table width=750><td width=20><td width=220 valign=top><pre style="margin-top:0">
|
||||
_____________________
|
||||
|# : : #|
|
||||
| : WebView : |
|
||||
| : para : |
|
||||
| : KolibriOS : |
|
||||
| : : |
|
||||
| :_______________: |
|
||||
| ____________ |
|
||||
| | __ | |
|
||||
| || | | |
|
||||
\_____||__|________|__|<font color=#DDD>lc</font>
|
||||
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 2.0 <font bg=#47D018> compatible
|
||||
<font bg=#3CE7FF> </font></font></font></font>
|
||||
|
||||
<kosicon n=6><a href=//ftp.kolibrios.org/users/Leency/test_files/showroom.htm>Escaparate</a>
|
||||
<kosicon n=73><a href=//builds.kolibrios.org/ci/latest/>Compilaciones nocturnas</a>
|
||||
|
||||
<td><pre>
|
||||
<kosicon n=58><a href=//kolibrios.org>Sitio oficial</a> <kosicon n=17><a href="//apps.kolibrios.org">Centro de aplicaciones</a> <kosicon n=5><a href="//ftp.kolibrios.org/users/Leency/index.htm">Leency's</a>
|
||||
|
||||
|
||||
<pre>Por cierto,
|
||||
<font color="#555">• Puede buscar actualizaciones del navegador desde el menu principal.
|
||||
• La tecla F6 mueve el cursor de texto a la barra de direcciones.
|
||||
• La codificacion de la pagina se cambia pulsando la etiqueta de la esquina inferior derecha.
|
||||
• Necesita mejor soporte web? Use Netsurf o WebKit.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
~+
|
||||
|
||||
* +
|
||||
' |
|
||||
() .-.,="``"=. - o -
|
||||
'=/_ \ |
|
||||
* | '=._ |
|
||||
\ `=./`, '
|
||||
. '=.__.=' `=' *
|
||||
+ +
|
||||
O * ' .<font color=#DDD>jgs</font>
|
||||
+15
-13
@@ -1,7 +1,7 @@
|
||||
<title>Новая вкладка</title>
|
||||
<html><head><title>Новая вкладка</title></head>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<center><table width=750><td width=20><td width=220 valign=top><pre style="margin-top:0">
|
||||
<table><tr><td width=20><td width=220><pre>
|
||||
_____________________
|
||||
|# : : #|
|
||||
| : WebView : |
|
||||
@@ -14,21 +14,23 @@
|
||||
| || | | |
|
||||
\_____||__|________|__|<font color=#DDD>lc</font>
|
||||
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 2.0 <font bg=#47D018> compatible
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 1.0 <font bg=#47D018> compatable
|
||||
<font bg=#3CE7FF> </font></font></font></font>
|
||||
<td>
|
||||
|
||||
<kosicon n=6><a href="//ftp.kolibrios.org/users/Leency/test_files/showroom.htm">Шоурум</a>
|
||||
<kosicon n=73><a href="//builds.kolibrios.org/ci/latest/">Ночные сборки</a>
|
||||
|
||||
<td><pre>
|
||||
<kosicon n=58><a href=//kolibrios.org>Официальный сайт</a> <kosicon n=17><a href="//apps.kolibrios.org">Центр Приложений</a> <kosicon n=5><a href="//ftp.kolibrios.org/users/Leency/index.htm">Leency's</a>
|
||||
|
||||
</pre>
|
||||
<kosicon n=58><a href=//kolibrios.org>Официальный сайт</a>
|
||||
<kosicon n=50><a href="//builds.kolibrios.org">Ночные сборки</a>
|
||||
<kosicon n=50><a href="//ftp.kolibrios.org">FTP сервер</a>
|
||||
<pre>
|
||||
|
||||
Кстати,
|
||||
<font color="#555">• Из главного меню можно проверить наличие обновлений.
|
||||
<font color="#555555">• Из главного меню можно проверить наличие обновлений
|
||||
• Для поиска в Google наберите тест в адресной строке и нажмите Ctrl+Enter
|
||||
• Клавиша F6 перемещает текстовый курсор в омнибокс.
|
||||
• Кодировку страницы можно изменить в правом нижнем углу окна.
|
||||
• Нужна лучшая поддержка вэба? Используйте Netsurf или WebKit.
|
||||
• Можно изменить кодировку страницы, для этого нажмите на надпись в правом нижнем углу окна.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +42,7 @@
|
||||
' |
|
||||
() .-.,="``"=. - o -
|
||||
'=/_ \ |
|
||||
* | '=.<a href=//ftp.kolibrios.org/users/Leency/test_files/links.htm> </a> |
|
||||
* | '=._ |
|
||||
\ `=./`, '
|
||||
. '=.__.=' `=' *
|
||||
+ +
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<title>Page not found</title>
|
||||
<body bgcolor=#ccc>
|
||||
<br>
|
||||
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff>
|
||||
<h3><kosicon n=67> Webpage Is Not Available</h3>
|
||||
<pre>
|
||||
If there is no Internet connection, the easiest way to get one is to plug a phone into this PC/laptop over a USB cable and enable USB tethering (modem mode) on the phone.
|
||||
|
||||
List of supported Ethernet network cards is <a target="_blank" href="file:///sys/welcome.htm#net">here</a>. WiFi is available when connecting the ISO.
|
||||
|
||||
Other possible causes of the error:
|
||||
• Check the page address for any typos.
|
||||
• The server is temporarily unavailable.
|
||||
• The browser did not handle the server's response properly.
|
||||
|
||||
<kosicon n=4><a href="file:///sys/network/netstat">Netstat</a> <kosicon n=24><a href="file:///sys/network/ping">Ping</a>
|
||||
<html>
|
||||
<head><title>Page not found</title></head>
|
||||
<body>
|
||||
<h1> Webpage Not Available</h1>
|
||||
<h2> What could be done:</h2>
|
||||
<ul>
|
||||
<li>Make sure that you have a working Internet connection.<br>
|
||||
Open <a href="/sys/network/netcfg">Netcfg</a> network diagnostic tool.<br>
|
||||
<li>Check the page address for any typos.<br>
|
||||
<li>Server is temporarily unavailable.<br>
|
||||
Refresh the page.<br>
|
||||
<li>Browser didn't properly handle the server's response.<br>
|
||||
Please, report an error.<br>
|
||||
</ul>
|
||||
@@ -1,16 +0,0 @@
|
||||
<title>Pagina no encontrada</title>
|
||||
<body bgcolor=#ccc>
|
||||
<br>
|
||||
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff>
|
||||
<h3><kosicon n=67> La pagina no se puede mostrar</h3>
|
||||
<pre>
|
||||
Si no hay conexion a Internet, puede conectar un telefono a este PC por USB y activar en el telefono el anclaje a red (modo modem).
|
||||
|
||||
Vea la lista de tarjetas de red Ethernet compatibles <a target="_blank" href="file:///sys/welcome.htm#net">aqui</a>. El WiFi funciona al arrancar desde la ISO.
|
||||
|
||||
Otras causas posibles del error:
|
||||
• Revise la direccion de la pagina por si tiene erratas.
|
||||
• El servidor no responde en este momento.
|
||||
• El navegador no entiende la respuesta del servidor.
|
||||
|
||||
<kosicon n=4><a href="file:///sys/network/netstat">Netstat</a> <kosicon n=24><a href="file:///sys/network/ping">Ping</a>
|
||||
@@ -1,16 +1,17 @@
|
||||
<meta charset="cp-866"><title>Страница не найдена</title>
|
||||
<body bgcolor=#ccc>
|
||||
<br>
|
||||
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff>
|
||||
<h3><kosicon n=67> Веб-страница недоступна</h3>
|
||||
<pre>
|
||||
Если отсутствует связь с Интернетом, есть простой способ её установить. Подключите телефон в режиме Модема (Tethering) через шнур USB к данному ПК/ноутбуку.
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="cp-866">
|
||||
<title>Страница не найдена</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1> Веб-страница недоступна <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAdCAMAAAD8QJ61AAAAIVBMVEUBAAAAAAAAgAAAgIAAAIAAAP+AgICZmZnAwMD/mZn///8vq9FJAAAAAXRSTlMAQObYZgAAAORJREFUeNpiGHQAUCcZoEgMwzAQyXbj9f8ffMJp7xz2Om2dgAaUQLF5j8vKBPAS5xWk6eM/iuKqcD7gK6+rjMEXAwqvkX61ICuSwo2uR5jjKBBkCA1ZJDiFrEqahzMIaAgnMG9AjwyBtdAVdO1uwVSQln0GCQvdsU4hkg2WgFhDSBl5DaEZgmVF1ZlPA2kxG0pjL4/QxnWZbQE9ekHHWJHRUMLns260ldA27RCw+m1BGwg+wu5odsM9LYZx3gF7hocwCYFfjr9Vhh5juBb3EQp0rYKgN91/Cu34iGdBHwT729V/8Q+bbAkSfuA/hwAAAABJRU5ErkJggg=="></h1>
|
||||
|
||||
Список поддерживаемых сетевых карт Ethernet <a target="_blank" href="file:///sys/welcome.htm#net">здесь</a>. WiFi доступен при подключении ISO.
|
||||
|
||||
Другие возможные причины ошибки:
|
||||
• Опечатка в адресе страницы.
|
||||
• Сервер временно недоступен.
|
||||
• Браузер не правильно обработал ответ сервера.
|
||||
|
||||
<kosicon n=4><a href="file:///sys/network/netstat">Netstat</a> <kosicon n=24><a href="file:///sys/network/ping">Ping</a>
|
||||
<ul>
|
||||
<li>Проверьте адресс страницы, возможно, была сделана опечатка.<br>
|
||||
<li>Сервер временно недоступен. Штош поделать...<br>
|
||||
<li>Убедитесь, что есть подключение к сети Интернет.<br>
|
||||
В Колибри поддерживается только подключение по кабелю (WiFi не работает). Если кабель подключен, проверьте установлен ли драйвер сетевой карты. Для этого откройте <a href="/sys/network/netcfg">Netcfg</a> и убедитесь, что в колонке DRIVER указано имя двайвера.<br>
|
||||
Список поддерживаемых сетевых карт <a target="_blank" href="/kolibrios/res/guide/hardware_support.htm#net">тут</a>.<br>
|
||||
<li>Текстовый браузер не правильно обработал ответ сервера.<br>
|
||||
Сообщите об ошибке.<br>
|
||||
</ul>
|
||||
@@ -0,0 +1,39 @@
|
||||
<html>
|
||||
<title>TEST</title>
|
||||
<style>a {display:block;font-size:130%;margin:5px 40px 5px 0;}</style>
|
||||
<body>
|
||||
|
||||
<pre><h2>HTTP</h2>
|
||||
<a href=//th99.infania.net>Total Hardware 1999</a>
|
||||
<a href=//wiby.org>wiby.org</a>
|
||||
<a href=//menuetos.net>menuetos.net</a>
|
||||
<a href=//macintosh.garden/>Macintosh Garden</a>
|
||||
<a href=//pci-ids.ucw.cz>pci.ids</a>
|
||||
<a href=//toastytech.com/guis/index.html>GUI Gallery</a>
|
||||
<a href=//www.weitek.com/textual/support/driver.html>weitek</a>
|
||||
<a href=//www.abandonwarering.com/?Page=Listing>abandonware</a>
|
||||
<a href=//neonfloppy.sytes.net/articles/win3-usb/>neonfloppy</a>
|
||||
<a href=//www.win31.de/eindex.htm>win31.de</a>
|
||||
|
||||
<h3>VPN-only</h3>
|
||||
<a href=//dgmag.in>dgmag.in</a>
|
||||
<a href=//fdd5-25.pdp-11.ru>fdd5-25</a>
|
||||
<a href=//old-dos.ru>old-dos.ru</a>
|
||||
<a href=//artcon.ru>artcon.ru</a>
|
||||
<a href=//nubo.ru>nubo.ru</a>
|
||||
<a href=//mirrors.pdp-11.ru>mirrors.pdp-11.ru</a>
|
||||
<a href=//nostalgy.net.ru/>nostalgy.net.ru</a>
|
||||
|
||||
<h2>HTTPS :(</h2>
|
||||
<a href=//os-menuet.narod.ru>os-menuet.narod.ru</a>
|
||||
<a href=//mestack.narod.ru/index.html>mestack.narod.ru</a>
|
||||
<a href=//coolthemes.narod.ru/indexold.html>coolthemes.narod.ru</a>
|
||||
<a href=//lionovsky.us>lionovsky.us</a>
|
||||
<a href=//acmp.ru/index.asp?main=tasks>acmp.ru</a>
|
||||
<a href=//xn--80abh7bk0c.xn--p1ai>bashorg</a>
|
||||
<a href=//opennet.ru>opennet</a>
|
||||
<a href=//linux.org.ru>LOR</a>
|
||||
<a href=//www.xlr8yourmac.com/OSX/os_x_network_cards.html>OSX network cards</a>
|
||||
<a href=//samlib.ru/b>samlib.ru/b</a>
|
||||
<a href=//vetusware.com>vetusware.com</a>
|
||||
<a href=//electromyne.de>electromyne.de</a>
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
qemu-system-i386 -m 512 -fda kolibri.img -boot a -vga vmware -net nic,model=rtl8139 -net user -soundhw ac97 -usb -usbdevice tablet -enable-kvm -drive file=fat:rw:.
|
||||
+9
-14
@@ -1,15 +1,10 @@
|
||||
_ini ini;
|
||||
|
||||
void LoadIniConfig()
|
||||
{
|
||||
ini.path = GetIni(#settings_file, "app.ini");
|
||||
ini.section = "WebView";
|
||||
|
||||
ini.GetString("proxy_address", #proxy_address, sizeof(proxy_address), NULL);
|
||||
ini.GetString("search_engine", #search_engine, sizeof(search_engine), "https://lite.duckduckgo.com/lite/?q=");
|
||||
IF (ini.GetInt("small_font", 0)) font_set_small(true);
|
||||
// SVG/WebP converter, wsrv.nl parameters are appended; empty: such pictures are skipped
|
||||
ini.GetString("image_converter", #image_converter, sizeof(image_converter), "http://wsrv.nl/?url=");
|
||||
IF (!ini.GetInt("show_images", 1)) show_images = false;
|
||||
IF (!ini.GetInt("show_css", 1)) show_css = false;
|
||||
_ini ini;
|
||||
|
||||
void LoadIniConfig()
|
||||
{
|
||||
ini.path = GetIni(#settings_file, "app.ini");
|
||||
ini.section = "WebView";
|
||||
|
||||
ini.GetString("proxy_address", #proxy_address, sizeof(proxy_address), NULL);
|
||||
ini.GetString("search_engine", #search_engine, sizeof(search_engine), "http://bing.com/search?q=");
|
||||
}
|
||||
+43
-49
@@ -1,100 +1,94 @@
|
||||
enum { TAG=1, VALUE, TEXT, COMMENT, SCRIPT};
|
||||
|
||||
int opened_font_counter;
|
||||
int opened_font_counter=0;
|
||||
int mode;
|
||||
|
||||
dword source_buf_start, source_buf_end, source_buf_cap;
|
||||
|
||||
// a single input byte expands up to ~30 bytes of markup: grow the buffer instead of
|
||||
// guessing its size. Every piece written at once is shorter than 256 bytes.
|
||||
void SourceBufReserve()
|
||||
{
|
||||
dword used = source_buf_end - source_buf_start;
|
||||
dword room = source_buf_cap - used;
|
||||
IF (room > 256) RETURN;
|
||||
source_buf_cap += source_buf_cap;
|
||||
source_buf_start = realloc(source_buf_start, source_buf_cap);
|
||||
source_buf_end = source_buf_start + used;
|
||||
}
|
||||
dword source_buf_end;
|
||||
|
||||
void SourceBufAdd(dword _mode, src)
|
||||
{
|
||||
IF (_mode) mode = _mode;
|
||||
dword font_found_pointer, src_orig = src;
|
||||
|
||||
if (_mode) mode = _mode;
|
||||
|
||||
SourceBufReserve();
|
||||
strcpy(source_buf_end, src);
|
||||
source_buf_end += strlen(source_buf_end);
|
||||
|
||||
IF (strstr(src, "</font>")) opened_font_counter--;
|
||||
IF (strstr(src, "<font ")) opened_font_counter++;
|
||||
if (font_found_pointer = strstr(src, "</font>")) {
|
||||
opened_font_counter--;
|
||||
src = font_found_pointer+2;
|
||||
}
|
||||
|
||||
src = src_orig;
|
||||
if (font_found_pointer = strstr(src, "<font ")) {
|
||||
opened_font_counter++;
|
||||
src = font_found_pointer+2;
|
||||
}
|
||||
}
|
||||
|
||||
void CloseAllOpenedFonts(dword _mode)
|
||||
{
|
||||
WHILE (opened_font_counter) SourceBufAdd(_mode, "</font>");
|
||||
while (opened_font_counter) SourceBufAdd(_mode, "</font>");
|
||||
}
|
||||
|
||||
void ShowSource(dword _bufdata, _in_bufsize)
|
||||
dword ShowSource(dword _bufdata, _in_bufsize)
|
||||
{
|
||||
dword i;
|
||||
dword i, j;
|
||||
bool activate_script_mode = false;
|
||||
dword source_buf_start;
|
||||
|
||||
opened_font_counter=0;
|
||||
mode = TEXT;
|
||||
source_buf_cap = _in_bufsize * 2;
|
||||
source_buf_cap += 4096;
|
||||
source_buf_start = malloc(source_buf_cap);
|
||||
source_buf_start = malloc(_in_bufsize*5);
|
||||
source_buf_end = source_buf_start;
|
||||
|
||||
SourceBufAdd(TEXT, "<html><head><title>View Source</title></head><body><pre>");
|
||||
SourceBufAdd(TEXT, "<html><head><title>View Source</title><body><pre>");
|
||||
|
||||
for (i=_bufdata; i<_bufdata+_in_bufsize; i++) switch (DSBYTE[i])
|
||||
for (i=_bufdata; i<_bufdata+_in_bufsize; i++) switch (ESBYTE[i])
|
||||
{
|
||||
CASE '<':
|
||||
IF (!strncmp(i+1,"!--", 3)) SourceBufAdd(COMMENT, "<font color=#bbb><");
|
||||
else IF (SCRIPT == mode) {
|
||||
IF (!strncmp(i+1,"/script>", 8)) {
|
||||
case '<':
|
||||
if (!strncmp(i+1,"!--", 3)) SourceBufAdd(COMMENT, "<font color=#bbb><");
|
||||
else if (SCRIPT == mode) {
|
||||
if (!strncmp(i+1,"/script>", 8)) {
|
||||
CloseAllOpenedFonts(NULL);
|
||||
SourceBufAdd(TAG, "<font color=#00f><");
|
||||
}
|
||||
ELSE SourceBufAdd(NULL, "<");
|
||||
else SourceBufAdd(NULL, "<");
|
||||
}
|
||||
ELSE IF (COMMENT == mode) {
|
||||
else if (COMMENT == mode) {
|
||||
SourceBufAdd(NULL, "<");
|
||||
}
|
||||
ELSE IF (TEXT == mode) {
|
||||
IF (!strncmp(i+1,"script", 6)) activate_script_mode = true;
|
||||
else if (TEXT == mode) {
|
||||
if (!strncmp(i+1,"script", 6)) activate_script_mode = true;
|
||||
SourceBufAdd(TAG, "<font color=#00f><");
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
IF (TAG == mode) && (activate_script_mode) {
|
||||
if (TAG == mode) && (activate_script_mode) {
|
||||
activate_script_mode = false;
|
||||
SourceBufAdd(SCRIPT, "></font><font color=#994500>");
|
||||
}
|
||||
ELSE IF (VALUE == mode) CloseAllOpenedFonts(TEXT);
|
||||
ELSE IF (COMMENT == mode) && (!strncmp(i-2,"--", 2)) {
|
||||
else if (VALUE == mode) CloseAllOpenedFonts(TEXT);
|
||||
else if (COMMENT == mode) && (!strncmp(i-2,"--", 2)) {
|
||||
SourceBufAdd(TEXT, ">");
|
||||
CloseAllOpenedFonts(TEXT);
|
||||
}
|
||||
ELSE IF (COMMENT == mode) || (SCRIPT == mode) SourceBufAdd(NULL, ">");
|
||||
ELSE IF (TAG == mode) SourceBufAdd(TEXT, "></font>");
|
||||
BREAK;
|
||||
else if (COMMENT == mode) || (SCRIPT == mode) SourceBufAdd(NULL, ">");
|
||||
else if (TAG == mode) SourceBufAdd(TEXT, "></font>");
|
||||
break;
|
||||
case '&':
|
||||
SourceBufAdd(NULL, "&");
|
||||
BREAK;
|
||||
break;
|
||||
case '\"':
|
||||
case '\'':
|
||||
IF (TAG == mode) SourceBufAdd(VALUE, "<font color=#F301F2>"");
|
||||
ELSE IF (VALUE == mode) SourceBufAdd(TAG, ""</font>");
|
||||
ELSE SourceBufAdd(NULL, """);
|
||||
BREAK;
|
||||
if (TAG == mode) SourceBufAdd(VALUE, "<font color=#F301F2>"");
|
||||
else if (VALUE == mode) SourceBufAdd(TAG, ""</font>");
|
||||
else SourceBufAdd(NULL, """);
|
||||
break;
|
||||
default:
|
||||
SourceBufReserve();
|
||||
DSBYTE[source_buf_end] = DSBYTE[i];
|
||||
ESBYTE[source_buf_end] = ESBYTE[i];
|
||||
source_buf_end++;
|
||||
}
|
||||
DSBYTE[source_buf_end] = 0;
|
||||
ESBYTE[source_buf_end] = 0;
|
||||
LoadInternalPage(source_buf_start, source_buf_end-source_buf_start);
|
||||
free(source_buf_start);
|
||||
}
|
||||
+45
-107
@@ -5,7 +5,7 @@
|
||||
// //
|
||||
//===================================================//
|
||||
|
||||
// TABS_MAX is defined in engine/engine.h (the engine keeps a document per tab too)
|
||||
#define TABS_MAX 5
|
||||
|
||||
TWebBrowser tabdata[TABS_MAX+1];
|
||||
_history tabstory[TABS_MAX+1];
|
||||
@@ -23,59 +23,39 @@ struct TAB
|
||||
|
||||
bool TAB::add()
|
||||
{
|
||||
IF (count==TABS_MAX) RETURN false;
|
||||
if (count==TABS_MAX) return false;
|
||||
save_state();
|
||||
count++;
|
||||
active = count-1;
|
||||
history = tabstory[active];
|
||||
engine_new_tab();
|
||||
RETURN true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TAB::close(int _tab_number)
|
||||
{
|
||||
int i;
|
||||
IF (count==1) return false;
|
||||
save_state();
|
||||
engine_free_tab(_tab_number, active);
|
||||
IF (tabdata[_tab_number].bufpointer) free(tabdata[_tab_number].bufpointer);
|
||||
IF (tabdata[_tab_number].o_bufpointer) free(tabdata[_tab_number].o_bufpointer);
|
||||
// the history and the image list of the closed tab (collection::drop() keeps
|
||||
// the offset buffer: freed by hand); the globals are reloaded from the active
|
||||
// slot right after
|
||||
history = tabstory[_tab_number];
|
||||
history.items.drop();
|
||||
free(history.items.offset.buf);
|
||||
history = tabstory[active];
|
||||
WB1 = tabdata[_tab_number];
|
||||
WB1.img_url.drop();
|
||||
IF (WB1.img_url.offset.buf) free(WB1.img_url.offset.buf);
|
||||
WB1 = tabdata[active];
|
||||
if (count==1) return false;
|
||||
for (i=_tab_number; i<TABS_MAX; i++) {
|
||||
tabdata[i] = tabdata[i+1];
|
||||
tabstory[i] = tabstory[i+1];
|
||||
tabdom[i] = tabdom[i+1];
|
||||
tabpool[i] = tabpool[i+1];
|
||||
}
|
||||
IF (_tab_number<active) && (active>0) active--;
|
||||
IF (active==count-1) && (active>0) active--;
|
||||
if (_tab_number<active) && (active>0) active--;
|
||||
if (active==count-1) && (active>0) active--;
|
||||
count--;
|
||||
RETURN true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TAB::save_state()
|
||||
{
|
||||
tabstory[active] = history;
|
||||
tabdata[active] = WB1;
|
||||
engine_save_tab(active);
|
||||
}
|
||||
|
||||
void TAB::restore(int _id)
|
||||
{
|
||||
tab.active = _id;
|
||||
WB1 = tabdata[_id];
|
||||
WB1 = tabdata[_id];
|
||||
history = tabstory[_id];
|
||||
engine_restore_tab(_id);
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
@@ -89,9 +69,9 @@ int tab_w = DEFAULT_TABW;
|
||||
|
||||
int GetTabWidth()
|
||||
{
|
||||
IF (tab.count == TABS_MAX) RETURN Form.cwidth / tab.count;
|
||||
IF (tab.count * DEFAULT_TABW + TAB_H < Form.cwidth) RETURN DEFAULT_TABW; ELSE
|
||||
RETURN Form.cwidth - TAB_H - 2 / tab.count;
|
||||
if (tab.count == TABS_MAX) return Form.cwidth / tab.count;
|
||||
if (tab.count * DEFAULT_TABW + TAB_H < Form.cwidth) return DEFAULT_TABW; else
|
||||
return Form.cwidth - TAB_H - 2 / tab.count;
|
||||
}
|
||||
|
||||
void DrawTab(int _id)
|
||||
@@ -99,28 +79,20 @@ void DrawTab(int _id)
|
||||
#define CLOSE_S 13
|
||||
dword bgcol, border_bottom_color;
|
||||
char header_no_version[sizeof(TWebBrowser.header)];
|
||||
char name[64];
|
||||
char name[DEFAULT_TABW/6];
|
||||
int xxx = _id * tab_w;
|
||||
int nlen;
|
||||
|
||||
IF (_id==tab.active) {
|
||||
if (_id==tab.active) {
|
||||
tab.save_state();
|
||||
bgcol = sc.light;
|
||||
border_bottom_color = sc.light;
|
||||
} ELSE {
|
||||
} else {
|
||||
bgcol=sc.work;
|
||||
border_bottom_color = sc.line;
|
||||
}
|
||||
name[0] = 0; // a tab still waiting for its page has no header yet
|
||||
IF (tabdata[_id].header) {
|
||||
if (tabdata[_id].header) {
|
||||
strncpy(#header_no_version, #tabdata[_id].header, strlen(#tabdata[_id].header)-sizeof(version)-2);
|
||||
// with TABS_MAX tabs the width is a share of the window: never wider than name[]
|
||||
nlen = tab_w - CLOSE_S;
|
||||
nlen /= 6;
|
||||
nlen -= 2;
|
||||
IF (nlen > 63) nlen = 63;
|
||||
IF (nlen < 0) nlen = 0;
|
||||
strncpy(#name, #header_no_version, nlen);
|
||||
strncpy(#name, #header_no_version, tab_w-CLOSE_S/6-2);
|
||||
}
|
||||
DrawBar(xxx, TOOLBAR_H, 1, TAB_H, sc.dark);
|
||||
DrawBar(xxx+1, TOOLBAR_H, tab_w-1, TAB_H-1, bgcol);
|
||||
@@ -128,40 +100,33 @@ void DrawTab(int _id)
|
||||
DefineHiddenButton(xxx, TOOLBAR_H-1, tab_w, TAB_H, TAB_ID+_id);
|
||||
WriteTextCenter(xxx, TOOLBAR_H+6, tab_w-CLOSE_S, sc.work_text, #name);
|
||||
|
||||
DefineHiddenButton(xxx+tab_w-CLOSE_S-5, TOOLBAR_H+2, CLOSE_S+2, CLOSE_S+2, TAB_CLOSE_ID+_id);
|
||||
WriteText(xxx+tab_w-CLOSE_S-2, TOOLBAR_H+1, 0x81, sc.dark, "x");
|
||||
DefineHiddenButton(xxx+tab_w-CLOSE_S-3, TOOLBAR_H+3, CLOSE_S-1, CLOSE_S-1, TAB_CLOSE_ID+_id);
|
||||
DrawBar(xxx+tab_w-CLOSE_S-3, TOOLBAR_H+3, CLOSE_S, CLOSE_S, sc.dark);
|
||||
WriteText(xxx+tab_w-CLOSE_S+1, TOOLBAR_H+5, 0x80, sc.light, "x");
|
||||
}
|
||||
|
||||
void DrawActiveTab()
|
||||
{
|
||||
IF (tab_w == GetTabWidth()) DrawTab(tab.active);
|
||||
ELSE DrawTabsBar();
|
||||
}
|
||||
|
||||
// a raised button with one character: the new tab "+", the find bar "<" and ">"
|
||||
void DrawRaisedButton(int _x, _y, _id, dword _text)
|
||||
{
|
||||
dword btn_light = MixColors(sc.button, 0xFFFfff, 220);
|
||||
dword btn_dark = MixColors(sc.button, 0, 180);
|
||||
int ty;
|
||||
ty = TAB_H - 14 / 2; // (_h - 14) / 2: no precedence in C--
|
||||
DrawBar(_x, _y, TAB_H, TAB_H-1, sc.button);
|
||||
DrawRectangle3D(_x, _y, TAB_H, TAB_H-1, btn_light, btn_dark);
|
||||
PutPixel(_x+TAB_H, _y, btn_dark);
|
||||
DefineHiddenButton(_x, _y, TAB_H-1, TAB_H-1-1, _id);
|
||||
WriteText(_x+6, _y+ty-1, 0x90, sc.button_text, _text);
|
||||
if (tab_w == GetTabWidth()) DrawTab(tab.active);
|
||||
else DrawTabsBar();
|
||||
}
|
||||
|
||||
int DrawNewTabButton()
|
||||
{
|
||||
dword btn_light = MixColors(sc.button, 0xFFFfff, 220);
|
||||
dword btn_dark = MixColors(sc.button, 0, 180);
|
||||
int xxx = tab.count * tab_w;
|
||||
|
||||
IF (tab.count < TABS_MAX) {
|
||||
if (tab.count < TABS_MAX) {
|
||||
DrawBar(xxx, TOOLBAR_H, 1, TAB_H, sc.line);
|
||||
DrawRaisedButton(xxx+1, TOOLBAR_H, NEW_TAB, "+");
|
||||
RETURN xxx + TAB_H + 2;
|
||||
} ELSE {
|
||||
RETURN xxx;
|
||||
DrawBar(xxx+1, TOOLBAR_H, TAB_H, TAB_H-1, sc.button);
|
||||
DrawRectangle3D(xxx+1, TOOLBAR_H, TAB_H, TAB_H-1, btn_light, btn_dark);
|
||||
PutPixel(xxx+1+TAB_H, TOOLBAR_H, btn_dark);
|
||||
DefineHiddenButton(xxx+1, TOOLBAR_H, TAB_H-1, TAB_H-1, NEW_TAB);
|
||||
WriteText(xxx+7, TOOLBAR_H+2, 0x90, sc.button_text, "+");
|
||||
return xxx + TAB_H + 2;
|
||||
} else {
|
||||
return xxx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,43 +134,26 @@ void DrawTabsBar()
|
||||
{
|
||||
dword i;
|
||||
tab_w = GetTabWidth();
|
||||
FOR (i=0; i<tab.count; i++) DrawTab(i);
|
||||
for (i=0; i<tab.count; i++) DrawTab(i);
|
||||
i = DrawNewTabButton();
|
||||
DrawBar(i, TOOLBAR_H, Form.cwidth-i, TAB_H-1, MixColors(sc.dark, sc.work, 128));
|
||||
DrawBar(i, TOOLBAR_H+TAB_H-1, Form.cwidth-i, 1, sc.line);
|
||||
}
|
||||
|
||||
// the tab just restored: laid out for this window and shown
|
||||
void ShowRestoredTab()
|
||||
{
|
||||
SetElementSizes();
|
||||
WB1.Reparse();
|
||||
page_draw_pending = false; // the canvas holds the other tab
|
||||
WB1.DrawPage();
|
||||
GetMissing();
|
||||
SetOmniboxText(history.current());
|
||||
}
|
||||
|
||||
void EventTabClose(int _id)
|
||||
{
|
||||
IF (_id < 0) || (_id >= tab.count) return;
|
||||
DeleteButton(TAB_ID + tab.count-1);
|
||||
DeleteButton(TAB_CLOSE_ID + tab.count-1);
|
||||
IF (_id == tab.active) {
|
||||
// a page still arriving must not land in the next tab (Ctrl+W, the close button),
|
||||
// nor its stylesheets and pictures: after the page itself only img_loading is set
|
||||
IF (http.transfer) || (img_loading) {
|
||||
page_draw_pending = false; // StopImgDownloads() would draw the closing tab
|
||||
StopLoading();
|
||||
}
|
||||
if (_id == tab.active) {
|
||||
tab.close(_id);
|
||||
tab.restore(tab.active);
|
||||
ShowRestoredTab();
|
||||
} ELSE {
|
||||
SetElementSizes();
|
||||
WB1.Reparse();
|
||||
WB1.DrawPage();
|
||||
SetOmniboxText(history.current());
|
||||
} else {
|
||||
tab.close(_id);
|
||||
}
|
||||
DrawTabsBar();
|
||||
status_text[0] = 0; // it spoke of the other tab
|
||||
DrawStatusBar(NULL);
|
||||
}
|
||||
|
||||
@@ -216,35 +164,25 @@ void EventCloseActiveTab()
|
||||
|
||||
void EventTabClick(int _id)
|
||||
{
|
||||
IF (http.transfer) RETURN; // evNetwork works on the globals of the active tab
|
||||
IF (_id>=tab.count) _id = 0;
|
||||
IF (_id==-1) _id = tab.count-1;
|
||||
if (_id>=tab.count) _id = 0;
|
||||
if (_id==-1) _id = tab.count-1;
|
||||
tab.save_state();
|
||||
tab.restore(_id);
|
||||
DrawTabsBar();
|
||||
ShowRestoredTab();
|
||||
status_text[0] = 0; // it spoke of the other tab
|
||||
SetElementSizes();
|
||||
WB1.Reparse();
|
||||
WB1.DrawPage();
|
||||
SetOmniboxText(history.current());
|
||||
DrawStatusBar(NULL);
|
||||
}
|
||||
|
||||
void EventOpenNewTab(dword _url)
|
||||
{
|
||||
// with every tab taken the page would replace the current one
|
||||
IF (tab.count >= TABS_MAX) { notify(T_NO_FREE_TAB); RETURN; }
|
||||
tab.add();
|
||||
OpenPage(_url);
|
||||
DrawTabsBar();
|
||||
}
|
||||
|
||||
// the "+" button and Ctrl+T: an empty tab waits for an address
|
||||
void EventNewBlankTab()
|
||||
{
|
||||
// Ctrl+T is not hidden with the "+" button: the omnibox of the current tab stays as it is
|
||||
IF (tab.count >= TABS_MAX) { notify(T_NO_FREE_TAB); RETURN; }
|
||||
EventOpenNewTab(URL_SERVICE_HOMEPAGE);
|
||||
OmniboxSelectAll();
|
||||
}
|
||||
|
||||
void EventActivateNextTab()
|
||||
{
|
||||
EventTabClick(tab.active+1);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<html><head><title>position / negative margins</title><style>
|
||||
#wrap { position: relative; width: 600px; background: #eef; padding: 6px; }
|
||||
#side { position: absolute; top: 40px; left: 8px; width: 150px; background: #fdd; }
|
||||
#main { margin-left: 170px; width: 400px; background: #dfd; }
|
||||
#pulled { margin-top: -30px; margin-left: 170px; width: 400px; background: #ffd; }
|
||||
.row { position: relative; background: #e8e8e8; margin-bottom: 2px; }
|
||||
.row .size { position: absolute; right: 4px; }
|
||||
.corner { position: absolute; right: 6px; top: 6px; background: #ddf; }
|
||||
</style></head><body>
|
||||
<div id="wrap">
|
||||
<div class="corner">pinned right</div>
|
||||
<h3>header of the box</h3>
|
||||
<div id="side">sidebar out of the flow, it must not push the main column down</div>
|
||||
<div id="main">main column, shifted right by a margin. It has to sit beside the sidebar and wrap inside its own 400 pixels.</div>
|
||||
<div id="pulled">this block is pulled up 30px by a negative top margin</div>
|
||||
<div class="row"><span>file-one.zip</span><span class="size">8.21M</span></div>
|
||||
<div class="row"><span>another-file-with-a-longer-name.rar</span><span class="size">640K</span></div>
|
||||
</div>
|
||||
<p>text after the box, in the normal flow</p>
|
||||
</body></html>
|
||||
@@ -1,30 +0,0 @@
|
||||
<html><head><title>Basic layout test</title>
|
||||
<style>
|
||||
.red { color: red; }
|
||||
#box { background-color: #ddeeff; padding: 6px; width: 60%; }
|
||||
p.right { text-align: right; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h1>Heading one</h1>
|
||||
<p>A paragraph with <b>bold</b>, <u>underlined</u>, <s>struck</s>, <i>italic</i> and <a href="http://kolibrios.org">a link to kolibrios.org</a>. Some more words so that the paragraph wraps onto several lines when the window is not very wide, and even more words to be sure.</p>
|
||||
<h2>Heading two</h2>
|
||||
<p class="right">Right aligned paragraph via CSS class.</p>
|
||||
<div id="box">A div with id box: light blue background, padding, 60% width. <span class="red">Red span inside.</span></div>
|
||||
<h3>Lists</h3>
|
||||
<ul><li>first bullet</li><li>second bullet with <code>code text</code></li>
|
||||
<li>nested:<ol><li>one</li><li>two</li></ol></li></ul>
|
||||
<blockquote>Blockquote text is indented from the left edge.</blockquote>
|
||||
<pre>pre keeps spaces
|
||||
and line breaks
|
||||
indented line</pre>
|
||||
<hr>
|
||||
<p align=center>Centered paragraph with an image placeholder <img src="missing.png" alt="missing picture"> and <kosicon n=6> icon.</p>
|
||||
<p>Entities: <tag> & "quoted" © — «text» end.</p>
|
||||
<button>Push me</button> <input type=text value="typed"> <input type=checkbox checked> <select><option>Choice A</option><option>B</option></select>
|
||||
<p>Last paragraph. <a href="#top">anchor link</a></p>
|
||||
</body></html>
|
||||
<!-- css regression: descendant selector, display:none, style attribute -->
|
||||
<style>div.note b { color: green; } .hidden { display: none; }</style>
|
||||
<div class="note">Descendant selector: <b>green bold</b>, <span style="background-color: yellow; font-weight: bold">inline style</span>.</div>
|
||||
<p class="hidden">THIS MUST NOT BE VISIBLE</p>
|
||||
<p>End of page.</p>
|
||||
@@ -1,20 +0,0 @@
|
||||
<html><head><title>calc()</title>
|
||||
<style>
|
||||
div{border-left:1px solid #609a21;margin:6px 0}
|
||||
#a{padding-left:calc(1.25em - 1px)}
|
||||
#b{padding-left:calc(10px + 2 * 5px)}
|
||||
#c{padding-left:calc( -5px + 45px )}
|
||||
#d{padding:0 0 0 calc(100px / 2)}
|
||||
#e{padding-left:calc(20px*3)}
|
||||
#f{padding-left:calc(100% - 20px)}
|
||||
#g{padding-left:CALC(2EM + 2PX)}
|
||||
</style></head><body>
|
||||
<div>no padding (0)</div>
|
||||
<div id=a>calc(1.25em - 1px) = 16px</div>
|
||||
<div id=b>calc(10px + 2 * 5px) = 20px</div>
|
||||
<div id=c>calc( -5px + 45px ) = 40px</div>
|
||||
<div id=d>padding shorthand calc(100px / 2) = 50px</div>
|
||||
<div id=e>calc(20px*3) = 60px</div>
|
||||
<div id=f>calc(100% - 20px): dropped, 0</div>
|
||||
<div id=g>CALC(2EM + 2PX) = 30px</div>
|
||||
</body></html>
|
||||
@@ -1,34 +0,0 @@
|
||||
<html><head><title>Checkboxes</title>
|
||||
<style>
|
||||
#t1{display:none}
|
||||
#box1{display:none;border-left:2px solid #609a21;padding-left:8px}
|
||||
#t1:checked ~ #box1{display:block}
|
||||
.lbl{color:#609a21;cursor:pointer}
|
||||
#fold{display:grid;grid-template-rows:0fr}
|
||||
#t2:checked ~ #fold{grid-template-rows:1fr}
|
||||
#t2{display:none}
|
||||
#row{display:flex;justify-content:center}
|
||||
#row span{order:1;flex:0 0 100%;display:none;text-align:center}
|
||||
#row input:checked+span{display:block}
|
||||
#row input{order:2;appearance:none;width:8px;height:8px;margin:0 8px;border-radius:50%;background:#ccc}
|
||||
#row input:checked{background:#333}
|
||||
</style></head><body>
|
||||
<h3>Native controls</h3>
|
||||
<p><input type=checkbox> unchecked <input type=checkbox checked> checked
|
||||
<input type=checkbox disabled> disabled</p>
|
||||
<p><input type=radio name=r checked> one <input type=radio name=r> two <input type=radio name=r> three</p>
|
||||
<p><label><input type=checkbox> label around (click the text)</label></p>
|
||||
<p><input type=checkbox id=c5> <label for=c5>label for=c5 (click the text)</label></p>
|
||||
|
||||
<h3>Spoiler: display</h3>
|
||||
<input type=checkbox id=t1><label for=t1 class=lbl><u>Show more</u></label>
|
||||
<div id=box1>Hidden text that opens on click.</div>
|
||||
|
||||
<h3>Spoiler: grid 0fr/1fr</h3>
|
||||
<input type=checkbox id=t2><label for=t2 class=lbl><u>Grid fold</u></label>
|
||||
<div id=fold><div>Folded with grid-template-rows.</div></div>
|
||||
|
||||
<h3>Slides</h3>
|
||||
<div id=row><input type=radio name=s id=s1 checked><span><label for=s2>[ Slide 1: click me ]</label></span><input type=radio name=s id=s2><span><label for=s3>[ Slide 2 ]</label></span><input type=radio name=s id=s3><span><label for=s1>[ Slide 3 ]</label></span></div>
|
||||
<p>End.</p>
|
||||
</body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><head><title>css link</title><link rel="stylesheet" href="test.css"></head><body><h1>Heading</h1><p class="note">A note paragraph.</p><p>Plain paragraph.</p></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><head><style>:root{--bg:#123;--fg:#eee}@media (prefers-color-scheme:light){:root{--bg:#ffe;--fg:#123}}body{background:var(--bg);color:var(--fg)}p{color:var(--nope, #c00)}h1{color:var(--nope)}@media print{body{background:#000}}</style></head><body><h1>Vars</h1><p>Fallback red.</p>Body text.</body></html>
|
||||
@@ -1,12 +0,0 @@
|
||||
<html><head><title>details</title>
|
||||
<style>.fx{position:fixed;top:0;background:white;padding:0 8px;display:block}
|
||||
.fx summary{position:absolute;left:22px;top:22px;cursor:pointer;display:block}
|
||||
.fx ul{width:320px;margin-top:65px}</style></head><body>
|
||||
<p>1. Text summary:</p>
|
||||
<details><summary>Click to open</summary><p>Hidden text of details 1.</p></details>
|
||||
<p>2. Open by markup:</p>
|
||||
<details open><summary>Open from the start</summary><p>Text of details 2.</p></details>
|
||||
<p>3. SVG-only summary (bbc.com without scripts):</p>
|
||||
<noscript><details class=fx><summary><svg viewBox="0 0 32 32" width="20" height="20"><path d="M1 7.5h30V1.9H1v5.6z"></path></svg></summary><ul><li><a href="/home">Home</a></li><li><a href="/news">News</a></li></ul></details></noscript>
|
||||
<p>End.</p>
|
||||
</body></html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<html><head><title>CSS display:table</title><style>
|
||||
.tbl { display: table; width: 100%; }
|
||||
.cell { display: table-cell; padding: 6px; }
|
||||
.a { width: 25%; background: #ffe0e0; }
|
||||
.b { width: 50%; background: #e0ffe0; }
|
||||
.c { width: 25%; background: #e0e0ff; }
|
||||
.boxy { display: table; background: #eee; margin: 8px 0; padding: 4px; }
|
||||
</style></head><body>
|
||||
<h2>display:table with table-cell children (anonymous row)</h2>
|
||||
<div class="tbl">
|
||||
<div class="cell a">left column, narrow, wraps a little</div>
|
||||
<div class="cell b">middle column, the wide one, with enough text in it that it has to wrap over several lines</div>
|
||||
<div class="cell c">right column, narrow too</div>
|
||||
</div>
|
||||
<h2>display:table without any cells (plain block)</h2>
|
||||
<div class="boxy">
|
||||
<h3>a heading</h3>
|
||||
<p>This div says display:table but has no cells at all. It must render like a block, not vanish.</p>
|
||||
</div>
|
||||
<p>end of page</p>
|
||||
</body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body><p>Fish & chips, a & b, 1 < 2 > 0, café © © &unknown; x</p><input type="button" value="Next >"><a href="?a=1&b=2" title="T & T">link »</a><img alt="A <B>" src="nope.png"></body></html>
|
||||
@@ -1,38 +0,0 @@
|
||||
<html><head><title>flex</title><style>
|
||||
.row { display: flex; background: #eef; margin-bottom: 10px; }
|
||||
.row > div { background: #cde; padding: 4px; }
|
||||
.gap { gap: 8px; }
|
||||
.grow > div { flex: 1; }
|
||||
.head { display: flex; align-items: center; background: #333; color: #fff; padding: 4px; }
|
||||
.head .logo { font-size: 20px; font-weight: bold; }
|
||||
.head nav { margin-left: auto; }
|
||||
.head a { color: #fff; margin-left: 12px; }
|
||||
.between { justify-content: space-between; }
|
||||
.center { justify-content: center; }
|
||||
.wrap { flex-wrap: wrap; }
|
||||
.wrap > div { width: 200px; margin: 4px; background: #dec; }
|
||||
.tall { height: 60px; }
|
||||
</style></head><body>
|
||||
<p>1 three items in a row, content width</p>
|
||||
<div class="row gap"><div>one</div><div>second item</div><div>third</div></div>
|
||||
|
||||
<p>2 flex: 1 - three equal columns</p>
|
||||
<div class="row grow gap"><div>A</div><div>B with more text in it</div><div>C</div></div>
|
||||
|
||||
<p>3 header: logo left, links pushed right by margin-left: auto, centred vertically</p>
|
||||
<div class="head"><span class="logo">Logo</span><nav><a href="#">Home</a><a href="#">News</a><a href="#">About</a></nav></div>
|
||||
|
||||
<p>4 space-between and center</p>
|
||||
<div class="row between"><div>left</div><div>middle</div><div>right</div></div>
|
||||
<div class="row center"><div>centred</div><div>pair</div></div>
|
||||
|
||||
<p>5 wrap: 200px cards go onto new rows</p>
|
||||
<div class="row wrap"><div>card 1</div><div>card 2</div><div>card 3</div><div>card 4</div><div>card 5</div><div>card 6</div></div>
|
||||
|
||||
<p>6 stretch: the short item is as tall as the tall one</p>
|
||||
<div class="row gap"><div class="tall">tall 60px</div><div>short</div></div>
|
||||
|
||||
<p>7 a long row squeezed: no letter-per-line columns</p>
|
||||
<div class="row"><div>averyveryverylongwordthatcannotbreak</div><div>another long piece of text that wraps inside its item when the row is too narrow for everything</div><div>third item with text</div></div>
|
||||
<p>end</p>
|
||||
</body></html>
|
||||
@@ -1,7 +0,0 @@
|
||||
<html><body><div>
|
||||
<section><table style="float:right;width:200px;border:1px solid"><tr><td>float<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>10<br>11<br>12</td></tr></table>
|
||||
<p>1 a float reaching below its section: this short intro is beside it</p></section>
|
||||
<section><div style="border-bottom:1px solid">2 next section</div>
|
||||
<p>its text must be beside the float too, not below it (Wikipedia infobox and the sections after it)</p></section>
|
||||
<section><p>3 below the float: full width again, more text more text more text more text more text more text</p></section>
|
||||
</div></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><head><style>.col{float:left;width:32%;margin-right:2%;background:#eef}.wide{float:left;width:100%;background:#fee}</style></head><body><div class="col"><h3>One</h3><p>Column one text that wraps around a bit.</p></div><div class="col"><h3>Two</h3><p>Second column.</p></div><div class="col"><h3>Three</h3><p>Third column with more text than the others so it is taller.</p></div><p>Paragraph after the columns.</p><div class="wide">Full width float</div><div class="col">A</div><div class="col">B</div><div class="col">C</div><div class="col">D wraps to next row</div></body></html>
|
||||
@@ -1,75 +0,0 @@
|
||||
<title>Font test</title>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<h3>The four fonts</h3>
|
||||
<p>There are two system fonts, 6x9 and 8x16, each one either as it is or doubled by the
|
||||
kernel. Everything below is one of these four; nothing else can be drawn.</p>
|
||||
<table border=1 cellpadding=4>
|
||||
<tr><th>FONT_*<th>kernel<th>w×h<th>upright<th>italic<th>bold<th>bold italic
|
||||
<tr><td>SMALL<td>6x9<td>6×9
|
||||
<td><span style="font-size:small">Handgloves</span>
|
||||
<td><span style="font-size:small"><i>Handgloves</i></span>
|
||||
<td><span style="font-size:small"><b>Handgloves</b></span>
|
||||
<td><span style="font-size:small"><b><i>Handgloves</i></b></span>
|
||||
<tr><td>NORMAL<td>8x16<td>8×14
|
||||
<td>Handgloves<td><i>Handgloves</i><td><b>Handgloves</b><td><b><i>Handgloves</i></b>
|
||||
<tr><td>MEDIUM<td>6x9 ×2<td>12×17
|
||||
<td><span style="font-size:18px">Handgloves</span>
|
||||
<td><span style="font-size:18px"><i>Handgloves</i></span>
|
||||
<td><span style="font-size:18px"><b>Handgloves</b></span>
|
||||
<td><span style="font-size:18px"><b><i>Handgloves</i></b></span>
|
||||
<tr><td>BIG<td>8x16 ×2<td>16×27
|
||||
<td><span style="font-size:24px">Handgloves</span>
|
||||
<td><span style="font-size:24px"><i>Handgloves</i></span>
|
||||
<td><span style="font-size:24px"><b>Handgloves</b></span>
|
||||
<td><span style="font-size:24px"><b><i>Handgloves</i></b></span>
|
||||
</table>
|
||||
<p>The plain fonts take one 1px step across the middle of the line, the doubled ones a
|
||||
step per third, 2px in all. If the doubled rows look overdone next to the plain ones,
|
||||
or the plain ones too upright, those are the two numbers to change.</p>
|
||||
|
||||
<h3>Where font-size switches over</h3>
|
||||
<p>A size only picks one of the four: 22px and up is BIG, 16px and up is MEDIUM, anything
|
||||
else is NORMAL. Each pair below must look identical, and each pair must differ from the
|
||||
next.</p>
|
||||
<p><span style="font-size:10px">10px</span> <span style="font-size:15px">15px</span>
|
||||
→ both NORMAL</p>
|
||||
<p><span style="font-size:16px">16px</span> <span style="font-size:21px">21px</span>
|
||||
→ both MEDIUM</p>
|
||||
<p><span style="font-size:22px">22px</span> <span style="font-size:40px">40px</span>
|
||||
→ both BIG</p>
|
||||
<p>The keywords: <span style="font-size:xx-small"><i>xx-small</i></span>
|
||||
<span style="font-size:x-small"><i>x-small</i></span>
|
||||
<span style="font-size:small"><i>small</i></span>
|
||||
<span style="font-size:medium"><i>medium</i></span>
|
||||
<span style="font-size:large"><i>large</i></span>
|
||||
<span style="font-size:x-large"><i>x-large</i></span>
|
||||
<span style="font-size:xx-large"><i>xx-large</i></span>
|
||||
→ only the three "small" ones reach FONT_SMALL.</p>
|
||||
|
||||
<h3>Tags that lean</h3>
|
||||
<p><i>i</i> <em>em</em> <cite>cite</cite> <var>var</var>
|
||||
<dfn>dfn</dfn> and <address>address, which is a block</address></p>
|
||||
<p><span style="font-style:italic">font-style: italic</span>
|
||||
<span style="font-style:oblique">oblique</span>
|
||||
<i style="font-style:normal">an i turned back to normal</i></p>
|
||||
<p><i><u>italic underline</u></i> <i><s>italic strike</s></i>
|
||||
<i><a href="fonts.htm">an italic link</a></i></p>
|
||||
|
||||
<h3>Ctrl+- and Ctrl++</h3>
|
||||
<p>Small print moves every slot one step down: NORMAL becomes 6x9, MEDIUM becomes 8x16,
|
||||
BIG becomes the doubled 6x9, SMALL stays 6x9. Press Ctrl+- and the four rows of the first
|
||||
table must still be four distinct sizes, and every italic must still lean.</p>
|
||||
|
||||
<h3>Edges</h3>
|
||||
<p>A run leans only while there is a column to slide into; at the very edge of the canvas
|
||||
it is drawn upright. Narrow the window over this line and watch where it stops.</p>
|
||||
<p><i>iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii iiiiiiiiii</i></p>
|
||||
|
||||
<h3>Inheritance and pre</h3>
|
||||
<div style="font-style:italic">A block set to italic: this sentence, <b>this bold run</b>,
|
||||
and <span style="font-style:normal">this span turned back to normal</span>.</div>
|
||||
<pre><i>pre keeps the spacing and still leans</i>
|
||||
normal line for comparison</pre>
|
||||
|
||||
</body>
|
||||
@@ -1,28 +0,0 @@
|
||||
<html><head><title>grid</title>
|
||||
<style>
|
||||
#t { display:grid; grid-template-columns:auto 1fr auto; border:1px solid #bbb; }
|
||||
#t .item { display:contents; }
|
||||
#t .line { grid-column:1 / span 3; display:flex; align-items:center; background:#eef; padding:6px 10px; }
|
||||
#t .cell { border-top:1px solid #ccc; padding:6px 10px; }
|
||||
#t .name { display:flex; overflow:hidden; max-width:35vw; }
|
||||
#t .msg { white-space:nowrap; overflow:hidden; color:#666; }
|
||||
#t .age { text-align:right; white-space:nowrap; color:#666; }
|
||||
.g3 { display:grid; grid-template-columns:repeat(3, 1fr); gap:8px; margin-top:12px; }
|
||||
.g3 div { background:#fec; padding:4px; }
|
||||
.wide { grid-column:1 / -1; }
|
||||
.cards { display:grid; grid-template-columns:repeat(auto-fill, minmax(200px, 1fr)); gap:10px; margin-top:12px; }
|
||||
.cards div { border:1px solid #999; padding:8px; }
|
||||
.mix { display:grid; grid-template-columns:120px 2fr 1fr; margin-top:12px; }
|
||||
.mix div { border:1px solid #c99; }
|
||||
</style></head><body>
|
||||
<div id="t">
|
||||
<div class="line"><b>Burer</b> 49b8cdcd54 ci: mark submodule contents dirty after a bump</div>
|
||||
<div class="item"><div class="cell name"><a href="#">_tools</a></div><div class="cell msg"><a href="#">[WS] Make run.py work on modern qemu</a></div><div class="cell age">2023-08-06 19:45:59</div></div>
|
||||
<div class="item"><div class="cell name"><a href="#">.gitea</a></div><div class="cell msg"><a href="#">ci: mark submodule contents dirty after a bump</a></div><div class="cell age">2026-09-22 21:28:30</div></div>
|
||||
<div class="item"><div class="cell name"><a href="#">contrib</a></div><div class="cell msg"><a href="#">apps/fplay: major player updates, performance, usability and UI improvements, bugfixes (#560) and a very long tail of text that should be cut off at the edge of the column</a></div><div class="cell age">2026-07-19 06:32:00</div></div>
|
||||
<div class="item"><div class="cell name"><a href="#">drivers</a></div><div class="cell msg"><a href="#">drivers: add i2chid HID-over-I2C touchpad driver (#692)</a></div><div class="cell age">2026-09-16 10:28:47</div></div>
|
||||
</div>
|
||||
<div class="g3"><div>one</div><div>two two two two two two two two two two two two two two</div><div>three</div><div class="wide">wide: 1 / -1</div><div>four</div><div>five</div></div>
|
||||
<div class="cards"><div>card 1</div><div>card 2</div><div>card 3</div><div>card 4</div><div>card 5</div><div>card 6</div><div>card 7</div></div>
|
||||
<div class="mix"><div>120px</div><div>2fr</div><div>1fr</div></div>
|
||||
</body></html>
|
||||
@@ -1,32 +0,0 @@
|
||||
<html><head><style>
|
||||
.inner{display:grid;column-gap:24px;grid-template:min-content 1fr min-content / 150px minmax(0,1fr);grid-template-areas:'siteNotice siteNotice' 'columnStart pageContent' 'footer footer'}
|
||||
.notice{grid-area:siteNotice;background:#ffd}
|
||||
.start{grid-area:columnStart;background:#eef}
|
||||
.content{grid-area:pageContent}
|
||||
.foot{grid-area:footer;background:#eee}
|
||||
.body{display:grid;grid-template:min-content min-content min-content 1fr / minmax(0,1fr) min-content;column-gap:24px;grid-template-areas:'titlebar-cx .' 'titlebar columnEnd' 'toolbar columnEnd' 'content columnEnd'}
|
||||
.title{grid-area:titlebar;background:#dfd}
|
||||
.tools{grid-area:toolbar;background:#fdd}
|
||||
.end{grid-area:columnEnd;width:120px;background:#ddf}
|
||||
.text{grid-area:content}
|
||||
.fc{display:grid;gap:1rem;grid-template-columns:1.5fr 1fr 1fr;grid-template-areas:"logo about legal" "copy copy copy";background:#f0f1f5;margin-top:1rem}
|
||||
.fl{grid-area:logo}.fa{grid-area:about}.fg{grid-area:legal}.fx{grid-area:copy;border-top:1px solid #ccc}
|
||||
</style></head><body>
|
||||
<div class="inner">
|
||||
<div class="notice">1 grid-template-areas: notice over both columns</div>
|
||||
<div class="start"><b>Contents</b><br>(Top)<br>Development<br>Features<br>Reception</div>
|
||||
<div class="content"><div class="body">
|
||||
<div class="title"><h2>Title</h2></div>
|
||||
<div class="tools">Article Talk</div>
|
||||
<div class="end">Tools column<br>spans three<br>rows<br>and<br>is<br>tall<br>at<br>the right</div>
|
||||
<div class="text">Article text in the content area: left of the tools column, below the toolbar.</div>
|
||||
</div></div>
|
||||
<div class="foot">Footer over both columns</div>
|
||||
</div>
|
||||
<div class="fc">
|
||||
<div class="fg"><b>Legal</b><br>third column, first in the source</div>
|
||||
<div class="fx">2 freebsd.org footer: copyright over all three columns</div>
|
||||
<div class="fl">Logo</div>
|
||||
<div class="fa"><b>About</b><br>second column</div>
|
||||
</div>
|
||||
</body></html>
|
||||
@@ -1,33 +0,0 @@
|
||||
<html><head><title>gridfit</title>
|
||||
<!-- needs a picture at /hd0/1/pic.png (like noscript.htm) -->
|
||||
<style>
|
||||
body { margin:0; background:#eee; }
|
||||
/* virtualbox.org: a "5vh" bar, the logo "height: 100%" of it, an icon picked by a
|
||||
selector of four child combinators ("height: 2vw") */
|
||||
#bar { height:5vh; display:flex; align-items:center; justify-content:space-between; background:#fff; border-bottom:1px solid #bbb; }
|
||||
#bar li { height:100%; list-style-type:none; margin:0 1vw; }
|
||||
#bar > ul { display:flex; align-items:center; margin:0 4vw 0 0; padding:0; }
|
||||
#logo, #logo img { display:block; height:100%; width:auto; }
|
||||
#bar > ul > li.first > a > img { height:2vw; }
|
||||
/* theguardian.com: six 280px cards in "repeat(6, 1fr)" of a narrower row */
|
||||
.wrap { max-width:740px; margin:12px auto; padding:8px 20px; background:#fff; }
|
||||
.car { display:grid; gap:20px; padding:0; margin:0; list-style:none; grid-auto-flow:column; overflow-x:auto; grid-template-columns:repeat(6, 1fr); }
|
||||
.card { display:flex; flex-direction:row; justify-content:space-between; column-gap:8px; width:280px; padding:10px 10px 0 10px; background:#fdecea; }
|
||||
.txt { display:flex; flex-direction:column; padding-bottom:10px; }
|
||||
.hl { font-size:17px; margin:0; font-weight:normal; }
|
||||
.media { flex-shrink:0; width:60px; height:60px; margin-bottom:10px; background:#c77a3c; }
|
||||
</style></head><body>
|
||||
<div id="bar"><li><a id="logo" href="#"><img alt="logo" src="/hd0/1/pic.png"></a></li>
|
||||
<ul><li><a href="#">Home</a></li><li><a href="#">Download</a></li><li><a href="#">Documentation</a></li>
|
||||
<li class="first"><a href="#"><img alt="user" src="/hd0/1/pic.png"></a></li></ul></div>
|
||||
<p>The bar above is one line: a short blue logo, the links, a small icon.</p>
|
||||
<div class="wrap"><ul class="car">
|
||||
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">One: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
|
||||
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Two: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
|
||||
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Three: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
|
||||
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Four: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
|
||||
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Five: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
|
||||
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Six: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
|
||||
</ul></div>
|
||||
<p>Cards above: as many 280px columns as fit (two in a 740px row), not six letter columns.</p>
|
||||
</body></html>
|
||||
@@ -1,35 +0,0 @@
|
||||
<html><head><title>:is() / :where()</title>
|
||||
<style>
|
||||
.t { padding: 2px 6px; margin: 3px 0; }
|
||||
:is(h3, h4) span { background: #8f8; }
|
||||
.box :where(.a, .b) { background: #8f8; }
|
||||
:where(#w1) { background: #f88; }
|
||||
p.w { background: #8f8; }
|
||||
li:is(.x .y) { background: #8f8; }
|
||||
.list > :where(*) { background: #8f8; }
|
||||
.n p:not(:where(.skip, .other *)) { background: #8f8; }
|
||||
:is(ul, ol) :is(.deep) { background: #8f8; }
|
||||
.c:is(p) { background: #8f8; }
|
||||
div:is(p).never { background: #f88; }
|
||||
:where(.m1, .m2):first-child { background: #8f8; }
|
||||
:IS(.up) { background: #8f8; }
|
||||
.pw p { background: #8f8; }
|
||||
.k:not(:where(b,i,u,s,q,em,tt,kbd,var,dfn,abbr,cite,code,span)) { background: #8f8; }
|
||||
.pw :where(p) { background: #f88; }
|
||||
</style></head><body>
|
||||
<h2>Every line must be green (none red, none white)</h2>
|
||||
<h3><span class="t">1. :is(h3, h4) span</span></h3>
|
||||
<h4><span class="t">2. :is(h3, h4) span</span></h4>
|
||||
<div class="box"><p class="t a">3. .box :where(.a, .b)</p><p class="t b">4. .box :where(.a, .b)</p></div>
|
||||
<p id="w1" class="t w">5. :where(#w1) red loses to p.w green (where counts 0)</p>
|
||||
<ul class="x"><li class="t y">6. li:is(.x .y)</li></ul>
|
||||
<div class="list"><p class="t">7. .list > :where(*)</p></div>
|
||||
<div class="n"><p class="t">8. p:not(:where(.skip, .other *))</p><p class="t skip" style="background:#8f8">9. .skip: not matched (inline green)</p></div>
|
||||
<ol><li><span class="t deep">10. :is(ul, ol) :is(.deep)</span></li></ol>
|
||||
<p class="t c">11. .c:is(p) = p.c</p>
|
||||
<p class="t never" style="background:#8f8">12. div:is(p).never never matches (inline green)</p>
|
||||
<div><p class="t m1">13. :where(.m1, .m2):first-child</p></div>
|
||||
<p class="t up">14. :IS(.up) upper case</p>
|
||||
<div class="pw"><p class="t">15. a later .pw :where(p) red loses to .pw p green</p></div>
|
||||
<p class="t k">16. .k:not(:where(b,i,u,...14 tags)) on a p</p>
|
||||
</body></html>
|
||||
@@ -1,27 +0,0 @@
|
||||
<html><head><title>min-height / max-height</title>
|
||||
<style>
|
||||
div.t { background: #cde; border: 1px solid #357; margin: 6px 0; }
|
||||
div.hero { min-height: 120px; background: #fc9; }
|
||||
.short { min-height: 20px; }
|
||||
.menu { max-height: 0; overflow: hidden; background: #f99; }
|
||||
.cut { max-height: 40px; overflow: hidden; }
|
||||
div.capped { height: 200px; max-height: 80px; background: #9f9; }
|
||||
div.both { min-height: 150px; max-height: 60px; background: #ccf; }
|
||||
.nomax { max-height: 30px; }
|
||||
</style></head><body>
|
||||
<p>1. min-height:120px, one line - box 120px tall (orange):</p>
|
||||
<div class="t hero">hero</div>
|
||||
<p>2. min-height:20px, three lines - as tall as the text:</p>
|
||||
<div class="t short">line one<br>line two<br>line three</div>
|
||||
<p>3. max-height:0 + overflow:hidden - nothing between the two rulers:</p>
|
||||
<hr><div class="menu">HIDDEN MENU 1<br>HIDDEN MENU 2</div><hr>
|
||||
<p>4. max-height:40px + overflow:hidden, six lines - two lines are left:</p>
|
||||
<div class="t cut">cut 1<br>cut 2<br>cut 3<br>cut 4<br>cut 5<br>cut 6</div>
|
||||
<p>5. height:200px + max-height:80px - 80px tall (green):</p>
|
||||
<div class="t capped">capped</div>
|
||||
<p>6. min-height:150px + max-height:60px - 60px tall (blue):</p>
|
||||
<div class="t both">both</div>
|
||||
<p>7. max-height:30px without overflow - all four lines are shown:</p>
|
||||
<div class="t nomax">a<br>b<br>c<br>d</div>
|
||||
<p>end</p>
|
||||
</body></html>
|
||||
@@ -1,16 +0,0 @@
|
||||
<html><body>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="#">Home</a></li>
|
||||
<li><a href="#">Docs</a>
|
||||
<ul>
|
||||
<li><a href="#">Install</a></li>
|
||||
<li><a href="#">API</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<p>Text after nav.</p>
|
||||
<ul><li>Normal list</li><li>Second</li></ul>
|
||||
</body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><head><meta charset="utf-8"><title>nbsp utf8</title></head><body><table><tr><td>1. </td><td> </td><td>x</td></tr></table><p> </p><p>a b c</p></body></html>
|
||||
@@ -1,7 +0,0 @@
|
||||
<html><body>
|
||||
<p>1 lazy picture + noscript copy: ONE picture below</p>
|
||||
<div><picture><source srcset="data:image/gif;base64,R0lGODlhAQABAHAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="><img src="/hd0/1/pic.png"></picture>
|
||||
<noscript><picture><img data-src="/hd0/1/pic.png"></picture></noscript></div>
|
||||
<p>2 noscript with text is shown:</p>
|
||||
<div><img src="/hd0/1/pic.png"><noscript><b>shown from noscript</b></noscript></div>
|
||||
</body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<html><body><table width="100%" border="1"><tr><td align="left" width="100%">left cell with text</td><td align="right" width="2%" nowrap><b>[btn1]</b> <b>[btn2]</b></td></tr></table><table width="100%" border="1"><tr><td width="70%">seventy</td><td width="70%">seventy again</td></tr></table><table border="1"><tr><td width="50%">half of an auto table</td><td>rest</td></tr></table></body></html>
|
||||
@@ -1,34 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Percent heights</title>
|
||||
<style>
|
||||
body { font-family: sans-serif; }
|
||||
.box { border: 1px solid #888; margin: 8px 0; }
|
||||
/* hotline.ua product gallery: 500px -> 85% -> 100%, the picture centred in it */
|
||||
.gallery { height: 300px; position: relative; background: #eef; }
|
||||
.gallery__content { display: flex; flex-wrap: wrap; height: 85%; }
|
||||
.gallery__canvas { height: 100%; position: relative; width: calc(100% - 64px); background: #dfd; }
|
||||
.gallery__img { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
|
||||
max-height: 100%; max-width: calc(100% - 50px); }
|
||||
.fixed { height: 120px; background: #fdd; }
|
||||
.fixed div { height: 50%; background: #f99; }
|
||||
.auto div { height: 50%; background: #99f; }
|
||||
.cap img { max-height: 60px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>1. The picture fits the green box (255px tall) and is centred in it:</p>
|
||||
<div class="gallery box"><div class="gallery__content"><div class="gallery__canvas">
|
||||
<img class="gallery__img" src="tire.jpg" alt="tire">
|
||||
</div></div></div>
|
||||
|
||||
<p>2. The red strip is half of the 120px pink box:</p>
|
||||
<div class="fixed box"><div>50%</div></div>
|
||||
|
||||
<p>3. A percentage of an auto height is dropped: the blue strip is one line:</p>
|
||||
<div class="auto box"><div>50% of auto</div></div>
|
||||
|
||||
<p>4. max-height: 60px on a picture:</p>
|
||||
<div class="cap box"><img src="tire.jpg" alt="tire"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
<html><head><title>selectors and positioning</title><style>
|
||||
li.file a { background: #ffd0d0; display: block; }
|
||||
ul.filelist li.file a { background: #d0ffd0; display: block; }
|
||||
ul.filelist li.file > span { background: #ffb0b0; display: block; }
|
||||
ul.filelist li.file a .sz { position: absolute; right: 2px; text-align: right; }
|
||||
li.file a { position: relative; }
|
||||
</style></head><body>
|
||||
<p>1 three part selector, single class (row must be GREEN)</p>
|
||||
<ul class="filelist"><li class="file"><a href="#">single-class li</a></li></ul>
|
||||
<p>2 multi class li (GREEN too)</p>
|
||||
<ul class="filelist"><li class="file archive has-diz"><a href="#">multi-class li</a></li></ul>
|
||||
<p>3 child combinator: the span sits inside the link, so it must NOT turn red,
|
||||
and the size must be pinned to the right of the green row</p>
|
||||
<ul class="filelist"><li class="file"><a href="#"><span>name.zip</span><span class="sz">8.21M</span></a></li></ul>
|
||||
<p>4 a real direct child span of the li (must be RED)</p>
|
||||
<ul class="filelist"><li class="file"><span>direct child span</span></li></ul>
|
||||
</body></html>
|
||||
@@ -1,58 +0,0 @@
|
||||
<html>
|
||||
<title>TEST</title>
|
||||
<style>
|
||||
td { padding: 0 20px; vertical-align: top; }
|
||||
</style>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<pre style="margin-top:-100px"><table>
|
||||
<tr><h3><kosicon n=22>Demoscene</h3>
|
||||
<a href="https://amp.dascene.net/home.php">Amiga Music Preservation</a>
|
||||
<a href=https://www.pouet.net/>Pouet.net</a>
|
||||
<a href=https://www.scene.org>scene.org</a>
|
||||
<a href=//menuetos.net>menuetos.net</a>
|
||||
|
||||
<td><h3><kosicon n=49>Search</h3>
|
||||
<a href=//wiby.org>wiby.org</a>
|
||||
<a href=//frogfind.com/>frogfind.com</a>
|
||||
<a href=//lite.duckduckgo.com>duckduckgo.com</a>
|
||||
|
||||
<td><h3><kosicon n=3>News</h3>
|
||||
<a href=https://osnews.com>osnews.com</a>
|
||||
<a href=https://hackaday.com>hackaday.com</a>
|
||||
<a href=https://slashdot.org>slashdot.org</a>
|
||||
<a href=https://news.ycombinator.com>news.ycombinator.com</a>
|
||||
<a href=https://habr.com>habr.com</a>
|
||||
<!----------------------------------------------------------------------------->
|
||||
<tr><h3><kosicon n=21>Tools</h3>
|
||||
<a href=//th99.infania.net>Total Hardware 1999</a>
|
||||
<a href=//xcal1.vodafone.co.uk>Vodafone Test Files</a>
|
||||
<a href="http://www.chipmunk.nl/DRAM/ChipManufacturers.htm">chipmunk.nl</a>
|
||||
|
||||
<td><h3><kosicon n=65>Oldware</h3>
|
||||
<a href="https://aminet.net">Aminet</a>
|
||||
<a href=//majorgeeks.com>majorgeeks.com</a>
|
||||
<a href=//macintoshgarden.org>macintoshgarden.org</a>
|
||||
<a href=https://macos9lives.com>macos9lives.com</a>
|
||||
<a href="http://dosdays.co.uk/">dosdays.co.uk</a>
|
||||
<a href=//toastytech.com/guis/index.html>GUI Gallery</a>
|
||||
<a href="http://games.freebasic.net/dumpfilter.php?genre=All&year=All&demo=All&source=All&scorecal=morethan&score=0">games.freebasic.net</a>
|
||||
|
||||
<td><h3><kosicon n=54>Cyrillic</h3>
|
||||
<a href=//dgmag.in>dgmag.in</a> <a href=//old-dos.ru>old-dos.ru</a> <a href=//retrolib.info>retrolib.info</a>
|
||||
<a href=//artcon.ru>artcon.ru</a> <a href=//nostalgy.net.ru/>nostalgy.net.ru</a> <a href=//acmp.ru/index.asp?main=tasks>acmp.ru</a>
|
||||
<a href=//opennet.ru>opennet</a> <a href=//linux.org.ru>LOR</a>
|
||||
<a href=//samlib.ru/b>samlib.ru/b</a>
|
||||
<a href=//os-menuet.narod.ru>os-menuet.narod.ru</a> <a href=//coolthemes.narod.ru/indexold.html>coolthemes.narod.ru</a>
|
||||
<!----------------------------------------------------------------------------->
|
||||
<tr><h3><kosicon n=29>Test pages</h3>
|
||||
<a href="https://pixeljoint.com/">pixeljoint.com</a>
|
||||
<a href="https://www.kernel.org/">www.kernel.org</a>
|
||||
<a href="https://github.com/jhhoward/MicroWeb">github</a>
|
||||
<a href="https://lospec.com/">lospec.com</a>
|
||||
<a href="https://browser.engineering/">browser.engineering</a>
|
||||
<a href=https://cobradile.neocities.org>145 images</a>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
out/
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,101 +0,0 @@
|
||||
# group url (modern = current JS-heavy sites, old = classic / lightweight markup)
|
||||
modern https://www.google.com
|
||||
modern https://www.youtube.com
|
||||
modern https://www.facebook.com
|
||||
modern https://x.com
|
||||
modern https://www.instagram.com
|
||||
modern https://www.linkedin.com
|
||||
modern https://www.reddit.com
|
||||
modern https://www.amazon.com
|
||||
modern https://www.netflix.com
|
||||
modern https://www.microsoft.com
|
||||
modern https://www.apple.com
|
||||
modern https://github.com
|
||||
modern https://stackoverflow.com
|
||||
modern https://en.wikipedia.org/wiki/Main_Page
|
||||
modern https://www.bbc.com
|
||||
modern https://edition.cnn.com
|
||||
modern https://www.nytimes.com
|
||||
modern https://www.theguardian.com
|
||||
modern https://www.twitch.tv
|
||||
modern https://open.spotify.com
|
||||
modern https://www.paypal.com
|
||||
modern https://www.ebay.com
|
||||
modern https://www.pinterest.com
|
||||
modern https://www.tumblr.com
|
||||
modern https://medium.com
|
||||
modern https://www.dropbox.com
|
||||
modern https://zoom.us
|
||||
modern https://slack.com
|
||||
modern https://www.notion.so
|
||||
modern https://www.figma.com
|
||||
modern https://www.airbnb.com
|
||||
modern https://www.booking.com
|
||||
modern https://www.imdb.com
|
||||
modern https://www.espn.com
|
||||
modern https://www.walmart.com
|
||||
modern https://www.target.com
|
||||
modern https://www.etsy.com
|
||||
modern https://www.quora.com
|
||||
modern https://discord.com
|
||||
modern https://openai.com
|
||||
modern https://www.adobe.com
|
||||
modern https://www.cloudflare.com
|
||||
modern https://vercel.com
|
||||
modern https://stripe.com
|
||||
modern https://www.shopify.com
|
||||
modern https://www.nike.com
|
||||
modern https://www.washingtonpost.com
|
||||
modern https://weather.com
|
||||
modern https://www.yahoo.com
|
||||
modern https://www.bing.com
|
||||
old https://news.ycombinator.com
|
||||
old https://www.craigslist.org
|
||||
old https://slashdot.org
|
||||
old https://www.drudgereport.com
|
||||
old https://lite.cnn.com
|
||||
old https://text.npr.org
|
||||
old https://www.gnu.org
|
||||
old https://www.kernel.org
|
||||
old https://www.debian.org
|
||||
old https://www.openbsd.org
|
||||
old https://www.w3.org
|
||||
old https://www.ietf.org
|
||||
old https://www.rfc-editor.org
|
||||
old http://paulgraham.com
|
||||
old https://danluu.com
|
||||
old https://www.berkshirehathaway.com
|
||||
old https://www.spacejam.com/1996/
|
||||
old https://www.lingscars.com
|
||||
old https://archive.org
|
||||
old http://www.textfiles.com
|
||||
old https://www.sqlite.org
|
||||
old https://www.lua.org
|
||||
old https://www.python.org
|
||||
old https://www.perl.org
|
||||
old https://www.php.net
|
||||
old https://www.apache.org
|
||||
old https://www.freebsd.org
|
||||
old https://www.netbsd.org
|
||||
old http://www.slackware.com
|
||||
old https://www.gentoo.org
|
||||
old http://www.catb.org/~esr/
|
||||
old https://norvig.com
|
||||
old https://www.arngren.net
|
||||
old http://68k.news
|
||||
old http://frogfind.com
|
||||
old https://wiby.me
|
||||
old https://lobste.rs
|
||||
old https://www.metafilter.com
|
||||
old https://www.fark.com
|
||||
old https://kottke.org
|
||||
old https://daringfireball.net
|
||||
old https://xkcd.com
|
||||
old https://www.penny-arcade.com
|
||||
old https://www.snopes.com
|
||||
old https://www.weather.gov
|
||||
old https://www.fileformat.info
|
||||
old https://www.jwz.org
|
||||
old https://www.7-zip.org
|
||||
old https://www.virtualbox.org
|
||||
old https://www.nasa.gov
|
||||
@@ -1,190 +0,0 @@
|
||||
"""WebView site smoke test: opens every URL of sites.txt in the KolibriOS test VM,
|
||||
waits for "Done in" in the status bar, saves a screenshot and records load time,
|
||||
crashes (the WebView window is gone) and hangs (no "Done" within the timeout).
|
||||
|
||||
python sitetest.py VMDIR [OUTDIR] [--from N] [--only substr] [--timeout SEC] [--report]
|
||||
|
||||
VMDIR holds vm.py of the test VM (see .vm/vm.py): its monitor port is used for keys
|
||||
and screendumps. The VM must be booted with AUTORUN starting WebView (reset restores it).
|
||||
Results: OUTDIR/results.json, OUTDIR/report.html (screenshots embedded), OUTDIR/shots/NN.png
|
||||
--report rebuilds report.html from results.json without the VM.
|
||||
"""
|
||||
import sys, os, time, json, argparse, html, warnings, io, base64
|
||||
warnings.filterwarnings("ignore")
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("vmdir")
|
||||
ap.add_argument("out", nargs="?", default=os.path.join(HERE, "out"))
|
||||
ap.add_argument("--from", dest="start", type=int, default=1)
|
||||
ap.add_argument("--only", default="")
|
||||
ap.add_argument("--timeout", type=float, default=90)
|
||||
ap.add_argument("--report", action="store_true", help="only rebuild report.html from results.json")
|
||||
opt = ap.parse_args()
|
||||
sys.path.insert(0, opt.vmdir)
|
||||
import vm
|
||||
|
||||
WIN = (80, 0, 930, 768) # WebView window at its default position
|
||||
BACK_BTN = (92, 32, 120, 60) # toolbar "back" button: gone = the window is gone
|
||||
REFRESH = (163, 32, 190, 60) # refresh / stop button: stop icon while loading
|
||||
DONE = (90, 750, 136, 762) # "Done in" at the start of the status bar
|
||||
STATUS = (90, 750, 400, 762)
|
||||
TITLE = (84, 2, 700, 22)
|
||||
CONTENT = (84, 92, 926, 745)
|
||||
|
||||
def grab(name="_poll"):
|
||||
png = os.path.join(opt.vmdir, name + ".png")
|
||||
if os.path.exists(png): os.remove(png)
|
||||
s = vm._conn()
|
||||
s.sendall(("screendump " + png + " -f png\n").encode())
|
||||
t = time.time()
|
||||
while time.time() - t < 5:
|
||||
time.sleep(0.1)
|
||||
try:
|
||||
im = Image.open(png); im.load(); s.close(); return im.convert("RGB")
|
||||
except Exception: pass
|
||||
s.close()
|
||||
raise RuntimeError("screendump failed")
|
||||
|
||||
def ink(im):
|
||||
"""share of content pixels that differ from the dominant (background) colour"""
|
||||
c = im.crop(sh(CONTENT)).resize((210, 163))
|
||||
px = list(c.getdata()); bg = max(set(px), key=px.count)
|
||||
return round(100.0 * sum(1 for p in px if sum(abs(x - y) for x, y in zip(p, bg)) > 30) / len(px), 1)
|
||||
|
||||
DX = 0 # window x shift from the reference position
|
||||
|
||||
def sh(box, dx=None):
|
||||
dx = DX if dx is None else dx
|
||||
return (box[0] + dx, box[1], box[2] + dx, box[3])
|
||||
|
||||
def find_window(im):
|
||||
"""x shift of the WebView window (it opens at a varying place after a reboot), None if gone"""
|
||||
ref = REF.crop(BACK_BTN)
|
||||
for dx in [0] + [d for d in range(-80, 150) if d]:
|
||||
if BACK_BTN[0] + dx < 0: continue
|
||||
d = ImageChops.difference(im.crop(sh(BACK_BTN, dx)), ref).convert("L")
|
||||
if sum(1 for p in d.getdata() if p > 40) <= 8: return dx
|
||||
return None
|
||||
|
||||
def webview_alive(im):
|
||||
global DX
|
||||
dx = find_window(im)
|
||||
if dx is None: return False
|
||||
DX = dx; return True
|
||||
|
||||
def same(a, b, box, tol=0):
|
||||
"""box is in reference coordinates: a is shifted by DX, b is REF (unshifted) or a live shot"""
|
||||
bb = box if b is REF else sh(box)
|
||||
d = ImageChops.difference(a.crop(sh(box)), b.crop(bb)).convert("L")
|
||||
return sum(1 for p in d.getdata() if p > 40) <= tol
|
||||
|
||||
def reboot():
|
||||
print(" rebooting VM...", flush=True)
|
||||
vm.cmd("system_reset")
|
||||
time.sleep(20)
|
||||
t = time.time()
|
||||
while time.time() - t < 120:
|
||||
time.sleep(2)
|
||||
try:
|
||||
im = grab()
|
||||
if webview_alive(im): time.sleep(3); return True
|
||||
except Exception: pass
|
||||
return False
|
||||
|
||||
def open_url(url):
|
||||
# Ctrl+A in the omnibox is not always taken: select the old address with End,
|
||||
# Shift+Home as well, typing then replaces it
|
||||
vm.cmd("sendkey f6", 0.15); vm.cmd("sendkey ctrl-a", 0.15)
|
||||
vm.cmd("sendkey end", 0.1); vm.cmd("sendkey shift-home", 0.15)
|
||||
vm.type_text(url); vm.cmd("sendkey ret", 0.05)
|
||||
|
||||
def run_one(n, group, url):
|
||||
before = grab()
|
||||
webview_alive(before)
|
||||
open_url(url)
|
||||
t0 = time.time()
|
||||
r = dict(n=n, group=group, url=url, status="timeout", seconds=None)
|
||||
changed = False
|
||||
last = before
|
||||
while True:
|
||||
im = grab(); el = time.time() - t0
|
||||
last = im
|
||||
if not webview_alive(im):
|
||||
time.sleep(1.5); im = grab() # a window redraw can blank the toolbar for a moment
|
||||
if not webview_alive(im):
|
||||
r.update(status="crash", seconds=round(el, 1)); last = im; break
|
||||
# "Done in N sec (M milisec)" of the previous page stays until the new one starts:
|
||||
# wait for a different status text, or a different title after a while
|
||||
if not same(im, before, TITLE): changed = True
|
||||
if same(im, REF, DONE, 4) and (not same(im, before, STATUS) or (changed and el > 8)):
|
||||
r.update(status="ok", seconds=round(el, 1)); break
|
||||
if el > opt.timeout:
|
||||
r["loading_icon"] = not same(im, REF, REFRESH, 8)
|
||||
break
|
||||
shot = os.path.join(opt.out, "shots", "%03d.png" % n)
|
||||
last.crop(sh(WIN)).save(shot)
|
||||
r["shot"] = "shots/%03d.png" % n
|
||||
r["ink"] = ink(last) if r["status"] != "crash" else 0
|
||||
if r["status"] == "ok" and r["ink"] < 0.5: r["status"] = "blank"
|
||||
return r
|
||||
|
||||
def report(res):
|
||||
tot = len(res); by = {}
|
||||
for r in res: by[r["status"]] = by.get(r["status"], 0) + 1
|
||||
def stats(g):
|
||||
rs = [r for r in res if r["group"] == g]
|
||||
ok = [r["seconds"] for r in rs if r["status"] in ("ok", "blank")]
|
||||
ok.sort()
|
||||
med = ok[len(ok) // 2] if ok else 0
|
||||
return "%s: %d sites, ok %d, blank %d, crash %d, timeout %d, median %.1fs" % (g, len(rs),
|
||||
sum(r["status"] == "ok" for r in rs), sum(r["status"] == "blank" for r in rs),
|
||||
sum(r["status"] == "crash" for r in rs), sum(r["status"] == "timeout" for r in rs), med)
|
||||
def embed(shot):
|
||||
# the screenshot inside the page as a JPEG: report.html is one file to send around
|
||||
try:
|
||||
im = Image.open(os.path.join(opt.out, shot)).convert("RGB")
|
||||
except OSError:
|
||||
return ""
|
||||
b = io.BytesIO(); im.save(b, "JPEG", quality=75)
|
||||
return "data:image/jpeg;base64," + base64.b64encode(b.getvalue()).decode()
|
||||
rows = []
|
||||
for r in res:
|
||||
rows.append('<div class="c %s"><img src="%s" loading="lazy" onclick="this.parentNode.classList.toggle("big")"><b>%d. %s</b><br>%s · %s · ink %s%%</div>' % (
|
||||
r["status"], embed(r["shot"]), r["n"], html.escape(r["url"]), r["status"].upper(),
|
||||
("%.1f s" % r["seconds"]) if r["seconds"] is not None else ">%ds" % opt.timeout, r["ink"]))
|
||||
page = """<!doctype html><meta charset=utf-8><title>WebView site test</title><style>
|
||||
body{font:14px sans-serif;margin:16px;background:#f4f4f4}.g{display:flex;flex-wrap:wrap;gap:12px}
|
||||
.c{width:300px;background:#fff;padding:6px;border-top:4px solid #3a3}.c img{width:100%%;display:block;cursor:zoom-in}
|
||||
.c.big{width:100%%;max-width:1024px}.c.big img{cursor:zoom-out}
|
||||
.crash{border-color:#d22}.timeout{border-color:#e90}.blank{border-color:#999}</style>
|
||||
<h1>WebView site test</h1><p>%s</p><p>%s</p><p>%s</p><div class=g>%s</div>""" % (
|
||||
time.strftime("%Y-%m-%d %H:%M"), stats("modern"), stats("old"), "\n".join(rows))
|
||||
open(os.path.join(opt.out, "report.html"), "w", encoding="utf-8").write(page)
|
||||
|
||||
jf = os.path.join(opt.out, "results.json")
|
||||
if opt.report:
|
||||
report(json.load(open(jf)))
|
||||
print("report:", os.path.join(opt.out, "report.html"))
|
||||
sys.exit(0)
|
||||
os.makedirs(os.path.join(opt.out, "shots"), exist_ok=True)
|
||||
REF = Image.open(os.path.join(HERE, "ref.png")).convert("RGB")
|
||||
sites = []
|
||||
for l in open(os.path.join(HERE, "sites.txt")):
|
||||
l = l.strip()
|
||||
if l and not l.startswith("#"):
|
||||
g, u = l.split(None, 1); sites.append((len(sites) + 1, g, u))
|
||||
vm.move_to(1010, 700) # keep the pointer out of the screenshots
|
||||
res = {r["n"]: r for r in json.load(open(jf))} if os.path.exists(jf) else {}
|
||||
for n, g, u in sites:
|
||||
if n < opt.start or opt.only not in u: continue
|
||||
print("%3d %-45s" % (n, u), end=" ", flush=True)
|
||||
r = run_one(n, g, u)
|
||||
print(r["status"], r["seconds"], "ink", r["ink"], flush=True)
|
||||
res[n] = r
|
||||
json.dump([res[k] for k in sorted(res)], open(jf, "w"), indent=1)
|
||||
report([res[k] for k in sorted(res)])
|
||||
if r["status"] in ("crash", "timeout"):
|
||||
if not reboot(): print("VM did not come back"); sys.exit(1)
|
||||
print("report:", os.path.join(opt.out, "report.html"))
|
||||
@@ -1,38 +0,0 @@
|
||||
<html><head><title>six features</title><style>
|
||||
body { background: #fff; }
|
||||
.wrap { max-width: 400px; margin: 0 auto; background: #def; }
|
||||
.right { width: 200px; margin-left: auto; background: #fde; }
|
||||
.ctab { margin: 4px auto; background: #eee; }
|
||||
a:link { color: #c00; }
|
||||
a:visited { color: #c00; }
|
||||
li:first-child { background: #ffd; }
|
||||
li:last-child { background: #dfd; }
|
||||
a:hover { color: #0c0; }
|
||||
.spacer { height: 60px; background: #ccc; }
|
||||
.tabs { background: #eef; }
|
||||
.tabs li { float: left; margin-right: 6px; background: #cce; list-style: none; }
|
||||
.tabs .r { float: right; background: #ecc; }
|
||||
</style></head><body>
|
||||
<p>1 max-width + margin auto: the blue box is 400px and centred</p>
|
||||
<div class="wrap">centred box with max-width 400px, text wraps inside it</div>
|
||||
<div class="right">pushed right</div>
|
||||
<table class="ctab" border="1"><tr><td>table with margin: auto</td></tr></table>
|
||||
|
||||
<p>2 lazy images: all three must show the picture</p>
|
||||
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACw=" data-src="/hd0/1/pic.png">
|
||||
<img data-lazy-src="/hd0/1/pic.png">
|
||||
<img srcset="/hd0/1/pic.png 1x, /hd0/1/nothing.png 2x">
|
||||
|
||||
<p>3 pseudo classes: <a href="#x">red link</a> (a:link), list with first yellow and last green</p>
|
||||
<ul><li>first</li><li>middle</li><li>last</li></ul>
|
||||
|
||||
<p>4 height: a grey 60px strip below, empty</p>
|
||||
<div class="spacer"></div>
|
||||
|
||||
<p>5 floats without width: tabs in a row, one on the right</p>
|
||||
<ul class="tabs"><li>Read</li><li>Edit</li><li>History</li><li class="r">Search</li></ul>
|
||||
<p style="clear:both">text after the floats</p>
|
||||
|
||||
<p>6 noscript content is shown:</p>
|
||||
<noscript><b>shown from noscript</b></noscript>
|
||||
</body></html>
|
||||
@@ -1,26 +0,0 @@
|
||||
<html><head><title>Tables test</title></head><body>
|
||||
<h2>Simple table with border</h2>
|
||||
<table border=1 cellpadding=4>
|
||||
<tr><th>Name</th><th>Size</th><th>Description</th></tr>
|
||||
<tr><td>WebView</td><td>24 KB</td><td>The browser itself, a fairly long description cell that should wrap when needed</td></tr>
|
||||
<tr><td>dl</td><td>5 KB</td><td>Downloader</td></tr>
|
||||
</table>
|
||||
<h2>colspan / rowspan, width 100%, bgcolor</h2>
|
||||
<table border=1 width=100% cellspacing=0 cellpadding=3>
|
||||
<tr bgcolor=#ffeecc><td colspan=3 align=center><b>Header spanning three columns</b></td></tr>
|
||||
<tr><td rowspan=2 bgcolor=#ccffcc>spans<br>two<br>rows</td><td>r1 c2</td><td width=40%>r1 c3 (40%)</td></tr>
|
||||
<tr><td>r2 c2</td><td>r2 c3</td></tr>
|
||||
</table>
|
||||
<h2>Nested tables</h2>
|
||||
<table border=1 cellpadding=2>
|
||||
<tr><td>outer 1</td>
|
||||
<td><table border=1 bgcolor=#eeeeff><tr><td>inner a</td><td>inner b</td></tr><tr><td colspan=2>inner wide</td></tr></table></td></tr>
|
||||
<tr><td>outer 2</td><td>plain cell</td></tr>
|
||||
</table>
|
||||
<h2>Layout table without border (menu | content)</h2>
|
||||
<table width=100%><tr valign=top>
|
||||
<td width=120 bgcolor=#dddddd><a href=a>Menu one</a><br><a href=b>Menu two</a><br><a href=c>Menu three</a></td>
|
||||
<td>Content column text that is long enough to wrap several times inside the second column of the layout table so we can see the widths were distributed properly between the two columns.</td>
|
||||
</tr></table>
|
||||
<p>Text after the tables.</p>
|
||||
</body></html>
|
||||
@@ -1,2 +0,0 @@
|
||||
h1 { color: #c00; }
|
||||
.note { background: #ffe; font-weight: bold; }
|
||||
@@ -1,8 +0,0 @@
|
||||
<html><head><title>Unquoted attrs</title></head><body>
|
||||
<p>1 <a href=https://www.pouet.net/>Pouet.net</a> should be a link</p>
|
||||
<p>2 <a href=//vetusware.com>vetusware.com</a> should be a link</p>
|
||||
<p>3 <a href="https://files.scene.org/">Scene.org quoted</a> should be a link</p>
|
||||
<p>4 self-closing with a space: <img src=/hd0/1/pic.png width=32 /> after the image</p>
|
||||
<p>5 <br/>line after a br</p>
|
||||
<p>6 <a href=https://aminet.net/tree?path=mods class=x>Aminet, two unquoted attrs</a></p>
|
||||
</body></html>
|
||||
@@ -1,8 +0,0 @@
|
||||
<html><head><title>webp twin</title></head><body>
|
||||
<p>1. src=...jpg.webp (should load the .jpg twin):</p>
|
||||
<img src="https://ichef.bbci.co.uk/news/240/cpsprodpb/fe3f/live/8c85eb50-b766-11f1-b1d1-571ed4d7ff2c.jpg.webp" alt="webp twin">
|
||||
<p>2. src=...jpg directly:</p>
|
||||
<img src="https://ichef.bbci.co.uk/news/240/cpsprodpb/fe3f/live/8c85eb50-b766-11f1-b1d1-571ed4d7ff2c.jpg" alt="plain jpg">
|
||||
<p>3. srcset only, first entry .jpg.webp:</p>
|
||||
<img srcset="https://ichef.bbci.co.uk/news/320/cpsprodpb/fe3f/live/8c85eb50-b766-11f1-b1d1-571ed4d7ff2c.jpg.webp 320w,https://ichef.bbci.co.uk/news/480/cpsprodpb/fe3f/live/8c85eb50-b766-11f1-b1d1-571ed4d7ff2c.jpg.webp 480w" alt="srcset twin">
|
||||
</body></html>
|
||||
@@ -1,97 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Welcome notes</title>
|
||||
</head>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<h1>
|
||||
<font color=1BB71A>Welcome to KolibriOS</font>
|
||||
</h1>
|
||||
<pre>
|
||||
Dear friends,
|
||||
|
||||
What you see now is the most compact operating system in the whole world. KolibriOS is a piece of programming art that is based on logic and coffee.
|
||||
|
||||
As no one reads manuals, I'm here to help with the most common issues that new users of KolibriOS are facing. This document is also required reading for any <b>YouTube blogger</b> who wants to make a review of the system :)
|
||||
|
||||
<dd><h3><kosicon n=5> Floppy version</h3>
|
||||
The asterisk [*] in the Menu means the app isn't available in the Floppy version, only in the Big distro (Flash/ISO/UEFI-HDD). Not all programs are available via Menu to encourage you to explore KolibriOS files by yourself.
|
||||
|
||||
<h3><kosicon n=25> If you have problems with USB</h3>
|
||||
Only USB 1.1 and USB 2.0 ports are supported, not USB 3+. And yep, the USB driver is written entirely in assembler. Some motherboards have options like "USB Legacy Support" or something similar, which may help the system detect your device. Also, some ports on your motherboard may work while others may not, only G-d knows why. So, play with the BIOS/UEFI settings and try different ports.
|
||||
|
||||
<h3><kosicon n=7> Sound</h3>
|
||||
List of supported devices:
|
||||
- Most AC97 cards: Intel, nForce, VIA
|
||||
- A variety of HD Audio devices: Intel, Realtek...
|
||||
- SiS PCI audio (SiS 7012)
|
||||
- Creative Labs EMU10K1X (Dell OEM)
|
||||
- ForteMedia FM801
|
||||
- ISA Sound Blaster 16
|
||||
<i>Only THIS particular model works,
|
||||
not other cards that have software SB16-emulation</i>
|
||||
|
||||
<h3><kosicon n=61> Video</h3>
|
||||
By default, you can change the resolution only on the blue boot screen. The modes listed there are provided by the video card's BIOS. It is impossible to 'simply add additional screen modes to the list'. If video card's BIOS doesn't provide some modes then you need a driver. It is not possible to write a generic video card driver. Separate driver must be written for each specific video card model.
|
||||
Intel and AMD drivers for some video cards are ported from Linux and are available in the Big distro.
|
||||
|
||||
Check SYSPANEL. Features: ability to change resolution in KolibriOS, hardware cursor (not actual anymore), bit-blit operations acceleration.
|
||||
|
||||
<h3><kosicon n=25> Wi-Fi</h3>
|
||||
Not supported. You should know that the Wi-Fi specification is so complex that it is almost impossible to write the code for it from scratch. Only big OSes like Windows / Linux / BSD have it. macOS and Haiku just ported the BSD drivers. I don't say porting them to KolibriOS is not possible, but the required effort is going to be tremendous.
|
||||
|
||||
<h3><kosicon n=25> LAN/Ethernet</h3>
|
||||
List of supported devices:
|
||||
- 3c59x 3Com Fast EtherLink (3c59x/450/555/556/575/900/905/980)
|
||||
- ar81xx Atheros 8131/8132/8151/8152/8161/8162/8171/8172
|
||||
Killer E2200/E2400/E2500
|
||||
- dec21x4x* Accton EN1207B-TX, DEC 21140, ..., Virtual PC
|
||||
- forcedeth* NVIDIA nForce chipset integrated Ethernet
|
||||
- i8255x Intel PRO/100 (i8255x, i82562, i82801, ...), QEMU
|
||||
- i8254x Intel PRO/1000 (i8254x), VirtualBox, Bochs, QEMU
|
||||
- mtd80x Myson MTD803
|
||||
- pcnet32 AMD PCnet (79c970, 79c978, 79c973, ...), VirtualBox, QEMU
|
||||
- r6040 RDC Semiconductor R6040
|
||||
- rhine VIA Rhine II and III
|
||||
- rtl8029 Realtek 8029/8019, NE2000 PCI, Bochs, QEMU
|
||||
- rtl8139 Realtek 8139 and clones, QEMU
|
||||
- rtl8169 Realtek 8110/8111/8167/8168/8169
|
||||
- sis900 SiS900, 7016
|
||||
|
||||
* - in development
|
||||
|
||||
Blessed be our Flemish friend Jeffrey, who has written most of the Network stuff by himself: stack, drivers, and a lot of apps (except WebView).
|
||||
|
||||
<h3><kosicon n=12> WebView</h3>
|
||||
It was never intended to be a browser, but rather a Help Center that uses HTML markup. It fits this purpose well, doesn't it? According to current WebView's codebase (that was my first app ever) it will never become a normal web browser. Please help us and write/port something good. For example, it would be nice to try to finish the NetSurf port.
|
||||
|
||||
<h3><kosicon n=9> How did you achieve so much while being so small?</h3>
|
||||
- Kernel, almost all drivers and most applications are written in assembly language
|
||||
- Files in IMG are packed via the LZMA algorithm from 7-Zip. They are unpacked to RAM when accessed
|
||||
- Most of the images are manually optimized: posterization, dithering and other techniques were used
|
||||
- Files names in IMG fit 8.3 naming convention (name.extension) which doesn't use additional FAT12 space
|
||||
- Image files are combined into a single file
|
||||
- Compact binary header of applications, no sections
|
||||
- Resources are reused, and file duplication is avoided, for example:
|
||||
~ Many apps use shared libraries
|
||||
~ KFM2 and Eolite are a single app, just with different appearance
|
||||
~ APP+, Games Center and System Panel are a single app also
|
||||
~ Memory Blocks game uses system icons as images
|
||||
|
||||
|
||||
See more <a href=http://wiki.kolibrios.org/wiki/Hardware_Support>http://wiki.kolibrios.org/wiki/Hardware_Support</a>
|
||||
</dd>
|
||||
|
||||
<h3><kosicon n=13> Last words</h3>
|
||||
<font bg=#F9F0A3>We want to express our gratitude to every KolibriOS contributor. Also, we would like to give special thanks to Ville M. Turjanmaa, the author of MenuetOS, the predecessor of our system.
|
||||
|
||||
Finally, we want to thank you, brave and curious users, as we are doing it all for you!
|
||||
|
||||
Have fun!</font>
|
||||
|
||||
Sincerely yours,
|
||||
KolibriOS Team
|
||||
|
||||
<img src=/kolibrios/res/system/signs.png alt="You have to use Big Distro to see this image!">
|
||||
<!-- Somewhere deep in the Galaxy stars light up and go out, empires are born and turn to dust. In this impenetrable darkness and decadence, there are people who are still working on KolibriOS. Many years will pass and perhaps you and I will no longer be here, and our great-grandchildren will ask "Who were those insane who made such an incredible OS?" -->
|
||||
@@ -1,127 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Compile every C-- program in this repository and fail if any target
|
||||
# does not build. This mirrors what `tup` does in the main KolibriOS
|
||||
# build, but standalone: it reads each Tupfile.lua and runs the c--
|
||||
# compiler on the declared target(s).
|
||||
#
|
||||
# The compiler is not part of this repository - it lives in KolibriOS/cmm
|
||||
# and is expected on PATH, exactly as the tup rules invoke it.
|
||||
#
|
||||
# Usage: ./build.sh [LANG_ENG|LANG_RUS] (default: LANG_ENG)
|
||||
# Env: CMM=/path/to/c-- override compiler (default: c-- from PATH)
|
||||
#
|
||||
# Exit status: 0 if every program compiled, 1 otherwise.
|
||||
|
||||
set -u
|
||||
|
||||
# Let /D=... /OPATH=... survive Git Bash (MSYS) path mangling; a no-op on Linux.
|
||||
export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*'
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
CMM=${CMM:-c--}
|
||||
LANG_DEF=${1:-LANG_ENG}
|
||||
CMM_TIMEOUT=${CMM_TIMEOUT:-60} # seconds; a real compile takes <2s, so this only fires on a hang
|
||||
CMM_TRIES=${CMM_TRIES:-4} # retries to absorb the flaky c-- crash/hang
|
||||
DIST="$ROOT/dist" # built binaries are collected here for the CI artifact
|
||||
rm -rf "$DIST"
|
||||
mkdir -p "$DIST"
|
||||
|
||||
if ! command -v "$CMM" >/dev/null 2>&1 && [ ! -x "$CMM" ]; then
|
||||
echo "c-- not found: $CMM" >&2
|
||||
echo "build it from https://git.kolibrios.org/KolibriOS/cmm and put it on PATH," >&2
|
||||
echo "or point CMM at it." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "compiler : $CMM"
|
||||
echo "language : $LANG_DEF"
|
||||
echo "root : $ROOT"
|
||||
echo
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
failed=''
|
||||
|
||||
# compile_one <dir> <source.c> <extra-defines>
|
||||
compile_one() {
|
||||
d=$1
|
||||
src=$2
|
||||
defs=$3
|
||||
out="${src%.c}.com"
|
||||
log=$(mktemp)
|
||||
rc=0
|
||||
attempt=0
|
||||
# The c-- 0.239 build is known to be flaky in a heap-layout-dependent way:
|
||||
# on an unlucky run it can segfault (rc 139) or spin forever on a valid
|
||||
# source. So each compile is wrapped in a hard timeout and retried a few
|
||||
# times. A real compile error (non-zero rc with output produced quickly)
|
||||
# is NOT retried -- we stop and report it.
|
||||
while [ "$attempt" -lt "$CMM_TRIES" ]; do
|
||||
attempt=$((attempt + 1))
|
||||
( cd "$d" && rm -f "$out" && timeout -k 5 "$CMM_TIMEOUT" "$CMM" $defs "$src" ) >"$log" 2>&1
|
||||
rc=$?
|
||||
if [ "$rc" -eq 0 ] && [ -s "$d/$out" ]; then
|
||||
break
|
||||
fi
|
||||
# retry only on a crash / hang (compiler flakiness); stop on a real error
|
||||
case "$rc" in
|
||||
124 | 137 | 139 | 134 | 143) ;; # timeout / SIGKILL / SIGSEGV / SIGABRT / SIGTERM
|
||||
*) break ;;
|
||||
esac
|
||||
[ "$attempt" -lt "$CMM_TRIES" ] && printf ' retry %s/%s (rc=%s, attempt %s)\n' "$d" "$src" "$rc" "$attempt"
|
||||
done
|
||||
|
||||
if [ "$rc" -eq 0 ] && [ -s "$d/$out" ]; then
|
||||
pass=$((pass + 1))
|
||||
printf ' ok %s/%s\n' "$d" "$src"
|
||||
# collect under dist/ named as KolibriOS ships it: lowercase, without
|
||||
# the extension. The program dir is mirrored so same-named outputs
|
||||
# like menu/menu.com and examples/menu.com don't clash.
|
||||
dst="$DIST/${d#./}/$(printf '%s' "${out%.com}" | tr 'A-Z' 'a-z')"
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
cp "$d/$out" "$dst"
|
||||
else
|
||||
fail=$((fail + 1))
|
||||
failed="$failed $d/$src"
|
||||
printf ' FAIL %s/%s (rc=%s)\n' "$d" "$src" "$rc"
|
||||
sed 's/^/ | /' "$log"
|
||||
fi
|
||||
rm -f "$log"
|
||||
}
|
||||
|
||||
cd "$ROOT" || exit 2
|
||||
|
||||
for tf in $(find . -name Tupfile.lua | sort); do
|
||||
d=$(dirname "$tf")
|
||||
rule=$(grep -E 'tup\.(foreach_)?rule\(' "$tf" | head -1)
|
||||
[ -n "$rule" ] || continue
|
||||
|
||||
defs=''
|
||||
printf '%s' "$rule" | grep -q 'C_LANG' && defs="$defs /D=$LANG_DEF"
|
||||
printf '%s' "$rule" | grep -q 'AUTOBUILD' && defs="$defs /D=AUTOBUILD"
|
||||
|
||||
# input = first quoted argument of the tup.rule / tup.foreach_rule call
|
||||
input=$(printf '%s' "$rule" | sed -E 's/.*tup\.(foreach_)?rule\("([^"]+)".*/\2/')
|
||||
|
||||
echo "[$d] defs:${defs:- none}"
|
||||
if printf '%s' "$rule" | grep -q 'foreach_rule'; then
|
||||
# build every source matching the glob (e.g. *.c)
|
||||
for f in "$d"/$input; do
|
||||
[ -f "$f" ] || continue
|
||||
compile_one "$d" "$(basename "$f")" "$defs"
|
||||
done
|
||||
else
|
||||
compile_one "$d" "$input" "$defs"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "================================================================"
|
||||
echo "built OK : $pass"
|
||||
echo "failed : $fail"
|
||||
if [ "$fail" -ne 0 ]; then
|
||||
echo "failures :"
|
||||
for x in $failed; do echo " - $x"; done
|
||||
exit 1
|
||||
fi
|
||||
echo "all C-- programs compiled."
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+12
@@ -0,0 +1,12 @@
|
||||
; ┬ ¤Єюь Їрщых ьюцэю шчьхэшЄ№ эрёЄЁющъш ъюьяшы ЄюЁр яю єьюыўрэш■.
|
||||
J0 ; эх фхырЄ№ эрўры№э√щ jump эр main()
|
||||
r- ; чряЁхЄшЄ№ шчьхэхэшх фюёЄєяэющ ярь Єш т *.com Їрщырї
|
||||
X ; чряЁхЄшЄ№ тъы■ўхэшх т т√їюфэющ Їрщы эрфяшёш SPHINXC--
|
||||
4 ; ъюьяшышЁютрЄ№ ъюф фы 80386 яЁюЎхёёюЁр
|
||||
oc ; юяЁшьшчрЎш яю ЁрчьхЁє ъюфр
|
||||
w ; т√фртрЄ№ яЁхфєяЁхцфхэш
|
||||
de ; шёяюы№чютрЄ№ тЁхьхээюх Ёрё°шЁхэшх Єшяр яЁш фхыхэшш
|
||||
ON ; тъы■ўшЄ№ юяЄшьшчрЎш■ ўшёхы
|
||||
ost ; юяЄшьшчшЁютрЄ№ ЄхъёЄют√х ъюэёЄрэЄ√
|
||||
;dos4gw
|
||||
;env
|
||||
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user