text.setStyle now reads `size` and `tracking` with `number_given` and
`length_given` from pathops.rs instead of its own copies.
Both helpers read a string only when all of it is a number, or for
`length_given` a length with a unit (`"5 mm"`). Before, `"20abc"` read
as 20, so `text.setStyle` applied a 20 pt size and Offset Path a 10 pt
offset for `"10abc"`. Such a string is now refused with "`size` must be
a number, not \"20abc\"" and the document is left as it was.
`text.setStyle` read `font`, `style` and `justify` with `str_param`,
`size` with `len_param` and `tracking` with `num_param`, so a value of
the wrong type was skipped: `{"size": 30, "style": ["Bold"]}` set the
size and ignored the style, and `{"font": "Inter", "size": [12]}` set
the font and ignored the size. `features` dropped an entry that was not
a string and applied the rest. `text.setRangeStyle` skipped a `font` or
`style` that was not a string, `type.ruby` skipped such a `text`,
`position` or `align`, and `text.setPathHandle` moved the out handle
for any `which` other than "in".
Each of these now returns an error that names the param, before the
command edits the document. Omitting a param or giving null keeps its
default. `size` and `leading` still take a length with a unit
("12 pt", "5 mm"), and `tracking` still takes a number written as a
string ("20").
- `text.setStyle`: `font`, `style`, `justify` (`text_param`), `size`
and `tracking` (checked reads in `typecmd.rs`), `features`.
- `text.setRangeStyle`: `font`, `style`, `features`.
- `type.ruby`: `text`, `position`, `align`.
- `text.setPathHandle`: `which` must be "in" or "out".
Docs: docs/mcp.md.
The command catalog said "* ", "# ", "> " and "+ " all start a list
with the filled dot. It now names the filled dot, hollow circle,
filled square and dash. The text.startList test checks each of the
four triggers and the marker the layout draws.
Typed list detection gave every bulleted trigger the filled dot. Now
`*` and a space start a list with the filled dot (•), `+` with the
hollow circle (◦), `#` with the filled square (▪) and `>` with the dash
(–), each one of the existing Bullets presets. `1.`, `1)`, `A.` and `A)`
start the same numbered lists as before.
Only the list a typed trigger starts changes. Lists already in a
document keep their bullets, pasted triggers start no list, and Undo
and Redo of a typed list work as before.
Docs: docs/mcp.md (typed list paragraph), docs/target-app-parity.md.
- typed_lists_off_leaves_typed_markers_as_text turns the preference back
on for each marker and checks that typing it in a new paragraph starts
a list with a Bullets and Numbering undo step.
- start_lists_from_typed_markers_is_a_saved_type_preference: the Type
page shows the checkbox, checked by default; unchecking it turns the
preference off in the session and in the saved preferences, and every
bundled language translates its label.
Preferences > Type gets Start Lists From Typed Markers (`typedLists`, on
by default). With it off, a list marker and a space typed with the Type
tool at the start of a paragraph (`1. `, `1) `, `A. `, `A) `, `* `,
`# `, `> `, `+ `) stay as text and start no list. `text.startList`,
`text.setFormat {list}` and the Bullets and Numbered List buttons work
as before.
The preference reaches the Type tool through `ToolContext::typed_lists`.
The label has a row in every catalog.
When a frame threaded in before the edited frame takes all of the story's
text, the edited frame is left with no text while the story stays
selected. The other frames' text is now highlighted, Delete and Backspace
replace the story, an Alt+arrow step changes the tracking of every frame,
and Copy and Cut take the story. A story whose frames hold no text has
nothing to copy, and Delete in it records no undo step.
With a whole story selected, the highlight and typing read the frames saved
at Select All, while a pick, a fill and the other story-wide edits read the
document's thread. A frame added by text.thread.add took a pick without being
highlighted, and typing left its text in place.
Both now read the thread through Document::story_of. A frame threaded in
during the selection is highlighted and takes the edit, and a frame that
leaves the thread is not highlighted and keeps its text.
appearance.copyFrom and a fill or stroke change on a story selected by
Select All no longer skip locked frames, hidden frames and frames on a
locked artboard. They apply to every frame of the story, as the Character
panel's story edits do. appearance.copyFrom still leaves out a frame that
is its source.
With a threaded story selected by Select All, switching to the Eyedropper kept
only the edited frame's selection, so a pick styled one frame. The selection now
carries the story's frames, a pick or Shift-click applies to the text of every
frame as one undo step, and switching back to a Type tool restores the story
selection. A selection inside one frame and unthreaded type behave as before.
A pick and a fill or stroke change on a selected story leave locked and hidden
frames and frames on a locked artboard as they are. appearance.copyFrom leaves
out a frame that is its source.
Typing over a selected story no longer removes the text of a frame whose thread
was removed after Select All.
As asked in review: the reference app has no such commands, so they ship
in the Color Effects add-on. paint.fillToStroke / paint.strokeToFill are
now addon.color.fillToStroke / addon.color.strokeToFill in
cmd/addons/color.rs, listed in Object > Plug-ins > Color Effects instead
of the Color panel menu, with catalog rows in all 26 catalogs.
Two fixes from CodeRabbit's review:
- A graph's series marks and labels give the graph the copied paint in
the same undo step (capture_series_paints / capture_label_styles, as
paint.setFill does), so drawing the graph again keeps it. Targets go
through graph::paint_targets, so a series group stands for its marks.
- A copy that would leave the effective paint as it is skips the object,
so None copied onto a missing fill or stroke adds no item and records
no undo step.
paint.fillToStroke and paint.strokeToFill copy each target's own top fill onto its top stroke, or the other way, so a mixed selection keeps each object's colour. They aim and undo like Swap Fill and Stroke: groups and compound paths by their members, each text run with its own paint, and only the characters the Type tool has selected. A stroke given paint at width 0 gets 1 pt, skipNone leaves objects whose source paint is None alone, and the defaults for new art change the same way. Both are in the Color panel menu after Invert and Complement.
While Select All holds a threaded story, a paragraph whose text starts in one of the
story's locked or hidden frames is no longer converted, and the empty parts of a converted
paragraph in such a frame are no longer restyled. Before, a locked frame whose first
paragraph took in the previous frame's empty last part was converted, and so was a
paragraph that ran on from a locked first frame into an editable one.
After Select All in a frame of threaded text, Remove Line Breaks and
Remove Paragraph Breaks changed only the edited frame, and Convert to
Text over MCP converted only the selected frame. Both now act on the
text of every frame of the story as one undo step.
- addon.cleanup.removeBreaks reads the frames of the story as one text,
so a break at the end of a frame before a word in the next one becomes
a space. The same applies when every frame of a story is selected as
objects.
- text.convertListToText without ids, id or a range converts every
paragraph of the story. Locked and hidden frames of the selected story
are not converted, even when ids names them. When the call converts
the whole story, the story stays selected; before, the call reset the
Type tool's selection to the edited frame.
- A selection inside one frame, and unthreaded type, behave as before.
Docs: docs/mcp.md, docs/target-app-parity.md.
`the_fallback_glyph_of_a_consonant_after_its_vowel_sign_is_reported` expected
the fallback face's cmap glyph for the vowel sign I. On the Ubuntu runner the
fallback face is Noto Sans Devanagari, whose shaping draws the sign with
another glyph (545 instead of 32), so the test failed on `main`. It now checks
that two glyphs are drawn in Ka's cluster, the first is not Ka's and the
second is, and keeps the `text.glyphAlternates` checks.
Fixes#5750
When a fallback face draws Ka with the vowel sign I, the vowel sign's
glyph comes first in Ka's cluster, and text.glyphAlternates reported it
for Ka, laid out or overflowing. The query now finds the face that draws
the character and takes that face's glyph for it by the same rule as
for the requested face.
When the requested font has no glyph for a character, the text draws it
from a fallback face, and text.glyphAlternates reports that glyph. For
a character that overflows its area, the query shaped the character
alone but kept only a glyph of the requested face, so it reported the
requested face's default glyph (0). It now takes the first glyph drawn
for the character when the requested face draws none, as it does for
laid-out text.
Ka, the halant, Ka again and the vowel sign I are shaped as one cluster:
the vowel sign, the first Ka's half form, then the second Ka's default
glyph. The glyph lookup looked for the character's default glyph across
the whole cluster before its alternates, so for the first Ka it found
the second Ka's default glyph instead of the half form. It again goes
through the cluster in order, and takes the first glyph that is the
character's default glyph or one of its alternates and is not the
default glyph of another character of the grapheme, so a vowel sign's
glyph that the font also lists as an alternate of the consonant is not
reported for the consonant.
text.glyphAlternates reported the default glyph as `chosen` for any
character that overflows its area. A character whose style substitutes
another glyph (`salt`, or `vert` in vertical type) was reported with a
glyph it is not drawn with. The query now shapes such a character alone,
with the rest of its grapheme in their own styles and in the text's
writing direction, by the same helper text.setGlyphAlternate uses.
The helper also shapes the character in the direction its paragraph
sets it in, from its bidi level, so a parenthesis set right to left is
reported with its mirrored glyph in both commands.
Ka with a nukta and the vowel sign I is shaped as one cluster: the vowel
sign's glyph, then the ligature of Ka and the nukta. The ligature is
neither Ka's default glyph nor an alternate, so the lookup found no
glyph for Ka: text.glyphAlternates reported the vowel sign's glyph and
text.setGlyphAlternate reported Ka's default glyph. When the cluster
holds neither, the lookup now takes the first glyph that is not drawn
for another character of the grapheme.
text.glyphAlternates reported the first glyph of the character's
cluster as the chosen glyph. A vowel sign drawn before its consonant
(Devanagari Ka with the vowel sign I) comes first in that cluster, so
the query reported the vowel sign's glyph for Ka, while
text.setGlyphAlternate reported Ka's own glyph. Both commands now take
the character's glyph by the same rule, and so does the check in
text.setGlyphAlternate that the chosen glyph is drawn.
A character that overflows its area is shaped with its grapheme, and
each character of the grapheme now keeps its own style there, so a
nukta of another size no longer makes a ligature with its consonant
that the laid-out text does not draw. A test font with a Devanagari
nukta ligature checks both.
text.setGlyphAlternate looked for the character's glyph only in a
cluster as long as the character. A character followed by a combining
mark is shaped in one cluster with the mark, so the lookup found
nothing: under a substituting feature such as salt, `gid: null`
reported the default glyph instead of the one drawn, and a request for
the default glyph changed nothing.
The lookup now also takes a cluster that holds the character's
grapheme, and in a cluster of several glyphs takes the character's
default glyph or one of its alternates. Text that overflows its area
is shaped with the whole grapheme.
Follows #5442.
- text.setGlyphAlternate keeps the default glyph as a choice when a
feature of the character's style (salt, a stylistic set) draws another
glyph for it, so the default glyph is drawn. A character that is not
laid out (overflowing text) is shaped alone in its style to decide.
gid: null reports the glyph the style draws.
- The Glyphs panel's alternates grid shows the default glyph first when
the font does not list it, so a chosen alternate can be put back.
- A test for a chosen character that starts a longer grapheme (f with a
combining acute), which is not split from its mark.
- The alternates tests in the engine hold the shared font lock while
they add fonts to the global font database.
text.startList read a negative or fractional `at` as the end of the
text, so it could remove a typed list marker there and start a list.
The `start` and `end` of text.editRange, text.getRange,
text.setRangeStyle and the type shortcut commands given an `id` read
such values as the start or end of the text.
These now return "`at` must be a whole number" (naming the key) and
change nothing. Omitted or null keys keep their defaults, and 2.0 is
read as 2.
While the Type tool edits a frame of threaded text, Select All selects
the text of every frame of the story, highlighted in each frame. The
tool edits the first frame that holds text.
- Typing, Delete and Cut replace the whole story as one undo step
(`text.editRange` takes `clear`, the other frames whose text goes).
- Copy and Cut take the whole story (`text.getRange {id, story: true}`).
- Fills and strokes, `type.step`, `appearance.copyFrom`, the Character,
Paragraph, OpenType and text style panels, the Tabs panel's tab
stops, the font menus and the Justification dialog act on every
frame's text (`text.setRangeStyle {id, story: true}` styles them in
one edit).
- A caret move, a click or a word or paragraph selection ends it.
Select All in unthreaded type is unchanged.
Docs: docs/mcp.md, docs/target-app-parity.md.
A stroke color given without strokeWidth left characters at weight 0, so
nothing was drawn. As paint.setStroke does, a painted stroke now gives a
run with no weight 1 pt, for the characters and for the label styles a
graph keeps; an explicit strokeWidth still sets the weight.
text.setStyle takes stroke (a color or "none") and strokeWidth (the character
stroke's weight in document points, as stroke.set gives it), with keepModel
for both colors. They apply to all the text or to the start..end range, and
to graph label styles, in the same undo step as the command's other keys.
A stroke key was ignored before.
The OpenType panel gets a Position menu after Figure, with Default
Position, Superscript, Subscript, Numerator and Denominator. `numr` and
`dnom` join the extra features (the existing bits keep their indices),
so they round-trip through the stored tags and reach the shaper.
text.setStyle and text.setRangeStyle take `featureChanges`, which turns
features on or off on top of each character's own features in one
command and keeps every tag the list does not name. The Figure menu,
the Position menu and the checkboxes send it, so on a selection whose
characters have different features each character keeps its other
features. Reset OpenType Features still sends `features`.
Catalog rows for Position:, Default Position, Numerator and
Denominator; one line each in docs/type-parity.md and
docs/target-app-parity.md.
Baseline shift now moves a character in the line's frame, outside
Character Rotation, so a positive shift moves a character up from its
line at any rotation, and the character turns about its shifted pen
position. The Touch Type tool splits a drag into its part along the line
(kerning) and its part up from the line (baseline shift) at every
rotation; before, within about 6 degrees of a quarter turn it dropped
the cross-line part.
Characters that already combine a rotation with a baseline shift are
drawn in a new position. Their stored values do not change.
- text: glyph transform order, PositionedGlyph::pivot (the turning
point), tab leaders in the same order
- tools: Touch Type splits drags against the line's up axis and measures
rotate-handle turns about the shifted pen position
- engine: text.touchType description; dy is up from the line
- docs: mcp.md Touch Type paragraph, target-app-parity.md progress line
The Character panel's All Caps, Underline and Strikethrough buttons, and
its menu's All Caps, Underline, Strikethrough, Proportional Metrics,
Tate-chu-yoko and No Break, read the first selected character's style.
With only some characters marked they could show on, and choosing one
turned the attribute off for every character. They now show on only
when every selected character has the attribute, and a click on a mixed
selection turns it on for all, as Small Caps and Standard Vertical Roman
Alignment do. type.underline (Cmd+Shift+U) underlines a mixed selection
the same way. The Character Alignment and Digits submenus check no item
while the selected characters differ.
text.setFormat ignored a value that was not true or false for underline,
strikethrough, allCaps, proportionalMetrics, tateChuYoko,
standardVerticalRoman, noBreak, hyphenate, hyphenateCapitalized and
romanHangingPunctuation. It now refuses it with "`<key>` must be true or
false" and changes nothing, as text.setRangeStyle does.
The reference app's shortcut list switches between its Type tools with
Shift and with Alt (Option). VectorCraft's Type tools had no such switch.
While no type is being edited, a press with Shift held makes new type
with the chosen tool's horizontal or vertical counterpart: Type and
Vertical Type, Area Type and Vertical Area Type, Type on a Path and
Vertical Type on a Path. A press with Alt held makes it with Type on a
Path in place of Area Type, or the reverse, in the same writing
direction. With both keys held, both switch. The keys held when the
press starts are the ones that count. The pointer shows the tool the
press acts as, and the chosen tool stays selected. While type is being
edited, Shift and Alt work as they did.
Docs: docs/target-app-parity.md.