diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index c61aa82f6..8637ea1cc 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -1607,7 +1607,9 @@ fn hyphen_breaks(text: &str, g: &[SGlyph], ws: usize, we: usize, opts: &Hyphenat } /// Break candidates for the every-line composer: hyphenation points under `hyphenate` (the -/// paragraph's Hyphenation options) when it is given. +/// paragraph's Hyphenation options) when it is given. No break comes before a space: a line that +/// may end after a dash, a Thai word or a space ends after the spaces that follow it, as in +/// [`break_line`]. fn candidates(text: &str, g: &[SGlyph], hyphenate: Option<&HyphenationOptions>, kinsoku: Kinsoku) -> Vec { let mut v = vec![]; let mut ws = 0; @@ -1622,7 +1624,7 @@ fn candidates(text: &str, g: &[SGlyph], hyphenate: Option<&HyphenationOptions>, } } let hy = if gl.is_soft_hyphen() { hyphen_glyph(gl).adv } else { 0.0 }; - if j + 1 < g.len() && g[j + 1].byte != gl.byte && kinsoku_allows(g, j, kinsoku) { + if j + 1 < g.len() && g[j + 1].byte != gl.byte && !g[j + 1].is_space() && kinsoku_allows(g, j, kinsoku) { v.push(Breakpoint { end: j + 1, hyphen: hy }); } ws = j + 1; @@ -1643,8 +1645,9 @@ fn candidates(text: &str, g: &[SGlyph], hyphenate: Option<&HyphenationOptions>, /// Paragraphs longer than this (glyphs) are always broken line by line. const MAX_COMPOSE_GLYPHS: usize = 200_000; -/// Every-line composition of paragraph glyphs `sg` if applicable (area text with uniform line -/// metrics, justified or ragged); `None` falls back to the greedy single-line composer. +/// Every-line composition of paragraph glyphs `sg` if applicable (area text, justified or ragged, +/// with uniform line metrics or in rectangular cells without text wrap); `None` falls back to the +/// greedy single-line composer. fn compose_para(cx: &Ctx<'_>, sg: &[SGlyph], flex: &[Flex], para: &ParaStyle, pen: &Pen<'_>, rtl: bool) -> Option> { let ragged = matches!(para.justify, Justify::Auto | Justify::Left | Justify::Center | Justify::Right); let composer = cx.opts.composer.unwrap_or(para.composer); @@ -1658,10 +1661,16 @@ fn compose_para(cx: &Ctx<'_>, sg: &[SGlyph], flex: &[Flex], para: &ParaStyle, pe return None; } let m = Metrics::of(&sg[0]); - if sg.iter().any(|g| Metrics::of(g) != m) { + // Line widths are independent of the breaks when every line has the same metrics, or when + // a line's width does not depend on how far down the frame it is set: rectangular cells + // without text wrap. Then a paragraph whose characters differ in line metrics (type size, + // font, leading, vertical scale, an inline graphic taller than the text) is composed too. In + // other shapes, or with text wrap, a taller line can move the lines below it to where the + // frame is narrower, so such a paragraph is broken line by line. + let plain = pen.regions.is_some_and(|r| r.iter().all(|r| (r.rect || r.polys.is_empty()) && r.wraps.is_empty())); + if !plain && sg.iter().any(|g| Metrics::of(g) != m) { return None; } - // Line widths are independent of the breaks when every line has the same metrics. let total: f64 = sg.iter().map(|g| g.adv).sum(); let mut sim = pen.clone(); let mut widths = vec![]; diff --git a/crates/text/src/tests_combos.rs b/crates/text/src/tests_combos.rs index 0453058b4..d61dccc51 100644 --- a/crates/text/src/tests_combos.rs +++ b/crates/text/src/tests_combos.rs @@ -294,6 +294,126 @@ fn composer_x_inline_graphics() { } } +#[test] +fn composer_x_mixed_sizes() { + // A 24 pt word among 12 pt text: in a rectangle the every-line composer breaks the paragraph as + // it breaks uniform 12 pt text in which that word is set at 200% horizontal scale, as wide as + // at 24 pt, and not as the single-line composer does. + let st = sans(12.0); + let runs = |x: CharStyle| vec![run("Destroy all creatures with toughness ", st.clone()), run("X", x), run(" or less.", st.clone())]; + let big = sans(24.0); + let wide = CharStyle { h_scale: 200.0, ..sans(12.0) }; + let mut probe = area(runs(big.clone()), Rect::new(0.0, 0.0, 10_000.0, 100.0)); + probe.runs[2] = run(" or", st.clone()); + let w = layout(db(), &probe).lines[0].x1 + 2.0; + let breaks = |x: &CharStyle, composer: Composer| { + let mut t = area(runs(x.clone()), Rect::new(0.0, 0.0, w, 200.0)); + t.para.composer = composer; + line_texts(&t, &layout(db(), &t)) + }; + let greedy = ["Destroy all creatures with toughness X or", "less."]; + let every = ["Destroy all creatures with toughness X", "or less."]; + assert_eq!(breaks(&wide, Composer::EveryLine), every, "uniform sizes"); + assert_eq!(breaks(&wide, Composer::SingleLine), greedy, "uniform sizes"); + assert_eq!(breaks(&big, Composer::EveryLine), every, "mixed sizes"); + assert_eq!(breaks(&big, Composer::SingleLine), greedy, "mixed sizes"); + // The same in a rectangle split into two columns, one line in each. + let mut t = area(runs(big.clone()), Rect::new(0.0, 0.0, 2.0 * w + 12.0, 34.0)); + (t.area.columns, t.area.gutter) = (2, 12.0); + t.para.composer = Composer::EveryLine; + let l = layout(db(), &t); + assert_eq!(line_texts(&t, &l), every, "two columns"); + assert!(!l.overflow && l.lines[1].x0 > w, "{:?}", l.lines); + // The 24 pt word's line is set at its own size. + let mut t = area(runs(big.clone()), Rect::new(0.0, 0.0, w, 200.0)); + t.para.composer = Composer::EveryLine; + let l = layout(db(), &t); + let x = l.glyphs.iter().find(|g| t.plain_text()[g.byte..].starts_with('X')).unwrap(); + assert_eq!(x.line, 0); + assert!(l.lines[0].ascent > l.lines[1].ascent + 1.0, "{:?}", l.lines); + // A rectangle that wraps around an object, and a frame that isn't a rectangle (a corner cut), + // both far below the text, break mixed sizes line by line and still break uniform sizes with + // the every-line composer. + let below = PathData::from_bezpath(&Rect::new(0.0, 180.0, 20.0, 190.0).to_path(0.1)); + for (x, want) in [(&wide, every), (&big, greedy)] { + let mut t = area(runs(x.clone()), Rect::new(0.0, 0.0, w, 200.0)); + t.wrap = vec![vectorcraft_doc::WrapShape { path: below.clone(), wrap: Default::default() }]; + t.para.composer = Composer::EveryLine; + assert_eq!(line_texts(&t, &layout(db(), &t)), want, "wrap, {}", x.size); + } + let cut = kurbo::BezPath::from_vec(vec![ + kurbo::PathEl::MoveTo((0.0, 0.0).into()), + kurbo::PathEl::LineTo((w, 0.0).into()), + kurbo::PathEl::LineTo((w, 190.0).into()), + kurbo::PathEl::LineTo((w - 10.0, 200.0).into()), + kurbo::PathEl::LineTo((0.0, 200.0).into()), + kurbo::PathEl::ClosePath, + ]); + for (x, want) in [(&wide, every), (&big, greedy)] { + let mut t = area(runs(x.clone()), Rect::ZERO); + t.kind = TextKind::Area { frame: PathData::from_bezpath(&cut) }; + t.para.composer = Composer::EveryLine; + assert_eq!(line_texts(&t, &layout(db(), &t)), want, "cut corner, {}", x.size); + } +} + +#[test] +fn composer_x_mixed_line_metrics() { + // In a rectangle the every-line composer also breaks a paragraph whose characters differ in + // other line metrics: vertical scale, leading, font, and an inline graphic raised above the text. + let st = sans(12.0); + let wide = CharStyle { h_scale: 200.0, ..sans(12.0) }; + let xs = [ + ("vertical scale", run("X", CharStyle { v_scale: 200.0, ..wide.clone() })), + ("leading", run("X", CharStyle { leading: Some(30.0), ..wide.clone() })), + ("font", run("X", serif(12.0, 0.0))), + ("inline graphic", sym(0.7, 20.0, st.clone())), + ]; + let mut wrong = vec![]; + for (name, x) in xs { + let runs = |end: &str| vec![run("Destroy all creatures with toughness ", st.clone()), x.clone(), run(end, st.clone())]; + let w = layout(db(), &area(runs(" or"), Rect::new(0.0, 0.0, 10_000.0, 100.0))).lines[0].x1 + 2.0; + let greedy = [format!("Destroy all creatures with toughness {} or", x.text), "less.".to_string()]; + let every = [format!("Destroy all creatures with toughness {}", x.text), "or less.".to_string()]; + for (composer, want) in [(Composer::EveryLine, every), (Composer::SingleLine, greedy)] { + let mut t = area(runs(" or less."), Rect::new(0.0, 0.0, w, 200.0)); + t.para.composer = composer; + let got = line_texts(&t, &layout(db(), &t)); + if got != want { + wrong.push((name, composer, got)); + } + } + } + assert!(wrong.is_empty(), "{wrong:?}"); +} + +#[test] +fn composer_x_lines_start_after_the_spaces() { + // A line may end after a dash or between two spaces, but the every-line composer breaks after + // the spaces that follow, as the single-line composer does: no line starts with a space, in + // uniform 12 pt text and with a 24 pt word in it. + let st = sans(12.0); + let runs = |x: CharStyle| { + vec![run("Destroy all creatures \u{2014} with toughness ", st.clone()), run("X", x), run(" or less \u{2014} then draw a card.", st.clone())] + }; + let mut wrong = vec![]; + for x in [sans(12.0), sans(24.0)] { + for justify in [Justify::Left, Justify::JustifyLeft] { + for w in (40..400).step_by(4) { + let mut t = area(runs(x.clone()), Rect::new(0.0, 0.0, f64::from(w), 2000.0)); + t.para.composer = Composer::EveryLine; + t.para.justify = justify; + let text = t.plain_text(); + let l = layout(db(), &t); + if l.lines.iter().any(|li| text[li.start..].starts_with(' ')) { + wrong.push((x.size, justify, w, line_texts(&t, &l))); + } + } + } + } + assert!(wrong.is_empty(), "{wrong:?}"); +} + const INLINE: char = vectorcraft_doc::text::INLINE_CHAR; // ---------- vertical alignment × … ---------- diff --git a/docs/mcp.md b/docs/mcp.md index bd5ce726c..edf3d6a1d 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4441,7 +4441,9 @@ when it is Single-line). Every-line, the default, picks the breaks of the whole justified text gets even word spacing, ragged text (left, centre or right aligned) an even rag, so it may move a word that fits to the next line to avoid a lone short word on the last line or a full line next to a short one. Single-line fills each line as far as it goes. Point type and type on a path have no line width, so both composers -give the same result there; paragraphs that mix type sizes fall back to Single-line. +give the same result there. A paragraph whose characters differ in line metrics (type size, font, leading or vertical +scale, or an inline graphic taller than the text) is composed with Every-line in rectangular area type, rows and +columns included; in area type of another shape, or with text wrap, it falls back to Single-line. ```json {"name":"run_command","arguments":{"command":"text.setFormat","params":{"ids":[7],"composer":"singleLine"}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index ff31ac2ed..3d51ba493 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -338,7 +338,7 @@ about 1–1.5 agent-hours each, so the other rows (estimated on the older scale) | Live vector effects | 5 | 85% | Outline Object, Pathfinder Hard/Soft Mix and Trap, SVG Filters, the Transform effect's Transform Objects/Patterns and Scale Strokes & Effects options | 6–10 | | Raster effects (Effect Gallery, Document Raster Effects Settings) | 4 | 35% | ~36 Photoshop-style filters (Artistic, Brush Strokes, Sketch, Stylize), Texturizer's Load Texture and the Effect Gallery; Document Raster Effects Settings, raster effects in PDF, the pixel-filter pipeline, Radial Blur, Smart Blur (Normal, Edge Only and Overlay Edge), Unsharp Mask, Glowing Edges, Distort (Diffuse Glow, Glass, Ocean Ripple), Pixelate (Color Halftone, Crystallize, Mezzotint, Pointillize), Texture (Craquelure, Grain, Mosaic Tiles, Patchwork, Stained Glass, Texturizer) and Video (De-Interlace, NTSC Colors) are done | 8–14 | | 3D and Materials | 4 | ~25% | Initial Revolve, Extrude & Bevel, Inflate and Rotate, with simple lighting; materials, light presets, shadows, ray tracing, 3D Classic and more complete visibility remain | 25–45 | -| Type core | 9 | 80% | Middle Eastern features beyond bidi layout and Paragraph Direction (Character Direction, kashidas, the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); Hyphenation… and Justification… options (Every-line Composer for justified and ragged text, per object, is done; it is tuned by eye, not yet compared side by side with Illustrator; mixed type sizes in a paragraph still break single-line), Optical Margin Alignment, paragraph spacing limit for vertically justified area type, hidden characters, paragraph attributes in the Type tool's copy and paste (the characters keep their styles, the paragraphs take the destination's), mixed-value display in the Paragraph panel for a selection across differing paragraphs, partial selection of type-area anchors by marquee, optional font-size preservation during group scaling (scaled character controls fixed in #1034) | 14–19 | +| Type core | 9 | 80% | Middle Eastern features beyond bidi layout and Paragraph Direction (Character Direction, kashidas, the Middle Eastern composers, editable bidirectional SVG text, a split caret where directions meet); Hyphenation… and Justification… options (Every-line Composer for justified and ragged text, per object, is done; it is tuned by eye, not yet compared side by side with Illustrator; mixed type sizes in a paragraph still break single-line in non-rectangular area type and with text wrap), Optical Margin Alignment, paragraph spacing limit for vertically justified area type, hidden characters, paragraph attributes in the Type tool's copy and paste (the characters keep their styles, the paragraphs take the destination's), mixed-value display in the Paragraph panel for a selection across differing paragraphs, partial selection of type-area anchors by marquee, optional font-size preservation during group scaling (scaled character controls fixed in #1034) | 14–19 | | Type advanced | 4 | 50% | CJK composition for vertical type (ruby, mojikumi beyond Line-end Punctuation Half Width — the other sets (and their paragraph-start bracket and indent rules), custom kinsoku sets (beyond Hard, Soft and None) and the Mojikumi Settings dialog —, manual tate-chu-yoko), variable font axis sliders (named instances are styles), tab leaders, spell check (open dictionary), Touch Type, Retype; vertical point/area/path type and Type Orientation have initial support (beyond parity, not scored: inline symbols in text) | 20–28 | | Symbols, blends, envelopes, Repeat, perspective | 5 | 90% | symbol libraries (original), dynamic symbols, envelope Anti-Alias/Preserve Shape output and warp point editing, brush interpolation in blends (M8 fidelity pass done); Symbol Options › Registration and Export Type done (9-slice guides remain) | 4–7 | | Image Trace, graphs, image tools | 3 | 72% | graph Column designs (Graph Design, Marker designs and series colours set with Group Selection are done), Create Object Mosaic, per-feature stroke/fill choice and varying widths in Image Trace's Create Strokes (Vector Halftone and the Image Trace View modes, Palette and Create Strokes are done) | 6–10 | @@ -648,6 +648,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Free Distort's dialog (Effect › Distort & Transform › Free Distort…) shows a box in the proportions of the first selected object's bounds with a handle on each corner. Dragging a handle moves that corner. A handle focused by a click or Tab moves 1% of the box with each arrow key, and 10% with Shift. Reset puts the four corners back on the box. The preview and OK run `effect.apply`, or `effect.setParams` when the dialog edits an applied effect, and the Appearance panel's inline editor opens the dialog with Edit…. Behavior reference: Illustrator CC Help (2014, [PDF](https://help.adobe.com/archive/en/illustrator/cc/2014/illustrator_reference.pdf)), page 285 (PDF page 291). - **Kerning across style runs:** characters that differ only in fill, stroke, underline, strikethrough, overprint, character style name, leading, baseline shift, rotation, Character Alignment, kerning, or tracking that keeps or drops ligatures alike are shaped together, so restyling one character keeps the font's kerning pairs with its neighbors. A character with manual kerning that follows one with automatic kerning keeps the font's kerning with it (unless All Caps or Small Caps sets it as two letters, as ß becomes SS), and its value replaces the font's kerning after it. A ligature does not form across a change of style, and a change of font, size or OpenType features still shapes the characters apart. Behavior reference: [Line and character spacing](https://web.archive.org/web/20260916050333/https://helpx.adobe.com/illustrator/using/line-character-spacing.html) (public documentation). Tests: `crates/text/src/tests_typo.rs`, `crates/text/src/tests_bidi.rs`. - **Type:** Character size, leading, baseline shift and horizontal-scale controls account for object/group transforms (#1034): exact document-point sizes, mixed-value display and keyboard increments, range edits, resettable horizontal stretch, undo/redo and native save/reopen (`tests_typescale`, Character panel tests). Point size is the transformed em-height axis, independent of glyph ink bounds; affine rotation, reflection and shear are retained. Group scaling still scales text; preserving font sizes is a separate enhancement. Behaviour reference: [Transform text](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/transform-text.html) (public documentation). Text Wrap, Type on a Path effects (Rainbow/Skew/3D Ribbon/Stair Step/Gravity) and Options (Flip, Align to Path, Spacing; OK keeps the type where it is), type on a path's start, centre and end brackets (Selection and Direct Selection drag them to set where the type runs, slide it along its path and, across the path, flip it: #429), Character and Paragraph Styles (override-preserving redefine), paragraph attributes per paragraph (alignment, indents, spacing, hyphenation, hanging punctuation, tab stops, paragraph style, direction, mojikumi and leading model; the Paragraph, Tabs and Paragraph Styles panels act on the paragraphs the Type tool's selection or caret touches, Return continues the paragraph's style, threads and SVG keep them), Area Type Options (rows/columns/inset/first baseline, vertical Align Top/Center/Bottom/Justify per row/column, reflowing non-rectangular frames and text wrap), threaded text across any closed shapes, Fit Headline, the Every-line Composer for justified and ragged text (paragraph-wide breaks for an even rag, Single-line per object from the Paragraph panel menu or `text.setFormat {composer}`). Area type resizes like Illustrator's type areas: a bounding-box handle drag (Selection tool) or Area Type Options Width/Height size the frame and the text reflows at its size (threads too); Direct Selection drags a frame corner or edge (`text.reshapeArea`). Point type still scales. Auto Size: the frame height follows the text after every edit, in the same undo step (rectangular frames of horizontal type in one row; columns get the least height that holds them; a height set by hand or threading turns it off), and the "Auto Size New Area Type" preference gives it to new area type. Shrink Text to Fit (beyond Illustrator): overflowing area type scales its sizes, leading and baseline shifts down by the largest factor (in 0.1 pt steps of the first run, down to a minimum percentage) that fits, at layout time so every export sees it. `text.areaOptions` and `document.inspect` report overflow and the shrink factor. The Selection tool shows the type widget beside selected type's bounding box (hollow on point type, filled on area type); double-clicking it converts between them (Type › Convert To Area Type / Convert To Point Type), keeping the text and its styles in place. Variable fonts list every named instance as a style of their family (Figtree Light to Black and their italics, Bahnschrift, Segoe UI Variable), and the instance chosen is shaped, drawn, outlined and measured at its axis settings; PDF and SVG embed it as that instance. The Character panel's Variable Font sliders set each axis (see Fonts). Beyond Illustrator: inline graphics (`text.insertInline`, Type ▸ Insert Inline Symbol) set a document symbol in a run of type as one character that flows, wraps and justifies like a glyph (InDesign-style inline objects, e.g. mana symbols in card rules text): sized to a multiple of the font size, centred on the cap height, opening up tall lines, following Redefine Symbol; a character of its run's size for Character Alignment and Top-to-Top leading, placed by the bidi reordering in right-to-left text (a neutral U+FFFC); drawn on the canvas, kept as live text plus a `` in SVG and as vector art in PDF; Backspace removes it whole. Hebrew and Arabic are laid out right to left with the Unicode bidirectional algorithm (embedded Latin and numbers keep their direction, lines put in visual order after wrapping) in point, area and path type, with caret, selection and arrow keys in visual order; each paragraph's direction comes from its first strong character unless Paragraph Direction sets it (Paragraph panel with Show Indic Options, `text.setFormat {direction}`), and new type aligns to the start of its direction. SVG export outlines right-to-left text. Arabic typefaces: builds made with a craft-fonts checkout that holds its Arabic families (Noto Sans Arabic, the Arabic fallback, plus Cairo, Tajawal, Almarai, IBM Plex Sans Arabic, Noto Kufi Arabic, Amiri, Alexandria, Fustat, Reem Kufi, Aref Ruqaa and Gulzar once craft-fonts adds them, storytold/craft-fonts#11; releases get them when their craft-fonts pin moves) list them in the font menus on desktop; the web build fetches them from beside the wasm (Noto Sans Arabic before start, so Arabic never shows as boxes, the rest in the background). Font Size and Leading are combo boxes in the Character and Properties panels (Font Size in the Control bar too, beside the Character link, font family and style): type, step with ↑/↓ or the spinner, scrub the label, or pick a preset (6–72 pt, shown in the type unit; Leading also Auto), each pick one undo step; Kerning and Tracking list -100…200; a size, leading, kerning or tracking the selected text doesn't share shows blank (#458). Type › Bold and Italic (beyond Illustrator, as page layout apps have them; Cmd+Shift+B / Cmd+Shift+I while the Type tool edits text) switch the selected type to its family's own Bold or Italic face and back, keeping its width (a Narrow face stays Narrow); a family without that face says so (#724). The commands that make or restyle type (and the MCP `add_text` tool) say in their result when area type does not fit its frame, and `add_text` takes Auto Size and Shrink Text to Fit (#1500, fixes #1471). Cmd+Shift+C (Align center) works on Windows and Linux too (#1940). A double-click on area type's bottom middle handle turns on Auto Size (#1940). Print and Print Preview warn about the area type and type on a path that prints without the text that doesn't fit, and give their ids. A tab stop in the Tabs panel drags from where the press landed, a stop just added included (#1487, fixes #1458). Area type in a non-rectangular shape moves a word too wide for the top lines down to the first line that holds it instead of breaking it (#1617, fixes #1615). Stylistic sets (ss01–ss20), stylistic and titling alternates, proportional and lining figures, and superscript and subscript glyphs can be turned on as OpenType features of a character style (`text.setStyle {features}`), and the OpenType panel keeps them (#1611, fixes #1607). Escape while typing ends the edit, keeps the type selected and switches to the Selection tool (#1802, #1686, #1656). Select › Same matches type on its characters' fill and stroke (#1746, #1737). `text.create`, `text.createInPath` and the MCP `add_text` tool take `justify`, so point type can be centred on or end at its point (#1752, #1748). + The Every-line Composer composes a paragraph whose characters differ in line metrics (type size, font, leading or vertical scale, or an inline graphic taller than the text) in rectangular area type, rows and columns included. In area type of another shape, or with text wrap, such a paragraph is broken with the Single-line Composer. The Character panel's Small Caps, Superscript and Subscript buttons and the panel menu's items of the same names turn small caps and the superscript or subscript position on and off for the selected type, or for the text the Type tool has selected (`text.setFormat` or `text.setRangeStyle` `{smallCaps, position}`), in Document Setup's proportions. Turning Superscript on turns Subscript off, and turning Subscript on turns Superscript off. A button or item shows as on when the first selected character (at a caret, the character before it) has its attribute. - **Paragraph alignment:** the shared Paragraph/Properties alignment row leaves every button unselected when the paragraphs it acts on differ. Auto resolves per paragraph; choosing an alignment uses the existing command. Headless paragraph_mixed_alignment_* regressions exercise mixed → Center → Undo, caret/uniform states, Unicode range boundaries, Auto/direction, split runs and grouped text. These regressions and full integration CI pass. Original captures confirm mixed → Center → Undo in the existing alignment-button layout. The [public Paragraph panel guide](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/paragraph-panel-overview.html) lists the seven controls, but does not specify mixed-state rendering. Other paragraph fields remain outside this change; scores and hours unchanged. For Auto Size area type in columns, the bisection for the frame's height runs on the lines of the one-column layout, and its result is checked with at most two layouts of the columns. When the text wraps around objects or does not fit the one-column layout, or when the check fails, the columns are laid out at every step of the bisection, as before. Typing at the start of a story of 50,000 characters in 2 columns took 1,670 ms a keystroke and now takes 292 ms (release build, `auto_height_in_columns_typing_timing`).