mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Include stroke thickness in the bounds of document exports sized to fit "All Artwork" or "Selection" (#4112)
* Include stroke thickness in the bounds of document exports sized to fit "All Artwork" or "Selection" * Code review fixes
This commit is contained in:
@@ -221,6 +221,18 @@ impl DocumentMetadata {
|
||||
self.bounding_box_with_transform(layer, self.transform_to_document(layer))
|
||||
}
|
||||
|
||||
/// Get the bounding box of the specified layer in document space, expanded to include the actual rendered
|
||||
/// stroke geometry when the layer is a vector with a stroke style. Falls back to the click-target-based
|
||||
/// bounds for non-vector layers (groups, raster, text, color, gradient).
|
||||
pub fn bounding_box_document_with_stroke(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
|
||||
if let Some(vector) = self.layer_vector_data.get(&layer)
|
||||
&& let Some(bounds) = vector.stroke_inclusive_bounding_box_with_transform(self.transform_to_document(layer))
|
||||
{
|
||||
return Some(bounds);
|
||||
}
|
||||
self.bounding_box_document(layer)
|
||||
}
|
||||
|
||||
/// Get the bounding box of the click target of the specified layer in viewport space
|
||||
pub fn bounding_box_viewport(&self, layer: LayerNodeIdentifier) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(layer, self.transform_to_viewport(layer))
|
||||
|
||||
@@ -1222,6 +1222,34 @@ impl NodeNetworkInterface {
|
||||
Some(transformed.bounding_box())
|
||||
}
|
||||
|
||||
/// Calculates the document bounds in document space, expanding vector layer bounds to include the rendered
|
||||
/// stroke width. Used for export so the output canvas captures strokes that overflow the path geometry.
|
||||
pub fn document_bounds_document_space_with_stroke(&self, include_artboards: bool) -> Option<[DVec2; 2]> {
|
||||
self.document_metadata
|
||||
.all_layers()
|
||||
.filter(|layer| include_artboards || !self.is_artboard(&layer.to_node(), &[]))
|
||||
.filter_map(|layer| {
|
||||
if !self.is_artboard(&layer.to_node(), &[])
|
||||
&& let Some(artboard_node_identifier) = layer
|
||||
.ancestors(self.document_metadata())
|
||||
.find(|ancestor| *ancestor != LayerNodeIdentifier::ROOT_PARENT && self.is_artboard(&ancestor.to_node(), &[]))
|
||||
&& let Some(artboard) = self.document_node(&artboard_node_identifier.to_node(), &[])
|
||||
&& let Some(clip_input) = artboard.inputs.get(5)
|
||||
&& let NodeInput::Value { tagged_value, .. } = clip_input
|
||||
&& tagged_value.clone().deref() == &TaggedValue::Bool(true)
|
||||
{
|
||||
return Some(Quad::clip(
|
||||
self.document_metadata.bounding_box_document_with_stroke(layer).unwrap_or_default(),
|
||||
self.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(),
|
||||
));
|
||||
}
|
||||
self.document_metadata.bounding_box_document_with_stroke(layer)
|
||||
})
|
||||
// Skip any layer bounds containing NaN to avoid poisoning the combined result
|
||||
.filter(|[min, max]| min.is_finite() && max.is_finite())
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculates the selected layer bounds in document space
|
||||
pub fn selected_bounds_document_space(&self, include_artboards: bool, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
|
||||
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
|
||||
@@ -1235,6 +1263,20 @@ impl NodeNetworkInterface {
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Calculates the selected layer bounds in document space, expanding vector layer bounds to include the
|
||||
/// rendered stroke width. Used for export so the output canvas captures strokes that overflow the path geometry.
|
||||
pub fn selected_bounds_document_space_with_stroke(&self, include_artboards: bool, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
|
||||
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
|
||||
log::error!("Could not get selected nodes in selected_bounds_document_space_with_stroke");
|
||||
return None;
|
||||
};
|
||||
selected_nodes
|
||||
.selected_layers(&self.document_metadata)
|
||||
.filter(|&layer| include_artboards || !self.is_artboard(&layer.to_node(), &[]))
|
||||
.filter_map(|layer| self.document_metadata.bounding_box_document_with_stroke(layer))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
/// Layers excluding ones that are children of other layers in the list.
|
||||
// TODO: Cache this
|
||||
pub fn shallowest_unique_layers(&self, network_path: &[NodeId]) -> impl Iterator<Item = LayerNodeIdentifier> + use<> {
|
||||
|
||||
@@ -243,10 +243,12 @@ impl NodeGraphExecutor {
|
||||
graphene_std::application_io::ExportFormat::Raster
|
||||
};
|
||||
|
||||
// Calculate the bounding box of the region to be exported (artboard bounds always contribute)
|
||||
// Calculate the bounding box of the region to be exported (artboard bounds always contribute).
|
||||
// `AllArtwork` and `Selection` expand vector layer bounds by the rendered stroke width so strokes
|
||||
// drawn at render-time (without a `Solidify Stroke`) aren't clipped at the export canvas edge.
|
||||
let bounds = match export_config.bounds {
|
||||
ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(true),
|
||||
ExportBounds::Selection => document.network_interface.selected_bounds_document_space(true, &[]),
|
||||
ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space_with_stroke(true),
|
||||
ExportBounds::Selection => document.network_interface.selected_bounds_document_space_with_stroke(true, &[]),
|
||||
ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id),
|
||||
}
|
||||
.ok_or_else(|| "No bounding box".to_string())?;
|
||||
|
||||
Reference in New Issue
Block a user