atmos/os_lib/web_engine/
iframe_preview.rs1extern crate alloc;
15use alloc::string::String;
16use alloc::vec::Vec;
17
18#[derive(Debug, Clone, PartialEq, Default)]
20pub struct Preview {
21 pub title: String,
22 pub description: String,
23 pub image: String,
25}
26
27fn parse_attrs(tag: &str) -> Vec<(String, String)> {
29 let b = tag.as_bytes();
30 let mut i = 0;
31 while i < b.len() && !b[i].is_ascii_whitespace() {
33 i += 1;
34 }
35 let mut out = Vec::new();
36 while i < b.len() {
37 while i < b.len() && (b[i].is_ascii_whitespace() || b[i] == b'/') {
38 i += 1;
39 }
40 let ns = i;
41 while i < b.len() && !b[i].is_ascii_whitespace() && b[i] != b'=' && b[i] != b'/' {
42 i += 1;
43 }
44 let name = tag.get(ns..i).unwrap_or("").to_ascii_lowercase();
45 while i < b.len() && b[i].is_ascii_whitespace() {
46 i += 1;
47 }
48 let mut value = String::new();
49 if i < b.len() && b[i] == b'=' {
50 i += 1;
51 while i < b.len() && b[i].is_ascii_whitespace() {
52 i += 1;
53 }
54 if i < b.len() && (b[i] == b'"' || b[i] == b'\'') {
55 let q = b[i];
56 i += 1;
57 let vs = i;
58 while i < b.len() && b[i] != q {
59 i += 1;
60 }
61 value = tag.get(vs..i).unwrap_or("").into();
62 i += 1;
63 } else {
64 let vs = i;
65 while i < b.len() && !b[i].is_ascii_whitespace() {
66 i += 1;
67 }
68 value = tag.get(vs..i).unwrap_or("").into();
69 }
70 }
71 if !name.is_empty() {
72 out.push((name, crate::os_lib::dom::decode_entities(&value)));
73 }
74 }
75 out
76}
77
78fn attr<'a>(attrs: &'a [(String, String)], name: &str) -> Option<&'a str> {
79 attrs.iter().find(|(n, _)| n == name).map(|(_, v)| v.as_str())
80}
81
82fn squash(s: &str) -> String {
84 let mut out = String::new();
85 for w in s.split_whitespace() {
86 if !out.is_empty() {
87 out.push(' ');
88 }
89 out.push_str(w);
90 }
91 out
92}
93
94pub fn parse_preview(html: &str) -> Option<Preview> {
96 let lower = html.to_ascii_lowercase();
98 let end = lower.find("<body").unwrap_or(html.len());
99 let head = html.get(..end).unwrap_or(html);
100 let lhead = lower.get(..end).unwrap_or(&lower);
101
102 let (mut og_title, mut og_desc, mut og_image) = (None, None, None);
103 let (mut meta_desc, mut tw_image) = (None, None);
104 let mut pos = 0;
105 while let Some(off) = lhead.get(pos..).and_then(|s| s.find("<meta")) {
106 let st = pos + off + 1;
107 let Some(len) = head.get(st..).and_then(|s| s.find('>')) else { break };
108 let attrs = parse_attrs(head.get(st..st + len).unwrap_or(""));
109 let key = attr(&attrs, "property").or_else(|| attr(&attrs, "name")).unwrap_or("").to_ascii_lowercase();
110 let content = attr(&attrs, "content").map(squash).filter(|c| !c.is_empty());
111 match key.as_str() {
112 "og:title" => og_title = og_title.or(content),
113 "og:description" => og_desc = og_desc.or(content),
114 "og:image" => og_image = og_image.or(content),
115 "description" => meta_desc = meta_desc.or(content),
116 "twitter:image" => tw_image = tw_image.or(content),
117 _ => {}
118 }
119 pos = st + len;
120 }
121 let title_tag = lhead.find("<title").and_then(|s| {
122 let open = s + lhead.get(s..)?.find('>')? + 1;
123 let close = open + lhead.get(open..)?.find("</title")?;
124 Some(squash(&crate::os_lib::dom::decode_entities(head.get(open..close)?)))
125 });
126 let title = og_title.or(title_tag.filter(|t| !t.is_empty())).unwrap_or_default();
127 let description = og_desc.or(meta_desc).unwrap_or_default();
128 if title.is_empty() && description.is_empty() {
129 return None;
130 }
131 Some(Preview { title, description, image: og_image.or(tw_image).unwrap_or_default() })
132}
133
134fn start_forbidden(c: char) -> bool {
136 "、。,.,.))」』】・ー!?!?".contains(c)
137}
138
139fn end_forbidden(c: char) -> bool {
141 "((「『【".contains(c)
142}
143
144pub fn wrap_lines(text: &str, max_w: i32, adv: impl Fn(char) -> i32) -> Vec<String> {
147 let chars: Vec<char> = text.chars().collect();
148 let advs: Vec<i32> = chars.iter().map(|&c| adv(c)).collect();
149 let breaks = crate::os_lib::layout::line_break::break_positions(
150 &chars,
151 &advs,
152 max_w,
153 false,
154 start_forbidden,
155 end_forbidden,
156 );
157 let mut out = Vec::new();
158 let mut start = 0;
159 for b in breaks.into_iter().chain(core::iter::once(chars.len())) {
160 let line: String = chars.get(start..b).unwrap_or(&[]).iter().collect();
161 let line = line.trim();
162 if !line.is_empty() {
163 out.push(line.into());
164 }
165 start = b;
166 }
167 out
168}