diff --git a/Cargo.lock b/Cargo.lock
index 217d206..07e9c16 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -604,9 +604,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "ming-wm-lib"
-version = "0.1.5"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d90e1d57dcc9ff559f34d885a0c62e86ef881b4371e3f3b02c909460d3454b5"
+checksum = "2cc80b6035509629ecba931bc6851513fca9fa8bef2be965b8bd437dd00b3930"
[[package]]
name = "miniz_oxide"
diff --git a/Cargo.toml b/Cargo.toml
index 7e164ba..2ea0ebc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "koxinga"
-version = "0.1.1"
+version = "0.2.0-beta.0"
edition = "2021"
[[bin]]
@@ -8,5 +8,5 @@ name = "mingInternet_Koxinga_Browser"
path = "src/main.rs"
[dependencies]
-ming-wm-lib = "0.1.5"
+ming-wm-lib = "0.2.2"
reqwest = { version = "0.12", features = [ "blocking" ] }
diff --git a/install b/install
old mode 100644
new mode 100755
diff --git a/koxinga_hn.png b/koxinga_hn.png
index d5e0ad0..e6d2e62 100644
Binary files a/koxinga_hn.png and b/koxinga_hn.png differ
diff --git a/koxinga_wiki.png b/koxinga_wiki.png
index 7699aa8..daa868e 100644
Binary files a/koxinga_wiki.png and b/koxinga_wiki.png differ
diff --git a/koxinga_within.png b/koxinga_within.png
index 84292c7..f7ef84e 100644
Binary files a/koxinga_within.png and b/koxinga_within.png differ
diff --git a/old_koxinga_wiki.png b/old_koxinga_wiki.png
new file mode 100644
index 0000000..7699aa8
Binary files /dev/null and b/old_koxinga_wiki.png differ
diff --git a/old_koxinga_within.png b/old_koxinga_within.png
new file mode 100644
index 0000000..84292c7
Binary files /dev/null and b/old_koxinga_within.png differ
diff --git a/real_tests/wikipedia.html b/real_tests/wikipedia.html
new file mode 100644
index 0000000..a2f0273
--- /dev/null
+++ b/real_tests/wikipedia.html
@@ -0,0 +1,882 @@
+
+
+
+
+Wikipedia
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+Wikipedia
+
+The Free Encyclopedia
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/http.rs b/src/http.rs
index 73226c6..99f89cc 100644
--- a/src/http.rs
+++ b/src/http.rs
@@ -1,10 +1,27 @@
-use reqwest::blocking;
+use reqwest::blocking::Client;
-pub fn get(url: &str) -> Option {
- if let Ok(resp) = blocking::get(url) {
- if let Ok(text) = resp.text() {
- return Some(text);
+//for now, just a thin wrapper
+pub struct HttpClient {
+ client: Client,
+}
+
+impl std::default::Default for HttpClient {
+ fn default() -> Self {
+ //for privacy can change to more common one
+ let client = Client::builder().user_agent("Koxinga").build().unwrap();
+ Self {
+ client,
}
}
- None
+}
+
+impl HttpClient {
+ pub fn get(&self, url: &str) -> Option {
+ if let Ok(resp) = self.client.get(url).send() {
+ if let Ok(text) = resp.text() {
+ return Some(text);
+ }
+ }
+ None
+ }
}
diff --git a/src/main.rs b/src/main.rs
index 76aedc2..e5d5027 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,15 +3,17 @@ use std::vec;
use std::fmt;
use std::boxed::Box;
+//use ming_wm_lib::logging::log;
use ming_wm_lib::window_manager_types::{ DrawInstructions, WindowLike, WindowLikeType };
use ming_wm_lib::messages::{ WindowMessage, WindowMessageResponse };
use ming_wm_lib::utils::Substring;
use ming_wm_lib::framebuffer_types::Dimensions;
use ming_wm_lib::themes::ThemeInfo;
+use ming_wm_lib::fonts::measure_text;
use ming_wm_lib::ipc::listen;
mod http;
-use crate::http::get;
+use crate::http::HttpClient;
mod xml;
use crate::xml::{ parse, Node, OutputType };
@@ -55,6 +57,7 @@ impl fmt::Display for Mode {
#[derive(Default)]
struct KoxingaBrowser {
+ client: HttpClient,
dimensions: Dimensions,
mode: Mode,
max_lines: usize,
@@ -62,6 +65,7 @@ struct KoxingaBrowser {
url: Option,
input: String,
links: Vec,
+ title: Option,
top_level_nodes: Vec>,
page: Vec<(usize, usize, String, bool)>, //x, y, text, link colour or not
}
@@ -81,7 +85,7 @@ impl WindowLike for KoxingaBrowser {
WindowMessage::KeyPress(key_press) => {
match self.mode {
Mode::Normal => {
- let max_lines_screen = (self.dimensions[1] - 4) / LINE_HEIGHT - 1;
+ let max_lines_screen = (self.dimensions[1] - 2) / LINE_HEIGHT - 1;
if key_press.key == 'u' {
self.mode = Mode::Url;
self.input = self.url.clone().unwrap_or(String::new());
@@ -99,7 +103,7 @@ impl WindowLike for KoxingaBrowser {
}
WindowMessageResponse::JustRedraw
} else if key_press.key == 'j' {
- if self.top_line_no < self.max_lines - max_lines_screen {
+ if self.top_line_no < self.max_lines - max_lines_screen + 1 {
self.top_line_no += 1;
WindowMessageResponse::JustRedraw
} else {
@@ -109,7 +113,7 @@ impl WindowLike for KoxingaBrowser {
self.top_line_no = 0;
WindowMessageResponse::JustRedraw
} else if key_press.key == 'G' {
- self.top_line_no = self.max_lines - max_lines_screen;
+ self.top_line_no = self.max_lines - max_lines_screen + 1;
WindowMessageResponse::JustRedraw
} else {
WindowMessageResponse::DoNothing
@@ -120,6 +124,7 @@ impl WindowLike for KoxingaBrowser {
let new_url = if self.mode == Mode::Search {
"https://old-search.marginalia.nu/search?query=".to_string() + &self.input
} else if self.mode == Mode::Link {
+ self.mode = Mode::Normal;
let link_index = self.input.parse::().unwrap();
let url = self.url.as_ref().unwrap();
let mut link;
@@ -142,9 +147,10 @@ impl WindowLike for KoxingaBrowser {
//if Mode::Url
self.input.clone()
};
- if let Some(text) = get(&new_url) {
+ if let Some(text) = self.client.get(&new_url) {
self.url = Some(new_url.clone());
self.top_line_no = 0;
+ //log(&text);
self.top_level_nodes = parse(&text);
self.input = String::new();
self.calc_page();
@@ -179,11 +185,13 @@ impl WindowLike for KoxingaBrowser {
fn draw(&self, theme_info: &ThemeInfo) -> Vec {
let mut instructions = Vec::new();
- let max_lines_screen = (self.dimensions[1] - 4) / LINE_HEIGHT - 1;
+ let max_lines_screen = (self.dimensions[1] - 2) / LINE_HEIGHT - 1;
for p in &self.page {
let line_no = (p.1 - 2) / LINE_HEIGHT;
- if line_no >= self.top_line_no && line_no < self.top_line_no + max_lines_screen {
- instructions.push(DrawInstructions::Text([p.0, p.1 - LINE_HEIGHT * self.top_line_no], vec!["nimbus-roman".to_string()], p.2.clone(), if p.3 { theme_info.top_text } else { theme_info.text }, theme_info.background, Some(1), Some(11)));
+ if line_no >= self.top_line_no + max_lines_screen {
+ break;
+ } else if line_no >= self.top_line_no && line_no < self.top_line_no + max_lines_screen {
+ instructions.push(DrawInstructions::Text([p.0, p.1 - LINE_HEIGHT * self.top_line_no], vec!["nimbus-roman".to_string()], p.2.clone(), if p.3 { theme_info.top_text } else { theme_info.text }, theme_info.background, Some(1), None));
}
}
//mode
@@ -201,7 +209,12 @@ impl WindowLike for KoxingaBrowser {
}
fn title(&self) -> String {
- "Koxinga Browser".to_string()
+ let t = if let Some(title) = &self.title {
+ format!(": {}", title)
+ } else {
+ " Browser".to_string()
+ };
+ "Koxinga".to_string() + &t
}
fn subtype(&self) -> WindowLikeType {
@@ -223,6 +236,7 @@ impl KoxingaBrowser {
}
pub fn calc_page(&mut self) {
+ self.title = None;
self.page = Vec::new();
self.links = Vec::new();
let mut outputs = Vec::new();
@@ -230,14 +244,26 @@ impl KoxingaBrowser {
let html_index = self.top_level_nodes.iter().position(|n| n.tag_name == "html");
if let Some(html_index) = html_index {
for n in &self.top_level_nodes[html_index].children {
- if n.tag_name == "body" {
+ if n.tag_name == "head" {
+ //look for title, if any
+ for hn in &n.children {
+ if hn.tag_name == "title" {
+ if hn.children[0].text_node {
+ self.title = Some(hn.children[0].tag_name.clone());
+ }
+ }
+ }
+ } else if n.tag_name == "body" {
outputs = n.to_output();
+ break;
}
}
}
}
let mut y = 2;
let mut x = 2;
+ let mut indent = 0;
+ let mut line_count = 0;
let mut colour = false;
let mut link_counter = 0;
for o in outputs {
@@ -273,31 +299,43 @@ impl KoxingaBrowser {
if let Some(s) = os {
//leading and trailing whitespace is probably a mistake
let mut line = String::new();
+ if x == 2 {
+ x += indent;
+ }
let mut start_x = x;
for c in s.chars() {
- if x + 12 > self.dimensions[0] {
+ let c_width = measure_text(&["nimbus-roman".to_string(), "shippori-mincho".to_string()], c.to_string()).width + 1; //+1 for horiz spacing
+ if x + c_width > self.dimensions[0] {
//full line, add draw instruction
self.page.push((start_x, y, line, colour));
line = String::new();
- x = 2;
+ x = 2 + indent;
start_x = x;
y += LINE_HEIGHT;
+ line_count += 1;
}
line += &c.to_string();
- x += 12;
+ x += c_width;
}
if line.len() > 0 {
self.page.push((start_x, y, line, colour));
}
}
+ if let OutputType::Indent(space) = o {
+ indent = space;
+ if x == 2 {
+ x += indent;
+ }
+ }
if o == OutputType::Newline {
x = 2;
y += LINE_HEIGHT;
+ line_count += 1;
} else if o == OutputType::EndLink {
colour = false;
}
}
- self.max_lines = (y - 2) / LINE_HEIGHT;
+ self.max_lines = line_count;
}
}
diff --git a/src/xml.rs b/src/xml.rs
index ac8d34d..8bb7df0 100644
--- a/src/xml.rs
+++ b/src/xml.rs
@@ -10,20 +10,38 @@ use ming_wm_lib::utils::Substring;
// is bad, is good!!
const SELF_CLOSING: [&'static str; 9] = ["link", "meta", "input", "img", "br", "hr", "source", "track", "!DOCTYPE"];
+//not all of them, eg there is intentionally no div
+const BLOCK_LEVEL: [&'static str; 13] = ["p", "br", "li", "tr", "header", "footer", "section", "h1", "h2", "h3", "h4", "h5", "h6"];
+
+const REPLACE: [(&'static str, &'static str); 6] = [
+ (" ", " "),
+ ("'", "'"),
+ (""", "\""),
+ ("/", "/"),
+ (">", ">"),
+ ("<", "<"),
+];
+
fn is_whitespace(c: char) -> bool {
c == ' ' || c == '\x09'
}
fn handle_escaped(s: &str) -> String {
- s.replace(" ", " ").replace("'", "'").replace(""", "\"").to_string()
+ let mut s = s.to_string();
+ for rp in REPLACE {
+ s = s.replace(rp.0, rp.1);
+ }
+ s
}
-#[derive(PartialEq)]
+#[derive(Debug, PartialEq)]
pub enum OutputType {
StartLink(String),
EndLink,
Text(String),
Newline,
+ //only support one per line, once indented, will keep being indented until overriden, for now
+ Indent(usize),
}
#[derive(Clone, Default, Debug, PartialEq)]
@@ -45,14 +63,23 @@ impl Node {
} else if self.tag_name == "script" || self.tag_name == "style" {
//ignore script and style tags
return output;
+ } else if self.tag_name == "li" {
+ output.push(OutputType::Text("-".to_string()));
} else if let Some(href) = self.attributes.get("href") {
link = true;
output.push(OutputType::StartLink(href.to_string()));
+ } else if let Some(indent) = self.attributes.get("indent") {
+ //non-standard indent attribute, basically just to support HN
+ //remove quotes
+ let indent = indent.substring(1, indent.len() - 1);
+ if let Ok(indent) = indent.parse::() {
+ output.push(OutputType::Indent(indent * 32));
+ }
}
for c in &self.children {
output.extend(c.to_output());
}
- if self.tag_name == "p" || self.tag_name == "br" || self.tag_name == "li" || self.tag_name == "tr" {
+ if BLOCK_LEVEL.contains(&self.tag_name.as_str()) {
output.push(OutputType::Newline);
} else if link {
output.push(OutputType::EndLink);
@@ -274,4 +301,13 @@ fn test_comments_xml_parse() {
assert!(nodes[1].children[0].tag_name == " afterwards");
}
+/*#[test]
+fn test_real() {
+ use std::fs::read_to_string;
+ let nodes = parse(&read_to_string("./real_tests/wikipedia.html").unwrap());
+ println!("{:#?}", nodes[1].children);
+ println!("{:?}", nodes[1].children[1].to_output());
+ println!("{}", nodes[1].children[1].tag_name);
+}*/
+
//more tests 100% needed. yoink from news.ycombinator.com and en.wikipedia.org