mirror of
https://github.com/penpot/penpot.git
synced 2026-08-28 23:59:00 +00:00
⚡ Paint plain text directly onto Current
Skip the Fills/Strokes blit and empty save_layers for stroke-free SrcOver text without blur or shadows. Multi-style spans stay on Paragraph TextStyles; stroked or effected text stays layered.
This commit is contained in:
parent
0a3352927d
commit
65cdc606f5
@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
- Raster `Fill::Image`: skip `save_layer` unless the shape has an image filter; plain
|
- Raster `Fill::Image`: skip `save_layer` unless the shape has an image filter; plain
|
||||||
Rect/Frame (no corners) also skip the container clip (`draw_image_fill` in fills.rs).
|
Rect/Frame (no corners) also skip the container clip (`draw_image_fill` in fills.rs).
|
||||||
|
- `can_render_directly` paints onto Current (no Fills/Strokes blit) for plain geometry and
|
||||||
|
for stroke-free text (SrcOver, no blur/shadows). Multi-style text is fine: span styles
|
||||||
|
live in Paragraph `TextStyle`s. Text skips the `nested_fills` guard (fills are on spans).
|
||||||
|
`draw_text` only `save_layer`s when stroke-group opacity is set; plain fill paint is direct.
|
||||||
|
- Text with strokes / shadows / blur stays on the layered Fills/Strokes path.
|
||||||
- Zoom settle: visible tiles present via `FrameType::ViewportReady` before interest-ring
|
- Zoom settle: visible tiles present via `FrameType::ViewportReady` before interest-ring
|
||||||
work; crop-cache rebuild is deferred to the later `Full` so the soft→sharp snap is
|
work; crop-cache rebuild is deferred to the later `Full` so the soft→sharp snap is
|
||||||
compose+present only.
|
compose+present only.
|
||||||
|
|||||||
@ -1430,6 +1430,17 @@ impl RenderState {
|
|||||||
// Stroke-only (fills_none) can go direct: empty fills are a no-op and
|
// Stroke-only (fills_none) can go direct: empty fills are a no-op and
|
||||||
// strokes paint into Current. Large files need mid-walk GPU drains so
|
// strokes paint into Current. Large files need mid-walk GPU drains so
|
||||||
// release builds do not backlog a huge ops buffer in one Partial.
|
// release builds do not backlog a huge ops buffer in one Partial.
|
||||||
|
//
|
||||||
|
// Plain text (no strokes / effects) also paints into Current: span styles
|
||||||
|
// live in Skia Paragraph TextStyles, so multi-style text is fine.
|
||||||
|
// Text skips the nested_fills guard because fills are on spans, not
|
||||||
|
// shape.fills. Strokes stay layered (masking needs save_layers).
|
||||||
|
let is_direct_geometry = matches!(
|
||||||
|
shape.shape_type,
|
||||||
|
Type::Rect(_) | Type::Circle | Type::Path(_) | Type::Bool(_) | Type::Frame(_)
|
||||||
|
) && !(shape.fills.is_empty() && has_nested_fills);
|
||||||
|
let is_direct_text =
|
||||||
|
matches!(shape.shape_type, Type::Text(_)) && !shape.has_visible_strokes();
|
||||||
let can_render_directly = apply_to_current_surface
|
let can_render_directly = apply_to_current_surface
|
||||||
&& offset.is_none()
|
&& offset.is_none()
|
||||||
&& parent_shadows.is_none()
|
&& parent_shadows.is_none()
|
||||||
@ -1440,11 +1451,7 @@ impl RenderState {
|
|||||||
&& shape.background_blur.is_none()
|
&& shape.background_blur.is_none()
|
||||||
&& !has_inherited_blur
|
&& !has_inherited_blur
|
||||||
&& !shadows_need_layered
|
&& !shadows_need_layered
|
||||||
&& matches!(
|
&& (is_direct_geometry || is_direct_text)
|
||||||
shape.shape_type,
|
|
||||||
Type::Rect(_) | Type::Circle | Type::Path(_) | Type::Bool(_) | Type::Frame(_)
|
|
||||||
)
|
|
||||||
&& !(shape.fills.is_empty() && has_nested_fills)
|
|
||||||
&& target_surface != SurfaceId::Export;
|
&& target_surface != SurfaceId::Export;
|
||||||
|
|
||||||
if can_render_directly {
|
if can_render_directly {
|
||||||
@ -1473,21 +1480,49 @@ impl RenderState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fills::render(self, shape, &shape.fills, antialias, target_surface, None)?;
|
if let Type::Text(stored_text_content) = &shape.shape_type {
|
||||||
|
self.tile_atlas_flushed = true;
|
||||||
|
|
||||||
// Clipped frames draw strokes in render_shape_exit over children.
|
let selrect = shape.selrect();
|
||||||
let skip_strokes = matches!(shape.shape_type, Type::Frame(_)) && shape.clip_content;
|
let stored_bounds = stored_text_content.bounds();
|
||||||
if !skip_strokes {
|
let bounds_match = (stored_bounds.width() - selrect.width()).abs() < 0.01
|
||||||
// Pass strokes in natural order; stroke merging handles top-most ordering internally.
|
&& (stored_bounds.height() - selrect.height()).abs() < 0.01;
|
||||||
let visible_strokes: Vec<&Stroke> = shape.visible_strokes().collect();
|
let rebound_text_content = if bounds_match {
|
||||||
strokes::render(
|
None
|
||||||
self,
|
} else {
|
||||||
|
Some(stored_text_content.new_bounds(selrect))
|
||||||
|
};
|
||||||
|
let text_content: &TextContent =
|
||||||
|
rebound_text_content.as_ref().unwrap_or(stored_text_content);
|
||||||
|
let mut paragraph_builders = text_content.paragraph_builder_group_from_text(None);
|
||||||
|
text::render(
|
||||||
|
Some(self),
|
||||||
|
None,
|
||||||
shape,
|
shape,
|
||||||
&visible_strokes,
|
&mut paragraph_builders,
|
||||||
Some(target_surface),
|
Some(target_surface),
|
||||||
antialias,
|
None,
|
||||||
outset,
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
} else {
|
||||||
|
fills::render(self, shape, &shape.fills, antialias, target_surface, None)?;
|
||||||
|
|
||||||
|
// Clipped frames draw strokes in render_shape_exit over children.
|
||||||
|
let skip_strokes = matches!(shape.shape_type, Type::Frame(_)) && shape.clip_content;
|
||||||
|
if !skip_strokes {
|
||||||
|
// Pass strokes in natural order; stroke merging handles top-most ordering internally.
|
||||||
|
let visible_strokes: Vec<&Stroke> = shape.visible_strokes().collect();
|
||||||
|
strokes::render(
|
||||||
|
self,
|
||||||
|
shape,
|
||||||
|
&visible_strokes,
|
||||||
|
Some(target_surface),
|
||||||
|
antialias,
|
||||||
|
outset,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.surfaces.apply_mut(target_surface as u32, |s| {
|
self.surfaces.apply_mut(target_surface as u32, |s| {
|
||||||
|
|||||||
@ -331,6 +331,8 @@ fn render_text_on_canvas(
|
|||||||
) {
|
) {
|
||||||
let layer_bounds = shape.layer_bounds();
|
let layer_bounds = shape.layer_bounds();
|
||||||
|
|
||||||
|
// Layer stack is managed here (blur / shadow / inset). `draw_text` is
|
||||||
|
// self-contained and only opens a layer when stroke-group opacity needs it.
|
||||||
if let Some(blur_filter) = blur {
|
if let Some(blur_filter) = blur {
|
||||||
let mut blur_paint = Paint::default();
|
let mut blur_paint = Paint::default();
|
||||||
blur_paint.set_image_filter(blur_filter.clone());
|
blur_paint.set_image_filter(blur_filter.clone());
|
||||||
@ -391,8 +393,6 @@ fn render_text_on_canvas(
|
|||||||
if blur.is_some() {
|
if blur.is_some() {
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lays out and paints paragraph builders without any layer management.
|
/// Lays out and paints paragraph builders without any layer management.
|
||||||
@ -748,20 +748,22 @@ fn draw_text(
|
|||||||
layer_opacity: Option<f32>,
|
layer_opacity: Option<f32>,
|
||||||
overlay_emoji: bool,
|
overlay_emoji: bool,
|
||||||
) {
|
) {
|
||||||
let layer_bounds = shape.layer_bounds();
|
// Multi-style spans are already encoded in each ParagraphBuilder's
|
||||||
|
// TextStyles; paragraph.paint handles them without an isolation layer.
|
||||||
|
// Only open a save_layer when stroke-group opacity must composite as one.
|
||||||
if let Some(opacity) = layer_opacity {
|
if let Some(opacity) = layer_opacity {
|
||||||
|
let layer_bounds = shape.layer_bounds();
|
||||||
let mut opacity_paint = Paint::default();
|
let mut opacity_paint = Paint::default();
|
||||||
opacity_paint.set_alpha_f(opacity);
|
opacity_paint.set_alpha_f(opacity);
|
||||||
let layer_rec = SaveLayerRec::default()
|
let layer_rec = SaveLayerRec::default()
|
||||||
.bounds(&layer_bounds)
|
.bounds(&layer_bounds)
|
||||||
.paint(&opacity_paint);
|
.paint(&opacity_paint);
|
||||||
canvas.save_layer(&layer_rec);
|
canvas.save_layer(&layer_rec);
|
||||||
|
paint_text_with_emoji_overlay(canvas, shape, paragraph_builder_groups, overlay_emoji);
|
||||||
|
canvas.restore();
|
||||||
} else {
|
} else {
|
||||||
canvas.save_layer(&SaveLayerRec::default().bounds(&layer_bounds));
|
paint_text_with_emoji_overlay(canvas, shape, paragraph_builder_groups, overlay_emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
paint_text_with_emoji_overlay(canvas, shape, paragraph_builder_groups, overlay_emoji);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a text stroke masked to the glyph shape.
|
/// Renders a text stroke masked to the glyph shape.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user