jan Pontaoski's Thoughts

libre

rust is quite a neat language, isn't it? gigantic library ecosystem, memory safety, tons of developer-friendly tools in it. for Ikona, I decided to utilise this language, and instead of relying on binding generators that hide half the magic away from you, I wrote all bindings by hand.

rust –> C++ by hand: how?

obviously, rust and C++ are different programming languages and neither of them have language-level interop with each other. what they do both have is C. C—the lingua franca of the computing world. unfortunately, C is a very bad lingua franca. something as basic as passing arrays between programming languages becomes boilerplate hell fast. however, it is possible and once you set up a standardised method of passing arrays, it becomes far easier.

rust to C

so, in order to start going from rust to C++, you need to stop at C first. for Ikona, I put C API bindings in a separate crate in the same workspace. you have a few best friends when writing rust to C here: – #[no_mangle]: keeps rustc from mangling your symbols from pure C – unsafe: because C is ridiculously unsafe and Rust hates unsafety unless you tell it that you know what you're doing – extern "C": makes rust expose a C ABI that can be eaten by the C++ half – #[repr(C)]: tells rust to lay out the memory of a thing like C does – Box: pointer management – CString: char* management

memory management

Box and CString are your friends for memory management when talking to C. the general cycle looks like this:

pub unsafe extern "C" new_thing() -> *mut Type {
    Box::into_raw(thing) // for non-rustaceans, the lack of a semicolon means this is returned
}
pub unsafe extern "C" free_thing(ptr: *mut Type) {
    assert!(!ptr.is_null());
    Box::from_raw(ptr);
}

into_raw tells rust to let C have fun with the pointer for a while, so it won't free the memory. when C is done playing with the pointer, it returns it to Rust so it can from_raw the pointer to free the memory.

structs

for Ikona, I didn't bother attempting to convert Rust structs into C structs, instead opting for opaque pointers, as they're a lot easier to deal with on the Rust side.

an average function for accessing a struct value in Ikona looks like this:

#[no_mangle]
pub unsafe extern "C" fn ikona_theme_get_root_path(ptr: *const IconTheme) -> *mut c_char {
    assert!(!ptr.is_null()); // make sure we don't have a null pointer

    let theme = &*ptr; // grab a reference to the Rust value the pointer represents

    CString::new(theme.root_path.clone()).expect("Failed to create CString").into_raw() // return a char* from the field being accessed
}

this is very similar to how calling methods on structs is bridged to C in Ikona.

#[no_mangle]
pub unsafe extern "C" fn ikona_icon_extract_subicon_by_id(
    ptr: *mut Icon,
    id: *mut c_char,
    target_size: i32,
) -> *mut Icon {
    assert!(!ptr.is_null()); // gotta make sure our Icon isn't null
    assert!(!id.is_null()); // making sure our string isn't null

    let id_string = CStr::from_ptr(id).to_str().unwrap(); // convert the C string into a Rust string, and explicitly crash instead of having undefined behaviour if something goes wrong

    let icon = &*ptr; // grab a reference to the Rust object from the pointer

    // now let's call the method C wanted to call
    let proc = match icon.extract_subicon_by_id(id_string, target_size) {
        Ok(icon) => icon,
        Err(_) => return ptr::null_mut::<Icon>(),
    };

    // make a new Box for the icon
    let boxed: Box<Icon> = Box::new(proc);

    // let C have fun with the pointer
    Box::into_raw(boxed)
}

enums

enums are very simple to bridge, given they aren't the fat enums Rust has. just declare them like this:

#[repr(C)]
pub enum IkonaDirectoryType {
    Scalable,
    Threshold,
    Fixed,
    None
}

and treat them as normal. no memory management shenanigans to be had here.

ABI? what about API?

C has header files, and we need to describe the C API for human usage.

structs

since Ikona operates on opaque pointers, C just needs to be told that the type for a struct is a pointer.

typedef void* IkonaIcon;

enums

enums are ridiculously easy.

#[repr(C)]
pub enum IkonaDirectoryType {
    Scalable,
    Threshold,
    Fixed,
    None
}

becomes

typedef enum {
  ScalableType,
  ThresholdType,
  FixedType,
  NoType,
} IkonaDirectoryType;

not much to it, eh?

methods

methods are the most boilerplate-y part of writing the header, but they're fairly easy. it's just keeping track of which rust thing corresponds to which C thing.

this declaration

pub unsafe extern "C" fn ikona_icon_new_from_path(in_path: *mut c_char) -> *mut Icon {

becomes

IkonaIcon ikona_icon_new_from_path(const char* in_path);

C to C++

once a C API is done being written, you can consume it from C++. you can either write a wrapper class to hide the ugly C or consume it directly. here in the KDE world where the wild Qt run free, you can use smart pointers and simple conversion methods to wrangle with the C types.

advantages

the big advantage for Ikona here is the library ecosystem for Rust. librsvg and resvg are both Rust SVG projects that Ikona can utilise, and both are better in many ways compared to the simplistic SVG machinery available from Qt. heck, resvg starts to near browser-grade SVG handling with a huge array of things to do to SVGs as well as general compatibility. Ikona barely taps into the potential of the Rust world currently, but future updates will leverage the boilerplate laid in 1.0 in order to implement new features that take advantage of the vibrant array, high performance, and fast speed of available Rust libraries.

what I would have done differently

writing a bunch of rust to C boilerplate isn't fun, especially with arrays. since glib-rs is already in the dependency chain of Ikona, I should have utilized the GList instead of writing my own list implementation.

tags: #libre

welp, looks like it's finally time to write this :D

so, ikona 1.0 is here and ready to take on the world (of helping icon designers).

some firsts

so, this is a personal first for me. it's the first time I've released a GUI application that I feel like is actually thoroughly polished.

I believe this is also the first KDE application to be released that's predominantly programmed in Rust—I'm aware of rust-qt-binding-generator, but I haven't seen any KDE apps consume it.

the application itself

would be heretical to write a blog post about the 1.0 release of Ikona without talking about what it actually is, ja?

Ikona is a companion application to a vector editor like Inkscape, providing utilities for wrangling with icons and an icon preview.

Ikona's home screen

Ikona opens up to a fairly unassuming screen, giving users two options: the colour palette or the icon view.

before we get to the meat of Ikona, let's look at the colour palette.

Ikona's colour palette.

Ikona's colour palette is fairly simple—it shows a bunch of colours, and clicking them copies the hex code. the colour palette was designed to offer icon designers a vibrant and large array of colours that fit into the Breeze style.

Ikona's preview screen, light Ikona's preview screen, dark

this is where Ikona's meat lies—the application icon view. it displays application icons at a pixel-perfect size in an environment similar to a Plasma desktop.

by default, it just shows Ikona's icon. the real meat is when you press “Create Icon.” this exports a special type of SVG with the suffix .ikona.app.svg.

the .ikona.app.svg is a special type of input SVG that ikona knows how to process. normally, multiple sizes of an icon are stored as different files, making managing all of them cumbersome. however, the .ikona.app.svg combines all sizes of an application's icon into a single file, making it easier to cross-reference elements shared between sizes in the same file. this also allows Ikona to intelligently split and place icons in the correct locations on export.

Ikona can also support regular SVG files, however only one size of icon can be previewed at a time and Ikona cannot export optimized icons from this format.

saving the icon will cause Ikona to instantly update its preview of the icon.

once you're done designing your icon, you use the export screen to export your icon.

Export screen

you can select which sizes to export, and how to export the icon (to one folder with different names, or to folders per size with same name).

you can also take montages of your icon using Ikona. for ease of sharing, the montages are copied directly into your clipboard for pasting into your favourite chat application.

that's it for the GUI application, but not for Ikona.

ikona-cli

Ikona isn't just a GUI application—there's also a fully independent command line interface to its functionality.

 ➜ ikona-cli
ikona-cli 1.0
Carson Black <uhhadd@gmail.com>
Command-line interface to Ikona

USAGE:
    ikona-cli [SUBCOMMAND]

SUBCOMMANDS:
    class       Class your icon
    convert     Convert your icon from light <-> dark
    extract     Extract icons from an Ikona template file
    optimize    Optimize your icon

There are four subcommands: – class — Injects stylesheets and replaces colours with stylesheet colours. – convert — Converts light icons to dark and dark icons to light. – extract — Allows splitting .ikona.app.svg icons into multiple files on the command line. – optimize — Optimizes your icon with a variety of methods. Unlike more commonly used SVG optimizers, Ikona is able to optimize for ease of rendering, reducing the work SVG libraries have to do to render an icon. this translates to faster rendering and better performance, despite a slightly larger file size.

for the next release

for the next release, two features are planned:

­— wrangling with icon themes. icon themes are a pain to deal with, and a tool like Ikona can be scaled to wrangle with hundreds or thousands of icons instead of just a few being designed. — monochromatic icon preview stylesheet injection and classing are perfect for dealing with monochromatic icons, and Ikona will be able to preview them.

for the packagers

yes, rust sucks to deal with.

if your distro mandates that you aren't allowed to bundle dependencies, most of Ikona's dependencies are dependencies of librsvg, a package that most distros should have. this means only a few new packages are needed if they're not already used by other applications.

if your distro is fine with you bundling dependencies, then you're in for happy days. just rename the cargo vendor tarball to ikona.cargo.vendor.tar.xz and plonk it in the source root alongside CMakeLists.txt. CMake will take care of the rest of the job for you.

Tags: #libre