@@ -467,6 +467,132 @@ void native_document::append_scene(
467467 }
468468 return result;
469469 }();
470+ struct retained_backdrop_effect final {
471+ bool present{};
472+ std::string resource;
473+ float maximum_blur_sigma{};
474+ };
475+ const auto backdrop_effect = [&]() {
476+ retained_backdrop_effect result{};
477+ const auto & effects = node.style .textual ().effect_values ;
478+ const auto known = effects.find (" backdrop-filter" );
479+ if (known == effects.end ()) return result;
480+ auto remaining = std::string_view{known->second };
481+ const auto trim = [](std::string_view value) {
482+ while (!value.empty ()
483+ && std::isspace (static_cast <unsigned char >(value.front ()))) {
484+ value.remove_prefix (1U );
485+ }
486+ while (!value.empty ()
487+ && std::isspace (static_cast <unsigned char >(value.back ()))) {
488+ value.remove_suffix (1U );
489+ }
490+ return value;
491+ };
492+ const auto equals_ascii_case_insensitive = [](std::string_view left,
493+ std::string_view right) {
494+ if (left.size () != right.size ()) return false ;
495+ for (size_t index = 0U ; index < left.size (); ++index) {
496+ const auto character = static_cast <unsigned char >(left[index]);
497+ const auto lower = character >= ' A' && character <= ' Z'
498+ ? static_cast <char >(character - ' A' + ' a' )
499+ : static_cast <char >(character);
500+ if (lower != right[index]) return false ;
501+ }
502+ return true ;
503+ };
504+ remaining = trim (remaining);
505+ if (remaining.empty ()
506+ || equals_ascii_case_insensitive (remaining, " none" )) {
507+ return result;
508+ }
509+ result.present = true ;
510+ const auto invalid = [&]() {
511+ result.resource = " webscene-backdrop-invalid-v1" ;
512+ result.maximum_blur_sigma = 0 .0F ;
513+ return result;
514+ };
515+ std::ostringstream resource;
516+ resource << " webscene-backdrop-v1\t " ;
517+ constexpr size_t maximum_filter_functions = 16U ;
518+ size_t function_count = 0U ;
519+ while (!remaining.empty ()) {
520+ if (++function_count > maximum_filter_functions) return invalid ();
521+ const auto opening = remaining.find (' (' );
522+ auto closing = std::string_view::npos;
523+ if (opening != std::string_view::npos) {
524+ auto depth = 1U ;
525+ for (auto index = opening + 1U ; index < remaining.size (); ++index) {
526+ if (remaining[index] == ' (' ) ++depth;
527+ else if (remaining[index] == ' )' && --depth == 0U ) {
528+ closing = index;
529+ break ;
530+ }
531+ }
532+ }
533+ if (opening == 0U || opening == std::string_view::npos
534+ || closing == std::string_view::npos) {
535+ return invalid ();
536+ }
537+ const auto name = trim (remaining.substr (0U , opening));
538+ const auto argument = trim (
539+ remaining.substr (opening + 1U , closing - opening - 1U ));
540+ float amount = -1 .0F ;
541+ if (equals_ascii_case_insensitive (name, " blur" )) {
542+ const auto length = parse_length (std::string{argument});
543+ switch (length.unit ) {
544+ case length_unit::pixels:
545+ case length_unit::em:
546+ case length_unit::rem:
547+ case length_unit::viewport_width:
548+ case length_unit::viewport_height:
549+ case length_unit::viewport_width_capped:
550+ case length_unit::viewport_height_capped:
551+ case length_unit::viewport_width_floored:
552+ case length_unit::viewport_height_floored:
553+ case length_unit::container_inline:
554+ case length_unit::container_block:
555+ case length_unit::container_min:
556+ case length_unit::container_max:
557+ amount = resolve_length (node, length, 0 .0F , -1 .0F );
558+ break ;
559+ default :
560+ break ;
561+ }
562+ constexpr auto maximum_backdrop_blur_sigma = 64 .0F ;
563+ if (!std::isfinite (amount) || amount < 0 .0F
564+ || amount > maximum_backdrop_blur_sigma) {
565+ return invalid ();
566+ }
567+ result.maximum_blur_sigma = std::max (
568+ result.maximum_blur_sigma , amount);
569+ resource << (function_count == 1U ? " " : " ;" )
570+ << " blur=" << amount;
571+ } else if (equals_ascii_case_insensitive (name, " saturate" )) {
572+ const auto parsed = css::parse_ascii_number_prefix (argument);
573+ if (!parsed.has_value () || parsed->value < 0 .0F ) return invalid ();
574+ const auto suffix = argument.substr (parsed->consumed );
575+ amount = suffix.empty ()
576+ ? parsed->value
577+ : suffix == " %" ? parsed->value / 100 .0F : -1 .0F ;
578+ if (!std::isfinite (amount) || amount < 0 .0F || amount > 10 .0F ) {
579+ return invalid ();
580+ }
581+ resource << (function_count == 1U ? " " : " ;" )
582+ << " saturate=" << amount;
583+ } else {
584+ return invalid ();
585+ }
586+ remaining.remove_prefix (closing + 1U );
587+ if (!remaining.empty ()
588+ && !std::isspace (static_cast <unsigned char >(remaining.front ()))) {
589+ return invalid ();
590+ }
591+ remaining = trim (remaining);
592+ }
593+ result.resource = resource.str ();
594+ return result;
595+ }();
470596 const auto mask_resource = [&]() -> std::optional<uint32_t > {
471597 const auto & effects = node.style .textual ().effect_values ;
472598 const auto known = effects.find (" mask-image" );
@@ -1280,7 +1406,9 @@ void native_document::append_scene(
12801406 // backgrounds must be emitted after retained chart canvases, just
12811407 // like their text and SVG foreground commands.
12821408 }
1283- return is_in_modal_layer (node) || node.paints_after_retained_canvas || fixed_layer || outermost_z_index > 0 ;
1409+ return backdrop_effect.present || is_in_modal_layer (node)
1410+ || node.paints_after_retained_canvas || fixed_layer
1411+ || outermost_z_index > 0 ;
12841412 }();
12851413 struct resolved_radii final {
12861414 float top_left;
@@ -1707,6 +1835,29 @@ void native_document::append_scene(
17071835 node.style .border_bottom_left_radius_y (),
17081836 node.layout .width ,
17091837 node.layout .height );
1838+ if (paint_self && backdrop_effect.present
1839+ && node.layout .width > 0 .0F && node.layout .height > 0 .0F ) {
1840+ append_ellipse_metadata (
1841+ radii,
1842+ node.layout .x ,
1843+ node.layout .y ,
1844+ node.layout .width ,
1845+ node.layout .height );
1846+ commands.push_back (webscene_scene_command{
1847+ 48U ,
1848+ append_scene_string (backdrop_effect.resource , strings, string_bytes),
1849+ node.layout .x ,
1850+ node.layout .y ,
1851+ node.layout .width ,
1852+ node.layout .height ,
1853+ 1U ,
1854+ node.id ,
1855+ radii.top_left ,
1856+ radii.top_right ,
1857+ radii.bottom_right ,
1858+ radii.bottom_left ,
1859+ backdrop_effect.maximum_blur_sigma });
1860+ }
17101861 if (paint_self
17111862 && node.style .box_shadow_present && !node.style .box_shadow_inset
17121863 && node.layout .width > 0
0 commit comments