151 stories
1 follower

Versus Bracket

1 Comment
Some works didn't make the cut; in "Ecks vs. Sever" vs. the passage of time, the latter seems to have won pretty decisively.
Read the whole story
2 days ago
Marvel is the clear winner here
Seattle, WA, US
Share this story

CSS in librsvg is now in Rust, courtesy of Mozilla Servo

1 Share

Summary: after an epic amount of refactoring, librsvg now does all CSS parsing and matching in Rust, without using libcroco. In addition, the CSS engine comes from Mozilla Servo, so it should be able to handle much more complex CSS than librsvg ever could before.

This is the story of CSS support in librsvg.


The first commit to introduce CSS parsing in librsvg dates from 2002. It was as minimal as possible, written to support a small subset of what was then CSS2.

Librsvg handled CSS stylesheets more "piecing them apart" than "parsing them". You know, when g_strsplit() is your best friend. The basic parsing algorithm was to turn a stylesheet like this:

rect { fill: blue; }

.classname {
    fill: green;
    stroke-width: 4;

Into a hash table whose keys are strings like rect and .classname, and whose values are everything inside curly braces.

The selector matching phase was equally simple. The code only handled a few possible match types as follows. If it wanted to match a certain kind of CSS selector, it would say, "what would this selector look like in CSS syntax", it would make up a string with that syntax, and compare it to the key strings it had stored in the hash table from above.

So, to match an element name selector, it would sprintf("%s", element->name), obtain something like rect and see if the hash table had such a key.

To match a class selector, it would sprintf(".%s", element->class), obtain something like .classname, and look it up in the hash table.

This scheme supported only a few combinations. It handled tag, .class, tag.class, and a few combinations with #id in them. This was enough to support very simple stylesheets.

The value corresponding to each key in the hash table was the stuff between curly braces in the stylesheet, so the second rule from the example above would contain fill: green; stroke-width: 4;. Once librsvg decided that an SVG element matched that CSS rule, it would re-parse the string with the CSS properties and apply them to the element's style.

I'm amazed that so little code was enough to deal with a good number of SVG files with stylesheets. I suspect that this was due to a few things:

  • While people were using complex CSS in HTML all the time, it was less common for SVG...

  • ... because CSS2 was somewhat new, and the SVG spec was still being written...

  • ... and SVGs created with illustration programs don't really use stylesheets; they include the full style information inside each element instead of symbolically referencing it from a stylesheet.

From the kinds of bugs that librsvg has gotten around "CSS support is too limited", it feels like SVGs which use CSS features are either hand-written, or machine-generated from custom programs like data plotting software. Illustration programs tend to list all style properties explicitly in each SVG element, and don't use CSS.

Libcroco appears

The first commit to introduce libcroco was to do CSS parsing, from March 2003.

At the same time, libcroco was introducing code to do CSS matching. However, this code never got used in librsvg; it still kept its simple string-based matcher. Maybe libcroco's API was not ready?

Libcroco fell out of maintainership around the first half of 2005, and volunteers have kept fixing it since then.

Problems with librsvg's string matcher for CSS

The C implementation of CSS matching in librsvg remained basically untouched until 2018, when Paolo Borelli and I started porting the surrounding code to Rust.

I had a lot of trouble figuring out the concepts from the code. I didn't know all the terminology of CSS implementations, and librsvg didn't use it, either.

I think that librsvg's code suffered from what the refactoring literature calls primitive obsession. Instead of having a parsed representation of CSS selectors, librsvg just stored a stringified version of them. So, a selector like rect#classname really was stored with a string like that, instead of an actual decomposition into structs.

Moreover, things were misnamed. This is the field that stored stylesheet data inside an RsvgHandle:

    GHashTable *css_props;

From just looking at the field declaration, this doesn't tell me anything about what kind of data is stored there. One has to grep the source code for where that field is used:

static void
rsvg_css_define_style (RsvgHandle * ctx,
                       const gchar * selector,
                       const gchar * style_name,
                       const gchar * style_value,
                       gboolean important)
    GHashTable *styles;

    styles = g_hash_table_lookup (ctx->priv->css_props, selector);

Okay, it looks up a selector by name in the css_props, and it gives back... another hash table styles? What's in there?

        g_hash_table_insert (styles,
                             g_strdup (style_name),
                             style_value_data_new (style_value, important));

Another string key called style_name, whose key is a StyleValueData; what's in it?

typedef struct _StyleValueData {
    gchar *value;
    gboolean important;
} StyleValueData;

The value is another string. Strings all the way!

At the time, I didn't really figure out what each level of nested hash tables was supposed to mean. I didn't understand why we handled style properties in a completely different part of the code, and yet this part had a css_props field that didn't seem to store properties at all.

It took a while to realize that css_props was misnamed. It wasn't storing a mapping of selector names to properties; it was storing a mapping of selector names to declaration lists, which are lists of property/value pairs.

So, when I started porting the CSS parsing code to Rust, I started to create real types with for each concept.

// Maps property_name -> Declaration
type DeclarationList = HashMap<String, Declaration>;

pub struct CssStyles {
    selectors_to_declarations: HashMap<String, DeclarationList>,

Even though the keys of those HashMaps are still strings, because librsvg didn't have a better way to represent their corresponding concepts, at least those declarations let one see what the hell is being stored without grepping the rest of the code. This is a part of the code that I didn't really touch very much, so it was nice to have that reminder.

The first port of the CSS matching code to Rust kept the same algorithm as the C code, the one that created strings with element.class and compared them to the stored selector names. Ugly, but it still worked in the same limited fashion.

Rustifying the CSS parsers

It turns out that CSS parsing is divided in two parts. One can have a style attribute inside an element, for example

<rect x="0" y="0" width="100" height="100"
      style="fill: green; stroke: magenta; stroke-width: 4;"/>

This is a plain declaration list which is not associated to any selectors, and which is applied directly to just the element in which it appears.

Then, there is the <style> element itself, with a normal-looking CSS stylesheet

<style type="text/css">
  rect {
    fill: green;
    stroke: magenta;
    stroke-width: 4;

This means that all <rect> elements will get that style applied.

I started to look for existing Rust crates to parse and handle CSS data. The cssparser and selectors crates come from Mozilla, so I thought they should do a pretty good job of things.

And they do! Except that they are not a drop-in replacement for anything. They are what gets used in Mozilla's Servo browser engine, so they are optimized to hell, and the code can be pretty intimidating.

Out of the box, cssparser provides a CSS tokenizer, but it does not know how to handle any properties/values in particular. One must use the tokenizer to implement a parser for each kind of CSS property one wants to support — Servo has mountains of code for all of HTML's style properties, and librsvg had to provide a smaller mountain of code for SVG style properties.

Thus started the big task of porting librsvg's string-based parsers for CSS properties into ones based on cssparser tokens. Cssparser provides a Parser struct, which extracts tokens out of a CSS stream. Out of this, librsvg defines a Parse trait for parsable things:

use cssparser::Parser;

pub trait Parse: Sized {
    type Err;

    fn parse(parser: &mut Parser<'_, '_>) -> Result<Self, Self::Err>;

What's with those two default lifetimes in Parser<'_, '_>? Cssparser tries very hard to be a zero-copy tokenizer. One of the lifetimes refers to the input string which is wrapped in a Tokenizer, which is wrapped in a ParserInput. The other lifetime is for the ParserInput itself.

In the actual implementation of that trait, the Err type also uses the lifetime that refers to the input string. For example, there is a BasicParseErrorKind::UnexpectedToken(Token<'i>), which one returns when there is an unexpected token. And to avoid copying the substring into the error, one returns a slice reference into the original string, thus the lifetime.

I was more of a Rust newbie back then, and it was very hard to make sense of how cssparser was meant to be used.

The process was more or less this:

  • Port the C parsers to Rust; implement types for each CSS property.

  • Port the &str-based parsers into ones that use cssparser.

  • Fix the error handling scheme to match what cssparser's high-level traits expect.

This last point was... hard. Again, I wasn't comfortable enough with Rust lifetimes and nested generics; in the end it was all right.

Moving declaration lists to Rust

With the individual parsers for CSS properties done, and with them already using a different type for each property, the next thing was to implement cssparser's traits to parse declaration lists.

Again, a declaration list looks like this:

fill: blue;
stroke-width: 4;

It's essentially a key/value list.

The trait that cssparser wants us to implement is this:

pub trait DeclarationParser<'i> {
    type Declaration;
    type Error: 'i;

    fn parse_value<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self::Declaration, ParseError<'i, Self::Error>>;

That is, define a type for a Declaration, and implement a parse_value() method that takes a name and a Parser, and outputs a Declaration or an error.

What this really means is that the type you implement for Declaration needs to be able to represent all the CSS property types that you care about. Thus, a struct plus a big enum like this:

pub struct Declaration {
    pub prop_name: String,
    pub property: ParsedProperty,
    pub important: bool,

pub enum ParsedProperty {

This gives us declaration lists (the stuff inside curly braces in a CSS stylesheet), but it doesn't give us qualified rules, which are composed of selector names plus a declaration list.

Refactoring towards real CSS concepts

Paolo Borelli has been steadily refactoring librsvg and fixing things like the primitive obsession I mentioned above. We now have real concepts like a Document, Stylesheet, QualifiedRule, Rule, AtRule.

This refactoring took a long time, because it involved redoing the XML loading code and its interaction with the CSS parser a few times.

Implementing traits from the selectors crate

The selectors crate contains Servo's code for parsing CSS selectors and doing matching. However, it is extremely generic. Using it involves implementing a good number of concepts.

For example, this SelectorImpl trait has no methods, and is just a collection of types that refer to your implementation of an element tree. How do you represent an attribute/value? How do you represent an identifier? How do you represent a namespace and a local name?

pub trait SelectorImpl {
    type ExtraMatchingData: ...;
    type AttrValue: ...;
    type Identifier: ...;
    type ClassName: ...;
    type PartName: ...;
    type LocalName: ...;
    type NamespaceUrl: ...;
    type NamespacePrefix: ...;
    type BorrowedNamespaceUrl: ...;
    type BorrowedLocalName: ...;
    type NonTSPseudoClass: ...;
    type PseudoElement: ...;

A lot of those can be String, but Servo has smarter things in store. I ended up using the markup5ever crate, which provides a string interning framework for markup and XML concepts like a LocalName, a Namespace, etc. This reduces memory consumption, because instead of storing string copies of element names everywhere, one just stores tokens for interned strings.

(In the meantime I had to implement support for XML namespaces, which the selectors code really wants, but which librsvg never supported.)

Then, the selectors crate wants you to say how your code implements an element tree. It has a monster trait Element:

pub trait Element {
    type Impl: SelectorImpl;

    fn opaque(&self) -> OpaqueElement;

    fn parent_element(&self) -> Option<Self>;

    fn parent_node_is_shadow_root(&self) -> bool;


    fn prev_sibling_element(&self) -> Option<Self>;
    fn next_sibling_element(&self) -> Option<Self>;

    fn has_local_name(
        local_name: &<Self::Impl as SelectorImpl>::BorrowedLocalName
    ) -> bool;

    fn has_id(
        id: &<Self::Impl as SelectorImpl>::Identifier,
        case_sensitivity: CaseSensitivity,
    ) -> bool;


That is, when you provide an implementation of Element and SelectorImpl, the selectors crate will know how to navigate your element tree and ask it questions like, "does this element have the id #foo?"; "does this element have the name rect?". It makes perfect sense in the end, but it is quite intimidating when you are not 100% comfortable with webs of traits and associated types and generics with a bunch of trait bounds!

I tried implementing that trait twice in the last year, and failed. It turns out that its API needed a key fix that landed last June, but I didn't notice until a couple of weeks ago.


Two days ago, Paolo and I committed the last code to be able to completely replace libcroco.

And, after implementing CSS specificity (which was easy now that we have real CSS concepts and a good pipeline for the CSS cascade), a bunch of very old bugs started falling down (1 2 3 4 5 6).

Now it is going to be easy to implement things like letting the application specify a user stylesheet. In particular, this should let GTK remove the rather egregious hack it has to recolor SVG icons while using librsvg indirectly.


This will appear in librsvg 2.47.1 — that version will no longer require libcroco.

As far as I know, the only module that still depends on libcroco (in GNOME or otherwise) is gnome-shell. It uses libcroco to parse CSS and get the basic structure of selectors so it can implement matching by hand.

Gnome-shell has some code which looks awfully similar to what librsvg had when it was written in C:

  • StTheme has the high-level CSS stylesheet parser and the selector matching code.

  • StThemeNode has the low-level CSS property parsers.

... and it turns out that those files come all the way from HippoCanvas, the CSS-aware canvas that Mugshot used! Mugshot was a circa-2006 pre-Facebook aggregator for social media data like blogs, Flickr pictures, etc. HippoCanvas also got used in Sugar, the GUI for One Laptop Per Child. Yes, our code is that old.

Libcroco is unmaintained, and has outstanding CVEs. I would be very happy to assist someone in porting gnome-shell's CSS code to Rust :)

Read the whole story
8 days ago
Seattle, WA, US
Share this story

First Shortwave Beta

1 Share

Earlier this year I announced Shortwave, the successor of Gradio. Now, almost 11 months later, I’m proud to announce the first public beta of Shortwave! 🎉

Shortwave is an internet radio player that lets you search for stations, listen to them and record songs automatically.

Automatic recording of songs

When a station is being played, everything gets automatically recorded in the background. You hear a song you like? No problem, you can save the song afterwards and play it with your favorite music player. Songs are automatically detected based on the stream metadata.

Adaptive interface

The interface of Shortwave is completely adaptive and adapts to all screen sizes. So you can use it on the desktop, but also on your Linux (not Android!) based smartphone. For this Shortwave uses the awesome libhandy library!


It’s possible to stream the audio playback to a network device, which implements the Google Cast protocol (e.g. Chromecast). So you can easily listen to your favorite stations e.g. from a TV.

Access to a huge database

Shortwave uses the internet service radio-browser.info as station database. It contains more than 25,000 stations. This ensures that you will find every radio station, whether a known or an exotic one. And if something is really missing, you can add your station here.

Where can I get it?

This is the first beta version of Shortwave. All basic features should work, but issues can appear. If somethings is wrong, please open a issue report here!

You can get it from Flathub (Beta). Install it with

flatpak install https://flathub.org/beta-repo/appstream/de.haeckerfelix.Shortwave.flatpakref

or just click here.

Read the whole story
8 days ago
Seattle, WA, US
Share this story

Would you trust Google with your medical records? It might already have them

1 Share
Mountain View, Calif. - May 21, 2018: Exterior view of a Googleplex building, the corporate headquarters complex of Google and its parent company Alphabet Inc.

Enlarge / Mountain View, Calif. - May 21, 2018: Exterior view of a Googleplex building, the corporate headquarters complex of Google and its parent company Alphabet Inc. (credit: Getty Images / zphotos)

Google quietly partnered last year with Ascension—the country's second-largest health system—and has since gained access to detailed medical records on tens of millions of Americans, according to a November 11 report by The Wall Street Journal.

The endeavor, code-named "Project Nightingale," has enabled at least 150 Google employees to see patient health information, which includes diagnoses, laboratory test results, hospitalization records, and other data, according to internal documents and the newspaper's sources. In all, the data amounts to complete medical records, WSJ notes, and contains patient names and birth dates.

The move is the latest by Google to get a grip on the sprawling health industry. At the start of the month, Google announced a deal to buy Fitbit, prompting concerns over what it will do with all the sensitive health data amassed from the popular wearables. Today's news will likely spur more concern over health privacy issues.

Read 7 remaining paragraphs | Comments

Read the whole story
8 days ago
Seattle, WA, US
Share this story

Seattle city council election results could bring back an “Amazon tax”

1 Share
The Amazon Spheres in downtown Seattle.

Enlarge / The Amazon Spheres in downtown Seattle. (credit: 400tmax / Getty)

Amazon has suffered a setback in its own backyard as several candidates for Seattle's City Council won election despite a $1.5 million campaign by business groups to defeat them. That included Kshama Sawant, an incumbent and self-described socialist who has been a thorn in Amazon's side in recent years. The vote was held last Tuesday, but the results only became clear in recent days.

The result is significant for Amazon because last year Seattle's city council passed a $275 per employee tax on large employers. Amazon, Starbucks, and other large Seattle businesses blasted the law and funded a ballot measure to overturn it. Facing the threat of having their law overturned by voters, the city council itself repealed the measure a month after it passed.

If business groups had defeated pro-tax candidates in last week's election, it would have made the city council very reluctant to consider taxing employers again. Instead, the election results have emboldened supporters of an "Amazon tax."

Read 7 remaining paragraphs | Comments

Read the whole story
8 days ago
Seattle, WA, US
Share this story

Sonic the Hedgehog’s movie look is fixed following fan outcry

1 Share

In early May, the first trailer for the Sonic the Hedgehog movie was released to almost universal criticism. The problem was, Sonic just looked… terrifying. Instead of the adorable16-bit hero most of us remember, this CGI Sonic was all wrong. His legs were weirdly tapered, and the body proportions were wrong, with perhaps the worst bit being that nightmare of mouth, full of teeth.

The backlash was immediate (and well-founded) and heard remarkably quickly. Within two days the film's director Jeff Fowler announced that changes would be made before the film's release, a date that soon slipped to February 2020—presumably once the scope of the work set in. On Tuesday, the extent of that became clear with the release of a new trailer for the movie, featuring a much-improved render of the main character.

The human body proportions are a thing of the past; now Sonic's feet are big and his ankles are implausibly wider than his thighs. His torso has shrunk, particularly at the waist, and his hands are now massive and once again clad in the proper white gloves. Our hedgehog's head has swelled, as have his eyes, and when he opens his mouth I no longer want to shriek in horror until professionals come and sedate me.

Read 1 remaining paragraphs | Comments

Read the whole story
8 days ago
Seattle, WA, US
Share this story
Next Page of Stories