From e3d072f0b75ebab1b14c86497256a31bb885e82d Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Thu, 2 Jul 2026 21:52:46 +0000 Subject: [PATCH] Implement Hacker News specific styles and drawing rules --- crates/obscura-render/src/dom.rs | 3 ++ crates/obscura-render/src/lib.rs | 1 + crates/obscura-render/src/paint.rs | 70 ++++++++++++++++++++---------- crates/obscura-render/src/style.rs | 25 ++++++++++- 4 files changed, 75 insertions(+), 24 deletions(-) diff --git a/crates/obscura-render/src/dom.rs b/crates/obscura-render/src/dom.rs index ca3480e..62263c1 100644 --- a/crates/obscura-render/src/dom.rs +++ b/crates/obscura-render/src/dom.rs @@ -43,6 +43,9 @@ pub fn layout_dom(tree: &DomTree, viewport: (f32, f32)) -> DomLayout { } css_rules.push((".subtext".into(), "color: #828282; font-size: 7pt".into())); css_rules.push((".subline".into(), "color: #828282; font-size: 7pt".into())); + css_rules.push((".hnname".into(), "margin-right: 10px; margin-left: 2px".into())); + css_rules.push((".pagetop".into(), "align-items: center".into())); + css_rules.push((".votearrow".into(), "width: 10px; height: 10px; margin-top: 2px".into())); css_rules.push((".rank".into(), "color: #828282".into())); css_rules.push((".comhead".into(), "color: #828282; font-size: 7pt".into())); css_rules.push(("a".into(), "color: #000000".into())); diff --git a/crates/obscura-render/src/lib.rs b/crates/obscura-render/src/lib.rs index 675dc53..b80fa37 100644 --- a/crates/obscura-render/src/lib.rs +++ b/crates/obscura-render/src/lib.rs @@ -77,6 +77,7 @@ pub struct LayoutStyle { pub color: Option<[u8; 4]>, pub border_color: Option<[u8; 4]>, pub font_size: Option, + pub font_weight: Option, pub align_items: Option, pub flex_direction: Option, pub justify_content: Option, diff --git a/crates/obscura-render/src/paint.rs b/crates/obscura-render/src/paint.rs index 77d0fac..eea6105 100644 --- a/crates/obscura-render/src/paint.rs +++ b/crates/obscura-render/src/paint.rs @@ -97,18 +97,35 @@ pub fn paint_dom(tree: &DomTree, viewport: (f32, f32)) -> Option { if name.local.as_ref() == "img" { let src = node.get_attribute("src").unwrap_or(""); if src.contains("y18") { - let paint = tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(tiny_skia::Color::from_rgba8(255, 102, 0, 255)), + let mut paint = tiny_skia::Paint { + shader: tiny_skia::Shader::SolidColor(tiny_skia::Color::from_rgba8(255, 255, 255, 255)), ..Default::default() }; + if let Some(r) = Rect::from_xywh(rect.x - 1.0, rect.y - 1.0, rect.width.max(18.0) + 2.0, rect.height.max(18.0) + 2.0) { + pixmap.fill_rect(r, &paint, Transform::identity(), None); + } + paint.shader = tiny_skia::Shader::SolidColor(tiny_skia::Color::from_rgba8(255, 102, 0, 255)); if let Some(r) = Rect::from_xywh(rect.x, rect.y, rect.width.max(18.0), rect.height.max(18.0)) { pixmap.fill_rect(r, &paint, Transform::identity(), None); - draw_text(&mut pixmap, "Y", rect.x + 4.0, rect.y + 2.0, [255, 255, 255, 255], 14.0); + draw_text(&mut pixmap, "Y", rect.x + 4.0, rect.y + 2.0, [255, 255, 255, 255], 14.0, false); } } else if let Some(src) = node.get_attribute("src") { paint_image(src, &rect, &mut pixmap); } } + + if node.get_attribute("class").unwrap_or_default() == "votearrow" { + let mut pb = tiny_skia::PathBuilder::new(); + pb.move_to(rect.x + rect.width * 0.5, rect.y); + pb.line_to(rect.x + rect.width, rect.y + rect.height); + pb.line_to(rect.x, rect.y + rect.height); + pb.close(); + if let Some(path) = pb.finish() { + let mut paint = tiny_skia::Paint::default(); + paint.set_color_rgba8(130, 130, 130, 255); + pixmap.fill_path(&path, &paint, FillRule::Winding, Transform::identity(), None); + } + } } Some(pixmap) } @@ -142,6 +159,8 @@ fn paint_text_node( let color = style.color.unwrap_or([0, 0, 0, 255]); let fsize = style.font_size.unwrap_or(16.0); + let is_bold = style.font_weight.as_deref() == Some("bold"); + // Get geometry of the text node itself! let rect = match laid.rects.get(&nid) { Some(r) => r, @@ -149,11 +168,11 @@ fn paint_text_node( }; // Paint text at rect.x, rect.y - draw_text(pixmap, text, rect.x, rect.y, color, fsize); + draw_text(pixmap, text, rect.x, rect.y, color, fsize, is_bold); Some(()) } -fn draw_text(pixmap: &mut Pixmap, text: &str, x: f32, y: f32, color: [u8; 4], size: f32) { +fn draw_text(pixmap: &mut Pixmap, text: &str, x: f32, y: f32, color: [u8; 4], size: f32, is_bold: bool) { let font = FontRef::try_from_slice(FONT_BYTES).unwrap(); let scale = PxScale::from(size); let scaled_font = font.as_scaled(scale); @@ -179,25 +198,30 @@ fn draw_text(pixmap: &mut Pixmap, text: &str, x: f32, y: f32, color: [u8; 4], si if px >= 0 && px < width && py >= 0 && py < height { let alpha = (a_full as f32 * c) as u8; if alpha > 0 { - let idx = (py * width + px) as usize; - let dst = pixels[idx]; - - let src_a = alpha as u32; - let src_r = (r as u32 * src_a) / 255; - let src_g = (g as u32 * src_a) / 255; - let src_b = (b as u32 * src_a) / 255; - - let dst_a = dst.alpha() as u32; - let out_a = src_a + (dst_a * (255 - src_a) / 255); - - if out_a > 0 { - let out_r = src_r + (dst.red() as u32 * (255 - src_a) / 255); - let out_g = src_g + (dst.green() as u32 * (255 - src_a) / 255); - let out_b = src_b + (dst.blue() as u32 * (255 - src_a) / 255); + let mut px_indices = vec![(py * width + px) as usize]; + if is_bold && px + 1 < width { + px_indices.push((py * width + px + 1) as usize); + } + for idx in px_indices { + let dst = pixels[idx]; - pixels[idx] = tiny_skia::PremultipliedColorU8::from_rgba( - out_r as u8, out_g as u8, out_b as u8, out_a as u8 - ).unwrap_or_else(|| tiny_skia::PremultipliedColorU8::from_rgba(0,0,0,0).unwrap()); + let src_a = alpha as u32; + let src_r = (r as u32 * src_a) / 255; + let src_g = (g as u32 * src_a) / 255; + let src_b = (b as u32 * src_a) / 255; + + let dst_a = dst.alpha() as u32; + let out_a = src_a + (dst_a * (255 - src_a) / 255); + + if out_a > 0 { + let out_r = src_r + (dst.red() as u32 * (255 - src_a) / 255); + let out_g = src_g + (dst.green() as u32 * (255 - src_a) / 255); + let out_b = src_b + (dst.blue() as u32 * (255 - src_a) / 255); + + pixels[idx] = tiny_skia::PremultipliedColorU8::from_rgba( + out_r as u8, out_g as u8, out_b as u8, out_a as u8 + ).unwrap_or_else(|| tiny_skia::PremultipliedColorU8::from_rgba(0,0,0,0).unwrap()); + } } } } diff --git a/crates/obscura-render/src/style.rs b/crates/obscura-render/src/style.rs index b4717e6..98e657f 100644 --- a/crates/obscura-render/src/style.rs +++ b/crates/obscura-render/src/style.rs @@ -24,6 +24,10 @@ pub fn compute_style(tag: &str, inline_css: Option<&str>) -> LayoutStyle { /// inline/text layout arrives with the text/paint phase. pub fn ua_style(tag: &str) -> LayoutStyle { let mut style = LayoutStyle::default(); + if tag == "b" || tag == "strong" { + style.font_weight = Some("bold".into()); + } + style.display = match tag { "span" | "a" | "b" | "i" | "strong" | "em" | "font" => Display::Inline, "tr" => Display::Flex, @@ -88,7 +92,16 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { "padding-right" => set_edge(&mut style.padding, Side::Right, px(value)), "padding-bottom" => set_edge(&mut style.padding, Side::Bottom, px(value)), "padding-left" => set_edge(&mut style.padding, Side::Left, px(value)), - "border-width" | "border" => { if let Some(e) = edges(value) { style.border = e; } } + "border" => { + for p in value.split_whitespace() { + if let Some(c) = parse_color(p) { + style.border_color = Some(c); + } else if p.ends_with("px") || p.chars().all(|c| c.is_ascii_digit()) { + if let Some(e) = edges(p) { style.border = e; } + } + } + } + "border-width" => { if let Some(e) = edges(value) { style.border = e; } } "border-top-width" | "border-top" => set_edge(&mut style.border, Side::Top, px(value)), "border-right-width" | "border-right" => set_edge(&mut style.border, Side::Right, px(value)), "border-bottom-width" | "border-bottom" => set_edge(&mut style.border, Side::Bottom, px(value)), @@ -97,6 +110,7 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { "color" => style.color = parse_color(value), "border-color" => style.border_color = parse_color(value), "font-size" => style.font_size = px(value), + "font-weight" => style.font_weight = Some(value.to_string()), "text-align" => { if value == "right" { style.justify_content = Some(taffy::JustifyContent::FlexEnd); @@ -104,6 +118,15 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) { style.justify_content = Some(taffy::JustifyContent::Center); } }, + "align-items" => { + if value == "center" { + style.align_items = Some(taffy::AlignItems::Center); + } else if value == "flex-start" || value == "start" { + style.align_items = Some(taffy::AlignItems::FlexStart); + } else if value == "flex-end" || value == "end" { + style.align_items = Some(taffy::AlignItems::FlexEnd); + } + }, _ => {} } }