librsvg source for verification 2026-05-22

This commit is contained in:
2026-05-22 16:45:08 +08:00
commit 75af7ac721
2138 changed files with 161177 additions and 0 deletions

20
devel-docs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@@ -0,0 +1,30 @@
..
Please also check to see if OSS-Fuzz dependencies need to be changed (see oss_fuzz.rst).
To compile librsvg, you need the following packages installed. The
minimum version is listed here; you may use a newer version instead.
**Compilers and build tools:**
* a C compiler
* `rust <https://www.rust-lang.org/>`_ 1.92.0 or later
* `cargo <https://www.rust-lang.org/>`_
* ``cargo-cbuild`` from `cargo-c <https://github.com/lu-zero/cargo-c>`_
* `meson <https://mesonbuild.com/>`_
* `vala <https://vala.dev/>`_ (optional)
**Mandatory dependencies:**
* `Cairo <https://gitlab.freedesktop.org/cairo/cairo>`_ 1.18.0 with PNG support
* `Freetype2 <https://gitlab.freedesktop.org/freetype/freetype>`_ 2.8.0
* `GLib <https://gitlab.gnome.org/GNOME/glib/>`_ 2.50.0
* `Libxml2 <https://gitlab.gnome.org/GNOME/libxml2>`_ 2.9.0
* `Pango <https://gitlab.gnome.org/GNOME/pango/>`_ 1.50.0
**Optional dependencies:**
* `GDK-Pixbuf <https://gitlab.gnome.org/GNOME/gdk-pixbuf/>`__ 2.20.0
* `GObject-Introspection <https://gitlab.gnome.org/GNOME/gobject-introspection>`_ 0.10.8
* `gi-docgen <https://gitlab.gnome.org/GNOME/gi-docgen>`_
* `python3-docutils <https://pypi.org/project/docutils/>`_
* `dav1d <https://code.videolan.org/videolan/dav1d>`_ 1.3.0 (to support the AVIF image format)

View File

@@ -0,0 +1,216 @@
"""Custom reStructuredText entities/helpers for referencing entities in the
librsvg internals documentation.
For any changes made in this module, please ensure
``devel-docs/devel_docs_mod_guide.rst`` is updated accordingly, if necessary.
For help/reference on modifying these, see:
- https://www.sphinx-doc.org/en/master/development/tutorials/extending_syntax.html
- https://docutils.sourceforge.io/docs/howto/rst-roles.html
- https://protips.readthedocs.io/link-roles.html
- https://www.sphinx-doc.org/en/master/development/tutorials/adding_domain.html
- https://www.sphinx-doc.org/en/master/extdev/utils.html#sphinx.util.docutils.SphinxRole
- https://www.sphinx-doc.org/en/master/extdev/utils.html#sphinx.util.docutils.ReferenceRole
- https://github.com/sphinx-doc/sphinx/blob/master/sphinx/roles.py
"""
from __future__ import annotations
from docutils.nodes import literal, reference
from sphinx.domains import Domain
from sphinx.util.docutils import ReferenceRole
BASE_URL = "https://gnome.pages.gitlab.gnome.org/librsvg/internals"
class CrateRole(ReferenceRole):
"""A custom Sphinx role for referencing crates in the librsvg internals
documentation.
"""
def run(self):
node = reference(
self.rawtext,
refuri=f"{BASE_URL}/{self.target}/index.html",
**self.options,
)
node += literal(self.rawtext, self.title)
return [node], []
class ModuleRole(ReferenceRole):
"""A custom Sphinx role for referencing modules in the librsvg internals
documentation.
"""
def run(self):
components = self.target.split("::")
try:
*parents, module = components
if not parents:
raise ValueError
except ValueError:
msg = self.inliner.reporter.error(
f"Invalid module target: {self.target!r}", line=self.lineno
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
node = reference(
self.rawtext,
refuri=f"{BASE_URL}/{'/'.join(parents)}/{module}/index.html",
**self.options,
)
node += literal(
self.rawtext, self.title if self.has_explicit_title else module
)
return [node], []
class TopLevelRole(ReferenceRole):
"""A custom Sphinx role for referencing top-level entities in the librsvg
internals documentation.
"""
def __init__(self, target_kind: str, *, target_is_callable: bool = False):
super().__init__()
self.target_kind = target_kind
self.target_is_callable = target_is_callable
def run(self):
*parents, item = self.target.split("::")
if not parents:
msg = self.inliner.reporter.error(
f"Invalid {self.target_kind} target: {self.target!r}",
line=self.lineno,
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
node = reference(
self.rawtext,
refuri=(
f"{BASE_URL}/{'/'.join(parents)}/{self.target_kind}"
f".{item}.html"
),
**self.options,
)
node += literal(
self.rawtext,
(
self.title if self.has_explicit_title
else f"{item}()" if self.target_is_callable
else item
),
)
return [node], []
class MemberRole(ReferenceRole):
"""A custom Sphinx role for referencing members of structs, enums, etc
in the librsvg internals documentation.
"""
def __init__(
self,
target_kind: str,
parent_tag: str,
member_tag: str,
*,
target_is_callable: bool = False,
):
super().__init__()
self.target_kind = target_kind
self.parent_tag = parent_tag
self.member_tag = member_tag
self.target_is_callable = target_is_callable
def run(self):
show_parent = not self.target.startswith("~")
target = self.target if show_parent else self.target[1:]
components = target.split("::")
try:
*parents, parent, member = components
if not parents:
raise ValueError
except ValueError:
msg = self.inliner.reporter.error(
f"Invalid {self.target_kind} target: {target!r}",
line=self.lineno,
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
node = reference(
self.rawtext,
refuri=(
f"{BASE_URL}/{'/'.join(parents)}/{self.parent_tag}"
f".{parent}.html#{self.member_tag}.{member}"
),
**self.options,
)
if not self.has_explicit_title:
title = f"{parent}::{member}" if show_parent else member
node += literal(
self.rawtext,
(
self.title if self.has_explicit_title
else f"{title}()" if self.target_is_callable
else title
),
)
return [node], []
class InternalsDomain(Domain):
"""A custom Sphinx domain for referencing the librsvg internals docs."""
name = "internals"
label = "Librsvg Internals Docs"
roles = {
"crate": CrateRole(),
"module": ModuleRole(),
# Top-level entities
"struct": TopLevelRole("struct"),
"enum": TopLevelRole("enum"),
"trait": TopLevelRole("trait"),
"type": TopLevelRole("type"),
"fn": TopLevelRole("fn", target_is_callable=True),
"macro": TopLevelRole("macro"),
"constant": TopLevelRole("constant"),
"static": TopLevelRole("static"),
# Member entities
"struct-field": MemberRole("struct field", "struct", "structfield"),
"struct-method": MemberRole(
"struct method", "struct", "method", target_is_callable=True
),
"enum-variant": MemberRole("enum variant", "enum", "variant"),
"trait-method": MemberRole(
"provided trait method", "trait", "method", target_is_callable=True
),
"trait-tymethod": MemberRole(
"required trait method",
"trait",
"tymethod",
target_is_callable=True,
),
}
def setup(app):
app.add_domain(InternalsDomain)

View File

@@ -0,0 +1,62 @@
"""Custom reStructuredText entities/helpers for referencing entries in the
librsvg source tree.
For any changes made in this module, please ensure
``devel-docs/devel_docs_mod_guide.rst`` is updated accordingly, if necessary.
For help/reference on modifying these, see:
- https://www.sphinx-doc.org/en/master/development/tutorials/extending_syntax.html
- https://docutils.sourceforge.io/docs/howto/rst-roles.html
- https://protips.readthedocs.io/link-roles.html
- https://www.sphinx-doc.org/en/master/extdev/utils.html#sphinx.util.docutils.SphinxRole
- https://www.sphinx-doc.org/en/master/extdev/utils.html#sphinx.util.docutils.ReferenceRole
- https://github.com/sphinx-doc/sphinx/blob/master/sphinx/roles.py
"""
from __future__ import annotations
from docutils.nodes import reference
from sphinx.util.docutils import ReferenceRole
BASE_URL = "https://gitlab.gnome.org/GNOME/librsvg/-/tree"
class SourceRole(ReferenceRole):
"""A custom Sphinx role for referencing entries in the librsvg source tree.
"""
def run(self):
ref, _, path = self.target.rpartition(":")
if path.startswith("/"):
msg = self.inliner.reporter.error(
f"Invalid source tree entry path: {path!r}", line=self.lineno
)
prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
return [prb], [msg]
if ref:
# 12 <= 9 characters + elipsis (3 periods);
# 12, to allow `librsvg-X.YZ`.
short_ref = f"{ref[:9]}..." if len(ref) > 12 else ref
else:
short_ref = ref = "main"
node = reference(
self.rawtext,
(
self.title if self.has_explicit_title
else path or 'the source tree' if ref == "main"
else f"{path or 'the source tree'} (@ {short_ref})"
),
refuri=f"{BASE_URL}/{ref}/{path}",
**self.options,
)
return [node], []
def setup(app):
app.add_role("source", SourceRole())

0
devel-docs/_static/.keep Normal file
View File

View File

@@ -0,0 +1,686 @@
How to add a new CSS property
=============================
This document is a little tour on how to add support for a CSS property
to librsvg. We will implement the |mask-type|_ property from the
**CSS Masking Module Level 1** specification.
What is ``mask-type``?
----------------------
The spec says about |mask-type|_:
The mask-type property defines whether the content of the mask
element is treated as as luminance mask or alpha mask, as described
in Calculating mask values.
A **luminance mask** takes the RGB values of each pixel, converts them
to a single luminance value, and uses that as a mask.
An **alpha mask** just takes the alpha value of each pixel and uses it
as a mask.
The only mask type that SVG1.1 supported was luminance masks; there
wasnt even a ``mask-type`` property back then. The SVG2 spec removed
descriptions of masking, and offloaded them to the `CSS Masking Module
Level 1 <https://www.w3.org/TR/css-masking-1/>`__ specification, which
it adds the ``mask-type`` property and others as well.
Lets start by figuring out how to read the spec.
What the specification says
---------------------------
The specification for ``mask-type`` is in
https://www.w3.org/TR/css-masking-1/#the-mask-type
In the specs, most of the descriptions for properties start with a table
that summarizes the property. For example, if you visit that link, you
will find a table that starts with these items:
- **Name:** ``mask-type``
- **Value:** ``luminance | alpha``
- **Initial:** ``luminance``
- **Applies to:** mask elements
- **Inherited:** no
- **Computed value:** as specified
Lets go through each of these:
**Name:** We have the name of the property (``mask-type``). Properties
are case-insensitive, and librsvg already has machinery to handle that.
**Value:** The possible values for the property can be ``luminance`` or
``alpha``. In the specs web page, even the little ``|`` between those
two values is a hyperlink; clicking it will take you to the
specification for CSS Values and Units, where it describes the grammar
that the CSS specs use to describe their values. Here you just need to
know that ``|`` means that exactly one of the two alternatives must
occur.
As you may imagine, librsvg already parses a lot of similar properties
that are just symbolic values. For example, the ``stroke-linecap``
property can have values ``butt | round | square``. Well see how to
write a parser for this kind of property with a minimal amount of code.
**Initial:** Then there is the initial or default value, which is
``luminance``. This means that if the ``mask-type`` property is not
specified on an element, it takes ``luminance`` as its default. This is
a sensible choice, since an SVG1.1 file that is processed by SVG2
software should retain the same semantics. It also means that if there
is a parse error, for example if you typed ``ahlpha``, the property will
silently revert back to the default ``luminance`` value.
**Applies to:** Librsvg doesnt pay much attention to “applies to” — it
just carries property values for all elements, and the elements that
dont handle a property just ignore it.
**Inherited:** This property is not inherited, which means that by
default, its value does not cascade. So if you have this:
.. code:: xml
<mask style="mask-type: alpha;">
<other>
<elements>
<here/>
</elements>
</other>
</mask>
Then the ``other``, ``elements``, ``here`` will not inherit the
``mask-type`` value from their ancestor.
**Computed value:** Finally, the computed value is “as specified”, which
means that librsvg does not need to modify it in any way when resolving
the CSS cascade. Other properties, like ``width: 1em;`` may need to be
resolved against the ``font-size`` to obtain the computed value.
The W3C specifications can get pretty verbose and it takes some practice
to read them, but fortunately this property is short and sweet.
Lets go on.
How librsvg represents properties
---------------------------------
Each property has a Rust type that can hold its values. Remember the
part of the masking spec from above, that says the ``mask-type``
property can have values ``luminance`` or ``alpha``, and the
initial/default is ``luminance``? This translates easily to Rust types:
.. code:: rust
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MaskType {
Luminance,
Alpha,
}
impl Default for MaskType {
fn default() -> MaskType {
MaskType::Luminance
}
}
Additionally, we need to be able to say that the property does not
inherit by default, and that its computed value is the same as the
specified value (e.g. we can just copy the original value without
changing it). Librsvg defines a ``Property`` trait for those actions:
.. code:: rust
pub trait Property {
fn inherits_automatically() -> bool;
fn compute(&self, _: &ComputedValues) -> Self;
}
For the ``mask-type`` property, we want ``inherits_automatically`` to
return ``false``, and ``compute`` to return the value unchanged. So,
like this:
.. code:: rust
impl Property for MaskType {
fn inherits_automatically() -> bool {
false
}
fn compute(&self, _: &ComputedValues) -> Self {
self.clone()
}
}
Ignore the ``ComputedValues`` argument for now — it is how librsvg
represents an elements complete set of property values.
As you can imagine, there are a lot of properties like ``mask-type``,
whose values are just symbolic names that map well to a data-less enum.
For all of them, it would be a lot of repetitive code to define their
default value, return whether they inherit or not, and clone them for
the computed value. Additionally, we have not even written the parser
for this propertys values yet.
Fortunately, librsvg has a ``make_property!`` macro that lets you do
this instead:
.. code:: rust
make_property!(
/// `mask-type` property. // (1)
///
/// https://www.w3.org/TR/css-masking-1/#the-mask-type
MaskType, // (2)
default: Luminance, // (3)
inherits_automatically: false, // (4)
identifiers: // (5)
"luminance" => Luminance,
"alpha" => Alpha,
);
-
(1) is a documentation comment for the ``MaskType`` enum being
defined.
-
(2) is ``MaskType``, the name we will use for the ``mask-type``
property.
-
(3) indicates the “initial value”, or default, for the property.
-
(4) … whether the spec says the property should inherit or not.
-
(5) Finally, ``identifiers:`` is what makes the ``make_property!``
macro know that it should generate a parser for the symbolic
names ``luminance`` and ``alpha``, and that they should
correspond to the values ``MaskType::Luminance`` and
``MaskType::Alpha``, respectively.
This saves a lot of typing! Also, it makes it easier to gradually change
the way properties are represented, as librsvg evolves.
Properties that use the same data type
--------------------------------------
Consider the ``stroke`` and ``fill`` properties; both store a |<paint>|_
value, which librsvg represents with a type called ``PaintServer``. The
``make_property!`` macro has a case for properties like that, so in the
librsvg source code you will find both of thsese:
.. code:: rust
make_property!(
/// `fill` property.
///
/// https://www.w3.org/TR/SVG/painting.html#FillProperty
///
/// https://www.w3.org/TR/SVG2/painting.html#FillProperty
Fill,
default: PaintServer::parse_str("#000").unwrap(),
inherits_automatically: true,
newtype_parse: PaintServer,
);
make_property!(
/// `stroke` property.
///
/// https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint
Stroke,
default: PaintServer::None,
inherits_automatically: true,
newtype_parse: PaintServer,
);
The ``newtype_parse:`` is what tells the macro that it should generate a
newtype like ``struct Stroke(PaintServer)``, and that it should just use
the parser that ``PaintServer`` already has.
Which parser is that? Read on.
Custom parsers
--------------
Librsvg has a ``Parse`` trait for property values which looks rather
scary:
.. code:: rust
pub trait Parse: Sized {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>>;
}
Dont let the lifetimes scare you. They are required because of
``cssparser::Parser``, from the ``cssparser`` crate, tries really hard
to let you implement zero-copy parsers, which give you string tokens as
slices from the original string being parsed, instead of allocating lots
of little ``String`` values. What this ``Parse`` trait means is, you get
tokens out of the ``Parser``, and return what is basically a
``Result<Self, Error>``.
In this tutorial we will just show you the parser for simple numeric
types, for example, for properties that can just be represented with an
``f64``. There is the ``stroke-miterlimit`` property defined like this:
.. code:: rust
make_property!(
/// `stroke-miterlimit` property.
///
/// https://www.w3.org/TR/SVG2/painting.html#StrokeMiterlimitProperty
StrokeMiterlimit,
default: 4f64,
inherits_automatically: true,
newtype_parse: f64,
);
And the ``impl Parse for f64`` looks like this:
.. code:: rust
impl Parse for f64 {
fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
let loc = parser.current_source_location(); // (1)
let n = parser.expect_number()?; // (2)
if n.is_finite() { // (3)
Ok(f64::from(n)) // (4)
} else {
Err(loc.new_custom_error(ValueErrorKind::value_error("expected finite number"))) // (5)
}
}
}
-
(1) Store the current location in the parser.
-
(2) Ask the parser for a number. If a non-numeric token comes out
(e.g. if the user put ``stroke-miterlimit: foo`` instead of
``stroke-miterlimit: 5``), ``expect_number`` will return an
``Err``, which we propagate upwards with the ``?``.
-
(3) Check the number for being non-infinite or NaN….
-
(4) … and return the number converted to f64 (``cssparser`` returns
f32, but we promote them so that subsequent calculations can use
the extra precision)…
-
(5) … or return an error based on the location from (1).
My advice: implement new parsers by doing cut&paste from existing ones,
and youll be okay.
Registering the property
------------------------
Okay! We defined ``MaskType`` and its symbolic identifiers with the
``make_property!`` macro, and the macro took care of writing a parser
for it and implementing the traits that the property needs.
Now we need to modify the code in a few places to process the property.
Register the property
---------------------
- First, look for ``longhands:`` in ``properties.rs``. You will find
that it is part of a long macro invocation:
.. code:: rust
make_properties! {
// ... stuff omitted here
longhands: {
// ... stuff omitted here
"marker-end" => (PresentationAttr::Yes, marker_end : MarkerEnd),
"marker-mid" => (PresentationAttr::Yes, marker_mid : MarkerMid),
"marker-start" => (PresentationAttr::Yes, marker_start : MarkerStart),
"mask" => (PresentationAttr::Yes, mask : Mask),
// "mask-type" => (PresentationAttr::Yes, unimplemented),
"opacity" => (PresentationAttr::Yes, opacity : Opacity),
"overflow" => (PresentationAttr::Yes, overflow : Overflow),
// ... stuff omitted here
}
}
In there, there is an entry for ``mask-type`` commented out. Lets
uncomment it and turn it into this:
.. code:: rust
"mask-type" => (PresentationAttr::Yes, mask_type : MaskType),
``PresentationAttr::Yes`` indicates whether the property has a
corresponding presentation attribute. This means that you can do
``<mask style="mask-type: alpha;">`` which is property, as well as
``<mask mask-type="alpha">``, which is a presentation attribute.
How did we find out that ``mask-type`` also exists as a presentation
attribute? Well, `the spec
<https://www.w3.org/TR/css-masking-1/#the-mask-type>`__ says:
The mask-type property is a presentation attribute for SVG elements.
But wait! If we compile, we get this:
::
error: no rules expected the token `"mask-type"`
--> src/properties.rs:450:9
|
450 | "mask-type" => (PresentationAttr::Yes, mask_type : MaskType),
| ^^^^^^^^^^^ no rules expected this token in macro call
When you see that error in exactly that macro invocation, it means this:
librsvg uses a crate called ``markup5ever`` to have a compact
representation of the names of properties/attributes/elements. It uses
string interning so that, for example, there is a single definition of
``rect`` in the programs heap instead of there being a thousands of
duplicated ``rect`` strings when you load a big document. The thing is,
``markup5ever`` only has ready-made definitions of the most common
HTML/SVG/CSS names, but unfortunately ``mask-type`` is not one of them.
So, we scroll down in ``properties.rs`` and move the ``mask-type``
registration there:
.. code:: rust
longhands_not_supported_by_markup5ever: {
"line-height" => (PresentationAttr::No, line_height : LineHeight),
"mask-type" => (PresentationAttr::Yes, mask_type : MaskType), // <- right here
"mix-blend-mode" => (PresentationAttr::No, mix_blend_mode : MixBlendMode),
"paint-order" => (PresentationAttr::Yes, paint_order : PaintOrder),
}
That block named ``longhands_not_supported_by_markup5ever`` is, well,
exactly what it says — a separate section with property names that are
not built into ``markup5ever``, so they must be dealt with specially.
Just put the property there and thats it.
Next, we have to calculate the computed value for the property.
Calculate the computed value
----------------------------
In ``properties.rs``, look for ``compute!``. You will find many
invocations of this macro:
.. code:: rust
compute!(MarkerEnd, marker_end);
compute!(MarkerMid, marker_mid);
compute!(MarkerStart, marker_start);
compute!(Mask, mask);
compute!(MixBlendMode, mix_blend_mode);
compute!(Opacity, opacity);
compute!(Overflow, overflow);
Add a call for ``MaskType``:
.. code:: rust
compute!(MarkerEnd, marker_end);
compute!(MarkerMid, marker_mid);
compute!(MarkerStart, marker_start);
compute!(Mask, mask);
compute!(MaskType, mask_type); // this is new
compute!(MixBlendMode, mix_blend_mode);
compute!(Opacity, opacity);
compute!(Overflow, overflow);
You will see that all those calls to ``compute!`` are inside a method
called ``SpecifiedValues::to_computed_values()``. This method is run as
part of the CSS cascade: it takes the ``SpecifiedValues`` from an
element and composes them onto the ``ComputedValues`` from its parent
element. For example, if you have a document with this bit:
.. code:: xml
<g stroke="red" fill="blue"> // ComputedValues with stroke:red, fill:blue
<rect fill="green"/> // SpecifiedValues with fill:green
</g>
The ``ComputedValues`` that results from the ``<g>`` will have
properties ``stroke:red`` and ``fill:blue`` in it. The
``SpecifiedValues`` from the ``<rect>`` just has ``fill:green``.
Composing them together for the ``<rect>`` gives us ``ComputedValues``
with ``stroke:red`` and ``fill:green``.
Now that the property is registered, we can actually handle it in the
drawing code!
Handling the property
---------------------
First, a digression: lets change the name of a few methods to better
reflect what the new structure of the code will be like.
There are a few methods called ``to_mask`` in the code, that take an
RGBA surface and turn it into an Alpha-only surface with the luminance
of the original surface; and also the corresponding method to do this
for a single pixel. Lets do this kind of renaming:
::
- pub fn to_mask(&self, opacity: UnitInterval) -> Result<SharedImageSurface, cairo::Error> {
+ pub fn to_luminance_mask(&self, opacity: UnitInterval) -> Result<SharedImageSurface, cairo::Error> {
Librsvg only effectively supported ``mask-type: luminance`` since that
is what was in SVG1.1, but now for SVG2 we want to add behavior for
``mask-type: alpha`` as well. So, it makes sense to rename ``to_mask``
as ``to_luminance_mask``.
``SharedImageSurface`` is the type that librsvg uses to represent images
in memory. They can be RGBA or Alpha-only. There is already a method
called ``extract_alpha`` that we can use to create an Alpha-only mask:
.. code:: rust
// there's a type alias SharedImageSurface for this
impl ImageSurface<Shared> {
pub fn extract_alpha(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> { ... }
}
Now lets look at where ``drawing_ctx.rs`` has this:
.. code:: rust
let mask = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)? // (1)
.to_luminance_mask()? // (2)
.into_image_surface()?; // (3)
-
(1) Wraps a ``SharedImageSurface`` around the Cairo surface that was
just rendered with the mask contents.
-
(2) Converts it to a luminance mask. We will need to change this!
-
(3) Extracts the Cairo image surface from the ``SharedImageSurface``,
for further processing.
Remember the ``ComputedValues`` where we had the ``mask_type``? We can
extract it with ``values.mask_type()``. Now lets change the lines above
to this:
.. code:: rust
let tmp = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)?;
let mask_result = match values.mask_type() {
MaskType::Luminance => tmp.to_luminance_mask()?,
MaskType::Alpha => tmp.extract_alpha(IRect::from_size(tmp.width(), tmp.height()))?,
};
let mask = mask_result.into_image_surface()?;
But wait! We dont have a test for this yet! Aaaaaargh, we are doing
test-driven development backwards!
No biggie. Lets write the tests.
Adding tests
------------
Testing graphical output is really annoying if you compare PNG files,
because any time Cairo changes something and antialiasing changes
juuuuuust a bit, the tests break. So, librsvg tries to do “reftests”, or
reference tests, by comparing the rendered results of two things:
- The SVG you actually want to test.
- An equivalent SVG that works only with known-good features.
For ``mask-type``, we need an SVG document that actually uses that
property with both of its values, and another document that produces the
same results but with simpler primitives.
Librsvg already has tests for luminance masks, as they were the only
available kind in SVG1.1. So we can be confident that they already work
- we just need to test that the presence of ``mask-type="luminance"``
actually does the same thing.
First, lets dissect the SVG that we want to test:
.. code:: xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<mask id="luminance" mask-type="luminance" maskContentUnits="objectBoundingBox">
<rect x="0.1" y="0.1" width="0.8" height="0.8" fill="white"/>
</mask>
<mask id="alpha" mask-type="alpha" maskContentUnits="objectBoundingBox">
<rect x="0.1" y="0.1" width="0.8" height="0.8" fill="black"/>
</mask>
<rect x="0" y="0" width="100" height="100" fill="green" mask="url(#luminance)"/>
<rect x="100" y="0" width="100" height="100" fill="green" mask="url(#alpha)"/>
</svg>
The image has two 100x100 ``green`` squares side by side. The one on the
left gets masked with the ``luminance`` mask, which reduces it to an
80x80 rectangle. That mask is a **white** square, so its has full
luminance at every pixel.
The square on the right gets masked with the ``alpha`` mask. That mask
is a **black** square, but with alpha=1.0, so it should produce the same
result as the first one.
Note that to make things easy, we use **white** for the luminance mask.
White pixels have full luminance (1.0), which gets used as the mask.
Conversely, we use **black** for the alpha mask. Those black pixels are
fully opaque, and since ``mask-type="alpha"`` only considers the alpha
channel, it will be using the full opacity of each pixel (1.0), which
also gets used as the mask. So, the masks should be equivalent.
Okay! Now lets write the reference SVG, the one built out of simpler
elements but that should produce the same rendering:
.. code:: xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<rect x="10" y="10" width="80" height="80" fill="green"/>
<rect x="110" y="10" width="80" height="80" fill="green"/>
</svg>
This is just the two original squares, but already clipped or masked to
the final result.
Now, where do we put those SVG documents for the tests?
Near the end of ``tests/src/filters.rs`` we can include this:
.. code:: rust
test_compare_render_output!(
mask_type,
200,
100,
br##"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<mask id="luminance" mask-type="luminance" maskContentUnits="objectBoundingBox">
<rect x="0.1" y="0.1" width="0.8" height="0.8" fill="white"/>
</mask>
<mask id="alpha" mask-type="alpha" maskContentUnits="objectBoundingBox">
<rect x="0.1" y="0.1" width="0.8" height="0.8" fill="black"/>
</mask>
<rect x="0" y="0" width="100" height="100" fill="green" mask="url(#luminance)"/>
<rect x="100" y="0" width="100" height="100" fill="green" mask="url(#alpha)"/>
</svg>
"##,
br##"<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
<rect x="10" y="10" width="80" height="80" fill="green"/>
<rect x="110" y="10" width="80" height="80" fill="green"/>
</svg>
"##,
);
Here, ``test_compare_render_output!`` is a macro that takes two SVG
documents, the test and the reference, and compares their rendered
results. It also takes a test name (``mask_type`` in this case), and the
pixel size of the image to generate for testing (200x100).
Final steps: documentation
--------------------------
To help people who are wondering what SVG features are supported in
librsvg, there is a ``FEATURES.md`` file. It has a section called “CSS
properties” with a big list of property names and notes about them.
Well patch it like this:
::
| marker-mid | |
| marker-start | |
| mask | |
+| mask-type | |
| mix-blend-mode | Not available as a presentation attribute. |
| opacity | |
| overflow | |
There is nothing remarkable about ``mask-type``, it is a plain old
property that also has a presentation attribute (remember the
``PresentationAttr::Yes`` from above?), so we dont need to list any
extra information.
And with that, we are done implementing ``mask-type``. Have fun!
.. See https://docutils.sourceforge.net/FAQ.html#is-nested-inline-markup-possible
.. |mask-type| replace:: ``mask-type``
.. _mask-type: https://www.w3.org/TR/css-masking-1/#the-mask-type
.. |<paint>| replace:: ``<paint>``
.. _<paint>: https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint

View File

@@ -0,0 +1,217 @@
Observability
=============
Librsvg supports basic, mostly ad-hoc logging with an ``RSVG_LOG=1``
environment variable. This has not been very effective in letting me,
the maintainer, know what went wrong when someone reports a bug about
librsvg doing the wrong thing in an application. Part of it is because
the code could be more thorough about logging (e.g. log at all error
points), but also part of it is that there is no logging about what API
calls are made into the library. On each bug reported on behalf of a
particular application, my thought process goes something like this:
- What was the app doing?
- Can I obtain the problematic SVG?
- Does the bug reporter even know what the problematic SVG was?
- Was the app rendering with direct calls to the librsvg API?
- Or was it using the gdk-pixbuf loader, and thus has very little
control of how librsvg is used?
- If non-pixbuf, what was the Cairo state when librsvg was called?
- What sort of interesting API calls are being used? Stylesheet
injection? Re-rendering single elements with different styles?
And every time, I must ask the bug reporter for information related to
that, or to point me to the relevant source code where they were using
librsvg… which is not terribly useful, since building their code and
reproducing the bug with it is A Yak That Should Not Have To Be Shaved.
Desiderata
----------
Know exactly what an application did with librsvg:
- All API calls and their parameters.
- State of the Cairo context at entry.
- “What SVG?” - be careful and explicit about exfiltrating SVG data to
the logs.
- Basic platform stuff? Is the platform triple enough? Distro ID?
- Versions of dependencies.
- Version of librsvg itself.
Internals of the library:
- Regular debug tracing. We may have options to enable/disable tracing
domains: parsing, cascading, referencing elements, temporary surfaces
during filtering, render tree, etc.
- Log all points where an error is detected/generated, even if it will
be discarded later (e.g. invalid CSS values are silently ignored, per
the spec).
Enabling logging
----------------
It may be useful to be able to enable logging in various ways:
- Programmatically, for when one has control of the source code of the
problematic application. Enable logging at the problem spot, for the
SVG you know that exhibits the problem, and be done with it. This can
probably be at the individual ``RsvgHandle`` level, not globally. For
global logging within a single process, see the next point.
- For a single process which one can easily launch via the command
line; e.g. with an environment variable. This works well for
non-sandboxed applications. Something like
``RSVG_LOG_CONFIG=my_log_config.toml``.
- With a configuration file, a la ``~/.config/librsvg.toml``. Many
programs use librsvg and you dont want logs for all of them; allow
the configuration file to specify a process name, or maybe other ways
of determining when to log. For session programs like gnome-shell,
you cant easily set an environment variable to enable logging -
hence, a configuration file that only turns on logging from the
gnome-shell process.
All of the above should be well documented, and then we can deprecate
``RSVG_LOG``.
Which SVG caused a crash?
-------------------------
Every once in a while, a bug report comes in like “$application crashed
in librsvg”. The application renders many SVGs, often indirectly via
gdk-pixbuf, and it is hard to know exactly which SVG caused the problem.
Think of gnome-shell or gnome-software.
For applications that call librsvg directly, if they pass the filename
or a GFile then it is not hard to find out the source SVG.
But for those that feed bytes into librsvg, including those that use it
indirectly via gdk-pixbuf, librsvg has no knowledge of the filename. We
need to use the base_uri then, or see if the pixbuf loader can be
modified to propagate this information (is it even available from the
GdkPixbufLoader machinery?).
If all else fails, we can have an exfiltration mechanism. How can we
avoid logging *all* the SVG data that gnome-shell renders, for example?
Configure the logger to skip the first N SVGs, and hope that the order
is deterministic? We cant really “log only if there is a crash during
rendering”.
Log only the checksums of SVGs or data lengths, and use that to find
which SVG caused the crash? I.e. have the user use a two-step process to
find a crash: get a log (written synchronously) of all SVG
checksums/lengths, and then reconfigure the logger to only exfiltrate
the last one that got logged - presumably that one caused the crash.
Which dynamically-created SVG caused a problem?
-----------------------------------------------
Consider a bug like :issue:`GNOME/gnome-shell#5415` where an
application dynamically generates an SVG and feeds it to librsvg. That
bug was not a crash; it was about incorrect values returned from an
librsvg API function. For those cases it may be useful to be able to
exfiltrate an SVG and its stylesheets only if it matches a user-provided
substring.
Global configuration
--------------------
``$(XDG_CONFIG_HOME)/librsvg.toml`` - for startup-like processes like
gnome-shell, for which it is hard to set an environment variable:
Per-process configuration
-------------------------
``RSVG_LOG_CONFIG=my_log_config.toml my_process``
Programmatic API
----------------
FIXME
Configuration format
--------------------
.. code:: toml
[logging]
enabled = true
process = "gnome-shell" # mandatory for global config - don't want to log all processes - warn to g_log if key is not set
output = "/home/username/rsvg.log" # if missing, log to g_log only - or use a output_to_g_log=true instead?
API logging
-----------
Log cr state at entry, surface type, starting transform.
Log name/base_uri of rendered document.
Can we know if it is a gresource? Or a byte buffer? Did it come from
gdk-pixbuf?
Implementation
--------------
There is currently the start of a :internals:struct:`rsvg::session::Session`
type woven throughout the source code, with the idea of it being the
thing that records logging events, it may be better to plug into the
``tracing`` ecosystem:
https://crates.io/crates/tracing
Initial ideas:
* See the "In libraries" section in ``tracing``'s README; it shows how
to create spans for API calls.
* How would we capture from gnome-shell? `tracing-journald
<https://tracing-rs.netlify.app/tracing_journald/index.html>`_?
Or would things be easier for casual users if we logged to a file?
* Maybe later, have a ``tracing-sysprof`` crate to send the events to
`sysprof <https://gitlab.gnome.org/GNOME/sysprof/-/tree/master/src>`_?
Log contents
------------
/home/username/rsvg.log - json doesnt have comments; put one of these
in a string somehow:
::
******************************************************************************
* This log file exists because you enabled logging in ~/.config/librsvg.toml *
* for the "gnome-shell" process. *
* *
* If you want to disable this kind of log, please turn it off in that file *
* or delete that file entirely. *
******************************************************************************
******************************************************************************
* This log file exists because you enabled logging with *
* RSVG_LOG_CONFIG=config.toml for the "single-process-name" process. *
* *
* If you want to disable this kind of log, FIXME */
******************************************************************************
To-do list
----------
- Audit code for GIO errors; log there.
- Audit code for Cairo calls that yield errors; log there.
- Log the entire ancestry of the element that caused the error? Is
that an insta-reproducer?

445
devel-docs/architecture.rst Normal file
View File

@@ -0,0 +1,445 @@
Architecture
============
This document roughly describes the architecture of librsvg, and future
plans for it. The code is continually evolving, so dont consider this
as the ground truth, but rather like a cheap map you buy at a street
corner.
The librarys internals are documented as Rust documentation comments;
you can look at the rendered version at
https://gnome.pages.gitlab.gnome.org/librsvg/internals/rsvg/index.html
You may also want to see the section below on
:ref:`some_interesting_parts_of_the_code`.
A bit of history
----------------
Librsvg is an old library. It started around 2001, when Eazel (the
original makers of GNOMEs file manager, Nautilus) needed a library to
render SVG images. At that time the SVG format was being standardized,
so librsvg grew along with the SVG specification. This is why you will
sometimes see references to deprecated SVG features in the source code.
Librsvg started as an experiment to use libxml2s new SAX parser, so
that SVG could be streamed in and rendered on the fly, instead of first
creating a DOM tree. Originally it used
`libart <https://levien.com/libart/>`__ as a rendering library; this was
GNOMEs first antialiased renderer with alpha compositing. Later, the
renderer was replaced with `Cairo <https://www.cairographics.org/>`__.
Librsvg is currently striving to support other rendering backends.
These days librsvg indeed builds a DOM tree by itself; it needs the
tree to run the CSS cascade, do selector matching, and to support
cross-element references like in SVG filters.
Librsvg started as a C library with an ad-hoc API. At some point it
got turned into a GObject library, so that the main
:internals:struct:`librsvg_c::handle::RsvgHandle`
class defines most of the entry points into the library. Through
`GObject Introspection <https://gi.readthedocs.io/en/latest/>`__, this
allows librsvg to be used from other programming languages.
In 2016, librsvg started getting ported to Rust. As of early 2021, the
whole library is implemented in Rust, and exports an intact C API/ABI.
It also exports a more idiomatic Rust API as well.
The C and Rust APIs
-------------------
Librsvg exports two public APIs, one for C and one for Rust.
The C API has hard requirements for API/ABI stability, because it is
used all over the GNOME project and API/ABI breaks would be highly
disruptive. Also, the C API is what allows librsvg to be called from
other programming languages, through `GObject
Introspection <https://gi.readthedocs.io/en/latest/>`__.
The Rust API is a bit more lax in its API stability, but we try to stick
to `semantic versioning <https://semver.org/>`__ as is common in Rust.
The public Rust API is implemented in :source:`rsvg/src/api.rs`. This
has all the primitives needed to load and render SVG documents or
individual elements, and to configure loading/rendering options.
The public C API is implemented in :source:`librsvg-c/src/`, and
it is implemented in terms of the public Rust API. Note that as of
2021/Feb the corresponding C header files are hand-written in
:source:`include/librsvg/`; maybe in the future they will be generated
automatically with
`cbindgen <https://github.com/mozilla/cbindgen/blob/master/docs.md>`__.
We consider it good practice to provide simple and clean primitives in
the Rust API, and have ``librsvg-c`` deal with all the idiosyncrasies and
historical considerations for the C API.
In short: the public C API calls the public Rust API, and the public
Rust API calls into the library's internals.
::
+----------------+
| Public C API |
| librsvg-c/src |
+----------------+
|
calls
|
v
+-------------------+
| Public Rust API |
| rsvg/src/api.rs |
+-------------------+
|
calls
|
v
+-------------------+
| library internals |
| rsvg/src/*.rs |
+-------------------+
The test suite
--------------
The test suite is documented in :source:`rsvg/tests/README.md`.
Code flow
---------
The caller of librsvg loads a document into a handle, and later may ask
to render the document or one of its elements, or measure their
geometries.
Loading an SVG document
~~~~~~~~~~~~~~~~~~~~~~~
The Rust API starts by constructing an :internals:struct:`rsvg::api::SvgHandle`
from a :internals:struct:`rsvg::api::Loader`; both of those are public types.
Internally, the ``SvgHandle`` is just a wrapper around a
:internals:struct:`rsvg::document::Document`, which is a private type that
stores an SVG document loaded in memory.
``SvgHandle`` and its companion :internals:struct:`rsvg::api::CairoRenderer`
provide the basic primitive operations like “render the whole
document” or “compute the geometry of an element” that are needed to
implement the public APIs.
A ``Document`` gets created by loading XML from a stream, into a tree of
:internals:type:`rsvg::node::Node` structures. This is similar to a web
browsers DOM tree. ``Node`` is just a type alias for
``rctree::Node<NodeData>``: an ``rctree`` is an N-ary tree of reference-counted
nodes, and :internals:enum:`rsvg::node::NodeData` is the enum that librsvg uses
to represent either XML element nodes, or text nodes in the XML.
Each XML element causes a new ``Node`` to get created with a
``NodeData::Element(e)``. The ``e`` is an
:internals:struct:`rsvg::element::Element`, which is a struct that holds an XML
element's name and its attributes. It also contains an
:internals:struct-field:`~rsvg::element::Element::element_data` field, which is
an :internals:enum:`rsvg::element::ElementData`: an enum that can represent all
the SVG element types. For example, a ``<path>`` element from XML gets turned
into a ``NodeData::Element(e)`` that has its ``element_data`` set to
:internals:enum-variant:`rsvg::element::ElementData::Path`.
When an ``Element`` is created from its corresponding XML, its
:internals:struct:`rsvg::xml::attributes::Attributes` get parsed. On one hand,
attributes that are specific to a particular element type, like the ``d`` in
``<path d="...">`` get parsed by the
:internals:trait-method:`~rsvg::element::ElementTrait::set_attributes` method
of each particular element type (in that case,
:internals:struct-method:`rsvg::shapes::Path::set_attributes`).
On the other hand, attributes that refer to styles, and which may
appear for any kind of element, get all parsed into a
:internals:struct:`rsvg::properties::SpecifiedValues` struct.
This is a memory-efficient representation of the CSS style properties that
an element has.
When the XML document is fully parsed, a ``Document`` contains a tree of
``Node`` structs and their inner ``Element`` structs. The tree has also
been validated to ensure that the root is an ``<svg>`` element.
After that, the CSS cascade step gets run.
The CSS cascade
~~~~~~~~~~~~~~~
Each ``Element`` has a :internals:struct:`rsvg::properties::SpecifiedValues`,
which has the CSS style properties that the XML specified for that
element. However, ``SpecifiedValues`` is sparse, as not all the
possible style properties may have been filled in. Cascading means
following the CSS/SVG rules for each property type to inherit missing
properties from parent elements. For example, in this document
fragment:
::
<g stroke-width="2" stroke="black">
<path d="M0,0 L10,0" fill="blue"/>
<path d="M20,0 L30,0" fill="green"/>
</g>
Each ``<path>`` element has a different fill color, but they both
*inherit* the ``stroke-width`` and ``stroke`` values from their parent
group. This is because both the ``stroke-width`` and ``stroke``
properties are defined in the CSS/SVG specifications to inherit
automatically. Some other properties, like ``opacity``, do not inherit
and are thus not copied to child elements.
In librsvg, the individual types for CSS properties are defined with
the ``make_property`` macro.
The cascading step takes each elements ``SpecifiedValues`` and
composes it by CSS inheritance onto a
:internals:struct:`rsvg::properties::ComputedValues`,
which has the result of the cascade for each element's properties.
When cascading is done, each ``Element`` has a fully resolved
``ComputedValues`` struct, which is what gets used during rendering to
look up things like the elements stroke width or fill color.
Parsing XML into a tree of Nodes / Elements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Librsvg uses an XML parser (`libxml2
<https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home>`_ at the time of
this writing) to do the first-stage parsing of the SVG
document. :internals:struct:`rsvg::xml::XmlState`
contains the XML parsing state, which is a stack of contexts depending
on the XML nesting structure. ``XmlState`` has public methods, called
from the XML parser as it goes. The most important one is
:internals:struct-method:`~rsvg::xml::XmlState::start_element`;
this is responsible for creating new ``Node`` structures in the tree,
within the :internals:struct:`rsvg::document::DocumentBuilder`
being built.
Nodes are either SVG elements (the :internals:struct:`rsvg::element::Element`
struct), or text data inside elements (the
:internals:struct:`rsvg::text::Chars` struct); this last one will not concern
us here, and we will only talk about ``Element``.
Each supported kind of ``Element`` parses its attributes in a
:internals:trait-method:`~rsvg::element::ElementTrait::set_attributes`
method. Each attribute is just a key/value pair; for example, the
``<rect width="5px">`` element has a ``width`` attribute whose value
is ``5px``.
While parsing its attributes, an element may encounter an invalid value,
for example, a negative width where only nonnegative ones are allowed.
In this case, the elements ``set_attributes`` method may return a
``Result::Err``. The caller will then do ``set_error`` to mark that
element as being in an error state. If an element is in error, its
children will get parsed as usual, but the element and its children will
be ignored during the rendering stage.
The SVG spec says that SVG rendering should stop on the first element
that is “in error”. However, most implementations simply seem to ignore
erroneous elements instead of completely stopping rendering, and we do
the same in librsvg.
CSS and styles
~~~~~~~~~~~~~~
Librsvg uses Servos `cssparser <https://crates.io/crates/cssparser>`_
crate as a CSS tokenizer, and `selectors
<https://crates.io/crates/selectors>`_ as a high-level parser for CSS
style data.
With the ``cssparser`` crate, the caller is responsible for providing
an implementation of the `DeclarationParser
<https://docs.rs/cssparser/0.29.6/cssparser/trait.DeclarationParser.html>`_
trait. Its `parse_value
<https://docs.rs/cssparser/0.29.6/cssparser/trait.DeclarationParser.html#tymethod.parse_value>`_
method takes the name of a CSS property like ``fill``, plus a
value like ``rgb(255, 0, 0)``, and it must return a value that
represents a parsed declaration. Librsvg uses the
:internals:struct:`rsvg::css::Declaration` struct for this.
The core of parsing CSS is the ``parse_value`` function, which returns
a :internals:enum:`rsvg::properties::ParsedProperty`:
.. code:: rust
pub enum ParsedProperty {
BaselineShift(SpecifiedValue<BaselineShift>),
ClipPath(SpecifiedValue<ClipPath>),
Color(SpecifiedValue<Color>),
// etc.
}
What is :internals:enum:`rsvg::properties::SpecifiedValue`?
It is the parsed value for a CSS property directly as it comes out of
the SVG document:
.. code:: rust
pub enum SpecifiedValue<T>
where
T: Property + Clone + Default,
{
Unspecified,
Inherit,
Specified(T),
}
A property declaration can look like ``opacity: inherit;`` - this would
create a ``ParsedProperty::Opacity(SpecifiedValue::Inherit)``.
Or it can look like ``opacity: 0.5;`` - this would create a
``ParsedProperty::Opacity(SpecifiedValue::Specified(Opacity(UnitInterval(0.5))))``.
Lets break this down:
- :internals:enum-variant:`rsvg::properties::ParsedProperty::Opacity` -
which property did we parse?
- :internals:enum-variant:`rsvg::properties::SpecifiedValue::Specified` -
it actually was specified by the document with a value; the other
interesting alternative is
:internals:enum-variant:`~rsvg::properties::SpecifiedValue::Inherit`,
which corresponds to the value ``inherit`` that all CSS property
declarations can have.
- ``Opacity(UnitInterval(0.5))`` - This is the type
:internals:struct:`rsvg::property_defs::Opacity`
property, which is a newtype around an internal
:internals:struct:`rsvg::unit_interval::UnitInterval` type, which in
turn guarantees that we have a float in the range ``[0.0, 1.0]``.
There is a Rust type for every CSS property that librsvg supports; many
of these types are newtypes around primitive types like ``f64``.
Eventually an entire CSS stylesheet, like the contents of a ``<style>``
element, gets parsed into a :internals:struct:`rsvg::css::Stylesheet`
struct. A stylesheet has a list of rules, where each rule is the CSS
selectors defined for it, and the style declarations that should be
applied for the ``Node``\ s that match the selectors. For example, in
a little stylesheet like this:
.. code:: xml
<style type="text/css">
rect, #some_id {
fill: blue;
stroke-width: 5px;
}
</style>
This stylesheet has a single rule. The rule has a selector list with two
selectors (``rect`` and ``#some_id``) and two style declarations
(``fill: blue`` and ``stroke-width: 5px``).
After parsing is done, there is a **cascading stage** where librsvg
walks the tree of nodes, and for each node it finds the CSS rules that
should be applied to it.
Rendering
---------
The rendering process starts at the
:internals:fn:`rsvg::drawing_ctx::draw_tree` function. This sets up a
:internals:struct:`rsvg::drawing_ctx::DrawingCtx`,
which carries around all the mutable state during rendering.
Rendering is a recursive process, which goes back and forth between
the utility functions in ``DrawingCtx`` and the
:internals:trait-method:`~rsvg::element::ElementTrait::draw`
method in elements.
The main job of ``DrawingCtx`` is to deal with the SVG drawing model.
Each element renders itself independently, and its result gets modified
before getting composited onto the final image:
1. Render an element to a temporary surface (example: stroke and fill a
path).
2. Apply filter effects (blur, color mapping, etc.).
3. Apply clipping paths.
4. Apply masks.
5. Composite the result onto the final image.
The temporary result from the last step also gets put in a stack; this
is because filter effects sometimes need to look at the currently-drawn
background to apply further filtering to it.
Youll see that most of the rendering-related functions return a
``Result<BoundingBox, RenderingError>``. Some SVG features require
knowing the bounding box of the object that is being rendered; for
historical reasons this bounding box is computed as part of the
rendering process in librsvg. When computing a subtrees bounding box,
the bounding boxes from the leaves get aggregated up to the root of
the subtree. Each node in the tree has its own coordinate system;
:internals:struct:`rsvg::bbox::BoundingBox`
is able to transform coordinate systems to get a bounding box that is
meaningful with respect to the roots transform.
Comparing floating-point numbers
--------------------------------
Librsvg sometimes needs to compute things like “are these points equal?”
or “did this computed result equal this test reference number?”.
We use ``f64`` numbers in Rust for all computations on real numbers.
Floating-point numbers cannot be compared with ``==`` effectively, since
it doesnt work when the numbers are slightly different due to numerical
inaccuracies.
Similarly, we dont ``assert_eq!(a, b)`` for floating-point numbers.
Most of the time we are dealing with coordinates which will get passed
to Cairo. In turn, Cairo converts them from doubles to a fixed-point
representation (as of March 2018, Cairo uses 24.8 fixnums with 24 bits
of integral part and 8 bits of fractional part).
So, we can consider two numbers to be “equal” if they would be
represented as the same fixed-point value by Cairo. Librsvg implements
this in the :internals:trait:`rsvg::float_eq_cairo::ApproxEqCairo`
trait. You can use it like this:
.. code:: rust
use float_eq_cairo::ApproxEqCairo; // bring the trait into scope
let a: f64 = ...;
let b: f64 = ...;
if a.approx_eq_cairo(&b) { // not a == b
... // equal!
}
assert!(1.0_f64.approx_eq_cairo(&1.001953125_f64)); // 1 + 1/512 - cairo rounds to 1
.. _some_interesting_parts_of_the_code:
Some interesting parts of the code
----------------------------------
- Are you adding support for a CSS property? Look at the
:doc:`adding_a_property` tutorial; look in the
:internals:module:`rsvg::property_defs`
and :internals:module:`rsvg::properties`
modules. ``property_defs`` defines most of the CSS properties that
librsvg supports, and ``properties`` actually puts all those
properties in the :internals:struct:`rsvg::properties::SpecifiedValues`
and :internals:struct:`rsvg::properties::ComputedValues` structs.
- The :internals:struct:`rsvg::drawing_ctx::DrawingCtx`
struct is active while an SVG handle is being drawn. It has all the
mutable state related to the drawing process, such as the stack of
temporary rendered surfaces, and the viewport stack.
- The :internals:struct:`rsvg::document::Document`
struct represents a loaded SVG document. It holds the tree of
:internals:type:`rsvg::node::Node` structs, some of which contain
:internals:struct:`rsvg::element::Element` and some others contain
:internals:struct:`rsvg::text::Chars` for text data in the XML.
A ``Document`` also contains a mapping of ``id`` attributes to the
corresponding element nodes.
- The :internals:module:`rsvg::xml` module receives events from an XML
parser, and builds a ``Document`` tree.
- The :internals:module:`rsvg::css`
module has the high-level machinery for parsing CSS and representing
parsed stylesheets. The low-level parsers for individual properties
are in :internals:module:`rsvg::property_defs` and
:internals:module:`rsvg::font_props`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<text style="font: 50px Ahem;" x="50%" y="50%" fill="black">A</text>
<g stroke-width="2" stroke="red">
<line x1="0" y1="50%" x2="100%" y2="50%"/>
<line x1="50%" y1="0" x2="50%" y2="100%"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<text style="font: 50px Ahem;" text-anchor="middle" x="50%" y="50%">
<tspan fill="lime">A</tspan><tspan fill="blue">B</tspan>
</text>
<g stroke-width="2" stroke="red">
<line x1="0" y1="50%" x2="100%" y2="50%"/>
<line x1="50%" y1="0" x2="50%" y2="100%"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<text style="font: 50px Ahem;" text-anchor="middle" x="50%" y="50%" fill="black">AB</text>
<g stroke-width="2" stroke="red">
<line x1="0" y1="50%" x2="100%" y2="50%"/>
<line x1="50%" y1="0" x2="50%" y2="100%"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300">
<text style="font: 40px Ahem;" x="50%" y="50%">ApÉ</text>
<g stroke-width="2" stroke="red">
<line x1="0" y1="50%" x2="100%" y2="50%"/>
<line x1="50%" y1="0" x2="50%" y2="100%"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<text style="font: 50px Ahem;" text-anchor="middle" x="50%" y="50%" direction="rtl">
<tspan fill="red">R</tspan><tspan fill="green">G</tspan><tspan fill="blue">B</tspan>
</text>
<g stroke-width="2" stroke="red">
<line x1="0" y1="50%" x2="100%" y2="50%"/>
<line x1="50%" y1="0" x2="50%" y2="100%"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

50
devel-docs/bugs.rst Normal file
View File

@@ -0,0 +1,50 @@
Reporting bugs
==============
Please report bugs at https://gitlab.gnome.org/GNOME/librsvg/-/issues
If you want to report a rendering bug, or a missing SVG feature,
please provide an example SVG file as an attachment to your bug
report. It really helps if you can minimize the SVG to only the
elements required to reproduce the bug or see the missing feature, but
it is not absolutely required. **Please be careful** of publishing
SVG images that you don't want other people to see, or images whose
copyright does not allow redistribution; the bug tracker is a public
resource and attachments are visible to everyone.
Feature requests
----------------
Librsvg aims to be a small and relatively simple SVG rendering
library. Currently we do not plan to support scripting, animation, or
interactive features like mouse events on SVG elements.
However, we *do* aim to provide good support for SVG's graphical
features. You can request new features by filing a bug report as
noted above.
It is especially helpful if you file a feature request along with a
sample SVG file that requires the feature. For example, a file that
uses an SVG element or CSS property that librsvg does not yet support.
Obtaining debug logs
--------------------
Librsvg can be asked to output debug logs. Set the ``RSVG_LOG``
environment variable, and then librsvg will print some
information to stdout:
.. code-block::
$ RSVG_LOG=1 some-program-that-uses-librsvg
... debug output goes here ...
As of librsvg 2.43.5, there are no options you can set in the
``RSVG_LOG`` variable; the library just checks whether that environment
variable is present or not.
Security bugs
-------------
For especially sensitive bugs, please see :doc:`security`.

View File

@@ -0,0 +1,155 @@
Building dependencies in CI
===========================
**Status as of 2024/Sep/11:** implemented in :pr:`896`.
Non-Rust dependencies are built from git tags and installed in the CI
container image. See the ``ci/build-dependencies.sh`` script that
does this.
Preamble
--------
Until Sep/2023, librsvg's CI has worked by building a container image
out of a snapshot of openSUSE Tumbleweed, a rolling release, that has
all of librsvg's dependencies in it. What this means for runtime
dependencies like Cairo, Pango, etc. is that they come in a
"reasonably recent" version, but they are not pinned, and can change
any time that the rolling release decides to update them.
This is not entirely terrible: librsvg's run-time dependencies are all
stable, mature libraries that don't change very much. From librsvg's
viewpoint, the only trouble comes in situations like these:
* A library involved in text rendering changes something, and text
output changes slightly. The test suite breaks as a result, since
it assumes exact rendering based on reference images.
* Less often, a library involved in Bézier path rendering changes
something, and parts of the test suite break because antialiasing is
not the same as before.
* Someone runs librsvg's test suite with a different set of dependency
versions than the test suite assumes; a bunch of tests fail for
them, and they file bugs that are not very useful. In the best
case, they are using newer libraries and the bug reports let me (the
maintainer) know that I'll need to re-generate the test suite's
images soon. In the worst case, I just close those bugs since they
don't provide useful information.
And probably the biggest problem of all: I am never sure of exactly
what set of versions are okay for the test suite to work; I just
regenerate test reference files when they break after a CI update.
Pinning dependency versions
---------------------------
Librsvg's CI needs to be able to build the library's dependencies in a
custom fashion, without necessarily assuming that the dependencies
come from system libraries. If we have that ability, then we can do a
few interesting things:
* Pin the versions of dependencies to a particular set, for example,
one that corresponds to a certain GNOME release. This is important
to keep CI working for old branches, so security patches are easy to
build.
* Compile the dependencies with a particular set of compiler options.
For example, for fuzzing, dependencies should be built with
sanitizers like asan/ubsan. For deep debugging, it would be nice to
have all the dependencies built in debug mode. For performance
testing, compile all the dependencies in release mode, etc.
Which dependencies? These ones; this list is already sorted in the
correct build order:
* glib
* gobject-introspection
* freetype2
* fontconfig
* cairo
* harfbuzz
* pango
* libxml2
* gdk-pixbuf
How do we achieve that?
-----------------------
**Option 1:** There is a script ``ci/build-dependencies.sh`` that can
already build librsvg's dependencies pinned to particular git tags.
In theory one can pass environment variables to change compiler
options; the script may need some tweaks to change the meson or
autotools invocations. The script builds and installs the
dependencies to a given prefix, and assumes that
``PATH/LD_LIBRARY_PATH/PKG_CONFIG_PATH`` are tweaked to use things
from that prefix.
**Option 2:** Alternatively, we can outsource the problem and use
GNOME's BuildStream images. These correspond to specific releases of
the GNOME platform libraries, including librsvg's dependencies... and
librsvg itself, as it *is* a platform library. One must be a little
careful to keep the test suite from using the "system's" librsvg in
that case, but this is not a huge problem.
Implementation notes - building dependencies explicitly
-------------------------------------------------------
As of Sep/2023 the CI builds three main images:
* An image with the MSRV, just used to test the promise that the MSRV
can still build the library and its Rust dependencies.
* An image with a recent, stable Rust compiler - the main image used
for most jobs, and what I use for local development.
* An image with the nightly compiler. This is not updated frequently
since the whole CI doesn't regenerate its images nightly.
* (Other images for other architectures or distros, not in scope for
this discussion.)
All images have whatever RPM versions are available in openSUSE for
librsvg's runtime dependencies.
Proposed change:
* Keep a single image, pre-populated with ``rustup toolchain install
<version>`` for the MSRV, the stable compiler, and nightly. Select
the compiler version on a per-job basis; hopefully this will be fast
since ``toolchain`` should cache them.
* In that single image, keep pre-built sets of runtime dependencies
(e.g. libraries built from git tags) in at least two configurations:
the minimum supported versions, and the latest stable ones. These
configurations can live in different prefixes,
e.g. ``/usr/local/librsvg-minimum`` and
``/usr/local/librsvg-stable``.
The idea is to ensure that the minimum-supported everything (rustc and
dependencies) actually works, in addition to testing the recent-stable
stuff.
Keeping everything in a single container image (versions of the Rust
toolchain, and sets of dependencies installed to different prefixes)
is just an optimization to build and maintain a single image, instead
of the three we have right now. If we determine that selecting
rustc/deps makes jobs too slow, we can go back to producing multiple
images.
Implementation note - BuildStream to test nightly dependencies
--------------------------------------------------------------
FIXME: alatiera's work goes here.
Other architectures, other distros
----------------------------------
I'd like to keep an ``aarch64`` image; it has let us notice
peculiarities like the different signedness of ``libc::c_char``. It
doesn't need the minimum/stable/nightly distinction; we can keep it
stable-only as currently.
I think we can drop the Fedora image. It still runs Fedora 36, is
seldom updated, and I don't pay attention to it. I'd rather make it
possible to have explicit git versions of dependencies.

225
devel-docs/ci.rst Normal file
View File

@@ -0,0 +1,225 @@
Continuous Integration
======================
Or, when robots are eager to help.
Librsvg's repository on gitlab.gnome.org is configured to use a
Continuous Integration (CI) pipeline, so that it compiles the code and
runs the test suite after every ``git push``.
If you have never read it before, please read `The Not Rocket Science
Rule of Software Engineering
<https://graydon2.dreamwidth.org/1597.html>`_, about automatically
maintaining a repository of code that always passes all the tests.
This is what librsvg tries to do!
In addition to running the test suite, the CI pipeline does other cool
things. The pipeline is divided into *stages*. Here is roughly what
they do:
- First, set up a *reproducible environment* to build and test things:
this builds a couple of container images and automatically updates
them in gitlab. The container images have all of librsvg's
dependencies, and all the tools required for compilation, building
the documentation, and casual debugging.
- Then, run a quick ``cargo check`` ("does this have a chance of
compiling?"), and ``cargo test`` (run a fast subset of the test
suite). This stage is intended to catch breakage early.
- Then, run the full test suite in a couple different configurations:
different versions of Rust, different distros with slightly
different versions of dependencies. This stage is intended to catch
common sources of breakage across environments.
- In parallel with the above, run ``cargo clippy`` and ``cargo fmt``.
The first usually has good suggestions to improve the code; the latter
is to guarantee a consistent indentation style.
- In parallel, obtain a test coverage report. We'll talk about this below.
- Check whether making a release at this point would actually work:
this builds a release tarball and tries to compile it and run the
test suite again.
- Finally, generate documentation: reference docs for the C and Rust
APIs, and the rendered version of this development guide. Publish
the docs and coverage report to a web page.
We'll explain each stage in detail next.
Creating a reproducible environment
-----------------------------------
The task of setting up CI for a particular distro or build
configuration is rather repetitive. One has to start with a "bare"
distro image, then install the build-time dependencies that your
project requires, then that is slow, then you want to build a
container image instead of installing packages every time, then you
want to test another distro, then you want to make those container
images easily available to your project's forks, and then you start
pulling your hair.
`Fredesktop CI Templates
<https://gitlab.freedesktop.org/freedesktop/ci-templates/>`_
(`documentation
<https://freedesktop.pages.freedesktop.org/ci-templates/>`_) are a
solution to this. They can automatically build container images for
various distros, make them available to forks of your project, and
have some nice amenities to reduce the maintenance burden.
Librsvg uses CI templates to test its various build configurations.
The container images are stored here:
https://gitlab.gnome.org/GNOME/librsvg/container_registry
See the section below on the "Full test suite and different
environments" for details on what gets tested on the different
container images produced by this stage.
.. NOTE: The target below is used outside this development guide.
.. _container-image-version:
.. important::
Whenever changes are made to the CI environments (such as updating
dependencies or CI tools, or their versions), the container image
version tag defined as ``BASE_TAG`` in :source:`ci/container_builds.yml`
should be incremented appropriately.
The tag name is (by convention) of the format
.. code::
<date>.<version>-[<user_name>_]<branch_name>
where:
- *date* is the current date when incrementing the version tag and is
of the format ``YYYY-MM-DD``.
- *version* is an index number (starting from zero) to differentiate
images built on the same day for the same branch.
- *branch_name* is the name of the branch on which the CI changes are
being made.
- *user_name* is the user name of the branch's owner and is optional
but recommended for branches on forks, in order to avoid tag name
clashes with equally-named branches on other forks.
For example:
- ``2024-10-20.0-main`` ->
first iteration for ``GNOME/librsvg:main`` on 2024-10-20
- ``2025-09-01.1-foo_bar`` ->
second iteration for ``GNOME/librsvg:foo-bar`` on 2025-09-01
- ``2099-12-31.3-federico_bar_foo`` ->
third iteration for ``federico/librsvg:bar-foo`` on 2099-12-31
For any branch that is **not** :source:`GNOME/librsvg:main <main:>` and
is intended **to be merged** into it: Once the new container images are
confirmed to be working as expected, the version tag should be
incremented again **before merging** into ``main``, this time without
*user_name* and with ``main`` as *branch_name*.
This is always best done **when the branch is ready to be merged**, in
order to avoid unnecessary tag name conflicts and CI surprises.
In the case of conflicting tag names with ``main``, be sure to increment
the *version* number if the tag name on ``main`` has the current date.
If unsure whether a change you made is concerned or for any further
clarification, please ask the
:source:`maintainers <README.md#maintainers>`.
Quick checks
------------
``cargo check`` and ``cargo test`` run relatively quickly, and can catch
trivial compilation problems as well as breakage in the "fast" section
of the test suite. When trying out things in a branch or a merge
request, you can generally look at only these two jobs for a fast
feedback loop.
Full test suite and different environments
------------------------------------------
- The "full test suite" in principle runs ``meson test``.
This runs the "fast" portion of the test suite, but also a few slow
tests which are designed to test librsvg's built-in limits. It also
runs the C API tests, which require a C compiler.
- There are builds use a certain Minimum Supported Rust Version
(MSRV), also a relatively recent stable Rust, and Rust nightly.
Building with the MSRV is to help distros that don't update Rust
super regularly, and also to ensure that librsvg's dependencies do
not suddently start depending on a too-recent Rust version, for
example. Building on nightly is hopefully to catch compiler bugs
early, or to get an early warning when the Rust compiler is about to
introduce newer lints/warnings.
- Build on a couple of distros. Librsvg's test suite is especially
sensitive to changes in rendering from Cairo, Pixman, and the
Pango/Freetype2/Harfbuzz stack. Building on a few distros gives us
slightly different versions of those dependencies, so that we can
catch breakage early.
Lints and formatting
--------------------
There is a job for ``cargo clippy``. Clippy usually has very good
suggestions to improve the coding style, so take advantage of them!
And if Clippy's suggetions don't make sense for a particular portion
of the code, feel free to add exceptions like
``#[allow(clippy::foo_bar)]`` to the corresponding block.
There is a job for ``cargo fmt``. Librsvg uses the default formatting
for Rust code. For portions of code that are more legible if
indented/aligned by hand, please use ``#[rustfmt::skip]``.
One job runs ``cargo deny``, which checks if there are dependencies with
vulnerabilities.
Another job runs a script to check that the version numbers mentioned
in various parts of the source code all match. For example,
``Cargo.toml`` and ``meson.build`` must have checks for the same Minimum
Supported Rust Version (MSRV).
Test coverage report
--------------------
There is a job that generates a `test coverage report
<https://gnome.pages.gitlab.gnome.org/librsvg/coverage/index.html>`_.
The code gets instrumented, and as the test suite runs, the
instrumentation remembers which lines of code were executed and which
ones were not; this then gets presented in an HTML report. This can
be used for various things:
- See which parts of the code are not executed while running the test
suite. Maybe we need to add tests that cause them to run!
- If you disable most of the test suite, you can use the coverage
report to explore which parts of the code get executed with a
particular SVG. This can aid in learning the code base.
Release tests
-------------
There is a job that runs ``meson dist``, a part of Meson that
simulates building a full release tarball. Running this in the CI
helps us guarantee that librsvg is always in a release-worthy state.
Generate documentation
----------------------
The following sets of documentation get generated:
- `C API docs
<https://gnome.pages.gitlab.gnome.org/librsvg/Rsvg-2.0/index.html>`_,
with `gi-docgen <https://gitlab.gnome.org/GNOME/gi-docgen>`_.
- `Rust API docs <https://gnome.pages.gitlab.gnome.org/librsvg/doc/rsvg/index.html>`_, with ``cargo doc``.
- `Internals docs <https://gnome.pages.gitlab.gnome.org/librsvg/internals/rsvg/index.html>`_, with ``cargo doc --document-private-items``.
- `This development guide <https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/index.html>`_, with ``sphinx``.

125
devel-docs/compiling.rst Normal file
View File

@@ -0,0 +1,125 @@
Detailed compilation instructions
=================================
A full build of librsvg requires the
`meson build system <https://mesonbuild.com>`_. A full build will
produce these artifacts (see :doc:`product` for details):
- ``rsvg-convert`` binary and its ``man`` page.
- librsvg shared library with the GObject-based API.
- GDK-Pixbuf loader for SVG files.
- HTML documentation for the GObject-based API, with ``gi-docgen``.
- GObject-introspection information for language bindings.
- Vala language bindings.
Some of the artifacts above are optional; please see the section
:ref:`compile_time_options` below for details.
It is perfectly fine to :source:`ask the maintainer <README.md#maintainers>`
if you have questions about the meson setup; its a tricky bit of
machinery, and we are glad to help.
The rest of this document explains librsvgs peculiarities apart from
the usual way of compiling meson projects.
.. _build_time_dependencies:
Build-time dependencies
-----------------------
.. include:: _build_dependencies.rst
See :doc:`devel_environment` for details on how to install these dependencies.
.. _basic_compilation_instructions:
Basic compilation instructions
------------------------------
If you are compiling a tarball:
.. code:: sh
mkdir -p _build
meson setup _build -Ddocs=enabled -Dintrospection=enabled -Dvala=enabled
meson compile -C _build
meson install -C _build
The options that start with ``-D`` are listed in the
``meson_options.txt`` file and are described in the next section.
.. _compile_time_options:
Compile-time options
--------------------
These are invoked during ``meson setup`` as ``-Doption_name=value``.
See `meson's documentation on using build-time options
<https://mesonbuild.com/Build-options.html>`_ for details.
These are librsvg's options:
* ``introspection`` - Specifies whether the build will generate
`GObject Introspection <https://gi.readthedocs.io/en/latest/>`_
information for language bindings. Values are
``enabled``/``disabled``/``auto``.
* ``pixbuf`` - Specifies whether to build with support for `gdk-pixbuf
<https://docs.gtk.org/gdk-pixbuf/>`_ in the library APIs.
Values are ``enabled``/``disabled``/``auto``.
* ``pixbuf-loader`` - Specifies whether to build a `gdk-pixbuf
<https://docs.gtk.org/gdk-pixbuf/>`_ module to let applications which use
gdk-pixbuf load and render SVG files as if they were raster images.
Values are ``enabled``/``disabled``/``auto``.
* ``rsvg-convert`` - Specifies whether to build the `rsvg-convert`
binary. You can disable this if you just need the library to link
into other programs. Values are ``enabled``/``disabled``/``auto``.
* ``docs`` - Specifies whether the C API reference and the
rsvg-convert manual page should be built. These require ``gi-docgen
<https://gnome.pages.gitlab.gnome.org/gi-docgen/>`_ and ``rst2man``
from Python's `docutils <https://www.docutils.org/>`_, respectively.
Values are ``enabled``/``disabled``/``auto``.
* ``vala`` - Specifies whether a `Vala <https://vala.dev/>`_ language
binding should be built. Requires the Vala compiler to be
installed. Values are ``enabled``/``disabled``/``auto``.
* ``tests`` - Specifies whether the test suite should be built.
Value is a boolean that defaults to ``true``.
* ``triplet`` - Specifies the `Rust target triplet
<https://doc.rust-lang.org/stable/rustc/platform-support.html>`_;
only needed for cross-compilation. Value is a string.
* ``avif`` - Specifies whether the image-rs crate, which librsvg uses
to load raster images, should be built with support for the AVIF
format. Requires the `dav1d
<https://code.videolan.org/videolan/dav1d>`_ library. Values are
``enabled``/``disabled``/``auto``.
* ``rustc-version`` - Specifies the ``rustc`` version to use; only
supported on Windows. Value is a string.
.. _building_with_no_network_access:
Building with no network access
-------------------------------
Automated build systems generally avoid network access so that they can
compile from known-good sources, instead of pulling random updates from
the net every time. However, normally Cargo likes to download
dependencies when it first compiles a Rust project.
You can use `cargo vendor
<https://doc.rust-lang.org/cargo/commands/cargo-vendor.html>`_ to
download librsvg's Rust dependencies ahead of time, so that subsequent
compilation don't require network access.
Build systems can use `Cargos source replacement
mechanism <https://doc.rust-lang.org/cargo/reference/source-replacement.html>`_ to override
the location of the source code for the Rust dependencies, for example,
in order to patch one of the Rust crates that librsvg uses internally.

100
devel-docs/conf.py Normal file
View File

@@ -0,0 +1,100 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
import os
import sys
sys.path.insert(0, os.path.abspath('_extensions'))
# -- Project information -----------------------------------------------------
project = 'Development guide for librsvg'
copyright = '2022, Federico Mena Quintero'
author = 'Federico Mena Quintero'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
# Used to shorten external links.
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
"sphinx.ext.extlinks",
# Used to link issues, merge requests, CVEs, etc.
# https://github.com/sloria/sphinx-issues
"sphinx_issues",
# Used to reference entities in the internals documentation.
# ./_extensions/internals.py
"internals",
# Used to reference entries in the source tree.
# ./_extensions/source.py
"source",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'furo'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Options for the linkcheck builder. This is used by the ci/check_docs_links.sh script.
#
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder
linkcheck_ignore = [
# These URLs fail for some reason, but work in the browser.
r'https://crates.io/crates/.*',
# Links with anchors for section names or line numbers fail. But since we usually
# use specific commit ids, they should be correct anyway.
r'https://github.com/.*#.*',
r'https://gitlab.gnome.org/.*#.*',
r'https://gitlab.freedesktop.org/.*#.*',
]
# Options for the `sphinx.ext.extlinks` extension. See `extensions` above.
extlinks = {
"rustsec": ("https://rustsec.org/advisories/RUSTSEC-%s", "RUSTSEC-%s"),
}
extlinks_detect_hardcoded_links = True
# Options for the `sphinx-issues` extension. See `extensions` above.
issues_default_group_project = "GNOME/librsvg"
issues_uri = "https://gitlab.gnome.org/{group}/{project}/-/issues/{issue}"
issues_prefix = "#"
issues_pr_uri = "https://gitlab.gnome.org/{group}/{project}/-/merge_requests/{pr}"
issues_pr_prefix = "!"
issues_commit_uri = "https://gitlab.gnome.org/{group}/{project}/-/commit/{commit}"
issues_commit_prefix = "@"
issues_user_uri = "https://gitlab.gnome.org/{user}"
issues_user_prefix = "@"

222
devel-docs/contributing.rst Normal file
View File

@@ -0,0 +1,222 @@
How to contribute
=================
Thank you for looking in this document! There are different ways of
contributing to librsvg, and we appreciate all of them.
All librsvg contributors are expected to follow `GNOME's Code of
Conduct <https://conduct.gnome.org>`_.
Source repository
-----------------
Librsvgs main source repository is at ``gitlab.gnome.org``. You can view
the web interface here:
https://gitlab.gnome.org/GNOME/librsvg
Development happens in the ``main`` branch. There are also branches for
stable releases.
Alternatively, you can use the mirror at GitHub:
https://github.com/GNOME/librsvg
Note that we dont do bug tracking in the GitHub mirror; see the next
section.
If you need to publish a branch, feel free to do it at any
publicly-accessible Git hosting service, although ``gitlab.gnome.org``
makes things easier for the maintainers of librsvg.
Hacking on librsvg
------------------
See the rest of this development guide, especially the chapter on
:doc:`architecture`, and the tutorial on :doc:`adding_a_property`.
The librarys internals are being documented at
https://gnome.pages.gitlab.gnome.org/librsvg/internals/rsvg/index.html
What can you hack on?
- `Bugs for
newcomers <https://gitlab.gnome.org/GNOME/librsvg/-/issues?label_name%5B%5D=4.+Newcomers>`__.
- `Bugs for intermediate developers, once you are a bit familiar with
the code
<https://gitlab.gnome.org/GNOME/librsvg/-/issues/?label_name%5B%5D=Intermediate>`__.
- Pick something from the `development
roadmap <https://gnome.pages.gitlab.gnome.org/librsvg/devel-docs/roadmap.html>`__.
- Tackle one of the `bigger projects
<https://gitlab.gnome.org/GNOME/librsvg/-/issues/?label_name%5B%5D=project>`_.
Working on the source
~~~~~~~~~~~~~~~~~~~~~
Make sure you have read the chapter on :doc:`devel_environment`.
A typical development cycle goes like this:
- Make some changes to the code; hopefully adding a test first.
- Build and run the tests. Use ``cargo test --workspace``.
- Repeat until you and the tests are happy.
Most of the time you can just work on the Rust source code and use
``cargo test --workspace``. This will run the Rust-based tests, which
are usually enough to test bug fixes in the SVG rendering code.
However, sometimes you'll want to do a full build and run the full
test suite, which includes the tests for the C API.
To do a full build, you can use something like this:
.. code-block:: sh
mkdir -p _build
meson setup _build -Ddocs=enabled -Dintrospection=enabled -Dvala=enabled
meson compile -C _build
meson test -C _build
Alternatively, you can make a merge request and wait for the
Continuous Integration machinery (CI) do the full build for you. The
CI will run all the tests on multiple platforms.
For a full build, librsvg uses the `Meson <https://mesonbuild.com>`_
build system. If you need to **add a new source file**, you need to
do it in ``rsvg/meson.build``. This is so that Meson can know when a
Rust file changed so it can call ``cargo`` as appropriate.
It is perfectly fine to ask the maintainer if you have questions about
the Meson setup; its a tricky bit of machinery, and we are glad
to help.
Continuous Integration
~~~~~~~~~~~~~~~~~~~~~~
If you fork librsvg in ``gitlab.gnome.org`` and push commits to your
forked version, the Continuous Integration machinery (CI) will run
automatically.
The CI infrastructure is documented in the :doc:`ci` chapter.
When you create a merge request, or push to a branch in a fork of
librsvg, GitLab's CI will run a *pipeline* on the contents of your
push: it will run the test suite, linters, try to build the
documentation, and generally see if everything that makes
:doc:`product` is working as intended. If any tests fail, the
pipeline will fail and you can then examine the build artifacts of
failed jobs to fix things.
**Automating the code formatting:** You may want to enable a
`client-side git
hook <https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks>`__ to
run ``rustfmt`` before you can commit something; otherwise the ``lint``
stage of CI pipelines will fail:
1. ``cd librsvg``
2. ``mv .git/hooks/pre-commit.sample .git/hooks/pre-commit``
3. Edit ``.git/hooks/pre-commit`` and put in one of the following
commands:
- If you want code reformatted automatically, no questions asked:
``cargo fmt``.
.. note::
If this actually reformats your code while committing, youll
have to re-stage the new changes and ``git commit --amend``.
Be careful if you had unstaged changes that got reformatted!
- If you want to examine errors if rustfmt doesnt like your
indentation, but dont want it to make changes on its own:
``cargo fmt --all -- --check``
Test suite
~~~~~~~~~~
All new features need to have corresponding tests. Please see the
file ``rsvg/tests/README.md`` to see how to add new tests to the test suite. In short:
- Add unit tests in the ``rsvg/src/*.rs`` files for internal things like
parsers or algorithms.
- Add rendering tests in ``rsvg/tests/src/*.rs`` for SVG or CSS features.
See ``rsvg/tests/README.md`` for details on how to do this.
- Tests for the C API go in ``librsvg-c/test-c/*.c``. Note that to
run these tests you must run a full meson build, not just ``cargo
test --workspace``.
- Tests for ``rsvg-convert`` go in ``rsvg_convert/tests/*.rs``.
In most cases, you can run ``cargo test --workspace`` if you set up your
development environment as instructed in the :doc:`devel_environment`
chapter. Alternatively, push your changes to a branch, and watch the
results of its CI pipeline.
Creating a merge request
~~~~~~~~~~~~~~~~~~~~~~~~
You may create a forked version of librsvg in `GNOMEs Gitlab instance
<https://gitlab.gnome.org/GNOME/librsvg>`__,. You can register an
account there, or log in with your account from other OAuth services.
For technical reasons, the maintainers of librsvg do not get
automatically notified if you submit a pull request through the GNOME
mirror in GitHub. In that case, please create a merge request at
``gitlab.gnome.org`` instead; you can ask the maintainer for assistance.
Formatting commit messages
~~~~~~~~~~~~~~~~~~~~~~~~~~
If a commit fixes a bug, please format its commit message like this:
::
(#123): Don't crash when foo is bar
Explanation for why the crash happened, or anything that is not
obvious from looking at the diff.
Fixes https://gitlab.gnome.org/GNOME/librsvg/issues/123
Note the ``(#123)`` in the first line. This is the line that shows up in
single-line git logs, and having the bug number there makes it easier to
write the release notes later — one does not have to read all the commit
messages to find the ids of fixed bugs.
Also, please paste the complete URL to the bug report somewhere in the
commit message, so that its easier to visit when reading the commit
logs.
Generally, commit messages should summarize *what* you did, and *why*.
Think of someone doing ``git blame`` in the future when trying to figure
out how some code works: they will want to see *why* a certain line of
source code is there. The commit where that line was introduced should
explain it.
Testing performance-related changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``rsvg-bench`` directory in the source tree has a tool to
benchmark librsvg. For example, you can ask rsvg-bench to render one
or more SVGs hundreds of times in a row, so you can take accurate
timings or run a sampling profiler and get enough samples.
To fully read on and understand the usage of
``rsvg-bench``, please refer to the :doc:`rsvg_bench` chapter.
It has a comprehensive detail on how to use the tool, and how to
interpret the results.
Included benchmarks
~~~~~~~~~~~~~~~~~~~
The ``rsvg/benches/`` directory has a couple of benchmarks for functions
related to SVG filter effects. You can run them with ``cargo bench``.
These benchmarks use the
`Criterion <https://crates.io/crates/criterion>`__ crate, which supports
some interesting options to generate plots and such.

View File

@@ -0,0 +1,107 @@
CSS Custom Properties — ``var()``
=================================
CSS custom properties, or the ``var()`` feature, let one define named
variables with CSS values that can then be substituted where a property is used. To quote an example from the spec:
.. code-block:: css
:root {
--main-color: #06c;
--accent-color: #006;
}
/* The rest of the CSS file */
#foo h1 {
color: var(--main-color);
}
Additionally, ``var())`` can specify a fallback value, in case the
implementation does not support defining custom properties:
.. code-block:: css
.foo { color: var(--main-color, #aabbcc); }
In this example, if ``--main-color`` is not defined, it will be
substituted with ``#aabbcc``.
OpenType fonts with SVG data and a color substitution table ("emoji fonts")
---------------------------------------------------------------------------
OpenType allows fonts to define some glyphs in terms of SVG documents.
These glyphs can be recolored: OpenType also allows fonts to have a
color substitution table that will be applied to the SVG. To do this,
RGB entries in the color table are effectively turned into custom
properties named ``color0``, ``color1``, etc. with RGB values. Then,
SVG elements specify their colors like ``<path fill="var(--color,
yellow)" d="..." />``, often with a fallback.
OpenType's minimal requirements are that SVG implementations support
``var()`` just in places where a color may be specified (i.e. the
properties that specify SVG paint servers), and that they support the
fallback value. It does **not** require that implementations actually
support defining custom values for the
``color0``/``color1``/``colorN`` variables, just that fallbacks are used.
This lets librsvg approach supporting CSS custom properties in an
incremental fashion.
Roadmap for incremental support
-------------------------------
* Stage 1 (:issue:`997`): support ``var(--blah, fallback)`` just for
colors in properties that take paint servers, plus properties like
``lightingColor`` (filters) and ``stopColor`` (gradients). Look in
``property_defs.rs`` for places that use the ``Color`` type. This
should will make OpenType fonts with color fallbacks work in minimal
fashion.
* Stage 2 (:issue:`459`): support defining custom properties and
referencing them. I wanted to cut&paste Servo's implementation of
this, but it is a bit involved and may require plenty of refactoring
to accomodate it from librsvg's code. If it is too complex, maybe
we can have a homegrown implementation that just lets one define
``--foo: value;`` in a ``:root`` selector, and that just substitutes
whole values without substitution into other tokens
(e.g. ``width: var(--some_number)px;`` wouldn't work).
* Stage 3, full support for custom properties with substitution into
other values.
Letting the caller define values for custom properties
------------------------------------------------------
Adobe's SVG Native Viewer has a `simple API to specify a color map
<https://github.com/adobe/svg-native-viewer/blob/ab9ea1d48b0ff055c2fb063ae4c68edafce5b7c5/svgnative/include/svgnative/SVGDocument.h#L103-L125>`_
that maps string names to RGBA colors. I think it would be more
future-proof to actually let the caller specify the values in a
``:root`` selector via an external stylesheet; this way we can
accomodate media queries in a clean fashion without growing the public
API. Media queries are often used to set the custom property values
depending on the media's characteristics (e.g. change colors depending
on dark-mode), and later the properties are used with ``var()``.
Security considerations
-----------------------
If we fully support variable substitution, be careful about the `macro
expansion attack
<https://drafts.csswg.org/css-variables/#long-variables>`_ that can be
done with them. The spec mentions a mitigation; I think the Servo
code already does this.
References
----------
* Specification: `CSS Custom Properties for Cascading Variables Module Level 1
<https://drafts.csswg.org/css-variables/#changes>`_
* OpenType specification for `Colors and Color Palettes
<https://learn.microsoft.com/en-us/typography/opentype/spec/svg#color-and-color-palettes>`_

View File

@@ -0,0 +1,157 @@
:orphan:
Modifying the development guide
===============================
The following are guidelines and tips for modifying this guide.
Extra reStructuredText roles
----------------------------
Aside, the `roles provided out-of-the box
<https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html>`_,
some 3rd-party and custom roles may also be used.
Referencing issues, commits, users, etc
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All roles provided by the `sphinx-issues
<https://github.com/sloria/sphinx-issues>`_ extension may be used as
described on its page but note that the repository-related and user roles
are restricted to the ``gitlab.gnome.org`` GitLab instance e.g:
- ``:user:`federico``` -> :user:`federico`,
- ``:issue:`GNOME/gnome-shell#5415``` -> :issue:`GNOME/gnome-shell#5415`,
and the default repository for the repository-related roles is
`GNOME/librsvg <https://gitlab.gnome.org/GNOME/librsvg>`_ e.g:
- ``:issue:`1``` -> :issue:`1`,
- ``:commit:`550ba0c83939dfd0e829528dc8175639ad92dd83```
-> :commit:`550ba0c83939dfd0e829528dc8175639ad92dd83`.
Referencing the source tree
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. rst:role:: source
References entries in the librsvg source tree.
The target should be a valid path to an entry in the source tree
relative to the source tree root, which may be prepended with a branch
name, tag name or commit hash to reference the source tree at that
branch, tag or commit.
In other words, the target is of the form ``[<ref>:][<path>]``.
``<ref>:`` may be omitted to reference the default branch (``main``) and
``<path>`` may be omitted to reference the source tree root.
For example:
- ``:source:`rsvg/src/``` -> :source:`rsvg/src/`
- ``:source:`Maintainers <README.md#maintainers>```
-> :source:`Maintainers <README.md#maintainers>`
- ``:source:`550ba0c83939dfd0e829528dc8175639ad92dd83:rsvg/src/```
-> :source:`550ba0c83939dfd0e829528dc8175639ad92dd83:rsvg/src/`
- ``:source:`2.59's README <2.59.0:README.md>```
-> :source:`2.59's README <2.59.0:README.md>`
- ``:source:`2.59.1:``` -> :source:`2.59.1:`
To add to or modify these roles see
:source:`devel-docs/_extensions/source.py`.
Referencing the internals documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following reference entities in the library's internals documentation:
.. rst:role:: internals:crate
This references a crate.
The target should be the name of the crate e.g
``:internals:crate:`librsvg_c``` -> :internals:crate:`librsvg_c`.
.. rst:role:: internals:module
This references a module.
The target should be a rust-style reference for the module e.g:
- ``:internals:module:`rsvg::api``` -> :internals:module:`rsvg::api`
- ``:internals:module:`rsvg::xml::xml2```
-> :internals:module:`rsvg::xml::xml2`
- ``:internals:module:`librsvg_c::handle <librsvg_c::handle>```
-> :internals:module:`librsvg_c::handle <librsvg_c::handle>`
.. rst:role:: internals:struct
.. rst:role:: internals:enum
.. rst:role:: internals:trait
.. rst:role:: internals:type
.. rst:role:: internals:fn
.. rst:role:: internals:macro
.. rst:role:: internals:constant
.. rst:role:: internals:static
These reference top-level entities.
The target should be the rust-style fully-qualified reference for an
entity e.g:
- ``:internals:enum:`rsvg::RenderingError```
-> :internals:enum:`rsvg::RenderingError`
- ``:internals:struct:`librsvg_c::handle::RsvgHandle```
-> :internals:struct:`librsvg_c::handle::RsvgHandle`
- ``:internals:fn:`rsvg::drawing_ctx::draw_tree```
-> :internals:fn:`rsvg::drawing_ctx::draw_tree`
- ``:internals:constant:`rsvg::xml::xml2::XML_SAX2_MAGIC```
-> :internals:constant:`rsvg::xml::xml2::XML_SAX2_MAGIC`
.. rst:role:: internals:struct-field
.. rst:role:: internals:struct-method
.. rst:role:: internals:enum-variant
.. rst:role:: internals:trait-method
.. rst:role:: internals:trait-tymethod
These reference members of structs, enums, etc.
The target should be the rust-style **fully-qualified** reference for a
member entity. This normally renders as ``<parent>::<member>`` but the
reference target may be prepended by a ``~`` (tilde) to render as just
``<member>``.
For example:
- ``:internals:struct-field:`rsvg::Length::unit```
-> :internals:struct-field:`rsvg::Length::unit`
- ``:internals:struct-method:`rsvg::element::Element::new```
-> :internals:struct-method:`rsvg::element::Element::new`
- ``:internals:struct-method:`~rsvg::element::Element::new```
-> :internals:struct-method:`~rsvg::element::Element::new`
- ``:internals:enum-variant:`rsvg::RenderingError::InvalidId```
-> :internals:enum-variant:`rsvg::RenderingError::InvalidId`
.. note::
:rst:role:`internals:trait-method` references a **provided** trait
method i.e a trait method that has a default implementation, such as
:internals:trait-method:`rsvg::element::ElementTrait::draw`;
while :rst:role:`internals:trait-tymethod` references a **required**
trait method i.e a trait method that only has a prototype, such as
:internals:trait-tymethod:`rsvg::length::Normalize::normalize`.
To reference a struct's implementation of a trait's method, use
:rst:role:`internals:struct-method`.
To add to or modify these roles see
:source:`devel-docs/_extensions/internals.py`.
Referencing RUSTSEC advisories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. rst:role:: rustsec
References the official page of a RUSTSEC advisory.
The target should be the ID of the advisory e.g
``:rustsec:`2020-0146``` -> :rustsec:`2020-0146`.

View File

@@ -0,0 +1,207 @@
Setting up your development environment
=======================================
This chapter will help you set your development environment for librsvg.
System requirements
-------------------
- A 64-bit installation of Linux.
- 8 GB of RAM, or 16 GB recommended if you will be running the full
test suite frequently.
- Around 10 GB free of hard disk space.
- You can either use `podman <https://podman.io/>`_ to work in a
containerized setup (this chapter will show you how), or you can
install librsvg's dependencies by hand.
- Make sure you have ``git`` installed.
- Your favorite text editor. We recommend a text editor configured to
use the Language Server Protocol plus `rust-analyzer
<https://rust-analyzer.github.io/>`_ so you can get autocompletion
and documentation of librsvg's internals within your editor.
Downloading the source code
---------------------------
.. code-block:: sh
git clone https://gitlab.gnome.org/GNOME/librsvg.git
.. _podman_setup:
Setting up with podman
----------------------
An easy way to set up a development environment is to use `podman
<https://podman.io/>`_ to download and run a container image. This is
similar to having a ``chroot`` with all of librsvg's dependencies
already set up.
Install ``podman`` on your distro, and then:
.. code-block:: sh
cd librsvg # wherever you did your "git clone"
sh ci/pull-container-image.sh
In the librsvg source tree, ``ci/pull-container-image.sh`` is a script
that will invoke ``podman pull`` to download the container image that
you can use for development. It is the same image that librsvg uses
for its continuous integration pipeline (CI), so you can have exactly
the same setup on your own machine.
That ``pull-container-image.sh`` script will give you instructions
similar to these:
.. code-block:: text
You can now run this:
podman run --rm -ti --cap-add=SYS_PTRACE -v $(pwd):/srv/project:z -w /srv/project $image_name
Don't forget to run this once inside the container:
source ci/env.sh
source ci/setup-dependencies-env.sh
You can cut&paste those commands (from the script's output, not from
this document!). The first one should give you a shell prompt inside
the container. The second and third ones will make Rust available in
the shell's environment, and adjust some environment variables so that
the compilation process can find the installed dependencies.
What's all that magic? Let's dissect the podman command line:
- ``podman run`` - run a specific container image. The image name is
the last parameter in that command; it will look something like
``registry.gitlab.gnome.org/gnome/librsvg/opensuse/tumbleweed:x86_64-1.60.0-2022-08-17.0-main``.
This is an image built on on a base of the openSUSE Tumbleweed, a
rolling distribution of Linux with very recent dependencies.
- ``--rm`` - Remove the container after exiting. It will terminate
when you ``exit`` the container's shell.
- ``-ti`` - Set up an interactive session.
- ``--cap-add=SYS_PTRACE`` - Make it possible to run ``gdb`` inside the container.
- ``-v $(pwd):/srv/project:z` - Mount the current directory as
``/srv/project`` inside the container. This lets you build from
your current source tree without first copying it into the
container; it will be available in ``/srv/project``. The ``:z`` at
the end is so that if your host distro uses selinux, it will label
the mounted volume properly (it does nothing if you are not on
selinux).
Finally, don't forget to ``source ci/env.sh`` and ``source
ci/setup-dependencies-env.sh`` once you are inside ``podman run``.
You can now skip to :ref:`build`.
.. _manual_setup:
Setting up dependencies manually
--------------------------------
.. include:: _build_dependencies.rst
The following sections describe how to install these dependencies on
several systems. For fully manual builds, you can try using the
script in ``ci/build-dependencies.sh``. Librsvg's continuous
integration (CI) infrastructure uses that script to install the
dependencies before building.
Debian based systems
~~~~~~~~~~~~~~~~~~~~
As of 2018/Feb/22, librsvg cannot be built in ``debian stable`` and
``ubuntu 18.04``, as they have packages that are too old.
**Build dependencies on Debian Testing or Ubuntu 18.10:**
.. code-block:: sh
apt-get install -y gcc rustc cargo cargo-c ninja-build \
meson gi-docgen python3-docutils git \
libgdk-pixbuf2.0-dev libgirepository1.0-dev \
libxml2-dev libcairo2-dev libpango1.0-dev
Additionally, as of September 2018 you need to add ``gdk-pixbuf``
utilities to your path, see :issue:`331` for details:
.. code-block:: sh
PATH="$PATH:/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0"
Fedora based systems
~~~~~~~~~~~~~~~~~~~~
.. code-block:: sh
dnf install -y gcc rust rust-std-static cargo cargo-c ninja-build \
meson gi-docgen python3-docutils git redhat-rpm-config \
gdk-pixbuf2-devel gobject-introspection-devel \
libxml2-devel cairo-devel cairo-gobject-devel pango-devel
openSUSE based systems
~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: sh
zypper install -y gcc rust rust-std cargo cargo-c ninja \
meson python3-gi-docgen python38-docutils git \
gdk-pixbuf-devel gobject-introspection-devel \
libxml2-devel cairo-devel pango-devel
macOS systems
~~~~~~~~~~~~~
Dependencies may be installed using `Homebrew <https://brew.sh>`_ or another
package manager.
.. code-block:: sh
brew install meson gi-docgen pkgconfig gobject-introspection gdk-pixbuf pango
.. _build:
Building and testing
--------------------
Make sure you have gone through the steps in :ref:`podman_setup` or
:ref:`manual_setup`. Then, do the following.
**Normal development:** You can use ``cargo build`` and
``cargo test`` as for a simple Rust project; this is what
you will use most of the time during regular development. If you are
using the podman container as per above, you should do this in the
``/srv/project`` directory most of the time.
After compiling with those commands, you can use the ``rsvg-convert``
binary to casually test rendering an SVG file, for example, one that
has a feature that you are developing. You can run
``target/debug/rsvg-convert -o output.png my_test_file.svg``.
If you do a release build with ``cargo build --release``, which includes
optimizations, the binary will be in ``target/release/rsvg-convert``.
This version is *much* faster than the debug version.
**Doing a full build:** You can use the following:
.. code-block:: sh
mkdir -p _build
meson setup _build -Ddocs=enabled -Dintrospection=enabled -Dvala=enabled
meson compile -C _build
meson test -C _build
You should only have to do that if you need to run the full test
suite, for the C API tests and the tests for limiting memory
consumption.
.. _podman: https://podman.io/

904
devel-docs/features.rst Normal file
View File

@@ -0,0 +1,904 @@
Supported SVG and CSS features
==============================
Librsvg tries to be a mostly complete renderer for `SVG1.1
<https://www.w3.org/TR/SVG11/>`_ and `SVG2
<https://www.w3.org/TR/SVG2/>`_.
In terms of processing external references, librsvg is a bit more
strict than SVGs “static mode” and a bit more lenient than “secure
static mode”. See "`Security and locations of referenced files
<https://gnome.pages.gitlab.gnome.org/librsvg/Rsvg-2.0/class.Handle.html#security-and-locations-of-referenced-files>`_"
in the reference documentation for details.
Animation, interactivity, and scripting are not supported.
The SVG1.2 specification never made it past draft status in the W3Cs
process, and librsvg does not support it. Note that SVG2 removed some of
the features proposed for SVG1.2.
Generally, librsvg tries to keep up with features in the SVG2 Candidate
Recommendation spec. It ignores features in the SVG2 drafts that are not
finalized yet.
Alternative versions of SVG (SVG Tiny, SVG Basic, :issue:`SVG Native
<689>`) are not explicitly supported. Their features which are a subset
of SVG1.1 or SVG2 are supported if they are equivalent to the ones
listed below.
SVG2 offloads many of its features to the family of CSS3 specifications.
Librsvg does not try to support them exhaustively (there are too many
features in CSS!). Instead, we try to prioritize new features based on
the needs which people express in librsvgs bug tracker. Do you want a
feature? `File an
issue <https://gitlab.gnome.org/GNOME/librsvg/-/issues>`__!
Supported raster image formats
------------------------------
The ``image`` and ``feImage`` elements allow including an external
file as raster data. Librsvg supports loading JPEG, PNG, GIF, and
WEBP. Librsvg can optionally be compiled with support for AVIF, too;
see the "Compile-time options" section in :doc:`compiling` for details.
Attributes supported by all elements
------------------------------------
+-----------------------------------+-----------------------------------+
| Attribute | Notes |
+===================================+===================================+
| class | |
+-----------------------------------+-----------------------------------+
| id | |
+-----------------------------------+-----------------------------------+
| requiredExtensions | Used in children of the |
| | ``switch`` element. |
+-----------------------------------+-----------------------------------+
| requiredFeatures | Used in children of the |
| | ``switch`` element. |
+-----------------------------------+-----------------------------------+
| systemLanguage | Used in children of the |
| | ``switch`` element. |
+-----------------------------------+-----------------------------------+
| style | |
+-----------------------------------+-----------------------------------+
| transform | The ``transform`` attribute has a |
| | different syntax than the CSS |
| | ``transform`` property. |
+-----------------------------------+-----------------------------------+
| xml:lang | |
+-----------------------------------+-----------------------------------+
| xml:space | |
+-----------------------------------+-----------------------------------+
.. _elements:
Elements and their specific attributes
--------------------------------------
+-----------------------+-----------------------+-------------------------------+
| Element | Attributes | Notes |
+=======================+=======================+===============================+
| a | | |
+-----------------------+-----------------------+-------------------------------+
| | xlink:href | Needs xlink namespace |
+-----------------------+-----------------------+-------------------------------+
| | href | SVG2 |
+-----------------------+-----------------------+-------------------------------+
| circle | | |
+-----------------------+-----------------------+-------------------------------+
| | cx | |
+-----------------------+-----------------------+-------------------------------+
| | cy | |
+-----------------------+-----------------------+-------------------------------+
| | r | |
+-----------------------+-----------------------+-------------------------------+
| clipPath | | |
+-----------------------+-----------------------+-------------------------------+
| | clipPathUnits | |
+-----------------------+-----------------------+-------------------------------+
| defs | | |
+-----------------------+-----------------------+-------------------------------+
| ellipse | | |
+-----------------------+-----------------------+-------------------------------+
| | cx | |
+-----------------------+-----------------------+-------------------------------+
| | cy | |
+-----------------------+-----------------------+-------------------------------+
| | rx | |
+-----------------------+-----------------------+-------------------------------+
| | ry | |
+-----------------------+-----------------------+-------------------------------+
| feBlend | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | in2 | |
+-----------------------+-----------------------+-------------------------------+
| | mode | |
+-----------------------+-----------------------+-------------------------------+
| feColorMatrix | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | type | |
+-----------------------+-----------------------+-------------------------------+
| | values | |
+-----------------------+-----------------------+-------------------------------+
| feComponentTransfer | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| feComposite | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | in2 | |
+-----------------------+-----------------------+-------------------------------+
| | operator | |
+-----------------------+-----------------------+-------------------------------+
| | k1 | |
+-----------------------+-----------------------+-------------------------------+
| | k2 | |
+-----------------------+-----------------------+-------------------------------+
| | k3 | |
+-----------------------+-----------------------+-------------------------------+
| | k4 | |
+-----------------------+-----------------------+-------------------------------+
| feConvolveMatrix | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | order | |
+-----------------------+-----------------------+-------------------------------+
| | divisor | |
+-----------------------+-----------------------+-------------------------------+
| | bias | |
+-----------------------+-----------------------+-------------------------------+
| | targetX | |
+-----------------------+-----------------------+-------------------------------+
| | targetY | |
+-----------------------+-----------------------+-------------------------------+
| | edgeMode | |
+-----------------------+-----------------------+-------------------------------+
| | kernelMatrix | |
+-----------------------+-----------------------+-------------------------------+
| | kernelUnitLength | |
+-----------------------+-----------------------+-------------------------------+
| | preserveAlpha | |
+-----------------------+-----------------------+-------------------------------+
| feDiffuseLighting | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | surfaceScale | |
+-----------------------+-----------------------+-------------------------------+
| | kernelUnitLength | |
+-----------------------+-----------------------+-------------------------------+
| | diffuseConstant | |
+-----------------------+-----------------------+-------------------------------+
| feDisplacementMap | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | in2 | |
+-----------------------+-----------------------+-------------------------------+
| | scale | |
+-----------------------+-----------------------+-------------------------------+
| | xChannelSelector | |
+-----------------------+-----------------------+-------------------------------+
| | yChannelSelector | |
+-----------------------+-----------------------+-------------------------------+
| feDistantLight | | |
+-----------------------+-----------------------+-------------------------------+
| | azimuth | |
+-----------------------+-----------------------+-------------------------------+
| | elevation | |
+-----------------------+-----------------------+-------------------------------+
| feDropShadow | | See :ref:`filter_effects` |
| | | |
| | | Also takes the |
| | | flood-color and |
| | | flood-opacity |
| | | properties. |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | dx | |
+-----------------------+-----------------------+-------------------------------+
| | dy | |
+-----------------------+-----------------------+-------------------------------+
| | stdDeviation | |
+-----------------------+-----------------------+-------------------------------+
| feFuncA | | See |
| | | :ref:`feComponentTransfer` |
+-----------------------+-----------------------+-------------------------------+
| feFuncB | | See |
| | | :ref:`feComponentTransfer` |
+-----------------------+-----------------------+-------------------------------+
| feFuncG | | See |
| | | :ref:`feComponentTransfer` |
+-----------------------+-----------------------+-------------------------------+
| feFuncR | | See |
| | | :ref:`feComponentTransfer` |
+-----------------------+-----------------------+-------------------------------+
| feFlood | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | | Parameters come from |
| | | the flood-color and |
| | | flood-opacity |
| | | properties. |
+-----------------------+-----------------------+-------------------------------+
| feGaussianBlur | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | stdDeviation | |
+-----------------------+-----------------------+-------------------------------+
| feImage | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | xlink:href | Needs xlink namespace |
+-----------------------+-----------------------+-------------------------------+
| | href | SVG2 |
+-----------------------+-----------------------+-------------------------------+
| | path | Non-standard; used by |
| | | old Adobe Illustrator |
| | | versions. |
+-----------------------+-----------------------+-------------------------------+
| | preserveAspectRatio | |
+-----------------------+-----------------------+-------------------------------+
| feMerge | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| feMergeNode | | |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| feMorphology | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | operator | |
+-----------------------+-----------------------+-------------------------------+
| | radius | |
+-----------------------+-----------------------+-------------------------------+
| feOffset | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | dx | |
+-----------------------+-----------------------+-------------------------------+
| | dy | |
+-----------------------+-----------------------+-------------------------------+
| fePointLight | | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | z | |
+-----------------------+-----------------------+-------------------------------+
| feSpecularLighting | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| | surfaceScale | |
+-----------------------+-----------------------+-------------------------------+
| | kernelUnitLength | |
+-----------------------+-----------------------+-------------------------------+
| | specularConstant | |
+-----------------------+-----------------------+-------------------------------+
| | specularExponent | |
+-----------------------+-----------------------+-------------------------------+
| feSpotLight | | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | z | |
+-----------------------+-----------------------+-------------------------------+
| | pointsAtX | |
+-----------------------+-----------------------+-------------------------------+
| | pointsAtY | |
+-----------------------+-----------------------+-------------------------------+
| | pointsAtZ | |
+-----------------------+-----------------------+-------------------------------+
| | specularExponent | |
+-----------------------+-----------------------+-------------------------------+
| | limitingConeAngle | |
+-----------------------+-----------------------+-------------------------------+
| feTile | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | in | |
+-----------------------+-----------------------+-------------------------------+
| feTurbulence | | See :ref:`filter_effects` |
+-----------------------+-----------------------+-------------------------------+
| | baseFrequency | |
+-----------------------+-----------------------+-------------------------------+
| | numOctaves | |
+-----------------------+-----------------------+-------------------------------+
| | seed | |
+-----------------------+-----------------------+-------------------------------+
| | stitchTiles | |
+-----------------------+-----------------------+-------------------------------+
| | type | |
+-----------------------+-----------------------+-------------------------------+
| filter | | |
+-----------------------+-----------------------+-------------------------------+
| | filterUnits | |
+-----------------------+-----------------------+-------------------------------+
| | primitiveUnits | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | width | |
+-----------------------+-----------------------+-------------------------------+
| | height | |
+-----------------------+-----------------------+-------------------------------+
| g | | |
+-----------------------+-----------------------+-------------------------------+
| image | | |
+-----------------------+-----------------------+-------------------------------+
| | xlink:href | Needs xlink namespace |
+-----------------------+-----------------------+-------------------------------+
| | href | SVG2 |
+-----------------------+-----------------------+-------------------------------+
| | path | Non-standard; used by |
| | | old Adobe Illustrator |
| | | versions. |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | width | |
+-----------------------+-----------------------+-------------------------------+
| | height | |
+-----------------------+-----------------------+-------------------------------+
| | preserveAspectRatio | |
+-----------------------+-----------------------+-------------------------------+
| line | | |
+-----------------------+-----------------------+-------------------------------+
| | x1 | |
+-----------------------+-----------------------+-------------------------------+
| | y1 | |
+-----------------------+-----------------------+-------------------------------+
| | x2 | |
+-----------------------+-----------------------+-------------------------------+
| | y2 | |
+-----------------------+-----------------------+-------------------------------+
| linearGradient | | |
+-----------------------+-----------------------+-------------------------------+
| | gradientUnits | |
+-----------------------+-----------------------+-------------------------------+
| | gradientTransform | |
+-----------------------+-----------------------+-------------------------------+
| | spreadMethod | |
+-----------------------+-----------------------+-------------------------------+
| | x1 | |
+-----------------------+-----------------------+-------------------------------+
| | y1 | |
+-----------------------+-----------------------+-------------------------------+
| | x2 | |
+-----------------------+-----------------------+-------------------------------+
| | y2 | |
+-----------------------+-----------------------+-------------------------------+
| marker | | |
+-----------------------+-----------------------+-------------------------------+
| | markerUnits | |
+-----------------------+-----------------------+-------------------------------+
| | refX | |
+-----------------------+-----------------------+-------------------------------+
| | refY | |
+-----------------------+-----------------------+-------------------------------+
| | markerWidth | |
+-----------------------+-----------------------+-------------------------------+
| | markerHeight | |
+-----------------------+-----------------------+-------------------------------+
| | orient | |
+-----------------------+-----------------------+-------------------------------+
| | preserveAspectRatio | |
+-----------------------+-----------------------+-------------------------------+
| | viewBox | |
+-----------------------+-----------------------+-------------------------------+
| mask | | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | width | |
+-----------------------+-----------------------+-------------------------------+
| | height | |
+-----------------------+-----------------------+-------------------------------+
| | maskUnits | |
+-----------------------+-----------------------+-------------------------------+
| | maskContentUnits | |
+-----------------------+-----------------------+-------------------------------+
| path | | |
+-----------------------+-----------------------+-------------------------------+
| | d | |
+-----------------------+-----------------------+-------------------------------+
| pattern | | |
+-----------------------+-----------------------+-------------------------------+
| | xlink:href | Needs xlink namespace |
+-----------------------+-----------------------+-------------------------------+
| | href | SVG2 |
+-----------------------+-----------------------+-------------------------------+
| | patternUnits | |
+-----------------------+-----------------------+-------------------------------+
| | patternContentUnits | |
+-----------------------+-----------------------+-------------------------------+
| | patternTransform | |
+-----------------------+-----------------------+-------------------------------+
| | preserveAspectRatio | |
+-----------------------+-----------------------+-------------------------------+
| | viewBox | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | width | |
+-----------------------+-----------------------+-------------------------------+
| | height | |
+-----------------------+-----------------------+-------------------------------+
| polygon | | |
+-----------------------+-----------------------+-------------------------------+
| | points | |
+-----------------------+-----------------------+-------------------------------+
| polyline | | |
+-----------------------+-----------------------+-------------------------------+
| | points | |
+-----------------------+-----------------------+-------------------------------+
| radialGradient | | |
+-----------------------+-----------------------+-------------------------------+
| | gradientUnits | |
+-----------------------+-----------------------+-------------------------------+
| | gradientTransform | |
+-----------------------+-----------------------+-------------------------------+
| | spreadMethod | |
+-----------------------+-----------------------+-------------------------------+
| | cx | |
+-----------------------+-----------------------+-------------------------------+
| | cy | |
+-----------------------+-----------------------+-------------------------------+
| | r | |
+-----------------------+-----------------------+-------------------------------+
| | fx | |
+-----------------------+-----------------------+-------------------------------+
| | fx | |
+-----------------------+-----------------------+-------------------------------+
| | fr | |
+-----------------------+-----------------------+-------------------------------+
| rect | | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | width | |
+-----------------------+-----------------------+-------------------------------+
| | height | |
+-----------------------+-----------------------+-------------------------------+
| | rx | |
+-----------------------+-----------------------+-------------------------------+
| | ry | |
+-----------------------+-----------------------+-------------------------------+
| stop | | |
+-----------------------+-----------------------+-------------------------------+
| | offset | |
+-----------------------+-----------------------+-------------------------------+
| style | | |
+-----------------------+-----------------------+-------------------------------+
| | type | |
+-----------------------+-----------------------+-------------------------------+
| svg | | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | width | |
+-----------------------+-----------------------+-------------------------------+
| | height | |
+-----------------------+-----------------------+-------------------------------+
| | viewBox | |
+-----------------------+-----------------------+-------------------------------+
| | preserveAspectRatio | |
+-----------------------+-----------------------+-------------------------------+
| switch | | |
+-----------------------+-----------------------+-------------------------------+
| symbol | | |
+-----------------------+-----------------------+-------------------------------+
| | preserveAspectRatio | |
+-----------------------+-----------------------+-------------------------------+
| | viewBox | |
+-----------------------+-----------------------+-------------------------------+
| text | | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | dx | |
+-----------------------+-----------------------+-------------------------------+
| | dy | |
+-----------------------+-----------------------+-------------------------------+
| tref | | |
+-----------------------+-----------------------+-------------------------------+
| | xlink:href | Needs xlink namespace |
+-----------------------+-----------------------+-------------------------------+
| tspan | | |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | dx | |
+-----------------------+-----------------------+-------------------------------+
| | dy | |
+-----------------------+-----------------------+-------------------------------+
| use | | |
+-----------------------+-----------------------+-------------------------------+
| | xlink:href | Needs xlink namespace |
+-----------------------+-----------------------+-------------------------------+
| | href | SVG2 |
+-----------------------+-----------------------+-------------------------------+
| | x | |
+-----------------------+-----------------------+-------------------------------+
| | y | |
+-----------------------+-----------------------+-------------------------------+
| | width | |
+-----------------------+-----------------------+-------------------------------+
| | height | |
+-----------------------+-----------------------+-------------------------------+
CSS properties
--------------
The following are shorthand properties. They are not available as
presentation attributes, only as style properties, so for example you
have to use ``<path style="marker: url(#foo);"/>``, since there is no
``marker`` attribute.
+----------------------------+--------------------------------------------------------------------+
| Property | Notes |
+============================+====================================================================+
| font | |
+----------------------------+--------------------------------------------------------------------+
| glyph-orientation-vertical | Supports only CSS Writing Modes 3 values: auto, 0, 90, 0deg, 90deg |
+----------------------------+--------------------------------------------------------------------+
| marker | |
+----------------------------+--------------------------------------------------------------------+
The following are longhand properties. Most of them are available as
presentation attributes, e.g. you can use ``<rect fill="blue"/>`` as
well as ``<rect style="fill: blue;"/>``. The Notes column indicates
which properties are not available as presentation attributes.
+-----------------------+----------------------------------------------+
| Property | Notes |
+=======================+==============================================+
| baseline-shift | |
+-----------------------+----------------------------------------------+
| clip-path | |
+-----------------------+----------------------------------------------+
| clip-rule | |
+-----------------------+----------------------------------------------+
| color | |
+-----------------------+----------------------------------------------+
| color- | |
| interpolation-filters | |
+-----------------------+----------------------------------------------+
| direction | |
+-----------------------+----------------------------------------------+
| display | |
+-----------------------+----------------------------------------------+
| enable-background | |
+-----------------------+----------------------------------------------+
| fill | |
+-----------------------+----------------------------------------------+
| fill-opacity | |
+-----------------------+----------------------------------------------+
| fill-rule | |
+-----------------------+----------------------------------------------+
| filter | |
+-----------------------+----------------------------------------------+
| flood-color | |
+-----------------------+----------------------------------------------+
| flood-opacity | |
+-----------------------+----------------------------------------------+
| font-family | |
+-----------------------+----------------------------------------------+
| font-size | |
+-----------------------+----------------------------------------------+
| font-stretch | |
+-----------------------+----------------------------------------------+
| font-style | |
+-----------------------+----------------------------------------------+
| font-variant | |
+-----------------------+----------------------------------------------+
| font-weight | |
+-----------------------+----------------------------------------------+
| image-rendering | |
+-----------------------+----------------------------------------------+
| isolation | Not available as a presentation attribute. |
+-----------------------+----------------------------------------------+
| letter-spacing | |
+-----------------------+----------------------------------------------+
| lighting-color | |
+-----------------------+----------------------------------------------+
| line-height | Not available as a presentation attribute. |
+-----------------------+----------------------------------------------+
| marker-end | |
+-----------------------+----------------------------------------------+
| marker-mid | |
+-----------------------+----------------------------------------------+
| marker-start | |
+-----------------------+----------------------------------------------+
| mask | |
+-----------------------+----------------------------------------------+
| mask-type | |
+-----------------------+----------------------------------------------+
| mix-blend-mode | Not available as a presentation attribute. |
+-----------------------+----------------------------------------------+
| opacity | |
+-----------------------+----------------------------------------------+
| overflow | |
+-----------------------+----------------------------------------------+
| paint-order | |
+-----------------------+----------------------------------------------+
| shape-rendering | |
+-----------------------+----------------------------------------------+
| stop-color | |
+-----------------------+----------------------------------------------+
| stop-opacity | |
+-----------------------+----------------------------------------------+
| stroke | |
+-----------------------+----------------------------------------------+
| stroke-dasharray | |
+-----------------------+----------------------------------------------+
| stroke-dashoffset | |
+-----------------------+----------------------------------------------+
| stroke-linecap | |
+-----------------------+----------------------------------------------+
| stroke-linejoin | |
+-----------------------+----------------------------------------------+
| stroke-miterlimit | |
+-----------------------+----------------------------------------------+
| stroke-opacity | |
+-----------------------+----------------------------------------------+
| stroke-width | |
+-----------------------+----------------------------------------------+
| text-anchor | |
+-----------------------+----------------------------------------------+
| text-decoration | |
+-----------------------+----------------------------------------------+
| text-orientation | Not available as a presentation attribute. |
+-----------------------+----------------------------------------------+
| text-rendering | |
+-----------------------+----------------------------------------------+
| transform | SVG2; different syntax from the |
| | ``transform`` attribute. |
+-----------------------+----------------------------------------------+
| unicode-bidi | |
+-----------------------+----------------------------------------------+
| vector-effect | Only ``non-scaling-stroke`` is supported for |
| | paths. |
+-----------------------+----------------------------------------------+
| visibility | |
+-----------------------+----------------------------------------------+
| white-space | Not available as a presentation attribute. |
+-----------------------+----------------------------------------------+
| writing-mode | |
+-----------------------+----------------------------------------------+
.. _filter_effects:
Filter effects
--------------
The following elements are filter effects:
- feBlend
- feColorMatrix
- feComponentTransfer
- feComposite
- feConvolveMatrix
- feDiffuseLighting
- feDisplacementMap
- feDropShadow
- feFlood
- feGaussianBlur
- feImage
- feMerge
- feMorphology
- feOffset
- feSpecularLighting
- feTile
- feTurbulence
All of those elements for filter effects support these attributes:
+-----------------------------------+-----------------------------------+
| Attribute | Notes |
+===================================+===================================+
| x | |
+-----------------------------------+-----------------------------------+
| y | |
+-----------------------------------+-----------------------------------+
| width | |
+-----------------------------------+-----------------------------------+
| height | |
+-----------------------------------+-----------------------------------+
| result | |
+-----------------------------------+-----------------------------------+
Some filter effect elements take one input in the ``in`` attribute, and
some others take two inputs in the ``in``, ``in2`` attributes. See the
:ref:`table of elements <elements>` above for details.
.. _feComponentTransfer:
Filter effect feComponentTransfer
---------------------------------
The ``feComponentTransfer`` element can contain children ``feFuncA``,
``feFuncR``, ``feFuncG``, ``feFuncB``, and those all support these
attributes:
=========== =====
Attribute Notes
=========== =====
type
tableValues
slope
intercept
amplitude
exponent
offset
=========== =====
CSS features
------------
Pseudo-classes
~~~~~~~~~~~~~~
+-----------------------------------+-----------------------------------+
| Pseudo-class | Notes |
+===================================+===================================+
| :link | |
+-----------------------------------+-----------------------------------+
| :visited | Because librsvg does not maintain |
| | browser history, this is parsed, |
| | but never matches |
+-----------------------------------+-----------------------------------+
| :lang() | |
+-----------------------------------+-----------------------------------+
| :not() | [1]_ |
+-----------------------------------+-----------------------------------+
| :first-child | [1]_ |
+-----------------------------------+-----------------------------------+
| :last-child | [1]_ |
+-----------------------------------+-----------------------------------+
| :only-child | [1]_ |
+-----------------------------------+-----------------------------------+
| :root | [1]_ |
+-----------------------------------+-----------------------------------+
| :empty | [1]_ |
+-----------------------------------+-----------------------------------+
| :nth-child() | [1]_ |
+-----------------------------------+-----------------------------------+
| :nth-last-child() | [1]_ |
+-----------------------------------+-----------------------------------+
| :nth-of-type() | [1]_ |
+-----------------------------------+-----------------------------------+
| :nth-last-of-type() | [1]_ |
+-----------------------------------+-----------------------------------+
| :first-of-type | [1]_ |
+-----------------------------------+-----------------------------------+
| :last-of-type | [1]_ |
+-----------------------------------+-----------------------------------+
| :only-of-type | [1]_ |
+-----------------------------------+-----------------------------------+
FIXME: which selectors, combinators, at-rules.
XML features
------------
XInclude
~~~~~~~~
Librsvg supports the following subset of `XML Inclusions (XInclude) <https://www.w3.org/TR/xinclude-11/>`_.
A document or element may declare the namespace for
``http://www.w3.org/2001/XInclude``, conventionally as an attribute
``xmlns:xi="http://www.w3.org/2001/XInclude"``.
The following discussion assumes an ``xi:`` shorthand; your namespace
declaration may use a different one, but ``xi:`` is conventional for
XInclude.
The following are examples of valid inclusions:
.. code-block:: xml
<xi:include href="foo.xml" parse="xml"/>
<!-- If foo.xml cannot be read, parsing stops with an error -->
<xi:include href="foo.xml" parse="xml">
<xi:fallback>
<some_fallback_element/>
<another_fallback_element/>
</xi:fallback>
</xi:include>
<!-- If foo.xml cannot be read, the elements inside xi:fallback are used instead.
If foo.xml has a syntax error, parsing stops with an error. -->
<xi:include href="foo.txt" parse="text" encoding="utf-8">
<xi:fallback>
Text to be included if foo.txt cannot be read.
</xi:fallback>
</xi:include>
For the ``xi:include`` element, the ``href`` attribute is mandatory,
and ``parse`` and ``encoding`` are optional:
* ``href`` - mandatory for librsvg. This is **different from the
XInclude specification**: the attribute is mandatory in librsvg,
while the spec assumes that if it is not present, then an
``xpointer`` or ``fragid`` attributes are used instead. Librsvg
does not support those. If there is no ``href`` attribute, librsvg
will ignore the whole ``xi:include`` element.
* ``parse`` - optional; supported values are ``xml`` and ``text``; the
default is ``xml``.
* ``encoding`` - optional; only used for including text files with
``parse="text"``. The value should be a `WHATWG label for an
encoding <https://encoding.spec.whatwg.org/#concept-encoding-get>`_,
for example, ``utf-8`` or ``koi8-r``.
Inside ``<xi:include>``, there can be an ``<xi:fallback>`` element to
specify what should be included if the ``href`` cannot be read.
``xml:lang`` and ``xml:space``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Individual elements can specify an `xml:lang attribute
<https://www.w3.org/TR/xml/#sec-lang-tag>`_ to specify their language.
This can be used in a ``<text>`` element for the main language of its
content, or generally for the purposes of CSS selector matching. Note
that this is different from the use of ``systemLanguage`` in children
of the ``<switch>`` element, which is used to `render different
elements depending on the system's language
<https://www.w3.org/TR/SVG2/struct.html#SwitchElement>`_.
Librsvg supports the `xml:space attribute
<https://www.w3.org/TR/xml/#sec-white-space>`_ and its handling per
SVG1.1. Note that this has been superseded in SVG2 with CSS
whitespace handling; librsvg does not support this yet as of
2023/Feb/10.
Explicitly Unsupported features
-------------------------------
- ``flowRoot`` element and its children - Inkscape, SVG 1.2 only.
- ``glyph-orientation-horizontal`` property - SVG1.1 only, removed in
SVG2
- The pseudo-classes ``:is()`` and ``:where()`` are part of Selectors
Level 4, which is still a working draft.
Footnotes
---------
.. [1]
These structural pseudo-classes are implemented in rust-selectors,
which librsvg uses.

184
devel-docs/index.rst Normal file
View File

@@ -0,0 +1,184 @@
Development guide for librsvg
=============================
.. toctree::
:caption: For Distributors and End Users
:maxdepth: 1
:hidden:
supported_versions
product
features
roadmap
compiling
security
bugs
.. toctree::
:caption: Getting Started as a Contributor
:maxdepth: 1
:hidden:
devel_environment
contributing
learning
.. toctree::
:caption: Understand the Code
:maxdepth: 1
:hidden:
Documentation of the library's internals <https://gnome.pages.gitlab.gnome.org/librsvg/internals/rsvg/index.html>
architecture
adding_a_property
memory_leaks
.. toctree::
:caption: Design Documents
:maxdepth: 1
:hidden:
text_layout
render_tree
api_observability
performance_tracking
viewport
custom_properties
building_deps_in_ci
xml_parser
.. toctree::
:caption: Info for Maintainers
:maxdepth: 1
:hidden:
releasing
ci
oss_fuzz
.. toctree::
:caption: Tools
:maxdepth: 1
:hidden:
rsvg_bench
Welcome to the developer's guide for librsvg. This is for people who
want to work on the development of librsvg itself, not for users of
the library or the ``rsvg-convert`` program.
If you want to modify this document, please see :source:`its source code
<devel-docs>` and :doc:`this guide <devel_docs_mod_guide>`.
Introduction
------------
Librsvg is a project with a long history; it started 2001 as a way to
use the then-new Scalable Vector Graphics format (SVG) for GNOME's
icons and other graphical assets on the desktop. Since then, it has
evolved into a few different tools.
- :doc:`supported_versions` - Versions where you can expect support and bugfixes.
- :doc:`product` - What comes out of this repository once it is compiled?
- :doc:`features` - Supported elements, attributes, and properties.
- :doc:`roadmap` - Ever-changing list of priorities for the
maintainers; check this often!
- :doc:`compiling` - Cross compilation, debug/release builds, special options.
- :doc:`security` - Reporting security bugs, releases with security
fixes, security of dependencies.
- :doc:`bugs`
Getting started
---------------
- :doc:`devel_environment`
- :doc:`contributing`
- :doc:`learning`
Understand the code
-------------------
.. Test suite - move tests/readme here?
- `Documentation of the library's internals
<https://gnome.pages.gitlab.gnome.org/librsvg/internals/rsvg/index.html>`_
- :doc:`architecture`
- :doc:`adding_a_property`
- :doc:`memory_leaks`
Design documents
----------------
Before embarking on big changes to librsvg, please write a little
design document modeled on the following ones, and submit a merge
request. We can then discuss it before coding. This way we will have
a sort of big-picture development history apart from commit messages.
- :doc:`text_layout`
- :doc:`render_tree`
- :doc:`api_observability`
- :doc:`performance_tracking`
- :doc:`viewport`
- :doc:`custom_properties`
- :doc:`building_deps_in_ci`
See https://rustc-dev-guide.rust-lang.org/walkthrough.html, section
Overview, to formalize the RFC process for features vs. drive-by
contributions.
Information for maintainers
---------------------------
- :doc:`releasing`
- :doc:`ci`
- :doc:`oss_fuzz`
..
- Overview of the maintainer's workflow.
- Marge-bot.
- Documentation on the CI.
- Documentation on the OSS-Fuzz integration and its maintenance.
Tools
-----
- :doc:`rsvg_bench`
References
----------
- `SVG2 specification <https://www.w3.org/TR/SVG2/>`_. This is the current Candidate Recommendation and it should
be your main reference...
- ... except for things which are later clarified in the `SVG2 Editor's Draft <https://svgwg.org/svg2-draft/>`_.
- `Filter Effects Module Level 1 <https://www.w3.org/TR/filter-effects/>`_.
- `References listed in the SVG2 spec
<https://www.w3.org/TR/SVG2/refs.html>`_ - if you need to consult
the CSS specifications.
- `SVG1.1 specification <https://www.w3.org/TR/SVG11/>`_. Use this mostly for historical reference.
- `SVG Working Group repository
<https://github.com/w3c/svgwg/tree/master>`_. The github issues are
especially interesting. Use this to ask for clarifications of the
spec.
- `SVG Working Group page <https://svgwg.org/>`_.
- Presentation at GUADEC 2017, `Replacing C library code with Rust: What I learned with
librsvg <https://viruta.org/docs/fmq-porting-c-to-rust.pdf>`_. It gives
a little history of librsvg, and how/why it was being ported to Rust
from C.
- Presentation at GUADEC 2018, `Patterns of refactoring C to Rust: the case of
librsvg <https://viruta.org/docs/fmq-refactoring-c-to-rust.pdf>`_. It
describes ways in which librsvg's C code was refactored to allow
porting it to Rust.
- `Federico Mena's blog posts on librsvg
<https://viruta.org/tag/librsvg.html>`_ - plenty of of history and
stories from the development process.
Talks on librsvg.

89
devel-docs/learning.rst Normal file
View File

@@ -0,0 +1,89 @@
Learning resources
==================
Like any other part of the web platform, SVG is a bit fractally
complex. There is the SVG format itself, its drawing model, text
layout, font rendering, image compositing, and a bunch of fascinating
topics. This chapter has links to various places where you can learn
about these topics.
The SVG format
--------------
`SVG Tutorial as an advent calendar <https://svg-tutorial.com/>`__
24 short lessons to learn basic SVG features.
`Blind SVG <https://blindsvg.com/>`__ — This is the absolute best
guide I have found for learning the SVG format gradually. It is
designed so that blind and low-vision people can learn to write their
own illustrations using SVG, but it is useful for everyone!
`SVG tutorial at Mozilla Developer's Network
<https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial>`__ —
Detailed and friendly.
`Codepen <https://codepen.io/pen/>`__ — lets you paste SVG code in its
usual HTML editor and see it rendered immediately. You can also add a
CSS stylesheet to experiment with styles.
The text rendering pipeline
---------------------------
`The journey of a word: how text ends up on a page
<https://www.youtube.com/watch?v=Is4PW6f4Pk4>`__ — talk by Simon
Cozens. Talks about peculiarities of different language families,
their writing systems, complex text shaping, the OpenType font
formats, and how Harfbuzz works. **You should absolutely watch this talk!**
`How Unicode Characters Become Glyphs on Your Screen
<https://www.youtube.com/watch?v=bt4MwIpcp2M>`__ — similar in spirit
to the talk above, but a bit more detailed. Talks about typography
terminology, text segmentation, OpenType font features, and text
layout in some detail.
`The rendering pipeline in Pango
<https://docs.gtk.org/Pango/pango_rendering.html>`__ — Pango is
GNOME's text layout engine, which librsvg also uses. This is a page
from Pango's documentation, with a high-level overview of the
pipeline. You can then research individual terms like *itemization*,
*shaping*, etc.
`Pango, an open-source Unicode text layout engine
<https://people.redhat.com/otaylor/iuc25/pango-unicode-paper.pdf>`__
by Owen Taylor, original author of Pango. This paper is a bit old,
but provides a good overview of what Pango does. See also one of the
first papers about it, `Pango: internationalized text handling
<https://web.archive.org/web/20120227064838/http://ols.fedoraproject.org/OLS/Reprints-2001/taylor.pdf>`__.
`Complex text layout <https://en.wikipedia.org/wiki/Complex_text_layout>`__ on Wikipedia
`Text directionality
<https://learn.microsoft.com/en-us/globalization/fonts-layout/text-directionality>`__
— describes how different writing systems use different
directionalities, how logical order differs from visual order, and the
Unicode directional formatting characters.
`Ten years of Harfbuzz
<https://www.youtube.com/watch?v=T79LMEXkf9w>`__ by Behdad
Esfahbod, the maintainer of Harfbuzz, which provides the text shaping
engine for librsvg and GNOME. This talk is mostly history, as it name implies.
`State of Text Rendering 2024 <https://behdad.org/text2024/>`__ — This
is a big document; read it casually. It describes all the parts, all
the things, all the people, all the projects.
General knowledge
-----------------
`Why are 2D vector graphics so much harder than 3D?
<https://blog.mecheye.net/2019/05/why-is-2d-graphics-is-harder-than-3d-graphics/>`__
— a quick history of 2D graphics with lots of links for you to dive
into history. The other articles in that blog are incredibly good, by
the way.
`Porter/Duff Compositing and Blend Modes
<https://ssp.impulsetrain.com/porterduff.html>`__ — how the alpha
channel works, the Porter/Duff compositing algebra and operators.

177
devel-docs/memory_leaks.rst Normal file
View File

@@ -0,0 +1,177 @@
Apparent memory leaks
=====================
If you run Valgrind or another memory checker on a program that uses librsvg, or
``rsvg-convert``, you may get false positives. This chapter explains why these occur by
giving some examples of false positives from Valgrind.
Note that there may be real memory leaks, and they should be fixed! This chapter just
explains why not everything that is reported as a memory leak is in fact a leak.
Example: false leak in fontconfig's code for ``FcPattern``
----------------------------------------------------------
.. code-block::
$ valgrind --leak-check=full --track-origins=yes rsvg-convert -o foo.png foo.svg
==5712== 5,378 (512 direct, 4,866 indirect) bytes in 1 blocks are definitely lost in loss record 1,463 of 1,487
==5712== at 0x484D82F: realloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==5712== by 0x5577D88: FcPatternObjectInsertElt (fcpat.c:516)
==5712== by 0x557BE08: FcPatternObjectAddWithBinding (fcpat.c:711)
==5712== by 0x557315F: UnknownInlinedFun (fcpat.c:738)
==5712== by 0x557315F: UnknownInlinedFun (fcpat.c:884)
==5712== by 0x557315F: FcDefaultSubstitute (fcdefault.c:257)
==5712== by 0x544E15D: UnknownInlinedFun (pangofc-fontmap.c:2066)
==5712== by 0x544E15D: UnknownInlinedFun (pangofc-fontmap.c:2143)
==5712== by 0x544E15D: pango_fc_font_map_load_fontset (pangofc-fontmap.c:2245)
==5712== by 0x4D74FBC: UnknownInlinedFun (itemize.c:892)
==5712== by 0x4D74FBC: UnknownInlinedFun (itemize.c:952)
==5712== by 0x4D74FBC: pango_itemize_with_font (itemize.c:1564)
==5712== by 0x4D880D1: pango_layout_check_lines.part.0.lto_priv.0 (pango-layout.c:4894)
==5712== by 0x4D7D58D: UnknownInlinedFun (pango-layout.c:4786)
==5712== by 0x4D7D58D: pango_layout_get_extents_internal.lto_priv.0 (pango-layout.c:2925)
==5712== by 0x4D7D735: pango_layout_get_size (pango-layout.c:3166)
==5712== by 0x6987FF: pango::auto::layout::Layout::size (layout.rs:321)
==5712== by 0x542342: librsvg::text::MeasuredSpan::from_span (text.rs:357)
==5712== by 0x33465C: librsvg::text::MeasuredChunk::from_chunk::{{closure}} (text.rs:146)
Fontconfig is a library to enumerate the system's fonts based on different configuration
parameters. It gets used by Pango, GNOME's library for text rendering, and in turn by
librsvg.
Is the report above a leak in fontconfig? No. Let's look at
``FcPatternObjectInsertElt()``, the function that called ``realloc()`` in the stack
trace above. `From fccpat.c
<https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/fd0753af/src/fcpat.c#L498-552>`_:
.. code-block:: c
int s = p->size + 16;
if (p->size)
{
FcPatternElt *e0 = FcPatternElts(p);
e = (FcPatternElt *) realloc (e0, s * sizeof (FcPatternElt));
if (!e) /* maybe it was mmapped */
{
e = malloc(s * sizeof (FcPatternElt));
if (e)
memcpy(e, e0, FcPatternObjectCount (p) * sizeof (FcPatternElt));
}
}
else
e = (FcPatternElt *) malloc (s * sizeof (FcPatternElt));
if (!e)
return FcFalse;
p->elts_offset = FcPtrToOffset (p, e);
The code inside the first ``if (p->size)`` essentially does a ``realloc()`` to resize an
existing array, or ``malloc()`` to allocate a new one. However, **that pointer is encoded
instead of stored plainly** in the last line: ``p->elts_offset = FcPtrToOffset (p, e)``.
If you look at the definition of `FcPtrToOffset
<https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/fd0753af/src/fcint.h#L161>`_,
you will see that it encodes the pointer as the offset between a base location and the
pointer's location itself (``p`` and ``e`` respectively in the code above).
What Valgrind thinks is, "oh, they just allocated this memory and immediately obliterated
the pointer to it, so it must be a leak!". However, what actually happens is that
fontconfig doesn't store that pointer plainly, but mangles it somehow. Then it un-mangles
it with `FcPatternElts
<https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/fd0753af/src/fcint.h#L232>`_
to access it, and frees the memory properly `later in FcPatternDestroy()
<https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/fd0753af/src/fcpat.c#L439-443>`_.
Example: false leak in Rust's hashbrown crate
---------------------------------------------
.. code-block::
$ valgrind --leak-check=full --track-origins=yes rsvg-convert -o foo.png foo.svg
==5712== 708 bytes in 3 blocks are possibly lost in loss record 1,380 of 1,487
==5712== at 0x48487B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==5712== by 0x79E17B: alloc::alloc::alloc (alloc.rs:87)
==5712== by 0x79E316: alloc::alloc::Global::alloc_impl (alloc.rs:169)
==5712== by 0x79E439: <alloc::alloc::Global as core::alloc::Allocator>::allocate (alloc.rs:229)
==5712== by 0x784A57: hashbrown::raw::alloc::inner::do_alloc (alloc.rs:11)
==5712== by 0x7C8ECD: hashbrown::raw::RawTableInner<A>::new_uninitialized (mod.rs:1086)
==5712== by 0x7C926B: hashbrown::raw::RawTableInner<A>::fallible_with_capacity (mod.rs:1115)
==5712== by 0x7CA2C5: hashbrown::raw::RawTableInner<A>::prepare_resize (mod.rs:1359)
==5712== by 0x7C7424: hashbrown::raw::RawTable<T,A>::reserve_rehash (mod.rs:1432)
==5712== by 0x7C7068: hashbrown::raw::RawTable<T,A>::reserve (mod.rs:652)
==5712== by 0x7C81F5: hashbrown::raw::RawTable<T,A>::insert (mod.rs:731)
==5712== by 0x77828B: hashbrown::map::HashMap<K,V,S,A>::insert (map.rs:1508)
Hashbrown is a Rust crate that implements an efficient hash table. Let's look at the code from `hashbrown::raw::RawTableInner<A>::new_uninitialized <https://github.com/rust-lang/hashbrown/blob/1d2c1a81d1b53285decbd64410a21a90112613d7/src/raw/mod.rs#L1080-L1085>`_:
.. code-block:: rust
let ptr: NonNull<u8> = match do_alloc(&alloc, layout) {
Ok(block) => block.cast(),
Err(_) => return Err(fallibility.alloc_err(layout)),
};
let ctrl = NonNull::new_unchecked(ptr.as_ptr().add(ctrl_offset));
First it calls ``do_alloc`` which is essentially ``malloc()`` underneath. Then, **it adds
an offset to the resulting ptr**, where it does ``ptr.as_ptr().add(ctrl_offset)``. You
can see a description of the actual layout `in the declaration of the ctrl field
<https://github.com/rust-lang/hashbrown/blob/1d2c1a81d1b53285decbd64410a21a90112613d7/src/raw/mod.rs#L374-L376>`_.
Similar to the example above for fontconfig, Valgrind sees that the code immediately
obliterates the only existing pointer to the newly-allocated memory, and thus thinks that
it leaks the corresponding memory.
Example: false leak in Rust's regex crate
-----------------------------------------
.. code-block::
$ valgrind --leak-check=full --track-origins=yes rsvg-convert -o foo.png foo.svg
==5712== 42 bytes in 6 blocks are possibly lost in loss record 793 of 1,487
==5712== at 0x48487B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==5712== by 0xA99074: alloc (alloc.rs:87)
==5712== by 0xA99074: alloc_impl (alloc.rs:169)
==5712== by 0xA99074: allocate (alloc.rs:229)
==5712== by 0xA99074: allocate_in<u8, alloc::alloc::Global> (raw_vec.rs:185)
==5712== by 0xA99074: with_capacity_in<u8, alloc::alloc::Global> (raw_vec.rs:132)
==5712== by 0xA99074: with_capacity_in<u8, alloc::alloc::Global> (mod.rs:609)
==5712== by 0xA99074: to_vec<u8, alloc::alloc::Global> (slice.rs:227)
==5712== by 0xA99074: to_vec<u8, alloc::alloc::Global> (slice.rs:176)
==5712== by 0xA99074: to_vec_in<u8, alloc::alloc::Global> (slice.rs:501)
==5712== by 0xA99074: clone<u8, alloc::alloc::Global> (mod.rs:2483)
==5712== by 0xA99074: <alloc::string::String as core::clone::Clone>::clone (string.rs:1861)
==5712== by 0x761A56: <T as alloc::borrow::ToOwned>::to_owned (borrow.rs:90)
==5712== by 0x765986: <alloc::string::String as alloc::string::ToString>::to_string (string.rs:2486)
==5712== by 0x7692DB: regex::compile::Compiler::c (compile.rs:373)
==5712== by 0x76C748: regex::compile::Compiler::c_concat (compile.rs:532)
==5712== by 0x769124: regex::compile::Compiler::c (compile.rs:384)
==5712== by 0x76927B: regex::compile::Compiler::c (compile.rs:364)
==5712== by 0x76E4B1: regex::compile::Compiler::c_repeat_zero_or_one (compile.rs:614)
==5712== by 0x76E302: regex::compile::Compiler::c_repeat (compile.rs:592)
==5712== by 0x769031: regex::compile::Compiler::c (compile.rs:388)
==5712== by 0x76C748: regex::compile::Compiler::c_concat (compile.rs:532)
This is related to the example above for the hashbrown crate. The regex crate, for regular expressions, builds a hash table with the names of captures. It `allocates a string for the name of each capture and inserts it in a hash table <https://github.com/rust-lang/regex/blob/9ca3099/src/compile.rs#L371-L378>`_:
.. code-block:: rust
hir::GroupKind::CaptureName { index, ref name } => {
if index as usize >= self.compiled.captures.len() {
let n = name.to_string();
self.compiled.captures.push(Some(n.clone()));
self.capture_name_idx.insert(n, index as usize);
}
self.c_capture(2 * index as usize, &g.hir)
}
The allocation happens in ``name.to_string()``. Two lines below, the string gets inserted
into the ``self.capture_name_idx`` hash table.
By looking at the `declaration for the capture_name_idx field
<https://github.com/rust-lang/regex/blob/9ca3099/src/compile.rs#L35>`_, we see that it is
a ``HashMap<String, usize>``. However, that ``HashMap`` is in fact a hashbrown table, as
in the previous section. Since hashbrown uses a special encoding for its internal
pointers, Valgrind thinks that the original pointer to the string is lost.

138
devel-docs/oss_fuzz.rst Normal file
View File

@@ -0,0 +1,138 @@
OSS-Fuzz
========
Platform overview
-----------------
`OSS-Fuzz <https://google.github.io/oss-fuzz/>`_ is Google's free fuzzing platform for open source
software.
It runs librsvg's :source:`fuzz targets <fuzz>` to help
detect reliability issues.
Google provides public `build logs <https://oss-fuzz-build-logs.storage.googleapis.com/index.html#librsvg>`_
and `fuzzing stats <https://introspector.oss-fuzz.com/project-profile?project=librsvg>`_, but most
of the details about bug reports and fuzzed testcases require approved access.
Gaining access
^^^^^^^^^^^^^^
The configuration files for the OSS-Fuzz integration can be found in the
`OSS-Fuzz repository <https://github.com/google/oss-fuzz/tree/master/projects/librsvg>`_.
The ``project.yaml`` file controls who has access to bug reports and testcases.
Ping the maintainer if you'd like to be added to the list (note: a Google account is required for
access).
Fuzzing progress
----------------
Once you have access to OSS-Fuzz, you can log in to https://oss-fuzz.com/ with your Google account
to see a dashboard of librsvg's fuzzing progress.
Testcases
^^^^^^^^^
The dashboard contains a link to a `testcases page <https://oss-fuzz.com/testcases?project=librsvg&open=yes>`_
that lists all testcases that currently trigger a bug in librsvg.
Every testcase has a dedicated page with links to view and download a minimized testcase for
reproducing the failure.
Each testcase page also contains a stacktrace for the failure and stats about how often the failure
is encountered while fuzzing.
Reproducing a failure
"""""""""""""""""""""
You can download a minimized testcase and run it with a local fuzz target to debug a failure on your
machine.
For example, to reproduce a failure with the ``render_document`` fuzz target, you can run a command
like this: ``cargo fuzz run render_document minimized.svg``
Individual fuzz targets can also be run inside of a debugger for further debugging information:
.. code:: bash
FUZZ_TARGET=$(find ./target/*/release/ -type f -name render_document)
gdb --args "$FUZZ_TARGET" minimized.svg
If the failure does not reproduce locally, you can try reproducing the issue in an OSS-Fuzz
container:
.. code:: bash
git clone https://github.com/google/oss-fuzz.git
cd oss-fuzz
python infra/helper.py build_image librsvg
python infra/helper.py build_fuzzers librsvg
python infra/helper.py reproduce librsvg render_document minimized.svg
Code coverage
^^^^^^^^^^^^^
The dashboard also links to code coverage data for individual fuzz targets and combined code
coverage data for all targets (click on the "TOTAL COVERAGE" link for the combined data).
The combined coverage data is helpful for identifying coverage gaps, insufficient corpus data, and
potential candidates for future fuzz targets.
Bug reports
^^^^^^^^^^^
Bug reports for new failures are automatically filed in the OSS-Fuzz bug tracker with a
`librsvg label <https://issues.oss-fuzz.com/issues?q=project:librsvg%20status:open>`_.
Make sure you are logged in to view all existing issues.
Build maintenance
-----------------
Google runs compiled fuzz targets on Google Compute Engine VMs.
This architecture requires each project to provide a ``Dockerfile`` and ``build.sh`` script to
download code, configure dependencies, compile fuzz targets, and package any corpus files.
librsvg's build files can be found in the
`OSS-Fuzz repo <https://github.com/google/oss-fuzz/blob/master/projects/librsvg/>`_.
If dependencies change or if new fuzz targets are added, then you may need to modify the build files
and build a new Docker image for OSS-Fuzz.
Building an image
^^^^^^^^^^^^^^^^^
Use the following commands to build librsvg's OSS-Fuzz image and fuzz targets:
.. code:: bash
git clone https://github.com/google/oss-fuzz.git
cd oss-fuzz
python infra/helper.py build_image librsvg
python infra/helper.py build_fuzzers librsvg
Any changes you make to the build files must be submitted as pull requests to the OSS-Fuzz repo.
Debugging build failures
""""""""""""""""""""""""
You can debug build failures during the ``build_fuzzers`` stage by creating a container and manually
running the ``compile`` command:
.. code:: bash
# Create a container for building fuzz targets
python infra/helper.py shell librsvg
# Run this command inside the container to build the fuzz targets
compile
This approach is faster than re-running the ``build_fuzzers`` command, which recompiles everything
from scratch each time the command is run.
The ``build.sh`` script will be located at ``/src/build.sh`` inside the container.
Quick links
-----------
* `OSS-Fuzz dashboard <https://oss-fuzz.com/>`_
* `OSS-Fuzz configuration files and build scripts for librsvg <https://github.com/google/oss-fuzz/tree/master/projects/librsvg>`_
* `All open OSS-Fuzz bugs for librsvg <https://issues.oss-fuzz.com/issues?q=project:librsvg%20status:open>`_
* `Google's OSS-Fuzz documentation <https://google.github.io/oss-fuzz/>`_

View File

@@ -0,0 +1,110 @@
Performance tracking
====================
This project is suitable for a few months of work, like an Outreachy
or Summer of Code internship.
As of 2022/Sep, there is no infrastructure to track librsvg's
performance over time. At different times there are efforts to reduce
the memory consumption of certain parts of the code, or to make them
faster, but there is no monitoring of how the library is doing as its
code evolves.
Given a set of example files (which can change over time), it would be
nice to track the following measurements over time:
- Maximum heap usage during loading and during rendering.
- CPU time for loading; CPU time for rendering.
These would let us answer questions like, is librsvg using more or
less memory over time? More or less CPU time? Can we detect
regressions with this tool?
Getting the actual measurements is not terribly hard; just running
``/usr/bin/time rsvg-convert car.svg > /dev/null`` already produces a useful
big-picture view of things:
::
$ /usr/bin/time rsvg-convert car.svg > /dev/null
0.90user 0.07system 0:01.10elapsed 88%CPU (0avgtext+0avgdata 39460maxresident)k
224inputs+0outputs (5major+6646minor)pagefaults 0swaps
The hard part is the logistics of accumulating the reports over time,
and graphing them. This is your project!
Goals
-----
- Given a librsvg commit, extract performance metrics for a corpus of
test files.
- Store those metrics somewhere, preferably in gnome.org.
- Plot the metrics over time. The plot should make it easy to jump to
a particular commit, so that we can do, "memory usage increased
considerably at this point; which commit is responsible?".
Questions to be researched by the intern
----------------------------------------
- Gathering a set of interesting documents to keep around for regular
testing; `Featured Pictures from Wikimedia Commons
<https://commons.wikimedia.org/wiki/Category:Featured_pictures_on_Wikimedia_Commons_-_vector>`_
is a good source. Ask the maintainer for more!
- Is CPU time an accurate measure, even with busy CI runners? Or does
this need a "quiet" machine? Do small files that get rendered very
quickly need to be measured by averaging several runs?
- Maintaining a history of measurements, probably keyed by commit id
and document. Where do we keep that data? In a git repo? Or in a
web service - can we host it at gnome.org? In the maintainer's
laptop? "Append something to a perf log file somewhere and commit"
-> "Run a plotting program in $somewhere's CI" is probably the
Minimum Viable thing.
- Notifying the performance infrastructure about a new commit to test.
Can we do this from the CI? Maybe with `downstream pipelines
<https://docs.gitlab.com/ee/ci/pipelines/downstream_pipelines.html>`_?
- `GitLab's reports of custom metrics
<https://docs.gitlab.com/ee/ci/testing/metrics_reports.html>`_ look
cool and are displayed conveniently as part of a merge request's
page, but are only present in the Premium edition, not in GNOME's
GitLab instance. Is any of that worth exploring? Is the suggested
`OpenMetrics format <https://openmetrics.io/>`_ good as-is, or is it
overkill?
- Instant gratification: can we generate a new plot and publish it as
part of a CI pipeline's artifacts? Sparklines of performance
metrics over time, to maximize the bling/size ratio?
Inspiration, but beware of overkill
-----------------------------------
- `Are We Fast Yet <https://arewefastyet.com/>`_, Firefox's performance metrics.
- `WebKit Performance Dashboard <https://perf.webkit.org/>`_.
- `Rust performance page <https://perf.rust-lang.org/>`_. See the
README in `this repository
<https://github.com/rust-timer/rustc-timing>`_ for the scripts and
data that drive this.
Sample documents
----------------
Interesting types of SVG documents to put into the performance tracker:
- Lots of objects.
- Lots of filters.
- Lots of text.
- Wikimedia Commons has good examples: `featured pictures
<https://commons.wikimedia.org/wiki/
Category:Featured_pictures_on_Wikimedia_Commons_-_vector>`_, `SVG by
subject
<https://commons.wikimedia.org/wiki/Category:SVG_by_subject>`_.

96
devel-docs/product.rst Normal file
View File

@@ -0,0 +1,96 @@
Librsvg as a product
====================
A full build of librsvg produces several *artifacts*, which are the
final "products" that are produced from the build. We will discuss
them in three groups: library artifacts, ``rsvg-convert`` artifacts,
and others.
Library artifacts
-----------------
Librsvg is part of the `GNOME platform libraries
<https://developer.gnome.org/documentation/introduction/overview/libraries.html>`_,
and needs to maintain a C-compatible API with ABI stability across versions.
The build produces these artifacts, which are typical of GNOME libraries that
can be used from C and other languages:
- A shared library ``librsvg-2.so`` (the file extension will be
different on MacOS or Windows). This is usually installed as part
of the system's libraries. For example, the GTK toolkit assumes
that librsvg's library is installed in the system, and uses it to
load SVG assets like icons. Applications can generally link to this
library and load SVG documents for different purposes.
- A group of C header files (``*.h``) that will be installed in the
system's location for header files. C and C++ programs can use
these directly.
- A ``librsvg-2.pc`` file for `pkg-config
<https://www.freedesktop.org/wiki/Software/pkg-config/>`_. This lets
compilation scripts find the location of the installed library and
header files.
- ``.gir`` and ``.typelib`` files for `GObject Introspection
<https://gi.readthedocs.io/en/latest/>`_. These are machine-readable
descriptions of the API/ABI in the ``.so`` library, which are used by
language bindings to make librsvg's functionality available to many
programming languages.
- A ``.vapi`` description of the API for the `Vala language
<https://vala.dev/>`_ compiler.
Rust API
^^^^^^^^
Apart from the C-compatible library, the Rust code for the library
defines a ``librsvg`` crate that can be used by Rust programs. Since
version 2.57.0, librsvg is available as a regular crate in
``crates.io``.
``rsvg-convert`` artifacts
--------------------------
``rsvg-convert`` is a command-line tool to render SVG documents to
various output formats. It is a very widely-used tool, and many
scripts and systems depend on it maintaining a stable set of
command-line options.
The build produces these:
- The ``rsvg-convert`` executable. This is the tool that most
end-users interact with.
- A Unix manual page for ``rsvg-convert(1)``.
Other artifacts
---------------
- A ``libpixbufloader-svg.so`` module for `gdk-pixbuf
<https://docs.gtk.org/gdk-pixbuf/>`_. This allows programs to use
the gdk-pixbuf API to load SVG documents, as if they were raster
files like JPEG or PNG.
- A ``librsvg.thumbnailer`` configuration file, to tell GNOME's
thumbnailing mechanism that it can just use gdk-pixbuf when trying
to create a thumbnail for an SVG file. These thumbnails can then
get displayed in file managers.
- `Documentation for the C API
<https://gnome.pages.gitlab.gnome.org/librsvg/Rsvg-2.0/index.html>`_,
published online and also installed on the system in a place where
GNOME's `DevHelp <https://gitlab.gnome.org/GNOME/devhelp>`_ can find
it.
- `Documentation for the Rust API
<https://gnome.pages.gitlab.gnome.org/librsvg/doc/rsvg/index.html>`_,
published online. This is not built from the normal ``make`` process,
but independently as part of the :doc:`ci` pipeline.
- The rendered HTML version of this development guide. This is not
built from the normal ``make`` process, but independently as part of
the :doc:`ci` pipeline.

318
devel-docs/releasing.rst Normal file
View File

@@ -0,0 +1,318 @@
Release process checklist
=========================
Feel free to print this document or copy it to a text editor to check
off items while making a release.
- ☐ Refresh your memory with
https://handbook.gnome.org/maintainers/making-a-release.html
**Versions:**
- ☐ Increase the package version number in ``meson.build`` (it may
already be increased but not released; double-check it).
- ☐ Copy version number to ``Cargo.toml``.
- ☐ Copy version number to ``doc/librsvg.toml``.
- ☐ Compute crate version number and write it to ``rsvg/Cargo.toml``, see :ref:`crate version<crate_version>` below.
- ☐ Copy the crate version number to the example in ``rsvg/src/lib.rs``.
-``cargo update -p librsvg`` - needed because you tweaked ``Cargo.toml``, and
also to get new dependencies.
- ☐ Tweak the library version number in ``meson.build`` if the API
changed; follow the steps there.
- ☐ Adjust the supported versions in ``README.md``.
- ☐ Adjust the supported versions in ``devel-docs/supported_versions.rst``.
- ☐ Adjust the supported versions in ``.gitlab/issue_templates/default.md``.
**Rust Bindings:**
- ☐ Make sure that librsvg-rebind is in sync with librsvg C bindings by calling ``./librsvg-rebind/regen.sh``
- ☐ If the bindings have changed from the last version, increase the package version in
- ☐ librsvg-rebind/librsvg-rebind/Cargo.toml
- ☐ librsvg-rebind/librsvg-rebind/sys/Cargo.toml
- ☐ For .0 releases, don't forget to update the librsvg-rebind{,sys} version number if it's still a beta.
- For either of those version updates, ``cargo update -p librsvg-rebind-sys -p librsvg-rebind``
**Release notes:**
- ☐ Update ``NEWS``, see below for the preferred format.
**CI:**
- ☐ Commit the changes above; push a branch.
- ☐ Create a merge request; fix it until it passes the CI. Merge it.
**Publish:**
- ☐ Publish ``librsvg`` to crates.io, see :ref:`crate_release` for details.
-``cargo publish -p librsvg``
- ☐ Publish ``librsvg-rebind`` to crates.io:
- The publish process does a test compilation and needs the `.so` installed to build, so do
``meson setup _build --prefix /usr/local/librsvg && meson compile -C _build && meson install -C _build``
-``cargo publish -p librsvg-rebind-sys``
-``cargo publish -p librsvg-rebind``
- ☐ If this is a development release, create a signed tag for the crate's version - ``git tag -s x.y.z-beta.w``.
- ☐ Create a signed tag for the merge commit - ``git tag -s x.y.z`` with the version number.
- ☐ If this is a development release ``git push`` the signed tag for the crate's version to gitlab.gnome.org/GNOME/librsvg
-``git push`` the signed tag for the GNOME version to gitlab.gnome.org/GNOME/librsvg
- ☐ Optionally edit the `release page ing Gitlab <https://gitlab.gnome.org/GNOME/librsvg/-/releases>`_.
For ``x.y.0`` releases, do the following:
- ☐ `Notify the release
team <https://gitlab.gnome.org/GNOME/releng/-/issues>`__ on whether
to use this ``librsvg-x.y.0`` for the next GNOME version via an issue
on their ``GNOME/releng`` project.
-``cargo-audit audit`` and ensure we dont have vulnerable
dependencies.
Gitlab release
--------------
- ☐ Select the tag ``x.y.z`` you just pushed.
- ☐ If there is an associated milestone, select it too.
- ☐ Fill in the release title - ``x.y.z``.
- ☐ Copy the release notes from NEWS (By default it uses the GIT_TAG_MESSAGE).
- ☐ Add a release asset link to
``https://download.gnome.org/sources/librsvg/x.y/librsvg-x.y.z.tar.xz``
and call it ``librsvg-x.y.z.tar.xz - release tarball``.
- ☐ Add a release asset link to
``https://download.gnome.org/sources/librsvg/x.y/librsvg-x.y.z.sha256sum``
and call it
``librsvg-x.y.z.sha256sum - release tarball sha256sum``.
Version numbers and release schedule
------------------------------------
``meson.build`` and ``Cargo.toml`` must have the same **package
version** number - this is the number that users of the library see.
``meson.build`` is where the **library version** is defined; this is
what gets encoded in the SONAME of ``librsvg.so``.
Librsvg follows `GNOME's release versioning as of 2022/September
<https://discourse.gnome.org/t/even-odd-versioning-is-confusing-lets-stop-doing-it/10391>`_.
(Note that it used an even/odd numbering scheme before librsvg 2.55.x)
Librsvg follows `GNOME's six-month release schedule
<https://release.gnome.org/calendar/index.html>`_.
The `release-team <https://gitlab.gnome.org/GNOME/releng/-/issues>`__
needs to be notified when a new series comes about, so they can adjust
their tooling for the stable GNOME releases. File an
issue in their `repository
<https://gitlab.gnome.org/GNOME/releng/-/issues>`__ to indicate that
the new ``librsvg-x.y.0`` is a stable series.
.. _crate_version:
Version number for public Rust crate
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``librsvg`` crate is `available on crates.io
<https://crates.io/crates/librsvg/>`_. This is for people who wish to
use librsvg directly from Rust, instead of via the C ABI library
(i.e. the ``.tar.xz`` release).
While the C ABI library uses the GNOME versioning scheme, Rust crates
use `SemVer <https://semver.org>`_. So, for librsvg, we have the
following scheme:
**Stable releases:**
* GNOME tarball: 2.57.0
* Rust crate: 2.57.0 (i.e. the same)
**Development releases:**
* GNOME tarball: 2.57.90 through 2.57.99 (.9x patch version means development release)
* Rust crate: 2.58.0-beta.0 through -beta.9 (SemVer supports a -beta.x suffix)
When making releases, you have to edit ``Cargo.toml`` and
``rsvg/Cargo.toml`` by hand to put in version numbers like the above.
The CI scripts will check that the correct versions are in place.
.. _crate_release:
Releasing to crates.io
----------------------
After preparing a GNOME release, you'll also want to release to
crates.io. This requires an `API token
<https://doc.rust-lang.org/cargo/reference/publishing.html#before-your-first-publish>`_;
if you are maintainer you should have one, and also write access to
the ``librsvg`` crate on crates.io.
To make a release, ``cargo publish -p librsvg``.
To publish the Rust bindings to the C library, ``cargo publish -p librsvg-rebind-sys``, ``cargo publish -p librsvg-rebind``.
After this succeeds, proceed with the rest of the steps in the
ref:`release_process_checklist`.
Minimum supported Rust version (MSRV)
-------------------------------------
While it may seem desirable to always require the latest released
version of the Rust toolchain, to get new language features and such,
this is really inconvenient for distributors of librsvg which do not
update Rust all the time. So, we make a compromise.
The ``meson.build`` script defines ``msrv`` with librsvgs minimum
supported Rust version (MSRV). This ensures that distros will get an
early failure during a build, at the ``meson setup`` step, if they have
a version of Rust that is too old — instead of getting an obscure
error message from ``rustc`` in the middle of the build when it finds
an unsupported language construct.
Please update all of these values when increasing the MSRV:
- ``msrv`` in ``meson.build``.
- ``cargo_c`` version in ``meson.build``.
- ``rust-version`` in ``Cargo.toml``.
- ``RUST_MINIMUM`` in ``ci/container_builds.yml``.
- The ``Compilers and build tools`` section in ``devel-docs/_build_dependencies.rst``.
Sometimes librsvgs dependencies update their MSRV and librsvg may need
to increase it as well. Please consider the following before doing this:
- Absolutely do not require a nightly snapshot of the compiler, or
crates that only build on nightly.
- Distributions with rolling releases usually keep their Rust
toolchains fairly well updated, maybe not always at the latest, but
within two or three releases earlier than the latest. If the MSRV you
want is within about six months of the latest, things are probably
safe.
- Enterprise distributions update more slowly. It is useful to watch
for the MSRV that Firefox requires, although sometimes Firefox
updates Rust very slowly as well. Now that distributions are shipping
packages other than Firefox that require Rust, they will probably
start updating more frequently.
Generally — two or three releases earlier than the latest stable Rust is
OK for rolling distros, probably perilous for enterprise distros.
Releases within a year of an enterprise distros shipping date are
probably OK.
If you are not sure, ask on the `forum for GNOME
distributors <https://discourse.gnome.org/tag/distributor>`__ about
their plans! (That is, posts on ``discourse.gnome.org`` with the
``distributor`` tag.)
Format for release notes in NEWS
--------------------------------
The ``NEWS`` file contains the release notes. Please use something
close to this format; it is not mandatory, but makes the formatting
consistent, and is what tooling expects elsewhere - also by writing
Markdown, you can just cut&paste it into a Gitlab release. You can skim
bits of the ``NEWS`` file for examples on style and content.
New entries go at the **top** of the file.
::
Version x.y.z
=============
Commentary on the release; put anything here that you want to
highlight. Note changes in the build process, if any, or any other
things that may trip up distributors.
## Description of a special feature
You can include headings with `##` in Markdown syntax.
Blah blah blah.
Next is a list of features added and issues fixed; use gitlab's issue
numbers. I tend to use this order: first security bugs, then new
features and user-visible changes, finally regular bugs. The
rationale is that if people stop reading early, at least they will
have seen the most important stuff first.
## Changes:
- #123 - title of the issue, or short summary if it warrants more
discussion than just the title.
- #456 - fix blah blah (Contributor's Name).
## Special thanks for this release:
- Any people that you want to highlight. Feel free to omit this
section if the release is otherwise unremarkable.
Making a tarball
----------------
Don't make a tarball by hand. Let the CI system do it. Look for
``distcheck`` in the checklist above. That job in the CI pipelines
has the release tarball which you can download.
Copying the tarball to master.gnome.org
---------------------------------------
If you dont have a maintainer account there, ask federico@gnome.org to
do it or `ask the release
team <https://gitlab.gnome.org/GNOME/releng/-/issues>`__ to do it by
filing an issue on their ``GNOME/releng`` project.
Rust dependencies
-----------------
Librsvg's ``Cargo.lock`` is checked into git because the resolved
versions of crates that it mentions are the ones that were actually
used to run the test suite automatically in CI, and are "known good".
In other words: `keep the results of dependency resolution in version
control, and update those results manually
<https://blog.ometer.com/2017/01/10/dear-package-managers-dependency-resolution-results-should-be-in-version-control/>`_.
It is important to keep these dependencies updated; you can do that
regularly with the ``cargo update`` step listed in the checklist
above.
`cargo-audit <https://github.com/rustsec/rustsec>`__ is very useful to
scan the list of dependencies for registered vulnerabilities in the
`RustSec vulnerability database <https://rustsec.org/>`__. Run it
especially before making a new ``x.y.0`` release, or check the output
of the ``deny`` job in CI pipelines — this runs `cargo-deny
<https://embarkstudios.github.io/cargo-deny/>`_ to check for
vulnerable and duplicate dependencies.
Sometimes cargo-audit will report crates that are not vulnerable, but
that are unmaintained. Keep an eye of those; you may want to file bugs
upstream to see if the crates are really unmaintained or if they should
be substituted for something else.
Creating a stable release branch
--------------------------------
- Create a branch named ``librsvg-xx.yy``, e.g. ``librsvg-2.54``
- Make the ``BASE_TAG`` in ``ci/container-builds.yml`` refer to the new
``librsvg-xx.yy`` branch instead of ``main``.
- Push that branch to origin.
- (Branches with that naming scheme are already automatically protected
in gitlabs Settings/Repository/Protected branches.)
- Edit the badge for the stable branch so it points to the new branch:
Settings/General/Badges, find the existing badge for the stable
branch, click on the edit button that looks like a pencil. Change the
**Link** and **Badge image URL**; usually it is enough to just change
the version number in both.

352
devel-docs/render_tree.rst Normal file
View File

@@ -0,0 +1,352 @@
Render tree
===========
For historical reasons, librsvg's code flow during rendering is as
follows. The rendering code traverses the SVG tree of elements, and
for each one, its ``::draw()`` method is called; its signature looks
like this (some arguments omitted):
.. code-block:: rust
pub fn draw(
&self,
...
draw_ctx: &mut DrawingCtx,
) -> Result<BoundingBox, RenderingError> { ... }
The draw() methods perform the actual rendering as side effects on the
``draw_ctx``, and return a ``BoundingBox``. That is, the bounding box of
an element is computed at the same time that it is rendered. This is
suboptimal for several reasons:
- Many things that happen during rendering depend on knowing the
bounding box. For example, gradients, patterns, and filters with
units set to ``objectBoundingBox`` need to know the bounds. The
rendering code in drawing_ctx.rs is cluttered because it must
resolve bounding boxes very late.
- This is especially problematic for filters, since a Cairo surface
needs to be created *before* rendering, and that surface should have
a size relative to the bounding box of the element being filtered!
:issue:`Bug #1 <1>` is precisely about this: librsvg instead creates
a temporary surface as big as the document's toplevel viewport and filters
it, but this doesn't work well for filters like Gaussian blur that should
actually reference pixels outside of the document's area (think of a
shape that extends past the document's area, which then gets
blurred).
- The way for an element to signal that it is not drawable
(e.g. ``<defs>`` is by returning an empty bounding box and not
rendering anything. This is awkward.
- When rendering to a temporary surface for filtering or masking,
there is a set of affine transformations that needs to be maintained
carefully: an affine for the clipping path outside the temporary
surface, an affine for drawing inside the surface, an affine to
composite the surface into the final result. This is hard to
understand and hard to test.
These problems can be solved by having a **render tree**.
What is a render tree?
----------------------
As of 2022/Oct/06, librsvg does not compute a render tree data
structure prior to rendering. Instead, in a very 2000s fashion, it
walks the tree of elements and calls a ``.draw()`` method for each
one. Each element then calls whatever methods it needs from
``DrawingCtx`` to draw itself. Elements which don't produce graphical
output (e.g. ``<defs>`` or ``<marker>``) simply have an empty
``draw()`` method.
Over time we have been refactoring that in the direction of actually
being able to produce a render tree. What would that look like?
Consider an SVG document like this:
.. code-block:: xml
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<defs>
<rect id="TheRect" x="10" y="10" width="20" height="20" fill="blue"/>
</defs>
<g>
<use href="#TheRect" stroke="red" stroke-width="2"/>
<circle cx="50" cy="50" r="20" fill="yellow"/>
</g>
</svg>
A render tree would be a list of nested instructions like this:
::
group { # refers to the toplevel SVG
width: 100
height: 100
establishes_viewport: true # because it is an <svg> element
children {
group { # refers to the <g>
establishes_viewport: false # because it is a simple <g>
children {
shape {
path="the <rect> above but resolved to path commands"
# note how the following is the cascaded style and the <use> semantics
fill: blue
stroke: red
stroke-width: 2
}
shape {
path="the <circle> above but resolved to path commands"
fill: yellow
}
}
}
}
}
That is, we take the high-level SVG instructions and "lower" them to a
few possible drawing primitives like path-based shapes that can be
grouped. All the primitives have everything that is needed to draw
them, like their set of computed values for styles, and their
coordinates resolved to their user-space coordinate system.
Browser engines produce render trees more or less similar to the above
(they don't always call them that), and get various benefits:
- The various recursively-nested subtrees can be rendered concurrently.
- Having low-level primitives makes it easier to switch to another
rendering engine in the future.
- The tree can be re-rendered without recomputation, or subtrees can
be recomputed efficiently if e.g. an animated element changes a few
of its properties.
Why did librsvg not do that since the beginning?
------------------------------------------------
Librsvg was originally written in the early 2000s, when several things
were happening at the same time:
- libxml2 (one of the early widely-available parsers for XML) had
recently gotten a SAX API for parsing XML. This lets an application
stream in the parsed XML elements and process them one by one,
without having to build a tree of elements+attributes first. In
those days, memory was at a premium and "not producing a tree" was
seen as beneficial.
- The SVG spec itself was being written, and it did not have all of
the features we know now. In particular, maybe at some point it
didn't have elements that worked by referencing others, like
``<use>`` or ``<filter>``. The CSS cascade could be done on the fly
for the XML elements being streamed in, and one could emit rendering
commands for each element to produce the final result.
That is, at that time, it was indeed feasible to do this: stream in
parsed XML elements one by one as produced by libxml2, and for each
element, compute its CSS cascade and render it.
This scheme probably stopped working at some point when SVG got
features that allowed referencing elements that have not been declared
yet (think of ``<use href="#foo"/>`` but with the ``<defs> <path
id="foo" .../> </defs>`` declared until later in the document). Or
elements that referenced others, like ``<rect filter="url(#blah)">``.
In both cases, one needs to actually build an in-memory tree of parsed
elements, and *then* resolve the references between them.
That is where much of the complexity of librsvg's code flow comes from:
- ``AcquiredNodes`` is the thing that resolves references when needed.
It also detects reference cycles, which are an error.
- ``ComputedValues`` often get resolved until pretty late, by passing
the ``CascadedValues`` state down to children as they are drawn.
- ``DrawingCtx`` was originally a giant ball of mutable state, but we
have been whittling it down and moving part of that state elsewhere.
Summary of the SVG rendering model
----------------------------------
In the SVG2 spec, this has been offloaded to the "`Order of graphical
operations
<https://www.w3.org/TR/compositing/#compositingandblendingorder>`_"
section of the Compositing and Blending Level 1 spec. Once the render
tree is resolved, each node is painted like this, conceptually to a
transparent, temporary surface:
- Paint the shape/text/etc.
- Filters.
- Clip paths.
- Masks.
- Blend/composite the temporary surface onto the result.
The most critical function in librsvg is probably
:internals:struct-method:`rsvg::drawing_ctx::DrawingCtx::with_discrete_layer`;
it implements this drawing model.
Current state (2023/03/30)
--------------------------
``layout.rs`` has the beginnings of the render tree. It's probably mis-named? It contains this:
- A ``LayerKind`` with primitives for path-based shapes, text, and images.
- A `stacking context
<https://www.w3.org/TR/SVG2/render.html#EstablishingStackingContex>`_,
which indicates each layer's opacity/clip/mask/filters.
- A ``Layer`` which composes the previous two. The ``StackingContext``
provides the compositing/masking/filtering parameters, while the
``LayerKind`` determines the primitive contents of the layer.
- Various ancillary structures that try to have only user-space
coordinates (e.g. a number of CSS pixels instead of ``5cm``) and no
references to other things.
The last point is not yet fully realized. For example,
``StackingContext.clip_in_user_space`` has a reference to an element,
which will be used as the clip path — that one needs to be normalized
to user-space coordinates in the end. Also,
``StackingContext.filter`` is a filter list as parsed from the SVG,
not a ``FilterSpec`` that has been resolved to user space.
It would be good to resolve everything as early as possible to allow
lowering concepts to their final renderable form. Whenever we have
done this via refactoring, it has simplified the code closer to the
actual rendering via Cairo.
Major subprojects
-----------------
Path based shapes (``layout::Shape``) and text primitives
(``layout::Text``) are almost done. The only missing thing for shapes
would be to "explode" their markers into the actual primitives that
would be rendered for them. However...
There is no primitive for groups yet. Every SVG element that allows
renderable children must produce a group primitive of some sort:
``svg``, ``g``, ``use``, ``marker``, etc. Among those, ``use`` and
``marker`` are especially interesting since they must explode their
referenced subtree into a shadow DOM, which librsvg doesn't support
yet for CSS cascading purposes (the reference subtree gets rendered
properly, but the full semantics of shadow DOM are not implemented
yet).
Elements that establish a viewport (``svg``, ``symbol``, ``image``,
``marker``, ``pattern``) need to carry information about this
viewport, which is a ``viewBox`` plus ``preserveAspectRatio`` and
``overflow``. See :issue:`298` for a somewhat obsolete description
of the refactoring work needed to unify this logic.
The ``layout::StackingContext`` struct should contain another field,
probably called ``layer``, with something like this:
.. code-block:: rust
struct StackingContext {
// ... all its current fields
layer: Layer
}
enum Layer {
Shape(Box<Shape>),
Text(Box<Text>),
StackingContext(Box<StackingContext>)
}
That is, every stacking context should contain the thing that it will
draw, and that thing may be a shape/text or another stacking context!
Bounding boxes
--------------
SVG depends on the ``objectBoundingBox`` of an element in many places:
to resolve a gradient's or pattern's units, to determine the size of
masks and clips, to determine the size of the filter region.
The current big bug to solve is :issue:`778`, which requires
knowing the ``objectBoundingBox`` of an element **before** rendering
it, so that a temporary surface of the appropriate size can be created
for rendering the element if it has isolated opacity or masks/filters.
Currently librsvg creates a temporary surface with the size and
position of the toplevel viewport, and this is wrong for shapes that
fall outside the viewport.
The problem is that librsvg computes bounding boxes at the time of
rendering, not before that. However, now ``layout::Shape`` and
``layout::Text`` already know their bounding box beforehand. Work
needs to be done to do the same for a ``layout::Group`` or whatever
that primitive ends up being called (by taking the union of its
children's bounding boxes, so e.g. that a group with a filter can
create a temporary surface to be able to render all of its children
and then filter the surface).
Being able to compute the ``objectBoundingBox`` of an element before
rendering it would open the door to fixing bug :issue:`1` (yeah, really):
currently, the temporary surface used for filtering has the size of
the toplevel viewport, but this doesn't work well when one tries to
Gaussian-blur an element that lies partially outside that viewport.
The filter should apply to the element's extents plus the filter
region, which takes into account the extra space needed for a Gaussian
blur to work around a shape. Since librsvg cannot render the full
shape if it lies partially outside of the toplevel viewport, the
blurred result shows up with a halo near the image's edge, since
transparent pixels get "blurred in" with the shape's pixels.
Status
------
* 2023/Mar/30 - the "current viewport" is no longer part of
``DrawingCtx``'s mutable state. Instead, a ``Viewport`` struct is
passed down the call chain via a function argument. This is not
complete yet, since the code modifies the current ``cr``'s transform
apart from the current viewport's transform. The goal is to have
the current viewport actually have the full transform to be applied
to the object being rendered. This should simplify gnarly code
paths like the one for rendering ``<pattern>``.
* 2025/Apr/09 - The current ``Viewport`` is passed as an argument to
functions that need it, and it holds the current transform
correctly. Evidence of this is that the code does not call
``cr.transform()`` anymore; only
``cr.set_matrix(viewport.transform)`` in the innermost code, right
before drawing operations on the ``cr``.
Elements that "establish a new viewport", per the SVG spec, use a
``LayoutViewport`` and pass it to
``DrawingCtx::with_discrete_layer()``. This composes the
appropriate transform into the current ``Viewport`` and passes it on
to the drawing functions. Only the markers code remains to be
cleaned up for this, see :issue:`1162`.
Next steps: :issue:`1162`, :issue:`1163`.
We can actually start defining ``layout::Group`` now, or rather, the
implementation for ``DrawingCtx::draw_group()``.
* 2026/Mar/31 - clipping paths (from the ``clip-path`` property) are
now resolved into a :internals:struct:`rsvg::layout::ClipPath`
struct that lives inside
:internals:struct:`rsvg::layout::StackingContext`. This is just
used for ``clipPathUnits=userSpaceOnUse``, since the other case,
``clipPathUnits=objectBoundingBox``, depends on the layout system
being able to handle all the structural elements, which it does not
yet do. When all elements like `<svg>`, `<switch>`, etc. are able
to generate a :internals:struct:`rsvg::layout::Group` with a known
bounding box, the clipping code will be able to use the bounding box
for ``objectBoundingBox``.
Next steps: :issue:`271`. Instead of using ``cr.clip()``, actually
render clipping paths as alpha masks. This will let us perform
arithmetic on them (union, intersection) to handle all the cases
that the ``clipPath`` element supports (librsvg does not support
proper unions of clipping paths right now).

33
devel-docs/roadmap.rst Normal file
View File

@@ -0,0 +1,33 @@
Roadmap
=======
This is an ever-changing list of development priorities for the
maintainers of librsvg. Check this often!
Short term
----------
- Fix :issue:`778` about incorrect offsetting for layers with opacity.
Solving this should make it easier to fix the root cause of :issue:`1`, where
librsvg cannot compute arbitrary regions for filter effects and it only takes the
user-specified viewport into account. See :doc:`render_tree` for details on this.
- Continue with the revamp of :doc:`text_layout`.
- Support CSS custom properties ``var()``, at least the minimal
feature set required for OpenType fonts. See :doc:`custom_properties`.
- Make fuzzing good and easy - :issue:`1018`.
See the discussion in that issue for details of the pending work.
Medium term
-----------
- Once we have a :doc:`render_tree` in place (see above), it would be
convenient if librsvg could generate a tree of paintables for GTK,
so that GTK could in turn render the SVG with the GPU. This needs
detailing in a design document; see :issue:`1140`.
- :issue:`459` - Support CSS ``var()`` for custom colors and other SVG properties.
- :issue:`843` - Support CSS ``calc()``.

219
devel-docs/rsvg_bench.rst Normal file
View File

@@ -0,0 +1,219 @@
rsvg-bench
==========
The `rsvg-bench
<https://gitlab.gnome.org/GNOME/librsvg/-/tree/main/rsvg-bench>`_
program is a small utility for benchmarking librsvg.
Goals
-----
To benchmark librsvg, we would like to do several things:
- Be able to process many SVG images with a single command. For
example, this lets us answer a question like, "how long does version
N of librsvg take to render a directory full of SVG icons?" — which
is important for the performance of an application chooser.
- Be able to **repeatedly** process SVG files, for example, "render this
SVG 1000 times in a row". This is useful to get accurate timings,
as a single render may only take a few microseconds and may be hard
to measure. It also helps with running profilers, as they will be
able to get more useful samples if the SVG rendering process runs
repeatedly for a long time.
- Exercise librsvg's major code paths for parsing and rendering
separately. For example, librsvg uses different parts of the XML
parser depending on whether it is being pushed data, vs. being asked
to pull data from a stream. Also, we may only want to benchmark the
parser but not the renderer; or we may want to parse SVGs only once
but render them many times after that.
Compiling
---------
This benchmark is compiled along with ``librsvg``.
To compile the benchmark, you need to setup your development or test environment
for the ``librsvg`` library. You can follow the instructions in
:doc:`devel_environment`.
You can also simply run ``cargo build --release -p rsvg-bench`` in the
root of the ``librsvg`` source tree. This command will compile but the
``librsvg`` library and the ``rsvg-bench`` benchmark, linked together in a
static binary. You can run this binary from ``./target/release/rsvg-bench``.
Usage / benchmarking
--------------------
After compiling it, the ``rsvg-bench`` binary will be available
in the ``target/release`` directory of the ``librsvg`` source tree.
Please make sure you compiled with ``-release``; when librsvg is
installed normally it gets built in release mode (with optimizations),
so this will get you meaningful timings.
You can run the ``rsvg-bench`` binary with the following command line options:
Running ``target/release/rsvg-bench --help`` will display the help message.
.. code-block:: bash
Benchmarking utility for librsvg.
Usage: rsvg-bench [OPTIONS] [inputs]...
Arguments:
[inputs]... Input files or directories
Options:
--sleep <sleep> Number of seconds to sleep before starting to process SVGs [default: 0]
--num-parse <num-parse> Number of times to parse each file [default: 1]
--num-render <num-render> Number of times to render each file [default: 1]
--hard-failures Stop all processing when a file cannot be rendered
-h, --help Print help
-V, --version Print version
Benchmarking files
------------------
Rsvg-bench does not extract timings by itself. You can use other
tools to do it. ``/usr/bin/time`` is a simple and accurate way (note
that this is different from the ``time`` command in most shells).
Benchmarking all the SVG files in a directory tree
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
/usr/bin/time target/release/rsvg-bench /path/to/svg/files
This command will benchmark the rendering of all the SVG files in the directory ``/path/to/svg/files``.
The benchmark will parse each file once and render it once.
.. code-block:: bash
hard_failures: false
Will parse each file 1 times
Will render each file 1 times
Rendering to Cairo image surface
Sleeping for 0 seconds before processing SVGs...
Processing files!
Processing "rsvg/tests/fixtures/text/"
Processing "rsvg/tests/fixtures/text/hello-world.svg"
Processing "rsvg/tests/fixtures/text/bounds-ref.svg"
Processing "rsvg/tests/fixtures/text/display-none.svg"
Processing "rsvg/tests/fixtures/text/visibility-hidden.svg"
Processing "rsvg/tests/fixtures/text/visibility-hidden-ref.svg"
Processing "rsvg/tests/fixtures/text/span-bounds-when-offset-by-dx.svg"
Processing "rsvg/tests/fixtures/text/bug806-text-anchor-chunk.svg"
Processing "rsvg/tests/fixtures/text/span-bounds-when-offset-by-dx-ref.svg"
Processing "rsvg/tests/fixtures/text/visibility-hidden-x-attr.svg"
Processing "rsvg/tests/fixtures/text/unicode-bidi-override.svg"
Processing "rsvg/tests/fixtures/text/display-none-ref.svg"
Processing "rsvg/tests/fixtures/text/bug804-tspan-direction-change-ref.svg"
Processing "rsvg/tests/fixtures/text/unicode-bidi-override-ref.svg"
Processing "rsvg/tests/fixtures/text/bug804-tspan-direction-change.svg"
Processing "rsvg/tests/fixtures/text/bug806-text-anchor-chunk-ref.svg"
Processing "rsvg/tests/fixtures/text/bounds.svg"
0.28user 0.05system 0:00.29elapsed 114%CPU (0avgtext+0avgdata 31912maxresident)k
136inputs+0outputs (2major+1941minor)pagefaults 0swaps
The output will show the time taken to render each file. The time is in seconds,
the number of times each files are parsed and rendered, and the number of files that were processed.
Benchmarking specific files
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
target/release/rsvg-bench /path/to/svg/files/file.svg
This command will benchmark the rendering of a single SVG file ``/path/to/svg/files/file.svg``.
The benchmark will parse the file once and render it once.
You can also benchmark multiple files by passing their names:
.. code-block:: bash
target/release/rsvg-bench /path/to/svg/files/file1.svg /path/to/svg/files/file2.svg /path/to/svg/files/file3.svg
This command will benchmark the rendering of the SVG files ``/path/to/svg/files/file1.svg``, ``/path/to/svg/files/file2.svg``,
and ``/path/to/svg/files/file3.svg``. The benchmark will parse each file once and render it once.
.. code-block:: bash
hard_failures: false
Will parse each file 1 times
Will render each file 1 times
Rendering to Cairo image surface
Sleeping for 0 seconds before processing SVGs...
Processing files!
Processing "/path/to/svg/files/file1.svg"
Processing "/path/to/svg/files/file2.svg"
Processing "/path/to/svg/files/file3.svg"
0.28user 0.05system 0:00.29elapsed 114%CPU (0avgtext+0avgdata 31912maxresident)k
136inputs+0outputs (2major+1941minor)pagefaults 0swaps
Benchmarking with options
-------------------------
The ``rsvg-bench`` binary has several command line options that can be used to customize the benchmarking process.
They are listed above when we ran the `--help` option with the `target/release/rsvg-bench` command.
These options are:
- ``--sleep <sleep>``: Number of seconds to sleep before starting to process SVGs [default: 0]
- ``--num-parse <num-parse>``: Number of times to parse each file [default: 1]
- ``--num-render <num-render>``: Number of times to render each file [default: 1]
- ``--hard-failures``: Stop all processing when a file cannot be rendered
You can ask ``rsvg-bench`` to sleep for a number of seconds before processing the SVG files.
This is useful when you want to give the system some time to settle before
starting the benchmarking process and also so that you can attach a profiler
to it. For example, ``sysprof <https://blogs.gnome.org/chergert/2016/04/19/how-to-sysprof/>_``
lets you choose an already-running process to monitor.
.. code-block:: bash
target/release/rsvg-bench --sleep 5 /path/to/svg/files
This command will benchmark the rendering of all the SVG files in the directory ``/path/to/svg/files``.
The benchmark will parse each file once and render it once.
The benchmark will sleep for 5 seconds before processing the SVG files.
.. code-block:: bash
hard_failures: false
Will parse each file 1 times
Will render each file 1 times
Rendering to Cairo image surface
Sleeping for 5 seconds before processing SVGs...
Processing files!
Processing "rsvg/tests/fixtures/text/"
Processing "rsvg/tests/fixtures/text/hello-world.svg"
Processing "rsvg/tests/fixtures/text/bounds-ref.svg"
Processing "rsvg/tests/fixtures/text/display-none.svg"
Processing "rsvg/tests/fixtures/text/visibility-hidden.svg"
Processing "rsvg/tests/fixtures/text/visibility-hidden-ref.svg"
Processing "rsvg/tests/fixtures/text/span-bounds-when-offset-by-dx.svg"
Processing "rsvg/tests/fixtures/text/bug806-text-anchor-chunk.svg"
Processing "rsvg/tests/fixtures/text/span-bounds-when-offset-by-dx-ref.svg"
Processing "rsvg/tests/fixtures/text/visibility-hidden-x-attr.svg"
Processing "rsvg/tests/fixtures/text/unicode-bidi-override.svg"
Processing "rsvg/tests/fixtures/text/display-none-ref.svg"
Processing "rsvg/tests/fixtures/text/bug804-tspan-direction-change-ref.svg"
Processing "rsvg/tests/fixtures/text/unicode-bidi-override-ref.svg"
Processing "rsvg/tests/fixtures/text/bug804-tspan-direction-change.svg"
Processing "rsvg/tests/fixtures/text/bug806-text-anchor-chunk-ref.svg"
Processing "rsvg/tests/fixtures/text/bounds.svg"
0.28user 0.05system 0:00.29elapsed 114%CPU (0avgtext+0avgdata 31912maxresident)k
136inputs+0outputs (2major+1941minor)pagefaults 0swaps
.. code-block:: bash
target/release/rsvg-bench --num-parse 2 --num-render 2 /path/to/svg/files
This command will benchmark the rendering of all the SVG files in the directory ``/path/to/svg/files``.
The benchmark will parse each file twice and render it twice.

368
devel-docs/security.rst Normal file
View File

@@ -0,0 +1,368 @@
Security
========
Reporting security bugs
-----------------------
Please mail the maintainer at federico@gnome.org. You can use the GPG
public key from https://viruta.org/docs/fmq-gpg.asc to send encrypted
mail.
Librsvg releases with security fixes
------------------------------------
Librsvg releases have a version number like major.minor.micro.
Before version 2.55.x, librsvg's versioning scheme was such that a
release with an *even* minor number was considered a stable release
suitable for production use (e.g. 2.54.x), and an *odd* minor number
was a development release only.
Starting with 2.55.x, all minor numbers are considered stable.
Development and beta versions have a micro version starting at 90
(e.g. 2.55.90), per `GNOME's release versioning as of 2022/September
<https://discourse.gnome.org/t/even-odd-versioning-is-confusing-lets-stop-doing-it/10391>`_.
The following list is only for stable release streams.
2.57.4
~~~~~~
:rustsec:`2024-0421` - idna accepts Punycode labels that do not
produce any non-ASCII when decoded
:rustsec:`2024-0404` - Unsoundness in anstream
2.56.5
~~~~~~
:rustsec:`2024-0421` - idna accepts Punycode labels that do not
produce any non-ASCII when decoded
:rustsec:`2024-0404` - Unsoundness in anstream
`GHSA-c827-hfw6-qwvm
<https://github.com/advisories/GHSA-c827-hfw6-qwvm>`__
(CVE-2024-43806) - memory explosion in rustix.
2.56.3
~~~~~~
.. The CVE URL is used directly here because Sphinx's `cve` role can't be
used in a substitution since it generates a target for an index entry.
.. |CVE-2023-38633| replace::
`CVE-2023-38633 <https://www.cve.org/CVERecord?id=CVE-2023-38633>`__ -
:issue:`996` - Arbitrary file read when xinclude href has special
characters.
|CVE-2023-38633|
2.55.3
~~~~~~
|CVE-2023-38633|
2.54.7
~~~~~~
|CVE-2023-38633|
2.52.12
~~~~~~~
:rustsec:`2024-0421` - idna accepts Punycode labels that do not
produce any non-ASCII when decoded
:rustsec:`2024-0404` - Unsoundness in anstream
`GHSA-c827-hfw6-qwvm
<https://github.com/advisories/GHSA-c827-hfw6-qwvm>`__
(CVE-2024-43806) - memory explosion in rustix.
2.52.11
~~~~~~~
|CVE-2023-38633|
2.50.9
~~~~~~
|CVE-2023-38633|
2.50.4
~~~~~~
:rustsec:`2020-0146` - lifetime erasure in generic-array.
2.48.12
~~~~~~~
|CVE-2023-38633|
2.48.10
~~~~~~~
:cve:`2020-35905` - :rustsec:`2020-0059` - data race in futures-util.
:cve:`2020-35906` - :rustsec:`2020-0060` - use-after-free in futures-task.
:cve:`2021-25900` - :rustsec:`2021-0003` - buffer overflow in smallvec.
:rustsec:`2020-0146` - lifetime erasure in generic-array.
2.48.0
~~~~~~
:cve:`2019-20446` - guard against exponential growth of CPU time from
malicious SVGs.
.. |see libcroco notes| replace::
See notes below on :ref:`libcroco <libcroco>`.
.. caution::
**Releases older than 2.48.0 are not recommended.**
|see libcroco notes|
2.46.7
~~~~~~
|CVE-2023-38633|
|see libcroco notes|
2.46.5
~~~~~~
:rustsec:`2020-0146` - lifetime erasure in generic-array.
:cve:`2021-25900` - :rustsec:`2021-0003` - buffer overflow in smallvec.
|see libcroco notes|
2.44.17
~~~~~~~
:rustsec:`2020-0146` - lifetime erasure in generic-array.
:cve:`2019-15554` - :rustsec:`2019-0012` - memory corruption in smallvec.
:cve:`2019-15551` - :rustsec:`2019-0009` - double-free and use-after-free
in smallvec.
:cve:`2021-25900` - :rustsec:`2021-0003` - buffer overflow in smallvec.
|see libcroco notes|
2.44.16
~~~~~~~
:cve:`2019-20446` - guard against exponential growth of CPU time from
malicious SVGs.
|see libcroco notes|
2.42.8
~~~~~~
:cve:`2019-20446` - guard against exponential growth of CPU time from
malicious SVGs.
|see libcroco notes|
2.42.9
~~~~~~
:cve:`2018-20991` - :rustsec:`2018-0003` - double-free in smallvec.
|see libcroco notes|
2.40.21
~~~~~~~
:cve:`2019-20446` - guard against exponential growth of CPU time from
malicious SVGs.
|see libcroco notes|
2.40.18
~~~~~~~
:cve:`2017-11464` - Fix division-by-zero in the Gaussian blur code.
|see libcroco notes|
.. attention::
**Earlier releases should be avoided and are not listed here.**
.. _libcroco:
.. admonition:: Important note on libcroco
Note that librsvg 2.46.x and earlier use
`libcroco <https://gitlab.gnome.org/Archive/libcroco/>`__ for parsing
CSS, but that library is deprecated, unmaintained, and has open CVEs as
of May 2021.
If your application processes untrusted data, please avoid using librsvg
2.46.x or earlier. The first release of librsvg that does not use
libcroco is 2.48.0.
Librsvgs C dependencies
------------------------
Librsvg depends on the following libraries implemented in memory-unsafe
languages:
- **libxml2** - loading XML data.
- **cairo** - 2D rendering engine.
- **freetype2** - font renderer.
- **harfbuzz** - text shaping engine.
- **pango** - high-level text rendering.
- **fontconfig** - system fonts and rules for using them.
And of course, their recursive dependencies as well, such as
**glib/gio**.
The required versions for those libraries are not pinned (fixed to a
specific version). Instead, the minimum required version is checked
via the ``meson`` build system, for shared library builds, or by Rust's
``system-deps`` which uses ``pkg-config`` underneath.
Librsvg's Rust dependencies
---------------------------
Librsvg's Rust dependencies are pinned to specific versions with
``Cargo.lock``. We track the security and recency of these versions in
various ways:
* There is a ``deny`` job in the CI which runs `cargo-deny
<https://github.com/EmbarkStudios/cargo-deny>`_. This presents
information about dependencies with vulnerabilities, duplicate
versions of dependencies, and other interesting data.
* There is a project badge in the `main librsvg project page
<https://gitlab.gnome.org/GNOME/librsvg>`_ which points to
``deps.rs``. This checks whether dependencies are out of date, and
flags vulnerable versions as well.
Security considerations for the image-rs crate
----------------------------------------------
Librsvg uses the `image-rs <https://github.com/image-rs/image>`_ crate
for decoding raster images. You may want to look at its dependencies
for specific codecs like the ``png`` or ``zune-jpeg`` crates.
Librsvg explicitly compiles ``image-rs`` with support for only the following formats:
* JPEG
* PNG
* GIF
* WEBP
The following formats are optional, and selected at compilation time:
* AVIF (compile-time option ``avif``)
See the :ref:`compile_time_options` section in :doc:`compiling` for details.
Security considerations for libxml2
-----------------------------------
Librsvg uses the following configuration for the SAX2 parser in libxml2:
- ``XML_PARSE_NONET`` - forbid network access.
- ``XML_PARSE_BIG_LINES`` - store big line numbers.
As a special case, librsvg enables ``replaceEntities`` in the
``_xmlParserCtxtPtr`` struct so that libxml2 will expand references only
to internal entities declared in the DTD subset. External entities are
disabled.
For example, the following document renders two rectangles that are
expanded from internal entities:
::
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd" [
<!ENTITY Rect1 "<rect x='15' y='10' width='20' height='30' fill='blue'/>">
<!ENTITY Rect2 "<rect x='10' y='5' width='10' height='20' fill='green'/>">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60">
&Rect1;
&Rect2;
</svg>
However, an external entity like
::
<!ENTITY foo SYSTEM "foo.xml">
will generate an XML parse error and the document will not be loaded.
Security considerations for Cairo
---------------------------------
Cairo versions before 1.17.0 are easy to crash if given coordinates
that fall outside the range of its 24.8 fixed-point numbers. Please
make sure that you use librsvg with Cairo 1.17.0 or newer.
The first version of librsvg to require at least Cairo 1.17.0 is
librsvg 2.56.90 (development), or librsvg 2.57.0 (stable).
Security considerations for librsvg
-----------------------------------
**Built-in limits:** Librsvg has built-in limits for the following:
- Limit on the maximum number of loaded XML elements, set to 1,000,000
(one million). SVG documents with more than this number of elements
will fail to load. This is a mitigation for malicious documents that
would otherwise consume large amounts of memory, for example by
including a huge number of ``<g/>`` elements with no useful content.
This is set in the file ``rsvg/src/limits.rs`` in the
``MAX_LOADED_ELEMENTS`` constant.
- Limit on the maximum number of referenced elements while rendering.
The ``<use>`` element in SVG and others like ``<pattern>`` can
reference other elements in the document. Malicious documents can
cause an exponential number of references to be resolved, so librsvg
places a limit of 500,000 references (half a million) to avoid
unbounded consumption of CPU time. This is set in the file
``rsvg/src/limits.rs`` in the ``MAX_REFERENCED_ELEMENTS`` constant.
- Limit on the nesting level for XML Includes (``xi:include``), to
avoid infinite recursion from an SVG file that includes itself.
This is set in the file ``rsvg/src/limits.rs`` in the
``MAX_XINCLUDE_DEPTH`` constant.
- Limit on the depth of nested groups and layers, to avoid unbounded
recursion. This is set in the file ``rsvg/src/limits.rs`` in the
``MAX_LAYER_NESTING_DEPTH`` constant.
Librsvg has no built-in limits on the total amount of memory or CPU time
consumed to process a document. Your application may want to place
limits on this, especially if it processes untrusted SVG documents.
**Processing external files:** Librsvg processes references to
external files by itself: XML XInclude, ``xlink:href`` attributes,
etc. Please see the section "`Security and locations of referenced
files
<https://gnome.pages.gitlab.gnome.org/librsvg/Rsvg-2.0/class.Handle.html#security-and-locations-of-referenced-files>`_"
in the reference documentation to see what criteria are used to accept
or reject a file based on its location. If your application has more
stringent requirements, it may need to sandbox its use of librsvg.
**SVG features:** Librsvg ignores animations, scripts, and events
declared in SVG documents. It always handles referenced images, similar
to SVGs `static processing
mode <https://www.w3.org/TR/SVG2/conform.html#static-mode>`__.

View File

@@ -0,0 +1,41 @@
Supported versions
==================
Only these versions are supported:
* 2.61.x
* 2.62.x
Older versions are not supported. Please try a newer version before
reporting bugs or missing features.
Policy for supported versions
-----------------------------
At any one time, we support the latest stable version stream and the
``main`` development branch. At the discretion of the maintainer, the
previous stable version stream may also be supported.
You can see the :doc:`security` chapter for information about versions
with security fixes.
Stable release streams
----------------------
Starting with librsvg 2.55.x, all subsequent release streams are
considered stable. Before that, release streams with an even minor
version (e.g. 2.54.x) were stable, and ones with an odd minor version
(2.53.x) were unstable. So, these are all stable now:
* 2.55.x
* 2.56.x
* 2.57.x
* etc.
Development releases
--------------------
Versions whose micro component is 90 or greater (e.g. 2.59.90) are
considered development or beta versions before the next stable release
stream. For example, 2.59.90 is the first beta before the 2.60.0
stable release.

676
devel-docs/text_layout.rst Normal file
View File

@@ -0,0 +1,676 @@
Text layout
===========
This document describes the state of text layout in librsvg as of
version 2.55.90, and how I want to overhaul it completely for SVG2.
Status as of librsvg 2.55.90
----------------------------
Basic supported features:
- Librsvg supports the elements ``text``, ``tspan``, ``a`` inside text,
and ``tref`` (deprecated in SVG2, but kept around for SVG1.1
compatibility). See below for the ``x/y/dx/dy`` attributes; librsvg
supports single-number values in these.
- ``text-anchor``.
- SVG1.1 values for ``direction``, ``writing-mode``. Non-LTR or
vertical text layout is very much untested.
- SVG1.1 values for ``letter-spacing``, ``baseline-shift``,
``text-decoration``.
- ``font`` (shorthand), ``font-family``, ``font-size``,
``font-stretch``, ``font-style``, ``font-variant``, ``font-weight``.
- ``text-rendering``.
- ``unicode-bidi`` and ``direction``. This is done for each text
span, but not for the whole ``<text>`` element yet. See below for
details.
Major missing features:
- ``text-orientation`` and ``glyph-orientation-vertical`` fallbacks,
SVG2 values for ``writing-mode``.
- SVG2 ``white-space`` handling. This deprecates ``xml:space`` from
SVG1.1.
- Support for multiple values in each of the attributes ``x/y/dx/dy``
from the ``text`` and ``tspan`` elements. Librsvg supports a single
value for each attribute, whereas SVG allows for multiple values —
these then get used to individually position “typographic characters”
(Pango clusters). In effect, librsvgs single values for each of
those attributes mean that each text span can be positioned
independently, but not each character.
- Relatedly, the ``rotate`` attribute is not supported. In SVG it also
allows multiple values, one for each character.
- ``glyph-orientation-vertical`` (note that
``glyph-orientation-horizontal`` is deprecated in SVG2).
- ``textPath`` is not supported at all. This will be made much easier
by implementing ``x/y/dx/dy/rotation`` first, since each character
needs to be positioned and oriented individually.
- ``@font-face`` and WOFF fonts.
- Emoji got inadvertently broken; see the "Emoji" section below.
Other missing features:
- ``display`` and ``visibility`` are not very well tested for the
sub-elements of ``<text>``.
- SVG2 text with a content area / multi-line / wrapped text:
``inline-size``, ``shape-inside``, ``shape-subtract``,
``shape-image-threshold``, ``shape-margin``, ``shape-padding``. This
is lower priority than the features above. Also the related
properties ``text-overflow``,
- ``text-align`` (shorthand), ``text-align-all``, ``text-align-last``,
``text-indent``, ``word-spacing``.
- Baselines: ``vertical-align`` (shorthand), ``dominant-baseline``,
``alignment-baseline``, ``baseline-source``, and SVG2 values for
``baseline-shift``. Note that Pango doesnt provide baseline
information yet.
- ``line-height`` (parsed, but not processed).
- SVG2 ``text-decoration``, which translates to
``text-decoration-line``, ``text-decoration-style``,
``text-decoration-color``.
- ``font-feature-settings``, ``font-kerning``, ``font-size-adjust``.
- CSS Text 3/4 features not mentioned here.
Features that will not be implemented:
- SVG1.1 features like ``<font>`` and the
``glyph-orientation-horizontal`` property, that were deprecated for
SVG2.
Roadmap summary
---------------
Since librsvg 2.52.1 Ive started to systematically improve text
support. Many thanks to Behdad Esfahbod, Khaled Ghetas, Matthias Clasen
for their advice and inspiration.
First, I want to get **bidi** to a state where it is reliable, at least
as much as LTR languages with Latin text are reliable right now:
- Add tests for the different combinations of ``text-anchor`` and
``direction``; right now there are only a few tested combinations.
- Test and implement multiply-nested changes of direction. I think only
a single level works right now.
- Even if white-space handling remains semi-broken, I think its more
important to have “mostly working” bidi than completely accurate
white-space handling and layout.
Second, actually overhaul librsvgs text engine by implementing the SVG2
text layout algorithm:
- Implement the ``text-orientation`` property, and implement fallbacks
from the deprecated ``glyph-orientation-vertical`` to it. If this
turns out to be hard with the current state of the code, I will defer
it until the SVG2 text layout algorithm below.
- Implement the SVG2 text layout algorithm and ``white-space`` handling
at the same time. See the detailed roadmap below.
Third, implement all the properties that are not critical for the text
layout algorithm, and things like ``@font-face``. Those can be done
gradually, but I feel the text layout algorithm has to be done all in a
single step.
Architecture notes
------------------
A common theme over the next subsections is, "we need a single
``pango::Layout`` per ``<text>`` element". Keep that in mind as the
main goal of initial refactoring.
Chunks and spans
~~~~~~~~~~~~~~~~
Librsvg implements a limited subset the `text layout as per SVG1.1
<https://www.w3.org/TR/SVG11/text.html>`_, which was feasible to
implement in terms of *chunks* and *spans*.
A *span* is an ``<tspan>`` element, or some character content inside ``<text>``.
When a ``tspan`` explicitly lists ``x`` or ``y`` attributes, it
creates a new *chunk*. A text chunk defines an absolutely-positioned
sequence of spans.
This is why you'll see that the code does this; start at
:internals:struct-method:`rsvg::text::Text::draw`:
- Start with an empty list of chunks
(:internals:struct-method:`rsvg::text::Text::make_chunks`).
Push an empty initial chunk defined by the ``x`` and ``y``
coordinates of the ``<text>`` element.
- Recursively call :internals:fn:`rsvg::text::children_to_chunks`
on the children of the ``<text>`` element, to create chunks and
spans for them.
- :internals:struct-method:`rsvg::text::TSpan::to_chunks`
sees if the span has ``x`` or ``y`` attributes; if so, it pushes a
new empty chunk with those coordinates. Then it recursively calls
``children_to_chunks`` to grab its character content and children.
- Later, ``Text::draw`` takes the list of chunks and their spans, and
converts them into a list of :internals:struct:`rsvg::text::MeasuredChunk`.
This process turns each span into a
:internals:struct:`rsvg::text::MeasuredSpan`. The key element here is to
create a ``pango::Layout`` for each span, and ask it for its size.
- Then, ``Text::draw`` takes the list of ``MeasuredChunk`` and turns them
into a list of :internals:struct:`rsvg::text::PositionedChunk`. Each of
those builds a list of :internals:struct:`rsvg::text::PositionedSpan` based
on the span's own text advance, plus the span's ``dx``/``dy`` attributes.
**Note about SVG2:** The `text layout algorithm for SVG2
<https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm>`_ is very
different from the above. It mostly dispenses with explicit
computation of chunks with spans, and instead, for each glyph it
stores a flag that says whether the glyph is at the beginning of a
chunk.
Layouts and spans
~~~~~~~~~~~~~~~~~
Librsvg creates a ``pango::Layout`` for each
text span in a ``<text>`` element, whether it comes from a ``<tspan>``
or not. For example, ``<text>A <tspan>B</tspan> C</text>`` has three
spans, and three Pango layouts created for it. Each span's
``pango::Layout`` gets configured via ``pango::AttrList`` with the
styles it needs (bold/italic, font size, etc.).
When a ``pango::AttrList`` gets created, each individual attribute has
a start/end index based on the byte offsets for the corresponding
characters. Currently, **all the attributes for a span occupy the whole text span**. So, for something like
.. code-block:: xml
<text>
Hello
<tspan font-weight="bold">
BOLD
</tspan>
World
</text>
three ``pango::Layout`` objects get created, with ``Hello``, ``BOLD``,
and ``World``, and the second one has a ``pango::AttrList`` that spans
its entire 4 bytes. (There's probably some whitespace in the span,
and the attribute list would include it — I'm saying "4" since it is
easy to visualize for example purposes.) So, currently there are
three ``PangoLayout`` and each with a ``PangoAttrList``:
.. image:: assets/multiple-layouts.jpg
However, this is sub-optimal. Ideally there should be a *single*
``pango::Layout`` for a single string, ``Hello BOLD World``, and the
attribute list should have a boldface attribute just for the word in
the middle.
.. image:: assets/single-layout.jpg
Why? Two reasons: shaping needs to happen across spans (it doesn't
right now), and the handling for ``unicode-bidi`` and ``direction``
need to be able to work across nested spans (they work with a single
level of nesting right now). Read the "Bidi handling" section below
for more info.
The ``add_pango_attributes`` function is already able to handle
substrings of a ``pango::Layout``; it's just that it is always called
with the whole layout right now.
**The initial refactoring:** Change the text handling code to first
gather all the character content inside a ``<text>`` into a single
string, while keeping track of the offsets of each span. Make the
``pango::AttrList`` taking those offsets into account. Then, feed
that single string to a ``pango::Layout``, with the attributes.
**Further work:** Don't just paint the layout, but iterate it / break
it up into individual ``pango::GlyphString``, so librsvg can lay out
each individual glyph itself using the SVG2 layout algorithm.
Be careful with PDF output when handling individual glyphs: grep for
``can_use_text_as_path`` in ``drawing_ctx.rs``.
Bidi handling
~~~~~~~~~~~~~
The ``unicode-bidi`` and ``direction`` properties get handled
together. The :internals:struct:`rsvg::text::BidiControl`
struct computes which Unicode control characters need to be inserted
at the start and end of a ``<tspan>``'s text; SVG authors use these
properties to override text direction when inserting LTR or RTL text
within each other.
Unfortunately, these control characters can only really work for
nested levels of embedding **if the whole text is in a single
``pango::Layout``**. Per the previous section, librsvg doesn't do
this yet.
:pr:`621` implemented the SVG2 values for the ``unicode-bidi`` property.
You may want to read the detailed commit messages there, and the
discussion in the merge request, to see details of future development.
Detailed roadmap
----------------
Add tests for combinations of ``text-anchor`` and ``direction``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These are easy to add now that librsvgs tests make use of the Ahem
font, in which each glyph is a 1x1 em square.
Implement the ``text-orientation`` property
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This may just be the property parser and hooking it up to the machinery
for properties. Actual processing may be easier to do in the SVG2 text
layout algorithm, detailed below.
Implement the SVG2 text layout algorithm and ``white-space`` handling.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Shaping:** One thing librsvg does wrong is that for each ``<tspan>``,
or for each synthesized text span from a ``<text>`` element, it creates
a separate ``pango::Layout``. This means that text shaping is not done
across element boundaries (SVG2 requirement). Implementing this can be
done by creating a string by recursively concatenating the character
content of each ``<text>`` element and its children, and adding
``pango::Attribute``\ s with the proper indexes based on each childs
character length. This creates an un-shaped string in logical order with
all the characters inside the ``<text>``, to be used in the next steps.
Pango details: create a single ``pango::Layout``, per ``<text>``
element, with ``pango::Attribute`` for each text span. Set the layout to
``set_single_paragraph_mode()`` so it does not break newlines. Pango
will then translate them to characters in the ``Layout``, and the
white-space handling and SVG2 text layout algorithm below can detect
them.
**White-space handling:** SVG2 has a new ``white-space`` property that
obsoletes ``xml:space`` from SVG1.1. Implementing this depends on the
concatenated string from the steps above, so that white-space can be
collapsed on the result. Maybe this needs to be done before inserting
bidi control characters, or maybe not, if the state machine is adjusted
to ignore the control characters.
**SVG2 text layout algorithm:** This is the big one. The spec has
pseudocode. It depends on the shaping results from Pango, and involves
correlating “typographic characters” (Pango clusters) with the
un-shaped string in logical order from the “Shaping”, and the
information about `discarded white-space characters
<https://www.w3.org/TR/css-text-3/#white-space-processing>`_. The
complete text layout algorithm would take care of supporting
multi-valued ``x/y/dx/dy/rotate``, ``textPath`` (see below), plus bidi
and vertical text.
Do look at the issues in the `svgwg repository at GitHub
<https://github.com/w3c/svgwg/tree/master>`_ - there are a couple that
mention bugs in the spec's pseudocode for the text layout algorithm.
Bidi embedding
~~~~~~~~~~~~~~
When there are nested left-to-right (LTR) and right-to-left (RTL) languages in a text element, this must happen:
1. Extract the text content from the spans, but...
2. Insert Unicode control characters at each embedding level (at each span boundary)...
3. So that Pango/Harfbuzz/etc. will know when text direction must change.
For example, consider this SVG from
:source:`rsvg/tests/fixtures/text/unicode-bidi-override.svg`:
.. code:: xml
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600">
<rect x="0" y="0" width="100%" height="100%" fill="white"/>
<text x="100" y="100" font-family="Ahem" font-size="20">ÉAp<tspan direction="rtl" unicode-bidi="bidi-override">ÉAp</tspan>ÉAp</text>
</svg>
It gets rendered like this (see the description of the Ahem font below to make sense of this):
.. image:: assets/unicode-bidi.jpg
Let's break it up part by part:
* The ``<text>`` element starts by default in left-to-right (LTR) direction.
* The first ``ÉAp`` gets laid out trivially, from left to right.
* The second ``ÉAp`` is in a ``tspan`` with ``direction="rtl"`` and ``unicode-bidi="bidi-override"``. It gets laid out right-to-left, and looks like ``pAÉ``, but for the platform libraries to know this, it needs to be surrounded with Unicode control characters "Right to Left Override (RLO)" and "Pop Directional Formatting (PDF)". This is done in `from_unicode_bidi_and_direction() <https://gitlab.gnome.org/GNOME/librsvg/-/blob/5d43d27adec414515669a817e015832c85ec7232/rsvg/src/text.rs?page=2#L1177-1198>`_ and `wrap_with_direction_control_chars() <https://gitlab.gnome.org/GNOME/librsvg/-/blob/5d43d27adec414515669a817e015832c85ec7232/rsvg/src/text.rs?page=2#L1200-1216>`_.
* The third ``ÉAp`` gets laid out again from left-to-right.
Text rendering
~~~~~~~~~~~~~~
Librsvg is moving towards a “render tree” or “display list” model,
instead of just rendering everything directly while traversing the DOM
tree.
Currently, the text layout process generates a ``layout::Text`` object,
which is basically an array of ``pango::Layout`` with extra information.
It should be possible to explode these into ``pango::GlyphItem`` or
``pango::GlyphString`` and annotate these with ``x/y/rotate``
information, which will be the actual results of the SVG2 text layout
algorithm.
Although currently Pango deals with underlining, it may be necessary to
do that in librsvg instead - I am not sure yet how ``textPath`` or
individually-positioned ``x/y/dx/dy/rotate`` interact with underlining. See also
Pango internals
~~~~~~~~~~~~~~~
::
/**
* pango_renderer_draw_glyph_item:
* @renderer: a `PangoRenderer`
* @text: (nullable): the UTF-8 text that @glyph_item refers to
* @glyph_item: a `PangoGlyphItem`
* @x: X position of left edge of baseline, in user space coordinates
* in Pango units
* @y: Y position of left edge of baseline, in user space coordinates
* in Pango units
*
* Draws the glyphs in @glyph_item with the specified `PangoRenderer`,
* embedding the text associated with the glyphs in the output if the
* output format supports it.
*
* This is useful for rendering text in PDF.
* ...
*/
Note that embedding text in PDF to make it selectable involves passing a
non-null ``text`` to pango_renderer_draw_glyph_item(). Well have to
implement this by hand, probably.
Wrapped text in a content area
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This roadmap does not consider the implementation fo wrapped text yet.
User-provided fonts, ``@font-face`` and WOFF
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This involves changes to the CSS machinery, to parse the ``@font-face``
at-rule. Librsvg would also have to obtain the font and feed it to
FontConfig. I am not sure if FontConfig can deal with WOFF just like
with normal ``.ttf`` files.
See the issue on the :issue:`Future of the pango dependency <876>`
for lots of goodies which may come in handy.
Emoji is broken
~~~~~~~~~~~~~~~
:issue:`599` is a terrible bug in Pango, which causes it to report
incorrect metrics when text is scaled non-proportionally (e.g. different
scale factors for the X/Y dimensions). Librsvg works around this by
converting all text to Bézier paths, then scaling the paths, and then
stroking/filling them.
However, this breaks emoji - :issue:`911`, since converting its glyphs
to paths loses the color information.
Two strategies to fix this; there may be more:
- Detect if the text is scaled proportionally (this is the common
case), and use the old code for that, without converting text to
paths. This may be easy to do? Grep for ``can_use_text_as_path``
in ``drawing_ctx.rs`` which already has some of the logic but for
handling PDF output.
- Do the whole "split a ``pango::Layout`` into glyphs" from above;
keep handling individual glyphs as paths, and special-case emoji to
render them via Cairo.
Issues
------
:issue:`795` - Implement SVG2 white-space behavior.
Issues that have not been filed yet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
From the spec: “It is possible to apply a gradient, pattern, clipping
path, mask or filter to text.” We need better tests for the
``objectBoundingBox`` of the whole ``<text>``; I think :issue:`they are
wrong for vertical text <55>`, and this shows up when filling its spans
with gradients or patterns.
Clip/mask/filter do not work on individual spans yet. I am not sure
if their ``objectBoundingBox`` refers to the whole ``<text>`` or just
the span.
Multiply-nested changes of text direction / bidi overrides; see the
"Bidi handling" section above.
Glossary so I dont have to check the Pango docs every time
-----------------------------------------------------------
PangoItem - A range within the users string that has the same
language/script/direction/level/etc. (Logical order).
PangoLayoutRun - same as PangoGlyphItem - a pair of PangoItem and the
PangoGlyphString it generated during shaping. (Visual order).
PangoGlyphString - The glyphs generated for a single PangoItem.
PangoGravityHint - Defines how horizontal scripts should behave in a
vertical context.
Development plan
----------------
There's a few of ways of implementing the SVG2 text algorithm, while
keeping the existing SVG1.1 code working:
* Try to refactor ``rsvg/src/text.rs`` gradually to SVG2. This is probably
overkill; the SVG1.1 algorithm there is very much oriented towards
just thinking of chunks ans spans, and the SVG2 algorithm subsumes
those.
* Disable all the tests that use text, and write a new implementation.
Enable the tests gradually as features appear.
* Implement a ``<text2>`` element with all the new code for SVG2.
Don't disable existing tests; rather, write a test suite based on
the Ahem font just for ``<text2>`` that lets us build things from
the ground up.
There is code in ``rsvg/src/text.rs`` that will still be useful: the
conversions between SVG types and Pango types, the tables with Unicode
directional formatting characters, and some of the utility functions
for Pango.
I think the third scheme is the best one to follow for internships:
* We can preserve the current code as it is; it works fine for many purposes.
* We can write a new test suite for text that we *know* is
comprehensive, instead of relying on the sketchy tests from the
SVG1.1 test suite. The Ahem font should make it easy to have
reftest-style tests and let us implement things in a "constructive"
fashion from the ground up.
* We can compare the output of ``<text2>`` and ``<text>`` without
hackery or conditional compilation, to ensure that new new
implementation produces as reasonable results as the old one.
* Eventually we can remove the old implementation, rename ``text2`` to
``text`` everywhere, and just leave the new implementation in place.
Reproducible text rendering for the test suite
----------------------------------------------
Librsvg uses the pango/harfbuzz/freetype/fontconfig stack, so it
assumes that fonts are installed in some system-wide location, and
perhaps the user's own ``~/.fonts``. However, for the test suite we
would like to have a 100% predictable set of available fonts, and a
reproducible configuration for things which even *have* configuration
like fontconfig.
Briefly:
* Pango - does high-level text layout for GTK and librsvg; uses all
the following libraries. You give it a Unicode string, and it will
do bidi/shaping/layout and render it for you.
* Harfbuzz - Does text shaping.
* Freetype - Renders glyphs.
* Fontconfig - Does "font enumeration", or finding the fonts that are
installed on the system, and substitues missing fonts; if you ask
for "Times New Roman" but don't have it installed, fontconfig can
give you another serif font instead.
Librsvg mostly only uses Pango directly, with one exception. The code
to set up the test suite calls Fontconfig to set up a minimal, custom
font map. We want to make the fonts available from
``rsvg/tests/resources/`` *and nothing else* so that we know exactly
what will be used while rendering test files. There's also a custom
``fonts.conf`` there for Fontconfig, to set up some minimal
substitutions.
Ahem font for tests
-------------------
A big problem when writing tests for a text layout engine is that font
rendering can change in extremely subtle ways depending on the
underlying font-rendering libraries, the available fonts, the
rendering options, etc. If a few pixels change in the antialiasing
around glyphs, or if glyphs shift around by sub-pixel distances, is
that a failed test or not?
To make it easy to write reproducible tests, there is the `Ahem font
<https://web-platform-tests.org/writing-tests/ahem.html>`_. It is
useless for human-readable text, but **most glyphs are 100% simple
squares** or simple rectangles that you can compare easily to
reference images.
This is the string ``A`` rendered in the Ahem font, with red lines
that cross at its anchor point and baseline:
.. image:: assets/ahem-a.png
.. literalinclude:: assets/ahem-a.svg
:language: xml
Note the following:
* This is a single glyph ``A`` that renders as a square.
* The square is exactly 50 pixels tall and wide, since we specified
``50px`` in the ``font:`` property.
* The ascent is 40 pixels, above the horizontal red line, and the
descent is 10 pixels below it.
Now let's render two glyphs ``AB``, centered:
.. image:: assets/ahem-ab.png
.. literalinclude:: assets/ahem-ab.svg
:language: xml
Now the same as before, but with each glyph in a separate ``tspan`` of a different color:
.. image:: assets/ahem-ab-color.png
.. literalinclude:: assets/ahem-ab-color.svg
:language: xml
What if we need to test some things but actually be able to
differentiate glyphs? Here, the glyphs for ``A``, ``p`` and ``É`` are
rendered different. `See the available glyphs
<https://hydrock.github.io/AhemFont/>`_. In any case, all the glyphs
fit in the em-square and are just rectangles that cover different
parts of that area.
.. image:: assets/ahem-different-glyphs.png
.. literalinclude:: assets/ahem-different-glyphs.svg
:language: xml
Details on the Ahem font
~~~~~~~~~~~~~~~~~~~~~~~~
* `Ahem font, main page <https://web-platform-tests.org/writing-tests/ahem.html>`_
* `Rendered glyphs for easy reference <https://hydrock.github.io/AhemFont/>`_
* `Source for the previous link, with interesting examples <https://github.com/Hydrock/AhemFont?tab=readme-ov-file>`_
* `Ahem font README with list of glyphs <https://www.w3.org/Style/CSS/Test/Fonts/Ahem/README>`_
* `Fonts for CSS testing <https://www.w3.org/Style/CSS/Test/Fonts/>`_
How librsvg's test suite uses Ahem
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you grep for ``Ahem`` in the test files (``rsvg/tests/``), you will
find how it is used to test the layout of various aspects of the
``<text>`` element.
When implementing the SVG2 text layout algorithm, we should have
fine-grained tests for each little feature, to ensure that it produces
the expected layout. Regretfully, the original SVG1.1 test suite only
has very high level tests for text layout, which don't make it easy to
automatically test if the building blocks of the code are correct.
In the Ahem font, for each glyph 4/5 of the heigth are above the
baseline, and 1/5 of the height is below the baseline. This means
that to get whole-pixel-exact rendering, your glyphs should have a
size that is a multiple of 5. In the examples above, we used font
heights of 40px and 50px, which are of course multiples of 5.
Some ideas for the ``<text2>`` tests with Ahem
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Testing general layout and ``text-anchor``.
* Testing bounding boxes.
* Use different glyphs and colors to test bidi embedding. For
example, here ``RGB`` renders as ``BGR`` due to ``direction="rtl"``:
.. image:: assets/ahem-rtl.png
.. literalinclude:: assets/ahem-rtl.svg
:language: xml

60
devel-docs/viewport.rst Normal file
View File

@@ -0,0 +1,60 @@
Viewport abstraction
====================
**Status:** implemented as of 2025/Apr/09.
:issue:`298` is about unifying several concepts into a Viewport abstraction.
This chapter attempts to explain that.
SVG has the concept of `establishing a new viewport
<https://svgwg.org/svg2-draft/coords.html#EstablishingANewSVGViewport>`_,
in particular for the elements ``<svg>`` and ``<symbol>``. The
viewport is set up like this:
- Apply the element's ``transform``.
- Compute a transform / coordinate system from the element's
size+position (``x``, ``y``, ``width``, ``height``), the
``preserveAspectRatio`` and ``viewBox`` attributes.
- Set up a clipping rectangle if the element's ``overflow`` property
says so.
However, that mechanism is general enough that it can also be made to
work when rendering the elements ``<image>``, ``<marker>``, and
``<pattern>``. They have their own way of specifying a size (e.g. the
marker-specific ``markerWidth`` and ``markerHeight`` attributes), but
they also need to compute a new transform, set up clipping, etc.
The original code for librsvg reimplemented the mechanism above
independently for each of ``<marker>``, ``<symbol>``, etc., by doing
direct calls to Cairo to set up a transform and a clipping rectangle.
Unfortunately, during the initial port to Rust we did not identify
this pattern to gather the various implementations and abstract them
in a single place. Gradual refactoring led to all calls to Cairo
happening in ``drawing_ctx.rs``, instead of all over the code. Still,
the various versions still exist, with slightly different mechanisms
for each.
What I'd like to do
-------------------
The idea in #298 is to consolidate all the parameters needed for a
viewport, as mentioned above, into a single place. Now that the
structs for a :doc:`render_tree` are starting to take hold, I think we
can do these:
- Move the ``Viewport`` struct from ``drawing_ctx.rs`` into ``layout.rs``.
- Add the necessary fields from the previous section (element's
transform, perhaps moved from the ``StackingCtx``), the viewport
size, ``preserveAspectRatio``, and ``overflow``.
- (Look at the Firefox source code a bit before doing that; they have
nice code for it.)
- One by one, migrate each part of librsvg that requires it to using
the new ``Viewport`` abstraction with everything in it. We can
probably start with ``<svg>`` and ``<use> / <symbol>``; markers and
patterns may need a little extra untangling.

144
devel-docs/xml_parser.rst Normal file
View File

@@ -0,0 +1,144 @@
XML Parser in Rust
==================
**Status as of 2025/May/02: not implemented**
The purpose of this proposal is to replace libxml2 in librsvg with a
Rust-based XML parser.
Librsvg uses `libxml2 <https://gitlab.gnome.org/GNOME/libxml2>`_ to do
the initial XML parsing of an SVG document. It does not let libxml2
build its own tree representation; instead, it uses the SAX2
"streaming" parser API and so librsvg builds a tree of its own with
tag names and attributes.
Pragmatically speaking, there is nothing wrong with using libxml2 in
the way that librsvg uses it:
* Libxml2 is fast.
* It is well-maintained, is fuzz-tested at scale, and is such a
critical piece of infrastructure that people actually pay attention
to it.
* It has built-in mitigations for common XML attacks like the
"`billion laughs
<https://en.wikipedia.org/wiki/Billion_laughs_attack>`".
* Librsvg is careful to turn off features like network access and
external XML entities, which are a well-known source of
attacks.
However, libxml2 has had many CVEs and security problems in the past.
It is the sort of infrastructure that should be replaced with
memory-safe code at some point.
Steps
-----
1. Separate the XML tree from the SVG element tree.
2. Change the XML tree to one that is produced by the new Rust-based
XML parser.
The sections below explore each of these steps.
Separating the XML tree from the SVG element tree
-------------------------------------------------
Librsvg has a tree data structure, managed by the ``rctree`` crate,
where each node is a combination of XML data (element name for the
tag, and a list of attributes with their string values) and the parsed
SVG data (individual structs for ``Group``, ``Path``, etc., plus
parsed properties and element-specific attributes).
From ``document.rs``:
.. code-block:: rust
pub struct Document {
/// Tree of nodes; the root is guaranteed to be an `<svg>` element.
tree: Node,
// ...
}
From ``node.rs``:
.. code-block:: rust
pub type Node = rctree::Node<NodeData>;
pub enum NodeData {
Element(Box<Element>),
Text(Box<Chars>),
}
From ``xml/attributes.rs``:
.. code-block:: rust
pub struct Attributes {
attrs: Box<[(QualName, AttributeValue)]>,
// ...
}
From ``element.rs``:
.. code-block:: rust
pub struct Element {
element_name: QualName,
attributes: Attributes,
specified_values: SpecifiedValues,
pub element_data: ElementData,
// ... some fields omitted
}
pub enum ElementData {
Circle(Box<Circle>),
ClipPath(Box<ClipPath>),
Ellipse(Box<Ellipse>),
// ...
}
Here, ``struct Element`` is a combination of XML string data
(``element_name``, ``attributes``), plus the result of parsing those
strings into SVG and CSS-specific information (``specified_values``,
``element_data``).
**Goal:** Basically, have ``Element`` *not* contain XML string data.
It may contain a pointer back to its corresponding XML node, and that
may even depend on what the crate that represents that XML tree lets
us do.
Things to consider
~~~~~~~~~~~~~~~~~~
* With the libxml2-based SAX2 parser, as soon as librsvg gets a "start
element" event it will parse each value in the list of attributes.
It will then use this information to construct an ``Element`` and
then a ``Node``. We may have to change this "build from the inside
out" process to instead assume that an XML tree is available and
full of strings, and later an SVG tree can be constructed from it.
Change the XML tree to one from a Rust-based parser
---------------------------------------------------
FIXME
* The code in ``css.rs`` which implements the ``selectors::Element``
trait for nodes in the tree, needs O(1) access to a node's parent and
to its next sibling.
Notes
-----
This used to be https://gitlab.gnome.org/GNOME/librsvg/-/issues/224
but it was mostly a wishlist item, instead of a specification document
like the present one.