Merge pull request #5586 from dotCooCoo/fix/text-tweaker-range

Text Tweaker: align byte ranges to character boundaries
This commit is contained in:
BFlat
2026-11-04 19:17:45 +07:00
committed by GitHub
3 changed files with 13 additions and 1 deletions
+2 -1
View File
@@ -46,7 +46,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Text Tweaker",
["Object", "Plug-ins", "Type Tools"],
None,
"{attribute?: \"size\" (pt 0.1..1296) | \"baselineShift\" (pt −1296..1296) | \"rotation\" (° −360..360) | \"hScale\" (% 1..10000) | \"vScale\" (% 1..10000) | \"tracking\" (1/1000 em −1000..10000) | \"leading\" (pt 0.1..5000) | \"strokeWidth\" (pt 0..1000) (\"size\"), from?: the first character's value, to?: the last character's value (each in the attribute's units and range; defaults: size 12 → 36, baselineShift 0 → 12, rotation 0 → 30, hScale and vScale 100 → 150, tracking 0 → 200, leading 12 → 36, strokeWidth 0 → 2), shape?: \"straight\" (an even ramp) | \"ease\" (slow at both ends) | \"bow\" (from at both ends, rising smoothly to `to` in the middle) | \"spike\" (from at both ends, straight up to `to` in the middle) | \"random\" (each character anywhere from `from` to `to`) (\"straight\"), seed?: whole number (0; random: the same seed and text give the same values), skipSpaces?: true (spaces and line breaks keep their values and don't count), id?, start?: byte, end?: byte, ids?: [id…] (instead of id)} give the characters one character attribute letter by letter, from `from` to `to` along the shape, as the Character panel sets it, as one undo step; the text stays live and editable. The range of `id`, else the type objects in `ids`, else the text the Type tool has selected (at a caret: all its text), else every selected type object; each object gets its own ramp. Stroke weight only shows on characters with a stroke → {count (characters changed), ids}",
"{attribute?: \"size\" (pt 0.1..1296) | \"baselineShift\" (pt −1296..1296) | \"rotation\" (° −360..360) | \"hScale\" (% 1..10000) | \"vScale\" (% 1..10000) | \"tracking\" (1/1000 em −1000..10000) | \"leading\" (pt 0.1..5000) | \"strokeWidth\" (pt 0..1000) (\"size\"), from?: the first character's value, to?: the last character's value (each in the attribute's units and range; defaults: size 12 → 36, baselineShift 0 → 12, rotation 0 → 30, hScale and vScale 100 → 150, tracking 0 → 200, leading 12 → 36, strokeWidth 0 → 2), shape?: \"straight\" (an even ramp) | \"ease\" (slow at both ends) | \"bow\" (from at both ends, rising smoothly to `to` in the middle) | \"spike\" (from at both ends, straight up to `to` in the middle) | \"random\" (each character anywhere from `from` to `to`) (\"straight\"), seed?: whole number (0; random: the same seed and text give the same values), skipSpaces?: true (spaces and line breaks keep their values and don't count), id?, start?: byte, end?: byte (range endpoints inside a character round down to its start), ids?: [id…] (instead of id)} give the characters one character attribute letter by letter, from `from` to `to` along the shape, as the Character panel sets it, as one undo step; the text stays live and editable. The range of `id`, else the type objects in `ids`, else the text the Type tool has selected (at a caret: all its text), else every selected type object; each object gets its own ramp. Stroke weight only shows on characters with a stroke → {count (characters changed), ids}",
has_selection,
text_tweaker
)]
@@ -97,6 +97,7 @@ fn text_tweaker(s: &mut Session, p: &Value) -> Result<Value> {
for (id, a, b, _) in ranges {
let t = text_ref(s, id)?;
let text = t.plain_text();
let (a, b) = (edit::floor_char(&text, a), edit::floor_char(&text, b));
let chars: Vec<usize> =
text.char_indices().filter(|&(i, c)| (a..b).contains(&i) && !(skip && (c.is_whitespace() || c.is_control()))).map(|(i, _)| i).collect();
seen += chars.len();
@@ -124,6 +124,16 @@ fn a_range_changes_only_its_characters() {
assert_eq!(tracking, [0.0, 100.0, 200.0, 300.0, 0.0]);
}
#[test]
fn a_range_inside_a_multibyte_character_uses_character_boundaries() {
let mut s = session();
let id = text(&mut s, "éx");
let out = s.execute(C, &json!({"id": id.0, "start": 1, "end": 2, "attribute": "tracking", "from": 100, "to": 300})).unwrap();
assert_eq!(out["count"], 1);
let tracking: Vec<f64> = styles(&s, id).iter().map(|(_, st)| st.tracking).collect();
assert_eq!(tracking, [100.0, 0.0]);
}
#[test]
fn random_values_stay_in_range_and_repeat_with_the_seed() {
let mut s = session();
+1
View File
@@ -6921,6 +6921,7 @@ type it is the size shown.
It changes the range of `id` (`start`/`end` bytes), else all the text of the type objects in `ids`, else the text the
Type tool has selected (at a caret: all of that object's text), else all the text of every selected type object; each
object gets its own ramp, and at most 100000 characters change in one run. A run that changes nothing makes no step.
A `start` or `end` byte inside a character rounds down to that character's start.
| Parameter | Values (default) |
|---|---|