Repository: linebender/resvg (language: rust)

Issue:
Implement a raqote backend
@RazrFalcon have you started on this?


Discussion / hints:
Yeap. Nothing to show yet, since I waiting for https://github.com/jrmuizel/raqote/issues/14

I will try the latest version now.
Great. I'm looking forward to doing some performance and correctness work on raqote and having the ability to get real content will help with that a lot.

Fix patch:
diff --git a/.travis.yml b/.travis.yml
index 2498625ac..6be7327f4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,3 +13,4 @@ env:
   matrix:
     - RESVG_QT_BACKEND=true
     - RESVG_CAIRO_BACKEND=true
+    - RESVG_RAQOTE_BACKEND=true
diff --git a/BUILD.adoc b/BUILD.adoc
index 8ba66a30d..e187b13ad 100755
--- a/BUILD.adoc
+++ b/BUILD.adoc
@@ -3,15 +3,14 @@
 
 = How to build *resvg*
 
-== Into
+== Intro
 
-*resvg* doesn't include a 2D graphics library and uses external ones via
-https://en.wikipedia.org/wiki/Foreign_function_interface[FFI].
+*resvg* doesn't include a 2D graphics library and uses external ones.
 Their support is implemented separately, therefore we call them _backends_.
 You can build them separately or together. +
-At the moment, there are only two backends: *Qt* and *cairo*.
-The first one uses the https://www.qt.io/[Qt framework] and the second one uses the
-https://www.cairographics.org/[cairo] library.
+At the moment, there are only three backends: https://www.qt.io/[Qt],
+https://www.cairographics.org/[cairo] and
+https://github.com/jrmuizel/raqote[raqote] (experimental).
 
 Since *resvg* is a https://www.rust-lang.org/[Rust] library, you should build it via `cargo`. +
 To enable a backend use the `--features` option:
@@ -21,19 +20,25 @@ To enable a backend use the `--features` option:
 cargo build --release --features="qt-backend"
 # or with a cairo backend
 cargo build --release --features="cairo-backend"
-# or with both
-cargo build --release --features="qt-backend cairo-backend"
+# or with a raqote backend
+cargo build --release --features="raqote-backend"
+# or with all
+cargo build --release --features="qt-backend cairo-backend raqote-backend"
 ```
 
 == Dependencies
 
 * The library requires the latest stable
   https://www.rust-lang.org/tools/install[Rust].
+* All backends depend on https://github.com/harfbuzz/harfbuzz[harfbuzz],
+  which will be built automatically by `cargo` and will be linked statically.
+  https://cmake.org/download/[CMake] is required as a build-time dependency.
 * The _Qt backend_ requires only `QtCore` and `QtGui` libraries
   and the JPEG image format plugin (aka `plugins/imageformats/qjpeg`). +
   Technically, any Qt 5 version should work, but we only support Qt >= 5.6.
 * The _cairo backend_ requires https://www.cairographics.org/[cairo] and `gdk-pixbuf` (part of GTK). +
   cairo >= 1.12 and gdk-pixbuf >= 2.30
+* The _raqote backend_ doesn't require any external dependencies.
 * (Linux, runtime) _fontconfig_. Specifically `fc-match`.
 
 == Windows
@@ -46,7 +51,7 @@ Install:
 * https://cmake.org/download/[CMake] (required to build harfbuzz).
 * Qt built with MSVC via an http://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe[official installer].
 
-Build using MSVC `cmd.exe` shell:
+Build using `x64 Native Tools Command Prompt for VS 2017` shell:
 
 ```batch
 set PATH=%userprofile%\.cargo\bin;%PATH%
@@ -89,21 +94,50 @@ pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-cairo mingw-w64-x86_64-gdk-pix
 cargo.exe build --release --features "cairo-backend"
 ```
 
-You can use x86/i686 target in the same way.
+You can use i686 target in the same way.
 
-== Linux
+=== raqote backend via MSVC
 
-For all backends we need `freetype` and `fontconfig` dev libraries.
+Install:
 
-On Ubuntu you can install them via:
+* `stable-x86_64-pc-windows-msvc` https://www.rust-lang.org/tools/install[Rust] target.
+* https://cmake.org/download/[CMake] (required to build harfbuzz).
+
+Build using `x64 Native Tools Command Prompt for VS 2017` shell:
+
+```batch
+set PATH=%userprofile%\.cargo\bin;C:\Program Files\CMake\bin;%PATH%
+
+cargo.exe build --release --features "raqote-backend"
+```
+
+=== raqote backend via MSYS2
+
+Install `stable-x86_64-pc-windows-gnu` https://www.rust-lang.org/tools/install[Rust] target.
+And then:
 
 ```bash
-sudo apt install pkg-config libfreetype6-dev libfontconfig1-dev
+# install harfbuzz dependencies:
+pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake
+
+cargo.exe build --release --features "raqote-backend"
 ```
 
+You can use i686 target in the same way.
+
+== Linux
+
 === Qt backend
 
-Install Qt 5 using your distributive's package manager and then build *resvg*:
+Install Qt 5 and `harfbuzz` using your distributive's package manager.
+
+On Ubuntu you can install them via:
+
+```
+sudo apt install qtbase5-dev libharfbuzz-dev
+```
+
+Build `resvg`:
 
 ```bash
 cargo build --release --features "qt-backend"
@@ -117,12 +151,12 @@ PKG_CONFIG_PATH='/path_to_qt/lib/pkgconfig' cargo build --release --features "qt
 
 === cairo backend
 
-Install `cairo` and `gdk-pixbuf` using your distributive's package manager.
+Install `cairo`, `gdk-pixbuf` and `harfbuzz` using your distributive's package manager.
 
 On Ubuntu you can install them via:
 
 ```
-sudo apt install libcairo2-dev libgdk-pixbuf2.0-dev
+sudo apt install libcairo2-dev libgdk-pixbuf2.0-dev libharfbuzz-dev
 ```
 
 Build `resvg`:
@@ -131,6 +165,22 @@ Build `resvg`:
 cargo build --release --features "cairo-backend"
 ```
 
+=== raqote backend
+
+Install `harfbuzz` using your distributive's package manager.
+
+On Ubuntu you can install it via:
+
+```
+sudo apt install libharfbuzz-dev
+```
+
+Build `resvg`:
+
+```bash
+cargo build --release --features "raqote-backend"
+```
+
 == macOS
 
 === Qt backend
@@ -162,6 +212,12 @@ brew install cairo gdk-pixbuf
 cargo build --release --features "cairo-backend"
 ```
 
+=== raqote backend
+
+```bash
+cargo build --release --features "raqote-backend"
+```
+
 == For maintainers
 
 *resvg* consists of 4 parts:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b1a48e2c..f8928d4f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,8 @@ This changelog also contains important changes in dependencies.
 - (usvg) `--quiet` argument.
 - (c-api) `resvg_get_image_bbox`.
 - (qt-api) `ResvgRenderer::boundingBox`.
+- (resvg) A [raqote](https://github.com/jrmuizel/raqote) backend thanks to
+  [jrmuizel](https://github.com/jrmuizel). Still experimental.
 
 ### Changed
 - Text will be converted into paths on the `usvg` side now.
diff --git a/Cargo.lock b/Cargo.lock
index 35849e8b8..50a444dda 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,6 +21,11 @@ dependencies = [
  "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "autocfg"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "base64"
 version = "0.10.1"
@@ -142,11 +147,39 @@ dependencies = [
  "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "deflate"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "difference"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "euclid"
+version = "0.19.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "euclid_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "euclid_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "fern"
 version = "0.5.8"
@@ -310,6 +343,36 @@ dependencies = [
  "harfbuzz-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "image"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "jpeg-decoder 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "inflate"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "kurbo"
 version = "0.2.3"
@@ -346,6 +409,21 @@ dependencies = [
  "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "lyon_geom"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.19.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lzw"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "matches"
 version = "0.1.8"
@@ -365,6 +443,43 @@ name = "nodrop"
 version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "num-integer"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "output_vt100"
 version = "0.1.2"
@@ -394,6 +509,17 @@ name = "pkg-config"
 version = "0.3.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "png"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "pretty_assertions"
 version = "0.6.1"
@@ -421,6 +547,18 @@ dependencies = [
  "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "raqote"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "euclid 0.19.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lyon_geom 0.12.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sw-composite 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "typed-arena 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "rctree"
 version = "0.3.2"
@@ -448,7 +586,9 @@ version = "0.6.1"
 dependencies = [
  "cairo-rs 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdk-pixbuf 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "image 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "raqote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "resvg-qt 0.6.1",
  "rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)",
  "usvg 0.6.1",
@@ -530,6 +670,11 @@ dependencies = [
  "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "sw-composite"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "syn"
 version = "0.14.9"
@@ -565,6 +710,11 @@ name = "ttf-parser"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "typed-arena"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.4"
@@ -646,6 +796,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
 "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
 "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
+"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf"
 "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
 "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd"
 "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
@@ -661,7 +812,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
 "checksum ctor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3b4c17619643c1252b5f690084b82639dd7fac141c57c8e77a00e0148132092c"
 "checksum data-url 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d33fe99ccedd6e84bc035f1931bb2e6be79739d6242bd895e7311c886c50dc9c"
+"checksum deflate 0.7.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8a6abb26e16e8d419b5c78662aa9f82857c2386a073da266840e474d5055ec86"
 "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+"checksum euclid 0.19.9 (registry+https://github.com/rust-lang/crates.io-index)" = "596b99621b9477e7a5f94d2d8dd13a9c5c302ac358b822c67a42b6f1054450e1"
+"checksum euclid_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcb84c18ea5037a1c5a23039b4ff29403abce2e0d6b1daa11cf0bde2b30be15"
 "checksum fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "29d26fa0f4d433d1956746e66ec10d6bf4d6c8b93cd39965cceea7f7cc78c7dd"
 "checksum float-cmp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17ae3a6394183450225f33d9419cbd627b3cfc831e14f3f1146d5bcaf984e00c"
 "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
@@ -679,21 +833,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum gumdrop_derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04c43663fd6604d9065f5cd26ae7a0301491fd941ad29b0a01665a6be3a1ddbd"
 "checksum harfbuzz-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e1042ab0b3e7bc1ff64f7f5935778b644ff2194a1cae5ec52167127d3fd23961"
 "checksum harfbuzz_rs 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "534c8e9b15d8db6e69654b07dad955f4132757194e7d2bba620d38cf08996088"
+"checksum image 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "99198e595d012efccf12abf4abc08da2d97be0b0355a2b08d101347527476ba4"
+"checksum inflate 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff"
+"checksum jpeg-decoder 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c8b7d43206b34b3f94ea9445174bda196e772049b9bddbc620c9d29b2d20110d"
 "checksum kurbo 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e6076333105a72e8d2c227ba6a6da0dc3c8e5f53f02053f598a6087a1ea8991"
 "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
 "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319"
 "checksum libflate 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)" = "76912aa0196b6f0e06d9c43ee877be45369157c06172ade12fe20ac3ee5ffa15"
 "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
+"checksum lyon_geom 0.12.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0638070e85f0e8b5da3909d8c5c0a66785b18da96a1e8dc30acb0aea9770529a"
+"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
 "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
 "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
 "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
+"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
+"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
+"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454"
+"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
 "checksum output_vt100 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
 "checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
 "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
 "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
+"checksum png 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63daf481fdd0defa2d1d2be15c674fbfa1b0fd71882c303a91f9a79b3252c359"
 "checksum pretty_assertions 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
 "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
 "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db"
+"checksum raqote 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1eb5325b144c13515099d844aea5df836234f64542fdeff41513c1929d2a8e09"
 "checksum rctree 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "193d11923532b462b7231e2d7fb785a9c0d580b03c0eccac460a1e9ee0e87fda"
 "checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252"
 "checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92"
@@ -704,10 +869,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
 "checksum svgdom 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ddce601e49ed213b0126ff4172cd9f5f8dba5f1df2277ecbe0e298f9865baba"
 "checksum svgtypes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "444c882c28925ae0585df228a90f9951569588646ceca4753560de93cdd02258"
+"checksum sw-composite 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5eba1755094da86216f071f7a28b0453345c3e6e558ea2fd7821c55eef8fb9b2"
 "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741"
 "checksum syn 0.15.36 (registry+https://github.com/rust-lang/crates.io-index)" = "8b4f551a91e2e3848aeef8751d0d4eec9489b6474c720fd4c55958d8d31a430c"
 "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
 "checksum ttf-parser 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29637063e570f7e5260924119666113215386c1b94e1d1ceb4525e269dfbe2d0"
+"checksum typed-arena 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c6c06a92aef38bb4dc5b0df00d68496fc31307c5344c867bb61678c6e1671ec5"
 "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
 "checksum unicode-script 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09f03ad95feb4fde244d79985bfd79eb34ff2702fedb441d2ba3f4ff813efd19"
 "checksum unicode-vo 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
diff --git a/Cargo.toml b/Cargo.toml
index ec6e0ddd2..8b7c3f1bf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,9 +35,20 @@ gdk-pixbuf = { version = "0.6", optional = true }
 # qt backend
 resvg-qt = { path = "resvg-qt", version = "0.6", optional = true }
 
+# raqote backend
+raqote = { version = "0.5.1", default-features = false, optional = true }
+
+[dependencies.image]
+version = "0.21"
+default-features = false
+features = ["jpeg", "png_codec"]
+optional = true
+
+
 [features]
 cairo-backend = ["cairo-rs", "gdk-pixbuf"]
 qt-backend = ["resvg-qt"]
+raqote-backend = ["raqote", "image"]
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/capi/Cargo.toml b/capi/Cargo.toml
index 0e97050d3..8740e05d3 100644
--- a/capi/Cargo.toml
+++ b/capi/Cargo.toml
@@ -21,3 +21,4 @@ cairo-sys-rs = { version = "0.8.0", optional = true }
 [features]
 cairo-backend = ["resvg/cairo-backend", "cairo-sys-rs"]
 qt-backend = ["resvg/qt-backend"]
+raqote-backend = ["resvg/raqote-backend"]
diff --git a/capi/include/resvg.h b/capi/include/resvg.h
index 8702978ff..1f1796b67 100644
--- a/capi/include/resvg.h
+++ b/capi/include/resvg.h
@@ -464,4 +464,24 @@ void resvg_qt_render_to_canvas_by_id(const resvg_render_tree *tree,
                                      void *painter);
 #endif /* RESVG_QT_BACKEND */
 
+#ifdef RESVG_RAQOTE_BACKEND
+/**
+ * @brief Renders the #resvg_render_tree to file.
+ *
+ * @param tree Render tree.
+ * @param opt Rendering options.
+ * @param file_path File path.
+ * @return #resvg_error
+ */
+int resvg_raqote_render_to_image(const resvg_render_tree *tree,
+                                 const resvg_options *opt,
+                                 const char *file_path);
+
+/**
+ * Raqote backend doesn't have render_to_canvas and render_to_canvas_by_id
+ * methods since it's a Rust library.
+ */
+
+#endif /* RESVG_RAQOTE_BACKEND */
+
 #endif /* RESVG_H */
diff --git a/capi/src/lib.rs b/capi/src/lib.rs
index 08d5dac71..c0f75f432 100644
--- a/capi/src/lib.rs
+++ b/capi/src/lib.rs
@@ -260,6 +260,17 @@ pub extern "C" fn resvg_cairo_render_to_image(
     render_to_image(tree, opt, file_path, backend)
 }
 
+#[cfg(feature = "raqote-backend")]
+#[no_mangle]
+pub extern "C" fn resvg_raqote_render_to_image(
+    tree: *const resvg_render_tree,
+    opt: *const resvg_options,
+    file_path: *const c_char,
+) -> i32 {
+    let backend = Box::new(resvg::backend_raqote::Backend);
+    render_to_image(tree, opt, file_path, backend)
+}
+
 fn render_to_image(
     tree: *const resvg_render_tree,
     opt: *const resvg_options,
diff --git a/src/backend_raqote/clip_and_mask.rs b/src/backend_raqote/clip_and_mask.rs
new file mode 100644
index 000000000..bb6b255ab
--- /dev/null
+++ b/src/backend_raqote/clip_and_mask.rs
@@ -0,0 +1,166 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use usvg::try_opt;
+
+use crate::{prelude::*, backend_utils::*};
+use super::{path, RaqoteLayers, RaqoteDrawTargetExt};
+
+
+pub fn clip(
+    node: &usvg::Node,
+    cp: &usvg::ClipPath,
+    opt: &Options,
+    bbox: Rect,
+    layers: &mut RaqoteLayers,
+    dt: &mut raqote::DrawTarget,
+) {
+    let clip_dt = try_opt!(layers.get());
+    let mut clip_dt = clip_dt.borrow_mut();
+
+    clip_dt.clear(raqote::SolidSource { r: 0, g: 0, b: 0, a: 255 });
+    clip_dt.set_transform(dt.get_transform());
+    clip_dt.transform(&cp.transform.to_native());
+
+    if cp.units == usvg::Units::ObjectBoundingBox {
+        clip_dt.transform(&usvg::Transform::from_bbox(bbox).to_native());
+    }
+
+    let ts = *clip_dt.get_transform();
+    for node in node.children() {
+        clip_dt.transform(&node.transform().to_native());
+
+        match *node.borrow() {
+            usvg::NodeKind::Path(ref p) => {
+                let draw_opt = raqote::DrawOptions {
+                    blend_mode: raqote::BlendMode::Clear,
+                    ..Default::default()
+                };
+
+                path::draw(&node.tree(), p, opt, draw_opt, &mut clip_dt);
+            }
+            usvg::NodeKind::Group(ref g) => {
+                clip_group(&node, g, opt, bbox, layers, &mut clip_dt);
+            }
+            _ => {}
+        }
+
+        clip_dt.set_transform(&ts);
+    }
+
+    if let Some(ref id) = cp.clip_path {
+        if let Some(ref clip_node) = node.tree().defs_by_id(id) {
+            if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() {
+                clip(clip_node, cp, opt, bbox, layers, dt);
+            }
+        }
+    }
+
+    dt.set_transform(&raqote::Transform::identity());
+    dt.draw_image_at(0.0, 0.0, &clip_dt.as_image(), &raqote::DrawOptions {
+        blend_mode: raqote::BlendMode::DstOut,
+        ..Default::default()
+    });
+}
+
+fn clip_group(
+    node: &usvg::Node,
+    g: &usvg::Group,
+    opt: &Options,
+    bbox: Rect,
+    layers: &mut RaqoteLayers,
+    dt: &mut raqote::DrawTarget,
+) {
+    if let Some(ref id) = g.clip_path {
+        if let Some(ref clip_node) = node.tree().defs_by_id(id) {
+            if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() {
+                // If a `clipPath` child also has a `clip-path`
+                // then we should render this child on a new canvas,
+                // clip it, and only then draw it to the `clipPath`.
+
+                let clip_dt = try_opt!(layers.get());
+                let mut clip_dt = clip_dt.borrow_mut();
+                clip_dt.set_transform(dt.get_transform());
+
+                draw_group_child(&node, opt, raqote::DrawOptions::default(), &mut clip_dt);
+                clip(clip_node, cp, opt, bbox, layers, &mut clip_dt);
+
+                dt.set_transform(&raqote::Transform::identity());
+                dt.draw_image_at(0.0, 0.0, &clip_dt.as_image(), &raqote::DrawOptions {
+                    blend_mode: raqote::BlendMode::Xor,
+                    ..Default::default()
+                });
+            }
+        }
+    }
+}
+
+
+fn draw_group_child(
+    node: &usvg::Node,
+    opt: &Options,
+    draw_options: raqote::DrawOptions,
+    dt: &mut raqote::DrawTarget,
+) {
+    if let Some(child) = node.first_child() {
+        dt.transform(&child.transform().to_native());
+
+        match *child.borrow() {
+            usvg::NodeKind::Path(ref path_node) => {
+                path::draw(&child.tree(), path_node, opt, draw_options, dt);
+            }
+            _ => {}
+        }
+    }
+}
+
+
+pub fn mask(
+    node: &usvg::Node,
+    mask: &usvg::Mask,
+    opt: &Options,
+    bbox: Rect,
+    layers: &mut RaqoteLayers,
+    sub_dt: &mut raqote::DrawTarget,
+) {
+    let mask_dt = try_opt!(layers.get());
+    let mut mask_dt = mask_dt.borrow_mut();
+
+    {
+        mask_dt.set_transform(sub_dt.get_transform());
+
+        let r = if mask.units == usvg::Units::ObjectBoundingBox {
+            mask.rect.bbox_transform(bbox)
+        } else {
+            mask.rect
+        };
+
+        let mut pb = raqote::PathBuilder::new();
+        pb.rect(r.x() as f32, r.y() as f32, r.width() as f32, r.height() as f32);
+        mask_dt.push_clip(&pb.finish());
+
+        if mask.content_units == usvg::Units::ObjectBoundingBox {
+            mask_dt.transform(&usvg::Transform::from_bbox(bbox).to_native());
+        }
+
+        super::render_group(node, opt, layers, &mut mask_dt);
+        mask_dt.pop_clip();
+    }
+
+    image_to_mask(mask_dt.get_data_u8_mut(), layers.image_size());
+
+    if let Some(ref id) = mask.mask {
+        if let Some(ref mask_node) = node.tree().defs_by_id(id) {
+            if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() {
+                self::mask(mask_node, mask, opt, bbox, layers, sub_dt);
+            }
+        }
+    }
+
+    sub_dt.set_transform(&raqote::Transform::identity());
+    sub_dt.draw_image_at(0.0, 0.0, &mask_dt.as_image(), &raqote::DrawOptions {
+        blend_mode: raqote::BlendMode::DstIn,
+        ..Default::default()
+    });
+}
diff --git a/src/backend_raqote/filter.rs b/src/backend_raqote/filter.rs
new file mode 100644
index 000000000..9c93c8181
--- /dev/null
+++ b/src/backend_raqote/filter.rs
@@ -0,0 +1,450 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use std::cmp;
+use std::rc::Rc;
+
+use rgb::FromSlice;
+use log::warn;
+
+use usvg::{try_opt_or, ColorInterpolation as ColorSpace};
+
+use crate::{prelude::*, backend_utils::*};
+use crate::backend_utils::filter::{Error, Filter, ImageExt};
+use super::{ColorExt, RaqoteDrawTargetExt};
+
+type Image = filter::Image<raqote::DrawTarget>;
+type FilterResult = filter::FilterResult<raqote::DrawTarget>;
+
+
+pub fn apply(
+    filter: &usvg::Filter,
+    bbox: Option<Rect>,
+    ts: &usvg::Transform,
+    opt: &Options,
+    canvas: &mut raqote::DrawTarget,
+) {
+    RaqoteFilter::apply(filter, bbox, ts, opt, canvas);
+}
+
+
+impl ImageExt for raqote::DrawTarget {
+    fn width(&self) -> u32 {
+        self.width() as u32
+    }
+
+    fn height(&self) -> u32 {
+        self.height() as u32
+    }
+
+    fn try_clone(&self) -> Result<Self, Error> {
+        let mut dt = raqote::DrawTarget::new(self.width(), self.height());
+        dt.draw_image_at(0.0, 0.0, &self.as_image(), &raqote::DrawOptions {
+            blend_mode: raqote::BlendMode::Src,
+            ..raqote::DrawOptions::default()
+        });
+
+        Ok(dt)
+    }
+
+    fn clip(&mut self, region: ScreenRect) {
+        let mut pb = raqote::PathBuilder::new();
+        pb.rect(0.0, 0.0, self.width() as f32, region.y() as f32);
+        pb.rect(0.0, 0.0, region.x() as f32, self.height() as f32);
+        pb.rect(region.right() as f32, 0.0, self.width() as f32, self.height() as f32);
+        pb.rect(0.0, region.bottom() as f32, self.width() as f32, self.height() as f32);
+
+        self.fill(&pb.finish(), &raqote::Source::Solid(raqote::SolidSource {
+            r: 0,
+            g: 0,
+            b: 0,
+            a: 0,
+        }), &raqote::DrawOptions {
+            blend_mode: raqote::BlendMode::Clear,
+            ..Default::default()
+        });
+    }
+
+    fn clear(&mut self) {
+        self.make_transparent();
+    }
+
+    fn into_srgb(&mut self) {
+        let data =  self.get_data_u8_mut();
+        from_premultiplied(data);
+
+        for p in data.as_bgra_mut() {
+            p.r = filter::LINEAR_RGB_TO_SRGB_TABLE[p.r as usize];
+            p.g = filter::LINEAR_RGB_TO_SRGB_TABLE[p.g as usize];
+            p.b = filter::LINEAR_RGB_TO_SRGB_TABLE[p.b as usize];
+        }
+
+        into_premultiplied(data);
+    }
+
+    fn into_linear_rgb(&mut self) {
+        let data =  self.get_data_u8_mut();
+        from_premultiplied(data);
+
+        for p in data.as_bgra_mut() {
+            p.r = filter::SRGB_TO_LINEAR_RGB_TABLE[p.r as usize];
+            p.g = filter::SRGB_TO_LINEAR_RGB_TABLE[p.g as usize];
+            p.b = filter::SRGB_TO_LINEAR_RGB_TABLE[p.b as usize];
+        }
+
+        into_premultiplied(data);
+    }
+}
+
+fn create_image(width: u32, height: u32) -> Result<raqote::DrawTarget, Error> {
+    Ok(raqote::DrawTarget::new(width as i32, height as i32))
+}
+
+fn copy_image(
+    image: &raqote::DrawTarget,
+    region: ScreenRect,
+) -> Result<raqote::DrawTarget, Error> {
+    let x = cmp::max(0, region.x()) as f32;
+    let y = cmp::max(0, region.y()) as f32;
+
+    let mut new_image = create_image(region.width(), region.height())?;
+
+    new_image.draw_image_at(-x, -y, &image.as_image(), &raqote::DrawOptions {
+        blend_mode: raqote::BlendMode::Src,
+        ..raqote::DrawOptions::default()
+    });
+
+    Ok(new_image)
+}
+
+fn from_premultiplied(data: &mut [u8]) {
+    // https://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t
+
+    for p in data.as_bgra_mut() {
+        let a = p.a as f64 / 255.0;
+        p.b = (p.b as f64 / a + 0.5) as u8;
+        p.g = (p.g as f64 / a + 0.5) as u8;
+        p.r = (p.r as f64 / a + 0.5) as u8;
+    }
+}
+
+fn into_premultiplied(data: &mut [u8]) {
+    // https://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-t
+
+    for p in data.as_bgra_mut() {
+        let a = p.a as f64 / 255.0;
+        p.b = (p.b as f64 * a + 0.5) as u8;
+        p.g = (p.g as f64 * a + 0.5) as u8;
+        p.r = (p.r as f64 * a + 0.5) as u8;
+    }
+}
+
+struct RaqoteFilter;
+
+impl Filter<raqote::DrawTarget> for RaqoteFilter {
+    fn get_input(
+        input: &usvg::FilterInput,
+        region: ScreenRect,
+        results: &[FilterResult],
+        canvas: &raqote::DrawTarget,
+    ) -> Result<Image, Error> {
+        match input {
+            usvg::FilterInput::SourceGraphic => {
+                let image = copy_image(canvas, region)?;
+
+                Ok(Image {
+                    image: Rc::new(image),
+                    region: region.translate_to(0, 0),
+                    color_space: ColorSpace::SRGB,
+                })
+            }
+            usvg::FilterInput::SourceAlpha => {
+                let mut image = copy_image(canvas, region)?;
+
+                // Set RGB to black. Keep alpha as is.
+                let data = image.get_data_u8_mut();
+                for p in data.chunks_mut(4) {
+                    p[0] = 0;
+                    p[1] = 0;
+                    p[2] = 0;
+                }
+
+                Ok(Image {
+                    image: Rc::new(image),
+                    region: region.translate_to(0, 0),
+                    color_space: ColorSpace::SRGB,
+                })
+            }
+            usvg::FilterInput::Reference(ref name) => {
+                if let Some(ref v) = results.iter().rev().find(|v| v.name == *name) {
+                    Ok(v.image.clone())
+                } else {
+                    // Technically unreachable.
+                    warn!("Unknown filter primitive reference '{}'.", name);
+                    Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas)
+                }
+            }
+            _ => {
+                warn!("Filter input '{}' is not supported.", input.to_string());
+                Self::get_input(&usvg::FilterInput::SourceGraphic, region, results, canvas)
+            }
+        }
+    }
+
+    fn apply_blur(
+        fe: &usvg::FeGaussianBlur,
+        units: usvg::Units,
+        cs: ColorSpace,
+        bbox: Option<Rect>,
+        ts: &usvg::Transform,
+        input: Image,
+    ) -> Result<Image, Error> {
+        let (std_dx, std_dy) = try_opt_or!(Self::resolve_std_dev(fe, units, bbox, ts), Ok(input));
+
+        let input = input.into_color_space(cs)?;
+        let mut buffer = input.take()?;
+
+        let (w, h) = (buffer.width() as u32, buffer.height() as u32);
+
+        let data = buffer.get_data_u8_mut();
+        from_premultiplied(data);
+        filter::blur::apply(data, w, h, std_dx, std_dy, 4);
+        into_premultiplied(data);
+
+        Ok(Image::from_image(buffer, cs))
+    }
+
+    fn apply_offset(
+        fe: &usvg::FeOffset,
+        units: usvg::Units,
+        bbox: Option<Rect>,
+        ts: &usvg::Transform,
+        input: Image,
+    ) -> Result<Image, Error> {
+        let (dx, dy) = try_opt_or!(Self::resolve_offset(fe, units, bbox, ts), Ok(input));
+
+        // TODO: do not use an additional buffer
+        let mut dt = create_image(input.width(), input.height())?;
+        dt.draw_image_at(
+            dx as f32, dy as f32, &input.as_ref().as_image(), &raqote::DrawOptions::default(),
+        );
+
+        Ok(Image::from_image(dt, input.color_space))
+    }
+
+    fn apply_blend(
+        fe: &usvg::FeBlend,
+        cs: ColorSpace,
+        region: ScreenRect,
+        input1: Image,
+        input2: Image,
+    ) -> Result<Image, Error> {
+        let input1 = input1.into_color_space(cs)?;
+        let input2 = input2.into_color_space(cs)?;
+
+        let mut dt = create_image(region.width(), region.height())?;
+        let draw_opt = raqote::DrawOptions {
+            blend_mode: raqote::BlendMode::Src,
+            ..raqote::DrawOptions::default()
+        };
+        dt.draw_image_at(0.0, 0.0, &input2.as_ref().as_image(), &draw_opt);
+
+        let blend_mode = match fe.mode {
+            usvg::FeBlendMode::Normal => raqote::BlendMode::SrcOver,
+            usvg::FeBlendMode::Multiply => raqote::BlendMode::Multiply,
+            usvg::FeBlendMode::Screen => raqote::BlendMode::Screen,
+            usvg::FeBlendMode::Darken => raqote::BlendMode::Darken,
+            usvg::FeBlendMode::Lighten => raqote::BlendMode::Lighten,
+        };
+
+        let draw_opt = raqote::DrawOptions { blend_mode, ..raqote::DrawOptions::default() };
+        dt.draw_image_at(0.0, 0.0, &input1.as_ref().as_image(), &draw_opt);
+
+        Ok(Image::from_image(dt, cs))
+    }
+
+    fn apply_composite(
+        fe: &usvg::FeComposite,
+        cs: ColorSpace,
+        region: ScreenRect,
+        input1: Image,
+        input2: Image,
+    ) -> Result<Image, Error> {
+        use rgb::alt::BGRA8;
+
+        let mut input1 = input1.into_color_space(cs)?.take()?;
+        let mut input2 = input2.into_color_space(cs)?.take()?;
+
+        let mut dt = create_image(region.width(), region.height())?;
+
+        if let Operator::Arithmetic { k1, k2, k3, k4 } = fe.operator {
+            let data1 = input1.get_data_u8_mut();
+            let data2 = input2.get_data_u8_mut();
+
+            let calc = |i1, i2, max| {
+                let i1 = i1 as f64 / 255.0;
+                let i2 = i2 as f64 / 255.0;
+                let result = k1.value() * i1 * i2 + k2.value() * i1 + k3.value() * i2 + k4.value();
+                f64_bound(0.0, result, max)
+            };
+
+            {
+                let mut i = 0;
+                let data3 = dt.get_data_u8_mut();
+                let data3 = data3.as_bgra_mut();
+                for (c1, c2) in data1.as_bgra().iter().zip(data2.as_bgra()) {
+                    let a = calc(c1.a, c2.a, 1.0);
+                    if a.is_fuzzy_zero() {
+                        continue;
+                    }
+
+                    let r = (calc(c1.r, c2.r, a) * 255.0) as u8;
+                    let g = (calc(c1.g, c2.g, a) * 255.0) as u8;
+                    let b = (calc(c1.b, c2.b, a) * 255.0) as u8;
+                    let a = (a * 255.0) as u8;
+
+                    data3[i] = BGRA8 { r, g, b, a };
+
+                    i += 1;
+                }
+            }
+
+            return Ok(Image::from_image(dt, cs));
+        }
+
+        let draw_opt = raqote::DrawOptions {
+            blend_mode: raqote::BlendMode::Src,
+            ..raqote::DrawOptions::default()
+        };
+        dt.draw_image_at(0.0, 0.0, &input2.as_image(), &draw_opt);
+
+        use usvg::FeCompositeOperator as Operator;
+        let blend_mode = match fe.operator {
+            Operator::Over => raqote::BlendMode::SrcOver,
+            Operator::In => raqote::BlendMode::SrcIn,
+            Operator::Out => raqote::BlendMode::SrcOut,
+            Operator::Atop => raqote::BlendMode::SrcAtop,
+            Operator::Xor => raqote::BlendMode::Xor,
+            Operator::Arithmetic { .. } => raqote::BlendMode::SrcOver,
+        };
+
+        let draw_opt = raqote::DrawOptions { blend_mode, ..raqote::DrawOptions::default() };
+        dt.draw_image_at(0.0, 0.0, &input1.as_image(), &draw_opt);
+
+        Ok(Image::from_image(dt, cs))
+    }
+
+    fn apply_merge(
+        fe: &usvg::FeMerge,
+        cs: ColorSpace,
+        region: ScreenRect,
+        results: &[FilterResult],
+        canvas: &raqote::DrawTarget,
+    ) -> Result<Image, Error> {
+        let mut dt = create_image(region.width(), region.height())?;
+
+        for input in &fe.inputs {
+            let input = Self::get_input(input, region, &results, canvas)?;
+            let input = input.into_color_space(cs)?;
+            dt.draw_image_at(0.0, 0.0, &input.as_ref().as_image(), &raqote::DrawOptions::default());
+        }
+
+        Ok(Image::from_image(dt, cs))
+    }
+
+    fn apply_flood(
+        fe: &usvg::FeFlood,
+        region: ScreenRect,
+    ) -> Result<Image, Error> {
+        let mut dt = create_image(region.width(), region.height())?;
+
+        let alpha = (fe.opacity.value() * 255.0) as u8;
+        dt.clear(fe.color.to_solid(alpha));
+
+        Ok(Image::from_image(dt, ColorSpace::SRGB))
+    }
+
+    fn apply_tile(
+        input: Image,
+        region: ScreenRect,
+    ) -> Result<Image, Error> {
+        let mut dt = create_image(region.width(), region.height())?;
+
+        let subregion = input.region.translate(-region.x(), -region.y());
+
+        let tile = copy_image(&input.image, subregion)?;
+        let brush_ts = usvg::Transform::new_translate(subregion.x() as f64, subregion.y() as f64);
+
+        let ts: raqote::Transform = brush_ts.to_native();
+        let ts = ts.inverse().unwrap();
+        let patt = raqote::Source::Image(
+            tile.as_image(),
+            raqote::ExtendMode::Repeat,
+            raqote::FilterMode::Bilinear,
+            ts,
+        );
+
+        let mut pb = raqote::PathBuilder::new();
+        pb.rect(0.0, 0.0, region.width() as f32, region.height() as f32);
+        dt.fill(&pb.finish(), &patt, &raqote::DrawOptions::default());
+
+        dt.set_transform(&raqote::Transform::default());
+        Ok(Image::from_image(dt, ColorSpace::SRGB))
+    }
+
+    fn apply_image(
+        fe: &usvg::FeImage,
+        region: ScreenRect,
+        subregion: ScreenRect,
+        opt: &Options,
+    ) -> Result<Image, Error> {
+        let mut dt = create_image(region.width(), region.height())?;
+
+        match fe.data {
+            usvg::FeImageKind::None => {}
+            usvg::FeImageKind::Image(ref data, format) => {
+                let dx = (subregion.x() - region.x()) as f64;
+                let dy = (subregion.y() - region.y()) as f64;
+                let ctm = dt.get_transform().pre_translate(raqote::Vector::new(dx as f32, dy as f32));
+                dt.set_transform(&ctm);
+
+                let view_box = usvg::ViewBox {
+                    rect: subregion.translate_to(0, 0).to_rect(),
+                    aspect: fe.aspect,
+                };
+
+                if format == usvg::ImageFormat::SVG {
+                    super::image::draw_svg(data, view_box, opt, &mut dt);
+                } else {
+                    super::image::draw_raster(data, view_box, fe.rendering_mode, opt, &mut dt);
+                }
+            }
+            usvg::FeImageKind::Use(..) => {}
+        }
+
+        dt.set_transform(&raqote::Transform::default());
+        Ok(Image::from_image(dt, ColorSpace::SRGB))
+    }
+
+    fn apply_to_canvas(
+        input: Image,
+        region: ScreenRect,
+        canvas: &mut raqote::DrawTarget,
+    ) -> Result<(), Error> {
+        let input = input.into_color_space(ColorSpace::SRGB)?;
+
+        canvas.set_transform(&raqote::Transform::identity());
+        canvas.make_transparent();
+
+        let draw_opt = raqote::DrawOptions {
+            blend_mode: raqote::BlendMode::SrcOver,
+            ..raqote::DrawOptions::default()
+        };
+        canvas.draw_image_at(
+            region.x() as f32, region.y() as f32, &input.as_ref().as_image(), &draw_opt,
+        );
+
+        Ok(())
+    }
+}
diff --git a/src/backend_raqote/image.rs b/src/backend_raqote/image.rs
new file mode 100644
index 000000000..eae91d53e
--- /dev/null
+++ b/src/backend_raqote/image.rs
@@ -0,0 +1,150 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use image::GenericImageView;
+use usvg::{try_opt, try_opt_warn};
+
+use crate::{prelude::*, backend_utils, backend_utils::ConvTransform};
+use super::RaqoteDrawTargetExt;
+
+
+pub fn draw(
+    image: &usvg::Image,
+    opt: &Options,
+    dt: &mut raqote::DrawTarget,
+) -> Rect {
+    if image.visibility != usvg::Visibility::Visible {
+        return image.view_box.rect;
+    }
+
+    if image.format == usvg::ImageFormat::SVG {
+        draw_svg(&image.data, image.view_box, opt, dt);
+    } else {
+        draw_raster(&image.data, image.view_box, image.rendering_mode, opt, dt);
+    }
+
+    image.view_box.rect
+}
+
+pub fn draw_raster(
+    data: &usvg::ImageData,
+    view_box: usvg::ViewBox,
+    rendering_mode: usvg::ImageRendering,
+    opt: &Options,
+    dt: &mut raqote::DrawTarget,
+) {
+    let img = match data {
+        usvg::ImageData::Path(ref path) => {
+            let path = backend_utils::image::get_abs_path(path, opt);
+            try_opt_warn!(
+                image::open(path.clone()).ok(),
+                "Failed to load an external image: {:?}.", path
+            )
+        }
+        usvg::ImageData::Raw(ref data) => {
+            match image::load_from_memory(data) {
+                Ok(img) => img,
+                Err(e) => {
+                    log::warn!("{}", e.to_string());
+                    return;
+                }
+            }
+        }
+    };
+
+    let img_size = img.dimensions();
+    let img_size = ScreenSize::new(img_size.0, img_size.1);
+    let img_size = try_opt!(img_size);
+
+    let sub_dt = try_opt!(image_to_surface(img, img_size));
+
+    let (ts, clip) = backend_utils::image::prepare_sub_svg_geom(view_box, img_size);
+
+    let mut pb = raqote::PathBuilder::new();
+    if let Some(clip) = clip {
+        pb.rect(clip.x() as f32, clip.y() as f32, clip.width() as f32, clip.height() as f32);
+    } else {
+        // We have to clip the image before rendering because we use `Extend::Pad`.
+        let r = backend_utils::image::image_rect(&view_box, img_size);
+        pb.rect(r.x() as f32, r.y() as f32, r.width() as f32, r.height() as f32);
+    }
+
+    let filter_mode = if rendering_mode == usvg::ImageRendering::OptimizeSpeed {
+        raqote::FilterMode::Nearest
+    } else {
+        raqote::FilterMode::Bilinear
+    };
+
+    let t: raqote::Transform = ts.to_native();
+    let patt = raqote::Source::Image(
+        sub_dt.as_image(),
+        raqote::ExtendMode::Pad,
+        filter_mode,
+        t.inverse().unwrap(),
+    );
+
+    dt.fill(&pb.finish(), &patt, &raqote::DrawOptions::default());
+}
+
+fn image_to_surface(
+    img: image::DynamicImage,
+    img_size: ScreenSize,
+) -> Option<raqote::DrawTarget> {
+    let mut surface = raqote::DrawTarget::new(img_size.width() as i32, img_size.height() as i32);
+    let surface_data = surface.get_data_u8_mut();
+
+    let w = img.dimensions().0 as u32;
+    let h = img.dimensions().1 as u32;
+    let pixels = img.to_rgba();
+    // We can't iterate over pixels directly, because width may not be equal to stride.
+    let mut i = 0;
+    for y in 0..h {
+        for x in 0..w {
+            let pixel = pixels[(x,y)];
+            let r = pixel[0] as u32;
+            let g = pixel[1] as u32;
+            let b = pixel[2] as u32;
+            let a = pixel[3] as u32;
+
+            let tr = a * r + 0x80;
+            let tg = a * g + 0x80;
+            let tb = a * b + 0x80;
+            surface_data[i + 0] = (((tb >> 8) + tb) >> 8) as u8;
+            surface_data[i + 1] = (((tg >> 8) + tg) >> 8) as u8;
+            surface_data[i + 2] = (((tr >> 8) + tr) >> 8) as u8;
+            surface_data[i + 3] = a as u8; // TODO: is needed?
+
+            // Surface is always ARGB.
+            i += 4;
+        }
+    }
+
+    Some(surface)
+}
+
+pub fn draw_svg(
+    data: &usvg::ImageData,
+    view_box: usvg::ViewBox,
+    opt: &Options,
+    dt: &mut raqote::DrawTarget,
+) {
+    let (tree, sub_opt) = try_opt!(backend_utils::image::load_sub_svg(data, opt));
+
+    let img_size = tree.svg_node().size.to_screen_size();
+    let (ts, clip) = backend_utils::image::prepare_sub_svg_geom(view_box, img_size);
+
+    if let Some(clip) = clip {
+        let mut pb = raqote::PathBuilder::new();
+
+        pb.rect(clip.x() as f32, clip.y() as f32, clip.width() as f32, clip.height() as f32);
+        dt.push_clip(&pb.finish());
+    }
+
+    dt.transform(&ts.to_native());
+    super::render_to_canvas(&tree, &sub_opt, img_size, dt);
+
+    if let Some(_) = clip {
+        dt.pop_clip();
+    }
+}
diff --git a/src/backend_raqote/mod.rs b/src/backend_raqote/mod.rs
new file mode 100644
index 000000000..31f003160
--- /dev/null
+++ b/src/backend_raqote/mod.rs
@@ -0,0 +1,354 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+//! Raqote backend implementation.
+
+use log::warn;
+
+use crate::{prelude::*, layers, backend_utils::ConvTransform};
+
+mod clip_and_mask;
+mod filter;
+mod image;
+mod path;
+mod style;
+
+
+type RaqoteLayers = layers::Layers<raqote::DrawTarget>;
+
+
+impl ConvTransform<raqote::Transform> for usvg::Transform {
+    fn to_native(&self) -> raqote::Transform {
+        raqote::Transform::row_major(self.a as f32, self.b as f32, self.c as f32,
+                                     self.d as f32, self.e as f32, self.f as f32)
+    }
+
+    fn from_native(ts: &raqote::Transform) -> Self {
+        Self::new(ts.m11 as f64, ts.m12 as f64, ts.m21 as f64,
+                  ts.m22 as f64, ts.m31 as f64, ts.m32 as f64)
+    }
+}
+
+
+pub(crate) trait RaqoteDrawTargetExt {
+    fn transform(&mut self, ts: &raqote::Transform);
+    fn as_image(&self) -> raqote::Image;
+    fn make_transparent(&mut self);
+}
+
+impl RaqoteDrawTargetExt for raqote::DrawTarget {
+    fn transform(&mut self, ts: &raqote::Transform) {
+        self.set_transform(&self.get_transform().pre_mul(ts));
+    }
+
+    fn as_image(&self) -> raqote::Image {
+        raqote::Image {
+            width: self.width() as i32,
+            height: self.height() as i32,
+            data: self.get_data(),
+        }
+    }
+
+    fn make_transparent(&mut self) {
+        // This is faster than DrawTarget::clear.
+        for i in self.get_data_u8_mut() {
+            *i = 0;
+        }
+    }
+}
+
+pub(crate) trait ColorExt {
+    fn to_solid(&self, a: u8) -> raqote::SolidSource;
+    fn to_u32(&self, a: u8) -> u32;
+}
+
+impl ColorExt for usvg::Color {
+    fn to_solid(&self, a: u8) -> raqote::SolidSource {
+        raqote::SolidSource {
+            r: premultiply(self.red, a),
+            g: premultiply(self.green, a),
+            b: premultiply(self.blue, a),
+            a,
+        }
+    }
+
+    fn to_u32(&self, a: u8) -> u32 {
+        let r = self.red as u32;
+        let g = self.green as u32;
+        let b = self.blue as u32;
+
+        ((a as u32 & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff)
+    }
+}
+
+fn premultiply(c: u8, a: u8) -> u8 {
+    let c = a as u32 * c as u32 + 0x80;
+    (((c >> 8) + c) >> 8) as u8
+}
+
+
+/// Raqote backend handle.
+#[derive(Clone, Copy)]
+pub struct Backend;
+
+impl Render for Backend {
+    fn render_to_image(
+        &self,
+        tree: &usvg::Tree,
+        opt: &Options,
+    ) -> Option<Box<OutputImage>> {
+        let img = render_to_image(tree, opt)?;
+        Some(Box::new(img))
+    }
+
+    fn render_node_to_image(
+        &self,
+        node: &usvg::Node,
+        opt: &Options,
+    ) -> Option<Box<OutputImage>> {
+        let img = render_node_to_image(node, opt)?;
+        Some(Box::new(img))
+    }
+}
+
+impl OutputImage for raqote::DrawTarget {
+    fn save(&self, path: &::std::path::Path) -> bool {
+        self.write_png(path).is_ok()
+    }
+}
+
+
+/// Renders SVG to image.
+pub fn render_to_image(
+    tree: &usvg::Tree,
+    opt: &Options,
+) -> Option<raqote::DrawTarget> {
+    let (mut dt, img_view) = create_target(
+        tree.svg_node().size.to_screen_size(),
+        opt,
+    )?;
+
+    // Fill background.
+    if let Some(c) = opt.background {
+        dt.clear(raqote::SolidSource { r: c.red, g: c.green, b: c.blue, a: 255 });
+    }
+
+    render_to_canvas(tree, opt, img_view, &mut dt);
+
+    Some(dt)
+}
+
+/// Renders SVG to image.
+pub fn render_node_to_image(
+    node: &usvg::Node,
+    opt: &Options,
+) -> Option<raqote::DrawTarget> {
+    let node_bbox = if let Some(bbox) = node.calculate_bbox() {
+        bbox
+    } else {
+        warn!("Node '{}' has a zero size.", node.id());
+        return None;
+    };
+
+    let (mut dt, img_size) = create_target(node_bbox.to_screen_size(), opt)?;
+
+    let vbox = usvg::ViewBox {
+        rect: node_bbox,
+        aspect: usvg::AspectRatio::default(),
+    };
+
+    // Fill background.
+    if let Some(c) = opt.background {
+        dt.clear(raqote::SolidSource { r: c.red, g: c.green, b: c.blue, a: 255 });
+    }
+
+    render_node_to_canvas(node, opt, vbox, img_size, &mut dt);
+
+    Some(dt)
+}
+
+/// Renders SVG to canvas.
+pub fn render_to_canvas(
+    tree: &usvg::Tree,
+    opt: &Options,
+    img_size: ScreenSize,
+    dt: &mut raqote::DrawTarget,
+) {
+    render_node_to_canvas(&tree.root(), opt, tree.svg_node().view_box, img_size, dt);
+}
+
+/// Renders SVG node to canvas.
+pub fn render_node_to_canvas(
+    node: &usvg::Node,
+    opt: &Options,
+    view_box: usvg::ViewBox,
+    img_size: ScreenSize,
+    dt: &mut raqote::DrawTarget,
+) {
+    let mut layers = create_layers(img_size);
+
+    apply_viewbox_transform(view_box, img_size, dt);
+
+    let curr_ts = *dt.get_transform();
+    let mut ts = node.abs_transform();
+    ts.append(&node.transform());
+
+    dt.transform(&ts.to_native());
+    render_node(node, opt, &mut layers, dt);
+    dt.set_transform(&curr_ts);
+}
+
+fn create_target(
+    size: ScreenSize,
+    opt: &Options,
+) -> Option<(raqote::DrawTarget, ScreenSize)> {
+    let img_size = utils::fit_to(size, opt.fit_to)?;
+
+    let dt = raqote::DrawTarget::new(img_size.width() as i32, img_size.height() as i32);
+
+    Some((dt, img_size))
+}
+
+/// Applies viewbox transformation to the painter.
+fn apply_viewbox_transform(
+    view_box: usvg::ViewBox,
+    img_size: ScreenSize,
+    dt: &mut raqote::DrawTarget,
+) {
+    let ts = utils::view_box_to_transform(view_box.rect, view_box.aspect, img_size.to_size());
+    dt.transform(&ts.to_native());
+}
+
+fn render_node(
+    node: &usvg::Node,
+    opt: &Options,
+    layers: &mut RaqoteLayers,
+    dt: &mut raqote::DrawTarget,
+) -> Option<Rect> {
+    match *node.borrow() {
+        usvg::NodeKind::Svg(_) => {
+            render_group(node, opt, layers, dt)
+        }
+        usvg::NodeKind::Path(ref path) => {
+            path::draw(&node.tree(), path, opt, raqote::DrawOptions::default(), dt)
+        }
+        usvg::NodeKind::Image(ref img) => {
+            Some(image::draw(img, opt, dt))
+        }
+        usvg::NodeKind::Group(ref g) => {
+            render_group_impl(node, g, opt, layers, dt)
+        }
+        _ => None,
+    }
+}
+
+fn render_group(
+    parent: &usvg::Node,
+    opt: &Options,
+    layers: &mut RaqoteLayers,
+    dt: &mut raqote::DrawTarget,
+) -> Option<Rect> {
+    let curr_ts = *dt.get_transform();
+    let mut g_bbox = Rect::new_bbox();
+
+    for node in parent.children() {
+        dt.transform(&node.transform().to_native());
+
+        let bbox = render_node(&node, opt, layers, dt);
+
+        if let Some(bbox) = bbox {
+            let bbox = bbox.transform(&node.transform()).unwrap();
+            g_bbox = g_bbox.expand(bbox);
+        }
+
+        // Revert transform.
+        dt.set_transform(&curr_ts);
+    }
+
+    // Check that bbox was changed, otherwise we will have a rect with x/y set to f64::MAX.
+    if g_bbox.fuzzy_ne(&Rect::new_bbox()) {
+        Some(g_bbox)
+    } else {
+        None
+    }
+}
+
+fn render_group_impl(
+    node: &usvg::Node,
+    g: &usvg::Group,
+    opt: &Options,
+    layers: &mut RaqoteLayers,
+    dt: &mut raqote::DrawTarget,
+) -> Option<Rect> {
+    let sub_dt = layers.get()?;
+    let mut sub_dt = sub_dt.borrow_mut();
+
+    let curr_ts = *dt.get_transform();
+
+    let bbox = {
+        sub_dt.set_transform(&curr_ts);
+        render_group(node, opt, layers, &mut sub_dt)
+    };
+
+    // Filter can be rendered on an object without a bbox,
+    // as long as filter uses `userSpaceOnUse`.
+    if let Some(ref id) = g.filter {
+        if let Some(filter_node) = node.tree().defs_by_id(id) {
+            if let usvg::NodeKind::Filter(ref filter) = *filter_node.borrow() {
+                let ts = usvg::Transform::from_native(&curr_ts);
+                filter::apply(filter, bbox, &ts, opt, &mut sub_dt);
+            }
+        }
+    }
+
+    // Clipping and masking can be done only for objects with a valid bbox.
+    if let Some(bbox) = bbox {
+        if let Some(ref id) = g.clip_path {
+            if let Some(clip_node) = node.tree().defs_by_id(id) {
+                if let usvg::NodeKind::ClipPath(ref cp) = *clip_node.borrow() {
+                    sub_dt.set_transform(&curr_ts);
+
+                    clip_and_mask::clip(&clip_node, cp, opt, bbox, layers, &mut sub_dt);
+                }
+            }
+        }
+
+        if let Some(ref id) = g.mask {
+            if let Some(mask_node) = node.tree().defs_by_id(id) {
+                if let usvg::NodeKind::Mask(ref mask) = *mask_node.borrow() {
+                    sub_dt.set_transform(&curr_ts);
+
+                    clip_and_mask::mask(&mask_node, mask, opt, bbox, layers, &mut sub_dt);
+                }
+            }
+        }
+    }
+
+    dt.set_transform(&raqote::Transform::default());
+
+    dt.draw_image_at(0.0, 0.0, &sub_dt.as_image(), &raqote::DrawOptions {
+        blend_mode: raqote::BlendMode::SrcOver,
+        alpha: g.opacity.value() as f32,
+        antialias: raqote::AntialiasMode::Gray,
+    });
+
+    dt.set_transform(&curr_ts);
+
+    bbox
+}
+
+fn create_layers(img_size: ScreenSize) -> RaqoteLayers {
+    layers::Layers::new(img_size, create_subsurface, clear_subsurface)
+}
+
+fn create_subsurface(
+    size: ScreenSize,
+) -> Option<raqote::DrawTarget> {
+    Some(raqote::DrawTarget::new(size.width() as i32, size.height() as i32))
+}
+
+fn clear_subsurface(dt: &mut raqote::DrawTarget) {
+    dt.set_transform(&raqote::Transform::identity());
+    dt.make_transparent();
+}
diff --git a/src/backend_raqote/path.rs b/src/backend_raqote/path.rs
new file mode 100644
index 000000000..dbbfa1632
--- /dev/null
+++ b/src/backend_raqote/path.rs
@@ -0,0 +1,133 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use crate::{prelude::*, backend_utils};
+use super::style;
+
+
+pub fn draw(
+    tree: &usvg::Tree,
+    path: &usvg::Path,
+    opt: &Options,
+    draw_opt: raqote::DrawOptions,
+    dt: &mut raqote::DrawTarget,
+) -> Option<Rect> {
+    let mut is_butt_cap = true;
+    if let Some(ref stroke) = path.stroke {
+        is_butt_cap = stroke.linecap == usvg::LineCap::Butt;
+    }
+
+    let mut segments = conv_path(&path.segments, is_butt_cap);
+
+    let bbox = utils::path_bbox(&path.segments, None, None);
+
+    // `usvg` guaranties that path without a bbox will not use
+    // a paint server with ObjectBoundingBox,
+    // so we can pass whatever rect we want, because it will not be used anyway.
+    let style_bbox = bbox.unwrap_or_else(|| Rect::new(0.0, 0.0, 1.0, 1.0).unwrap());
+
+    if path.visibility != usvg::Visibility::Visible {
+        return bbox;
+    }
+
+    if let Some(ref fill) = path.fill {
+        match fill.rule {
+            usvg::FillRule::NonZero => segments.winding = raqote::Winding::NonZero,
+            usvg::FillRule::EvenOdd => segments.winding = raqote::Winding::EvenOdd,
+        }
+    }
+
+    let mut draw_opt = draw_opt.clone();
+    if !backend_utils::use_shape_antialiasing(path.rendering_mode) {
+        draw_opt.antialias = raqote::AntialiasMode::None;
+    }
+
+    style::fill(tree, &segments, &path.fill, opt, style_bbox, &draw_opt, dt);
+    style::stroke(tree, &segments, &path.stroke, opt, style_bbox, &draw_opt, dt);
+
+    bbox
+}
+
+fn conv_path(
+    segments: &[usvg::PathSegment],
+    is_butt_cap: bool,
+) -> raqote::Path {
+    let mut pb = raqote::PathBuilder::new();
+
+    let mut i = 0;
+    loop {
+        let subpath = get_subpath(i, segments);
+        if subpath.is_empty() {
+            break;
+        }
+
+        conv_subpath(subpath, is_butt_cap, &mut pb);
+        i += subpath.len();
+    }
+
+    pb.finish()
+}
+
+fn get_subpath(
+    start: usize,
+    segments: &[usvg::PathSegment],
+) -> &[usvg::PathSegment] {
+    let mut i = start;
+    while i < segments.len() {
+        match segments[i] {
+            usvg::PathSegment::MoveTo { .. } => {
+                if i != start {
+                    break;
+                }
+            }
+            usvg::PathSegment::ClosePath => {
+                i += 1;
+                break;
+            }
+            _ => {}
+        }
+
+        i += 1;
+    }
+
+    &segments[start..i]
+}
+
+fn conv_subpath(
+    segments: &[usvg::PathSegment],
+    is_butt_cap: bool,
+    pb: &mut raqote::PathBuilder,
+) {
+    assert_ne!(segments.len(), 0);
+
+    // Raqote doesn't support line caps on zero-length subpaths,
+    // so we have to implement them manually.
+    let is_zero_path = !is_butt_cap && utils::path_length(segments).is_fuzzy_zero();
+
+    if !is_zero_path {
+        for seg in segments {
+            match *seg {
+                usvg::PathSegment::MoveTo { x, y } => {
+                    pb.move_to(x as f32, y as f32);
+                }
+                usvg::PathSegment::LineTo { x, y } => {
+                    pb.line_to(x as f32, y as f32);
+                }
+                usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y } => {
+                    pb.cubic_to(x1 as f32, y1 as f32, x2 as f32, y2 as f32, x as f32, y as f32);
+                }
+                usvg::PathSegment::ClosePath => {
+                    pb.close();
+                }
+            }
+        }
+    } else {
+        if let usvg::PathSegment::MoveTo { x, y } = segments[0] {
+            // Draw zero length path.
+            let shift = 0.002; // Purely empirical.
+            pb.move_to(x as f32, y as f32);
+            pb.line_to(x as f32 + shift, y as f32);
+        }
+    }
+}
diff --git a/src/backend_raqote/style.rs b/src/backend_raqote/style.rs
new file mode 100644
index 000000000..ea97d11ed
--- /dev/null
+++ b/src/backend_raqote/style.rs
@@ -0,0 +1,313 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use usvg::try_opt;
+
+use crate::{prelude::*, backend_utils::ConvTransform};
+use super::{ColorExt, RaqoteDrawTargetExt};
+
+
+pub fn fill(
+    tree: &usvg::Tree,
+    path: &raqote::Path,
+    fill: &Option<usvg::Fill>,
+    opt: &Options,
+    bbox: Rect,
+    draw_opt: &raqote::DrawOptions,
+    dt: &mut raqote::DrawTarget,
+) {
+    if let Some(ref fill) = fill {
+        let patt_dt;
+        let source = match fill.paint {
+            usvg::Paint::Color(c) => {
+                let alpha = (fill.opacity.value() * 255.0) as u8;
+                raqote::Source::Solid(c.to_solid(alpha))
+            }
+            usvg::Paint::Link(ref id) => {
+                if let Some(node) = tree.defs_by_id(id) {
+                    match *node.borrow() {
+                        usvg::NodeKind::LinearGradient(ref lg) => {
+                            prepare_linear(lg, fill.opacity, bbox)
+                        }
+                        usvg::NodeKind::RadialGradient(ref rg) => {
+                            prepare_radial(rg, fill.opacity, bbox)
+                        }
+                        usvg::NodeKind::Pattern(ref pattern) => {
+                            let ts = *dt.get_transform();
+                            let (sub_dt, patt_ts) = try_opt!(
+                                prepare_pattern(&node, pattern, opt, ts, bbox, fill.opacity)
+                            );
+                            patt_dt = sub_dt;
+                            create_pattern_image(&patt_dt, patt_ts)
+                        }
+                        _ => {
+                            return;
+                        }
+                    }
+                } else {
+                    return;
+                }
+            }
+        };
+
+        dt.fill(
+            path,
+            &source,
+            draw_opt,
+        );
+    }
+}
+
+pub fn stroke(
+    tree: &usvg::Tree,
+    path: &raqote::Path,
+    stroke: &Option<usvg::Stroke>,
+    opt: &Options,
+    bbox: Rect,
+    draw_opt: &raqote::DrawOptions,
+    dt: &mut raqote::DrawTarget,
+) {
+    if let Some(ref stroke) = stroke {
+        let cap = match stroke.linecap {
+            usvg::LineCap::Butt => raqote::LineCap::Butt,
+            usvg::LineCap::Round => raqote::LineCap::Round,
+            usvg::LineCap::Square => raqote::LineCap::Square,
+        };
+
+        let join = match stroke.linejoin {
+            usvg::LineJoin::Miter => raqote::LineJoin::Miter,
+            usvg::LineJoin::Round => raqote::LineJoin::Round,
+            usvg::LineJoin::Bevel => raqote::LineJoin::Bevel,
+        };
+
+        let mut dash_array = Vec::new();
+        if let Some(ref list) = stroke.dasharray {
+            dash_array = list.iter().map(|n| *n as f32).collect();
+        }
+
+        let style = raqote::StrokeStyle {
+            cap,
+            join,
+            width: stroke.width.value() as f32,
+            miter_limit: stroke.miterlimit.value() as f32,
+            dash_array,
+            dash_offset: stroke.dashoffset,
+        };
+
+        let patt_dt;
+        let source = match stroke.paint {
+            usvg::Paint::Color(c) => {
+                let alpha = (stroke.opacity.value() * 255.0) as u8;
+                raqote::Source::Solid(c.to_solid(alpha))
+            }
+            usvg::Paint::Link(ref id) => {
+                if let Some(node) = tree.defs_by_id(id) {
+                    match *node.borrow() {
+                        usvg::NodeKind::LinearGradient(ref lg) => {
+                            prepare_linear(lg, stroke.opacity, bbox)
+                        }
+                        usvg::NodeKind::RadialGradient(ref rg) => {
+                            prepare_radial(rg, stroke.opacity, bbox)
+                        }
+                        usvg::NodeKind::Pattern(ref pattern) => {
+                            let ts = *dt.get_transform();
+                            let (sub_dt, patt_ts) = try_opt!(
+                                prepare_pattern(&node, pattern, opt, ts, bbox, stroke.opacity)
+                            );
+                            patt_dt = sub_dt;
+                            create_pattern_image(&patt_dt, patt_ts)
+                        }
+                        _ => {
+                            return;
+                        }
+                    }
+                } else {
+                    return;
+                }
+            }
+        };
+
+        dt.stroke(
+            &path,
+            &source,
+            &style,
+            draw_opt,
+        );
+    }
+}
+
+fn prepare_linear<'a>(
+    g: &usvg::LinearGradient,
+    opacity: usvg::Opacity,
+    bbox: Rect,
+) -> raqote::Source<'a> {
+    let ts = if g.units == usvg::Units::ObjectBoundingBox {
+        let mut ts = usvg::Transform::from_bbox(bbox);
+        ts.append(&g.transform);
+        ts
+    } else {
+        g.transform
+    };
+
+    let mut grad = raqote::Source::new_linear_gradient(
+        raqote::Gradient { stops: conv_stops(g, opacity) },
+        raqote::Point::new(g.x1 as f32, g.y1 as f32),
+        raqote::Point::new(g.x2 as f32, g.y2 as f32),
+        conv_spread(g.base.spread_method),
+    );
+
+    if let raqote::Source::LinearGradient(_, _, ref mut transform) = grad {
+        let ts: raqote::Transform = ts.to_native();
+        if let Some(ts) = ts.inverse() {
+            *transform = transform.pre_mul(&ts);
+        }
+    }
+
+    grad
+}
+
+fn prepare_radial<'a>(
+    g: &usvg::RadialGradient,
+    opacity: usvg::Opacity,
+    bbox: Rect,
+) -> raqote::Source<'a> {
+    let ts = if g.units == usvg::Units::ObjectBoundingBox {
+        let mut ts = usvg::Transform::from_bbox(bbox);
+        ts.append(&g.transform);
+        ts
+    } else {
+        g.transform
+    };
+
+    let mut grad;
+
+    if g.fx == g.cx && g.fy == g.cy {
+        grad = raqote::Source::new_radial_gradient(
+            raqote::Gradient { stops: conv_stops(g, opacity) },
+            raqote::Point::new(g.cx as f32, g.cy as f32),
+            g.r.value() as f32,
+            conv_spread(g.base.spread_method),
+        );
+    } else {
+        grad = raqote::Source::new_two_circle_radial_gradient(
+            raqote::Gradient { stops: conv_stops(g, opacity) },
+            raqote::Point::new(g.fx as f32, g.fy as f32),
+            0.0,
+            raqote::Point::new(g.cx as f32, g.cy as f32),
+            g.r.value() as f32,
+            conv_spread(g.base.spread_method),
+        );
+    }
+
+    match grad {
+        raqote::Source::RadialGradient(_, _, ref mut transform)
+        | raqote::Source::TwoCircleRadialGradient(_, _, _, _, _, _, ref mut transform) => {
+            let ts: raqote::Transform = ts.to_native();
+            if let Some(ts) = ts.inverse() {
+                *transform = transform.pre_mul(&ts);
+            }
+        }
+        _ => {}
+    }
+
+    grad
+}
+
+fn conv_spread(v: usvg::SpreadMethod) -> raqote::Spread {
+    match v {
+        usvg::SpreadMethod::Pad => raqote::Spread::Pad,
+        usvg::SpreadMethod::Reflect => raqote::Spread::Reflect,
+        usvg::SpreadMethod::Repeat => raqote::Spread::Repeat,
+    }
+}
+
+fn conv_stops(
+    g: &usvg::BaseGradient,
+    opacity: usvg::Opacity,
+) -> Vec<raqote::GradientStop> {
+    let mut stops = Vec::new();
+
+    for stop in &g.stops {
+        let alpha = stop.opacity.value() * opacity.value();
+        stops.push(raqote::GradientStop {
+            position: stop.offset.value() as f32,
+            color: stop.color.to_u32((alpha * 255.0) as u8),
+        });
+    }
+
+    stops
+}
+
+fn prepare_pattern<'a>(
+    pattern_node: &usvg::Node,
+    pattern: &usvg::Pattern,
+    opt: &Options,
+    global_ts: raqote::Transform,
+    bbox: Rect,
+    opacity: usvg::Opacity,
+) -> Option<(raqote::DrawTarget, usvg::Transform)> {
+    let r = if pattern.units == usvg::Units::ObjectBoundingBox {
+        pattern.rect.bbox_transform(bbox)
+    } else {
+        pattern.rect
+    };
+
+    let global_ts = usvg::Transform::from_native(&global_ts);
+    let (sx, sy) = global_ts.get_scale();
+
+    let img_size = Size::new(r.width() * sx, r.height() * sy)?.to_screen_size();
+    let mut dt = raqote::DrawTarget::new(img_size.width() as i32, img_size.height() as i32);
+
+    dt.transform(&raqote::Transform::create_scale(sx as f32, sy as f32));
+    if let Some(vbox) = pattern.view_box {
+        let ts = utils::view_box_to_transform(vbox.rect, vbox.aspect, r.size());
+        dt.transform(&ts.to_native());
+    } else if pattern.content_units == usvg::Units::ObjectBoundingBox {
+        // 'Note that this attribute has no effect if attribute `viewBox` is specified.'
+
+        // We don't use Transform::from_bbox(bbox) because `x` and `y` should be
+        // ignored for some reasons...
+        dt.transform(&raqote::Transform::create_scale(bbox.width() as f32, bbox.height() as f32));
+    }
+
+    let mut layers = super::create_layers(img_size);
+    super::render_group(pattern_node, opt, &mut layers, &mut dt);
+
+    let img = if !opacity.is_default() {
+        // If `opacity` isn't `1` then we have to make image semitransparent.
+        // The only way to do this is by making a new image and rendering
+        // the pattern on it with transparency.
+
+        let mut img2 = raqote::DrawTarget::new(img_size.width() as i32, img_size.height() as i32);
+        img2.draw_image_at(0.0, 0.0, &dt.as_image(), &raqote::DrawOptions {
+            blend_mode: raqote::BlendMode::Src,
+            alpha: opacity.value() as f32,
+            ..raqote::DrawOptions::default()
+        });
+
+        img2
+    } else {
+        dt
+    };
+
+    let mut ts = usvg::Transform::default();
+    ts.append(&pattern.transform);
+    ts.translate(r.x(), r.y());
+    ts.scale(1.0 / sx, 1.0 / sy);
+
+    Some((img, ts))
+}
+
+fn create_pattern_image(
+    dt: &raqote::DrawTarget,
+    ts: usvg::Transform,
+) -> raqote::Source {
+    let ts: raqote::Transform = ts.to_native();
+    raqote::Source::Image(
+        dt.as_image(),
+        raqote::ExtendMode::Repeat,
+        raqote::FilterMode::Bilinear,
+        ts.inverse().unwrap(),
+    )
+}
diff --git a/src/lib.rs b/src/lib.rs
index 877947aad..08588d5f5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,6 +25,9 @@ pub use cairo;
 #[cfg(feature = "qt-backend")]
 pub use resvg_qt as qt;
 
+#[cfg(feature = "raqote-backend")]
+pub use raqote;
+
 pub use usvg::{self, svgdom, Error};
 
 
@@ -34,6 +37,9 @@ pub mod backend_cairo;
 #[cfg(feature = "qt-backend")]
 pub mod backend_qt;
 
+#[cfg(feature = "raqote-backend")]
+pub mod backend_raqote;
+
 pub mod utils;
 mod backend_utils;
 mod geom;
@@ -101,5 +107,10 @@ pub fn default_backend() -> Box<Render> {
         return Box::new(backend_qt::Backend);
     }
 
+    #[cfg(feature = "raqote-backend")]
+    {
+        return Box::new(backend_raqote::Backend);
+    }
+
     unreachable!("at least one backend must be enabled")
 }
diff --git a/tools/rendersvg/Cargo.toml b/tools/rendersvg/Cargo.toml
index 860bb1ae2..9efee47f3 100644
--- a/tools/rendersvg/Cargo.toml
+++ b/tools/rendersvg/Cargo.toml
@@ -17,3 +17,4 @@ time = "0.1"
 [features]
 cairo-backend = ["resvg/cairo-backend"]
 qt-backend = ["resvg/qt-backend"]
+raqote-backend = ["resvg/raqote-backend"]
diff --git a/tools/rendersvg/src/args.rs b/tools/rendersvg/src/args.rs
index c93868653..58e315116 100644
--- a/tools/rendersvg/src/args.rs
+++ b/tools/rendersvg/src/args.rs
@@ -301,6 +301,9 @@ fn default_backend() -> &'static str {
     #[cfg(feature = "qt-backend")]
     { return "qt" }
 
+    #[cfg(feature = "raqote-backend")]
+    { return "raqote" }
+
     unreachable!();
 }
 
@@ -313,5 +316,8 @@ fn backends() -> Vec<&'static str> {
     #[cfg(feature = "qt-backend")]
     { list.push("qt"); }
 
+    #[cfg(feature = "raqote-backend")]
+    { list.push("raqote"); }
+
     list
 }
diff --git a/tools/rendersvg/src/main.rs b/tools/rendersvg/src/main.rs
index 52657227a..811255922 100644
--- a/tools/rendersvg/src/main.rs
+++ b/tools/rendersvg/src/main.rs
@@ -33,7 +33,9 @@ fn main() {
 }
 
 fn process() -> Result<(), String> {
-    #[cfg(all(not(feature = "cairo-backend"), not(feature = "qt-backend")))]
+    #[cfg(all(not(feature = "cairo-backend"),
+              not(feature = "qt-backend"),
+              not(feature = "raqote-backend")))]
     {
         bail!("rendersvg has been built without any backends")
     }
@@ -64,6 +66,8 @@ fn process() -> Result<(), String> {
         "cairo" => Box::new(resvg::backend_cairo::Backend),
         #[cfg(feature = "qt-backend")]
         "qt" => Box::new(resvg::backend_qt::Backend),
+        #[cfg(feature = "raqote-backend")]
+        "raqote" => Box::new(resvg::backend_raqote::Backend),
         _ => bail!("unknown backend"),
     };
 


Test patch:
diff --git a/testing-tools/regression/allow-raqote.txt b/testing-tools/regression/allow-raqote.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/testing-tools/regression/regression.py b/testing-tools/regression/regression.py
index 459aa3115..f4f69633f 100755
--- a/testing-tools/regression/regression.py
+++ b/testing-tools/regression/regression.py
@@ -67,7 +67,7 @@ def rm_file(file_path):
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
-    parser.add_argument('--backend', help='Sets resvg backend', choices=['qt', 'cairo'])
+    parser.add_argument('--backend', help='Sets resvg backend', choices=['qt', 'cairo', 'raqote'])
     parser.add_argument('--use-prev-commit', help='Use previous git commit as a reference',
                         action='store_true')
     parser.add_argument('in_dir', type=Path, help='Sets input directory')
diff --git a/testing-tools/run-tests.py b/testing-tools/run-tests.py
index 4185efe2c..adfe7559d 100755
--- a/testing-tools/run-tests.py
+++ b/testing-tools/run-tests.py
@@ -98,6 +98,20 @@ def regression_testing(backend):
                 exit(1)
 
 
+if 'RESVG_RAQOTE_BACKEND' in os.environ:
+    # build raqote backend
+    with cd('tools/rendersvg'):
+        run(['cargo', 'build', '--features', 'raqote-backend'], check=True)
+
+    # regression testing of the cairo backend
+    if not args.no_regression:
+        with cd('testing-tools/regression'):
+            try:
+                regression_testing('raqote')
+            except subprocess.CalledProcessError:
+                exit(1)
+
+
 # # try to build with all backends
 # with cd('tools/rendersvg'):
 #     run(['cargo', 'build', '--all-features'], check=True)
@@ -150,3 +164,12 @@ def regression_testing(backend):
     # build cairo-rs example
     with cd('examples/cairo-rs'):
         run(['cargo', 'build'], check=True)
+
+
+if 'RESVG_RAQOTE_BACKEND' in os.environ:
+    # build C-API
+    # with cd('capi'):
+    #     run(['cargo', 'build', '--features', 'raqote-backend'], check=True)
+
+    # run tests and build examples
+    run(['cargo', 'test', '--features', 'raqote-backend'], check=True)