This commit is contained in:
chayleaf 2023-12-30 19:02:37 +07:00
commit 4d75050ac0
Signed by: chayleaf
GPG key ID: 78171AD46227E68E
22 changed files with 5685 additions and 0 deletions

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.ppm filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

2615
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

20
Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[package]
name = "swarmos"
version = "0.1.0"
edition = "2021"
[dependencies]
env_logger = "0.10.1"
cpal = "0.15.2"
log = "0.4.20"
once_cell = { version = "1.19.0", features = ["parking_lot"] }
pixels = "0.13.0"
rquickjs = { version = "0.4.0", features = ["parallel", "futures"] }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
tokio = { version = "1.35.1", features = ["rt", "macros", "sync", "time"] }
usfx = { version = "0.1.5", features = ["serde"] }
winit = "0.28"
symphonia = { version = "0.5.3", features = ["mp3"] }
rubato = { version = "0.14.1", features = ["log"] }
arrayvec = "0.7.4"

BIN
data/Strut.mp3 (Stored with Git LFS) Normal file

Binary file not shown.

BIN
data/a.ppm (Stored with Git LFS) Normal file

Binary file not shown.

11
data/index.html Normal file
View file

@ -0,0 +1,11 @@
<html>
<style src="file://style.css"></style>
<script src="file://script.js"></script>
<body style="color:5">
<tabs>VIEW</tabs>
YAY HTML
<p>idk</p>
<img handler="handler1" src="file://a.ppm"></img>
</body>
a b a b
</html>

8
data/index1.html Normal file
View file

@ -0,0 +1,8 @@
<html style="width:10">
<body style="color:5">
<p>TESTING STUFF</p>
YAY HTML
<p>idk</p>
<img handler="handler1" src="file://a.ppm"></img>
</body>
</html>

21
data/master.js Normal file
View file

@ -0,0 +1,21 @@
console.log('[js] starting');
const bgm = { name: null, handle: null };
globalThis.onmessage = (source, message) => {
console.log('[js] msg', source, message);
if (message == 'reload') {
game.loadFile(source, source + '.html');
} else if (message == 'unload') {
game.unload(source);
} else if (message.kind) {
if (message.kind == 'bgm') {
if (message.bgm != bgm.name) {
if (bgm.handle != null) game.stop(bgm.handle);
bgm.handle = game.play(bgm.name = message.bgm, { loop: true, });
}
}
}
};
game.loadFile('index', 'index.html');
//game.loadFile('index1', 'index1.html');
//game.load('index', "<html style=\"x:10;x_view:10;width:10\"><script src=\"file://script.js\"></script><body style=\"color:5\"><p>TESTING STUFF</p>YAY HTML<p>idk</p><img src=\"file://a.ppm\"></img></html>");
//game.load('index1', "<html style=\"width:10\"><script></script><body style=\"color:5\"><p>TESTING STUFF</p>YAY HTML<p>idk</p><img src=\"file://a.ppm\"></img></html>");

8
data/sample.html Normal file
View file

@ -0,0 +1,8 @@
<html>
<head>
I'm literally not alive??
</head>
<body>
I'm fucking DEAD
</body>
</html>

57
data/script.js Normal file
View file

@ -0,0 +1,57 @@
let schedule = () => {
let samp = freq => game.sample({
volume: 0.1,
osc_type: "Sine",
osc_frequency: freq,
osc_duty_cycle: "Eight",
env_attack: 0.1,
env_decay: 0.00,
env_sustain: 0.1,
env_release: 0.0,
dis_crunch: 0.9,
dis_drive: 0.9,
});
game.sleep(0.00).then(()=>samp(1990));
game.sleep(0.13).then(()=>samp(1990));
game.sleep(0.26).then(()=>samp(1990));
game.sleep(0.39).then(()=>samp(1990));
game.sleep(1.166).then(schedule);
};
schedule();
game.message('master', { kind: 'bgm', bgm: 'Strut.mp3' });
globalThis.onkeydown = (code, _ch) => {
console.log('pressed', code, game.keycodes.F5);
game.message('master', 'idk');
return false;
};
globalThis.handler1 = {
click: btn => {
game.select("html").append(btn ? 'd' : 'c');
}
};
/*console.log('test');
globalThis.handler1 = {
click: () => {
game.append('c');
}
};
const pressed = {};
globalThis.onkeydown = (code, _ch) => {
pressed[code] = true;
};
globalThis.onkeyup = (code, _ch) => {
pressed[code] = false;
};
globalThis.ontext = (ch) => {
console.log(ch);
game.setOuter("a", `<text id=a>${ch}</text>`);
};
game.append('<text id=a></text>');*/
/*for (let i = 0; i < 1; ++i) {
game.append('<img src=\"file://a.ppm\">');
}*/
//"<html style=\"x:10;x_view:10;width:10\"><script src=\"file://script.js\"></script><body style=\"color:5\"><p>TESTING STUFF</p>YAY HTML<p>idk</p><img src=\"file://a.ppm\"></img></html>",

3
data/style.css Normal file
View file

@ -0,0 +1,3 @@
tabs {
color: 6;
}

24
shell.nix Normal file
View file

@ -0,0 +1,24 @@
{ pkgs ? import <nixpkgs> { }, lib ? pkgs.lib }:
pkgs.mkShell rec {
name = "rust-env";
nativeBuildInputs = with pkgs; [
pkg-config
rustc
cargo
];
buildInputs = with pkgs; [
openssl
alsa-lib
xorg.libX11
xorg.libXcursor
xorg.libXrandr
xorg.libXi
wayland
libxkbcommon
libGL
];
LD_LIBRARY_PATH = "${lib.makeLibraryPath buildInputs}";
}

288
src/color.rs Normal file
View file

@ -0,0 +1,288 @@
pub const COLORS: [[u8; 3]; 256] = [
*b"\x00\x00\x00",
*b"\x80\x00\x00",
*b"\x00\x80\x00",
*b"\x80\x80\x00",
*b"\x00\x00\x80",
*b"\x80\x00\x80",
*b"\x00\x80\x80",
*b"\xc0\xc0\xc0",
*b"\x80\x80\x80",
*b"\xff\x00\x00",
*b"\x00\xff\x00",
*b"\xff\xff\x00",
*b"\x00\x00\xff",
*b"\xff\x00\xff",
*b"\x00\xff\xff",
*b"\xff\xff\xff",
*b"\x00\x00\x00",
*b"\x00\x00\x5f",
*b"\x00\x00\x87",
*b"\x00\x00\xaf",
*b"\x00\x00\xd7",
*b"\x00\x00\xff",
*b"\x00\x5f\x00",
*b"\x00\x5f\x5f",
*b"\x00\x5f\x87",
*b"\x00\x5f\xaf",
*b"\x00\x5f\xd7",
*b"\x00\x5f\xff",
*b"\x00\x87\x00",
*b"\x00\x87\x5f",
*b"\x00\x87\x87",
*b"\x00\x87\xaf",
*b"\x00\x87\xd7",
*b"\x00\x87\xff",
*b"\x00\xaf\x00",
*b"\x00\xaf\x5f",
*b"\x00\xaf\x87",
*b"\x00\xaf\xaf",
*b"\x00\xaf\xd7",
*b"\x00\xaf\xff",
*b"\x00\xd7\x00",
*b"\x00\xd7\x5f",
*b"\x00\xd7\x87",
*b"\x00\xd7\xaf",
*b"\x00\xd7\xd7",
*b"\x00\xd7\xff",
*b"\x00\xff\x00",
*b"\x00\xff\x5f",
*b"\x00\xff\x87",
*b"\x00\xff\xaf",
*b"\x00\xff\xd7",
*b"\x00\xff\xff",
*b"\x5f\x00\x00",
*b"\x5f\x00\x5f",
*b"\x5f\x00\x87",
*b"\x5f\x00\xaf",
*b"\x5f\x00\xd7",
*b"\x5f\x00\xff",
*b"\x5f\x5f\x00",
*b"\x5f\x5f\x5f",
*b"\x5f\x5f\x87",
*b"\x5f\x5f\xaf",
*b"\x5f\x5f\xd7",
*b"\x5f\x5f\xff",
*b"\x5f\x87\x00",
*b"\x5f\x87\x5f",
*b"\x5f\x87\x87",
*b"\x5f\x87\xaf",
*b"\x5f\x87\xd7",
*b"\x5f\x87\xff",
*b"\x5f\xaf\x00",
*b"\x5f\xaf\x5f",
*b"\x5f\xaf\x87",
*b"\x5f\xaf\xaf",
*b"\x5f\xaf\xd7",
*b"\x5f\xaf\xff",
*b"\x5f\xd7\x00",
*b"\x5f\xd7\x5f",
*b"\x5f\xd7\x87",
*b"\x5f\xd7\xaf",
*b"\x5f\xd7\xd7",
*b"\x5f\xd7\xff",
*b"\x5f\xff\x00",
*b"\x5f\xff\x5f",
*b"\x5f\xff\x87",
*b"\x5f\xff\xaf",
*b"\x5f\xff\xd7",
*b"\x5f\xff\xff",
*b"\x87\x00\x00",
*b"\x87\x00\x5f",
*b"\x87\x00\x87",
*b"\x87\x00\xaf",
*b"\x87\x00\xd7",
*b"\x87\x00\xff",
*b"\x87\x5f\x00",
*b"\x87\x5f\x5f",
*b"\x87\x5f\x87",
*b"\x87\x5f\xaf",
*b"\x87\x5f\xd7",
*b"\x87\x5f\xff",
*b"\x87\x87\x00",
*b"\x87\x87\x5f",
*b"\x87\x87\x87",
*b"\x87\x87\xaf",
*b"\x87\x87\xd7",
*b"\x87\x87\xff",
*b"\x87\xaf\x00",
*b"\x87\xaf\x5f",
*b"\x87\xaf\x87",
*b"\x87\xaf\xaf",
*b"\x87\xaf\xd7",
*b"\x87\xaf\xff",
*b"\x87\xd7\x00",
*b"\x87\xd7\x5f",
*b"\x87\xd7\x87",
*b"\x87\xd7\xaf",
*b"\x87\xd7\xd7",
*b"\x87\xd7\xff",
*b"\x87\xff\x00",
*b"\x87\xff\x5f",
*b"\x87\xff\x87",
*b"\x87\xff\xaf",
*b"\x87\xff\xd7",
*b"\x87\xff\xff",
*b"\xaf\x00\x00",
*b"\xaf\x00\x5f",
*b"\xaf\x00\x87",
*b"\xaf\x00\xaf",
*b"\xaf\x00\xd7",
*b"\xaf\x00\xff",
*b"\xaf\x5f\x00",
*b"\xaf\x5f\x5f",
*b"\xaf\x5f\x87",
*b"\xaf\x5f\xaf",
*b"\xaf\x5f\xd7",
*b"\xaf\x5f\xff",
*b"\xaf\x87\x00",
*b"\xaf\x87\x5f",
*b"\xaf\x87\x87",
*b"\xaf\x87\xaf",
*b"\xaf\x87\xd7",
*b"\xaf\x87\xff",
*b"\xaf\xaf\x00",
*b"\xaf\xaf\x5f",
*b"\xaf\xaf\x87",
*b"\xaf\xaf\xaf",
*b"\xaf\xaf\xd7",
*b"\xaf\xaf\xff",
*b"\xaf\xd7\x00",
*b"\xaf\xd7\x5f",
*b"\xaf\xd7\x87",
*b"\xaf\xd7\xaf",
*b"\xaf\xd7\xd7",
*b"\xaf\xd7\xff",
*b"\xaf\xff\x00",
*b"\xaf\xff\x5f",
*b"\xaf\xff\x87",
*b"\xaf\xff\xaf",
*b"\xaf\xff\xd7",
*b"\xaf\xff\xff",
*b"\xd7\x00\x00",
*b"\xd7\x00\x5f",
*b"\xd7\x00\x87",
*b"\xd7\x00\xaf",
*b"\xd7\x00\xd7",
*b"\xd7\x00\xff",
*b"\xd7\x5f\x00",
*b"\xd7\x5f\x5f",
*b"\xd7\x5f\x87",
*b"\xd7\x5f\xaf",
*b"\xd7\x5f\xd7",
*b"\xd7\x5f\xff",
*b"\xd7\x87\x00",
*b"\xd7\x87\x5f",
*b"\xd7\x87\x87",
*b"\xd7\x87\xaf",
*b"\xd7\x87\xd7",
*b"\xd7\x87\xff",
*b"\xd7\xaf\x00",
*b"\xd7\xaf\x5f",
*b"\xd7\xaf\x87",
*b"\xd7\xaf\xaf",
*b"\xd7\xaf\xd7",
*b"\xd7\xaf\xff",
*b"\xd7\xd7\x00",
*b"\xd7\xd7\x5f",
*b"\xd7\xd7\x87",
*b"\xd7\xd7\xaf",
*b"\xd7\xd7\xd7",
*b"\xd7\xd7\xff",
*b"\xd7\xff\x00",
*b"\xd7\xff\x5f",
*b"\xd7\xff\x87",
*b"\xd7\xff\xaf",
*b"\xd7\xff\xd7",
*b"\xd7\xff\xff",
*b"\xff\x00\x00",
*b"\xff\x00\x5f",
*b"\xff\x00\x87",
*b"\xff\x00\xaf",
*b"\xff\x00\xd7",
*b"\xff\x00\xff",
*b"\xff\x5f\x00",
*b"\xff\x5f\x5f",
*b"\xff\x5f\x87",
*b"\xff\x5f\xaf",
*b"\xff\x5f\xd7",
*b"\xff\x5f\xff",
*b"\xff\x87\x00",
*b"\xff\x87\x5f",
*b"\xff\x87\x87",
*b"\xff\x87\xaf",
*b"\xff\x87\xd7",
*b"\xff\x87\xff",
*b"\xff\xaf\x00",
*b"\xff\xaf\x5f",
*b"\xff\xaf\x87",
*b"\xff\xaf\xaf",
*b"\xff\xaf\xd7",
*b"\xff\xaf\xff",
*b"\xff\xd7\x00",
*b"\xff\xd7\x5f",
*b"\xff\xd7\x87",
*b"\xff\xd7\xaf",
*b"\xff\xd7\xd7",
*b"\xff\xd7\xff",
*b"\xff\xff\x00",
*b"\xff\xff\x5f",
*b"\xff\xff\x87",
*b"\xff\xff\xaf",
*b"\xff\xff\xd7",
*b"\xff\xff\xff",
*b"\x08\x08\x08",
*b"\x12\x12\x12",
*b"\x1c\x1c\x1c",
*b"\x26\x26\x26",
*b"\x30\x30\x30",
*b"\x3a\x3a\x3a",
*b"\x44\x44\x44",
*b"\x4e\x4e\x4e",
*b"\x58\x58\x58",
*b"\x62\x62\x62",
*b"\x6c\x6c\x6c",
*b"\x76\x76\x76",
*b"\x80\x80\x80",
*b"\x8a\x8a\x8a",
*b"\x94\x94\x94",
*b"\x9e\x9e\x9e",
*b"\xa8\xa8\xa8",
*b"\xb2\xb2\xb2",
*b"\xbc\xbc\xbc",
*b"\xc6\xc6\xc6",
*b"\xd0\xd0\xd0",
*b"\xda\xda\xda",
*b"\xe4\xe4\xe4",
*b"\xee\xee\xee",
];
pub fn rgb28(col: [u8; 3]) -> u8 {
let rgb = col.map(|c| {
const W: [u8; 6] = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
for ab in W.windows(2) {
let a = *ab.first().unwrap();
let b = *ab.last().unwrap();
if c <= b {
return if c - a < b - c { a } else { b };
}
}
0
});
for (i, col) in COLORS.iter().enumerate() {
if rgb == *col {
return i as u8;
}
}
1
}
#[cfg(test)]
mod test {
use crate::color::rgb28;
#[test]
fn test() {
assert_eq!(rgb28([0x12, 0x34, 0x56]), 23);
}
}

101
src/css.rs Normal file
View file

@ -0,0 +1,101 @@
use std::collections::{HashMap, VecDeque};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum Condition {
Class(String),
Id(String),
Tag(String),
}
impl From<&str> for Condition {
fn from(value: &str) -> Self {
if let Some(id) = value.strip_prefix('#') {
Condition::Id(id.to_owned())
} else if let Some(class) = value.strip_prefix('.') {
Condition::Class(class.to_owned())
} else {
Condition::Tag(value.to_owned())
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rule {
pub cond: Vec<VecDeque<Condition>>,
pub rule: HashMap<String, isize>,
}
pub fn parse_rule<'a>(tokens: &mut impl Iterator<Item = &'a str>) -> HashMap<String, isize> {
let mut rule = HashMap::<String, _>::new();
let mut k = None::<&str>;
let mut k2 = None::<&str>;
for token in tokens.by_ref() {
if token == "}" {
break;
}
if token == ":" {
k2 = k.take();
continue;
}
if token == ";" {
k = None;
k2 = None;
continue;
}
if let Some(k) = k2.as_ref() {
if let Ok(v) = token.parse() {
rule.insert(k.to_string(), v);
}
} else {
k = Some(token);
}
}
rule
}
pub fn parse<'a>(tokens: &mut impl Iterator<Item = &'a str>) -> Vec<Rule> {
let mut ret = vec![];
while let Some(token) = tokens.next() {
let mut cond = Vec::<VecDeque<Condition>>::new();
cond.push([token.into()].into());
for token in tokens.by_ref() {
if token == "{" {
break;
}
if token == "*" {
continue;
}
if token == "," {
cond.push([].into());
continue;
}
cond.last_mut().unwrap().push_back(token.into());
}
let rule = parse_rule(tokens);
ret.push(Rule { cond, rule });
}
ret
}
pub fn tokenize(s: &str) -> Vec<String> {
let mut cur = "".to_owned();
let mut ret = vec![];
for ch in s.chars() {
let sep = matches!(ch, ';' | '{' | '}' | ':' | '*');
if sep || ch.is_whitespace() {
if !cur.is_empty() {
ret.push(cur.clone());
cur.clear();
}
if sep {
ret.push(ch.to_string());
}
} else {
cur.push(ch);
}
}
if !cur.is_empty() {
ret.push(cur);
}
ret
}

626
src/html.rs Normal file
View file

@ -0,0 +1,626 @@
use std::{
collections::{HashMap, VecDeque, BTreeMap},
fs,
};
use crate::{
css::{self, Condition},
ppm, unifont,
};
#[derive(Clone, Eq, PartialEq)]
pub struct Data(Vec<u8>);
impl std::fmt::Debug for Data {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Data(...)")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Node {
Complex {
tag: String,
attrs: HashMap<String, String>,
contents: Vec<Node>,
rules: BTreeMap<VecDeque<Condition>, HashMap<String, isize>>,
},
Simple(String),
Bitmap(Data, usize),
}
pub enum RenderData<'a> {
Bitmap { data: &'a [u8], width: usize },
Text(&'a str, u8),
}
impl<'a> RenderData<'a> {
fn height(&self) -> usize {
match self {
Self::Bitmap { data, width } => data.len() / width,
Self::Text(..) => 16,
}
}
fn width(&self) -> usize {
match self {
Self::Text(x, ..) => x.chars().map(|x| unifont::width(x) * 8).sum(),
Self::Bitmap { width, .. } => *width,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Rect {
pub x: isize,
pub y: isize,
pub skip_x: isize,
pub skip_y: isize,
pub w: isize,
pub h: isize,
}
pub struct RenderNode<'a> {
pub data: RenderData<'a>,
pub rect: Rect,
pub handlers: Vec<String>,
}
pub struct Render<'a> {
node: &'a Node,
next_x: isize,
next_y: isize,
max_x: isize,
max_y: isize,
inner: Option<Box<Render<'a>>>,
rules: BTreeMap<VecDeque<Condition>, HashMap<String, isize>>,
handlers: Vec<String>,
n: usize,
}
impl<'a> Render<'a> {
pub fn style(&self, k: &str, d: isize) -> isize {
if let Some(rules) = self.rules.get(&VecDeque::new()) {
rules.get(k).copied().unwrap_or(d)
} else {
d
}
}
}
impl<'a> Iterator for Render<'a> {
type Item = RenderNode<'a>;
fn next(&mut self) -> Option<RenderNode<'a>> {
let display = self.style("display", 1);
if display == 0 {
return None;
}
let x0 = self.style("x", 0);
let y0 = self.style("y", 0);
let skip_x = self.style("x_view", 0);
let skip_y = self.style("y_view", 0);
let max_w = self.style("width", 0xFFFFFF);
let max_h = self.style("height", 0xFFFFFF);
loop {
if let Some(inner) = &mut self.inner {
if let Some(mut w) = inner.next() {
w.rect.x += self.next_x;
w.rect.y += self.next_y;
if inner.style("display", 1) != 4 {
self.max_x = w.rect.x + w.rect.w;
self.max_y = w.rect.y + w.rect.h;
}
w.rect.x -= skip_x;
w.rect.y -= skip_y;
if w.rect.x < 0 {
w.rect.skip_x += -w.rect.x;
w.rect.w += w.rect.x;
w.rect.x = 0;
}
if w.rect.y < 0 {
w.rect.skip_y += -w.rect.y;
w.rect.h += w.rect.y;
w.rect.y = 0;
}
w.rect.w = w.rect.w.min(max_w - w.rect.x);
w.rect.h = w.rect.h.min(max_h - w.rect.y);
w.rect.x += x0;
w.rect.y += y0;
return Some(w);
} else {
self.inner = None;
}
}
match (self.node, self.n) {
(Node::Simple(x), 0) => {
self.n += 1;
let data =
RenderData::Text(x, u8::try_from(self.style("color", -1)).unwrap_or(0xFF));
return Some(RenderNode {
rect: Rect {
x: x0 + self.next_x,
y: y0 + self.next_y,
skip_x,
skip_y,
w: (data.width() as isize).min(max_w - self.next_x),
h: (data.height() as isize).min(max_h - self.next_y),
},
data,
handlers: self.handlers.clone(),
});
}
(Node::Bitmap(data, width), 0) => {
self.n += 1;
let data = RenderData::Bitmap {
data: data.0.as_slice(),
width: *width,
};
return Some(RenderNode {
rect: Rect {
x: x0 + self.next_x,
y: y0 + self.next_y,
skip_x,
skip_y,
w: (data.width() as isize).min(max_w - self.next_x),
h: (data.height() as isize).min(max_h - self.next_y),
},
data,
handlers: self.handlers.clone(),
});
}
(
Node::Complex {
tag,
attrs: _,
contents,
rules: _,
},
mut n,
) if n < contents.len() + (if tag == "img" { 1 } else { 0 }) => {
if tag == "img" {
if n == 0 {
self.n += 1;
continue;
} else {
n -= 1;
}
}
let q = contents.get(n.wrapping_sub(1));
let w = contents.get(n).unwrap();
self.n += 1;
if display == 2
|| (display == 1 && matches!(q, Some(Node::Complex { .. }))
|| matches!(w, Node::Complex { .. }))
{
self.next_x = 0;
self.next_y = self.max_y;
self.max_x = 0;
} else {
self.next_x = self.max_x;
if self.next_x > 0 {
self.next_x += 8;
}
}
self.inner = Some(Box::new(
w.render(self.rules.clone(), self.handlers.clone()),
));
}
_ => return None,
}
}
}
}
impl Node {
pub fn set_attr(&mut self, k: String, v: String) -> Vec<Script> {
let mut scripts = vec![];
if let Node::Complex {
tag,
attrs,
contents,
rules,
} = self
{
if !k.is_empty() || !v.is_empty() {
if tag == "img" && k == "src" {
if let Some((w, d)) = v
.strip_prefix("file://")
.and_then(|x| fs::read(x).ok())
.and_then(|x| ppm::read(&x))
{
contents.clear();
contents.push(Node::Bitmap(Data(d), w));
}
} else if tag == "img" && k == "data" {
if let Some((w, data)) = k.split_once(':') {
if let Ok(w) = w.parse::<usize>() {
contents.clear();
let data = data
.as_bytes()
.chunks_exact(2)
.flat_map(|x| {
std::str::from_utf8(x)
.ok()
.and_then(|x| u8::from_str_radix(x, 16).ok())
})
.collect();
contents.push(Node::Bitmap(Data(data), w));
}
}
} else if tag == "script" && k == "src" {
if let Some(w) = v
.strip_prefix("file://")
.and_then(|x| fs::read_to_string(x).ok())
{
scripts.push(Script::Js(w));
}
} else if tag == "style" && k == "src" {
if let Some(w) = v
.strip_prefix("file://")
.and_then(|x| fs::read_to_string(x).ok())
{
scripts.push(Script::Css(w));
}
} else if k == "style" {
*rules.entry(VecDeque::new()).or_default() =
css::parse_rule(&mut css::tokenize(&v).iter().map(|x| x.as_str()));
}
attrs.insert(k, v);
}
}
scripts
}
pub fn find_element_by_id_mut(&mut self, id: &str) -> Option<&mut Self> {
if let Self::Complex { attrs, .. } = self {
if attrs.get("id").map(|x| x.as_str()) == Some(id) {
return Some(self);
}
}
if let Self::Complex { contents, .. } = self {
for x in contents {
if let Some(ret) = x.find_element_by_id_mut(id) {
return Some(ret);
}
}
}
None
}
pub fn find_element_by_class_mut(&mut self, class: &str) -> Option<&mut Self> {
if let Self::Complex { attrs, .. } = self {
if let Some(cls) = attrs.get("class").map(|x| x.as_str()) {
for cls in cls.split_whitespace() {
if cls == class {
return Some(self);
}
}
}
}
if let Self::Complex { contents, .. } = self {
for x in contents {
if let Some(ret) = x.find_element_by_class_mut(class) {
return Some(ret);
}
}
}
None
}
pub fn find_element_by_tag_mut(&mut self, tag: &str) -> Option<&mut Self> {
if let Self::Complex { tag: t, .. } = self {
if tag == t {
return Some(self);
}
}
if let Self::Complex { contents, .. } = self {
for x in contents {
if let Some(ret) = x.find_element_by_tag_mut(tag) {
return Some(ret);
}
}
}
None
}
pub fn find_element_by_selector_mut(&mut self, selector: &str) -> Option<&mut Self> {
if let Some(id) = selector.strip_prefix('#') {
self.find_element_by_id_mut(id)
} else if let Some(id) = selector.strip_prefix('.') {
self.find_element_by_class_mut(id)
} else {
self.find_element_by_tag_mut(selector)
}
}
pub fn render(
&self,
mut rules: BTreeMap<VecDeque<Condition>, HashMap<String, isize>>,
mut handlers: Vec<String>,
) -> Render<'_> {
let mut new_rules = BTreeMap::<VecDeque<_>, HashMap<_, _>>::new();
for v in rules.values_mut() {
v.remove("x_view");
v.remove("y_view");
v.remove("x");
v.remove("y");
v.remove("width");
v.remove("height");
}
match self {
Node::Complex {
rules: r,
tag,
attrs,
..
} => {
if let Some(handler) = attrs.get("handler") {
handlers.push(handler.to_string());
}
for (mut k, v) in rules.into_iter() {
while match k.front() {
None => false,
Some(Condition::Id(id)) => attrs.get("id") == Some(id),
Some(Condition::Class(class)) => attrs.get("class") == Some(class),
Some(Condition::Tag(t)) => tag == t,
} {
k.pop_front();
}
let e = new_rules.entry(k).or_default();
for (k, v) in v.into_iter() {
e.insert(k, v);
}
}
for (k, v) in r.iter() {
let e = new_rules.entry(k.clone()).or_default();
for (k, v) in v.iter() {
e.insert(k.to_owned(), *v);
}
}
}
_ => new_rules = rules,
}
Render {
node: self,
inner: None,
next_x: 0,
next_y: 0,
max_x: 0,
max_y: 0,
n: 0,
rules: new_rules,
handlers,
}
}
}
pub struct Iter {
data: Vec<char>,
pos: usize,
}
impl From<&str> for Iter {
fn from(value: &str) -> Self {
let mut ret = Self {
data: [' '].into_iter().chain(value.chars()).collect(),
pos: 0,
};
ret.next();
ret
}
}
impl Iterator for Iter {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
self.data.get(self.pos).copied().map(|ret| {
self.pos += 1;
ret
})
}
}
impl Iter {
fn peek(&self, cnt: usize) -> Option<&[char]> {
self.data.get(self.pos..self.pos + cnt)
}
}
pub fn parse_attr(data: &mut Iter) -> (String, String) {
let mut q = false;
let mut x = String::new();
while let Some(mut ch) = {
if !q && matches!(data.peek(1), Some(&['>'])) {
None
} else {
data.next()
}
} {
if ch == '"' {
q = !q;
continue;
}
if q && ch == '\\' {
if let Some(c) = match data.peek(1) {
Some(&['n']) => Some('\n'),
Some(&['r']) => Some('\r'),
Some(&['t']) => Some('\t'),
Some(&[ch]) => Some(ch),
_ => None,
} {
ch = c;
data.next();
}
}
if !q && ch.is_whitespace() {
break;
}
x.push(ch);
}
let (k, v) = x
.split_once('=')
.map(|(a, b)| (a.to_owned(), b.to_owned()))
.unwrap_or((x, String::new()));
(k, v)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Script {
Js(String),
Css(String),
}
pub fn parse_node(data: &mut Iter) -> Option<(Node, Vec<Script>)> {
let mut ret = None;
while let Some(ch) = {
if matches!(ret, Some((Node::Simple(_), _))) && matches!(data.peek(1), Some(&['<'])) {
None
} else {
data.next()
}
} {
if ch == '>' {
if let Some((
Node::Complex {
tag,
attrs: _,
contents,
rules: _,
},
scripts,
)) = &mut ret
{
loop {
while matches!(data.peek(1), Some(&[x]) if x.is_whitespace()) {
data.next();
}
if matches!(data.peek(2), Some(&['<', '/'])) {
if matches!(data.peek(3 + tag.len()), Some(x) if x.starts_with(&['<','/']) && x.ends_with(&['>']) &&
x.iter().copied().skip(2).zip(tag.chars()).all(|(a, b)| a == b)
) {
for _ in 0..3 + tag.len() {
data.next();
}
}
return ret;
}
if let Some((node, scr)) = parse_node(data) {
contents.push(node);
scripts.extend(scr);
} else {
break;
}
}
return ret;
}
}
if ch.is_whitespace() {
match &mut ret {
Some((Node::Simple(_), _)) => return ret,
Some((node @ Node::Complex { .. }, scripts)) => loop {
let (k, v) = parse_attr(data);
if !k.is_empty() || !v.is_empty() {
scripts.extend(node.set_attr(k, v));
} else {
break;
}
},
_ => {}
}
continue;
}
if ret.is_none() {
if ch == '<' {
ret = Some((
Node::Complex {
tag: "".to_owned(),
attrs: HashMap::new(),
contents: vec![],
rules: BTreeMap::new(),
},
vec![],
));
} else {
ret = Some((Node::Simple(ch.to_string()), vec![]));
}
continue;
}
match &mut ret {
Some((Node::Simple(x), _)) => x.push(ch),
Some((Node::Complex { tag, .. }, _)) => tag.push(ch),
_ => {}
}
}
ret
}
pub fn parse(s: impl Into<Iter>) -> (Vec<Node>, Vec<String>) {
let mut it = s.into();
let mut ret = vec![];
let mut js = vec![];
while let Some((mut node, scripts)) = parse_node(&mut it) {
for script in scripts {
match script {
Script::Js(x) => js.push(x),
Script::Css(x) => {
let css = css::parse(&mut css::tokenize(&x).iter().map(|x| x.as_str()));
match &mut node {
Node::Complex { rules, .. } => {
for rule in css {
for cond in rule.cond {
let e = rules.entry(cond).or_default();
for (k, v) in rule.rule.iter() {
e.insert(k.to_owned(), *v);
}
}
}
}
_ => {}
}
}
}
}
ret.push(node);
}
(ret, js)
}
#[cfg(test)]
mod test {
use std::collections::{BTreeMap, HashMap};
use crate::html::{parse_attr, parse_node, Node};
#[test]
fn test() {
assert_eq!(
parse_attr(&mut "abc".into()),
("abc".to_owned(), "".to_owned())
);
assert_eq!(
parse_attr(&mut "abc=\"a\\\\\\\"\"".into()),
("abc".to_owned(), "a\\\"".to_owned())
);
assert_eq!(
parse_node(&mut "<data a=b > a </data>".into()),
Some((
Node::Complex {
tag: "data".to_owned(),
attrs: {
let mut x = HashMap::new();
x.insert("a".to_owned(), "b".to_owned());
x
},
contents: vec![Node::Simple("a".to_owned())],
rules: BTreeMap::new(),
},
vec![]
)),
);
assert_eq!(
parse_node(&mut "<html>A</html>".into()),
Some((
Node::Complex {
tag: "html".to_owned(),
attrs: HashMap::new(),
contents: vec![Node::Simple("A".to_owned())],
rules: BTreeMap::new(),
},
vec![]
)),
);
}
}

1607
src/main.rs Normal file

File diff suppressed because it is too large Load diff

68
src/ppm.rs Normal file
View file

@ -0,0 +1,68 @@
pub fn read(data: &[u8]) -> Option<(usize, Vec<u8>)> {
assert!(data.starts_with(b"P6"));
let mut data = data.split_at(2).1;
while let Some((a, b)) = data.split_first() {
if a.is_ascii_whitespace() {
data = b;
} else {
break;
}
}
let mut w = String::new();
while let Some((a, b)) = data.split_first() {
data = b;
if a.is_ascii_whitespace() {
break;
}
w.push(*a as char);
}
while let Some((a, b)) = data.split_first() {
if a.is_ascii_whitespace() {
data = b;
} else {
break;
}
}
let mut h = String::new();
while let Some((a, b)) = data.split_first() {
data = b;
if a.is_ascii_whitespace() {
break;
}
h.push(*a as char);
}
while let Some((a, b)) = data.split_first() {
if a.is_ascii_whitespace() {
data = b;
} else {
break;
}
}
let mut c = String::new();
while let Some((a, b)) = data.split_first() {
data = b;
if a.is_ascii_whitespace() {
break;
}
c.push(*a as char);
}
let w: usize = w.parse().ok()?;
let h: usize = h.parse().ok()?;
let c: usize = c.parse().ok()?;
if c >= 256 {
return None;
}
let mut ret = vec![];
for _ in 0..h {
for _ in 0..w {
let (r, d) = data.split_first()?;
data = d;
let (g, d) = data.split_first()?;
data = d;
let (b, d) = data.split_first()?;
data = d;
ret.push(crate::color::rgb28([*r, *g, *b]));
}
}
Some((w, ret))
}

153
src/resampler.rs Normal file
View file

@ -0,0 +1,153 @@
// Symphonia
// Copyright (c) 2019-2022 The Project Symphonia Developers.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, SignalSpec};
use symphonia::core::conv::{FromSample, IntoSample};
use symphonia::core::sample::Sample;
pub struct Resampler<T> {
resampler: rubato::FftFixedIn<f32>,
input: Vec<Vec<f32>>,
output: Vec<Vec<f32>>,
interleaved: Vec<T>,
duration: usize,
}
impl<T> Resampler<T>
where
T: Sample + FromSample<f32> + IntoSample<f32>,
{
fn resample_inner(&mut self) -> &[T] {
{
let mut input: arrayvec::ArrayVec<&[f32], 32> = Default::default();
for channel in self.input.iter() {
input.push(&channel[..self.duration]);
}
// Resample.
rubato::Resampler::process_into_buffer(
&mut self.resampler,
&input,
&mut self.output,
None,
)
.unwrap();
}
// Remove consumed samples from the input buffer.
for channel in self.input.iter_mut() {
channel.drain(0..self.duration);
}
// Interleave the planar samples from Rubato.
let num_channels = self.output.len();
self.interleaved
.resize(num_channels * self.output[0].len(), T::MID);
for (i, frame) in self.interleaved.chunks_exact_mut(num_channels).enumerate() {
for (ch, s) in frame.iter_mut().enumerate() {
*s = self.output[ch][i].into_sample();
}
}
&self.interleaved
}
}
impl<T> Resampler<T>
where
T: Sample + FromSample<f32> + IntoSample<f32>,
{
pub fn new(spec: SignalSpec, to_sample_rate: usize, duration: u64) -> Self {
let duration = duration as usize;
let num_channels = spec.channels.count();
let resampler = rubato::FftFixedIn::<f32>::new(
spec.rate as usize,
to_sample_rate,
duration,
2,
num_channels,
)
.unwrap();
let output = rubato::Resampler::output_buffer_allocate(&resampler, true);
let input = vec![Vec::with_capacity(duration); num_channels];
Self {
resampler,
input,
output,
duration,
interleaved: Default::default(),
}
}
/// Resamples a planar/non-interleaved input.
///
/// Returns the resampled samples in an interleaved format.
pub fn resample(&mut self, input: AudioBufferRef<'_>) -> Option<&[T]> {
// Copy and convert samples into input buffer.
convert_samples_any(&input, &mut self.input);
// Check if more samples are required.
if self.input[0].len() < self.duration {
return None;
}
Some(self.resample_inner())
}
/// Resample any remaining samples in the resample buffer.
pub fn flush(&mut self) -> Option<&[T]> {
let len = self.input[0].len();
if len == 0 {
return None;
}
let partial_len = len % self.duration;
if partial_len != 0 {
// Fill each input channel buffer with silence to the next multiple of the resampler
// duration.
for channel in self.input.iter_mut() {
channel.resize(len + (self.duration - partial_len), f32::MID);
}
}
Some(self.resample_inner())
}
}
fn convert_samples_any(input: &AudioBufferRef<'_>, output: &mut [Vec<f32>]) {
match input {
AudioBufferRef::U8(input) => convert_samples(input, output),
AudioBufferRef::U16(input) => convert_samples(input, output),
AudioBufferRef::U24(input) => convert_samples(input, output),
AudioBufferRef::U32(input) => convert_samples(input, output),
AudioBufferRef::S8(input) => convert_samples(input, output),
AudioBufferRef::S16(input) => convert_samples(input, output),
AudioBufferRef::S24(input) => convert_samples(input, output),
AudioBufferRef::S32(input) => convert_samples(input, output),
AudioBufferRef::F32(input) => convert_samples(input, output),
AudioBufferRef::F64(input) => convert_samples(input, output),
}
}
fn convert_samples<S>(input: &AudioBuffer<S>, output: &mut [Vec<f32>])
where
S: Sample + IntoSample<f32>,
{
for (c, dst) in output.iter_mut().enumerate() {
let src = input.chan(c);
dst.extend(src.iter().map(|&s| s.into_sample()));
}
}

BIN
src/unifont.bin (Stored with Git LFS) Normal file

Binary file not shown.

46
src/unifont.rs Normal file
View file

@ -0,0 +1,46 @@
use std::collections::BTreeMap;
use once_cell::sync::Lazy;
const fn parse<const N: usize>(mut data: &[u8]) -> [(u32, &[u8]); N] {
let mut ret = [(0, &[] as &[u8]); N];
let mut i = 0;
while i < ret.len() {
let sz = data[0] as usize;
ret[i].0 = u32::from_be_bytes([0, data[1], data[2], data[3]]);
let (a, b) = data.split_at(4).1.split_at(16 * sz);
ret[i].1 = a;
data = b;
i += 1;
}
ret
}
#[allow(long_running_const_eval)]
const RAW_DATA: [(u32, &[u8]); 256 /*123234*/] = parse(include_bytes!("unifont.bin"));
static DATA: Lazy<BTreeMap<u32, &[u8]>> = Lazy::new(|| RAW_DATA.into_iter().collect());
pub fn width(c: char) -> usize {
DATA.get(&(c as u32)).map(|x| x.len() / 16).unwrap_or(0)
}
pub fn data(c: char) -> &'static [u8] {
DATA.get(&(c as u32)).copied().unwrap_or(&[] as &[u8])
}
#[cfg(test)]
mod test {
use crate::unifont::{DATA, RAW_DATA};
#[test]
fn test() {
assert_eq!(RAW_DATA[5].0, 5);
assert_eq!(RAW_DATA[5].1, b"\xAA\xAA\x00\x01\x80\x00\x00\x01\x80\x00\x7A\x4D\xC2\x52\x7B\x53\xC2\xD6\x7A\x4F\x80\x00\x00\x01\x80\x00\x00\x01\x80\x00\x55\x55");
assert_eq!(RAW_DATA[32].0, 32);
assert_eq!(
RAW_DATA[32].1,
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
);
assert_eq!(RAW_DATA[32].1, DATA[&32],);
}
}

16
unifont.py Normal file
View file

@ -0,0 +1,16 @@
import struct
with open('unifont_all-15.1.04.hex', 'rt') as f:
data = f.readlines()
ret = []
for w in data:
a, w = w.strip().split(':')
e = [int(w[i:i+2], 16) for i in range(0, len(w), 2)]
b = int(a, 16)
ret.append(bytes([len(e) // 16]))
ret.append(bytes([(b >> 16) & 0xFF]))
ret.append(bytes([(b >> 8) & 0xFF]))
ret.append(bytes([(b >> 0) & 0xFF]))
ret.append(bytes(e))
with open('src/unifont.bin', 'wb') as f:
for x in ret:
f.write(x)