Webview 4.0 #14
No files matched your search
@@ -1,2 +1,3 @@
|
||||
# CI build output collected by build.sh
|
||||
/dist/
|
||||
.vm/
|
||||
@@ -1,285 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
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.
|
Before Width: | Height: | Size: 3.2 KiB |
@@ -1,184 +0,0 @@
|
||||
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 (debug_mode) && (pw > draw_w + BODY_MARGIN) {
|
||||
debugln("anomaly");
|
||||
debugln(_line);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
//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 + left_gap - BODY_MARGIN / list.font_w;
|
||||
if (br < 0) br = 0;
|
||||
while(br) {
|
||||
if (ESBYTE[lbp + br]==' ') || (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) {
|
||||
br = draw_w - draw_x + left_gap - BODY_MARGIN / list.font_w;
|
||||
if (br < 0) {
|
||||
NewLine();
|
||||
br = draw_w - draw_x + left_gap - BODY_MARGIN / 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,513 +0,0 @@
|
||||
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 (text_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) {
|
||||
chrncat(#linebuf, ' ', sizeof(TWebBrowser.linebuf));
|
||||
} else 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");
|
||||
//}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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];
|
||||
}
|
||||
+1343
-394
File diff suppressed because it is too large.
Load diff
@@ -6,12 +6,3 @@
|
||||
@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
|
||||
)
|
||||
+193
-13
@@ -6,19 +6,17 @@ 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, _type, _charset)
|
||||
void _cache::add(dword _url, _data, _size, _charset)
|
||||
{
|
||||
dword data_pointer;
|
||||
data_pointer = malloc(_size);
|
||||
@@ -27,7 +25,6 @@ void _cache::add(dword _url, _data, _size, _type, _charset)
|
||||
|
||||
url.add(_url);
|
||||
size.add(_size);
|
||||
type.add(_type);
|
||||
charset.add(_charset);
|
||||
|
||||
current_buf = data_pointer;
|
||||
@@ -37,28 +34,211 @@ void _cache::add(dword _url, _data, _size, _type, _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();
|
||||
type.drop();
|
||||
charset.drop();
|
||||
current_buf = NULL;
|
||||
current_size = NULL;
|
||||
current_type = 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();
|
||||
}
|
||||
+49
-29
@@ -6,7 +6,6 @@
|
||||
//===================================================//
|
||||
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+<
|
||||
@@ -16,9 +15,11 @@ Forward|Ctrl+>
|
||||
Редактировать исходник|F2";
|
||||
char main_menu[] =
|
||||
"Открыть файл...|Ctrl+O
|
||||
Новое окно|Ctrl+N
|
||||
Открыть в NetSurf
|
||||
-
|
||||
Мелкий шрифт|Ctrl-
|
||||
Крупный шрифт|Ctrl+
|
||||
-
|
||||
История|Ctrl+H
|
||||
Менеджер загрузок|Ctrl+J
|
||||
Настройки|Ctrl+E
|
||||
Очистить кэш|Ctrl+F5
|
||||
@@ -29,16 +30,27 @@ char link_menu[] =
|
||||
-
|
||||
Копировать ссылку
|
||||
Скачать содержимое ссылки";
|
||||
char loading_text[] = "Загрузка...";
|
||||
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 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 update_url[] = "http://builds.kolibrios.org/ci/latest/ru_RU/data/programs/cmm/browser/WebView.com";
|
||||
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
|
||||
//===================================================//
|
||||
// //
|
||||
@@ -47,7 +59,6 @@ char clear_cache_ok[] = "'WebView\n
|
||||
//===================================================//
|
||||
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+<
|
||||
@@ -57,9 +68,11 @@ View source|Ctrl+U
|
||||
Edit source|F2";
|
||||
char main_menu[] =
|
||||
"Open local file...|Ctrl+O
|
||||
New window|Ctrl+N
|
||||
Open in NetSurf
|
||||
-
|
||||
Smaller font|Ctrl-
|
||||
Larger font|Ctrl+
|
||||
-
|
||||
History|Ctrl+H
|
||||
Download Manager|Ctrl+J
|
||||
Settings|Ctrl+E
|
||||
Clear cache|Ctrl+F5
|
||||
@@ -70,15 +83,26 @@ Open in new window
|
||||
-
|
||||
Copy link
|
||||
Download link contents";
|
||||
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 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 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
|
||||
|
||||
//===================================================//
|
||||
@@ -87,11 +111,7 @@ 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";
|
||||
|
||||
@@ -100,19 +120,19 @@ enum {
|
||||
ENCODINGS=700,
|
||||
BACK_BUTTON=800,
|
||||
FORWARD_BUTTON, VIEW_SOURCE, EDIT_SOURCE,
|
||||
REFRESH_BUTTON, GOTOURL_BUTTON, CHANGE_ENCODING,
|
||||
GO_BUTTON, SEARCH_BUTTON, SANDWICH_BUTTON,
|
||||
OPEN_FILE, NEW_WINDOW, VIEW_HISTORY, DOWNLOAD_MANAGER,
|
||||
REFRESH_BUTTON, CHANGE_ENCODING, SANDWICH_BUTTON, DOWNLOADS_FOLDER,
|
||||
OPEN_FILE, OPEN_IN_NETSURF, FONT_SMALLER, FONT_LARGER, DOWNLOAD_MANAGER,
|
||||
OPEN_SETTINGS, CLEAR_CACHE, UPDATE_BROWSER, IN_NEW_TAB, IN_NEW_WINDOW,
|
||||
COPY_LINK_URL, DOWNLOAD_LINK_CT, TAB_ID,
|
||||
COPY_LINK_URL, DOWNLOAD_LINK_CT, IMG_OPEN, IMG_SAVE, IMG_COPY_URL,
|
||||
COPY_SELECTION,
|
||||
FIND_PREV, FIND_NEXT, FIND_CLOSE,
|
||||
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 3.98";
|
||||
char version[]="WebView 4.0";
|
||||
@@ -0,0 +1,207 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,68 @@
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,908 @@
|
||||
// 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 420
|
||||
#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%
|
||||
#define GRID_TRACKS 8
|
||||
#define GT_PCT 0x20000000
|
||||
#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 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_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
|
||||
@@ -0,0 +1,972 @@
|
||||
// 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"
|
||||
@@ -0,0 +1,829 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
// 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
@@ -0,0 +1,752 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
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), and
|
||||
// in the 8x16 font (_big) the down arrow 0x19, which only the 6x9 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;
|
||||
n++;
|
||||
}
|
||||
RETURN n;
|
||||
}
|
||||
|
||||
// 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, or the arrow of a <select> drawn in the small font
|
||||
IF (DSBYTE[_str + room] == 0x19) {
|
||||
k = room * fw;
|
||||
k += _x;
|
||||
cv_arrow(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;
|
||||
|
||||
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); }
|
||||
}
|
||||
|
||||
// ---- 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:
|
||||
IF (DSDWORD[f + F_BG] != NO_COLOR) cv_bar(fx, fy, fw, fh, DSDWORD[f + F_BG]);
|
||||
paint_bg_image(DSDWORD[f + F_NODE], 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.
|
After Width: | Height: | Size: 766 B |
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,199 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
// 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.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
||||
▐▐▌бЯЮВБ~xwtВБ~бЯЮ▐▐▌ ╜╗╗ ¤¤¤млк{xuЙЕГСОМЦУССОМЙЕГ{xuмлл╗╗╣rpm нмй~{ПНКЙЕГ~{x{xu|yЙЕГПНККЖДЙЕГxvs ▐▐▌{xuПНКБ|ввЯччх√√√ччхеввКИДЩХТЩХТyxu вЯЯЙЕГЙЕГввЯ ╛╜╗КЖДЩХТЩХТyxu ГБСОМ~{xччх ╜╗╗rpmxvsyxuyxutqo xwtЦУС{xu··· ДБСОМ~{xшчч бЮЭЙЕГЙЕГдба пон}yx┤│▓ ▌▌▌{xuПНКБ|двбччч√√√чччдвб~{ЙЕГ|yv нкк~{ПНКЙЕГ~{x{xu~{xЙЕГПНК~{нлй √√√нкк{xuЙЕГСОМЦУССОМЙЕГ{xuнкк№№№ ▐▌▌вЯЯВБxwtВБвЯЯ▐▌▌ ▄▄▄ ,╨ ,╨ ,╨ФОъ,╨ ,╨ФОъ,╨ ,╨ФОъJA┘JA┘JA┘ JA┘ФОъJA┘JA┘,╨ ,╨JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘,╨ жJA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘,╨ ж жJA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘,╨ ж ,╨JA┘JA┘JA┘JA┘JA┘JA┘JA┘,╨ ж ,╨JA┘JA┘JA┘JA┘JA┘JA┘,╨ ,╨JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘ ж ,╨ФОъJA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘JA┘,╨ ,╨ФОъJA┘JA┘JA┘JA┘,╨,╨JA┘JA┘JA┘JA┘JA┘,╨ ,╨JA┘JA┘JA┘JA┘,╨ ,╨JA┘JA┘JA┘,╨ ж ,╨JA┘,╨ ж ,╨JA┘,╨ ж ж ж ,╨ ж ▄▄▄
|
||||
@@ -1,19 +0,0 @@
|
||||
<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
|
||||
@@ -1,17 +0,0 @@
|
||||
<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> Перемещает текстовый курсор в омнибокс
|
||||
@@ -1,121 +0,0 @@
|
||||
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);
|
||||
}
|
||||
+12
-16
@@ -1,7 +1,7 @@
|
||||
<html><head><title>New tab</title></head>
|
||||
<title>New tab</title>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<table><tr><td width=20><td width=220><pre>
|
||||
<center><table width=750><td width=20><td width=220 valign=top><pre style="margin-top:0">
|
||||
_____________________
|
||||
|# : : #|
|
||||
| : WebView : |
|
||||
@@ -14,25 +14,21 @@
|
||||
| || | | |
|
||||
\_____||__|________|__|<font color=#DDD>lc</font>
|
||||
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 1.0 <font bg=#47D018> compatible
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 2.0 <font bg=#47D018> compatible
|
||||
<font bg=#3CE7FF> </font></font></font></font>
|
||||
<td>
|
||||
|
||||
</pre>
|
||||
<kosicon n=58><a href=//kolibrios.org>Homepage</a>
|
||||
<kosicon n=50><a href="//builds.kolibrios.org/ci/latest/">Night-builds</a>
|
||||
<kosicon n=50><a href="//ftp.kolibrios.org">FTP Server</a> <br>
|
||||
<br>
|
||||
<kosicon n=17><a href="//apps.kolibrios.org">Kolibri App Storage</a>
|
||||
<pre>
|
||||
<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>
|
||||
|
||||
By the way,
|
||||
<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="#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.
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +40,7 @@ By the way,
|
||||
' |
|
||||
() .-.,="``"=. - o -
|
||||
'=/_ \ |
|
||||
* | '=.<a href=WebView:test> </a> |
|
||||
* | '=.<a href=//ftp.kolibrios.org/users/Leency/test_files/links.htm> </a> |
|
||||
\ `=./`, '
|
||||
. '=.__.=' `=' *
|
||||
+ +
|
||||
|
||||
+13
-17
@@ -1,7 +1,7 @@
|
||||
<html><head><title>Новая вкладка</title></head>
|
||||
<title>Новая вкладка</title>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<table><tr><td width=20><td width=220><pre>
|
||||
<center><table width=750><td width=20><td width=220 valign=top><pre style="margin-top:0">
|
||||
_____________________
|
||||
|# : : #|
|
||||
| : WebView : |
|
||||
@@ -14,25 +14,21 @@
|
||||
| || | | |
|
||||
\_____||__|________|__|<font color=#DDD>lc</font>
|
||||
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 1.0 <font bg=#47D018> compatable
|
||||
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 2.0 <font bg=#47D018> compatible
|
||||
<font bg=#3CE7FF> </font></font></font></font>
|
||||
<td>
|
||||
|
||||
</pre>
|
||||
<kosicon n=58><a href=//kolibrios.org>Официальный сайт</a>
|
||||
<kosicon n=50><a href="//builds.kolibrios.org/ci/latest/">Ночные сборки</a>
|
||||
<kosicon n=50><a href="//ftp.kolibrios.org">FTP сервер</a> <br>
|
||||
<br>
|
||||
<kosicon n=17><a href="//apps.kolibrios.org">Центр Приложений</a>
|
||||
<pre>
|
||||
<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>
|
||||
|
||||
|
||||
Кстати,
|
||||
<font color="#555555">• Из главного меню можно проверить наличие обновлений
|
||||
• Для поиска в Google наберите тест в адресной строке и нажмите Ctrl+Enter
|
||||
<font color="#555555">• Из главного меню можно проверить наличие обновлений.
|
||||
• Клавиша F6 перемещает текстовый курсор в омнибокс.
|
||||
• Можно изменить кодировку страницы, для этого нажмите на надпись в правом нижнем углу окна.
|
||||
|
||||
|
||||
• Кодировку страницы можно изменить в правом нижнем углу окна.
|
||||
• Нужна лучшая поддержка вэба? Используйте Netsurf или WebKit.
|
||||
|
||||
|
||||
|
||||
@@ -44,7 +40,7 @@
|
||||
' |
|
||||
() .-.,="``"=. - o -
|
||||
'=/_ \ |
|
||||
* | '=.<a href=WebView:test> </a> |
|
||||
* | '=.<a href=//ftp.kolibrios.org/users/Leency/test_files/links.htm> </a> |
|
||||
\ `=./`, '
|
||||
. '=.__.=' `=' *
|
||||
+ +
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<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>
|
||||
<title>Page not found</title>
|
||||
<body bgcolor=#ccc>
|
||||
<br>
|
||||
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff><tr><td>
|
||||
<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>
|
||||
@@ -1,17 +1,16 @@
|
||||
<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>
|
||||
<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 к данному ПК/ноутбуку.
|
||||
|
||||
<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>
|
||||
Список поддерживаемых сетевых карт 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>
|
||||
@@ -1,39 +0,0 @@
|
||||
<html>
|
||||
<title>TEST</title>
|
||||
<body bgcolor=#fff>
|
||||
|
||||
<pre><h3><kosicon n=21>Tables</h3>
|
||||
<a href=//th99.infania.net>Total Hardware 1999</a>
|
||||
<a href=//xcal1.vodafone.co.uk>Vodafone Test Files</a>
|
||||
<a href=//vetusware.com>vetusware.com</a>
|
||||
<a href=//toastytech.com/guis/index.html>GUI Gallery</a>
|
||||
<a href=//www.weitek.com/textual/support/driver.html>weitek</a>
|
||||
<a href=http://www.retro-computing.com/Home/retro-computing.php>retro-computing</a>
|
||||
<a href=https://www.alanwood.net/unicode/cyrillic.html>cyrillic</a>
|
||||
|
||||
<pre><h3><kosicon n=22>Demoscene</h3>
|
||||
<a href="https://amp.dascene.net/home.php">Amiga Music Preservation</a>
|
||||
<a href="https://files.scene.org">Scene.org</a>
|
||||
<a href=https://www.pouet.net/>Pouet.net</a>
|
||||
<a href="https://aminet.net/tree?path=mods">Aminet</a>
|
||||
<a href=https://barefeats.com/indarch.html>barefeats</a>
|
||||
|
||||
<h3><kosicon n=13>Forms</h3>
|
||||
<a href=//wiby.org>wiby.org</a>
|
||||
|
||||
|
||||
<h3><kosicon n=3>Other</h3>
|
||||
<a href=//menuetos.net>menuetos.net</a>
|
||||
<a href=//osnews.com>osnews.com</a>
|
||||
|
||||
|
||||
<h3><kosicon n=54>Cyrillic only</h3>
|
||||
<a href=//dgmag.in>dgmag.in</a> <a href=//old-dos.ru>old-dos.ru</a> <a href=//lionovsky.us>lionovsky.us</a> <a href=//mirrors.pdp-11.ru>mirrors.pdp-11.ru</a>
|
||||
<a href=//artcon.ru>artcon.ru</a> <a href=//nubo.ru>nubo.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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
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:.
|
||||
+14
-9
@@ -1,10 +1,15 @@
|
||||
_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=");
|
||||
_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;
|
||||
}
|
||||
+49
-40
@@ -1,91 +1,100 @@
|
||||
enum { TAG=1, VALUE, TEXT, COMMENT, SCRIPT};
|
||||
|
||||
int opened_font_counter=0;
|
||||
int opened_font_counter;
|
||||
int mode;
|
||||
|
||||
dword source_buf_end;
|
||||
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;
|
||||
}
|
||||
|
||||
void SourceBufAdd(dword _mode, src)
|
||||
{
|
||||
dword font_found_pointer, src_orig = src;
|
||||
|
||||
if (_mode) mode = _mode;
|
||||
IF (_mode) mode = _mode;
|
||||
|
||||
SourceBufReserve();
|
||||
strcpy(source_buf_end, src);
|
||||
source_buf_end += strlen(source_buf_end);
|
||||
|
||||
if (font_found_pointer = strstr(src, "</font>")) {
|
||||
opened_font_counter--;
|
||||
}
|
||||
|
||||
if (font_found_pointer = strstr(src_orig, "<font ")) {
|
||||
opened_font_counter++;
|
||||
}
|
||||
IF (strstr(src, "</font>")) opened_font_counter--;
|
||||
IF (strstr(src, "<font ")) opened_font_counter++;
|
||||
}
|
||||
|
||||
void CloseAllOpenedFonts(dword _mode)
|
||||
{
|
||||
while (opened_font_counter) SourceBufAdd(_mode, "</font>");
|
||||
WHILE (opened_font_counter) SourceBufAdd(_mode, "</font>");
|
||||
}
|
||||
|
||||
dword ShowSource(dword _bufdata, _in_bufsize)
|
||||
void ShowSource(dword _bufdata, _in_bufsize)
|
||||
{
|
||||
dword i, j;
|
||||
dword i;
|
||||
bool activate_script_mode = false;
|
||||
dword source_buf_start;
|
||||
|
||||
opened_font_counter=0;
|
||||
source_buf_start = malloc(_in_bufsize*5);
|
||||
mode = TEXT;
|
||||
source_buf_cap = _in_bufsize * 2;
|
||||
source_buf_cap += 4096;
|
||||
source_buf_start = malloc(source_buf_cap);
|
||||
source_buf_end = source_buf_start;
|
||||
|
||||
SourceBufAdd(TEXT, "<html><head><title>View Source</title><body><pre>");
|
||||
SourceBufAdd(TEXT, "<html><head><title>View Source</title></head><body><pre>");
|
||||
|
||||
for (i=_bufdata; i<_bufdata+_in_bufsize; i++) switch (ESBYTE[i])
|
||||
for (i=_bufdata; i<_bufdata+_in_bufsize; i++) switch (DSBYTE[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:
|
||||
ESBYTE[source_buf_end] = ESBYTE[i];
|
||||
SourceBufReserve();
|
||||
DSBYTE[source_buf_end] = DSBYTE[i];
|
||||
source_buf_end++;
|
||||
}
|
||||
ESBYTE[source_buf_end] = 0;
|
||||
DSBYTE[source_buf_end] = 0;
|
||||
LoadInternalPage(source_buf_start, source_buf_end-source_buf_start);
|
||||
free(source_buf_start);
|
||||
}
|
||||
+107
-45
@@ -5,7 +5,7 @@
|
||||
// //
|
||||
//===================================================//
|
||||
|
||||
#define TABS_MAX 5
|
||||
// TABS_MAX is defined in engine/engine.h (the engine keeps a document per tab too)
|
||||
|
||||
TWebBrowser tabdata[TABS_MAX+1];
|
||||
_history tabstory[TABS_MAX+1];
|
||||
@@ -23,39 +23,59 @@ 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];
|
||||
return true;
|
||||
engine_new_tab();
|
||||
RETURN true;
|
||||
}
|
||||
|
||||
bool TAB::close(int _tab_number)
|
||||
{
|
||||
int i;
|
||||
if (count==1) return false;
|
||||
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];
|
||||
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);
|
||||
}
|
||||
|
||||
//===================================================//
|
||||
@@ -69,9 +89,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)
|
||||
@@ -79,20 +99,28 @@ void DrawTab(int _id)
|
||||
#define CLOSE_S 13
|
||||
dword bgcol, border_bottom_color;
|
||||
char header_no_version[sizeof(TWebBrowser.header)];
|
||||
char name[DEFAULT_TABW/6];
|
||||
char name[64];
|
||||
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;
|
||||
}
|
||||
if (tabdata[_id].header) {
|
||||
name[0] = 0; // a tab still waiting for its page has no header yet
|
||||
IF (tabdata[_id].header) {
|
||||
strncpy(#header_no_version, #tabdata[_id].header, strlen(#tabdata[_id].header)-sizeof(version)-2);
|
||||
strncpy(#name, #header_no_version, tab_w-CLOSE_S/6-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);
|
||||
}
|
||||
DrawBar(xxx, TOOLBAR_H, 1, TAB_H, sc.dark);
|
||||
DrawBar(xxx+1, TOOLBAR_H, tab_w-1, TAB_H-1, bgcol);
|
||||
@@ -100,33 +128,40 @@ 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-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");
|
||||
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");
|
||||
}
|
||||
|
||||
void DrawActiveTab()
|
||||
{
|
||||
if (tab_w == GetTabWidth()) DrawTab(tab.active);
|
||||
else DrawTabsBar();
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
DrawRaisedButton(xxx+1, TOOLBAR_H, NEW_TAB, "+");
|
||||
RETURN xxx + TAB_H + 2;
|
||||
} ELSE {
|
||||
RETURN xxx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,26 +169,43 @@ 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);
|
||||
if (_id == tab.active) {
|
||||
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();
|
||||
}
|
||||
tab.close(_id);
|
||||
tab.restore(tab.active);
|
||||
SetElementSizes();
|
||||
WB1.Reparse();
|
||||
WB1.DrawPage();
|
||||
SetOmniboxText(history.current());
|
||||
} else {
|
||||
ShowRestoredTab();
|
||||
} ELSE {
|
||||
tab.close(_id);
|
||||
}
|
||||
DrawTabsBar();
|
||||
status_text[0] = 0; // it spoke of the other tab
|
||||
DrawStatusBar(NULL);
|
||||
}
|
||||
|
||||
@@ -164,25 +216,35 @@ void EventCloseActiveTab()
|
||||
|
||||
void EventTabClick(int _id)
|
||||
{
|
||||
if (_id>=tab.count) _id = 0;
|
||||
if (_id==-1) _id = tab.count-1;
|
||||
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;
|
||||
tab.save_state();
|
||||
tab.restore(_id);
|
||||
DrawTabsBar();
|
||||
SetElementSizes();
|
||||
WB1.Reparse();
|
||||
WB1.DrawPage();
|
||||
SetOmniboxText(history.current());
|
||||
ShowRestoredTab();
|
||||
status_text[0] = 0; // it spoke of the other tab
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<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>
|
||||
@@ -0,0 +1,30 @@
|
||||
<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>
|
||||
@@ -0,0 +1,20 @@
|
||||
<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>
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
@@ -0,0 +1,12 @@
|
||||
<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>
|
||||
@@ -0,0 +1,21 @@
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
@@ -0,0 +1,75 @@
|
||||
<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>
|
||||
@@ -0,0 +1,28 @@
|
||||
<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>
|
||||
@@ -0,0 +1,32 @@
|
||||
<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>
|
||||
@@ -0,0 +1,35 @@
|
||||
<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>
|
||||
@@ -0,0 +1,27 @@
|
||||
<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>
|
||||
@@ -0,0 +1,16 @@
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<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>
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
@@ -0,0 +1,34 @@
|
||||
<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>
|
||||
@@ -0,0 +1,17 @@
|
||||
<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>
|
||||
@@ -0,0 +1,58 @@
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
out/
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,101 @@
|
||||
# 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
|
||||
@@ -0,0 +1,190 @@
|
||||
"""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"))
|
||||
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
@@ -0,0 +1,26 @@
|
||||
<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>
|
||||
@@ -0,0 +1,2 @@
|
||||
h1 { color: #c00; }
|
||||
.note { background: #ffe; font-weight: bold; }
|
||||
@@ -0,0 +1,8 @@
|
||||
<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>
|
||||
@@ -0,0 +1,8 @@
|
||||
<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>
|
||||
@@ -0,0 +1,97 @@
|
||||
<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?" -->
|
||||
BIN
Binary file not shown.
@@ -21,6 +21,8 @@
|
||||
|
||||
#define T_FILE_SAVED_AS "'Менеджер загрузок\nФайл сохранен как %s' -Dt"
|
||||
#define T_FILE_NOT_SAVED "'Менеджер загрузок\nОшибка! Файл не может быть сохранен как\n%s' -Et"
|
||||
#define T_STATUS_BROKEN "Загрузка прервана, файл не сохранён. "
|
||||
#define T_ERROR_DOWNLOAD_BROKEN "'Менеджер загрузок\nСоединение оборвалось до конца файла.\nНичего не сохранено.' -Et"
|
||||
#define T_ERROR_STARTING_DOWNLOAD "'Невозможно начать скачивание.\nПроверьте введенный путь и соединение с Интернетом.' -E"
|
||||
char accept_language[]= "Accept-Language: ru\n";
|
||||
#else
|
||||
@@ -44,6 +46,8 @@
|
||||
|
||||
#define T_FILE_SAVED_AS "'Download manager\nFile saved as %s' -Dt"
|
||||
#define T_FILE_NOT_SAVED "'Download manager\nError! Can\96t save file as %s' -Et"
|
||||
#define T_STATUS_BROKEN "Download interrupted, nothing saved. "
|
||||
#define T_ERROR_DOWNLOAD_BROKEN "'Download manager\nThe connection was lost before the end of the file.\nNothing was saved.' -Et"
|
||||
#define T_ERROR_STARTING_DOWNLOAD "'Error while starting download process.\nCheck entered path and Internet connection.' -E"
|
||||
char accept_language[]= "Accept-Language: en\n";
|
||||
#endif
|
||||
|
||||
+85
-14
@@ -13,6 +13,12 @@
|
||||
#include "const.h"
|
||||
|
||||
bool open_file = false;
|
||||
bool invisible = false;
|
||||
|
||||
// a hidden dl gives up by itself: the one who started it must not kill it,
|
||||
// the kernel does not close the sockets of a killed process
|
||||
#define INVISIBLE_TIMEOUT 2500 // 1/100 s
|
||||
dword start_time;
|
||||
|
||||
dword speed;
|
||||
|
||||
@@ -58,7 +64,7 @@ void main()
|
||||
sc.get();
|
||||
InitDownload();
|
||||
|
||||
if (param) {
|
||||
if (param[0]) {
|
||||
path_pointer = #param;
|
||||
//save path specified? Example: https://example.com/file|/sys/media/ac97snd
|
||||
if (save_path_separator_pos = strchr(#param, '|')) {
|
||||
@@ -66,11 +72,19 @@ void main()
|
||||
strcpy(#filepath, save_path_separator_pos+1);
|
||||
ESBYTE[save_path_separator_pos] = '\0';
|
||||
}
|
||||
if (streqrp(#param, "-e ")) {
|
||||
// invisible window
|
||||
if (!strncmp(#param, "-i ", 3)) {
|
||||
autoclose.checked = true;
|
||||
invisible = true;
|
||||
path_pointer += 3;
|
||||
}
|
||||
// autoclose
|
||||
if (!strncmp(#param, "-e ", 3)) {
|
||||
autoclose.checked = true;
|
||||
path_pointer += 3;
|
||||
}
|
||||
if (streqrp(#param, "-eo ")) {
|
||||
// autoclose and open file
|
||||
if (!strncmp(#param, "-eo ", 4)) {
|
||||
autoclose.checked = true;
|
||||
open_file = true;
|
||||
path_pointer += 4;
|
||||
@@ -95,7 +109,8 @@ void main()
|
||||
}
|
||||
|
||||
@SetEventMask(EVM_REDRAW + EVM_KEY + EVM_BUTTON + EVM_MOUSE + EVM_MOUSE_FILTER + EVM_STACK);
|
||||
loop() switch(@WaitEvent())
|
||||
start_time = GetStartTime();
|
||||
loop() switch(@WaitEventTimeout(10))
|
||||
{
|
||||
case evMouse: edit_box_mouse stdcall (#ed); break;
|
||||
case evButton: ProcessButtonClick(@GetButtonID()); break;
|
||||
@@ -151,6 +166,8 @@ void DrawDlButton(int x, id, t)
|
||||
|
||||
void DefineWindow()
|
||||
{
|
||||
if (invisible) return;
|
||||
|
||||
sc.get();
|
||||
pb.frame_color = ed.border_color = ed.focus_border_color = sc.line;
|
||||
DefineAndDrawWindow(110 + random(300), 100 + random(300), WIN_W+9,
|
||||
@@ -161,6 +178,9 @@ void DefineWindow()
|
||||
void DrawWindow()
|
||||
{
|
||||
#define BUT_Y 58;
|
||||
|
||||
if (invisible) return;
|
||||
|
||||
if (!http.transfer) && (!filepath) {
|
||||
DrawBar(GAPX, 102, WIN_W - GAPX - GAPX+1, 24+1, sc.work);
|
||||
DeleteButton(BTN_DIR);
|
||||
@@ -200,6 +220,7 @@ void StartDownloading()
|
||||
pb.back_color = 0xFFFfff;
|
||||
if (!strncmp(#uEdit,"https://",8)) {
|
||||
if (!proxy_address[0]) {
|
||||
if (invisible) DlExit();
|
||||
pb.progress_color = PB_COL_ERROR;
|
||||
notify("'HTTPS URLs require proxy_address in app.ini' -E");
|
||||
StopDownloading();
|
||||
@@ -208,7 +229,7 @@ void StartDownloading()
|
||||
return;
|
||||
}
|
||||
strcpy(#get_url, #proxy_address);
|
||||
strncat(#get_url, #uEdit, URL_SIZE);
|
||||
strcat_proxy_site(#get_url, #uEdit, sizeof(get_url));
|
||||
} else {
|
||||
strcpy(#get_url, #uEdit);
|
||||
}
|
||||
@@ -220,6 +241,7 @@ void StartDownloading()
|
||||
DrawWindow();
|
||||
} else {
|
||||
pb.progress_color = PB_COL_ERROR;
|
||||
if (invisible) DlExit();
|
||||
notify(T_ERROR_STARTING_DOWNLOAD);
|
||||
StopDownloading();
|
||||
EditBox_UpdateText(#ed, ed_focus + ed_always_focus);
|
||||
@@ -255,10 +277,18 @@ void DrawDownloadingProgress()
|
||||
WriteTextWithBg(GAPX, pb.top + 22, 0xD0, sc.work_text, active_status, sc.work);
|
||||
}
|
||||
|
||||
// leave without a socket behind: a connection nobody frees keeps getting data for a
|
||||
// thread that is gone ("SOCKET_notify: error finding thread" on the debug board)
|
||||
void DlExit()
|
||||
{
|
||||
StopDownloading();
|
||||
ExitProcess();
|
||||
}
|
||||
|
||||
void StopDownloading()
|
||||
{
|
||||
http.stop();
|
||||
if (http.content_pointer) http.content_pointer = free(http.content_pointer);
|
||||
http.stop(); // http_free() also releases content_ptr, do not free it twice
|
||||
http.content_pointer = 0;
|
||||
http.content_received = http.content_length = 0;
|
||||
}
|
||||
|
||||
@@ -275,6 +305,11 @@ void InitDownload()
|
||||
void MonitorProgress()
|
||||
{
|
||||
char redirect_url[URL_SIZE];
|
||||
dword t, got_all;
|
||||
if (invisible) {
|
||||
t = GetStartTime() - start_time;
|
||||
if (t > INVISIBLE_TIMEOUT) DlExit();
|
||||
}
|
||||
if (http.transfer <= 0) return;
|
||||
http.receive();
|
||||
if (!http.content_length) http.content_length = http.content_received * 20; //MOVE?
|
||||
@@ -285,6 +320,7 @@ void MonitorProgress()
|
||||
if (http.status_code >= 300) && (http.status_code < 400) {
|
||||
redirect_count++;
|
||||
if (redirect_count >= 5) {
|
||||
if (invisible) DlExit();
|
||||
notify(T_ERROR_TOO_MANY_REDIRECTS);
|
||||
ProcessButtonClick(BTN_STOP);
|
||||
EditBox_UpdateText(#ed, ed_focus + ed_always_focus);
|
||||
@@ -297,15 +333,38 @@ void MonitorProgress()
|
||||
StartDownloading();
|
||||
return;
|
||||
} else {
|
||||
// a hidden downloader must not store an error page as the requested file
|
||||
if (invisible) && (http.status_code >= 400) DlExit();
|
||||
got_all = http.flags & HTTP_GOT_ALL_DATA;
|
||||
if (!got_all) {
|
||||
DownloadBroken();
|
||||
return;
|
||||
}
|
||||
CompleteDownload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The transfer ended before the whole file came: libHTTP gave up after 5 s without
|
||||
// data, the connection dropped, or the server closed it before Content-Length.
|
||||
// Nothing is saved: a cut archive or picture would pass for the real file. A hidden
|
||||
// dl just leaves (WebView sees no file and treats it as a failed download).
|
||||
void DownloadBroken()
|
||||
{
|
||||
if (invisible) DlExit();
|
||||
StopDownloading();
|
||||
pb.progress_color = PB_COL_ERROR;
|
||||
progressbar_draw stdcall(#pb);
|
||||
active_status = T_STATUS_BROKEN;
|
||||
notify(T_ERROR_DOWNLOAD_BROKEN);
|
||||
EditBox_UpdateText(#ed, ed_focus + ed_always_focus);
|
||||
DrawWindow();
|
||||
}
|
||||
|
||||
void CompleteDownload()
|
||||
{
|
||||
SaveFile(0);
|
||||
if (autoclose.checked) ExitProcess();
|
||||
if (autoclose.checked) DlExit();
|
||||
pb.value = pb.max;
|
||||
pb.progress_color = PB_COL_COMPLETE;
|
||||
StopDownloading();
|
||||
@@ -318,6 +377,7 @@ void SaveFile(int attempt)
|
||||
int i;
|
||||
char notify_message[4296];
|
||||
char file_name[URL_SIZE+96];
|
||||
dword filename_pos = 0;
|
||||
|
||||
//if save_path is not set then we have to combine it
|
||||
if (!save_path_set) {
|
||||
@@ -336,11 +396,22 @@ void SaveFile(int attempt)
|
||||
|
||||
//Content-Disposition: attachment; filename="RealFootball_2018_Nokia_5800_EN_IGP_EU_TS_101.zip"
|
||||
if (http.header_field("content-disposition", #file_name, sizeof(file_name))) {
|
||||
if (EDX = strstr(#file_name,"filename=\"")) {
|
||||
strcat(#filepath, EDX+10);
|
||||
ESBYTE[strchr(#filepath,'\"')] = '\0';
|
||||
filename_pos = strstr(#file_name,"filename=");
|
||||
if (filename_pos) {
|
||||
filename_pos += 9;
|
||||
if (ESBYTE[filename_pos] == '\"') {
|
||||
// filename="name.ext"
|
||||
filename_pos++;
|
||||
if (EAX = strchr(filename_pos, '\"')) ESBYTE[EAX] = '\0';
|
||||
} else {
|
||||
// filename=name.ext; other=param
|
||||
if (EAX = strchr(filename_pos, ';')) ESBYTE[EAX] = '\0';
|
||||
if (EAX = strchr(filename_pos, ' ')) ESBYTE[EAX] = '\0';
|
||||
}
|
||||
strcat(#filepath, filename_pos);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if (!filename_pos) {
|
||||
// Clean all slashes at the end
|
||||
strcpy(#file_name, #uEdit);
|
||||
while (file_name[strlen(#file_name)-1] == '/') {
|
||||
@@ -367,7 +438,7 @@ void SaveFile(int attempt)
|
||||
miniprintf(#notify_message, T_FILE_NOT_SAVED, #filepath);
|
||||
}
|
||||
|
||||
if (!autoclose.checked) notify(#notify_message);
|
||||
notify(#notify_message);
|
||||
|
||||
|
||||
if (!strcmpi(#filepath+strlen(#filepath)-4, ".zip"))
|
||||
@@ -376,7 +447,7 @@ void SaveFile(int attempt)
|
||||
}
|
||||
|
||||
if (open_file) ProcessButtonClick(BTN_RUN);
|
||||
if (autoclose.checked) ExitProcess();
|
||||
if (autoclose.checked) DlExit();
|
||||
}
|
||||
|
||||
void Unarchive(dword _arc)
|
||||
|
||||
@@ -93,6 +93,7 @@ struct collection
|
||||
void drop();
|
||||
dword get_last();
|
||||
bool pop();
|
||||
void swap();
|
||||
};
|
||||
|
||||
:dword collection::add(dword in) {
|
||||
@@ -144,6 +145,12 @@ struct collection
|
||||
count = 0;
|
||||
}
|
||||
|
||||
// exchange two entries: only their offsets move, the strings stay where they are
|
||||
:void collection::swap(dword pos1, pos2) {
|
||||
if (pos1 >= count) || (pos2 >= count) return;
|
||||
offset.swap(pos1, pos2);
|
||||
}
|
||||
|
||||
:bool collection::pop() {
|
||||
if (count>0) count--;
|
||||
}
|
||||
|
||||
+39
-25
@@ -8,6 +8,10 @@
|
||||
|
||||
dword buf_data=0;
|
||||
|
||||
// The canvas keeps the size Init() gives it: what is drawn below its bottom is not
|
||||
// (WebView and Quark size it to the window). A program that needs more calls
|
||||
// IncreaseBufSize() itself (+50 MB a step) or writes with WriteTextLongBuf().
|
||||
// ":" before a method: it is compiled only into a program that calls it.
|
||||
|
||||
struct CANVAS {
|
||||
dword bufx, bufy, bufw, bufh;
|
||||
@@ -19,6 +23,7 @@ struct CANVAS {
|
||||
void DrawBar();
|
||||
void DrawImage();
|
||||
void WriteText();
|
||||
void WriteTextLongBuf();
|
||||
void PutPixel();
|
||||
void AlignCenter();
|
||||
void AlignRight();
|
||||
@@ -26,10 +31,6 @@ struct CANVAS {
|
||||
bool BufIsInvalid();
|
||||
};
|
||||
|
||||
char draw_buf_not_enaught_ram[] =
|
||||
"'CANVAS requested %i MB more memory than the system has.
|
||||
Application could be unstable.' -E";
|
||||
|
||||
bool CANVAS::BufIsInvalid()
|
||||
{
|
||||
if (!buf_data) || (!bufw) return true;
|
||||
@@ -40,10 +41,10 @@ bool CANVAS::Init(dword i_bufx, i_bufy, i_bufw, i_bufh)
|
||||
{
|
||||
bufx = i_bufx;
|
||||
bufy = i_bufy;
|
||||
bufw = i_bufw;
|
||||
bufw = i_bufw;
|
||||
bufh = i_bufh;
|
||||
if (buf_data) buf_data = free(buf_data);
|
||||
IncreaseBufSize();
|
||||
if (bufw) buf_data = malloc(bufh * bufw * 4 + 8);
|
||||
if (!buf_data) return false;
|
||||
ESDWORD[buf_data] = bufw;
|
||||
ESDWORD[buf_data+4] = bufh;
|
||||
@@ -60,24 +61,23 @@ void CANVAS::Fill(dword start_pointer, i_fill_color)
|
||||
|
||||
void CANVAS::DrawBar(dword x, y, w, h, color)
|
||||
{
|
||||
dword i, j;
|
||||
dword j, p, row;
|
||||
if (BufIsInvalid()) return;
|
||||
if (y + h >= bufh) IncreaseBufSize();
|
||||
row = bufw * 4;
|
||||
p = y * bufw + x * 4 + 8 + buf_data;
|
||||
for (j=0; j<h; j++) {
|
||||
for (i = y+j*bufw+x<<2+8+buf_data; i<y+j*bufw+x+w<<2+8+buf_data; i+=4) {
|
||||
ESDWORD[i] = color;
|
||||
}
|
||||
@MEMSETD(p, w, color);
|
||||
p += row;
|
||||
}
|
||||
}
|
||||
|
||||
void CANVAS::DrawImage(dword x, y, w, h, img)
|
||||
:void CANVAS::DrawImage(dword x, y, w, h, img)
|
||||
{
|
||||
dword i, j, p=0;
|
||||
dword ystart;
|
||||
if (BufIsInvalid()) return;
|
||||
if (y < 0) y = 0;
|
||||
if (x < 0) x = 0;
|
||||
while (y + h >= bufh) IncreaseBufSize(); //WHY NOT WORKING?
|
||||
for (j=0; j<h; j++) {
|
||||
ystart = y + j * bufw + x;
|
||||
for (i = ystart<<2+8+buf_data; i<ystart+w<<2+8+buf_data; i+=4) {
|
||||
@@ -87,14 +87,27 @@ void CANVAS::DrawImage(dword x, y, w, h, img)
|
||||
}
|
||||
}
|
||||
|
||||
void CANVAS::WriteText(dword x, y, byte fontType, dword color, str_offset, strlen)
|
||||
// text that would reach below the canvas is not drawn: drawing into a buffer the kernel
|
||||
// clips at its width only. 32 rows: the 8x16 font doubled, the tallest WebView and Quark use
|
||||
:void CANVAS::WriteText(dword x, y, byte fontType, dword color, str_offset, strlen)
|
||||
{
|
||||
if (BufIsInvalid()) return;
|
||||
if (y + 32 < bufh) {
|
||||
ESI = strlen;
|
||||
WriteBufText(x, y, fontType, color, str_offset, buf_data);
|
||||
}
|
||||
}
|
||||
|
||||
// text anywhere: the canvas grows under it, and past y 32000 the kernel is given the
|
||||
// part of the buffer from there on
|
||||
:void CANVAS::WriteTextLongBuf(dword x, y, byte fontType, dword color, str_offset, strlen)
|
||||
{
|
||||
#define BUGFIX_32000 32000
|
||||
dword ydiv=0;
|
||||
dword reserve_data_1, reserve_data_2;
|
||||
dword new_buf_offset;
|
||||
if (BufIsInvalid()) return;
|
||||
if (y + 30 >= bufh) IncreaseBufSize();
|
||||
if (y + 32 >= bufh) IncreaseBufSize();
|
||||
if (y < BUGFIX_32000) {
|
||||
ESI = strlen;
|
||||
WriteBufText(x, y, fontType, color, str_offset, buf_data);
|
||||
@@ -117,14 +130,14 @@ void CANVAS::WriteText(dword x, y, byte fontType, dword color, str_offset, strle
|
||||
}
|
||||
}
|
||||
|
||||
void CANVAS::PutPixel(dword x, y, color)
|
||||
:void CANVAS::PutPixel(dword x, y, color)
|
||||
{
|
||||
dword pos = y*bufw+x*4+8+buf_data;
|
||||
if (BufIsInvalid()) return;
|
||||
ESDWORD[pos] = color;
|
||||
}
|
||||
|
||||
void CANVAS::AlignRight(dword x,y,w,h, content_width)
|
||||
:void CANVAS::AlignRight(dword x,y,w,h, content_width)
|
||||
{
|
||||
dword i, j, l;
|
||||
dword content_left = w - content_width / 2;
|
||||
@@ -138,7 +151,7 @@ void CANVAS::AlignRight(dword x,y,w,h, content_width)
|
||||
}
|
||||
}
|
||||
|
||||
void CANVAS::AlignCenter(dword x,y,w,h, content_width)
|
||||
:void CANVAS::AlignCenter(dword x,y,w,h, content_width)
|
||||
{
|
||||
dword i, j, l;
|
||||
dword content_left = w - content_width / 2;
|
||||
@@ -158,9 +171,10 @@ void CANVAS::Show(dword _y_offset, _h)
|
||||
PutPaletteImage(_y_offset * bufw * 4 + buf_data+8, bufw, _h, bufx, bufy, 32, 0);
|
||||
}
|
||||
|
||||
void CANVAS::IncreaseBufSize()
|
||||
// the canvas 50 MB taller, the new rows filled with the last Fill() color
|
||||
:void CANVAS::IncreaseBufSize()
|
||||
{
|
||||
static dword bufh_initial;
|
||||
dword bufh_old;
|
||||
dword alloc_size;
|
||||
dword free_ram_size;
|
||||
char error_str[256];
|
||||
@@ -171,19 +185,19 @@ void CANVAS::IncreaseBufSize()
|
||||
alloc_size = bufh * bufw * 4 + 8;
|
||||
buf_data = malloc(alloc_size);
|
||||
} else {
|
||||
if (bufh_initial != bufh) bufh_initial = bufh;
|
||||
bufh_old = bufh;
|
||||
bufh += 4096*1600/bufw; //+50 Mb
|
||||
alloc_size = bufh * bufw * 4 + 8;
|
||||
buf_data = realloc(buf_data, alloc_size);
|
||||
Fill(bufh_initial * bufw * 4 + 8, fill_color);
|
||||
Fill(bufh_old * bufw * 4 + 8, fill_color);
|
||||
}
|
||||
bufh_initial = bufh;
|
||||
free_ram_size = GetFreeRAM() * 1024;
|
||||
if (alloc_size > free_ram_size) {
|
||||
sprintf(#error_str, #draw_buf_not_enaught_ram, alloc_size - free_ram_size/1048576);
|
||||
sprintf(#error_str, "'CANVAS requested %i MB more memory than the system has.
|
||||
Application could be unstable.' -E", alloc_size - free_ram_size/1048576);
|
||||
notify(#error_str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -16,6 +16,7 @@
|
||||
shared_mem = memopen(#shared_name, 16, SHM_CREATE + SHM_WRITE);
|
||||
if (EDX) shared_mem = memopen(#shared_name, 16, SHM_WRITE);
|
||||
}
|
||||
GetProcessInfo(#Form, SelfInfo);
|
||||
ESDWORD[shared_mem ] = _selected;
|
||||
ESDWORD[shared_mem + 4] = _x + Form.left + 5;
|
||||
ESDWORD[shared_mem + 8] = _y + Form.top + skin_h;
|
||||
|
||||
@@ -107,6 +107,8 @@
|
||||
#define KEY_SCROLLLOCK 00100000000b
|
||||
#define KEY_LWIN 01000000000b
|
||||
#define KEY_RWIN 10000000000b
|
||||
//Only real modifiers, without CapsLock/NumLock/ScrollLock/Win toggles
|
||||
#define KEY_MODIFIERS 00000111111b
|
||||
|
||||
:unsigned char key_ascii;
|
||||
:dword key_scancode, key_modifier, key_editbox;
|
||||
|
||||
+5
-4
@@ -83,6 +83,7 @@ PathShow_draw stdcall(#PathShow);
|
||||
#define ed_pass 1b
|
||||
#define ed_focus 10b //focused
|
||||
#define ed_shift 100b //flag is set when Shift is pressed
|
||||
#define ed_select 100b //rewritten box_lib (ES.SELECT): text between shift and pos is selected
|
||||
#define ed_shift_on 1000b
|
||||
#define ed_shift_bac 10000b //bit for Shift reset, if set the smth is selected
|
||||
#define ed_left_fl 100000b
|
||||
@@ -110,10 +111,10 @@ dword width, left, top,
|
||||
text,
|
||||
mouse_variable,
|
||||
flags,
|
||||
size, pos, offset,
|
||||
cl_curs_x, cl_curs_y,
|
||||
shift, shift_old,
|
||||
height, char_width;
|
||||
size, pos, offset;
|
||||
word cl_curs_x, cl_curs_y, // words in box_lib: dwords here put shift into ed_height
|
||||
shift, shift_old;
|
||||
dword height, char_width;
|
||||
};
|
||||
|
||||
:void EditBox_UpdateText(dword ed, _flags)
|
||||
|
||||
+143
-13
@@ -69,6 +69,10 @@ char aURIunescape[] = "unescape";
|
||||
#define FLAG_TIMEOUT_ERROR 1 << 19
|
||||
#define FLAG_TRANSFER_FAILED 1 << 20
|
||||
|
||||
// FLAG_GOT_ALL_DATA as a plain number for tests: C-- has no precedence, so
|
||||
// "flags & FLAG_GOT_ALL_DATA" would be "(flags & 1) << 2"
|
||||
#define HTTP_GOT_ALL_DATA 4
|
||||
|
||||
struct http_msg{
|
||||
dword socket; //socket on which the actual transfer happens
|
||||
dword flags; //flags, reflects status of the transfer using bitflags
|
||||
@@ -101,8 +105,14 @@ struct _http
|
||||
dword status_code;
|
||||
dword receive_result;
|
||||
dword content_pointer;
|
||||
dword headers; // extra request headers, 0 means accept_language
|
||||
// http_msg.flags after the last receive(). receive_result 0 means the transfer is
|
||||
// over, not that it succeeded: a timeout or a dropped connection ends it the same
|
||||
// way. Only (flags & HTTP_GOT_ALL_DATA) says the whole answer came.
|
||||
dword flags;
|
||||
|
||||
dword get();
|
||||
dword post();
|
||||
bool stop();
|
||||
void hfree();
|
||||
void receive();
|
||||
@@ -112,11 +122,31 @@ struct _http
|
||||
dword _http::get(dword _url)
|
||||
{
|
||||
cur_url = _url;
|
||||
http_get stdcall (_url, 0, 0, #accept_language);
|
||||
if (headers) http_get stdcall (_url, 0, 0, headers);
|
||||
else http_get stdcall (_url, 0, 0, #accept_language);
|
||||
transfer = EAX;
|
||||
return EAX;
|
||||
}
|
||||
|
||||
// Send a form by POST: _data holds _len bytes of application/x-www-form-urlencoded
|
||||
// body. libHTTP opens the connection with the headers, the body follows over the
|
||||
// same socket; the answer is then received like any other page.
|
||||
char post_form_type[] = "application/x-www-form-urlencoded";
|
||||
|
||||
dword _http::post(dword _url, _data, _len)
|
||||
{
|
||||
dword h, t;
|
||||
cur_url = _url;
|
||||
h = headers;
|
||||
if (!h) h = #accept_language;
|
||||
http_post stdcall (_url, 0, 0, h, #post_form_type, _len);
|
||||
t = EAX;
|
||||
transfer = t;
|
||||
if (!t) return 0;
|
||||
http_send stdcall (t, _data, _len);
|
||||
return t;
|
||||
}
|
||||
|
||||
void _http::hfree()
|
||||
{
|
||||
http_free stdcall (transfer);
|
||||
@@ -154,6 +184,7 @@ void _http::receive()
|
||||
}
|
||||
content_length = EDI.http_msg.content_length;
|
||||
content_received = EDI.http_msg.content_received;
|
||||
flags = EDI.http_msg.flags;
|
||||
}
|
||||
|
||||
:dword _http::header_field(dword _header_field, _dst, _size)
|
||||
@@ -181,24 +212,122 @@ void _http::receive()
|
||||
// //
|
||||
//===================================================//
|
||||
|
||||
:bool local_disk_num(dword _p)
|
||||
{
|
||||
if (ESBYTE[_p] < '0') return false;
|
||||
if (ESBYTE[_p] > '9') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
:int check_is_the_adress_local(dword _in)
|
||||
{
|
||||
if (ESBYTE[_in]!='/') return false;
|
||||
_in++;
|
||||
if(!strncmp(_in,"rd/",3)) return true;
|
||||
if(!strncmp(_in,"fd/",3)) return true;
|
||||
if(!strncmp(_in,"hd",2)) return true;
|
||||
if(!strncmp(_in,"bd",2)) return true;
|
||||
if(!strncmp(_in,"cd",2)) return true;
|
||||
// a disk name is followed by its number: "/cdn-cgi/image/..." (Cloudflare) or
|
||||
// "/hdimages/" on a site is a path of that site, not a CD or a hard disk
|
||||
if(!strncmp(_in,"hd",2)) && (local_disk_num(_in+2)) return true;
|
||||
if(!strncmp(_in,"bd",2)) && (local_disk_num(_in+2)) return true;
|
||||
if(!strncmp(_in,"cd",2)) && (local_disk_num(_in+2)) return true;
|
||||
if(!strncmp(_in,"sys/",4)) return true;
|
||||
if(!strncmp(_in,"tmp",3)) return true;
|
||||
if(!strncmp(_in,"usbhd",5)) return true;
|
||||
if(!strncmp(_in,"tmp",3)) && (local_disk_num(_in+3)) return true;
|
||||
if(!strncmp(_in,"usbhd",5)) && (local_disk_num(_in+5)) return true;
|
||||
if(!strncmp(_in,"kolibrios",9)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// "file:///sys/x" -> "/sys/x" in place; the rest of the code works with plain paths
|
||||
:void strip_file_scheme(dword _in)
|
||||
{
|
||||
if (!strncmp(_in, "file://", 7)) strcpy(_in, _in + 7);
|
||||
}
|
||||
|
||||
// A space is not allowed in a request line, but pages do write it:
|
||||
// href="/theme/M9L reloaded/style.css" has to travel as "M9L%20reloaded".
|
||||
:void url_escape_spaces(dword _url)
|
||||
{
|
||||
char buf[URL_SIZE+1];
|
||||
dword s, d, e, c;
|
||||
if (!strchr(_url, ' ')) return;
|
||||
s = _url;
|
||||
d = #buf;
|
||||
e = #buf;
|
||||
e += URL_SIZE;
|
||||
e -= 4;
|
||||
while (ESBYTE[s]) {
|
||||
if (d >= e) break;
|
||||
c = ESBYTE[s];
|
||||
if (c == ' ') {
|
||||
ESBYTE[d] = '%'; d++;
|
||||
ESBYTE[d] = '2'; d++;
|
||||
ESBYTE[d] = '0'; d++;
|
||||
} else {
|
||||
ESBYTE[d] = c; d++;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
ESBYTE[d] = 0;
|
||||
strcpy(_url, #buf);
|
||||
}
|
||||
|
||||
// A url as the value of the proxy's "?site=": the proxy decodes it once, so the
|
||||
// characters that split or end a query value travel escaped. Without this
|
||||
// "index.php?ct=core&action=css&id=2" reaches the site as "index.php?ct=core".
|
||||
// Appends to _dst, which holds _size bytes.
|
||||
:void strcat_proxy_site(dword _dst, _url, _size)
|
||||
{
|
||||
dword d, e, c;
|
||||
d = strlen(_dst);
|
||||
d += _dst;
|
||||
e = _dst;
|
||||
e += _size;
|
||||
e -= 4;
|
||||
while (ESBYTE[_url]) {
|
||||
if (d >= e) break;
|
||||
c = ESBYTE[_url];
|
||||
if (c == '%') || (c == '&') || (c == '+') || (c == '#') {
|
||||
ESBYTE[d] = '%'; d++;
|
||||
ESBYTE[d] = '2'; d++;
|
||||
if (c == '%') ESBYTE[d] = '5';
|
||||
else if (c == '&') ESBYTE[d] = '6';
|
||||
else if (c == '+') ESBYTE[d] = 'B';
|
||||
else ESBYTE[d] = '3';
|
||||
d++;
|
||||
} else {
|
||||
ESBYTE[d] = c; d++;
|
||||
}
|
||||
_url++;
|
||||
}
|
||||
ESBYTE[d] = 0;
|
||||
}
|
||||
|
||||
// the inverse of strcat_proxy_site(), in place
|
||||
:void proxy_site_unescape(dword _url)
|
||||
{
|
||||
dword s, d, c, h;
|
||||
s = _url;
|
||||
d = _url;
|
||||
while (ESBYTE[s]) {
|
||||
c = ESBYTE[s];
|
||||
if (c == '%') && (ESBYTE[s+1] == '2') {
|
||||
h = ESBYTE[s+2];
|
||||
if (h == '5') c = '%';
|
||||
else if (h == '6') c = '&';
|
||||
else if (h == 'B') || (h == 'b') c = '+';
|
||||
else if (h == '3') c = '#';
|
||||
if (c != '%') || (h == '5') s += 2;
|
||||
}
|
||||
ESBYTE[d] = c;
|
||||
d++;
|
||||
s++;
|
||||
}
|
||||
ESBYTE[d] = 0;
|
||||
}
|
||||
|
||||
:int check_is_the_url_absolute(dword _in)
|
||||
{
|
||||
if(!strncmp(_in,"file:",5)) return true;
|
||||
if(!strncmp(_in,"ftp:",4)) return true;
|
||||
if(!strncmp(_in,"http:",5)) return true;
|
||||
if(!strncmp(_in,"https:",6)) return true;
|
||||
@@ -218,19 +347,20 @@ void _http::receive()
|
||||
strcpy(#newurl, base_URL);
|
||||
|
||||
while (i=strstr(new_URL, "&")) strcpy(i+1, i+5);
|
||||
strip_file_scheme(new_URL);
|
||||
url_escape_spaces(new_URL);
|
||||
|
||||
if (check_is_the_url_absolute(new_URL)) return new_URL;
|
||||
|
||||
switch(ESBYTE[new_URL]) {
|
||||
case '.':
|
||||
new_URL++;
|
||||
if (ESBYTE[new_URL] == '/') {
|
||||
new_URL++;
|
||||
}
|
||||
else if (ESBYTE[new_URL] == '.') && (ESBYTE[new_URL+1] == '/') {
|
||||
_GO_UP:
|
||||
if (ESBYTE[new_URL+1] == '/') {
|
||||
new_URL+=2;
|
||||
newurl[strrchr(#newurl, '/')-1] = '\0';
|
||||
}
|
||||
else if (ESBYTE[new_URL+1] == '.') && (ESBYTE[new_URL+2] == '/') {
|
||||
_GO_UP:
|
||||
new_URL+=3;
|
||||
newurl[strrchr(#newurl, '/')-1] = '\0';
|
||||
}
|
||||
goto _DEFAULT;
|
||||
case '?':
|
||||
|
||||
@@ -29,7 +29,6 @@ dword ChangeCharset(dword from_chs, to_chs, conv_buf)
|
||||
|
||||
iconv_open cdecl (to_chs*10+#charsets, from_chs*10+#charsets);
|
||||
if (EAX==-1) {
|
||||
debugln("iconv: unsupported charset");
|
||||
return 0;
|
||||
}
|
||||
cd = EAX;
|
||||
@@ -41,7 +40,6 @@ dword ChangeCharset(dword from_chs, to_chs, conv_buf)
|
||||
if (EAX!=0)
|
||||
{
|
||||
cd = EAX;
|
||||
debugval("iconv failed", cd);
|
||||
if (from_chs == CH_UTF8) && (to_chs == CH_CP866) {
|
||||
utf8rutodos(conv_buf);
|
||||
}
|
||||
|
||||
+7
-1
@@ -18,6 +18,7 @@ collection real_names=0; //contains only real items without separators
|
||||
collection hotkeys=0;
|
||||
|
||||
int selected, win_x, win_y;
|
||||
bool window_defined = false;
|
||||
|
||||
int max_name_len;
|
||||
int max_hotkey_len;
|
||||
@@ -151,7 +152,11 @@ void main()
|
||||
@SetEventMask(EVM_REDRAW + EVM_KEY + EVM_BUTTON + EVM_MOUSE);
|
||||
loop() switch(WaitEvent())
|
||||
{
|
||||
case evMouse:
|
||||
case evMouse:
|
||||
// The kernel hands out a pending mouse event before the first
|
||||
// redraw, i.e. before our window exists and can be active: a
|
||||
// mouse move during startup would close the menu at once.
|
||||
if (!window_defined) break;
|
||||
GetProcessInfo(#Form, SelfInfo);
|
||||
if (!CheckActiveProcess(Form.ID)) exit();
|
||||
mouse.get();
|
||||
@@ -168,6 +173,7 @@ void main()
|
||||
|
||||
case evReDraw:
|
||||
DefineAndDrawWindow(win_x, win_y, menu1.w+4, menu1.h+4, 0x01, 0, 0, 0x01fffFFF);
|
||||
window_defined = true;
|
||||
Draw3DPopup(0,0,menu1.w+2,menu1.h+2);
|
||||
draw_list();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user