mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 14:41:52 +00:00
Merge pull request #7403 from dotCooCoo/feat/colr-gradient-repeat
Type: COLR glyphs with repeating or reflecting gradients draw as vector art
This commit is contained in:
+39
-13
@@ -12,7 +12,7 @@ use skrifa::outline::DrawSettings;
|
||||
use skrifa::raw::TableProvider;
|
||||
use skrifa::raw::types::BoundingBox;
|
||||
use skrifa::{GlyphId, MetadataProvider};
|
||||
use vectorcraft_doc::color::{BlendMode, Color, Gradient, GradientGeom, GradientKind, GradientPaint, GradientStop, Paint};
|
||||
use vectorcraft_doc::color::{BlendMode, Color, Gradient, GradientGeom, GradientKind, GradientPaint, GradientSpread, GradientStop, Paint};
|
||||
use vectorcraft_doc::{Appearance, FillLayer, ImageBlob, Node, NodeId, NodeKind};
|
||||
use vectorcraft_geom::PathData;
|
||||
use vello_cpu::peniko;
|
||||
@@ -279,8 +279,10 @@ impl<'a> Tree<'a> {
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let gradient = |kind, stops: Vec<GradientStop>, geom| {
|
||||
let mut paint = GradientPaint::new(Gradient { kind, stops, ..Gradient::default() });
|
||||
let gradient = |kind, stops: Vec<GradientStop>, spread, geom| {
|
||||
// COLR repeats the interval from the first stop to the last, and one run of the
|
||||
// stops spans the gradient's vector.
|
||||
let mut paint = GradientPaint::new(Gradient { kind, stops, spread, ..Gradient::default() });
|
||||
paint.geom = Some(geom);
|
||||
Paint::Gradient(Box::new(paint))
|
||||
};
|
||||
@@ -290,22 +292,22 @@ impl<'a> Tree<'a> {
|
||||
Some((Paint::solid(c), a))
|
||||
}
|
||||
Brush::LinearGradient { p0, p1, color_stops, extend } => {
|
||||
if *extend != Extend::Pad {
|
||||
self.unsupported.get_or_insert("a repeating or reflecting gradient");
|
||||
let spread = spread(*extend);
|
||||
let (p0, p1) = (point(*p0), point(*p1));
|
||||
let stops = stops(color_stops, self);
|
||||
// Without a stop, the others would repeat over a different interval.
|
||||
if !spread.is_pad() && stops.len() < color_stops.len() {
|
||||
self.unsupported.get_or_insert("a repeating or reflecting gradient with a stop missing from the palette");
|
||||
return None;
|
||||
}
|
||||
let (p0, p1) = (point(*p0), point(*p1));
|
||||
let (stops, first, last) = normalized(stops(color_stops, self))?;
|
||||
let (stops, first, last) = normalized(stops)?;
|
||||
// Stops past 0 and 1 move the ends instead.
|
||||
let mut geom = GradientGeom { start: p0.lerp(p1, f64::from(first)), end: p0.lerp(p1, f64::from(last)), aspect: 1.0, focal: None };
|
||||
geom.transform(xf, GradientKind::Linear);
|
||||
Some((gradient(GradientKind::Linear, stops, geom), 1.0))
|
||||
Some((gradient(GradientKind::Linear, stops, spread, geom), 1.0))
|
||||
}
|
||||
Brush::RadialGradient { c0, r0, c1, r1, color_stops, extend } => {
|
||||
if *extend != Extend::Pad {
|
||||
self.unsupported.get_or_insert("a repeating or reflecting gradient");
|
||||
return None;
|
||||
}
|
||||
let spread = spread(*extend);
|
||||
let (c0, c1, r0, r1) = (point(*c0), point(*c1), f64::from(*r0), f64::from(*r1));
|
||||
let concentric = (c0 - c1).hypot() < 1e-6;
|
||||
if (r0 > 1e-6 && !concentric) || r1 <= r0 || (c0 - c1).hypot() >= r1 {
|
||||
@@ -317,13 +319,27 @@ impl<'a> Tree<'a> {
|
||||
self.unsupported.get_or_insert("a radial gradient with stops past its circles");
|
||||
return None;
|
||||
}
|
||||
if !spread.is_pad() {
|
||||
if stops.len() < color_stops.len() {
|
||||
self.unsupported.get_or_insert("a repeating or reflecting gradient with a stop missing from the palette");
|
||||
return None;
|
||||
}
|
||||
// A run of the stops is the whole way from the center to the circle: one that
|
||||
// starts at a circle (a first stop past the center comes as one) repeats over
|
||||
// less of it. The normalized ends can be a rounding error off 0 and 1.
|
||||
let (lo, hi) = stops.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), s| (lo.min(s.offset), hi.max(s.offset)));
|
||||
if r0.abs() > 1e-6 || lo.abs() > 1e-4 || (hi - 1.0).abs() > 1e-4 {
|
||||
self.unsupported.get_or_insert("a repeating or reflecting radial gradient that doesn't start at its center");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// A start circle around the centre starts the stops further out.
|
||||
for s in &mut stops {
|
||||
s.offset = ((r0 + f64::from(s.offset) * (r1 - r0)) / r1) as f32;
|
||||
}
|
||||
let mut geom = GradientGeom { start: c1, end: c1 + Vec2::new(r1, 0.0), aspect: 1.0, focal: (!concentric).then_some(c0) };
|
||||
geom.transform(xf, GradientKind::Radial);
|
||||
Some((gradient(GradientKind::Radial, stops, geom), 1.0))
|
||||
Some((gradient(GradientKind::Radial, stops, spread, geom), 1.0))
|
||||
}
|
||||
Brush::SweepGradient { .. } => {
|
||||
self.unsupported.get_or_insert("a sweep gradient");
|
||||
@@ -345,6 +361,16 @@ impl<'a> Tree<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A COLR extend mode as the gradient's spread; an unknown one pads, as the COLR specification
|
||||
/// says.
|
||||
fn spread(extend: Extend) -> GradientSpread {
|
||||
match extend {
|
||||
Extend::Repeat => GradientSpread::Repeat,
|
||||
Extend::Reflect => GradientSpread::Reflect,
|
||||
_ => GradientSpread::Pad,
|
||||
}
|
||||
}
|
||||
|
||||
/// `stops` sorted, from 0 to 1 (as the gradient's ends then move), and where the first and last
|
||||
/// were; `None` without stops.
|
||||
fn normalized(mut stops: Vec<GradientStop>) -> Option<(Vec<GradientStop>, f32, f32)> {
|
||||
|
||||
@@ -326,12 +326,8 @@ fn paints_documents_lack_make_the_glyph_an_image() {
|
||||
let line = |extend| ColorLine::new(extend, 2, vec![ColorStop::new(F2Dot14::from_f32(0.0), 0, one), ColorStop::new(one, 1, one)]);
|
||||
let w = FWord::new;
|
||||
type GlyphPaint<'a> = Box<dyn Fn(write_fonts::types::GlyphId16) -> ColrPaint + 'a>;
|
||||
let cases: [(&str, GlyphPaint<'_>); 3] = [
|
||||
let cases: [(&str, GlyphPaint<'_>); 2] = [
|
||||
("Colrtest Xor", Box::new(|o| ColrPaint::composite(ColrPaint::glyph(solid(2), o), CompositeMode::Xor, ColrPaint::glyph(solid(0), o)))),
|
||||
(
|
||||
"Colrtest Repeat",
|
||||
Box::new(move |o| ColrPaint::glyph(ColrPaint::linear_gradient(line(Extend::Repeat), w(0), w(0), w(100), w(0), w(0), w(100)), o)),
|
||||
),
|
||||
(
|
||||
"Colrtest Circles",
|
||||
Box::new(move |o| {
|
||||
@@ -362,6 +358,129 @@ fn a_radial_gradient_from_a_circle_around_its_centre_is_vector_art() {
|
||||
assert_eq!(offsets, [0.25, 1.0], "the stops start a quarter of the way out");
|
||||
}
|
||||
|
||||
/// A COLRv1 color line through `stops` (offset, palette index) that extends by `extend`.
|
||||
fn color_line(extend: write_fonts::tables::colr::Extend, stops: &[(f32, u16)]) -> write_fonts::tables::colr::ColorLine {
|
||||
use write_fonts::tables::colr::{ColorLine, ColorStop};
|
||||
use write_fonts::types::F2Dot14;
|
||||
let one = F2Dot14::from_f32(1.0);
|
||||
let stops: Vec<ColorStop> = stops.iter().map(|&(at, index)| ColorStop::new(F2Dot14::from_f32(at), index, one)).collect();
|
||||
ColorLine::new(extend, u16::try_from(stops.len()).unwrap(), stops)
|
||||
}
|
||||
|
||||
/// The gradient filling the glyph's one path.
|
||||
fn glyph_gradient(font: &[u8]) -> vectorcraft_doc::color::GradientPaint {
|
||||
let root = art(font, COLOUR_CHAR);
|
||||
let [only] = children(&root) else { panic!("one path") };
|
||||
let Paint::Gradient(g) = fill(only) else { panic!("a gradient") };
|
||||
*g
|
||||
}
|
||||
|
||||
/// A linear gradient that repeats or reflects is vector art. The gradient spreads the same way,
|
||||
/// and its stops run once from the first stop's place to the last stop's place.
|
||||
#[test]
|
||||
fn a_repeating_or_reflecting_linear_gradient_is_vector_art() {
|
||||
use vectorcraft_doc::color::GradientSpread;
|
||||
use write_fonts::tables::colr::Extend;
|
||||
use write_fonts::types::FWord;
|
||||
let w = FWord::new;
|
||||
for (family, extend, spread) in
|
||||
[("Colrtest Linear Repeat", Extend::Repeat, GradientSpread::Repeat), ("Colrtest Linear Reflect", Extend::Reflect, GradientSpread::Reflect)]
|
||||
{
|
||||
let line = color_line(extend, &[(0.2, 0), (0.6, 1)]);
|
||||
let font =
|
||||
v1_font(family, COLOUR_CHAR, |o| ColrPaint::glyph(ColrPaint::linear_gradient(line.clone(), w(0), w(0), w(100), w(0), w(0), w(100)), o));
|
||||
let g = glyph_gradient(&font);
|
||||
assert_eq!(g.gradient.kind, GradientKind::Linear, "{family}");
|
||||
assert_eq!(g.gradient.painted_spread(), (spread, 1), "{family}");
|
||||
let offsets: Vec<f32> = g.gradient.stops.iter().map(|s| s.offset).collect();
|
||||
assert_eq!(offsets, [0.0, 1.0], "{family}");
|
||||
let geom = g.geom.unwrap();
|
||||
assert!((geom.start.x - 20.0).abs() < 0.01 && (geom.end.x - 60.0).abs() < 0.01, "{family}: {:?} → {:?}", geom.start, geom.end);
|
||||
}
|
||||
}
|
||||
|
||||
/// A radial gradient from its center that repeats or reflects is vector art that spreads the same
|
||||
/// way, also with a focal point. Its circle is at the last stop, and its stops run once from the
|
||||
/// center out to that circle.
|
||||
#[test]
|
||||
fn a_repeating_or_reflecting_radial_gradient_from_its_center_is_vector_art() {
|
||||
use vectorcraft_doc::color::GradientSpread;
|
||||
use write_fonts::tables::colr::Extend;
|
||||
use write_fonts::types::{FWord, UfWord};
|
||||
let w = FWord::new;
|
||||
let cases = [
|
||||
("Colrtest Radial Repeat", Extend::Repeat, GradientSpread::Repeat, 250, 1.0, 400.0),
|
||||
("Colrtest Radial Reflect", Extend::Reflect, GradientSpread::Reflect, 300, 1.0, 400.0),
|
||||
("Colrtest Radial Half", Extend::Repeat, GradientSpread::Repeat, 300, 0.5, 200.0),
|
||||
// Normalized, the last stop is a rounding error short of 1.
|
||||
("Colrtest Radial Fine", Extend::Repeat, GradientSpread::Repeat, 250, 41.0 / 16384.0, 400.0 * 41.0 / 16384.0),
|
||||
];
|
||||
for (family, extend, spread, x0, last, radius) in cases {
|
||||
let line = color_line(extend, &[(0.0, 0), (last / 2.0, 2), (last, 1)]);
|
||||
let font = v1_font(family, COLOUR_CHAR, |o| {
|
||||
ColrPaint::glyph(ColrPaint::radial_gradient(line.clone(), w(x0), w(300), UfWord::new(0), w(250), w(300), UfWord::new(400)), o)
|
||||
});
|
||||
let g = glyph_gradient(&font);
|
||||
assert_eq!(g.gradient.kind, GradientKind::Radial, "{family}");
|
||||
assert_eq!(g.gradient.painted_spread(), (spread, 1), "{family}");
|
||||
let offsets: Vec<f32> = g.gradient.stops.iter().map(|s| s.offset).collect();
|
||||
assert!(offsets.len() == 3 && offsets.iter().zip([0.0, 0.5, 1.0]).all(|(a, b)| (a - b).abs() < 0.05), "{family}: {offsets:?}");
|
||||
let geom = g.geom.unwrap();
|
||||
assert!(((geom.end - geom.start).hypot() - radius).abs() < 0.1, "{family}: {geom:?}");
|
||||
assert_eq!(geom.focal.is_some(), x0 != 250, "{family}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A radial gradient that repeats from a circle stays an image. The circle is either a start circle
|
||||
/// around the center or the place a first stop past the center puts the start.
|
||||
#[test]
|
||||
fn a_repeating_radial_gradient_from_a_circle_makes_the_glyph_an_image() {
|
||||
use write_fonts::tables::colr::Extend;
|
||||
use write_fonts::types::{FWord, UfWord};
|
||||
let w = FWord::new;
|
||||
for (family, r0, first) in [("Colrtest Ring Repeat", 100, 0.0), ("Colrtest Short Repeat", 0, 0.25)] {
|
||||
let line = color_line(Extend::Repeat, &[(first, 0), (1.0, 1)]);
|
||||
let font = v1_font(family, COLOUR_CHAR, |o| {
|
||||
ColrPaint::glyph(ColrPaint::radial_gradient(line.clone(), w(250), w(300), UfWord::new(r0), w(250), w(300), UfWord::new(400)), o)
|
||||
});
|
||||
assert!(matches!(content(&font, COLOUR_CHAR), Some(ColourContent::Image(_))), "{family}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A gradient that repeats or reflects with a stop whose palette index is outside the palette stays
|
||||
/// an image. Without that stop, the other stops would repeat over a different interval.
|
||||
#[test]
|
||||
fn a_repeating_gradient_with_a_stop_missing_from_the_palette_makes_the_glyph_an_image() {
|
||||
use write_fonts::tables::colr::Extend;
|
||||
use write_fonts::types::{FWord, UfWord};
|
||||
let w = FWord::new;
|
||||
let line = color_line(Extend::Repeat, &[(0.0, 0), (0.5, 1), (1.0, 200)]);
|
||||
let linear = v1_font("Colrtest Linear Missing", COLOUR_CHAR, |o| {
|
||||
ColrPaint::glyph(ColrPaint::linear_gradient(line.clone(), w(0), w(0), w(100), w(0), w(0), w(100)), o)
|
||||
});
|
||||
let radial = v1_font("Colrtest Radial Missing", COLOUR_CHAR, |o| {
|
||||
ColrPaint::glyph(ColrPaint::radial_gradient(line.clone(), w(250), w(300), UfWord::new(0), w(250), w(300), UfWord::new(400)), o)
|
||||
});
|
||||
for (family, font) in [("linear", linear), ("radial", radial)] {
|
||||
assert!(matches!(content(&font, COLOUR_CHAR), Some(ColourContent::Image(_))), "{family}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A color line with an extend value the COLR specification doesn't define pads.
|
||||
#[test]
|
||||
fn an_unknown_extend_value_pads() {
|
||||
use vectorcraft_doc::color::GradientSpread;
|
||||
use write_fonts::tables::colr::Extend;
|
||||
use write_fonts::types::FWord;
|
||||
let w = FWord::new;
|
||||
let line = color_line(Extend::Unknown, &[(0.0, 0), (1.0, 1)]);
|
||||
let font = v1_font("Colrtest Unknown Extend", COLOUR_CHAR, |o| {
|
||||
ColrPaint::glyph(ColrPaint::linear_gradient(line.clone(), w(0), w(0), w(100), w(0), w(0), w(100)), o)
|
||||
});
|
||||
let g = glyph_gradient(&font);
|
||||
assert_eq!(g.gradient.painted_spread(), (GradientSpread::Pad, 1));
|
||||
}
|
||||
|
||||
/// A glyph whose layers don't use the foreground colour is read once, whatever the type's fill.
|
||||
#[test]
|
||||
fn a_glyph_without_the_foreground_colour_is_read_once_for_every_fill() {
|
||||
|
||||
@@ -673,7 +673,7 @@ since 2026-10-01.
|
||||
- **Artboard tab-switch follow-up:** changing the active document through its tab aligns the Artboard tool with the displayed board before Properties Delete; clicking the already-active tab preserves multiselection and Move/Scale Artwork options. Three headless `chrome::tests::artboard_document_tab_*` regressions cover actual tab A → B → A clicks followed by Properties Delete, surviving boards and guides, preserved artwork/symbols and Undo/Redo; controls cover same-tab multiselection, Move/Scale Artwork options and other tools. Historical focused validation on `dbd2497b` recorded one expected RED failure with two passing controls, then all three GREEN tests passed. All three also passed in historical full CI on `a0671777`. A full CI retry on the five-unit combined source at `a0671777` passed all seven stages with 6,613 test outcomes on 2026-10-14. An earlier full-CI run on the identical source panicked in the randomized `ai_mutated_save_never_panics` test; the cause remains unresolved. This five-fix batch does not claim to repair that failure.
|
||||
- **Artboard clean active-tab close (2026-10-15):** with the Artboard tool active, closing a clean active tab restores the surviving document's displayed board before Properties Delete. Nine targeted checks passed: three existing tab regressions, one clean close/Delete regression and five existing unsaved-document controls. The close regression checks the correct surviving board, preserved artwork/symbols and object selection, affected guides, one Delete edit, saved state and Undo/Redo. Three reviewed original full-app headless captures show the active tab before closing, the surviving document's shown board after closing, and the correct survivor after Properties Delete, using the actual tab × and Delete controls.
|
||||
- **Colour emoji with bitmap glyphs:** `sbix` (Apple Color Emoji) and `CBDT` (Noto Color Emoji) glyphs draw as their largest strike's PNG, placed with the type and unpainted by its fill; PDF writes them in Type 3 glyphs or as images, SVG as images with outlined text, EPS and legacy `.ai` as images, and Create Outlines makes embedded images. Synthetic test fonts in `crates/text/src/test_fonts.rs`; tests `crates/text/src/tests/bitmaps.rs`, `crates/render/src/tests/bitmapglyphs.rs`, `crates/svg/tests/it/colour_glyphs.rs`, `pdf::tests::colour_bitmap_glyphs_export_as_images`, the `eps` `colour_bitmap_glyphs_*` tests and `tests_pathops::create_outlines_turns_bitmap_glyphs_into_images`. COLR and OpenType SVG fonts remain.
|
||||
- **COLR colour fonts:** v0 and v1 glyphs draw as vector layers (solid colours, linear and radial gradients, clips, blend modes, the foreground colour from the run's fill) on the canvas, as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as Type 3 text in PDF, and as editable groups with Create Outlines; sweep gradients and Porter-Duff compositing draw the glyph as an image. Measured by `tests_colr`, `render::tests_colrglyphs`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/it/colour_glyphs.rs`, `eps` `colr_glyphs_*`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups`. Scores unchanged (estimated).
|
||||
- **COLR colour fonts:** v0 and v1 glyphs draw as vector layers (solid colours, linear and radial gradients, including ones that repeat or reflect, clips, blend modes, the foreground colour from the run's fill) on the canvas, as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as Type 3 text in PDF, and as editable groups with Create Outlines; sweep gradients, Porter-Duff compositing and radial gradients that repeat or reflect and start at a circle draw the glyph as an image. Measured by `tests_colr`, `render::tests_colrglyphs`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/it/colour_glyphs.rs`, `eps` `colr_glyphs_*`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups`. Scores unchanged (estimated).
|
||||
- **OpenType SVG colour fonts:** each glyph's SVG document is read by the SVG importer (registered by the engine) into vector art in the run's colours, then drawn, exported and outlined as COLR glyphs are; PDF real text writes them as text that draws nothing and draws their art where they sit. Measured by `tests_svg_glyphs`, `svg/tests/it/svg_glyphs.rs`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`. Scores unchanged (estimated).
|
||||
- **Create Outlines keeps the type object's own appearance:** Type › Create Outlines and Object › Expand put the type object's own fills and strokes (the Appearance panel's items around its Characters row) on paths of the whole outline, below or above the glyph paths as that row places them, with one gradient across every glyph, so the outlines draw what the type drew; `object.expand.info` reports those strokes, pattern fills and gradient fills. Outlined characters keep their stroke and fill (the help page, <https://help.adobe.com/archive/en_US/illustrator/cs5/illustrator_cs5_help.pdf#page=337>); a type object's appearance is separate from its characters' (the help page, <https://helpx.adobe.com/illustrator/using/appearance-attributes.html>); where the object's items go is VectorCraft's own rule. Measured by `tests::pathops::create_outlines_keeps_the_objects_own_fills_and_strokes`, `tests::objexpand::create_outlines_draws_the_type_objects_own_paint`, `tests::objexpand::type_objects_own_stroke_pattern_and_gradient_are_reported`, `tests::objexpand::type_without_an_outline_reports_no_own_stroke_or_fill`, `tests::objexpand::a_stroke_under_an_unpainted_stroke_is_reported_and_outlined` and `tests::objexpand::expand_keeps_the_type_objects_own_paint`. Scores unchanged (estimated).
|
||||
- **Composite fonts that two documents define differently:** when two open documents each define a composite font (Type › Composite Fonts…) by the same name with different parts, each document draws and exports its type in its own definition: in a tiled or floating window drawn while the front document draws, when it is placed in a document that defines the name differently, and in an export made while another document is in front. Evidence: `tests::composite::nested_loans_of_one_name_use_each_documents_definition` and `threads_lending_one_name_at_once_use_their_own_definitions` (`crates/text`), `tests::fontchange::a_placed_document_draws_its_own_definition_of_a_name_the_page_defines` (`crates/render`). Scores unchanged (estimated).
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ against Illustrator's public documentation; **estimated** unless noted.
|
||||
| Full font-name matching (OpenType name ID 4) | done | #1258: loaded/cataloged faces and Missing Fonts searches; localized names and variable default faces; Windows font-service QA pending |
|
||||
| Variable fonts | done | named instances as styles; Character panel › Variable Font sets each axis (`fontAxes`); the font menu's Font Technology › Variable lists only variable font families, with the script, kind and ★ filters ([Find, apply, and adjust variable fonts](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-apply-and-adjust-variable-fonts.html)); a family with a variable file beside static ones counts as variable. Evidence: `font_menu::tests::the_variable_filter_lists_only_variable_families`, `tests_axes::a_family_with_variation_axes_is_variable`, `tests_sysfonts::a_family_with_a_variable_file_beside_static_ones_is_variable` |
|
||||
| Fallback glyphs keep the text's weight and slant | done | #2491: a character from a fallback face is set in that family's face nearest the run's weight and slant; Bold Italic of a missing family is drawn Bold (Source Sans 3 has no Bold Italic) rather than Italic; a letter and its combining marks (a grapheme cluster) are drawn in one face, the first that has all of them, so marks the text's font lacks attach to their letter instead of drawing as missing glyphs (#4491). Vietnamese letters with two marks (Ể, Ứ, Ự) sit on their letter whether typed precomposed, decomposed or as a vowel plus a tone mark, built from GPOS mark-to-mark in fonts without the precomposed letters. Evidence: `tests::vietnamese` |
|
||||
| Colour fonts (bitmap, COLR, OpenType SVG) | partial | bitmap glyphs (`sbix`, `CBDT`) done: the largest strike's PNG, transformed with the type, fill and stroke ignored; PDF Type 3 glyphs or images, SVG images with outlined text (live text leaves emoji to the viewer, their fonts not embedded), EPS and legacy `.ai` images, Create Outlines images; not `sbix` duplicate, flipped, JPEG or TIFF glyphs, or `CBDT` BGRA or mask data, or ruby; in envelopes they warp as images do, and a Warp effect moves them without bending them. COLR (v0 and v1) done: a glyph's layers as vector art (solid colours, linear and radial gradients, clip glyphs and boxes, blend modes, transforms, palette 0, the foreground colour from the run's fill, variable fonts at the face's instance), transformed with the type; drawn on the canvas, written as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as text in PDF (Type 3 glyphs), and Create Outlines makes groups of editable paths. Paints documents have no form for draw the glyph as an image (256 px per em): sweep gradients, Porter-Duff compositing, radial gradients from a circle that isn't the centre, repeating or reflecting gradients. Not ruby. COLR and SVG glyphs also go with type that geometry effects reshape (on the canvas and in exports), into Expand Appearance and EMF/WMF export, as their art, and bitmap glyphs as images. OpenType SVG done: a glyph's SVG document (its element `glyph<gid>`) is read by the SVG importer into vector art in font units, `currentColor` in the run's fill (and the legacy `context-fill` and `context-stroke` in its fill and stroke), a `viewBox` mapped onto the em square, and then drawn, exported and outlined as COLR glyphs are; in PDF real text such glyphs are text that draws nothing, with their art drawn where they sit. Images in glyph documents are in the art, holding their pixels until a document takes them in; text, scripts and embedded content are left out, as the table rules them out. Not CPAL palette variables. Evidence: `tests_svg_glyphs`, `svg/tests/it/svg_glyphs.rs`, `tests_colourfonts::svg_glyph_images_draw_and_outline`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`, `tests_colourfonts::expand_appearance_keeps_the_colour_glyphs_of_type_with_effects`, `render::tests_colrglyphs::colour_glyphs_go_with_type_that_a_geometry_effect_reshapes`, `tests_bitmaps`, `tests_colr`, `render::tests_bitmapglyphs`, `render::tests_colrglyphs`, `pdf::tests::colour_bitmap_glyphs_export_as_images`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/it/colour_glyphs.rs`, `eps` `colour_bitmap_glyphs_*` and `colr_glyphs_*`, `tests_pathops::create_outlines_turns_bitmap_glyphs_into_images`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups` |
|
||||
| Colour fonts (bitmap, COLR, OpenType SVG) | partial | bitmap glyphs (`sbix`, `CBDT`) done: the largest strike's PNG, transformed with the type, fill and stroke ignored; PDF Type 3 glyphs or images, SVG images with outlined text (live text leaves emoji to the viewer, their fonts not embedded), EPS and legacy `.ai` images, Create Outlines images; not `sbix` duplicate, flipped, JPEG or TIFF glyphs, or `CBDT` BGRA or mask data, or ruby; in envelopes they warp as images do, and a Warp effect moves them without bending them. COLR (v0 and v1) done: a glyph's layers as vector art (solid colours, linear and radial gradients that pad, repeat or reflect (the [COLR specification](https://learn.microsoft.com/en-us/typography/opentype/spec/colr)'s extend modes), clip glyphs and boxes, blend modes, transforms, palette 0, the foreground colour from the run's fill, variable fonts at the face's instance), transformed with the type; drawn on the canvas, written as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as text in PDF (Type 3 glyphs), and Create Outlines makes groups of editable paths. Paints documents have no form for draw the glyph as an image (256 px per em): sweep gradients, Porter-Duff compositing, radial gradients from a circle that isn't the centre, radial gradients that repeat or reflect and start at a circle, gradients that repeat or reflect with a stop whose color isn't in the palette. Not ruby. COLR and SVG glyphs also go with type that geometry effects reshape (on the canvas and in exports), into Expand Appearance and EMF/WMF export, as their art, and bitmap glyphs as images. OpenType SVG done: a glyph's SVG document (its element `glyph<gid>`) is read by the SVG importer into vector art in font units, `currentColor` in the run's fill (and the legacy `context-fill` and `context-stroke` in its fill and stroke), a `viewBox` mapped onto the em square, and then drawn, exported and outlined as COLR glyphs are; in PDF real text such glyphs are text that draws nothing, with their art drawn where they sit. Images in glyph documents are in the art, holding their pixels until a document takes them in; text, scripts and embedded content are left out, as the table rules them out. Not CPAL palette variables. Evidence: `tests_svg_glyphs`, `svg/tests/it/svg_glyphs.rs`, `tests_colourfonts::svg_glyph_images_draw_and_outline`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`, `tests_colourfonts::expand_appearance_keeps_the_colour_glyphs_of_type_with_effects`, `render::tests_colrglyphs::colour_glyphs_go_with_type_that_a_geometry_effect_reshapes`, `tests_bitmaps`, `tests_colr`, `render::tests_bitmapglyphs`, `render::tests_colrglyphs`, `pdf::tests::colour_bitmap_glyphs_export_as_images`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/it/colour_glyphs.rs`, `eps` `colour_bitmap_glyphs_*` and `colr_glyphs_*`, `tests_pathops::create_outlines_turns_bitmap_glyphs_into_images`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups` |
|
||||
| Every-line and Single-line composers | done | tuned by eye, never compared side by side |
|
||||
| Optical Margin Alignment | done | Type › Optical Margin Alignment, `type.opticalMarginAlignment`: per type object, with one setting for all the frames of a thread, in point and area type of left-to-right paragraphs, and at the brackets of type on a path whose first paragraph runs left to right; right-to-left paragraphs of point and area type, and type on a path whose first paragraph runs right to left, do not hang |
|
||||
| System Layout | done | Character panel menu › System Layout, `type.systemLayout`: the whole document, off by default and for documents from before it, saved only when on. On, every text object advances by whole points (at least one when a character takes space). New point, area and path type, threaded frames, placed text, graph and dimension labels and pasted plain text inherit it. Evidence: `tests::system_layout_rounds_positive_advances_to_whole_points`, `typecmd::area_tests::system_layout_sets_the_document_and_every_text_object`, `panels::character::tests::system_layout_menu_item_toggles_the_document` |
|
||||
|
||||
Reference in New Issue
Block a user