ltk_webkit/wpe/
snapshot.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! The whole document as pixels, for the host to print or save it: WPE has no printing of its own.

use std::ptr;

use gio::ffi::GAsyncResult;
use glib_sys::{ g_bytes_get_data, g_error_free, gpointer, GError };
use gobject_sys::{ g_object_unref, GObject };
use wpe_webkit_sys::*;

use super::WebView;
use crate::types::Snapshot;

type SnapshotDone = Box<dyn FnOnce( Result<Snapshot, String> )>;

impl WebView
{
	/// The whole document, what is scrolled out of view included; `done` gets it from a later [`WebView::tick`].
	pub fn snapshot_document( &self, done: impl FnOnce( Result<Snapshot, String> ) + 'static )
	{
		let Some( webview ) = self.webview_ptr() else { return done( Err( "no view".to_string() ) ) };
		snapshot( webview, WEBKIT_SNAPSHOT_REGION_FULL_DOCUMENT, done );
	}
}

pub( super ) fn snapshot( webview: *mut WebKitWebView, region: WebKitSnapshotRegion, done: impl FnOnce( Result<Snapshot, String> ) + 'static )
{
	let data = Box::into_raw( Box::new( Box::new( done ) as SnapshotDone ) ) as gpointer;
	// SAFETY: the caller passes a live view; `on_snapshot` runs exactly once and reclaims `data`.
	unsafe
	{
		webkit_web_view_get_snapshot( webview, region, WEBKIT_SNAPSHOT_OPTIONS_NONE, ptr::null_mut(), Some( on_snapshot ), data );
	}
}

unsafe extern "C" fn on_snapshot( source: *mut GObject, result: *mut GAsyncResult, user_data: gpointer )
{
	// SAFETY: `user_data` is the box `snapshot_document` leaked; the image returned is ours, its bytes are the image's.
	unsafe
	{
		let done = Box::from_raw( user_data as *mut SnapshotDone );
		let mut err: *mut GError = ptr::null_mut();
		let image = webkit_web_view_get_snapshot_finish( source as *mut WebKitWebView, result, &mut err );
		if image.is_null()
		{
			let message = super::error_message_or_default( err, "no snapshot" );
			if !err.is_null() { g_error_free( err ); }
			return ( *done )( Err( message ) );
		}
		let snapshot = read( image );
		g_object_unref( image as *mut GObject );
		( *done )( snapshot )
	}
}

/// WebKit gives 32 bits a pixel in the platform's byte order, which is BGRA on the little-endian machines Eydos runs on.
unsafe fn read( image: *mut WebKitImage ) -> Result<Snapshot, String>
{
	// SAFETY: a live image; its bytes live as long as it does.
	unsafe
	{
		let width  = webkit_image_get_width( image ).max( 0 ) as usize;
		let height = webkit_image_get_height( image ).max( 0 ) as usize;
		let stride = webkit_image_get_stride( image ) as usize;
		let bytes  = webkit_image_as_bytes( image );
		if bytes.is_null() || width == 0 || height == 0 || stride < width * 4 { return Err( "empty snapshot".to_string() ); }
		let mut size = 0;
		let data = g_bytes_get_data( bytes, &mut size ) as *const u8;
		if data.is_null() || size < stride * ( height - 1 ) + width * 4 { return Err( "short snapshot".to_string() ); }
		let source = std::slice::from_raw_parts( data, size );
		let mut rgba = Vec::with_capacity( width * height * 4 );
		for row in 0..height
		{
			for px in source[ row * stride..row * stride + width * 4 ].chunks_exact( 4 )
			{
				rgba.extend_from_slice( &[ px[ 2 ], px[ 1 ], px[ 0 ], px[ 3 ] ] );
			}
		}
		Ok( Snapshot { rgba, width: width as u32, height: height as u32 } )
	}
}