init
This commit is contained in:
commit
4d75050ac0
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal 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
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
2615
Cargo.lock
generated
Normal file
2615
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
20
Cargo.toml
Normal file
20
Cargo.toml
Normal 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
BIN
data/Strut.mp3
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
data/a.ppm
(Stored with Git LFS)
Normal file
BIN
data/a.ppm
(Stored with Git LFS)
Normal file
Binary file not shown.
11
data/index.html
Normal file
11
data/index.html
Normal 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
8
data/index1.html
Normal 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
21
data/master.js
Normal 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
8
data/sample.html
Normal 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
57
data/script.js
Normal 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
3
data/style.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
tabs {
|
||||
color: 6;
|
||||
}
|
24
shell.nix
Normal file
24
shell.nix
Normal 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
288
src/color.rs
Normal 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
101
src/css.rs
Normal 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
626
src/html.rs
Normal 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
1607
src/main.rs
Normal file
File diff suppressed because it is too large
Load diff
68
src/ppm.rs
Normal file
68
src/ppm.rs
Normal 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
153
src/resampler.rs
Normal 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
BIN
src/unifont.bin
(Stored with Git LFS)
Normal file
Binary file not shown.
46
src/unifont.rs
Normal file
46
src/unifont.rs
Normal 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
16
unifont.py
Normal 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)
|
Loading…
Reference in a new issue