Add support for static SVG images using resvg crate (#36721)

This change adds support for rendering static SVG images using the
`resvg` crate, allowing svg sources in the `img` tag and in CSS
`background` and `content` properties. There are some limitations in
using resvg:

1. There is no support for animations or interactivity as these would
require implementing the full DOM layer of SVG specification.
2. Only system fonts can be used for text rendering. There is some
mechanism to provide a custom font resolver to usvg, but that is not
explored in this change.
3. resvg's handling of certain edge cases involving lack of explicit
`width` and `height` on the root svg element deviates from what the
specification expects from browsers. For example, resvg uses the values
in `viewBox` to derive the missing width or height dimension, but
without scaling that dimension to preserve the aspect ratio. It also
doesn't allow overriding this behavior.

Demo screenshot:
![servo - resvg
img](https://github.com/user-attachments/assets/8ecb2de2-ab7c-48e2-9f08-2d09d2cb8791)

<details>
<summary>Source</summary>

```
<style>
 #svg1 {
   border: 1px solid red;
 }

 #svg2 {
   border: 1px solid red;
   width: 300px;
 }
 #svg3 {
   border: 1px solid red;
   width: 300px;
   height: 200px;
   object-fit: contain;
 }
 #svg4 {
   border: 1px solid red;
   width: 300px;
   height: 200px;
   object-fit: cover;
 }
 #svg5 {
   border: 1px solid red;
   width: 300px;
   height: 200px;
   object-fit: fill;
 }
 #svg6 {
   border: 1px solid red;
   width: 300px;
   height: 200px;
   object-fit: none;
 }
</style>
</head>
<body>
        <div>
          <img id="svg1" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo">
        </div>
        <div>
          <img id="svg2" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo">
          <img id="svg3" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo">
          <img id="svg4" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo">
        </div>
        <div>
          <img id="svg5" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo">
          <img id="svg6" src="https://raw.githubusercontent.com/servo/servo/refs/heads/main/resources/servo.svg" alt="Servo logo">
        </div>
</body>
```

</details>

---------

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Mukilan Thiyagarajan 2025-05-27 16:32:40 +05:30 committed by GitHub
parent 324196351e
commit 8a20e42de4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
267 changed files with 2374 additions and 544 deletions

View file

@ -30,7 +30,7 @@ use libc::c_void;
use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::image_cache::{ImageCache, PendingImageId};
use pixels::Image;
use pixels::RasterImage;
use profile_traits::mem::Report;
use profile_traits::time;
use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
@ -49,6 +49,7 @@ use style::properties::style_structs::Font;
use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
use style::stylesheets::Stylesheet;
use webrender_api::ImageKey;
use webrender_api::units::DeviceIntSize;
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
fn as_any(&self) -> &dyn Any;
@ -153,6 +154,16 @@ pub struct PendingImage {
pub origin: ImmutableOrigin,
}
/// A data structure to tarck vector image that are fully loaded (i.e has a parsed SVG
/// tree) but not yet rasterized to the size needed by layout. The rasterization is
/// happening in the image cache.
#[derive(Debug)]
pub struct PendingRasterizationImage {
pub node: UntrustedNodeAddress,
pub id: PendingImageId,
pub size: DeviceIntSize,
}
#[derive(Clone, Copy, Debug)]
pub struct MediaFrame {
pub image_key: webrender_api::ImageKey,
@ -392,6 +403,8 @@ pub type IFrameSizes = FnvHashMap<BrowsingContextId, IFrameSize>;
pub struct ReflowResult {
/// The list of images that were encountered that are in progress.
pub pending_images: Vec<PendingImage>,
/// The list of vector images that were encountered that still need to be rasterized.
pub pending_rasterization_images: Vec<PendingRasterizationImage>,
/// The list of iframes in this layout and their sizes, used in order
/// to communicate them with the Constellation and also the `Window`
/// element of their content pages.
@ -507,13 +520,13 @@ pub fn node_id_from_scroll_id(id: usize) -> Option<usize> {
#[derive(Clone, Debug, MallocSizeOf)]
pub struct ImageAnimationState {
#[ignore_malloc_size_of = "Arc is hard"]
pub image: Arc<Image>,
pub image: Arc<RasterImage>,
pub active_frame: usize,
last_update_time: f64,
}
impl ImageAnimationState {
pub fn new(image: Arc<Image>, last_update_time: f64) -> Self {
pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
Self {
image,
active_frame: 0,
@ -579,7 +592,7 @@ mod test {
use std::time::Duration;
use ipc_channel::ipc::IpcSharedMemory;
use pixels::{CorsStatus, Image, ImageFrame, PixelFormat};
use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
use crate::ImageAnimationState;
@ -593,9 +606,11 @@ mod test {
})
.take(10)
.collect();
let image = Image {
width: 100,
height: 100,
let image = RasterImage {
metadata: ImageMetadata {
width: 100,
height: 100,
},
format: PixelFormat::BGRA8,
id: None,
bytes: IpcSharedMemory::from_byte(1, 1),

View file

@ -6,13 +6,13 @@
use std::borrow::Cow;
use std::fmt::Debug;
use std::sync::Arc as StdArc;
use atomic_refcell::AtomicRef;
use base::id::{BrowsingContextId, PipelineId};
use fonts_traits::ByteIndex;
use html5ever::{LocalName, Namespace};
use pixels::{Image, ImageMetadata};
use net_traits::image_cache::Image;
use pixels::ImageMetadata;
use range::Range;
use servo_arc::Arc;
use servo_url::ServoUrl;
@ -222,7 +222,7 @@ pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialE
fn image_density(&self) -> Option<f64>;
/// If this is an image element, returns its image data. Otherwise, returns `None`.
fn image_data(&self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)>;
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)>;
fn canvas_data(&self) -> Option<HTMLCanvasData>;