I would like to mentor people for librsvg and gnome-class this Summer, both for Outreachy and Summer of Code.
Librsvg projects
Project: port filter effects from C to Rust
Currently librsvg implements SVG filter effects in C. These are basic image processing filters like Gaussian blur, matrix convolution, Porter-Duff alpha compositing, etc.
There are some things that need to be done:
-
Split the single
rsvg-filter.c
into multiple source files, so it's easier to port each one individually. -
Figure out the common infrasctructure:
RsvgFilter
,RsvgFilterPrimitive
. All the filter use these to store intermediate results when processing SVG elements. -
Experiment with the correct Rust abstractions to process images pixel-by-pixel. We would like to omit per-pixel bounds checks on array accesses. The image crate has some nice iterator traits for pixels. WebKit's implementation of SVG filters also has interesting abstractions for things like the need for a sliding window with edge handling for Gaussian blurs.
-
Ensure that our current filters code is actually working. Not all of the official SVG test suite's tests are in place right now for the filter effects; it is likely that some of our implementation is broken.
For this project, it will be especially helpful to have a little background in image processing. You don't need to be an expert; just to have done some pixel crunching at some point. You need to be able to read C and write Rust.
Project: CSS styling with rust-selectors
Librsvg uses an very simplistic algorithm for CSS cascading. It uses libcroco to parse CSS style data; libcroco is unmaintained and rather prone to exploits. I want to use Servo's selectors crate to do the cascading; we already use the rust-cssparser crate as a tokenizer for basic CSS properties.
-
For each node in its DOM tree, librsvg's
Node
structure keeps aVec<>
of children. We need to move this to store the next sibling and the first/last children instead. This is the data structure that rust-selectors prefers. The Kuchiki crate has an example implementation; borrowing some patterns from there could also help us simplify our reference counting for nodes. -
Our styling machinery needs porting to Rust. We have a big
RsvgState
struct which holds the CSS state for each node. It is easy to port this to Rust; it's more interesting to gradually move it to a scheme like Servo's, with a distinction between specified/computed/used values for each CSS property.
For this project, it will be helpful to know a bit of how CSS works. Definitely be comfortable with Rust concepts like ownership and borrowing. You don't need to be an expert, but if you are going through the "fighting the borrow checker" stage, you'll have a harder time with this. Or it may be what lets you grow out of it! You need to be able to read C and write Rust.
Bugs for newcomers: We have a number of easy bugs for newcomers to librsvg. Some of these are in the Rust part, some in the C part, some in both — take your pick!
Projects for gnome-class
Gnome-class is the code generator that lets you write GObject implementations in Rust. Or at least that's the intention — the project is in early development. The code is so new that practically all of our bugs are of an exploratory nature.
Gnome-class works like a little compiler. This is from one of the
examples; note the call to gobject_gen!
in there:
struct SignalerPrivate {
val: Cell<u32>
}
impl Default for SignalerPrivate {
fn default() -> Self {
SignalerPrivate {
val: Cell::new(0)
}
}
}
gobject_gen! {
class Signaler {
type InstancePrivate = SignalerPrivate;
}
impl Signaler {
signal fn value_changed(&self);
fn set_value(&self, v: u32) {
let private = self.get_priv();
private.val.set(v);
self.emit_value_changed();
}
}
}
Gnome-class implements this gobject_gen!
macro as follows:
-
First we parse the code inside the macro using the
syn
crate. This is a crate that lets you parse Rust source code from theTokenStream
that the compiler hands to implementations of procedural macros. You give aTokenStream
tosyn
, and it gives you back structs that represent function definitions,impl
blocks, expressions, etc. From this parsing stage we build an Abstract Syntax Tree (AST) that closely matches the structure of the code that the user wrote. -
Second, we take the AST and convert it to higher-level concepts, while verifying that the code is semantically valid. For example, we build up a
Class
structure for each defined GObject class, and annotate it with the methods and signals that the user defined for it. This stage is the High-level Internal Representation (HIR). -
Third, we generate Rust code from the validated HIR. For each class, we write out the boilerplate needed to register it against the GObject type system. For each virtual method we write a trampoline to let the C code call into the Rust implementation, and then write out the actual Rust impl that the user wrote. For each signal, we register it against the GObjectClass, and write the appropriate trampolines both to invoke the signal's default handler and any Rust callbacks for signal handlers.
For this project, you definitely need to have written GObject code in C in the past. You don't need to know the GObject internals; just know that there are things like type registration, signal creation, argument marshalling, etc.
You don't need to know about compiler internals.
You don't need to have written Rust procedural macros; you can learn as you go. The code has enough infrastructure right now that you can cut&paste useful bits to get started with new features. You should definitely be comfortable with the Rust borrow checker and simple lifetimes — again, you can cut&paste useful code already, and I'm happy to help with those.
This project demands a little patience. Working on the implementation of procedural macros is not the smoothest experience right now (one needs to examine generated code carefully, and play some tricks with the compiler to debug things), but it's getting better very fast.