Under heavy research and development, please don't use this yet!
Several macros and helpers for embedding RSX Renderers on various platforms, rendering display lists generated by the RSX Primitives library. Handles JSON and WebRender display items, targetting web platforms (WebGL
, Canvas
, DOM
), iOS using UIView
and native controls, VR using WebVR
, and native using WebRender, powered by glutin and gleam.
This crate allows Rust-based frontends built using RSX to be embedded across a multitude of platforms.
For quick and easy example demos, simply check out here.
To build your own project from scratch, follow the steps outlined below.
To get access to the rsx!
, css!
and link!
macros, as well as the renderers, add this to your Cargo.toml
file:
[dependencies]
rsx = { git = "https://github.com/victorporof/rsx.git" }
rsx-embedding = { git = "https://github.com/victorporof/rsx-embedding.git" }
...and clone the rsx-renderers
crate as a submodule:
git submodule add https://github.com/victorporof/rsx-renderers.git
Why a submodule and not a dependency? The rsx-renderers
contains helpers for rendering in external environments, is used this way to allow linking to the pre-built renderers as .js and .swift files for web and iOS targets respectively, without having to rely on build steps. It can be equally straightforward to depend on them normally, as a dependency listed on crates.io or a link to a github repository, but that could involve several build steps using webpack, gulp, CocoaPods etc.
Then, simply import the library into your code and use the rsx!
, css!
macros to parse RSX and CSS into rsx_dom::DOMNode
, or rsx_dom::Stylesheet
data structures respectively. Use the link!
macro, combined with feature gates to specify and link to the target platform. Enable proc_macro
and link_args
if the current version of the Rust nighly compiler still has them turned off by default.
#![feature(box_syntax)]
#![feature(proc_macro)]
#![feature(link_args)]
extern crate rsx;
#[macro_use]
extern crate rsx_embedding;
use rsx::{css, load_font, load_image, rsx};
use rsx_embedding::rsx_dom::types::*;
use rsx_embedding::rsx_stylesheet::types::*;
use rsx_embedding::rsx_primitives::prelude::{DOMNode, FileCache, FontCache, ImageCache};
use rsx_embedding::rsx_resources::fonts::types::{EncodedFont, FontId};
use rsx_embedding::rsx_resources::images::types::{EncodedImage, ImageEncodingFormat, ImageId};
fn setup(_: &mut FileCache, images: &mut ImageCache, fonts: &mut FontCache) {
let logo = load_image!("fixtures/images/Quantum.png");
images.add_image(ImageId::new("logo"), &logo).ok();
let font = load_font!("fixtures/fonts/FreeSans.ttf");
fonts.add_font(FontId::new("font"), &font, 0).ok();
}
fn render() -> DOMNode {
let mut stylesheet = css!("src/example.css");
rsx! {
<view style={stylesheet.take(".center")}>
<view style={stylesheet.take(".root")}>
<image style={stylesheet.take(".image")} src="logo" />
<text style={stylesheet.take(".text")}>
{ "Hello World!" }
</text>
</view>
</view>
}
}
link!(setup, render);
The link!
macro's behavior depends on the rsx-renderers/*-embedding
feature gate and will target the appropriate code for externing and turning on the link flags necessary to have Rust code available to external consumers, such as asmjs-unknown-emscripten
, wasm32-unknown-emscripten
, x86_64-apple-darwin
etc.
For native targets, similarly to any other targets, implementation code remains the same but different features must be specified in the crate.
Enable compiler plugin macros and a native WebRender-based renderer using WebRender display lists like this:
[features]
default = ["target-native"]
target-native = [
"rsx-renderers/webrender-display-list",
"rsx-renderers/native-embedding"
]
Then simply:
cargo build
For web targets, similarly to any other targets, implementation code remains the same but different features must be specified in the crate. Targetting the web involves enabling some custom features in your Cargo.toml
file, as well as installing emscripten and embedding into a regular web project.
Enable compiler plugin macros and web-based renderers using JSON-serialized display lists like this:
[features]
default = ["target-web"]
target-web = [
"rsx-renderers/json-display-list",
"rsx-renderers/web-embedding"
]
To build, you need to install Emscripten, and a couple of Rust targets.
rustup target add asmjs-unknown-emscripten
rustup target add wasm32-unknown-emscripten
Follow the steps outlined in the official docs, or build from source from the Github repo. If you're using fish as your shell, the emsdk_env.sh
script won't add the necessary entries to your $PATH, so either run it under sh
or add the paths yourself.
./emsdk update
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
cargo build --target=asmjs-unknown-emscripten
This will generate the apropriate asm.js code inside the cargo targets
directory, which can then be used in content code. embedding this into a document is the client's responsibility, and fairly straightforward (see below).
The dist/target-web directory contains the webpacked web renderers readily available for embedding. These are RSXCanvasRenderer
(for 2D and WebGL contexts) and RSXDOMRenderer
(for creating a web-based DOM tree from a display list) respectively. The latter retains a11y features.
<style>
body {
margin: 0;
}
</style>
<body>
<script type="text/javascript" src="rsx-renderers/dist/target-web/renderer.min.js" charset="utf-8"></script>
<script type="text/javascript" src="target/asmjs-unknown-emscripten/../example.js" charset="utf-8"></script>
<script>
const getDisplayList = cwrap('__get_display_list', 'string', ['number', 'number']);
const width = window.innerWidth;
const height = window.innerHeight;
const displayList = JSON.parse(getDisplayList(width, height));
const canvas = new RSXCanvasRenderer(width, height);
canvas.mount(document.body);
canvas.draw(displayList);
</script>
</body>
For iOS targets, implementation code remains the same but different features must be specified for the crate, building and linking jemalloc, as well as configuring a new Swift-powered XCode project being necessary.
For example, enable compiler plugin macros and ios-based renderers using JSON-serialized display lists like this:
[lib]
name = "demo"
path = "src/main.rs"
crate-type = ["staticlib", "cdylib"]
[features]
default = ["target-ios"]
target-ios = [
"rsx-renderers/json-display-list",
"rsx-renderers/ios-embedding"
]
To build the above example for iOS, install the appropriate architectures, as well as cargo-lipo
:
rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios
cargo install cargo-lipo
Then simply:
cargo lipo --release
To embed into an iOS project, a few steps are necessary: building and linking jemalloc
(required for enabling several underlying Servo components to work), configuring a new Swift-powered XCode project and importing the necessary renderers.
To build a universal jemalloc
iOS library, one can either get really angry (or sad) trying for a while, or use this script instead. It will download, configure, build and install the library into a "jemalloc" subdirectory.
./build-jemalloc.sh
Setting up XCode is fairly easy and follows the steps described here, with the additional linking of jemalloc
and importing the necessary renderers into the project.
Link the following files to a new Swift-powered XCode project.
#include <stdint.h>
const char* __get_display_list(float, float);
void __free_display_list(char *);
#ifndef bridging_header_h
#define bridging_header_h
#include "externals.h"
#endif
Don't forget to specify the bridging header file under the Project Settings. See this tutorial for how to do that.
Link the libresolv.tbd
library, as well as the jemalloc
and library created with cargo lipo --release
in the steps above (located under cargo/target/universal/release/
).
Don't forget to specify the library search paths under the Project Settings. See this tutorial for how to do that.
If you'd like to deal with JSON in a more easier way, installing CocoaPods and depending on SwiftyJSON
is a good idea.
sudo gem install cocoapods
pod init
Add this to your podfile:
target 'Example' do
use_frameworks!
pod 'SwiftyJSON'
end
Then install with:
pod install
To display images loaded from remote URLs in your project, add this to your Info.plist
:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
The src/target-ios directory contains the iOS renderers readily available for embedding. This only includes UIViewRenderer
for the time being, which uses iOS UIView
and subclasses to render the display list.
Add UIViewRenderer.swift
to your XCode project, then:
import Foundation
import SwiftyJSON
class Bridge {
func getDisplayList(width: Float, height: Float) -> JSON {
let result = __get_display_list(width, height)
let swift_result = String(cString: result!)
let json = JSON(data: swift_result.data(using: .utf8)!)
__free_display_list(UnsafeMutablePointer(mutating: result))
return json
}
}
...
let sizeRect = self.view.frame.size
let width = Float(sizeRect.width)
let height = Float(sizeRect.height)
let bridge = Bridge()
let displayList = bridge.getDisplayList(width: width, height: height)
let renderer = UIViewRenderer(deviceWidth: width, deviceHeight: height)
renderer.mount(parentView: self.view)
renderer.draw(displayList: displayList)
Note: rsx-renderers
also re-exports the rsx-primitives
crate, the rsx-dom
crate, the rsx-stylesheet
crate and the rsx-layout
crate.