Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,19 +280,35 @@ drawn by the studio itself (through [Blitz](https://github.com/DioxusLabs/blitz)
rather than a browser, on every platform. Its CSS animations and transitions
run on the program clock, so they land on the same frame in the program and in
a recording or replay of it, whatever the studio's load. It runs no scripts
and loads nothing: no images, stylesheets, or fonts from anywhere. Its text is
set in the bundled Noto Sans, regular, bold, or italic, whatever `font-family`
names, so it looks the same on every machine. An overlay outside what
overlays draw fails to start, naming the first element, attribute, rule, or
property it refused:
and never touches the network. Its text is set in the bundled Noto Sans,
regular, bold, or italic, unless it loads a face of its own, and never in the
machine's fonts, so it looks the same on every machine.

An overlay's own images and fonts, such as a logo or a brand face, come from
`data:` URLs or from files in the scene file's directory, named relative to
it (`url(brand/logo.png)`), never from the web or anywhere else on disk. The
studio reads them all as the overlay starts, so its first frame has them, and
a file changed on disk changes nothing on air until the overlay starts again,
as when it is edited. Images are PNG or JPEG, at most 4096 pixels a side;
fonts are TrueType, OpenType, WOFF, or WOFF2, loaded with `@font-face`:

```css
@font-face { font-family: Brand; src: url(brand/face.woff2); font-weight: bold }
.title { font-family: Brand; background: url(brand/logo.png) no-repeat }
```

An overlay outside what overlays draw fails to start, naming the first
element, attribute, rule, property, or asset it refused:

- Elements: text and the boxes around it (`div`, `span`, `p`, headings,
lists, tables, and the like), plus `style`, `title`, and `meta`. Not
`script`, `img`, `link`, `iframe`, `svg`, `canvas`, or form controls.
lists, tables, and the like), `img`, plus `style`, `title`, and `meta`. Not
`script`, `link`, `iframe`, `svg`, `canvas`, or form controls.
- Attributes: `id`, `class`, `style`, `lang`, `dir`, `title`, `hidden`,
table and list numbering ones, and `data-*` and `aria-*`.
- CSS rules: style rules, nested ones included, `@media`, and `@keyframes`.
Not `@import` or `@font-face`, nor any `url()` or `image-set()`.
table and list numbering ones, `data-*` and `aria-*`, and an `img`'s `src`
and `alt`.
- CSS rules: style rules, nested ones included, `@media`, `@keyframes`, and
`@font-face` with `font-family`, `src`, `font-weight`, `font-style`, and
`font-display`. Not `@import`, nor `local()` or `image-set()`.
- CSS properties: box layout, flexbox, grid, positioning, margins and
padding, colors, opacity, gradients, borders and their radii, `box-shadow`,
outlines, transforms, `clip-path`, fonts and text layout, text decoration,
Expand Down
1 change: 0 additions & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ than opening an m2 for them.

- [Viewer-side composition](/quest/m1/compose/README.md) - sources as their own tracks plus a layout track, composed by viewers and by a server
- [Overlays restart on air](/quest/m1/overlay-restart.md) - an overlay's CSS timeline restarts at each cut unless set to keep running
- [Overlay assets](/quest/m1/overlay-assets.md) - overlays use fonts and images from data: URLs or files beside the scene
- [Hotkeys and editor preferences](/quest/m1/hotkeys.md) - system-wide hotkeys, arrow nudge, aspect lock, and persisted editor settings
- [Isolated guest recordings](/quest/m1/guest-recording.md) - record-each-source also writes each guest's tracks to its own file
- [Scene graph view](/quest/m1/scene-graph.md) - a node view of picture and audio scenes, their routing, volume, pan, and waveforms
27 changes: 0 additions & 27 deletions quest/m1/overlay-assets.md

This file was deleted.

12 changes: 10 additions & 2 deletions rs/moq-scene/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ cef = ["dep:cef"]
# Overlays: Blitz lays HTML and CSS out and paints it into a Vello scene, which
# Vello renders on the compositor's device. Pinned to the beta whose Vello and
# wgpu match moq-video's: the stable 0.2 line is on wgpu 26, which cannot share
# the device. Fonts and network access are left off, so an overlay loads nothing.
# the device. System fonts and network access are left off, so an overlay loads
# only its own assets; `woff` decodes the WOFF and WOFF2 fonts it loads.
anyrender_vello = "0.15"
blitz-dom = { version = "=0.3.0-beta.4", default-features = false }
blitz-dom = { version = "=0.3.0-beta.4", default-features = false, features = ["woff"] }
blitz-html = "=0.3.0-beta.4"
blitz-paint = { version = "=0.3.0-beta.4", default-features = false }
blitz-traits = "=0.3.0-beta.4"
# Decodes an overlay's base64 `data:` URLs.
base64 = "0.23"
bytemuck = { version = "1", features = ["derive"] }
bytes = { workspace = true }
# Reads the declarations an overlay's CSS checks, as Stylo serializes them.
Expand All @@ -61,6 +64,8 @@ moq-net = { workspace = true }
# `render` is the compositor's whole input path: it turns every decoded or
# captured frame into a texture on the studio's device.
moq-video = { workspace = true, features = ["render"] }
# Decodes an overlay's `data:` URLs.
percent-encoding = "2"
serde = { workspace = true }
serde_with = { workspace = true }
# Stylo, Blitz's style engine, which parses an overlay's CSS to check it.
Expand All @@ -71,6 +76,9 @@ toml = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
vello = "0.11"
# Decompresses an overlay's WOFF and WOFF2 fonts to check them. Already in the
# graph through Blitz's `woff` feature.
wuff = "0.2"

# The helper process a browser source runs CEF in, and the socket between them.
[target.'cfg(target_os = "linux")'.dependencies]
Expand Down
5 changes: 3 additions & 2 deletions rs/moq-scene/src/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1455,8 +1455,9 @@ pub enum Source {
/// A graphic written as HTML and CSS without scripts, such as a title or
/// lower third. Its CSS animations run on the program clock, so they land
/// on the same frame in the program and everything made from it. It loads
/// nothing, and HTML or CSS outside what overlays draw fails the source,
/// naming it.
/// only images and fonts from `data:` URLs and files under the scene
/// file's directory, and HTML, CSS, or an asset outside what overlays draw
/// fails the source, naming it.
Overlay {
/// The document, its CSS in `<style>` elements and `style` attributes.
html: String,
Expand Down
37 changes: 34 additions & 3 deletions rs/moq-scene/src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,8 +519,8 @@ impl Compositor {
layer.lut = Some(self.effects.lut(&cube));
continue;
}
source::Picture::Overlay => {
let Source::Overlay { html, width, height } = source else {
source::Picture::Overlay(content) => {
let Source::Overlay { width, height, .. } = source else {
unreachable!("only an overlay source shows an overlay")
};
let overlays = match &mut self.overlays {
Expand All @@ -529,7 +529,7 @@ impl Compositor {
};
match overlays {
Ok(overlays) => {
let overlay = overlays.open(html, *width, *height, now);
let overlay = overlays.open(content, *width, *height, now);
layer.picture = Some(self.effects.picture(overlay.texture()));
layer.overlay = Some(overlay);
}
Expand Down Expand Up @@ -2841,6 +2841,37 @@ mod tests {
.await;
}

/// An overlay's image, a file beside the scene file, is on the first
/// frame that shows the overlay.
#[tokio::test]
#[ignore = "needs a Vulkan device; run by just rs render-sw"]
async fn an_overlay_draws_its_image_on_its_first_frame() {
local(async {
let dir = tempfile::tempdir().unwrap();
image::RgbaImage::from_pixel(1, 1, image::Rgba(BLUE))
.save(dir.path().join("logo.png"))
.unwrap();
let mut collection = collection();
collection.sources.insert(
"overlay".into(),
Source::Overlay {
html: "<style>
body { margin: 0 }
div { width: 4px; height: 4px; background: url(logo.png) 0 0 / 100% 100% }
</style><div></div>"
.into(),
width: 4,
height: 4,
},
);
let fill = Rect::fill(&collection.canvas);
collection.scenes[0].items = vec![Item::new("red", fill), Item::new("overlay", fill)];
let program = drawn(collection, dir).await;
assert_eq!(pixel(&program, 3, 3), BLUE);
})
.await;
}

/// An overlay's CSS animation runs on the program clock from when the
/// overlay opens: a frame shows it where the program time says, however late
/// that frame is drawn, even the first one to show it.
Expand Down
Loading