Add image render tests
Add tests that will match rendered words/paragraphs against reference images. Use env var `GENERATE_IMAGES` to write the initial reference images to the repository.
This commit is contained in:
parent
e2adc1e8da
commit
8db03fe3cf
16 changed files with 342 additions and 0 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1 +1,2 @@
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
sample/udhr*
|
sample/udhr*
|
||||||
target
|
target
|
||||||
|
**/.DS_Store
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ harness = false
|
||||||
members = ["examples/*"]
|
members = ["examples/*"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
tiny-skia = "0.11"
|
||||||
criterion = { version = "0.5.1", default-features = false, features = [
|
criterion = { version = "0.5.1", default-features = false, features = [
|
||||||
"cargo_bench_support",
|
"cargo_bench_support",
|
||||||
] }
|
] }
|
||||||
|
|
|
||||||
Binary file not shown.
93
fonts/NotoSans-LICENSE
Normal file
93
fonts/NotoSans-LICENSE
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
Copyright 2012 Google Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
3
fonts/NotoSans-Regular.ttf
Normal file
3
fonts/NotoSans-Regular.ttf
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:2ec33f84606cbaa0a1a944488e14f97faf2f6a25ecdd8354f5358f06da13c7d9
|
||||||
|
size 556216
|
||||||
3
fonts/NotoSansArabic.ttf
Normal file
3
fonts/NotoSansArabic.ttf
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ee489b994b3e62def9874c918145e32b133b625abaf98cec60502bdb40102c56
|
||||||
|
size 765740
|
||||||
3
fonts/NotoSansHebrew.ttf
Normal file
3
fonts/NotoSansHebrew.ttf
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3d4fef85b449ade4d165de982969374fa30b2a5fe7bc679f5a3f5bfc047fb703
|
||||||
|
size 183688
|
||||||
144
tests/common/mod.rs
Normal file
144
tests/common/mod.rs
Normal 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(
|
||||||
|
(self.canvas_width - margins * 2) as f32,
|
||||||
|
(self.canvas_height - margins * 2) as f32,
|
||||||
|
);
|
||||||
|
buffer.set_text(&self.text, self.font.as_attrs(), Shaping::Advanced);
|
||||||
|
buffer.shape_until_scroll();
|
||||||
|
|
||||||
|
// 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:?}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
tests/images/a_hebrew_paragraph.png
Normal file
3
tests/images/a_hebrew_paragraph.png
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e0350ffaa09ac5b9976b0441cfb0fab0db0b13e3a2d33260592e113c347bedfe
|
||||||
|
size 23202
|
||||||
3
tests/images/a_hebrew_word.png
Normal file
3
tests/images/a_hebrew_word.png
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:95bbe8f4db74914f6a124547f024463a3e434b3c7be3f86c1464af71ed39bba0
|
||||||
|
size 3512
|
||||||
3
tests/images/an_arabic_paragraph.png
Normal file
3
tests/images/an_arabic_paragraph.png
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:eb6966eec660c39584f11d353940f6a274eb90125c8b58fabf3c3e5066a5e1e4
|
||||||
|
size 24322
|
||||||
3
tests/images/an_arabic_word.png
Normal file
3
tests/images/an_arabic_word.png
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ba0219c8e226f4bd79e0681a3189013ed035459fd3ed90405ec53d595e70ab81
|
||||||
|
size 3851
|
||||||
3
tests/images/some_english_mixed_with_arabic.png
Normal file
3
tests/images/some_english_mixed_with_arabic.png
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:ffbac330d91e73d8e28a2a83010e7f231bff6abfc9d9eda8720cb339c587084e
|
||||||
|
size 23093
|
||||||
3
tests/images/some_english_mixed_with_hebrew.png
Normal file
3
tests/images/some_english_mixed_with_hebrew.png
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:29fa23360829f2c11b960343b716eda01a069f6256a09b4eeb30d010abf7977f
|
||||||
|
size 57340
|
||||||
75
tests/shaping_and_rendering.rs
Normal file
75
tests/shaping_and_rendering.rs
Normal 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(100, 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(100, 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();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue