Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

144
vendor/cosmic-text/tests/common/mod.rs vendored Normal file
View File

@@ -0,0 +1,144 @@
use std::path::PathBuf;
use cosmic_text::{
fontdb::Database, Attrs, AttrsOwned, Buffer, Color, Family, FontSystem, Metrics, Shaping,
SwashCache,
};
use tiny_skia::{Paint, Pixmap, Rect, Transform};
/// The test configuration.
/// The text in the test will be rendered as image using the one of the fonts found under the
/// `fonts` directory in this repository.
/// The image will then be compared to an image with the name `name` under the `tests/images`
/// directory in this repository.
/// If the images do not match the test will fail.
/// NOTE: if an environment variable `GENERATE_IMAGES` is set, the test will create and save
/// the images instead.
#[derive(Debug)]
pub struct DrawTestCfg {
/// The name of the test.
/// Will be used for the image name under the `tests/images` directory in this repository.
name: String,
/// The text to render to image
text: String,
/// The name, details of the font to be used.
/// Expected to be one of the fonts found under the `fonts` directory in this repository.
font: AttrsOwned,
font_size: f32,
line_height: f32,
canvas_width: u32,
canvas_height: u32,
}
impl Default for DrawTestCfg {
fn default() -> Self {
let font = Attrs::new().family(Family::Serif);
Self {
name: "default".into(),
font: AttrsOwned::new(font),
text: "".into(),
font_size: 16.0,
line_height: 20.0,
canvas_width: 300,
canvas_height: 300,
}
}
}
impl DrawTestCfg {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
..Default::default()
}
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = text.into();
self
}
pub fn font_attrs(mut self, attrs: Attrs) -> Self {
self.font = AttrsOwned::new(attrs);
self
}
pub fn font_size(mut self, font_size: f32, line_height: f32) -> Self {
self.font_size = font_size;
self.line_height = line_height;
self
}
pub fn canvas(mut self, width: u32, height: u32) -> Self {
self.canvas_width = width;
self.canvas_height = height;
self
}
pub fn validate_text_rendering(self) {
let repo_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
// Create a db with just the fonts in our fonts dir to make sure we only test those
let fonts_path = PathBuf::from(&repo_dir).join("fonts");
let mut font_db = Database::new();
font_db.load_fonts_dir(fonts_path);
let mut font_system = FontSystem::new_with_locale_and_db("En-US".into(), font_db);
let mut swash_cache = SwashCache::new();
let metrics = Metrics::new(self.font_size, self.line_height);
let mut buffer = Buffer::new(&mut font_system, metrics);
let mut buffer = buffer.borrow_with(&mut font_system);
let margins = 5;
buffer.set_size(
Some((self.canvas_width - margins * 2) as f32),
Some((self.canvas_height - margins * 2) as f32),
);
buffer.set_text(&self.text, self.font.as_attrs(), Shaping::Advanced);
buffer.shape_until_scroll(true);
// Black
let text_color = Color::rgb(0x00, 0x00, 0x00);
let mut pixmap = Pixmap::new(self.canvas_width, self.canvas_height).unwrap();
pixmap.fill(tiny_skia::Color::WHITE);
buffer.draw(&mut swash_cache, text_color, |x, y, w, h, color| {
let mut paint = Paint {
anti_alias: true,
..Paint::default()
};
paint.set_color_rgba8(color.r(), color.g(), color.b(), color.a());
let rect = Rect::from_xywh(
(x + margins as i32) as f32,
(y + margins as i32) as f32,
w as f32,
h as f32,
)
.unwrap();
pixmap.fill_rect(rect, &paint, Transform::identity(), None);
});
let image_name = format!("{}.png", self.name);
let reference_image_path = PathBuf::from(&repo_dir)
.join("tests")
.join("images")
.join(image_name);
let generate_images = std::env::var("GENERATE_IMAGES")
.map(|v| {
let val = v.trim().to_ascii_lowercase();
["t", "true", "1"].iter().any(|&v| v == val)
})
.unwrap_or_default();
if generate_images {
pixmap.save_png(reference_image_path).unwrap();
} else {
let reference_image_data = std::fs::read(reference_image_path).unwrap();
let image_data = pixmap.encode_png().unwrap();
assert_eq!(
reference_image_data, image_data,
"rendering failed of {self:?}"
)
}
}
}

View File

@@ -0,0 +1,227 @@
#![cfg(feature = "vi")]
use std::sync::OnceLock;
use cosmic_text::{Buffer, Cursor, Edit, Metrics, SyntaxEditor, SyntaxSystem, ViEditor};
static SYNTAX_SYSTEM: OnceLock<SyntaxSystem> = OnceLock::new();
// New editor for tests
fn editor() -> ViEditor<'static, 'static> {
// More or less copied from cosmic-edit
let font_size: f32 = 14.0;
let line_height = (font_size * 1.4).ceil();
let metrics = Metrics::new(font_size, line_height);
let buffer = Buffer::new_empty(metrics);
let editor = SyntaxEditor::new(
buffer,
SYNTAX_SYSTEM.get_or_init(SyntaxSystem::new),
"base16-eighties.dark",
)
.expect("Default theme `base16-eighties.dark` should be found");
ViEditor::new(editor)
}
// Tests that inserting into an empty editor correctly sets the editor as modified.
#[test]
fn insert_in_empty_editor_sets_changed() {
let mut editor = editor();
assert!(!editor.changed());
editor.start_change();
editor.insert_at(Cursor::new(0, 0), "Robert'); DROP TABLE Students;--", None);
editor.finish_change();
assert!(editor.changed());
}
// Tests an edge case where a save point is never set.
// Undoing changes should set the editor back to unmodified.
#[test]
fn insert_and_undo_in_unsaved_editor_is_unchanged() {
let mut editor = editor();
assert!(!editor.changed());
editor.start_change();
editor.insert_at(Cursor::new(0, 0), "loop {}", None);
editor.finish_change();
assert!(editor.changed());
// Undoing the above change should set the editor as unchanged even if the save state is unset
editor.start_change();
editor.undo();
editor.finish_change();
assert!(!editor.changed());
}
#[test]
fn undo_to_save_point_sets_editor_to_unchanged() {
let mut editor = editor();
// Latest saved change is the first change
editor.start_change();
let cursor = editor.insert_at(Cursor::new(0, 0), "Ferris is Rust's ", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set to changed after insertion"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be set to unchanged after setting a save point"
);
// A new insert should set the editor as modified and the pivot should still be on the first
// change from earlier
editor.start_change();
editor.insert_at(cursor, "mascot", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set to changed after inserting text after a save point"
);
// Undoing the latest change should set the editor to unmodified again
editor.start_change();
editor.undo();
editor.finish_change();
assert!(
!editor.changed(),
"Editor should be set to unchanged after undoing to save point"
);
}
#[test]
fn redoing_to_save_point_sets_editor_as_unchanged() {
let mut editor = editor();
// Initial change
assert!(
!editor.changed(),
"Editor should start in an unchanged state"
);
editor.start_change();
editor.insert_at(Cursor::new(0, 0), "editor.start_change();", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set as modified after insert() and finish_change()"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
// Change to undo then redo
editor.start_change();
editor.insert_at(Cursor::new(1, 0), "editor.finish_change()", None);
editor.finish_change();
assert!(
editor.changed(),
"Editor should be set as modified after insert() and finish_change()"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
editor.undo();
assert!(
editor.changed(),
"Undoing past save point should set editor as changed"
);
editor.redo();
assert!(
!editor.changed(),
"Redoing to save point should set editor as unchanged"
);
}
#[test]
fn redoing_past_save_point_sets_editor_to_changed() {
let mut editor = editor();
// Save point change to undo to and then redo past.
editor.start_change();
editor.insert_string("Walt Whitman ", None);
editor.finish_change();
// Set save point to the change above.
assert!(
editor.changed(),
"Editor should be set as modified after insert() and finish_change()"
);
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
editor.start_change();
editor.insert_string("Allen Ginsberg ", None);
editor.finish_change();
editor.start_change();
editor.insert_string("Jack Kerouac ", None);
editor.finish_change();
assert!(editor.changed(), "Editor should be modified insertion");
// Undo to Whitman
editor.undo();
editor.undo();
assert!(
!editor.changed(),
"Editor should be unmodified after undoing to the save point"
);
// Redo to Kerouac
editor.redo();
editor.redo();
assert!(
editor.changed(),
"Editor should be modified after redoing past the save point"
);
}
#[test]
fn undoing_past_save_point_sets_editor_to_changed() {
let mut editor = editor();
editor.start_change();
editor.insert_string("Robert Fripp ", None);
editor.finish_change();
// Save point change to undo past.
editor.start_change();
editor.insert_string("Thurston Moore ", None);
editor.finish_change();
assert!(editor.changed(), "Editor should be changed after insertion");
editor.save_point();
assert!(
!editor.changed(),
"Editor should be unchanged after setting a save point"
);
editor.start_change();
editor.insert_string("Kim Deal ", None);
editor.finish_change();
// Undo to the first change
editor.undo();
editor.undo();
assert!(
editor.changed(),
"Editor should be changed after undoing past save point"
);
}
// #[test]
// fn undo_all_changes() {
// unimplemented!()
// }

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,75 @@
use common::DrawTestCfg;
use cosmic_text::Attrs;
use fontdb::Family;
mod common;
#[test]
fn test_hebrew_word_rendering() {
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("a_hebrew_word")
.font_size(36., 40.)
.font_attrs(attrs)
.text("בדיקה")
.canvas(120, 60)
.validate_text_rendering();
}
#[test]
fn test_hebrew_paragraph_rendering() {
let paragraph = "השועל החום המהיר קופץ מעל הכלב העצלן";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("a_hebrew_paragraph")
.font_size(36., 40.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 110)
.validate_text_rendering();
}
#[test]
fn test_english_mixed_with_hebrew_paragraph_rendering() {
let paragraph = "Many computer programs fail to display bidirectional text correctly. For example, this page is mostly LTR English script, and here is the RTL Hebrew name Sarah: שרה, spelled sin (ש) on the right, resh (ר) in the middle, and heh (ה) on the left.";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("some_english_mixed_with_hebrew")
.font_size(16., 20.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 120)
.validate_text_rendering();
}
#[test]
fn test_arabic_word_rendering() {
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("an_arabic_word")
.font_size(36., 40.)
.font_attrs(attrs)
.text("خالصة")
.canvas(120, 60)
.validate_text_rendering();
}
#[test]
fn test_arabic_paragraph_rendering() {
let paragraph = "الثعلب البني السريع يقفز فوق الكلب الكسول";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("an_arabic_paragraph")
.font_size(36., 40.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 110)
.validate_text_rendering();
}
#[test]
fn test_english_mixed_with_arabic_paragraph_rendering() {
let paragraph = "I like to render اللغة العربية in Rust!";
let attrs = Attrs::new().family(Family::Name("Noto Sans"));
DrawTestCfg::new("some_english_mixed_with_arabic")
.font_size(36., 40.)
.font_attrs(attrs)
.text(paragraph)
.canvas(400, 110)
.validate_text_rendering();
}

View File

@@ -0,0 +1,132 @@
use cosmic_text::{
fontdb, Align, Attrs, AttrsList, BidiParagraphs, Buffer, Family, FontSystem, LayoutLine,
Metrics, ShapeLine, Shaping, Weight, Wrap,
};
// Test for https://github.com/pop-os/cosmic-text/issues/134
//
// Being able to get the same wrapping when feeding the measured width back into ShapeLine::layout
// as the new width limit is very useful for certain UI layout use cases.
#[test]
fn stable_wrap() {
let font_size = 18.0;
let attrs = AttrsList::new(
Attrs::new()
.family(Family::Name("FiraMono"))
.weight(Weight::MEDIUM),
);
let mut font_system =
FontSystem::new_with_locale_and_db("en-US".into(), fontdb::Database::new());
let font = std::fs::read("fonts/FiraMono-Medium.ttf").unwrap();
font_system.db_mut().load_font_data(font);
let mut check_wrap = |text: &_, wrap, align_opt, start_width_opt| {
let line = ShapeLine::new(&mut font_system, text, &attrs, Shaping::Advanced, 8);
let layout_unbounded = line.layout(font_size, start_width_opt, wrap, align_opt, None);
let max_width = layout_unbounded.iter().map(|l| l.w).fold(0.0, f32::max);
let new_limit = match start_width_opt {
Some(start_width) => f32::min(start_width, max_width),
None => max_width,
};
let layout_bounded = line.layout(font_size, Some(new_limit), wrap, align_opt, None);
let bounded_max_width = layout_bounded.iter().map(|l| l.w).fold(0.0, f32::max);
// For debugging:
// dbg_layout_lines(text, &layout_unbounded);
// dbg_layout_lines(text, &layout_bounded);
assert_eq!(
(max_width, layout_unbounded.len()),
(bounded_max_width, layout_bounded.len()),
"Wrap \"{wrap:?}\" and align \"{align_opt:?}\" with text: \"{text}\"",
);
for (u, b) in layout_unbounded[1..].iter().zip(layout_bounded[1..].iter()) {
assert_eq!(
u.w, b.w,
"Wrap {wrap:?} and align \"{align_opt:?}\" with text: \"{text}\"",
);
}
};
let hello_sample = std::fs::read_to_string("sample/hello.txt").unwrap();
let cases = [
"(6) SomewhatBoringDisplayTransform",
"",
" ",
" ",
" ",
" ",
]
.into_iter()
// This has several cases where the line would wrap when the computed width was used as the
// width limit.
.chain(BidiParagraphs::new(&hello_sample));
for text in cases {
for wrap in [Wrap::None, Wrap::Glyph, Wrap::Word, Wrap::WordOrGlyph] {
for align_opt in [
None,
Some(Align::Left),
Some(Align::Right),
Some(Align::Center),
//TODO: Align::Justified
Some(Align::End),
] {
for start_width_opt in [
None,
Some(f32::MAX),
Some(80.0),
Some(198.2132),
Some(20.0),
Some(4.0),
Some(300.0),
] {
check_wrap(text, wrap, align_opt, start_width_opt);
let with_spaces = format!("{text} ");
check_wrap(&with_spaces, wrap, align_opt, start_width_opt);
let with_spaces_2 = format!("{text} ");
check_wrap(&with_spaces_2, wrap, align_opt, start_width_opt);
}
}
}
}
}
#[test]
fn wrap_extra_line() {
let mut font_system = FontSystem::new();
let metrics = Metrics::new(14.0, 20.0);
let mut buffer = Buffer::new(&mut font_system, metrics);
let mut buffer = buffer.borrow_with(&mut font_system);
// Add some text!
buffer.set_wrap(Wrap::Word);
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing\n\nweeewoooo minim sint cillum sint consectetur cupidatat.", Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced);
// Set a size for the text buffer, in pixels
buffer.set_size(Some(50.0), Some(1000.0));
// Perform shaping as desired
buffer.shape_until_scroll(false);
let empty_lines = buffer.layout_runs().filter(|x| x.line_w == 0.).count();
let overflow_lines = buffer.layout_runs().filter(|x| x.line_w > 50.).count();
assert_eq!(empty_lines, 1);
assert_eq!(overflow_lines, 4);
}
#[allow(dead_code)]
fn dbg_layout_lines(text: &str, lines: &[LayoutLine]) {
for line in lines {
let mut s = String::new();
for glyph in line.glyphs.iter() {
s.push_str(&text[glyph.start..glyph.end]);
}
println!("\"{s}\"");
}
}

View File

@@ -0,0 +1,37 @@
use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, Wrap};
// Tests the ability to fallback to glyph wrapping when a word can't fit on a line by itself.
// No line should ever overflow the buffer size.
#[test]
fn wrap_word_fallback() {
let mut font_system =
FontSystem::new_with_locale_and_db("en-US".into(), fontdb::Database::new());
let font = std::fs::read("fonts/Inter-Regular.ttf").unwrap();
font_system.db_mut().load_font_data(font);
let metrics = Metrics::new(14.0, 20.0);
let mut buffer = Buffer::new(&mut font_system, metrics);
let mut buffer = buffer.borrow_with(&mut font_system);
buffer.set_wrap(Wrap::WordOrGlyph);
buffer.set_text("Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", Attrs::new().family(cosmic_text::Family::Name("Inter")), Shaping::Advanced);
buffer.set_size(Some(50.0), Some(1000.0));
buffer.shape_until_scroll(false);
let measured_size = measure(&buffer);
assert!(
measured_size <= buffer.size().0.unwrap_or(0.0),
"Measured width is larger than buffer width\n{} <= {}",
measured_size,
buffer.size().0.unwrap_or(0.0)
);
}
fn measure(buffer: &Buffer) -> f32 {
buffer
.layout_runs()
.fold(0.0f32, |width, run| width.max(run.line_w))
}