mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
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:  <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:
parent
324196351e
commit
8a20e42de4
267 changed files with 2374 additions and 544 deletions
|
@ -16,7 +16,7 @@ use euclid::Rect;
|
|||
use ipc_channel::ipc::IpcSender;
|
||||
use log::warn;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use strum_macros::IntoStaticStr;
|
||||
use style_traits::CSSPixel;
|
||||
use webrender_api::DocumentId;
|
||||
|
@ -83,7 +83,7 @@ pub enum CompositorMsg {
|
|||
CreatePng(
|
||||
WebViewId,
|
||||
Option<Rect<f32, CSSPixel>>,
|
||||
IpcSender<Option<Image>>,
|
||||
IpcSender<Option<RasterImage>>,
|
||||
),
|
||||
/// A reply to the compositor asking if the output image is stable.
|
||||
IsReadyToSaveImageReply(bool),
|
||||
|
|
|
@ -30,7 +30,7 @@ use log::warn;
|
|||
use malloc_size_of::malloc_size_of_is_0;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use num_derive::FromPrimitive;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use servo_url::ServoUrl;
|
||||
use strum_macros::IntoStaticStr;
|
||||
|
@ -674,16 +674,16 @@ pub struct Notification {
|
|||
/// The URL of an icon. The icon will be displayed as part of the notification.
|
||||
pub icon_url: Option<ServoUrl>,
|
||||
/// Icon's raw image data and metadata.
|
||||
pub icon_resource: Option<Arc<Image>>,
|
||||
pub icon_resource: Option<Arc<RasterImage>>,
|
||||
/// The URL of a badge. The badge is used when there is no enough space to display the notification,
|
||||
/// such as on a mobile device's notification bar.
|
||||
pub badge_url: Option<ServoUrl>,
|
||||
/// Badge's raw image data and metadata.
|
||||
pub badge_resource: Option<Arc<Image>>,
|
||||
pub badge_resource: Option<Arc<RasterImage>>,
|
||||
/// The URL of an image. The image will be displayed as part of the notification.
|
||||
pub image_url: Option<ServoUrl>,
|
||||
/// Image's raw image data and metadata.
|
||||
pub image_resource: Option<Arc<Image>>,
|
||||
pub image_resource: Option<Arc<RasterImage>>,
|
||||
/// Actions available for users to choose from for interacting with the notification.
|
||||
pub actions: Vec<NotificationAction>,
|
||||
}
|
||||
|
@ -698,7 +698,7 @@ pub struct NotificationAction {
|
|||
/// The URL of an icon. The icon will be displayed with the action.
|
||||
pub icon_url: Option<ServoUrl>,
|
||||
/// Icon's raw image data and metadata.
|
||||
pub icon_resource: Option<Arc<Image>>,
|
||||
pub icon_resource: Option<Arc<RasterImage>>,
|
||||
}
|
||||
|
||||
/// Information about a `WebView`'s screen geometry and offset. This is used
|
||||
|
|
|
@ -14,7 +14,7 @@ use hyper_serde::Serde;
|
|||
use ipc_channel::ipc::IpcSender;
|
||||
use keyboard_types::KeyboardEvent;
|
||||
use keyboard_types::webdriver::Event as WebDriverInputEvent;
|
||||
use pixels::Image;
|
||||
use pixels::RasterImage;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_url::ServoUrl;
|
||||
use style_traits::CSSPixel;
|
||||
|
@ -69,7 +69,7 @@ pub enum WebDriverCommandMsg {
|
|||
TakeScreenshot(
|
||||
WebViewId,
|
||||
Option<Rect<f32, CSSPixel>>,
|
||||
IpcSender<Option<Image>>,
|
||||
IpcSender<Option<RasterImage>>,
|
||||
),
|
||||
/// Create a new webview that loads about:blank. The constellation will use
|
||||
/// the provided channels to return the top level browsing context id
|
||||
|
|
|
@ -41,6 +41,7 @@ servo_rand = { path = "../../rand" }
|
|||
servo_url = { path = "../../url" }
|
||||
url = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
webrender_api = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
embedder_traits = { workspace = true, features = ["baked-default-resources"] }
|
||||
|
|
|
@ -10,10 +10,11 @@ use ipc_channel::ipc::IpcSender;
|
|||
use log::debug;
|
||||
use malloc_size_of::MallocSizeOfOps;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use pixels::{Image, ImageMetadata};
|
||||
use pixels::{CorsStatus, ImageMetadata, RasterImage};
|
||||
use profile_traits::mem::Report;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use webrender_api::units::DeviceIntSize;
|
||||
|
||||
use crate::FetchResponseMsg;
|
||||
use crate::request::CorsSettings;
|
||||
|
@ -22,12 +23,52 @@ use crate::request::CorsSettings;
|
|||
// Aux structs and enums.
|
||||
// ======================================================================
|
||||
|
||||
pub type VectorImageId = PendingImageId;
|
||||
|
||||
// Represents either a raster image for which the pixel data is available
|
||||
// or a vector image for which only the natural dimensions are available
|
||||
// and thus requires a further rasterization step to render.
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub enum Image {
|
||||
Raster(#[conditional_malloc_size_of] Arc<RasterImage>),
|
||||
Vector(VectorImage),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub struct VectorImage {
|
||||
pub id: VectorImageId,
|
||||
pub metadata: ImageMetadata,
|
||||
pub cors_status: CorsStatus,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn metadata(&self) -> ImageMetadata {
|
||||
match self {
|
||||
Image::Vector(image, ..) => image.metadata,
|
||||
Image::Raster(image) => image.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cors_status(&self) -> CorsStatus {
|
||||
match self {
|
||||
Image::Vector(image) => image.cors_status,
|
||||
Image::Raster(image) => image.cors_status,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_raster_image(&self) -> Option<Arc<RasterImage>> {
|
||||
match self {
|
||||
Image::Raster(image) => Some(image.clone()),
|
||||
Image::Vector(..) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicating either entire image or just metadata availability
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub enum ImageOrMetadataAvailable {
|
||||
ImageAvailable {
|
||||
#[ignore_malloc_size_of = "Arc"]
|
||||
image: Arc<Image>,
|
||||
image: Image,
|
||||
url: ServoUrl,
|
||||
is_placeholder: bool,
|
||||
},
|
||||
|
@ -39,19 +80,19 @@ pub enum ImageOrMetadataAvailable {
|
|||
/// image load completes. It is typically used to trigger a reflow
|
||||
/// and/or repaint.
|
||||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub struct ImageResponder {
|
||||
pub struct ImageLoadListener {
|
||||
pipeline_id: PipelineId,
|
||||
pub id: PendingImageId,
|
||||
sender: IpcSender<PendingImageResponse>,
|
||||
sender: IpcSender<ImageCacheResponseMessage>,
|
||||
}
|
||||
|
||||
impl ImageResponder {
|
||||
impl ImageLoadListener {
|
||||
pub fn new(
|
||||
sender: IpcSender<PendingImageResponse>,
|
||||
sender: IpcSender<ImageCacheResponseMessage>,
|
||||
pipeline_id: PipelineId,
|
||||
id: PendingImageId,
|
||||
) -> ImageResponder {
|
||||
ImageResponder {
|
||||
) -> ImageLoadListener {
|
||||
ImageLoadListener {
|
||||
pipeline_id,
|
||||
sender,
|
||||
id,
|
||||
|
@ -63,11 +104,15 @@ impl ImageResponder {
|
|||
// This send can fail if thread waiting for this notification has panicked.
|
||||
// That's not a case that's worth warning about.
|
||||
// TODO(#15501): are there cases in which we should perform cleanup?
|
||||
let _ = self.sender.send(PendingImageResponse {
|
||||
pipeline_id: self.pipeline_id,
|
||||
response,
|
||||
id: self.id,
|
||||
});
|
||||
let _ = self
|
||||
.sender
|
||||
.send(ImageCacheResponseMessage::NotifyPendingImageLoadStatus(
|
||||
PendingImageResponse {
|
||||
pipeline_id: self.pipeline_id,
|
||||
response,
|
||||
id: self.id,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,11 +120,11 @@ impl ImageResponder {
|
|||
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
|
||||
pub enum ImageResponse {
|
||||
/// The requested image was loaded.
|
||||
Loaded(#[conditional_malloc_size_of] Arc<Image>, ServoUrl),
|
||||
Loaded(Image, ServoUrl),
|
||||
/// The request image metadata was loaded.
|
||||
MetadataLoaded(ImageMetadata),
|
||||
/// The requested image failed to load, so a placeholder was loaded instead.
|
||||
PlaceholderLoaded(#[conditional_malloc_size_of] Arc<Image>, ServoUrl),
|
||||
PlaceholderLoaded(#[conditional_malloc_size_of] Arc<RasterImage>, ServoUrl),
|
||||
/// Neither the requested image nor the placeholder could be loaded.
|
||||
None,
|
||||
}
|
||||
|
@ -95,6 +140,19 @@ pub struct PendingImageResponse {
|
|||
pub id: PendingImageId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct RasterizationCompleteResponse {
|
||||
pub pipeline_id: PipelineId,
|
||||
pub image_id: PendingImageId,
|
||||
pub requested_size: DeviceIntSize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum ImageCacheResponseMessage {
|
||||
NotifyPendingImageLoadStatus(PendingImageResponse),
|
||||
VectorImageRasterizationComplete(RasterizationCompleteResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub enum UsePlaceholder {
|
||||
No,
|
||||
|
@ -125,7 +183,7 @@ pub trait ImageCache: Sync + Send {
|
|||
url: ServoUrl,
|
||||
origin: ImmutableOrigin,
|
||||
cors_setting: Option<CorsSettings>,
|
||||
) -> Option<Arc<Image>>;
|
||||
) -> Option<Image>;
|
||||
|
||||
fn get_cached_image_status(
|
||||
&self,
|
||||
|
@ -135,9 +193,31 @@ pub trait ImageCache: Sync + Send {
|
|||
use_placeholder: UsePlaceholder,
|
||||
) -> ImageCacheResult;
|
||||
|
||||
/// Returns `Some` if the given `image_id` has already been rasterized at the given `size`.
|
||||
/// Otherwise, triggers a new job to perform the rasterization. If a notification
|
||||
/// is needed after rasterization is completed, the `add_rasterization_complete_listener`
|
||||
/// API below can be used to add a listener.
|
||||
fn rasterize_vector_image(
|
||||
&self,
|
||||
image_id: VectorImageId,
|
||||
size: DeviceIntSize,
|
||||
) -> Option<RasterImage>;
|
||||
|
||||
/// Adds a new listener to be notified once the given `image_id` has been rasterized at
|
||||
/// the given `size`. The listener will receive a `VectorImageRasterizationComplete`
|
||||
/// message on the given `sender`, even if the listener is called after rasterization
|
||||
/// at has already completed.
|
||||
fn add_rasterization_complete_listener(
|
||||
&self,
|
||||
pipeline_id: PipelineId,
|
||||
image_id: VectorImageId,
|
||||
size: DeviceIntSize,
|
||||
sender: IpcSender<ImageCacheResponseMessage>,
|
||||
);
|
||||
|
||||
/// Add a new listener for the given pending image id. If the image is already present,
|
||||
/// the responder will still receive the expected response.
|
||||
fn add_listener(&self, listener: ImageResponder);
|
||||
fn add_listener(&self, listener: ImageLoadListener);
|
||||
|
||||
/// Inform the image cache about a response for a pending request.
|
||||
fn notify_pending_response(&self, id: PendingImageId, action: FetchResponseMsg);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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>;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue