diff --git a/crates/eps/src/tests.rs b/crates/eps/src/tests.rs index 8547c4d015..3eaf476489 100644 --- a/crates/eps/src/tests.rs +++ b/crates/eps/src/tests.rs @@ -1324,6 +1324,79 @@ fn the_page_limit_counts_a_repeated_images_samples_once() { assert!(page.body.len() + page.setup.len() <= inline + 1000 + 400, "{}", page.body.len()); } +/// Type whose color bitmap glyphs fit under the page limit but whose run fill passes it is left +/// out with its glyph image placements: the page is the page without that type. A glyph image +/// that other art draws once more is written inline there again, and an image that other art +/// draws twice keeps its shared samples. +#[test] +fn type_taken_back_for_the_page_limit_takes_its_glyph_images_with_it() { + use vectorcraft_doc::{CharStyle, TextObject}; + use vectorcraft_text::test_fonts::{COLOUR_CHAR, colour_bitmap_family, colour_bitmap_font}; + vectorcraft_text::FontDb::global().add_font(colour_bitmap_font(false).unwrap()); + let black = Paint::solid(Color::rgb(0.0, 0.0, 0.0)); + let type_node = |d: &mut Document, at: Point, text: &str, fill: Paint| { + let st = CharStyle { font_family: colour_bitmap_family(false).into(), size: 32.0, fill, ..CharStyle::default() }; + Node::new(d.alloc_id(), NodeKind::Text(Box::new(TextObject::point(at, text, st)))) + }; + // Six glyph images, then outlines enough for the fill to pass the limit. + let target = format!("{}{}", COLOUR_CHAR.to_string().repeat(6), "W".repeat(40)); + // `shared`: an image drawn twice comes first; `before`: one glyph drawn with the type's image; + // `fill`: the type's run fill (`None`: no type). A red rectangle comes last. + let build = |shared: bool, before: bool, fill: Option| { + let mut d = if shared { + let mut d = image_doc(&noise(8, 8), 1.0); + place_again(&mut d, 0, Affine::translate((40.0, 0.0))); + d + } else { + Document::new(200.0, 200.0) + }; + let mut nodes = vec![]; + if before { + nodes.push(type_node(&mut d, Point::new(20.0, 40.0), &COLOUR_CHAR.to_string(), black.clone())); + } + if let Some(fill) = fill { + nodes.push(type_node(&mut d, Point::new(20.0, 100.0), &target, fill)); + } + nodes.push(red_rect(&mut d)); + for n in nodes { + d.insert(d.default_layer(), usize::MAX, n).unwrap(); + } + d + }; + for shared in [false, true] { + for before in [false, true] { + let case = format!("shared {shared}, before {before}"); + let (without, unfilled, filled) = + (build(shared, before, None), build(shared, before, Some(Paint::None)), build(shared, before, Some(black.clone()))); + let o = opts(&filled); + let glyph = usize::from(shared); + let fits = crate::scene::Scene::new(&unfilled, &o).run(); + let full = crate::scene::Scene::new(&filled, &o).run(); + assert!(fits.warnings.is_empty() && full.warnings.is_empty(), "{case}: {:?} {:?}", fits.warnings, full.warnings); + assert_eq!(reuses(&fits.body, glyph, o.level), 6 + usize::from(before), "{case}: {}", fits.body); + assert_eq!(reuses(&full.body, glyph, o.level), 6 + usize::from(before), "{case}: {}", full.body); + let (fits_len, full_len) = (fits.body.len() + fits.setup.len(), full.body.len() + full.setup.len()); + assert!(full_len > fits_len + 2000, "{case}: {fits_len} {full_len}"); + let limit = (fits_len + full_len) / 2; + // Without its fill the type fits, every glyph image with it. + let page = crate::scene::Scene::new(&unfilled, &o).with_max_body(limit).run(); + assert!(page.warnings.is_empty(), "{case}: {:?}", page.warnings); + assert_eq!((page.setup, page.body), (fits.setup, fits.body), "{case}"); + // With it the type is taken back after its glyph images were placed. + let page = crate::scene::Scene::new(&filled, &o).with_max_body(limit).run(); + assert!(page.warnings.iter().any(|w| w.contains("too large")), "{case}: {:?}", page.warnings); + let alone = crate::scene::Scene::new(&without, &o).run(); + assert!(alone.warnings.is_empty(), "{case}: {:?}", alone.warnings); + assert_eq!(reuses(&page.body, glyph, o.level), 0, "{case}: {}", page.body); + assert!(!page.setup.contains(&format!("/VCim{glyph} ")), "{case}: {}", page.setup); + assert_eq!(page.body.matches("/Width 16 /Height 16").count(), usize::from(before), "{case}: {}", page.body); + assert_eq!(reuses(&page.body, 0, o.level), if shared { 2 } else { 0 }, "{case}: {}", page.body); + assert_eq!((&page.setup, &page.body), (&alone.setup, &alone.body), "{case}"); + checked_page(&page.body); + } + } +} + /// A freeform gradient of `points` (x, y and color). fn freeform(points: &[(f64, f64, Color)]) -> Paint { let mut g = GradientPaint::new(Gradient { kind: GradientKind::Freeform, ..Default::default() }); diff --git a/docs/type-parity.md b/docs/type-parity.md index 31a835772d..72b8bcfc9d 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -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 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`) 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`) 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_*`, `colr_glyphs_*` and `type_taken_back_for_the_page_limit_takes_its_glyph_images_with_it`, `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` |