Commit Graph
52 Commits
Author SHA1 Message Date
BFlat 7f9866f42a Merge pull request #6818 from dotCooCoo/feat/korean-line-break-anywhere
Type: Korean words break at spaces unless Korean Line Break Anywhere is on
2026-11-12 19:13:57 +07:00
dotCooCoo 1c3c3e81dd Type: Convert To Area Type and Convert To Point Type keep vertical type in place
Vertical type lays its lines out in line space and turns them a quarter turn
into text space. Convert To Area Type built the frame from line-space line
edges mixed with text-space bounds, so vertical point type moved (a 12 pt line
at (295.44, 40) went to (320.8, 28)). Convert To Point Type placed the new
origin in line space, so vertical area type moved down its column.

Both conversions now work out the frame or origin in line space and map it
through the layout's line transform. Horizontal type is unchanged.
2026-11-12 00:21:48 -08:00
dotCooCoo ff826ed1bd Type: Korean words break at spaces unless Korean Line Break Anywhere is on
Korean text broke after every Hangul syllable. A paragraph now has
Korean Line Break Anywhere, set from the Paragraph panel menu (with the
East Asian options), from the East Asian Composition Settings section of
Paragraph Style Options, and with `text.setFormat {koreanLineBreakAnywhere}`.

- Off, a word that contains Hangul (the characters between spaces) breaks
  only at its spaces and after East Asian punctuation, or where the word
  is wider than the line. Both composers follow it, and the word takes no
  hyphen. On, a line may break between any two syllables, as before.
- New type has it off. Paragraphs and text objects saved before the
  option read as on, so their lines do not move; the value is saved only
  when it is off.
- Paragraph styles carry it, Reset Panel turns it off, and the .ai writer
  reports it as lost for Korean paragraphs.

The help page: https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/set-line-breaks-korean-text.html

The command is described in docs/mcp.md, and docs/target-app-parity.md
lists the option.
2026-11-11 23:06:55 -08:00
BFlat 210e9ad7d0 Merge pull request #6632 from dotCooCoo/feat/ruler-guides-as-objects
Guides: ruler guides are document objects on their layers
2026-11-12 01:32:07 +07:00
dotCooCoo 5ac7d97d85 Guides: ruler guides are document objects on their layers
Ruler guides were a list kept beside the layers. Each guide is now an
object of its layer or sublayer, always directly in it and above the
layer's art. These behave as on main:

- Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All,
  Select All, the art bounds, clicks and marquees, and a layer's
  Pathfinder and Transform effects leave them out. The Layers panel
  lists them as guide rows of their layer, the highest guide.list
  index first.
- Deleting a layer deletes its guides, and merging layers keeps their
  guides in the merged layer.
- The guide commands number guides as guide.list does, in the order
  they were made.

New: each guide takes an id from the document's object ids. An object
command given a guide's id fails with an error, and node.move cannot
put a guide into a group or out of its layers. A guide made while an
opacity mask, a symbol or a pattern is edited moves to the top
visible, unlocked layer when editing ends.

File format: native files still save the guides in the document's
guides list, in every file version, so older builds read them. Each
entry may now also carry the guide's id, name, locked and hidden
flags; older builds ignore those fields. Opening a file puts each
saved guide on its layer (on the top visible, unlocked layer when the
file names none or a layer it does not have), and each saved guide
counts against the object limit for opening a file. A ruler guide node
that a file holds below a layer's art moves up to the layer's guides,
and one anywhere else (in a group or another object, beside the
layers, or in a symbol) is removed. Ruler guides read from an .ai file
become guide objects on the layer they were on, numbered in the order
the file lists them.
2026-11-11 09:52:28 -08:00
jmvergesandClaude Opus 5.5 9f80b4daae Type: aligned lines leave the tracking after their last character out (#6610)
A centred or right-aligned line of tracked type was measured with the
tracking after its last character, so it sat half that tracking
(centred) or the whole tracking (right-aligned) left of where the page
of users' .ai files draws it. That page aligns a line as wide as its
characters without that tracking.

Shaping now records the tracking each glyph's advance holds
(SGlyph::tracking), and a line that isn't justified leaves its last
character's out of the width it is aligned by. The last character is
the last one the line draws before a paragraph's end, a forced line
break or the spaces a wrap leaves; spaces or a tab typed before a
paragraph's end or a forced line break keep the letter's tracking, as
the page does. Forced line breaks and wraps aren't in the files: they
follow the rule so that turning a wrap into a paragraph or a forced line
break keeps the line in place. Hyphenated lines, lines whose last
character hangs, vertical and right-to-left lines and type on a path
keep it. The tracking stays in the glyph's advance, so the caret,
selections, line breaking and Fit Headline are unchanged.
LineInfo::trailing gives the amount. Right-aligned area type sets the
text after its last tab the same way.

What aligns by line widths follows. Convert To Area Type makes its
frame end where the lines are aligned, with room for the tracking, so
tracked type converted either way stays in place. Affinity import lines
up differently aligned paragraphs of artistic text by the aligned
widths. SVG import moves a centred or right-aligned letter-spaced line
back by its share of the last letter spacing, so it stays where a reader
draws it and SVG export, which anchors lines at the end of their
advances, reads back unchanged.

Centred and right-aligned tracked type lays out differently than before
in every document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 16:07:40 +00:00
dotCooCoo 07664847bd Type: text.setStyle refuses a leading with trailing text
`leading: "12abc"` set 12 pt leading and returned success, while `size`
and `tracking` in the same command refused trailing text. `leading` now
reads its value with `length_given`, so a string must be a whole number
or a length with a unit. The error message is unchanged.
2026-11-11 04:24:11 -08:00
dotCooCoo 67462cf049 Glyphs: gid null inside a ligature reports the ligature
When text.setGlyphAlternate leaves the text unchanged, it now reads
the glyph from the unchanged text. A character after the first in a run
that holds a glyph choice could be reported with its own glyph instead
of the ligature drawn for it, which text.glyphAlternates reports.
2026-11-11 01:54:02 -08:00
BFlat d56ec4830d Merge pull request #6554 from dotCooCoo/fix/set-glyph-alternate-drawn-gid
Glyphs: text.setGlyphAlternate with gid null reports the glyph drawn
2026-11-11 16:47:58 +07:00
dotCooCoo 1851f8769a Glyphs: text.setGlyphAlternate with gid null reports the glyph drawn
With no glyph choice left on the character, text.setGlyphAlternate
returned the font's default glyph for it: 0 for a character the font
lacks, which a fallback font draws, and the lowercase glyph for a
letter under All Caps. It now reports the glyph the character is drawn
with, as text.glyphAlternates does.
2026-11-11 01:05:16 -08:00
jmvergesandClaude Opus 5.5 df50bbf0f3 Type: tracking and manual kerning take the Horizontal Scale
In horizontal type, tracking and manual kerning are now thousandths of
the em as the Horizontal Scale draws it: at 50%, a tracking of 100 adds
0.05 em after each character. They were added at the full em while the
glyph advances took the scale, so narrow tracked type drifted from the
page of users' .ai files letter by letter. That page writes such type's
character spacing as the tracking itself in a text space as wide as the
scale, which PDF scales by it (ISO 32000-1 9.4.4). Vertical type keeps
the full em: its advances down the column don't all take the scale.

vectorcraft_text::spacing_scale gives the factor. Shaping, inline
graphics and the Optical pairs use it, and so do the conversions from
points back to thousandths of an em: the automatic kerning the Character
panel reads and Touch Type's limits (glyph_em), Combine Point Text, and
PDF import, whose tracking is now the file's Tc. Scale Text Attributes
now keeps tracked type where a horizontal stretch drew it.
SVG export writes letter-spacing in thousandths of the font size it
writes (the vertical scale), so uniformly scaled type keeps its spacing
in a viewer.

Type with both a Horizontal Scale other than 100% and tracking or manual
kerning lays out differently than before, in every document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 07:16:55 +00:00
dotCooCoo ea612dbf19 Type: use pathops' number and length helpers; refuse a number with trailing text
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.
2026-11-10 16:00:07 -08:00
dotCooCoo bf09404c33 Type: character formats, Ruby and Move Type Path Handle reject a param of the wrong type
`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.
2026-11-10 14:56:13 -08:00
dotCooCoo 7b8d5ba8f5 Type: Tab and Shift+Tab move a list item down and up a level
Squashed from #5037 onto main.
2026-11-09 03:39:05 +07:00
dotCooCoo 6fce6d1884 Type: text.startList names the bullet each typed trigger starts
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.
2026-11-08 23:50:35 +07:00
dotCooCoo 246b8ed8cd Type: typed +, # and > start lists with a hollow circle, square and dash
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.
2026-11-08 23:50:35 +07:00
dotCooCoo 8f833f6a1e Tests: Start Lists From Typed Markers on again, and its preferences checkbox
- 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.
2026-11-08 23:50:33 +07:00
dotCooCoo c1164f271f Type: Start Lists From Typed Markers turns typed list detection off
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.
2026-11-08 23:50:33 +07:00
dotCooCoo 18015f3631 Type: a selected story stays selected when its text leaves the edited frame
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.
2026-11-07 05:15:12 +07:00
dotCooCoo 22bb2ac8a6 Type: a selected story's highlight and typing follow its current thread
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.
2026-11-07 05:15:11 +07:00
dotCooCoo c969b7857d Type: an Eyedropper pick on a selected story styles every frame
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.
2026-11-07 05:15:11 +07:00
dotCooCoo f534b0061a Type: switching to the Eyedropper keeps a whole-story selection
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.
2026-11-07 05:15:10 +07:00
Hamza 728352269d Color Effects: Apply Fill to Stroke and Apply Stroke to Fill become add-on commands
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.
2026-11-05 21:46:43 +07:00
Hamza 0db0c7ddae Paint: Apply Fill to Stroke and Apply Stroke to Fill (#5530)
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.
2026-11-05 21:46:43 +07:00
dotCooCoo bf7ca194bb Type: Convert to Text leaves a paragraph that starts in a locked frame of the selected story
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.
2026-11-05 14:34:27 +07:00
dotCooCoo 2c1f3d7605 Type: text cleanup and Convert to Text act on every frame of a selected story
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.
2026-11-05 14:34:27 +07:00
dotCooCoo 96ca33b0ef Tests: the Devanagari fallback-glyph test passes when the fallback face substitutes the vowel sign
`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
2026-11-05 14:26:32 +07:00
dotCooCoo f0335d2f17 Glyphs: a fallback face's consonant glyph is reported, not its vowel sign's
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.
2026-11-05 08:37:18 +07:00
dotCooCoo 8118b59d19 Glyphs: the Glyphs query reports the fallback glyph of an overflowing character
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.
2026-11-05 07:56:26 +07:00
dotCooCoo e33be0eea9 Glyphs: a character's glyph is the first of its glyphs in its cluster
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.
2026-11-05 07:30:43 +07:00
dotCooCoo 8f8f71f803 Glyphs: the Glyphs query reports the glyph of an overflowing character
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.
2026-11-05 06:55:55 +07:00
dotCooCoo 92ecb4a6b0 Glyphs: a consonant's ligature in a cluster with its vowel sign is its glyph
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.
2026-11-05 05:20:03 +07:00
dotCooCoo 67140d5e54 Glyphs: the Glyphs query reports a consonant's glyph, not its vowel sign's
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.
2026-11-05 04:11:37 +07:00
dotCooCoo 440b3b7444 Glyphs: overflowing vertical type keeps a chosen default glyph
Squashed from #5676 onto main.
2026-11-05 02:23:42 +07:00
dotCooCoo b0d1ca4419 Glyphs: a character with a combining mark reports the glyph it is drawn with
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.
2026-11-05 01:56:16 +07:00
dotCooCoo fae49d31fd Glyphs: the default glyph can be chosen back under a substituting feature
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.
2026-11-04 23:58:54 +07:00
dotCooCoo 88b5170622 Glyphs panel: alternates for the current text selection
Squashed from #5442 onto main.
2026-11-04 22:29:53 +07:00
dotCooCoo 595db10900 Type: refuse negative or fractional byte offsets in text commands
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.
2026-11-04 16:55:22 +07:00
dotCooCoo 92640179e5 Type: Select All in a threaded frame selects the whole story
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.
2026-11-04 15:13:20 +07:00
dotCooCoo a0a4633c26 Restore Type editing after a dropped press 2026-11-04 10:25:57 +07:00
dotCooCoo cdae392fe0 Type: guard Touch Type movement at direction boundaries 2026-11-03 23:57:53 +07:00
dotCooCoo dfc25c85e4 Type: move right-to-left characters with Touch Type 2026-11-03 23:57:53 +07:00
dotCooCoo e1ea0ecad1 Type: the caret, a click and the arrow keys reach the characters inside a ligature
Squashed from #5225 onto main.
2026-11-03 16:38:36 +07:00
dotCooCoo 58e6ce9f02 Type: a text.setStyle stroke gives unstroked characters 1 pt
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.
2026-11-03 16:03:38 +07:00
dotCooCoo b1dc20b811 Type: text.setStyle sets the text's stroke as well as its fill
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.
2026-11-03 16:03:38 +07:00
dotCooCoo 3cb252198a OpenType: the panel's Position menu sets superscript, subscript, numerator or denominator glyphs
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.
2026-11-02 12:04:15 +07:00
dotCooCoo a6c64e2cf2 Type: choosing Optical kerning clears manual pair values, as Auto does 2026-11-02 12:04:15 +07:00
dotCooCoo 5716a996ab Type: Touch Type moves a character rotated near 90 degrees across its line
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
2026-11-01 11:08:57 +07:00
dotCooCoo 94ef9b87c2 Type: Character panel toggles show off for a mixed selection, and text.setFormat checks on/off values
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.
2026-11-01 11:08:56 +07:00
dotCooCoo 9896f633da Type: Kerning has an Optical option that spaces characters by their shapes
Squashed from #4825 onto main.
2026-11-01 01:22:04 +07:00