Add sizing gizmos to the Text tool's text area (#2176)

* Fix abortion while dragging

* Create function for text bounding box

* Reorder arms of text tool FSM

* add transform cage to textbox pt.1

* add transform cage pt.2

* Fix minor issue after merge

* Get bounding box working in place without action keys

* Add max_height and disable pivot drag

* Cleanup code and write doco for new utility function

* Minor change due to merge

* Add bottom overlay

* Get modifier keys to work pt.1

* Code cleanup

* cleanup

* Fix transform

* Minor improvements

* Undo debug statement!

* Add comments and keep original layer transformation

* Alt from centre

* Fix merge conflict

* Minor code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: hypercube <0hypercube@gmail.com>
Co-authored-by: James Lindsay <78500760+0HyperCube@users.noreply.github.com>
This commit is contained in:
Nitish Choudhary
2025-03-12 15:37:10 +05:30
committed by GitHub
parent a696aae044
commit c0d3eb8072
8 changed files with 321 additions and 65 deletions

View File

@@ -138,7 +138,7 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: Types
}
}
// Clip when the height is exceeded
if typesetting.max_height.is_some_and(|max_height| builder.pos.y > max_height) {
if typesetting.max_height.is_some_and(|max_height| builder.pos.y > max_height - line_height) {
return builder.other_subpaths;
}
@@ -160,7 +160,7 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: Types
builder.other_subpaths
}
pub fn bounding_box(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> DVec2 {
pub fn bounding_box(str: &str, buzz_face: Option<&rustybuzz::Face>, typesetting: TypesettingConfig) -> DVec2 {
let buzz_face = match buzz_face {
Some(face) => face,
// Show blank layer if font has not loaded
@@ -168,7 +168,7 @@ pub fn bounding_box(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting:
};
let space_glyph = buzz_face.glyph_index(' ');
let (scale, line_height, mut buffer) = font_properties(&buzz_face, typesetting.font_size, typesetting.line_height_ratio);
let (scale, line_height, mut buffer) = font_properties(buzz_face, typesetting.font_size, typesetting.line_height_ratio);
let mut pos = DVec2::ZERO;
let mut bounds = DVec2::ZERO;
@@ -177,7 +177,7 @@ pub fn bounding_box(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting:
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
push_str(&mut buffer, word);
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
let glyph_buffer = rustybuzz::shape(buzz_face, &[], buffer);
// Don't wrap the first word
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, pos.x, space_glyph) {
@@ -215,6 +215,59 @@ pub fn load_face(data: &[u8]) -> rustybuzz::Face {
rustybuzz::Face::from_slice(data, 0).expect("Loading font failed")
}
pub fn lines_clipping(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> bool {
let buzz_face = match buzz_face {
Some(face) => face,
// False if font hasn't loaded
None => return false,
};
if typesetting.max_height.is_none() {
return false;
}
let space_glyph = buzz_face.glyph_index(' ');
let (scale, line_height, mut buffer) = font_properties(&buzz_face, typesetting.font_size, typesetting.line_height_ratio);
let mut pos = DVec2::ZERO;
let mut bounds = DVec2::ZERO;
for line in str.split('\n') {
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
push_str(&mut buffer, word);
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
// Don't wrap the first word
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, pos.x, space_glyph) {
pos = DVec2::new(0., pos.y + line_height);
}
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
let glyph_id = GlyphId(glyph_info.glyph_id as u16);
if let Some(max_width) = typesetting.max_width {
if space_glyph != Some(glyph_id) && pos.x + (glyph_position.x_advance as f64 * scale * typesetting.character_spacing) >= max_width {
pos = DVec2::new(0., pos.y + line_height);
}
}
pos += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * scale;
bounds = bounds.max(pos + DVec2::new(0., line_height));
}
buffer = glyph_buffer.clear();
}
pos = DVec2::new(0., pos.y + line_height);
bounds = bounds.max(pos);
}
if typesetting.max_height.unwrap() < bounds.y {
return true;
}
false
}
struct SplitWordsIncludingSpaces<'a> {
text: &'a str,
start_byte: usize,