Tooltips in a window are popups beside their widget, theme SVGs with plus-lighter highlights are no longer painted grey, and the folder-files icon is shaped as a folder
CI / build + test (push) Canceled after 0s
CI / cargo audit (push) Canceled after 0s

A tooltip was a layer-shell surface spanning the output, placed at the coordinates of its widget in the main surface. In a layer-shell shell the two share an origin, but an xdg-shell window does not know where it sits on screen, so in a window the tooltip appeared shifted by the window's position, wherever that put it. The application icons of the default theme, exported from Figma, light their glass with `feBlend mode="plus-lighter"`, which resvg does not know and paints as `normal`, so the highlights came out as grey edges; at dock size those edges cover whole pixels and the icon looks dirty. And `archives/folder-files`, the icon applications show for a folder, was a clipboard and a sheet rather than a folder.
Tooltips. `tooltip_popup` tells when the tooltip must be an xdg-popup, that is when the main surface is a window and the widget is on it, and gives its anchor rect and logical size. `reconcile_overlays` then creates the tooltip's overlay as a popup child of the window, anchored to the widget's rect, above it with a 6 px gap (`TOOLTIP_GAP`), flipped below or slid sideways when it does not fit, so the compositor puts it beside the widget. It takes no grab and keeps the empty input region, so it never takes the pointer from the widget it describes. Its view is the pill centred in the popup. Layer-shell surfaces keep the previous placement, now in `tooltip_on_screen`. `tooltip_id` and `tooltip_size` hold the id and the estimated size both paths share. `OverlaySpec` is unchanged, so applications that build overlays need no change.
Blend modes. `decode_svg_bytes` replaces `plus-lighter` with `screen`, the closest mode resvg supports, before parsing; a document without it is parsed as it comes, without a copy. A test covers both cases.
The folder icon. Both variants of `archives/folder-files` are now the outline of `folder-empty` with the sheet of the previous icon inside, placed and sized as the lock of `folder-locked`: drawn in strokes in `line`, cut out of the filled folder in `filled`.
This commit is contained in:
2026-10-15 10:32:50 +02:00
parent dcb1c7dff3
commit 312fb7e0fd
5 changed files with 133 additions and 34 deletions
+33
View File
@@ -27,6 +27,10 @@ use wayland_protocols::xdg::shell::client::xdg_positioner::
use crate::app::App; use crate::app::App;
use crate::types::{ Length, Rect }; use crate::types::{ Length, Rect };
use super::{ AppData, LayerConfig, SurfaceFocus, SurfaceKind, SurfaceState }; use super::{ AppData, LayerConfig, SurfaceFocus, SurfaceKind, SurfaceState };
use super::tooltip::tooltip_id;
/// Logical pixels between a tooltip popup and its widget.
const TOOLTIP_GAP: i32 = 6;
/// Sync `data.overlays` with the app's current `App::overlays()` spec list: /// Sync `data.overlays` with the app's current `App::overlays()` spec list:
/// destroy any overlay whose id disappeared and create a fresh `SurfaceState` /// destroy any overlay whose id disappeared and create a fresh `SurfaceState`
@@ -37,6 +41,7 @@ pub( super ) fn reconcile_overlays<A: App>( data: &mut AppData<A> )
{ {
let mut specs = data.app.overlays(); let mut specs = data.app.overlays();
if let Some( ts ) = data.tooltip_overlay() { specs.push( ts ); } if let Some( ts ) = data.tooltip_overlay() { specs.push( ts ); }
let tooltip_popup = data.tooltip_popup();
let next_ids: Vec<_> = specs.iter().map( |s| s.id ).collect(); let next_ids: Vec<_> = specs.iter().map( |s| s.id ).collect();
let wanted: HashSet<crate::app::OverlayId> = next_ids.iter().copied().collect(); let wanted: HashSet<crate::app::OverlayId> = next_ids.iter().copied().collect();
@@ -237,6 +242,34 @@ pub( super ) fn reconcile_overlays<A: App>( data: &mut AppData<A> )
} }
continue; continue;
} }
// The tooltip in a window is a popup beside its widget, without a grab so it never takes the pointer from it.
if spec.id == tooltip_id()
{
if let ( Some( ( anchor, ( w, h ) ) ), Some( xdg_shell ), Some( parent ) ) = ( tooltip_popup, xdg_shell_opt, parent_xdg.as_ref() )
{
let Ok( positioner ) = XdgPositioner::new( xdg_shell ) else { continue };
positioner.set_size( w.max( 1 ), h.max( 1 ) );
positioner.set_anchor_rect(
( anchor.x / parent_scale ).round() as i32,
( anchor.y / parent_scale ).round() as i32,
( anchor.width / parent_scale ).round().max( 1.0 ) as i32,
( anchor.height / parent_scale ).round().max( 1.0 ) as i32,
);
positioner.set_anchor( PositionerAnchor::Top );
positioner.set_gravity( Gravity::Top );
positioner.set_offset( 0, -TOOLTIP_GAP );
positioner.set_constraint_adjustment(
ConstraintAdjustment::FlipY | ConstraintAdjustment::SlideX | ConstraintAdjustment::SlideY,
);
let Ok( surface ) = Surface::new( cs, qh ) else { continue };
let Ok( popup ) = Popup::from_surface( Some( parent ), &positioner, qh, surface, xdg_shell ) else { continue };
popup.wl_surface().commit();
let mut ss = SurfaceState::<A::Message>::new( SurfaceKind::Popup( popup ), 0.0, String::new() );
ss.last_requested_size = resolved_size;
overlays_m.insert( spec.id, ss );
continue;
}
}
// `anchor_widget_id = Some(_)` → xdg-popup path; `None` → // `anchor_widget_id = Some(_)` → xdg-popup path; `None` →
// wlr-layer-shell path. // wlr-layer-shell path.
if let Some( anchor_id ) = spec.anchor_widget_id if let Some( anchor_id ) = spec.anchor_widget_id
+56 -23
View File
@@ -2,7 +2,7 @@
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net> // Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
use super::app_data::AppData; use super::app_data::AppData;
use super::surface::SurfaceFocus; use super::surface::{ SurfaceFocus, SurfaceKind };
use crate::app::App; use crate::app::App;
use crate::types::{ Length, Rect }; use crate::types::{ Length, Rect };
@@ -85,14 +85,20 @@ impl<A: App> AppData<A>
self.main.request_redraw(); self.main.request_redraw();
} }
/// Anchor rect (physical, in the main surface) and logical size of the tooltip when it is an xdg-popup: in a window the
/// surface's place on screen is unknown, so only the compositor can put the tooltip beside its widget.
pub( crate ) fn tooltip_popup( &self ) -> Option<( Rect, ( i32, i32 ) )>
{
let v = self.tooltip_visible.as_ref()?;
if v.focus != SurfaceFocus::Main || !matches!( self.main.surface, SurfaceKind::Window( _ ) ) { return None; }
let ( w, h ) = tooltip_size( &v.text );
Some( ( v.anchor, ( w.ceil() as i32, h.ceil() as i32 ) ) )
}
pub( crate ) fn tooltip_overlay( &self ) -> Option<crate::app::OverlaySpec<A::Message>> pub( crate ) fn tooltip_overlay( &self ) -> Option<crate::app::OverlaySpec<A::Message>>
{ {
let v = self.tooltip_visible.as_ref()?; let v = self.tooltip_visible.as_ref()?;
let id = tooltip_id();
use std::hash::{ Hash, Hasher };
let mut hasher = std::collections::hash_map::DefaultHasher::new();
"ltk-tooltip".hash( &mut hasher );
let id = crate::app::OverlayId( hasher.finish() as u32 );
// Layout runs in the overlay's physical buffer space, so every // Layout runs in the overlay's physical buffer space, so every
// coordinate below is physical. The overlay's own scale wins once // coordinate below is physical. The overlay's own scale wins once
@@ -101,7 +107,6 @@ impl<A: App> AppData<A>
.map( |ss| ss.scale_factor ) .map( |ss| ss.scale_factor )
.unwrap_or( self.main.scale_factor ) .unwrap_or( self.main.scale_factor )
.max( 1 ) as f32; .max( 1 ) as f32;
let ts = crate::types::text_scale();
let ( ox, oy ) = self.surface_offset_for( v.focus ); let ( ox, oy ) = self.surface_offset_for( v.focus );
let src_scale = self.try_surface( v.focus )?.scale_factor.max( 1 ) as f32; let src_scale = self.try_surface( v.focus )?.scale_factor.max( 1 ) as f32;
@@ -123,22 +128,14 @@ impl<A: App> AppData<A>
.radius( 8.0 * sf ) .radius( 8.0 * sf )
.into(); .into();
let estimated_w = ( v.text.chars().count() as f32 * 7.5 * ts * sf + 24.0 * sf ) let view: crate::widget::Element<A::Message> = if self.tooltip_popup().is_some()
.clamp( 40.0 * sf, 320.0 * sf ); {
let estimated_h = 16.0 * ts * sf + 12.0 * sf; crate::layout::stack::stack()
let gap = 6.0 * sf; .push_aligned( pill, crate::layout::stack::HAlign::Center, crate::layout::stack::VAlign::Center )
let margin = 4.0 * sf; .into()
let mut x = anchor_x + ( anchor_w - estimated_w ) / 2.0; } else {
let mut y = anchor_y - estimated_h - gap; self.tooltip_on_screen( pill, ( anchor_x, anchor_y, anchor_w, anchor_h ), &v.text, sf )
let sw = self.main.width as f32 * sf; };
let sh = self.main.height as f32 * sf;
x = x.clamp( margin, ( sw - estimated_w - margin ).max( margin ) );
if y < margin { y = anchor_y + anchor_h + gap; }
if y + estimated_h > sh - margin { y = ( sh - estimated_h - margin ).max( margin ); }
let view: crate::widget::Element<A::Message> = crate::layout::stack::stack()
.push_translated( pill, crate::layout::stack::HAlign::Start, crate::layout::stack::VAlign::Top, x, y )
.into();
Some( crate::app::OverlaySpec Some( crate::app::OverlaySpec
{ {
@@ -154,4 +151,40 @@ impl<A: App> AppData<A>
anchor_widget_id: None, anchor_widget_id: None,
} ) } )
} }
/// Above the anchor, or below it near the top, kept within the main surface; for a layer surface, which spans the output.
fn tooltip_on_screen( &self, pill: crate::widget::Element<A::Message>, ( anchor_x, anchor_y, anchor_w, anchor_h ): ( f32, f32, f32, f32 ), text: &str, sf: f32 ) -> crate::widget::Element<A::Message>
{
let ( estimated_w, estimated_h ) = tooltip_size( text );
let ( estimated_w, estimated_h ) = ( estimated_w * sf, estimated_h * sf );
let gap = 6.0 * sf;
let margin = 4.0 * sf;
let mut x = anchor_x + ( anchor_w - estimated_w ) / 2.0;
let mut y = anchor_y - estimated_h - gap;
let sw = self.main.width as f32 * sf;
let sh = self.main.height as f32 * sf;
x = x.clamp( margin, ( sw - estimated_w - margin ).max( margin ) );
if y < margin { y = anchor_y + anchor_h + gap; }
if y + estimated_h > sh - margin { y = ( sh - estimated_h - margin ).max( margin ); }
crate::layout::stack::stack()
.push_translated( pill, crate::layout::stack::HAlign::Start, crate::layout::stack::VAlign::Top, x, y )
.into()
}
}
pub( crate ) fn tooltip_id() -> crate::app::OverlayId
{
use std::hash::{ Hash, Hasher };
let mut hasher = std::collections::hash_map::DefaultHasher::new();
"ltk-tooltip".hash( &mut hasher );
crate::app::OverlayId( hasher.finish() as u32 )
}
/// Logical size of the pill, estimated from its text.
fn tooltip_size( text: &str ) -> ( f32, f32 )
{
let ts = crate::types::text_scale();
let w = ( text.chars().count() as f32 * 7.5 * ts + 24.0 ).clamp( 40.0, 320.0 );
( w, 16.0 * ts + 12.0 )
} }
+35 -1
View File
@@ -5,6 +5,7 @@
//! icons, launcher icon, and the SVG rasterisation pipeline (with //! icons, launcher icon, and the SVG rasterisation pipeline (with
//! process-wide cache) that the canvas consumes. //! process-wide cache) that the canvas consumes.
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{ Path, PathBuf }; use std::path::{ Path, PathBuf };
use std::sync::{ Arc, Mutex }; use std::sync::{ Arc, Mutex };
@@ -355,7 +356,7 @@ pub fn decode_svg_bytes( svg_bytes: &[u8], size: u32 ) -> Option<( Arc<Vec<u8>>,
opts.default_size = ds; opts.default_size = ds;
} }
opts.fontdb = system_fontdb(); opts.fontdb = system_fontdb();
let tree = resvg::usvg::Tree::from_data( svg_bytes, &opts ).ok()?; let tree = resvg::usvg::Tree::from_data( &supported_blends( svg_bytes ), &opts ).ok()?;
let svg_size = tree.size(); let svg_size = tree.size();
let longest = svg_size.width().max( svg_size.height() ); let longest = svg_size.width().max( svg_size.height() );
if longest <= 0.0 { return None; } if longest <= 0.0 { return None; }
@@ -368,6 +369,30 @@ pub fn decode_svg_bytes( svg_bytes: &[u8], size: u32 ) -> Option<( Arc<Vec<u8>>,
Some( ( Arc::new( pixmap.take() ), w, h ) ) Some( ( Arc::new( pixmap.take() ), w, h ) )
} }
/// resvg falls back to `normal` for the CSS-only `plus-lighter` blend, painting highlights grey; `screen` is the closest it supports.
fn supported_blends( svg: &[u8] ) -> Cow<'_, [u8]>
{
const FROM: &[u8] = b"plus-lighter";
const TO: &[u8] = b"screen";
let find = |s: &[u8]| s.windows( FROM.len() ).position( |w| w == FROM );
let Some( mut at ) = find( svg ) else { return Cow::Borrowed( svg ) };
let mut out = Vec::with_capacity( svg.len() );
let mut rest = svg;
loop
{
out.extend_from_slice( &rest[ ..at ] );
out.extend_from_slice( TO );
rest = &rest[ at + FROM.len().. ];
match find( rest )
{
Some( next ) => at = next,
None => break,
}
}
out.extend_from_slice( rest );
Cow::Owned( out )
}
/// Resolve a theme-relative icon name to an absolute path inside the /// Resolve a theme-relative icon name to an absolute path inside the
/// active theme's `icons/catalogue/` tree. /// active theme's `icons/catalogue/` tree.
/// ///
@@ -475,6 +500,15 @@ mod tests
assert_eq!( h, 32 ); assert_eq!( h, 32 );
} }
#[ test ]
fn plus_lighter_blends_become_screen()
{
let svg = br#"<feBlend mode="plus-lighter"/><feBlend mode="plus-lighter" in2="x"/>"#;
assert_eq!( &*supported_blends( svg ), br#"<feBlend mode="screen"/><feBlend mode="screen" in2="x"/>"# );
let plain = br#"<feBlend mode="normal"/>"#;
assert!( matches!( supported_blends( plain ), Cow::Borrowed( _ ) ) );
}
#[ test ] #[ test ]
fn decode_svg_bytes_rejects_garbage() fn decode_svg_bytes_rejects_garbage()
{ {
@@ -1,6 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25.000 25.000" fill="none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25.000 23.300" fill="none">
<g transform="translate(-499.500 -5091.500)" fill="#000000"> <g transform="translate(-395.500 -5092.340)" fill="#000000">
<path fill-rule="evenodd" clip-rule="evenodd" d="M502.441 5092.46C502.686 5092.17 503.047 5092 503.429 5092H512C512.381 5092 512.743 5092.17 512.987 5092.46C513.231 5092.75 513.333 5093.14 513.265 5093.52L513.224 5093.74C514.47 5093.92 515.429 5094.99 515.429 5096.29V5097.57H512.429C509.588 5097.57 507.286 5099.87 507.286 5102.71V5110.86H502.571C501.151 5110.86 500 5109.71 500 5108.29V5096.29C500 5094.99 500.958 5093.92 502.204 5093.74L502.164 5093.52C502.095 5093.14 502.197 5092.75 502.441 5092.46ZM504.969 5094.57L505.167 5095.66C505.278 5096.27 505.81 5096.71 506.432 5096.71H508.997C509.618 5096.71 510.151 5096.27 510.262 5095.66L510.459 5094.57H504.969Z"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M406.503 5093.38C406.039 5093.03 405.469 5092.84 404.886 5092.86H398.571C397.889 5092.86 397.235 5093.13 396.753 5093.61C396.271 5094.09 396 5094.75 396 5095.43V5112.57C396 5113.25 396.271 5113.91 396.753 5114.39C397.235 5114.87 397.889 5115.14 398.571 5115.14H417.429C418.111 5115.14 418.765 5114.87 419.247 5114.39C419.729 5113.91 420 5113.25 420 5112.57V5098.86C420 5098.17 419.729 5097.52 419.247 5097.04C418.765 5096.56 418.111 5096.29 417.429 5096.29H407.812L407.444 5094.81C407.303 5094.24 406.97 5093.74 406.503 5093.38ZM405 5099.859C403.84 5099.859 402.9 5100.804 402.9 5101.959V5109.162C402.9 5110.324 403.84 5111.262 405 5111.262H411C412.16 5111.262 413.1 5110.324 413.1 5109.162V5101.959C413.1 5100.804 412.16 5099.859 411 5099.859H405ZM405.75 5104.059C405.75 5103.646 406.086 5103.31 406.5 5103.31H409.5C409.914 5103.31 410.25 5103.646 410.25 5104.059C410.25 5104.479 409.914 5104.815 409.5 5104.815H406.5C406.086 5104.815 405.75 5104.479 405.75 5104.059ZM406.5 5106.313C406.086 5106.313 405.75 5106.649 405.75 5107.062C405.75 5107.475 406.086 5107.811 406.5 5107.811H409.5C409.914 5107.811 410.25 5107.475 410.25 5107.062C410.25 5106.649 409.914 5106.313 409.5 5106.313H406.5Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M512.429 5099.71C510.772 5099.71 509.429 5101.06 509.429 5102.71V5113C509.429 5114.66 510.772 5116 512.429 5116H521C522.657 5116 524 5114.66 524 5113V5102.71C524 5101.06 522.657 5099.71 521 5099.71H512.429ZM513.5 5105.71C513.5 5105.12 513.98 5104.64 514.571 5104.64H518.857C519.449 5104.64 519.929 5105.12 519.929 5105.71C519.929 5106.31 519.449 5106.79 518.857 5106.79H514.571C513.98 5106.79 513.5 5106.31 513.5 5105.71ZM514.571 5108.93C513.98 5108.93 513.5 5109.41 513.5 5110C513.5 5110.59 513.98 5111.07 514.571 5111.07H518.857C519.449 5111.07 519.929 5110.59 519.929 5110C519.929 5109.41 519.449 5108.93 518.857 5108.93H514.571Z"/>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,8 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.428 23.420" fill="none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 23.428 22.650" fill="none">
<g transform="translate(-468.286 -5092.290)" fill="#000000"> <g transform="translate(-364.286 -5092.670)" fill="#000000">
<path d="M489.214 5103.21C489.214 5102.9 488.958 5102.64 488.643 5102.64H480.786C480.47 5102.64 480.214 5102.9 480.214 5103.21V5112.64C480.214 5112.96 480.47 5113.21 480.786 5113.21H488.643C488.958 5113.21 489.214 5112.96 489.214 5112.64V5103.21ZM491.214 5112.64C491.214 5114.06 490.063 5115.21 488.643 5115.21H480.786C479.365 5115.21 478.214 5114.06 478.214 5112.64V5103.21C478.214 5101.79 479.365 5100.64 480.786 5100.64H488.643C490.063 5100.64 491.214 5101.79 491.214 5103.21V5112.64Z"/> <path d="M364.786 5112.25V5095.75C364.786 5095.07 365.056 5094.41 365.539 5093.93C366.021 5093.45 366.675 5093.18 367.357 5093.18H373.156C373.733 5093.17 374.297 5093.35 374.757 5093.7C375.224 5094.06 375.556 5094.56 375.697 5095.13L375.995 5096.32H384.643C385.325 5096.32 385.979 5096.59 386.461 5097.07C386.943 5097.56 387.214 5098.21 387.214 5098.89V5112.25C387.214 5112.93 386.943 5113.59 386.461 5114.07L386.46 5114.07C385.978 5114.55 385.325 5114.82 384.643 5114.82H367.357C366.675 5114.82 366.021 5114.55 365.539 5114.07C365.056 5113.59 364.786 5112.93 364.786 5112.25ZM366.786 5112.25C366.786 5112.4 366.846 5112.55 366.953 5112.65L366.954 5112.65C367.061 5112.76 367.205 5112.82 367.357 5112.82H384.643C384.795 5112.82 384.94 5112.76 385.046 5112.65L385.119 5112.57C385.181 5112.47 385.214 5112.36 385.214 5112.25V5098.89C385.214 5098.74 385.154 5098.6 385.046 5098.49C384.939 5098.38 384.794 5098.32 384.643 5098.32H375.214C374.755 5098.32 374.355 5098.01 374.244 5097.56L373.757 5095.62L373.756 5095.61C373.725 5095.49 373.651 5095.37 373.547 5095.29C373.443 5095.21 373.315 5095.17 373.185 5095.18C373.175 5095.18 373.165 5095.18 373.156 5095.18H367.357C367.205 5095.18 367.06 5095.24 366.953 5095.35C366.846 5095.45 366.786 5095.6 366.786 5095.75V5112.25Z"/>
<path d="M487.071 5105.36C487.623 5105.36 488.071 5105.8 488.071 5106.36C488.071 5106.91 487.623 5107.36 487.071 5107.36H482.357C481.805 5107.36 481.357 5106.91 481.357 5106.36C481.357 5105.8 481.805 5105.36 482.357 5105.36H487.071Z"/> <path d="M379.375 5102.034C379.375 5101.801 379.183 5101.606 378.947 5101.606H373.054C372.817 5101.606 372.625 5101.801 372.625 5102.034V5109.106C372.625 5109.346 372.817 5109.534 373.054 5109.534H378.947C379.183 5109.534 379.375 5109.346 379.375 5109.106V5102.034ZM380.875 5109.106C380.875 5110.171 380.012 5111.034 378.947 5111.034H373.054C371.988 5111.034 371.125 5110.171 371.125 5109.106V5102.034C371.125 5100.969 371.988 5100.106 373.054 5100.106H378.947C380.012 5100.106 380.875 5100.969 380.875 5102.034V5109.106Z"/>
<path d="M487.071 5108.5C487.623 5108.5 488.071 5108.95 488.071 5109.5C488.071 5110.05 487.623 5110.5 487.071 5110.5H482.357C481.805 5110.5 481.357 5110.05 481.357 5109.5C481.357 5108.95 481.805 5108.5 482.357 5108.5H487.071Z"/> <path d="M377.768 5103.646C378.182 5103.646 378.518 5103.976 378.518 5104.396C378.518 5104.809 378.182 5105.146 377.768 5105.146H374.232C373.818 5105.146 373.482 5104.809 373.482 5104.396C373.482 5103.976 373.818 5103.646 374.232 5103.646H377.768Z"/>
<path d="M479.607 5092.79C479.915 5092.79 480.207 5092.93 480.396 5093.17C480.585 5093.41 480.652 5093.73 480.577 5094.03L480.494 5094.36H480.786C481.467 5094.36 482.122 5094.63 482.604 5095.11C483.086 5095.59 483.357 5096.25 483.357 5096.93V5098.5C483.357 5099.05 482.909 5099.5 482.357 5099.5C481.805 5099.5 481.357 5099.05 481.357 5098.5V5096.93C481.357 5096.78 481.297 5096.63 481.19 5096.52C481.083 5096.42 480.937 5096.36 480.786 5096.36H479.988L479.932 5096.58C479.839 5096.96 479.621 5097.3 479.311 5097.55C478.997 5097.79 478.609 5097.93 478.208 5097.93L473.934 5097.93C473.534 5097.93 473.145 5097.79 472.832 5097.55C472.522 5097.3 472.304 5096.96 472.211 5096.58L472.156 5096.36H471.357C471.205 5096.36 471.06 5096.42 470.953 5096.52C470.846 5096.63 470.786 5096.78 470.786 5096.93V5107.93C470.786 5108.08 470.846 5108.23 470.953 5108.33C471.06 5108.44 471.206 5108.5 471.357 5108.5H475.286C475.838 5108.5 476.285 5108.95 476.286 5109.5C476.286 5110.05 475.838 5110.5 475.286 5110.5H471.357C470.675 5110.5 470.021 5110.23 469.539 5109.75C469.056 5109.26 468.786 5108.61 468.786 5107.93V5096.93C468.786 5096.25 469.056 5095.59 469.539 5095.11C470.021 5094.63 470.675 5094.36 471.357 5094.36H471.649L471.566 5094.03C471.49 5093.73 471.557 5093.41 471.747 5093.17C471.936 5092.93 472.227 5092.79 472.536 5092.79H479.607ZM474.109 5095.93H478.034L478.323 5094.79H473.82L474.109 5095.93Z"/> <path d="M377.768 5106.001C378.182 5106.001 378.518 5106.339 378.518 5106.751C378.518 5107.164 378.182 5107.501 377.768 5107.501H374.232C373.818 5107.501 373.482 5107.164 373.482 5106.751C373.482 5106.339 373.818 5106.001 374.232 5106.001H377.768Z"/>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB