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 + + + + + + + + + + + + + + + + + +
+ + + + +
+
+ + + + + + + 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