librsvg source for verification 2026-05-22
20
devel-docs/Makefile
Normal 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)
|
||||
30
devel-docs/_build_dependencies.rst
Normal 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)
|
||||
216
devel-docs/_extensions/internals.py
Normal 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)
|
||||
62
devel-docs/_extensions/source.py
Normal 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
686
devel-docs/adding_a_property.rst
Normal 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
|
||||
wasn’t 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.
|
||||
|
||||
Let’s 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
|
||||
|
||||
Let’s 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 spec’s 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``. We’ll 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 doesn’t pay much attention to “applies to” — it
|
||||
just carries property values for all elements, and the elements that
|
||||
don’t 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.
|
||||
|
||||
Let’s 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 element’s 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 property’s 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>>;
|
||||
}
|
||||
|
||||
Don’t 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 you’ll 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. Let’s
|
||||
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 program’s 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 that’s 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: let’s 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. Let’s 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 let’s 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 let’s 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 don’t have a test for this yet! Aaaaaargh, we are doing
|
||||
test-driven development backwards!
|
||||
|
||||
No biggie. Let’s 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, let’s 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 let’s 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.
|
||||
|
||||
We’ll 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 don’t 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
|
||||
217
devel-docs/api_observability.rst
Normal 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 don’t 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 can’t 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 can’t 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 doesn’t 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
@@ -0,0 +1,445 @@
|
||||
Architecture
|
||||
============
|
||||
|
||||
This document roughly describes the architecture of librsvg, and future
|
||||
plans for it. The code is continually evolving, so don’t consider this
|
||||
as the ground truth, but rather like a cheap map you buy at a street
|
||||
corner.
|
||||
|
||||
The library’s 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 GNOME’s 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 libxml2’s 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
|
||||
GNOME’s 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
|
||||
browser’s 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 element’s ``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 element’s 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 element’s ``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 Servo’s `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))))``.
|
||||
Let’s 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.
|
||||
|
||||
You’ll 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 subtree’s 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 root’s 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 doesn’t work when the numbers are slightly different due to numerical
|
||||
inaccuracies.
|
||||
|
||||
Similarly, we don’t ``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`.
|
||||
BIN
devel-docs/assets/ahem-a.png
Normal file
|
After Width: | Height: | Size: 656 B |
8
devel-docs/assets/ahem-a.svg
Normal 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 |
BIN
devel-docs/assets/ahem-ab-color.png
Normal file
|
After Width: | Height: | Size: 675 B |
10
devel-docs/assets/ahem-ab-color.svg
Normal 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 |
BIN
devel-docs/assets/ahem-ab.png
Normal file
|
After Width: | Height: | Size: 665 B |
8
devel-docs/assets/ahem-ab.svg
Normal 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 |
BIN
devel-docs/assets/ahem-different-glyphs.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
8
devel-docs/assets/ahem-different-glyphs.svg
Normal 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 |
BIN
devel-docs/assets/ahem-rtl.png
Normal file
|
After Width: | Height: | Size: 688 B |
10
devel-docs/assets/ahem-rtl.svg
Normal 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 |
BIN
devel-docs/assets/multiple-layouts.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
devel-docs/assets/single-layout.jpg
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
devel-docs/assets/unicode-bidi.jpg
Normal file
|
After Width: | Height: | Size: 227 KiB |
50
devel-docs/bugs.rst
Normal 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`.
|
||||
155
devel-docs/building_deps_in_ci.rst
Normal 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
@@ -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
@@ -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; it’s a tricky bit of
|
||||
machinery, and we are glad to help.
|
||||
|
||||
The rest of this document explains librsvg’s 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 `Cargo’s 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
@@ -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
@@ -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
|
||||
-----------------
|
||||
|
||||
Librsvg’s 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 don’t 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 library’s 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; it’s 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, you’ll
|
||||
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 doesn’t like your
|
||||
indentation, but don’t 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 `GNOME’s 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 it’s 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.
|
||||
107
devel-docs/custom_properties.rst
Normal 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>`_
|
||||
|
||||
|
||||
157
devel-docs/devel_docs_mod_guide.rst
Normal 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`.
|
||||
207
devel-docs/devel_environment.rst
Normal 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
@@ -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 SVG’s “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 W3C’s
|
||||
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 librsvg’s 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
@@ -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
@@ -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
@@ -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
@@ -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/>`_
|
||||
110
devel-docs/performance_tracking.rst
Normal 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
@@ -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
@@ -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 don’t 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 librsvg’s 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 librsvg’s 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 distro’s 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 don’t 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 gitlab’s 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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
Librsvg’s 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 SVG’s `static processing
|
||||
mode <https://www.w3.org/TR/SVG2/conform.html#static-mode>`__.
|
||||
41
devel-docs/supported_versions.rst
Normal 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
@@ -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, librsvg’s 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 doesn’t 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 I’ve 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 it’s more
|
||||
important to have “mostly working” bidi than completely accurate
|
||||
white-space handling and layout.
|
||||
|
||||
Second, actually overhaul librsvg’s 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 librsvg’s 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 child’s
|
||||
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(). We’ll 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 don’t have to check the Pango docs every time
|
||||
-----------------------------------------------------------
|
||||
|
||||
PangoItem - A range within the user’s 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
@@ -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
@@ -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.
|
||||